]> git.lizzy.rs Git - irrlicht.git/blob - source/Irrlicht/CB3DMeshWriter.cpp
Avoid potential number overflows.
[irrlicht.git] / source / Irrlicht / CB3DMeshWriter.cpp
1 // Copyright (C) 2014 Lauri Kasanen\r
2 // This file is part of the "Irrlicht Engine".\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h\r
4 \r
5 // TODO: replace printf's by logging messages\r
6 \r
7 \r
8 #include "CB3DMeshWriter.h"\r
9 #include "os.h"\r
10 #include "ISkinnedMesh.h"\r
11 #include "IMeshBuffer.h"\r
12 #include "IWriteFile.h"\r
13 #include "ITexture.h"\r
14 \r
15 \r
16 namespace irr\r
17 {\r
18 namespace scene\r
19 {\r
20 \r
21 using namespace core;\r
22 using namespace video;\r
23 \r
24 CB3DMeshWriter::CB3DMeshWriter()\r
25 {\r
26         #ifdef _DEBUG\r
27         setDebugName("CB3DMeshWriter");\r
28         #endif\r
29 }\r
30 \r
31 \r
32 //! Returns the type of the mesh writer\r
33 EMESH_WRITER_TYPE CB3DMeshWriter::getType() const\r
34 {\r
35     return EMWT_B3D;\r
36 }\r
37 \r
38 \r
39 //! writes a mesh\r
40 bool CB3DMeshWriter::writeMesh(io::IWriteFile* file, IMesh* const mesh, s32 flags)\r
41 {\r
42     if (!file || !mesh)\r
43         return false;\r
44 #ifdef __BIG_ENDIAN__\r
45     os::Printer::log("B3D export does not support big-endian systems.", ELL_ERROR);\r
46     return false;\r
47 #endif\r
48 \r
49     file->write("BB3D", 4);\r
50     file->write("size", 4); // BB3D chunk size, updated later\r
51 \r
52     const u32 version = 1;\r
53     file->write(&version, 4);\r
54 \r
55     //\r
56 \r
57     const u32 numMeshBuffers = mesh->getMeshBufferCount();\r
58     array<SB3dTexture> texs;\r
59     std::map<ITexture *, u32> tex2id;   // TODO: texture pointer as key not sufficient as same texture can have several id's\r
60     u32 texsizes = 0;\r
61     for (u32 i = 0; i < numMeshBuffers; i++)\r
62         {\r
63         const IMeshBuffer * const mb = mesh->getMeshBuffer(i);\r
64         const SMaterial &mat = mb->getMaterial();\r
65 \r
66         for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)\r
67                 {\r
68             if (mat.getTexture(j))\r
69                         {\r
70                 SB3dTexture t;\r
71                                 t.TextureName = core::stringc(mat.getTexture(j)->getName().getPath());\r
72 \r
73                                 // TODO: need some description of Blitz3D texture-flags to figure this out. But Blend should likely depend on material-type.\r
74                                 t.Flags = j == 2 ? 65536 : 1;\r
75                                 t.Blend = 2;\r
76 \r
77                                 // TODO: evaluate texture matrix\r
78                                 t.Xpos = 0;\r
79                                 t.Ypos = 0;\r
80                                 t.Xscale = 1;\r
81                                 t.Yscale = 1;\r
82                                 t.Angle = 0;\r
83 \r
84                 texs.push_back(t);\r
85                 texsizes += 7*4 + t.TextureName.size() + 1;\r
86                 tex2id[mat.getTexture(j)] = texs.size() - 1;\r
87             }\r
88         }\r
89     }\r
90 \r
91     file->write("TEXS", 4);\r
92     file->write(&texsizes, 4);\r
93 \r
94     u32 numTexture = texs.size();\r
95     for (u32 i = 0; i < numTexture; i++)\r
96         {\r
97         file->write(texs[i].TextureName.c_str(), (size_t)texs[i].TextureName.size() + 1);\r
98         file->write(&texs[i].Flags, 7*4);\r
99     }\r
100 \r
101     //\r
102 \r
103     file->write("BRUS", 4);\r
104     const u32 brushSizeAdress = file->getPos();\r
105     file->write(&brushSizeAdress, 4); // BRUSH chunk size, updated later\r
106 \r
107     const u32 usedtex = MATERIAL_MAX_TEXTURES;\r
108     file->write(&usedtex, 4);\r
109 \r
110     for (u32 i = 0; i < numMeshBuffers; i++)\r
111         {\r
112         const IMeshBuffer * const mb = mesh->getMeshBuffer(i);\r
113         const SMaterial &mat = mb->getMaterial();\r
114 \r
115         file->write("", 1);\r
116 \r
117         float f = 1;\r
118         file->write(&f, 4);\r
119         file->write(&f, 4);\r
120         file->write(&f, 4);\r
121         file->write(&f, 4);\r
122 \r
123         f = 0;\r
124         file->write(&f, 4);\r
125 \r
126         u32 tmp = 1;\r
127         file->write(&tmp, 4);\r
128         tmp = 0;\r
129         file->write(&tmp, 4);\r
130 \r
131         for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)\r
132                 {\r
133                     s32 id = -1;\r
134             if (mat.getTexture(j))\r
135                         {\r
136                 id = tex2id[mat.getTexture(j)];\r
137             }\r
138             file->write(&id, 4);\r
139         }\r
140     }\r
141     writeSizeFrom(file, brushSizeAdress+4, brushSizeAdress); // BRUSH chunk size\r
142 \r
143     file->write("NODE", 4);\r
144     u32 nodeSizeAdress = file->getPos();\r
145     file->write(&nodeSizeAdress, 4); // NODE chunk size, updated later\r
146 \r
147     // Node\r
148     file->write("", 1);\r
149 \r
150     // position\r
151     writeVector3(file, core::vector3df(0.f, 0.f, 0.f));\r
152 \r
153     // scale\r
154     writeVector3(file, core::vector3df(1.f, 1.f, 1.f));\r
155 \r
156     // rotation\r
157     writeQuaternion(file, core::quaternion(0.f, 0.f, 0.f, 1.f));\r
158 \r
159     // Mesh\r
160     file->write("MESH", 4);\r
161     const u32 meshSizeAdress = file->getPos();\r
162     file->write(&meshSizeAdress, 4); // MESH chunk size, updated later\r
163 \r
164         s32 brushID = -1;\r
165     file->write(&brushID, 4);\r
166 \r
167 \r
168 \r
169     // Verts\r
170     file->write("VRTS", 4);\r
171     const u32 verticesSizeAdress = file->getPos();\r
172     file->write(&verticesSizeAdress, 4);\r
173 \r
174     u32 flagsB3D = 3; // 1=normal values present, 2=rgba values present\r
175     file->write(&flagsB3D, 4);\r
176 \r
177     const u32 texcoordsCount = getUVlayerCount(mesh);\r
178     file->write(&texcoordsCount, 4);\r
179     flagsB3D = 2;\r
180     file->write(&flagsB3D, 4);\r
181 \r
182     for (u32 i = 0; i < numMeshBuffers; i++)\r
183     {\r
184         const IMeshBuffer * const mb = mesh->getMeshBuffer(i);\r
185         const u32 numVertices = mb->getVertexCount();\r
186         for (u32 j = 0; j < numVertices; j++)\r
187                 {\r
188             const vector3df &pos = mb->getPosition(j);\r
189             writeVector3(file, pos);\r
190 \r
191             const vector3df &n = mb->getNormal(j);\r
192             writeVector3(file, n);\r
193 \r
194             switch (mb->getVertexType())\r
195                         {\r
196                 case EVT_STANDARD:\r
197                 {\r
198                     S3DVertex *v = (S3DVertex *) mb->getVertices();\r
199                     const SColorf col(v[j].Color);\r
200                     writeColor(file, col);\r
201 \r
202                     const core::vector2df uv1 = v[j].TCoords;\r
203                     writeVector2(file, uv1);\r
204                     if (texcoordsCount == 2)\r
205                     {\r
206                         writeVector2(file, core::vector2df(0.f, 0.f));\r
207                     }\r
208                 }\r
209                 break;\r
210                 case EVT_2TCOORDS:\r
211                 {\r
212                     S3DVertex2TCoords *v = (S3DVertex2TCoords *) mb->getVertices();\r
213                     const SColorf col(v[j].Color);\r
214                     writeColor(file, col);\r
215 \r
216                     const core::vector2df uv1 = v[j].TCoords;\r
217                     writeVector2(file, uv1);\r
218                     const core::vector2df uv2 = v[j].TCoords;\r
219                     writeVector2(file, uv2);\r
220                 }\r
221                 break;\r
222                 case EVT_TANGENTS:\r
223                 {\r
224                     S3DVertexTangents *v = (S3DVertexTangents *) mb->getVertices();\r
225                     const SColorf col(v[j].Color);\r
226                     writeColor(file, col);\r
227 \r
228                     const core::vector2df uv1 = v[j].TCoords;\r
229                     writeVector2(file, uv1);\r
230                     if (texcoordsCount == 2)\r
231                     {\r
232                         writeVector2(file, core::vector2df(0.f, 0.f));\r
233                     }\r
234                 }\r
235                 break;\r
236             }\r
237         }\r
238     }\r
239     writeSizeFrom(file, verticesSizeAdress+4, verticesSizeAdress); // VERT chunk size\r
240 \r
241 \r
242     u32 currentMeshBufferIndex = 0;\r
243     // Tris\r
244     for (u32 i = 0; i < numMeshBuffers; i++)\r
245     {\r
246         const IMeshBuffer * const mb = mesh->getMeshBuffer(i);\r
247         file->write("TRIS", 4);\r
248         const u32 trisSizeAdress = file->getPos();\r
249         file->write(&trisSizeAdress, 4); // TRIS chunk size, updated later\r
250 \r
251         file->write(&i, 4);\r
252 \r
253         u32 numIndices = mb->getIndexCount();\r
254         const u16 * const idx = (u16 *) mb->getIndices();\r
255         for (u32 j = 0; j < numIndices; j += 3)\r
256                 {\r
257             u32 tmp = idx[j] + currentMeshBufferIndex;\r
258             file->write(&tmp, sizeof(u32));\r
259 \r
260             tmp = idx[j + 1] + currentMeshBufferIndex;\r
261             file->write(&tmp, sizeof(u32));\r
262 \r
263             tmp = idx[j + 2] + currentMeshBufferIndex;\r
264             file->write(&tmp, sizeof(u32));\r
265         }\r
266         writeSizeFrom(file, trisSizeAdress+4, trisSizeAdress);  // TRIS chunk size\r
267 \r
268         currentMeshBufferIndex += mb->getVertexCount();\r
269     }\r
270     writeSizeFrom(file, meshSizeAdress+4, meshSizeAdress); // MESH chunk size\r
271 \r
272 \r
273     if(ISkinnedMesh *skinnedMesh = getSkinned(mesh))\r
274     {\r
275         // Write animation data\r
276         f32 animationSpeedMultiplier = 1.f;\r
277         if (!skinnedMesh->isStatic())\r
278         {\r
279             file->write("ANIM", 4);\r
280 \r
281             const u32 animsize = 12;\r
282             file->write(&animsize, 4);\r
283 \r
284             const u32 flags = 0;\r
285             f32 fps = skinnedMesh->getAnimationSpeed();\r
286 \r
287             /* B3D file format use integer as keyframe, so there is some potential issues if the model use float as keyframe (Irrlicht use float) with a low animation FPS value\r
288             So we define a minimum animation FPS value to multiply the frame and FPS value if the FPS of the animation is too low to store the keyframe with integers */\r
289             const int minimumAnimationFPS = 60;\r
290 \r
291             if (fps < minimumAnimationFPS)\r
292             {\r
293                 animationSpeedMultiplier = minimumAnimationFPS / fps;\r
294                 fps = minimumAnimationFPS;\r
295             }\r
296             const u32 frames = static_cast<u32>(skinnedMesh->getFrameCount() * animationSpeedMultiplier);\r
297 \r
298             file->write(&flags, 4);\r
299             file->write(&frames, 4);\r
300             file->write(&fps, 4);\r
301         }\r
302 \r
303         // Write joints\r
304         core::array<ISkinnedMesh::SJoint*> rootJoints = getRootJoints(skinnedMesh);\r
305 \r
306         for (u32 i = 0; i < rootJoints.size(); i++)\r
307         {\r
308             writeJointChunk(file, skinnedMesh, rootJoints[i], animationSpeedMultiplier);\r
309         }\r
310     }\r
311 \r
312     writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // Node chunk size\r
313         writeSizeFrom(file, 8, 4); // BB3D chunk size\r
314 \r
315     return true;\r
316 }\r
317 \r
318 \r
319 \r
320 void CB3DMeshWriter::writeJointChunk(io::IWriteFile* file, ISkinnedMesh* mesh, ISkinnedMesh::SJoint* joint, f32 animationSpeedMultiplier)\r
321 {\r
322     // Node\r
323     file->write("NODE", 4);\r
324     const u32 nodeSizeAdress = file->getPos();\r
325     file->write(&nodeSizeAdress, 4);\r
326 \r
327 \r
328     core::stringc name = joint->Name;\r
329     file->write(name.c_str(), name.size());\r
330     file->write("", 1);\r
331 \r
332     // Position\r
333     const core::vector3df pos = joint->Animatedposition;\r
334     writeVector3(file, pos);\r
335 \r
336     // Scale\r
337     core::vector3df scale = joint->Animatedscale;\r
338     if (scale == core::vector3df(0, 0, 0))\r
339         scale = core::vector3df(1, 1, 1);\r
340 \r
341     writeVector3(file, scale);\r
342 \r
343     // Rotation\r
344     const core::quaternion quat = joint->Animatedrotation;\r
345     writeQuaternion(file, quat);\r
346 \r
347     // Bone\r
348     file->write("BONE", 4);\r
349     u32 bonesize = 8 * joint->Weights.size();\r
350     file->write(&bonesize, 4);\r
351 \r
352     // Skinning ------------------\r
353     for (u32 i = 0; i < joint->Weights.size(); i++)\r
354     {\r
355         const u32 vertexID = joint->Weights[i].vertex_id;\r
356         const u32 bufferID = joint->Weights[i].buffer_id;\r
357         const f32 weight = joint->Weights[i].strength;\r
358 \r
359         u32 b3dVertexID = vertexID;\r
360         for (u32 j = 0; j < bufferID; j++)\r
361         {\r
362             b3dVertexID += mesh->getMeshBuffer(j)->getVertexCount();\r
363         }\r
364 \r
365         file->write(&b3dVertexID, 4);\r
366         file->write(&weight, 4);\r
367     }\r
368     // ---------------------------\r
369 \r
370     f32 floatBuffer[5];\r
371     // Animation keys\r
372     if (joint->PositionKeys.size())\r
373     {\r
374         file->write("KEYS", 4);\r
375         u32 keysSize = 4 * joint->PositionKeys.size() * 4; // X, Y and Z pos + frame\r
376         keysSize += 4;  // Flag to define the type of the key\r
377         file->write(&keysSize, 4);\r
378 \r
379         u32 flag = 1; // 1 = flag for position keys\r
380         file->write(&flag, 4);\r
381 \r
382         for (u32 i = 0; i < joint->PositionKeys.size(); i++)\r
383         {\r
384             const s32 frame = static_cast<s32>(joint->PositionKeys[i].frame * animationSpeedMultiplier);\r
385             file->write(&frame, 4);\r
386 \r
387             const core::vector3df pos = joint->PositionKeys[i].position;\r
388             pos.getAs3Values(floatBuffer);\r
389             file->write(floatBuffer, 12);\r
390         }\r
391     }\r
392     if (joint->RotationKeys.size())\r
393     {\r
394         file->write("KEYS", 4);\r
395         u32 keysSize = 4 * joint->RotationKeys.size() * 5; // W, X, Y and Z rot + frame\r
396         keysSize += 4; // Flag\r
397         file->write(&keysSize, 4);\r
398 \r
399         u32 flag = 4;\r
400         file->write(&flag, 4);\r
401 \r
402         for (u32 i = 0; i < joint->RotationKeys.size(); i++)\r
403         {\r
404             const s32 frame = static_cast<s32>(joint->RotationKeys[i].frame * animationSpeedMultiplier);\r
405             const core::quaternion rot = joint->RotationKeys[i].rotation;\r
406 \r
407             memcpy(floatBuffer, &frame, 4);\r
408             floatBuffer[1] = rot.W;\r
409             floatBuffer[2] = rot.X;\r
410             floatBuffer[3] = rot.Y;\r
411             floatBuffer[4] = rot.Z;\r
412             file->write(floatBuffer, 20);\r
413         }\r
414     }\r
415     if (joint->ScaleKeys.size())\r
416     {\r
417         file->write("KEYS", 4);\r
418         u32 keysSize = 4 * joint->ScaleKeys.size() * 4; // X, Y and Z scale + frame\r
419         keysSize += 4; // Flag\r
420         file->write(&keysSize, 4);\r
421 \r
422         u32 flag = 2;\r
423         file->write(&flag, 4);\r
424 \r
425         for (u32 i = 0; i < joint->ScaleKeys.size(); i++)\r
426         {\r
427             const s32 frame = static_cast<s32>(joint->ScaleKeys[i].frame * animationSpeedMultiplier);\r
428             file->write(&frame, 4);\r
429 \r
430             const core::vector3df scale = joint->ScaleKeys[i].scale;\r
431             scale.getAs3Values(floatBuffer);\r
432             file->write(floatBuffer, 12);\r
433         }\r
434     }\r
435 \r
436     for (u32 i = 0; i < joint->Children.size(); i++)\r
437     {\r
438         writeJointChunk(file, mesh, joint->Children[i], animationSpeedMultiplier);\r
439     }\r
440 \r
441     writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // NODE chunk size\r
442 }\r
443 \r
444 \r
445 ISkinnedMesh* CB3DMeshWriter::getSkinned (IMesh *mesh)\r
446 {\r
447         if (mesh->getMeshType() == EAMT_SKINNED)\r
448     {\r
449                 return static_cast<ISkinnedMesh*>(mesh);\r
450     }\r
451     return 0;\r
452 }\r
453 \r
454 core::array<ISkinnedMesh::SJoint*> CB3DMeshWriter::getRootJoints(const ISkinnedMesh* mesh)\r
455 {\r
456     core::array<ISkinnedMesh::SJoint*> roots;\r
457 \r
458     core::array<ISkinnedMesh::SJoint*> allJoints = mesh->getAllJoints();\r
459     for (u32 i = 0; i < allJoints.size(); i++)\r
460     {\r
461         bool isRoot = true;\r
462         ISkinnedMesh::SJoint* testedJoint = allJoints[i];\r
463         for (u32 j = 0; j < allJoints.size(); j++)\r
464         {\r
465            ISkinnedMesh::SJoint* testedJoint2 = allJoints[j];\r
466            for (u32 k = 0; k < testedJoint2->Children.size(); k++)\r
467            {\r
468                if (testedJoint == testedJoint2->Children[k])\r
469                     isRoot = false;\r
470            }\r
471         }\r
472         if (isRoot)\r
473             roots.push_back(testedJoint);\r
474     }\r
475 \r
476     return roots;\r
477 }\r
478 \r
479 u32 CB3DMeshWriter::getUVlayerCount(IMesh* mesh)\r
480 {\r
481     const u32 numBeshBuffers = mesh->getMeshBufferCount();\r
482     for (u32 i = 0; i < numBeshBuffers; i++)\r
483     {\r
484         const IMeshBuffer * const mb = mesh->getMeshBuffer(i);\r
485 \r
486         if (mb->getVertexType() == EVT_2TCOORDS)\r
487         {\r
488             return 2;\r
489         }\r
490     }\r
491     return 1;\r
492 }\r
493 \r
494 void CB3DMeshWriter::writeVector2(io::IWriteFile* file, const core::vector2df& vec2)\r
495 {\r
496     f32 buffer[2] = {vec2.X, vec2.Y};\r
497     file->write(buffer, 8);\r
498 }\r
499 \r
500 void CB3DMeshWriter::writeVector3(io::IWriteFile* file, const core::vector3df& vec3)\r
501 {\r
502     f32 buffer[3];\r
503     vec3.getAs3Values(buffer);\r
504     file->write(buffer, 12);\r
505 }\r
506 \r
507 void CB3DMeshWriter::writeQuaternion(io::IWriteFile* file, const core::quaternion& quat)\r
508 {\r
509     f32 buffer[4] = {quat.W, quat.X, quat.Y, quat.Z};\r
510     file->write(buffer, 16);\r
511 }\r
512 \r
513 void CB3DMeshWriter::writeColor(io::IWriteFile* file, const video::SColorf& color)\r
514 {\r
515     f32 buffer[4] = {color.r, color.g, color.b, color.a};\r
516     file->write(buffer, 16);\r
517 }\r
518 \r
519 // Write the size from a given position to current position at a specific position in the file\r
520 void CB3DMeshWriter::writeSizeFrom(io::IWriteFile* file, const u32 from, const u32 adressToWrite)\r
521 {\r
522     const long back = file->getPos();\r
523     file->seek(adressToWrite);\r
524     const u32 sizeToWrite = back - from;\r
525     file->write(&sizeToWrite, 4);\r
526     file->seek(back);\r
527 }\r
528 \r
529 } // end namespace\r
530 } // end namespace\r