Coverage Report

Created: 2025-12-05 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/Assjson/mesh_splitter.cpp
Line
Count
Source
1
/*
2
Assimp2Json
3
Copyright (c) 2011, Alexander C. Gessler
4
5
Licensed under a 3-clause BSD license. See the LICENSE file for more information.
6
7
*/
8
9
#include "mesh_splitter.h"
10
11
#include <assimp/scene.h>
12
13
// ----------------------------------------------------------------------------
14
// Note: this is largely based on assimp's SplitLargeMeshes_Vertex process.
15
// it is refactored and the coding style is slightly improved, though.
16
// ----------------------------------------------------------------------------
17
18
// ------------------------------------------------------------------------------------------------
19
// Executes the post processing step on the given imported data.
20
0
void MeshSplitter::Execute( aiScene* pScene) {
21
0
  std::vector<std::pair<aiMesh*, unsigned int> > source_mesh_map;
22
23
0
  for( unsigned int a = 0; a < pScene->mNumMeshes; a++) {
24
0
    SplitMesh(a, pScene->mMeshes[a],source_mesh_map);
25
0
  }
26
27
0
  const unsigned int size = static_cast<unsigned int>(source_mesh_map.size());
28
0
  if (size != pScene->mNumMeshes) {
29
    // it seems something has been split. rebuild the mesh list
30
0
    delete[] pScene->mMeshes;
31
0
    pScene->mNumMeshes = size;
32
0
    pScene->mMeshes = new aiMesh*[size]();
33
34
0
    for (unsigned int i = 0; i < size;++i) {
35
0
      pScene->mMeshes[i] = source_mesh_map[i].first;
36
0
    }
37
38
    // now we need to update all nodes
39
0
    UpdateNode(pScene->mRootNode,source_mesh_map);
40
0
  }
41
0
}
42
43
44
// ------------------------------------------------------------------------------------------------
45
0
void MeshSplitter::UpdateNode(aiNode* pcNode, const std::vector<std::pair<aiMesh*, unsigned int> >& source_mesh_map) {
46
  // TODO: should better use std::(multi)set for source_mesh_map.
47
48
  // for every index in out list build a new entry
49
0
  std::vector<unsigned int> aiEntries;
50
0
  aiEntries.reserve(pcNode->mNumMeshes + 1);
51
0
  for (unsigned int i = 0; i < pcNode->mNumMeshes;++i) {
52
0
    for (unsigned int a = 0, end = static_cast<unsigned int>(source_mesh_map.size()); a < end;++a) {
53
0
      if (source_mesh_map[a].second == pcNode->mMeshes[i]) {
54
0
        aiEntries.push_back(a);
55
0
      }
56
0
    }
57
0
  }
58
59
  // now build the new list
60
0
  delete pcNode->mMeshes;
61
0
  pcNode->mNumMeshes = static_cast<unsigned int>(aiEntries.size());
62
0
  pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
63
64
0
  for (unsigned int b = 0; b < pcNode->mNumMeshes;++b) {
65
0
    pcNode->mMeshes[b] = aiEntries[b];
66
0
  }
67
68
  // recursively update children
69
0
  for (unsigned int i = 0, end = pcNode->mNumChildren; i < end;++i) {
70
0
    UpdateNode ( pcNode->mChildren[i], source_mesh_map );
71
0
  }
72
0
}
73
74
static const unsigned int WAS_NOT_COPIED = 0xffffffff;
75
76
using PerVertexWeight = std::pair <unsigned int,float>;
77
using VertexWeightTable = std::vector <PerVertexWeight>;
78
79
// ------------------------------------------------------------------------------------------------
80
0
VertexWeightTable* ComputeVertexBoneWeightTable(const aiMesh* pMesh) {
81
0
  if (!pMesh || !pMesh->mNumVertices || !pMesh->mNumBones) {
82
0
    return nullptr;
83
0
  }
84
85
0
  VertexWeightTable* const avPerVertexWeights = new VertexWeightTable[pMesh->mNumVertices];
86
0
  for (unsigned int i = 0; i < pMesh->mNumBones;++i) {
87
88
0
    aiBone* bone = pMesh->mBones[i];
89
0
    for (unsigned int a = 0; a < bone->mNumWeights;++a) {
90
0
      const aiVertexWeight& weight = bone->mWeights[a];
91
0
      avPerVertexWeights[weight.mVertexId].emplace_back(i,weight.mWeight);
92
0
    }
93
0
  }
94
0
  return avPerVertexWeights;
95
0
}
96
97
// ------------------------------------------------------------------------------------------------
98
0
void MeshSplitter :: SplitMesh(unsigned int a, aiMesh* in_mesh, std::vector<std::pair<aiMesh*, unsigned int> >& source_mesh_map) {
99
  // TODO: should better use std::(multi)set for source_mesh_map.
100
101
0
  if (in_mesh->mNumVertices <= LIMIT) {
102
0
    source_mesh_map.emplace_back(in_mesh,a);
103
0
    return;
104
0
  }
105
106
  // build a per-vertex weight list if necessary
107
0
  VertexWeightTable* avPerVertexWeights = ComputeVertexBoneWeightTable(in_mesh);
108
109
  // we need to split this mesh into sub meshes. Estimate submesh size
110
0
  const unsigned int sub_meshes = (in_mesh->mNumVertices / LIMIT) + 1;
111
112
  // create a std::vector<unsigned int> to remember which vertices have already
113
  // been copied and to which position (i.e. output index)
114
0
  std::vector<unsigned int> was_copied_to;
115
0
  was_copied_to.resize(in_mesh->mNumVertices,WAS_NOT_COPIED);
116
117
  // Try to find a good estimate for the number of output faces
118
  // per mesh. Add 12.5% as buffer
119
0
  unsigned int size_estimated = in_mesh->mNumFaces / sub_meshes;
120
0
  size_estimated += size_estimated / 8;
121
122
  // now generate all submeshes
123
0
  unsigned int base = 0;
124
0
  while (true) {
125
0
    const unsigned int out_vertex_index = LIMIT;
126
127
0
    aiMesh* out_mesh = new aiMesh();
128
0
    out_mesh->mNumVertices = 0;
129
0
    out_mesh->mMaterialIndex = in_mesh->mMaterialIndex;
130
131
    // the name carries the adjacency information between the meshes
132
0
    out_mesh->mName = in_mesh->mName;
133
134
0
    typedef std::vector<aiVertexWeight> BoneWeightList;
135
0
    if (in_mesh->HasBones()) {
136
0
      out_mesh->mBones = new aiBone*[in_mesh->mNumBones]();
137
0
    }
138
139
    // clear the temporary helper array
140
0
    if (base) {
141
0
      std::fill(was_copied_to.begin(), was_copied_to.end(), WAS_NOT_COPIED);
142
0
    }
143
144
0
    std::vector<aiFace> vFaces;
145
146
    // reserve enough storage for most cases
147
0
    if (in_mesh->HasPositions()) {
148
0
      out_mesh->mVertices = new aiVector3D[out_vertex_index];
149
0
    }
150
151
0
    if (in_mesh->HasNormals()) {
152
0
      out_mesh->mNormals = new aiVector3D[out_vertex_index];
153
0
    }
154
155
0
    if (in_mesh->HasTangentsAndBitangents()) {
156
0
      out_mesh->mTangents = new aiVector3D[out_vertex_index];
157
0
      out_mesh->mBitangents = new aiVector3D[out_vertex_index];
158
0
    }
159
160
0
    for (unsigned int c = 0; in_mesh->HasVertexColors(c);++c) {
161
0
      out_mesh->mColors[c] = new aiColor4D[out_vertex_index];
162
0
    }
163
164
0
    for (unsigned int c = 0; in_mesh->HasTextureCoords(c);++c) {
165
0
      out_mesh->mNumUVComponents[c] = in_mesh->mNumUVComponents[c];
166
0
      out_mesh->mTextureCoords[c] = new aiVector3D[out_vertex_index];
167
0
    }
168
0
    vFaces.reserve(size_estimated);
169
170
    // (we will also need to copy the array of indices)
171
0
    while (base < in_mesh->mNumFaces) {
172
0
      const unsigned int iNumIndices = in_mesh->mFaces[base].mNumIndices;
173
174
      // doesn't catch degenerates but is quite fast
175
0
      unsigned int iNeed = 0;
176
0
      for (unsigned int v = 0; v < iNumIndices;++v) {
177
0
        unsigned int index = in_mesh->mFaces[base].mIndices[v];
178
179
        // check whether we do already have this vertex
180
0
        if (WAS_NOT_COPIED == was_copied_to[index]) {
181
0
          iNeed++;
182
0
        }
183
0
      }
184
0
      if (out_mesh->mNumVertices + iNeed > out_vertex_index) {
185
        // don't use this face
186
0
        break;
187
0
      }
188
189
0
      vFaces.emplace_back();
190
0
      aiFace& rFace = vFaces.back();
191
192
      // setup face type and number of indices
193
0
      rFace.mNumIndices = iNumIndices;
194
0
      rFace.mIndices = new unsigned int[iNumIndices];
195
196
      // need to update the output primitive types
197
0
      switch (rFace.mNumIndices)
198
0
      {
199
0
      case 1:
200
0
        out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
201
0
        break;
202
0
      case 2:
203
0
        out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
204
0
        break;
205
0
      case 3:
206
0
        out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
207
0
        break;
208
0
      default:
209
0
        out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
210
0
      }
211
212
      // and copy the contents of the old array, offset them by current base
213
0
      for (unsigned int v = 0; v < iNumIndices;++v) {
214
0
        const unsigned int index = in_mesh->mFaces[base].mIndices[v];
215
216
        // check whether we do already have this vertex
217
0
        if (WAS_NOT_COPIED != was_copied_to[index]) {
218
0
          rFace.mIndices[v] = was_copied_to[index];
219
0
          continue;
220
0
        }
221
222
        // copy positions
223
0
        out_mesh->mVertices[out_mesh->mNumVertices] = (in_mesh->mVertices[index]);
224
225
        // copy normals
226
0
        if (in_mesh->HasNormals()) {
227
0
          out_mesh->mNormals[out_mesh->mNumVertices] = (in_mesh->mNormals[index]);
228
0
        }
229
230
        // copy tangents/bi-tangents
231
0
        if (in_mesh->HasTangentsAndBitangents()) {
232
0
          out_mesh->mTangents[out_mesh->mNumVertices] = (in_mesh->mTangents[index]);
233
0
          out_mesh->mBitangents[out_mesh->mNumVertices] = (in_mesh->mBitangents[index]);
234
0
        }
235
236
        // texture coordinates
237
0
        for (unsigned int c = 0;  c < AI_MAX_NUMBER_OF_TEXTURECOORDS;++c) {
238
0
          if (in_mesh->HasTextureCoords( c)) {
239
0
            out_mesh->mTextureCoords[c][out_mesh->mNumVertices] = in_mesh->mTextureCoords[c][index];
240
0
          }
241
0
        }
242
        // vertex colors
243
0
        for (unsigned int c = 0;  c < AI_MAX_NUMBER_OF_COLOR_SETS;++c) {
244
0
          if (in_mesh->HasVertexColors( c)) {
245
0
            out_mesh->mColors[c][out_mesh->mNumVertices] = in_mesh->mColors[c][index];
246
0
          }
247
0
        }
248
        // check whether we have bone weights assigned to this vertex
249
0
        rFace.mIndices[v] = out_mesh->mNumVertices;
250
0
        if (avPerVertexWeights) {
251
0
          VertexWeightTable& table = avPerVertexWeights[ out_mesh->mNumVertices ];
252
0
          for (VertexWeightTable::const_iterator iter = table.begin(), end = table.end(); iter != end;++iter) {
253
            // allocate the bone weight array if necessary and store it in the mBones field (HACK!)
254
0
            BoneWeightList* weight_list = reinterpret_cast<BoneWeightList*>(out_mesh->mBones[(*iter).first]);
255
0
            if (!weight_list) {
256
0
              weight_list = new BoneWeightList();
257
0
              out_mesh->mBones[(*iter).first] = reinterpret_cast<aiBone*>(weight_list);
258
0
            }
259
0
            weight_list->push_back(aiVertexWeight(out_mesh->mNumVertices,(*iter).second));
260
0
          }
261
0
        }
262
263
0
        was_copied_to[index] = out_mesh->mNumVertices;
264
0
        out_mesh->mNumVertices++;
265
0
      }
266
0
      base++;
267
0
      if(out_mesh->mNumVertices == out_vertex_index) {
268
        // break here. The face is only added if it was complete
269
0
        break;
270
0
      }
271
0
    }
272
273
    // check which bones we'll need to create for this submesh
274
0
    if (in_mesh->HasBones()) {
275
0
      aiBone** ppCurrent = out_mesh->mBones;
276
0
      for (unsigned int k = 0; k < in_mesh->mNumBones;++k) {
277
        // check whether the bone exists
278
0
        BoneWeightList* const weight_list = reinterpret_cast<BoneWeightList*>(out_mesh->mBones[k]);
279
280
0
        if (weight_list) {
281
0
          const aiBone* const bone_in = in_mesh->mBones[k];
282
0
          aiBone* const bone_out = new aiBone();
283
0
          *ppCurrent++ = bone_out;
284
0
          bone_out->mName = aiString(bone_in->mName);
285
0
          bone_out->mOffsetMatrix =bone_in->mOffsetMatrix;
286
0
          bone_out->mNumWeights = (unsigned int)weight_list->size();
287
0
          bone_out->mWeights = new aiVertexWeight[bone_out->mNumWeights];
288
289
          // copy the vertex weights
290
0
          ::memcpy(bone_out->mWeights, &(*weight_list)[0],bone_out->mNumWeights * sizeof(aiVertexWeight));
291
292
0
          delete weight_list;
293
0
          out_mesh->mNumBones++;
294
0
        }
295
0
      }
296
0
    }
297
298
    // copy the face list to the mesh
299
0
    out_mesh->mFaces = new aiFace[vFaces.size()];
300
0
    out_mesh->mNumFaces = (unsigned int)vFaces.size();
301
302
0
    for (unsigned int p = 0; p < out_mesh->mNumFaces;++p) {
303
0
      out_mesh->mFaces[p] = vFaces[p];
304
0
    }
305
306
    // add the newly created mesh to the list
307
0
    source_mesh_map.emplace_back(out_mesh,a);
308
309
0
    if (base == in_mesh->mNumFaces) {
310
0
      break;
311
0
    }
312
0
  }
313
314
  // delete the per-vertex weight list again
315
0
  delete[] avPerVertexWeights;
316
317
  // now delete the old mesh data
318
0
  delete in_mesh;
319
0
}