1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details 2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt 3 4 #include "Loader.h" 5 #include "BinaryConverter.h" 6 #include "CollisionGeometry.h" 7 #include "FileSystem.h" 8 #include "LOD.h" 9 #include "Parser.h" 10 #include "SceneGraph.h" 11 #include "StringF.h" 12 #include "graphics/RenderState.h" 13 #include "graphics/Renderer.h" 14 #include "graphics/TextureBuilder.h" 15 #include "scenegraph/Animation.h" 16 #include "utils.h" 17 #include <assimp/material.h> 18 #include <assimp/postprocess.h> 19 #include <assimp/scene.h> 20 #include <assimp/IOStream.hpp> 21 #include <assimp/IOSystem.hpp> 22 #include <assimp/Importer.hpp> 23 24 namespace { 25 class AssimpFileReadStream : public Assimp::IOStream { 26 public: AssimpFileReadStream(const RefCountedPtr<FileSystem::FileData> & data)27 explicit AssimpFileReadStream(const RefCountedPtr<FileSystem::FileData> &data) : 28 m_data(data) 29 { 30 m_cursor = m_data->GetData(); 31 } 32 ~AssimpFileReadStream()33 virtual ~AssimpFileReadStream() {} 34 FileSize() const35 virtual size_t FileSize() const { return m_data->GetSize(); } 36 Read(void * buf,size_t size,size_t count)37 virtual size_t Read(void *buf, size_t size, size_t count) 38 { 39 const char *const data_end = m_data->GetData() + m_data->GetSize(); 40 const size_t remaining = (data_end - m_cursor); 41 const size_t requested = size * count; 42 const size_t len = std::min(remaining, requested); 43 memcpy(static_cast<char *>(buf), m_cursor, len); 44 m_cursor += len; 45 return len; 46 } 47 Seek(size_t offset,aiOrigin origin)48 virtual aiReturn Seek(size_t offset, aiOrigin origin) 49 { 50 switch (origin) { 51 case aiOrigin_SET: break; 52 case aiOrigin_CUR: offset += Tell(); break; 53 case aiOrigin_END: offset += m_data->GetSize(); break; 54 default: assert(0); break; 55 } 56 if (offset > m_data->GetSize()) 57 return aiReturn_FAILURE; 58 m_cursor = m_data->GetData() + offset; 59 return aiReturn_SUCCESS; 60 } 61 Tell() const62 virtual size_t Tell() const 63 { 64 return size_t(m_cursor - m_data->GetData()); 65 } 66 Write(const void * buf,size_t size,size_t count)67 virtual size_t Write(const void *buf, size_t size, size_t count) __attribute((noreturn)) 68 { 69 assert(0); 70 abort(); 71 RETURN_ZERO_NONGNU_ONLY; 72 } 73 Flush()74 virtual void Flush() 75 { 76 assert(0); 77 abort(); 78 } 79 80 private: 81 RefCountedPtr<FileSystem::FileData> m_data; 82 const char *m_cursor; 83 }; 84 85 class AssimpFileSystem : public Assimp::IOSystem { 86 public: AssimpFileSystem(FileSystem::FileSource & fs)87 AssimpFileSystem(FileSystem::FileSource &fs) : 88 m_fs(fs) {} ~AssimpFileSystem()89 virtual ~AssimpFileSystem() {} 90 Exists(const char * path) const91 virtual bool Exists(const char *path) const 92 { 93 const FileSystem::FileInfo info = m_fs.Lookup(path); 94 return info.Exists(); 95 } 96 getOsSeparator() const97 virtual char getOsSeparator() const { return '/'; } 98 Open(const char * path,const char * mode)99 virtual Assimp::IOStream *Open(const char *path, const char *mode) 100 { 101 assert(mode[0] == 'r'); 102 assert(!strchr(mode, '+')); 103 RefCountedPtr<FileSystem::FileData> data = m_fs.ReadFile(path); 104 return (data ? new AssimpFileReadStream(data) : 0); 105 } 106 Close(Assimp::IOStream * file)107 virtual void Close(Assimp::IOStream *file) 108 { 109 delete file; 110 } 111 112 private: 113 FileSystem::FileSource &m_fs; 114 }; 115 } // anonymous namespace 116 117 namespace SceneGraph { Loader(Graphics::Renderer * r,bool logWarnings,bool loadSGMfiles)118 Loader::Loader(Graphics::Renderer *r, bool logWarnings, bool loadSGMfiles) : 119 BaseLoader(r), 120 m_doLog(logWarnings), 121 m_loadSGMs(loadSGMfiles), 122 m_mostDetailedLod(false) 123 { 124 } 125 LoadModel(const std::string & filename)126 Model *Loader::LoadModel(const std::string &filename) 127 { 128 return LoadModel(filename, "models"); 129 } 130 LoadModel(const std::string & shortname,const std::string & basepath)131 Model *Loader::LoadModel(const std::string &shortname, const std::string &basepath) 132 { 133 PROFILE_SCOPED() 134 m_logMessages.clear(); 135 136 std::vector<std::string> list_model; 137 std::vector<std::string> list_sgm; 138 FileSystem::FileSource &fileSource = FileSystem::gameDataFiles; 139 for (FileSystem::FileEnumerator files(fileSource, basepath, FileSystem::FileEnumerator::Recurse); !files.Finished(); files.Next()) { 140 const FileSystem::FileInfo &info = files.Current(); 141 const std::string &fpath = info.GetPath(); 142 143 //check it's the expected type 144 if (info.IsFile()) { 145 if (ends_with_ci(fpath, ".model")) { // store the path for ".model" files 146 list_model.push_back(fpath); 147 } else if (m_loadSGMs & ends_with_ci(fpath, ".sgm")) { // store only the shortname for ".sgm" files. 148 list_sgm.push_back(info.GetName().substr(0, info.GetName().size() - 4)); 149 } 150 } 151 } 152 153 if (m_loadSGMs) { 154 for (auto &sgmname : list_sgm) { 155 if (sgmname == shortname) { 156 //binary loader expects extension-less name. Might want to change this. 157 SceneGraph::BinaryConverter bc(m_renderer); 158 m_model = bc.Load(shortname); 159 if (m_model) 160 return m_model; 161 else 162 break; // we'll have to load the non-sgm file 163 } 164 } 165 } 166 167 for (auto &fpath : list_model) { 168 RefCountedPtr<FileSystem::FileData> filedata = FileSystem::gameDataFiles.ReadFile(fpath); 169 if (!filedata) { 170 Output("LoadModel: %s: could not read file\n", fpath.c_str()); 171 return nullptr; 172 } 173 174 //check it's the wanted name & load it 175 const FileSystem::FileInfo &info = filedata->GetInfo(); 176 const std::string name = info.GetName(); 177 if (name.substr(0, name.length() - 6) == shortname) { 178 ModelDefinition modelDefinition; 179 try { 180 //curPath is used to find textures, patterns, 181 //possibly other data files for this model. 182 //Strip trailing slash 183 m_curPath = info.GetDir(); 184 assert(!m_curPath.empty()); 185 if (m_curPath[m_curPath.length() - 1] == '/') 186 m_curPath = m_curPath.substr(0, m_curPath.length() - 1); 187 188 Parser p(fileSource, fpath, m_curPath); 189 p.Parse(&modelDefinition); 190 } catch (ParseError &err) { 191 Output("%s\n", err.what()); 192 throw LoadingError(err.what()); 193 } 194 modelDefinition.name = shortname; 195 return CreateModel(modelDefinition); 196 } 197 } 198 throw(LoadingError("File not found")); 199 } 200 CreateModel(ModelDefinition & def)201 Model *Loader::CreateModel(ModelDefinition &def) 202 { 203 PROFILE_SCOPED() 204 using Graphics::Material; 205 if (def.matDefs.empty()) return 0; 206 if (def.lodDefs.empty()) return 0; 207 208 Model *model = new Model(m_renderer, def.name); 209 m_model = model; 210 bool patternsUsed = false; 211 212 m_thrustersRoot.Reset(new Group(m_renderer)); 213 m_billboardsRoot.Reset(new Group(m_renderer)); 214 215 //create materials from definitions 216 for (std::vector<MaterialDefinition>::const_iterator it = def.matDefs.begin(); 217 it != def.matDefs.end(); ++it) { 218 if (it->use_pattern) patternsUsed = true; 219 ConvertMaterialDefinition(*it); 220 } 221 //Output("Loaded %d materials\n", int(model->m_materials.size())); 222 223 //load meshes 224 //"mesh" here refers to a "mesh xxx.yyy" 225 //defined in the .model 226 std::map<std::string, RefCountedPtr<Node>> meshCache; 227 LOD *lodNode = 0; 228 if (def.lodDefs.size() > 1) { //don't bother with a lod node if only one level 229 lodNode = new LOD(m_renderer); 230 model->GetRoot()->AddChild(lodNode); 231 } 232 for (std::vector<LodDefinition>::const_iterator lod = def.lodDefs.begin(); 233 lod != def.lodDefs.end(); ++lod) { 234 m_mostDetailedLod = (lod == def.lodDefs.end() - 1); 235 236 //does a detail level have multiple meshes? If so, we need a Group. 237 Group *group = 0; 238 if (lodNode && (*lod).meshNames.size() > 1) { 239 group = new Group(m_renderer); 240 lodNode->AddLevel((*lod).pixelSize, group); 241 } 242 for (std::vector<std::string>::const_iterator it = (*lod).meshNames.begin(); 243 it != (*lod).meshNames.end(); ++it) { 244 try { 245 //multiple lods might use the same mesh 246 RefCountedPtr<Node> mesh; 247 std::map<std::string, RefCountedPtr<Node>>::iterator cacheIt = meshCache.find((*it)); 248 if (cacheIt != meshCache.end()) 249 mesh = (*cacheIt).second; 250 else { 251 try { 252 mesh = LoadMesh(*it, def.animDefs); 253 } catch (LoadingError &err) { 254 //append filename - easiest to do here 255 throw(LoadingError(stringf("%0:\n%1", *it, err.what()))); 256 } 257 meshCache[*(it)] = mesh; 258 } 259 assert(mesh.Valid()); 260 261 if (group) 262 group->AddChild(mesh.Get()); 263 else if (lodNode) { 264 lodNode->AddLevel((*lod).pixelSize, mesh.Get()); 265 } else 266 model->GetRoot()->AddChild(mesh.Get()); 267 } catch (LoadingError &err) { 268 delete model; 269 Output("%s\n", err.what()); 270 throw; 271 } 272 } 273 } 274 275 if (m_thrustersRoot->GetNumChildren() > 0) { 276 m_thrustersRoot->SetName("thrusters"); 277 m_thrustersRoot->SetNodeMask(NODE_TRANSPARENT); 278 model->GetRoot()->AddChild(m_thrustersRoot.Get()); 279 } 280 281 if (m_billboardsRoot->GetNumChildren() > 0) { 282 m_billboardsRoot->SetName("navlights"); 283 m_billboardsRoot->SetNodeMask(NODE_TRANSPARENT); 284 model->GetRoot()->AddChild(m_billboardsRoot.Get()); 285 } 286 287 // Load collision meshes 288 // They are added at the top level of the model root as CollisionGeometry nodes 289 for (std::vector<std::string>::const_iterator it = def.collisionDefs.begin(); 290 it != def.collisionDefs.end(); ++it) { 291 try { 292 LoadCollision(*it); 293 } catch (LoadingError &err) { 294 throw(LoadingError(stringf("%0:\n%1", *it, err.what()))); 295 } 296 } 297 298 // Run CollisionVisitor to create the initial CM and its GeomTree. 299 // If no collision mesh is defined, a simple bounding box will be generated 300 Output("CreateCollisionMesh for : (%s)\n", m_model->m_name.c_str()); 301 m_model->CreateCollisionMesh(); 302 303 // Do an initial animation update to get all the animation transforms correct 304 m_model->InitAnimations(); 305 306 //find usable pattern textures from the model directory 307 if (patternsUsed) 308 SetUpPatterns(); 309 310 return model; 311 } 312 LoadMesh(const std::string & filename,const AnimList & animDefs)313 RefCountedPtr<Node> Loader::LoadMesh(const std::string &filename, const AnimList &animDefs) 314 { 315 PROFILE_SCOPED() 316 //remove path from filename for nicer logging 317 size_t slashpos = filename.rfind("/"); 318 m_curMeshDef = filename.substr(slashpos + 1, filename.length() - slashpos); 319 320 Assimp::Importer importer; 321 importer.SetIOHandler(new AssimpFileSystem(FileSystem::gameDataFiles)); 322 323 //Removing components is suggested to optimize loading. We do not care about vtx colors now. 324 importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, aiComponent_COLORS); 325 importer.SetPropertyInteger(AI_CONFIG_PP_SLM_VERTEX_LIMIT, AI_SLM_DEFAULT_MAX_VERTICES); 326 327 //There are several optimizations assimp can do, intentionally skipping them now 328 const aiScene *scene = importer.ReadFile( 329 filename, 330 aiProcess_RemoveComponent | 331 aiProcess_Triangulate | 332 aiProcess_SortByPType | //ignore point, line primitive types (collada dummy nodes seem to be fine) 333 aiProcess_GenUVCoords | 334 aiProcess_FlipUVs | 335 aiProcess_CalcTangentSpace | 336 aiProcess_JoinIdenticalVertices | 337 aiProcess_GenSmoothNormals | //only if normals not specified 338 aiProcess_ImproveCacheLocality | 339 aiProcess_LimitBoneWeights | 340 aiProcess_FindDegenerates | 341 aiProcess_FindInvalidData); 342 343 if (!scene) 344 throw LoadingError("Couldn't load file"); 345 346 if (scene->mNumMeshes == 0) 347 throw LoadingError("No geometry found"); 348 349 //turn all scene aiMeshes into Surfaces 350 //Index matches assimp index. 351 std::vector<RefCountedPtr<StaticGeometry>> geoms; 352 ConvertAiMeshes(geoms, scene); 353 354 // Recursive structure conversion. Matrix needs to be accumulated for 355 // special features that are absolute-positioned (thrusters) 356 RefCountedPtr<Node> meshRoot(new Group(m_renderer)); 357 358 ConvertNodes(scene->mRootNode, static_cast<Group *>(meshRoot.Get()), geoms, matrix4x4f::Identity()); 359 ConvertAnimations(scene, animDefs, static_cast<Group *>(meshRoot.Get())); 360 361 return meshRoot; 362 } 363 in_range(double keytime,double start,double end)364 static bool in_range(double keytime, double start, double end) 365 { 366 return (keytime >= start - 0.001 && keytime - 0.001 <= end); 367 } 368 369 // check animation channel has a key within time range CheckKeysInRange(const aiNodeAnim * chan,double start,double end)370 bool Loader::CheckKeysInRange(const aiNodeAnim *chan, double start, double end) 371 { 372 int posKeysInRange = 0; 373 int rotKeysInRange = 0; 374 int sclKeysInRange = 0; 375 376 for (unsigned int k = 0; k < chan->mNumPositionKeys; k++) { 377 const aiVectorKey &aikey = chan->mPositionKeys[k]; 378 if (in_range(aikey.mTime, start, end)) posKeysInRange++; 379 } 380 381 for (unsigned int k = 0; k < chan->mNumRotationKeys; k++) { 382 const aiQuatKey &aikey = chan->mRotationKeys[k]; 383 if (in_range(aikey.mTime, start, end)) rotKeysInRange++; 384 } 385 386 for (unsigned int k = 0; k < chan->mNumScalingKeys; k++) { 387 const aiVectorKey &aikey = chan->mScalingKeys[k]; 388 if (in_range(aikey.mTime, start, end)) sclKeysInRange++; 389 } 390 391 return (posKeysInRange > 0 || rotKeysInRange > 0 || sclKeysInRange > 0); 392 } 393 AddLog(const std::string & msg)394 void Loader::AddLog(const std::string &msg) 395 { 396 if (m_doLog) m_logMessages.push_back(msg); 397 } 398 CheckAnimationConflicts(const Animation * anim,const std::vector<Animation * > & otherAnims)399 void Loader::CheckAnimationConflicts(const Animation *anim, const std::vector<Animation *> &otherAnims) 400 { 401 typedef std::vector<AnimationChannel>::const_iterator ChannelIterator; 402 typedef std::vector<Animation *>::const_iterator AnimIterator; 403 404 if (anim->m_channels.empty() || otherAnims.empty()) return; 405 406 //check all other animations that they don't control the same nodes as this animation, since 407 //that is not supported at this point 408 for (ChannelIterator chan = anim->m_channels.begin(); chan != anim->m_channels.end(); ++chan) { 409 for (AnimIterator other = otherAnims.begin(); other != otherAnims.end(); ++other) { 410 const Animation *otherAnim = (*other); 411 if (otherAnim == anim) 412 continue; 413 for (ChannelIterator otherChan = otherAnim->m_channels.begin(); otherChan != otherAnim->m_channels.end(); ++otherChan) { 414 //warnings as errors mentality - this is not really fatal 415 if (chan->node == otherChan->node) 416 throw LoadingError(stringf("Animations %0 and %1 both control node: %2", anim->GetName(), otherAnim->GetName(), chan->node->GetName())); 417 } 418 } 419 } 420 } 421 422 #pragma pack(push, 4) 423 struct ModelVtx { 424 vector3f pos; 425 vector3f nrm; 426 vector2f uv0; 427 }; 428 429 struct ModelTangentVtx { 430 vector3f pos; 431 vector3f nrm; 432 vector2f uv0; 433 vector3f tangent; 434 }; 435 #pragma pack(pop) 436 ConvertAiMeshes(std::vector<RefCountedPtr<StaticGeometry>> & geoms,const aiScene * scene)437 void Loader::ConvertAiMeshes(std::vector<RefCountedPtr<StaticGeometry>> &geoms, const aiScene *scene) 438 { 439 PROFILE_SCOPED() 440 //XXX sigh, workaround for obj loader 441 int matIdxOffs = 0; 442 if (scene->mNumMaterials > scene->mNumMeshes) 443 matIdxOffs = 1; 444 445 //turn meshes into static geometry nodes 446 for (unsigned int i = 0; i < scene->mNumMeshes; i++) { 447 const aiMesh *mesh = scene->mMeshes[i]; 448 assert(mesh->HasNormals()); 449 450 RefCountedPtr<StaticGeometry> geom(new StaticGeometry(m_renderer)); 451 geom->SetName(stringf("sgMesh%0{u}", i)); 452 453 const bool hasUVs = mesh->HasTextureCoords(0); 454 const bool hasTangents = mesh->HasTangentsAndBitangents(); 455 if (!hasUVs) 456 AddLog(stringf("%0: missing UV coordinates", m_curMeshDef)); 457 if (!hasTangents) 458 AddLog(stringf("%0: missing Tangents and Bitangents coordinates", m_curMeshDef)); 459 //sadly, aimesh name is usually empty so no help for logging 460 461 //Material names are not consistent throughout formats. 462 //try matching name first, if that fails use index 463 RefCountedPtr<Graphics::Material> mat; 464 const aiMaterial *amat = scene->mMaterials[mesh->mMaterialIndex]; 465 aiString aiMatName; 466 if (AI_SUCCESS == amat->Get(AI_MATKEY_NAME, aiMatName)) 467 mat = m_model->GetMaterialByName(std::string(aiMatName.C_Str())); 468 469 if (!mat.Valid()) { 470 const unsigned int matIdx = mesh->mMaterialIndex - matIdxOffs; 471 AddLog(stringf("%0: no material %1, using material %2{u} instead", m_curMeshDef, aiMatName.C_Str(), matIdx + 1)); 472 mat = m_model->GetMaterialByIndex(matIdx); 473 } 474 assert(mat.Valid()); 475 476 Graphics::RenderStateDesc rsd; 477 //turn on alpha blending and mark entire node as transparent 478 //(all importers split by material so far) 479 if (mat->diffuse.a < 255) { 480 geom->SetNodeMask(NODE_TRANSPARENT); 481 geom->m_blendMode = Graphics::BLEND_ALPHA; 482 rsd.blendMode = Graphics::BLEND_ALPHA; 483 rsd.depthWrite = false; 484 } 485 486 geom->SetRenderState(m_renderer->CreateRenderState(rsd)); 487 488 Graphics::VertexBufferDesc vbd; 489 vbd.attrib[0].semantic = Graphics::ATTRIB_POSITION; 490 vbd.attrib[0].format = Graphics::ATTRIB_FORMAT_FLOAT3; 491 vbd.attrib[0].offset = hasTangents ? offsetof(ModelTangentVtx, pos) : offsetof(ModelVtx, pos); 492 vbd.attrib[1].semantic = Graphics::ATTRIB_NORMAL; 493 vbd.attrib[1].format = Graphics::ATTRIB_FORMAT_FLOAT3; 494 vbd.attrib[1].offset = hasTangents ? offsetof(ModelTangentVtx, nrm) : offsetof(ModelVtx, nrm); 495 vbd.attrib[2].semantic = Graphics::ATTRIB_UV0; 496 vbd.attrib[2].format = Graphics::ATTRIB_FORMAT_FLOAT2; 497 vbd.attrib[2].offset = hasTangents ? offsetof(ModelTangentVtx, uv0) : offsetof(ModelVtx, uv0); 498 if (hasTangents) { 499 vbd.attrib[3].semantic = Graphics::ATTRIB_TANGENT; 500 vbd.attrib[3].format = Graphics::ATTRIB_FORMAT_FLOAT3; 501 vbd.attrib[3].offset = offsetof(ModelTangentVtx, tangent); 502 } 503 vbd.stride = hasTangents ? sizeof(ModelTangentVtx) : sizeof(ModelVtx); 504 vbd.numVertices = mesh->mNumVertices; 505 vbd.usage = Graphics::BUFFER_USAGE_STATIC; 506 507 RefCountedPtr<Graphics::VertexBuffer> vb(m_renderer->CreateVertexBuffer(vbd)); 508 509 // huge meshes are split by the importer so this should not exceed 65K indices 510 std::vector<Uint32> indices; 511 if (mesh->mNumFaces > 0) { 512 indices.reserve(mesh->mNumFaces * 3); 513 for (unsigned int f = 0; f < mesh->mNumFaces; f++) { 514 const aiFace *face = &mesh->mFaces[f]; 515 for (unsigned int j = 0; j < face->mNumIndices; j++) { 516 indices.push_back(face->mIndices[j]); 517 } 518 } 519 } else { 520 //generate dummy indices 521 AddLog(stringf("Missing indices in mesh %0{u}", i)); 522 indices.reserve(mesh->mNumVertices); 523 for (unsigned int v = 0; v < mesh->mNumVertices; v++) 524 indices.push_back(v); 525 } 526 527 assert(indices.size() > 0); 528 529 //create buffer & copy 530 RefCountedPtr<Graphics::IndexBuffer> ib(m_renderer->CreateIndexBuffer(indices.size(), Graphics::BUFFER_USAGE_STATIC)); 531 Uint32 *idxPtr = ib->Map(Graphics::BUFFER_MAP_WRITE); 532 for (Uint32 j = 0; j < indices.size(); j++) 533 idxPtr[j] = indices[j]; 534 ib->Unmap(); 535 536 //copy vertices, always assume normals 537 //replace nonexistent UVs with zeros 538 if (!hasTangents) { 539 ModelVtx *vtxPtr = vb->Map<ModelVtx>(Graphics::BUFFER_MAP_WRITE); 540 for (unsigned int v = 0; v < mesh->mNumVertices; v++) { 541 const aiVector3D &vtx = mesh->mVertices[v]; 542 const aiVector3D &norm = mesh->mNormals[v]; 543 const aiVector3D &uv0 = hasUVs ? mesh->mTextureCoords[0][v] : aiVector3D(0.f); 544 vtxPtr[v].pos = vector3f(vtx.x, vtx.y, vtx.z); 545 vtxPtr[v].nrm = vector3f(norm.x, norm.y, norm.z); 546 vtxPtr[v].uv0 = vector2f(uv0.x, uv0.y); 547 548 //update bounding box 549 //untransformed points, collision visitor will transform 550 geom->m_boundingBox.Update(vtx.x, vtx.y, vtx.z); 551 } 552 vb->Unmap(); 553 } else { 554 ModelTangentVtx *vtxPtr = vb->Map<ModelTangentVtx>(Graphics::BUFFER_MAP_WRITE); 555 for (unsigned int v = 0; v < mesh->mNumVertices; v++) { 556 const aiVector3D &vtx = mesh->mVertices[v]; 557 const aiVector3D &norm = mesh->mNormals[v]; 558 const aiVector3D &uv0 = hasUVs ? mesh->mTextureCoords[0][v] : aiVector3D(0.f); 559 const aiVector3D &tangents = mesh->mTangents[v]; 560 vtxPtr[v].pos = vector3f(vtx.x, vtx.y, vtx.z); 561 vtxPtr[v].nrm = vector3f(norm.x, norm.y, norm.z); 562 vtxPtr[v].uv0 = vector2f(uv0.x, uv0.y); 563 vtxPtr[v].tangent = vector3f(tangents.x, tangents.y, tangents.z); 564 565 //update bounding box 566 //untransformed points, collision visitor will transform 567 geom->m_boundingBox.Update(vtx.x, vtx.y, vtx.z); 568 } 569 vb->Unmap(); 570 } 571 572 geom->AddMesh(vb, ib, mat); 573 574 geoms.push_back(geom); 575 } 576 } 577 ConvertAnimations(const aiScene * scene,const AnimList & animDefs,Node * meshRoot)578 void Loader::ConvertAnimations(const aiScene *scene, const AnimList &animDefs, Node *meshRoot) 579 { 580 PROFILE_SCOPED() 581 //Split convert assimp animations according to anim defs 582 //This is very limited, and all animdefs are processed for all 583 //meshes, potentially leading to duplicate and wrongly split animations 584 if (animDefs.empty() || scene->mNumAnimations == 0) return; 585 if (scene->mNumAnimations > 1) Output("File has %d animations, treating as one animation\n", scene->mNumAnimations); 586 587 std::vector<Animation *> &animations = m_model->m_animations; 588 589 for (AnimList::const_iterator def = animDefs.begin(); 590 def != animDefs.end(); 591 ++def) { 592 //XXX format differences: for a 40-frame animation exported from Blender, 593 //.X results in duration 39 and Collada in Duration 1.25. 594 //duration is calculated after adding all keys 595 //take TPS from the first animation 596 const aiAnimation *firstAnim = scene->mAnimations[0]; 597 const double ticksPerSecond = firstAnim->mTicksPerSecond > 0.0 ? firstAnim->mTicksPerSecond : 24.0; 598 const double secondsPerTick = 1.0 / ticksPerSecond; 599 600 double start = DBL_MAX; 601 double end = -DBL_MAX; 602 603 //Ranges are specified in frames (since that's nice) but Collada 604 //uses seconds. This is easiest to detect from ticksPerSecond, 605 //but assuming 24 FPS here 606 //Could make FPS an additional define or always require 24 607 double defStart = def->start; 608 double defEnd = def->end; 609 if (is_equal_exact(ticksPerSecond, 1.0)) { 610 defStart /= 24.0; 611 defEnd /= 24.0; 612 } 613 614 // Add channels to current animation if it's already present 615 // Necessary to make animations work in multiple LODs 616 Animation *animation = m_model->FindAnimation(def->name); 617 const bool newAnim = !animation; 618 if (newAnim) animation = new Animation(def->name, 0.0); 619 620 const size_t first_new_channel = animation->m_channels.size(); 621 622 for (unsigned int i = 0; i < scene->mNumAnimations; i++) { 623 const aiAnimation *aianim = scene->mAnimations[i]; 624 for (unsigned int j = 0; j < aianim->mNumChannels; j++) { 625 const aiNodeAnim *aichan = aianim->mChannels[j]; 626 //do a preliminary check that at least two keys in one channel are within range 627 if (!CheckKeysInRange(aichan, defStart, defEnd)) 628 continue; 629 630 const std::string channame(aichan->mNodeName.C_Str()); 631 MatrixTransform *trans = dynamic_cast<MatrixTransform *>(meshRoot->FindNode(channame)); 632 assert(trans); 633 animation->m_channels.push_back(AnimationChannel(trans)); 634 AnimationChannel &chan = animation->m_channels.back(); 635 636 for (unsigned int k = 0; k < aichan->mNumPositionKeys; k++) { 637 const aiVectorKey &aikey = aichan->mPositionKeys[k]; 638 const aiVector3D &aipos = aikey.mValue; 639 if (in_range(aikey.mTime, defStart, defEnd)) { 640 const double t = aikey.mTime * secondsPerTick; 641 chan.positionKeys.push_back(PositionKey(t, vector3f(aipos.x, aipos.y, aipos.z))); 642 start = std::min(start, t); 643 end = std::max(end, t); 644 } 645 } 646 647 //scale interpolation will blow up without rotation keys, 648 //so skipping them when rotkeys < 2 is correct 649 if (aichan->mNumRotationKeys < 2) continue; 650 651 for (unsigned int k = 0; k < aichan->mNumRotationKeys; k++) { 652 const aiQuatKey &aikey = aichan->mRotationKeys[k]; 653 const aiQuaternion &airot = aikey.mValue; 654 if (in_range(aikey.mTime, defStart, defEnd)) { 655 const double t = aikey.mTime * secondsPerTick; 656 chan.rotationKeys.push_back(RotationKey(t, Quaternionf(airot.w, airot.x, airot.y, airot.z))); 657 start = std::min(start, t); 658 end = std::max(end, t); 659 } 660 } 661 662 for (unsigned int k = 0; k < aichan->mNumScalingKeys; k++) { 663 const aiVectorKey &aikey = aichan->mScalingKeys[k]; 664 const aiVector3D &aipos = aikey.mValue; 665 if (in_range(aikey.mTime, defStart, defEnd)) { 666 const double t = aikey.mTime * secondsPerTick; 667 chan.scaleKeys.push_back(ScaleKey(t, vector3f(aipos.x, aipos.y, aipos.z))); 668 start = std::min(start, t); 669 end = std::max(end, t); 670 } 671 } 672 } 673 } 674 675 // convert remove initial offset (so the first keyframe is at exactly t=0) 676 for (std::vector<AnimationChannel>::iterator chan = animation->m_channels.begin() + first_new_channel; 677 chan != animation->m_channels.end(); ++chan) { 678 for (unsigned int k = 0; k < chan->positionKeys.size(); ++k) { 679 chan->positionKeys[k].time -= start; 680 assert(chan->positionKeys[k].time >= 0.0); 681 } 682 for (unsigned int k = 0; k < chan->rotationKeys.size(); ++k) { 683 chan->rotationKeys[k].time -= start; 684 assert(chan->rotationKeys[k].time >= 0.0); 685 } 686 for (unsigned int k = 0; k < chan->scaleKeys.size(); ++k) { 687 chan->scaleKeys[k].time -= start; 688 assert(chan->scaleKeys[k].time >= 0.0); 689 } 690 } 691 692 // set actual duration 693 const double dur = end - start; 694 animation->m_duration = newAnim ? dur : std::max(animation->m_duration, dur); 695 696 //do final sanity checking before adding 697 try { 698 CheckAnimationConflicts(animation, animations); 699 } catch (LoadingError &) { 700 if (newAnim) delete animation; 701 throw; 702 } 703 704 if (newAnim) { 705 if (animation->m_channels.empty()) 706 delete animation; 707 else 708 animations.push_back(animation); 709 } 710 } 711 } 712 ConvertMatrix(const aiMatrix4x4 & trans) const713 matrix4x4f Loader::ConvertMatrix(const aiMatrix4x4 &trans) const 714 { 715 matrix4x4f m; 716 m[0] = trans.a1; 717 m[1] = trans.b1; 718 m[2] = trans.c1; 719 m[3] = trans.d1; 720 721 m[4] = trans.a2; 722 m[5] = trans.b2; 723 m[6] = trans.c2; 724 m[7] = trans.d2; 725 726 m[8] = trans.a3; 727 m[9] = trans.b3; 728 m[10] = trans.c3; 729 m[11] = trans.d3; 730 731 m[12] = trans.a4; 732 m[13] = trans.b4; 733 m[14] = trans.c4; 734 m[15] = trans.d4; 735 return m; 736 } 737 CreateLabel(Group * parent,const matrix4x4f & m)738 void Loader::CreateLabel(Group *parent, const matrix4x4f &m) 739 { 740 PROFILE_SCOPED() 741 MatrixTransform *trans = new MatrixTransform(m_renderer, m); 742 Label3D *label = new Label3D(m_renderer, m_labelFont); 743 label->SetText("Bananas"); 744 trans->AddChild(label); 745 parent->AddChild(trans); 746 } 747 CreateThruster(const std::string & name,const matrix4x4f & m)748 void Loader::CreateThruster(const std::string &name, const matrix4x4f &m) 749 { 750 PROFILE_SCOPED() 751 if (!m_mostDetailedLod) return AddLog("Thruster outside highest LOD, ignored"); 752 753 const bool linear = starts_with(name, "thruster_linear"); 754 755 matrix4x4f transform = m; 756 757 MatrixTransform *trans = new MatrixTransform(m_renderer, transform); 758 759 const vector3f pos = transform.GetTranslate(); 760 transform.ClearToRotOnly(); 761 762 const vector3f direction = transform * vector3f(0.f, 0.f, 1.f); 763 764 Thruster *thruster = new Thruster(m_renderer, linear, 765 pos, direction.Normalized()); 766 767 thruster->SetName(name); 768 trans->AddChild(thruster); 769 770 m_thrustersRoot->AddChild(trans); 771 } 772 CreateNavlight(const std::string & name,const matrix4x4f & m)773 void Loader::CreateNavlight(const std::string &name, const matrix4x4f &m) 774 { 775 PROFILE_SCOPED() 776 if (!m_mostDetailedLod) return AddLog("Navlight outside highest LOD, ignored"); 777 778 //Create a MT, lights are attached by client 779 //we only really need the final position, so this is 780 //a waste of transform 781 const matrix4x4f lightPos = matrix4x4f::Translation(m.GetTranslate()); 782 MatrixTransform *lightPoint = new MatrixTransform(m_renderer, lightPos); 783 lightPoint->SetNodeMask(0x0); //don't render 784 lightPoint->SetName(name); 785 786 m_billboardsRoot->AddChild(lightPoint); 787 } 788 CreateCollisionGeometry(RefCountedPtr<StaticGeometry> geom,unsigned int collFlag)789 RefCountedPtr<CollisionGeometry> Loader::CreateCollisionGeometry(RefCountedPtr<StaticGeometry> geom, unsigned int collFlag) 790 { 791 PROFILE_SCOPED() 792 //Convert StaticMesh points & indices into cgeom 793 //note: it's not slow, but the amount of data being copied is just stupid: 794 //assimp to vtxbuffer, vtxbuffer to vector, vector to cgeom, cgeom to geomtree... 795 assert(geom->GetNumMeshes() == 1); 796 StaticGeometry::Mesh mesh = geom->GetMeshAt(0); 797 798 const Uint32 posOffs = mesh.vertexBuffer->GetDesc().GetOffset(Graphics::ATTRIB_POSITION); 799 const Uint32 stride = mesh.vertexBuffer->GetDesc().stride; 800 const Uint32 numVtx = mesh.vertexBuffer->GetDesc().numVertices; 801 const Uint32 numIdx = mesh.indexBuffer->GetSize(); 802 803 //copy vertex positions from buffer 804 std::vector<vector3f> pos; 805 pos.reserve(numVtx); 806 807 Uint8 *vtxPtr = mesh.vertexBuffer->Map<Uint8>(Graphics::BUFFER_MAP_READ); 808 for (Uint32 i = 0; i < numVtx; i++) 809 pos.push_back(*reinterpret_cast<vector3f *>(vtxPtr + (i * stride) + posOffs)); 810 mesh.vertexBuffer->Unmap(); 811 812 //copy indices from buffer 813 std::vector<Uint32> idx; 814 idx.reserve(numIdx); 815 816 Uint32 *idxPtr = mesh.indexBuffer->Map(Graphics::BUFFER_MAP_READ); 817 for (Uint32 i = 0; i < numIdx; i++) 818 idx.push_back(idxPtr[i]); 819 mesh.indexBuffer->Unmap(); 820 RefCountedPtr<CollisionGeometry> cgeom(new CollisionGeometry(m_renderer, pos, idx, collFlag)); 821 return cgeom; 822 } 823 ConvertNodes(aiNode * node,Group * _parent,std::vector<RefCountedPtr<StaticGeometry>> & geoms,const matrix4x4f & accum)824 void Loader::ConvertNodes(aiNode *node, Group *_parent, std::vector<RefCountedPtr<StaticGeometry>> &geoms, const matrix4x4f &accum) 825 { 826 PROFILE_SCOPED() 827 Group *parent = _parent; 828 const std::string nodename(node->mName.C_Str()); 829 const aiMatrix4x4 &trans = node->mTransformation; 830 matrix4x4f m = ConvertMatrix(trans); 831 832 //lights, and possibly other special nodes should be leaf nodes (without meshes) 833 if (node->mNumChildren == 0 && node->mNumMeshes == 0) { 834 if (starts_with(nodename, "navlight_")) { 835 CreateNavlight(nodename, accum * m); 836 } else if (starts_with(nodename, "thruster_")) { 837 CreateThruster(nodename, accum * m); 838 } else if (starts_with(nodename, "label_")) { 839 CreateLabel(parent, m); 840 } else if (starts_with(nodename, "tag_")) { 841 m_model->AddTag(nodename, new MatrixTransform(m_renderer, accum * m)); 842 } else if (starts_with(nodename, "entrance_")) { 843 m_model->AddTag(nodename, new MatrixTransform(m_renderer, m)); 844 } else if (starts_with(nodename, "loc_")) { 845 m_model->AddTag(nodename, new MatrixTransform(m_renderer, m)); 846 } else if (starts_with(nodename, "exit_")) { 847 m_model->AddTag(nodename, new MatrixTransform(m_renderer, m)); 848 } 849 return; 850 } 851 852 //if the transform is identity and the node is not animated, 853 //could just add a group 854 parent = new MatrixTransform(m_renderer, m); 855 _parent->AddChild(parent); 856 parent->SetName(nodename); 857 858 //nodes named collision_* are not added as renderable geometry 859 if (node->mNumMeshes == 1 && starts_with(nodename, "collision_")) { 860 const unsigned int collflag = GetGeomFlagForNodeName(nodename); 861 RefCountedPtr<CollisionGeometry> cgeom = CreateCollisionGeometry(geoms.at(node->mMeshes[0]), collflag); 862 cgeom->SetName(nodename + "_cgeom"); 863 cgeom->SetDynamic(starts_with(nodename, "collision_d")); 864 parent->AddChild(cgeom.Get()); 865 return; 866 } 867 868 //nodes with visible geometry (StaticGeometry and decals) 869 if (node->mNumMeshes > 0) { 870 //expecting decal_0X 871 unsigned int numDecal = 0; 872 if (starts_with(nodename, "decal_")) { 873 numDecal = atoi(nodename.substr(7, 1).c_str()); 874 if (numDecal > 4) 875 throw LoadingError("More than 4 different decals"); 876 } 877 878 for (unsigned int i = 0; i < node->mNumMeshes; i++) { 879 RefCountedPtr<StaticGeometry> geom = geoms.at(node->mMeshes[i]); 880 881 //handle special decal material 882 //set special material for decals 883 if (numDecal > 0) { 884 geom->SetNodeMask(NODE_TRANSPARENT); 885 geom->m_blendMode = Graphics::BLEND_ALPHA; 886 geom->GetMeshAt(0).material = GetDecalMaterial(numDecal); 887 geom->SetNodeFlags(geom->GetNodeFlags() | NODE_DECAL); 888 Graphics::RenderStateDesc rsd; 889 rsd.blendMode = Graphics::BLEND_ALPHA; 890 rsd.depthWrite = false; 891 //XXX add polygon offset to decal state 892 geom->SetRenderState(m_renderer->CreateRenderState(rsd)); 893 } 894 895 parent->AddChild(geom.Get()); 896 } 897 } 898 899 for (unsigned int i = 0; i < node->mNumChildren; i++) { 900 aiNode *child = node->mChildren[i]; 901 ConvertNodes(child, parent, geoms, accum * m); 902 } 903 } 904 LoadCollision(const std::string & filename)905 void Loader::LoadCollision(const std::string &filename) 906 { 907 PROFILE_SCOPED() 908 //Convert all found aiMeshes into a geomtree. Materials, 909 //Animations and node structure can be ignored 910 assert(m_model); 911 912 Assimp::Importer importer; 913 importer.SetIOHandler(new AssimpFileSystem(FileSystem::gameDataFiles)); 914 915 //discard extra data 916 importer.SetPropertyInteger(AI_CONFIG_PP_RVC_FLAGS, 917 aiComponent_COLORS | 918 aiComponent_TEXCOORDS | 919 aiComponent_NORMALS | 920 aiComponent_MATERIALS); 921 const aiScene *scene = importer.ReadFile( 922 filename, 923 aiProcess_RemoveComponent | 924 aiProcess_Triangulate | 925 aiProcess_PreTransformVertices //"bake" transformations so we can disregard the structure 926 ); 927 928 if (!scene) 929 throw LoadingError("Could not load file"); 930 931 if (scene->mNumMeshes == 0) 932 throw LoadingError("No geometry found"); 933 934 std::vector<Uint32> indices; 935 std::vector<vector3f> vertices; 936 Uint32 indexOffset = 0; 937 938 for (Uint32 i = 0; i < scene->mNumMeshes; i++) { 939 aiMesh *mesh = scene->mMeshes[i]; 940 941 //copy indices 942 //we assume aiProcess_Triangulate does its job 943 assert(mesh->mNumFaces > 0); 944 for (unsigned int f = 0; f < mesh->mNumFaces; f++) { 945 const aiFace *face = &mesh->mFaces[f]; 946 for (unsigned int j = 0; j < face->mNumIndices; j++) { 947 indices.push_back(indexOffset + face->mIndices[j]); 948 } 949 } 950 indexOffset += mesh->mNumFaces * 3; 951 952 //vertices 953 for (unsigned int v = 0; v < mesh->mNumVertices; v++) { 954 const aiVector3D &vtx = mesh->mVertices[v]; 955 vertices.push_back(vector3f(vtx.x, vtx.y, vtx.z)); 956 } 957 } 958 959 assert(!vertices.empty() && !indices.empty()); 960 961 //add pre-transformed geometry at the top level 962 m_model->GetRoot()->AddChild(new CollisionGeometry(m_renderer, vertices, indices, 0)); 963 } 964 GetGeomFlagForNodeName(const std::string & nodename)965 unsigned int Loader::GetGeomFlagForNodeName(const std::string &nodename) 966 { 967 PROFILE_SCOPED() 968 //special names after collision_ 969 if (nodename.length() > 10) { 970 //landing pads 971 if (nodename.length() >= 14 && nodename.substr(10, 3) == "pad") { 972 const std::string pad = nodename.substr(13); 973 const int padID = atoi(pad.c_str()) - 1; 974 if (padID < 240) { 975 return 0x10 + padID; 976 } 977 } 978 } 979 //anything else is static collision 980 return 0x0; 981 } 982 983 } // namespace SceneGraph 984