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  if (assimp_gen_normals) {
133  if (assimp_smooth_normal_angle == 0.0) {
134  flags |= aiProcess_GenNormals;
135  }
136  else {
137  flags |= aiProcess_GenSmoothNormals;
138  _importer.SetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,
139  assimp_smooth_normal_angle);
140  }
141  }
142 
143  _scene = _importer.ReadFile(_filename.c_str(), flags);
144  if (_scene == nullptr) {
145  _error = true;
146  return false;
147  }
148 
149  _error = false;
150  return true;
151 }
152 
153 /**
154  * Converts scene graph structures into a Panda3D scene graph, with _root
155  * being the root node.
156  */
157 void AssimpLoader::
159  nassertv(_scene != nullptr); // read() must be called first
160  nassertv(!_error); // and have succeeded
161 
162  // Protect the import process
163  MutexHolder holder(_lock);
164 
165  _root = new ModelRoot(_filename.get_basename());
166 
167  // Import all of the embedded textures first.
168  _textures = new PT(Texture)[_scene->mNumTextures];
169  for (size_t i = 0; i < _scene->mNumTextures; ++i) {
170  load_texture(i);
171  }
172 
173  // Then the materials.
174  _mat_states = new CPT(RenderState)[_scene->mNumMaterials];
175  for (size_t i = 0; i < _scene->mNumMaterials; ++i) {
176  load_material(i);
177  }
178 
179  // And then the meshes.
180  _geoms = new PT(Geom)[_scene->mNumMeshes];
181  _geom_matindices = new unsigned int[_scene->mNumMeshes];
182  for (size_t i = 0; i < _scene->mNumMeshes; ++i) {
183  load_mesh(i);
184  }
185 
186  // And now the node structure.
187  if (_scene->mRootNode != nullptr) {
188  load_node(*_scene->mRootNode, _root);
189  }
190 
191  // And lastly, the lights.
192  for (size_t i = 0; i < _scene->mNumLights; ++i) {
193  load_light(*_scene->mLights[i]);
194  }
195 
196  delete[] _textures;
197  delete[] _mat_states;
198  delete[] _geoms;
199  delete[] _geom_matindices;
200 }
201 
202 /**
203  * Finds a node by name.
204  */
205 const aiNode *AssimpLoader::
206 find_node(const aiNode &root, const aiString &name) {
207  const aiNode *node;
208 
209  if (root.mName == name) {
210  return &root;
211  } else {
212  for (size_t i = 0; i < root.mNumChildren; ++i) {
213  node = find_node(*root.mChildren[i], name);
214  if (node) {
215  return node;
216  }
217  }
218  }
219 
220  return nullptr;
221 }
222 
223 /**
224  * Converts an aiTexture into a Texture.
225  */
226 void AssimpLoader::
227 load_texture(size_t index) {
228  const aiTexture &tex = *_scene->mTextures[index];
229 
230  PT(Texture) ptex = new Texture;
231 
232  if (tex.mHeight == 0) {
233  // Compressed texture.
234  assimp_cat.debug()
235  << "Reading embedded compressed texture with format " << tex.achFormatHint << " and size " << tex.mWidth << "\n";
236  stringstream str;
237  str.write((char*) tex.pcData, tex.mWidth);
238 
239  if (strncmp(tex.achFormatHint, "dds", 3) == 0) {
240  ptex->read_dds(str);
241 
242  } else {
244  PNMFileType *ftype;
245  PNMImage img;
246 
247  // Work around a bug in Assimp, it sometimes writes jp instead of jpg
248  if (strncmp(tex.achFormatHint, "jp\0", 3) == 0) {
249  ftype = reg->get_type_from_extension("jpg");
250  } else {
251  ftype = reg->get_type_from_extension(tex.achFormatHint);
252  }
253 
254  if (img.read(str, "", ftype)) {
255  ptex->load(img);
256  } else {
257  ptex = nullptr;
258  }
259  }
260  } else {
261  assimp_cat.debug()
262  << "Reading embedded raw texture with size " << tex.mWidth << "x" << tex.mHeight << "\n";
263 
264  ptex->setup_2d_texture(tex.mWidth, tex.mHeight, Texture::T_unsigned_byte, Texture::F_rgba);
265  PTA_uchar data = ptex->modify_ram_image();
266 
267  size_t p = 0;
268  for (size_t i = 0; i < tex.mWidth * tex.mHeight; ++i) {
269  const aiTexel &texel = tex.pcData[i];
270  data[p++] = texel.b;
271  data[p++] = texel.g;
272  data[p++] = texel.r;
273  data[p++] = texel.a;
274  }
275  }
276 
277  // ostringstream path; path << "tmp" << index << ".png";
278  // ptex->write(path.str());
279 
280  _textures[index] = ptex;
281 
282 }
283 
284 /**
285  * Converts an aiMaterial into a RenderState.
286  */
287 void AssimpLoader::
288 load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype, CPT(TextureAttrib) &tattr) {
289  aiString path;
290  aiTextureMapping mapping;
291  unsigned int uvindex;
292  float blend;
293  aiTextureOp op;
294  aiTextureMapMode mapmode;
295 
296  for (size_t i = 0; i < mat.GetTextureCount(ttype); ++i) {
297  mat.GetTexture(ttype, i, &path, &mapping, nullptr, &blend, &op, &mapmode);
298 
299  if (AI_SUCCESS != mat.Get(AI_MATKEY_UVWSRC(ttype, i), uvindex)) {
300  // If there's no texture coordinate set for this texture, assume that
301  // it's the same as the index on the stack. TODO: if there's only one
302  // set on the mesh, force everything to use just the first stage.
303  uvindex = i;
304  }
305 
306  stringstream str;
307  str << uvindex;
308  PT(TextureStage) stage = new TextureStage(str.str());
309  if (uvindex > 0) {
310  stage->set_texcoord_name(InternalName::get_texcoord_name(str.str()));
311  }
312  PT(Texture) ptex = nullptr;
313 
314  // I'm not sure if this is the right way to handle it, as I couldn't find
315  // much information on embedded textures.
316  if (path.data[0] == '*') {
317  long num = strtol(path.data + 1, nullptr, 10);
318  ptex = _textures[num];
319 
320  } else if (path.length > 0) {
321  Filename fn = Filename::from_os_specific(string(path.data, path.length));
322 
323  // Try to find the file by moving up twice in the hierarchy.
325  Filename dir (_filename);
326  _filename.make_canonical();
327  dir = _filename.get_dirname();
328 
329  // Quake 3 BSP doesn't specify an extension for textures.
330  if (vfs->is_regular_file(Filename(dir, fn))) {
331  fn = Filename(dir, fn);
332  } else if (vfs->is_regular_file(Filename(dir, fn + ".tga"))) {
333  fn = Filename(dir, fn + ".tga");
334  } else if (vfs->is_regular_file(Filename(dir, fn + ".jpg"))) {
335  fn = Filename(dir, fn + ".jpg");
336  } else {
337  dir = _filename.get_dirname();
338  if (vfs->is_regular_file(Filename(dir, fn))) {
339  fn = Filename(dir, fn);
340  } else if (vfs->is_regular_file(Filename(dir, fn + ".tga"))) {
341  fn = Filename(dir, fn + ".tga");
342  } else if (vfs->is_regular_file(Filename(dir, fn + ".jpg"))) {
343  fn = Filename(dir, fn + ".jpg");
344  }
345  }
346 
347  ptex = TexturePool::load_texture(fn);
348  }
349 
350  if (ptex != nullptr) {
351  tattr = DCAST(TextureAttrib, tattr->add_on_stage(stage, ptex));
352  }
353  }
354 }
355 
356 /**
357  * Converts an aiMaterial into a RenderState.
358  */
359 void AssimpLoader::
360 load_material(size_t index) {
361  const aiMaterial &mat = *_scene->mMaterials[index];
362 
363  CPT(RenderState) state = RenderState::make_empty();
364 
365  aiColor3D col;
366  bool have;
367  int ival;
368  PN_stdfloat fval;
369 
370  // XXX a lot of this is untested.
371 
372  // First do the material attribute.
373  PT(Material) pmat = new Material;
374  have = false;
375  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_DIFFUSE, col)) {
376  pmat->set_diffuse(LColor(col.r, col.g, col.b, 1));
377  have = true;
378  }
379  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_SPECULAR, col)) {
380  if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS_STRENGTH, fval)) {
381  pmat->set_specular(LColor(col.r * fval, col.g * fval, col.b * fval, 1));
382  } else {
383  pmat->set_specular(LColor(col.r, col.g, col.b, 1));
384  }
385  have = true;
386  }
387  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_AMBIENT, col)) {
388  pmat->set_specular(LColor(col.r, col.g, col.b, 1));
389  have = true;
390  }
391  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_EMISSIVE, col)) {
392  pmat->set_emission(LColor(col.r, col.g, col.b, 1));
393  have = true;
394  }
395  if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_TRANSPARENT, col)) {
396  // FIXME: ???
397  }
398  if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS, fval)) {
399  pmat->set_shininess(fval);
400  have = true;
401  }
402  if (have) {
403  state = state->add_attrib(MaterialAttrib::make(pmat));
404  }
405 
406  // Wireframe.
407  if (AI_SUCCESS == mat.Get(AI_MATKEY_ENABLE_WIREFRAME, ival)) {
408  if (ival) {
409  state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_wireframe));
410  } else {
411  state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_filled));
412  }
413  }
414 
415  // Backface culling. Not sure if this is also supposed to set the twoside
416  // flag in the material, I'm guessing not.
417  if (AI_SUCCESS == mat.Get(AI_MATKEY_TWOSIDED, ival)) {
418  if (ival) {
419  state = state->add_attrib(CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
420  } else {
421  state = state->add_attrib(CullFaceAttrib::make_default());
422  }
423  }
424 
425  // And let's not forget the textures!
426  CPT(TextureAttrib) tattr = DCAST(TextureAttrib, TextureAttrib::make());
427  load_texture_stage(mat, aiTextureType_DIFFUSE, tattr);
428  load_texture_stage(mat, aiTextureType_LIGHTMAP, tattr);
429  if (tattr->get_num_on_stages() > 0) {
430  state = state->add_attrib(tattr);
431  }
432 
433  _mat_states[index] = state;
434 }
435 
436 /**
437  * Creates a CharacterJoint from an aiNode
438  */
439 void AssimpLoader::
440 create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node) {
441  const aiMatrix4x4 &t = node.mTransformation;
442  LMatrix4 mat(t.a1, t.b1, t.c1, t.d1,
443  t.a2, t.b2, t.c2, t.d2,
444  t.a3, t.b3, t.c3, t.d3,
445  t.a4, t.b4, t.c4, t.d4);
446  PT(CharacterJoint) joint = new CharacterJoint(character, bundle, parent, node.mName.C_Str(), mat);
447 
448  assimp_cat.debug()
449  << "Creating joint for: " << node.mName.C_Str() << "\n";
450 
451  for (size_t i = 0; i < node.mNumChildren; ++i) {
452  if (_bonemap.find(node.mChildren[i]->mName.C_Str()) != _bonemap.end()) {
453  create_joint(character, bundle, joint, *node.mChildren[i]);
454  }
455  }
456 }
457 
458 /**
459  * Creates a AnimChannelMatrixXfmTable from an aiNodeAnim
460  */
461 void AssimpLoader::
462 create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node) {
463  PT(AnimChannelMatrixXfmTable) group = new AnimChannelMatrixXfmTable(parent, node.mName.C_Str());
464 
465  // See if there is a channel for this node
466  aiNodeAnim *node_anim = nullptr;
467  for (size_t i = 0; i < anim.mNumChannels; ++i) {
468  if (anim.mChannels[i]->mNodeName == node.mName) {
469  node_anim = anim.mChannels[i];
470  }
471  }
472 
473  if (node_anim) {
474  assimp_cat.debug()
475  << "Found channel for node: " << node.mName.C_Str() << "\n";
476  // assimp_cat.debug() << "Num Position Keys " <<
477  // node_anim->mNumPositionKeys << "\n"; assimp_cat.debug() << "Num
478  // Rotation Keys " << node_anim->mNumRotationKeys << "\n";
479  // assimp_cat.debug() << "Num Scaling Keys " << node_anim->mNumScalingKeys
480  // << "\n";
481 
482  // Convert positions
483  PTA_stdfloat tablex = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
484  PTA_stdfloat tabley = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
485  PTA_stdfloat tablez = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
486  for (size_t i = 0; i < node_anim->mNumPositionKeys; ++i) {
487  tablex[i] = node_anim->mPositionKeys[i].mValue.x;
488  tabley[i] = node_anim->mPositionKeys[i].mValue.y;
489  tablez[i] = node_anim->mPositionKeys[i].mValue.z;
490  }
491  group->set_table('x', tablex);
492  group->set_table('y', tabley);
493  group->set_table('z', tablez);
494 
495  // Convert rotations
496  PTA_stdfloat tableh = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
497  PTA_stdfloat tablep = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
498  PTA_stdfloat tabler = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
499  for (size_t i = 0; i < node_anim->mNumRotationKeys; ++i) {
500  aiQuaternion ai_quat = node_anim->mRotationKeys[i].mValue;
501  LVecBase3 hpr = LQuaternion(ai_quat.w, ai_quat.x, ai_quat.y, ai_quat.z).get_hpr();
502  tableh[i] = hpr.get_x();
503  tablep[i] = hpr.get_y();
504  tabler[i] = hpr.get_z();
505  }
506  group->set_table('h', tableh);
507  group->set_table('p', tablep);
508  group->set_table('r', tabler);
509 
510  // Convert scales
511  PTA_stdfloat tablei = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
512  PTA_stdfloat tablej = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
513  PTA_stdfloat tablek = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
514  for (size_t i = 0; i < node_anim->mNumScalingKeys; ++i) {
515  tablei[i] = node_anim->mScalingKeys[i].mValue.x;
516  tablej[i] = node_anim->mScalingKeys[i].mValue.y;
517  tablek[i] = node_anim->mScalingKeys[i].mValue.z;
518  }
519  group->set_table('i', tablei);
520  group->set_table('j', tablej);
521  group->set_table('k', tablek);
522  }
523  else {
524  assimp_cat.debug()
525  << "No channel found for node: " << node.mName.C_Str() << "\n";
526  }
527 
528 
529  for (size_t i = 0; i < node.mNumChildren; ++i) {
530  if (_bonemap.find(node.mChildren[i]->mName.C_Str()) != _bonemap.end()) {
531  create_anim_channel(anim, bundle, group, *node.mChildren[i]);
532  }
533  }
534 }
535 
536 /**
537  * Converts an aiMesh into a Geom.
538  */
539 void AssimpLoader::
540 load_mesh(size_t index) {
541  const aiMesh &mesh = *_scene->mMeshes[index];
542 
543  // Check if we need to make a Character
544  PT(Character) character = nullptr;
545  if (mesh.HasBones()) {
546  assimp_cat.debug()
547  << "Creating character for " << mesh.mName.C_Str() << "\n";
548 
549  // Find and add all bone nodes to the bone map
550  for (size_t i = 0; i < mesh.mNumBones; ++i) {
551  const aiBone &bone = *mesh.mBones[i];
552  const aiNode *node = find_node(*_scene->mRootNode, bone.mName);
553  _bonemap[bone.mName.C_Str()] = node;
554  }
555 
556  // Now create a character from the bones
557  character = new Character(mesh.mName.C_Str());
558  PT(CharacterJointBundle) bundle = character->get_bundle(0);
559  PT(PartGroup) skeleton = new PartGroup(bundle, "<skeleton>");
560 
561  for (size_t i = 0; i < mesh.mNumBones; ++i) {
562  const aiBone &bone = *mesh.mBones[i];
563 
564  // Find the root bone node
565  const aiNode *root = _bonemap[bone.mName.C_Str()];
566  while (root->mParent && _bonemap.find(root->mParent->mName.C_Str()) != _bonemap.end()) {
567  root = root->mParent;
568  }
569 
570  // Don't process this root if we already have a joint for it
571  if (character->find_joint(root->mName.C_Str())) {
572  continue;
573  }
574 
575  create_joint(character, bundle, skeleton, *root);
576  }
577  }
578 
579  // Create transform blend table
580  PT(TransformBlendTable) tbtable = new TransformBlendTable;
581  pvector<BoneWeightList> bone_weights(mesh.mNumVertices);
582  if (character) {
583  for (size_t i = 0; i < mesh.mNumBones; ++i) {
584  const aiBone &bone = *mesh.mBones[i];
585  CharacterJoint *joint = character->find_joint(bone.mName.C_Str());
586  if (joint == nullptr) {
587  assimp_cat.debug()
588  << "Could not find joint for bone: " << bone.mName.C_Str() << "\n";
589  continue;
590  }
591 
592  CPT(JointVertexTransform) jvt = new JointVertexTransform(joint);
593 
594  for (size_t j = 0; j < bone.mNumWeights; ++j) {
595  const aiVertexWeight &weight = bone.mWeights[j];
596 
597  bone_weights[weight.mVertexId].push_back(BoneWeight(jvt, weight.mWeight));
598  }
599  }
600  }
601 
602  // Create the vertex format.
604  aformat->add_column(InternalName::get_vertex(), 3, Geom::NT_stdfloat, Geom::C_point);
605  if (mesh.HasNormals()) {
606  aformat->add_column(InternalName::get_normal(), 3, Geom::NT_stdfloat, Geom::C_vector);
607  }
608  if (mesh.HasVertexColors(0)) {
609  aformat->add_column(InternalName::get_color(), 4, Geom::NT_stdfloat, Geom::C_color);
610  }
611  unsigned int num_uvs = mesh.GetNumUVChannels();
612  if (num_uvs > 0) {
613  // UV sets are named texcoord, texcoord.1, texcoord.2...
614  aformat->add_column(InternalName::get_texcoord(), 3, Geom::NT_stdfloat, Geom::C_texcoord);
615  for (unsigned int u = 1; u < num_uvs; ++u) {
616  ostringstream out;
617  out << u;
618  aformat->add_column(InternalName::get_texcoord_name(out.str()), 3, Geom::NT_stdfloat, Geom::C_texcoord);
619  }
620  }
621 
622  PT(GeomVertexArrayFormat) tb_aformat = new GeomVertexArrayFormat;
623  tb_aformat->add_column(InternalName::make("transform_blend"), 1, Geom::NT_uint16, Geom::C_index);
624 
625  // Check to see if we need to convert any animations
626  for (size_t i = 0; i < _scene->mNumAnimations; ++i) {
627  aiAnimation &ai_anim = *_scene->mAnimations[i];
628  bool convert_anim = false;
629 
630  assimp_cat.debug()
631  << "Checking to see if anim (" << ai_anim.mName.C_Str() << ") matches character (" << mesh.mName.C_Str() << ")\n";
632  for (size_t j = 0; j < ai_anim.mNumChannels; ++j) {
633  assimp_cat.debug()
634  << "Searching for " << ai_anim.mChannels[j]->mNodeName.C_Str() << " in bone map" << "\n";
635  if (_bonemap.find(ai_anim.mChannels[j]->mNodeName.C_Str()) != _bonemap.end()) {
636  convert_anim = true;
637  break;
638  }
639  }
640 
641  if (convert_anim) {
642  assimp_cat.debug()
643  << "Found animation (" << ai_anim.mName.C_Str() << ") for character (" << mesh.mName.C_Str() << ")\n";
644 
645  // Now create the animation
646  unsigned int frames = 0;
647  for (size_t j = 0; j < ai_anim.mNumChannels; ++j) {
648  if (ai_anim.mChannels[j]->mNumPositionKeys > frames) {
649  frames = ai_anim.mChannels[j]->mNumPositionKeys;
650  }
651  if (ai_anim.mChannels[j]->mNumRotationKeys > frames) {
652  frames = ai_anim.mChannels[j]->mNumRotationKeys;
653  }
654  if (ai_anim.mChannels[j]->mNumScalingKeys > frames) {
655  frames = ai_anim.mChannels[j]->mNumScalingKeys;
656  }
657  }
658  PN_stdfloat fps = frames / (ai_anim.mTicksPerSecond * ai_anim.mDuration);
659  assimp_cat.debug()
660  << "FPS " << fps << "\n";
661  assimp_cat.debug()
662  << "Frames " << frames << "\n";
663 
664  PT(AnimBundle) bundle = new AnimBundle(mesh.mName.C_Str(), fps, frames);
665  PT(AnimGroup) skeleton = new AnimGroup(bundle, "<skeleton>");
666 
667  for (size_t i = 0; i < mesh.mNumBones; ++i) {
668  const aiBone &bone = *mesh.mBones[i];
669 
670  // Find the root bone node
671  const aiNode *root = _bonemap[bone.mName.C_Str()];
672  while (root->mParent && _bonemap.find(root->mParent->mName.C_Str()) != _bonemap.end()) {
673  root = root->mParent;
674  }
675 
676  // Only convert root nodes
677  if (root->mName == bone.mName) {
678  create_anim_channel(ai_anim, bundle, skeleton, *root);
679 
680  // Attach the animation to the character node
681  PT(AnimBundleNode) bundle_node = new AnimBundleNode(bone.mName.C_Str(), bundle);
682  character->add_child(bundle_node);
683  }
684  }
685  }
686  }
687 
688  // TODO: if there is only one UV set, hackily iterate over the texture
689  // stages and clear the texcoord name things
690 
691  PT(GeomVertexFormat) format = new GeomVertexFormat;
692  format->add_array(aformat);
693  if (character) {
694  format->add_array(tb_aformat);
695 
697  aspec.set_panda();
698  format->set_animation(aspec);
699  }
700 
701  // Create the GeomVertexData.
702  string name (mesh.mName.data, mesh.mName.length);
703  PT(GeomVertexData) vdata = new GeomVertexData(name, GeomVertexFormat::register_format(format), Geom::UH_static);
704  if (character) {
705  vdata->set_transform_blend_table(tbtable);
706  }
707  vdata->unclean_set_num_rows(mesh.mNumVertices);
708 
709  // Read out the vertices.
710  GeomVertexWriter vertex (vdata, InternalName::get_vertex());
711  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
712  const aiVector3D &vec = mesh.mVertices[i];
713  vertex.add_data3(vec.x, vec.y, vec.z);
714  }
715 
716  // Now the normals, if any.
717  if (mesh.HasNormals()) {
718  GeomVertexWriter normal (vdata, InternalName::get_normal());
719  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
720  const aiVector3D &vec = mesh.mNormals[i];
721  normal.add_data3(vec.x, vec.y, vec.z);
722  }
723  }
724 
725  // Vertex colors, if any. We only import the first set.
726  if (mesh.HasVertexColors(0)) {
727  GeomVertexWriter color (vdata, InternalName::get_color());
728  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
729  const aiColor4D &col = mesh.mColors[0][i];
730  color.add_data4(col.r, col.g, col.b, col.a);
731  }
732  }
733 
734  // Now the texture coordinates.
735  if (num_uvs > 0) {
736  // UV sets are named texcoord, texcoord.1, texcoord.2...
737  GeomVertexWriter texcoord0 (vdata, InternalName::get_texcoord());
738  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
739  const aiVector3D &vec = mesh.mTextureCoords[0][i];
740  texcoord0.add_data3(vec.x, vec.y, vec.z);
741  }
742  for (unsigned int u = 1; u < num_uvs; ++u) {
743  ostringstream out;
744  out << u;
745  GeomVertexWriter texcoord (vdata, InternalName::get_texcoord_name(out.str()));
746  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
747  const aiVector3D &vec = mesh.mTextureCoords[u][i];
748  texcoord.add_data3(vec.x, vec.y, vec.z);
749  }
750  }
751  }
752 
753  // Now the transform blend table
754  if (character) {
755  GeomVertexWriter transform_blend (vdata, InternalName::get_transform_blend());
756 
757  for (size_t i = 0; i < mesh.mNumVertices; ++i) {
758  TransformBlend tblend;
759 
760  for (size_t j = 0; j < bone_weights[i].size(); ++j) {
761  tblend.add_transform(bone_weights[i][j].joint_vertex_xform, bone_weights[i][j].weight);
762  }
763  transform_blend.add_data1i(tbtable->add_blend(tblend));
764  }
765 
766  tbtable->set_rows(SparseArray::lower_on(vdata->get_num_rows()));
767  }
768 
769  // Now read out the primitives. Keep in mind that we called ReadFile with
770  // the aiProcess_Triangulate flag earlier, so we don't have to worry about
771  // polygons.
772  PT(GeomPoints) points = new GeomPoints(Geom::UH_static);
773  PT(GeomLines) lines = new GeomLines(Geom::UH_static);
774  PT(GeomTriangles) triangles = new GeomTriangles(Geom::UH_static);
775 
776  // Now add the vertex indices.
777  for (size_t i = 0; i < mesh.mNumFaces; ++i) {
778  const aiFace &face = mesh.mFaces[i];
779 
780  if (face.mNumIndices == 0) {
781  // It happens, strangely enough.
782  continue;
783  } else if (face.mNumIndices == 1) {
784  points->add_vertex(face.mIndices[0]);
785  points->close_primitive();
786  } else if (face.mNumIndices == 2) {
787  lines->add_vertices(face.mIndices[0], face.mIndices[1]);
788  lines->close_primitive();
789  } else if (face.mNumIndices == 3) {
790  triangles->add_vertices(face.mIndices[0], face.mIndices[1], face.mIndices[2]);
791  triangles->close_primitive();
792  } else {
793  nassertd(false) continue;
794  }
795  }
796 
797  // Create a geom and add the primitives to it.
798  PT(Geom) geom = new Geom(vdata);
799  if (points->get_num_primitives() > 0) {
800  geom->add_primitive(points);
801  }
802  if (lines->get_num_primitives() > 0) {
803  geom->add_primitive(lines);
804  }
805  if (triangles->get_num_primitives() > 0) {
806  geom->add_primitive(triangles);
807  }
808 
809  _geoms[index] = geom;
810  _geom_matindices[index] = mesh.mMaterialIndex;
811 
812  if (character) {
813  _charmap[mesh.mName.C_Str()] = character;
814  }
815 }
816 
817 /**
818  * Converts an aiNode into a PandaNode.
819  */
820 void AssimpLoader::
821 load_node(const aiNode &node, PandaNode *parent) {
822  PT(PandaNode) pnode;
823  PT(Character) character;
824 
825  // Skip nodes we've converted to joints
826  if (_bonemap.find(node.mName.C_Str()) != _bonemap.end()) {
827  return;
828  }
829 
830  // Create the node and give it a name.
831  string name (node.mName.data, node.mName.length);
832  if (node.mNumMeshes > 0) {
833  pnode = new GeomNode(name);
834  } else {
835  pnode = new PandaNode(name);
836  }
837 
838  if (_charmap.find(node.mName.C_Str()) != _charmap.end()) {
839  character = _charmap[node.mName.C_Str()];
840  parent->add_child(character);
841  } else {
842  parent->add_child(pnode);
843  }
844 
845  // Load in the transformation matrix.
846  const aiMatrix4x4 &t = node.mTransformation;
847  if (!t.IsIdentity()) {
848  LMatrix4 mat(t.a1, t.b1, t.c1, t.d1,
849  t.a2, t.b2, t.c2, t.d2,
850  t.a3, t.b3, t.c3, t.d3,
851  t.a4, t.b4, t.c4, t.d4);
852  pnode->set_transform(TransformState::make_mat(mat));
853  }
854 
855  for (size_t i = 0; i < node.mNumChildren; ++i) {
856  load_node(*node.mChildren[i], pnode);
857  }
858 
859  if (node.mNumMeshes > 0) {
860  // Remember, we created this as GeomNode earlier.
861  PT(GeomNode) gnode = DCAST(GeomNode, pnode);
862  size_t meshIndex;
863 
864  // If there's only mesh, don't bother using a per-geom state.
865  if (node.mNumMeshes == 1) {
866  meshIndex = node.mMeshes[0];
867  gnode->add_geom(_geoms[meshIndex]);
868  gnode->set_state(_mat_states[_geom_matindices[meshIndex]]);
869  } else {
870  for (size_t i = 0; i < node.mNumMeshes; ++i) {
871  meshIndex = node.mMeshes[i];
872  gnode->add_geom(_geoms[node.mMeshes[i]],
873  _mat_states[_geom_matindices[meshIndex]]);
874  }
875  }
876 
877  if (character) {
878  assimp_cat.debug() << "Adding char to geom\n";
879  character->add_child(gnode);
880  }
881  }
882 }
883 
884 /**
885  * Converts an aiLight into a LightNode.
886  */
887 void AssimpLoader::
888 load_light(const aiLight &light) {
889  string name (light.mName.data, light.mName.length);
890  assimp_cat.debug() << "Found light '" << name << "'\n";
891 
892  aiColor3D col;
893  aiVector3D vec;
894 
895  switch (light.mType) {
896  case aiLightSource_DIRECTIONAL: {
897  PT(DirectionalLight) dlight = new DirectionalLight(name);
898  _root->add_child(dlight);
899 
900  col = light.mColorDiffuse;
901  dlight->set_color(LColor(col.r, col.g, col.b, 1));
902 
903  col = light.mColorSpecular;
904  dlight->set_specular_color(LColor(col.r, col.g, col.b, 1));
905 
906  vec = light.mPosition;
907  dlight->set_point(LPoint3(vec.x, vec.y, vec.z));
908 
909  vec = light.mDirection;
910  dlight->set_direction(LVector3(vec.x, vec.y, vec.z));
911  break; }
912 
913  case aiLightSource_POINT: {
914  PT(PointLight) plight = new PointLight(name);
915  _root->add_child(plight);
916 
917  col = light.mColorDiffuse;
918  plight->set_color(LColor(col.r, col.g, col.b, 1));
919 
920  col = light.mColorSpecular;
921  plight->set_specular_color(LColor(col.r, col.g, col.b, 1));
922 
923  vec = light.mPosition;
924  plight->set_point(LPoint3(vec.x, vec.y, vec.z));
925 
926  plight->set_attenuation(LVecBase3(light.mAttenuationConstant,
927  light.mAttenuationLinear,
928  light.mAttenuationQuadratic));
929  break; }
930 
931  case aiLightSource_SPOT: {
932  PT(Spotlight) plight = new Spotlight(name);
933  _root->add_child(plight);
934 
935  col = light.mColorDiffuse;
936  plight->set_color(LColor(col.r, col.g, col.b, 1));
937 
938  col = light.mColorSpecular;
939  plight->set_specular_color(LColor(col.r, col.g, col.b, 1));
940 
941  plight->set_attenuation(LVecBase3(light.mAttenuationConstant,
942  light.mAttenuationLinear,
943  light.mAttenuationQuadratic));
944 
945  plight->get_lens()->set_fov(light.mAngleOuterCone);
946  // TODO: translate mAngleInnerCone to an exponent, somehow
947 
948  // This *should* be about right.
949  vec = light.mDirection;
950  LPoint3 pos (light.mPosition.x, light.mPosition.y, light.mPosition.z);
951  LQuaternion quat;
952  ::look_at(quat, LPoint3(vec.x, vec.y, vec.z), LVector3::up());
953  plight->set_transform(TransformState::make_pos_quat_scale(pos, quat, LVecBase3(1, 1, 1)));
954  break; }
955 
956  // This is a somewhat recent addition to Assimp, so let's be kind to those
957  // that don't have an up-to-date version of Assimp.
958  case 0x4: //aiLightSource_AMBIENT:
959  // This is handled below.
960  break;
961 
962  default:
963  assimp_cat.warning() << "Light '" << name << "' has an unknown type!\n";
964  return;
965  }
966 
967  // If there's an ambient color, add it as ambient light.
968  col = light.mColorAmbient;
969  LVecBase4 ambient (col.r, col.g, col.b, 0);
970  if (ambient != LVecBase4::zero()) {
971  PT(AmbientLight) alight = new AmbientLight(name);
972  alight->set_color(ambient);
973  _root->add_child(alight);
974  }
975 }
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,...
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,...
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's animation, containing an animating transform matrix.
set_texcoord_name
Indicate which set of UV'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's a global PandaLogger object and makes sure that it is Assimp'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,...
Definition: filename.cxx:328
bool write(const Filename &fullpath)
Writes the texture to the named filename.
Definition: texture.I:298
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.