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