diff options
Diffstat (limited to 'libs/assimp/code/AssetLib/LWO')
-rw-r--r-- | libs/assimp/code/AssetLib/LWO/LWOAnimation.cpp | 609 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/LWO/LWOAnimation.h | 346 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/LWO/LWOBLoader.cpp | 428 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/LWO/LWOFileData.h | 638 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/LWO/LWOLoader.cpp | 1422 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/LWO/LWOLoader.h | 468 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/LWO/LWOMaterial.cpp | 844 |
7 files changed, 4755 insertions, 0 deletions
diff --git a/libs/assimp/code/AssetLib/LWO/LWOAnimation.cpp b/libs/assimp/code/AssetLib/LWO/LWOAnimation.cpp new file mode 100644 index 0000000..c2ee2d9 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOAnimation.cpp @@ -0,0 +1,609 @@ +/* +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 LWOAnimation.cpp + * @brief LWOAnimationResolver utility class + * + * It's a very generic implementation of LightWave's system of + * component-wise-animated stuff. The one and only fully free + * implementation of LightWave envelopes of which I know. +*/ + +#if (!defined ASSIMP_BUILD_NO_LWO_IMPORTER) && (!defined ASSIMP_BUILD_NO_LWS_IMPORTER) + +#include <functional> + +// internal headers +#include "LWOFileData.h" +#include <assimp/anim.h> + +using namespace Assimp; +using namespace Assimp::LWO; + +// ------------------------------------------------------------------------------------------------ +// Construct an animation resolver from a given list of envelopes +AnimResolver::AnimResolver(std::list<Envelope> &_envelopes, double tick) : + envelopes(_envelopes), + sample_rate(0.), + envl_x(), + envl_y(), + envl_z(), + end_x(), + end_y(), + end_z(), + flags(), + sample_delta() { + trans_x = trans_y = trans_z = nullptr; + rotat_x = rotat_y = rotat_z = nullptr; + scale_x = scale_y = scale_z = nullptr; + + first = last = 150392.; + + // find transformation envelopes + for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) { + + (*it).old_first = 0; + (*it).old_last = (*it).keys.size() - 1; + + if ((*it).keys.empty()) { + continue; + } + if ((int)(*it).type < 1 || (int)(*it).type>EnvelopeType_Unknown) { + continue; + } + switch ((*it).type) { + // translation + case LWO::EnvelopeType_Position_X: + trans_x = &*it; + break; + case LWO::EnvelopeType_Position_Y: + trans_y = &*it; + break; + case LWO::EnvelopeType_Position_Z: + trans_z = &*it; + break; + + // rotation + case LWO::EnvelopeType_Rotation_Heading: + rotat_x = &*it; + break; + case LWO::EnvelopeType_Rotation_Pitch: + rotat_y = &*it; + break; + case LWO::EnvelopeType_Rotation_Bank: + rotat_z = &*it; + break; + + // scaling + case LWO::EnvelopeType_Scaling_X: + scale_x = &*it; + break; + case LWO::EnvelopeType_Scaling_Y: + scale_y = &*it; + break; + case LWO::EnvelopeType_Scaling_Z: + scale_z = &*it; + break; + default: + continue; + }; + + // convert from seconds to ticks + for (std::vector<LWO::Key>::iterator d = (*it).keys.begin(); d != (*it).keys.end(); ++d) + (*d).time *= tick; + + // set default animation range (minimum and maximum time value for which we have a keyframe) + first = std::min(first, (*it).keys.front().time); + last = std::max(last, (*it).keys.back().time); + } + + // deferred setup of animation range to increase performance. + // typically the application will want to specify its own. + need_to_setup = true; +} + +// ------------------------------------------------------------------------------------------------ +// Reset all envelopes to their original contents +void AnimResolver::ClearAnimRangeSetup() { + for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) { + + (*it).keys.erase((*it).keys.begin(), (*it).keys.begin() + (*it).old_first); + (*it).keys.erase((*it).keys.begin() + (*it).old_last + 1, (*it).keys.end()); + } +} + +// ------------------------------------------------------------------------------------------------ +// Insert additional keys to match LWO's pre& post behaviors. +void AnimResolver::UpdateAnimRangeSetup() { + // XXX doesn't work yet (hangs if more than one envelope channels needs to be interpolated) + + for (std::list<LWO::Envelope>::iterator it = envelopes.begin(); it != envelopes.end(); ++it) { + if ((*it).keys.empty()) continue; + + const double my_first = (*it).keys.front().time; + const double my_last = (*it).keys.back().time; + + const double delta = my_last - my_first; + const size_t old_size = (*it).keys.size(); + + const float value_delta = (*it).keys.back().value - (*it).keys.front().value; + + // NOTE: We won't handle reset, linear and constant here. + // See DoInterpolation() for their implementation. + + // process pre behavior + switch ((*it).pre) { + case LWO::PrePostBehaviour_OffsetRepeat: + case LWO::PrePostBehaviour_Repeat: + case LWO::PrePostBehaviour_Oscillate: { + const double start_time = delta - std::fmod(my_first - first, delta); + std::vector<LWO::Key>::iterator n = std::find_if((*it).keys.begin(), (*it).keys.end(), + [start_time](double t) { return start_time > t; }), + m; + + size_t ofs = 0; + if (n != (*it).keys.end()) { + // copy from here - don't use iterators, insert() would invalidate them + ofs = (*it).keys.end() - n; + (*it).keys.insert((*it).keys.begin(), ofs, LWO::Key()); + + std::copy((*it).keys.end() - ofs, (*it).keys.end(), (*it).keys.begin()); + } + + // do full copies. again, no iterators + const unsigned int num = (unsigned int)((my_first - first) / delta); + (*it).keys.resize((*it).keys.size() + num * old_size); + + n = (*it).keys.begin() + ofs; + bool reverse = false; + for (unsigned int i = 0; i < num; ++i) { + m = n + old_size * (i + 1); + std::copy(n, n + old_size, m); + const bool res = ((*it).pre == LWO::PrePostBehaviour_Oscillate); + reverse = !reverse; + if (res && reverse) { + std::reverse(m, m + old_size - 1); + } + } + + // update time values + n = (*it).keys.end() - (old_size + 1); + double cur_minus = delta; + unsigned int tt = 1; + for (const double tmp = delta * (num + 1); cur_minus <= tmp; cur_minus += delta, ++tt) { + m = (delta == tmp ? (*it).keys.begin() : n - (old_size + 1)); + for (; m != n; --n) { + (*n).time -= cur_minus; + + // offset repeat? add delta offset to key value + if ((*it).pre == LWO::PrePostBehaviour_OffsetRepeat) { + (*n).value += tt * value_delta; + } + } + } + break; + } + default: + // silence compiler warning + break; + } + + // process post behavior + switch ((*it).post) { + + case LWO::PrePostBehaviour_OffsetRepeat: + case LWO::PrePostBehaviour_Repeat: + case LWO::PrePostBehaviour_Oscillate: + + break; + + default: + // silence compiler warning + break; + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Extract bind pose matrix +void AnimResolver::ExtractBindPose(aiMatrix4x4 &out) { + // If we have no envelopes, return identity + if (envelopes.empty()) { + out = aiMatrix4x4(); + return; + } + aiVector3D angles, scaling(1.f, 1.f, 1.f), translation; + + if (trans_x) translation.x = trans_x->keys[0].value; + if (trans_y) translation.y = trans_y->keys[0].value; + if (trans_z) translation.z = trans_z->keys[0].value; + + if (rotat_x) angles.x = rotat_x->keys[0].value; + if (rotat_y) angles.y = rotat_y->keys[0].value; + if (rotat_z) angles.z = rotat_z->keys[0].value; + + if (scale_x) scaling.x = scale_x->keys[0].value; + if (scale_y) scaling.y = scale_y->keys[0].value; + if (scale_z) scaling.z = scale_z->keys[0].value; + + // build the final matrix + aiMatrix4x4 s, rx, ry, rz, t; + aiMatrix4x4::RotationZ(angles.z, rz); + aiMatrix4x4::RotationX(angles.y, rx); + aiMatrix4x4::RotationY(angles.x, ry); + aiMatrix4x4::Translation(translation, t); + aiMatrix4x4::Scaling(scaling, s); + out = t * ry * rx * rz * s; +} + +// ------------------------------------------------------------------------------------------------ +// Do a single interpolation on a channel +void AnimResolver::DoInterpolation(std::vector<LWO::Key>::const_iterator cur, + LWO::Envelope *envl, double time, float &fill) { + if (envl->keys.size() == 1) { + fill = envl->keys[0].value; + return; + } + + // check whether we're at the beginning of the animation track + if (cur == envl->keys.begin()) { + + // ok ... this depends on pre behaviour now + // we don't need to handle repeat&offset repeat&oszillate here, see UpdateAnimRangeSetup() + switch (envl->pre) { + case LWO::PrePostBehaviour_Linear: + DoInterpolation2(cur, cur + 1, time, fill); + return; + + case LWO::PrePostBehaviour_Reset: + fill = 0.f; + return; + + default: //case LWO::PrePostBehaviour_Constant: + fill = (*cur).value; + return; + } + } + // check whether we're at the end of the animation track + else if (cur == envl->keys.end() - 1 && time > envl->keys.rbegin()->time) { + // ok ... this depends on post behaviour now + switch (envl->post) { + case LWO::PrePostBehaviour_Linear: + DoInterpolation2(cur, cur - 1, time, fill); + return; + + case LWO::PrePostBehaviour_Reset: + fill = 0.f; + return; + + default: //case LWO::PrePostBehaviour_Constant: + fill = (*cur).value; + return; + } + } + + // Otherwise do a simple interpolation + DoInterpolation2(cur - 1, cur, time, fill); +} + +// ------------------------------------------------------------------------------------------------ +// Almost the same, except we won't handle pre/post conditions here +void AnimResolver::DoInterpolation2(std::vector<LWO::Key>::const_iterator beg, + std::vector<LWO::Key>::const_iterator end, double time, float &fill) { + switch ((*end).inter) { + + case LWO::IT_STEP: + // no interpolation at all - take the value of the last key + fill = (*beg).value; + return; + default: + + // silence compiler warning + break; + } + // linear interpolation - default + double duration = (*end).time - (*beg).time; + if (duration > 0.0) { + fill = (*beg).value + ((*end).value - (*beg).value) * (float)(((time - (*beg).time) / duration)); + } else { + fill = (*beg).value; + } +} + +// ------------------------------------------------------------------------------------------------ +// Subsample animation track by given key values +void AnimResolver::SubsampleAnimTrack(std::vector<aiVectorKey> & /*out*/, + double /*time*/, double /*sample_delta*/) { + //ai_assert(out.empty() && sample_delta); + + //const double time_start = out.back().mTime; + // for () +} + +// ------------------------------------------------------------------------------------------------ +// Track interpolation +void AnimResolver::InterpolateTrack(std::vector<aiVectorKey> &out, aiVectorKey &fill, double time) { + // subsample animation track? + if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) { + SubsampleAnimTrack(out, time, sample_delta); + } + + fill.mTime = time; + + // get x + if ((*cur_x).time == time) { + fill.mValue.x = (*cur_x).value; + + if (cur_x != envl_x->keys.end() - 1) /* increment x */ + ++cur_x; + else + end_x = true; + } else + DoInterpolation(cur_x, envl_x, time, (float &)fill.mValue.x); + + // get y + if ((*cur_y).time == time) { + fill.mValue.y = (*cur_y).value; + + if (cur_y != envl_y->keys.end() - 1) /* increment y */ + ++cur_y; + else + end_y = true; + } else + DoInterpolation(cur_y, envl_y, time, (float &)fill.mValue.y); + + // get z + if ((*cur_z).time == time) { + fill.mValue.z = (*cur_z).value; + + if (cur_z != envl_z->keys.end() - 1) /* increment z */ + ++cur_z; + else + end_x = true; + } else + DoInterpolation(cur_z, envl_z, time, (float &)fill.mValue.z); +} + +// ------------------------------------------------------------------------------------------------ +// Build linearly subsampled keys from three single envelopes, one for each component (x,y,z) +void AnimResolver::GetKeys(std::vector<aiVectorKey> &out, + LWO::Envelope *_envl_x, + LWO::Envelope *_envl_y, + LWO::Envelope *_envl_z, + unsigned int _flags) { + envl_x = _envl_x; + envl_y = _envl_y; + envl_z = _envl_z; + flags = _flags; + + // generate default channels if none are given + LWO::Envelope def_x, def_y, def_z; + LWO::Key key_dummy; + key_dummy.time = 0.f; + if ((envl_x && envl_x->type == LWO::EnvelopeType_Scaling_X) || + (envl_y && envl_y->type == LWO::EnvelopeType_Scaling_Y) || + (envl_z && envl_z->type == LWO::EnvelopeType_Scaling_Z)) { + key_dummy.value = 1.f; + } else + key_dummy.value = 0.f; + + if (!envl_x) { + envl_x = &def_x; + envl_x->keys.push_back(key_dummy); + } + if (!envl_y) { + envl_y = &def_y; + envl_y->keys.push_back(key_dummy); + } + if (!envl_z) { + envl_z = &def_z; + envl_z->keys.push_back(key_dummy); + } + + // guess how many keys we'll get + size_t reserve; + double sr = 1.; + if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) { + if (!sample_rate) + sr = 100.f; + else + sr = sample_rate; + sample_delta = 1.f / sr; + + reserve = (size_t)( + std::max(envl_x->keys.rbegin()->time, + std::max(envl_y->keys.rbegin()->time, envl_z->keys.rbegin()->time)) * + sr); + } else + reserve = std::max(envl_x->keys.size(), std::max(envl_x->keys.size(), envl_z->keys.size())); + out.reserve(reserve + (reserve >> 1)); + + // Iterate through all three arrays at once - it's tricky, but + // rather interesting to implement. + cur_x = envl_x->keys.begin(); + cur_y = envl_y->keys.begin(); + cur_z = envl_z->keys.begin(); + + end_x = end_y = end_z = false; + while (1) { + + aiVectorKey fill; + + if ((*cur_x).time == (*cur_y).time && (*cur_x).time == (*cur_z).time) { + + // we have a keyframe for all of them defined .. this means + // we don't need to interpolate here. + fill.mTime = (*cur_x).time; + + fill.mValue.x = (*cur_x).value; + fill.mValue.y = (*cur_y).value; + fill.mValue.z = (*cur_z).value; + + // subsample animation track + if (flags & AI_LWO_ANIM_FLAG_SAMPLE_ANIMS) { + //SubsampleAnimTrack(out,cur_x, cur_y, cur_z, d, sample_delta); + } + } + + // Find key with lowest time value + else if ((*cur_x).time <= (*cur_y).time && !end_x) { + + if ((*cur_z).time <= (*cur_x).time && !end_z) { + InterpolateTrack(out, fill, (*cur_z).time); + } else { + InterpolateTrack(out, fill, (*cur_x).time); + } + } else if ((*cur_z).time <= (*cur_y).time && !end_y) { + InterpolateTrack(out, fill, (*cur_y).time); + } else if (!end_y) { + // welcome on the server, y + InterpolateTrack(out, fill, (*cur_y).time); + } else { + // we have reached the end of at least 2 channels, + // only one is remaining. Extrapolate the 2. + if (end_y) { + InterpolateTrack(out, fill, (end_x ? (*cur_z) : (*cur_x)).time); + } else if (end_x) { + InterpolateTrack(out, fill, (end_z ? (*cur_y) : (*cur_z)).time); + } else { // if (end_z) + InterpolateTrack(out, fill, (end_y ? (*cur_x) : (*cur_y)).time); + } + } + double lasttime = fill.mTime; + out.push_back(fill); + + if (lasttime >= (*cur_x).time) { + if (cur_x != envl_x->keys.end() - 1) + ++cur_x; + else + end_x = true; + } + if (lasttime >= (*cur_y).time) { + if (cur_y != envl_y->keys.end() - 1) + ++cur_y; + else + end_y = true; + } + if (lasttime >= (*cur_z).time) { + if (cur_z != envl_z->keys.end() - 1) + ++cur_z; + else + end_z = true; + } + + if (end_x && end_y && end_z) /* finished? */ + break; + } + + if (flags & AI_LWO_ANIM_FLAG_START_AT_ZERO) { + for (std::vector<aiVectorKey>::iterator it = out.begin(); it != out.end(); ++it) + (*it).mTime -= first; + } +} + +// ------------------------------------------------------------------------------------------------ +// Extract animation channel +void AnimResolver::ExtractAnimChannel(aiNodeAnim **out, unsigned int /*= 0*/) { + *out = nullptr; + + //FIXME: crashes if more than one component is animated at different timings, to be resolved. + + // If we have no envelopes, return nullptr + if (envelopes.empty()) { + return; + } + + // We won't spawn an animation channel if we don't have at least one envelope with more than one keyframe defined. + const bool trans = ((trans_x && trans_x->keys.size() > 1) || (trans_y && trans_y->keys.size() > 1) || (trans_z && trans_z->keys.size() > 1)); + const bool rotat = ((rotat_x && rotat_x->keys.size() > 1) || (rotat_y && rotat_y->keys.size() > 1) || (rotat_z && rotat_z->keys.size() > 1)); + const bool scale = ((scale_x && scale_x->keys.size() > 1) || (scale_y && scale_y->keys.size() > 1) || (scale_z && scale_z->keys.size() > 1)); + if (!trans && !rotat && !scale) + return; + + // Allocate the output animation + aiNodeAnim *anim = *out = new aiNodeAnim(); + + // Setup default animation setup if necessary + if (need_to_setup) { + UpdateAnimRangeSetup(); + need_to_setup = false; + } + + // copy translation keys + if (trans) { + std::vector<aiVectorKey> keys; + GetKeys(keys, trans_x, trans_y, trans_z, flags); + + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys = static_cast<unsigned int>(keys.size())]; + std::copy(keys.begin(), keys.end(), anim->mPositionKeys); + } + + // copy rotation keys + if (rotat) { + std::vector<aiVectorKey> keys; + GetKeys(keys, rotat_x, rotat_y, rotat_z, flags); + + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys = static_cast<unsigned int>(keys.size())]; + + // convert heading, pitch, bank to quaternion + // mValue.x=Heading=Rot(Y), mValue.y=Pitch=Rot(X), mValue.z=Bank=Rot(Z) + // Lightwave's rotation order is ZXY + aiVector3D X(1.0, 0.0, 0.0); + aiVector3D Y(0.0, 1.0, 0.0); + aiVector3D Z(0.0, 0.0, 1.0); + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + aiQuatKey &qk = anim->mRotationKeys[i]; + qk.mTime = keys[i].mTime; + qk.mValue = aiQuaternion(Y, keys[i].mValue.x) * aiQuaternion(X, keys[i].mValue.y) * aiQuaternion(Z, keys[i].mValue.z); + } + } + + // copy scaling keys + if (scale) { + std::vector<aiVectorKey> keys; + GetKeys(keys, scale_x, scale_y, scale_z, flags); + + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys = static_cast<unsigned int>(keys.size())]; + std::copy(keys.begin(), keys.end(), anim->mScalingKeys); + } +} + +#endif // no lwo or no lws diff --git a/libs/assimp/code/AssetLib/LWO/LWOAnimation.h b/libs/assimp/code/AssetLib/LWO/LWOAnimation.h new file mode 100644 index 0000000..1e419d4 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOAnimation.h @@ -0,0 +1,346 @@ +/* +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 LWOAnimation.h + * @brief LWOAnimationResolver utility class + * + * This is for all lightwave-related file format, not only LWO. + * LWS isthe main purpose. +*/ +#ifndef AI_LWO_ANIMATION_INCLUDED +#define AI_LWO_ANIMATION_INCLUDED + +// +#include <vector> +#include <list> + +struct aiNodeAnim; +struct aiVectorKey; + +namespace Assimp { +namespace LWO { + +// --------------------------------------------------------------------------- +/** \brief List of recognized LWO envelopes + */ +enum EnvelopeType +{ + EnvelopeType_Position_X = 0x1, + EnvelopeType_Position_Y = 0x2, + EnvelopeType_Position_Z = 0x3, + + EnvelopeType_Rotation_Heading = 0x4, + EnvelopeType_Rotation_Pitch = 0x5, + EnvelopeType_Rotation_Bank = 0x6, + + EnvelopeType_Scaling_X = 0x7, + EnvelopeType_Scaling_Y = 0x8, + EnvelopeType_Scaling_Z = 0x9, + + // -- currently not yet handled + EnvelopeType_Color_R = 0xa, + EnvelopeType_Color_G = 0xb, + EnvelopeType_Color_B = 0xc, + + EnvelopeType_Falloff_X = 0xd, + EnvelopeType_Falloff_Y = 0xe, + EnvelopeType_Falloff_Z = 0xf, + + EnvelopeType_Unknown +}; + +// --------------------------------------------------------------------------- +/** \brief List of recognized LWO interpolation modes + */ +enum InterpolationType +{ + IT_STEP, IT_LINE, IT_TCB, IT_HERM, IT_BEZI, IT_BEZ2 +}; + + +// --------------------------------------------------------------------------- +/** \brief List of recognized LWO pre or post range behaviours + */ +enum PrePostBehaviour +{ + PrePostBehaviour_Reset = 0x0, + PrePostBehaviour_Constant = 0x1, + PrePostBehaviour_Repeat = 0x2, + PrePostBehaviour_Oscillate = 0x3, + PrePostBehaviour_OffsetRepeat = 0x4, + PrePostBehaviour_Linear = 0x5 +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO animation keyframe + */ +struct Key { + Key() AI_NO_EXCEPT + : time() + , value() + , inter(IT_LINE) + , params() { + // empty + } + + //! Current time + double time; + + //! Current value + float value; + + //! How to interpolate this key with previous key? + InterpolationType inter; + + //! Interpolation parameters + float params[5]; + + + // for std::find() + operator double () { + return time; + } +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO animation envelope + */ +struct Envelope { + Envelope() AI_NO_EXCEPT + : index() + , type(EnvelopeType_Unknown) + , pre(PrePostBehaviour_Constant) + , post(PrePostBehaviour_Constant) + , old_first(0) + , old_last(0) { + // empty + } + + //! Index of this envelope + unsigned int index; + + //! Type of envelope + EnvelopeType type; + + //! Pre- and post-behavior + PrePostBehaviour pre,post; + + //! Keyframes for this envelope + std::vector<Key> keys; + + // temporary data for AnimResolver + size_t old_first,old_last; +}; + +// --------------------------------------------------------------------------- +//! @def AI_LWO_ANIM_FLAG_SAMPLE_ANIMS +//! Flag for AnimResolver, subsamples the input data with the rate specified +//! by AnimResolver::SetSampleRate(). +#define AI_LWO_ANIM_FLAG_SAMPLE_ANIMS 0x1 + + +// --------------------------------------------------------------------------- +//! @def AI_LWO_ANIM_FLAG_START_AT_ZERO +//! Flag for AnimResolver, ensures that the animations starts at zero. +#define AI_LWO_ANIM_FLAG_START_AT_ZERO 0x2 + +// --------------------------------------------------------------------------- +/** @brief Utility class to build Assimp animations from LWO envelopes. + * + * Used for both LWO and LWS (MOT also). + */ +class AnimResolver +{ +public: + + // ------------------------------------------------------------------ + /** @brief Construct an AnimResolver from a given list of envelopes + * @param envelopes Input envelopes. May be empty. + * @param Output tick rate, per second + * @note The input envelopes are possibly modified. + */ + AnimResolver(std::list<Envelope>& envelopes, double tick); + +public: + + // ------------------------------------------------------------------ + /** @brief Extract the bind-pose transformation matrix. + * @param out Receives bind-pose transformation matrix + */ + void ExtractBindPose(aiMatrix4x4& out); + + // ------------------------------------------------------------------ + /** @brief Extract a node animation channel + * @param out Receives a pointer to a newly allocated node anim. + * If there's just one keyframe defined, *out is set to nullptr and + * no animation channel is computed. + * @param flags Any combination of the AI_LWO_ANIM_FLAG_XXX flags. + */ + void ExtractAnimChannel(aiNodeAnim** out, unsigned int flags = 0); + + + // ------------------------------------------------------------------ + /** @brief Set the sampling rate for ExtractAnimChannel(). + * + * Non-linear interpolations are subsampled with this rate (keys + * per second). Closer sampling positions, if existent, are kept. + * The sampling rate defaults to 0, if this value is not changed and + * AI_LWO_ANIM_FLAG_SAMPLE_ANIMS is specified for ExtractAnimChannel(), + * the class finds a suitable sample rate by itself. + */ + void SetSampleRate(double sr) { + sample_rate = sr; + } + + // ------------------------------------------------------------------ + /** @brief Getter for SetSampleRate() + */ + double GetSampleRate() const { + return sample_rate; + } + + // ------------------------------------------------------------------ + /** @brief Set the animation time range + * + * @param first Time where the animation starts, in ticks + * @param last Time where the animation ends, in ticks + */ + void SetAnimationRange(double _first, double _last) { + first = _first; + last = _last; + + ClearAnimRangeSetup(); + UpdateAnimRangeSetup(); + } + +protected: + + // ------------------------------------------------------------------ + /** @brief Build linearly subsampled keys from 3 single envelopes + * @param out Receives output keys + * @param envl_x X-component envelope + * @param envl_y Y-component envelope + * @param envl_z Z-component envelope + * @param flags Any combination of the AI_LWO_ANIM_FLAG_XXX flags. + * @note Up to two input envelopes may be nullptr + */ + void GetKeys(std::vector<aiVectorKey>& out, + LWO::Envelope* envl_x, + LWO::Envelope* envl_y, + LWO::Envelope* envl_z, + unsigned int flags); + + // ------------------------------------------------------------------ + /** @brief Resolve a single animation key by applying the right + * interpolation to it. + * @param cur Current key + * @param envl Envelope working on + * @param time time to be interpolated + * @param fill Receives the interpolated output value. + */ + void DoInterpolation(std::vector<LWO::Key>::const_iterator cur, + LWO::Envelope* envl,double time, float& fill); + + // ------------------------------------------------------------------ + /** @brief Almost the same, except we won't handle pre/post + * conditions here. + * @see DoInterpolation + */ + void DoInterpolation2(std::vector<LWO::Key>::const_iterator beg, + std::vector<LWO::Key>::const_iterator end,double time, float& fill); + + // ------------------------------------------------------------------ + /** @brief Interpolate 2 tracks if one is given + * + * @param out Receives extra output keys + * @param key_out Primary output key + * @param time Time to interpolate for + */ + void InterpolateTrack(std::vector<aiVectorKey>& out, + aiVectorKey& key_out,double time); + + // ------------------------------------------------------------------ + /** @brief Subsample an animation track by a given sampling rate + * + * @param out Receives output keys. Last key at input defines the + * time where subsampling starts. + * @param time Time to end subsampling at + * @param sample_delta Time delta between two samples + */ + void SubsampleAnimTrack(std::vector<aiVectorKey>& out, + double time,double sample_delta); + + // ------------------------------------------------------------------ + /** @brief Delete all keys which we inserted to match anim setup + */ + void ClearAnimRangeSetup(); + + // ------------------------------------------------------------------ + /** @brief Insert extra keys to match LWO's pre and post behaviours + * in a given time range [first...last] + */ + void UpdateAnimRangeSetup(); + +private: + std::list<Envelope>& envelopes; + double sample_rate; + + LWO::Envelope* trans_x, *trans_y, *trans_z; + LWO::Envelope* rotat_x, *rotat_y, *rotat_z; + LWO::Envelope* scale_x, *scale_y, *scale_z; + + double first, last; + bool need_to_setup; + + // temporary storage + LWO::Envelope* envl_x, * envl_y, * envl_z; + std::vector<LWO::Key>::const_iterator cur_x,cur_y,cur_z; + bool end_x, end_y, end_z; + + unsigned int flags; + double sample_delta; +}; + +} // end namespace LWO +} // end namespace Assimp + +#endif // !! AI_LWO_ANIMATION_INCLUDED diff --git a/libs/assimp/code/AssetLib/LWO/LWOBLoader.cpp b/libs/assimp/code/AssetLib/LWO/LWOBLoader.cpp new file mode 100644 index 0000000..1fbd9b9 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOBLoader.cpp @@ -0,0 +1,428 @@ +/* +--------------------------------------------------------------------------- +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 Implementation of the LWO importer class for the older LWOB + file formats, including materials */ + + +#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER + +// Internal headers +#include "LWOLoader.h" +using namespace Assimp; + + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOBFile() +{ + LE_NCONST uint8_t* const end = mFileBuffer + fileSize; + bool running = true; + while (running) + { + if (mFileBuffer + sizeof(IFF::ChunkHeader) > end)break; + const IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + { + throw DeadlyImportError("LWOB: Invalid chunk length"); + break; + } + uint8_t* const next = mFileBuffer+head.length; + switch (head.type) + { + // vertex list + case AI_LWO_PNTS: + { + if (!mCurLayer->mTempPoints.empty()) + ASSIMP_LOG_WARN("LWO: PNTS chunk encountered twice"); + else LoadLWOPoints(head.length); + break; + } + // face list + case AI_LWO_POLS: + { + + if (!mCurLayer->mFaces.empty()) + ASSIMP_LOG_WARN("LWO: POLS chunk encountered twice"); + else LoadLWOBPolygons(head.length); + break; + } + // list of tags + case AI_LWO_SRFS: + { + if (!mTags->empty()) + ASSIMP_LOG_WARN("LWO: SRFS chunk encountered twice"); + else LoadLWOTags(head.length); + break; + } + + // surface chunk + case AI_LWO_SURF: + { + LoadLWOBSurface(head.length); + break; + } + } + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOBPolygons(unsigned int length) +{ + // first find out how many faces and vertices we'll finally need + LE_NCONST uint16_t* const end = (LE_NCONST uint16_t*)(mFileBuffer+length); + LE_NCONST uint16_t* cursor = (LE_NCONST uint16_t*)mFileBuffer; + + // perform endianness conversions +#ifndef AI_BUILD_BIG_ENDIAN + while (cursor < end)ByteSwap::Swap2(cursor++); + cursor = (LE_NCONST uint16_t*)mFileBuffer; +#endif + + unsigned int iNumFaces = 0,iNumVertices = 0; + CountVertsAndFacesLWOB(iNumVertices,iNumFaces,cursor,end); + + // allocate the output array and copy face indices + if (iNumFaces) + { + cursor = (LE_NCONST uint16_t*)mFileBuffer; + + mCurLayer->mFaces.resize(iNumFaces); + FaceList::iterator it = mCurLayer->mFaces.begin(); + CopyFaceIndicesLWOB(it,cursor,end); + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::CountVertsAndFacesLWOB(unsigned int& verts, unsigned int& faces, + LE_NCONST uint16_t*& cursor, const uint16_t* const end, unsigned int max) +{ + while (cursor < end && max--) + { + uint16_t numIndices; + // must have 2 shorts left for numIndices and surface + if (end - cursor < 2) { + throw DeadlyImportError("LWOB: Unexpected end of file"); + } + ::memcpy(&numIndices, cursor++, 2); + // must have enough left for indices and surface + if (end - cursor < (1 + numIndices)) { + throw DeadlyImportError("LWOB: Unexpected end of file"); + } + verts += numIndices; + faces++; + cursor += numIndices; + int16_t surface; + ::memcpy(&surface, cursor++, 2); + if (surface < 0) + { + // there are detail polygons + ::memcpy(&numIndices, cursor++, 2); + CountVertsAndFacesLWOB(verts,faces,cursor,end,numIndices); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::CopyFaceIndicesLWOB(FaceList::iterator& it, + LE_NCONST uint16_t*& cursor, + const uint16_t* const end, + unsigned int max) +{ + while (cursor < end && max--) + { + LWO::Face& face = *it;++it; + uint16_t numIndices; + ::memcpy(&numIndices, cursor++, 2); + face.mNumIndices = numIndices; + if(face.mNumIndices) + { + if (cursor + face.mNumIndices >= end) + { + break; + } + face.mIndices = new unsigned int[face.mNumIndices]; + for (unsigned int i = 0; i < face.mNumIndices;++i) { + unsigned int & mi = face.mIndices[i]; + uint16_t index; + ::memcpy(&index, cursor++, 2); + mi = index; + if (mi > mCurLayer->mTempPoints.size()) + { + ASSIMP_LOG_WARN("LWOB: face index is out of range"); + mi = (unsigned int)mCurLayer->mTempPoints.size()-1; + } + } + } else { + ASSIMP_LOG_WARN("LWOB: Face has 0 indices"); + } + int16_t surface; + ::memcpy(&surface, cursor++, 2); + if (surface < 0) + { + surface = -surface; + + // there are detail polygons. + uint16_t numPolygons; + ::memcpy(&numPolygons, cursor++, 2); + if (cursor < end) + { + CopyFaceIndicesLWOB(it,cursor,end,numPolygons); + } + } + face.surfaceIndex = surface-1; + } +} + +// ------------------------------------------------------------------------------------------------ +LWO::Texture* LWOImporter::SetupNewTextureLWOB(LWO::TextureList& list,unsigned int size) +{ + list.push_back(LWO::Texture()); + LWO::Texture* tex = &list.back(); + + std::string type; + GetS0(type,size); + const char* s = type.c_str(); + + if(strstr(s, "Image Map")) + { + // Determine mapping type + if(strstr(s, "Planar")) + tex->mapMode = LWO::Texture::Planar; + else if(strstr(s, "Cylindrical")) + tex->mapMode = LWO::Texture::Cylindrical; + else if(strstr(s, "Spherical")) + tex->mapMode = LWO::Texture::Spherical; + else if(strstr(s, "Cubic")) + tex->mapMode = LWO::Texture::Cubic; + else if(strstr(s, "Front")) + tex->mapMode = LWO::Texture::FrontProjection; + } + else + { + // procedural or gradient, not supported + ASSIMP_LOG_ERROR("LWOB: Unsupported legacy texture: ", type); + } + + return tex; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOBSurface(unsigned int size) +{ + LE_NCONST uint8_t* const end = mFileBuffer + size; + + mSurfaces->push_back( LWO::Surface () ); + LWO::Surface& surf = mSurfaces->back(); + LWO::Texture *pTex = nullptr; + + GetS0(surf.mName,size); + bool running = true; + while (running) { + if (mFileBuffer + 6 >= end) + break; + + IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + /* A single test file (sonycam.lwo) seems to have invalid surface chunks. + * I'm assuming it's the fault of a single, unknown exporter so there are + * probably THOUSANDS of them. Here's a dirty workaround: + * + * We don't break if the chunk limit is exceeded. Instead, we're computing + * how much storage is actually left and work with this value from now on. + */ + if (mFileBuffer + head.length > end) { + ASSIMP_LOG_ERROR("LWOB: Invalid surface chunk length. Trying to continue."); + head.length = (uint16_t) (end - mFileBuffer); + } + + uint8_t* const next = mFileBuffer+head.length; + switch (head.type) + { + // diffuse color + case AI_LWO_COLR: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,COLR,3); + surf.mColor.r = GetU1() / 255.0f; + surf.mColor.g = GetU1() / 255.0f; + surf.mColor.b = GetU1() / 255.0f; + break; + } + // diffuse strength ... + case AI_LWO_DIFF: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,DIFF,2); + surf.mDiffuseValue = GetU2() / 255.0f; + break; + } + // specular strength ... + case AI_LWO_SPEC: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SPEC,2); + surf.mSpecularValue = GetU2() / 255.0f; + break; + } + // luminosity ... + case AI_LWO_LUMI: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,LUMI,2); + surf.mLuminosity = GetU2() / 255.0f; + break; + } + // transparency + case AI_LWO_TRAN: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TRAN,2); + surf.mTransparency = GetU2() / 255.0f; + break; + } + // surface flags + case AI_LWO_FLAG: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,FLAG,2); + uint16_t flag = GetU2(); + if (flag & 0x4 ) surf.mMaximumSmoothAngle = 1.56207f; + if (flag & 0x8 ) surf.mColorHighlights = 1.f; + if (flag & 0x100) surf.bDoubleSided = true; + break; + } + // maximum smoothing angle + case AI_LWO_SMAN: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SMAN,4); + surf.mMaximumSmoothAngle = std::fabs( GetF4() ); + break; + } + // glossiness + case AI_LWO_GLOS: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,GLOS,2); + surf.mGlossiness = (float)GetU2(); + break; + } + // color texture + case AI_LWO_CTEX: + { + pTex = SetupNewTextureLWOB(surf.mColorTextures, + head.length); + break; + } + // diffuse texture + case AI_LWO_DTEX: + { + pTex = SetupNewTextureLWOB(surf.mDiffuseTextures, + head.length); + break; + } + // specular texture + case AI_LWO_STEX: + { + pTex = SetupNewTextureLWOB(surf.mSpecularTextures, + head.length); + break; + } + // bump texture + case AI_LWO_BTEX: + { + pTex = SetupNewTextureLWOB(surf.mBumpTextures, + head.length); + break; + } + // transparency texture + case AI_LWO_TTEX: + { + pTex = SetupNewTextureLWOB(surf.mOpacityTextures, + head.length); + break; + } + // texture path + case AI_LWO_TIMG: + { + if (pTex) { + GetS0(pTex->mFileName,head.length); + } else { + ASSIMP_LOG_WARN("LWOB: Unexpected TIMG chunk"); + } + break; + } + // texture strength + case AI_LWO_TVAL: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TVAL,1); + if (pTex) { + pTex->mStrength = (float)GetU1()/ 255.f; + } else { + ASSIMP_LOG_ERROR("LWOB: Unexpected TVAL chunk"); + } + break; + } + // texture flags + case AI_LWO_TFLG: + { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TFLG,2); + + if (nullptr != pTex) { + const uint16_t s = GetU2(); + if (s & 1) + pTex->majorAxis = LWO::Texture::AXIS_X; + else if (s & 2) + pTex->majorAxis = LWO::Texture::AXIS_Y; + else if (s & 4) + pTex->majorAxis = LWO::Texture::AXIS_Z; + + if (s & 16) { + ASSIMP_LOG_WARN("LWOB: Ignoring \'negate\' flag on texture"); + } + } + else { + ASSIMP_LOG_WARN("LWOB: Unexpected TFLG chunk"); + } + break; + } + } + mFileBuffer = next; + } +} + +#endif // !! ASSIMP_BUILD_NO_LWO_IMPORTER diff --git a/libs/assimp/code/AssetLib/LWO/LWOFileData.h b/libs/assimp/code/AssetLib/LWO/LWOFileData.h new file mode 100644 index 0000000..f477f58 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOFileData.h @@ -0,0 +1,638 @@ +/* +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 LWOFileData.h + * @brief Defines chunk constants used by the LWO file format + +The chunks are taken from the official LightWave SDK headers. + +*/ +#ifndef AI_LWO_FILEDATA_INCLUDED +#define AI_LWO_FILEDATA_INCLUDED + +// STL headers +#include <list> +#include <vector> + +// public ASSIMP headers +#include <assimp/mesh.h> + +// internal headers +#include "AssetLib/LWO/LWOAnimation.h" +#include "Common/IFF.h" + +namespace Assimp { +namespace LWO { + +#define AI_LWO_FOURCC_LWOB AI_IFF_FOURCC('L', 'W', 'O', 'B') +#define AI_LWO_FOURCC_LWO2 AI_IFF_FOURCC('L', 'W', 'O', '2') +#define AI_LWO_FOURCC_LXOB AI_IFF_FOURCC('L', 'X', 'O', 'B') + +// chunks specific to the LWOB format +#define AI_LWO_SRFS AI_IFF_FOURCC('S', 'R', 'F', 'S') +#define AI_LWO_FLAG AI_IFF_FOURCC('F', 'L', 'A', 'G') +#define AI_LWO_VLUM AI_IFF_FOURCC('V', 'L', 'U', 'M') +#define AI_LWO_VDIF AI_IFF_FOURCC('V', 'D', 'I', 'F') +#define AI_LWO_VSPC AI_IFF_FOURCC('V', 'S', 'P', 'C') +#define AI_LWO_RFLT AI_IFF_FOURCC('R', 'F', 'L', 'T') +#define AI_LWO_BTEX AI_IFF_FOURCC('B', 'T', 'E', 'X') +#define AI_LWO_CTEX AI_IFF_FOURCC('C', 'T', 'E', 'X') +#define AI_LWO_DTEX AI_IFF_FOURCC('D', 'T', 'E', 'X') +#define AI_LWO_LTEX AI_IFF_FOURCC('L', 'T', 'E', 'X') +#define AI_LWO_RTEX AI_IFF_FOURCC('R', 'T', 'E', 'X') +#define AI_LWO_STEX AI_IFF_FOURCC('S', 'T', 'E', 'X') +#define AI_LWO_TTEX AI_IFF_FOURCC('T', 'T', 'E', 'X') +#define AI_LWO_TFLG AI_IFF_FOURCC('T', 'F', 'L', 'G') +#define AI_LWO_TSIZ AI_IFF_FOURCC('T', 'S', 'I', 'Z') +#define AI_LWO_TCTR AI_IFF_FOURCC('T', 'C', 'T', 'R') +#define AI_LWO_TFAL AI_IFF_FOURCC('T', 'F', 'A', 'L') +#define AI_LWO_TVEL AI_IFF_FOURCC('T', 'V', 'E', 'L') +#define AI_LWO_TCLR AI_IFF_FOURCC('T', 'C', 'L', 'R') +#define AI_LWO_TVAL AI_IFF_FOURCC('T', 'V', 'A', 'L') +#define AI_LWO_TAMP AI_IFF_FOURCC('T', 'A', 'M', 'P') +#define AI_LWO_TIMG AI_IFF_FOURCC('T', 'I', 'M', 'G') +#define AI_LWO_TAAS AI_IFF_FOURCC('T', 'A', 'A', 'S') +#define AI_LWO_TREF AI_IFF_FOURCC('T', 'R', 'E', 'F') +#define AI_LWO_TOPC AI_IFF_FOURCC('T', 'O', 'P', 'C') +#define AI_LWO_SDAT AI_IFF_FOURCC('S', 'D', 'A', 'T') +#define AI_LWO_TFP0 AI_IFF_FOURCC('T', 'F', 'P', '0') +#define AI_LWO_TFP1 AI_IFF_FOURCC('T', 'F', 'P', '1') + +/* top-level chunks */ +#define AI_LWO_LAYR AI_IFF_FOURCC('L', 'A', 'Y', 'R') +#define AI_LWO_TAGS AI_IFF_FOURCC('T', 'A', 'G', 'S') +#define AI_LWO_PNTS AI_IFF_FOURCC('P', 'N', 'T', 'S') +#define AI_LWO_BBOX AI_IFF_FOURCC('B', 'B', 'O', 'X') +#define AI_LWO_VMAP AI_IFF_FOURCC('V', 'M', 'A', 'P') +#define AI_LWO_VMAD AI_IFF_FOURCC('V', 'M', 'A', 'D') +#define AI_LWO_POLS AI_IFF_FOURCC('P', 'O', 'L', 'S') +#define AI_LWO_PTAG AI_IFF_FOURCC('P', 'T', 'A', 'G') +#define AI_LWO_ENVL AI_IFF_FOURCC('E', 'N', 'V', 'L') +#define AI_LWO_CLIP AI_IFF_FOURCC('C', 'L', 'I', 'P') +#define AI_LWO_SURF AI_IFF_FOURCC('S', 'U', 'R', 'F') +#define AI_LWO_DESC AI_IFF_FOURCC('D', 'E', 'S', 'C') +#define AI_LWO_TEXT AI_IFF_FOURCC('T', 'E', 'X', 'T') +#define AI_LWO_ICON AI_IFF_FOURCC('I', 'C', 'O', 'N') + +/* polygon types */ +#define AI_LWO_FACE AI_IFF_FOURCC('F', 'A', 'C', 'E') +#define AI_LWO_CURV AI_IFF_FOURCC('C', 'U', 'R', 'V') +#define AI_LWO_PTCH AI_IFF_FOURCC('P', 'T', 'C', 'H') +#define AI_LWO_MBAL AI_IFF_FOURCC('M', 'B', 'A', 'L') +#define AI_LWO_BONE AI_IFF_FOURCC('B', 'O', 'N', 'E') +#define AI_LWO_SUBD AI_IFF_FOURCC('S', 'U', 'B', 'D') + +/* polygon tags */ +#define AI_LWO_SURF AI_IFF_FOURCC('S', 'U', 'R', 'F') +#define AI_LWO_PART AI_IFF_FOURCC('P', 'A', 'R', 'T') +#define AI_LWO_SMGP AI_IFF_FOURCC('S', 'M', 'G', 'P') + +/* envelopes */ +#define AI_LWO_PRE AI_IFF_FOURCC('P', 'R', 'E', ' ') +#define AI_LWO_POST AI_IFF_FOURCC('P', 'O', 'S', 'T') +#define AI_LWO_KEY AI_IFF_FOURCC('K', 'E', 'Y', ' ') +#define AI_LWO_SPAN AI_IFF_FOURCC('S', 'P', 'A', 'N') +#define AI_LWO_TCB AI_IFF_FOURCC('T', 'C', 'B', ' ') +#define AI_LWO_HERM AI_IFF_FOURCC('H', 'E', 'R', 'M') +#define AI_LWO_BEZI AI_IFF_FOURCC('B', 'E', 'Z', 'I') +#define AI_LWO_BEZ2 AI_IFF_FOURCC('B', 'E', 'Z', '2') +#define AI_LWO_LINE AI_IFF_FOURCC('L', 'I', 'N', 'E') +#define AI_LWO_STEP AI_IFF_FOURCC('S', 'T', 'E', 'P') + +/* clips */ +#define AI_LWO_STIL AI_IFF_FOURCC('S', 'T', 'I', 'L') +#define AI_LWO_ISEQ AI_IFF_FOURCC('I', 'S', 'E', 'Q') +#define AI_LWO_ANIM AI_IFF_FOURCC('A', 'N', 'I', 'M') +#define AI_LWO_XREF AI_IFF_FOURCC('X', 'R', 'E', 'F') +#define AI_LWO_STCC AI_IFF_FOURCC('S', 'T', 'C', 'C') +#define AI_LWO_TIME AI_IFF_FOURCC('T', 'I', 'M', 'E') +#define AI_LWO_CONT AI_IFF_FOURCC('C', 'O', 'N', 'T') +#define AI_LWO_BRIT AI_IFF_FOURCC('B', 'R', 'I', 'T') +#define AI_LWO_SATR AI_IFF_FOURCC('S', 'A', 'T', 'R') +#define AI_LWO_HUE AI_IFF_FOURCC('H', 'U', 'E', ' ') +#define AI_LWO_GAMM AI_IFF_FOURCC('G', 'A', 'M', 'M') +#define AI_LWO_NEGA AI_IFF_FOURCC('N', 'E', 'G', 'A') +#define AI_LWO_IFLT AI_IFF_FOURCC('I', 'F', 'L', 'T') +#define AI_LWO_PFLT AI_IFF_FOURCC('P', 'F', 'L', 'T') + +/* surfaces */ +#define AI_LWO_COLR AI_IFF_FOURCC('C', 'O', 'L', 'R') +#define AI_LWO_LUMI AI_IFF_FOURCC('L', 'U', 'M', 'I') +#define AI_LWO_DIFF AI_IFF_FOURCC('D', 'I', 'F', 'F') +#define AI_LWO_SPEC AI_IFF_FOURCC('S', 'P', 'E', 'C') +#define AI_LWO_GLOS AI_IFF_FOURCC('G', 'L', 'O', 'S') +#define AI_LWO_REFL AI_IFF_FOURCC('R', 'E', 'F', 'L') +#define AI_LWO_RFOP AI_IFF_FOURCC('R', 'F', 'O', 'P') +#define AI_LWO_RIMG AI_IFF_FOURCC('R', 'I', 'M', 'G') +#define AI_LWO_RSAN AI_IFF_FOURCC('R', 'S', 'A', 'N') +#define AI_LWO_TRAN AI_IFF_FOURCC('T', 'R', 'A', 'N') +#define AI_LWO_TROP AI_IFF_FOURCC('T', 'R', 'O', 'P') +#define AI_LWO_TIMG AI_IFF_FOURCC('T', 'I', 'M', 'G') +#define AI_LWO_RIND AI_IFF_FOURCC('R', 'I', 'N', 'D') +#define AI_LWO_TRNL AI_IFF_FOURCC('T', 'R', 'N', 'L') +#define AI_LWO_BUMP AI_IFF_FOURCC('B', 'U', 'M', 'P') +#define AI_LWO_SMAN AI_IFF_FOURCC('S', 'M', 'A', 'N') +#define AI_LWO_SIDE AI_IFF_FOURCC('S', 'I', 'D', 'E') +#define AI_LWO_CLRH AI_IFF_FOURCC('C', 'L', 'R', 'H') +#define AI_LWO_CLRF AI_IFF_FOURCC('C', 'L', 'R', 'F') +#define AI_LWO_ADTR AI_IFF_FOURCC('A', 'D', 'T', 'R') +#define AI_LWO_SHRP AI_IFF_FOURCC('S', 'H', 'R', 'P') +#define AI_LWO_LINE AI_IFF_FOURCC('L', 'I', 'N', 'E') +#define AI_LWO_LSIZ AI_IFF_FOURCC('L', 'S', 'I', 'Z') +#define AI_LWO_ALPH AI_IFF_FOURCC('A', 'L', 'P', 'H') +#define AI_LWO_AVAL AI_IFF_FOURCC('A', 'V', 'A', 'L') +#define AI_LWO_GVAL AI_IFF_FOURCC('G', 'V', 'A', 'L') +#define AI_LWO_BLOK AI_IFF_FOURCC('B', 'L', 'O', 'K') +#define AI_LWO_VCOL AI_IFF_FOURCC('V', 'C', 'O', 'L') + +/* texture layer */ +#define AI_LWO_TYPE AI_IFF_FOURCC('T', 'Y', 'P', 'E') +#define AI_LWO_CHAN AI_IFF_FOURCC('C', 'H', 'A', 'N') +#define AI_LWO_NAME AI_IFF_FOURCC('N', 'A', 'M', 'E') +#define AI_LWO_ENAB AI_IFF_FOURCC('E', 'N', 'A', 'B') +#define AI_LWO_OPAC AI_IFF_FOURCC('O', 'P', 'A', 'C') +#define AI_LWO_FLAG AI_IFF_FOURCC('F', 'L', 'A', 'G') +#define AI_LWO_PROJ AI_IFF_FOURCC('P', 'R', 'O', 'J') +#define AI_LWO_STCK AI_IFF_FOURCC('S', 'T', 'C', 'K') +#define AI_LWO_TAMP AI_IFF_FOURCC('T', 'A', 'M', 'P') + +/* texture coordinates */ +#define AI_LWO_TMAP AI_IFF_FOURCC('T', 'M', 'A', 'P') +#define AI_LWO_AXIS AI_IFF_FOURCC('A', 'X', 'I', 'S') +#define AI_LWO_CNTR AI_IFF_FOURCC('C', 'N', 'T', 'R') +#define AI_LWO_SIZE AI_IFF_FOURCC('S', 'I', 'Z', 'E') +#define AI_LWO_ROTA AI_IFF_FOURCC('R', 'O', 'T', 'A') +#define AI_LWO_OREF AI_IFF_FOURCC('O', 'R', 'E', 'F') +#define AI_LWO_FALL AI_IFF_FOURCC('F', 'A', 'L', 'L') +#define AI_LWO_CSYS AI_IFF_FOURCC('C', 'S', 'Y', 'S') + +/* image map */ +#define AI_LWO_IMAP AI_IFF_FOURCC('I', 'M', 'A', 'P') +#define AI_LWO_IMAG AI_IFF_FOURCC('I', 'M', 'A', 'G') +#define AI_LWO_WRAP AI_IFF_FOURCC('W', 'R', 'A', 'P') +#define AI_LWO_WRPW AI_IFF_FOURCC('W', 'R', 'P', 'W') +#define AI_LWO_WRPH AI_IFF_FOURCC('W', 'R', 'P', 'H') +#define AI_LWO_VMAP AI_IFF_FOURCC('V', 'M', 'A', 'P') +#define AI_LWO_AAST AI_IFF_FOURCC('A', 'A', 'S', 'T') +#define AI_LWO_PIXB AI_IFF_FOURCC('P', 'I', 'X', 'B') + +/* procedural */ +#define AI_LWO_PROC AI_IFF_FOURCC('P', 'R', 'O', 'C') +#define AI_LWO_COLR AI_IFF_FOURCC('C', 'O', 'L', 'R') +#define AI_LWO_VALU AI_IFF_FOURCC('V', 'A', 'L', 'U') +#define AI_LWO_FUNC AI_IFF_FOURCC('F', 'U', 'N', 'C') +#define AI_LWO_FTPS AI_IFF_FOURCC('F', 'T', 'P', 'S') +#define AI_LWO_ITPS AI_IFF_FOURCC('I', 'T', 'P', 'S') +#define AI_LWO_ETPS AI_IFF_FOURCC('E', 'T', 'P', 'S') + +/* gradient */ +#define AI_LWO_GRAD AI_IFF_FOURCC('G', 'R', 'A', 'D') +#define AI_LWO_GRST AI_IFF_FOURCC('G', 'R', 'S', 'T') +#define AI_LWO_GREN AI_IFF_FOURCC('G', 'R', 'E', 'N') +#define AI_LWO_PNAM AI_IFF_FOURCC('P', 'N', 'A', 'M') +#define AI_LWO_INAM AI_IFF_FOURCC('I', 'N', 'A', 'M') +#define AI_LWO_GRPT AI_IFF_FOURCC('G', 'R', 'P', 'T') +#define AI_LWO_FKEY AI_IFF_FOURCC('F', 'K', 'E', 'Y') +#define AI_LWO_IKEY AI_IFF_FOURCC('I', 'K', 'E', 'Y') + +/* shader */ +#define AI_LWO_SHDR AI_IFF_FOURCC('S', 'H', 'D', 'R') +#define AI_LWO_DATA AI_IFF_FOURCC('D', 'A', 'T', 'A') + +/* VMAP types */ +#define AI_LWO_TXUV AI_IFF_FOURCC('T', 'X', 'U', 'V') +#define AI_LWO_RGB AI_IFF_FOURCC('R', 'G', 'B', ' ') +#define AI_LWO_RGBA AI_IFF_FOURCC('R', 'G', 'B', 'A') +#define AI_LWO_WGHT AI_IFF_FOURCC('W', 'G', 'H', 'T') + +#define AI_LWO_MNVW AI_IFF_FOURCC('M', 'N', 'V', 'W') +#define AI_LWO_MORF AI_IFF_FOURCC('M', 'O', 'R', 'F') +#define AI_LWO_SPOT AI_IFF_FOURCC('S', 'P', 'O', 'T') +#define AI_LWO_PICK AI_IFF_FOURCC('P', 'I', 'C', 'K') + +// MODO extension - per-vertex normal vectors +#define AI_LWO_MODO_NORM AI_IFF_FOURCC('N', 'O', 'R', 'M') + +// --------------------------------------------------------------------------- +/** \brief Data structure for a face in a LWO file + * + * \note We can't use the code in SmoothingGroups.inl here - the mesh + * structures of 3DS/ASE and LWO are too different. + */ +struct Face : public aiFace { + //! Default construction + Face() AI_NO_EXCEPT + : surfaceIndex(0), + smoothGroup(0), + type(AI_LWO_FACE) { + // empty + } + + //! Construction from given type + explicit Face(uint32_t _type) : + surfaceIndex(0), smoothGroup(0), type(_type) {} + + //! Copy construction + Face(const Face &f) : + aiFace() { + *this = f; + } + + //! Zero-based index into tags chunk + unsigned int surfaceIndex; + + //! Smooth group this face is assigned to + unsigned int smoothGroup; + + //! Type of face + uint32_t type; + + //! Assignment operator + Face &operator=(const LWO::Face &f) { + aiFace::operator=(f); + surfaceIndex = f.surfaceIndex; + smoothGroup = f.smoothGroup; + type = f.type; + return *this; + } +}; + +// --------------------------------------------------------------------------- +/** \brief Base structure for all vertex map representations + */ +struct VMapEntry { + explicit VMapEntry(unsigned int _dims) : + dims(_dims) {} + + virtual ~VMapEntry() {} + + //! allocates memory for the vertex map + virtual void Allocate(unsigned int num) { + if (!rawData.empty()) + return; // return if already allocated + + const unsigned int m = num * dims; + rawData.reserve(m + (m >> 2u)); // 25% as extra storage for VMADs + rawData.resize(m, 0.f); + abAssigned.resize(num, false); + } + + std::string name; + unsigned int dims; + + std::vector<float> rawData; + std::vector<bool> abAssigned; +}; + +// --------------------------------------------------------------------------- +/** \brief Represents an extra vertex color channel + */ +struct VColorChannel : public VMapEntry { + VColorChannel() : + VMapEntry(4) {} + + //! need to overwrite this function - the alpha channel must + //! be initialized to 1.0 by default + virtual void Allocate(unsigned int num) { + if (!rawData.empty()) + return; // return if already allocated + + unsigned int m = num * dims; + rawData.reserve(m + (m >> 2u)); // 25% as extra storage for VMADs + rawData.resize(m); + + for (aiColor4D *p = (aiColor4D *)&rawData[0]; p < (aiColor4D *)&rawData[m - 1]; ++p) + p->a = 1.f; + + abAssigned.resize(num, false); + } +}; + +// --------------------------------------------------------------------------- +/** \brief Represents an extra vertex UV channel + */ +struct UVChannel : public VMapEntry { + UVChannel() : + VMapEntry(2) {} +}; + +// --------------------------------------------------------------------------- +/** \brief Represents a weight map + */ +struct WeightChannel : public VMapEntry { + WeightChannel() : + VMapEntry(1) {} +}; + +// --------------------------------------------------------------------------- +/** \brief Represents a vertex-normals channel (MODO extension) + */ +struct NormalChannel : public VMapEntry { + NormalChannel() : + VMapEntry(3) {} +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO file texture + */ +struct Texture { + // we write the enum values out here to make debugging easier ... + enum BlendType { + Normal = 0, + Subtractive = 1, + Difference = 2, + Multiply = 3, + Divide = 4, + Alpha = 5, + TextureDispl = 6, + Additive = 7 + }; + + enum MappingMode { + Planar = 0, + Cylindrical = 1, + Spherical = 2, + Cubic = 3, + FrontProjection = 4, + UV = 5 + }; + + enum Axes { + AXIS_X = 0, + AXIS_Y = 1, + AXIS_Z = 2 + }; + + enum Wrap { + RESET = 0, + REPEAT = 1, + MIRROR = 2, + EDGE = 3 + }; + + Texture() : + mClipIdx(UINT_MAX), mStrength(1.0f), type(), mUVChannelIndex("unknown"), mRealUVIndex(UINT_MAX), enabled(true), blendType(Additive), bCanUse(true), mapMode(UV), majorAxis(AXIS_X), wrapAmountH(1.0f), wrapAmountW(1.0f), wrapModeWidth(REPEAT), wrapModeHeight(REPEAT), ordinal("\x00") {} + + //! File name of the texture + std::string mFileName; + + //! Clip index + unsigned int mClipIdx; + + //! Strength of the texture - blend factor + float mStrength; + + uint32_t type; // type of the texture + + //! Name of the corresponding UV channel + std::string mUVChannelIndex; + unsigned int mRealUVIndex; + + //! is the texture enabled? + bool enabled; + + //! blend type + BlendType blendType; + + //! are we able to use the texture? + bool bCanUse; + + //! mapping mode + MappingMode mapMode; + + //! major axis for planar, cylindrical, spherical projections + Axes majorAxis; + + //! wrap amount for cylindrical and spherical projections + float wrapAmountH, wrapAmountW; + + //! wrapping mode for the texture + Wrap wrapModeWidth, wrapModeHeight; + + //! ordinal string of the texture + std::string ordinal; +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO file clip + */ +struct Clip { + enum Type { + STILL, + SEQ, + REF, + UNSUPPORTED + } type; + + Clip() : + type(UNSUPPORTED), clipRef(), idx(0), negate(false) {} + + //! path to the base texture - + std::string path; + + //! reference to another CLIP + unsigned int clipRef; + + //! index of the clip + unsigned int idx; + + //! Negate the clip? + bool negate; +}; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO file shader + * + * Later + */ +struct Shader { + Shader() : + ordinal("\x00"), functionName("unknown"), enabled(true) {} + + std::string ordinal; + std::string functionName; + bool enabled; +}; + +typedef std::list<Texture> TextureList; +typedef std::list<Shader> ShaderList; + +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO file surface (= material) + */ +struct Surface { + Surface() : + mColor(0.78431f, 0.78431f, 0.78431f), bDoubleSided(false), mDiffuseValue(1.f), mSpecularValue(0.f), mTransparency(0.f), mGlossiness(0.4f), mLuminosity(0.f), mColorHighlights(0.f), mMaximumSmoothAngle(0.f) // 0 == not specified, no smoothing + , + mVCMap(), + mVCMapType(AI_LWO_RGBA), + mIOR(1.f) // vakuum + , + mBumpIntensity(1.f), + mWireframe(false), + mAdditiveTransparency(0.f) {} + + //! Name of the surface + std::string mName; + + //! Color of the surface + aiColor3D mColor; + + //! true for two-sided materials + bool bDoubleSided; + + //! Various material parameters + float mDiffuseValue, mSpecularValue, mTransparency, mGlossiness, mLuminosity, mColorHighlights; + + //! Maximum angle between two adjacent triangles + //! that they can be smoothed - in degrees + float mMaximumSmoothAngle; + + //! Vertex color map to be used to color the surface + std::string mVCMap; + uint32_t mVCMapType; + + //! Names of the special shaders to be applied to the surface + ShaderList mShaders; + + //! Textures - the first entry in the list is evaluated first + TextureList mColorTextures, // color textures are added to both diffuse and specular texture stacks + mDiffuseTextures, + mSpecularTextures, + mOpacityTextures, + mBumpTextures, + mGlossinessTextures, + mReflectionTextures; + + //! Index of refraction + float mIOR; + + //! Bump intensity scaling + float mBumpIntensity; + + //! Wireframe flag + bool mWireframe; + + //! Intensity of additive blending + float mAdditiveTransparency; +}; + +// --------------------------------------------------------------------------- +#define AI_LWO_VALIDATE_CHUNK_LENGTH(length, name, size) \ + if (length < size) { \ + throw DeadlyImportError("LWO: " #name " chunk is too small"); \ + } + +// some typedefs ... to make life with loader monsters like this easier +typedef std::vector<aiVector3D> PointList; +typedef std::vector<LWO::Face> FaceList; +typedef std::vector<LWO::Surface> SurfaceList; +typedef std::vector<std::string> TagList; +typedef std::vector<unsigned int> TagMappingTable; +typedef std::vector<unsigned int> ReferrerList; +typedef std::vector<WeightChannel> WeightChannelList; +typedef std::vector<VColorChannel> VColorChannelList; +typedef std::vector<UVChannel> UVChannelList; +typedef std::vector<Clip> ClipList; +typedef std::vector<Envelope> EnvelopeList; +typedef std::vector<unsigned int> SortedRep; + +// --------------------------------------------------------------------------- +/** \brief Represents a layer in the file + */ +struct Layer { + Layer() : + mFaceIDXOfs(0), mPointIDXOfs(0), mParent(0x0), mIndex(0xffff), skip(false) {} + + /** Temporary point list from the file */ + PointList mTempPoints; + + /** Lists for every point the index of another point + that has been copied from *this* point or UINT_MAX if + no copy of the point has been made */ + ReferrerList mPointReferrers; + + /** Weight channel list from the file */ + WeightChannelList mWeightChannels; + + /** Subdivision weight channel list from the file */ + WeightChannelList mSWeightChannels; + + /** Vertex color list from the file */ + VColorChannelList mVColorChannels; + + /** UV channel list from the file */ + UVChannelList mUVChannels; + + /** Normal vector channel from the file */ + NormalChannel mNormals; + + /** Temporary face list from the file*/ + FaceList mFaces; + + /** Current face indexing offset from the beginning of the buffers*/ + unsigned int mFaceIDXOfs; + + /** Current point indexing offset from the beginning of the buffers*/ + unsigned int mPointIDXOfs; + + /** Parent index */ + uint16_t mParent; + + /** Index of the layer */ + uint16_t mIndex; + + /** Name of the layer */ + std::string mName; + + /** Pivot point of the layer */ + aiVector3D mPivot; + + /** Skip this layer? */ + bool skip; +}; + +typedef std::list<LWO::Layer> LayerList; + +} // namespace LWO +} // namespace Assimp + +#endif // !! AI_LWO_FILEDATA_INCLUDED diff --git a/libs/assimp/code/AssetLib/LWO/LWOLoader.cpp b/libs/assimp/code/AssetLib/LWO/LWOLoader.cpp new file mode 100644 index 0000000..7410fb6 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOLoader.cpp @@ -0,0 +1,1422 @@ +/* +--------------------------------------------------------------------------- +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 LWOLoader.cpp + * @brief Implementation of the LWO importer class + */ + +#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER + +// internal headers +#include "AssetLib/LWO/LWOLoader.h" +#include "PostProcessing/ConvertToLHProcess.h" +#include "PostProcessing/ProcessHelper.h" + +#include <assimp/ByteSwapper.h> +#include <assimp/SGSpatialSort.h> +#include <assimp/StringComparison.h> +#include <assimp/importerdesc.h> +#include <assimp/IOSystem.hpp> + +#include <iomanip> +#include <map> +#include <memory> +#include <sstream> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "LightWave/Modo Object Importer", + "", + "", + "https://www.lightwave3d.com/lightwave_sdk/", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "lwo lxo" +}; + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +LWOImporter::LWOImporter() : + mIsLWO2(), + mIsLXOB(), + mLayers(), + mCurLayer(), + mTags(), + mMapping(), + mSurfaces(), + mFileBuffer(), + fileSize(), + mScene(nullptr), + configSpeedFlag(), + configLayerIndex(), + hasNamedLayer() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +LWOImporter::~LWOImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool LWOImporter::CanRead(const std::string &file, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint32_t tokens[] = { + AI_LWO_FOURCC_LWOB, + AI_LWO_FOURCC_LWO2, + AI_LWO_FOURCC_LXOB + }; + return CheckMagicToken(pIOHandler, file, tokens, AI_COUNT_OF(tokens), 8); +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void LWOImporter::SetupProperties(const Importer *pImp) { + configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED, 0) ? true : false); + configLayerIndex = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY, UINT_MAX); + configLayerName = pImp->GetPropertyString(AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY, ""); +} + +// ------------------------------------------------------------------------------------------------ +// Get list of file extensions +const aiImporterDesc *LWOImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void LWOImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, + IOSystem *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 LWO file ", pFile, "."); + } + + if ((this->fileSize = (unsigned int)file->FileSize()) < 12) { + throw DeadlyImportError("LWO: The file is too small to contain the IFF header"); + } + + // Allocate storage and copy the contents of the file to a memory buffer + std::vector<uint8_t> mBuffer(fileSize); + file->Read(&mBuffer[0], 1, fileSize); + mScene = pScene; + + // Determine the type of the file + uint32_t fileType; + const char *sz = IFF::ReadHeader(&mBuffer[0], fileType); + if (sz) { + throw DeadlyImportError(sz); + } + + mFileBuffer = &mBuffer[0] + 12; + fileSize -= 12; + + // Initialize some members with their default values + hasNamedLayer = false; + + // Create temporary storage on the stack but store pointers to it in the class + // instance. Therefore everything will be destructed properly if an exception + // is thrown and we needn't take care of that. + LayerList _mLayers; + SurfaceList _mSurfaces; + TagList _mTags; + TagMappingTable _mMapping; + + mLayers = &_mLayers; + mTags = &_mTags; + mMapping = &_mMapping; + mSurfaces = &_mSurfaces; + + // Allocate a default layer (layer indices are 1-based from now) + mLayers->push_back(Layer()); + mCurLayer = &mLayers->back(); + mCurLayer->mName = "<LWODefault>"; + mCurLayer->mIndex = (uint16_t) -1; + + // old lightwave file format (prior to v6) + if (AI_LWO_FOURCC_LWOB == fileType) { + ASSIMP_LOG_INFO("LWO file format: LWOB (<= LightWave 5.5)"); + + mIsLWO2 = false; + mIsLXOB = false; + LoadLWOBFile(); + } else if (AI_LWO_FOURCC_LWO2 == fileType) { + // New lightwave format + mIsLXOB = false; + ASSIMP_LOG_INFO("LWO file format: LWO2 (>= LightWave 6)"); + } else if (AI_LWO_FOURCC_LXOB == fileType) { + // MODO file format + mIsLXOB = true; + ASSIMP_LOG_INFO("LWO file format: LXOB (Modo)"); + } + else { + char szBuff[5]; + szBuff[0] = (char)(fileType >> 24u); + szBuff[1] = (char)(fileType >> 16u); + szBuff[2] = (char)(fileType >> 8u); + szBuff[3] = (char)(fileType); + szBuff[4] = '\0'; + throw DeadlyImportError("Unknown LWO sub format: ", szBuff); + } + + if (AI_LWO_FOURCC_LWOB != fileType) { + mIsLWO2 = true; + LoadLWO2File(); + + // The newer lightwave format allows the user to configure the + // loader that just one layer is used. If this is the case + // we need to check now whether the requested layer has been found. + if (UINT_MAX != configLayerIndex) { + unsigned int layerCount = 0; + for (std::list<LWO::Layer>::iterator itLayers = mLayers->begin(); itLayers != mLayers->end(); ++itLayers) + if (!itLayers->skip) + layerCount++; + if (layerCount != 2) + throw DeadlyImportError("LWO2: The requested layer was not found"); + } + + if (configLayerName.length() && !hasNamedLayer) { + throw DeadlyImportError("LWO2: Unable to find the requested layer: ", configLayerName); + } + } + + // now, as we have loaded all data, we can resolve cross-referenced tags and clips + ResolveTags(); + ResolveClips(); + + // now process all layers and build meshes and nodes + std::vector<aiMesh *> apcMeshes; + std::map<uint16_t, aiNode *> apcNodes; + + apcMeshes.reserve(mLayers->size() * std::min(((unsigned int)mSurfaces->size() / 2u), 1u)); + + unsigned int iDefaultSurface = UINT_MAX; // index of the default surface + for (LWO::Layer &layer : *mLayers) { + if (layer.skip) + continue; + + // I don't know whether there could be dummy layers, but it would be possible + const unsigned int meshStart = (unsigned int)apcMeshes.size(); + if (!layer.mFaces.empty() && !layer.mTempPoints.empty()) { + + // now sort all faces by the surfaces assigned to them + std::vector<SortedRep> pSorted(mSurfaces->size() + 1); + + unsigned int i = 0; + for (FaceList::iterator it = layer.mFaces.begin(), end = layer.mFaces.end(); it != end; ++it, ++i) { + // Check whether we support this face's type + if ((*it).type != AI_LWO_FACE && (*it).type != AI_LWO_PTCH && + (*it).type != AI_LWO_BONE && (*it).type != AI_LWO_SUBD) { + continue; + } + + unsigned int idx = (*it).surfaceIndex; + if (idx >= mTags->size()) { + ASSIMP_LOG_WARN("LWO: Invalid face surface index"); + idx = UINT_MAX; + } + if (UINT_MAX == idx || UINT_MAX == (idx = _mMapping[idx])) { + if (UINT_MAX == iDefaultSurface) { + iDefaultSurface = (unsigned int)mSurfaces->size(); + mSurfaces->push_back(LWO::Surface()); + LWO::Surface &surf = mSurfaces->back(); + surf.mColor.r = surf.mColor.g = surf.mColor.b = 0.6f; + surf.mName = "LWODefaultSurface"; + } + idx = iDefaultSurface; + } + pSorted[idx].push_back(i); + } + if (UINT_MAX == iDefaultSurface) { + pSorted.erase(pSorted.end() - 1); + } + for (unsigned int p = 0, j = 0; j < mSurfaces->size(); ++j) { + SortedRep &sorted = pSorted[j]; + if (sorted.empty()) + continue; + + // generate the mesh + aiMesh *mesh = new aiMesh(); + apcMeshes.push_back(mesh); + mesh->mNumFaces = (unsigned int)sorted.size(); + + // count the number of vertices + SortedRep::const_iterator it = sorted.begin(), end = sorted.end(); + for (; it != end; ++it) { + mesh->mNumVertices += layer.mFaces[*it].mNumIndices; + } + + aiVector3D *nrm = nullptr, *pv = mesh->mVertices = new aiVector3D[mesh->mNumVertices]; + aiFace *pf = mesh->mFaces = new aiFace[mesh->mNumFaces]; + mesh->mMaterialIndex = j; + + // find out which vertex color channels and which texture coordinate + // channels are really required by the material attached to this mesh + unsigned int vUVChannelIndices[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + unsigned int vVColorIndices[AI_MAX_NUMBER_OF_COLOR_SETS]; + +#ifdef ASSIMP_BUILD_DEBUG + for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++mui) { + vUVChannelIndices[mui] = UINT_MAX; + } + for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_COLOR_SETS; ++mui) { + vVColorIndices[mui] = UINT_MAX; + } +#endif + + FindUVChannels(_mSurfaces[j], sorted, layer, vUVChannelIndices); + FindVCChannels(_mSurfaces[j], sorted, layer, vVColorIndices); + + // allocate storage for UV and CV channels + aiVector3D *pvUV[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++mui) { + if (UINT_MAX == vUVChannelIndices[mui]) { + break; + } + + pvUV[mui] = mesh->mTextureCoords[mui] = new aiVector3D[mesh->mNumVertices]; + + // LightWave doesn't support more than 2 UV components (?) + mesh->mNumUVComponents[0] = 2; + } + + if (layer.mNormals.name.length()) { + nrm = mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + } + + aiColor4D *pvVC[AI_MAX_NUMBER_OF_COLOR_SETS]; + for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_COLOR_SETS; ++mui) { + if (UINT_MAX == vVColorIndices[mui]) { + break; + } + pvVC[mui] = mesh->mColors[mui] = new aiColor4D[mesh->mNumVertices]; + } + + // we would not need this extra array, but the code is much cleaner if we use it + std::vector<unsigned int> &smoothingGroups = layer.mPointReferrers; + smoothingGroups.erase(smoothingGroups.begin(), smoothingGroups.end()); + smoothingGroups.resize(mesh->mNumFaces, 0); + + // now convert all faces + unsigned int vert = 0; + std::vector<unsigned int>::iterator outIt = smoothingGroups.begin(); + for (it = sorted.begin(); it != end; ++it, ++outIt) { + const LWO::Face &face = layer.mFaces[*it]; + *outIt = face.smoothGroup; + + // copy all vertices + for (unsigned int q = 0; q < face.mNumIndices; ++q, ++vert) { + unsigned int idx = face.mIndices[q]; + *pv++ = layer.mTempPoints[idx] /*- layer.mPivot*/; + + // process UV coordinates + for (unsigned int w = 0; w < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++w) { + if (UINT_MAX == vUVChannelIndices[w]) { + break; + } + aiVector3D *&pp = pvUV[w]; + const aiVector2D &src = ((aiVector2D *)&layer.mUVChannels[vUVChannelIndices[w]].rawData[0])[idx]; + pp->x = src.x; + pp->y = src.y; + pp++; + } + + // process normals (MODO extension) + if (nrm) { + *nrm = ((aiVector3D *)&layer.mNormals.rawData[0])[idx]; + nrm->z *= -1.f; + ++nrm; + } + + // process vertex colors + for (unsigned int w = 0; w < AI_MAX_NUMBER_OF_COLOR_SETS; ++w) { + if (UINT_MAX == vVColorIndices[w]) { + break; + } + *pvVC[w] = ((aiColor4D *)&layer.mVColorChannels[vVColorIndices[w]].rawData[0])[idx]; + + // If a RGB color map is explicitly requested delete the + // alpha channel - it could theoretically be != 1. + if (_mSurfaces[j].mVCMapType == AI_LWO_RGB) + pvVC[w]->a = 1.f; + + pvVC[w]++; + } + +#if 0 + // process vertex weights. We can't properly reconstruct the whole skeleton for now, + // but we can create dummy bones for all weight channels which we have. + for (unsigned int w = 0; w < layer.mWeightChannels.size();++w) + { + } +#endif + + face.mIndices[q] = vert; + } + pf->mIndices = face.mIndices; + pf->mNumIndices = face.mNumIndices; + unsigned int **facePtr = (unsigned int **)&face.mIndices; + *facePtr = nullptr; // HACK: make sure it won't be deleted + pf++; + } + + if (!mesh->mNormals) { + // Compute normal vectors for the mesh - we can't use our GenSmoothNormal- + // Step here since it wouldn't handle smoothing groups correctly for LWO. + // So we use a separate implementation. + ComputeNormals(mesh, smoothingGroups, _mSurfaces[j]); + } else { + ASSIMP_LOG_VERBOSE_DEBUG("LWO2: No need to compute normals, they're already there"); + } + ++p; + } + } + + // Generate nodes to render the mesh. Store the source layer in the mParent member of the nodes + unsigned int num = static_cast<unsigned int>(apcMeshes.size() - meshStart); + if (layer.mName != "<LWODefault>" || num > 0) { + aiNode *pcNode = new aiNode(); + pcNode->mName.Set(layer.mName); + pcNode->mParent = (aiNode *)&layer; + pcNode->mNumMeshes = num; + + if (pcNode->mNumMeshes) { + pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes]; + for (unsigned int p = 0; p < pcNode->mNumMeshes; ++p) + pcNode->mMeshes[p] = p + meshStart; + } + apcNodes[layer.mIndex] = pcNode; + } + } + + if (apcNodes.empty() || apcMeshes.empty()) + throw DeadlyImportError("LWO: No meshes loaded"); + + // The RemoveRedundantMaterials step will clean this up later + pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials = (unsigned int)mSurfaces->size()]; + for (unsigned int mat = 0; mat < pScene->mNumMaterials; ++mat) { + aiMaterial *pcMat = new aiMaterial(); + pScene->mMaterials[mat] = pcMat; + ConvertMaterial((*mSurfaces)[mat], pcMat); + } + + // copy the meshes to the output structure + pScene->mMeshes = new aiMesh *[pScene->mNumMeshes = (unsigned int)apcMeshes.size()]; + ::memcpy(pScene->mMeshes, &apcMeshes[0], pScene->mNumMeshes * sizeof(void *)); + + // generate the final node graph + GenerateNodeGraph(apcNodes); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &smoothingGroups, + const LWO::Surface &surface) { + // Allocate output storage + mesh->mNormals = new aiVector3D[mesh->mNumVertices]; + + // First generate per-face normals + aiVector3D *out; + std::vector<aiVector3D> faceNormals; + + // ... in some cases that's already enough + if (!surface.mMaximumSmoothAngle) + out = mesh->mNormals; + else { + faceNormals.resize(mesh->mNumVertices); + out = &faceNormals[0]; + } + + aiFace *begin = mesh->mFaces, *const end = mesh->mFaces + mesh->mNumFaces; + for (; begin != end; ++begin) { + aiFace &face = *begin; + + if (face.mNumIndices < 3) { + continue; + } + + // LWO doc: "the normal is defined as the cross product of the first and last edges" + aiVector3D *pV1 = mesh->mVertices + face.mIndices[0]; + aiVector3D *pV2 = mesh->mVertices + face.mIndices[1]; + aiVector3D *pV3 = mesh->mVertices + face.mIndices[face.mNumIndices - 1]; + + aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).Normalize(); + for (unsigned int i = 0; i < face.mNumIndices; ++i) + out[face.mIndices[i]] = vNor; + } + if (!surface.mMaximumSmoothAngle) return; + const float posEpsilon = ComputePositionEpsilon(mesh); + + // Now generate the spatial sort tree + SGSpatialSort sSort; + std::vector<unsigned int>::const_iterator it = smoothingGroups.begin(); + for (begin = mesh->mFaces; begin != end; ++begin, ++it) { + aiFace &face = *begin; + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + unsigned int tt = face.mIndices[i]; + sSort.Add(mesh->mVertices[tt], tt, *it); + } + } + // Sort everything - this takes O(nlogn) time + sSort.Prepare(); + std::vector<unsigned int> poResult; + poResult.reserve(20); + + // Generate vertex normals. We have O(logn) for the binary lookup, which we need + // for n elements, thus the EXPECTED complexity is O(nlogn) + if (surface.mMaximumSmoothAngle < 3.f && !configSpeedFlag) { + const float fLimit = std::cos(surface.mMaximumSmoothAngle); + + for (begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it) { + const aiFace &face = *begin; + unsigned int *beginIdx = face.mIndices, *const endIdx = face.mIndices + face.mNumIndices; + for (; beginIdx != endIdx; ++beginIdx) { + unsigned int idx = *beginIdx; + sSort.FindPositions(mesh->mVertices[idx], *it, posEpsilon, poResult, true); + + aiVector3D vNormals; + for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) { + const aiVector3D &v = faceNormals[*a]; + if (v * faceNormals[idx] < fLimit) + continue; + vNormals += v; + } + mesh->mNormals[idx] = vNormals.Normalize(); + } + } + } + // faster code path in case there is no smooth angle + else { + std::vector<bool> vertexDone(mesh->mNumVertices, false); + for (begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it) { + const aiFace &face = *begin; + unsigned int *beginIdx = face.mIndices, *const endIdx = face.mIndices + face.mNumIndices; + for (; beginIdx != endIdx; ++beginIdx) { + unsigned int idx = *beginIdx; + if (vertexDone[idx]) + continue; + sSort.FindPositions(mesh->mVertices[idx], *it, posEpsilon, poResult, true); + + aiVector3D vNormals; + for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) { + const aiVector3D &v = faceNormals[*a]; + vNormals += v; + } + vNormals.Normalize(); + for (std::vector<unsigned int>::const_iterator a = poResult.begin(); a != poResult.end(); ++a) { + mesh->mNormals[*a] = vNormals; + vertexDone[*a] = true; + } + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::GenerateNodeGraph(std::map<uint16_t, aiNode *> &apcNodes) { + // now generate the final nodegraph - generate a root node and attach children + aiNode *root = mScene->mRootNode = new aiNode(); + root->mName.Set("<LWORoot>"); + + //Set parent of all children, inserting pivots + std::map<uint16_t, aiNode *> mapPivot; + for (auto itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) { + + //Get the parent index + LWO::Layer *nodeLayer = (LWO::Layer *)(itapcNodes->second->mParent); + uint16_t parentIndex = nodeLayer->mParent; + + //Create pivot node, store it into the pivot map, and set the parent as the pivot + aiNode *pivotNode = new aiNode(); + pivotNode->mName.Set("Pivot-" + std::string(itapcNodes->second->mName.data)); + itapcNodes->second->mParent = pivotNode; + + //Look for the parent node to attach the pivot to + if (apcNodes.find(parentIndex) != apcNodes.end()) { + pivotNode->mParent = apcNodes[parentIndex]; + } else { + //If not, attach to the root node + pivotNode->mParent = root; + } + + //Set the node and the pivot node transformation + itapcNodes->second->mTransformation.a4 = -nodeLayer->mPivot.x; + itapcNodes->second->mTransformation.b4 = -nodeLayer->mPivot.y; + itapcNodes->second->mTransformation.c4 = -nodeLayer->mPivot.z; + pivotNode->mTransformation.a4 = nodeLayer->mPivot.x; + pivotNode->mTransformation.b4 = nodeLayer->mPivot.y; + pivotNode->mTransformation.c4 = nodeLayer->mPivot.z; + mapPivot[-(itapcNodes->first + 2)] = pivotNode; + } + + //Merge pivot map into node map + for (auto itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) { + apcNodes[itMapPivot->first] = itMapPivot->second; + } + + //Set children of all parents + apcNodes[(uint16_t)-1] = root; + for (auto itMapParentNodes = apcNodes.begin(); itMapParentNodes != apcNodes.end(); ++itMapParentNodes) { + for (auto itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) { + if ((itMapParentNodes->first != itMapChildNodes->first) && (itMapParentNodes->second == itMapChildNodes->second->mParent)) { + ++(itMapParentNodes->second->mNumChildren); + } + } + if (itMapParentNodes->second->mNumChildren) { + itMapParentNodes->second->mChildren = new aiNode *[itMapParentNodes->second->mNumChildren]; + uint16_t p = 0; + for (auto itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) { + if ((itMapParentNodes->first != itMapChildNodes->first) && (itMapParentNodes->second == itMapChildNodes->second->mParent)) { + itMapParentNodes->second->mChildren[p++] = itMapChildNodes->second; + } + } + } + } + + if (!mScene->mRootNode->mNumChildren) + throw DeadlyImportError("LWO: Unable to build a valid node graph"); + + // Remove a single root node with no meshes assigned to it ... + if (1 == mScene->mRootNode->mNumChildren) { + aiNode *pc = mScene->mRootNode->mChildren[0]; + pc->mParent = mScene->mRootNode->mChildren[0] = nullptr; + delete mScene->mRootNode; + mScene->mRootNode = pc; + } + + // convert the whole stuff to RH with CCW winding + MakeLeftHandedProcess maker; + maker.Execute(mScene); + + FlipWindingOrderProcess flipper; + flipper.Execute(mScene); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::ResolveTags() { + // --- this function is used for both LWO2 and LWOB + mMapping->resize(mTags->size(), UINT_MAX); + for (unsigned int a = 0; a < mTags->size(); ++a) { + + const std::string &c = (*mTags)[a]; + for (unsigned int i = 0; i < mSurfaces->size(); ++i) { + + const std::string &d = (*mSurfaces)[i].mName; + if (!ASSIMP_stricmp(c, d)) { + + (*mMapping)[a] = i; + break; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::ResolveClips() { + for (unsigned int i = 0; i < mClips.size(); ++i) { + + Clip &clip = mClips[i]; + if (Clip::REF == clip.type) { + + if (clip.clipRef >= mClips.size()) { + ASSIMP_LOG_ERROR("LWO2: Clip referrer index is out of range"); + clip.clipRef = 0; + } + + Clip &dest = mClips[clip.clipRef]; + if (Clip::REF == dest.type) { + ASSIMP_LOG_ERROR("LWO2: Clip references another clip reference"); + clip.type = Clip::UNSUPPORTED; + } + + else { + clip.path = dest.path; + clip.type = dest.type; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::AdjustTexturePath(std::string &out) { + // --- this function is used for both LWO2 and LWOB + if (!mIsLWO2 && ::strstr(out.c_str(), "(sequence)")) { + + // remove the (sequence) and append 000 + ASSIMP_LOG_INFO("LWOB: Sequence of animated texture found. It will be ignored"); + out = out.substr(0, out.length() - 10) + "000"; + } + + // format: drive:path/file - we just need to insert a slash after the drive + std::string::size_type n = out.find_first_of(':'); + if (std::string::npos != n) { + out.insert(n + 1, "/"); + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOTags(unsigned int size) { + // --- this function is used for both LWO2 and LWOB + + const char *szCur = (const char *)mFileBuffer, *szLast = szCur; + const char *const szEnd = szLast + size; + while (szCur < szEnd) { + if (!(*szCur)) { + const size_t len = (size_t)(szCur - szLast); + // FIX: skip empty-sized tags + if (len) + mTags->push_back(std::string(szLast, len)); + szCur += (len & 0x1 ? 1 : 2); + szLast = szCur; + } + szCur++; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWOPoints(unsigned int length) { + // --- this function is used for both LWO2 and LWOB but for + // LWO2 we need to allocate 25% more storage - it could be we'll + // need to duplicate some points later. + const size_t vertexLen = 12; + if ((length % vertexLen) != 0) { + throw DeadlyImportError("LWO2: Points chunk length is not multiple of vertexLen (12)"); + } + unsigned int regularSize = (unsigned int)mCurLayer->mTempPoints.size() + length / 12; + if (mIsLWO2) { + mCurLayer->mTempPoints.reserve(regularSize + (regularSize >> 2u)); + mCurLayer->mTempPoints.resize(regularSize); + + // initialize all point referrers with the default values + mCurLayer->mPointReferrers.reserve(regularSize + (regularSize >> 2u)); + mCurLayer->mPointReferrers.resize(regularSize, UINT_MAX); + } else + mCurLayer->mTempPoints.resize(regularSize); + + // perform endianness conversions +#ifndef AI_BUILD_BIG_ENDIAN + for (unsigned int i = 0; i<length >> 2; ++i) + ByteSwap::Swap4(mFileBuffer + (i << 2)); +#endif + ::memcpy(&mCurLayer->mTempPoints[0], mFileBuffer, length); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2Polygons(unsigned int length) { + LE_NCONST uint16_t *const end = (LE_NCONST uint16_t *)(mFileBuffer + length); + const uint32_t type = GetU4(); + + // Determine the type of the polygons + switch (type) { + // read unsupported stuff too (although we won't process it) + case AI_LWO_MBAL: + ASSIMP_LOG_WARN("LWO2: Encountered unsupported primitive chunk (METABALL)"); + break; + case AI_LWO_CURV: + ASSIMP_LOG_WARN("LWO2: Encountered unsupported primitive chunk (SPLINE)"); + ; + break; + + // These are ok with no restrictions + case AI_LWO_PTCH: + case AI_LWO_FACE: + case AI_LWO_BONE: + case AI_LWO_SUBD: + break; + default: + + // hm!? wtf is this? ok ... + ASSIMP_LOG_ERROR("LWO2: Ignoring unknown polygon type."); + break; + } + + // first find out how many faces and vertices we'll finally need + uint16_t *cursor = (uint16_t *)mFileBuffer; + + unsigned int iNumFaces = 0, iNumVertices = 0; + CountVertsAndFacesLWO2(iNumVertices, iNumFaces, cursor, end); + + // allocate the output array and copy face indices + if (iNumFaces) { + cursor = (uint16_t *)mFileBuffer; + + mCurLayer->mFaces.resize(iNumFaces, LWO::Face(type)); + FaceList::iterator it = mCurLayer->mFaces.begin(); + CopyFaceIndicesLWO2(it, cursor, end); + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::CountVertsAndFacesLWO2(unsigned int &verts, unsigned int &faces, + uint16_t *&cursor, const uint16_t *const end, unsigned int max) { + while (cursor < end && max--) { + uint16_t numIndices; + ::memcpy(&numIndices, cursor++, 2); + AI_LSWAP2(numIndices); + numIndices &= 0x03FF; + + verts += numIndices; + ++faces; + + for (uint16_t i = 0; i < numIndices; i++) { + ReadVSizedIntLWO2((uint8_t *&)cursor); + } + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::CopyFaceIndicesLWO2(FaceList::iterator &it, + uint16_t *&cursor, + const uint16_t *const end) { + while (cursor < end) { + LWO::Face &face = *it++; + uint16_t numIndices; + ::memcpy(&numIndices, cursor++, 2); + AI_LSWAP2(numIndices); + face.mNumIndices = numIndices & 0x03FF; + + if (face.mNumIndices) /* byte swapping has already been done */ + { + face.mIndices = new unsigned int[face.mNumIndices]; + for (unsigned int i = 0; i < face.mNumIndices; i++) { + face.mIndices[i] = ReadVSizedIntLWO2((uint8_t *&)cursor) + mCurLayer->mPointIDXOfs; + if (face.mIndices[i] > mCurLayer->mTempPoints.size()) { + ASSIMP_LOG_WARN("LWO2: Failure evaluating face record, index is out of range"); + face.mIndices[i] = (unsigned int)mCurLayer->mTempPoints.size() - 1; + } + } + } else + throw DeadlyImportError("LWO2: Encountered invalid face record with zero indices"); + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2PolygonTags(unsigned int length) { + LE_NCONST uint8_t *const end = mFileBuffer + length; + + AI_LWO_VALIDATE_CHUNK_LENGTH(length, PTAG, 4); + uint32_t type = GetU4(); + + if (type != AI_LWO_SURF && type != AI_LWO_SMGP) + return; + + while (mFileBuffer < end) { + unsigned int i = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mFaceIDXOfs; + unsigned int j = GetU2(); + + if (i >= mCurLayer->mFaces.size()) { + ASSIMP_LOG_WARN("LWO2: face index in PTAG is out of range"); + continue; + } + + switch (type) { + + case AI_LWO_SURF: + mCurLayer->mFaces[i].surfaceIndex = j; + break; + case AI_LWO_SMGP: /* is that really used? */ + mCurLayer->mFaces[i].smoothGroup = j; + break; + }; + } +} + +// ------------------------------------------------------------------------------------------------ +template <class T> +VMapEntry *FindEntry(std::vector<T> &list, const std::string &name, bool perPoly) { + for (auto &elem : list) { + if (elem.name == name) { + if (!perPoly) { + ASSIMP_LOG_WARN("LWO2: Found two VMAP sections with equal names"); + } + return &elem; + } + } + list.push_back(T()); + VMapEntry *p = &list.back(); + p->name = name; + return p; +} + +// ------------------------------------------------------------------------------------------------ +template <class T> +inline void CreateNewEntry(T &chan, unsigned int srcIdx) { + if (!chan.name.length()) + return; + + chan.abAssigned[srcIdx] = true; + chan.abAssigned.resize(chan.abAssigned.size() + 1, false); + + for (unsigned int a = 0; a < chan.dims; ++a) + chan.rawData.push_back(chan.rawData[srcIdx * chan.dims + a]); +} + +// ------------------------------------------------------------------------------------------------ +template <class T> +inline void CreateNewEntry(std::vector<T> &list, unsigned int srcIdx) { + for (auto &elem : list) { + CreateNewEntry(elem, srcIdx); + } +} + +// ------------------------------------------------------------------------------------------------ +inline void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry *base, unsigned int numRead, + unsigned int idx, float *data) { + ai_assert(nullptr != data); + LWO::ReferrerList &refList = mCurLayer->mPointReferrers; + unsigned int i; + + if (idx >= base->abAssigned.size()) { + throw DeadlyImportError("Bad index"); + } + base->abAssigned[idx] = true; + for (i = 0; i < numRead; ++i) { + base->rawData[idx * base->dims + i] = data[i]; + } + + if (UINT_MAX != (i = refList[idx])) { + DoRecursiveVMAPAssignment(base, numRead, i, data); + } +} + +// ------------------------------------------------------------------------------------------------ +inline void AddToSingleLinkedList(ReferrerList &refList, unsigned int srcIdx, unsigned int destIdx) { + if (UINT_MAX == refList[srcIdx]) { + refList[srcIdx] = destIdx; + return; + } + AddToSingleLinkedList(refList, refList[srcIdx], destIdx); +} + +// ------------------------------------------------------------------------------------------------ +// Load LWO2 vertex map +void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly) { + LE_NCONST uint8_t *const end = mFileBuffer + length; + + AI_LWO_VALIDATE_CHUNK_LENGTH(length, VMAP, 6); + unsigned int type = GetU4(); + unsigned int dims = GetU2(); + + VMapEntry *base; + + // read the name of the vertex map + std::string name; + GetS0(name, length); + + switch (type) { + case AI_LWO_TXUV: + if (dims != 2) { + ASSIMP_LOG_WARN("LWO2: Skipping UV channel \'", name, "\' with !2 components"); + return; + } + base = FindEntry(mCurLayer->mUVChannels, name, perPoly); + break; + case AI_LWO_WGHT: + case AI_LWO_MNVW: + if (dims != 1) { + ASSIMP_LOG_WARN("LWO2: Skipping Weight Channel \'", name, "\' with !1 components"); + return; + } + base = FindEntry((type == AI_LWO_WGHT ? mCurLayer->mWeightChannels : mCurLayer->mSWeightChannels), name, perPoly); + break; + case AI_LWO_RGB: + case AI_LWO_RGBA: + if (dims != 3 && dims != 4) { + ASSIMP_LOG_WARN("LWO2: Skipping Color Map \'", name, "\' with a dimension > 4 or < 3"); + return; + } + base = FindEntry(mCurLayer->mVColorChannels, name, perPoly); + break; + + case AI_LWO_MODO_NORM: + /* This is a non-standard extension chunk used by Luxology's MODO. + * It stores per-vertex normals. This VMAP exists just once, has + * 3 dimensions and is btw extremely beautiful. + */ + if (name != "vert_normals" || dims != 3 || mCurLayer->mNormals.name.length()) + return; + + ASSIMP_LOG_INFO("Processing non-standard extension: MODO VMAP.NORM.vert_normals"); + + mCurLayer->mNormals.name = name; + base = &mCurLayer->mNormals; + break; + + case AI_LWO_PICK: /* these VMAPs are just silently dropped */ + case AI_LWO_MORF: + case AI_LWO_SPOT: + return; + + default: + if (name == "APS.Level") { + // XXX handle this (seems to be subdivision-related). + } + ASSIMP_LOG_WARN("LWO2: Skipping unknown VMAP/VMAD channel \'", name, "\'"); + return; + }; + base->Allocate((unsigned int)mCurLayer->mTempPoints.size()); + + // now read all entries in the map + type = std::min(dims, base->dims); + const unsigned int diff = (dims - type) << 2u; + + LWO::FaceList &list = mCurLayer->mFaces; + LWO::PointList &pointList = mCurLayer->mTempPoints; + LWO::ReferrerList &refList = mCurLayer->mPointReferrers; + + const unsigned int numPoints = (unsigned int)pointList.size(); + const unsigned int numFaces = (unsigned int)list.size(); + + while (mFileBuffer < end) { + + unsigned int idx = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mPointIDXOfs; + if (idx >= numPoints) { + ASSIMP_LOG_WARN("LWO2: Failure evaluating VMAP/VMAD entry \'", name, "\', vertex index is out of range"); + mFileBuffer += base->dims << 2u; + continue; + } + if (perPoly) { + unsigned int polyIdx = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mFaceIDXOfs; + if (base->abAssigned[idx]) { + // we have already a VMAP entry for this vertex - thus + // we need to duplicate the corresponding polygon. + if (polyIdx >= numFaces) { + ASSIMP_LOG_WARN("LWO2: Failure evaluating VMAD entry \'", name, "\', polygon index is out of range"); + mFileBuffer += base->dims << 2u; + continue; + } + + LWO::Face &src = list[polyIdx]; + + // generate a new unique vertex for the corresponding index - but only + // if we can find the index in the face + bool had = false; + for (unsigned int i = 0; i < src.mNumIndices; ++i) { + + unsigned int srcIdx = src.mIndices[i], tmp = idx; + do { + if (tmp == srcIdx) + break; + } while ((tmp = refList[tmp]) != UINT_MAX); + if (tmp == UINT_MAX) { + continue; + } + + had = true; + refList.resize(refList.size() + 1, UINT_MAX); + + idx = (unsigned int)pointList.size(); + src.mIndices[i] = (unsigned int)pointList.size(); + + // store the index of the new vertex in the old vertex + // so we get a single linked list we can traverse in + // only one direction + AddToSingleLinkedList(refList, srcIdx, src.mIndices[i]); + pointList.push_back(pointList[srcIdx]); + + CreateNewEntry(mCurLayer->mVColorChannels, srcIdx); + CreateNewEntry(mCurLayer->mUVChannels, srcIdx); + CreateNewEntry(mCurLayer->mWeightChannels, srcIdx); + CreateNewEntry(mCurLayer->mSWeightChannels, srcIdx); + CreateNewEntry(mCurLayer->mNormals, srcIdx); + } + if (!had) { + ASSIMP_LOG_WARN("LWO2: Failure evaluating VMAD entry \'", name, "\', vertex index wasn't found in that polygon"); + ai_assert(had); + } + } + } + + std::unique_ptr<float[]> temp(new float[type]); + for (unsigned int l = 0; l < type; ++l) + temp[l] = GetF4(); + + DoRecursiveVMAPAssignment(base, type, idx, temp.get()); + mFileBuffer += diff; + } +} + +// ------------------------------------------------------------------------------------------------ +// Load LWO2 clip +void LWOImporter::LoadLWO2Clip(unsigned int length) { + AI_LWO_VALIDATE_CHUNK_LENGTH(length, CLIP, 10); + + mClips.push_back(LWO::Clip()); + LWO::Clip &clip = mClips.back(); + + // first - get the index of the clip + clip.idx = GetU4(); + + IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + switch (head.type) { + case AI_LWO_STIL: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, STIL, 1); + + // "Normal" texture + GetS0(clip.path, head.length); + clip.type = Clip::STILL; + break; + + case AI_LWO_ISEQ: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, ISEQ, 16); + // Image sequence. We'll later take the first. + { + uint8_t digits = GetU1(); + mFileBuffer++; + int16_t offset = GetU2(); + mFileBuffer += 4; + int16_t start = GetU2(); + mFileBuffer += 4; + + std::string s; + std::ostringstream ss; + GetS0(s, head.length); + + head.length -= (uint16_t)s.length() + 1; + ss << s; + ss << std::setw(digits) << offset + start; + GetS0(s, head.length); + ss << s; + clip.path = ss.str(); + clip.type = Clip::SEQ; + } + break; + + case AI_LWO_STCC: + ASSIMP_LOG_WARN("LWO2: Color shifted images are not supported"); + break; + + case AI_LWO_ANIM: + ASSIMP_LOG_WARN("LWO2: Animated textures are not supported"); + break; + + case AI_LWO_XREF: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, XREF, 4); + + // Just a cross-reference to another CLIp + clip.type = Clip::REF; + clip.clipRef = GetU4(); + break; + + case AI_LWO_NEGA: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, NEGA, 2); + clip.negate = (0 != GetU2()); + break; + + default: + ASSIMP_LOG_WARN("LWO2: Encountered unknown CLIP sub-chunk"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Load envelope description +void LWOImporter::LoadLWO2Envelope(unsigned int length) { + LE_NCONST uint8_t *const end = mFileBuffer + length; + AI_LWO_VALIDATE_CHUNK_LENGTH(length, ENVL, 4); + + mEnvelopes.push_back(LWO::Envelope()); + LWO::Envelope &envelope = mEnvelopes.back(); + + // Get the index of the envelope + envelope.index = ReadVSizedIntLWO2(mFileBuffer); + + // It looks like there might be an extra U4 right after the index, + // at least in modo (LXOB) files: we'll ignore it if it's zero, + // otherwise it represents the start of a subchunk, so we backtrack. + if (mIsLXOB) { + uint32_t extra = GetU4(); + if (extra) { + mFileBuffer -= 4; + } + } + + // ... and read all subchunks + while (true) { + if (mFileBuffer + 6 >= end) break; + LE_NCONST IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid envelope chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + // Type & representation of the envelope + case AI_LWO_TYPE: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, TYPE, 2); + mFileBuffer++; // skip user format + + // Determine type of envelope + envelope.type = (LWO::EnvelopeType)*mFileBuffer; + ++mFileBuffer; + break; + + // precondition + case AI_LWO_PRE: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, PRE, 2); + envelope.pre = (LWO::PrePostBehaviour)GetU2(); + break; + + // postcondition + case AI_LWO_POST: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, POST, 2); + envelope.post = (LWO::PrePostBehaviour)GetU2(); + break; + + // keyframe + case AI_LWO_KEY: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, KEY, 8); + + envelope.keys.push_back(LWO::Key()); + LWO::Key &key = envelope.keys.back(); + + key.time = GetF4(); + key.value = GetF4(); + break; + } + + // interval interpolation + case AI_LWO_SPAN: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SPAN, 4); + if (envelope.keys.size() < 2) + ASSIMP_LOG_WARN("LWO2: Unexpected SPAN chunk"); + else { + LWO::Key &key = envelope.keys.back(); + switch (GetU4()) { + case AI_LWO_STEP: + key.inter = LWO::IT_STEP; + break; + case AI_LWO_LINE: + key.inter = LWO::IT_LINE; + break; + case AI_LWO_TCB: + key.inter = LWO::IT_TCB; + break; + case AI_LWO_HERM: + key.inter = LWO::IT_HERM; + break; + case AI_LWO_BEZI: + key.inter = LWO::IT_BEZI; + break; + case AI_LWO_BEZ2: + key.inter = LWO::IT_BEZ2; + break; + default: + ASSIMP_LOG_WARN("LWO2: Unknown interval interpolation mode"); + }; + + // todo ... read params + } + break; + } + + default: + ASSIMP_LOG_WARN("LWO2: Encountered unknown ENVL subchunk"); + break; + } + // regardless how much we did actually read, go to the next chunk + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +// Load file - master function +void LWOImporter::LoadLWO2File() { + bool skip = false; + + LE_NCONST uint8_t *const end = mFileBuffer + fileSize; + unsigned int iUnnamed = 0; + while (true) { + if (mFileBuffer + sizeof(IFF::ChunkHeader) > end) break; + const IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) { + throw DeadlyImportError("LWO2: Chunk length points behind the file"); + break; + } + uint8_t *const next = mFileBuffer + head.length; + + if (!head.length) { + mFileBuffer = next; + continue; + } + + switch (head.type) { + // new layer + case AI_LWO_LAYR: { + // add a new layer to the list .... + mLayers->push_back(LWO::Layer()); + LWO::Layer &layer = mLayers->back(); + mCurLayer = &layer; + + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, LAYR, 16); + + // layer index. + layer.mIndex = GetU2(); + + // Continue loading this layer or ignore it? Check the layer index property + if (UINT_MAX != configLayerIndex && (configLayerIndex - 1) != layer.mIndex) { + skip = true; + } else + skip = false; + + // pivot point + mFileBuffer += 2; /* unknown */ + mCurLayer->mPivot.x = GetF4(); + mCurLayer->mPivot.y = GetF4(); + mCurLayer->mPivot.z = GetF4(); + GetS0(layer.mName, head.length - 16); + + // if the name is empty, generate a default name + if (layer.mName.empty()) { + char buffer[128]; // should be sufficiently large + ::ai_snprintf(buffer, 128, "Layer_%i", iUnnamed++); + layer.mName = buffer; + } + + // load this layer or ignore it? Check the layer name property + if (configLayerName.length() && configLayerName != layer.mName) { + skip = true; + } else + hasNamedLayer = true; + + // optional: parent of this layer + if (mFileBuffer + 2 <= next) + layer.mParent = GetU2(); + else + layer.mParent = (uint16_t) -1; + + // Set layer skip parameter + layer.skip = skip; + + break; + } + + // vertex list + case AI_LWO_PNTS: { + if (skip) + break; + + unsigned int old = (unsigned int)mCurLayer->mTempPoints.size(); + LoadLWOPoints(head.length); + mCurLayer->mPointIDXOfs = old; + break; + } + // vertex tags + case AI_LWO_VMAD: + if (mCurLayer->mFaces.empty()) { + ASSIMP_LOG_WARN("LWO2: Unexpected VMAD chunk"); + break; + } + // --- intentionally no break here + case AI_LWO_VMAP: { + if (skip) + break; + + if (mCurLayer->mTempPoints.empty()) + ASSIMP_LOG_WARN("LWO2: Unexpected VMAP chunk"); + else + LoadLWO2VertexMap(head.length, head.type == AI_LWO_VMAD); + break; + } + // face list + case AI_LWO_POLS: { + if (skip) + break; + + unsigned int old = (unsigned int)mCurLayer->mFaces.size(); + LoadLWO2Polygons(head.length); + mCurLayer->mFaceIDXOfs = old; + break; + } + // polygon tags + case AI_LWO_PTAG: { + if (skip) + break; + + if (mCurLayer->mFaces.empty()) { + ASSIMP_LOG_WARN("LWO2: Unexpected PTAG"); + } else { + LoadLWO2PolygonTags(head.length); + } + break; + } + // list of tags + case AI_LWO_TAGS: { + if (!mTags->empty()) { + ASSIMP_LOG_WARN("LWO2: SRFS chunk encountered twice"); + } else { + LoadLWOTags(head.length); + } + break; + } + + // surface chunk + case AI_LWO_SURF: { + LoadLWO2Surface(head.length); + break; + } + + // clip chunk + case AI_LWO_CLIP: { + LoadLWO2Clip(head.length); + break; + } + + // envelope chunk + case AI_LWO_ENVL: { + LoadLWO2Envelope(head.length); + break; + } + } + mFileBuffer = next; + } +} + +#endif // !! ASSIMP_BUILD_NO_LWO_IMPORTER diff --git a/libs/assimp/code/AssetLib/LWO/LWOLoader.h b/libs/assimp/code/AssetLib/LWO/LWOLoader.h new file mode 100644 index 0000000..f3add53 --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOLoader.h @@ -0,0 +1,468 @@ +/* +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 Declaration of the LWO importer class. */ +#pragma once +#ifndef AI_LWOLOADER_H_INCLUDED +#define AI_LWOLOADER_H_INCLUDED + +#include "LWOFileData.h" +#include <assimp/BaseImporter.h> +#include <assimp/material.h> +#include <assimp/DefaultLogger.hpp> + +#include <map> + +struct aiTexture; +struct aiNode; +struct aiMaterial; + +namespace Assimp { +using namespace LWO; + +// --------------------------------------------------------------------------- +/** Class to load LWO files. + * + * @note Methods named "xxxLWO2[xxx]" are used with the newer LWO2 format. + * Methods named "xxxLWOB[xxx]" are used with the older LWOB format. + * Methods named "xxxLWO[xxx]" are used with both formats. + * Methods named "xxx" are used to preprocess the loaded data - + * they aren't specific to one format version +*/ +// --------------------------------------------------------------------------- +class LWOImporter : public BaseImporter { +public: + LWOImporter(); + ~LWOImporter() override; + + // ------------------------------------------------------------------- + /** Returns whether the class can handle the format of the given file. + * See BaseImporter::CanRead() for details. + */ + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, + bool checkSig) const override; + + // ------------------------------------------------------------------- + /** Called prior to ReadFile(). + * The function is a request to the importer to update its configuration + * basing on the Importer's configuration property list. + */ + void SetupProperties(const Importer *pImp) override; + +protected: + // ------------------------------------------------------------------- + // Get list of supported extensions + const aiImporterDesc *GetInfo() const override; + + // ------------------------------------------------------------------- + /** Imports the given file into the given scene structure. + * See BaseImporter::InternReadFile() for details + */ + void InternReadFile(const std::string &pFile, aiScene *pScene, + IOSystem *pIOHandler) override; + +private: + // ------------------------------------------------------------------- + /** Loads a LWO file in the older LWOB format (LW < 6) + */ + void LoadLWOBFile(); + + // ------------------------------------------------------------------- + /** Loads a LWO file in the newer LWO2 format (LW >= 6) + */ + void LoadLWO2File(); + + // ------------------------------------------------------------------- + /** Parsing functions used for all file format versions + */ + inline void GetS0(std::string &out, unsigned int max); + inline float GetF4(); + inline uint32_t GetU4(); + inline uint16_t GetU2(); + inline uint8_t GetU1(); + + // ------------------------------------------------------------------- + /** Loads a surface chunk from an LWOB file + * @param size Maximum size to be read, in bytes. + */ + void LoadLWOBSurface(unsigned int size); + + // ------------------------------------------------------------------- + /** Loads a surface chunk from an LWO2 file + * @param size Maximum size to be read, in bytes. + */ + void LoadLWO2Surface(unsigned int size); + + // ------------------------------------------------------------------- + /** Loads a texture block from a LWO2 file. + * @param size Maximum size to be read, in bytes. + * @param head Header of the SUF.BLOK header + */ + void LoadLWO2TextureBlock(LE_NCONST IFF::SubChunkHeader *head, + unsigned int size); + + // ------------------------------------------------------------------- + /** Loads a shader block from a LWO2 file. + * @param size Maximum size to be read, in bytes. + * @param head Header of the SUF.BLOK header + */ + void LoadLWO2ShaderBlock(LE_NCONST IFF::SubChunkHeader *head, + unsigned int size); + + // ------------------------------------------------------------------- + /** Loads an image map from a LWO2 file + * @param size Maximum size to be read, in bytes. + * @param tex Texture object to be filled + */ + void LoadLWO2ImageMap(unsigned int size, LWO::Texture &tex); + void LoadLWO2Gradient(unsigned int size, LWO::Texture &tex); + void LoadLWO2Procedural(unsigned int size, LWO::Texture &tex); + + // loads the header - used by thethree functions above + void LoadLWO2TextureHeader(unsigned int size, LWO::Texture &tex); + + // ------------------------------------------------------------------- + /** Loads the LWO tag list from the file + * @param size Maximum size to be read, in bytes. + */ + void LoadLWOTags(unsigned int size); + + // ------------------------------------------------------------------- + /** Load polygons from a POLS chunk + * @param length Size of the chunk + */ + void LoadLWO2Polygons(unsigned int length); + void LoadLWOBPolygons(unsigned int length); + + // ------------------------------------------------------------------- + /** Load polygon tags from a PTAG chunk + * @param length Size of the chunk + */ + void LoadLWO2PolygonTags(unsigned int length); + + // ------------------------------------------------------------------- + /** Load a vertex map from a VMAP/VMAD chunk + * @param length Size of the chunk + * @param perPoly Operate on per-polygon base? + */ + void LoadLWO2VertexMap(unsigned int length, bool perPoly); + + // ------------------------------------------------------------------- + /** Load polygons from a PNTS chunk + * @param length Size of the chunk + */ + void LoadLWOPoints(unsigned int length); + + // ------------------------------------------------------------------- + /** Load a clip from a CLIP chunk + * @param length Size of the chunk + */ + void LoadLWO2Clip(unsigned int length); + + // ------------------------------------------------------------------- + /** Load an envelope from an EVL chunk + * @param length Size of the chunk + */ + void LoadLWO2Envelope(unsigned int length); + + // ------------------------------------------------------------------- + /** Count vertices and faces in a LWOB/LWO2 file + */ + void CountVertsAndFacesLWO2(unsigned int &verts, + unsigned int &faces, + uint16_t *&cursor, + const uint16_t *const end, + unsigned int max = UINT_MAX); + + void CountVertsAndFacesLWOB(unsigned int &verts, + unsigned int &faces, + LE_NCONST uint16_t *&cursor, + const uint16_t *const end, + unsigned int max = UINT_MAX); + + // ------------------------------------------------------------------- + /** Read vertices and faces in a LWOB/LWO2 file + */ + void CopyFaceIndicesLWO2(LWO::FaceList::iterator &it, + uint16_t *&cursor, + const uint16_t *const end); + + // ------------------------------------------------------------------- + void CopyFaceIndicesLWOB(LWO::FaceList::iterator &it, + LE_NCONST uint16_t *&cursor, + const uint16_t *const end, + unsigned int max = UINT_MAX); + + // ------------------------------------------------------------------- + /** Resolve the tag and surface lists that have been loaded. + * Generates the mMapping table. + */ + void ResolveTags(); + + // ------------------------------------------------------------------- + /** Resolve the clip list that has been loaded. + * Replaces clip references with real clips. + */ + void ResolveClips(); + + // ------------------------------------------------------------------- + /** Add a texture list to an output material description. + * + * @param pcMat Output material + * @param in Input texture list + * @param type Type identifier of the texture list + */ + bool HandleTextures(aiMaterial *pcMat, const TextureList &in, + aiTextureType type); + + // ------------------------------------------------------------------- + /** Adjust a texture path + */ + void AdjustTexturePath(std::string &out); + + // ------------------------------------------------------------------- + /** Convert a LWO surface description to an ASSIMP material + */ + void ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat); + + // ------------------------------------------------------------------- + /** Get a list of all UV/VC channels required by a specific surface. + * + * @param surf Working surface + * @param layer Working layer + * @param out Output list. The members are indices into the + * UV/VC channel lists of the layer + */ + void FindUVChannels(/*const*/ LWO::Surface &surf, + LWO::SortedRep &sorted, + /*const*/ LWO::Layer &layer, + unsigned int out[AI_MAX_NUMBER_OF_TEXTURECOORDS]); + + // ------------------------------------------------------------------- + char FindUVChannels(LWO::TextureList &list, + LWO::Layer &layer, LWO::UVChannel &uv, unsigned int next); + + // ------------------------------------------------------------------- + void FindVCChannels(const LWO::Surface &surf, + LWO::SortedRep &sorted, + const LWO::Layer &layer, + unsigned int out[AI_MAX_NUMBER_OF_COLOR_SETS]); + + // ------------------------------------------------------------------- + /** Generate the final node graph + * Unused nodes are deleted. + * @param apcNodes Flat list of nodes + */ + void GenerateNodeGraph(std::map<uint16_t, aiNode *> &apcNodes); + + // ------------------------------------------------------------------- + /** Add children to a node + * @param node Node to become a father + * @param parent Index of the node + * @param apcNodes Flat list of nodes - used nodes are set to nullptr. + */ + void AddChildren(aiNode *node, uint16_t parent, + std::vector<aiNode *> &apcNodes); + + // ------------------------------------------------------------------- + /** Read a variable sized integer + * @param inout Input and output buffer + */ + int ReadVSizedIntLWO2(uint8_t *&inout); + + // ------------------------------------------------------------------- + /** Assign a value from a VMAP to a vertex and all vertices + * attached to it. + * @param base VMAP destination data + * @param numRead Number of float's to be read + * @param idx Absolute index of the first vertex + * @param data Value of the VMAP to be assigned - read numRead + * floats from this array. + */ + void DoRecursiveVMAPAssignment(VMapEntry *base, unsigned int numRead, + unsigned int idx, float *data); + + // ------------------------------------------------------------------- + /** Compute normal vectors for a mesh + * @param mesh Input mesh + * @param smoothingGroups Smoothing-groups-per-face array + * @param surface Surface for the mesh + */ + void ComputeNormals(aiMesh *mesh, const std::vector<unsigned int> &smoothingGroups, + const LWO::Surface &surface); + + // ------------------------------------------------------------------- + /** Setup a new texture after the corresponding chunk was + * encountered in the file. + * @param list Texture list + * @param size Maximum number of bytes to be read + * @return Pointer to new texture + */ + LWO::Texture *SetupNewTextureLWOB(LWO::TextureList &list, + unsigned int size); + +protected: + /** true if the file is a LWO2 file*/ + bool mIsLWO2; + + /** true if the file is a LXOB file*/ + bool mIsLXOB; + + /** Temporary list of layers from the file */ + LayerList *mLayers; + + /** Pointer to the current layer */ + LWO::Layer *mCurLayer; + + /** Temporary tag list from the file */ + TagList *mTags; + + /** Mapping table to convert from tag to surface indices. + UINT_MAX indicates that a no corresponding surface is available */ + TagMappingTable *mMapping; + + /** Temporary surface list from the file */ + SurfaceList *mSurfaces; + + /** Temporary clip list from the file */ + ClipList mClips; + + /** Temporary envelope list from the file */ + EnvelopeList mEnvelopes; + + /** file buffer */ + uint8_t *mFileBuffer; + + /** Size of the file, in bytes */ + unsigned int fileSize; + + /** Output scene */ + aiScene *mScene; + + /** Configuration option: speed flag set? */ + bool configSpeedFlag; + + /** Configuration option: index of layer to be loaded */ + unsigned int configLayerIndex; + + /** Configuration option: name of layer to be loaded */ + std::string configLayerName; + + /** True if we have a named layer */ + bool hasNamedLayer; +}; + +// ------------------------------------------------------------------------------------------------ +inline float LWOImporter::GetF4() { + float f; + ::memcpy(&f, mFileBuffer, 4); + mFileBuffer += 4; + AI_LSWAP4(f); + return f; +} + +// ------------------------------------------------------------------------------------------------ +inline uint32_t LWOImporter::GetU4() { + uint32_t f; + ::memcpy(&f, mFileBuffer, 4); + mFileBuffer += 4; + AI_LSWAP4(f); + return f; +} + +// ------------------------------------------------------------------------------------------------ +inline uint16_t LWOImporter::GetU2() { + uint16_t f; + ::memcpy(&f, mFileBuffer, 2); + mFileBuffer += 2; + AI_LSWAP2(f); + return f; +} + +// ------------------------------------------------------------------------------------------------ +inline uint8_t LWOImporter::GetU1() { + return *mFileBuffer++; +} + +// ------------------------------------------------------------------------------------------------ +inline int LWOImporter::ReadVSizedIntLWO2(uint8_t *&inout) { + int i; + int c = *inout; + inout++; + if (c != 0xFF) { + i = c << 8; + c = *inout; + inout++; + i |= c; + } else { + c = *inout; + inout++; + i = c << 16; + c = *inout; + inout++; + i |= c << 8; + c = *inout; + inout++; + i |= c; + } + return i; +} + +// ------------------------------------------------------------------------------------------------ +inline void LWOImporter::GetS0(std::string &out, unsigned int max) { + unsigned int iCursor = 0; + const char *sz = (const char *)mFileBuffer; + while (*mFileBuffer) { + if (++iCursor > max) { + ASSIMP_LOG_WARN("LWO: Invalid file, string is is too long"); + break; + } + ++mFileBuffer; + } + size_t len = (size_t)((const char *)mFileBuffer - sz); + out = std::string(sz, len); + mFileBuffer += (len & 0x1 ? 1 : 2); +} + +} // end of namespace Assimp + +#endif // AI_LWOIMPORTER_H_INCLUDED diff --git a/libs/assimp/code/AssetLib/LWO/LWOMaterial.cpp b/libs/assimp/code/AssetLib/LWO/LWOMaterial.cpp new file mode 100644 index 0000000..b6f0bcc --- /dev/null +++ b/libs/assimp/code/AssetLib/LWO/LWOMaterial.cpp @@ -0,0 +1,844 @@ +/* +--------------------------------------------------------------------------- +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 Implementation of the material oart of the LWO importer class */ + +#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER + +// internal headers +#include "LWOLoader.h" +#include <assimp/ByteSwapper.h> + +using namespace Assimp; + +// ------------------------------------------------------------------------------------------------ +template <class T> +T lerp(const T &one, const T &two, float val) { + return one + (two - one) * val; +} + +// ------------------------------------------------------------------------------------------------ +// Convert a lightwave mapping mode to our's +inline aiTextureMapMode GetMapMode(LWO::Texture::Wrap in) { + switch (in) { + case LWO::Texture::REPEAT: + return aiTextureMapMode_Wrap; + + case LWO::Texture::MIRROR: + return aiTextureMapMode_Mirror; + + case LWO::Texture::RESET: + ASSIMP_LOG_WARN("LWO2: Unsupported texture map mode: RESET"); + + // fall though here + case LWO::Texture::EDGE: + return aiTextureMapMode_Clamp; + } + return (aiTextureMapMode)0; +} + +// ------------------------------------------------------------------------------------------------ +bool LWOImporter::HandleTextures(aiMaterial *pcMat, const TextureList &in, aiTextureType type) { + ai_assert(nullptr != pcMat); + + unsigned int cur = 0, temp = 0; + aiString s; + bool ret = false; + + for (const auto &texture : in) { + if (!texture.enabled || !texture.bCanUse) + continue; + ret = true; + + // Convert lightwave's mapping modes to ours. We let them + // as they are, the GenUVcoords step will compute UV + // channels if they're not there. + + aiTextureMapping mapping = aiTextureMapping_OTHER; + switch (texture.mapMode) { + case LWO::Texture::Planar: + mapping = aiTextureMapping_PLANE; + break; + case LWO::Texture::Cylindrical: + mapping = aiTextureMapping_CYLINDER; + break; + case LWO::Texture::Spherical: + mapping = aiTextureMapping_SPHERE; + break; + case LWO::Texture::Cubic: + mapping = aiTextureMapping_BOX; + break; + case LWO::Texture::FrontProjection: + ASSIMP_LOG_ERROR("LWO2: Unsupported texture mapping: FrontProjection"); + mapping = aiTextureMapping_OTHER; + break; + case LWO::Texture::UV: { + if (UINT_MAX == texture.mRealUVIndex) { + // We have no UV index for this texture, so we can't display it + continue; + } + + // add the UV source index + temp = texture.mRealUVIndex; + pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_UVWSRC(type, cur)); + + mapping = aiTextureMapping_UV; + } break; + default: + ai_assert(false); + }; + + if (mapping != aiTextureMapping_UV) { + // Setup the main axis + aiVector3D v; + switch (texture.majorAxis) { + case Texture::AXIS_X: + v = aiVector3D(1.0, 0.0, 0.0); + break; + case Texture::AXIS_Y: + v = aiVector3D(0.0, 1.0, 0.0); + break; + default: // case Texture::AXIS_Z: + v = aiVector3D(0.0, 0.0, 1.0); + break; + } + + pcMat->AddProperty(&v, 1, AI_MATKEY_TEXMAP_AXIS(type, cur)); + + // Setup UV scalings for cylindric and spherical projections + if (mapping == aiTextureMapping_CYLINDER || mapping == aiTextureMapping_SPHERE) { + aiUVTransform trafo; + trafo.mScaling.x = texture.wrapAmountW; + trafo.mScaling.y = texture.wrapAmountH; + + static_assert(sizeof(aiUVTransform) / sizeof(ai_real) == 5, "sizeof(aiUVTransform)/sizeof(ai_real) == 5"); + pcMat->AddProperty(&trafo, 1, AI_MATKEY_UVTRANSFORM(type, cur)); + } + ASSIMP_LOG_VERBOSE_DEBUG("LWO2: Setting up non-UV mapping"); + } + + // The older LWOB format does not use indirect references to clips. + // The file name of a texture is directly specified in the tex chunk. + if (mIsLWO2) { + // find the corresponding clip (take the last one if multiple + // share the same index) + ClipList::iterator end = mClips.end(), candidate = end; + temp = texture.mClipIdx; + for (ClipList::iterator clip = mClips.begin(); clip != end; ++clip) { + if ((*clip).idx == temp) { + candidate = clip; + } + } + if (candidate == end) { + ASSIMP_LOG_ERROR("LWO2: Clip index is out of bounds"); + temp = 0; + + // fixme: apparently some LWO files shipping with Doom3 don't + // have clips at all ... check whether that's true or whether + // it's a bug in the loader. + + s.Set("$texture.png"); + + //continue; + } else { + if (Clip::UNSUPPORTED == (*candidate).type) { + ASSIMP_LOG_ERROR("LWO2: Clip type is not supported"); + continue; + } + AdjustTexturePath((*candidate).path); + s.Set((*candidate).path); + + // Additional image settings + int flags = 0; + if ((*candidate).negate) { + flags |= aiTextureFlags_Invert; + } + pcMat->AddProperty(&flags, 1, AI_MATKEY_TEXFLAGS(type, cur)); + } + } else { + std::string ss = texture.mFileName; + if (!ss.length()) { + ASSIMP_LOG_WARN("LWOB: Empty file name"); + continue; + } + AdjustTexturePath(ss); + s.Set(ss); + } + pcMat->AddProperty(&s, AI_MATKEY_TEXTURE(type, cur)); + + // add the blend factor + pcMat->AddProperty<float>(&texture.mStrength, 1, AI_MATKEY_TEXBLEND(type, cur)); + + // add the blend operation + switch (texture.blendType) { + case LWO::Texture::Normal: + case LWO::Texture::Multiply: + temp = (unsigned int)aiTextureOp_Multiply; + break; + + case LWO::Texture::Subtractive: + case LWO::Texture::Difference: + temp = (unsigned int)aiTextureOp_Subtract; + break; + + case LWO::Texture::Divide: + temp = (unsigned int)aiTextureOp_Divide; + break; + + case LWO::Texture::Additive: + temp = (unsigned int)aiTextureOp_Add; + break; + + default: + temp = (unsigned int)aiTextureOp_Multiply; + ASSIMP_LOG_WARN("LWO2: Unsupported texture blend mode: alpha or displacement"); + } + // Setup texture operation + pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_TEXOP(type, cur)); + + // setup the mapping mode + int mapping_ = static_cast<int>(mapping); + pcMat->AddProperty<int>(&mapping_, 1, AI_MATKEY_MAPPING(type, cur)); + + // add the u-wrapping + temp = (unsigned int)GetMapMode(texture.wrapModeWidth); + pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_MAPPINGMODE_U(type, cur)); + + // add the v-wrapping + temp = (unsigned int)GetMapMode(texture.wrapModeHeight); + pcMat->AddProperty<int>((int *)&temp, 1, AI_MATKEY_MAPPINGMODE_V(type, cur)); + + ++cur; + } + return ret; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat) { + // copy the name of the surface + aiString st; + st.Set(surf.mName); + pcMat->AddProperty(&st, AI_MATKEY_NAME); + + const int i = surf.bDoubleSided ? 1 : 0; + pcMat->AddProperty(&i, 1, AI_MATKEY_TWOSIDED); + + // add the refraction index and the bump intensity + pcMat->AddProperty(&surf.mIOR, 1, AI_MATKEY_REFRACTI); + pcMat->AddProperty(&surf.mBumpIntensity, 1, AI_MATKEY_BUMPSCALING); + + aiShadingMode m; + if (surf.mSpecularValue && surf.mGlossiness) { + float fGloss; + if (mIsLWO2) { + fGloss = std::pow(surf.mGlossiness * ai_real(10.0) + ai_real(2.0), ai_real(2.0)); + } else { + if (16.0 >= surf.mGlossiness) + fGloss = 6.0; + else if (64.0 >= surf.mGlossiness) + fGloss = 20.0; + else if (256.0 >= surf.mGlossiness) + fGloss = 50.0; + else + fGloss = 80.0; + } + + pcMat->AddProperty(&surf.mSpecularValue, 1, AI_MATKEY_SHININESS_STRENGTH); + pcMat->AddProperty(&fGloss, 1, AI_MATKEY_SHININESS); + m = aiShadingMode_Phong; + } else + m = aiShadingMode_Gouraud; + + // specular color + aiColor3D clr = lerp(aiColor3D(1.0, 1.0, 1.0), surf.mColor, surf.mColorHighlights); + pcMat->AddProperty(&clr, 1, AI_MATKEY_COLOR_SPECULAR); + pcMat->AddProperty(&surf.mSpecularValue, 1, AI_MATKEY_SHININESS_STRENGTH); + + // emissive color + // luminosity is not really the same but it affects the surface in a similar way. Some scaling looks good. + clr.g = clr.b = clr.r = surf.mLuminosity * ai_real(0.8); + pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_EMISSIVE); + + // opacity ... either additive or default-blended, please + if (0.0 != surf.mAdditiveTransparency) { + const int add = aiBlendMode_Additive; + pcMat->AddProperty(&surf.mAdditiveTransparency, 1, AI_MATKEY_OPACITY); + pcMat->AddProperty(&add, 1, AI_MATKEY_BLEND_FUNC); + } else if (10e10f != surf.mTransparency) { + const int def = aiBlendMode_Default; + const float f = 1.0f - surf.mTransparency; + pcMat->AddProperty(&f, 1, AI_MATKEY_OPACITY); + pcMat->AddProperty(&def, 1, AI_MATKEY_BLEND_FUNC); + } + + // ADD TEXTURES to the material + // TODO: find out how we can handle COLOR textures correctly... + bool b = HandleTextures(pcMat, surf.mColorTextures, aiTextureType_DIFFUSE); + b = (b || HandleTextures(pcMat, surf.mDiffuseTextures, aiTextureType_DIFFUSE)); + HandleTextures(pcMat, surf.mSpecularTextures, aiTextureType_SPECULAR); + HandleTextures(pcMat, surf.mGlossinessTextures, aiTextureType_SHININESS); + HandleTextures(pcMat, surf.mBumpTextures, aiTextureType_HEIGHT); + HandleTextures(pcMat, surf.mOpacityTextures, aiTextureType_OPACITY); + HandleTextures(pcMat, surf.mReflectionTextures, aiTextureType_REFLECTION); + + // Now we need to know which shader to use .. iterate through the shader list of + // the surface and search for a name which we know ... + for (const auto &shader : surf.mShaders) { + if (shader.functionName == "LW_SuperCelShader" || shader.functionName == "AH_CelShader") { + ASSIMP_LOG_INFO("LWO2: Mapping LW_SuperCelShader/AH_CelShader to aiShadingMode_Toon"); + + m = aiShadingMode_Toon; + break; + } else if (shader.functionName == "LW_RealFresnel" || shader.functionName == "LW_FastFresnel") { + ASSIMP_LOG_INFO("LWO2: Mapping LW_RealFresnel/LW_FastFresnel to aiShadingMode_Fresnel"); + + m = aiShadingMode_Fresnel; + break; + } else { + ASSIMP_LOG_WARN("LWO2: Unknown surface shader: ", shader.functionName); + } + } + if (surf.mMaximumSmoothAngle <= 0.0) + m = aiShadingMode_Flat; + int m_ = static_cast<int>(m); + pcMat->AddProperty(&m_, 1, AI_MATKEY_SHADING_MODEL); + + // (the diffuse value is just a scaling factor) + // If a diffuse texture is set, we set this value to 1.0 + clr = (b && false ? aiColor3D(1.0, 1.0, 1.0) : surf.mColor); + clr.r *= surf.mDiffuseValue; + clr.g *= surf.mDiffuseValue; + clr.b *= surf.mDiffuseValue; + pcMat->AddProperty<aiColor3D>(&clr, 1, AI_MATKEY_COLOR_DIFFUSE); +} + +// ------------------------------------------------------------------------------------------------ +char LWOImporter::FindUVChannels(LWO::TextureList &list, + LWO::Layer & /*layer*/, LWO::UVChannel &uv, unsigned int next) { + char ret = 0; + for (auto &texture : list) { + + // Ignore textures with non-UV mappings for the moment. + if (!texture.enabled || !texture.bCanUse || texture.mapMode != LWO::Texture::UV) { + continue; + } + + if (texture.mUVChannelIndex == uv.name) { + ret = 1; + + // got it. + if (texture.mRealUVIndex == UINT_MAX || texture.mRealUVIndex == next) { + texture.mRealUVIndex = next; + } else { + // channel mismatch. need to duplicate the material. + ASSIMP_LOG_WARN("LWO: Channel mismatch, would need to duplicate surface [design bug]"); + + // TODO + } + } + } + return ret; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::FindUVChannels(LWO::Surface &surf, + LWO::SortedRep &sorted, LWO::Layer &layer, + unsigned int out[AI_MAX_NUMBER_OF_TEXTURECOORDS]) { + unsigned int next = 0, extra = 0, num_extra = 0; + + // Check whether we have an UV entry != 0 for one of the faces in 'sorted' + for (unsigned int i = 0; i < layer.mUVChannels.size(); ++i) { + LWO::UVChannel &uv = layer.mUVChannels[i]; + + for (LWO::SortedRep::const_iterator it = sorted.begin(); it != sorted.end(); ++it) { + + LWO::Face &face = layer.mFaces[*it]; + + for (unsigned int n = 0; n < face.mNumIndices; ++n) { + unsigned int idx = face.mIndices[n]; + + if (uv.abAssigned[idx] && ((aiVector2D *)&uv.rawData[0])[idx] != aiVector2D()) { + + if (extra >= AI_MAX_NUMBER_OF_TEXTURECOORDS) { + + ASSIMP_LOG_ERROR("LWO: Maximum number of UV channels for " + "this mesh reached. Skipping channel \'" + + uv.name + "\'"); + + } else { + // Search through all textures assigned to 'surf' and look for this UV channel + char had = 0; + had |= FindUVChannels(surf.mColorTextures, layer, uv, next); + had |= FindUVChannels(surf.mDiffuseTextures, layer, uv, next); + had |= FindUVChannels(surf.mSpecularTextures, layer, uv, next); + had |= FindUVChannels(surf.mGlossinessTextures, layer, uv, next); + had |= FindUVChannels(surf.mOpacityTextures, layer, uv, next); + had |= FindUVChannels(surf.mBumpTextures, layer, uv, next); + had |= FindUVChannels(surf.mReflectionTextures, layer, uv, next); + + // We have a texture referencing this UV channel so we have to take special care + // and are willing to drop unreferenced channels in favour of it. + if (had != 0) { + if (num_extra) { + + for (unsigned int a = next; a < std::min(extra, AI_MAX_NUMBER_OF_TEXTURECOORDS - 1u); ++a) { + out[a + 1] = out[a]; + } + } + ++extra; + out[next++] = i; + } + // Bah ... seems not to be used at all. Push to end if enough space is available. + else { + out[extra++] = i; + ++num_extra; + } + } + it = sorted.end() - 1; + break; + } + } + } + } + if (extra < AI_MAX_NUMBER_OF_TEXTURECOORDS) { + out[extra] = UINT_MAX; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::FindVCChannels(const LWO::Surface &surf, LWO::SortedRep &sorted, const LWO::Layer &layer, + unsigned int out[AI_MAX_NUMBER_OF_COLOR_SETS]) { + unsigned int next = 0; + + // Check whether we have an vc entry != 0 for one of the faces in 'sorted' + for (unsigned int i = 0; i < layer.mVColorChannels.size(); ++i) { + const LWO::VColorChannel &vc = layer.mVColorChannels[i]; + + if (surf.mVCMap == vc.name) { + // The vertex color map is explicitly requested by the surface so we need to take special care of it + for (unsigned int a = 0; a < std::min(next, AI_MAX_NUMBER_OF_COLOR_SETS - 1u); ++a) { + out[a + 1] = out[a]; + } + out[0] = i; + ++next; + } else { + + for (LWO::SortedRep::iterator it = sorted.begin(); it != sorted.end(); ++it) { + const LWO::Face &face = layer.mFaces[*it]; + + for (unsigned int n = 0; n < face.mNumIndices; ++n) { + unsigned int idx = face.mIndices[n]; + + if (vc.abAssigned[idx] && ((aiColor4D *)&vc.rawData[0])[idx] != aiColor4D(0.0, 0.0, 0.0, 1.0)) { + if (next >= AI_MAX_NUMBER_OF_COLOR_SETS) { + + ASSIMP_LOG_ERROR("LWO: Maximum number of vertex color channels for " + "this mesh reached. Skipping channel \'" + + vc.name + "\'"); + + } else { + out[next++] = i; + } + it = sorted.end() - 1; + break; + } + } + } + } + } + if (next != AI_MAX_NUMBER_OF_COLOR_SETS) { + out[next] = UINT_MAX; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2ImageMap(unsigned int size, LWO::Texture &tex) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + while (true) { + if (mFileBuffer + 6 >= end) break; + LE_NCONST IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid SURF.BLOCK chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + case AI_LWO_PROJ: + tex.mapMode = (Texture::MappingMode)GetU2(); + break; + case AI_LWO_WRAP: + tex.wrapModeWidth = (Texture::Wrap)GetU2(); + tex.wrapModeHeight = (Texture::Wrap)GetU2(); + break; + case AI_LWO_AXIS: + tex.majorAxis = (Texture::Axes)GetU2(); + break; + case AI_LWO_IMAG: + tex.mClipIdx = GetU2(); + break; + case AI_LWO_VMAP: + GetS0(tex.mUVChannelIndex, head.length); + break; + case AI_LWO_WRPH: + tex.wrapAmountH = GetF4(); + break; + case AI_LWO_WRPW: + tex.wrapAmountW = GetF4(); + break; + } + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2Procedural(unsigned int /*size*/, LWO::Texture &tex) { + // --- not supported at the moment + ASSIMP_LOG_ERROR("LWO2: Found procedural texture, this is not supported"); + tex.bCanUse = false; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2Gradient(unsigned int /*size*/, LWO::Texture &tex) { + // --- not supported at the moment + ASSIMP_LOG_ERROR("LWO2: Found gradient texture, this is not supported"); + tex.bCanUse = false; +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2TextureHeader(unsigned int size, LWO::Texture &tex) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + // get the ordinal string + GetS0(tex.ordinal, size); + + // we could crash later if this is an empty string ... + if (!tex.ordinal.length()) { + ASSIMP_LOG_ERROR("LWO2: Ill-formed SURF.BLOK ordinal string"); + tex.ordinal = "\x00"; + } + while (true) { + if (mFileBuffer + 6 >= end) break; + const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid texture header chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + case AI_LWO_CHAN: + tex.type = GetU4(); + break; + case AI_LWO_ENAB: + tex.enabled = GetU2() ? true : false; + break; + case AI_LWO_OPAC: + tex.blendType = (Texture::BlendType)GetU2(); + tex.mStrength = GetF4(); + break; + } + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2TextureBlock(LE_NCONST IFF::SubChunkHeader *head, unsigned int size) { + ai_assert(!mSurfaces->empty()); + LWO::Surface &surf = mSurfaces->back(); + LWO::Texture tex; + + // load the texture header + LoadLWO2TextureHeader(head->length, tex); + size -= head->length + 6; + + // now get the exact type of the texture + switch (head->type) { + case AI_LWO_PROC: + LoadLWO2Procedural(size, tex); + break; + case AI_LWO_GRAD: + LoadLWO2Gradient(size, tex); + break; + case AI_LWO_IMAP: + LoadLWO2ImageMap(size, tex); + } + + // get the destination channel + TextureList *listRef = nullptr; + switch (tex.type) { + case AI_LWO_COLR: + listRef = &surf.mColorTextures; + break; + case AI_LWO_DIFF: + listRef = &surf.mDiffuseTextures; + break; + case AI_LWO_SPEC: + listRef = &surf.mSpecularTextures; + break; + case AI_LWO_GLOS: + listRef = &surf.mGlossinessTextures; + break; + case AI_LWO_BUMP: + listRef = &surf.mBumpTextures; + break; + case AI_LWO_TRAN: + listRef = &surf.mOpacityTextures; + break; + case AI_LWO_REFL: + listRef = &surf.mReflectionTextures; + break; + default: + ASSIMP_LOG_WARN("LWO2: Encountered unknown texture type"); + return; + } + + // now attach the texture to the parent surface - sort by ordinal string + for (TextureList::iterator it = listRef->begin(); it != listRef->end(); ++it) { + if (::strcmp(tex.ordinal.c_str(), (*it).ordinal.c_str()) < 0) { + listRef->insert(it, tex); + return; + } + } + listRef->push_back(tex); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2ShaderBlock(LE_NCONST IFF::SubChunkHeader * /*head*/, unsigned int size) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + ai_assert(!mSurfaces->empty()); + LWO::Surface &surf = mSurfaces->back(); + LWO::Shader shader; + + // get the ordinal string + GetS0(shader.ordinal, size); + + // we could crash later if this is an empty string ... + if (!shader.ordinal.length()) { + ASSIMP_LOG_ERROR("LWO2: Ill-formed SURF.BLOK ordinal string"); + shader.ordinal = "\x00"; + } + + // read the header + while (true) { + if (mFileBuffer + 6 >= end) break; + const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid shader header chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + case AI_LWO_ENAB: + shader.enabled = GetU2() ? true : false; + break; + + case AI_LWO_FUNC: + GetS0(shader.functionName, head.length); + } + mFileBuffer = next; + } + + // now attach the shader to the parent surface - sort by ordinal string + for (ShaderList::iterator it = surf.mShaders.begin(); it != surf.mShaders.end(); ++it) { + if (::strcmp(shader.ordinal.c_str(), (*it).ordinal.c_str()) < 0) { + surf.mShaders.insert(it, shader); + return; + } + } + surf.mShaders.push_back(shader); +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadLWO2Surface(unsigned int size) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + mSurfaces->push_back(LWO::Surface()); + LWO::Surface &surf = mSurfaces->back(); + + GetS0(surf.mName, size); + + // check whether this surface was derived from any other surface + std::string derived; + GetS0(derived, (unsigned int)(end - mFileBuffer)); + if (derived.length()) { + // yes, find this surface + for (SurfaceList::iterator it = mSurfaces->begin(), itEnd = mSurfaces->end() - 1; it != itEnd; ++it) { + if ((*it).mName == derived) { + // we have it ... + surf = *it; + derived.clear(); + break; + } + } + if (derived.size()) { + ASSIMP_LOG_WARN("LWO2: Unable to find source surface: ", derived); + } + } + + while (true) { + if (mFileBuffer + 6 >= end) + break; + const IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO2: Invalid surface chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + // diffuse color + case AI_LWO_COLR: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, COLR, 12); + surf.mColor.r = GetF4(); + surf.mColor.g = GetF4(); + surf.mColor.b = GetF4(); + break; + } + // diffuse strength ... hopefully + case AI_LWO_DIFF: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, DIFF, 4); + surf.mDiffuseValue = GetF4(); + break; + } + // specular strength ... hopefully + case AI_LWO_SPEC: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SPEC, 4); + surf.mSpecularValue = GetF4(); + break; + } + // transparency + case AI_LWO_TRAN: { + // transparency explicitly disabled? + if (surf.mTransparency == 10e10f) + break; + + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, TRAN, 4); + surf.mTransparency = GetF4(); + break; + } + // additive transparency + case AI_LWO_ADTR: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, ADTR, 4); + surf.mAdditiveTransparency = GetF4(); + break; + } + // wireframe mode + case AI_LWO_LINE: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, LINE, 2); + if (GetU2() & 0x1) + surf.mWireframe = true; + break; + } + // glossiness + case AI_LWO_GLOS: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, GLOS, 4); + surf.mGlossiness = GetF4(); + break; + } + // bump intensity + case AI_LWO_BUMP: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, BUMP, 4); + surf.mBumpIntensity = GetF4(); + break; + } + // color highlights + case AI_LWO_CLRH: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, CLRH, 4); + surf.mColorHighlights = GetF4(); + break; + } + // index of refraction + case AI_LWO_RIND: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, RIND, 4); + surf.mIOR = GetF4(); + break; + } + // polygon sidedness + case AI_LWO_SIDE: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SIDE, 2); + surf.bDoubleSided = (3 == GetU2()); + break; + } + // maximum smoothing angle + case AI_LWO_SMAN: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SMAN, 4); + surf.mMaximumSmoothAngle = std::fabs(GetF4()); + break; + } + // vertex color channel to be applied to the surface + case AI_LWO_VCOL: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, VCOL, 12); + surf.mDiffuseValue *= GetF4(); // strength + ReadVSizedIntLWO2(mFileBuffer); // skip envelope + surf.mVCMapType = GetU4(); // type of the channel + + // name of the channel + GetS0(surf.mVCMap, (unsigned int)(next - mFileBuffer)); + break; + } + // surface bock entry + case AI_LWO_BLOK: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, BLOK, 4); + IFF::SubChunkHeader head2 = IFF::LoadSubChunk(mFileBuffer); + + switch (head2.type) { + case AI_LWO_PROC: + case AI_LWO_GRAD: + case AI_LWO_IMAP: + LoadLWO2TextureBlock(&head2, head.length); + break; + case AI_LWO_SHDR: + LoadLWO2ShaderBlock(&head2, head.length); + break; + + default: + ASSIMP_LOG_WARN("LWO2: Found an unsupported surface BLOK"); + }; + + break; + } + } + mFileBuffer = next; + } +} + +#endif // !! ASSIMP_BUILD_NO_X_IMPORTER |