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 #ifdef _IRR_COMPILE_WITH_B3D_WRITER_
\r
11 #include "CB3DMeshWriter.h"
\r
13 #include "ISkinnedMesh.h"
\r
14 #include "IMeshBuffer.h"
\r
15 #include "IWriteFile.h"
\r
16 #include "ITexture.h"
\r
24 using namespace core;
\r
25 using namespace video;
\r
27 CB3DMeshWriter::CB3DMeshWriter()
\r
30 setDebugName("CB3DMeshWriter");
\r
35 //! Returns the type of the mesh writer
\r
36 EMESH_WRITER_TYPE CB3DMeshWriter::getType() const
\r
43 bool CB3DMeshWriter::writeMesh(io::IWriteFile* file, IMesh* const mesh, s32 flags)
\r
47 #ifdef __BIG_ENDIAN__
\r
48 os::Printer::log("B3D export does not support big-endian systems.", ELL_ERROR);
\r
52 file->write("BB3D", 4);
\r
53 file->write("size", 4); // BB3D chunk size, updated later
\r
55 const u32 version = 1;
\r
56 file->write(&version, 4);
\r
60 const u32 numMeshBuffers = mesh->getMeshBufferCount();
\r
61 array<SB3dTexture> texs;
\r
62 std::map<ITexture *, u32> tex2id; // TODO: texture pointer as key not sufficient as same texture can have several id's
\r
64 for (u32 i = 0; i < numMeshBuffers; i++)
\r
66 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
67 const SMaterial &mat = mb->getMaterial();
\r
69 for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)
\r
71 if (mat.getTexture(j))
\r
74 t.TextureName = core::stringc(mat.getTexture(j)->getName().getPath());
\r
76 // TODO: need some description of Blitz3D texture-flags to figure this out. But Blend should likely depend on material-type.
\r
77 t.Flags = j == 2 ? 65536 : 1;
\r
80 // TODO: evaluate texture matrix
\r
88 texsizes += 7*4 + t.TextureName.size() + 1;
\r
89 tex2id[mat.getTexture(j)] = texs.size() - 1;
\r
94 file->write("TEXS", 4);
\r
95 file->write(&texsizes, 4);
\r
97 u32 numTexture = texs.size();
\r
98 for (u32 i = 0; i < numTexture; i++)
\r
100 file->write(texs[i].TextureName.c_str(), texs[i].TextureName.size() + 1);
\r
101 file->write(&texs[i].Flags, 7*4);
\r
106 file->write("BRUS", 4);
\r
107 const u32 brushSizeAdress = file->getPos();
\r
108 file->write(&brushSizeAdress, 4); // BRUSH chunk size, updated later
\r
110 const u32 usedtex = MATERIAL_MAX_TEXTURES;
\r
111 file->write(&usedtex, 4);
\r
113 for (u32 i = 0; i < numMeshBuffers; i++)
\r
115 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
116 const SMaterial &mat = mb->getMaterial();
\r
118 file->write("", 1);
\r
121 file->write(&f, 4);
\r
122 file->write(&f, 4);
\r
123 file->write(&f, 4);
\r
124 file->write(&f, 4);
\r
127 file->write(&f, 4);
\r
130 file->write(&tmp, 4);
\r
132 file->write(&tmp, 4);
\r
134 for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)
\r
137 if (mat.getTexture(j))
\r
139 id = tex2id[mat.getTexture(j)];
\r
141 file->write(&id, 4);
\r
144 writeSizeFrom(file, brushSizeAdress+4, brushSizeAdress); // BRUSH chunk size
\r
146 file->write("NODE", 4);
\r
147 u32 nodeSizeAdress = file->getPos();
\r
148 file->write(&nodeSizeAdress, 4); // NODE chunk size, updated later
\r
151 file->write("", 1);
\r
154 writeVector3(file, core::vector3df(0.f, 0.f, 0.f));
\r
157 writeVector3(file, core::vector3df(1.f, 1.f, 1.f));
\r
160 writeQuaternion(file, core::quaternion(0.f, 0.f, 0.f, 1.f));
\r
163 file->write("MESH", 4);
\r
164 const u32 meshSizeAdress = file->getPos();
\r
165 file->write(&meshSizeAdress, 4); // MESH chunk size, updated later
\r
168 file->write(&brushID, 4);
\r
173 file->write("VRTS", 4);
\r
174 const u32 verticesSizeAdress = file->getPos();
\r
175 file->write(&verticesSizeAdress, 4);
\r
177 u32 flagsB3D = 3; // 1=normal values present, 2=rgba values present
\r
178 file->write(&flagsB3D, 4);
\r
180 const u32 texcoordsCount = getUVlayerCount(mesh);
\r
181 file->write(&texcoordsCount, 4);
\r
183 file->write(&flagsB3D, 4);
\r
185 for (u32 i = 0; i < numMeshBuffers; i++)
\r
187 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
188 const u32 numVertices = mb->getVertexCount();
\r
189 for (u32 j = 0; j < numVertices; j++)
\r
191 const vector3df &pos = mb->getPosition(j);
\r
192 writeVector3(file, pos);
\r
194 const vector3df &n = mb->getNormal(j);
\r
195 writeVector3(file, n);
\r
197 switch (mb->getVertexType())
\r
201 S3DVertex *v = (S3DVertex *) mb->getVertices();
\r
202 const SColorf col(v[j].Color);
\r
203 writeColor(file, col);
\r
205 const core::vector2df uv1 = v[j].TCoords;
\r
206 writeVector2(file, uv1);
\r
207 if (texcoordsCount == 2)
\r
209 writeVector2(file, core::vector2df(0.f, 0.f));
\r
215 S3DVertex2TCoords *v = (S3DVertex2TCoords *) mb->getVertices();
\r
216 const SColorf col(v[j].Color);
\r
217 writeColor(file, col);
\r
219 const core::vector2df uv1 = v[j].TCoords;
\r
220 writeVector2(file, uv1);
\r
221 const core::vector2df uv2 = v[j].TCoords;
\r
222 writeVector2(file, uv2);
\r
227 S3DVertexTangents *v = (S3DVertexTangents *) mb->getVertices();
\r
228 const SColorf col(v[j].Color);
\r
229 writeColor(file, col);
\r
231 const core::vector2df uv1 = v[j].TCoords;
\r
232 writeVector2(file, uv1);
\r
233 if (texcoordsCount == 2)
\r
235 writeVector2(file, core::vector2df(0.f, 0.f));
\r
242 writeSizeFrom(file, verticesSizeAdress+4, verticesSizeAdress); // VERT chunk size
\r
245 u32 currentMeshBufferIndex = 0;
\r
247 for (u32 i = 0; i < numMeshBuffers; i++)
\r
249 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
250 file->write("TRIS", 4);
\r
251 const u32 trisSizeAdress = file->getPos();
\r
252 file->write(&trisSizeAdress, 4); // TRIS chunk size, updated later
\r
254 file->write(&i, 4);
\r
256 u32 numIndices = mb->getIndexCount();
\r
257 const u16 * const idx = (u16 *) mb->getIndices();
\r
258 for (u32 j = 0; j < numIndices; j += 3)
\r
260 u32 tmp = idx[j] + currentMeshBufferIndex;
\r
261 file->write(&tmp, sizeof(u32));
\r
263 tmp = idx[j + 1] + currentMeshBufferIndex;
\r
264 file->write(&tmp, sizeof(u32));
\r
266 tmp = idx[j + 2] + currentMeshBufferIndex;
\r
267 file->write(&tmp, sizeof(u32));
\r
269 writeSizeFrom(file, trisSizeAdress+4, trisSizeAdress); // TRIS chunk size
\r
271 currentMeshBufferIndex += mb->getVertexCount();
\r
273 writeSizeFrom(file, meshSizeAdress+4, meshSizeAdress); // MESH chunk size
\r
276 if(ISkinnedMesh *skinnedMesh = getSkinned(mesh))
\r
278 // Write animation data
\r
279 f32 animationSpeedMultiplier = 1.f;
\r
280 if (!skinnedMesh->isStatic())
\r
282 file->write("ANIM", 4);
\r
284 const u32 animsize = 12;
\r
285 file->write(&animsize, 4);
\r
287 const u32 flags = 0;
\r
288 f32 fps = skinnedMesh->getAnimationSpeed();
\r
290 /* 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
291 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
292 const int minimumAnimationFPS = 60;
\r
294 if (fps < minimumAnimationFPS)
\r
296 animationSpeedMultiplier = minimumAnimationFPS / fps;
\r
297 fps = minimumAnimationFPS;
\r
299 const u32 frames = static_cast<u32>(skinnedMesh->getFrameCount() * animationSpeedMultiplier);
\r
301 file->write(&flags, 4);
\r
302 file->write(&frames, 4);
\r
303 file->write(&fps, 4);
\r
307 core::array<ISkinnedMesh::SJoint*> rootJoints = getRootJoints(skinnedMesh);
\r
309 for (u32 i = 0; i < rootJoints.size(); i++)
\r
311 writeJointChunk(file, skinnedMesh, rootJoints[i], animationSpeedMultiplier);
\r
315 writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // Node chunk size
\r
316 writeSizeFrom(file, 8, 4); // BB3D chunk size
\r
323 void CB3DMeshWriter::writeJointChunk(io::IWriteFile* file, ISkinnedMesh* mesh, ISkinnedMesh::SJoint* joint, f32 animationSpeedMultiplier)
\r
326 file->write("NODE", 4);
\r
327 const u32 nodeSizeAdress = file->getPos();
\r
328 file->write(&nodeSizeAdress, 4);
\r
331 core::stringc name = joint->Name;
\r
332 file->write(name.c_str(), name.size());
\r
333 file->write("", 1);
\r
336 const core::vector3df pos = joint->Animatedposition;
\r
337 writeVector3(file, pos);
\r
340 core::vector3df scale = joint->Animatedscale;
\r
341 if (scale == core::vector3df(0, 0, 0))
\r
342 scale = core::vector3df(1, 1, 1);
\r
344 writeVector3(file, scale);
\r
347 const core::quaternion quat = joint->Animatedrotation;
\r
348 writeQuaternion(file, quat);
\r
351 file->write("BONE", 4);
\r
352 u32 bonesize = 8 * joint->Weights.size();
\r
353 file->write(&bonesize, 4);
\r
355 // Skinning ------------------
\r
356 for (u32 i = 0; i < joint->Weights.size(); i++)
\r
358 const u32 vertexID = joint->Weights[i].vertex_id;
\r
359 const u32 bufferID = joint->Weights[i].buffer_id;
\r
360 const f32 weight = joint->Weights[i].strength;
\r
362 u32 b3dVertexID = vertexID;
\r
363 for (u32 j = 0; j < bufferID; j++)
\r
365 b3dVertexID += mesh->getMeshBuffer(j)->getVertexCount();
\r
368 file->write(&b3dVertexID, 4);
\r
369 file->write(&weight, 4);
\r
371 // ---------------------------
\r
373 f32 floatBuffer[5];
\r
375 if (joint->PositionKeys.size())
\r
377 file->write("KEYS", 4);
\r
378 u32 keysSize = 4 * joint->PositionKeys.size() * 4; // X, Y and Z pos + frame
\r
379 keysSize += 4; // Flag to define the type of the key
\r
380 file->write(&keysSize, 4);
\r
382 u32 flag = 1; // 1 = flag for position keys
\r
383 file->write(&flag, 4);
\r
385 for (u32 i = 0; i < joint->PositionKeys.size(); i++)
\r
387 const s32 frame = static_cast<s32>(joint->PositionKeys[i].frame * animationSpeedMultiplier);
\r
388 file->write(&frame, 4);
\r
390 const core::vector3df pos = joint->PositionKeys[i].position;
\r
391 pos.getAs3Values(floatBuffer);
\r
392 file->write(floatBuffer, 12);
\r
395 if (joint->RotationKeys.size())
\r
397 file->write("KEYS", 4);
\r
398 u32 keysSize = 4 * joint->RotationKeys.size() * 5; // W, X, Y and Z rot + frame
\r
399 keysSize += 4; // Flag
\r
400 file->write(&keysSize, 4);
\r
403 file->write(&flag, 4);
\r
405 for (u32 i = 0; i < joint->RotationKeys.size(); i++)
\r
407 const s32 frame = static_cast<s32>(joint->RotationKeys[i].frame * animationSpeedMultiplier);
\r
408 const core::quaternion rot = joint->RotationKeys[i].rotation;
\r
410 memcpy(floatBuffer, &frame, 4);
\r
411 floatBuffer[1] = rot.W;
\r
412 floatBuffer[2] = rot.X;
\r
413 floatBuffer[3] = rot.Y;
\r
414 floatBuffer[4] = rot.Z;
\r
415 file->write(floatBuffer, 20);
\r
418 if (joint->ScaleKeys.size())
\r
420 file->write("KEYS", 4);
\r
421 u32 keysSize = 4 * joint->ScaleKeys.size() * 4; // X, Y and Z scale + frame
\r
422 keysSize += 4; // Flag
\r
423 file->write(&keysSize, 4);
\r
426 file->write(&flag, 4);
\r
428 for (u32 i = 0; i < joint->ScaleKeys.size(); i++)
\r
430 const s32 frame = static_cast<s32>(joint->ScaleKeys[i].frame * animationSpeedMultiplier);
\r
431 file->write(&frame, 4);
\r
433 const core::vector3df scale = joint->ScaleKeys[i].scale;
\r
434 scale.getAs3Values(floatBuffer);
\r
435 file->write(floatBuffer, 12);
\r
439 for (u32 i = 0; i < joint->Children.size(); i++)
\r
441 writeJointChunk(file, mesh, joint->Children[i], animationSpeedMultiplier);
\r
444 writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // NODE chunk size
\r
448 ISkinnedMesh* CB3DMeshWriter::getSkinned (IMesh *mesh)
\r
450 if (mesh->getMeshType() == EAMT_SKINNED)
\r
452 return static_cast<ISkinnedMesh*>(mesh);
\r
457 core::array<ISkinnedMesh::SJoint*> CB3DMeshWriter::getRootJoints(const ISkinnedMesh* mesh)
\r
459 core::array<ISkinnedMesh::SJoint*> roots;
\r
461 core::array<ISkinnedMesh::SJoint*> allJoints = mesh->getAllJoints();
\r
462 for (u32 i = 0; i < allJoints.size(); i++)
\r
464 bool isRoot = true;
\r
465 ISkinnedMesh::SJoint* testedJoint = allJoints[i];
\r
466 for (u32 j = 0; j < allJoints.size(); j++)
\r
468 ISkinnedMesh::SJoint* testedJoint2 = allJoints[j];
\r
469 for (u32 k = 0; k < testedJoint2->Children.size(); k++)
\r
471 if (testedJoint == testedJoint2->Children[k])
\r
476 roots.push_back(testedJoint);
\r
482 u32 CB3DMeshWriter::getUVlayerCount(IMesh* mesh)
\r
484 const u32 numBeshBuffers = mesh->getMeshBufferCount();
\r
485 for (u32 i = 0; i < numBeshBuffers; i++)
\r
487 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
489 if (mb->getVertexType() == EVT_2TCOORDS)
\r
497 void CB3DMeshWriter::writeVector2(io::IWriteFile* file, const core::vector2df& vec2)
\r
499 f32 buffer[2] = {vec2.X, vec2.Y};
\r
500 file->write(buffer, 8);
\r
503 void CB3DMeshWriter::writeVector3(io::IWriteFile* file, const core::vector3df& vec3)
\r
506 vec3.getAs3Values(buffer);
\r
507 file->write(buffer, 12);
\r
510 void CB3DMeshWriter::writeQuaternion(io::IWriteFile* file, const core::quaternion& quat)
\r
512 f32 buffer[4] = {quat.W, quat.X, quat.Y, quat.Z};
\r
513 file->write(buffer, 16);
\r
516 void CB3DMeshWriter::writeColor(io::IWriteFile* file, const video::SColorf& color)
\r
518 f32 buffer[4] = {color.r, color.g, color.b, color.a};
\r
519 file->write(buffer, 16);
\r
522 // Write the size from a given position to current position at a specific position in the file
\r
523 void CB3DMeshWriter::writeSizeFrom(io::IWriteFile* file, const u32 from, const u32 adressToWrite)
\r
525 const long back = file->getPos();
\r
526 file->seek(adressToWrite);
\r
527 const u32 sizeToWrite = back - from;
\r
528 file->write(&sizeToWrite, 4);
\r
535 #endif // _IRR_COMPILE_WITH_B3D_WRITER_
\r