TDME2  1.9.200
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
DAEReader.cpp
Go to the documentation of this file.
2 
3 #include <map>
4 #include <memory>
5 #include <string>
6 #include <unordered_set>
7 #include <vector>
8 
9 #include <tdme/tdme.h>
14 #include <tdme/engine/Color4.h>
15 #include <tdme/engine/model/Face.h>
21 #include <tdme/engine/model/Node.h>
28 #include <tdme/engine/Texture.h>
29 #include <tdme/engine/Transform.h>
30 #include <tdme/math/Math.h>
31 #include <tdme/math/Matrix4x4.h>
32 #include <tdme/math/Vector3.h>
36 #include <tdme/utilities/Console.h>
38 #include <tdme/utilities/Float.h>
39 #include <tdme/utilities/Integer.h>
43 
44 #include <ext/tinyxml/tinyxml.h>
45 
46 #define AVOID_NULLPTR_STRING(arg) (arg == nullptr?"":arg)
47 
48 using std::array;
49 using std::make_unique;
50 using std::map;
51 using std::string;
52 using std::to_string;
53 using std::unordered_set;
54 using std::vector;
55 
76 using tdme::math::Math;
89 
93 
94 const Color4 DAEReader::BLENDER_AMBIENT_NONE(0.0f, 0.0f, 0.0f, 1.0f);
95 
96 Model* DAEReader::read(const string& pathName, const string& fileName, bool useBC7TextureCompression)
97 {
98  // load dae xml document
99  auto xmlContent = FileSystem::getInstance()->getContentAsString(pathName, fileName);
100  TiXmlDocument xmlDocument;
101  xmlDocument.Parse(xmlContent.c_str());
102  if (xmlDocument.Error() == true) {
103  throw ModelFileIOException(
104  string("Could not parse XML. Error='") + string(xmlDocument.ErrorDesc()) + string("'")
105  );
106  }
107  TiXmlElement* xmlRoot = xmlDocument.RootElement();
108 
109  // authoring tool
110  auto authoringTool = getAuthoringTool(xmlRoot);
111 
112  // up vector and rotation order
113  auto upVector = getUpVector(xmlRoot);
114  RotationOrder* rotationOrder = nullptr;
115  if (upVector == UpVector::Y_UP) {
116  rotationOrder = RotationOrder::ZYX;
117  } else
118  if (upVector == UpVector::Z_UP) {
119  rotationOrder = RotationOrder::XYZ;
120  }
121 
122  // create model
123  auto model = make_unique<Model>(
124  fileName,
125  fileName,
126  upVector,
127  rotationOrder,
128  nullptr,
129  authoringTool
130  );
131 
132  // import matrix
133  setupModelImportRotationMatrix(xmlRoot, model.get());
134  setupModelImportScaleMatrix(xmlRoot, model.get());
135 
136  // parse scene from xml
137  string xmlSceneId;
138  auto xmlScene = getChildrenByTagName(xmlRoot, "scene").at(0);
139  for (auto xmlInstanceVisualscene: getChildrenByTagName(xmlScene, "instance_visual_scene")) {
140  xmlSceneId = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlInstanceVisualscene->Attribute("url"))), 1);
141  }
142 
143  // parse visual scenes
144  auto xmlLibraryVisualScenes = getChildrenByTagName(xmlRoot, "library_visual_scenes").at(0);
145  for (auto xmlLibraryVisualScene: getChildrenByTagName(xmlLibraryVisualScenes, "visual_scene")) {
146  auto xmlVisualSceneId = string(AVOID_NULLPTR_STRING(xmlLibraryVisualScene->Attribute("id")));
147  if (xmlVisualSceneId == xmlSceneId) {
148  // default FPS
149  auto fps = 30.0f;
150  // parse frames per second
151  auto xmlExtraNodes = getChildrenByTagName(xmlLibraryVisualScene, "extra");
152  if (xmlExtraNodes.empty() == false) {
153  auto xmlExtraNode = xmlExtraNodes.at(0);
154  for (auto xmlTechnique: getChildrenByTagName(xmlExtraNode, "technique")) {
155  auto xmlFrameRateNodes = getChildrenByTagName(xmlTechnique, "frame_rate");
156  if (xmlFrameRateNodes.empty() == false) {
157  fps = Float::parse(string(AVOID_NULLPTR_STRING(xmlFrameRateNodes.at(0)->GetText())));
158  break;
159  }
160  }
161  }
162  // set up frames per seconds
163  model->setFPS(fps);
164  // visual scene root nodes
165  for (auto xmlNode: getChildrenByTagName(xmlLibraryVisualScene, "node")) {
166  auto node = readVisualSceneNode(pathName, model.get(), nullptr, xmlRoot, xmlNode, fps, useBC7TextureCompression);
167  if (node != nullptr) {
168  model->getSubNodes()[node->getId()] = node;
169  model->getNodes()[node->getId()] = node;
170  }
171  }
172  }
173  }
174  if (ModelTools::hasDefaultAnimation(model.get()) == false) ModelTools::createDefaultAnimation(model.get(), 0);
175  // set up joints
176  ModelTools::setupJoints(model.get());
177  // fix animation length
178  ModelTools::fixAnimationLength(model.get());
179  // prepare for indexed rendering
180  ModelTools::prepareForIndexedRendering(model.get());
181  //
182  return model.release();
183 }
184 
185 Model::AuthoringTool DAEReader::getAuthoringTool(TiXmlElement* xmlRoot)
186 {
187  for (auto xmlAsset: getChildrenByTagName(xmlRoot, "asset")) {
188  for (auto xmlContributer: getChildrenByTagName(xmlAsset, "contributor")) {
189  for (auto xmlAuthoringTool: getChildrenByTagName(xmlContributer, "authoring_tool")) {
190  if (string(AVOID_NULLPTR_STRING(xmlAuthoringTool->GetText())).find("Blender") != -1) {
191  return Model::AUTHORINGTOOL_BLENDER;
192  }
193  }
194  }
195  }
196  return Model::AUTHORINGTOOL_UNKNOWN;
197 }
198 
200 {
201  // determine up axis
202  for (auto xmlAsset: getChildrenByTagName(xmlRoot, "asset")) {
203  for (auto xmlAssetUpAxis: getChildrenByTagName(xmlAsset, "up_axis")) {
204  auto upAxis = string(AVOID_NULLPTR_STRING(xmlAssetUpAxis->GetText()));
205  if (StringTools::equalsIgnoreCase(upAxis, "Y_UP") == true) {
206  return UpVector::Y_UP;
207  } else
208  if (StringTools::equalsIgnoreCase(upAxis, "Z_UP") == true) {
209  return UpVector::Z_UP;
210  } else
211  if (StringTools::equalsIgnoreCase(upAxis, "X_UP") == true) {
212  throw ModelFileIOException("X-Up is not supported");
213  } else {
214  throw ModelFileIOException("Unknown Up vector");
215  }
216  }
217  }
218  throw ModelFileIOException("Unknown Up vector");
219 }
220 
222 {
223  // determine rotation matrix
224  for (auto xmlAsset: getChildrenByTagName(xmlRoot, "asset")) {
225  for (auto xmlAssetUpAxis: getChildrenByTagName(xmlAsset, "up_axis")) {
226  auto upAxis = string(AVOID_NULLPTR_STRING(xmlAssetUpAxis->GetText()));
227  if (StringTools::equalsIgnoreCase(upAxis, "Y_UP") == true) {
228  } else
229  if (StringTools::equalsIgnoreCase(upAxis, "Z_UP") == true) {
230  model->setImportTransformMatrix(model->getImportTransformMatrix().clone().setAxes(Vector3(1.0f, 0.0f, 0.0f), -90.0f));
231  } else
232  if (StringTools::equalsIgnoreCase(upAxis, "X_UP") == true) {
233  model->setImportTransformMatrix(model->getImportTransformMatrix().clone().setAxes(Vector3(0.0f, 1.0f, 0.0f), -90.0f));
234  } else {
235  Console::println(string("Warning: Unknown up axis: " + upAxis));
236  }
237  }
238  }
239 }
240 
242 {
243  // determine scale
244  for (auto xmlAsset: getChildrenByTagName(xmlRoot, "asset")) {
245  for (auto xmlAssetUnit: getChildrenByTagName(xmlAsset, "unit")) {
246  string tmp;
247  if ((tmp = string(AVOID_NULLPTR_STRING(xmlAssetUnit->Attribute("meter")))).length() > 0) {
248  float scaleFactor = Float::parse(tmp);
249  model->setImportTransformMatrix(model->getImportTransformMatrix().clone().scale(scaleFactor));
250  }
251  }
252  }
253 }
254 
255 Node* DAEReader::readVisualSceneNode(const string& pathName, Model* model, Node* parentNode, TiXmlElement* xmlRoot, TiXmlElement* xmlNode, float fps, bool useBC7TextureCompression)
256 {
257  auto xmlInstanceControllers = getChildrenByTagName(xmlNode, "instance_controller");
258  if (xmlInstanceControllers.empty() == false) {
259  return readVisualSceneInstanceController(pathName, model, parentNode, xmlRoot, xmlNode, useBC7TextureCompression);
260  } else {
261  return readNode(pathName, model, parentNode, xmlRoot, xmlNode, fps, useBC7TextureCompression);
262  }
263 }
264 
265 Node* DAEReader::readNode(const string& pathName, Model* model, Node* parentNode, TiXmlElement* xmlRoot, TiXmlElement* xmlNode, float fps, bool useBC7TextureCompression)
266 {
267  auto xmlNodeId = string(AVOID_NULLPTR_STRING(xmlNode->Attribute("id")));
268  auto xmlNodeName = string(AVOID_NULLPTR_STRING(xmlNode->Attribute("name")));
269  if (xmlNodeId.length() == 0) xmlNodeId = xmlNodeName;
270 
271  // create node
272  auto node = make_unique<Node>(model, parentNode, xmlNodeId, xmlNodeName);
273 
274  // set up local transform matrix
275  auto xmlMatrixElements = getChildrenByTagName(xmlNode, "matrix");
276  if (xmlMatrixElements.empty() == false) {
277  StringTokenizer t;
278  Matrix4x4 transformMatrix;
279  auto xmlMatrix = string(AVOID_NULLPTR_STRING(xmlMatrixElements.at(0)->GetText()));
280  t.tokenize(xmlMatrix, " \n\r");
281  array<float, 16> transformMatrixArray;
282  for (auto i = 0; i < transformMatrixArray.size(); i++) {
283  transformMatrixArray[i] = Float::parse(t.nextToken());
284  }
285  transformMatrix.set(transformMatrixArray).transpose();
286  node->setTransformMatrix(transformMatrix);
287  }
288 
289  // parse animations
290  auto xmlAnimationsLibrary = getChildrenByTagName(xmlRoot, "library_animations");
291  if (xmlAnimationsLibrary.empty() == false) {
292  for (auto xmlAnimation: getChildrenByTagName(xmlAnimationsLibrary.at(0), "animation")) {
293  // older DAE has animation/animation xml nodes
294  auto _xmlAnimation = getChildrenByTagName(xmlAnimation, "animation");
295  if (_xmlAnimation.empty() == false) {
296  xmlAnimation = _xmlAnimation.at(0);
297  }
298  // find sampler source
299  string xmlSamplerSource;
300  auto xmlChannel = getChildrenByTagName(xmlAnimation, "channel").at(0);
301  if (StringTools::startsWith(string(AVOID_NULLPTR_STRING(xmlChannel->Attribute("target"))), xmlNodeId + "/") == true) {
302  xmlSamplerSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlChannel->Attribute("source"))), 1);
303  }
304  // check for sampler source
305  if (xmlSamplerSource.length() == 0) {
306  continue;
307  }
308  // parse animation output matrices
309  string xmlSamplerOutputSource;
310  string xmlSamplerInputSource;
311  auto xmlSampler = getChildrenByTagName(xmlAnimation, "sampler").at(0);
312  for (auto xmlSamplerInput: getChildrenByTagName(xmlSampler, "input")) {
313  if (string(AVOID_NULLPTR_STRING(xmlSamplerInput->Attribute("semantic"))) == "OUTPUT") {
314  xmlSamplerOutputSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlSamplerInput->Attribute("source"))), 1);
315  } else
316  if (string(AVOID_NULLPTR_STRING(xmlSamplerInput->Attribute("semantic"))) == "INPUT") {
317  xmlSamplerInputSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlSamplerInput->Attribute("source"))), 1);
318  }
319  }
320  // check for sampler source
321  if (xmlSamplerOutputSource.length() == 0) {
322  throw ModelFileIOException(
323  "Could not find xml sampler output source for animation for '" + xmlNodeId + "'"
324  );
325  }
326  // load animation input matrices
327  // TODO: check accessor "time"
328  vector<float> keyFrameTimes;
329  for (auto xmlAnimationSource: getChildrenByTagName(xmlAnimation, "source")) {
330  if (string(AVOID_NULLPTR_STRING(xmlAnimationSource->Attribute("id"))) == xmlSamplerInputSource) {
331  auto xmlFloatArray = getChildrenByTagName(xmlAnimationSource, "float_array").at(0);
332  auto frames = Integer::parse(string(AVOID_NULLPTR_STRING(xmlFloatArray->Attribute("count"))));
333  auto valueString = string(AVOID_NULLPTR_STRING(xmlFloatArray->GetText()));
334  auto keyFrameIdx = 0;
335  keyFrameTimes.resize(frames);
336  StringTokenizer t;
337  t.tokenize(valueString, " \n\r");
338  while (t.hasMoreTokens()) {
339  keyFrameTimes[keyFrameIdx++] = Float::parse(t.nextToken());
340  }
341  }
342  }
343  // load animation output matrices
344  // TODO: check accessor "transform"
345  if (keyFrameTimes.size() > 0) {
346  for (auto xmlAnimationSource: getChildrenByTagName(xmlAnimation, "source")) {
347  if (string(AVOID_NULLPTR_STRING(xmlAnimationSource->Attribute("id"))) == xmlSamplerOutputSource) {
348  auto xmlFloatArray = getChildrenByTagName(xmlAnimationSource, "float_array").at(0);
349  auto keyFrames = Integer::parse(string(AVOID_NULLPTR_STRING(xmlFloatArray->Attribute("count")))) / 16;
350  // some models have animations without frames
351  if (keyFrames > 0) {
352  auto valueString = string(AVOID_NULLPTR_STRING(xmlFloatArray->GetText()));
353  StringTokenizer t;
354  t.tokenize(valueString, " \n\r");
355  // parse key frame
356  int32_t keyFrameIdx = 0;
357  vector<Matrix4x4> keyFrameMatrices;
358  keyFrameMatrices.resize(keyFrames);
359  while (t.hasMoreTokens()) {
360  // set animation transform matrix at frame
361  array<float, 16> keyFrameMatricesArray;
362  for (auto i = 0; i < keyFrameMatricesArray.size() ;i++) {
363  keyFrameMatricesArray[i] = Float::parse(t.nextToken());
364  }
365  keyFrameMatrices[keyFrameIdx].set(keyFrameMatricesArray);
366  keyFrameMatrices[keyFrameIdx].transpose();
367  keyFrameIdx++;
368  }
369 
370  auto frames = static_cast<int32_t>(Math::ceil(keyFrameTimes[keyFrameTimes.size() - 1] * fps));
371  if (frames > 0) {
372  ModelTools::createDefaultAnimation(model, frames);
373  auto animation = make_unique<Animation>();
374  vector<Matrix4x4> transformMatrices;
375  transformMatrices.resize(frames);
376  auto tansformationsMatrixLast = &keyFrameMatrices[0];
377  keyFrameIdx = 0;
378  auto frameIdx = 0;
379  auto timeStampLast = 0.0f;
380  for (auto keyFrameTime : keyFrameTimes) {
381  auto transformMatrixCurrent = &keyFrameMatrices[(keyFrameIdx) % keyFrameMatrices.size()];
382  float timeStamp;
383  for (timeStamp = timeStampLast; timeStamp < keyFrameTime; timeStamp += 1.0f / fps) {
384  if (frameIdx >= frames) {
385  // TODO: check me again!
386  // Console::println(string("Warning: skipping frame: ") + to_string(frameIdx));
387  frameIdx++;
388  continue;
389  }
390  transformMatrices[frameIdx] = Matrix4x4::interpolateLinear(*tansformationsMatrixLast, *transformMatrixCurrent, (timeStamp - timeStampLast) / (keyFrameTime - timeStampLast));
391  frameIdx++;
392  }
393  timeStampLast = timeStamp;
394  tansformationsMatrixLast = transformMatrixCurrent;
395  keyFrameIdx++;
396  }
397  animation->setTransformMatrices(transformMatrices);
398  node->setAnimation(animation.release());
399  }
400  }
401  }
402  }
403  }
404  }
405  }
406 
407  // parse sub nodes
408  for (auto _xmlNode: getChildrenByTagName(xmlNode, "node")) {
409  auto _node = readVisualSceneNode(pathName, model, node.get(), xmlRoot, _xmlNode, fps, useBC7TextureCompression);
410  if (_node != nullptr) {
411  node->getSubNodes()[_node->getId()] = _node;
412  model->getNodes()[_node->getId()] = _node;
413  }
414  }
415 
416  // check for geometry data
417  string xmlInstanceGeometryId;
418  auto xmlInstanceGeometryElements = getChildrenByTagName(xmlNode, "instance_geometry");
419  if (xmlInstanceGeometryElements.empty() == false) {
420  auto xmlInstanceGeometryElement = xmlInstanceGeometryElements.at(0);
421  // fetch instance geometry url
422  xmlInstanceGeometryId = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlInstanceGeometryElement->Attribute("url"))), 1);
423  // determine bound materials
424  map<string, string> materialSymbols;
425  for (auto xmlBindMaterial: getChildrenByTagName(xmlInstanceGeometryElement, "bind_material"))
426  for (auto xmlTechniqueCommon: getChildrenByTagName(xmlBindMaterial, "technique_common"))
427  for (auto xmlInstanceMaterial: getChildrenByTagName(xmlTechniqueCommon, "instance_material")) {
428  materialSymbols[string(AVOID_NULLPTR_STRING(xmlInstanceMaterial->Attribute("symbol")))] =
429  string(AVOID_NULLPTR_STRING(xmlInstanceMaterial->Attribute("target")));
430  }
431  // parse geometry
432  readGeometry(pathName, model, node.get(), xmlRoot, xmlInstanceGeometryId, materialSymbols, useBC7TextureCompression);
433  //
434  return node.release();
435  }
436 
437  // otherwise check for "instance_node"
438  string xmlInstanceNodeId;
439  for (auto xmlInstanceNodeElement: getChildrenByTagName(xmlNode, "instance_node")) {
440  xmlInstanceNodeId = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlInstanceNodeElement->Attribute("url"))), 1);
441  }
442  // do we have a instance node id?
443  if (xmlInstanceNodeId.length() > 0) {
444  for (auto xmlLibraryNodes: getChildrenByTagName(xmlRoot, "library_nodes"))
445  for (auto xmlLibraryNode: getChildrenByTagName(xmlLibraryNodes, "node"))
446  if (string(AVOID_NULLPTR_STRING(xmlLibraryNode->Attribute("id"))) == xmlInstanceNodeId) {
447  // parse sub nodes
448  for (auto _xmlNode: getChildrenByTagName(xmlLibraryNode, "node")) {
449  auto _node = readVisualSceneNode(pathName, model, parentNode, xmlRoot, _xmlNode, fps, useBC7TextureCompression);
450  if (_node != nullptr) {
451  node->getSubNodes()[_node->getId()] = _node;
452  model->getNodes()[_node->getId()] = _node;
453  }
454  }
455  // parse geometry
456  for (auto xmlInstanceGeometry: getChildrenByTagName(xmlLibraryNode, "instance_geometry")) {
457  auto xmlGeometryId = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlInstanceGeometry->Attribute("url"))), 1);
458  // parse material symbols
459  map<string, string> materialSymbols;
460  for (auto xmlBindMaterial: getChildrenByTagName(xmlInstanceGeometry, "bind_material"))
461  for (auto xmlTechniqueCommon: getChildrenByTagName(xmlBindMaterial, "technique_common"))
462  for (auto xmlInstanceMaterial: getChildrenByTagName(xmlTechniqueCommon, "instance_material")) {
463  materialSymbols[string(AVOID_NULLPTR_STRING(xmlInstanceMaterial->Attribute("symbol")))] =
464  string(AVOID_NULLPTR_STRING(xmlInstanceMaterial->Attribute("target")));
465  }
466  // parse geometry
467  readGeometry(pathName, model, node.get(), xmlRoot, xmlGeometryId, materialSymbols, useBC7TextureCompression);
468  }
469  }
470  }
471  //
472  return node.release();
473 }
474 
475 Node* DAEReader::readVisualSceneInstanceController(const string& pathName, Model* model, Node* parentNode, TiXmlElement* xmlRoot, TiXmlElement* xmlNode, bool useBC7TextureCompression)
476 {
477  auto xmlNodeId = string(AVOID_NULLPTR_STRING(xmlNode->Attribute("id")));
478  auto xmlNodeName = string(AVOID_NULLPTR_STRING(xmlNode->Attribute("name")));
479  map<string, string> materialSymbols;
480  // geometry id
481  string xmlGeometryId;
482  // parse library controllers, find our controller
483  auto xmlInstanceControllers = getChildrenByTagName(xmlNode, "instance_controller");
484  TiXmlElement* xmlSkin = nullptr;
485  auto xmlInstanceController = xmlInstanceControllers.at(0);
486 
487  // parse material symbols
488  for (auto xmlBindMaterial: getChildrenByTagName(xmlInstanceController, "bind_material"))
489  for (auto xmlTechniqueCommon: getChildrenByTagName(xmlBindMaterial, "technique_common"))
490  for (auto xmlInstanceMaterial: getChildrenByTagName(xmlTechniqueCommon, "instance_material")) {
491  materialSymbols[string(AVOID_NULLPTR_STRING(xmlInstanceMaterial->Attribute("symbol")))] =
492  string(AVOID_NULLPTR_STRING(xmlInstanceMaterial->Attribute("target")));
493  }
494 
495  auto xmlInstanceControllerId = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlInstanceController->Attribute("url"))), 1);
496  auto xmlLibraryControllers = getChildrenByTagName(xmlRoot, "library_controllers").at(0);
497  for (auto xmlLibraryController: getChildrenByTagName(xmlLibraryControllers, "controller")) {
498  // our controller ?
499  if (string(AVOID_NULLPTR_STRING(xmlLibraryController->Attribute("id"))) == xmlInstanceControllerId) {
500  // parse skin
501  auto xmlSkins = getChildrenByTagName(xmlLibraryController, "skin");
502  if (xmlSkins.empty() == false) {
503  xmlSkin = xmlSkins.at(0);
504  }
505  }
506  }
507 
508  // check for xml skin
509  if (xmlSkin == nullptr) {
510  throw ModelFileIOException(
511  "skin not found for instance controller '" + xmlNodeId + "'"
512  );
513  }
514 
515  // get geometry id
516  xmlGeometryId = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlSkin->Attribute("source"))), 1);
517 
518  // parse bind shape matrix
519  auto xmlMatrix = string(AVOID_NULLPTR_STRING(getChildrenByTagName(xmlSkin, "bind_shape_matrix").at(0)->GetText()));
520  StringTokenizer t;;
521  t.tokenize(xmlMatrix, " \n\r");
522  array<float, 16> bindShapeMatrixArray;
523  for (auto i = 0; i < bindShapeMatrixArray.size(); i++) {
524  bindShapeMatrixArray[i] = Float::parse(t.nextToken());
525  }
526  Matrix4x4 bindShapeMatrix;
527  bindShapeMatrix.set(bindShapeMatrixArray).transpose();
528 
529  // create node
530  auto node = make_unique<Node>(model, parentNode, xmlNodeId, xmlNodeName);
531 
532  // create skinning
533  auto skinning = make_unique<Skinning>();
534 
535  // parse geometry
536  readGeometry(pathName, model, node.get(), xmlRoot, xmlGeometryId, materialSymbols, useBC7TextureCompression);
537 
538  // parse joints
539  string xmlJointsSource;
540  string xmlJointsInverseBindMatricesSource;
541  auto xmlJoints = getChildrenByTagName(xmlSkin, "joints").at(0);
542  for (auto xmlJointsInput: getChildrenByTagName(xmlJoints, "input")) {
543  if (string(AVOID_NULLPTR_STRING(xmlJointsInput->Attribute("semantic"))) == "JOINT") {
544  xmlJointsSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlJointsInput->Attribute("source"))), 1);
545  } else
546  if (string(AVOID_NULLPTR_STRING(xmlJointsInput->Attribute("semantic"))) == "INV_BIND_MATRIX") {
547  xmlJointsInverseBindMatricesSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlJointsInput->Attribute("source"))), 1);
548  }
549  }
550 
551  // check for joints sources
552  if (xmlJointsSource.length() == 0) {
553  throw ModelFileIOException(
554  "joint source not found for instance controller '" + xmlNodeId + "'"
555  );
556  }
557 
558  // parse joint ids
559  vector<Joint> joints;
560  for (auto xmlSkinSource: getChildrenByTagName(xmlSkin, "source")) {
561  if (string(AVOID_NULLPTR_STRING(xmlSkinSource->Attribute("id"))) == xmlJointsSource) {
562  t.tokenize(string(AVOID_NULLPTR_STRING(getChildrenByTagName(xmlSkinSource, "Name_array").at(0)->GetText())), " \n\r");
563  while (t.hasMoreTokens()) {
564  joints.emplace_back(t.nextToken());
565  }
566  }
567  }
568 
569  // check for inverse bind matrices source
570  if (xmlJointsInverseBindMatricesSource.length() == 0) {
571  throw ModelFileIOException(
572  "inverse bind matrices source not found for instance controller '" + xmlNodeId + "'"
573  );
574  }
575 
576  // Create joints bind matrices
577  for (auto xmlSkinSource: getChildrenByTagName(xmlSkin, "source")) {
578  if (string(AVOID_NULLPTR_STRING(xmlSkinSource->Attribute("id"))) == xmlJointsInverseBindMatricesSource) {
579  t.tokenize(string(AVOID_NULLPTR_STRING(getChildrenByTagName(xmlSkinSource, "float_array").at(0)->GetText())), " \n\r");
580  for (auto i = 0; i < joints.size(); i++) {
581  // The vertices are defined in model space
582  // The transform to the local space of the joint is called the inverse bind matrix
583  array<float, 16> bindMatrixArray;
584  for (auto i = 0; i < bindMatrixArray.size(); i++) {
585  bindMatrixArray[i] = Float::parse(t.nextToken());
586  }
587  Matrix4x4 bindMatrix;
588  bindMatrix.set(bindShapeMatrix);
589  bindMatrix.multiply((Matrix4x4(bindMatrixArray)).transpose());
590  joints[i].setBindMatrix(bindMatrix);
591  }
592  }
593  }
594 
595  skinning->setJoints(joints);
596 
597  // read vertex influences
598  vector<float> weights;
599  auto xmlJointOffset = -1;
600  auto xmlWeightOffset = -1;
601  string xmlWeightsSource;
602  auto xmlVertexWeights = getChildrenByTagName(xmlSkin, "vertex_weights").at(0);
603  auto xmlVertexWeightInputs = getChildrenByTagName(xmlVertexWeights, "input");
604  for (auto xmlVertexWeightInput: xmlVertexWeightInputs) {
605  if (string(AVOID_NULLPTR_STRING(xmlVertexWeightInput->Attribute("semantic"))) == "JOINT") {
606  if ((StringTools::substring(string(AVOID_NULLPTR_STRING(xmlVertexWeightInput->Attribute("source"))), 1) == xmlJointsSource) == false) {
607  throw ModelFileIOException("joint inverse bind matrices source do not match");
608  }
609  xmlJointOffset = Integer::parse(string(AVOID_NULLPTR_STRING(xmlVertexWeightInput->Attribute("offset"))));
610  } else
611  if (string(AVOID_NULLPTR_STRING(xmlVertexWeightInput->Attribute("semantic"))) == "WEIGHT") {
612  xmlWeightOffset = Integer::parse(string(AVOID_NULLPTR_STRING(xmlVertexWeightInput->Attribute("offset"))));
613  xmlWeightsSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlVertexWeightInput->Attribute("source"))), 1);
614  }
615  }
616 
617  // check for vertex weight parameter
618  if (xmlJointOffset == -1) {
619  throw ModelFileIOException(
620  "xml vertex weight joint offset missing for node '" + xmlNodeId + "'"
621  );
622  }
623  if (xmlWeightOffset == -1) {
624  throw ModelFileIOException(
625  "xml vertex weight weight offset missing for node " + xmlNodeId + "'"
626  );
627  }
628  if (xmlWeightsSource.length() == 0) {
629  throw ModelFileIOException(
630  "xml vertex weight weight source missing for node '" + xmlNodeId + "'"
631  );
632  }
633 
634  // parse weights
635  for (auto xmlSkinSource: getChildrenByTagName(xmlSkin, "source")) {
636  if (string(AVOID_NULLPTR_STRING(xmlSkinSource->Attribute("id"))) == xmlWeightsSource) {
637  t.tokenize(string(AVOID_NULLPTR_STRING(getChildrenByTagName(xmlSkinSource, "float_array").at(0)->GetText())), " \n\r");
638  while (t.hasMoreTokens()) {
639  weights.push_back(Float::parse(t.nextToken()));
640  }
641  }
642  }
643  skinning->setWeights(weights);
644 
645  // actually do parse joint influences of each vertex
646  auto xmlVertexWeightInputCount = xmlVertexWeightInputs.size();
647  auto vertexJointsInfluenceCountString = string(AVOID_NULLPTR_STRING(getChildrenByTagName(xmlVertexWeights, "vcount").at(0)->GetText()));
648  auto vertexJointsInfluencesString = string(AVOID_NULLPTR_STRING(getChildrenByTagName(xmlVertexWeights, "v").at(0)->GetText()));
649  t.tokenize(vertexJointsInfluenceCountString, " \n\r");
650  StringTokenizer t2;
651  t2.tokenize(vertexJointsInfluencesString, " \n\r");
652  auto offset = 0;
653  vector<vector<JointWeight>> verticesJointsWeights;
654  while (t.hasMoreTokens()) {
655  // read joint influences for current vertex
656  auto vertexJointsInfluencesCount = Integer::parse(t.nextToken());
657  vector<JointWeight>vertexJointsWeights;
658  for (auto i = 0; i < vertexJointsInfluencesCount; i++) {
659  auto vertexJoint = -1;
660  auto vertexWeight = -1;
661  while (vertexJoint == -1 || vertexWeight == -1) {
662  auto value = Integer::parse(t2.nextToken());
663  if (offset % xmlVertexWeightInputCount == xmlJointOffset) {
664  vertexJoint = value;
665  } else if (offset % xmlVertexWeightInputCount == xmlWeightOffset) {
666  vertexWeight = value;
667  }
668  offset++;
669  }
670  vertexJointsWeights.emplace_back(vertexJoint, vertexWeight);
671  }
672  verticesJointsWeights.push_back(vertexJointsWeights);
673  }
674  skinning->setVerticesJointsWeights(verticesJointsWeights);
675  node->setSkinning(skinning.release());
676  //
677  return node.release();
678 }
679 
680 void DAEReader::readGeometry(const string& pathName, Model* model, Node* node, TiXmlElement* xmlRoot, const string& xmlNodeId, const map<string, string>& materialSymbols, bool useBC7TextureCompression)
681 {
682  vector<FacesEntity> facesEntities = node->getFacesEntities();
683  auto verticesOffset = static_cast<int32_t>(node->getVertices().size());
684  vector<Vector3> vertices = node->getVertices();
685  auto normalsOffset = static_cast<int32_t>(node->getNormals().size());
686  vector<Vector3> normals = node->getNormals();;
687  auto textureCoordinatesOffset = static_cast<int32_t>(node->getTextureCoordinates().size());
688  auto textureCoordinates = node->getTextureCoordinates();
689  auto xmlLibraryGeometries = getChildrenByTagName(xmlRoot, "library_geometries").at(0);
690  for (auto xmlGeometry: getChildrenByTagName(xmlLibraryGeometries, "geometry")) {
691  if (string(AVOID_NULLPTR_STRING(xmlGeometry->Attribute("id"))) == xmlNodeId) {
692  auto xmlMesh = getChildrenByTagName(xmlGeometry, "mesh").at(0);
693  vector<TiXmlElement*> xmlPolygonsList;
694  // try to read from triangles
695  for (auto xmlTriangesElement: getChildrenByTagName(xmlMesh, "triangles")) {
696  xmlPolygonsList.push_back(xmlTriangesElement);
697  }
698  // try to read from polylist
699  for (auto xmlPolyListElement: getChildrenByTagName(xmlMesh, "polylist")) {
700  xmlPolygonsList.push_back(xmlPolyListElement);
701  }
702  // try to read from polygons
703  for (auto xmlPolygonsElement: getChildrenByTagName(xmlMesh, "polygons")) {
704  xmlPolygonsList.push_back(xmlPolygonsElement);
705  }
706  // parse from xml polygons elements
707  for (auto xmlPolygons: xmlPolygonsList) {
708  vector<Face> faces;
709  FacesEntity facesEntity(node, xmlNodeId);
710  if (StringTools::toLowerCase((xmlPolygons->Value())) == "polylist") {
711  StringTokenizer t;
712  t.tokenize(string(AVOID_NULLPTR_STRING(getChildrenByTagName(xmlPolygons, "vcount").at(0)->GetText())), " \t\n\r\f");
713  while (t.hasMoreTokens()) {
714  auto vertexCount = Integer::parse(t.nextToken());
715  if (vertexCount != 3) {
716  throw ModelFileIOException(
717  "we only support triangles in '" + xmlNodeId + "'"
718  );
719  }
720  }
721  }
722  auto xmlInputs = -1;
723  auto xmlVerticesOffset = -1;
724  string xmlVerticesSource;
725  auto xmlNormalsOffset = -1;
726  string xmlNormalsSource;
727  auto xmlTexCoordOffset = -1;
728  string xmlTexCoordSource;
729  auto xmlColorOffset = -1;
730  string xmlColorSource;
731  auto xmlMaterialId = string(AVOID_NULLPTR_STRING(xmlPolygons->Attribute("material")));
732  auto materialSymbolIt = materialSymbols.find(xmlMaterialId);
733  if (materialSymbolIt != materialSymbols.end()) {
734  xmlMaterialId = materialSymbolIt->second;
735  xmlMaterialId = StringTools::substring(xmlMaterialId, 1);
736  }
737 
738  if (xmlMaterialId.length() > 0) {
739  Material* material = nullptr;
740  auto materialIt = model->getMaterials().find(xmlMaterialId);
741  if (materialIt != model->getMaterials().end()) {
742  material = materialIt->second;
743  } else {
744  // parse material as we do not have it yet
745  material = readMaterial(pathName, model, xmlRoot, xmlMaterialId, useBC7TextureCompression);
746  }
747  // set it up
748  facesEntity.setMaterial(material);
749  }
750  unordered_set<int32_t> xmlInputSet;
751  for (auto xmlTrianglesInput: getChildrenByTagName(xmlPolygons, "input")) {
752  // check for vertices sources
753  if (string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("semantic"))) == "VERTEX") {
754  xmlVerticesOffset = Integer::parse(string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("offset"))));
755  xmlVerticesSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("source"))), 1);
756  xmlInputSet.insert(xmlVerticesOffset);
757  } else
758  // check for normals sources
759  if (string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("semantic"))) == "NORMAL") {
760  xmlNormalsOffset = Integer::parse(string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("offset"))));
761  xmlNormalsSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("source"))), 1);
762  xmlInputSet.insert(xmlNormalsOffset);
763  } else
764  // check for texture coordinate sources
765  if (string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("semantic"))) == "TEXCOORD") {
766  xmlTexCoordOffset = Integer::parse(string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("offset"))));
767  xmlTexCoordSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("source"))), 1);
768  xmlInputSet.insert(xmlTexCoordOffset);
769  } else
770  // check for color coordinate sources
771  if (string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("semantic"))) == "COLOR") {
772  xmlColorOffset = Integer::parse(string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("offset"))));
773  xmlColorSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlTrianglesInput->Attribute("source"))), 1);
774  xmlInputSet.insert(xmlColorOffset);
775  }
776  }
777  xmlInputs = xmlInputSet.size();
778  // get vertices source
779  for (auto xmlVertices: getChildrenByTagName(xmlMesh, "vertices")) {
780  if (string(AVOID_NULLPTR_STRING(xmlVertices->Attribute("id"))) == xmlVerticesSource) {
781  for (auto xmlVerticesInput: getChildrenByTagName(xmlVertices, "input")) {
782  if (StringTools::equalsIgnoreCase(string(AVOID_NULLPTR_STRING(xmlVerticesInput->Attribute("semantic"))), "position") == true) {
783  xmlVerticesSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlVerticesInput->Attribute("source"))), 1);
784  } else
785  if (StringTools::equalsIgnoreCase(string(AVOID_NULLPTR_STRING(xmlVerticesInput->Attribute("semantic"))), "normal") == true) {
786  xmlNormalsSource = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlVerticesInput->Attribute("source"))), 1);
787  }
788  }
789  }
790  }
791  // check for triangles vertices sources
792  if (xmlVerticesSource.length() == 0) {
793  throw ModelFileIOException(
794  "Could not determine triangles vertices source for '" + xmlNodeId + "'"
795  );
796  }
797  // check for triangles normals sources
798  if (xmlNormalsSource.length() == 0) {
799  throw ModelFileIOException(
800  "Could not determine triangles normal source for '" + xmlNodeId + "'"
801  );
802  }
803  // load vertices, normals, texture coordinates
804  for (auto xmlMeshSource: getChildrenByTagName(xmlMesh, "source")) {
805  // vertices
806  if (string(AVOID_NULLPTR_STRING(xmlMeshSource->Attribute("id"))) == xmlVerticesSource) {
807  auto xmlFloatArray = getChildrenByTagName(xmlMeshSource, "float_array").at(0);
808  auto valueString = string(AVOID_NULLPTR_STRING(xmlFloatArray->GetText()));
809  StringTokenizer t;
810  t.tokenize(valueString, " \n\r");
811  while (t.hasMoreTokens()) {
812  float x = Float::parse(t.nextToken());
813  float y = Float::parse(t.nextToken());
814  float z = Float::parse(t.nextToken());
815  vertices.emplace_back(x, y, z);
816  }
817  } else
818  // normals
819  if (string(AVOID_NULLPTR_STRING(xmlMeshSource->Attribute("id"))) == xmlNormalsSource) {
820  auto xmlFloatArray = getChildrenByTagName(xmlMeshSource, "float_array").at(0);
821  auto valueString = string(AVOID_NULLPTR_STRING(xmlFloatArray->GetText()));
822  StringTokenizer t;
823  t.tokenize(valueString, " \n\r");
824  while (t.hasMoreTokens()) {
825  float x = Float::parse(t.nextToken());
826  float y = Float::parse(t.nextToken());
827  float z = Float::parse(t.nextToken());
828  normals.emplace_back(x, y, z);
829  }
830  } else
831  // texture coordinates
832  if (xmlTexCoordSource.length() > 0) {
833  if (string(AVOID_NULLPTR_STRING(xmlMeshSource->Attribute("id"))) == xmlTexCoordSource) {
834  auto xmlFloatArray = getChildrenByTagName(xmlMeshSource, "float_array").at(0);
835  auto valueString = string(AVOID_NULLPTR_STRING(xmlFloatArray->GetText()));
836  StringTokenizer t;
837  t.tokenize(valueString, " \n\r");
838  while (t.hasMoreTokens()) {
839  float u = Float::parse(t.nextToken());
840  float v = Float::parse(t.nextToken());
841  textureCoordinates.emplace_back(u, 1.0f - v);
842  }
843  }
844  }
845  }
846  // load faces
847  for (auto xmlPolygon: getChildrenByTagName(xmlPolygons, "p")) {
848  auto valueString = string(AVOID_NULLPTR_STRING(xmlPolygon->GetText()));
849  StringTokenizer t;
850  t.tokenize(valueString, " \n\r");
851  array<int32_t, 3> vi;
852  auto viIdx = 0;
853  array<int32_t, 3> ni;
854  auto niIdx = 0;
855  array<int32_t, 3> ti;
856  auto tiIdx = 0;
857  auto valueIdx = 0;
858  auto valid = true;
859  while (t.hasMoreTokens()) {
860  auto value = Integer::parse(t.nextToken());
861  if (valueIdx % xmlInputs == xmlVerticesOffset) {
862  vi[viIdx++] = value;
863  // validate
864  if (value < 0 || value >= vertices.size() - verticesOffset) {
865  valid = false;
866  }
867  // fix for some strange models
868  if (xmlNormalsSource.length() > 0 && xmlNormalsOffset == -1) {
869  ni[niIdx++] = value;
870  if (value < 0 || value >= normals.size() - normalsOffset) {
871  valid = false;
872  }
873  }
874  }
875  if (xmlNormalsOffset != -1 && valueIdx % xmlInputs == xmlNormalsOffset) {
876  ni[niIdx++] = value;
877  // validate
878  if (value < 0 || value >= normals.size() - normalsOffset) {
879  valid = false;
880  }
881  }
882  if (xmlTexCoordOffset != -1 && valueIdx % xmlInputs == xmlTexCoordOffset) {
883  ti[tiIdx++] = value;
884  // validate
885  if (value < 0 || value >= textureCoordinates.size() - textureCoordinatesOffset) {
886  valid = false;
887  }
888  }
889  if (viIdx == 3 && niIdx == 3 && (xmlTexCoordSource.length() == 0 || tiIdx == 3)) {
890  // only add valid faces
891  if (valid == true) {
892  // add face
893  if (xmlTexCoordSource.empty() == false) {
894  faces.emplace_back(
895  node,
896  vi[0] + verticesOffset,
897  vi[1] + verticesOffset,
898  vi[2] + verticesOffset,
899  ni[0] + normalsOffset,
900  ni[1] + normalsOffset,
901  ni[2] + normalsOffset,
902  ti[0] + textureCoordinatesOffset,
903  ti[1] + textureCoordinatesOffset,
904  ti[2] + textureCoordinatesOffset
905  );
906  } else {
907  faces.emplace_back(
908  node,
909  vi[0] + verticesOffset,
910  vi[1] + verticesOffset,
911  vi[2] + verticesOffset,
912  ni[0] + normalsOffset,
913  ni[1] + normalsOffset,
914  ni[2] + normalsOffset
915  );
916  }
917  }
918  viIdx = 0;
919  niIdx = 0;
920  tiIdx = 0;
921  valid = true;
922  }
923  valueIdx++;
924  }
925  }
926  // add faces entities if we have any
927  if (faces.empty() == false) {
928  facesEntity.setFaces(faces);
929  facesEntities.push_back(facesEntity);
930  }
931  }
932  }
933  }
934 
935  // set up node
936  node->setVertices(vertices);
937  node->setNormals(normals);
938  node->setTextureCoordinates(textureCoordinates);
939  node->setFacesEntities(facesEntities);
940 }
941 
942 Material* DAEReader::readMaterial(const string& pathName, Model* model, TiXmlElement* xmlRoot, const string& xmlNodeId, bool useBC7TextureCompression)
943 {
944  // determine effect id
945  string xmlEffectId;
946  auto xmlLibraryMaterials = getChildrenByTagName(xmlRoot, "library_materials").at(0);
947  for (auto xmlMaterial: getChildrenByTagName(xmlLibraryMaterials, "material")) {
948  if (string(AVOID_NULLPTR_STRING(xmlMaterial->Attribute("id"))) == xmlNodeId) {
949  auto xmlInstanceEffect = getChildrenByTagName(xmlMaterial, "instance_effect").at(0);
950  xmlEffectId = StringTools::substring(string(AVOID_NULLPTR_STRING(xmlInstanceEffect->Attribute("url"))), 1);
951  }
952  }
953  if (xmlEffectId.length() == 0) {
954  Console::println(
955  string(
956  "Could not determine effect id for '" +
957  xmlNodeId +
958  "'"
959  )
960  );
961  return nullptr;
962  }
963  // parse effect
964  auto material = make_unique<Material>(xmlNodeId);
965  auto specularMaterialProperties = make_unique<SpecularMaterialProperties>();
966  string xmlDiffuseTextureId;
967  string xmlTransparencyTextureId;
968  string xmlSpecularTextureId;
969  string xmlBumpTextureId;
970  auto xmlLibraryEffects = getChildrenByTagName(xmlRoot, "library_effects").at(0);
971  for (auto xmlEffect: getChildrenByTagName(xmlLibraryEffects, "effect")) {
972  if (string(AVOID_NULLPTR_STRING(xmlEffect->Attribute("id"))) == xmlEffectId) {
973  auto xmlProfile = getChildrenByTagName(xmlEffect, "profile_COMMON").at(0);
974  // mappings
975  map<string, string> samplerSurfaceMapping;
976  map<string, string> surfaceImageMapping;
977  for (auto xmlNewParam: getChildrenByTagName(xmlProfile, "newparam")) {
978  auto xmlNewParamSID = string(AVOID_NULLPTR_STRING(xmlNewParam->Attribute("sid")));
979  for (auto xmlSurface: getChildrenByTagName(xmlNewParam, "surface"))
980  for (auto xmlSurfaceInitFrom: getChildrenByTagName(xmlSurface, "init_from")) {
981  surfaceImageMapping[xmlNewParamSID] =
982  string(AVOID_NULLPTR_STRING(xmlSurfaceInitFrom->GetText()));
983  }
984  for (auto xmlSampler2D: getChildrenByTagName(xmlNewParam, "sampler2D"))
985  for (auto xmlSampler2DSource: getChildrenByTagName(xmlSampler2D, "source")) {
986  samplerSurfaceMapping[xmlNewParamSID] =
987  string(AVOID_NULLPTR_STRING(xmlSampler2DSource->GetText()));
988  }
989  }
990  for (auto xmlTechnique: getChildrenByTagName(xmlProfile, "technique")) {
991  for (auto xmlTechniqueNode: getChildren(xmlTechnique)) {
992  for (auto xmlDiffuse: getChildrenByTagName(xmlTechniqueNode, "transparent")) {
993  for (auto xmlTexture: getChildrenByTagName(xmlDiffuse, "texture")) {
994  xmlTransparencyTextureId = string(AVOID_NULLPTR_STRING(xmlTexture->Attribute("texture")));
995 
996  auto sample2SurfaceIt = samplerSurfaceMapping.find(xmlTransparencyTextureId);
997  string sample2Surface;
998  if (sample2SurfaceIt != samplerSurfaceMapping.end()) {
999  sample2Surface = sample2SurfaceIt->second;
1000  }
1001  if (sample2Surface.length() == 0) continue;
1002 
1003  string surface2Image;
1004  auto surface2ImageIt = surfaceImageMapping.find(sample2Surface);
1005  if (surface2ImageIt != surfaceImageMapping.end()) {
1006  surface2Image = surface2ImageIt->second;
1007  }
1008  if (surface2Image.length() > 0) {
1009  xmlTransparencyTextureId = surface2Image;
1010  }
1011  }
1012  }
1013  // diffuse
1014  for (auto xmlDiffuse: getChildrenByTagName(xmlTechniqueNode, "diffuse")) {
1015  // color
1016  for (auto xmlColor: getChildrenByTagName(xmlDiffuse, "color")) {
1017  StringTokenizer t;
1018  t.tokenize(string(AVOID_NULLPTR_STRING(xmlColor->GetText())), " ");
1019  array<float, 4> colorArray;
1020  for (auto i = 0; i < colorArray.size(); i++) {
1021  colorArray[i] = Float::parse(t.nextToken());
1022  }
1023  specularMaterialProperties->setDiffuseColor(Color4(colorArray));
1024  }
1025  // texture
1026  for (auto xmlTexture: getChildrenByTagName(xmlDiffuse, "texture")) {
1027  xmlDiffuseTextureId = string(AVOID_NULLPTR_STRING(xmlTexture->Attribute("texture")));
1028 
1029  auto sample2SurfaceIt = samplerSurfaceMapping.find(xmlDiffuseTextureId);
1030  string sample2Surface;
1031  if (sample2SurfaceIt != samplerSurfaceMapping.end()) {
1032  sample2Surface = sample2SurfaceIt->second;
1033  }
1034  if (sample2Surface.length() == 0) continue;
1035 
1036  string surface2Image;
1037  auto surface2ImageIt = surfaceImageMapping.find(sample2Surface);
1038  if (surface2ImageIt != surfaceImageMapping.end()) {
1039  surface2Image = surface2ImageIt->second;
1040  }
1041  if (surface2Image.length() > 0) {
1042  xmlDiffuseTextureId = surface2Image;
1043  }
1044  }
1045  }
1046  // ambient
1047  for (auto xmlAmbient: getChildrenByTagName(xmlTechniqueNode, "ambient")) {
1048  // color
1049  for (auto xmlColor: getChildrenByTagName(xmlAmbient, "color")) {
1050  StringTokenizer t;
1051  t.tokenize(string(AVOID_NULLPTR_STRING(xmlColor->GetText())), " ");
1052  array<float, 4> colorArray;
1053  for (auto i = 0; i < colorArray.size(); i++) {
1054  colorArray[i] = Float::parse(t.nextToken());
1055  }
1056  specularMaterialProperties->setAmbientColor(Color4(colorArray));
1057  }
1058  }
1059  // emission
1060  for (auto xmlEmission: getChildrenByTagName(xmlTechniqueNode, "emission")) {
1061  // color
1062  for (auto xmlColor: getChildrenByTagName(xmlEmission, "color")) {
1063  StringTokenizer t;
1064  t.tokenize(string(AVOID_NULLPTR_STRING(xmlColor->GetText())), " ");
1065  array<float, 4> colorArray;
1066  for (auto i = 0; i < colorArray.size(); i++) {
1067  colorArray[i] = Float::parse(t.nextToken());
1068  }
1069  specularMaterialProperties->setEmissionColor(Color4(colorArray));
1070  }
1071  }
1072  // specular
1073  auto hasSpecularMap = false;
1074  auto hasSpecularColor = false;
1075  for (auto xmlSpecular: getChildrenByTagName(xmlTechniqueNode, "specular")) {
1076  // texture
1077  for (auto xmlTexture: getChildrenByTagName(xmlSpecular, "texture")) {
1078  xmlSpecularTextureId = string(AVOID_NULLPTR_STRING(xmlTexture->Attribute("texture")));
1079 
1080  auto sample2SurfaceIt = samplerSurfaceMapping.find(xmlSpecularTextureId);
1081  string sample2Surface;
1082  if (sample2SurfaceIt != samplerSurfaceMapping.end()) {
1083  sample2Surface = sample2SurfaceIt->second;
1084  }
1085  if (sample2Surface.length() == 0) continue;
1086 
1087  string surface2Image;
1088  auto surface2ImageIt = surfaceImageMapping.find(sample2Surface);
1089  if (surface2ImageIt != surfaceImageMapping.end()) {
1090  surface2Image = surface2ImageIt->second;
1091  }
1092 
1093  if (surface2Image.length() > 0) {
1094  xmlSpecularTextureId = surface2Image;
1095  hasSpecularMap = true;
1096  }
1097  }
1098  // color
1099  for (auto xmlColor: getChildrenByTagName(xmlSpecular, "color")) {
1100  StringTokenizer t;
1101  t.tokenize(string(AVOID_NULLPTR_STRING(xmlColor->GetText())), " ");
1102  array<float, 4> colorArray;
1103  for (auto i = 0; i < colorArray.size(); i++) {
1104  colorArray[i] = Float::parse(t.nextToken());
1105  }
1106  specularMaterialProperties->setSpecularColor(Color4(colorArray));
1107  hasSpecularColor = true;
1108  }
1109  }
1110  if (hasSpecularMap == true && hasSpecularColor == false) {
1111  specularMaterialProperties->setSpecularColor(Color4(1.0f, 1.0f, 1.0f, 1.0f));
1112  }
1113  // shininess
1114  for (auto xmlShininess: getChildrenByTagName(xmlTechniqueNode, "shininess"))
1115  for (auto xmlFloat: getChildrenByTagName(xmlShininess, "float")) {
1116  specularMaterialProperties->setShininess(Float::parse(string(AVOID_NULLPTR_STRING(xmlFloat->GetText()))));
1117  }
1118  }
1119  // normal/bump texture
1120  for (auto xmlBumpExtra: getChildrenByTagName(xmlTechnique, "extra"))
1121  for (auto xmlBumpTechnique: getChildrenByTagName(xmlBumpExtra, "technique"))
1122  for (auto xmlBumpTechniqueBump: getChildrenByTagName(xmlBumpTechnique, "bump"))
1123  for (auto xmlBumpTexture: getChildrenByTagName(xmlBumpTechniqueBump, "texture")) {
1124  xmlBumpTextureId = string(AVOID_NULLPTR_STRING(xmlBumpTexture->Attribute("texture")));
1125 
1126  auto sample2SurfaceIt = samplerSurfaceMapping.find(xmlBumpTextureId);
1127  string sample2Surface;
1128  if (sample2SurfaceIt != samplerSurfaceMapping.end()) {
1129  sample2Surface = sample2SurfaceIt->second;
1130  }
1131  if (sample2Surface.length() == 0) continue;
1132 
1133  string surface2Image;
1134  auto surface2ImageIt = surfaceImageMapping.find(sample2Surface);
1135  if (surface2ImageIt != surfaceImageMapping.end()) {
1136  surface2Image = surface2ImageIt->second;
1137  }
1138 
1139  if (surface2Image.length() > 0) xmlBumpTextureId = surface2Image;
1140  }
1141  }
1142  }
1143  }
1144 
1145  // diffuse transparency texture
1146  string xmlTransparencyTextureFilename;
1147  if (xmlDiffuseTextureId.length() > 0) {
1148  xmlTransparencyTextureFilename = getTextureFileNameById(xmlRoot, xmlTransparencyTextureId);
1149  // do we have a file name
1150  if (xmlTransparencyTextureFilename.length() > 0) {
1151  // add texture
1152  xmlTransparencyTextureFilename = makeFileNameRelative(xmlTransparencyTextureFilename);
1153  }
1154  }
1155 
1156  // diffuse texture
1157  string xmlDiffuseTextureFilename;
1158  if (xmlDiffuseTextureId.length() > 0) {
1159  xmlDiffuseTextureFilename = getTextureFileNameById(xmlRoot, xmlDiffuseTextureId);
1160  // do we have a file name
1161  if (xmlDiffuseTextureFilename.length() > 0) {
1162  xmlDiffuseTextureFilename = makeFileNameRelative(xmlDiffuseTextureFilename);
1163  // add texture
1164  specularMaterialProperties->setDiffuseTexture(pathName, xmlDiffuseTextureFilename, pathName, xmlTransparencyTextureFilename);
1165  if (specularMaterialProperties->getDiffuseTexture() != nullptr) specularMaterialProperties->getDiffuseTexture()->setUseCompression(useBC7TextureCompression);
1166  if (specularMaterialProperties->hasDiffuseTextureTransparency() == true) specularMaterialProperties->setDiffuseTextureMaskedTransparency(true);
1167  }
1168  }
1169 
1170  // specular texture
1171  string xmlSpecularTextureFilename;
1172  if (xmlSpecularTextureId.length() > 0) {
1173  xmlSpecularTextureFilename = getTextureFileNameById(xmlRoot, xmlSpecularTextureId);
1174  // do we have a file name
1175  if (xmlSpecularTextureFilename.length() > 0) {
1176  xmlSpecularTextureFilename = makeFileNameRelative(xmlSpecularTextureFilename);
1177  // add texture
1178  specularMaterialProperties->setSpecularTexture(pathName, xmlSpecularTextureFilename);
1179  if (specularMaterialProperties->getSpecularTexture() != nullptr) specularMaterialProperties->getSpecularTexture()->setUseCompression(useBC7TextureCompression);
1180  }
1181  }
1182 
1183  // normal/bump texture
1184  string xmlBumpTextureFilename;
1185  if (xmlBumpTextureId.length() > 0) {
1186  xmlBumpTextureFilename = getTextureFileNameById(xmlRoot, xmlBumpTextureId);
1187  // do we have a file name
1188  if (xmlBumpTextureFilename.length() > 0) {
1189  xmlBumpTextureFilename = makeFileNameRelative(xmlBumpTextureFilename);
1190  // add texture
1191  specularMaterialProperties->setNormalTexture(pathName, xmlBumpTextureFilename);
1192  if (specularMaterialProperties->getNormalTexture() != nullptr) specularMaterialProperties->getNormalTexture()->setUseCompression(useBC7TextureCompression);
1193  }
1194  }
1195 
1196  /*
1197  // determine displacement map file name
1198  string xmlDisplacementFilename;
1199  // add texture
1200  if (xmlDisplacementFilename.length() > 0) {
1201  specularMaterialProperties->setDisplacementTexture(pathName, xmlDisplacementFilename);
1202  }
1203  */
1204 
1205  // adjust ambient light with blender
1206  if (model->getAuthoringTool() == Model::AUTHORINGTOOL_BLENDER && specularMaterialProperties->getAmbientColor().equals(BLENDER_AMBIENT_NONE)) {
1207  specularMaterialProperties->setAmbientColor(
1208  Color4(
1209  specularMaterialProperties->getDiffuseColor().getRed() * BLENDER_AMBIENT_FROM_DIFFUSE_SCALE,
1210  specularMaterialProperties->getDiffuseColor().getGreen() * BLENDER_AMBIENT_FROM_DIFFUSE_SCALE,
1211  specularMaterialProperties->getDiffuseColor().getBlue() * BLENDER_AMBIENT_FROM_DIFFUSE_SCALE,
1212  1.0f
1213  )
1214  );
1215  specularMaterialProperties->setDiffuseColor(
1216  Color4(
1217  specularMaterialProperties->getDiffuseColor().getRed() * BLENDER_DIFFUSE_SCALE,
1218  specularMaterialProperties->getDiffuseColor().getGreen() * BLENDER_DIFFUSE_SCALE,
1219  specularMaterialProperties->getDiffuseColor().getBlue() * BLENDER_DIFFUSE_SCALE,
1220  specularMaterialProperties->getDiffuseColor().getAlpha()
1221  )
1222  );
1223  }
1224 
1225  // add specular material properties
1226  material->setSpecularMaterialProperties(specularMaterialProperties.release());
1227 
1228  // add material to library
1229  model->getMaterials()[material->getId()] = material.get();
1230 
1231  //
1232  return material.release();
1233 }
1234 
1235 const string DAEReader::makeFileNameRelative(const string& fileName)
1236 {
1237  // check if absolute path
1238  if (StringTools::startsWith(fileName, "/") == true ||
1239  StringTools::regexMatch(fileName, "^[A-Z]\\:\\\\.*$") == true) {
1240  int indexSlash = fileName.find_last_of(L'/');
1241  int indexBackslash = fileName.find_last_of(L'\\');
1242  if (indexSlash != -1 || indexBackslash != -1) {
1243  if (indexSlash > indexBackslash) {
1244  return StringTools::substring(fileName, indexSlash + 1);
1245  } else {
1246  return StringTools::substring(fileName, indexBackslash + 1);
1247  }
1248  }
1249  }
1250  return fileName;
1251 }
1252 
1253 const string DAEReader::getTextureFileNameById(TiXmlElement* xmlRoot, const string& xmlTextureId)
1254 {
1255  string xmlTextureFilename;
1256  auto xmlLibraryImages = getChildrenByTagName(xmlRoot, "library_images").at(0);
1257  for (auto xmlImage: getChildrenByTagName(xmlLibraryImages, "image")) {
1258  if (string(AVOID_NULLPTR_STRING(xmlImage->Attribute("id"))) == xmlTextureId) {
1259  xmlTextureFilename = string(AVOID_NULLPTR_STRING(getChildrenByTagName(xmlImage, "init_from").at(0)->GetText()));
1260  if (StringTools::startsWith(xmlTextureFilename, "file://") == true) {
1261  xmlTextureFilename = StringTools::substring(xmlTextureFilename, 7);
1262  }
1263  break;
1264  }
1265  }
1266  return xmlTextureFilename;
1267 }
1268 
1269 const vector<TiXmlElement*> DAEReader::getChildrenByTagName(TiXmlElement* parent, const char* name)
1270 {
1271  vector<TiXmlElement*> elementList;
1272  for (auto *child = parent->FirstChildElement(name); child != nullptr; child = child->NextSiblingElement(name)) {
1273  elementList.push_back(child);
1274  }
1275  return elementList;
1276 }
1277 
1278 const vector<TiXmlElement*> DAEReader::getChildren(TiXmlElement* parent)
1279 {
1280  vector<TiXmlElement*> elementList;
1281  for (auto *child = parent->FirstChildElement(); child != nullptr; child = child->NextSiblingElement()) {
1282  elementList.push_back(child);
1283  }
1284  return elementList;
1285 }
#define AVOID_NULLPTR_STRING(arg)
Definition: DAEReader.cpp:46
Color 4 definition class.
Definition: Color4.h:18
Texture entity.
Definition: Texture.h:24
Transform which contain scale, rotations and translation.
Definition: Transform.h:29
Collada DAE model reader.
Definition: DAEReader.h:39
static Node * readVisualSceneNode(const string &pathName, Model *model, Node *parentNode, TiXmlElement *xmlRoot, TiXmlElement *xmlNode, float fps, bool useBC7TextureCompression)
Read a DAE visual scene node.
Definition: DAEReader.cpp:255
static void readGeometry(const string &pathName, Model *model, Node *node, TiXmlElement *xmlRoot, const string &xmlNodeId, const map< string, string > &materialSymbols, bool useBC7TextureCompression)
Reads a geometry.
Definition: DAEReader.cpp:680
static Node * readNode(const string &pathName, Model *model, Node *parentNode, TiXmlElement *xmlRoot, TiXmlElement *xmlNode, float fps, bool useBC7TextureCompression)
Reads a DAE visual scene node node.
Definition: DAEReader.cpp:265
static const vector< TiXmlElement * > getChildren(TiXmlElement *parent)
Returns immediate children tags.
Definition: DAEReader.cpp:1278
static constexpr float BLENDER_AMBIENT_FROM_DIFFUSE_SCALE
Definition: DAEReader.h:43
static Node * readVisualSceneInstanceController(const string &pathName, Model *model, Node *parentNode, TiXmlElement *xmlRoot, TiXmlElement *xmlNode, bool useBC7TextureCompression)
Reads a instance controller.
Definition: DAEReader.cpp:475
static constexpr float BLENDER_DIFFUSE_SCALE
Definition: DAEReader.h:44
static UpVector * getUpVector(TiXmlElement *xmlRoot)
Get Up vector.
Definition: DAEReader.cpp:199
static void setupModelImportRotationMatrix(TiXmlElement *xmlRoot, Model *model)
Set up model import rotation matrix.
Definition: DAEReader.cpp:221
static const string makeFileNameRelative(const string &fileName)
Make file name relative.
Definition: DAEReader.cpp:1235
static const string getTextureFileNameById(TiXmlElement *xmlRoot, const string &xmlTextureId)
Get texture file name by id.
Definition: DAEReader.cpp:1253
static STATIC_DLL_IMPEXT const Color4 BLENDER_AMBIENT_NONE
Definition: DAEReader.h:42
static void setupModelImportScaleMatrix(TiXmlElement *xmlRoot, Model *model)
Set up model import scale matrix.
Definition: DAEReader.cpp:241
static Material * readMaterial(const string &pathName, Model *model, TiXmlElement *xmlRoot, const string &xmlNodeId, bool useBC7TextureCompression)
Reads a material.
Definition: DAEReader.cpp:942
static const vector< TiXmlElement * > getChildrenByTagName(TiXmlElement *parent, const char *name)
Returns immediate children tags by tag name.
Definition: DAEReader.cpp:1269
static Model::AuthoringTool getAuthoringTool(TiXmlElement *xmlRoot)
Get authoring tool.
Definition: DAEReader.cpp:185
Represents a model face, consisting of vertex, normal, tangent and bitangent vectors,...
Definition: Face.h:18
Node faces entity A node can have multiple entities containing faces and a applied material.
Definition: FacesEntity.h:23
void setMaterial(Material *material)
Set up the entity's material.
Definition: FacesEntity.h:67
void setFaces(const vector< Face > &faces)
Set up entity's faces.
Definition: FacesEntity.cpp:43
Joint / Bone.
Definition: Joint.h:19
Represents a material.
Definition: Material.h:23
Representation of a 3D model.
Definition: Model.h:35
unordered_map< string, Material * > & getMaterials()
Returns all object materials.
Definition: Model.h:185
void setImportTransformMatrix(const Matrix4x4 &importTransformMatrix)
Set import transform matrix.
Definition: Model.h:348
AuthoringTool getAuthoringTool()
Definition: Model.h:114
const Matrix4x4 & getImportTransformMatrix()
Definition: Model.h:340
unordered_map< string, Node * > & getNodes()
Returns all object's nodes.
Definition: Model.h:199
Model node.
Definition: Node.h:32
void setVertices(const vector< Vector3 > &vertices)
Set vertices.
Definition: Node.cpp:48
void setFacesEntities(const vector< FacesEntity > &facesEntities)
Set up faces entities.
Definition: Node.cpp:121
void setTextureCoordinates(const vector< Vector2 > &textureCoordinates)
Set texture coordinates.
Definition: Node.cpp:68
const vector< Vector3 > & getVertices() const
Definition: Node.h:155
const vector< Vector3 > & getNormals() const
Definition: Node.h:177
const vector< Vector2 > & getTextureCoordinates() const
Definition: Node.h:190
void setNormals(const vector< Vector3 > &normals)
Set normals.
Definition: Node.cpp:58
const vector< FacesEntity > & getFacesEntities() const
Definition: Node.h:260
Represents rotation orders of a model.
Definition: RotationOrder.h:23
Skinning definition for nodes.
Definition: Skinning.h:25
Represents specular material properties.
Model up vector.
Definition: UpVector.h:20
Standard math functions.
Definition: Math.h:19
Matrix4x4 class representing matrix4x4 mathematical structure and operations for 3d space.
Definition: Matrix4x4.h:23
Matrix4x4 clone() const
Clones this matrix.
Definition: Matrix4x4.h:619
Matrix4x4 & scale(float scalar)
Scales by scalar.
Definition: Matrix4x4.h:183
Vector3 multiply(const Vector3 &vector3) const
Multiplies this matrix with vector3.
Definition: Matrix4x4.h:225
Matrix4x4 & set(float r0c0, float r0c1, float r0c2, float r0c3, float r1c0, float r1c1, float r1c2, float r1c3, float r2c0, float r2c1, float r2c2, float r2c3, float r3c0, float r3c1, float r3c2, float r3c3)
Sets this matrix by its components.
Definition: Matrix4x4.h:108
Matrix4x4 & transpose()
Transposes this matrix.
Definition: Matrix4x4.h:453
Matrix4x4 & setAxes(const Vector3 &xAxis, const Vector3 &yAxis, const Vector3 &zAxis)
Set coordinate system axes.
Definition: Matrix4x4.h:334
Vector3 class representing vector3 mathematical structure and operations with x, y,...
Definition: Vector3.h:20
File system singleton class.
Definition: FileSystem.h:17
Console class.
Definition: Console.h:29
Float class.
Definition: Float.h:27
Integer class.
Definition: Integer.h:25
Model tools functions class.
Definition: ModelTools.h:42
String tokenizer class.
void tokenize(const string &str, const string &delimiters, bool emptyTokens=false)
Tokenize.
String tools class.
Definition: StringTools.h:22
An attribute is a name-value pair.
Definition: tinyxml.h:734
Always the top level node.
Definition: tinyxml.h:1317
virtual const char * Parse(const char *p, TiXmlParsingData *data=0, TiXmlEncoding encoding=TIXML_DEFAULT_ENCODING)
Parse the given null terminated block of xml data.
const char * ErrorDesc() const
Contains a textual (english) description of the error if one occurs.
Definition: tinyxml.h:1382
const TiXmlElement * RootElement() const
Get the root element – the only top level element – of the document.
Definition: tinyxml.h:1371
bool Error() const
If an error occurs, Error will be set to true.
Definition: tinyxml.h:1379
The element is a container class.
Definition: tinyxml.h:886
const char * Attribute(const char *name) const
Given an attribute name, Attribute() returns the value for the attribute of that name,...
Definition: tinyxml.cpp:564
const TiXmlElement * NextSiblingElement() const
Convenience function to get through elements.
Definition: tinyxml.cpp:471
const TiXmlElement * FirstChildElement() const
Convenience function to get through elements.
Definition: tinyxml.cpp:441
std::exception Exception
Exception base class.
Definition: Exception.h:18