vrshoot

annotate libs/assimp/FBXConverter.cpp @ 0:b2f14e535253

initial commit
author John Tsiombikas <nuclear@member.fsf.org>
date Sat, 01 Feb 2014 19:58:19 +0200
parents
children
rev   line source
nuclear@0 1 /*
nuclear@0 2 Open Asset Import Library (assimp)
nuclear@0 3 ----------------------------------------------------------------------
nuclear@0 4
nuclear@0 5 Copyright (c) 2006-2012, assimp team
nuclear@0 6 All rights reserved.
nuclear@0 7
nuclear@0 8 Redistribution and use of this software in source and binary forms,
nuclear@0 9 with or without modification, are permitted provided that the
nuclear@0 10 following conditions are met:
nuclear@0 11
nuclear@0 12 * Redistributions of source code must retain the above
nuclear@0 13 copyright notice, this list of conditions and the
nuclear@0 14 following disclaimer.
nuclear@0 15
nuclear@0 16 * Redistributions in binary form must reproduce the above
nuclear@0 17 copyright notice, this list of conditions and the
nuclear@0 18 following disclaimer in the documentation and/or other
nuclear@0 19 materials provided with the distribution.
nuclear@0 20
nuclear@0 21 * Neither the name of the assimp team, nor the names of its
nuclear@0 22 contributors may be used to endorse or promote products
nuclear@0 23 derived from this software without specific prior
nuclear@0 24 written permission of the assimp team.
nuclear@0 25
nuclear@0 26 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
nuclear@0 27 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
nuclear@0 28 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
nuclear@0 29 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
nuclear@0 30 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
nuclear@0 31 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
nuclear@0 32 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
nuclear@0 33 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
nuclear@0 34 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
nuclear@0 35 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
nuclear@0 36 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
nuclear@0 37
nuclear@0 38 ----------------------------------------------------------------------
nuclear@0 39 */
nuclear@0 40
nuclear@0 41 /** @file FBXConverter.cpp
nuclear@0 42 * @brief Implementation of the FBX DOM -> aiScene converter
nuclear@0 43 */
nuclear@0 44 #include "AssimpPCH.h"
nuclear@0 45
nuclear@0 46 #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
nuclear@0 47
nuclear@0 48 #include <iterator>
nuclear@0 49 #include <boost/tuple/tuple.hpp>
nuclear@0 50
nuclear@0 51 #include "FBXParser.h"
nuclear@0 52 #include "FBXConverter.h"
nuclear@0 53 #include "FBXDocument.h"
nuclear@0 54 #include "FBXUtil.h"
nuclear@0 55 #include "FBXProperties.h"
nuclear@0 56 #include "FBXImporter.h"
nuclear@0 57
nuclear@0 58 namespace Assimp {
nuclear@0 59 namespace FBX {
nuclear@0 60
nuclear@0 61 using namespace Util;
nuclear@0 62
nuclear@0 63
nuclear@0 64 #define MAGIC_NODE_TAG "_$AssimpFbx$"
nuclear@0 65 #define MAGIC_NULL_TAG "_$AssimpFbxNull$"
nuclear@0 66
nuclear@0 67 #define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
nuclear@0 68
nuclear@0 69 // XXX vc9's debugger won't step into anonymous namespaces
nuclear@0 70 //namespace {
nuclear@0 71
nuclear@0 72 /** Dummy class to encapsulate the conversion process */
nuclear@0 73 class Converter
nuclear@0 74 {
nuclear@0 75 public:
nuclear@0 76
nuclear@0 77 /** the different parts that make up the final local transformation of a fbx node */
nuclear@0 78 enum TransformationComp
nuclear@0 79 {
nuclear@0 80 TransformationComp_Translation = 0,
nuclear@0 81 TransformationComp_RotationOffset,
nuclear@0 82 TransformationComp_RotationPivot,
nuclear@0 83 TransformationComp_PreRotation,
nuclear@0 84 TransformationComp_Rotation,
nuclear@0 85 TransformationComp_PostRotation,
nuclear@0 86 TransformationComp_RotationPivotInverse,
nuclear@0 87 TransformationComp_ScalingOffset,
nuclear@0 88 TransformationComp_ScalingPivot,
nuclear@0 89 TransformationComp_Scaling,
nuclear@0 90 TransformationComp_ScalingPivotInverse,
nuclear@0 91
nuclear@0 92 TransformationComp_MAXIMUM
nuclear@0 93 };
nuclear@0 94
nuclear@0 95
nuclear@0 96
nuclear@0 97 public:
nuclear@0 98
nuclear@0 99 Converter(aiScene* out, const Document& doc)
nuclear@0 100 : defaultMaterialIndex()
nuclear@0 101 , out(out)
nuclear@0 102 , doc(doc)
nuclear@0 103 {
nuclear@0 104 // animations need to be converted first since this will
nuclear@0 105 // populate the node_anim_chain_bits map, which is needed
nuclear@0 106 // to determine which nodes need to be generated.
nuclear@0 107 ConvertAnimations();
nuclear@0 108 ConvertRootNode();
nuclear@0 109
nuclear@0 110 if(doc.Settings().readAllMaterials) {
nuclear@0 111 // unfortunately this means we have to evaluate all objects
nuclear@0 112 BOOST_FOREACH(const ObjectMap::value_type& v,doc.Objects()) {
nuclear@0 113
nuclear@0 114 const Object* ob = v.second->Get();
nuclear@0 115 if(!ob) {
nuclear@0 116 continue;
nuclear@0 117 }
nuclear@0 118
nuclear@0 119 const Material* mat = dynamic_cast<const Material*>(ob);
nuclear@0 120 if(mat) {
nuclear@0 121
nuclear@0 122 if (materials_converted.find(mat) == materials_converted.end()) {
nuclear@0 123 ConvertMaterial(*mat);
nuclear@0 124 }
nuclear@0 125 }
nuclear@0 126 }
nuclear@0 127 }
nuclear@0 128
nuclear@0 129 TransferDataToScene();
nuclear@0 130
nuclear@0 131 // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
nuclear@0 132 // to make sure the scene passes assimp's validation. FBX files
nuclear@0 133 // need not contain geometry (i.e. camera animations, raw armatures).
nuclear@0 134 if (out->mNumMeshes == 0) {
nuclear@0 135 out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
nuclear@0 136 }
nuclear@0 137 }
nuclear@0 138
nuclear@0 139
nuclear@0 140 ~Converter()
nuclear@0 141 {
nuclear@0 142 std::for_each(meshes.begin(),meshes.end(),Util::delete_fun<aiMesh>());
nuclear@0 143 std::for_each(materials.begin(),materials.end(),Util::delete_fun<aiMaterial>());
nuclear@0 144 std::for_each(animations.begin(),animations.end(),Util::delete_fun<aiAnimation>());
nuclear@0 145 std::for_each(lights.begin(),lights.end(),Util::delete_fun<aiLight>());
nuclear@0 146 std::for_each(cameras.begin(),cameras.end(),Util::delete_fun<aiCamera>());
nuclear@0 147 }
nuclear@0 148
nuclear@0 149
nuclear@0 150 private:
nuclear@0 151
nuclear@0 152 // ------------------------------------------------------------------------------------------------
nuclear@0 153 // find scene root and trigger recursive scene conversion
nuclear@0 154 void ConvertRootNode()
nuclear@0 155 {
nuclear@0 156 out->mRootNode = new aiNode();
nuclear@0 157 out->mRootNode->mName.Set("RootNode");
nuclear@0 158
nuclear@0 159 // root has ID 0
nuclear@0 160 ConvertNodes(0L, *out->mRootNode);
nuclear@0 161 }
nuclear@0 162
nuclear@0 163
nuclear@0 164 // ------------------------------------------------------------------------------------------------
nuclear@0 165 // collect and assign child nodes
nuclear@0 166 void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4())
nuclear@0 167 {
nuclear@0 168 const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
nuclear@0 169
nuclear@0 170 std::vector<aiNode*> nodes;
nuclear@0 171 nodes.reserve(conns.size());
nuclear@0 172
nuclear@0 173 std::vector<aiNode*> nodes_chain;
nuclear@0 174
nuclear@0 175 try {
nuclear@0 176 BOOST_FOREACH(const Connection* con, conns) {
nuclear@0 177
nuclear@0 178 // ignore object-property links
nuclear@0 179 if(con->PropertyName().length()) {
nuclear@0 180 continue;
nuclear@0 181 }
nuclear@0 182
nuclear@0 183 const Object* const object = con->SourceObject();
nuclear@0 184 if(!object) {
nuclear@0 185 FBXImporter::LogWarn("failed to convert source object for Model link");
nuclear@0 186 continue;
nuclear@0 187 }
nuclear@0 188
nuclear@0 189 const Model* const model = dynamic_cast<const Model*>(object);
nuclear@0 190
nuclear@0 191 if(model) {
nuclear@0 192 nodes_chain.clear();
nuclear@0 193
nuclear@0 194 aiMatrix4x4 new_abs_transform = parent_transform;
nuclear@0 195
nuclear@0 196 // even though there is only a single input node, the design of
nuclear@0 197 // assimp (or rather: the complicated transformation chain that
nuclear@0 198 // is employed by fbx) means that we may need multiple aiNode's
nuclear@0 199 // to represent a fbx node's transformation.
nuclear@0 200 GenerateTransformationNodeChain(*model,nodes_chain);
nuclear@0 201
nuclear@0 202 ai_assert(nodes_chain.size());
nuclear@0 203
nuclear@0 204 const std::string& original_name = FixNodeName(model->Name());
nuclear@0 205
nuclear@0 206 // check if any of the nodes in the chain has the name the fbx node
nuclear@0 207 // is supposed to have. If there is none, add another node to
nuclear@0 208 // preserve the name - people might have scripts etc. that rely
nuclear@0 209 // on specific node names.
nuclear@0 210 aiNode* name_carrier = NULL;
nuclear@0 211 BOOST_FOREACH(aiNode* prenode, nodes_chain) {
nuclear@0 212 if ( !strcmp(prenode->mName.C_Str(), original_name.c_str()) ) {
nuclear@0 213 name_carrier = prenode;
nuclear@0 214 break;
nuclear@0 215 }
nuclear@0 216 }
nuclear@0 217
nuclear@0 218 if(!name_carrier) {
nuclear@0 219 nodes_chain.push_back(new aiNode(original_name));
nuclear@0 220 name_carrier = nodes_chain.back();
nuclear@0 221 }
nuclear@0 222
nuclear@0 223 // link all nodes in a row
nuclear@0 224 aiNode* last_parent = &parent;
nuclear@0 225 BOOST_FOREACH(aiNode* prenode, nodes_chain) {
nuclear@0 226 ai_assert(prenode);
nuclear@0 227
nuclear@0 228 if(last_parent != &parent) {
nuclear@0 229 last_parent->mNumChildren = 1;
nuclear@0 230 last_parent->mChildren = new aiNode*[1];
nuclear@0 231 last_parent->mChildren[0] = prenode;
nuclear@0 232 }
nuclear@0 233
nuclear@0 234 prenode->mParent = last_parent;
nuclear@0 235 last_parent = prenode;
nuclear@0 236
nuclear@0 237 new_abs_transform *= prenode->mTransformation;
nuclear@0 238 }
nuclear@0 239
nuclear@0 240 // attach geometry
nuclear@0 241 ConvertModel(*model, *nodes_chain.back(), new_abs_transform);
nuclear@0 242
nuclear@0 243 // attach sub-nodes
nuclear@0 244 ConvertNodes(model->ID(), *nodes_chain.back(), new_abs_transform);
nuclear@0 245
nuclear@0 246 if(doc.Settings().readLights) {
nuclear@0 247 ConvertLights(*model);
nuclear@0 248 }
nuclear@0 249
nuclear@0 250 if(doc.Settings().readCameras) {
nuclear@0 251 ConvertCameras(*model);
nuclear@0 252 }
nuclear@0 253
nuclear@0 254 // preserve the info that a node was marked as Null node
nuclear@0 255 // in the original file.
nuclear@0 256 if(model->IsNull()) {
nuclear@0 257 const std::string& new_name = original_name + MAGIC_NULL_TAG;
nuclear@0 258 RenameNode(original_name, new_name);
nuclear@0 259 name_carrier->mName.Set( new_name.c_str() );
nuclear@0 260 }
nuclear@0 261
nuclear@0 262 nodes.push_back(nodes_chain.front());
nuclear@0 263 nodes_chain.clear();
nuclear@0 264 }
nuclear@0 265 }
nuclear@0 266
nuclear@0 267 if(nodes.size()) {
nuclear@0 268 parent.mChildren = new aiNode*[nodes.size()]();
nuclear@0 269 parent.mNumChildren = static_cast<unsigned int>(nodes.size());
nuclear@0 270
nuclear@0 271 std::swap_ranges(nodes.begin(),nodes.end(),parent.mChildren);
nuclear@0 272 }
nuclear@0 273 }
nuclear@0 274 catch(std::exception&) {
nuclear@0 275 Util::delete_fun<aiNode> deleter;
nuclear@0 276 std::for_each(nodes.begin(),nodes.end(),deleter);
nuclear@0 277 std::for_each(nodes_chain.begin(),nodes_chain.end(),deleter);
nuclear@0 278 }
nuclear@0 279 }
nuclear@0 280
nuclear@0 281
nuclear@0 282 // ------------------------------------------------------------------------------------------------
nuclear@0 283 void ConvertLights(const Model& model)
nuclear@0 284 {
nuclear@0 285 const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
nuclear@0 286 BOOST_FOREACH(const NodeAttribute* attr, node_attrs) {
nuclear@0 287 const Light* const light = dynamic_cast<const Light*>(attr);
nuclear@0 288 if(light) {
nuclear@0 289 ConvertLight(model, *light);
nuclear@0 290 }
nuclear@0 291 }
nuclear@0 292 }
nuclear@0 293
nuclear@0 294
nuclear@0 295 // ------------------------------------------------------------------------------------------------
nuclear@0 296 void ConvertCameras(const Model& model)
nuclear@0 297 {
nuclear@0 298 const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
nuclear@0 299 BOOST_FOREACH(const NodeAttribute* attr, node_attrs) {
nuclear@0 300 const Camera* const cam = dynamic_cast<const Camera*>(attr);
nuclear@0 301 if(cam) {
nuclear@0 302 ConvertCamera(model, *cam);
nuclear@0 303 }
nuclear@0 304 }
nuclear@0 305 }
nuclear@0 306
nuclear@0 307
nuclear@0 308 // ------------------------------------------------------------------------------------------------
nuclear@0 309 void ConvertLight(const Model& model, const Light& light)
nuclear@0 310 {
nuclear@0 311 lights.push_back(new aiLight());
nuclear@0 312 aiLight* const out_light = lights.back();
nuclear@0 313
nuclear@0 314 out_light->mName.Set(FixNodeName(model.Name()));
nuclear@0 315
nuclear@0 316 const float intensity = light.Intensity();
nuclear@0 317 const aiVector3D& col = light.Color();
nuclear@0 318
nuclear@0 319 out_light->mColorDiffuse = aiColor3D(col.x,col.y,col.z);
nuclear@0 320 out_light->mColorDiffuse.r *= intensity;
nuclear@0 321 out_light->mColorDiffuse.g *= intensity;
nuclear@0 322 out_light->mColorDiffuse.b *= intensity;
nuclear@0 323
nuclear@0 324 out_light->mColorSpecular = out_light->mColorDiffuse;
nuclear@0 325
nuclear@0 326 switch(light.LightType())
nuclear@0 327 {
nuclear@0 328 case Light::Type_Point:
nuclear@0 329 out_light->mType = aiLightSource_POINT;
nuclear@0 330 break;
nuclear@0 331
nuclear@0 332 case Light::Type_Directional:
nuclear@0 333 out_light->mType = aiLightSource_DIRECTIONAL;
nuclear@0 334 break;
nuclear@0 335
nuclear@0 336 case Light::Type_Spot:
nuclear@0 337 out_light->mType = aiLightSource_SPOT;
nuclear@0 338 out_light->mAngleOuterCone = AI_DEG_TO_RAD(light.OuterAngle());
nuclear@0 339 out_light->mAngleInnerCone = AI_DEG_TO_RAD(light.InnerAngle());
nuclear@0 340 break;
nuclear@0 341
nuclear@0 342 case Light::Type_Area:
nuclear@0 343 FBXImporter::LogWarn("cannot represent area light, set to UNDEFINED");
nuclear@0 344 out_light->mType = aiLightSource_UNDEFINED;
nuclear@0 345 break;
nuclear@0 346
nuclear@0 347 case Light::Type_Volume:
nuclear@0 348 FBXImporter::LogWarn("cannot represent volume light, set to UNDEFINED");
nuclear@0 349 out_light->mType = aiLightSource_UNDEFINED;
nuclear@0 350 break;
nuclear@0 351 default:
nuclear@0 352 ai_assert(false);
nuclear@0 353 }
nuclear@0 354
nuclear@0 355 // XXX: how to best convert the near and far decay ranges?
nuclear@0 356 switch(light.DecayType())
nuclear@0 357 {
nuclear@0 358 case Light::Decay_None:
nuclear@0 359 out_light->mAttenuationConstant = 1.0f;
nuclear@0 360 break;
nuclear@0 361 case Light::Decay_Linear:
nuclear@0 362 out_light->mAttenuationLinear = 1.0f;
nuclear@0 363 break;
nuclear@0 364 case Light::Decay_Quadratic:
nuclear@0 365 out_light->mAttenuationQuadratic = 1.0f;
nuclear@0 366 break;
nuclear@0 367 case Light::Decay_Cubic:
nuclear@0 368 FBXImporter::LogWarn("cannot represent cubic attenuation, set to Quadratic");
nuclear@0 369 out_light->mAttenuationQuadratic = 1.0f;
nuclear@0 370 break;
nuclear@0 371 default:
nuclear@0 372 ai_assert(false);
nuclear@0 373 }
nuclear@0 374 }
nuclear@0 375
nuclear@0 376
nuclear@0 377 // ------------------------------------------------------------------------------------------------
nuclear@0 378 void ConvertCamera(const Model& model, const Camera& cam)
nuclear@0 379 {
nuclear@0 380 cameras.push_back(new aiCamera());
nuclear@0 381 aiCamera* const out_camera = cameras.back();
nuclear@0 382
nuclear@0 383 out_camera->mName.Set(FixNodeName(model.Name()));
nuclear@0 384
nuclear@0 385 out_camera->mAspect = cam.AspectWidth();
nuclear@0 386 out_camera->mPosition = cam.Position();
nuclear@0 387 out_camera->mLookAt = cam.InterestPosition() - out_camera->mPosition;
nuclear@0 388
nuclear@0 389 out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView());
nuclear@0 390 }
nuclear@0 391
nuclear@0 392
nuclear@0 393 // ------------------------------------------------------------------------------------------------
nuclear@0 394 // this returns unified names usable within assimp identifiers (i.e. no space characters -
nuclear@0 395 // while these would be allowed, they are a potential trouble spot so better not use them).
nuclear@0 396 const char* NameTransformationComp(TransformationComp comp)
nuclear@0 397 {
nuclear@0 398 switch(comp)
nuclear@0 399 {
nuclear@0 400 case TransformationComp_Translation:
nuclear@0 401 return "Translation";
nuclear@0 402 case TransformationComp_RotationOffset:
nuclear@0 403 return "RotationOffset";
nuclear@0 404 case TransformationComp_RotationPivot:
nuclear@0 405 return "RotationPivot";
nuclear@0 406 case TransformationComp_PreRotation:
nuclear@0 407 return "PreRotation";
nuclear@0 408 case TransformationComp_Rotation:
nuclear@0 409 return "Rotation";
nuclear@0 410 case TransformationComp_PostRotation:
nuclear@0 411 return "PostRotation";
nuclear@0 412 case TransformationComp_RotationPivotInverse:
nuclear@0 413 return "RotationPivotInverse";
nuclear@0 414 case TransformationComp_ScalingOffset:
nuclear@0 415 return "ScalingOffset";
nuclear@0 416 case TransformationComp_ScalingPivot:
nuclear@0 417 return "ScalingPivot";
nuclear@0 418 case TransformationComp_Scaling:
nuclear@0 419 return "Scaling";
nuclear@0 420 case TransformationComp_ScalingPivotInverse:
nuclear@0 421 return "ScalingPivotInverse";
nuclear@0 422 case TransformationComp_MAXIMUM: // this is to silence compiler warnings
nuclear@0 423 break;
nuclear@0 424 }
nuclear@0 425
nuclear@0 426 ai_assert(false);
nuclear@0 427 return NULL;
nuclear@0 428 }
nuclear@0 429
nuclear@0 430
nuclear@0 431 // ------------------------------------------------------------------------------------------------
nuclear@0 432 // note: this returns the REAL fbx property names
nuclear@0 433 const char* NameTransformationCompProperty(TransformationComp comp)
nuclear@0 434 {
nuclear@0 435 switch(comp)
nuclear@0 436 {
nuclear@0 437 case TransformationComp_Translation:
nuclear@0 438 return "Lcl Translation";
nuclear@0 439 case TransformationComp_RotationOffset:
nuclear@0 440 return "RotationOffset";
nuclear@0 441 case TransformationComp_RotationPivot:
nuclear@0 442 return "RotationPivot";
nuclear@0 443 case TransformationComp_PreRotation:
nuclear@0 444 return "PreRotation";
nuclear@0 445 case TransformationComp_Rotation:
nuclear@0 446 return "Lcl Rotation";
nuclear@0 447 case TransformationComp_PostRotation:
nuclear@0 448 return "PostRotation";
nuclear@0 449 case TransformationComp_RotationPivotInverse:
nuclear@0 450 return "RotationPivotInverse";
nuclear@0 451 case TransformationComp_ScalingOffset:
nuclear@0 452 return "ScalingOffset";
nuclear@0 453 case TransformationComp_ScalingPivot:
nuclear@0 454 return "ScalingPivot";
nuclear@0 455 case TransformationComp_Scaling:
nuclear@0 456 return "Lcl Scaling";
nuclear@0 457 case TransformationComp_ScalingPivotInverse:
nuclear@0 458 return "ScalingPivotInverse";
nuclear@0 459 case TransformationComp_MAXIMUM: // this is to silence compiler warnings
nuclear@0 460 break;
nuclear@0 461 }
nuclear@0 462
nuclear@0 463 ai_assert(false);
nuclear@0 464 return NULL;
nuclear@0 465 }
nuclear@0 466
nuclear@0 467
nuclear@0 468 // ------------------------------------------------------------------------------------------------
nuclear@0 469 aiVector3D TransformationCompDefaultValue(TransformationComp comp)
nuclear@0 470 {
nuclear@0 471 // XXX a neat way to solve the never-ending special cases for scaling
nuclear@0 472 // would be to do everything in log space!
nuclear@0 473 return comp == TransformationComp_Scaling ? aiVector3D(1.f,1.f,1.f) : aiVector3D();
nuclear@0 474 }
nuclear@0 475
nuclear@0 476
nuclear@0 477 // ------------------------------------------------------------------------------------------------
nuclear@0 478 void GetRotationMatrix(Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out)
nuclear@0 479 {
nuclear@0 480 if(mode == Model::RotOrder_SphericXYZ) {
nuclear@0 481 FBXImporter::LogError("Unsupported RotationMode: SphericXYZ");
nuclear@0 482 out = aiMatrix4x4();
nuclear@0 483 return;
nuclear@0 484 }
nuclear@0 485
nuclear@0 486 const float angle_epsilon = 1e-6f;
nuclear@0 487
nuclear@0 488 out = aiMatrix4x4();
nuclear@0 489
nuclear@0 490 bool is_id[3] = { true, true, true };
nuclear@0 491
nuclear@0 492 aiMatrix4x4 temp[3];
nuclear@0 493 if(fabs(rotation.z) > angle_epsilon) {
nuclear@0 494 aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(rotation.z),temp[2]);
nuclear@0 495 is_id[2] = false;
nuclear@0 496 }
nuclear@0 497 if(fabs(rotation.y) > angle_epsilon) {
nuclear@0 498 aiMatrix4x4::RotationY(AI_DEG_TO_RAD(rotation.y),temp[1]);
nuclear@0 499 is_id[1] = false;
nuclear@0 500 }
nuclear@0 501 if(fabs(rotation.x) > angle_epsilon) {
nuclear@0 502 aiMatrix4x4::RotationX(AI_DEG_TO_RAD(rotation.x),temp[0]);
nuclear@0 503 is_id[0] = false;
nuclear@0 504 }
nuclear@0 505
nuclear@0 506 int order[3] = {-1, -1, -1};
nuclear@0 507
nuclear@0 508 // note: rotation order is inverted since we're left multiplying as is usual in assimp
nuclear@0 509 switch(mode)
nuclear@0 510 {
nuclear@0 511 case Model::RotOrder_EulerXYZ:
nuclear@0 512 order[0] = 2;
nuclear@0 513 order[1] = 1;
nuclear@0 514 order[2] = 0;
nuclear@0 515 break;
nuclear@0 516
nuclear@0 517 case Model::RotOrder_EulerXZY:
nuclear@0 518 order[0] = 1;
nuclear@0 519 order[1] = 2;
nuclear@0 520 order[2] = 0;
nuclear@0 521 break;
nuclear@0 522
nuclear@0 523 case Model::RotOrder_EulerYZX:
nuclear@0 524 order[0] = 0;
nuclear@0 525 order[1] = 2;
nuclear@0 526 order[2] = 1;
nuclear@0 527 break;
nuclear@0 528
nuclear@0 529 case Model::RotOrder_EulerYXZ:
nuclear@0 530 order[0] = 2;
nuclear@0 531 order[1] = 0;
nuclear@0 532 order[2] = 1;
nuclear@0 533 break;
nuclear@0 534
nuclear@0 535 case Model::RotOrder_EulerZXY:
nuclear@0 536 order[0] = 1;
nuclear@0 537 order[1] = 0;
nuclear@0 538 order[2] = 2;
nuclear@0 539 break;
nuclear@0 540
nuclear@0 541 case Model::RotOrder_EulerZYX:
nuclear@0 542 order[0] = 0;
nuclear@0 543 order[1] = 1;
nuclear@0 544 order[2] = 2;
nuclear@0 545 break;
nuclear@0 546
nuclear@0 547 default:
nuclear@0 548 ai_assert(false);
nuclear@0 549 }
nuclear@0 550
nuclear@0 551 if(!is_id[order[0]]) {
nuclear@0 552 out = temp[order[0]];
nuclear@0 553 }
nuclear@0 554
nuclear@0 555 if(!is_id[order[1]]) {
nuclear@0 556 out = out * temp[order[1]];
nuclear@0 557 }
nuclear@0 558
nuclear@0 559 if(!is_id[order[2]]) {
nuclear@0 560 out = out * temp[order[2]];
nuclear@0 561 }
nuclear@0 562 }
nuclear@0 563
nuclear@0 564
nuclear@0 565 // ------------------------------------------------------------------------------------------------
nuclear@0 566 /** checks if a node has more than just scaling, rotation and translation components */
nuclear@0 567 bool NeedsComplexTransformationChain(const Model& model)
nuclear@0 568 {
nuclear@0 569 const PropertyTable& props = model.Props();
nuclear@0 570 bool ok;
nuclear@0 571
nuclear@0 572 const float zero_epsilon = 1e-6f;
nuclear@0 573 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
nuclear@0 574 const TransformationComp comp = static_cast<TransformationComp>(i);
nuclear@0 575
nuclear@0 576 if(comp == TransformationComp_Rotation || comp == TransformationComp_Scaling ||
nuclear@0 577 comp == TransformationComp_Translation) {
nuclear@0 578
nuclear@0 579 continue;
nuclear@0 580 }
nuclear@0 581
nuclear@0 582 const aiVector3D& v = PropertyGet<aiVector3D>(props,NameTransformationCompProperty(comp),ok);
nuclear@0 583 if(ok && v.SquareLength() > zero_epsilon) {
nuclear@0 584 return true;
nuclear@0 585 }
nuclear@0 586 }
nuclear@0 587
nuclear@0 588 return false;
nuclear@0 589 }
nuclear@0 590
nuclear@0 591
nuclear@0 592 // ------------------------------------------------------------------------------------------------
nuclear@0 593 // note: name must be a FixNodeName() result
nuclear@0 594 std::string NameTransformationChainNode(const std::string& name, TransformationComp comp)
nuclear@0 595 {
nuclear@0 596 return name + std::string(MAGIC_NODE_TAG) + "_" + NameTransformationComp(comp);
nuclear@0 597 }
nuclear@0 598
nuclear@0 599
nuclear@0 600 // ------------------------------------------------------------------------------------------------
nuclear@0 601 /** note: memory for output_nodes will be managed by the caller */
nuclear@0 602 void GenerateTransformationNodeChain(const Model& model,
nuclear@0 603 std::vector<aiNode*>& output_nodes)
nuclear@0 604 {
nuclear@0 605 const PropertyTable& props = model.Props();
nuclear@0 606 const Model::RotOrder rot = model.RotationOrder();
nuclear@0 607
nuclear@0 608 bool ok;
nuclear@0 609
nuclear@0 610 aiMatrix4x4 chain[TransformationComp_MAXIMUM];
nuclear@0 611 std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
nuclear@0 612
nuclear@0 613 // generate transformation matrices for all the different transformation components
nuclear@0 614 const float zero_epsilon = 1e-6f;
nuclear@0 615 bool is_complex = false;
nuclear@0 616
nuclear@0 617 const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props,"PreRotation",ok);
nuclear@0 618 if(ok && PreRotation.SquareLength() > zero_epsilon) {
nuclear@0 619 is_complex = true;
nuclear@0 620
nuclear@0 621 GetRotationMatrix(rot, PreRotation, chain[TransformationComp_PreRotation]);
nuclear@0 622 }
nuclear@0 623
nuclear@0 624 const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props,"PostRotation",ok);
nuclear@0 625 if(ok && PostRotation.SquareLength() > zero_epsilon) {
nuclear@0 626 is_complex = true;
nuclear@0 627
nuclear@0 628 GetRotationMatrix(rot, PostRotation, chain[TransformationComp_PostRotation]);
nuclear@0 629 }
nuclear@0 630
nuclear@0 631 const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props,"RotationPivot",ok);
nuclear@0 632 if(ok && RotationPivot.SquareLength() > zero_epsilon) {
nuclear@0 633 is_complex = true;
nuclear@0 634
nuclear@0 635 aiMatrix4x4::Translation(RotationPivot,chain[TransformationComp_RotationPivot]);
nuclear@0 636 aiMatrix4x4::Translation(-RotationPivot,chain[TransformationComp_RotationPivotInverse]);
nuclear@0 637 }
nuclear@0 638
nuclear@0 639 const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props,"RotationOffset",ok);
nuclear@0 640 if(ok && RotationOffset.SquareLength() > zero_epsilon) {
nuclear@0 641 is_complex = true;
nuclear@0 642
nuclear@0 643 aiMatrix4x4::Translation(RotationOffset,chain[TransformationComp_RotationOffset]);
nuclear@0 644 }
nuclear@0 645
nuclear@0 646 const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props,"ScalingOffset",ok);
nuclear@0 647 if(ok && ScalingOffset.SquareLength() > zero_epsilon) {
nuclear@0 648 is_complex = true;
nuclear@0 649
nuclear@0 650 aiMatrix4x4::Translation(ScalingOffset,chain[TransformationComp_ScalingOffset]);
nuclear@0 651 }
nuclear@0 652
nuclear@0 653 const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props,"ScalingPivot",ok);
nuclear@0 654 if(ok && ScalingPivot.SquareLength() > zero_epsilon) {
nuclear@0 655 is_complex = true;
nuclear@0 656
nuclear@0 657 aiMatrix4x4::Translation(ScalingPivot,chain[TransformationComp_ScalingPivot]);
nuclear@0 658 aiMatrix4x4::Translation(-ScalingPivot,chain[TransformationComp_ScalingPivotInverse]);
nuclear@0 659 }
nuclear@0 660
nuclear@0 661 const aiVector3D& Translation = PropertyGet<aiVector3D>(props,"Lcl Translation",ok);
nuclear@0 662 if(ok && Translation.SquareLength() > zero_epsilon) {
nuclear@0 663 aiMatrix4x4::Translation(Translation,chain[TransformationComp_Translation]);
nuclear@0 664 }
nuclear@0 665
nuclear@0 666 const aiVector3D& Scaling = PropertyGet<aiVector3D>(props,"Lcl Scaling",ok);
nuclear@0 667 if(ok && fabs(Scaling.SquareLength()-1.0f) > zero_epsilon) {
nuclear@0 668 aiMatrix4x4::Scaling(Scaling,chain[TransformationComp_Scaling]);
nuclear@0 669 }
nuclear@0 670
nuclear@0 671 const aiVector3D& Rotation = PropertyGet<aiVector3D>(props,"Lcl Rotation",ok);
nuclear@0 672 if(ok && Rotation.SquareLength() > zero_epsilon) {
nuclear@0 673 GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
nuclear@0 674 }
nuclear@0 675
nuclear@0 676 // is_complex needs to be consistent with NeedsComplexTransformationChain()
nuclear@0 677 // or the interplay between this code and the animation converter would
nuclear@0 678 // not be guaranteed.
nuclear@0 679 ai_assert(NeedsComplexTransformationChain(model) == is_complex);
nuclear@0 680
nuclear@0 681 const std::string& name = FixNodeName(model.Name());
nuclear@0 682
nuclear@0 683 // now, if we have more than just Translation, Scaling and Rotation,
nuclear@0 684 // we need to generate a full node chain to accommodate for assimp's
nuclear@0 685 // lack to express pivots and offsets.
nuclear@0 686 if(is_complex && doc.Settings().preservePivots) {
nuclear@0 687 FBXImporter::LogInfo("generating full transformation chain for node: " + name);
nuclear@0 688
nuclear@0 689 // query the anim_chain_bits dictionary to find out which chain elements
nuclear@0 690 // have associated node animation channels. These can not be dropped
nuclear@0 691 // even if they have identity transform in bind pose.
nuclear@0 692 NodeAnimBitMap::const_iterator it = node_anim_chain_bits.find(name);
nuclear@0 693 const unsigned int anim_chain_bitmask = (it == node_anim_chain_bits.end() ? 0 : (*it).second);
nuclear@0 694
nuclear@0 695 unsigned int bit = 0x1;
nuclear@0 696 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
nuclear@0 697 const TransformationComp comp = static_cast<TransformationComp>(i);
nuclear@0 698
nuclear@0 699 if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) {
nuclear@0 700 continue;
nuclear@0 701 }
nuclear@0 702
nuclear@0 703 aiNode* nd = new aiNode();
nuclear@0 704 output_nodes.push_back(nd);
nuclear@0 705
nuclear@0 706 nd->mName.Set(NameTransformationChainNode(name, comp));
nuclear@0 707 nd->mTransformation = chain[i];
nuclear@0 708 }
nuclear@0 709
nuclear@0 710 ai_assert(output_nodes.size());
nuclear@0 711 return;
nuclear@0 712 }
nuclear@0 713
nuclear@0 714 // else, we can just multiply the matrices together
nuclear@0 715 aiNode* nd = new aiNode();
nuclear@0 716 output_nodes.push_back(nd);
nuclear@0 717
nuclear@0 718 nd->mName.Set(name);
nuclear@0 719
nuclear@0 720 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
nuclear@0 721 nd->mTransformation = nd->mTransformation * chain[i];
nuclear@0 722 }
nuclear@0 723 }
nuclear@0 724
nuclear@0 725
nuclear@0 726 // ------------------------------------------------------------------------------------------------
nuclear@0 727 void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform)
nuclear@0 728 {
nuclear@0 729 const std::vector<const Geometry*>& geos = model.GetGeometry();
nuclear@0 730
nuclear@0 731 std::vector<unsigned int> meshes;
nuclear@0 732 meshes.reserve(geos.size());
nuclear@0 733
nuclear@0 734 BOOST_FOREACH(const Geometry* geo, geos) {
nuclear@0 735
nuclear@0 736 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
nuclear@0 737 if(mesh) {
nuclear@0 738 const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, node_global_transform);
nuclear@0 739 std::copy(indices.begin(),indices.end(),std::back_inserter(meshes) );
nuclear@0 740 }
nuclear@0 741 else {
nuclear@0 742 FBXImporter::LogWarn("ignoring unrecognized geometry: " + geo->Name());
nuclear@0 743 }
nuclear@0 744 }
nuclear@0 745
nuclear@0 746 if(meshes.size()) {
nuclear@0 747 nd.mMeshes = new unsigned int[meshes.size()]();
nuclear@0 748 nd.mNumMeshes = static_cast<unsigned int>(meshes.size());
nuclear@0 749
nuclear@0 750 std::swap_ranges(meshes.begin(),meshes.end(),nd.mMeshes);
nuclear@0 751 }
nuclear@0 752 }
nuclear@0 753
nuclear@0 754
nuclear@0 755 // ------------------------------------------------------------------------------------------------
nuclear@0 756 // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
nuclear@0 757 std::vector<unsigned int> ConvertMesh(const MeshGeometry& mesh,const Model& model,
nuclear@0 758 const aiMatrix4x4& node_global_transform)
nuclear@0 759 {
nuclear@0 760 std::vector<unsigned int> temp;
nuclear@0 761
nuclear@0 762 MeshMap::const_iterator it = meshes_converted.find(&mesh);
nuclear@0 763 if (it != meshes_converted.end()) {
nuclear@0 764 std::copy((*it).second.begin(),(*it).second.end(),std::back_inserter(temp));
nuclear@0 765 return temp;
nuclear@0 766 }
nuclear@0 767
nuclear@0 768 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
nuclear@0 769 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
nuclear@0 770 if(vertices.empty() || faces.empty()) {
nuclear@0 771 FBXImporter::LogWarn("ignoring empty geometry: " + mesh.Name());
nuclear@0 772 return temp;
nuclear@0 773 }
nuclear@0 774
nuclear@0 775 // one material per mesh maps easily to aiMesh. Multiple material
nuclear@0 776 // meshes need to be split.
nuclear@0 777 const MatIndexArray& mindices = mesh.GetMaterialIndices();
nuclear@0 778 if (doc.Settings().readMaterials && !mindices.empty()) {
nuclear@0 779 const MatIndexArray::value_type base = mindices[0];
nuclear@0 780 BOOST_FOREACH(MatIndexArray::value_type index, mindices) {
nuclear@0 781 if(index != base) {
nuclear@0 782 return ConvertMeshMultiMaterial(mesh, model, node_global_transform);
nuclear@0 783 }
nuclear@0 784 }
nuclear@0 785 }
nuclear@0 786
nuclear@0 787 // faster codepath, just copy the data
nuclear@0 788 temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform));
nuclear@0 789 return temp;
nuclear@0 790 }
nuclear@0 791
nuclear@0 792
nuclear@0 793 // ------------------------------------------------------------------------------------------------
nuclear@0 794 aiMesh* SetupEmptyMesh(const MeshGeometry& mesh)
nuclear@0 795 {
nuclear@0 796 aiMesh* const out_mesh = new aiMesh();
nuclear@0 797 meshes.push_back(out_mesh);
nuclear@0 798 meshes_converted[&mesh].push_back(static_cast<unsigned int>(meshes.size()-1));
nuclear@0 799
nuclear@0 800 // set name
nuclear@0 801 std::string name = mesh.Name();
nuclear@0 802 if (name.substr(0,10) == "Geometry::") {
nuclear@0 803 name = name.substr(10);
nuclear@0 804 }
nuclear@0 805
nuclear@0 806 if(name.length()) {
nuclear@0 807 out_mesh->mName.Set(name);
nuclear@0 808 }
nuclear@0 809
nuclear@0 810 return out_mesh;
nuclear@0 811 }
nuclear@0 812
nuclear@0 813
nuclear@0 814 // ------------------------------------------------------------------------------------------------
nuclear@0 815 unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
nuclear@0 816 const aiMatrix4x4& node_global_transform)
nuclear@0 817 {
nuclear@0 818 const MatIndexArray& mindices = mesh.GetMaterialIndices();
nuclear@0 819 aiMesh* const out_mesh = SetupEmptyMesh(mesh);
nuclear@0 820
nuclear@0 821 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
nuclear@0 822 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
nuclear@0 823
nuclear@0 824 // copy vertices
nuclear@0 825 out_mesh->mNumVertices = static_cast<unsigned int>(vertices.size());
nuclear@0 826 out_mesh->mVertices = new aiVector3D[vertices.size()];
nuclear@0 827 std::copy(vertices.begin(),vertices.end(),out_mesh->mVertices);
nuclear@0 828
nuclear@0 829 // generate dummy faces
nuclear@0 830 out_mesh->mNumFaces = static_cast<unsigned int>(faces.size());
nuclear@0 831 aiFace* fac = out_mesh->mFaces = new aiFace[faces.size()]();
nuclear@0 832
nuclear@0 833 unsigned int cursor = 0;
nuclear@0 834 BOOST_FOREACH(unsigned int pcount, faces) {
nuclear@0 835 aiFace& f = *fac++;
nuclear@0 836 f.mNumIndices = pcount;
nuclear@0 837 f.mIndices = new unsigned int[pcount];
nuclear@0 838 switch(pcount)
nuclear@0 839 {
nuclear@0 840 case 1:
nuclear@0 841 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
nuclear@0 842 break;
nuclear@0 843 case 2:
nuclear@0 844 out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
nuclear@0 845 break;
nuclear@0 846 case 3:
nuclear@0 847 out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
nuclear@0 848 break;
nuclear@0 849 default:
nuclear@0 850 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
nuclear@0 851 break;
nuclear@0 852 }
nuclear@0 853 for (unsigned int i = 0; i < pcount; ++i) {
nuclear@0 854 f.mIndices[i] = cursor++;
nuclear@0 855 }
nuclear@0 856 }
nuclear@0 857
nuclear@0 858 // copy normals
nuclear@0 859 const std::vector<aiVector3D>& normals = mesh.GetNormals();
nuclear@0 860 if(normals.size()) {
nuclear@0 861 ai_assert(normals.size() == vertices.size());
nuclear@0 862
nuclear@0 863 out_mesh->mNormals = new aiVector3D[vertices.size()];
nuclear@0 864 std::copy(normals.begin(),normals.end(),out_mesh->mNormals);
nuclear@0 865 }
nuclear@0 866
nuclear@0 867 // copy tangents - assimp requires both tangents and bitangents (binormals)
nuclear@0 868 // to be present, or neither of them. Compute binormals from normals
nuclear@0 869 // and tangents if needed.
nuclear@0 870 const std::vector<aiVector3D>& tangents = mesh.GetTangents();
nuclear@0 871 const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
nuclear@0 872
nuclear@0 873 if(tangents.size()) {
nuclear@0 874 std::vector<aiVector3D> tempBinormals;
nuclear@0 875 if (!binormals->size()) {
nuclear@0 876 if (normals.size()) {
nuclear@0 877 tempBinormals.resize(normals.size());
nuclear@0 878 for (unsigned int i = 0; i < tangents.size(); ++i) {
nuclear@0 879 tempBinormals[i] = normals[i] ^ tangents[i];
nuclear@0 880 }
nuclear@0 881
nuclear@0 882 binormals = &tempBinormals;
nuclear@0 883 }
nuclear@0 884 else {
nuclear@0 885 binormals = NULL;
nuclear@0 886 }
nuclear@0 887 }
nuclear@0 888
nuclear@0 889 if(binormals) {
nuclear@0 890 ai_assert(tangents.size() == vertices.size() && binormals->size() == vertices.size());
nuclear@0 891
nuclear@0 892 out_mesh->mTangents = new aiVector3D[vertices.size()];
nuclear@0 893 std::copy(tangents.begin(),tangents.end(),out_mesh->mTangents);
nuclear@0 894
nuclear@0 895 out_mesh->mBitangents = new aiVector3D[vertices.size()];
nuclear@0 896 std::copy(binormals->begin(),binormals->end(),out_mesh->mBitangents);
nuclear@0 897 }
nuclear@0 898 }
nuclear@0 899
nuclear@0 900 // copy texture coords
nuclear@0 901 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
nuclear@0 902 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
nuclear@0 903 if(uvs.empty()) {
nuclear@0 904 break;
nuclear@0 905 }
nuclear@0 906
nuclear@0 907 aiVector3D* out_uv = out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()];
nuclear@0 908 BOOST_FOREACH(const aiVector2D& v, uvs) {
nuclear@0 909 *out_uv++ = aiVector3D(v.x,v.y,0.0f);
nuclear@0 910 }
nuclear@0 911
nuclear@0 912 out_mesh->mNumUVComponents[i] = 2;
nuclear@0 913 }
nuclear@0 914
nuclear@0 915 // copy vertex colors
nuclear@0 916 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) {
nuclear@0 917 const std::vector<aiColor4D>& colors = mesh.GetVertexColors(i);
nuclear@0 918 if(colors.empty()) {
nuclear@0 919 break;
nuclear@0 920 }
nuclear@0 921
nuclear@0 922 out_mesh->mColors[i] = new aiColor4D[vertices.size()];
nuclear@0 923 std::copy(colors.begin(),colors.end(),out_mesh->mColors[i]);
nuclear@0 924 }
nuclear@0 925
nuclear@0 926 if(!doc.Settings().readMaterials || mindices.empty()) {
nuclear@0 927 FBXImporter::LogError("no material assigned to mesh, setting default material");
nuclear@0 928 out_mesh->mMaterialIndex = GetDefaultMaterial();
nuclear@0 929 }
nuclear@0 930 else {
nuclear@0 931 ConvertMaterialForMesh(out_mesh,model,mesh,mindices[0]);
nuclear@0 932 }
nuclear@0 933
nuclear@0 934 if(doc.Settings().readWeights && mesh.DeformerSkin() != NULL) {
nuclear@0 935 ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION);
nuclear@0 936 }
nuclear@0 937
nuclear@0 938 return static_cast<unsigned int>(meshes.size() - 1);
nuclear@0 939 }
nuclear@0 940
nuclear@0 941
nuclear@0 942 // ------------------------------------------------------------------------------------------------
nuclear@0 943 std::vector<unsigned int> ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
nuclear@0 944 const aiMatrix4x4& node_global_transform)
nuclear@0 945 {
nuclear@0 946 const MatIndexArray& mindices = mesh.GetMaterialIndices();
nuclear@0 947 ai_assert(mindices.size());
nuclear@0 948
nuclear@0 949 std::set<MatIndexArray::value_type> had;
nuclear@0 950 std::vector<unsigned int> indices;
nuclear@0 951
nuclear@0 952 BOOST_FOREACH(MatIndexArray::value_type index, mindices) {
nuclear@0 953 if(had.find(index) == had.end()) {
nuclear@0 954
nuclear@0 955 indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform));
nuclear@0 956 had.insert(index);
nuclear@0 957 }
nuclear@0 958 }
nuclear@0 959
nuclear@0 960 return indices;
nuclear@0 961 }
nuclear@0 962
nuclear@0 963
nuclear@0 964 // ------------------------------------------------------------------------------------------------
nuclear@0 965 unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
nuclear@0 966 MatIndexArray::value_type index,
nuclear@0 967 const aiMatrix4x4& node_global_transform)
nuclear@0 968 {
nuclear@0 969 aiMesh* const out_mesh = SetupEmptyMesh(mesh);
nuclear@0 970
nuclear@0 971 const MatIndexArray& mindices = mesh.GetMaterialIndices();
nuclear@0 972 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
nuclear@0 973 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
nuclear@0 974
nuclear@0 975 const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL;
nuclear@0 976
nuclear@0 977 unsigned int count_faces = 0;
nuclear@0 978 unsigned int count_vertices = 0;
nuclear@0 979
nuclear@0 980 // count faces
nuclear@0 981 std::vector<unsigned int>::const_iterator itf = faces.begin();
nuclear@0 982 for(MatIndexArray::const_iterator it = mindices.begin(),
nuclear@0 983 end = mindices.end(); it != end; ++it, ++itf)
nuclear@0 984 {
nuclear@0 985 if ((*it) != index) {
nuclear@0 986 continue;
nuclear@0 987 }
nuclear@0 988 ++count_faces;
nuclear@0 989 count_vertices += *itf;
nuclear@0 990 }
nuclear@0 991
nuclear@0 992 ai_assert(count_faces);
nuclear@0 993 ai_assert(count_vertices);
nuclear@0 994
nuclear@0 995 // mapping from output indices to DOM indexing, needed to resolve weights
nuclear@0 996 std::vector<unsigned int> reverseMapping;
nuclear@0 997
nuclear@0 998 if (process_weights) {
nuclear@0 999 reverseMapping.resize(count_vertices);
nuclear@0 1000 }
nuclear@0 1001
nuclear@0 1002 // allocate output data arrays, but don't fill them yet
nuclear@0 1003 out_mesh->mNumVertices = count_vertices;
nuclear@0 1004 out_mesh->mVertices = new aiVector3D[count_vertices];
nuclear@0 1005
nuclear@0 1006 out_mesh->mNumFaces = count_faces;
nuclear@0 1007 aiFace* fac = out_mesh->mFaces = new aiFace[count_faces]();
nuclear@0 1008
nuclear@0 1009
nuclear@0 1010 // allocate normals
nuclear@0 1011 const std::vector<aiVector3D>& normals = mesh.GetNormals();
nuclear@0 1012 if(normals.size()) {
nuclear@0 1013 ai_assert(normals.size() == vertices.size());
nuclear@0 1014 out_mesh->mNormals = new aiVector3D[vertices.size()];
nuclear@0 1015 }
nuclear@0 1016
nuclear@0 1017 // allocate tangents, binormals.
nuclear@0 1018 const std::vector<aiVector3D>& tangents = mesh.GetTangents();
nuclear@0 1019 const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
nuclear@0 1020
nuclear@0 1021 if(tangents.size()) {
nuclear@0 1022 std::vector<aiVector3D> tempBinormals;
nuclear@0 1023 if (!binormals->size()) {
nuclear@0 1024 if (normals.size()) {
nuclear@0 1025 // XXX this computes the binormals for the entire mesh, not only
nuclear@0 1026 // the part for which we need them.
nuclear@0 1027 tempBinormals.resize(normals.size());
nuclear@0 1028 for (unsigned int i = 0; i < tangents.size(); ++i) {
nuclear@0 1029 tempBinormals[i] = normals[i] ^ tangents[i];
nuclear@0 1030 }
nuclear@0 1031
nuclear@0 1032 binormals = &tempBinormals;
nuclear@0 1033 }
nuclear@0 1034 else {
nuclear@0 1035 binormals = NULL;
nuclear@0 1036 }
nuclear@0 1037 }
nuclear@0 1038
nuclear@0 1039 if(binormals) {
nuclear@0 1040 ai_assert(tangents.size() == vertices.size() && binormals->size() == vertices.size());
nuclear@0 1041
nuclear@0 1042 out_mesh->mTangents = new aiVector3D[vertices.size()];
nuclear@0 1043 out_mesh->mBitangents = new aiVector3D[vertices.size()];
nuclear@0 1044 }
nuclear@0 1045 }
nuclear@0 1046
nuclear@0 1047 // allocate texture coords
nuclear@0 1048 unsigned int num_uvs = 0;
nuclear@0 1049 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i, ++num_uvs) {
nuclear@0 1050 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
nuclear@0 1051 if(uvs.empty()) {
nuclear@0 1052 break;
nuclear@0 1053 }
nuclear@0 1054
nuclear@0 1055 out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()];
nuclear@0 1056 out_mesh->mNumUVComponents[i] = 2;
nuclear@0 1057 }
nuclear@0 1058
nuclear@0 1059 // allocate vertex colors
nuclear@0 1060 unsigned int num_vcs = 0;
nuclear@0 1061 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs) {
nuclear@0 1062 const std::vector<aiColor4D>& colors = mesh.GetVertexColors(i);
nuclear@0 1063 if(colors.empty()) {
nuclear@0 1064 break;
nuclear@0 1065 }
nuclear@0 1066
nuclear@0 1067 out_mesh->mColors[i] = new aiColor4D[vertices.size()];
nuclear@0 1068 }
nuclear@0 1069
nuclear@0 1070 unsigned int cursor = 0, in_cursor = 0;
nuclear@0 1071
nuclear@0 1072 itf = faces.begin();
nuclear@0 1073 for(MatIndexArray::const_iterator it = mindices.begin(),
nuclear@0 1074 end = mindices.end(); it != end; ++it, ++itf)
nuclear@0 1075 {
nuclear@0 1076 const unsigned int pcount = *itf;
nuclear@0 1077 if ((*it) != index) {
nuclear@0 1078 in_cursor += pcount;
nuclear@0 1079 continue;
nuclear@0 1080 }
nuclear@0 1081
nuclear@0 1082 aiFace& f = *fac++;
nuclear@0 1083
nuclear@0 1084 f.mNumIndices = pcount;
nuclear@0 1085 f.mIndices = new unsigned int[pcount];
nuclear@0 1086 switch(pcount)
nuclear@0 1087 {
nuclear@0 1088 case 1:
nuclear@0 1089 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
nuclear@0 1090 break;
nuclear@0 1091 case 2:
nuclear@0 1092 out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
nuclear@0 1093 break;
nuclear@0 1094 case 3:
nuclear@0 1095 out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
nuclear@0 1096 break;
nuclear@0 1097 default:
nuclear@0 1098 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
nuclear@0 1099 break;
nuclear@0 1100 }
nuclear@0 1101 for (unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor) {
nuclear@0 1102 f.mIndices[i] = cursor;
nuclear@0 1103
nuclear@0 1104 if(reverseMapping.size()) {
nuclear@0 1105 reverseMapping[cursor] = in_cursor;
nuclear@0 1106 }
nuclear@0 1107
nuclear@0 1108 out_mesh->mVertices[cursor] = vertices[in_cursor];
nuclear@0 1109
nuclear@0 1110 if(out_mesh->mNormals) {
nuclear@0 1111 out_mesh->mNormals[cursor] = normals[in_cursor];
nuclear@0 1112 }
nuclear@0 1113
nuclear@0 1114 if(out_mesh->mTangents) {
nuclear@0 1115 out_mesh->mTangents[cursor] = tangents[in_cursor];
nuclear@0 1116 out_mesh->mBitangents[cursor] = (*binormals)[in_cursor];
nuclear@0 1117 }
nuclear@0 1118
nuclear@0 1119 for (unsigned int i = 0; i < num_uvs; ++i) {
nuclear@0 1120 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
nuclear@0 1121 out_mesh->mTextureCoords[i][cursor] = aiVector3D(uvs[in_cursor].x,uvs[in_cursor].y, 0.0f);
nuclear@0 1122 }
nuclear@0 1123
nuclear@0 1124 for (unsigned int i = 0; i < num_vcs; ++i) {
nuclear@0 1125 const std::vector<aiColor4D>& cols = mesh.GetVertexColors(i);
nuclear@0 1126 out_mesh->mColors[i][cursor] = cols[in_cursor];
nuclear@0 1127 }
nuclear@0 1128 }
nuclear@0 1129 }
nuclear@0 1130
nuclear@0 1131 ConvertMaterialForMesh(out_mesh,model,mesh,index);
nuclear@0 1132
nuclear@0 1133 if(process_weights) {
nuclear@0 1134 ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping);
nuclear@0 1135 }
nuclear@0 1136
nuclear@0 1137 return static_cast<unsigned int>(meshes.size() - 1);
nuclear@0 1138 }
nuclear@0 1139
nuclear@0 1140 static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
nuclear@0 1141 static_cast<unsigned int>(-1);
nuclear@0 1142
nuclear@0 1143
nuclear@0 1144 // ------------------------------------------------------------------------------------------------
nuclear@0 1145 /** - if materialIndex == NO_MATERIAL_SEPARATION, materials are not taken into
nuclear@0 1146 * account when determining which weights to include.
nuclear@0 1147 * - outputVertStartIndices is only used when a material index is specified, it gives for
nuclear@0 1148 * each output vertex the DOM index it maps to. */
nuclear@0 1149 void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
nuclear@0 1150 const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
nuclear@0 1151 unsigned int materialIndex = NO_MATERIAL_SEPARATION,
nuclear@0 1152 std::vector<unsigned int>* outputVertStartIndices = NULL)
nuclear@0 1153 {
nuclear@0 1154 ai_assert(geo.DeformerSkin());
nuclear@0 1155
nuclear@0 1156 std::vector<size_t> out_indices;
nuclear@0 1157 std::vector<size_t> index_out_indices;
nuclear@0 1158 std::vector<size_t> count_out_indices;
nuclear@0 1159
nuclear@0 1160 const Skin& sk = *geo.DeformerSkin();
nuclear@0 1161
nuclear@0 1162 std::vector<aiBone*> bones;
nuclear@0 1163 bones.reserve(sk.Clusters().size());
nuclear@0 1164
nuclear@0 1165 const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
nuclear@0 1166 ai_assert(no_mat_check || outputVertStartIndices);
nuclear@0 1167
nuclear@0 1168 try {
nuclear@0 1169
nuclear@0 1170 BOOST_FOREACH(const Cluster* cluster, sk.Clusters()) {
nuclear@0 1171 ai_assert(cluster);
nuclear@0 1172
nuclear@0 1173 const WeightIndexArray& indices = cluster->GetIndices();
nuclear@0 1174
nuclear@0 1175 if(indices.empty()) {
nuclear@0 1176 continue;
nuclear@0 1177 }
nuclear@0 1178
nuclear@0 1179 const MatIndexArray& mats = geo.GetMaterialIndices();
nuclear@0 1180
nuclear@0 1181 bool ok = false;
nuclear@0 1182
nuclear@0 1183 const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
nuclear@0 1184
nuclear@0 1185 count_out_indices.clear();
nuclear@0 1186 index_out_indices.clear();
nuclear@0 1187 out_indices.clear();
nuclear@0 1188
nuclear@0 1189 // now check if *any* of these weights is contained in the output mesh,
nuclear@0 1190 // taking notes so we don't need to do it twice.
nuclear@0 1191 BOOST_FOREACH(WeightIndexArray::value_type index, indices) {
nuclear@0 1192
nuclear@0 1193 unsigned int count;
nuclear@0 1194 const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count);
nuclear@0 1195
nuclear@0 1196 index_out_indices.push_back(no_index_sentinel);
nuclear@0 1197 count_out_indices.push_back(0);
nuclear@0 1198
nuclear@0 1199 for(unsigned int i = 0; i < count; ++i) {
nuclear@0 1200 if (no_mat_check || static_cast<size_t>(mats[geo.FaceForVertexIndex(out_idx[i])]) == materialIndex) {
nuclear@0 1201
nuclear@0 1202 if (index_out_indices.back() == no_index_sentinel) {
nuclear@0 1203 index_out_indices.back() = out_indices.size();
nuclear@0 1204
nuclear@0 1205 }
nuclear@0 1206
nuclear@0 1207 if (no_mat_check) {
nuclear@0 1208 out_indices.push_back(out_idx[i]);
nuclear@0 1209 }
nuclear@0 1210 else {
nuclear@0 1211 // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn)
nuclear@0 1212 const std::vector<unsigned int>::iterator it = std::lower_bound(
nuclear@0 1213 outputVertStartIndices->begin(),
nuclear@0 1214 outputVertStartIndices->end(),
nuclear@0 1215 out_idx[i]
nuclear@0 1216 );
nuclear@0 1217
nuclear@0 1218 out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
nuclear@0 1219 }
nuclear@0 1220
nuclear@0 1221 ++count_out_indices.back();
nuclear@0 1222 ok = true;
nuclear@0 1223 }
nuclear@0 1224 }
nuclear@0 1225 }
nuclear@0 1226
nuclear@0 1227 // if we found at least one, generate the output bones
nuclear@0 1228 // XXX this could be heavily simplified by collecting the bone
nuclear@0 1229 // data in a single step.
nuclear@0 1230 if (ok) {
nuclear@0 1231 ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
nuclear@0 1232 count_out_indices, node_global_transform);
nuclear@0 1233 }
nuclear@0 1234 }
nuclear@0 1235 }
nuclear@0 1236 catch (std::exception&) {
nuclear@0 1237 std::for_each(bones.begin(),bones.end(),Util::delete_fun<aiBone>());
nuclear@0 1238 throw;
nuclear@0 1239 }
nuclear@0 1240
nuclear@0 1241 if(bones.empty()) {
nuclear@0 1242 return;
nuclear@0 1243 }
nuclear@0 1244
nuclear@0 1245 out->mBones = new aiBone*[bones.size()]();
nuclear@0 1246 out->mNumBones = static_cast<unsigned int>(bones.size());
nuclear@0 1247
nuclear@0 1248 std::swap_ranges(bones.begin(),bones.end(),out->mBones);
nuclear@0 1249 }
nuclear@0 1250
nuclear@0 1251
nuclear@0 1252
nuclear@0 1253 // ------------------------------------------------------------------------------------------------
nuclear@0 1254 void ConvertCluster(std::vector<aiBone*>& bones, const Model& model, const Cluster& cl,
nuclear@0 1255 std::vector<size_t>& out_indices,
nuclear@0 1256 std::vector<size_t>& index_out_indices,
nuclear@0 1257 std::vector<size_t>& count_out_indices,
nuclear@0 1258 const aiMatrix4x4& node_global_transform)
nuclear@0 1259 {
nuclear@0 1260
nuclear@0 1261 aiBone* const bone = new aiBone();
nuclear@0 1262 bones.push_back(bone);
nuclear@0 1263
nuclear@0 1264 bone->mName = FixNodeName(cl.TargetNode()->Name());
nuclear@0 1265
nuclear@0 1266 bone->mOffsetMatrix = cl.TransformLink();
nuclear@0 1267 bone->mOffsetMatrix.Inverse();
nuclear@0 1268
nuclear@0 1269 bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
nuclear@0 1270
nuclear@0 1271 bone->mNumWeights = static_cast<unsigned int>(out_indices.size());
nuclear@0 1272 aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
nuclear@0 1273
nuclear@0 1274 const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
nuclear@0 1275 const WeightArray& weights = cl.GetWeights();
nuclear@0 1276
nuclear@0 1277 const size_t c = index_out_indices.size();
nuclear@0 1278 for (size_t i = 0; i < c; ++i) {
nuclear@0 1279 const size_t index_index = index_out_indices[i];
nuclear@0 1280
nuclear@0 1281 if (index_index == no_index_sentinel) {
nuclear@0 1282 continue;
nuclear@0 1283 }
nuclear@0 1284
nuclear@0 1285 const size_t cc = count_out_indices[i];
nuclear@0 1286 for (size_t j = 0; j < cc; ++j) {
nuclear@0 1287 aiVertexWeight& out_weight = *cursor++;
nuclear@0 1288
nuclear@0 1289 out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
nuclear@0 1290 out_weight.mWeight = weights[i];
nuclear@0 1291 }
nuclear@0 1292 }
nuclear@0 1293 }
nuclear@0 1294
nuclear@0 1295
nuclear@0 1296 // ------------------------------------------------------------------------------------------------
nuclear@0 1297 void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
nuclear@0 1298 MatIndexArray::value_type materialIndex)
nuclear@0 1299 {
nuclear@0 1300 // locate source materials for this mesh
nuclear@0 1301 const std::vector<const Material*>& mats = model.GetMaterials();
nuclear@0 1302 if (static_cast<unsigned int>(materialIndex) >= mats.size() || materialIndex < 0) {
nuclear@0 1303 FBXImporter::LogError("material index out of bounds, setting default material");
nuclear@0 1304 out->mMaterialIndex = GetDefaultMaterial();
nuclear@0 1305 return;
nuclear@0 1306 }
nuclear@0 1307
nuclear@0 1308 const Material* const mat = mats[materialIndex];
nuclear@0 1309 MaterialMap::const_iterator it = materials_converted.find(mat);
nuclear@0 1310 if (it != materials_converted.end()) {
nuclear@0 1311 out->mMaterialIndex = (*it).second;
nuclear@0 1312 return;
nuclear@0 1313 }
nuclear@0 1314
nuclear@0 1315 out->mMaterialIndex = ConvertMaterial(*mat);
nuclear@0 1316 materials_converted[mat] = out->mMaterialIndex;
nuclear@0 1317 }
nuclear@0 1318
nuclear@0 1319
nuclear@0 1320 // ------------------------------------------------------------------------------------------------
nuclear@0 1321 unsigned int GetDefaultMaterial()
nuclear@0 1322 {
nuclear@0 1323 if (defaultMaterialIndex) {
nuclear@0 1324 return defaultMaterialIndex - 1;
nuclear@0 1325 }
nuclear@0 1326
nuclear@0 1327 aiMaterial* out_mat = new aiMaterial();
nuclear@0 1328 materials.push_back(out_mat);
nuclear@0 1329
nuclear@0 1330 const aiColor3D diffuse = aiColor3D(0.8f,0.8f,0.8f);
nuclear@0 1331 out_mat->AddProperty(&diffuse,1,AI_MATKEY_COLOR_DIFFUSE);
nuclear@0 1332
nuclear@0 1333 aiString s;
nuclear@0 1334 s.Set(AI_DEFAULT_MATERIAL_NAME);
nuclear@0 1335
nuclear@0 1336 out_mat->AddProperty(&s,AI_MATKEY_NAME);
nuclear@0 1337
nuclear@0 1338 defaultMaterialIndex = static_cast<unsigned int>(materials.size());
nuclear@0 1339 return defaultMaterialIndex - 1;
nuclear@0 1340 }
nuclear@0 1341
nuclear@0 1342
nuclear@0 1343 // ------------------------------------------------------------------------------------------------
nuclear@0 1344 // Material -> aiMaterial
nuclear@0 1345 unsigned int ConvertMaterial(const Material& material)
nuclear@0 1346 {
nuclear@0 1347 const PropertyTable& props = material.Props();
nuclear@0 1348
nuclear@0 1349 // generate empty output material
nuclear@0 1350 aiMaterial* out_mat = new aiMaterial();
nuclear@0 1351 materials_converted[&material] = static_cast<unsigned int>(materials.size());
nuclear@0 1352
nuclear@0 1353 materials.push_back(out_mat);
nuclear@0 1354
nuclear@0 1355 aiString str;
nuclear@0 1356
nuclear@0 1357 // stip Material:: prefix
nuclear@0 1358 std::string name = material.Name();
nuclear@0 1359 if(name.substr(0,10) == "Material::") {
nuclear@0 1360 name = name.substr(10);
nuclear@0 1361 }
nuclear@0 1362
nuclear@0 1363 // set material name if not empty - this could happen
nuclear@0 1364 // and there should be no key for it in this case.
nuclear@0 1365 if(name.length()) {
nuclear@0 1366 str.Set(name);
nuclear@0 1367 out_mat->AddProperty(&str,AI_MATKEY_NAME);
nuclear@0 1368 }
nuclear@0 1369
nuclear@0 1370 // shading stuff and colors
nuclear@0 1371 SetShadingPropertiesCommon(out_mat,props);
nuclear@0 1372
nuclear@0 1373 // texture assignments
nuclear@0 1374 SetTextureProperties(out_mat,material.Textures());
nuclear@0 1375
nuclear@0 1376 return static_cast<unsigned int>(materials.size() - 1);
nuclear@0 1377 }
nuclear@0 1378
nuclear@0 1379
nuclear@0 1380 // ------------------------------------------------------------------------------------------------
nuclear@0 1381 void TrySetTextureProperties(aiMaterial* out_mat, const TextureMap& textures,
nuclear@0 1382 const std::string& propName,
nuclear@0 1383 aiTextureType target)
nuclear@0 1384 {
nuclear@0 1385 TextureMap::const_iterator it = textures.find(propName);
nuclear@0 1386 if(it == textures.end()) {
nuclear@0 1387 return;
nuclear@0 1388 }
nuclear@0 1389
nuclear@0 1390 const Texture* const tex = (*it).second;
nuclear@0 1391
nuclear@0 1392 aiString path;
nuclear@0 1393 path.Set(tex->RelativeFilename());
nuclear@0 1394
nuclear@0 1395 out_mat->AddProperty(&path,_AI_MATKEY_TEXTURE_BASE,target,0);
nuclear@0 1396
nuclear@0 1397 aiUVTransform uvTrafo;
nuclear@0 1398 // XXX handle all kinds of UV transformations
nuclear@0 1399 uvTrafo.mScaling = tex->UVScaling();
nuclear@0 1400 uvTrafo.mTranslation = tex->UVTranslation();
nuclear@0 1401 out_mat->AddProperty(&uvTrafo,1,_AI_MATKEY_UVTRANSFORM_BASE,target,0);
nuclear@0 1402
nuclear@0 1403 const PropertyTable& props = tex->Props();
nuclear@0 1404
nuclear@0 1405 int uvIndex = 0;
nuclear@0 1406
nuclear@0 1407 bool ok;
nuclear@0 1408 const std::string& uvSet = PropertyGet<std::string>(props,"UVSet",ok);
nuclear@0 1409 if(ok) {
nuclear@0 1410 // "default" is the name which usually appears in the FbxFileTexture template
nuclear@0 1411 if(uvSet != "default" && uvSet.length()) {
nuclear@0 1412 // this is a bit awkward - we need to find a mesh that uses this
nuclear@0 1413 // material and scan its UV channels for the given UV name because
nuclear@0 1414 // assimp references UV channels by index, not by name.
nuclear@0 1415
nuclear@0 1416 // XXX: the case that UV channels may appear in different orders
nuclear@0 1417 // in meshes is unhandled. A possible solution would be to sort
nuclear@0 1418 // the UV channels alphabetically, but this would have the side
nuclear@0 1419 // effect that the primary (first) UV channel would sometimes
nuclear@0 1420 // be moved, causing trouble when users read only the first
nuclear@0 1421 // UV channel and ignore UV channel assignments altogether.
nuclear@0 1422
nuclear@0 1423 const unsigned int matIndex = static_cast<unsigned int>(std::distance(materials.begin(),
nuclear@0 1424 std::find(materials.begin(),materials.end(),out_mat)
nuclear@0 1425 ));
nuclear@0 1426
nuclear@0 1427 uvIndex = -1;
nuclear@0 1428 BOOST_FOREACH(const MeshMap::value_type& v,meshes_converted) {
nuclear@0 1429 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> (v.first);
nuclear@0 1430 if(!mesh) {
nuclear@0 1431 continue;
nuclear@0 1432 }
nuclear@0 1433
nuclear@0 1434 const MatIndexArray& mats = mesh->GetMaterialIndices();
nuclear@0 1435 if(std::find(mats.begin(),mats.end(),matIndex) == mats.end()) {
nuclear@0 1436 continue;
nuclear@0 1437 }
nuclear@0 1438
nuclear@0 1439 int index = -1;
nuclear@0 1440 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
nuclear@0 1441 if(mesh->GetTextureCoords(i).empty()) {
nuclear@0 1442 break;
nuclear@0 1443 }
nuclear@0 1444 const std::string& name = mesh->GetTextureCoordChannelName(i);
nuclear@0 1445 if(name == uvSet) {
nuclear@0 1446 index = static_cast<int>(i);
nuclear@0 1447 break;
nuclear@0 1448 }
nuclear@0 1449 }
nuclear@0 1450 if(index == -1) {
nuclear@0 1451 FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material");
nuclear@0 1452 continue;
nuclear@0 1453 }
nuclear@0 1454
nuclear@0 1455 if(uvIndex == -1) {
nuclear@0 1456 uvIndex = index;
nuclear@0 1457 }
nuclear@0 1458 else {
nuclear@0 1459 FBXImporter::LogWarn("the UV channel named " + uvSet +
nuclear@0 1460 " appears at different positions in meshes, results will be wrong");
nuclear@0 1461 }
nuclear@0 1462 }
nuclear@0 1463
nuclear@0 1464 if(uvIndex == -1) {
nuclear@0 1465 FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel");
nuclear@0 1466 uvIndex = 0;
nuclear@0 1467 }
nuclear@0 1468 }
nuclear@0 1469 }
nuclear@0 1470
nuclear@0 1471 out_mat->AddProperty(&uvIndex,1,_AI_MATKEY_UVWSRC_BASE,target,0);
nuclear@0 1472 }
nuclear@0 1473
nuclear@0 1474
nuclear@0 1475 // ------------------------------------------------------------------------------------------------
nuclear@0 1476 void SetTextureProperties(aiMaterial* out_mat, const TextureMap& textures)
nuclear@0 1477 {
nuclear@0 1478 TrySetTextureProperties(out_mat, textures, "DiffuseColor", aiTextureType_DIFFUSE);
nuclear@0 1479 TrySetTextureProperties(out_mat, textures, "AmbientColor", aiTextureType_AMBIENT);
nuclear@0 1480 TrySetTextureProperties(out_mat, textures, "EmissiveColor", aiTextureType_EMISSIVE);
nuclear@0 1481 TrySetTextureProperties(out_mat, textures, "SpecularColor", aiTextureType_SPECULAR);
nuclear@0 1482 TrySetTextureProperties(out_mat, textures, "TransparentColor", aiTextureType_OPACITY);
nuclear@0 1483 TrySetTextureProperties(out_mat, textures, "ReflectionColor", aiTextureType_REFLECTION);
nuclear@0 1484 TrySetTextureProperties(out_mat, textures, "DisplacementColor", aiTextureType_DISPLACEMENT);
nuclear@0 1485 TrySetTextureProperties(out_mat, textures, "NormalMap", aiTextureType_NORMALS);
nuclear@0 1486 TrySetTextureProperties(out_mat, textures, "Bump", aiTextureType_HEIGHT);
nuclear@0 1487 }
nuclear@0 1488
nuclear@0 1489
nuclear@0 1490
nuclear@0 1491 // ------------------------------------------------------------------------------------------------
nuclear@0 1492 aiColor3D GetColorPropertyFromMaterial(const PropertyTable& props, const std::string& baseName,
nuclear@0 1493 bool& result)
nuclear@0 1494 {
nuclear@0 1495 result = true;
nuclear@0 1496
nuclear@0 1497 bool ok;
nuclear@0 1498 const aiVector3D& Diffuse = PropertyGet<aiVector3D>(props,baseName,ok);
nuclear@0 1499 if(ok) {
nuclear@0 1500 return aiColor3D(Diffuse.x,Diffuse.y,Diffuse.z);
nuclear@0 1501 }
nuclear@0 1502 else {
nuclear@0 1503 aiVector3D DiffuseColor = PropertyGet<aiVector3D>(props,baseName + "Color",ok);
nuclear@0 1504 if(ok) {
nuclear@0 1505 float DiffuseFactor = PropertyGet<float>(props,baseName + "Factor",ok);
nuclear@0 1506 if(ok) {
nuclear@0 1507 DiffuseColor *= DiffuseFactor;
nuclear@0 1508 }
nuclear@0 1509
nuclear@0 1510 return aiColor3D(DiffuseColor.x,DiffuseColor.y,DiffuseColor.z);
nuclear@0 1511 }
nuclear@0 1512 }
nuclear@0 1513 result = false;
nuclear@0 1514 return aiColor3D(0.0f,0.0f,0.0f);
nuclear@0 1515 }
nuclear@0 1516
nuclear@0 1517
nuclear@0 1518 // ------------------------------------------------------------------------------------------------
nuclear@0 1519 void SetShadingPropertiesCommon(aiMaterial* out_mat, const PropertyTable& props)
nuclear@0 1520 {
nuclear@0 1521 // set shading properties. There are various, redundant ways in which FBX materials
nuclear@0 1522 // specify their shading settings (depending on shading models, prop
nuclear@0 1523 // template etc.). No idea which one is right in a particular context.
nuclear@0 1524 // Just try to make sense of it - there's no spec to verify this against,
nuclear@0 1525 // so why should we.
nuclear@0 1526 bool ok;
nuclear@0 1527 const aiColor3D& Diffuse = GetColorPropertyFromMaterial(props,"Diffuse",ok);
nuclear@0 1528 if(ok) {
nuclear@0 1529 out_mat->AddProperty(&Diffuse,1,AI_MATKEY_COLOR_DIFFUSE);
nuclear@0 1530 }
nuclear@0 1531
nuclear@0 1532 const aiColor3D& Emissive = GetColorPropertyFromMaterial(props,"Emissive",ok);
nuclear@0 1533 if(ok) {
nuclear@0 1534 out_mat->AddProperty(&Emissive,1,AI_MATKEY_COLOR_EMISSIVE);
nuclear@0 1535 }
nuclear@0 1536
nuclear@0 1537 const aiColor3D& Ambient = GetColorPropertyFromMaterial(props,"Ambient",ok);
nuclear@0 1538 if(ok) {
nuclear@0 1539 out_mat->AddProperty(&Ambient,1,AI_MATKEY_COLOR_AMBIENT);
nuclear@0 1540 }
nuclear@0 1541
nuclear@0 1542 const aiColor3D& Specular = GetColorPropertyFromMaterial(props,"Specular",ok);
nuclear@0 1543 if(ok) {
nuclear@0 1544 out_mat->AddProperty(&Specular,1,AI_MATKEY_COLOR_SPECULAR);
nuclear@0 1545 }
nuclear@0 1546
nuclear@0 1547 const float Opacity = PropertyGet<float>(props,"Opacity",ok);
nuclear@0 1548 if(ok) {
nuclear@0 1549 out_mat->AddProperty(&Opacity,1,AI_MATKEY_OPACITY);
nuclear@0 1550 }
nuclear@0 1551
nuclear@0 1552 const float Reflectivity = PropertyGet<float>(props,"Reflectivity",ok);
nuclear@0 1553 if(ok) {
nuclear@0 1554 out_mat->AddProperty(&Reflectivity,1,AI_MATKEY_REFLECTIVITY);
nuclear@0 1555 }
nuclear@0 1556
nuclear@0 1557 const float Shininess = PropertyGet<float>(props,"Shininess",ok);
nuclear@0 1558 if(ok) {
nuclear@0 1559 out_mat->AddProperty(&Shininess,1,AI_MATKEY_SHININESS_STRENGTH);
nuclear@0 1560 }
nuclear@0 1561
nuclear@0 1562 const float ShininessExponent = PropertyGet<float>(props,"ShininessExponent",ok);
nuclear@0 1563 if(ok) {
nuclear@0 1564 out_mat->AddProperty(&ShininessExponent,1,AI_MATKEY_SHININESS);
nuclear@0 1565 }
nuclear@0 1566 }
nuclear@0 1567
nuclear@0 1568
nuclear@0 1569 // ------------------------------------------------------------------------------------------------
nuclear@0 1570 // get the number of fps for a FrameRate enumerated value
nuclear@0 1571 static double FrameRateToDouble(FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0)
nuclear@0 1572 {
nuclear@0 1573 switch(fp) {
nuclear@0 1574 case FileGlobalSettings::FrameRate_DEFAULT:
nuclear@0 1575 return 1.0;
nuclear@0 1576
nuclear@0 1577 case FileGlobalSettings::FrameRate_120:
nuclear@0 1578 return 120.0;
nuclear@0 1579
nuclear@0 1580 case FileGlobalSettings::FrameRate_100:
nuclear@0 1581 return 100.0;
nuclear@0 1582
nuclear@0 1583 case FileGlobalSettings::FrameRate_60:
nuclear@0 1584 return 60.0;
nuclear@0 1585
nuclear@0 1586 case FileGlobalSettings::FrameRate_50:
nuclear@0 1587 return 50.0;
nuclear@0 1588
nuclear@0 1589 case FileGlobalSettings::FrameRate_48:
nuclear@0 1590 return 48.0;
nuclear@0 1591
nuclear@0 1592 case FileGlobalSettings::FrameRate_30:
nuclear@0 1593 case FileGlobalSettings::FrameRate_30_DROP:
nuclear@0 1594 return 30.0;
nuclear@0 1595
nuclear@0 1596 case FileGlobalSettings::FrameRate_NTSC_DROP_FRAME:
nuclear@0 1597 case FileGlobalSettings::FrameRate_NTSC_FULL_FRAME:
nuclear@0 1598 return 29.9700262;
nuclear@0 1599
nuclear@0 1600 case FileGlobalSettings::FrameRate_PAL:
nuclear@0 1601 return 25.0;
nuclear@0 1602
nuclear@0 1603 case FileGlobalSettings::FrameRate_CINEMA:
nuclear@0 1604 return 24.0;
nuclear@0 1605
nuclear@0 1606 case FileGlobalSettings::FrameRate_1000:
nuclear@0 1607 return 1000.0;
nuclear@0 1608
nuclear@0 1609 case FileGlobalSettings::FrameRate_CINEMA_ND:
nuclear@0 1610 return 23.976;
nuclear@0 1611
nuclear@0 1612 case FileGlobalSettings::FrameRate_CUSTOM:
nuclear@0 1613 return customFPSVal;
nuclear@0 1614
nuclear@0 1615 case FileGlobalSettings::FrameRate_MAX: // this is to silence compiler warnings
nuclear@0 1616 break;
nuclear@0 1617 }
nuclear@0 1618
nuclear@0 1619 ai_assert(false);
nuclear@0 1620 return -1.0f;
nuclear@0 1621 }
nuclear@0 1622
nuclear@0 1623
nuclear@0 1624 // ------------------------------------------------------------------------------------------------
nuclear@0 1625 // convert animation data to aiAnimation et al
nuclear@0 1626 void ConvertAnimations()
nuclear@0 1627 {
nuclear@0 1628 // first of all determine framerate
nuclear@0 1629 const FileGlobalSettings::FrameRate fps = doc.GlobalSettings().TimeMode();
nuclear@0 1630 const float custom = doc.GlobalSettings().CustomFrameRate();
nuclear@0 1631 anim_fps = FrameRateToDouble(fps, custom);
nuclear@0 1632
nuclear@0 1633 const std::vector<const AnimationStack*>& animations = doc.AnimationStacks();
nuclear@0 1634 BOOST_FOREACH(const AnimationStack* stack, animations) {
nuclear@0 1635 ConvertAnimationStack(*stack);
nuclear@0 1636 }
nuclear@0 1637 }
nuclear@0 1638
nuclear@0 1639
nuclear@0 1640 // ------------------------------------------------------------------------------------------------
nuclear@0 1641 // rename a node already partially converted. fixed_name is a string previously returned by
nuclear@0 1642 // FixNodeName, new_name specifies the string FixNodeName should return on all further invocations
nuclear@0 1643 // which would previously have returned the old value.
nuclear@0 1644 //
nuclear@0 1645 // this also updates names in node animations, cameras and light sources and is thus slow.
nuclear@0 1646 //
nuclear@0 1647 // NOTE: the caller is responsible for ensuring that the new name is unique and does
nuclear@0 1648 // not collide with any other identifiers. The best way to ensure this is to only
nuclear@0 1649 // append to the old name, which is guaranteed to match these requirements.
nuclear@0 1650 void RenameNode(const std::string& fixed_name, const std::string& new_name)
nuclear@0 1651 {
nuclear@0 1652 ai_assert(node_names.find(fixed_name) != node_names.end());
nuclear@0 1653 ai_assert(node_names.find(new_name) == node_names.end());
nuclear@0 1654
nuclear@0 1655 renamed_nodes[fixed_name] = new_name;
nuclear@0 1656
nuclear@0 1657 const aiString fn(fixed_name);
nuclear@0 1658
nuclear@0 1659 BOOST_FOREACH(aiCamera* cam, cameras) {
nuclear@0 1660 if (cam->mName == fn) {
nuclear@0 1661 cam->mName.Set(new_name);
nuclear@0 1662 break;
nuclear@0 1663 }
nuclear@0 1664 }
nuclear@0 1665
nuclear@0 1666 BOOST_FOREACH(aiLight* light, lights) {
nuclear@0 1667 if (light->mName == fn) {
nuclear@0 1668 light->mName.Set(new_name);
nuclear@0 1669 break;
nuclear@0 1670 }
nuclear@0 1671 }
nuclear@0 1672
nuclear@0 1673 BOOST_FOREACH(aiAnimation* anim, animations) {
nuclear@0 1674 for (unsigned int i = 0; i < anim->mNumChannels; ++i) {
nuclear@0 1675 aiNodeAnim* const na = anim->mChannels[i];
nuclear@0 1676 if (na->mNodeName == fn) {
nuclear@0 1677 na->mNodeName.Set(new_name);
nuclear@0 1678 break;
nuclear@0 1679 }
nuclear@0 1680 }
nuclear@0 1681 }
nuclear@0 1682 }
nuclear@0 1683
nuclear@0 1684
nuclear@0 1685 // ------------------------------------------------------------------------------------------------
nuclear@0 1686 // takes a fbx node name and returns the identifier to be used in the assimp output scene.
nuclear@0 1687 // the function is guaranteed to provide consistent results over multiple invocations
nuclear@0 1688 // UNLESS RenameNode() is called for a particular node name.
nuclear@0 1689 std::string FixNodeName(const std::string& name)
nuclear@0 1690 {
nuclear@0 1691 // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if
nuclear@0 1692 // this causes ambiguities, well possible between empty identifiers,
nuclear@0 1693 // such as "Model::" and ""). Make sure the behaviour is consistent
nuclear@0 1694 // across multiple calls to FixNodeName().
nuclear@0 1695 if(name.substr(0,7) == "Model::") {
nuclear@0 1696 std::string temp = name.substr(7);
nuclear@0 1697
nuclear@0 1698 const NodeNameMap::const_iterator it = node_names.find(temp);
nuclear@0 1699 if (it != node_names.end()) {
nuclear@0 1700 if (!(*it).second) {
nuclear@0 1701 return FixNodeName(name + "_");
nuclear@0 1702 }
nuclear@0 1703 }
nuclear@0 1704 node_names[temp] = true;
nuclear@0 1705
nuclear@0 1706 const NameNameMap::const_iterator rit = renamed_nodes.find(temp);
nuclear@0 1707 return rit == renamed_nodes.end() ? temp : (*rit).second;
nuclear@0 1708 }
nuclear@0 1709
nuclear@0 1710 const NodeNameMap::const_iterator it = node_names.find(name);
nuclear@0 1711 if (it != node_names.end()) {
nuclear@0 1712 if ((*it).second) {
nuclear@0 1713 return FixNodeName(name + "_");
nuclear@0 1714 }
nuclear@0 1715 }
nuclear@0 1716 node_names[name] = false;
nuclear@0 1717
nuclear@0 1718 const NameNameMap::const_iterator rit = renamed_nodes.find(name);
nuclear@0 1719 return rit == renamed_nodes.end() ? name : (*rit).second;
nuclear@0 1720 }
nuclear@0 1721
nuclear@0 1722
nuclear@0 1723 typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
nuclear@0 1724
nuclear@0 1725 // XXX: better use multi_map ..
nuclear@0 1726 typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
nuclear@0 1727
nuclear@0 1728
nuclear@0 1729 // ------------------------------------------------------------------------------------------------
nuclear@0 1730 void ConvertAnimationStack(const AnimationStack& st)
nuclear@0 1731 {
nuclear@0 1732 const AnimationLayerList& layers = st.Layers();
nuclear@0 1733 if(layers.empty()) {
nuclear@0 1734 return;
nuclear@0 1735 }
nuclear@0 1736
nuclear@0 1737 aiAnimation* const anim = new aiAnimation();
nuclear@0 1738 animations.push_back(anim);
nuclear@0 1739
nuclear@0 1740 // strip AnimationStack:: prefix
nuclear@0 1741 std::string name = st.Name();
nuclear@0 1742 if(name.substr(0,16) == "AnimationStack::") {
nuclear@0 1743 name = name.substr(16);
nuclear@0 1744 }
nuclear@0 1745
nuclear@0 1746 anim->mName.Set(name);
nuclear@0 1747
nuclear@0 1748 // need to find all nodes for which we need to generate node animations -
nuclear@0 1749 // it may happen that we need to merge multiple layers, though.
nuclear@0 1750 NodeMap node_map;
nuclear@0 1751
nuclear@0 1752 // reverse mapping from curves to layers, much faster than querying
nuclear@0 1753 // the FBX DOM for it.
nuclear@0 1754 LayerMap layer_map;
nuclear@0 1755
nuclear@0 1756 const char* prop_whitelist[] = {
nuclear@0 1757 "Lcl Scaling",
nuclear@0 1758 "Lcl Rotation",
nuclear@0 1759 "Lcl Translation"
nuclear@0 1760 };
nuclear@0 1761
nuclear@0 1762 BOOST_FOREACH(const AnimationLayer* layer, layers) {
nuclear@0 1763 ai_assert(layer);
nuclear@0 1764
nuclear@0 1765 const AnimationCurveNodeList& nodes = layer->Nodes(prop_whitelist, 3);
nuclear@0 1766 BOOST_FOREACH(const AnimationCurveNode* node, nodes) {
nuclear@0 1767 ai_assert(node);
nuclear@0 1768
nuclear@0 1769 const Model* const model = dynamic_cast<const Model*>(node->Target());
nuclear@0 1770 // this can happen - it could also be a NodeAttribute (i.e. for camera animations)
nuclear@0 1771 if(!model) {
nuclear@0 1772 continue;
nuclear@0 1773 }
nuclear@0 1774
nuclear@0 1775 const std::string& name = FixNodeName(model->Name());
nuclear@0 1776 node_map[name].push_back(node);
nuclear@0 1777
nuclear@0 1778 layer_map[node] = layer;
nuclear@0 1779 }
nuclear@0 1780 }
nuclear@0 1781
nuclear@0 1782 // generate node animations
nuclear@0 1783 std::vector<aiNodeAnim*> node_anims;
nuclear@0 1784
nuclear@0 1785 double min_time = 1e10;
nuclear@0 1786 double max_time = -1e10;
nuclear@0 1787
nuclear@0 1788 try {
nuclear@0 1789 BOOST_FOREACH(const NodeMap::value_type& kv, node_map) {
nuclear@0 1790 GenerateNodeAnimations(node_anims,
nuclear@0 1791 kv.first,
nuclear@0 1792 kv.second,
nuclear@0 1793 layer_map,
nuclear@0 1794 max_time,
nuclear@0 1795 min_time);
nuclear@0 1796 }
nuclear@0 1797 }
nuclear@0 1798 catch(std::exception&) {
nuclear@0 1799 std::for_each(node_anims.begin(), node_anims.end(), Util::delete_fun<aiNodeAnim>());
nuclear@0 1800 throw;
nuclear@0 1801 }
nuclear@0 1802
nuclear@0 1803 if(node_anims.size()) {
nuclear@0 1804 anim->mChannels = new aiNodeAnim*[node_anims.size()]();
nuclear@0 1805 anim->mNumChannels = static_cast<unsigned int>(node_anims.size());
nuclear@0 1806
nuclear@0 1807 std::swap_ranges(node_anims.begin(),node_anims.end(),anim->mChannels);
nuclear@0 1808 }
nuclear@0 1809 else {
nuclear@0 1810 // empty animations would fail validation, so drop them
nuclear@0 1811 delete anim;
nuclear@0 1812 animations.pop_back();
nuclear@0 1813 FBXImporter::LogInfo("ignoring empty AnimationStack (using IK?): " + name);
nuclear@0 1814 return;
nuclear@0 1815 }
nuclear@0 1816
nuclear@0 1817 // for some mysterious reason, mDuration is simply the maximum key -- the
nuclear@0 1818 // validator always assumes animations to start at zero.
nuclear@0 1819 anim->mDuration = max_time /*- min_time */;
nuclear@0 1820 anim->mTicksPerSecond = anim_fps;
nuclear@0 1821 }
nuclear@0 1822
nuclear@0 1823
nuclear@0 1824 // ------------------------------------------------------------------------------------------------
nuclear@0 1825 void GenerateNodeAnimations(std::vector<aiNodeAnim*>& node_anims,
nuclear@0 1826 const std::string& fixed_name,
nuclear@0 1827 const std::vector<const AnimationCurveNode*>& curves,
nuclear@0 1828 const LayerMap& layer_map,
nuclear@0 1829 double& max_time,
nuclear@0 1830 double& min_time)
nuclear@0 1831 {
nuclear@0 1832
nuclear@0 1833 NodeMap node_property_map;
nuclear@0 1834 ai_assert(curves.size());
nuclear@0 1835
nuclear@0 1836 // sanity check whether the input is ok
nuclear@0 1837 #ifdef _DEBUG
nuclear@0 1838 { const Object* target = NULL;
nuclear@0 1839 BOOST_FOREACH(const AnimationCurveNode* node, curves) {
nuclear@0 1840 if(!target) {
nuclear@0 1841 target = node->Target();
nuclear@0 1842 }
nuclear@0 1843 ai_assert(node->Target() == target);
nuclear@0 1844 }}
nuclear@0 1845 #endif
nuclear@0 1846
nuclear@0 1847 const AnimationCurveNode* curve_node;
nuclear@0 1848 BOOST_FOREACH(const AnimationCurveNode* node, curves) {
nuclear@0 1849 ai_assert(node);
nuclear@0 1850
nuclear@0 1851 if (node->TargetProperty().empty()) {
nuclear@0 1852 FBXImporter::LogWarn("target property for animation curve not set: " + node->Name());
nuclear@0 1853 continue;
nuclear@0 1854 }
nuclear@0 1855
nuclear@0 1856 curve_node = node;
nuclear@0 1857 if (node->Curves().empty()) {
nuclear@0 1858 FBXImporter::LogWarn("no animation curves assigned to AnimationCurveNode: " + node->Name());
nuclear@0 1859 continue;
nuclear@0 1860 }
nuclear@0 1861
nuclear@0 1862 node_property_map[node->TargetProperty()].push_back(node);
nuclear@0 1863 }
nuclear@0 1864
nuclear@0 1865 ai_assert(curve_node);
nuclear@0 1866 ai_assert(curve_node->TargetAsModel());
nuclear@0 1867
nuclear@0 1868 const Model& target = *curve_node->TargetAsModel();
nuclear@0 1869
nuclear@0 1870 // check for all possible transformation components
nuclear@0 1871 NodeMap::const_iterator chain[TransformationComp_MAXIMUM];
nuclear@0 1872
nuclear@0 1873 bool has_any = false;
nuclear@0 1874 bool has_complex = false;
nuclear@0 1875
nuclear@0 1876 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
nuclear@0 1877 const TransformationComp comp = static_cast<TransformationComp>(i);
nuclear@0 1878
nuclear@0 1879 // inverse pivots don't exist in the input, we just generate them
nuclear@0 1880 if (comp == TransformationComp_RotationPivotInverse || comp == TransformationComp_ScalingPivotInverse) {
nuclear@0 1881 chain[i] = node_property_map.end();
nuclear@0 1882 continue;
nuclear@0 1883 }
nuclear@0 1884
nuclear@0 1885 chain[i] = node_property_map.find(NameTransformationCompProperty(comp));
nuclear@0 1886 if (chain[i] != node_property_map.end()) {
nuclear@0 1887
nuclear@0 1888 // check if this curves contains redundant information by looking
nuclear@0 1889 // up the corresponding node's transformation chain.
nuclear@0 1890 if (doc.Settings().optimizeEmptyAnimationCurves &&
nuclear@0 1891 IsRedundantAnimationData(target, comp, (*chain[i]).second)) {
nuclear@0 1892
nuclear@0 1893 FBXImporter::LogDebug("dropping redundant animation channel for node " + target.Name());
nuclear@0 1894 continue;
nuclear@0 1895 }
nuclear@0 1896
nuclear@0 1897 has_any = true;
nuclear@0 1898
nuclear@0 1899 if (comp != TransformationComp_Rotation && comp != TransformationComp_Scaling &&
nuclear@0 1900 comp != TransformationComp_Translation) {
nuclear@0 1901
nuclear@0 1902 has_complex = true;
nuclear@0 1903 }
nuclear@0 1904 }
nuclear@0 1905 }
nuclear@0 1906
nuclear@0 1907 if (!has_any) {
nuclear@0 1908 FBXImporter::LogWarn("ignoring node animation, did not find any transformation key frames");
nuclear@0 1909 return;
nuclear@0 1910 }
nuclear@0 1911
nuclear@0 1912 // this needs to play nicely with GenerateTransformationNodeChain() which will
nuclear@0 1913 // be invoked _later_ (animations come first). If this node has only rotation,
nuclear@0 1914 // scaling and translation _and_ there are no animated other components either,
nuclear@0 1915 // we can use a single node and also a single node animation channel.
nuclear@0 1916 if (!has_complex && !NeedsComplexTransformationChain(target)) {
nuclear@0 1917
nuclear@0 1918 aiNodeAnim* const nd = GenerateSimpleNodeAnim(fixed_name, target, chain,
nuclear@0 1919 node_property_map.end(),
nuclear@0 1920 layer_map,
nuclear@0 1921 max_time,
nuclear@0 1922 min_time,
nuclear@0 1923 true // input is TRS order, assimp is SRT
nuclear@0 1924 );
nuclear@0 1925
nuclear@0 1926 ai_assert(nd);
nuclear@0 1927 node_anims.push_back(nd);
nuclear@0 1928 return;
nuclear@0 1929 }
nuclear@0 1930
nuclear@0 1931 // otherwise, things get gruesome and we need separate animation channels
nuclear@0 1932 // for each part of the transformation chain. Remember which channels
nuclear@0 1933 // we generated and pass this information to the node conversion
nuclear@0 1934 // code to avoid nodes that have identity transform, but non-identity
nuclear@0 1935 // animations, being dropped.
nuclear@0 1936 unsigned int flags = 0, bit = 0x1;
nuclear@0 1937 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
nuclear@0 1938 const TransformationComp comp = static_cast<TransformationComp>(i);
nuclear@0 1939
nuclear@0 1940 if (chain[i] != node_property_map.end()) {
nuclear@0 1941 flags |= bit;
nuclear@0 1942
nuclear@0 1943 ai_assert(comp != TransformationComp_RotationPivotInverse);
nuclear@0 1944 ai_assert(comp != TransformationComp_ScalingPivotInverse);
nuclear@0 1945
nuclear@0 1946 const std::string& chain_name = NameTransformationChainNode(fixed_name, comp);
nuclear@0 1947
nuclear@0 1948 aiNodeAnim* na;
nuclear@0 1949 switch(comp)
nuclear@0 1950 {
nuclear@0 1951 case TransformationComp_Rotation:
nuclear@0 1952 case TransformationComp_PreRotation:
nuclear@0 1953 case TransformationComp_PostRotation:
nuclear@0 1954 na = GenerateRotationNodeAnim(chain_name,
nuclear@0 1955 target,
nuclear@0 1956 (*chain[i]).second,
nuclear@0 1957 layer_map,
nuclear@0 1958 max_time,
nuclear@0 1959 min_time);
nuclear@0 1960
nuclear@0 1961 break;
nuclear@0 1962
nuclear@0 1963 case TransformationComp_RotationOffset:
nuclear@0 1964 case TransformationComp_RotationPivot:
nuclear@0 1965 case TransformationComp_ScalingOffset:
nuclear@0 1966 case TransformationComp_ScalingPivot:
nuclear@0 1967 case TransformationComp_Translation:
nuclear@0 1968 na = GenerateTranslationNodeAnim(chain_name,
nuclear@0 1969 target,
nuclear@0 1970 (*chain[i]).second,
nuclear@0 1971 layer_map,
nuclear@0 1972 max_time,
nuclear@0 1973 min_time);
nuclear@0 1974
nuclear@0 1975 // pivoting requires us to generate an implicit inverse channel to undo the pivot translation
nuclear@0 1976 if (comp == TransformationComp_RotationPivot) {
nuclear@0 1977 const std::string& invName = NameTransformationChainNode(fixed_name,
nuclear@0 1978 TransformationComp_RotationPivotInverse);
nuclear@0 1979
nuclear@0 1980 aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName,
nuclear@0 1981 target,
nuclear@0 1982 (*chain[i]).second,
nuclear@0 1983 layer_map,
nuclear@0 1984 max_time,
nuclear@0 1985 min_time,
nuclear@0 1986 true);
nuclear@0 1987
nuclear@0 1988 ai_assert(inv);
nuclear@0 1989 node_anims.push_back(inv);
nuclear@0 1990
nuclear@0 1991 ai_assert(TransformationComp_RotationPivotInverse > i);
nuclear@0 1992 flags |= bit << (TransformationComp_RotationPivotInverse - i);
nuclear@0 1993 }
nuclear@0 1994 else if (comp == TransformationComp_ScalingPivot) {
nuclear@0 1995 const std::string& invName = NameTransformationChainNode(fixed_name,
nuclear@0 1996 TransformationComp_ScalingPivotInverse);
nuclear@0 1997
nuclear@0 1998 aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName,
nuclear@0 1999 target,
nuclear@0 2000 (*chain[i]).second,
nuclear@0 2001 layer_map,
nuclear@0 2002 max_time,
nuclear@0 2003 min_time,
nuclear@0 2004 true);
nuclear@0 2005
nuclear@0 2006 ai_assert(inv);
nuclear@0 2007 node_anims.push_back(inv);
nuclear@0 2008
nuclear@0 2009 ai_assert(TransformationComp_RotationPivotInverse > i);
nuclear@0 2010 flags |= bit << (TransformationComp_RotationPivotInverse - i);
nuclear@0 2011 }
nuclear@0 2012
nuclear@0 2013 break;
nuclear@0 2014
nuclear@0 2015 case TransformationComp_Scaling:
nuclear@0 2016 na = GenerateScalingNodeAnim(chain_name,
nuclear@0 2017 target,
nuclear@0 2018 (*chain[i]).second,
nuclear@0 2019 layer_map,
nuclear@0 2020 max_time,
nuclear@0 2021 min_time);
nuclear@0 2022
nuclear@0 2023 break;
nuclear@0 2024
nuclear@0 2025 default:
nuclear@0 2026 ai_assert(false);
nuclear@0 2027 }
nuclear@0 2028
nuclear@0 2029 ai_assert(na);
nuclear@0 2030 node_anims.push_back(na);
nuclear@0 2031 continue;
nuclear@0 2032 }
nuclear@0 2033 }
nuclear@0 2034
nuclear@0 2035 node_anim_chain_bits[fixed_name] = flags;
nuclear@0 2036 }
nuclear@0 2037
nuclear@0 2038
nuclear@0 2039 // ------------------------------------------------------------------------------------------------
nuclear@0 2040 bool IsRedundantAnimationData(const Model& target,
nuclear@0 2041 TransformationComp comp,
nuclear@0 2042 const std::vector<const AnimationCurveNode*>& curves)
nuclear@0 2043 {
nuclear@0 2044 ai_assert(curves.size());
nuclear@0 2045
nuclear@0 2046 // look for animation nodes with
nuclear@0 2047 // * sub channels for all relevant components set
nuclear@0 2048 // * one key/value pair per component
nuclear@0 2049 // * combined values match up the corresponding value in the bind pose node transformation
nuclear@0 2050 // only such nodes are 'redundant' for this function.
nuclear@0 2051
nuclear@0 2052 if (curves.size() > 1) {
nuclear@0 2053 return false;
nuclear@0 2054 }
nuclear@0 2055
nuclear@0 2056 const AnimationCurveNode& nd = *curves.front();
nuclear@0 2057 const AnimationCurveMap& sub_curves = nd.Curves();
nuclear@0 2058
nuclear@0 2059 const AnimationCurveMap::const_iterator dx = sub_curves.find("d|X");
nuclear@0 2060 const AnimationCurveMap::const_iterator dy = sub_curves.find("d|Y");
nuclear@0 2061 const AnimationCurveMap::const_iterator dz = sub_curves.find("d|Z");
nuclear@0 2062
nuclear@0 2063 if (dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end()) {
nuclear@0 2064 return false;
nuclear@0 2065 }
nuclear@0 2066
nuclear@0 2067 const KeyValueList& vx = (*dx).second->GetValues();
nuclear@0 2068 const KeyValueList& vy = (*dy).second->GetValues();
nuclear@0 2069 const KeyValueList& vz = (*dz).second->GetValues();
nuclear@0 2070
nuclear@0 2071 if(vx.size() != 1 || vy.size() != 1 || vz.size() != 1) {
nuclear@0 2072 return false;
nuclear@0 2073 }
nuclear@0 2074
nuclear@0 2075 const aiVector3D dyn_val = aiVector3D(vx[0], vy[0], vz[0]);
nuclear@0 2076 const aiVector3D& static_val = PropertyGet<aiVector3D>(target.Props(),
nuclear@0 2077 NameTransformationCompProperty(comp),
nuclear@0 2078 TransformationCompDefaultValue(comp)
nuclear@0 2079 );
nuclear@0 2080
nuclear@0 2081 const float epsilon = 1e-6f;
nuclear@0 2082 return (dyn_val - static_val).SquareLength() < epsilon;
nuclear@0 2083 }
nuclear@0 2084
nuclear@0 2085
nuclear@0 2086 // ------------------------------------------------------------------------------------------------
nuclear@0 2087 aiNodeAnim* GenerateRotationNodeAnim(const std::string& name,
nuclear@0 2088 const Model& target,
nuclear@0 2089 const std::vector<const AnimationCurveNode*>& curves,
nuclear@0 2090 const LayerMap& layer_map,
nuclear@0 2091 double& max_time,
nuclear@0 2092 double& min_time)
nuclear@0 2093 {
nuclear@0 2094 ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
nuclear@0 2095 na->mNodeName.Set(name);
nuclear@0 2096
nuclear@0 2097 ConvertRotationKeys(na, curves, layer_map, max_time,min_time, target.RotationOrder());
nuclear@0 2098
nuclear@0 2099 // dummy scaling key
nuclear@0 2100 na->mScalingKeys = new aiVectorKey[1];
nuclear@0 2101 na->mNumScalingKeys = 1;
nuclear@0 2102
nuclear@0 2103 na->mScalingKeys[0].mTime = 0.;
nuclear@0 2104 na->mScalingKeys[0].mValue = aiVector3D(1.0f,1.0f,1.0f);
nuclear@0 2105
nuclear@0 2106 // dummy position key
nuclear@0 2107 na->mPositionKeys = new aiVectorKey[1];
nuclear@0 2108 na->mNumPositionKeys = 1;
nuclear@0 2109
nuclear@0 2110 na->mPositionKeys[0].mTime = 0.;
nuclear@0 2111 na->mPositionKeys[0].mValue = aiVector3D();
nuclear@0 2112
nuclear@0 2113 return na.dismiss();
nuclear@0 2114 }
nuclear@0 2115
nuclear@0 2116
nuclear@0 2117 // ------------------------------------------------------------------------------------------------
nuclear@0 2118 aiNodeAnim* GenerateScalingNodeAnim(const std::string& name,
nuclear@0 2119 const Model& target,
nuclear@0 2120 const std::vector<const AnimationCurveNode*>& curves,
nuclear@0 2121 const LayerMap& layer_map,
nuclear@0 2122 double& max_time,
nuclear@0 2123 double& min_time)
nuclear@0 2124 {
nuclear@0 2125 ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
nuclear@0 2126 na->mNodeName.Set(name);
nuclear@0 2127
nuclear@0 2128 ConvertScaleKeys(na, curves, layer_map, max_time,min_time);
nuclear@0 2129
nuclear@0 2130 // dummy rotation key
nuclear@0 2131 na->mRotationKeys = new aiQuatKey[1];
nuclear@0 2132 na->mNumRotationKeys = 1;
nuclear@0 2133
nuclear@0 2134 na->mRotationKeys[0].mTime = 0.;
nuclear@0 2135 na->mRotationKeys[0].mValue = aiQuaternion();
nuclear@0 2136
nuclear@0 2137 // dummy position key
nuclear@0 2138 na->mPositionKeys = new aiVectorKey[1];
nuclear@0 2139 na->mNumPositionKeys = 1;
nuclear@0 2140
nuclear@0 2141 na->mPositionKeys[0].mTime = 0.;
nuclear@0 2142 na->mPositionKeys[0].mValue = aiVector3D();
nuclear@0 2143
nuclear@0 2144 return na.dismiss();
nuclear@0 2145 }
nuclear@0 2146
nuclear@0 2147
nuclear@0 2148 // ------------------------------------------------------------------------------------------------
nuclear@0 2149 aiNodeAnim* GenerateTranslationNodeAnim(const std::string& name,
nuclear@0 2150 const Model& target,
nuclear@0 2151 const std::vector<const AnimationCurveNode*>& curves,
nuclear@0 2152 const LayerMap& layer_map,
nuclear@0 2153 double& max_time,
nuclear@0 2154 double& min_time,
nuclear@0 2155 bool inverse = false)
nuclear@0 2156 {
nuclear@0 2157 ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
nuclear@0 2158 na->mNodeName.Set(name);
nuclear@0 2159
nuclear@0 2160 ConvertTranslationKeys(na, curves, layer_map, max_time,min_time);
nuclear@0 2161
nuclear@0 2162 if (inverse) {
nuclear@0 2163 for (unsigned int i = 0; i < na->mNumPositionKeys; ++i) {
nuclear@0 2164 na->mPositionKeys[i].mValue *= -1.0f;
nuclear@0 2165 }
nuclear@0 2166 }
nuclear@0 2167
nuclear@0 2168 // dummy scaling key
nuclear@0 2169 na->mScalingKeys = new aiVectorKey[1];
nuclear@0 2170 na->mNumScalingKeys = 1;
nuclear@0 2171
nuclear@0 2172 na->mScalingKeys[0].mTime = 0.;
nuclear@0 2173 na->mScalingKeys[0].mValue = aiVector3D(1.0f,1.0f,1.0f);
nuclear@0 2174
nuclear@0 2175 // dummy rotation key
nuclear@0 2176 na->mRotationKeys = new aiQuatKey[1];
nuclear@0 2177 na->mNumRotationKeys = 1;
nuclear@0 2178
nuclear@0 2179 na->mRotationKeys[0].mTime = 0.;
nuclear@0 2180 na->mRotationKeys[0].mValue = aiQuaternion();
nuclear@0 2181
nuclear@0 2182 return na.dismiss();
nuclear@0 2183 }
nuclear@0 2184
nuclear@0 2185
nuclear@0 2186 // ------------------------------------------------------------------------------------------------
nuclear@0 2187 // generate node anim, extracting only Rotation, Scaling and Translation from the given chain
nuclear@0 2188 aiNodeAnim* GenerateSimpleNodeAnim(const std::string& name,
nuclear@0 2189 const Model& target,
nuclear@0 2190 NodeMap::const_iterator chain[TransformationComp_MAXIMUM],
nuclear@0 2191 NodeMap::const_iterator iter_end,
nuclear@0 2192 const LayerMap& layer_map,
nuclear@0 2193 double& max_time,
nuclear@0 2194 double& min_time,
nuclear@0 2195 bool reverse_order = false)
nuclear@0 2196
nuclear@0 2197 {
nuclear@0 2198 ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
nuclear@0 2199 na->mNodeName.Set(name);
nuclear@0 2200
nuclear@0 2201 const PropertyTable& props = target.Props();
nuclear@0 2202
nuclear@0 2203 // need to convert from TRS order to SRT?
nuclear@0 2204 if(reverse_order) {
nuclear@0 2205
nuclear@0 2206 aiVector3D def_scale, def_translate;
nuclear@0 2207 aiQuaternion def_rot;
nuclear@0 2208
nuclear@0 2209 KeyFrameListList scaling;
nuclear@0 2210 KeyFrameListList translation;
nuclear@0 2211 KeyFrameListList rotation;
nuclear@0 2212
nuclear@0 2213 if(chain[TransformationComp_Scaling] != iter_end) {
nuclear@0 2214 scaling = GetKeyframeList((*chain[TransformationComp_Scaling]).second);
nuclear@0 2215 }
nuclear@0 2216 else {
nuclear@0 2217 def_scale = PropertyGet(props,"Lcl Scaling",aiVector3D(1.f,1.f,1.f));
nuclear@0 2218 }
nuclear@0 2219
nuclear@0 2220 if(chain[TransformationComp_Translation] != iter_end) {
nuclear@0 2221 translation = GetKeyframeList((*chain[TransformationComp_Translation]).second);
nuclear@0 2222 }
nuclear@0 2223 else {
nuclear@0 2224 def_translate = PropertyGet(props,"Lcl Translation",aiVector3D(0.f,0.f,0.f));
nuclear@0 2225 }
nuclear@0 2226
nuclear@0 2227 if(chain[TransformationComp_Rotation] != iter_end) {
nuclear@0 2228 rotation = GetKeyframeList((*chain[TransformationComp_Rotation]).second);
nuclear@0 2229 }
nuclear@0 2230 else {
nuclear@0 2231 def_rot = EulerToQuaternion(PropertyGet(props,"Lcl Rotation",aiVector3D(0.f,0.f,0.f)),
nuclear@0 2232 target.RotationOrder());
nuclear@0 2233 }
nuclear@0 2234
nuclear@0 2235 KeyFrameListList joined;
nuclear@0 2236 joined.insert(joined.end(), scaling.begin(), scaling.end());
nuclear@0 2237 joined.insert(joined.end(), translation.begin(), translation.end());
nuclear@0 2238 joined.insert(joined.end(), rotation.begin(), rotation.end());
nuclear@0 2239
nuclear@0 2240 const KeyTimeList& times = GetKeyTimeList(joined);
nuclear@0 2241
nuclear@0 2242 aiQuatKey* out_quat = new aiQuatKey[times.size()];
nuclear@0 2243 aiVectorKey* out_scale = new aiVectorKey[times.size()];
nuclear@0 2244 aiVectorKey* out_translation = new aiVectorKey[times.size()];
nuclear@0 2245
nuclear@0 2246 ConvertTransformOrder_TRStoSRT(out_quat, out_scale, out_translation,
nuclear@0 2247 scaling,
nuclear@0 2248 translation,
nuclear@0 2249 rotation,
nuclear@0 2250 times,
nuclear@0 2251 max_time,
nuclear@0 2252 min_time,
nuclear@0 2253 target.RotationOrder(),
nuclear@0 2254 def_scale,
nuclear@0 2255 def_translate,
nuclear@0 2256 def_rot);
nuclear@0 2257
nuclear@0 2258 // XXX remove duplicates / redundant keys which this operation did
nuclear@0 2259 // likely produce if not all three channels were equally dense.
nuclear@0 2260
nuclear@0 2261 na->mNumScalingKeys = static_cast<unsigned int>(times.size());
nuclear@0 2262 na->mNumRotationKeys = na->mNumScalingKeys;
nuclear@0 2263 na->mNumPositionKeys = na->mNumScalingKeys;
nuclear@0 2264
nuclear@0 2265 na->mScalingKeys = out_scale;
nuclear@0 2266 na->mRotationKeys = out_quat;
nuclear@0 2267 na->mPositionKeys = out_translation;
nuclear@0 2268 }
nuclear@0 2269 else {
nuclear@0 2270
nuclear@0 2271 // if a particular transformation is not given, grab it from
nuclear@0 2272 // the corresponding node to meet the semantics of aiNodeAnim,
nuclear@0 2273 // which requires all of rotation, scaling and translation
nuclear@0 2274 // to be set.
nuclear@0 2275 if(chain[TransformationComp_Scaling] != iter_end) {
nuclear@0 2276 ConvertScaleKeys(na, (*chain[TransformationComp_Scaling]).second,
nuclear@0 2277 layer_map,
nuclear@0 2278 max_time,
nuclear@0 2279 min_time);
nuclear@0 2280 }
nuclear@0 2281 else {
nuclear@0 2282 na->mScalingKeys = new aiVectorKey[1];
nuclear@0 2283 na->mNumScalingKeys = 1;
nuclear@0 2284
nuclear@0 2285 na->mScalingKeys[0].mTime = 0.;
nuclear@0 2286 na->mScalingKeys[0].mValue = PropertyGet(props,"Lcl Scaling",
nuclear@0 2287 aiVector3D(1.f,1.f,1.f));
nuclear@0 2288 }
nuclear@0 2289
nuclear@0 2290 if(chain[TransformationComp_Rotation] != iter_end) {
nuclear@0 2291 ConvertRotationKeys(na, (*chain[TransformationComp_Rotation]).second,
nuclear@0 2292 layer_map,
nuclear@0 2293 max_time,
nuclear@0 2294 min_time,
nuclear@0 2295 target.RotationOrder());
nuclear@0 2296 }
nuclear@0 2297 else {
nuclear@0 2298 na->mRotationKeys = new aiQuatKey[1];
nuclear@0 2299 na->mNumRotationKeys = 1;
nuclear@0 2300
nuclear@0 2301 na->mRotationKeys[0].mTime = 0.;
nuclear@0 2302 na->mRotationKeys[0].mValue = EulerToQuaternion(
nuclear@0 2303 PropertyGet(props,"Lcl Rotation",aiVector3D(0.f,0.f,0.f)),
nuclear@0 2304 target.RotationOrder());
nuclear@0 2305 }
nuclear@0 2306
nuclear@0 2307 if(chain[TransformationComp_Translation] != iter_end) {
nuclear@0 2308 ConvertTranslationKeys(na, (*chain[TransformationComp_Translation]).second,
nuclear@0 2309 layer_map,
nuclear@0 2310 max_time,
nuclear@0 2311 min_time);
nuclear@0 2312 }
nuclear@0 2313 else {
nuclear@0 2314 na->mPositionKeys = new aiVectorKey[1];
nuclear@0 2315 na->mNumPositionKeys = 1;
nuclear@0 2316
nuclear@0 2317 na->mPositionKeys[0].mTime = 0.;
nuclear@0 2318 na->mPositionKeys[0].mValue = PropertyGet(props,"Lcl Translation",
nuclear@0 2319 aiVector3D(0.f,0.f,0.f));
nuclear@0 2320 }
nuclear@0 2321
nuclear@0 2322 }
nuclear@0 2323 return na.dismiss();
nuclear@0 2324 }
nuclear@0 2325
nuclear@0 2326
nuclear@0 2327
nuclear@0 2328 // key (time), value, mapto (component index)
nuclear@0 2329 typedef boost::tuple< const KeyTimeList*, const KeyValueList*, unsigned int > KeyFrameList;
nuclear@0 2330 typedef std::vector<KeyFrameList> KeyFrameListList;
nuclear@0 2331
nuclear@0 2332
nuclear@0 2333
nuclear@0 2334 // ------------------------------------------------------------------------------------------------
nuclear@0 2335 KeyFrameListList GetKeyframeList(const std::vector<const AnimationCurveNode*>& nodes)
nuclear@0 2336 {
nuclear@0 2337 KeyFrameListList inputs;
nuclear@0 2338 inputs.reserve(nodes.size()*3);
nuclear@0 2339
nuclear@0 2340 BOOST_FOREACH(const AnimationCurveNode* node, nodes) {
nuclear@0 2341 ai_assert(node);
nuclear@0 2342
nuclear@0 2343 const AnimationCurveMap& curves = node->Curves();
nuclear@0 2344 BOOST_FOREACH(const AnimationCurveMap::value_type& kv, curves) {
nuclear@0 2345
nuclear@0 2346 unsigned int mapto;
nuclear@0 2347 if (kv.first == "d|X") {
nuclear@0 2348 mapto = 0;
nuclear@0 2349 }
nuclear@0 2350 else if (kv.first == "d|Y") {
nuclear@0 2351 mapto = 1;
nuclear@0 2352 }
nuclear@0 2353 else if (kv.first == "d|Z") {
nuclear@0 2354 mapto = 2;
nuclear@0 2355 }
nuclear@0 2356 else {
nuclear@0 2357 FBXImporter::LogWarn("ignoring scale animation curve, did not recognize target component");
nuclear@0 2358 continue;
nuclear@0 2359 }
nuclear@0 2360
nuclear@0 2361 const AnimationCurve* const curve = kv.second;
nuclear@0 2362 ai_assert(curve->GetKeys().size() == curve->GetValues().size() && curve->GetKeys().size());
nuclear@0 2363
nuclear@0 2364 inputs.push_back(boost::make_tuple(&curve->GetKeys(), &curve->GetValues(), mapto));
nuclear@0 2365 }
nuclear@0 2366 }
nuclear@0 2367 return inputs; // pray for NRVO :-)
nuclear@0 2368 }
nuclear@0 2369
nuclear@0 2370
nuclear@0 2371 // ------------------------------------------------------------------------------------------------
nuclear@0 2372 KeyTimeList GetKeyTimeList(const KeyFrameListList& inputs)
nuclear@0 2373 {
nuclear@0 2374 ai_assert(inputs.size());
nuclear@0 2375
nuclear@0 2376 // reserve some space upfront - it is likely that the keyframe lists
nuclear@0 2377 // have matching time values, so max(of all keyframe lists) should
nuclear@0 2378 // be a good estimate.
nuclear@0 2379 KeyTimeList keys;
nuclear@0 2380
nuclear@0 2381 size_t estimate = 0;
nuclear@0 2382 BOOST_FOREACH(const KeyFrameList& kfl, inputs) {
nuclear@0 2383 estimate = std::max(estimate, kfl.get<0>()->size());
nuclear@0 2384 }
nuclear@0 2385
nuclear@0 2386 keys.reserve(estimate);
nuclear@0 2387
nuclear@0 2388 std::vector<unsigned int> next_pos;
nuclear@0 2389 next_pos.resize(inputs.size(),0);
nuclear@0 2390
nuclear@0 2391 const size_t count = inputs.size();
nuclear@0 2392 while(true) {
nuclear@0 2393
nuclear@0 2394 uint64_t min_tick = std::numeric_limits<uint64_t>::max();
nuclear@0 2395 for (size_t i = 0; i < count; ++i) {
nuclear@0 2396 const KeyFrameList& kfl = inputs[i];
nuclear@0 2397
nuclear@0 2398 if (kfl.get<0>()->size() > next_pos[i] && kfl.get<0>()->at(next_pos[i]) < min_tick) {
nuclear@0 2399 min_tick = kfl.get<0>()->at(next_pos[i]);
nuclear@0 2400 }
nuclear@0 2401 }
nuclear@0 2402
nuclear@0 2403 if (min_tick == std::numeric_limits<uint64_t>::max()) {
nuclear@0 2404 break;
nuclear@0 2405 }
nuclear@0 2406 keys.push_back(min_tick);
nuclear@0 2407
nuclear@0 2408 for (size_t i = 0; i < count; ++i) {
nuclear@0 2409 const KeyFrameList& kfl = inputs[i];
nuclear@0 2410
nuclear@0 2411
nuclear@0 2412 while(kfl.get<0>()->size() > next_pos[i] && kfl.get<0>()->at(next_pos[i]) == min_tick) {
nuclear@0 2413 ++next_pos[i];
nuclear@0 2414 }
nuclear@0 2415 }
nuclear@0 2416 }
nuclear@0 2417
nuclear@0 2418 return keys;
nuclear@0 2419 }
nuclear@0 2420
nuclear@0 2421
nuclear@0 2422 // ------------------------------------------------------------------------------------------------
nuclear@0 2423 void InterpolateKeys(aiVectorKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs,
nuclear@0 2424 const bool geom,
nuclear@0 2425 double& max_time,
nuclear@0 2426 double& min_time)
nuclear@0 2427
nuclear@0 2428 {
nuclear@0 2429 ai_assert(keys.size());
nuclear@0 2430 ai_assert(valOut);
nuclear@0 2431
nuclear@0 2432 std::vector<unsigned int> next_pos;
nuclear@0 2433 const size_t count = inputs.size();
nuclear@0 2434
nuclear@0 2435 next_pos.resize(inputs.size(),0);
nuclear@0 2436
nuclear@0 2437 BOOST_FOREACH(KeyTimeList::value_type time, keys) {
nuclear@0 2438 float result[3] = {0.0f, 0.0f, 0.0f};
nuclear@0 2439 if(geom) {
nuclear@0 2440 result[0] = result[1] = result[2] = 1.0f;
nuclear@0 2441 }
nuclear@0 2442
nuclear@0 2443 for (size_t i = 0; i < count; ++i) {
nuclear@0 2444 const KeyFrameList& kfl = inputs[i];
nuclear@0 2445
nuclear@0 2446 const size_t ksize = kfl.get<0>()->size();
nuclear@0 2447 if (ksize > next_pos[i] && kfl.get<0>()->at(next_pos[i]) == time) {
nuclear@0 2448 ++next_pos[i];
nuclear@0 2449 }
nuclear@0 2450
nuclear@0 2451 const size_t id0 = next_pos[i]>0 ? next_pos[i]-1 : 0;
nuclear@0 2452 const size_t id1 = next_pos[i]==ksize ? ksize-1 : next_pos[i];
nuclear@0 2453
nuclear@0 2454 // use lerp for interpolation
nuclear@0 2455 const KeyValueList::value_type valueA = kfl.get<1>()->at(id0);
nuclear@0 2456 const KeyValueList::value_type valueB = kfl.get<1>()->at(id1);
nuclear@0 2457
nuclear@0 2458 const KeyTimeList::value_type timeA = kfl.get<0>()->at(id0);
nuclear@0 2459 const KeyTimeList::value_type timeB = kfl.get<0>()->at(id1);
nuclear@0 2460
nuclear@0 2461 // do the actual interpolation in double-precision arithmetics
nuclear@0 2462 // because it is a bit sensitive to rounding errors.
nuclear@0 2463 const double factor = timeB == timeA ? 0. : static_cast<double>((time - timeA) / (timeB - timeA));
nuclear@0 2464 const float interpValue = static_cast<float>(valueA + (valueB - valueA) * factor);
nuclear@0 2465
nuclear@0 2466 if(geom) {
nuclear@0 2467 result[kfl.get<2>()] *= interpValue;
nuclear@0 2468 }
nuclear@0 2469 else {
nuclear@0 2470 result[kfl.get<2>()] += interpValue;
nuclear@0 2471 }
nuclear@0 2472 }
nuclear@0 2473
nuclear@0 2474 // magic value to convert fbx times to seconds
nuclear@0 2475 valOut->mTime = CONVERT_FBX_TIME(time) * anim_fps;
nuclear@0 2476
nuclear@0 2477 min_time = std::min(min_time, valOut->mTime);
nuclear@0 2478 max_time = std::max(max_time, valOut->mTime);
nuclear@0 2479
nuclear@0 2480 valOut->mValue.x = result[0];
nuclear@0 2481 valOut->mValue.y = result[1];
nuclear@0 2482 valOut->mValue.z = result[2];
nuclear@0 2483
nuclear@0 2484 ++valOut;
nuclear@0 2485 }
nuclear@0 2486 }
nuclear@0 2487
nuclear@0 2488
nuclear@0 2489 // ------------------------------------------------------------------------------------------------
nuclear@0 2490 void InterpolateKeys(aiQuatKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs,
nuclear@0 2491 const bool geom,
nuclear@0 2492 double& maxTime,
nuclear@0 2493 double& minTime,
nuclear@0 2494 Model::RotOrder order)
nuclear@0 2495 {
nuclear@0 2496 ai_assert(keys.size());
nuclear@0 2497 ai_assert(valOut);
nuclear@0 2498
nuclear@0 2499 boost::scoped_array<aiVectorKey> temp(new aiVectorKey[keys.size()]);
nuclear@0 2500 InterpolateKeys(temp.get(),keys,inputs,geom,maxTime, minTime);
nuclear@0 2501
nuclear@0 2502 aiMatrix4x4 m;
nuclear@0 2503
nuclear@0 2504 aiQuaternion lastq;
nuclear@0 2505
nuclear@0 2506 for (size_t i = 0, c = keys.size(); i < c; ++i) {
nuclear@0 2507
nuclear@0 2508 valOut[i].mTime = temp[i].mTime;
nuclear@0 2509
nuclear@0 2510
nuclear@0 2511 GetRotationMatrix(order, temp[i].mValue, m);
nuclear@0 2512 aiQuaternion quat = aiQuaternion(aiMatrix3x3(m));
nuclear@0 2513
nuclear@0 2514 // take shortest path by checking the inner product
nuclear@0 2515 // http://www.3dkingdoms.com/weekly/weekly.php?a=36
nuclear@0 2516 if (quat.x * lastq.x + quat.y * lastq.y + quat.z * lastq.z + quat.w * lastq.w < 0)
nuclear@0 2517 {
nuclear@0 2518 quat.x = -quat.x;
nuclear@0 2519 quat.y = -quat.y;
nuclear@0 2520 quat.z = -quat.z;
nuclear@0 2521 quat.w = -quat.w;
nuclear@0 2522 }
nuclear@0 2523 lastq = quat;
nuclear@0 2524
nuclear@0 2525 valOut[i].mValue = quat;
nuclear@0 2526 }
nuclear@0 2527 }
nuclear@0 2528
nuclear@0 2529
nuclear@0 2530 // ------------------------------------------------------------------------------------------------
nuclear@0 2531 void ConvertTransformOrder_TRStoSRT(aiQuatKey* out_quat, aiVectorKey* out_scale,
nuclear@0 2532 aiVectorKey* out_translation,
nuclear@0 2533 const KeyFrameListList& scaling,
nuclear@0 2534 const KeyFrameListList& translation,
nuclear@0 2535 const KeyFrameListList& rotation,
nuclear@0 2536 const KeyTimeList& times,
nuclear@0 2537 double& maxTime,
nuclear@0 2538 double& minTime,
nuclear@0 2539 Model::RotOrder order,
nuclear@0 2540 const aiVector3D& def_scale,
nuclear@0 2541 const aiVector3D& def_translate,
nuclear@0 2542 const aiQuaternion& def_rotation)
nuclear@0 2543 {
nuclear@0 2544 if (rotation.size()) {
nuclear@0 2545 InterpolateKeys(out_quat, times, rotation, false, maxTime, minTime, order);
nuclear@0 2546 }
nuclear@0 2547 else {
nuclear@0 2548 for (size_t i = 0; i < times.size(); ++i) {
nuclear@0 2549 out_quat[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
nuclear@0 2550 out_quat[i].mValue = def_rotation;
nuclear@0 2551 }
nuclear@0 2552 }
nuclear@0 2553
nuclear@0 2554 if (scaling.size()) {
nuclear@0 2555 InterpolateKeys(out_scale, times, scaling, true, maxTime, minTime);
nuclear@0 2556 }
nuclear@0 2557 else {
nuclear@0 2558 for (size_t i = 0; i < times.size(); ++i) {
nuclear@0 2559 out_scale[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
nuclear@0 2560 out_scale[i].mValue = def_scale;
nuclear@0 2561 }
nuclear@0 2562 }
nuclear@0 2563
nuclear@0 2564 if (translation.size()) {
nuclear@0 2565 InterpolateKeys(out_translation, times, translation, false, maxTime, minTime);
nuclear@0 2566 }
nuclear@0 2567 else {
nuclear@0 2568 for (size_t i = 0; i < times.size(); ++i) {
nuclear@0 2569 out_translation[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
nuclear@0 2570 out_translation[i].mValue = def_translate;
nuclear@0 2571 }
nuclear@0 2572 }
nuclear@0 2573
nuclear@0 2574 const size_t count = times.size();
nuclear@0 2575 for (size_t i = 0; i < count; ++i) {
nuclear@0 2576 aiQuaternion& r = out_quat[i].mValue;
nuclear@0 2577 aiVector3D& s = out_scale[i].mValue;
nuclear@0 2578 aiVector3D& t = out_translation[i].mValue;
nuclear@0 2579
nuclear@0 2580 aiMatrix4x4 mat, temp;
nuclear@0 2581 aiMatrix4x4::Translation(t, mat);
nuclear@0 2582 mat *= aiMatrix4x4( r.GetMatrix() );
nuclear@0 2583 mat *= aiMatrix4x4::Scaling(s, temp);
nuclear@0 2584
nuclear@0 2585 mat.Decompose(s, r, t);
nuclear@0 2586 }
nuclear@0 2587 }
nuclear@0 2588
nuclear@0 2589
nuclear@0 2590 // ------------------------------------------------------------------------------------------------
nuclear@0 2591 // euler xyz -> quat
nuclear@0 2592 aiQuaternion EulerToQuaternion(const aiVector3D& rot, Model::RotOrder order)
nuclear@0 2593 {
nuclear@0 2594 aiMatrix4x4 m;
nuclear@0 2595 GetRotationMatrix(order, rot, m);
nuclear@0 2596
nuclear@0 2597 return aiQuaternion(aiMatrix3x3(m));
nuclear@0 2598 }
nuclear@0 2599
nuclear@0 2600
nuclear@0 2601 // ------------------------------------------------------------------------------------------------
nuclear@0 2602 void ConvertScaleKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& layers,
nuclear@0 2603 double& maxTime,
nuclear@0 2604 double& minTime)
nuclear@0 2605 {
nuclear@0 2606 ai_assert(nodes.size());
nuclear@0 2607
nuclear@0 2608 // XXX for now, assume scale should be blended geometrically (i.e. two
nuclear@0 2609 // layers should be multiplied with each other). There is a FBX
nuclear@0 2610 // property in the layer to specify the behaviour, though.
nuclear@0 2611
nuclear@0 2612 const KeyFrameListList& inputs = GetKeyframeList(nodes);
nuclear@0 2613 const KeyTimeList& keys = GetKeyTimeList(inputs);
nuclear@0 2614
nuclear@0 2615 na->mNumScalingKeys = static_cast<unsigned int>(keys.size());
nuclear@0 2616 na->mScalingKeys = new aiVectorKey[keys.size()];
nuclear@0 2617 InterpolateKeys(na->mScalingKeys, keys, inputs, true, maxTime, minTime);
nuclear@0 2618 }
nuclear@0 2619
nuclear@0 2620
nuclear@0 2621 // ------------------------------------------------------------------------------------------------
nuclear@0 2622 void ConvertTranslationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
nuclear@0 2623 const LayerMap& layers,
nuclear@0 2624 double& maxTime,
nuclear@0 2625 double& minTime)
nuclear@0 2626 {
nuclear@0 2627 ai_assert(nodes.size());
nuclear@0 2628
nuclear@0 2629 // XXX see notes in ConvertScaleKeys()
nuclear@0 2630 const KeyFrameListList& inputs = GetKeyframeList(nodes);
nuclear@0 2631 const KeyTimeList& keys = GetKeyTimeList(inputs);
nuclear@0 2632
nuclear@0 2633 na->mNumPositionKeys = static_cast<unsigned int>(keys.size());
nuclear@0 2634 na->mPositionKeys = new aiVectorKey[keys.size()];
nuclear@0 2635 InterpolateKeys(na->mPositionKeys, keys, inputs, false, maxTime, minTime);
nuclear@0 2636 }
nuclear@0 2637
nuclear@0 2638
nuclear@0 2639 // ------------------------------------------------------------------------------------------------
nuclear@0 2640 void ConvertRotationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
nuclear@0 2641 const LayerMap& layers,
nuclear@0 2642 double& maxTime,
nuclear@0 2643 double& minTime,
nuclear@0 2644 Model::RotOrder order)
nuclear@0 2645 {
nuclear@0 2646 ai_assert(nodes.size());
nuclear@0 2647
nuclear@0 2648 // XXX see notes in ConvertScaleKeys()
nuclear@0 2649 const std::vector< KeyFrameList >& inputs = GetKeyframeList(nodes);
nuclear@0 2650 const KeyTimeList& keys = GetKeyTimeList(inputs);
nuclear@0 2651
nuclear@0 2652 na->mNumRotationKeys = static_cast<unsigned int>(keys.size());
nuclear@0 2653 na->mRotationKeys = new aiQuatKey[keys.size()];
nuclear@0 2654 InterpolateKeys(na->mRotationKeys, keys, inputs, false, maxTime, minTime, order);
nuclear@0 2655 }
nuclear@0 2656
nuclear@0 2657
nuclear@0 2658 // ------------------------------------------------------------------------------------------------
nuclear@0 2659 // copy generated meshes, animations, lights, cameras and textures to the output scene
nuclear@0 2660 void TransferDataToScene()
nuclear@0 2661 {
nuclear@0 2662 ai_assert(!out->mMeshes && !out->mNumMeshes);
nuclear@0 2663
nuclear@0 2664 // note: the trailing () ensures initialization with NULL - not
nuclear@0 2665 // many C++ users seem to know this, so pointing it out to avoid
nuclear@0 2666 // confusion why this code works.
nuclear@0 2667
nuclear@0 2668 if(meshes.size()) {
nuclear@0 2669 out->mMeshes = new aiMesh*[meshes.size()]();
nuclear@0 2670 out->mNumMeshes = static_cast<unsigned int>(meshes.size());
nuclear@0 2671
nuclear@0 2672 std::swap_ranges(meshes.begin(),meshes.end(),out->mMeshes);
nuclear@0 2673 }
nuclear@0 2674
nuclear@0 2675 if(materials.size()) {
nuclear@0 2676 out->mMaterials = new aiMaterial*[materials.size()]();
nuclear@0 2677 out->mNumMaterials = static_cast<unsigned int>(materials.size());
nuclear@0 2678
nuclear@0 2679 std::swap_ranges(materials.begin(),materials.end(),out->mMaterials);
nuclear@0 2680 }
nuclear@0 2681
nuclear@0 2682 if(animations.size()) {
nuclear@0 2683 out->mAnimations = new aiAnimation*[animations.size()]();
nuclear@0 2684 out->mNumAnimations = static_cast<unsigned int>(animations.size());
nuclear@0 2685
nuclear@0 2686 std::swap_ranges(animations.begin(),animations.end(),out->mAnimations);
nuclear@0 2687 }
nuclear@0 2688
nuclear@0 2689 if(lights.size()) {
nuclear@0 2690 out->mLights = new aiLight*[lights.size()]();
nuclear@0 2691 out->mNumLights = static_cast<unsigned int>(lights.size());
nuclear@0 2692
nuclear@0 2693 std::swap_ranges(lights.begin(),lights.end(),out->mLights);
nuclear@0 2694 }
nuclear@0 2695
nuclear@0 2696 if(cameras.size()) {
nuclear@0 2697 out->mCameras = new aiCamera*[cameras.size()]();
nuclear@0 2698 out->mNumCameras = static_cast<unsigned int>(cameras.size());
nuclear@0 2699
nuclear@0 2700 std::swap_ranges(cameras.begin(),cameras.end(),out->mCameras);
nuclear@0 2701 }
nuclear@0 2702 }
nuclear@0 2703
nuclear@0 2704
nuclear@0 2705 private:
nuclear@0 2706
nuclear@0 2707 // 0: not assigned yet, others: index is value - 1
nuclear@0 2708 unsigned int defaultMaterialIndex;
nuclear@0 2709
nuclear@0 2710 std::vector<aiMesh*> meshes;
nuclear@0 2711 std::vector<aiMaterial*> materials;
nuclear@0 2712 std::vector<aiAnimation*> animations;
nuclear@0 2713 std::vector<aiLight*> lights;
nuclear@0 2714 std::vector<aiCamera*> cameras;
nuclear@0 2715
nuclear@0 2716 typedef std::map<const Material*, unsigned int> MaterialMap;
nuclear@0 2717 MaterialMap materials_converted;
nuclear@0 2718
nuclear@0 2719 typedef std::map<const Geometry*, std::vector<unsigned int> > MeshMap;
nuclear@0 2720 MeshMap meshes_converted;
nuclear@0 2721
nuclear@0 2722 // fixed node name -> which trafo chain components have animations?
nuclear@0 2723 typedef std::map<std::string, unsigned int> NodeAnimBitMap;
nuclear@0 2724 NodeAnimBitMap node_anim_chain_bits;
nuclear@0 2725
nuclear@0 2726 // name -> has had its prefix_stripped?
nuclear@0 2727 typedef std::map<std::string, bool> NodeNameMap;
nuclear@0 2728 NodeNameMap node_names;
nuclear@0 2729
nuclear@0 2730 typedef std::map<std::string, std::string> NameNameMap;
nuclear@0 2731 NameNameMap renamed_nodes;
nuclear@0 2732
nuclear@0 2733 double anim_fps;
nuclear@0 2734
nuclear@0 2735 aiScene* const out;
nuclear@0 2736 const FBX::Document& doc;
nuclear@0 2737 };
nuclear@0 2738
nuclear@0 2739 //} // !anon
nuclear@0 2740
nuclear@0 2741 // ------------------------------------------------------------------------------------------------
nuclear@0 2742 void ConvertToAssimpScene(aiScene* out, const Document& doc)
nuclear@0 2743 {
nuclear@0 2744 Converter converter(out,doc);
nuclear@0 2745 }
nuclear@0 2746
nuclear@0 2747 } // !FBX
nuclear@0 2748 } // !Assimp
nuclear@0 2749
nuclear@0 2750 #endif