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
8 #include "CB3DMeshWriter.h"
\r
10 #include "ISkinnedMesh.h"
\r
11 #include "IMeshBuffer.h"
\r
12 #include "IWriteFile.h"
\r
13 #include "ITexture.h"
\r
21 using namespace core;
\r
22 using namespace video;
\r
24 CB3DMeshWriter::CB3DMeshWriter()
\r
27 setDebugName("CB3DMeshWriter");
\r
32 //! Returns the type of the mesh writer
\r
33 EMESH_WRITER_TYPE CB3DMeshWriter::getType() const
\r
40 bool CB3DMeshWriter::writeMesh(io::IWriteFile* file, IMesh* const mesh, s32 flags)
\r
44 #ifdef __BIG_ENDIAN__
\r
45 os::Printer::log("B3D export does not support big-endian systems.", ELL_ERROR);
\r
49 file->write("BB3D", 4);
\r
50 file->write("size", 4); // BB3D chunk size, updated later
\r
52 const u32 version = 1;
\r
53 file->write(&version, 4);
\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
61 for (u32 i = 0; i < numMeshBuffers; i++)
\r
63 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
64 const SMaterial &mat = mb->getMaterial();
\r
66 for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)
\r
68 if (mat.getTexture(j))
\r
71 t.TextureName = core::stringc(mat.getTexture(j)->getName().getPath());
\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
77 // TODO: evaluate texture matrix
\r
85 texsizes += 7*4 + t.TextureName.size() + 1;
\r
86 tex2id[mat.getTexture(j)] = texs.size() - 1;
\r
91 file->write("TEXS", 4);
\r
92 file->write(&texsizes, 4);
\r
94 u32 numTexture = texs.size();
\r
95 for (u32 i = 0; i < numTexture; i++)
\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
103 file->write("BRUS", 4);
\r
104 const u32 brushSizeAdress = file->getPos();
\r
105 file->write(&brushSizeAdress, 4); // BRUSH chunk size, updated later
\r
107 const u32 usedtex = MATERIAL_MAX_TEXTURES;
\r
108 file->write(&usedtex, 4);
\r
110 for (u32 i = 0; i < numMeshBuffers; i++)
\r
112 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
113 const SMaterial &mat = mb->getMaterial();
\r
115 file->write("", 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
124 file->write(&f, 4);
\r
127 file->write(&tmp, 4);
\r
129 file->write(&tmp, 4);
\r
131 for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)
\r
134 if (mat.getTexture(j))
\r
136 id = tex2id[mat.getTexture(j)];
\r
138 file->write(&id, 4);
\r
141 writeSizeFrom(file, brushSizeAdress+4, brushSizeAdress); // BRUSH chunk size
\r
143 file->write("NODE", 4);
\r
144 u32 nodeSizeAdress = file->getPos();
\r
145 file->write(&nodeSizeAdress, 4); // NODE chunk size, updated later
\r
148 file->write("", 1);
\r
151 writeVector3(file, core::vector3df(0.f, 0.f, 0.f));
\r
154 writeVector3(file, core::vector3df(1.f, 1.f, 1.f));
\r
157 writeQuaternion(file, core::quaternion(0.f, 0.f, 0.f, 1.f));
\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
165 file->write(&brushID, 4);
\r
170 file->write("VRTS", 4);
\r
171 const u32 verticesSizeAdress = file->getPos();
\r
172 file->write(&verticesSizeAdress, 4);
\r
174 u32 flagsB3D = 3; // 1=normal values present, 2=rgba values present
\r
175 file->write(&flagsB3D, 4);
\r
177 const u32 texcoordsCount = getUVlayerCount(mesh);
\r
178 file->write(&texcoordsCount, 4);
\r
180 file->write(&flagsB3D, 4);
\r
182 for (u32 i = 0; i < numMeshBuffers; i++)
\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
188 const vector3df &pos = mb->getPosition(j);
\r
189 writeVector3(file, pos);
\r
191 const vector3df &n = mb->getNormal(j);
\r
192 writeVector3(file, n);
\r
194 switch (mb->getVertexType())
\r
198 S3DVertex *v = (S3DVertex *) mb->getVertices();
\r
199 const SColorf col(v[j].Color);
\r
200 writeColor(file, col);
\r
202 const core::vector2df uv1 = v[j].TCoords;
\r
203 writeVector2(file, uv1);
\r
204 if (texcoordsCount == 2)
\r
206 writeVector2(file, core::vector2df(0.f, 0.f));
\r
212 S3DVertex2TCoords *v = (S3DVertex2TCoords *) mb->getVertices();
\r
213 const SColorf col(v[j].Color);
\r
214 writeColor(file, col);
\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
224 S3DVertexTangents *v = (S3DVertexTangents *) mb->getVertices();
\r
225 const SColorf col(v[j].Color);
\r
226 writeColor(file, col);
\r
228 const core::vector2df uv1 = v[j].TCoords;
\r
229 writeVector2(file, uv1);
\r
230 if (texcoordsCount == 2)
\r
232 writeVector2(file, core::vector2df(0.f, 0.f));
\r
239 writeSizeFrom(file, verticesSizeAdress+4, verticesSizeAdress); // VERT chunk size
\r
242 u32 currentMeshBufferIndex = 0;
\r
244 for (u32 i = 0; i < numMeshBuffers; i++)
\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
251 file->write(&i, 4);
\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
257 u32 tmp = idx[j] + currentMeshBufferIndex;
\r
258 file->write(&tmp, sizeof(u32));
\r
260 tmp = idx[j + 1] + currentMeshBufferIndex;
\r
261 file->write(&tmp, sizeof(u32));
\r
263 tmp = idx[j + 2] + currentMeshBufferIndex;
\r
264 file->write(&tmp, sizeof(u32));
\r
266 writeSizeFrom(file, trisSizeAdress+4, trisSizeAdress); // TRIS chunk size
\r
268 currentMeshBufferIndex += mb->getVertexCount();
\r
270 writeSizeFrom(file, meshSizeAdress+4, meshSizeAdress); // MESH chunk size
\r
273 if(ISkinnedMesh *skinnedMesh = getSkinned(mesh))
\r
275 // Write animation data
\r
276 f32 animationSpeedMultiplier = 1.f;
\r
277 if (!skinnedMesh->isStatic())
\r
279 file->write("ANIM", 4);
\r
281 const u32 animsize = 12;
\r
282 file->write(&animsize, 4);
\r
284 const u32 flags = 0;
\r
285 f32 fps = skinnedMesh->getAnimationSpeed();
\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
291 if (fps < minimumAnimationFPS)
\r
293 animationSpeedMultiplier = minimumAnimationFPS / fps;
\r
294 fps = minimumAnimationFPS;
\r
296 const u32 frames = static_cast<u32>(skinnedMesh->getFrameCount() * animationSpeedMultiplier);
\r
298 file->write(&flags, 4);
\r
299 file->write(&frames, 4);
\r
300 file->write(&fps, 4);
\r
304 core::array<ISkinnedMesh::SJoint*> rootJoints = getRootJoints(skinnedMesh);
\r
306 for (u32 i = 0; i < rootJoints.size(); i++)
\r
308 writeJointChunk(file, skinnedMesh, rootJoints[i], animationSpeedMultiplier);
\r
312 writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // Node chunk size
\r
313 writeSizeFrom(file, 8, 4); // BB3D chunk size
\r
320 void CB3DMeshWriter::writeJointChunk(io::IWriteFile* file, ISkinnedMesh* mesh, ISkinnedMesh::SJoint* joint, f32 animationSpeedMultiplier)
\r
323 file->write("NODE", 4);
\r
324 const u32 nodeSizeAdress = file->getPos();
\r
325 file->write(&nodeSizeAdress, 4);
\r
328 core::stringc name = joint->Name;
\r
329 file->write(name.c_str(), name.size());
\r
330 file->write("", 1);
\r
333 const core::vector3df pos = joint->Animatedposition;
\r
334 writeVector3(file, pos);
\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
341 writeVector3(file, scale);
\r
344 const core::quaternion quat = joint->Animatedrotation;
\r
345 writeQuaternion(file, quat);
\r
348 file->write("BONE", 4);
\r
349 u32 bonesize = 8 * joint->Weights.size();
\r
350 file->write(&bonesize, 4);
\r
352 // Skinning ------------------
\r
353 for (u32 i = 0; i < joint->Weights.size(); i++)
\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
359 u32 b3dVertexID = vertexID;
\r
360 for (u32 j = 0; j < bufferID; j++)
\r
362 b3dVertexID += mesh->getMeshBuffer(j)->getVertexCount();
\r
365 file->write(&b3dVertexID, 4);
\r
366 file->write(&weight, 4);
\r
368 // ---------------------------
\r
370 f32 floatBuffer[5];
\r
372 if (joint->PositionKeys.size())
\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
379 u32 flag = 1; // 1 = flag for position keys
\r
380 file->write(&flag, 4);
\r
382 for (u32 i = 0; i < joint->PositionKeys.size(); i++)
\r
384 const s32 frame = static_cast<s32>(joint->PositionKeys[i].frame * animationSpeedMultiplier);
\r
385 file->write(&frame, 4);
\r
387 const core::vector3df pos = joint->PositionKeys[i].position;
\r
388 pos.getAs3Values(floatBuffer);
\r
389 file->write(floatBuffer, 12);
\r
392 if (joint->RotationKeys.size())
\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
400 file->write(&flag, 4);
\r
402 for (u32 i = 0; i < joint->RotationKeys.size(); i++)
\r
404 const s32 frame = static_cast<s32>(joint->RotationKeys[i].frame * animationSpeedMultiplier);
\r
405 const core::quaternion rot = joint->RotationKeys[i].rotation;
\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
415 if (joint->ScaleKeys.size())
\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
423 file->write(&flag, 4);
\r
425 for (u32 i = 0; i < joint->ScaleKeys.size(); i++)
\r
427 const s32 frame = static_cast<s32>(joint->ScaleKeys[i].frame * animationSpeedMultiplier);
\r
428 file->write(&frame, 4);
\r
430 const core::vector3df scale = joint->ScaleKeys[i].scale;
\r
431 scale.getAs3Values(floatBuffer);
\r
432 file->write(floatBuffer, 12);
\r
436 for (u32 i = 0; i < joint->Children.size(); i++)
\r
438 writeJointChunk(file, mesh, joint->Children[i], animationSpeedMultiplier);
\r
441 writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // NODE chunk size
\r
445 ISkinnedMesh* CB3DMeshWriter::getSkinned (IMesh *mesh)
\r
447 if (mesh->getMeshType() == EAMT_SKINNED)
\r
449 return static_cast<ISkinnedMesh*>(mesh);
\r
454 core::array<ISkinnedMesh::SJoint*> CB3DMeshWriter::getRootJoints(const ISkinnedMesh* mesh)
\r
456 core::array<ISkinnedMesh::SJoint*> roots;
\r
458 core::array<ISkinnedMesh::SJoint*> allJoints = mesh->getAllJoints();
\r
459 for (u32 i = 0; i < allJoints.size(); i++)
\r
461 bool isRoot = true;
\r
462 ISkinnedMesh::SJoint* testedJoint = allJoints[i];
\r
463 for (u32 j = 0; j < allJoints.size(); j++)
\r
465 ISkinnedMesh::SJoint* testedJoint2 = allJoints[j];
\r
466 for (u32 k = 0; k < testedJoint2->Children.size(); k++)
\r
468 if (testedJoint == testedJoint2->Children[k])
\r
473 roots.push_back(testedJoint);
\r
479 u32 CB3DMeshWriter::getUVlayerCount(const IMesh* mesh)
\r
481 const u32 numBeshBuffers = mesh->getMeshBufferCount();
\r
482 for (u32 i = 0; i < numBeshBuffers; i++)
\r
484 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
486 if (mb->getVertexType() == EVT_2TCOORDS)
\r
494 void CB3DMeshWriter::writeVector2(io::IWriteFile* file, const core::vector2df& vec2)
\r
496 f32 buffer[2] = {vec2.X, vec2.Y};
\r
497 file->write(buffer, 8);
\r
500 void CB3DMeshWriter::writeVector3(io::IWriteFile* file, const core::vector3df& vec3)
\r
503 vec3.getAs3Values(buffer);
\r
504 file->write(buffer, 12);
\r
507 void CB3DMeshWriter::writeQuaternion(io::IWriteFile* file, const core::quaternion& quat)
\r
509 f32 buffer[4] = {quat.W, quat.X, quat.Y, quat.Z};
\r
510 file->write(buffer, 16);
\r
513 void CB3DMeshWriter::writeColor(io::IWriteFile* file, const video::SColorf& color)
\r
515 f32 buffer[4] = {color.r, color.g, color.b, color.a};
\r
516 file->write(buffer, 16);
\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
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