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
5 // TODO: replace printf's by logging messages
\r
7 #include "IrrCompileConfig.h"
\r
9 #include "CB3DMeshWriter.h"
\r
11 #include "ISkinnedMesh.h"
\r
12 #include "IMeshBuffer.h"
\r
13 #include "IWriteFile.h"
\r
14 #include "ITexture.h"
\r
22 using namespace core;
\r
23 using namespace video;
\r
25 CB3DMeshWriter::CB3DMeshWriter()
\r
28 setDebugName("CB3DMeshWriter");
\r
33 //! Returns the type of the mesh writer
\r
34 EMESH_WRITER_TYPE CB3DMeshWriter::getType() const
\r
41 bool CB3DMeshWriter::writeMesh(io::IWriteFile* file, IMesh* const mesh, s32 flags)
\r
45 #ifdef __BIG_ENDIAN__
\r
46 os::Printer::log("B3D export does not support big-endian systems.", ELL_ERROR);
\r
50 file->write("BB3D", 4);
\r
51 file->write("size", 4); // BB3D chunk size, updated later
\r
53 const u32 version = 1;
\r
54 file->write(&version, 4);
\r
58 const u32 numMeshBuffers = mesh->getMeshBufferCount();
\r
59 array<SB3dTexture> texs;
\r
60 std::map<ITexture *, u32> tex2id; // TODO: texture pointer as key not sufficient as same texture can have several id's
\r
62 for (u32 i = 0; i < numMeshBuffers; i++)
\r
64 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
65 const SMaterial &mat = mb->getMaterial();
\r
67 for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)
\r
69 if (mat.getTexture(j))
\r
72 t.TextureName = core::stringc(mat.getTexture(j)->getName().getPath());
\r
74 // TODO: need some description of Blitz3D texture-flags to figure this out. But Blend should likely depend on material-type.
\r
75 t.Flags = j == 2 ? 65536 : 1;
\r
78 // TODO: evaluate texture matrix
\r
86 texsizes += 7*4 + t.TextureName.size() + 1;
\r
87 tex2id[mat.getTexture(j)] = texs.size() - 1;
\r
92 file->write("TEXS", 4);
\r
93 file->write(&texsizes, 4);
\r
95 u32 numTexture = texs.size();
\r
96 for (u32 i = 0; i < numTexture; i++)
\r
98 file->write(texs[i].TextureName.c_str(), texs[i].TextureName.size() + 1);
\r
99 file->write(&texs[i].Flags, 7*4);
\r
104 file->write("BRUS", 4);
\r
105 const u32 brushSizeAdress = file->getPos();
\r
106 file->write(&brushSizeAdress, 4); // BRUSH chunk size, updated later
\r
108 const u32 usedtex = MATERIAL_MAX_TEXTURES;
\r
109 file->write(&usedtex, 4);
\r
111 for (u32 i = 0; i < numMeshBuffers; i++)
\r
113 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
114 const SMaterial &mat = mb->getMaterial();
\r
116 file->write("", 1);
\r
119 file->write(&f, 4);
\r
120 file->write(&f, 4);
\r
121 file->write(&f, 4);
\r
122 file->write(&f, 4);
\r
125 file->write(&f, 4);
\r
128 file->write(&tmp, 4);
\r
130 file->write(&tmp, 4);
\r
132 for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)
\r
135 if (mat.getTexture(j))
\r
137 id = tex2id[mat.getTexture(j)];
\r
139 file->write(&id, 4);
\r
142 writeSizeFrom(file, brushSizeAdress+4, brushSizeAdress); // BRUSH chunk size
\r
144 file->write("NODE", 4);
\r
145 u32 nodeSizeAdress = file->getPos();
\r
146 file->write(&nodeSizeAdress, 4); // NODE chunk size, updated later
\r
149 file->write("", 1);
\r
152 writeVector3(file, core::vector3df(0.f, 0.f, 0.f));
\r
155 writeVector3(file, core::vector3df(1.f, 1.f, 1.f));
\r
158 writeQuaternion(file, core::quaternion(0.f, 0.f, 0.f, 1.f));
\r
161 file->write("MESH", 4);
\r
162 const u32 meshSizeAdress = file->getPos();
\r
163 file->write(&meshSizeAdress, 4); // MESH chunk size, updated later
\r
166 file->write(&brushID, 4);
\r
171 file->write("VRTS", 4);
\r
172 const u32 verticesSizeAdress = file->getPos();
\r
173 file->write(&verticesSizeAdress, 4);
\r
175 u32 flagsB3D = 3; // 1=normal values present, 2=rgba values present
\r
176 file->write(&flagsB3D, 4);
\r
178 const u32 texcoordsCount = getUVlayerCount(mesh);
\r
179 file->write(&texcoordsCount, 4);
\r
181 file->write(&flagsB3D, 4);
\r
183 for (u32 i = 0; i < numMeshBuffers; i++)
\r
185 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
186 const u32 numVertices = mb->getVertexCount();
\r
187 for (u32 j = 0; j < numVertices; j++)
\r
189 const vector3df &pos = mb->getPosition(j);
\r
190 writeVector3(file, pos);
\r
192 const vector3df &n = mb->getNormal(j);
\r
193 writeVector3(file, n);
\r
195 switch (mb->getVertexType())
\r
199 S3DVertex *v = (S3DVertex *) mb->getVertices();
\r
200 const SColorf col(v[j].Color);
\r
201 writeColor(file, col);
\r
203 const core::vector2df uv1 = v[j].TCoords;
\r
204 writeVector2(file, uv1);
\r
205 if (texcoordsCount == 2)
\r
207 writeVector2(file, core::vector2df(0.f, 0.f));
\r
213 S3DVertex2TCoords *v = (S3DVertex2TCoords *) mb->getVertices();
\r
214 const SColorf col(v[j].Color);
\r
215 writeColor(file, col);
\r
217 const core::vector2df uv1 = v[j].TCoords;
\r
218 writeVector2(file, uv1);
\r
219 const core::vector2df uv2 = v[j].TCoords;
\r
220 writeVector2(file, uv2);
\r
225 S3DVertexTangents *v = (S3DVertexTangents *) mb->getVertices();
\r
226 const SColorf col(v[j].Color);
\r
227 writeColor(file, col);
\r
229 const core::vector2df uv1 = v[j].TCoords;
\r
230 writeVector2(file, uv1);
\r
231 if (texcoordsCount == 2)
\r
233 writeVector2(file, core::vector2df(0.f, 0.f));
\r
240 writeSizeFrom(file, verticesSizeAdress+4, verticesSizeAdress); // VERT chunk size
\r
243 u32 currentMeshBufferIndex = 0;
\r
245 for (u32 i = 0; i < numMeshBuffers; i++)
\r
247 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
248 file->write("TRIS", 4);
\r
249 const u32 trisSizeAdress = file->getPos();
\r
250 file->write(&trisSizeAdress, 4); // TRIS chunk size, updated later
\r
252 file->write(&i, 4);
\r
254 u32 numIndices = mb->getIndexCount();
\r
255 const u16 * const idx = (u16 *) mb->getIndices();
\r
256 for (u32 j = 0; j < numIndices; j += 3)
\r
258 u32 tmp = idx[j] + currentMeshBufferIndex;
\r
259 file->write(&tmp, sizeof(u32));
\r
261 tmp = idx[j + 1] + currentMeshBufferIndex;
\r
262 file->write(&tmp, sizeof(u32));
\r
264 tmp = idx[j + 2] + currentMeshBufferIndex;
\r
265 file->write(&tmp, sizeof(u32));
\r
267 writeSizeFrom(file, trisSizeAdress+4, trisSizeAdress); // TRIS chunk size
\r
269 currentMeshBufferIndex += mb->getVertexCount();
\r
271 writeSizeFrom(file, meshSizeAdress+4, meshSizeAdress); // MESH chunk size
\r
274 if(ISkinnedMesh *skinnedMesh = getSkinned(mesh))
\r
276 // Write animation data
\r
277 f32 animationSpeedMultiplier = 1.f;
\r
278 if (!skinnedMesh->isStatic())
\r
280 file->write("ANIM", 4);
\r
282 const u32 animsize = 12;
\r
283 file->write(&animsize, 4);
\r
285 const u32 flags = 0;
\r
286 f32 fps = skinnedMesh->getAnimationSpeed();
\r
288 /* 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
289 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
290 const int minimumAnimationFPS = 60;
\r
292 if (fps < minimumAnimationFPS)
\r
294 animationSpeedMultiplier = minimumAnimationFPS / fps;
\r
295 fps = minimumAnimationFPS;
\r
297 const u32 frames = static_cast<u32>(skinnedMesh->getFrameCount() * animationSpeedMultiplier);
\r
299 file->write(&flags, 4);
\r
300 file->write(&frames, 4);
\r
301 file->write(&fps, 4);
\r
305 core::array<ISkinnedMesh::SJoint*> rootJoints = getRootJoints(skinnedMesh);
\r
307 for (u32 i = 0; i < rootJoints.size(); i++)
\r
309 writeJointChunk(file, skinnedMesh, rootJoints[i], animationSpeedMultiplier);
\r
313 writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // Node chunk size
\r
314 writeSizeFrom(file, 8, 4); // BB3D chunk size
\r
321 void CB3DMeshWriter::writeJointChunk(io::IWriteFile* file, ISkinnedMesh* mesh, ISkinnedMesh::SJoint* joint, f32 animationSpeedMultiplier)
\r
324 file->write("NODE", 4);
\r
325 const u32 nodeSizeAdress = file->getPos();
\r
326 file->write(&nodeSizeAdress, 4);
\r
329 core::stringc name = joint->Name;
\r
330 file->write(name.c_str(), name.size());
\r
331 file->write("", 1);
\r
334 const core::vector3df pos = joint->Animatedposition;
\r
335 writeVector3(file, pos);
\r
338 core::vector3df scale = joint->Animatedscale;
\r
339 if (scale == core::vector3df(0, 0, 0))
\r
340 scale = core::vector3df(1, 1, 1);
\r
342 writeVector3(file, scale);
\r
345 const core::quaternion quat = joint->Animatedrotation;
\r
346 writeQuaternion(file, quat);
\r
349 file->write("BONE", 4);
\r
350 u32 bonesize = 8 * joint->Weights.size();
\r
351 file->write(&bonesize, 4);
\r
353 // Skinning ------------------
\r
354 for (u32 i = 0; i < joint->Weights.size(); i++)
\r
356 const u32 vertexID = joint->Weights[i].vertex_id;
\r
357 const u32 bufferID = joint->Weights[i].buffer_id;
\r
358 const f32 weight = joint->Weights[i].strength;
\r
360 u32 b3dVertexID = vertexID;
\r
361 for (u32 j = 0; j < bufferID; j++)
\r
363 b3dVertexID += mesh->getMeshBuffer(j)->getVertexCount();
\r
366 file->write(&b3dVertexID, 4);
\r
367 file->write(&weight, 4);
\r
369 // ---------------------------
\r
371 f32 floatBuffer[5];
\r
373 if (joint->PositionKeys.size())
\r
375 file->write("KEYS", 4);
\r
376 u32 keysSize = 4 * joint->PositionKeys.size() * 4; // X, Y and Z pos + frame
\r
377 keysSize += 4; // Flag to define the type of the key
\r
378 file->write(&keysSize, 4);
\r
380 u32 flag = 1; // 1 = flag for position keys
\r
381 file->write(&flag, 4);
\r
383 for (u32 i = 0; i < joint->PositionKeys.size(); i++)
\r
385 const s32 frame = static_cast<s32>(joint->PositionKeys[i].frame * animationSpeedMultiplier);
\r
386 file->write(&frame, 4);
\r
388 const core::vector3df pos = joint->PositionKeys[i].position;
\r
389 pos.getAs3Values(floatBuffer);
\r
390 file->write(floatBuffer, 12);
\r
393 if (joint->RotationKeys.size())
\r
395 file->write("KEYS", 4);
\r
396 u32 keysSize = 4 * joint->RotationKeys.size() * 5; // W, X, Y and Z rot + frame
\r
397 keysSize += 4; // Flag
\r
398 file->write(&keysSize, 4);
\r
401 file->write(&flag, 4);
\r
403 for (u32 i = 0; i < joint->RotationKeys.size(); i++)
\r
405 const s32 frame = static_cast<s32>(joint->RotationKeys[i].frame * animationSpeedMultiplier);
\r
406 const core::quaternion rot = joint->RotationKeys[i].rotation;
\r
408 memcpy(floatBuffer, &frame, 4);
\r
409 floatBuffer[1] = rot.W;
\r
410 floatBuffer[2] = rot.X;
\r
411 floatBuffer[3] = rot.Y;
\r
412 floatBuffer[4] = rot.Z;
\r
413 file->write(floatBuffer, 20);
\r
416 if (joint->ScaleKeys.size())
\r
418 file->write("KEYS", 4);
\r
419 u32 keysSize = 4 * joint->ScaleKeys.size() * 4; // X, Y and Z scale + frame
\r
420 keysSize += 4; // Flag
\r
421 file->write(&keysSize, 4);
\r
424 file->write(&flag, 4);
\r
426 for (u32 i = 0; i < joint->ScaleKeys.size(); i++)
\r
428 const s32 frame = static_cast<s32>(joint->ScaleKeys[i].frame * animationSpeedMultiplier);
\r
429 file->write(&frame, 4);
\r
431 const core::vector3df scale = joint->ScaleKeys[i].scale;
\r
432 scale.getAs3Values(floatBuffer);
\r
433 file->write(floatBuffer, 12);
\r
437 for (u32 i = 0; i < joint->Children.size(); i++)
\r
439 writeJointChunk(file, mesh, joint->Children[i], animationSpeedMultiplier);
\r
442 writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // NODE chunk size
\r
446 ISkinnedMesh* CB3DMeshWriter::getSkinned (IMesh *mesh)
\r
448 if (mesh->getMeshType() == EAMT_SKINNED)
\r
450 return static_cast<ISkinnedMesh*>(mesh);
\r
455 core::array<ISkinnedMesh::SJoint*> CB3DMeshWriter::getRootJoints(const ISkinnedMesh* mesh)
\r
457 core::array<ISkinnedMesh::SJoint*> roots;
\r
459 core::array<ISkinnedMesh::SJoint*> allJoints = mesh->getAllJoints();
\r
460 for (u32 i = 0; i < allJoints.size(); i++)
\r
462 bool isRoot = true;
\r
463 ISkinnedMesh::SJoint* testedJoint = allJoints[i];
\r
464 for (u32 j = 0; j < allJoints.size(); j++)
\r
466 ISkinnedMesh::SJoint* testedJoint2 = allJoints[j];
\r
467 for (u32 k = 0; k < testedJoint2->Children.size(); k++)
\r
469 if (testedJoint == testedJoint2->Children[k])
\r
474 roots.push_back(testedJoint);
\r
480 u32 CB3DMeshWriter::getUVlayerCount(IMesh* mesh)
\r
482 const u32 numBeshBuffers = mesh->getMeshBufferCount();
\r
483 for (u32 i = 0; i < numBeshBuffers; i++)
\r
485 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
487 if (mb->getVertexType() == EVT_2TCOORDS)
\r
495 void CB3DMeshWriter::writeVector2(io::IWriteFile* file, const core::vector2df& vec2)
\r
497 f32 buffer[2] = {vec2.X, vec2.Y};
\r
498 file->write(buffer, 8);
\r
501 void CB3DMeshWriter::writeVector3(io::IWriteFile* file, const core::vector3df& vec3)
\r
504 vec3.getAs3Values(buffer);
\r
505 file->write(buffer, 12);
\r
508 void CB3DMeshWriter::writeQuaternion(io::IWriteFile* file, const core::quaternion& quat)
\r
510 f32 buffer[4] = {quat.W, quat.X, quat.Y, quat.Z};
\r
511 file->write(buffer, 16);
\r
514 void CB3DMeshWriter::writeColor(io::IWriteFile* file, const video::SColorf& color)
\r
516 f32 buffer[4] = {color.r, color.g, color.b, color.a};
\r
517 file->write(buffer, 16);
\r
520 // Write the size from a given position to current position at a specific position in the file
\r
521 void CB3DMeshWriter::writeSizeFrom(io::IWriteFile* file, const u32 from, const u32 adressToWrite)
\r
523 const long back = file->getPos();
\r
524 file->seek(adressToWrite);
\r
525 const u32 sizeToWrite = back - from;
\r
526 file->write(&sizeToWrite, 4);
\r