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
25 using namespace core;
\r
26 using namespace video;
\r
28 CB3DMeshWriter::CB3DMeshWriter()
\r
31 setDebugName("CB3DMeshWriter");
\r
36 //! Returns the type of the mesh writer
\r
37 EMESH_WRITER_TYPE CB3DMeshWriter::getType() const
\r
44 bool CB3DMeshWriter::writeMesh(io::IWriteFile* file, IMesh* const mesh, s32 flags)
\r
48 #ifdef __BIG_ENDIAN__
\r
49 os::Printer::log("B3D export does not support big-endian systems.", ELL_ERROR);
\r
53 file->write("BB3D", 4);
\r
54 file->write("size", 4); // BB3D chunk size, updated later
\r
56 const u32 version = 1;
\r
57 file->write(&version, 4);
\r
61 const u32 numMeshBuffers = mesh->getMeshBufferCount();
\r
62 array<SB3dTexture> texs;
\r
63 map<ITexture *, u32> tex2id; // TODO: texture pointer as key not sufficient as same texture can have several id's
\r
65 for (u32 i = 0; i < numMeshBuffers; i++)
\r
67 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
68 const SMaterial &mat = mb->getMaterial();
\r
70 for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)
\r
72 if (mat.getTexture(j))
\r
75 t.TextureName = core::stringc(mat.getTexture(j)->getName().getPath());
\r
77 // TODO: need some description of Blitz3D texture-flags to figure this out. But Blend should likely depend on material-type.
\r
78 t.Flags = j == 2 ? 65536 : 1;
\r
81 // TODO: evaluate texture matrix
\r
89 texsizes += 7*4 + t.TextureName.size() + 1;
\r
90 tex2id[mat.getTexture(j)] = texs.size() - 1;
\r
95 file->write("TEXS", 4);
\r
96 file->write(&texsizes, 4);
\r
98 u32 numTexture = texs.size();
\r
99 for (u32 i = 0; i < numTexture; i++)
\r
101 file->write(texs[i].TextureName.c_str(), texs[i].TextureName.size() + 1);
\r
102 file->write(&texs[i].Flags, 7*4);
\r
107 file->write("BRUS", 4);
\r
108 const u32 brushSizeAdress = file->getPos();
\r
109 file->write(&brushSizeAdress, 4); // BRUSH chunk size, updated later
\r
111 const u32 usedtex = MATERIAL_MAX_TEXTURES;
\r
112 file->write(&usedtex, 4);
\r
114 for (u32 i = 0; i < numMeshBuffers; i++)
\r
116 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
117 const SMaterial &mat = mb->getMaterial();
\r
119 file->write("", 1);
\r
122 file->write(&f, 4);
\r
123 file->write(&f, 4);
\r
124 file->write(&f, 4);
\r
125 file->write(&f, 4);
\r
128 file->write(&f, 4);
\r
131 file->write(&tmp, 4);
\r
133 file->write(&tmp, 4);
\r
135 for (u32 j = 0; j < MATERIAL_MAX_TEXTURES; j++)
\r
138 if (mat.getTexture(j))
\r
140 id = tex2id[mat.getTexture(j)];
\r
142 file->write(&id, 4);
\r
145 writeSizeFrom(file, brushSizeAdress+4, brushSizeAdress); // BRUSH chunk size
\r
147 file->write("NODE", 4);
\r
148 u32 nodeSizeAdress = file->getPos();
\r
149 file->write(&nodeSizeAdress, 4); // NODE chunk size, updated later
\r
152 file->write("", 1);
\r
155 writeVector3(file, core::vector3df(0.f, 0.f, 0.f));
\r
158 writeVector3(file, core::vector3df(1.f, 1.f, 1.f));
\r
161 writeQuaternion(file, core::quaternion(0.f, 0.f, 0.f, 1.f));
\r
164 file->write("MESH", 4);
\r
165 const u32 meshSizeAdress = file->getPos();
\r
166 file->write(&meshSizeAdress, 4); // MESH chunk size, updated later
\r
169 file->write(&brushID, 4);
\r
174 file->write("VRTS", 4);
\r
175 const u32 verticesSizeAdress = file->getPos();
\r
176 file->write(&verticesSizeAdress, 4);
\r
178 u32 flagsB3D = 3; // 1=normal values present, 2=rgba values present
\r
179 file->write(&flagsB3D, 4);
\r
181 const u32 texcoordsCount = getUVlayerCount(mesh);
\r
182 file->write(&texcoordsCount, 4);
\r
184 file->write(&flagsB3D, 4);
\r
186 for (u32 i = 0; i < numMeshBuffers; i++)
\r
188 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
189 const u32 numVertices = mb->getVertexCount();
\r
190 for (u32 j = 0; j < numVertices; j++)
\r
192 const vector3df &pos = mb->getPosition(j);
\r
193 writeVector3(file, pos);
\r
195 const vector3df &n = mb->getNormal(j);
\r
196 writeVector3(file, n);
\r
198 switch (mb->getVertexType())
\r
202 S3DVertex *v = (S3DVertex *) mb->getVertices();
\r
203 const SColorf col(v[j].Color);
\r
204 writeColor(file, col);
\r
206 const core::vector2df uv1 = v[j].TCoords;
\r
207 writeVector2(file, uv1);
\r
208 if (texcoordsCount == 2)
\r
210 writeVector2(file, core::vector2df(0.f, 0.f));
\r
216 S3DVertex2TCoords *v = (S3DVertex2TCoords *) mb->getVertices();
\r
217 const SColorf col(v[j].Color);
\r
218 writeColor(file, col);
\r
220 const core::vector2df uv1 = v[j].TCoords;
\r
221 writeVector2(file, uv1);
\r
222 const core::vector2df uv2 = v[j].TCoords;
\r
223 writeVector2(file, uv2);
\r
228 S3DVertexTangents *v = (S3DVertexTangents *) mb->getVertices();
\r
229 const SColorf col(v[j].Color);
\r
230 writeColor(file, col);
\r
232 const core::vector2df uv1 = v[j].TCoords;
\r
233 writeVector2(file, uv1);
\r
234 if (texcoordsCount == 2)
\r
236 writeVector2(file, core::vector2df(0.f, 0.f));
\r
243 writeSizeFrom(file, verticesSizeAdress+4, verticesSizeAdress); // VERT chunk size
\r
246 u32 currentMeshBufferIndex = 0;
\r
248 for (u32 i = 0; i < numMeshBuffers; i++)
\r
250 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
251 file->write("TRIS", 4);
\r
252 const u32 trisSizeAdress = file->getPos();
\r
253 file->write(&trisSizeAdress, 4); // TRIS chunk size, updated later
\r
255 file->write(&i, 4);
\r
257 u32 numIndices = mb->getIndexCount();
\r
258 const u16 * const idx = (u16 *) mb->getIndices();
\r
259 for (u32 j = 0; j < numIndices; j += 3)
\r
261 u32 tmp = idx[j] + currentMeshBufferIndex;
\r
262 file->write(&tmp, sizeof(u32));
\r
264 tmp = idx[j + 1] + currentMeshBufferIndex;
\r
265 file->write(&tmp, sizeof(u32));
\r
267 tmp = idx[j + 2] + currentMeshBufferIndex;
\r
268 file->write(&tmp, sizeof(u32));
\r
270 writeSizeFrom(file, trisSizeAdress+4, trisSizeAdress); // TRIS chunk size
\r
272 currentMeshBufferIndex += mb->getVertexCount();
\r
274 writeSizeFrom(file, meshSizeAdress+4, meshSizeAdress); // MESH chunk size
\r
277 if(ISkinnedMesh *skinnedMesh = getSkinned(mesh))
\r
279 // Write animation data
\r
280 f32 animationSpeedMultiplier = 1.f;
\r
281 if (!skinnedMesh->isStatic())
\r
283 file->write("ANIM", 4);
\r
285 const u32 animsize = 12;
\r
286 file->write(&animsize, 4);
\r
288 const u32 flags = 0;
\r
289 f32 fps = skinnedMesh->getAnimationSpeed();
\r
291 /* 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
292 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
293 const int minimumAnimationFPS = 60;
\r
295 if (fps < minimumAnimationFPS)
\r
297 animationSpeedMultiplier = minimumAnimationFPS / fps;
\r
298 fps = minimumAnimationFPS;
\r
300 const u32 frames = static_cast<u32>(skinnedMesh->getFrameCount() * animationSpeedMultiplier);
\r
302 file->write(&flags, 4);
\r
303 file->write(&frames, 4);
\r
304 file->write(&fps, 4);
\r
308 core::array<ISkinnedMesh::SJoint*> rootJoints = getRootJoints(skinnedMesh);
\r
310 for (u32 i = 0; i < rootJoints.size(); i++)
\r
312 writeJointChunk(file, skinnedMesh, rootJoints[i], animationSpeedMultiplier);
\r
316 writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // Node chunk size
\r
317 writeSizeFrom(file, 8, 4); // BB3D chunk size
\r
324 void CB3DMeshWriter::writeJointChunk(io::IWriteFile* file, ISkinnedMesh* mesh, ISkinnedMesh::SJoint* joint, f32 animationSpeedMultiplier)
\r
327 file->write("NODE", 4);
\r
328 const u32 nodeSizeAdress = file->getPos();
\r
329 file->write(&nodeSizeAdress, 4);
\r
332 core::stringc name = joint->Name;
\r
333 file->write(name.c_str(), name.size());
\r
334 file->write("", 1);
\r
337 const core::vector3df pos = joint->Animatedposition;
\r
338 writeVector3(file, pos);
\r
341 core::vector3df scale = joint->Animatedscale;
\r
342 if (scale == core::vector3df(0, 0, 0))
\r
343 scale = core::vector3df(1, 1, 1);
\r
345 writeVector3(file, scale);
\r
348 const core::quaternion quat = joint->Animatedrotation;
\r
349 writeQuaternion(file, quat);
\r
352 file->write("BONE", 4);
\r
353 u32 bonesize = 8 * joint->Weights.size();
\r
354 file->write(&bonesize, 4);
\r
356 // Skinning ------------------
\r
357 for (u32 i = 0; i < joint->Weights.size(); i++)
\r
359 const u32 vertexID = joint->Weights[i].vertex_id;
\r
360 const u32 bufferID = joint->Weights[i].buffer_id;
\r
361 const f32 weight = joint->Weights[i].strength;
\r
363 u32 b3dVertexID = vertexID;
\r
364 for (u32 j = 0; j < bufferID; j++)
\r
366 b3dVertexID += mesh->getMeshBuffer(j)->getVertexCount();
\r
369 file->write(&b3dVertexID, 4);
\r
370 file->write(&weight, 4);
\r
372 // ---------------------------
\r
374 f32 floatBuffer[5];
\r
376 if (joint->PositionKeys.size())
\r
378 file->write("KEYS", 4);
\r
379 u32 keysSize = 4 * joint->PositionKeys.size() * 4; // X, Y and Z pos + frame
\r
380 keysSize += 4; // Flag to define the type of the key
\r
381 file->write(&keysSize, 4);
\r
383 u32 flag = 1; // 1 = flag for position keys
\r
384 file->write(&flag, 4);
\r
386 for (u32 i = 0; i < joint->PositionKeys.size(); i++)
\r
388 const s32 frame = static_cast<s32>(joint->PositionKeys[i].frame * animationSpeedMultiplier);
\r
389 file->write(&frame, 4);
\r
391 const core::vector3df pos = joint->PositionKeys[i].position;
\r
392 pos.getAs3Values(floatBuffer);
\r
393 file->write(floatBuffer, 12);
\r
396 if (joint->RotationKeys.size())
\r
398 file->write("KEYS", 4);
\r
399 u32 keysSize = 4 * joint->RotationKeys.size() * 5; // W, X, Y and Z rot + frame
\r
400 keysSize += 4; // Flag
\r
401 file->write(&keysSize, 4);
\r
404 file->write(&flag, 4);
\r
406 for (u32 i = 0; i < joint->RotationKeys.size(); i++)
\r
408 const s32 frame = static_cast<s32>(joint->RotationKeys[i].frame * animationSpeedMultiplier);
\r
409 const core::quaternion rot = joint->RotationKeys[i].rotation;
\r
411 memcpy(floatBuffer, &frame, 4);
\r
412 floatBuffer[1] = rot.W;
\r
413 floatBuffer[2] = rot.X;
\r
414 floatBuffer[3] = rot.Y;
\r
415 floatBuffer[4] = rot.Z;
\r
416 file->write(floatBuffer, 20);
\r
419 if (joint->ScaleKeys.size())
\r
421 file->write("KEYS", 4);
\r
422 u32 keysSize = 4 * joint->ScaleKeys.size() * 4; // X, Y and Z scale + frame
\r
423 keysSize += 4; // Flag
\r
424 file->write(&keysSize, 4);
\r
427 file->write(&flag, 4);
\r
429 for (u32 i = 0; i < joint->ScaleKeys.size(); i++)
\r
431 const s32 frame = static_cast<s32>(joint->ScaleKeys[i].frame * animationSpeedMultiplier);
\r
432 file->write(&frame, 4);
\r
434 const core::vector3df scale = joint->ScaleKeys[i].scale;
\r
435 scale.getAs3Values(floatBuffer);
\r
436 file->write(floatBuffer, 12);
\r
440 for (u32 i = 0; i < joint->Children.size(); i++)
\r
442 writeJointChunk(file, mesh, joint->Children[i], animationSpeedMultiplier);
\r
445 writeSizeFrom(file, nodeSizeAdress+4, nodeSizeAdress); // NODE chunk size
\r
449 ISkinnedMesh* CB3DMeshWriter::getSkinned (IMesh *mesh)
\r
451 if (mesh->getMeshType() == EAMT_SKINNED)
\r
453 return static_cast<ISkinnedMesh*>(mesh);
\r
458 core::array<ISkinnedMesh::SJoint*> CB3DMeshWriter::getRootJoints(const ISkinnedMesh* mesh)
\r
460 core::array<ISkinnedMesh::SJoint*> roots;
\r
462 core::array<ISkinnedMesh::SJoint*> allJoints = mesh->getAllJoints();
\r
463 for (u32 i = 0; i < allJoints.size(); i++)
\r
465 bool isRoot = true;
\r
466 ISkinnedMesh::SJoint* testedJoint = allJoints[i];
\r
467 for (u32 j = 0; j < allJoints.size(); j++)
\r
469 ISkinnedMesh::SJoint* testedJoint2 = allJoints[j];
\r
470 for (u32 k = 0; k < testedJoint2->Children.size(); k++)
\r
472 if (testedJoint == testedJoint2->Children[k])
\r
477 roots.push_back(testedJoint);
\r
483 u32 CB3DMeshWriter::getUVlayerCount(IMesh* mesh)
\r
485 const u32 numBeshBuffers = mesh->getMeshBufferCount();
\r
486 for (u32 i = 0; i < numBeshBuffers; i++)
\r
488 const IMeshBuffer * const mb = mesh->getMeshBuffer(i);
\r
490 if (mb->getVertexType() == EVT_2TCOORDS)
\r
498 void CB3DMeshWriter::writeVector2(io::IWriteFile* file, const core::vector2df& vec2)
\r
500 f32 buffer[2] = {vec2.X, vec2.Y};
\r
501 file->write(buffer, 8);
\r
504 void CB3DMeshWriter::writeVector3(io::IWriteFile* file, const core::vector3df& vec3)
\r
507 vec3.getAs3Values(buffer);
\r
508 file->write(buffer, 12);
\r
511 void CB3DMeshWriter::writeQuaternion(io::IWriteFile* file, const core::quaternion& quat)
\r
513 f32 buffer[4] = {quat.W, quat.X, quat.Y, quat.Z};
\r
514 file->write(buffer, 16);
\r
517 void CB3DMeshWriter::writeColor(io::IWriteFile* file, const video::SColorf& color)
\r
519 f32 buffer[4] = {color.r, color.g, color.b, color.a};
\r
520 file->write(buffer, 16);
\r
523 // Write the size from a given position to current position at a specific position in the file
\r
524 void CB3DMeshWriter::writeSizeFrom(io::IWriteFile* file, const u32 from, const u32 adressToWrite)
\r
526 const long back = file->getPos();
\r
527 file->seek(adressToWrite);
\r
528 const u32 sizeToWrite = back - from;
\r
529 file->write(&sizeToWrite, 4);
\r
536 #endif // _IRR_COMPILE_WITH_B3D_WRITER_
\r