Panda3D
Loading...
Searching...
No Matches
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 "alphaTestAttrib.h"
25#include "materialAttrib.h"
26#include "textureAttrib.h"
27#include "cullFaceAttrib.h"
28#include "transparencyAttrib.h"
29#include "ambientLight.h"
30#include "directionalLight.h"
31#include "spotlight.h"
32#include "pointLight.h"
33#include "look_at.h"
34#include "texturePool.h"
35#include "character.h"
36#include "animBundle.h"
37#include "animBundleNode.h"
39#include "pvector.h"
40#include "cmath.h"
41#include "deg_2_rad.h"
42#include "string_utils.h"
43
44#include "pandaIOSystem.h"
45#include "pandaLogger.h"
46
47#include <assimp/postprocess.h>
48
49#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR
50#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR "$mat.gltf.pbrMetallicRoughness.baseColorFactor", 0, 0
51#endif
52
53#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR
54#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR "$mat.gltf.pbrMetallicRoughness.metallicFactor", 0, 0
55#endif
56
57#ifndef AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR
58#define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR "$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0
59#endif
60
61#ifndef AI_MATKEY_GLTF_ALPHAMODE
62#define AI_MATKEY_GLTF_ALPHAMODE "$mat.gltf.alphaMode", 0, 0
63#endif
64
65#ifndef AI_MATKEY_GLTF_ALPHACUTOFF
66#define AI_MATKEY_GLTF_ALPHACUTOFF "$mat.gltf.alphaCutoff", 0, 0
67#endif
68
69// Older versions of Assimp used these glTF-specific keys instead.
70#ifndef AI_MATKEY_BASE_COLOR
71#define AI_MATKEY_BASE_COLOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR
72#endif
73
74#ifndef AI_MATKEY_METALLIC_FACTOR
75#define AI_MATKEY_METALLIC_FACTOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR
76#endif
77
78#ifndef AI_MATKEY_ROUGHNESS_FACTOR
79#define AI_MATKEY_ROUGHNESS_FACTOR AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR
80#endif
81
82using std::ostringstream;
83using std::stringstream;
84using std::string;
85
86struct BoneWeight {
87 CPT(JointVertexTransform) joint_vertex_xform;
88 float weight;
89
90 BoneWeight(CPT(JointVertexTransform) joint_vertex_xform, float weight)
91 : joint_vertex_xform(joint_vertex_xform), weight(weight)
92 {}
93};
95
96/**
97 *
98 */
99AssimpLoader::
100AssimpLoader() :
101 _error (false),
102 _geoms (nullptr) {
103
105 _importer.SetIOHandler(new PandaIOSystem);
106}
107
108/**
109 *
110 */
111AssimpLoader::
112~AssimpLoader() {
113 _importer.FreeScene();
114}
115
116/**
117 * Returns a space-separated list of extensions that Assimp can load, without
118 * the leading dots.
119 */
121get_extensions(string &ext) const {
122 aiString aexts;
123 _importer.GetExtensionList(aexts);
124
125 // The format is like: *.mdc;*.mdl;*.mesh.xml;*.mot
126 char *sub = strtok(aexts.data, ";");
127 while (sub != nullptr) {
128 ext += sub + 2;
129 sub = strtok(nullptr, ";");
130
131 if (sub != nullptr) {
132 ext += ' ';
133 }
134 }
135}
136
137/**
138 * Reads from the indicated file.
139 */
141read(const Filename &filename) {
142 _filename = filename;
143
144 unsigned int flags = aiProcess_Triangulate | aiProcess_GenUVCoords;
145
146 if (assimp_calc_tangent_space) {
147 flags |= aiProcess_CalcTangentSpace;
148 }
149 if (assimp_join_identical_vertices) {
150 flags |= aiProcess_JoinIdenticalVertices;
151 }
152 if (assimp_improve_cache_locality) {
153 flags |= aiProcess_ImproveCacheLocality;
154 }
155 if (assimp_remove_redundant_materials) {
156 flags |= aiProcess_RemoveRedundantMaterials;
157 }
158 if (assimp_fix_infacing_normals) {
159 flags |= aiProcess_FixInfacingNormals;
160 }
161 if (assimp_optimize_meshes) {
162 flags |= aiProcess_OptimizeMeshes;
163 }
164 if (assimp_optimize_graph) {
165 flags |= aiProcess_OptimizeGraph;
166 }
167 if (assimp_flip_winding_order) {
168 flags |= aiProcess_FlipWindingOrder;
169 }
170 if (assimp_gen_normals) {
171 if (assimp_smooth_normal_angle == 0.0) {
172 flags |= aiProcess_GenNormals;
173 }
174 else {
175 flags |= aiProcess_GenSmoothNormals;
176 _importer.SetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,
177 assimp_smooth_normal_angle);
178 }
179 }
180
181 _scene = _importer.ReadFile(_filename.c_str(), flags);
182 if (_scene == nullptr) {
183 _error = true;
184 return false;
185 }
186
187 _error = false;
188 return true;
189}
190
191/**
192 * Converts scene graph structures into a Panda3D scene graph, with _root
193 * being the root node.
194 */
196build_graph() {
197 nassertv(_scene != nullptr); // read() must be called first
198 nassertv(!_error); // and have succeeded
199
200 // Protect the import process
201 MutexHolder holder(_lock);
202
203 _root = new ModelRoot(_filename.get_basename());
204
205 // Import all of the embedded textures first.
206 _textures = new PT(Texture)[_scene->mNumTextures];
207 for (size_t i = 0; i < _scene->mNumTextures; ++i) {
208 load_texture(i);
209 }
210
211 // Then the materials.
212 _mat_states = new CPT(RenderState)[_scene->mNumMaterials];
213 for (size_t i = 0; i < _scene->mNumMaterials; ++i) {
214 load_material(i);
215 }
216
217 // And then the meshes.
218 _geoms = new Geoms[_scene->mNumMeshes];
219 for (size_t i = 0; i < _scene->mNumMeshes; ++i) {
220 load_mesh(i);
221 }
222
223 // And now the node structure.
224 if (_scene->mRootNode != nullptr) {
225 load_node(*_scene->mRootNode, _root);
226 }
227
228 // And lastly, the lights.
229 for (size_t i = 0; i < _scene->mNumLights; ++i) {
230 load_light(*_scene->mLights[i]);
231 }
232
233 delete[] _textures;
234 delete[] _mat_states;
235 delete[] _geoms;
236}
237
238/**
239 * Finds a node by name.
240 */
241const aiNode *AssimpLoader::
242find_node(const aiNode &root, const aiString &name) {
243 const aiNode *node;
244
245 if (root.mName == name) {
246 return &root;
247 } else {
248 for (size_t i = 0; i < root.mNumChildren; ++i) {
249 node = find_node(*root.mChildren[i], name);
250 if (node) {
251 return node;
252 }
253 }
254 }
255
256 return nullptr;
257}
258
259/**
260 * Converts an aiTexture into a Texture.
261 */
262void AssimpLoader::
263load_texture(size_t index) {
264 const aiTexture &tex = *_scene->mTextures[index];
265
266 PT(Texture) ptex = new Texture;
267
268 if (tex.mHeight == 0) {
269 // Compressed texture.
270 if (assimp_cat.is_debug()) {
271 assimp_cat.debug()
272 << "Reading embedded compressed texture with format "
273 << tex.achFormatHint << " and size " << tex.mWidth << "\n";
274 }
275 stringstream str;
276 str.write((char*) tex.pcData, tex.mWidth);
277
278 if (strncmp(tex.achFormatHint, "dds", 3) == 0) {
279 ptex->read_dds(str);
280
281 } else {
283 PNMFileType *ftype;
284 PNMImage img;
285
286 // Work around a bug in Assimp, it sometimes writes jp instead of jpg
287 if (strncmp(tex.achFormatHint, "jp\0", 3) == 0) {
288 ftype = reg->get_type_from_extension("jpg");
289 } else {
290 ftype = reg->get_type_from_extension(tex.achFormatHint);
291 }
292
293 if (img.read(str, "", ftype)) {
294 ptex->load(img);
295 } else {
296 ptex = nullptr;
297 }
298 }
299 } else {
300 if (assimp_cat.is_debug()) {
301 assimp_cat.debug()
302 << "Reading embedded raw texture with size "
303 << tex.mWidth << "x" << tex.mHeight << "\n";
304 }
305
306 ptex->setup_2d_texture(tex.mWidth, tex.mHeight, Texture::T_unsigned_byte, Texture::F_rgba);
307 PTA_uchar data = ptex->modify_ram_image();
308
309 size_t p = 0;
310 for (size_t i = 0; i < tex.mWidth * tex.mHeight; ++i) {
311 const aiTexel &texel = tex.pcData[i];
312 data[p++] = texel.b;
313 data[p++] = texel.g;
314 data[p++] = texel.r;
315 data[p++] = texel.a;
316 }
317 }
318
319 // ostringstream path; path << "tmp" << index << ".png";
320 // ptex->write(path.str());
321
322 _textures[index] = ptex;
323
324}
325
326/**
327 * Converts an aiMaterial into a RenderState.
328 */
329void AssimpLoader::
330load_texture_stage(const aiMaterial &mat, const aiTextureType &ttype,
331 TextureStage::Mode mode, CPT(TextureAttrib) &tattr,
332 CPT(TexMatrixAttrib) &tmattr) {
333 aiString path;
334 aiTextureMapping mapping;
335 unsigned int uvindex;
336 float blend;
337 aiTextureOp op;
338 aiTextureMapMode mapmode[3];
339
340 for (size_t i = 0; i < mat.GetTextureCount(ttype); ++i) {
341 mat.GetTexture(ttype, i, &path, &mapping, nullptr, &blend, &op, mapmode);
342
343 if (AI_SUCCESS != mat.Get(AI_MATKEY_UVWSRC(ttype, i), uvindex)) {
344 // If there's no texture coordinate set for this texture, assume that
345 // it's the same as the index on the stack. TODO: if there's only one
346 // set on the mesh, force everything to use just the first stage.
347 uvindex = i;
348 }
349
350 if (ttype == aiTextureType_DIFFUSE && i == 1) {
351 // The glTF 2 importer duplicates this slot in older versions of Assimp.
352 // Since glTF doesn't support multiple diffuse textures anyway, we check
353 // for this old glTF-specific key, and if present, ignore this texture.
354 aiColor4D col;
355 if (AI_SUCCESS == mat.Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, col)) {
356 return;
357 }
358 }
359
360 std::string uvindex_str = format_string(uvindex);
361 PT(TextureStage) stage = new TextureStage(uvindex_str);
362 stage->set_mode(mode);
363 if (uvindex > 0) {
364 stage->set_texcoord_name(InternalName::get_texcoord_name(uvindex_str));
365 }
366 PT(Texture) ptex;
367
368 // I'm not sure if this is the right way to handle it, as I couldn't find
369 // much information on embedded textures.
370 if (path.data[0] == '*') {
371 long num = strtol(path.data + 1, nullptr, 10);
372 ptex = _textures[num];
373
374 } else if (path.length > 0) {
375 Filename fn = Filename::from_os_specific(string(path.data, path.length));
376
377 // Try to find the file by moving up twice in the hierarchy.
379 Filename dir (_filename);
380 _filename.make_canonical();
381 dir = _filename.get_dirname();
382
383 // Quake 3 BSP doesn't specify an extension for textures.
384 if (vfs->is_regular_file(Filename(dir, fn))) {
385 fn = Filename(dir, fn);
386 } else if (vfs->is_regular_file(Filename(dir, fn + ".tga"))) {
387 fn = Filename(dir, fn + ".tga");
388 } else if (vfs->is_regular_file(Filename(dir, fn + ".jpg"))) {
389 fn = Filename(dir, fn + ".jpg");
390 } else {
391 dir = _filename.get_dirname();
392 if (vfs->is_regular_file(Filename(dir, fn))) {
393 fn = Filename(dir, fn);
394 } else if (vfs->is_regular_file(Filename(dir, fn + ".tga"))) {
395 fn = Filename(dir, fn + ".tga");
396 } else if (vfs->is_regular_file(Filename(dir, fn + ".jpg"))) {
397 fn = Filename(dir, fn + ".jpg");
398 }
399 }
400
401 ptex = TexturePool::load_texture(fn);
402 }
403
404 if (ptex != nullptr) {
405 // Apply the mapping modes.
406 switch (mapmode[0]) {
407 case aiTextureMapMode_Wrap:
408 ptex->set_wrap_u(SamplerState::WM_repeat);
409 break;
410 case aiTextureMapMode_Clamp:
411 ptex->set_wrap_u(SamplerState::WM_clamp);
412 break;
413 case aiTextureMapMode_Decal:
414 ptex->set_wrap_u(SamplerState::WM_border_color);
415 ptex->set_border_color(LColor(0, 0, 0, 0));
416 break;
417 case aiTextureMapMode_Mirror:
418 ptex->set_wrap_u(SamplerState::WM_mirror);
419 break;
420 default:
421 break;
422 }
423 switch (mapmode[1]) {
424 case aiTextureMapMode_Wrap:
425 ptex->set_wrap_v(SamplerState::WM_repeat);
426 break;
427 case aiTextureMapMode_Clamp:
428 ptex->set_wrap_v(SamplerState::WM_clamp);
429 break;
430 case aiTextureMapMode_Decal:
431 ptex->set_wrap_v(SamplerState::WM_border_color);
432 ptex->set_border_color(LColor(0, 0, 0, 0));
433 break;
434 case aiTextureMapMode_Mirror:
435 ptex->set_wrap_v(SamplerState::WM_mirror);
436 break;
437 default:
438 break;
439 }
440 switch (mapmode[2]) {
441 case aiTextureMapMode_Wrap:
442 ptex->set_wrap_w(SamplerState::WM_repeat);
443 break;
444 case aiTextureMapMode_Clamp:
445 ptex->set_wrap_w(SamplerState::WM_clamp);
446 break;
447 case aiTextureMapMode_Decal:
448 ptex->set_wrap_w(SamplerState::WM_border_color);
449 ptex->set_border_color(LColor(0, 0, 0, 0));
450 break;
451 case aiTextureMapMode_Mirror:
452 ptex->set_wrap_w(SamplerState::WM_mirror);
453 break;
454 default:
455 break;
456 }
457
458 tattr = DCAST(TextureAttrib, tattr->add_on_stage(stage, ptex));
459
460 // Is there a texture transform?
461 aiUVTransform transform;
462 if (AI_SUCCESS == mat.Get(AI_MATKEY_UVTRANSFORM(ttype, i), transform)) {
463 // Reconstruct the original origin from the glTF file.
464 PN_stdfloat rcos, rsin;
465 csincos(-transform.mRotation, &rsin, &rcos);
466 transform.mTranslation.x -= (0.5 * transform.mScaling.x) * (-rcos + rsin + 1);
467 transform.mTranslation.y -= ((0.5 * transform.mScaling.y) * (rsin + rcos - 1)) + 1 - transform.mScaling.y;
468
469 LMatrix3 matrix =
470 LMatrix3::translate_mat(0, -1) *
471 LMatrix3::scale_mat(transform.mScaling.x, transform.mScaling.y) *
472 LMatrix3::rotate_mat(rad_2_deg(-transform.mRotation)) *
473 LMatrix3::translate_mat(transform.mTranslation.x, 1 + transform.mTranslation.y);
474
475 CPT(TransformState) cstate =
476 TransformState::make_mat3(matrix);
477
478 CPT(RenderAttrib) new_attr = (tmattr == nullptr)
479 ? TexMatrixAttrib::make(stage, std::move(cstate))
480 : tmattr->add_stage(stage, std::move(cstate));
481 tmattr = DCAST(TexMatrixAttrib, std::move(new_attr));
482 }
483 }
484 }
485}
486
487/**
488 * Converts an aiMaterial into a RenderState.
489 */
490void AssimpLoader::
491load_material(size_t index) {
492 const aiMaterial &mat = *_scene->mMaterials[index];
493
494 CPT(RenderState) state = RenderState::make_empty();
495
496 aiColor4D col;
497 bool have;
498 int ival;
499 PN_stdfloat fval;
500
501 // XXX a lot of this is untested.
502
503 // First do the material attribute.
504 PT(Material) pmat = new Material;
505 have = false;
506 if (AI_SUCCESS == mat.Get(AI_MATKEY_BASE_COLOR, col)) {
507 pmat->set_base_color(LColor(col.r, col.g, col.b, col.a));
508 have = true;
509 }
510 else if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_DIFFUSE, col)) {
511 pmat->set_diffuse(LColor(col.r, col.g, col.b, 1));
512 have = true;
513 }
514 if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_SPECULAR, col)) {
515 if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS_STRENGTH, fval)) {
516 pmat->set_specular(LColor(col.r * fval, col.g * fval, col.b * fval, 1));
517 } else {
518 pmat->set_specular(LColor(col.r, col.g, col.b, 1));
519 }
520 have = true;
521 }
522 //else {
523 // if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS_STRENGTH, fval)) {
524 // pmat->set_specular(LColor(fval, fval, fval, 1));
525 // } else {
526 // pmat->set_specular(LColor(1, 1, 1, 1));
527 // }
528 //}
529 if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_AMBIENT, col)) {
530 pmat->set_specular(LColor(col.r, col.g, col.b, 1));
531 have = true;
532 }
533 if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_EMISSIVE, col)) {
534 pmat->set_emission(LColor(col.r, col.g, col.b, 1));
535 have = true;
536 }
537 if (AI_SUCCESS == mat.Get(AI_MATKEY_COLOR_TRANSPARENT, col)) {
538 // FIXME: ???
539 }
540 if (AI_SUCCESS == mat.Get(AI_MATKEY_SHININESS, fval)) {
541 pmat->set_shininess(fval);
542 have = true;
543 }
544 if (AI_SUCCESS == mat.Get(AI_MATKEY_METALLIC_FACTOR, fval)) {
545 pmat->set_metallic(fval);
546 have = true;
547 }
548 if (AI_SUCCESS == mat.Get(AI_MATKEY_ROUGHNESS_FACTOR, fval)) {
549 pmat->set_roughness(fval);
550 have = true;
551 }
552 if (AI_SUCCESS == mat.Get(AI_MATKEY_REFRACTI, fval)) {
553 pmat->set_refractive_index(fval);
554 have = true;
555 }
556 else if (pmat->has_metallic()) {
557 // Default refractive index to 1.5 for PBR models
558 pmat->set_refractive_index(1.5);
559 }
560 if (have) {
561 state = state->add_attrib(MaterialAttrib::make(pmat));
562 }
563
564 // Wireframe.
565 if (AI_SUCCESS == mat.Get(AI_MATKEY_ENABLE_WIREFRAME, ival)) {
566 if (ival) {
567 state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_wireframe));
568 } else {
569 state = state->add_attrib(RenderModeAttrib::make(RenderModeAttrib::M_filled));
570 }
571 }
572
573 // Backface culling. Not sure if this is also supposed to set the twoside
574 // flag in the material, I'm guessing not.
575 if (AI_SUCCESS == mat.Get(AI_MATKEY_TWOSIDED, ival)) {
576 if (ival) {
577 state = state->add_attrib(CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
578 } else {
579 state = state->add_attrib(CullFaceAttrib::make_default());
580 }
581 }
582
583 // Alpha mode.
584 aiString alpha_mode;
585 if (AI_SUCCESS == mat.Get(AI_MATKEY_GLTF_ALPHAMODE, alpha_mode)) {
586 if (strcmp(alpha_mode.C_Str(), "MASK") == 0) {
587 PN_stdfloat cutoff = 0.5;
588 mat.Get(AI_MATKEY_GLTF_ALPHACUTOFF, cutoff);
589 state = state->add_attrib(AlphaTestAttrib::make(AlphaTestAttrib::M_greater_equal, cutoff));
590 }
591 else if (strcmp(alpha_mode.C_Str(), "BLEND") == 0) {
592 state = state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha));
593 }
594 }
595
596 // And let's not forget the textures!
597 CPT(TextureAttrib) tattr = DCAST(TextureAttrib, TextureAttrib::make());
598 CPT(TexMatrixAttrib) tmattr;
599 load_texture_stage(mat, aiTextureType_DIFFUSE, TextureStage::M_modulate, tattr, tmattr);
600
601 // Check for an ORM map, from the glTF/OBJ importer. glTF also puts it in the
602 // LIGHTMAP slot, despite only having the lightmap in the red channel, so we
603 // have to ignore it.
604 if (mat.GetTextureCount(aiTextureType_UNKNOWN) > 0) {
605 load_texture_stage(mat, aiTextureType_UNKNOWN, TextureStage::M_selector, tattr, tmattr);
606 } else {
607 load_texture_stage(mat, aiTextureType_LIGHTMAP, TextureStage::M_modulate, tattr, tmattr);
608 }
609
610 load_texture_stage(mat, aiTextureType_NORMALS, TextureStage::M_normal, tattr, tmattr);
611 load_texture_stage(mat, aiTextureType_EMISSIVE, TextureStage::M_emission, tattr, tmattr);
612 load_texture_stage(mat, aiTextureType_HEIGHT, TextureStage::M_height, tattr, tmattr);
613 if (tattr->get_num_on_stages() > 0) {
614 state = state->add_attrib(tattr);
615 }
616 if (tmattr != nullptr) {
617 state = state->add_attrib(tmattr);
618 }
619
620 _mat_states[index] = std::move(state);
621}
622
623/**
624 * Creates a CharacterJoint from an aiNode
625 */
626void AssimpLoader::
627create_joint(Character *character, CharacterJointBundle *bundle, PartGroup *parent, const aiNode &node) {
628 const aiMatrix4x4 &t = node.mTransformation;
629 LMatrix4 mat(t.a1, t.b1, t.c1, t.d1,
630 t.a2, t.b2, t.c2, t.d2,
631 t.a3, t.b3, t.c3, t.d3,
632 t.a4, t.b4, t.c4, t.d4);
633 PT(CharacterJoint) joint = new CharacterJoint(character, bundle, parent, node.mName.C_Str(), mat);
634
635 if (assimp_cat.is_debug()) {
636 assimp_cat.debug()
637 << "Creating joint for: " << node.mName.C_Str() << "\n";
638 }
639
640 for (size_t i = 0; i < node.mNumChildren; ++i) {
641 if (_bonemap.find(node.mChildren[i]->mName.C_Str()) != _bonemap.end()) {
642 create_joint(character, bundle, joint, *node.mChildren[i]);
643 }
644 }
645}
646
647/**
648 * Creates a AnimChannelMatrixXfmTable from an aiNodeAnim
649 */
650void AssimpLoader::
651create_anim_channel(const aiAnimation &anim, AnimBundle *bundle, AnimGroup *parent, const aiNode &node) {
652 PT(AnimChannelMatrixXfmTable) group = new AnimChannelMatrixXfmTable(parent, node.mName.C_Str());
653
654 // See if there is a channel for this node
655 aiNodeAnim *node_anim = nullptr;
656 for (size_t i = 0; i < anim.mNumChannels; ++i) {
657 if (anim.mChannels[i]->mNodeName == node.mName) {
658 node_anim = anim.mChannels[i];
659 }
660 }
661
662 if (node_anim) {
663 if (assimp_cat.is_debug()) {
664 assimp_cat.debug()
665 << "Found channel for node: " << node.mName.C_Str() << "\n";
666 }
667 // assimp_cat.debug() << "Num Position Keys " <<
668 // node_anim->mNumPositionKeys << "\n"; assimp_cat.debug() << "Num
669 // Rotation Keys " << node_anim->mNumRotationKeys << "\n";
670 // assimp_cat.debug() << "Num Scaling Keys " << node_anim->mNumScalingKeys
671 // << "\n";
672
673 // Convert positions
674 PTA_stdfloat tablex = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
675 PTA_stdfloat tabley = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
676 PTA_stdfloat tablez = PTA_stdfloat::empty_array(node_anim->mNumPositionKeys);
677 for (size_t i = 0; i < node_anim->mNumPositionKeys; ++i) {
678 tablex[i] = node_anim->mPositionKeys[i].mValue.x;
679 tabley[i] = node_anim->mPositionKeys[i].mValue.y;
680 tablez[i] = node_anim->mPositionKeys[i].mValue.z;
681 }
682 group->set_table('x', tablex);
683 group->set_table('y', tabley);
684 group->set_table('z', tablez);
685
686 // Convert rotations
687 PTA_stdfloat tableh = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
688 PTA_stdfloat tablep = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
689 PTA_stdfloat tabler = PTA_stdfloat::empty_array(node_anim->mNumRotationKeys);
690 for (size_t i = 0; i < node_anim->mNumRotationKeys; ++i) {
691 aiQuaternion ai_quat = node_anim->mRotationKeys[i].mValue;
692 LVecBase3 hpr = LQuaternion(ai_quat.w, ai_quat.x, ai_quat.y, ai_quat.z).get_hpr();
693 tableh[i] = hpr.get_x();
694 tablep[i] = hpr.get_y();
695 tabler[i] = hpr.get_z();
696 }
697 group->set_table('h', tableh);
698 group->set_table('p', tablep);
699 group->set_table('r', tabler);
700
701 // Convert scales
702 PTA_stdfloat tablei = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
703 PTA_stdfloat tablej = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
704 PTA_stdfloat tablek = PTA_stdfloat::empty_array(node_anim->mNumScalingKeys);
705 for (size_t i = 0; i < node_anim->mNumScalingKeys; ++i) {
706 tablei[i] = node_anim->mScalingKeys[i].mValue.x;
707 tablej[i] = node_anim->mScalingKeys[i].mValue.y;
708 tablek[i] = node_anim->mScalingKeys[i].mValue.z;
709 }
710 group->set_table('i', tablei);
711 group->set_table('j', tablej);
712 group->set_table('k', tablek);
713 }
714 else if (assimp_cat.is_debug()) {
715 assimp_cat.debug()
716 << "No channel found for node: " << node.mName.C_Str() << "\n";
717 }
718
719
720 for (size_t i = 0; i < node.mNumChildren; ++i) {
721 if (_bonemap.find(node.mChildren[i]->mName.C_Str()) != _bonemap.end()) {
722 create_anim_channel(anim, bundle, group, *node.mChildren[i]);
723 }
724 }
725}
726
727/**
728 * Converts an aiMesh into a Geom.
729 */
730void AssimpLoader::
731load_mesh(size_t index) {
732 const aiMesh &mesh = *_scene->mMeshes[index];
733
734 // Check if we need to make a Character
735 PT(Character) character = nullptr;
736 if (mesh.HasBones()) {
737 if (assimp_cat.is_debug()) {
738 assimp_cat.debug()
739 << "Creating character for " << mesh.mName.C_Str() << "\n";
740 }
741
742 // Find and add all bone nodes to the bone map
743 for (size_t i = 0; i < mesh.mNumBones; ++i) {
744 const aiBone &bone = *mesh.mBones[i];
745 const aiNode *node = find_node(*_scene->mRootNode, bone.mName);
746 _bonemap[bone.mName.C_Str()] = node;
747 }
748
749 // Now create a character from the bones
750 character = new Character(mesh.mName.C_Str());
751 PT(CharacterJointBundle) bundle = character->get_bundle(0);
752 PT(PartGroup) skeleton = new PartGroup(bundle, "<skeleton>");
753
754 for (size_t i = 0; i < mesh.mNumBones; ++i) {
755 const aiBone &bone = *mesh.mBones[i];
756
757 // Find the root bone node
758 const aiNode *root = _bonemap[bone.mName.C_Str()];
759 while (root->mParent && _bonemap.find(root->mParent->mName.C_Str()) != _bonemap.end()) {
760 root = root->mParent;
761 }
762
763 // Don't process this root if we already have a joint for it
764 if (character->find_joint(root->mName.C_Str())) {
765 continue;
766 }
767
768 create_joint(character, bundle, skeleton, *root);
769 }
770 }
771
772 // Create transform blend table
774 pvector<BoneWeightList> bone_weights(mesh.mNumVertices);
775 if (character) {
776 for (size_t i = 0; i < mesh.mNumBones; ++i) {
777 const aiBone &bone = *mesh.mBones[i];
778 CharacterJoint *joint = character->find_joint(bone.mName.C_Str());
779 if (joint == nullptr) {
780 if (assimp_cat.is_debug()) {
781 assimp_cat.debug()
782 << "Could not find joint for bone: " << bone.mName.C_Str() << "\n";
783 }
784 continue;
785 }
786
787 CPT(JointVertexTransform) jvt = new JointVertexTransform(joint);
788
789 for (size_t j = 0; j < bone.mNumWeights; ++j) {
790 const aiVertexWeight &weight = bone.mWeights[j];
791
792 bone_weights[weight.mVertexId].push_back(BoneWeight(jvt, weight.mWeight));
793 }
794 }
795 }
796
797 // Create the vertex format.
799 aformat->add_column(InternalName::get_vertex(), 3, Geom::NT_stdfloat, Geom::C_point);
800 if (mesh.HasNormals()) {
801 aformat->add_column(InternalName::get_normal(), 3, Geom::NT_stdfloat, Geom::C_normal);
802 }
803 if (mesh.HasVertexColors(0)) {
804 aformat->add_column(InternalName::get_color(), 4, Geom::NT_stdfloat, Geom::C_color);
805 }
806 unsigned int num_uvs = mesh.GetNumUVChannels();
807 if (num_uvs > 0) {
808 // UV sets are named texcoord, texcoord.1, texcoord.2...
809 aformat->add_column(InternalName::get_texcoord(), 3, Geom::NT_stdfloat, Geom::C_texcoord);
810 for (unsigned int u = 1; u < num_uvs; ++u) {
811 ostringstream out;
812 out << u;
813 aformat->add_column(InternalName::get_texcoord_name(out.str()), 3, Geom::NT_stdfloat, Geom::C_texcoord);
814 }
815 }
816 if (mesh.HasTangentsAndBitangents()) {
817 aformat->add_column(InternalName::get_tangent(), 3, Geom::NT_stdfloat, Geom::C_vector);
818 aformat->add_column(InternalName::get_binormal(), 3, Geom::NT_stdfloat, Geom::C_vector);
819 }
820
821 PT(GeomVertexArrayFormat) tb_aformat = new GeomVertexArrayFormat;
822 tb_aformat->add_column(InternalName::make("transform_blend"), 1, Geom::NT_uint16, Geom::C_index);
823
824 // Check to see if we need to convert any animations
825 for (size_t i = 0; i < _scene->mNumAnimations; ++i) {
826 aiAnimation &ai_anim = *_scene->mAnimations[i];
827 bool convert_anim = false;
828
829 if (assimp_cat.is_debug()) {
830 assimp_cat.debug()
831 << "Checking to see if anim (" << ai_anim.mName.C_Str()
832 << ") matches character (" << mesh.mName.C_Str() << ")\n";
833 }
834 for (size_t j = 0; j < ai_anim.mNumChannels; ++j) {
835 if (assimp_cat.is_debug()) {
836 assimp_cat.debug()
837 << "Searching for " << ai_anim.mChannels[j]->mNodeName.C_Str()
838 << " in bone map" << "\n";
839 }
840 if (_bonemap.find(ai_anim.mChannels[j]->mNodeName.C_Str()) != _bonemap.end()) {
841 convert_anim = true;
842 break;
843 }
844 }
845
846 if (convert_anim) {
847 if (assimp_cat.is_debug()) {
848 assimp_cat.debug()
849 << "Found animation (" << ai_anim.mName.C_Str() << ") for character ("
850 << mesh.mName.C_Str() << ")\n";
851 }
852
853 // Now create the animation
854 unsigned int frames = 0;
855 for (size_t j = 0; j < ai_anim.mNumChannels; ++j) {
856 if (ai_anim.mChannels[j]->mNumPositionKeys > frames) {
857 frames = ai_anim.mChannels[j]->mNumPositionKeys;
858 }
859 if (ai_anim.mChannels[j]->mNumRotationKeys > frames) {
860 frames = ai_anim.mChannels[j]->mNumRotationKeys;
861 }
862 if (ai_anim.mChannels[j]->mNumScalingKeys > frames) {
863 frames = ai_anim.mChannels[j]->mNumScalingKeys;
864 }
865 }
866 PN_stdfloat fps = frames / (ai_anim.mTicksPerSecond * ai_anim.mDuration);
867 if (assimp_cat.is_debug()) {
868 assimp_cat.debug()
869 << "FPS " << fps << "\n";
870 assimp_cat.debug()
871 << "Frames " << frames << "\n";
872 }
873
874 PT(AnimBundle) bundle = new AnimBundle(mesh.mName.C_Str(), fps, frames);
875 PT(AnimGroup) skeleton = new AnimGroup(bundle, "<skeleton>");
876
877 for (size_t i = 0; i < mesh.mNumBones; ++i) {
878 const aiBone &bone = *mesh.mBones[i];
879
880 // Find the root bone node
881 const aiNode *root = _bonemap[bone.mName.C_Str()];
882 while (root->mParent && _bonemap.find(root->mParent->mName.C_Str()) != _bonemap.end()) {
883 root = root->mParent;
884 }
885
886 // Only convert root nodes
887 if (root->mName == bone.mName) {
888 create_anim_channel(ai_anim, bundle, skeleton, *root);
889
890 // Attach the animation to the character node
891 PT(AnimBundleNode) bundle_node = new AnimBundleNode(bone.mName.C_Str(), bundle);
892 character->add_child(bundle_node);
893 }
894 }
895 }
896 }
897
898 // TODO: if there is only one UV set, hackily iterate over the texture
899 // stages and clear the texcoord name things
900
901 PT(GeomVertexFormat) format = new GeomVertexFormat;
902 format->add_array(aformat);
903 if (character) {
904 format->add_array(tb_aformat);
905
907 aspec.set_panda();
908 format->set_animation(aspec);
909 }
910
911 // Create the GeomVertexData.
912 string name (mesh.mName.data, mesh.mName.length);
913 PT(GeomVertexData) vdata = new GeomVertexData(name, GeomVertexFormat::register_format(format), Geom::UH_static);
914 if (character) {
915 vdata->set_transform_blend_table(tbtable);
916 }
917 vdata->unclean_set_num_rows(mesh.mNumVertices);
918
919 // Read out the vertices.
920 GeomVertexWriter vertex (vdata, InternalName::get_vertex());
921 for (size_t i = 0; i < mesh.mNumVertices; ++i) {
922 const aiVector3D &vec = mesh.mVertices[i];
923 vertex.set_data3(vec.x, vec.y, vec.z);
924 }
925
926 // Now the normals, if any.
927 if (mesh.HasNormals()) {
928 GeomVertexWriter normal (vdata, InternalName::get_normal());
929 for (size_t i = 0; i < mesh.mNumVertices; ++i) {
930 const aiVector3D &vec = mesh.mNormals[i];
931 normal.set_data3(vec.x, vec.y, vec.z);
932 }
933 }
934
935 // Vertex colors, if any. We only import the first set.
936 if (mesh.HasVertexColors(0)) {
937 GeomVertexWriter color (vdata, InternalName::get_color());
938 for (size_t i = 0; i < mesh.mNumVertices; ++i) {
939 const aiColor4D &col = mesh.mColors[0][i];
940 color.set_data4(col.r, col.g, col.b, col.a);
941 }
942 }
943
944 // Now the texture coordinates.
945 if (num_uvs > 0) {
946 // UV sets are named texcoord, texcoord.1, texcoord.2...
947 GeomVertexWriter texcoord0 (vdata, InternalName::get_texcoord());
948 for (size_t i = 0; i < mesh.mNumVertices; ++i) {
949 const aiVector3D &vec = mesh.mTextureCoords[0][i];
950 texcoord0.set_data3(vec.x, vec.y, vec.z);
951 }
952 for (unsigned int u = 1; u < num_uvs; ++u) {
953 ostringstream out;
954 out << u;
955 GeomVertexWriter texcoord (vdata, InternalName::get_texcoord_name(out.str()));
956 for (size_t i = 0; i < mesh.mNumVertices; ++i) {
957 const aiVector3D &vec = mesh.mTextureCoords[u][i];
958 texcoord.set_data3(vec.x, vec.y, vec.z);
959 }
960 }
961 }
962
963 // Now the tangents and bitangents, if any.
964 if (mesh.HasTangentsAndBitangents()) {
965 GeomVertexWriter tangent (vdata, InternalName::get_tangent());
966 GeomVertexWriter binormal (vdata, InternalName::get_binormal());
967 for (size_t i = 0; i < mesh.mNumVertices; ++i) {
968 const aiVector3D &tvec = mesh.mTangents[i];
969 const aiVector3D &bvec = mesh.mBitangents[i];
970 tangent.set_data3(tvec.x, tvec.y, tvec.z);
971 binormal.set_data3(bvec.x, bvec.y, bvec.z);
972 }
973 }
974
975 // Now the transform blend table
976 if (character) {
977 GeomVertexWriter transform_blend (vdata, InternalName::get_transform_blend());
978
979 for (size_t i = 0; i < mesh.mNumVertices; ++i) {
980 TransformBlend tblend;
981
982 for (size_t j = 0; j < bone_weights[i].size(); ++j) {
983 tblend.add_transform(bone_weights[i][j].joint_vertex_xform, bone_weights[i][j].weight);
984 }
985 transform_blend.set_data1i(tbtable->add_blend(tblend));
986 }
987
988 tbtable->set_rows(SparseArray::lower_on(vdata->get_num_rows()));
989 }
990
991 // Now read out the primitives. Keep in mind that we called ReadFile with
992 // the aiProcess_Triangulate flag earlier, so we don't have to worry about
993 // polygons.
994 PT(GeomPoints) points = new GeomPoints(Geom::UH_static);
995 PT(GeomLines) lines = new GeomLines(Geom::UH_static);
996 PT(GeomTriangles) triangles = new GeomTriangles(Geom::UH_static);
997
998 // Now add the vertex indices.
999 for (size_t i = 0; i < mesh.mNumFaces; ++i) {
1000 const aiFace &face = mesh.mFaces[i];
1001
1002 if (face.mNumIndices == 0) {
1003 // It happens, strangely enough.
1004 continue;
1005 } else if (face.mNumIndices == 1) {
1006 points->add_vertex(face.mIndices[0]);
1007 points->close_primitive();
1008 } else if (face.mNumIndices == 2) {
1009 lines->add_vertices(face.mIndices[0], face.mIndices[1]);
1010 lines->close_primitive();
1011 } else if (face.mNumIndices == 3) {
1012 triangles->add_vertices(face.mIndices[0], face.mIndices[1], face.mIndices[2]);
1013 triangles->close_primitive();
1014 } else {
1015 nassertd(false) continue;
1016 }
1017 }
1018
1019 // Create a geom and add the primitives to it.
1020 Geoms &geoms = _geoms[index];
1021 geoms._mat_index = mesh.mMaterialIndex;
1022
1023 if (points->get_num_primitives() > 0) {
1024 geoms._points = new Geom(vdata);
1025 geoms._points->add_primitive(points);
1026 }
1027 if (lines->get_num_primitives() > 0) {
1028 geoms._lines = new Geom(vdata);
1029 geoms._lines->add_primitive(lines);
1030 }
1031 if (triangles->get_num_primitives() > 0) {
1032 geoms._triangles = new Geom(vdata);
1033 geoms._triangles->add_primitive(triangles);
1034 }
1035
1036 if (character) {
1037 _charmap[mesh.mName.C_Str()] = character;
1038 }
1039}
1040
1041/**
1042 * Converts an aiNode into a PandaNode.
1043 */
1044void AssimpLoader::
1045load_node(const aiNode &node, PandaNode *parent) {
1046 PT(PandaNode) pnode;
1047 PT(Character) character;
1048
1049 // Skip nodes we've converted to joints
1050 if (_bonemap.find(node.mName.C_Str()) != _bonemap.end()) {
1051 return;
1052 }
1053
1054 // Create the node and give it a name.
1055 string name (node.mName.data, node.mName.length);
1056 if (node.mNumMeshes > 0) {
1057 pnode = new GeomNode(name);
1058 } else {
1059 // Many importers create a dummy root node, but they all call it
1060 // differently, and some (glTF) create it only conditionally.
1061 // It usually has some funny name like <SomethingRoot> or $dummy_root,
1062 // except the .obj loader, which assigns it the model base name like we do.
1063 if (parent == _root && assimp_collapse_dummy_root_node &&
1064 _charmap.find(node.mName.C_Str()) == _charmap.end() &&
1065 (name.empty() || name[0] == '$' || name == "RootNode" || name == "ROOT" || name == "Root" || (name.size() > 2 && name[0] == '<' && name[name.size() - 1] == '>') || name == _root->get_name())) {
1066 // Collapse root node.
1067 pnode = _root;
1068 } else {
1069 pnode = new PandaNode(name);
1070 }
1071 }
1072
1073 if (_charmap.find(node.mName.C_Str()) != _charmap.end()) {
1074 character = _charmap[node.mName.C_Str()];
1075 parent->add_child(character);
1076 }
1077 else if (parent != pnode) {
1078 parent->add_child(pnode);
1079 }
1080
1081 if (node.mMetaData != nullptr) {
1082 for (unsigned i = 0; i < node.mMetaData->mNumProperties; ++i) {
1083 const aiMetadataEntry &entry = node.mMetaData->mValues[i];
1084 std::string value;
1085 switch (entry.mType) {
1086 //case AI_BOOL:
1087 // value = (*static_cast<bool *>(entry.mData)) ? "1" : "";
1088 // break;
1089 case (aiMetadataType)1: // AI_INT32
1090 value = format_string(*static_cast<int32_t *>(entry.mData));
1091 break;
1092 case AI_UINT64:
1093 value = format_string(*static_cast<uint64_t *>(entry.mData));
1094 break;
1095 case AI_FLOAT:
1096 value = format_string(*static_cast<float *>(entry.mData));
1097 break;
1098 case AI_AISTRING:
1099 {
1100 const aiString *str = static_cast<const aiString *>(entry.mData);
1101 value = std::string(str->data, str->length);
1102 }
1103 break;
1104 default:
1105 // Special case because AI_DOUBLE was added in Assimp 4.0 with the same
1106 // value as AI_AISTRING. Defined as if so that we don't get a duplicate
1107 // case error with moder ncompilers.
1108 if (entry.mType == (aiMetadataType)4) {
1109 value = format_string(*static_cast<double *>(entry.mData));
1110 break;
1111 }
1112 continue;
1113 }
1114 const aiString &key = node.mMetaData->mKeys[i];
1115 pnode->set_tag(std::string(key.data, key.length), std::move(value));
1116 }
1117 }
1118
1119 // Load in the transformation matrix.
1120 const aiMatrix4x4 &t = node.mTransformation;
1121 if (!t.IsIdentity()) {
1122 LMatrix4 mat(t.a1, t.b1, t.c1, t.d1,
1123 t.a2, t.b2, t.c2, t.d2,
1124 t.a3, t.b3, t.c3, t.d3,
1125 t.a4, t.b4, t.c4, t.d4);
1126 pnode->set_transform(TransformState::make_mat(mat));
1127 }
1128
1129 for (size_t i = 0; i < node.mNumChildren; ++i) {
1130 load_node(*node.mChildren[i], pnode);
1131 }
1132
1133 if (node.mNumMeshes > 0) {
1134 // Remember, we created this as GeomNode earlier.
1135 PT(GeomNode) gnode = DCAST(GeomNode, pnode);
1136 size_t meshIndex;
1137
1138 // If there's only mesh, don't bother using a per-geom state.
1139 if (node.mNumMeshes == 1) {
1140 meshIndex = node.mMeshes[0];
1141 const Geoms &geoms = _geoms[meshIndex];
1142 if (geoms._points != nullptr) {
1143 gnode->add_geom(geoms._points);
1144 }
1145 if (geoms._lines != nullptr) {
1146 gnode->add_geom(geoms._lines);
1147 }
1148 if (geoms._triangles != nullptr) {
1149 gnode->add_geom(geoms._triangles);
1150 }
1151 gnode->set_state(_mat_states[geoms._mat_index]);
1152 } else {
1153 for (size_t i = 0; i < node.mNumMeshes; ++i) {
1154 meshIndex = node.mMeshes[i];
1155 const Geoms &geoms = _geoms[meshIndex];
1156 const RenderState *state = _mat_states[geoms._mat_index];
1157 if (geoms._points != nullptr) {
1158 gnode->add_geom(geoms._points, state);
1159 }
1160 if (geoms._lines != nullptr) {
1161 gnode->add_geom(geoms._lines, state);
1162 }
1163 if (geoms._triangles != nullptr) {
1164 gnode->add_geom(geoms._triangles, state);
1165 }
1166 }
1167 }
1168
1169 if (character) {
1170 if (assimp_cat.is_debug()) {
1171 assimp_cat.debug() << "Adding char to geom\n";
1172 }
1173 character->add_child(gnode);
1174 }
1175 }
1176}
1177
1178/**
1179 * Converts an aiLight into a LightNode.
1180 */
1181void AssimpLoader::
1182load_light(const aiLight &light) {
1183 string name (light.mName.data, light.mName.length);
1184 if (assimp_cat.is_debug()) {
1185 assimp_cat.debug() << "Found light '" << name << "'\n";
1186 }
1187
1188 aiColor3D col;
1189 aiVector3D vec;
1190
1191 switch (light.mType) {
1192 case aiLightSource_DIRECTIONAL: {
1193 PT(DirectionalLight) dlight = new DirectionalLight(name);
1194 _root->add_child(dlight);
1195
1196 col = light.mColorDiffuse;
1197 dlight->set_color(LColor(col.r, col.g, col.b, 1));
1198
1199 col = light.mColorSpecular;
1200 dlight->set_specular_color(LColor(col.r, col.g, col.b, 1));
1201
1202 vec = light.mPosition;
1203 dlight->set_point(LPoint3(vec.x, vec.y, vec.z));
1204
1205 vec = light.mDirection;
1206 dlight->set_direction(LVector3(vec.x, vec.y, vec.z));
1207 break; }
1208
1209 case aiLightSource_POINT: {
1210 PT(PointLight) plight = new PointLight(name);
1211 _root->add_child(plight);
1212
1213 col = light.mColorDiffuse;
1214 plight->set_color(LColor(col.r, col.g, col.b, 1));
1215
1216 col = light.mColorSpecular;
1217 plight->set_specular_color(LColor(col.r, col.g, col.b, 1));
1218
1219 vec = light.mPosition;
1220 plight->set_point(LPoint3(vec.x, vec.y, vec.z));
1221
1222 plight->set_attenuation(LVecBase3(light.mAttenuationConstant,
1223 light.mAttenuationLinear,
1224 light.mAttenuationQuadratic));
1225 break; }
1226
1227 case aiLightSource_SPOT: {
1228 PT(Spotlight) plight = new Spotlight(name);
1229 _root->add_child(plight);
1230
1231 col = light.mColorDiffuse;
1232 plight->set_color(LColor(col.r, col.g, col.b, 1));
1233
1234 col = light.mColorSpecular;
1235 plight->set_specular_color(LColor(col.r, col.g, col.b, 1));
1236
1237 plight->set_attenuation(LVecBase3(light.mAttenuationConstant,
1238 light.mAttenuationLinear,
1239 light.mAttenuationQuadratic));
1240
1241 plight->get_lens()->set_fov(light.mAngleOuterCone);
1242 // TODO: translate mAngleInnerCone to an exponent, somehow
1243
1244 // This *should* be about right.
1245 vec = light.mDirection;
1246 LPoint3 pos (light.mPosition.x, light.mPosition.y, light.mPosition.z);
1247 LQuaternion quat;
1248 ::look_at(quat, LPoint3(vec.x, vec.y, vec.z), LVector3::up());
1249 plight->set_transform(TransformState::make_pos_quat_scale(pos, quat, LVecBase3(1, 1, 1)));
1250 break; }
1251
1252 // This is a somewhat recent addition to Assimp, so let's be kind to those
1253 // that don't have an up-to-date version of Assimp.
1254 case 0x4: //aiLightSource_AMBIENT:
1255 // This is handled below.
1256 break;
1257
1258 default:
1259 assimp_cat.warning() << "Light '" << name << "' has an unknown type!\n";
1260 return;
1261 }
1262
1263 // If there's an ambient color, add it as ambient light.
1264 col = light.mColorAmbient;
1265 LVecBase4 ambient (col.r, col.g, col.b, 0);
1266 if (ambient != LVecBase4::zero()) {
1267 PT(AmbientLight) alight = new AmbientLight(name);
1268 alight->set_color(ambient);
1269 _root->add_child(alight);
1270 }
1271}
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.
A light source that seems to illuminate all points in space at once.
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 ...
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:44
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...
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.
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
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_base_color
Specifies the base color of the material.
Definition material.h:112
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.
static void set_default()
Makes sure there's a global PandaLogger object and makes sure that it is Assimp's default logger.
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 is the base class for a number of render attributes (other than transform) that may be set on sc...
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
Applies a transform matrix to UV's before they are rendered.
Indicates the set of TextureStages and their associated Textures that should be applied to (or remove...
get_num_on_stages
Returns the number of stages that are turned on by the attribute.
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.
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition texture.h:72
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.
Indicates a coordinate-system transform on vertices.
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 as a regular file in the virtual file sys...
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.
STL namespace.
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.