OpenClonk
StdMeshLoaderBinary.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2010-2016, The OpenClonk Team and contributors
5  *
6  * Distributed under the terms of the ISC license; see accompanying file
7  * "COPYING" for details.
8  *
9  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10  * See accompanying file "TRADEMARK" for details.
11  *
12  * To redistribute this file separately, substitute the full license texts
13  * for the above references.
14  */
15 
16 // A loader for the OGRE .mesh binary file format
17 
18 #include "C4Include.h"
19 #include "lib/StdMesh.h"
20 #include "lib/StdMeshLoader.h"
23 #include "lib/StdMeshMaterial.h"
24 
25 namespace
26 {
28 
29  bool VertexDeclarationIsSane(const unique_ptr_vector<Ogre::Mesh::ChunkGeometryVertexDeclElement> &decl, const char *filename)
30  {
31  bool semanticSeen[Ogre::Mesh::ChunkGeometryVertexDeclElement::VDES_MAX + 1] = { false };
32  for(auto& element: decl)
33  {
34  switch (element->semantic)
35  {
37  // FIXME: The Ogre format supports denoting multiple texture coordinates, but the rendering code only supports one
38  // currently only the first set is read, any additional ones are ignored
39  break;
44  // Only one set of each of these elements allowed
45  if (semanticSeen[element->semantic])
46  return false;
47  break;
48  default:
49  // We ignore unhandled element semantics.
50  break;
51  }
52  semanticSeen[element->semantic] = true;
53  }
54  return true;
55  }
56 
57  template<size_t N>
58  void ReadNormalizedVertexData(float (&dest)[N], const char *source, Ogre::Mesh::ChunkGeometryVertexDeclElement::Type vdet)
59  {
60  static_assert(N >= 4, "");
61  dest[0] = dest[1] = dest[2] = 0; dest[3] = 1;
62  switch (vdet)
63  {
64  // All VDET_Float* fall through.
66  dest[3] = *reinterpret_cast<const float*>(source + sizeof(float) * 3);
68  dest[2] = *reinterpret_cast<const float*>(source + sizeof(float) * 2);
70  dest[1] = *reinterpret_cast<const float*>(source + sizeof(float) * 1);
72  dest[0] = *reinterpret_cast<const float*>(source + sizeof(float) * 0);
73  break;
75  dest[3] = source[0] / 255.0f;
76  for (int i = 0; i < 3; ++i)
77  dest[i] = source[3 - i] / 255.0f;
78  break;
80  dest[3] = source[0] / 255.0f;
81  for (int i = 0; i < 3; ++i)
82  dest[i] = source[i + 1] / 255.0f;
83  break;
84  default:
85  assert(!"Unexpected enum value");
86  break;
87  }
88  }
89 
90  std::vector<StdSubMesh::Vertex> ReadSubmeshGeometry(const Ogre::Mesh::ChunkGeometry &geo, const char *filename)
91  {
92  if (!VertexDeclarationIsSane(geo.vertexDeclaration, filename))
93  throw Ogre::Mesh::InvalidVertexDeclaration();
94 
95  // Get maximum size of a vertex according to the declaration
96  std::map<int, size_t> max_offset;
97  for(const auto &el: geo.vertexDeclaration)
98  {
99  size_t elsize = 0;
100  switch (el->type)
101  {
102  case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float1: elsize = sizeof(float) * 1; break;
103  case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float2: elsize = sizeof(float) * 2; break;
104  case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float3: elsize = sizeof(float) * 3; break;
105  case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Float4: elsize = sizeof(float) * 4; break;
107  case Ogre::Mesh::ChunkGeometryVertexDeclElement::VDET_Color_ARGB: elsize = sizeof(uint8_t) * 4; break;
108  default: assert(!"Unexpected enum value"); break;
109  }
110  max_offset[el->source] = std::max<size_t>(max_offset[el->source], el->offset + elsize);
111  }
112 
113  // Generate array of vertex buffer cursors
114  std::map<int, const char *> cursors;
115  for(const auto &buf: geo.vertexBuffers)
116  {
117  if (cursors.find(buf->index) != cursors.end())
118  throw Ogre::MultipleSingletonChunks("Multiple vertex buffers were bound to the same stream");
119  cursors[buf->index] = static_cast<const char *>(buf->data->data);
120  // Check that the vertices don't overlap
121  if (buf->vertexSize < max_offset[buf->index])
122  throw Ogre::InsufficientData("Vertices overlapping");
123  // Check that the vertex buffer has enough room for all vertices
124  if (buf->GetSize() < (geo.vertexCount - 1) * buf->vertexSize + max_offset[buf->index])
125  throw Ogre::InsufficientData("Vertex buffer too small");
126  max_offset.erase(buf->index);
127  }
128 
129  if (!max_offset.empty())
130  throw Ogre::InsufficientData("A vertex element references an unbound stream");
131 
132  // Generate vertices
133  std::vector<StdSubMesh::Vertex> vertices;
134  vertices.reserve(geo.vertexCount);
135  for (size_t i = 0; i < geo.vertexCount; ++i)
136  {
137  StdSubMesh::Vertex vertex;
138  vertex.nx = vertex.ny = vertex.nz = 0;
139  vertex.x = vertex.y = vertex.z = 0;
140  vertex.u = vertex.v = 0;
141  bool read_tex = false;
142  // Read vertex declaration
143  for(const auto& element: geo.vertexDeclaration)
144  {
145  float values[4];
146  ReadNormalizedVertexData(values, cursors[element->source] + element->offset, element->type);
147  switch (element->semantic)
148  {
150  vertex.x = values[0];
151  vertex.y = values[1];
152  vertex.z = values[2];
153  break;
155  vertex.nx = values[0];
156  vertex.ny = values[1];
157  vertex.nz = values[2];
158  break;
160  if (!read_tex) {
161  vertex.u = values[0];
162  vertex.v = values[1];
163  read_tex = true;
164  }
165  break;
166  default:
167  // We ignore unhandled element semantics.
168  break;
169  }
170  }
171  vertices.push_back(OgreToClonk::TransformVertex(vertex));
172  // Advance vertex buffer cursors
173  for(const std::unique_ptr<Ogre::Mesh::ChunkGeometryVertexBuffer> &buf: geo.vertexBuffers)
174  cursors[buf->index] += buf->vertexSize;
175  }
176 
177  return vertices;
178  }
179 }
180 
181 void StdMeshSkeletonLoader::StoreSkeleton(const char* groupname, const char* filename, std::shared_ptr<StdMeshSkeleton> skeleton)
182 {
183  assert(groupname != nullptr);
184  assert(filename != nullptr);
185  assert(skeleton != nullptr);
186 
187  // Create mirrored animations (#401)
188  // this is still going to be somewhere else, but for now it will keep moving around
189  skeleton->PostInit();
190 
191 
192  // save in map
193  StdCopyStrBuf filepath;
194  MakeFullSkeletonPath(filepath, groupname, filename);
195 
196  AddSkeleton(filepath, skeleton);
197 
198  // memorize which skeletons can be appended
199  // skins get broken down to their original definition, which is a little messy at the moment.
200  StdCopyStrBuf buf_filename(GetFilenameOnly(filename));
201  StdCopyStrBuf command_with_definition(GetFilenameOnly(buf_filename.getData())); // include.Clonk becomes include, include.Clonk.Farmer becomes include.Clonk
202  StdCopyStrBuf command(GetFilenameOnly(command_with_definition.getData())); // include stays include, include.Clonk becomes include
203  StdCopyStrBuf definition(GetExtension(buf_filename.getData())); // include.Clonk becomes Clonk, include.Clonk.Farmer becomes Farmer
204 
205  if (!(command_with_definition == command)) // include.Clonk != include?
206  {
207  definition = StdCopyStrBuf(GetExtension(command_with_definition.getData())); // change definition to the part behind the .: Clonk
208  }
209 
210  const char* appendto = "appendto"; // has to be a constant
211  const char* include = "include"; // dito
212 
213  // check where to store
214  if (command == appendto)
215  {
216  AppendtoSkeletons.insert(std::make_pair(filepath, definition));
217  }
218  else if (command == include)
219  {
220  IncludeSkeletons.insert(std::make_pair(filepath, definition));
221  }
222 }
223 
225 {
226  // DebugLogF("Removing skeletons in group: %s", groupname);
227 
228  std::vector<StdCopyStrBuf> delete_skeletons;
229 
230  std::map<StdCopyStrBuf, std::shared_ptr<StdMeshSkeleton>>::iterator it;
231  for (it = Skeletons.begin(); it != Skeletons.end(); it++)
232  {
233  StdCopyStrBuf skeletonpath(it->first.getData());
234  StdCopyStrBuf group(groupname);
235  group.ToLowerCase();
236 
237  StdCopyStrBuf skeletongroup;
238  GetParentPath(skeletonpath.getData(), &skeletongroup);
239 
240  if (skeletongroup == group)
241  {
242  // DebugLogF("Found skeleton in group: %s", it->first.getData());
243 
244  delete_skeletons.push_back(skeletonpath);
245  }
246  }
247 
248  for (const auto & delete_skeleton : delete_skeletons)
249  {
250  RemoveSkeleton(delete_skeleton);
251  }
252 }
253 
254 void StdMeshSkeletonLoader::RemoveSkeleton(const char* groupname, const char* filename)
255 {
256  StdCopyStrBuf filepath;
257  MakeFullSkeletonPath(filepath, groupname, filename);
258  RemoveSkeleton(filepath);
259 }
260 
262 {
263  std::map<StdCopyStrBuf, std::shared_ptr<StdMeshSkeleton>>::iterator existing_skeleton = Skeletons.find(filepath);
264  if (existing_skeleton != Skeletons.end())
265  {
266  Skeletons.erase(existing_skeleton);
267  }
268 
269  std::map<StdCopyStrBuf, StdCopyStrBuf>::iterator appendto_skeleton = AppendtoSkeletons.find(filepath);
270  if (appendto_skeleton != AppendtoSkeletons.end())
271  {
272  AppendtoSkeletons.erase(appendto_skeleton);
273  }
274 
275  std::map<StdCopyStrBuf, StdCopyStrBuf>::iterator include_skeleton = IncludeSkeletons.find(filepath);
276  if (include_skeleton != IncludeSkeletons.end())
277  {
278  IncludeSkeletons.erase(include_skeleton);
279  }
280 }
281 
282 void StdMeshSkeletonLoader::AddSkeleton(const StdCopyStrBuf& filepath, std::shared_ptr<StdMeshSkeleton> skeleton)
283 {
284  std::pair<StdCopyStrBuf, std::shared_ptr<StdMeshSkeleton>> key_and_value = std::make_pair(filepath, skeleton);
285  std::pair<std::map<StdCopyStrBuf, std::shared_ptr<StdMeshSkeleton>>::iterator, bool> insert = Skeletons.insert(key_and_value);
286 
287  if (insert.second == false)
288  {
289  LogF("WARNING: Overloading skeleton %s", filepath.getData());
290 
291  Skeletons[filepath] = skeleton;
292  }
293 }
294 
295 std::shared_ptr<StdMeshSkeleton> StdMeshSkeletonLoader::GetSkeletonByName(const StdStrBuf& name) const
296 {
297  StdCopyStrBuf filename(name);
298 
299  std::map<StdCopyStrBuf, std::shared_ptr<StdMeshSkeleton>>::const_iterator iter = Skeletons.find(filename);
300  if (iter == Skeletons.end()) return nullptr;
301  return iter->second;
302 }
303 
304 void StdMeshSkeletonLoader::LoadSkeletonBinary(const char* groupname, const char* filename, const char *sourcefile, size_t size)
305 {
306  std::unique_ptr<Ogre::Skeleton::Chunk> chunk;
307  Ogre::DataStream stream(sourcefile, size);
308 
309  std::shared_ptr<StdMeshSkeleton> Skeleton(new StdMeshSkeleton);
310 
311  // First chunk must be the header
312  chunk = Ogre::Skeleton::Chunk::Read(&stream);
313  if (chunk->GetType() != Ogre::Skeleton::CID_Header)
314  throw Ogre::Skeleton::InvalidVersion();
315 
316  std::map<uint16_t, std::unique_ptr<StdMeshBone>> bones;
317  unique_ptr_vector<Ogre::Skeleton::ChunkAnimation> animations;
320  id = Ogre::Skeleton::Chunk::Peek(&stream)
321  )
322  {
323  std::unique_ptr<Ogre::Skeleton::Chunk> chunk(Ogre::Skeleton::Chunk::Read(&stream));
324  switch (chunk->GetType())
325  {
327  {
328  Ogre::Skeleton::ChunkBlendMode& cblend = *static_cast<Ogre::Skeleton::ChunkBlendMode*>(chunk.get());
329  // TODO: Handle it
330  if (cblend.blend_mode != 0) // 0 is average, 1 is cumulative. I'm actually not sure what the difference really is... anyway we implement only one method yet. I think it's average, but not 100% sure.
331  LogF("StdMeshLoader: CID_BlendMode not implemented.");
332  }
333  break;
335  {
336  Ogre::Skeleton::ChunkBone &cbone = *static_cast<Ogre::Skeleton::ChunkBone*>(chunk.get());
337  // Check that the bone ID is unique
338  if (bones.find(cbone.handle) != bones.end())
339  throw Ogre::Skeleton::IdNotUnique();
340  auto bone = std::make_unique<StdMeshBone>();
341  bone->Parent = nullptr;
342  bone->ID = cbone.handle;
343  bone->Name = cbone.name.c_str();
344  bone->Transformation.translate = cbone.position;
345  bone->Transformation.rotate = cbone.orientation;
346  bone->Transformation.scale = cbone.scale;
347  bone->InverseTransformation = StdMeshTransformation::Inverse(bone->Transformation);
348  bones.insert(std::make_pair(cbone.handle, std::move(bone)));
349  }
350  break;
352  {
353  Ogre::Skeleton::ChunkBoneParent &cbparent = *static_cast<Ogre::Skeleton::ChunkBoneParent*>(chunk.get());
354  if (bones.find(cbparent.parentHandle) == bones.end() || bones.find(cbparent.childHandle) == bones.end())
355  throw Ogre::Skeleton::BoneNotFound();
356  bones[cbparent.parentHandle]->Children.push_back(bones[cbparent.childHandle].get());
357  bones[cbparent.childHandle]->Parent = bones[cbparent.parentHandle].get();
358  }
359  break;
361  // Collect animations for later (need bone table index, which we don't know yet)
362  animations.emplace_back(static_cast<Ogre::Skeleton::ChunkAnimation*>(chunk.release()));
363  break;
364  default:
365  assert(!"Unexpected enum value");
366  break;
367  }
368  if (stream.AtEof()) break;
369  }
370 
371  // Find master bone (i.e., the one without a parent)
372  StdMeshBone *master = nullptr;
373  for (auto& b: bones)
374  {
375  if (!b.second->Parent)
376  {
377  master = b.second.get();
378  Skeleton->AddMasterBone(master);
379  }
380  }
381  if (!master)
382  throw Ogre::Skeleton::MissingMasterBone();
383 
384  // Transfer bone ownership to mesh
385  for (auto& b: bones) b.second.release();
386  bones.clear();
387 
388  // Build handle->index quick access table
389  std::map<uint16_t, size_t> handle_lookup;
390  for (size_t i = 0; i < Skeleton->GetNumBones(); ++i)
391  {
392  handle_lookup[Skeleton->GetBone(i).ID] = i;
393  }
394 
395  // Fixup animations
396  for(auto &canim: animations)
397  {
398  StdMeshAnimation &anim = Skeleton->Animations[StdCopyStrBuf(canim->name.c_str())];
399  anim.Name = canim->name.c_str();
400  anim.Length = canim->duration;
401  anim.Tracks.resize(Skeleton->GetNumBones());
402  anim.OriginSkeleton = &(*Skeleton);
403 
404  for(auto &catrack: canim->tracks)
405  {
406  const StdMeshBone &bone = Skeleton->GetBone(handle_lookup[catrack->bone]);
407  StdMeshTrack *&track = anim.Tracks[bone.Index];
408  if (track != nullptr)
409  throw Ogre::Skeleton::MultipleBoneTracks();
410  track = new StdMeshTrack;
411  for(auto &catkf: catrack->keyframes)
412  {
413  StdMeshKeyFrame &kf = track->Frames[catkf->time];
414  kf.Transformation.rotate = catkf->rotation;
415  kf.Transformation.scale = catkf->scale;
416  kf.Transformation.translate = bone.InverseTransformation.rotate * (bone.InverseTransformation.scale * catkf->translation);
418  }
419  }
420  }
421 
422  // Fixup bone transforms
423  for(StdMeshBone *bone: Skeleton->Bones)
424  {
425  if (bone->Parent)
427  else
429 
431  }
432 
433  StoreSkeleton(groupname, filename, Skeleton);
434 }
435 
436 StdMesh *StdMeshLoader::LoadMeshBinary(const char *sourcefile, size_t length, const StdMeshMatManager &mat_mgr, StdMeshSkeletonLoader &loader, const char *filename)
437 {
438  std::unique_ptr<Ogre::Mesh::Chunk> root;
439  Ogre::DataStream stream(sourcefile, length);
440 
441  // First chunk must be the header
442  root = Ogre::Mesh::Chunk::Read(&stream);
443  if (root->GetType() != Ogre::Mesh::CID_Header)
444  throw Ogre::Mesh::InvalidVersion();
445 
446  // Second chunk is the mesh itself
447  root = Ogre::Mesh::Chunk::Read(&stream);
448  if (root->GetType() != Ogre::Mesh::CID_Mesh)
449  throw Ogre::Mesh::InvalidVersion();
450 
451  // Generate mesh from data
452  Ogre::Mesh::ChunkMesh &cmesh = *static_cast<Ogre::Mesh::ChunkMesh*>(root.get());
453  std::unique_ptr<StdMesh> mesh(new StdMesh);
454 
455  // if the mesh has a skeleton, then try loading
456  // it from the loader by the definition name
457  if (!cmesh.skeletonFile.empty())
458  {
459  StdCopyStrBuf skeleton_filename = StdCopyStrBuf();
460  StdMeshSkeletonLoader::MakeFullSkeletonPath(skeleton_filename, filename, cmesh.skeletonFile.c_str());
461 
462  mesh->Skeleton = loader.GetSkeletonByName(skeleton_filename);
463 
464  // with this exception the assert below is useless
465  // also, I think the bone_lookup should only be used if there is a skeleton anyway
466  // so there could be meshes without bones even?
467  if (mesh->Skeleton == nullptr)
468  {
469  StdCopyStrBuf exception("The specified skeleton file was not found: ");
470  exception.Append(skeleton_filename.getData());
471  throw Ogre::InsufficientData(exception.getData());
472  }
473  }
474 
475  assert(mesh->Skeleton != nullptr); // the bone assignments could instead be added only, if there is a skeleton
476 
477  // Build bone handle->index quick access table
478  std::map<uint16_t, size_t> bone_lookup;
479  for (size_t i = 0; i < mesh->GetSkeleton().GetNumBones(); ++i)
480  {
481  bone_lookup[mesh->GetSkeleton().GetBone(i).ID] = i;
482  }
483 
484  // Read submeshes
485  mesh->SubMeshes.reserve(cmesh.submeshes.size());
486  for (size_t i = 0; i < cmesh.submeshes.size(); ++i)
487  {
488  mesh->SubMeshes.push_back(StdSubMesh());
489  StdSubMesh &sm = mesh->SubMeshes.back();
490  Ogre::Mesh::ChunkSubmesh &csm = *cmesh.submeshes[i];
491  sm.Material = mat_mgr.GetMaterial(csm.material.c_str());
492  if (!sm.Material)
493  throw Ogre::Mesh::InvalidMaterial();
494  if (csm.operation != Ogre::Mesh::ChunkSubmesh::SO_TriList)
495  throw Ogre::Mesh::NotImplemented("Submesh operations other than TriList aren't implemented yet");
496  sm.Faces.resize(csm.faceVertices.size() / 3);
497  for (size_t face = 0; face < sm.Faces.size(); ++face)
498  {
499  sm.Faces[face].Vertices[0] = csm.faceVertices[face * 3 + 0];
500  sm.Faces[face].Vertices[1] = csm.faceVertices[face * 3 + 1];
501  sm.Faces[face].Vertices[2] = csm.faceVertices[face * 3 + 2];
502  }
503  Ogre::Mesh::ChunkGeometry &geo = *(csm.hasSharedVertices ? cmesh.geometry : csm.geometry);
504  sm.Vertices = ReadSubmeshGeometry(geo, filename);
505 
506  // Read bone assignments
507  std::vector<Ogre::Mesh::BoneAssignment> &boneAssignments = (csm.hasSharedVertices ? cmesh.boneAssignments : csm.boneAssignments);
508  assert(!csm.hasSharedVertices || csm.boneAssignments.empty());
509  for(const auto &ba : boneAssignments)
510  {
511  if (ba.vertex >= sm.GetNumVertices())
512  throw Ogre::Mesh::VertexNotFound();
513  if (bone_lookup.find(ba.bone) == bone_lookup.end())
514  throw Ogre::Skeleton::BoneNotFound();
515  size_t bone_index = bone_lookup[ba.bone];
516  // Check quickly if all weight slots are used
517  StdSubMesh::Vertex &vertex = sm.Vertices[ba.vertex];
518  if (vertex.bone_weight[StdMeshVertex::MaxBoneWeightCount - 1] != 0)
519  {
520  throw Ogre::Mesh::NotImplemented("Vertex is influenced by too many bones");
521  }
522  for (size_t weight_index = 0; weight_index < StdMeshVertex::MaxBoneWeightCount; ++weight_index)
523  {
524  if (vertex.bone_weight[weight_index] == 0)
525  {
526  vertex.bone_weight[weight_index] = ba.weight;
527  vertex.bone_index[weight_index] = bone_index;
528  break;
529  }
530  }
531  }
532 
533  // Normalize bone assignments
534  for(StdSubMesh::Vertex &vertex : sm.Vertices)
535  {
536  float sum = 0;
537  for (float weight : vertex.bone_weight)
538  sum += weight;
539  if (sum != 0)
540  for (float &weight : vertex.bone_weight)
541  weight /= sum;
542  else
543  vertex.bone_weight[0] = 1.0f;
544  }
545  }
546 
547  // Construct bounding box. Don't use bounds and radius from cmesh
548  // because they are in a different coordinate frame.
549  //mesh->BoundingBox = cmesh.bounds;
550  //mesh->BoundingRadius = cmesh.radius;
551 
552  bool first = true;
553  for (unsigned int i = 0; i < mesh->SubMeshes.size() + 1; ++i)
554  {
555  const std::vector<StdSubMesh::Vertex>* vertices = nullptr;
556  if (i < mesh->SubMeshes.size())
557  vertices = &mesh->SubMeshes[i].Vertices;
558  else
559  vertices = &mesh->SharedVertices;
560 
561  for (const auto & vertex : *vertices)
562  {
563  const float d = std::sqrt(vertex.x*vertex.x
564  + vertex.y*vertex.y
565  + vertex.z*vertex.z);
566 
567  // First vertex
568  if (first)
569  {
570  mesh->BoundingBox.x1 = mesh->BoundingBox.x2 = vertex.x;
571  mesh->BoundingBox.y1 = mesh->BoundingBox.y2 = vertex.y;
572  mesh->BoundingBox.z1 = mesh->BoundingBox.z2 = vertex.z;
573  mesh->BoundingRadius = d;
574  first = false;
575  }
576  else
577  {
578  mesh->BoundingBox.x1 = std::min(vertex.x, mesh->BoundingBox.x1);
579  mesh->BoundingBox.x2 = std::max(vertex.x, mesh->BoundingBox.x2);
580  mesh->BoundingBox.y1 = std::min(vertex.y, mesh->BoundingBox.y1);
581  mesh->BoundingBox.y2 = std::max(vertex.y, mesh->BoundingBox.y2);
582  mesh->BoundingBox.z1 = std::min(vertex.z, mesh->BoundingBox.z1);
583  mesh->BoundingBox.z2 = std::max(vertex.z, mesh->BoundingBox.z2);
584  mesh->BoundingRadius = std::max(mesh->BoundingRadius, d);
585  }
586  }
587  }
588 
589  // We allow bounding box to be empty if it's only due to Z direction since
590  // this is what goes inside the screen in Clonk.
591  if(mesh->BoundingBox.x1 == mesh->BoundingBox.x2 || mesh->BoundingBox.y1 == mesh->BoundingBox.y2)
592  throw Ogre::Mesh::EmptyBoundingBox();
593 
594  return mesh.release();
595 }
596 
598 {
599  DoResetSkeletons();
600  DoAppendSkeletons();
601  DoIncludeSkeletons();
602 }
603 
604 void StdMeshSkeletonLoader::DoResetSkeletons()
605 {
606  std::map<StdCopyStrBuf, std::shared_ptr<StdMeshSkeleton>>::iterator it;
607  for (it = Skeletons.begin(); it != Skeletons.end(); it++)
608  {
609  std::shared_ptr<StdMeshSkeleton> skeleton = it->second;
610 
611  // remove animations from destination
612  std::map<StdCopyStrBuf, StdMeshAnimation>::const_iterator animations = skeleton->Animations.begin();
613 
614  while (animations != skeleton->Animations.end())
615  {
616  if (animations->second.OriginSkeleton != &(*(skeleton)))
617  {
618  animations = skeleton->Animations.erase(animations);
619  }
620  else
621  {
622  ++animations;
623  }
624  }
625  }
626 }
627 
628 void StdMeshSkeletonLoader::DoAppendSkeletons()
629 {
630  // handle the "appendto.<C4ID>.skeleton" files.
631 
632  std::map<StdCopyStrBuf, StdCopyStrBuf>::iterator it;
633  for (it = AppendtoSkeletons.begin(); it != AppendtoSkeletons.end(); it++)
634  {
635  StdCopyStrBuf id(it->second);
636 
637  StdMeshSkeleton* destination = GetSkeletonByDefinition(id.getData());
638 
639  // append animations, if the definition has a mesh
640  if (destination == nullptr)
641  {
642  // Note that GetSkeletonByDefinition logs already why
643  // the skeleton does not exist.
644  LogF("WARNING: Appending skeleton '%s' failed", it->first.getData());
645  }
646  else
647  {
648  std::shared_ptr<StdMeshSkeleton> source = GetSkeletonByName(it->first);
649 
650  std::map<StdCopyStrBuf, StdMeshAnimation>::const_iterator animations;
651 
652  // append animations from source
653  for (animations = source->Animations.begin(); animations != source->Animations.end(); animations++)
654  {
655  if (destination->Animations.find(animations->first) != destination->Animations.end())
656  {
657  LogF("WARNING: Overloading animation '%s' is not allowed. This animation already exists in '%s'.", animations->first.getData(), id.getData());
658  }
659  else
660  {
661  destination->InsertAnimation(*source, animations->second);
662  }
663  }
664  }
665  }
666 }
667 
668 void StdMeshSkeletonLoader::DoIncludeSkeletons()
669 {
670  // handle the "include.<C4ID>.skeleton" files.
671 
672  std::map<StdCopyStrBuf, StdCopyStrBuf>::iterator it;
673  for (it = IncludeSkeletons.begin(); it != IncludeSkeletons.end(); it++)
674  {
675  StdCopyStrBuf id(it->second);
676 
677  StdMeshSkeleton* source = GetSkeletonByDefinition(id.getData());
678 
679  // append animations, if the definition has a mesh
680  if (source == nullptr)
681  {
682  // Note that GetSkeletonByDefinition logs already why
683  // the skeleton does not exist.
684  LogF("WARNING: Including skeleton '%s' failed", it->first.getData());
685  }
686  else
687  {
688  std::shared_ptr<StdMeshSkeleton> destination = GetSkeletonByName(it->first);
689 
690  std::map<StdCopyStrBuf, StdMeshAnimation>::const_iterator animations;
691 
692  // append animations from source
693  for (animations = source->Animations.begin(); animations != source->Animations.end(); animations++)
694  {
695  if (destination->Animations.find(animations->first) != destination->Animations.end())
696  {
697  LogF("WARNING: Animation '%s' from %s is not included. A newer version of the animation exists in the destination file.", animations->first.getData(), id.getData());
698  }
699  else
700  {
701  destination->InsertAnimation(*source, animations->second);
702  }
703  }
704  }
705  }
706 }
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
#define b
char * GetExtension(char *szFilename)
Definition: StdFile.cpp:118
bool GetParentPath(const char *szFilename, char *szBuffer)
Definition: StdFile.cpp:186
const char * GetFilenameOnly(const char *strFilename)
Definition: StdFile.cpp:57
StdMeshTransformation Transformation
Definition: StdMesh.h:68
static Type Peek(const DataStream *stream)
unique_ptr_vector< ChunkGeometryVertexDeclElement > vertexDeclaration
unique_ptr_vector< ChunkGeometryVertexBuffer > vertexBuffers
static std::unique_ptr< Chunk > Read(DataStream *stream)
std::unique_ptr< ChunkGeometry > geometry
std::vector< BoneAssignment > boneAssignments
unique_ptr_vector< ChunkSubmesh > submeshes
std::vector< size_t > faceVertices
std::unique_ptr< ChunkGeometry > geometry
enum Ogre::Mesh::ChunkSubmesh::SubmeshOperation SO_TriList
std::vector< BoneAssignment > boneAssignments
static std::unique_ptr< Chunk > Read(DataStream *stream)
StdCopyStrBuf Name
Definition: StdMesh.h:97
float Length
Definition: StdMesh.h:98
StdMeshTransformation InverseTransformation
Definition: StdMesh.h:39
StdMeshTransformation Transformation
Definition: StdMesh.h:37
unsigned int Index
Definition: StdMesh.h:32
static StdMesh * LoadMeshBinary(const char *sourcefile, size_t size, const StdMeshMatManager &mat_mgr, StdMeshSkeletonLoader &loader, const char *filename=nullptr)
const StdMeshMaterial * GetMaterial(const char *material_name) const
void InsertAnimation(const StdMeshAnimation &animation)
Definition: StdMesh.cpp:473
virtual StdMeshSkeleton * GetSkeletonByDefinition(const char *definition) const =0
void LoadSkeletonBinary(const char *groupname, const char *filename, const char *sourcefile, size_t size)
static void MakeFullSkeletonPath(StdCopyStrBuf &out, const char *groupname, const char *filename)
Definition: StdMeshLoader.h:66
void StoreSkeleton(const char *groupname, const char *filename, std::shared_ptr< StdMeshSkeleton > skeleton)
std::shared_ptr< StdMeshSkeleton > GetSkeletonByName(const StdStrBuf &name) const
void RemoveSkeletonsInGroup(const char *groupname)
void RemoveSkeleton(const StdCopyStrBuf &filepath)
void ToLowerCase()
Definition: StdBuf.cpp:385
const char * getData() const
Definition: StdBuf.h:442
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
size_t GetNumVertices() const
Definition: StdMesh.h:165
std::vector< std::unique_ptr< T > > unique_ptr_vector
StdSubMesh::Vertex TransformVertex(const StdSubMesh::Vertex &vertex)
StdMeshTransformation TransformTransformation(const StdMeshTransformation &trans)
StdMeshVector translate
Definition: StdMeshMath.h:76
static StdMeshTransformation Inverse(const StdMeshTransformation &transform)
StdMeshQuaternion rotate
Definition: StdMeshMath.h:75
StdMeshVector scale
Definition: StdMeshMath.h:74
uint16_t bone_index[MaxBoneWeightCount]
Definition: StdMeshMath.h:45
static const size_t MaxBoneWeightCount
Definition: StdMeshMath.h:37
float bone_weight[MaxBoneWeightCount]
Definition: StdMeshMath.h:44