| /* |
| --------------------------------------------------------------------------- |
| Open Asset Import Library (assimp) |
| --------------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp team |
| |
| |
| All rights reserved. |
| |
| Redistribution and use of this software in source and binary forms, |
| with or without modification, are permitted provided that the following |
| conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| --------------------------------------------------------------------------- |
| */ |
| |
| /** @file LWSLoader.cpp |
| * @brief Implementation of the LWS importer class |
| */ |
| |
| |
| #ifndef ASSIMP_BUILD_NO_LWS_IMPORTER |
| |
| #include "LWSLoader.h" |
| #include "ParsingUtils.h" |
| #include "fast_atof.h" |
| |
| #include <assimp/SceneCombiner.h> |
| #include "GenericProperty.h" |
| #include "SkeletonMeshBuilder.h" |
| #include "ConvertToLHProcess.h" |
| #include "Importer.h" |
| #include <assimp/DefaultLogger.hpp> |
| #include <assimp/scene.h> |
| #include <assimp/IOSystem.hpp> |
| #include <assimp/importerdesc.h> |
| #include <memory> |
| |
| using namespace Assimp; |
| |
| static const aiImporterDesc desc = { |
| "LightWave Scene Importer", |
| "", |
| "", |
| "http://www.newtek.com/lightwave.html=", |
| aiImporterFlags_SupportTextFlavour, |
| 0, |
| 0, |
| 0, |
| 0, |
| "lws mot" |
| }; |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Recursive parsing of LWS files |
| void LWS::Element::Parse (const char*& buffer) |
| { |
| for (;SkipSpacesAndLineEnd(&buffer);SkipLine(&buffer)) { |
| |
| // begin of a new element with children |
| bool sub = false; |
| if (*buffer == '{') { |
| ++buffer; |
| SkipSpaces(&buffer); |
| sub = true; |
| } |
| else if (*buffer == '}') |
| return; |
| |
| children.push_back(Element()); |
| |
| // copy data line - read token per token |
| |
| const char* cur = buffer; |
| while (!IsSpaceOrNewLine(*buffer)) ++buffer; |
| children.back().tokens[0] = std::string(cur,(size_t) (buffer-cur)); |
| SkipSpaces(&buffer); |
| |
| if (children.back().tokens[0] == "Plugin") |
| { |
| DefaultLogger::get()->debug("LWS: Skipping over plugin-specific data"); |
| |
| // strange stuff inside Plugin/Endplugin blocks. Needn't |
| // follow LWS syntax, so we skip over it |
| for (;SkipSpacesAndLineEnd(&buffer);SkipLine(&buffer)) { |
| if (!::strncmp(buffer,"EndPlugin",9)) { |
| //SkipLine(&buffer); |
| break; |
| } |
| } |
| continue; |
| } |
| |
| cur = buffer; |
| while (!IsLineEnd(*buffer)) ++buffer; |
| children.back().tokens[1] = std::string(cur,(size_t) (buffer-cur)); |
| |
| // parse more elements recursively |
| if (sub) |
| children.back().Parse(buffer); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Constructor to be privately used by Importer |
| LWSImporter::LWSImporter() |
| : configSpeedFlag(), |
| io(), |
| first(), |
| last(), |
| fps(), |
| noSkeletonMesh() |
| { |
| // nothing to do here |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Destructor, private as well |
| LWSImporter::~LWSImporter() |
| { |
| // nothing to do here |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Returns whether the class can handle the format of the given file. |
| bool LWSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler,bool checkSig) const |
| { |
| const std::string extension = GetExtension(pFile); |
| if (extension == "lws" || extension == "mot") |
| return true; |
| |
| // if check for extension is not enough, check for the magic tokens LWSC and LWMO |
| if (!extension.length() || checkSig) { |
| uint32_t tokens[2]; |
| tokens[0] = AI_MAKE_MAGIC("LWSC"); |
| tokens[1] = AI_MAKE_MAGIC("LWMO"); |
| return CheckMagicToken(pIOHandler,pFile,tokens,2); |
| } |
| return false; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Get list of file extensions |
| const aiImporterDesc* LWSImporter::GetInfo () const |
| { |
| return &desc; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Setup configuration properties |
| void LWSImporter::SetupProperties(const Importer* pImp) |
| { |
| // AI_CONFIG_FAVOUR_SPEED |
| configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0)); |
| |
| // AI_CONFIG_IMPORT_LWS_ANIM_START |
| first = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_START, |
| 150392 /* magic hack */); |
| |
| // AI_CONFIG_IMPORT_LWS_ANIM_END |
| last = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWS_ANIM_END, |
| 150392 /* magic hack */); |
| |
| if (last < first) { |
| std::swap(last,first); |
| } |
| |
| noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Read an envelope description |
| void LWSImporter::ReadEnvelope(const LWS::Element& dad, LWO::Envelope& fill ) |
| { |
| if (dad.children.empty()) { |
| DefaultLogger::get()->error("LWS: Envelope descriptions must not be empty"); |
| return; |
| } |
| |
| // reserve enough storage |
| std::list< LWS::Element >::const_iterator it = dad.children.begin();; |
| fill.keys.reserve(strtoul10(it->tokens[1].c_str())); |
| |
| for (++it; it != dad.children.end(); ++it) { |
| const char* c = (*it).tokens[1].c_str(); |
| |
| if ((*it).tokens[0] == "Key") { |
| fill.keys.push_back(LWO::Key()); |
| LWO::Key& key = fill.keys.back(); |
| |
| float f; |
| SkipSpaces(&c); |
| c = fast_atoreal_move<float>(c,key.value); |
| SkipSpaces(&c); |
| c = fast_atoreal_move<float>(c,f); |
| |
| key.time = f; |
| |
| unsigned int span = strtoul10(c,&c), num = 0; |
| switch (span) { |
| |
| case 0: |
| key.inter = LWO::IT_TCB; |
| num = 5; |
| break; |
| case 1: |
| case 2: |
| key.inter = LWO::IT_HERM; |
| num = 5; |
| break; |
| case 3: |
| key.inter = LWO::IT_LINE; |
| num = 0; |
| break; |
| case 4: |
| key.inter = LWO::IT_STEP; |
| num = 0; |
| break; |
| case 5: |
| key.inter = LWO::IT_BEZ2; |
| num = 4; |
| break; |
| default: |
| DefaultLogger::get()->error("LWS: Unknown span type"); |
| } |
| for (unsigned int i = 0; i < num;++i) { |
| SkipSpaces(&c); |
| c = fast_atoreal_move<float>(c,key.params[i]); |
| } |
| } |
| else if ((*it).tokens[0] == "Behaviors") { |
| SkipSpaces(&c); |
| fill.pre = (LWO::PrePostBehaviour) strtoul10(c,&c); |
| SkipSpaces(&c); |
| fill.post = (LWO::PrePostBehaviour) strtoul10(c,&c); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Read animation channels in the old LightWave animation format |
| void LWSImporter::ReadEnvelope_Old( |
| std::list< LWS::Element >::const_iterator& it, |
| const std::list< LWS::Element >::const_iterator& end, |
| LWS::NodeDesc& nodes, |
| unsigned int /*version*/) |
| { |
| unsigned int num,sub_num; |
| if (++it == end)goto unexpected_end; |
| |
| num = strtoul10((*it).tokens[0].c_str()); |
| for (unsigned int i = 0; i < num; ++i) { |
| |
| nodes.channels.push_back(LWO::Envelope()); |
| LWO::Envelope& envl = nodes.channels.back(); |
| |
| envl.index = i; |
| envl.type = (LWO::EnvelopeType)(i+1); |
| |
| if (++it == end)goto unexpected_end; |
| sub_num = strtoul10((*it).tokens[0].c_str()); |
| |
| for (unsigned int n = 0; n < sub_num;++n) { |
| |
| if (++it == end)goto unexpected_end; |
| |
| // parse value and time, skip the rest for the moment. |
| LWO::Key key; |
| const char* c = fast_atoreal_move<float>((*it).tokens[0].c_str(),key.value); |
| SkipSpaces(&c); |
| float f; |
| fast_atoreal_move<float>((*it).tokens[0].c_str(),f); |
| key.time = f; |
| |
| envl.keys.push_back(key); |
| } |
| } |
| return; |
| |
| unexpected_end: |
| DefaultLogger::get()->error("LWS: Encountered unexpected end of file while parsing object motion"); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Setup a nice name for a node |
| void LWSImporter::SetupNodeName(aiNode* nd, LWS::NodeDesc& src) |
| { |
| const unsigned int combined = src.number | ((unsigned int)src.type) << 28u; |
| |
| // the name depends on the type. We break LWS's strange naming convention |
| // and return human-readable, but still machine-parsable and unique, strings. |
| if (src.type == LWS::NodeDesc::OBJECT) { |
| |
| if (src.path.length()) { |
| std::string::size_type s = src.path.find_last_of("\\/"); |
| if (s == std::string::npos) |
| s = 0; |
| else ++s; |
| std::string::size_type t = src.path.substr(s).find_last_of("."); |
| |
| nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)",src.path.substr(s).substr(0,t).c_str(),combined); |
| return; |
| } |
| } |
| nd->mName.length = ::ai_snprintf(nd->mName.data, MAXLEN, "%s_(%08X)",src.name,combined); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Recursively build the scenegraph |
| void LWSImporter::BuildGraph(aiNode* nd, LWS::NodeDesc& src, std::vector<AttachmentInfo>& attach, |
| BatchLoader& batch, |
| aiCamera**& camOut, |
| aiLight**& lightOut, |
| std::vector<aiNodeAnim*>& animOut) |
| { |
| // Setup a very cryptic name for the node, we want the user to be happy |
| SetupNodeName(nd,src); |
| aiNode* ndAnim = nd; |
| |
| // If the node is an object |
| if (src.type == LWS::NodeDesc::OBJECT) { |
| |
| // If the object is from an external file, get it |
| aiScene* obj = NULL; |
| if (src.path.length() ) { |
| obj = batch.GetImport(src.id); |
| if (!obj) { |
| DefaultLogger::get()->error("LWS: Failed to read external file " + src.path); |
| } |
| else { |
| if (obj->mRootNode->mNumChildren == 1) { |
| |
| //If the pivot is not set for this layer, get it from the external object |
| if (!src.isPivotSet) { |
| src.pivotPos.x = +obj->mRootNode->mTransformation.a4; |
| src.pivotPos.y = +obj->mRootNode->mTransformation.b4; |
| src.pivotPos.z = -obj->mRootNode->mTransformation.c4; //The sign is the RH to LH back conversion |
| } |
| |
| //Remove first node from obj (the old pivot), reset transform of second node (the mesh node) |
| aiNode* newRootNode = obj->mRootNode->mChildren[0]; |
| obj->mRootNode->mChildren[0] = NULL; |
| delete obj->mRootNode; |
| |
| obj->mRootNode = newRootNode; |
| obj->mRootNode->mTransformation.a4 = 0.0; |
| obj->mRootNode->mTransformation.b4 = 0.0; |
| obj->mRootNode->mTransformation.c4 = 0.0; |
| } |
| } |
| } |
| |
| //Setup the pivot node (also the animation node), the one we received |
| nd->mName = std::string("Pivot:") + nd->mName.data; |
| ndAnim = nd; |
| |
| //Add the attachment node to it |
| nd->mNumChildren = 1; |
| nd->mChildren = new aiNode*[1]; |
| nd->mChildren[0] = new aiNode(); |
| nd->mChildren[0]->mParent = nd; |
| nd->mChildren[0]->mTransformation.a4 = -src.pivotPos.x; |
| nd->mChildren[0]->mTransformation.b4 = -src.pivotPos.y; |
| nd->mChildren[0]->mTransformation.c4 = -src.pivotPos.z; |
| SetupNodeName(nd->mChildren[0], src); |
| |
| //Update the attachment node |
| nd = nd->mChildren[0]; |
| |
| //Push attachment, if the object came from an external file |
| if (obj) { |
| attach.push_back(AttachmentInfo(obj,nd)); |
| } |
| } |
| |
| // If object is a light source - setup a corresponding ai structure |
| else if (src.type == LWS::NodeDesc::LIGHT) { |
| aiLight* lit = *lightOut++ = new aiLight(); |
| |
| // compute final light color |
| lit->mColorDiffuse = lit->mColorSpecular = src.lightColor*src.lightIntensity; |
| |
| // name to attach light to node -> unique due to LWs indexing system |
| lit->mName = nd->mName; |
| |
| // detemine light type and setup additional members |
| if (src.lightType == 2) { /* spot light */ |
| |
| lit->mType = aiLightSource_SPOT; |
| lit->mAngleInnerCone = (float)AI_DEG_TO_RAD( src.lightConeAngle ); |
| lit->mAngleOuterCone = lit->mAngleInnerCone+(float)AI_DEG_TO_RAD( src.lightEdgeAngle ); |
| |
| } |
| else if (src.lightType == 1) { /* directional light source */ |
| lit->mType = aiLightSource_DIRECTIONAL; |
| } |
| else lit->mType = aiLightSource_POINT; |
| |
| // fixme: no proper handling of light falloffs yet |
| if (src.lightFalloffType == 1) |
| lit->mAttenuationConstant = 1.f; |
| else if (src.lightFalloffType == 1) |
| lit->mAttenuationLinear = 1.f; |
| else |
| lit->mAttenuationQuadratic = 1.f; |
| } |
| |
| // If object is a camera - setup a corresponding ai structure |
| else if (src.type == LWS::NodeDesc::CAMERA) { |
| aiCamera* cam = *camOut++ = new aiCamera(); |
| |
| // name to attach cam to node -> unique due to LWs indexing system |
| cam->mName = nd->mName; |
| } |
| |
| // Get the node transformation from the LWO key |
| LWO::AnimResolver resolver(src.channels,fps); |
| resolver.ExtractBindPose(ndAnim->mTransformation); |
| |
| // .. and construct animation channels |
| aiNodeAnim* anim = NULL; |
| |
| if (first != last) { |
| resolver.SetAnimationRange(first,last); |
| resolver.ExtractAnimChannel(&anim,AI_LWO_ANIM_FLAG_SAMPLE_ANIMS|AI_LWO_ANIM_FLAG_START_AT_ZERO); |
| if (anim) { |
| anim->mNodeName = ndAnim->mName; |
| animOut.push_back(anim); |
| } |
| } |
| |
| // Add children |
| if (!src.children.empty()) { |
| nd->mChildren = new aiNode*[src.children.size()]; |
| for (std::list<LWS::NodeDesc*>::iterator it = src.children.begin(); it != src.children.end(); ++it) { |
| aiNode* ndd = nd->mChildren[nd->mNumChildren++] = new aiNode(); |
| ndd->mParent = nd; |
| |
| BuildGraph(ndd,**it,attach,batch,camOut,lightOut,animOut); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Determine the exact location of a LWO file |
| std::string LWSImporter::FindLWOFile(const std::string& in) |
| { |
| // insert missing directory separator if necessary |
| std::string tmp; |
| if (in.length() > 3 && in[1] == ':'&& in[2] != '\\' && in[2] != '/') |
| { |
| tmp = in[0] + (std::string(":\\") + in.substr(2)); |
| } |
| else tmp = in; |
| |
| if (io->Exists(tmp)) { |
| return in; |
| } |
| |
| // file is not accessible for us ... maybe it's packed by |
| // LightWave's 'Package Scene' command? |
| |
| // Relevant for us are the following two directories: |
| // <folder>\Objects\<hh>\<*>.lwo |
| // <folder>\Scenes\<hh>\<*>.lws |
| // where <hh> is optional. |
| |
| std::string test = std::string("..") + (io->getOsSeparator() + tmp); |
| if (io->Exists(test)) { |
| return test; |
| } |
| |
| test = std::string("..") + (io->getOsSeparator() + test); |
| if (io->Exists(test)) { |
| return test; |
| } |
| |
| |
| // return original path, maybe the IOsystem knows better |
| return tmp; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Read file into given scene data structure |
| void LWSImporter::InternReadFile( const std::string& pFile, aiScene* pScene, |
| IOSystem* pIOHandler) |
| { |
| io = pIOHandler; |
| std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb")); |
| |
| // Check whether we can read from the file |
| if( file.get() == NULL) { |
| throw DeadlyImportError( "Failed to open LWS file " + pFile + "."); |
| } |
| |
| // Allocate storage and copy the contents of the file to a memory buffer |
| std::vector< char > mBuffer; |
| TextFileToBuffer(file.get(),mBuffer); |
| |
| // Parse the file structure |
| LWS::Element root; const char* dummy = &mBuffer[0]; |
| root.Parse(dummy); |
| |
| // Construct a Batchimporter to read more files recursively |
| BatchLoader batch(pIOHandler); |
| // batch.SetBasePath(pFile); |
| |
| // Construct an array to receive the flat output graph |
| std::list<LWS::NodeDesc> nodes; |
| |
| unsigned int cur_light = 0, cur_camera = 0, cur_object = 0; |
| unsigned int num_light = 0, num_camera = 0, num_object = 0; |
| |
| // check magic identifier, 'LWSC' |
| bool motion_file = false; |
| std::list< LWS::Element >::const_iterator it = root.children.begin(); |
| |
| if ((*it).tokens[0] == "LWMO") |
| motion_file = true; |
| |
| if ((*it).tokens[0] != "LWSC" && !motion_file) |
| throw DeadlyImportError("LWS: Not a LightWave scene, magic tag LWSC not found"); |
| |
| // get file format version and print to log |
| ++it; |
| unsigned int version = strtoul10((*it).tokens[0].c_str()); |
| DefaultLogger::get()->info("LWS file format version is " + (*it).tokens[0]); |
| first = 0.; |
| last = 60.; |
| fps = 25.; /* seems to be a good default frame rate */ |
| |
| // Now read all elements in a very straghtforward manner |
| for (; it != root.children.end(); ++it) { |
| const char* c = (*it).tokens[1].c_str(); |
| |
| // 'FirstFrame': begin of animation slice |
| if ((*it).tokens[0] == "FirstFrame") { |
| if (150392. != first /* see SetupProperties() */) |
| first = strtoul10(c,&c)-1.; /* we're zero-based */ |
| } |
| |
| // 'LastFrame': end of animation slice |
| else if ((*it).tokens[0] == "LastFrame") { |
| if (150392. != last /* see SetupProperties() */) |
| last = strtoul10(c,&c)-1.; /* we're zero-based */ |
| } |
| |
| // 'FramesPerSecond': frames per second |
| else if ((*it).tokens[0] == "FramesPerSecond") { |
| fps = strtoul10(c,&c); |
| } |
| |
| // 'LoadObjectLayer': load a layer of a specific LWO file |
| else if ((*it).tokens[0] == "LoadObjectLayer") { |
| |
| // get layer index |
| const int layer = strtoul10(c,&c); |
| |
| // setup the layer to be loaded |
| BatchLoader::PropertyMap props; |
| SetGenericProperty(props.ints,AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY,layer); |
| |
| // add node to list |
| LWS::NodeDesc d; |
| d.type = LWS::NodeDesc::OBJECT; |
| if (version >= 4) { // handle LWSC 4 explicit ID |
| SkipSpaces(&c); |
| d.number = strtoul16(c,&c) & AI_LWS_MASK; |
| } |
| else d.number = cur_object++; |
| |
| // and add the file to the import list |
| SkipSpaces(&c); |
| std::string path = FindLWOFile( c ); |
| d.path = path; |
| d.id = batch.AddLoadRequest(path,0,&props); |
| |
| nodes.push_back(d); |
| num_object++; |
| } |
| // 'LoadObject': load a LWO file into the scenegraph |
| else if ((*it).tokens[0] == "LoadObject") { |
| |
| // add node to list |
| LWS::NodeDesc d; |
| d.type = LWS::NodeDesc::OBJECT; |
| |
| if (version >= 4) { // handle LWSC 4 explicit ID |
| d.number = strtoul16(c,&c) & AI_LWS_MASK; |
| SkipSpaces(&c); |
| } |
| else d.number = cur_object++; |
| std::string path = FindLWOFile( c ); |
| d.id = batch.AddLoadRequest(path,0,NULL); |
| |
| d.path = path; |
| nodes.push_back(d); |
| num_object++; |
| } |
| // 'AddNullObject': add a dummy node to the hierarchy |
| else if ((*it).tokens[0] == "AddNullObject") { |
| |
| // add node to list |
| LWS::NodeDesc d; |
| d.type = LWS::NodeDesc::OBJECT; |
| if (version >= 4) { // handle LWSC 4 explicit ID |
| d.number = strtoul16(c,&c) & AI_LWS_MASK; |
| SkipSpaces(&c); |
| } |
| else d.number = cur_object++; |
| d.name = c; |
| nodes.push_back(d); |
| |
| num_object++; |
| } |
| // 'NumChannels': Number of envelope channels assigned to last layer |
| else if ((*it).tokens[0] == "NumChannels") { |
| // ignore for now |
| } |
| // 'Channel': preceedes any envelope description |
| else if ((*it).tokens[0] == "Channel") { |
| if (nodes.empty()) { |
| if (motion_file) { |
| |
| // LightWave motion file. Add dummy node |
| LWS::NodeDesc d; |
| d.type = LWS::NodeDesc::OBJECT; |
| d.name = c; |
| d.number = cur_object++; |
| nodes.push_back(d); |
| } |
| else DefaultLogger::get()->error("LWS: Unexpected keyword: \'Channel\'"); |
| } |
| |
| // important: index of channel |
| nodes.back().channels.push_back(LWO::Envelope()); |
| LWO::Envelope& env = nodes.back().channels.back(); |
| |
| env.index = strtoul10(c); |
| |
| // currently we can just interpret the standard channels 0...9 |
| // (hack) assume that index-i yields the binary channel type from LWO |
| env.type = (LWO::EnvelopeType)(env.index+1); |
| |
| } |
| // 'Envelope': a single animation channel |
| else if ((*it).tokens[0] == "Envelope") { |
| if (nodes.empty() || nodes.back().channels.empty()) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'Envelope\'"); |
| else { |
| ReadEnvelope((*it),nodes.back().channels.back()); |
| } |
| } |
| // 'ObjectMotion': animation information for older lightwave formats |
| else if (version < 3 && ((*it).tokens[0] == "ObjectMotion" || |
| (*it).tokens[0] == "CameraMotion" || |
| (*it).tokens[0] == "LightMotion")) { |
| |
| if (nodes.empty()) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'<Light|Object|Camera>Motion\'"); |
| else { |
| ReadEnvelope_Old(it,root.children.end(),nodes.back(),version); |
| } |
| } |
| // 'Pre/PostBehavior': pre/post animation behaviour for LWSC 2 |
| else if (version == 2 && (*it).tokens[0] == "Pre/PostBehavior") { |
| if (nodes.empty()) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'Pre/PostBehavior'"); |
| else { |
| for (std::list<LWO::Envelope>::iterator it = nodes.back().channels.begin(); it != nodes.back().channels.end(); ++it) { |
| // two ints per envelope |
| LWO::Envelope& env = *it; |
| env.pre = (LWO::PrePostBehaviour) strtoul10(c,&c); SkipSpaces(&c); |
| env.post = (LWO::PrePostBehaviour) strtoul10(c,&c); SkipSpaces(&c); |
| } |
| } |
| } |
| // 'ParentItem': specifies the parent of the current element |
| else if ((*it).tokens[0] == "ParentItem") { |
| if (nodes.empty()) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'ParentItem\'"); |
| |
| else nodes.back().parent = strtoul16(c,&c); |
| } |
| // 'ParentObject': deprecated one for older formats |
| else if (version < 3 && (*it).tokens[0] == "ParentObject") { |
| if (nodes.empty()) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'ParentObject\'"); |
| |
| else { |
| nodes.back().parent = strtoul10(c,&c) | (1u << 28u); |
| } |
| } |
| // 'AddCamera': add a camera to the scenegraph |
| else if ((*it).tokens[0] == "AddCamera") { |
| |
| // add node to list |
| LWS::NodeDesc d; |
| d.type = LWS::NodeDesc::CAMERA; |
| |
| if (version >= 4) { // handle LWSC 4 explicit ID |
| d.number = strtoul16(c,&c) & AI_LWS_MASK; |
| } |
| else d.number = cur_camera++; |
| nodes.push_back(d); |
| |
| num_camera++; |
| } |
| // 'CameraName': set name of currently active camera |
| else if ((*it).tokens[0] == "CameraName") { |
| if (nodes.empty() || nodes.back().type != LWS::NodeDesc::CAMERA) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'CameraName\'"); |
| |
| else nodes.back().name = c; |
| } |
| // 'AddLight': add a light to the scenegraph |
| else if ((*it).tokens[0] == "AddLight") { |
| |
| // add node to list |
| LWS::NodeDesc d; |
| d.type = LWS::NodeDesc::LIGHT; |
| |
| if (version >= 4) { // handle LWSC 4 explicit ID |
| d.number = strtoul16(c,&c) & AI_LWS_MASK; |
| } |
| else d.number = cur_light++; |
| nodes.push_back(d); |
| |
| num_light++; |
| } |
| // 'LightName': set name of currently active light |
| else if ((*it).tokens[0] == "LightName") { |
| if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightName\'"); |
| |
| else nodes.back().name = c; |
| } |
| // 'LightIntensity': set intensity of currently active light |
| else if ((*it).tokens[0] == "LightIntensity" || (*it).tokens[0] == "LgtIntensity" ) { |
| if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightIntensity\'"); |
| |
| else fast_atoreal_move<float>(c, nodes.back().lightIntensity ); |
| |
| } |
| // 'LightType': set type of currently active light |
| else if ((*it).tokens[0] == "LightType") { |
| if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightType\'"); |
| |
| else nodes.back().lightType = strtoul10(c); |
| |
| } |
| // 'LightFalloffType': set falloff type of currently active light |
| else if ((*it).tokens[0] == "LightFalloffType") { |
| if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightFalloffType\'"); |
| |
| else nodes.back().lightFalloffType = strtoul10(c); |
| |
| } |
| // 'LightConeAngle': set cone angle of currently active light |
| else if ((*it).tokens[0] == "LightConeAngle") { |
| if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightConeAngle\'"); |
| |
| else nodes.back().lightConeAngle = fast_atof(c); |
| |
| } |
| // 'LightEdgeAngle': set area where we're smoothing from min to max intensity |
| else if ((*it).tokens[0] == "LightEdgeAngle") { |
| if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightEdgeAngle\'"); |
| |
| else nodes.back().lightEdgeAngle = fast_atof(c); |
| |
| } |
| // 'LightColor': set color of currently active light |
| else if ((*it).tokens[0] == "LightColor") { |
| if (nodes.empty() || nodes.back().type != LWS::NodeDesc::LIGHT) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'LightColor\'"); |
| |
| else { |
| c = fast_atoreal_move<float>(c, (float&) nodes.back().lightColor.r ); |
| SkipSpaces(&c); |
| c = fast_atoreal_move<float>(c, (float&) nodes.back().lightColor.g ); |
| SkipSpaces(&c); |
| c = fast_atoreal_move<float>(c, (float&) nodes.back().lightColor.b ); |
| } |
| } |
| |
| // 'PivotPosition': position of local transformation origin |
| else if ((*it).tokens[0] == "PivotPosition" || (*it).tokens[0] == "PivotPoint") { |
| if (nodes.empty()) |
| DefaultLogger::get()->error("LWS: Unexpected keyword: \'PivotPosition\'"); |
| else { |
| c = fast_atoreal_move<float>(c, (float&) nodes.back().pivotPos.x ); |
| SkipSpaces(&c); |
| c = fast_atoreal_move<float>(c, (float&) nodes.back().pivotPos.y ); |
| SkipSpaces(&c); |
| c = fast_atoreal_move<float>(c, (float&) nodes.back().pivotPos.z ); |
| // Mark pivotPos as set |
| nodes.back().isPivotSet = true; |
| } |
| } |
| } |
| |
| // resolve parenting |
| for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) { |
| |
| // check whether there is another node which calls us a parent |
| for (std::list<LWS::NodeDesc>::iterator dit = nodes.begin(); dit != nodes.end(); ++dit) { |
| if (dit != it && *it == (*dit).parent) { |
| if ((*dit).parent_resolved) { |
| // fixme: it's still possible to produce an overflow due to cross references .. |
| DefaultLogger::get()->error("LWS: Found cross reference in scenegraph"); |
| continue; |
| } |
| |
| (*it).children.push_back(&*dit); |
| (*dit).parent_resolved = &*it; |
| } |
| } |
| } |
| |
| // find out how many nodes have no parent yet |
| unsigned int no_parent = 0; |
| for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) { |
| if (!(*it).parent_resolved) |
| ++ no_parent; |
| } |
| if (!no_parent) |
| throw DeadlyImportError("LWS: Unable to find scene root node"); |
| |
| |
| // Load all subsequent files |
| batch.LoadAll(); |
| |
| // and build the final output graph by attaching the loaded external |
| // files to ourselves. first build a master graph |
| aiScene* master = new aiScene(); |
| aiNode* nd = master->mRootNode = new aiNode(); |
| |
| // allocate storage for cameras&lights |
| if (num_camera) { |
| master->mCameras = new aiCamera*[master->mNumCameras = num_camera]; |
| } |
| aiCamera** cams = master->mCameras; |
| if (num_light) { |
| master->mLights = new aiLight*[master->mNumLights = num_light]; |
| } |
| aiLight** lights = master->mLights; |
| |
| std::vector<AttachmentInfo> attach; |
| std::vector<aiNodeAnim*> anims; |
| |
| nd->mName.Set("<LWSRoot>"); |
| nd->mChildren = new aiNode*[no_parent]; |
| for (std::list<LWS::NodeDesc>::iterator it = nodes.begin(); it != nodes.end(); ++it) { |
| if (!(*it).parent_resolved) { |
| aiNode* ro = nd->mChildren[ nd->mNumChildren++ ] = new aiNode(); |
| ro->mParent = nd; |
| |
| // ... and build the scene graph. If we encounter object nodes, |
| // add then to our attachment table. |
| BuildGraph(ro,*it, attach, batch, cams, lights, anims); |
| } |
| } |
| |
| // create a master animation channel for us |
| if (anims.size()) { |
| master->mAnimations = new aiAnimation*[master->mNumAnimations = 1]; |
| aiAnimation* anim = master->mAnimations[0] = new aiAnimation(); |
| anim->mName.Set("LWSMasterAnim"); |
| |
| // LWS uses seconds as time units, but we convert to frames |
| anim->mTicksPerSecond = fps; |
| anim->mDuration = last-(first-1); /* fixme ... zero or one-based?*/ |
| |
| anim->mChannels = new aiNodeAnim*[anim->mNumChannels = static_cast<unsigned int>(anims.size())]; |
| std::copy(anims.begin(),anims.end(),anim->mChannels); |
| } |
| |
| // convert the master scene to RH |
| MakeLeftHandedProcess monster_cheat; |
| monster_cheat.Execute(master); |
| |
| // .. ccw |
| FlipWindingOrderProcess flipper; |
| flipper.Execute(master); |
| |
| // OK ... finally build the output graph |
| SceneCombiner::MergeScenes(&pScene,master,attach, |
| AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES | (!configSpeedFlag ? ( |
| AI_INT_MERGE_SCENE_GEN_UNIQUE_NAMES_IF_NECESSARY | AI_INT_MERGE_SCENE_GEN_UNIQUE_MATNAMES) : 0)); |
| |
| // Check flags |
| if (!pScene->mNumMeshes || !pScene->mNumMaterials) { |
| pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; |
| |
| if (pScene->mNumAnimations && !noSkeletonMesh) { |
| // construct skeleton mesh |
| SkeletonMeshBuilder builder(pScene); |
| } |
| } |
| |
| } |
| |
| #endif // !! ASSIMP_BUILD_NO_LWS_IMPORTER |