/src/assimp/code/AssetLib/SIB/SIBImporter.cpp
Line | Count | Source |
1 | | /* |
2 | | --------------------------------------------------------------------------- |
3 | | Open Asset Import Library (assimp) |
4 | | --------------------------------------------------------------------------- |
5 | | |
6 | | Copyright (c) 2006-2026, assimp team |
7 | | |
8 | | All rights reserved. |
9 | | |
10 | | Redistribution and use of this software in source and binary forms, |
11 | | with or without modification, are permitted provided that the following |
12 | | conditions are met: |
13 | | |
14 | | * Redistributions of source code must retain the above |
15 | | copyright notice, this list of conditions and the |
16 | | following disclaimer. |
17 | | |
18 | | * Redistributions in binary form must reproduce the above |
19 | | copyright notice, this list of conditions and the |
20 | | following disclaimer in the documentation and/or other |
21 | | materials provided with the distribution. |
22 | | |
23 | | * Neither the name of the assimp team, nor the names of its |
24 | | contributors may be used to endorse or promote products |
25 | | derived from this software without specific prior |
26 | | written permission of the assimp team. |
27 | | |
28 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
29 | | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
30 | | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
31 | | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
32 | | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
33 | | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
34 | | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
35 | | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
36 | | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
37 | | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
38 | | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
39 | | --------------------------------------------------------------------------- |
40 | | */ |
41 | | |
42 | | /** @file SIBImporter.cpp |
43 | | * @brief Implementation of the SIB importer class. |
44 | | * |
45 | | * The Nevercenter Silo SIB format is undocumented. |
46 | | * All details here have been reverse engineered from |
47 | | * studying the binary files output by Silo. |
48 | | * |
49 | | * Nevertheless, this implementation is reasonably complete. |
50 | | */ |
51 | | |
52 | | #ifndef ASSIMP_BUILD_NO_SIB_IMPORTER |
53 | | |
54 | | // internal headers |
55 | | #include "SIBImporter.h" |
56 | | #include <assimp/ByteSwapper.h> |
57 | | #include <assimp/StreamReader.h> |
58 | | #include <assimp/TinyFormatter.h> |
59 | | #include "utf8.h" |
60 | | #include <assimp/importerdesc.h> |
61 | | #include <assimp/scene.h> |
62 | | #include <assimp/DefaultLogger.hpp> |
63 | | #include <assimp/IOSystem.hpp> |
64 | | #include <assimp/StringUtils.h> |
65 | | |
66 | | #include <map> |
67 | | |
68 | | namespace Assimp { |
69 | | |
70 | | static constexpr aiImporterDesc desc = { |
71 | | "Silo SIB Importer", |
72 | | "Richard Mitton (http://www.codersnotes.com/about)", |
73 | | "", |
74 | | "Does not apply subdivision.", |
75 | | aiImporterFlags_SupportBinaryFlavour, |
76 | | 0, 0, |
77 | | 0, 0, |
78 | | "sib" |
79 | | }; |
80 | | |
81 | | struct SIBChunk { |
82 | | uint32_t Tag; |
83 | | uint32_t Size; |
84 | | }; |
85 | | |
86 | | enum { |
87 | | POS, |
88 | | NRM, |
89 | | UV, |
90 | | N |
91 | | }; |
92 | | |
93 | | using SIBPair = std::pair<uint32_t, uint32_t>; |
94 | | |
95 | | struct SIBEdge { |
96 | | uint32_t faceA, faceB; |
97 | | bool creased; |
98 | | }; |
99 | | |
100 | | struct SIBMesh { |
101 | | aiMatrix4x4 axis; |
102 | | uint32_t numPts; |
103 | | std::vector<aiVector3D> pos, nrm, uv; |
104 | | std::vector<uint32_t> idx; |
105 | | std::vector<uint32_t> faceStart; |
106 | | std::vector<uint32_t> mtls; |
107 | | std::vector<SIBEdge> edges; |
108 | | std::map<SIBPair, uint32_t> edgeMap; |
109 | | }; |
110 | | |
111 | | struct SIBObject { |
112 | | aiString name; |
113 | | aiMatrix4x4 axis; |
114 | | size_t meshIdx, meshCount; |
115 | | }; |
116 | | |
117 | | struct SIB { |
118 | | std::vector<aiMaterial *> mtls; |
119 | | std::vector<aiMesh *> meshes; |
120 | | std::vector<aiLight *> lights; |
121 | | std::vector<SIBObject> objs, insts; |
122 | | }; |
123 | | |
124 | | // ------------------------------------------------------------------------------------------------ |
125 | 0 | static SIBEdge &GetEdge(SIBMesh *mesh, uint32_t posA, uint32_t posB) { |
126 | 0 | SIBPair pair = (posA < posB) ? SIBPair(posA, posB) : SIBPair(posB, posA); |
127 | 0 | std::map<SIBPair, uint32_t>::iterator it = mesh->edgeMap.find(pair); |
128 | 0 | if (it != mesh->edgeMap.end()) |
129 | 0 | return mesh->edges[it->second]; |
130 | | |
131 | 0 | SIBEdge edge; |
132 | 0 | edge.creased = false; |
133 | 0 | edge.faceA = edge.faceB = 0xffffffff; |
134 | 0 | mesh->edgeMap[pair] = static_cast<uint32_t>(mesh->edges.size()); |
135 | 0 | mesh->edges.push_back(edge); |
136 | 0 | return mesh->edges.back(); |
137 | 0 | } |
138 | | |
139 | | // ------------------------------------------------------------------------------------------------ |
140 | | // Helpers for reading chunked data. |
141 | | |
142 | 0 | #define TAG(A, B, C, D) ((A << 24) | (B << 16) | (C << 8) | D) |
143 | | |
144 | 0 | static SIBChunk ReadChunk(StreamReaderLE *stream) { |
145 | 0 | SIBChunk chunk; |
146 | 0 | chunk.Tag = stream->GetU4(); |
147 | 0 | chunk.Size = stream->GetU4(); |
148 | 0 | if (chunk.Size > stream->GetRemainingSizeToLimit()) |
149 | 0 | ASSIMP_LOG_ERROR("SIB: Chunk overflow"); |
150 | 0 | ByteSwap::Swap4(&chunk.Tag); |
151 | 0 | return chunk; |
152 | 0 | } |
153 | | |
154 | 0 | static aiColor3D ReadColor(StreamReaderLE *stream) { |
155 | 0 | float r = stream->GetF4(); |
156 | 0 | float g = stream->GetF4(); |
157 | 0 | float b = stream->GetF4(); |
158 | 0 | stream->GetU4(); // Colors have an unused(?) 4th component. |
159 | 0 | return aiColor3D(r, g, b); |
160 | 0 | } |
161 | | |
162 | 0 | static void UnknownChunk(StreamReaderLE * /*stream*/, const SIBChunk &chunk) { |
163 | 0 | char temp[4] = { |
164 | 0 | static_cast<char>((chunk.Tag >> 24) & 0xff), |
165 | 0 | static_cast<char>((chunk.Tag >> 16) & 0xff), |
166 | 0 | static_cast<char>((chunk.Tag >> 8) & 0xff), |
167 | 0 | static_cast<char>(chunk.Tag & 0xff) |
168 | 0 | }; |
169 | |
|
170 | 0 | ASSIMP_LOG_WARN("SIB: Skipping unknown '", ai_str_toprintable(temp, 4), "' chunk."); |
171 | 0 | } |
172 | | |
173 | | // Reads a UTF-16LE string and returns it at UTF-8. |
174 | 0 | static aiString ReadString(StreamReaderLE *stream, uint32_t numWChars) { |
175 | 0 | if (nullptr == stream || 0 == numWChars) { |
176 | 0 | return aiString(); |
177 | 0 | } |
178 | | |
179 | | // Allocate buffers (max expansion is 1 byte -> 4 bytes for UTF-8) |
180 | 0 | std::vector<unsigned char> str; |
181 | 0 | str.reserve(numWChars * 4 + 1); |
182 | 0 | uint16_t *temp = new uint16_t[numWChars]; |
183 | 0 | for (uint32_t n = 0; n < numWChars; ++n) { |
184 | 0 | temp[n] = stream->GetU2(); |
185 | 0 | } |
186 | | |
187 | | // Convert it and NUL-terminate. |
188 | 0 | const uint16_t *start(temp), *end(temp + numWChars); |
189 | 0 | utf8::utf16to8(start, end, back_inserter(str)); |
190 | 0 | str[str.size() - 1] = '\0'; |
191 | | |
192 | | // Return the final string. |
193 | 0 | aiString result = aiString((const char *)&str[0]); |
194 | 0 | delete[] temp; |
195 | |
|
196 | 0 | return result; |
197 | 0 | } |
198 | | |
199 | | // ------------------------------------------------------------------------------------------------ |
200 | | // Returns whether the class can handle the format of the given file. |
201 | 68 | bool SIBImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const { |
202 | 68 | return SimpleExtensionCheck(filename, "sib"); |
203 | 68 | } |
204 | | |
205 | | // ------------------------------------------------------------------------------------------------ |
206 | 63.6k | const aiImporterDesc *SIBImporter::GetInfo() const { |
207 | 63.6k | return &desc; |
208 | 63.6k | } |
209 | | |
210 | | // ------------------------------------------------------------------------------------------------ |
211 | 0 | static void ReadVerts(SIBMesh *mesh, StreamReaderLE *stream, uint32_t count) { |
212 | 0 | if (nullptr == mesh || nullptr == stream) { |
213 | 0 | return; |
214 | 0 | } |
215 | | |
216 | 0 | mesh->pos.resize(count); |
217 | 0 | for (uint32_t n = 0; n < count; ++n) { |
218 | 0 | mesh->pos[n].x = stream->GetF4(); |
219 | 0 | mesh->pos[n].y = stream->GetF4(); |
220 | 0 | mesh->pos[n].z = stream->GetF4(); |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | | // ------------------------------------------------------------------------------------------------ |
225 | 0 | static void ReadFaces(SIBMesh *mesh, StreamReaderLE *stream) { |
226 | 0 | uint32_t ptIdx = 0; |
227 | 0 | while (stream->GetRemainingSizeToLimit() > 0) { |
228 | 0 | uint32_t numPoints = stream->GetU4(); |
229 | | |
230 | | // Store room for the N index channels, plus the point count. |
231 | 0 | size_t pos = mesh->idx.size() + 1; |
232 | 0 | mesh->idx.resize(pos + numPoints * N); |
233 | 0 | mesh->idx[pos - 1] = numPoints; |
234 | 0 | uint32_t *idx = &mesh->idx[pos]; |
235 | |
|
236 | 0 | mesh->faceStart.push_back(static_cast<uint32_t>(pos - 1)); |
237 | 0 | mesh->mtls.push_back(0); |
238 | | |
239 | | // Read all the position data. |
240 | | // UV/normals will be supplied later. |
241 | | // Positions are supplied indexed already, so we preserve that |
242 | | // mapping. UVs are supplied uniquely, so we allocate unique indices. |
243 | 0 | for (uint32_t n = 0; n < numPoints; n++, idx += N, ptIdx++) { |
244 | 0 | uint32_t p = stream->GetU4(); |
245 | 0 | if (p >= mesh->pos.size()) |
246 | 0 | throw DeadlyImportError("Vertex index is out of range."); |
247 | 0 | idx[POS] = p; |
248 | 0 | idx[NRM] = ptIdx; |
249 | 0 | idx[UV] = ptIdx; |
250 | 0 | } |
251 | 0 | } |
252 | | |
253 | | // Allocate data channels for normals/UVs. |
254 | 0 | mesh->nrm.resize(ptIdx, aiVector3D(0, 0, 0)); |
255 | 0 | mesh->uv.resize(ptIdx, aiVector3D(0, 0, 0)); |
256 | |
|
257 | 0 | mesh->numPts = ptIdx; |
258 | 0 | } |
259 | | |
260 | | // ------------------------------------------------------------------------------------------------ |
261 | 0 | static void ReadUVs(SIBMesh *mesh, StreamReaderLE *stream) { |
262 | 0 | while (stream->GetRemainingSizeToLimit() > 0) { |
263 | 0 | uint32_t faceIdx = stream->GetU4(); |
264 | 0 | uint32_t numPoints = stream->GetU4(); |
265 | |
|
266 | 0 | if (faceIdx >= mesh->faceStart.size()) |
267 | 0 | throw DeadlyImportError("Invalid face index."); |
268 | | |
269 | 0 | uint32_t pos = mesh->faceStart[faceIdx]; |
270 | 0 | uint32_t *idx = &mesh->idx[pos + 1]; |
271 | |
|
272 | 0 | for (uint32_t n = 0; n < numPoints; n++, idx += N) { |
273 | 0 | uint32_t id = idx[UV]; |
274 | 0 | mesh->uv[id].x = stream->GetF4(); |
275 | 0 | mesh->uv[id].y = stream->GetF4(); |
276 | 0 | } |
277 | 0 | } |
278 | 0 | } |
279 | | |
280 | | // ------------------------------------------------------------------------------------------------ |
281 | 0 | static void ReadMtls(SIBMesh *mesh, StreamReaderLE *stream) { |
282 | | // Material assignments are stored run-length encoded. |
283 | | // Also, we add 1 to each material so that we can use mtl #0 |
284 | | // as the default material. |
285 | 0 | uint32_t prevFace = stream->GetU4(); |
286 | 0 | uint32_t prevMtl = stream->GetU4() + 1; |
287 | 0 | while (stream->GetRemainingSizeToLimit() > 0) { |
288 | 0 | uint32_t face = stream->GetU4(); |
289 | 0 | uint32_t mtl = stream->GetU4() + 1; |
290 | 0 | while (prevFace < face) { |
291 | 0 | if (prevFace >= mesh->mtls.size()) |
292 | 0 | throw DeadlyImportError("Invalid face index."); |
293 | 0 | mesh->mtls[prevFace++] = prevMtl; |
294 | 0 | } |
295 | | |
296 | 0 | prevFace = face; |
297 | 0 | prevMtl = mtl; |
298 | 0 | } |
299 | | |
300 | 0 | while (prevFace < mesh->mtls.size()) |
301 | 0 | mesh->mtls[prevFace++] = prevMtl; |
302 | 0 | } |
303 | | |
304 | | // ------------------------------------------------------------------------------------------------ |
305 | 0 | static void ReadAxis(aiMatrix4x4 &axis, StreamReaderLE *stream) { |
306 | 0 | axis.a4 = stream->GetF4(); |
307 | 0 | axis.b4 = stream->GetF4(); |
308 | 0 | axis.c4 = stream->GetF4(); |
309 | 0 | axis.d4 = 1; |
310 | 0 | axis.a1 = stream->GetF4(); |
311 | 0 | axis.b1 = stream->GetF4(); |
312 | 0 | axis.c1 = stream->GetF4(); |
313 | 0 | axis.d1 = 0; |
314 | 0 | axis.a2 = stream->GetF4(); |
315 | 0 | axis.b2 = stream->GetF4(); |
316 | 0 | axis.c2 = stream->GetF4(); |
317 | 0 | axis.d2 = 0; |
318 | 0 | axis.a3 = stream->GetF4(); |
319 | 0 | axis.b3 = stream->GetF4(); |
320 | 0 | axis.c3 = stream->GetF4(); |
321 | 0 | axis.d3 = 0; |
322 | 0 | } |
323 | | |
324 | | // ------------------------------------------------------------------------------------------------ |
325 | 0 | static void ReadEdges(SIBMesh *mesh, StreamReaderLE *stream) { |
326 | 0 | while (stream->GetRemainingSizeToLimit() > 0) { |
327 | 0 | uint32_t posA = stream->GetU4(); |
328 | 0 | uint32_t posB = stream->GetU4(); |
329 | 0 | GetEdge(mesh, posA, posB); |
330 | 0 | } |
331 | 0 | } |
332 | | |
333 | | // ------------------------------------------------------------------------------------------------ |
334 | 0 | static void ReadCreases(SIBMesh *mesh, StreamReaderLE *stream) { |
335 | 0 | while (stream->GetRemainingSizeToLimit() > 0) { |
336 | 0 | uint32_t edge = stream->GetU4(); |
337 | 0 | if (edge >= mesh->edges.size()) |
338 | 0 | throw DeadlyImportError("SIB: Invalid edge index."); |
339 | 0 | mesh->edges[edge].creased = true; |
340 | 0 | } |
341 | 0 | } |
342 | | |
343 | | // ------------------------------------------------------------------------------------------------ |
344 | 0 | static void ConnectFaces(SIBMesh *mesh) { |
345 | | // Find faces connected to each edge. |
346 | 0 | size_t numFaces = mesh->faceStart.size(); |
347 | 0 | for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) { |
348 | 0 | uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]]; |
349 | 0 | uint32_t numPoints = *idx++; |
350 | 0 | uint32_t prev = idx[(numPoints - 1) * N + POS]; |
351 | |
|
352 | 0 | for (uint32_t i = 0; i < numPoints; i++, idx += N) { |
353 | 0 | uint32_t next = idx[POS]; |
354 | | |
355 | | // Find this edge. |
356 | 0 | SIBEdge &edge = GetEdge(mesh, prev, next); |
357 | | |
358 | | // Link this face onto it. |
359 | | // This gives potentially undesirable normals when used |
360 | | // with non-2-manifold surfaces, but then so does Silo to begin with. |
361 | 0 | if (edge.faceA == 0xffffffff) |
362 | 0 | edge.faceA = static_cast<uint32_t>(faceIdx); |
363 | 0 | else if (edge.faceB == 0xffffffff) |
364 | 0 | edge.faceB = static_cast<uint32_t>(faceIdx); |
365 | |
|
366 | 0 | prev = next; |
367 | 0 | } |
368 | 0 | } |
369 | 0 | } |
370 | | |
371 | | // ------------------------------------------------------------------------------------------------ |
372 | | static aiVector3D CalculateVertexNormal(SIBMesh *mesh, uint32_t faceIdx, uint32_t pos, |
373 | 0 | const std::vector<aiVector3D> &faceNormals) { |
374 | | // Creased edges complicate this. We need to find the start/end range of the |
375 | | // ring of faces that touch this position. |
376 | | // We do this in two passes. The first pass is to find the end of the range, |
377 | | // the second is to work backwards to the start and calculate the final normal. |
378 | 0 | aiVector3D vtxNormal; |
379 | 0 | for (int pass = 0; pass < 2; pass++) { |
380 | 0 | vtxNormal = aiVector3D(0, 0, 0); |
381 | 0 | uint32_t startFaceIdx = faceIdx; |
382 | 0 | uint32_t prevFaceIdx = faceIdx; |
383 | | |
384 | | // Process each connected face. |
385 | 0 | while (true) { |
386 | | // Accumulate the face normal. |
387 | 0 | vtxNormal += faceNormals[faceIdx]; |
388 | |
|
389 | 0 | uint32_t nextFaceIdx = 0xffffffff; |
390 | | |
391 | | // Move to the next edge sharing this position. |
392 | 0 | uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]]; |
393 | 0 | uint32_t numPoints = *idx++; |
394 | 0 | uint32_t posA = idx[(numPoints - 1) * N + POS]; |
395 | 0 | for (uint32_t n = 0; n < numPoints; n++, idx += N) { |
396 | 0 | uint32_t posB = idx[POS]; |
397 | | |
398 | | // Test if this edge shares our target position. |
399 | 0 | if (posA == pos || posB == pos) { |
400 | 0 | SIBEdge &edge = GetEdge(mesh, posA, posB); |
401 | | |
402 | | // Non-manifold meshes can produce faces which share |
403 | | // positions but have no edge entry, so check it. |
404 | 0 | if (edge.faceA == faceIdx || edge.faceB == faceIdx) { |
405 | | // Move to whichever side we didn't just come from. |
406 | 0 | if (!edge.creased) { |
407 | 0 | if (edge.faceA != prevFaceIdx && edge.faceA != faceIdx && edge.faceA != 0xffffffff) |
408 | 0 | nextFaceIdx = edge.faceA; |
409 | 0 | else if (edge.faceB != prevFaceIdx && edge.faceB != faceIdx && edge.faceB != 0xffffffff) |
410 | 0 | nextFaceIdx = edge.faceB; |
411 | 0 | } |
412 | 0 | } |
413 | 0 | } |
414 | |
|
415 | 0 | posA = posB; |
416 | 0 | } |
417 | | |
418 | | // Stop once we hit either an creased/unconnected edge, or we |
419 | | // wrapped around and hit our start point. |
420 | 0 | if (nextFaceIdx == 0xffffffff || nextFaceIdx == startFaceIdx) |
421 | 0 | break; |
422 | | |
423 | 0 | prevFaceIdx = faceIdx; |
424 | 0 | faceIdx = nextFaceIdx; |
425 | 0 | } |
426 | 0 | } |
427 | | |
428 | | // Normalize it. |
429 | 0 | float len = vtxNormal.Length(); |
430 | 0 | if (len > 0.000000001f) |
431 | 0 | vtxNormal /= len; |
432 | 0 | return vtxNormal; |
433 | 0 | } |
434 | | |
435 | | // ------------------------------------------------------------------------------------------------ |
436 | 0 | static void CalculateNormals(SIBMesh *mesh) { |
437 | 0 | size_t numFaces = mesh->faceStart.size(); |
438 | | |
439 | | // Calculate face normals. |
440 | 0 | std::vector<aiVector3D> faceNormals(numFaces); |
441 | 0 | for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) { |
442 | 0 | uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]]; |
443 | 0 | uint32_t numPoints = *idx++; |
444 | |
|
445 | 0 | aiVector3D faceNormal(0, 0, 0); |
446 | |
|
447 | 0 | uint32_t *prev = &idx[(numPoints - 1) * N]; |
448 | |
|
449 | 0 | for (uint32_t i = 0; i < numPoints; i++) { |
450 | 0 | uint32_t *next = &idx[i * N]; |
451 | |
|
452 | 0 | faceNormal += mesh->pos[prev[POS]] ^ mesh->pos[next[POS]]; |
453 | 0 | prev = next; |
454 | 0 | } |
455 | |
|
456 | 0 | faceNormals[faceIdx] = faceNormal; |
457 | 0 | } |
458 | | |
459 | | // Calculate vertex normals. |
460 | 0 | for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) { |
461 | 0 | uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]]; |
462 | 0 | uint32_t numPoints = *idx++; |
463 | |
|
464 | 0 | for (uint32_t i = 0; i < numPoints; i++) { |
465 | 0 | uint32_t pos = idx[i * N + POS]; |
466 | 0 | uint32_t nrm = idx[i * N + NRM]; |
467 | 0 | aiVector3D vtxNorm = CalculateVertexNormal(mesh, static_cast<uint32_t>(faceIdx), pos, faceNormals); |
468 | 0 | mesh->nrm[nrm] = vtxNorm; |
469 | 0 | } |
470 | 0 | } |
471 | 0 | } |
472 | | |
473 | | // ------------------------------------------------------------------------------------------------ |
474 | | struct TempMesh { |
475 | | std::vector<aiVector3D> vtx; |
476 | | std::vector<aiVector3D> nrm; |
477 | | std::vector<aiVector3D> uv; |
478 | | std::vector<aiFace> faces; |
479 | | }; |
480 | | |
481 | 0 | static void ReadShape(SIB *sib, StreamReaderLE *stream) { |
482 | 0 | SIBMesh smesh; |
483 | 0 | aiString name; |
484 | |
|
485 | 0 | while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) { |
486 | 0 | SIBChunk chunk = ReadChunk(stream); |
487 | 0 | unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size); |
488 | |
|
489 | 0 | switch (chunk.Tag) { |
490 | 0 | case TAG('M', 'I', 'R', 'P'): break; // mirror plane maybe? |
491 | 0 | case TAG('I', 'M', 'R', 'P'): break; // instance mirror? (not supported here yet) |
492 | 0 | case TAG('D', 'I', 'N', 'F'): break; // display info, not needed |
493 | 0 | case TAG('P', 'I', 'N', 'F'): break; // ? |
494 | 0 | case TAG('V', 'M', 'I', 'R'): break; // ? |
495 | 0 | case TAG('F', 'M', 'I', 'R'): break; // ? |
496 | 0 | case TAG('T', 'X', 'S', 'M'): break; // ? |
497 | 0 | case TAG('F', 'A', 'H', 'S'): break; // ? |
498 | 0 | case TAG('V', 'R', 'T', 'S'): ReadVerts(&smesh, stream, chunk.Size / 12); break; |
499 | 0 | case TAG('F', 'A', 'C', 'S'): ReadFaces(&smesh, stream); break; |
500 | 0 | case TAG('F', 'T', 'V', 'S'): ReadUVs(&smesh, stream); break; |
501 | 0 | case TAG('S', 'N', 'A', 'M'): name = ReadString(stream, chunk.Size / 2); break; |
502 | 0 | case TAG('F', 'A', 'M', 'A'): ReadMtls(&smesh, stream); break; |
503 | 0 | case TAG('A', 'X', 'I', 'S'): ReadAxis(smesh.axis, stream); break; |
504 | 0 | case TAG('E', 'D', 'G', 'S'): ReadEdges(&smesh, stream); break; |
505 | 0 | case TAG('E', 'C', 'R', 'S'): ReadCreases(&smesh, stream); break; |
506 | 0 | default: UnknownChunk(stream, chunk); break; |
507 | 0 | } |
508 | | |
509 | 0 | stream->SetCurrentPos(stream->GetReadLimit()); |
510 | 0 | stream->SetReadLimit(oldLimit); |
511 | 0 | } |
512 | | |
513 | 0 | ai_assert(smesh.faceStart.size() == smesh.mtls.size()); // sanity check |
514 | | |
515 | | // Silo doesn't store any normals in the file - we need to compute |
516 | | // them ourselves. We can't let AssImp handle it as AssImp doesn't |
517 | | // know about our creased edges. |
518 | 0 | ConnectFaces(&smesh); |
519 | 0 | CalculateNormals(&smesh); |
520 | | |
521 | | // Construct the transforms. |
522 | 0 | aiMatrix4x4 worldToLocal = smesh.axis; |
523 | 0 | worldToLocal.Inverse(); |
524 | 0 | aiMatrix4x4 worldToLocalN = worldToLocal; |
525 | 0 | worldToLocalN.a4 = worldToLocalN.b4 = worldToLocalN.c4 = 0.0f; |
526 | 0 | worldToLocalN.Inverse().Transpose(); |
527 | | |
528 | | // Allocate final mesh data. |
529 | | // We'll allocate one mesh for each material. (we'll strip unused ones after) |
530 | 0 | std::vector<TempMesh> meshes(sib->mtls.size()); |
531 | | |
532 | | // Un-index the source data and apply to each vertex. |
533 | 0 | for (unsigned fi = 0; fi < smesh.faceStart.size(); fi++) { |
534 | 0 | uint32_t start = smesh.faceStart[fi]; |
535 | 0 | uint32_t mtl = smesh.mtls[fi]; |
536 | 0 | uint32_t *idx = &smesh.idx[start]; |
537 | |
|
538 | 0 | if (mtl >= meshes.size()) { |
539 | 0 | ASSIMP_LOG_ERROR("SIB: Face material index is invalid."); |
540 | 0 | mtl = 0; |
541 | 0 | } |
542 | |
|
543 | 0 | TempMesh &dest = meshes[mtl]; |
544 | |
|
545 | 0 | aiFace face; |
546 | 0 | face.mNumIndices = *idx++; |
547 | 0 | face.mIndices = new unsigned[face.mNumIndices]; |
548 | 0 | for (unsigned pt = 0; pt < face.mNumIndices; pt++, idx += N) { |
549 | 0 | size_t vtxIdx = dest.vtx.size(); |
550 | 0 | face.mIndices[pt] = static_cast<unsigned int>(vtxIdx); |
551 | | |
552 | | // De-index it. We don't need to validate here as |
553 | | // we did it when creating the data. |
554 | 0 | aiVector3D pos = smesh.pos[idx[POS]]; |
555 | 0 | aiVector3D nrm = smesh.nrm[idx[NRM]]; |
556 | 0 | aiVector3D uv = smesh.uv[idx[UV]]; |
557 | | |
558 | | // The verts are supplied in world-space, so let's |
559 | | // transform them back into the local space of this mesh: |
560 | 0 | pos = worldToLocal * pos; |
561 | 0 | nrm = worldToLocalN * nrm; |
562 | |
|
563 | 0 | dest.vtx.push_back(pos); |
564 | 0 | dest.nrm.push_back(nrm); |
565 | 0 | dest.uv.push_back(uv); |
566 | 0 | } |
567 | 0 | dest.faces.push_back(face); |
568 | 0 | } |
569 | |
|
570 | 0 | SIBObject obj; |
571 | 0 | obj.name = name; |
572 | 0 | obj.axis = smesh.axis; |
573 | 0 | obj.meshIdx = sib->meshes.size(); |
574 | | |
575 | | // Now that we know the size of everything, |
576 | | // we can build the final one-material-per-mesh data. |
577 | 0 | for (size_t n = 0; n < meshes.size(); n++) { |
578 | 0 | TempMesh &src = meshes[n]; |
579 | 0 | if (src.faces.empty()) |
580 | 0 | continue; |
581 | | |
582 | 0 | aiMesh *mesh = new aiMesh; |
583 | 0 | mesh->mName = name; |
584 | 0 | mesh->mNumFaces = static_cast<unsigned int>(src.faces.size()); |
585 | 0 | mesh->mFaces = new aiFace[mesh->mNumFaces]; |
586 | 0 | mesh->mNumVertices = static_cast<unsigned int>(src.vtx.size()); |
587 | 0 | mesh->mVertices = new aiVector3D[mesh->mNumVertices]; |
588 | 0 | mesh->mNormals = new aiVector3D[mesh->mNumVertices]; |
589 | 0 | mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices]; |
590 | 0 | mesh->mNumUVComponents[0] = 2; |
591 | 0 | mesh->mMaterialIndex = static_cast<unsigned int>(n); |
592 | |
|
593 | 0 | for (unsigned i = 0; i < mesh->mNumVertices; i++) { |
594 | 0 | mesh->mVertices[i] = src.vtx[i]; |
595 | 0 | mesh->mNormals[i] = src.nrm[i]; |
596 | 0 | mesh->mTextureCoords[0][i] = src.uv[i]; |
597 | 0 | } |
598 | 0 | for (unsigned i = 0; i < mesh->mNumFaces; i++) { |
599 | 0 | mesh->mFaces[i] = src.faces[i]; |
600 | 0 | } |
601 | |
|
602 | 0 | sib->meshes.push_back(mesh); |
603 | 0 | } |
604 | |
|
605 | 0 | obj.meshCount = sib->meshes.size() - obj.meshIdx; |
606 | 0 | sib->objs.push_back(obj); |
607 | 0 | } |
608 | | |
609 | | // ------------------------------------------------------------------------------------------------ |
610 | 0 | static void ReadMaterial(SIB *sib, StreamReaderLE *stream) { |
611 | 0 | aiColor3D diff = ReadColor(stream); |
612 | 0 | aiColor3D ambi = ReadColor(stream); |
613 | 0 | aiColor3D spec = ReadColor(stream); |
614 | 0 | aiColor3D emis = ReadColor(stream); |
615 | 0 | float shiny = (float)stream->GetU4(); |
616 | |
|
617 | 0 | uint32_t nameLen = stream->GetU4(); |
618 | 0 | aiString name = ReadString(stream, nameLen / 2); |
619 | 0 | uint32_t texLen = stream->GetU4(); |
620 | 0 | aiString tex = ReadString(stream, texLen / 2); |
621 | |
|
622 | 0 | aiMaterial *mtl = new aiMaterial(); |
623 | 0 | mtl->AddProperty(&diff, 1, AI_MATKEY_COLOR_DIFFUSE); |
624 | 0 | mtl->AddProperty(&ambi, 1, AI_MATKEY_COLOR_AMBIENT); |
625 | 0 | mtl->AddProperty(&spec, 1, AI_MATKEY_COLOR_SPECULAR); |
626 | 0 | mtl->AddProperty(&emis, 1, AI_MATKEY_COLOR_EMISSIVE); |
627 | 0 | mtl->AddProperty(&shiny, 1, AI_MATKEY_SHININESS); |
628 | 0 | mtl->AddProperty(&name, AI_MATKEY_NAME); |
629 | 0 | if (tex.length > 0) { |
630 | 0 | mtl->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(0)); |
631 | 0 | mtl->AddProperty(&tex, AI_MATKEY_TEXTURE_AMBIENT(0)); |
632 | 0 | } |
633 | |
|
634 | 0 | sib->mtls.push_back(mtl); |
635 | 0 | } |
636 | | |
637 | | // ------------------------------------------------------------------------------------------------ |
638 | 0 | static void ReadLightInfo(aiLight *light, StreamReaderLE *stream) { |
639 | 0 | uint32_t type = stream->GetU4(); |
640 | 0 | switch (type) { |
641 | 0 | case 0: light->mType = aiLightSource_POINT; break; |
642 | 0 | case 1: light->mType = aiLightSource_SPOT; break; |
643 | 0 | case 2: light->mType = aiLightSource_DIRECTIONAL; break; |
644 | 0 | default: light->mType = aiLightSource_UNDEFINED; break; |
645 | 0 | } |
646 | | |
647 | 0 | light->mPosition.x = stream->GetF4(); |
648 | 0 | light->mPosition.y = stream->GetF4(); |
649 | 0 | light->mPosition.z = stream->GetF4(); |
650 | 0 | light->mDirection.x = stream->GetF4(); |
651 | 0 | light->mDirection.y = stream->GetF4(); |
652 | 0 | light->mDirection.z = stream->GetF4(); |
653 | 0 | light->mColorDiffuse = ReadColor(stream); |
654 | 0 | light->mColorAmbient = ReadColor(stream); |
655 | 0 | light->mColorSpecular = ReadColor(stream); |
656 | 0 | ai_real spotExponent = stream->GetF4(); |
657 | 0 | ai_real spotCutoff = stream->GetF4(); |
658 | 0 | light->mAttenuationConstant = stream->GetF4(); |
659 | 0 | light->mAttenuationLinear = stream->GetF4(); |
660 | 0 | light->mAttenuationQuadratic = stream->GetF4(); |
661 | | |
662 | | // Silo uses the OpenGL default lighting model for it's |
663 | | // spot cutoff/exponent. AssImp unfortunately, does not. |
664 | | // Let's try and approximate it by solving for the |
665 | | // 99% and 1% percentiles. |
666 | | // OpenGL: I = cos(angle)^E |
667 | | // Solving: angle = acos(I^(1/E)) |
668 | 0 | ai_real E = ai_real(1.0) / std::max(spotExponent, (ai_real)0.00001); |
669 | 0 | ai_real inner = std::acos(std::pow((ai_real)0.99, E)); |
670 | 0 | ai_real outer = std::acos(std::pow((ai_real)0.01, E)); |
671 | | |
672 | | // Apply the cutoff. |
673 | 0 | outer = std::min(outer, AI_DEG_TO_RAD(spotCutoff)); |
674 | |
|
675 | 0 | light->mAngleInnerCone = std::min(inner, outer); |
676 | 0 | light->mAngleOuterCone = outer; |
677 | 0 | } |
678 | | |
679 | 0 | static void ReadLight(SIB *sib, StreamReaderLE *stream) { |
680 | 0 | aiLight *light = new aiLight(); |
681 | |
|
682 | 0 | while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) { |
683 | 0 | SIBChunk chunk = ReadChunk(stream); |
684 | 0 | unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size); |
685 | |
|
686 | 0 | switch (chunk.Tag) { |
687 | 0 | case TAG('L', 'N', 'F', 'O'): ReadLightInfo(light, stream); break; |
688 | 0 | case TAG('S', 'N', 'A', 'M'): light->mName = ReadString(stream, chunk.Size / 2); break; |
689 | 0 | default: UnknownChunk(stream, chunk); break; |
690 | 0 | } |
691 | | |
692 | 0 | stream->SetCurrentPos(stream->GetReadLimit()); |
693 | 0 | stream->SetReadLimit(oldLimit); |
694 | 0 | } |
695 | | |
696 | 0 | sib->lights.push_back(light); |
697 | 0 | } |
698 | | |
699 | | // ------------------------------------------------------------------------------------------------ |
700 | 0 | static void ReadScale(aiMatrix4x4 &axis, StreamReaderLE *stream) { |
701 | 0 | aiMatrix4x4 scale; |
702 | 0 | scale.a1 = stream->GetF4(); |
703 | 0 | scale.b1 = stream->GetF4(); |
704 | 0 | scale.c1 = stream->GetF4(); |
705 | 0 | scale.d1 = stream->GetF4(); |
706 | 0 | scale.a2 = stream->GetF4(); |
707 | 0 | scale.b2 = stream->GetF4(); |
708 | 0 | scale.c2 = stream->GetF4(); |
709 | 0 | scale.d2 = stream->GetF4(); |
710 | 0 | scale.a3 = stream->GetF4(); |
711 | 0 | scale.b3 = stream->GetF4(); |
712 | 0 | scale.c3 = stream->GetF4(); |
713 | 0 | scale.d3 = stream->GetF4(); |
714 | 0 | scale.a4 = stream->GetF4(); |
715 | 0 | scale.b4 = stream->GetF4(); |
716 | 0 | scale.c4 = stream->GetF4(); |
717 | 0 | scale.d4 = stream->GetF4(); |
718 | |
|
719 | 0 | axis = axis * scale; |
720 | 0 | } |
721 | | |
722 | 0 | static void ReadInstance(SIB *sib, StreamReaderLE *stream) { |
723 | 0 | SIBObject inst; |
724 | 0 | uint32_t shapeIndex = 0; |
725 | |
|
726 | 0 | while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) { |
727 | 0 | SIBChunk chunk = ReadChunk(stream); |
728 | 0 | unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size); |
729 | |
|
730 | 0 | switch (chunk.Tag) { |
731 | 0 | case TAG('D', 'I', 'N', 'F'): break; // display info, not needed |
732 | 0 | case TAG('P', 'I', 'N', 'F'): break; // ? |
733 | 0 | case TAG('A', 'X', 'I', 'S'): ReadAxis(inst.axis, stream); break; |
734 | 0 | case TAG('I', 'N', 'S', 'I'): shapeIndex = stream->GetU4(); break; |
735 | 0 | case TAG('S', 'M', 'T', 'X'): ReadScale(inst.axis, stream); break; |
736 | 0 | case TAG('S', 'N', 'A', 'M'): inst.name = ReadString(stream, chunk.Size / 2); break; |
737 | 0 | default: UnknownChunk(stream, chunk); break; |
738 | 0 | } |
739 | | |
740 | 0 | stream->SetCurrentPos(stream->GetReadLimit()); |
741 | 0 | stream->SetReadLimit(oldLimit); |
742 | 0 | } |
743 | | |
744 | 0 | if (shapeIndex >= sib->objs.size()) { |
745 | 0 | throw DeadlyImportError("SIB: Invalid shape index."); |
746 | 0 | } |
747 | | |
748 | 0 | const SIBObject &src = sib->objs[shapeIndex]; |
749 | 0 | inst.meshIdx = src.meshIdx; |
750 | 0 | inst.meshCount = src.meshCount; |
751 | 0 | sib->insts.push_back(inst); |
752 | 0 | } |
753 | | |
754 | | // ------------------------------------------------------------------------------------------------ |
755 | 0 | static void CheckVersion(StreamReaderLE *stream) { |
756 | 0 | uint32_t version = stream->GetU4(); |
757 | 0 | if (version < 1 || version > 2) { |
758 | 0 | throw DeadlyImportError("SIB: Unsupported file version."); |
759 | 0 | } |
760 | 0 | } |
761 | | |
762 | 0 | static void ReadScene(SIB *sib, StreamReaderLE *stream) { |
763 | | // Parse each chunk in turn. |
764 | 0 | while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) { |
765 | 0 | SIBChunk chunk = ReadChunk(stream); |
766 | 0 | unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size); |
767 | |
|
768 | 0 | switch (chunk.Tag) { |
769 | 0 | case TAG('H', 'E', 'A', 'D'): CheckVersion(stream); break; |
770 | 0 | case TAG('S', 'H', 'A', 'P'): ReadShape(sib, stream); break; |
771 | 0 | case TAG('G', 'R', 'P', 'S'): break; // group assignment, we don't import this |
772 | 0 | case TAG('T', 'E', 'X', 'P'): break; // ? |
773 | 0 | case TAG('I', 'N', 'S', 'T'): ReadInstance(sib, stream); break; |
774 | 0 | case TAG('M', 'A', 'T', 'R'): ReadMaterial(sib, stream); break; |
775 | 0 | case TAG('L', 'G', 'H', 'T'): ReadLight(sib, stream); break; |
776 | 0 | default: UnknownChunk(stream, chunk); break; |
777 | 0 | } |
778 | | |
779 | 0 | stream->SetCurrentPos(stream->GetReadLimit()); |
780 | 0 | stream->SetReadLimit(oldLimit); |
781 | 0 | } |
782 | 0 | } |
783 | | |
784 | | // ------------------------------------------------------------------------------------------------ |
785 | | // Imports the given file into the given scene structure. |
786 | | void SIBImporter::InternReadFile(const std::string &pFile, |
787 | 0 | aiScene *pScene, IOSystem *pIOHandler) { |
788 | |
|
789 | 0 | auto file = pIOHandler->Open(pFile, "rb"); |
790 | 0 | if (!file) |
791 | 0 | throw DeadlyImportError("SIB: Could not open ", pFile); |
792 | | |
793 | 0 | StreamReaderLE stream(file); |
794 | | |
795 | | // We should have at least one chunk |
796 | 0 | if (stream.GetRemainingSize() < 16) |
797 | 0 | throw DeadlyImportError("SIB file is either empty or corrupt: ", pFile); |
798 | | |
799 | 0 | SIB sib; |
800 | | |
801 | | // Default material. |
802 | 0 | aiMaterial *defmtl = new aiMaterial; |
803 | 0 | aiString defname = aiString(AI_DEFAULT_MATERIAL_NAME); |
804 | 0 | defmtl->AddProperty(&defname, AI_MATKEY_NAME); |
805 | 0 | sib.mtls.push_back(defmtl); |
806 | | |
807 | | // Read it all. |
808 | 0 | ReadScene(&sib, &stream); |
809 | | |
810 | | // Join the instances and objects together. |
811 | 0 | size_t firstInst = sib.objs.size(); |
812 | 0 | sib.objs.insert(sib.objs.end(), sib.insts.begin(), sib.insts.end()); |
813 | 0 | sib.insts.clear(); |
814 | | |
815 | | // Transfer to the aiScene. |
816 | 0 | pScene->mNumMaterials = static_cast<unsigned int>(sib.mtls.size()); |
817 | 0 | pScene->mNumMeshes = static_cast<unsigned int>(sib.meshes.size()); |
818 | 0 | pScene->mNumLights = static_cast<unsigned int>(sib.lights.size()); |
819 | 0 | pScene->mMaterials = pScene->mNumMaterials ? new aiMaterial *[pScene->mNumMaterials] : nullptr; |
820 | 0 | pScene->mMeshes = pScene->mNumMeshes ? new aiMesh *[pScene->mNumMeshes] : nullptr; |
821 | 0 | pScene->mLights = pScene->mNumLights ? new aiLight *[pScene->mNumLights] : nullptr; |
822 | 0 | if (pScene->mNumMaterials) |
823 | 0 | memcpy(pScene->mMaterials, &sib.mtls[0], sizeof(aiMaterial *) * pScene->mNumMaterials); |
824 | 0 | if (pScene->mNumMeshes) |
825 | 0 | memcpy(pScene->mMeshes, &sib.meshes[0], sizeof(aiMesh *) * pScene->mNumMeshes); |
826 | 0 | if (pScene->mNumLights) |
827 | 0 | memcpy(pScene->mLights, &sib.lights[0], sizeof(aiLight *) * pScene->mNumLights); |
828 | | |
829 | | // Construct the root node. |
830 | 0 | size_t childIdx = 0; |
831 | 0 | aiNode *root = new aiNode(); |
832 | 0 | root->mName.Set("<SIBRoot>"); |
833 | 0 | root->mNumChildren = static_cast<unsigned int>(sib.objs.size() + sib.lights.size()); |
834 | 0 | root->mChildren = root->mNumChildren ? new aiNode *[root->mNumChildren] : nullptr; |
835 | 0 | pScene->mRootNode = root; |
836 | | |
837 | | // Add nodes for each object. |
838 | 0 | for (size_t n = 0; n < sib.objs.size(); n++) { |
839 | 0 | ai_assert(root->mChildren); |
840 | 0 | SIBObject &obj = sib.objs[n]; |
841 | 0 | aiNode *node = new aiNode; |
842 | 0 | root->mChildren[childIdx++] = node; |
843 | 0 | node->mName = obj.name; |
844 | 0 | node->mParent = root; |
845 | 0 | node->mTransformation = obj.axis; |
846 | |
|
847 | 0 | node->mNumMeshes = static_cast<unsigned int>(obj.meshCount); |
848 | 0 | node->mMeshes = node->mNumMeshes ? new unsigned[node->mNumMeshes] : nullptr; |
849 | 0 | for (unsigned i = 0; i < node->mNumMeshes; i++) |
850 | 0 | node->mMeshes[i] = static_cast<unsigned int>(obj.meshIdx + i); |
851 | | |
852 | | // Mark instanced objects as being so. |
853 | 0 | if (n >= firstInst) { |
854 | 0 | node->mMetaData = aiMetadata::Alloc(1); |
855 | 0 | node->mMetaData->Set(0, "IsInstance", true); |
856 | 0 | } |
857 | 0 | } |
858 | | |
859 | | // Add nodes for each light. |
860 | | // (no transformation as the light is already in world space) |
861 | 0 | for (size_t n = 0; n < sib.lights.size(); n++) { |
862 | 0 | ai_assert(root->mChildren); |
863 | 0 | aiLight *light = sib.lights[n]; |
864 | 0 | if (nullptr != light) { |
865 | 0 | aiNode *node = new aiNode; |
866 | 0 | root->mChildren[childIdx++] = node; |
867 | 0 | node->mName = light->mName; |
868 | 0 | node->mParent = root; |
869 | 0 | } |
870 | 0 | } |
871 | 0 | } |
872 | | |
873 | | } // namespace Assimp |
874 | | |
875 | | #endif // !! ASSIMP_BUILD_NO_SIB_IMPORTER |