Panda3D
assimpLoader.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file assimpLoader.cxx
10  * @author rdb
11  * @date 2011-03-29
12  */
13 
14 #include "assimpLoader.h"
15 
16 #include "geomNode.h"
17 #include "luse.h"
18 #include "geomVertexWriter.h"
19 #include "geomPoints.h"
20 #include "geomLines.h"
21 #include "geomTriangles.h"
22 #include "pnmFileTypeRegistry.h"
23 #include "pnmImage.h"
24 #include "materialAttrib.h"
25 #include "textureAttrib.h"
26 #include "cullFaceAttrib.h"
27 #include "ambientLight.h"
28 #include "directionalLight.h"
29 #include "spotlight.h"
30 #include "pointLight.h"
31 #include "look_at.h"
32 #include "texturePool.h"
33 #include "character.h"
34 #include "animBundle.h"
35 #include "animBundleNode.h"
37 #include "pvector.h"
38 
39 #include "pandaIOSystem.h"
40 #include "pandaLogger.h"
41 
42 #include <assimp/postprocess.h>
43 
44 using std::ostringstream;
45 using std::stringstream;
46 using std::string;
47 
48 struct BoneWeight {
49  CPT(JointVertexTransform) joint_vertex_xform;
50  float weight;
51 
52  BoneWeight(CPT(JointVertexTransform) joint_vertex_xform, float weight)
53  : joint_vertex_xform(joint_vertex_xform), weight(weight)
54  {}
55 };
57 
58 /**
59  *
60  */
61 AssimpLoader::
62 AssimpLoader() :
63  _error (false),
64  _geoms (nullptr) {
65 
67  _importer.SetIOHandler(new PandaIOSystem);
68 }
69 
70 /**
71  *
72  */
73 AssimpLoader::
74 ~AssimpLoader() {
75  _importer.FreeScene();
76 }
77 
78 /**
79  * Returns a space-separated list of extensions that Assimp can load, without
80  * the leading dots.
81  */
82 void AssimpLoader::
83 get_extensions(string &ext) const {
84  aiString aexts;
85  _importer.GetExtensionList(aexts);
86 
87  // The format is like: *.mdc;*.mdl;*.mesh.xml;*.mot
88  char *sub = strtok(aexts.data, ";");
89  while (sub != nullptr) {
90  ext += sub + 2;
91  sub = strtok(nullptr, ";");
92 
93  if (sub != nullptr) {
94  ext += ' ';
95  }
96  }
97 }
98 
99 /**
100  * Reads from the indicated file.
101  */
102 bool AssimpLoader::
103 read(const Filename &filename) {
104  _filename = filename;
105 
106  unsigned int flags = aiProcess_Triangulate | aiProcess_GenUVCoords;
107 
108  if (assimp_calc_tangent_space) {
109  flags |= aiProcess_CalcTangentSpace;
110  }
111  if (assimp_join_identical_vertices) {
112  flags |= aiProcess_JoinIdenticalVertices;
113  }
114  if (assimp_improve_cache_locality) {
115  flags |= aiProcess_ImproveCacheLocality;
116  }
117  if (assimp_remove_redundant_materials) {
118  flags |= aiProcess_RemoveRedundantMaterials;
119  }
120  if (assimp_fix_infacing_normals) {
121  flags |= aiProcess_FixInfacingNormals;
122  }
123  if (assimp_optimize_meshes) {
124  flags |= aiProcess_OptimizeMeshes;
125  }
126  if (assimp_optimize_graph) {
127  flags |= aiProcess_OptimizeGraph;
128  }
129  if (assimp_flip_winding_order) {
130  flags |= aiProcess_FlipWindingOrder;
131  }
132 
133  _scene = _importer.ReadFile(_filename.c_str(), flags);
134  if (_scene == nullptr) {
135  _error = true;
136  return false;
137  }
138 
139  _error = false;
140  return true;
141 }
142 
143 /**
144  * Converts scene graph structures into a Panda3D scene graph, with _root
145  * being the root node.
146  */
147 void AssimpLoader::
149  nassertv(_scene != nullptr); // read() must be called first
150  nassertv(!_error); // and have succeeded
151 
152  // Protect the import process
153  MutexHolder holder(_lock);
154 
155  _root = new ModelRoot(_filename.get_basename());
156 
157  // Import all of the embedded textures first.
158  _textures = new PT(Texture)[_scene->mNumTextures];
159  for (size_t i = 0; i < _scene->mNumTextures; ++i) {
160  load_texture(i);
161  }
162 
163  // Then the materials.
164  _mat_states = new CPT(RenderState)[_scene->mNumMaterials];
165  for (size_t i = 0; i < _scene->mNumMaterials; ++i) {
166  load_material(i);
167  }
168 
169  // And then the meshes.
170  _geoms = new PT(Geom)[_scene->mNumMeshes];
171  _geom_matindices = new unsigned int[_scene->mNumMeshes];
172  for (size_t i = 0; i < _scene->mNumMeshes; ++i) {
173  load_mesh(i);
174  }
175 
176  // And now the node structure.
177  if (_scene->mRootNode != nullptr) {
178  load_node(*_scene->mRootNode, _root);
179  }
180 
181  // And lastly, the lights.
182  for (size_t i = 0; i < _scene->mNumLights; ++i) {
183  load_light(*_scene->mLights[i]);
184  }
185 
186  delete[] _textures;
187  delete[] _mat_states;
188  delete[] _geoms;
189  delete[] _geom_matindices;
190 }
191 
192 /**
193  * Finds a node by name.
194  */
195 const aiNode *AssimpLoader::
196 find_node(const aiNode &root, const aiString &name) {
197  const aiNode *node;
198 
199  if (root.mName == name) {
200  return &root;
201  } else {
202  for (size_t i = 0; i < root.mNumChildren; ++i) {
203  node = find_node(*root.mChildren[i], name);
204  if (node) {
205  return node;
206  }
207  }
208  }
209 
210  return nullptr;
211 }
212 
213 /**
214  * Converts an aiTexture into a Texture.
215  */
216 void AssimpLoader::
217 load_texture(size_t index) {
218  const aiTexture &tex = *_scene->mTextures[index];
219 
220  PT(Texture) ptex = new Texture;
221 
222  if (tex.mHeight == 0) {
223  // Compressed texture.
224  assimp_cat.debug()
225  << "Reading embedded compressed texture with format " << tex.achFormatHint << " and size " << tex.mWidth << "\n";
226  stringstream str;
227  str.write((char*) tex.pcData, tex.mWidth);
228 
229  if (strncmp(tex.achFormatHint, "dds", 3) == 0) {
230  ptex->read_dds(str);
231 
232  } else {
234  PNMFileType *ftype;
235  PNMImage img;
236 
237  // Work around a bug in Assimp, it sometimes writes jp instead of jpg
238  if (strncmp(tex.achFormatHint, "jp\0", 3) == 0) {
239  ftype = reg->get_type_from_extension("jpg");
240  } else {
241  ftype = reg->get_type_from_extension(tex.achFormatHint);
242  }
243 
244  if (img.read(str, "", ftype)) {
245  ptex->load(img);
246  } else {
247  ptex = nullptr;
248  }
249  }
250  } else {
251  assimp_cat.debug()
252  << "Reading embedded raw texture with size " << tex.mWidth << "x" << tex.mHeight << "\n";
253 
254  ptex->setup_2d_texture(tex.mWidth, tex.mHeight, Texture::T_unsigned_byte, Texture::F_rgba);
255  PTA_uchar data = ptex->modify_ram_image();
256 
257  size_t p = 0;
258  for (size_t i = 0; i < tex.mWidth * tex.mHeight; ++i) {
259  const aiTexel &texel = tex.pcData[i];
260  data[p++] = texel.b;
261  data[p++] = texel.g;
262  data[p++] = texel.r;
263  data[p++] = texel.a;
264  }
265  }
266 
267  // ostringstream path; path << "tmp" << index << ".png";
268  // ptex->write(path.str());
269 
270  _textures[index] = ptex;
271 
272 }
273 
274 /**
275  * Converts an aiMaterial into a RenderState.
276  */
277 void AssimpLoader::
278 load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, CPT(TextureAttrib) &tattr) {
279  aiString path;
280  aiTextureMapping mapping;
281  unsigned int uvindex;
282  float blend;
283  aiTextureOp op;
284  aiTextureMapMode mapmode;
285 
286  for (size_t i = 0; i < mat.GetTextureCount(ttype); ++i) {
287  mat.GetTexture(ttype, i, &path, &mapping, nullptr, &blend, &op, &mapmode);
288 
289  if (AI_SUCCESS != mat.Get(AI_MATKEY_UVWSRC(ttype, i), uvindex)) {
290  // If there's no texture coordinate set for this texture, assume that
291  // it's the same as the index on the stack. TODO: if there's only one
292  // set on the mesh, force everything to use just the first stage.
293  uvindex = i;
294  }
295 
296  stringstream str;
297  str << uvindex;
298  PT(TextureStage) stage = new TextureStage(str.str());
299  if (uvindex > 0) {
300  stage->set_texcoord_name(InternalName::get_texcoord_name(str.str()));
301  }
302  PT(Texture) ptex = nullptr;
303 
304  // I'm not sure if this is the right way to handle it, as I couldn't find
305  // much information on embedded textures.
306  if (path.data[0] == '*') {
307  long num = strtol(path.data + 1, nullptr, 10);
308  ptex = _textures[num];
309 
310  } else if (path.length > 0) {
311  Filename fn = Filename::from_os_specific(string(path.data, path.length));
312 
313  // Try to find the file by moving up twice in the hierarchy.
315  Filename dir (_filename);
316  _filename.make_canonical();
317  dir = _filename.get_dirname();
318 
319  // Quake 3 BSP doesn't specify an extension for textures.
320  if (vfs->is_regular_file(Filename(dir, fn))) {
321  fn = Filename(dir, fn);
322  } else if (vfs->is_regular_file(Filename(dir, fn + ".tga"))) {
323  fn = Filename(dir, fn + ".tga");
324  } else if (vfs->is_regular_file(Filename(dir, fn + ".jpg"))) {
325  fn = Filename(dir, fn + ".jpg");
326  } else {
327  dir = _filename.get_dirname();
328  if (vfs->is_regular_file(Filename(dir, fn))) {
329  fn = Filename(dir, fn);
330  } else if (vfs->is_regular_file(Filename(dir, fn + ".tga"))) {
331  fn = Filename(dir, fn + ".tga");
332  } else if (vfs->is_regular_file(Filename(dir, fn + ".jpg"))) {
333  fn = Filename(dir, fn + ".jpg");
334  }
335  }
336 
337  ptex = TexturePool::load_texture(fn);
338  }
339 
340  if (ptex != nullptr) {
341  tattr = DCAST(TextureAttrib, tattr->add_on_stage(stage, ptex));
342  }
343  }
344 }
345 
346 /**
347  * Converts an aiMaterial into a RenderState.
348  */
349 void AssimpLoader::
350 load_material(size_t index) {
351  const aiMaterial &mat = *_scene->mMaterials[index];
352 
353  CPT(RenderState) state = RenderState::make_empty();
354 
355  aiColor3D col;
356  bool have;
357  int ival;
358  PN_stdfloat fval;
359 
360  // XXX a lot of this is untested.
361 
362  // First do the material attribute.
363  PT(Material) pmat = new Material;
364  have = false;
365  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_DIFFUSE, col)) {
366  pmat->set_diffuse(LColor(col.r, col.g, col.b, 1));
367  have = true;
368  }
369  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_SPECULAR, col)) {
370  if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS_STRENGTH, fval)) {
371  pmat->set_specular(LColor(col.r * fval, col.g * fval, col.b * fval, 1));
372  } else {
373  pmat->set_specular(LColor(col.r, col.g, col.b, 1));
374  }
375  have = true;
376  }
377  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_AMBIENT, col)) {
378  pmat->set_specular(LColor(col.r, col.g, col.b, 1));
379  have = true;
380  }
381  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_EMISSIVE, col)) {
382  pmat->set_emission(LColor(col.r, col.g, col.b, 1));
383  have = true;
384  }
385  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_TRANSPARENT, col)) {
386  // FIXME: ???
387  }
388  if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS, fval)) {
389  pmat->set_shininess(fval);
390  have = true;
391  }
392  if (have) {
393  state = state->add_attrib(MaterialAttrib::make(pmat));
394  }
395 
396  // Wireframe.
397  if (AI_SUCCESS == mat.Get(AI_MATKEY_ENABLE_WIREFRAME, ival)) {
398  if (ival) {
399  state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_wireframe));
400  } else {
401  state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_filled));
402  }
403  }
404 
405  // Backface culling. Not sure if this is also supposed to set the twoside
406  // flag in the material, I'm guessing not.
407  if (AI_SUCCESS == mat.Get(AI_MATKEY_TWOSIDED, ival)) {
408  if (ival) {
409  state = state->add_attrib(CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
410  } else {
411  state = state->add_attrib(CullFaceAttrib::make_default());
412  }
413  }
414 
415  // And let's not forget the textures!
416  CPT(TextureAttrib) tattr = DCAST(TextureAttrib, TextureAttrib::make());
417  load_texture_stage(mat, aiTextureType_DIFFUSE, tattr);
418  load_texture_stage(mat, aiTextureType_LIGHTMAP, tattr);
419  if (tattr->get_num_on_stages() > 0) {
420  state = state->add_attrib(tattr);
421  }
422 
423  _mat_states[index] = state;
424 }
425 
426 /**
427  * Creates a CharacterJoint from an aiNode
428  */
429 void AssimpLoader::
430 create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node) {
431  const aiMatrix4x4 &t = node.mTransformation;
432  LMatrix4 mat(t.a1, t.b1, t.c1, t.d1,
433  t.a2, t.b2, t.c2, t.d2,
434  t.a3, t.b3, t.c3, t.d3,
435  t.a4, t.b4, t.c4, t.d4);
436  PT(CharacterJoint) joint = new CharacterJoint(character, bundle, parent, node.mName.C_Str(), mat);
437 
438  assimp_cat.debug()
439  << "Creating joint for: " << node.mName.C_Str() << "\n";
440 
441  for (size_t i = 0; i < node.mNumChildren; ++i) {
442  if (_bonemap.find(node.mChildren[i]->mName.C_Str()) != _bonemap.end()) {
443  create_joint(character, bundle, joint, *node.mChildren[i]);
444  }
445  }
446 }
447 
448 /**
449  * Creates a AnimChannelMatrixXfmTable from an aiNodeAnim
450  */
451 void AssimpLoader::
452 create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node) {
453  PT(AnimChannelMatrixXfmTable) group = new AnimChannelMatrixXfmTable(parent, node.mName.C_Str());
454 
455  // See if there is a channel for this node
456  aiNodeAnim *node_anim = nullptr;
457  for (size_t i = 0; i < anim.mNumChannels; ++i) {
458  if (anim.mChannels[i]->mNodeName == node.mName) {
459  node_anim = anim.mChannels[i];
460  }
461  }
462 
463  if (node_anim) {
464  assimp_cat.debug()
465  << "Found channel for node: " << node.mName.C_Str() << "\n";
466  // assimp_cat.debug() << "Num Position Keys " <<
467  // node_anim->mNumPositionKeys << "\n"; assimp_cat.debug() << "Num
468  // Rotation Keys " << node_anim->mNumRotationKeys << "\n";
469  // assimp_cat.debug() << "Num Scaling Keys " << node_anim->mNumScalingKeys
470  // << "\n";
471 
472  // Convert positions
473  PTA_stdfloat tablex = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
474  PTA_stdfloat tabley = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
475  PTA_stdfloat tablez = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
476  for (size_t i = 0; i < node_anim->mNumPositionKeys; ++i) {
477  tablex[i] = node_anim->mPositionKeys[i].mValue.x;
478  tabley[i] = node_anim->mPositionKeys[i].mValue.y;
479  tablez[i] = node_anim->mPositionKeys[i].mValue.z;
480  }
481  group->set_table('x', tablex);
482  group->set_table('y', tabley);
483  group->set_table('z', tablez);
484 
485  // Convert rotations
486  PTA_stdfloat tableh = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
487  PTA_stdfloat tablep = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
488  PTA_stdfloat tabler = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
489  for (size_t i = 0; i < node_anim->mNumRotationKeys; ++i) {
490  aiQuaternion ai_quat = node_anim->mRotationKeys[i].mValue;
491  LVecBase3 hpr = LQuaternion(ai_quat.w, ai_quat.x, ai_quat.y, ai_quat.z).get_hpr();
492  tableh[i] = hpr.get_x();
493  tablep[i] = hpr.get_y();
494  tabler[i] = hpr.get_z();
495  }
496  group->set_table('h', tableh);
497  group->set_table('p', tablep);
498  group->set_table('r', tabler);
499 
500  // Convert scales
501  PTA_stdfloat tablei = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
502  PTA_stdfloat tablej = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
503  PTA_stdfloat tablek = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
504  for (size_t i = 0; i < node_anim->mNumScalingKeys; ++i) {
505  tablei[i] = node_anim->mScalingKeys[i].mValue.x;
506  tablej[i] = node_anim->mScalingKeys[i].mValue.y;
507  tablek[i] = node_anim->mScalingKeys[i].mValue.z;
508  }
509  group->set_table('i', tablei);
510  group->set_table('j', tablej);
511  group->set_table('k', tablek);
512  }
513  else {
514  assimp_cat.debug()
515  << "No channel found for node: " << node.mName.C_Str() << "\n";
516  }
517 
518 
519  for (size_t i = 0; i < node.mNumChildren; ++i) {
520  if (_bonemap.find(node.mChildren[i]->mName.C_Str()) != _bonemap.end()) {
521  create_anim_channel(anim, bundle, group, *node.mChildren[i]);
522  }
523  }
524 }
525 
526 /**
527  * Converts an aiMesh into a Geom.
528  */
529 void AssimpLoader::
530 load_mesh(size_t index) {
531  const aiMesh &mesh = *_scene->mMeshes[index];
532 
533  // Check if we need to make a Character
534  PT(Character) character = nullptr;
535  if (mesh.HasBones()) {
536  assimp_cat.debug()
537  << "Creating character for " << mesh.mName.C_Str() << "\n";
538 
539  // Find and add all bone nodes to the bone map
540  for (size_t i = 0; i < mesh.mNumBones; ++i) {
541  const aiBone &bone = *mesh.mBones[i];
542  const aiNode *node = find_node(*_scene->mRootNode, bone.mName);
543  _bonemap[bone.mName.C_Str()] = node;
544  }
545 
546  // Now create a character from the bones
547  character = new Character(mesh.mName.C_Str());
548  PT(CharacterJointBundle) bundle = character->get_bundle(0);
549  PT(PartGroup) skeleton = new PartGroup(bundle, "<skeleton>");
550 
551  for (size_t i = 0; i < mesh.mNumBones; ++i) {
552  const aiBone &bone = *mesh.mBones[i];
553 
554  // Find the root bone node
555  const aiNode *root = _bonemap[bone.mName.C_Str()];
556  while (root->mParent && _bonemap.find(root->mParent->mName.C_Str()) != _bonemap.end()) {
557  root = root->mParent;
558  }
559 
560  // Don't process this root if we already have a joint for it
561  if (character->find_joint(root->mName.C_Str())) {
562  continue;
563  }
564 
565  create_joint(character, bundle, skeleton, *root);
566  }
567  }
568 
569  // Create transform blend table
570  PT(TransformBlendTable) tbtable = new TransformBlendTable;
571  pvector<BoneWeightList> bone_weights(mesh.mNumVertices);
572  if (character) {
573  for (size_t i = 0; i < mesh.mNumBones; ++i) {
574  const aiBone &bone = *mesh.mBones[i];
575  CharacterJoint *joint = character->find_joint(bone.mName.C_Str());
576  if (joint == nullptr) {
577  assimp_cat.debug()
578  << "Could not find joint for bone: " << bone.mName.C_Str() << "\n";
579  continue;
580  }
581 
582  CPT(JointVertexTransform) jvt = new JointVertexTransform(joint);
583 
584  for (size_t j = 0; j < bone.mNumWeights; ++j) {
585  const aiVertexWeight &weight = bone.mWeights[j];
586 
587  bone_weights[weight.mVertexId].push_back(BoneWeight(jvt, weight.mWeight));
588  }
589  }
590  }
591 
592  // Create the vertex format.
594  aformat->add_column(InternalName::get_vertex(), 3, Geom::NT_stdfloat, Geom::C_point);
595  if (mesh.HasNormals()) {
596  aformat->add_column(InternalName::get_normal(), 3, Geom::NT_stdfloat, Geom::C_vector);
597  }
598  if (mesh.HasVertexColors(0)) {
599  aformat->add_column(InternalName::get_color(), 4, Geom::NT_stdfloat, Geom::C_color);
600  }
601  unsigned int num_uvs = mesh.GetNumUVChannels();
602  if (num_uvs > 0) {
603  // UV sets are named texcoord, texcoord.1, texcoord.2...
604  aformat->add_column(InternalName::get_texcoord(), 3, Geom::NT_stdfloat, Geom::C_texcoord);
605  for (unsigned int u = 1; u < num_uvs; ++u) {
606  ostringstream out;
607  out << u;
608  aformat->add_column(InternalName::get_texcoord_name(out.str()), 3, Geom::NT_stdfloat, Geom::C_texcoord);
609  }
610  }
611 
612  PT(GeomVertexArrayFormat) tb_aformat = new GeomVertexArrayFormat;
613  tb_aformat->add_column(InternalName::make("transform_blend"), 1, Geom::NT_uint16, Geom::C_index);
614 
615  // Check to see if we need to convert any animations
616  for (size_t i = 0; i < _scene->mNumAnimations; ++i) {
617  aiAnimation &ai_anim = *_scene->mAnimations[i];
618  bool convert_anim = false;
619 
620  assimp_cat.debug()
621  << "Checking to see if anim (" << ai_anim.mName.C_Str() << ") matches character (" << mesh.mName.C_Str() << ")\n";
622  for (size_t j = 0; j < ai_anim.mNumChannels; ++j) {
623  assimp_cat.debug()
624  << "Searching for " << ai_anim.mChannels[j]->mNodeName.C_Str() << " in bone map" << "\n";
625  if (_bonemap.find(ai_anim.mChannels[j]->mNodeName.C_Str()) != _bonemap.end()) {
626  convert_anim = true;
627  break;
628  }
629  }
630 
631  if (convert_anim) {
632  assimp_cat.debug()
633  << "Found animation (" << ai_anim.mName.C_Str() << ") for character (" << mesh.mName.C_Str() << ")\n";
634 
635  // Now create the animation
636  unsigned int frames = 0;
637  for (size_t j = 0; j < ai_anim.mNumChannels; ++j) {
638  if (ai_anim.mChannels[j]->mNumPositionKeys > frames) {
639  frames = ai_anim.mChannels[j]->mNumPositionKeys;
640  }
641  if (ai_anim.mChannels[j]->mNumRotationKeys > frames) {
642  frames = ai_anim.mChannels[j]->mNumRotationKeys;
643  }
644  if (ai_anim.mChannels[j]->mNumScalingKeys > frames) {
645  frames = ai_anim.mChannels[j]->mNumScalingKeys;
646  }
647  }
648  PN_stdfloat fps = frames / (ai_anim.mTicksPerSecond * ai_anim.mDuration);
649  assimp_cat.debug()
650  << "FPS " << fps << "\n";
651  assimp_cat.debug()
652  << "Frames " << frames << "\n";
653 
654  PT(AnimBundle) bundle = new AnimBundle(mesh.mName.C_Str(), fps, frames);
655  PT(AnimGroup) skeleton = new AnimGroup(bundle, "<skeleton>");
656 
657  for (size_t i = 0; i < mesh.mNumBones; ++i) {
658  const aiBone &bone = *mesh.mBones[i];
659 
660  // Find the root bone node
661  const aiNode *root = _bonemap[bone.mName.C_Str()];
662  while (root->mParent && _bonemap.find(root->mParent->mName.C_Str()) != _bonemap.end()) {
663  root = root->mParent;
664  }
665 
666  // Only convert root nodes
667  if (root->mName == bone.mName) {
668  create_anim_channel(ai_anim, bundle, skeleton, *root);
669 
670  // Attach the animation to the character node
671  PT(AnimBundleNode) bundle_node = new AnimBundleNode(bone.mName.C_Str(), bundle);
672  character->add_child(bundle_node);
673  }
674  }
675  }
676  }
677 
678  // TODO: if there is only one UV set, hackily iterate over the texture
679  // stages and clear the texcoord name things
680 
681  PT(GeomVertexFormat) format = new GeomVertexFormat;
682  format->add_array(aformat);
683  if (character) {
684  format->add_array(tb_aformat);
685 
687  aspec.set_panda();
688  format->set_animation(aspec);
689  }
690 
691  // Create the GeomVertexData.
692  string name (mesh.mName.data, mesh.mName.length);
693  PT(GeomVertexData) vdata = new GeomVertexData(name, GeomVertexFormat::register_format(format), Geom::UH_static);
694  if (character) {
695  vdata->set_transform_blend_table(tbtable);
696  }
697  vdata->unclean_set_num_rows(mesh.mNumVertices);
698 
699  // Read out the vertices.
700  GeomVertexWriter vertex (vdata, InternalName::get_vertex());
701  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
702  const aiVector3D &vec = mesh.mVertices[i];
703  vertex.add_data3(vec.x, vec.y, vec.z);
704  }
705 
706  // Now the normals, if any.
707  if (mesh.HasNormals()) {
708  GeomVertexWriter normal (vdata, InternalName::get_normal());
709  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
710  const aiVector3D &vec = mesh.mNormals[i];
711  normal.add_data3(vec.x, vec.y, vec.z);
712  }
713  }
714 
715  // Vertex colors, if any. We only import the first set.
716  if (mesh.HasVertexColors(0)) {
717  GeomVertexWriter color (vdata, InternalName::get_color());
718  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
719  const aiColor4D &col = mesh.mColors[0][i];
720  color.add_data4(col.r, col.g, col.b, col.a);
721  }
722  }
723 
724  // Now the texture coordinates.
725  if (num_uvs > 0) {
726  // UV sets are named texcoord, texcoord.1, texcoord.2...
727  GeomVertexWriter texcoord0 (vdata, InternalName::get_texcoord());
728  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
729  const aiVector3D &vec = mesh.mTextureCoords[0][i];
730  texcoord0.add_data3(vec.x, vec.y, vec.z);
731  }
732  for (unsigned int u = 1; u < num_uvs; ++u) {
733  ostringstream out;
734  out << u;
735  GeomVertexWriter texcoord (vdata, InternalName::get_texcoord_name(out.str()));
736  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
737  const aiVector3D &vec = mesh.mTextureCoords[u][i];
738  texcoord.add_data3(vec.x, vec.y, vec.z);
739  }
740  }
741  }
742 
743  // Now the transform blend table
744  if (character) {
745  GeomVertexWriter transform_blend (vdata, InternalName::get_transform_blend());
746 
747  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
748  TransformBlend tblend;
749 
750  for (size_t j = 0; j < bone_weights[i].size(); ++j) {
751  tblend.add_transform(bone_weights[i][j].joint_vertex_xform, bone_weights[i][j].weight);
752  }
753  transform_blend.add_data1i(tbtable->add_blend(tblend));
754  }
755 
756  tbtable->set_rows(SparseArray::lower_on(vdata->get_num_rows()));
757  }
758 
759  // Now read out the primitives. Keep in mind that we called ReadFile with
760  // the aiProcess_Triangulate flag earlier, so we don't have to worry about
761  // polygons.
762  PT(GeomPoints) points = new GeomPoints(Geom::UH_static);
763  PT(GeomLines) lines = new GeomLines(Geom::UH_static);
764  PT(GeomTriangles) triangles = new GeomTriangles(Geom::UH_static);
765 
766  // Now add the vertex indices.
767  for (size_t i = 0; i < mesh.mNumFaces; ++i) {
768  const aiFace &face = mesh.mFaces[i];
769 
770  if (face.mNumIndices == 0) {
771  // It happens, strangely enough.
772  continue;
773  } else if (face.mNumIndices == 1) {
774  points->add_vertex(face.mIndices[0]);
775  points->close_primitive();
776  } else if (face.mNumIndices == 2) {
777  lines->add_vertices(face.mIndices[0], face.mIndices[1]);
778  lines->close_primitive();
779  } else if (face.mNumIndices == 3) {
780  triangles->add_vertices(face.mIndices[0], face.mIndices[1], face.mIndices[2]);
781  triangles->close_primitive();
782  } else {
783  nassertd(false) continue;
784  }
785  }
786 
787  // Create a geom and add the primitives to it.
788  PT(Geom) geom = new Geom(vdata);
789  if (points->get_num_primitives() > 0) {
790  geom->add_primitive(points);
791  }
792  if (lines->get_num_primitives() > 0) {
793  geom->add_primitive(lines);
794  }
795  if (triangles->get_num_primitives() > 0) {
796  geom->add_primitive(triangles);
797  }
798 
799  _geoms[index] = geom;
800  _geom_matindices[index] = mesh.mMaterialIndex;
801 
802  if (character) {
803  _charmap[mesh.mName.C_Str()] = character;
804  }
805 }
806 
807 /**
808  * Converts an aiNode into a PandaNode.
809  */
810 void AssimpLoader::
811 load_node(const aiNode &node, PandaNode *parent) {
812  PT(PandaNode) pnode;
813  PT(Character) character;
814 
815  // Skip nodes we've converted to joints
816  if (_bonemap.find(node.mName.C_Str()) != _bonemap.end()) {
817  return;
818  }
819 
820  // Create the node and give it a name.
821  string name (node.mName.data, node.mName.length);
822  if (node.mNumMeshes > 0) {
823  pnode = new GeomNode(name);
824  } else {
825  pnode = new PandaNode(name);
826  }
827 
828  if (_charmap.find(node.mName.C_Str()) != _charmap.end()) {
829  character = _charmap[node.mName.C_Str()];
830  parent->add_child(character);
831  } else {
832  parent->add_child(pnode);
833  }
834 
835  // Load in the transformation matrix.
836  const aiMatrix4x4 &t = node.mTransformation;
837  if (!t.IsIdentity()) {
838  LMatrix4 mat(t.a1, t.b1, t.c1, t.d1,
839  t.a2, t.b2, t.c2, t.d2,
840  t.a3, t.b3, t.c3, t.d3,
841  t.a4, t.b4, t.c4, t.d4);
842  pnode->set_transform(TransformState::make_mat(mat));
843  }
844 
845  for (size_t i = 0; i < node.mNumChildren; ++i) {
846  load_node(*node.mChildren[i], pnode);
847  }
848 
849  if (node.mNumMeshes > 0) {
850  // Remember, we created this as GeomNode earlier.
851  PT(GeomNode) gnode = DCAST(GeomNode, pnode);
852  size_t meshIndex;
853 
854  // If there's only mesh, don't bother using a per-geom state.
855  if (node.mNumMeshes == 1) {
856  meshIndex = node.mMeshes[0];
857  gnode->add_geom(_geoms[meshIndex]);
858  gnode->set_state(_mat_states[_geom_matindices[meshIndex]]);
859  } else {
860  for (size_t i = 0; i < node.mNumMeshes; ++i) {
861  meshIndex = node.mMeshes[i];
862  gnode->add_geom(_geoms[node.mMeshes[i]],
863  _mat_states[_geom_matindices[meshIndex]]);
864  }
865  }
866 
867  if (character) {
868  assimp_cat.debug() << "Adding char to geom\n";
869  character->add_child(gnode);
870  }
871  }
872 }
873 
874 /**
875  * Converts an aiLight into a LightNode.
876  */
877 void AssimpLoader::
878 load_light(const aiLight &light) {
879  string name (light.mName.data, light.mName.length);
880  assimp_cat.debug() << "Found light '" << name << "'\n";
881 
882  aiColor3D col;
883  aiVector3D vec;
884 
885  switch (light.mType) {
886  case aiLightSource_DIRECTIONAL: {
887  PT(DirectionalLight) dlight = new DirectionalLight(name);
888  _root->add_child(dlight);
889 
890  col = light.mColorDiffuse;
891  dlight->set_color(LColor(col.r, col.g, col.b, 1));
892 
893  col = light.mColorSpecular;
894  dlight->set_specular_color(LColor(col.r, col.g, col.b, 1));
895 
896  vec = light.mPosition;
897  dlight->set_point(LPoint3(vec.x, vec.y, vec.z));
898 
899  vec = light.mDirection;
900  dlight->set_direction(LVector3(vec.x, vec.y, vec.z));
901  break; }
902 
903  case aiLightSource_POINT: {
904  PT(PointLight) plight = new PointLight(name);
905  _root->add_child(plight);
906 
907  col = light.mColorDiffuse;
908  plight->set_color(LColor(col.r, col.g, col.b, 1));
909 
910  col = light.mColorSpecular;
911  plight->set_specular_color(LColor(col.r, col.g, col.b, 1));
912 
913  vec = light.mPosition;
914  plight->set_point(LPoint3(vec.x, vec.y, vec.z));
915 
916  plight->set_attenuation(LVecBase3(light.mAttenuationConstant,
917  light.mAttenuationLinear,
918  light.mAttenuationQuadratic));
919  break; }
920 
921  case aiLightSource_SPOT: {
922  PT(Spotlight) plight = new Spotlight(name);
923  _root->add_child(plight);
924 
925  col = light.mColorDiffuse;
926  plight->set_color(LColor(col.r, col.g, col.b, 1));
927 
928  col = light.mColorSpecular;
929  plight->set_specular_color(LColor(col.r, col.g, col.b, 1));
930 
931  plight->set_attenuation(LVecBase3(light.mAttenuationConstant,
932  light.mAttenuationLinear,
933  light.mAttenuationQuadratic));
934 
935  plight->get_lens()->set_fov(light.mAngleOuterCone);
936  // TODO: translate mAngleInnerCone to an exponent, somehow
937 
938  // This *should* be about right.
939  vec = light.mDirection;
940  LPoint3 pos (light.mPosition.x, light.mPosition.y, light.mPosition.z);
941  LQuaternion quat;
942  ::look_at(quat, LPoint3(vec.x, vec.y, vec.z), LVector3::up());
943  plight->set_transform(TransformState::make_pos_quat_scale(pos, quat, LVecBase3(1, 1, 1)));
944  break; }
945 
946  // This is a somewhat recent addition to Assimp, so let's be kind to those
947  // that don't have an up-to-date version of Assimp.
948  case 0x4: //aiLightSource_AMBIENT:
949  // This is handled below.
950  break;
951 
952  default:
953  assimp_cat.warning() << "Light '" << name << "' has an unknown type!\n";
954  return;
955  }
956 
957  // If there's an ambient color, add it as ambient light.
958  col = light.mColorAmbient;
959  LVecBase4 ambient (col.r, col.g, col.b, 0);
960  if (ambient != LVecBase4::zero()) {
961  PT(AmbientLight) alight = new AmbientLight(name);
962  alight->set_color(ambient);
963  _root->add_child(alight);
964  }
965 }
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A node of this type is created automatically at the root of each model file that is loaded...
Definition: modelRoot.h:27
bool read(const Filename &filename)
Reads from the indicated file.
A light shining from infinitely far away in a particular direction, like sunlight.
std::string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:358
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A basic node of the scene graph or data graph.
Definition: pandaNode.h:64
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
CharacterJoint * find_joint(const std::string &name) const
Returns a pointer to the joint with the given name, if there is such a joint, or NULL if there is no ...
Definition: character.cxx:358
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
The collection of all the joints and sliders in the character.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Defines a series of disconnected points.
Definition: geomPoints.h:23
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An animated character, with skeleton-morph animation and either soft- skinned or hard-skinned vertice...
Definition: character.h:38
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This object describes how the vertex animation, if any, represented in a GeomVertexData is encoded...
get_num_on_stages
Returns the number of stages that are turned on by the attribute.
Definition: textureAttrib.h:55
This is the root of an AnimChannel hierarchy.
Definition: animBundle.h:29
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS&#39;s file system.
void build_graph()
Converts scene graph structures into a Panda3D scene graph, with _root being the root node...
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int add_column(CPT_InternalName name, int num_components, NumericType numeric_type, Contents contents, int start=-1, int column_alignment=0)
Adds a new column to the specification.
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:278
void get_extensions(std::string &ext) const
Returns a space-separated list of extensions that Assimp can load, without the leading dots...
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
Definition: filename.cxx:1011
void add_primitive(const GeomPrimitive *primitive)
Inserts a new GeomPrimitive structure to the Geom object.
Definition: geom.I:116
A light source that seems to illuminate all points in space at once.
Definition: ambientLight.h:26
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
set_diffuse
Specifies the diffuse color setting of the material.
Definition: material.h:116
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
Indicates the set of TextureStages and their associated Textures that should be applied to (or remove...
Definition: textureAttrib.h:31
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_panda()
Specifies that vertex animation is to be performed by Panda.
This is the base class for AnimChannel and AnimBundle.
Definition: animGroup.h:33
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A container for geometry primitives.
Definition: geom.h:54
A light originating from a single point in space, and shining in a particular direction, with a cone-shaped falloff.
Definition: spotlight.h:32
static Texture * load_texture(const Filename &filename, int primary_file_num_channels=0, bool read_mipmaps=false, const LoaderOptions &options=LoaderOptions())
Loads the given filename up into a texture, if it has not already been loaded, and returns the new te...
Definition: texturePool.I:47
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Defines the way an object appears in the presence of lighting.
Definition: material.h:43
PNMFileType * get_type_from_extension(const std::string &filename) const
Tries to determine what the PNMFileType is likely to be for a particular image file based on its exte...
static SparseArray lower_on(int on_bits)
Returns a SparseArray whose lower on_bits bits are on.
Definition: sparseArray.I:43
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
Defines a series of disconnected line segments.
Definition: geomLines.h:23
This defines a single entry in a TransformBlendTable.
This class defines the physical layout of the vertex data stored within a Geom.
This is a specialization on VertexTransform that returns the transform necessary to move vertices as ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class maintains the set of all known PNMFileTypes in the universe.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool is_regular_file(const Filename &filename) const
Convenience function; returns true if the named file exists and is a regular file.
This represents one joint of the character&#39;s animation, containing an animating transform matrix...
set_texcoord_name
Indicate which set of UV&#39;s this texture stage will use.
Definition: textureStage.h:192
This describes the structure of a single array within a Geom data.
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
This structure collects together the different combinations of transforms and blend amounts used by a...
An animation channel that issues a matrix each frame, read from a table such as might have been read ...
size_t add_array(const GeomVertexArrayFormat *array_format)
Adds the indicated array definition to the list of arrays included within this vertex format definiti...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a node that contains a pointer to an AnimBundle.
Defines the properties of a named stage of the multitexture pipeline.
Definition: textureStage.h:35
static void set_default()
Makes sure there&#39;s a global PandaLogger object and makes sure that it is Assimp&#39;s default logger...
Definition: pandaLogger.cxx:25
void add_transform(const VertexTransform *transform, PN_stdfloat weight)
Adds a new transform to the blend.
Custom implementation of Assimp::IOSystem.
Definition: pandaIOSystem.h:25
A light originating from a single point in space, and shining in all directions.
Definition: pointLight.h:25
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
static Filename from_os_specific(const std::string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:328
bool write(const Filename &fullpath)
Writes the texture to the named filename.
Definition: texture.I:294
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the base class for PartRoot and MovingPart.
Definition: partGroup.h:43
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.