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