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
44using std::ostringstream;
45using std::stringstream;
46using std::string;
47
48struct 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 */
61AssimpLoader::
62AssimpLoader() :
63 _error (false),
64 _geoms (nullptr) {
65
67 _importer.SetIOHandler(new PandaIOSystem);
68}
69
70/**
71 *
72 */
73AssimpLoader::
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 */
83get_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 */
103read(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 */
158build_graph() {
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 */
205const aiNode *AssimpLoader::
206find_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 */
226void AssimpLoader::
227load_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 */
287void AssimpLoader::
288load_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 */
359void AssimpLoader::
360load_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 */
439void AssimpLoader::
440create_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 */
461void AssimpLoader::
462create_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 */
539void AssimpLoader::
540load_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
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_normal);
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 */
820void AssimpLoader::
821load_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 */
887void AssimpLoader::
888load_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.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A light source that seems to illuminate all points in space at once.
Definition: ambientLight.h:26
This is a node that contains a pointer to an AnimBundle.
This is the root of an AnimChannel hierarchy.
Definition: animBundle.h:29
An animation channel that issues a matrix each frame, read from a table such as might have been read ...
This is the base class for AnimChannel and AnimBundle.
Definition: animGroup.h:33
void get_extensions(std::string &ext) const
Returns a space-separated list of extensions that Assimp can load, without the leading dots.
bool read(const Filename &filename)
Reads from the indicated file.
void build_graph()
Converts scene graph structures into a Panda3D scene graph, with _root being the root node.
The collection of all the joints and sliders in the character.
This represents one joint of the character's animation, containing an animating transform matrix.
An animated character, with skeleton-morph animation and either soft- skinned or hard-skinned vertice...
Definition: character.h:38
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
A light shining from infinitely far away in a particular direction, like sunlight.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
Definition: filename.cxx:1011
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
std::string get_dirname() const
Returns the directory part of the filename.
Definition: filename.I:358
Defines a series of disconnected line segments.
Definition: geomLines.h:23
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
Defines a series of disconnected points.
Definition: geomPoints.h:23
Defines a series of disconnected triangles.
Definition: geomTriangles.h:23
This object describes how the vertex animation, if any, represented in a GeomVertexData is encoded.
void set_panda()
Specifies that vertex animation is to be performed by Panda.
This describes the structure of a single array within a Geom data.
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 defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
This class defines the physical layout of the vertex data stored within a Geom.
size_t add_array(const GeomVertexArrayFormat *array_format)
Adds the indicated array definition to the list of arrays included within this vertex format definiti...
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
A container for geometry primitives.
Definition: geom.h:54
void add_primitive(const GeomPrimitive *primitive)
Inserts a new GeomPrimitive structure to the Geom object.
Definition: geom.I:116
This is a specialization on VertexTransform that returns the transform necessary to move vertices as ...
Defines the way an object appears in the presence of lighting.
Definition: material.h:43
set_diffuse
Specifies the diffuse color setting of the material.
Definition: material.h:116
A node of this type is created automatically at the root of each model file that is loaded.
Definition: modelRoot.h:27
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
This class maintains the set of all known PNMFileTypes in the universe.
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
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...
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition: pnmFileType.h:32
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
bool read(const Filename &filename, PNMFileType *type=nullptr, bool report_unknown_type=true)
Reads the indicated image filename.
Definition: pnmImage.cxx:278
Custom implementation of Assimp::IOSystem.
Definition: pandaIOSystem.h:25
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
A basic node of the scene graph or data graph.
Definition: pandaNode.h:65
This is the base class for PartRoot and MovingPart.
Definition: partGroup.h:43
A light originating from a single point in space, and shining in all directions.
Definition: pointLight.h:25
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
static SparseArray lower_on(int on_bits)
Returns a SparseArray whose lower on_bits bits are on.
Definition: sparseArray.I:43
A light originating from a single point in space, and shining in a particular direction,...
Definition: spotlight.h:32
Indicates the set of TextureStages and their associated Textures that should be applied to (or remove...
Definition: textureAttrib.h:31
get_num_on_stages
Returns the number of stages that are turned on by the attribute.
Definition: textureAttrib.h:55
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:70
Defines the properties of a named stage of the multitexture pipeline.
Definition: textureStage.h:35
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
bool write(const Filename &fullpath)
Writes the texture to the named filename.
Definition: texture.I:298
This structure collects together the different combinations of transforms and blend amounts used by a...
This defines a single entry in a TransformBlendTable.
void add_transform(const VertexTransform *transform, PN_stdfloat weight)
Adds a new transform to the blend.
A hierarchy of directories and files that appears to be one continuous file system,...
bool is_regular_file(const Filename &filename) const
Convenience function; returns true if the named file exists and is a regular file.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.