diff options
Diffstat (limited to 'libs/assimp/code/AssetLib/LWS')
-rw-r--r-- | libs/assimp/code/AssetLib/LWS/LWSLoader.cpp | 929 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/LWS/LWSLoader.h | 237 |
2 files changed, 1166 insertions, 0 deletions
diff --git a/libs/assimp/code/AssetLib/LWS/LWSLoader.cpp b/libs/assimp/code/AssetLib/LWS/LWSLoader.cpp new file mode 100644 index 0000000..951dbe1 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWS/LWSLoader.cpp @@ -0,0 +1,929 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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 "AssetLib/LWS/LWSLoader.h" +#include "Common/Importer.h" +#include "PostProcessing/ConvertToLHProcess.h" + +#include <assimp/GenericProperty.h> +#include <assimp/ParsingUtils.h> +#include <assimp/SceneCombiner.h> +#include <assimp/SkeletonMeshBuilder.h> +#include <assimp/fast_atof.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +#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") { + ASSIMP_LOG_VERBOSE_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 { + static const uint32_t tokens[] = { + AI_MAKE_MAGIC("LWSC"), + AI_MAKE_MAGIC("LWMO") + }; + return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens)); +} + +// ------------------------------------------------------------------------------------------------ +// 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()) { + ASSIMP_LOG_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: + ASSIMP_LOG_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: + ASSIMP_LOG_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 scene-graph +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 = nullptr; + if (src.path.length()) { + obj = batch.GetImport(src.id); + if (!obj) { + ASSIMP_LOG_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] = nullptr; + 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; + + // determine 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 == 2) { + lit->mAttenuationLinear = 1.f; + } else { + lit->mAttenuationQuadratic = 1.f; + } + } else if (src.type == LWS::NodeDesc::CAMERA) { // If object is a camera - setup a corresponding ai structure + 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 = nullptr; + + 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(in); + if (in.length() > 3 && in[1] == ':' && in[2] != '\\' && in[2] != '/') { + tmp = in[0] + (std::string(":\\") + in.substr(2)); + } + + 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() == nullptr) { + 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 Batch-importer to read more files recursively + BatchLoader batch(pIOHandler); + + // 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; + + if (it == root.children.end() || (*it).tokens[0].empty()) { + ASSIMP_LOG_ERROR("Invalid LWS file detectedm abort import."); + return; + } + unsigned int version = strtoul10((*it).tokens[0].c_str()); + ASSIMP_LOG_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 straightforward 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") { + // see SetupProperties() + if (150392. != first ) { + first = strtoul10(c, &c) - 1.; // we're zero-based + } + } else if ((*it).tokens[0] == "LastFrame") { // 'LastFrame': end of animation slice + // see SetupProperties() + if (150392. != last ) { + last = strtoul10(c, &c) - 1.; // we're zero-based + } + } else if ((*it).tokens[0] == "FramesPerSecond") { // 'FramesPerSecond': frames per second + fps = strtoul10(c, &c); + } else if ((*it).tokens[0] == "LoadObjectLayer") { // 'LoadObjectLayer': load a layer of a specific LWO file + + // 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; + } else if ((*it).tokens[0] == "LoadObject") { // 'LoadObject': load a LWO file into the scene-graph + + // 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, nullptr); + + d.path = path; + nodes.push_back(d); + ++num_object; + } else if ((*it).tokens[0] == "AddNullObject") { // 'AddNullObject': add a dummy node to the hierarchy + + // 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); + } + ASSIMP_LOG_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()) + ASSIMP_LOG_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()) + ASSIMP_LOG_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()) + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'Pre/PostBehavior'"); + else { + for (std::list<LWO::Envelope>::iterator envelopeIt = nodes.back().channels.begin(); envelopeIt != nodes.back().channels.end(); ++envelopeIt) { + // two ints per envelope + LWO::Envelope &env = *envelopeIt; + 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()) + ASSIMP_LOG_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()) + ASSIMP_LOG_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) + ASSIMP_LOG_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) + ASSIMP_LOG_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) { + ASSIMP_LOG_ERROR("LWS: Unexpected keyword: \'LightIntensity\'"); + } else { + const std::string env = "(envelope)"; + if (0 == strncmp(c, env.c_str(), env.size())) { + ASSIMP_LOG_ERROR("LWS: envelopes for LightIntensity not supported, set to 1.0"); + nodes.back().lightIntensity = (ai_real)1.0; + } 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) + ASSIMP_LOG_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) + ASSIMP_LOG_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) + ASSIMP_LOG_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) + ASSIMP_LOG_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) + ASSIMP_LOG_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()) + ASSIMP_LOG_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 ndIt = nodes.begin(); ndIt != nodes.end(); ++ndIt) { + + // 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 != ndIt && *ndIt == (*dit).parent) { + if ((*dit).parent_resolved) { + // fixme: it's still possible to produce an overflow due to cross references .. + ASSIMP_LOG_ERROR("LWS: Found cross reference in scene-graph"); + continue; + } + + ndIt->children.push_back(&*dit); + (*dit).parent_resolved = &*ndIt; + } + } + } + + // find out how many nodes have no parent yet + unsigned int no_parent = 0; + for (std::list<LWS::NodeDesc>::iterator ndIt = nodes.begin(); ndIt != nodes.end(); ++ndIt) { + if (!ndIt->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 ndIt = nodes.begin(); ndIt != nodes.end(); ++ndIt) { + if (!ndIt->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, *ndIt, 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 diff --git a/libs/assimp/code/AssetLib/LWS/LWSLoader.h b/libs/assimp/code/AssetLib/LWS/LWSLoader.h new file mode 100644 index 0000000..225186f --- /dev/null +++ b/libs/assimp/code/AssetLib/LWS/LWSLoader.h @@ -0,0 +1,237 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, 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.h + * @brief Declaration of the LightWave scene importer class. + */ +#pragma once +#ifndef AI_LWSLOADER_H_INCLUDED +#define AI_LWSLOADER_H_INCLUDED + +#include "AssetLib/LWO/LWOFileData.h" + +#include <assimp/BaseImporter.h> +#include <assimp/SceneCombiner.h> + +struct aiImporterDesc; + +namespace Assimp { + +class BatchLoader; +class Importer; +class IOSystem; + +namespace LWS { + +// --------------------------------------------------------------------------- +/** Represents an element in a LWS file. + * + * This can either be a single data line - <name> <value> or a data + * group - { name <data_line0> ... n } + */ +class Element { +public: + Element() {} + + // first: name, second: rest + std::string tokens[2]; + std::list<Element> children; + + //! Recursive parsing function + void Parse(const char *&buffer); +}; + +#define AI_LWS_MASK (0xffffffff >> 4u) + +// --------------------------------------------------------------------------- +/** Represents a LWS scenegraph element + */ +struct NodeDesc { + NodeDesc() : + type(), + id(), + number(0), + parent(0), + name(), + isPivotSet(false), + lightColor(1.f, 1.f, 1.f), + lightIntensity(1.f), + lightType(0), + lightFalloffType(0), + lightConeAngle(45.f), + lightEdgeAngle(), + parent_resolved(nullptr) {} + + enum { + + OBJECT = 1, + LIGHT = 2, + CAMERA = 3, + BONE = 4 + } type; // type of node + + // if object: path + std::string path; + unsigned int id; + + // number of object + unsigned int number; + + // index of parent index + unsigned int parent; + + // lights & cameras & dummies: name + const char *name; + + // animation channels + std::list<LWO::Envelope> channels; + + // position of pivot point + aiVector3D pivotPos; + bool isPivotSet; + + // color of light source + aiColor3D lightColor; + + // intensity of light source + float lightIntensity; + + // type of light source + unsigned int lightType; + + // falloff type of light source + unsigned int lightFalloffType; + + // cone angle of (spot) light source + float lightConeAngle; + + // soft cone angle of (spot) light source + float lightEdgeAngle; + + // list of resolved children + std::list<NodeDesc *> children; + + // resolved parent node + NodeDesc *parent_resolved; + + // for std::find() + bool operator==(unsigned int num) const { + if (!num) + return false; + unsigned int _type = num >> 28u; + + return _type == static_cast<unsigned int>(type) && (num & AI_LWS_MASK) == number; + } +}; + +} // end namespace LWS + +// --------------------------------------------------------------------------- +/** LWS (LightWave Scene Format) importer class. + * + * This class does heavily depend on the LWO importer class. LWS files + * contain mainly descriptions how LWO objects are composed together + * in a scene. +*/ +class LWSImporter : public BaseImporter { +public: + LWSImporter(); + ~LWSImporter() override; + + // ------------------------------------------------------------------- + // Check whether we can read a specific file + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + +protected: + // ------------------------------------------------------------------- + // Get list of supported extensions + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + // Import file into given scene data structure + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + + // ------------------------------------------------------------------- + // Setup import properties + void SetupProperties(const Importer *pImp) override; + +private: + // ------------------------------------------------------------------- + // Read an envelope description + void ReadEnvelope(const LWS::Element &dad, LWO::Envelope &out); + + // ------------------------------------------------------------------- + // Read an envelope description for the older LW file format + void ReadEnvelope_Old(std::list<LWS::Element>::const_iterator &it, + const std::list<LWS::Element>::const_iterator &end, + LWS::NodeDesc &nodes, + unsigned int version); + + // ------------------------------------------------------------------- + // Setup a nice name for a node + void SetupNodeName(aiNode *nd, LWS::NodeDesc &src); + + // ------------------------------------------------------------------- + // Recursively build the scenegraph + void BuildGraph(aiNode *nd, + LWS::NodeDesc &src, + std::vector<AttachmentInfo> &attach, + BatchLoader &batch, + aiCamera **&camOut, + aiLight **&lightOut, + std::vector<aiNodeAnim *> &animOut); + + // ------------------------------------------------------------------- + // Try several dirs until we find the right location of a LWS file. + std::string FindLWOFile(const std::string &in); + +private: + bool configSpeedFlag; + IOSystem *io; + double first, last, fps; + bool noSkeletonMesh; +}; + +} // end of namespace Assimp + +#endif // AI_LWSIMPORTER_H_INC |