diff options
author | sanine <sanine.not@pm.me> | 2022-04-16 11:55:09 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2022-04-16 11:55:09 -0500 |
commit | db81b925d776103326128bf629cbdda576a223e7 (patch) | |
tree | 58bea8155c686733310009f6bed7363f91fbeb9d /libs/assimp/code/AssetLib/3DS | |
parent | 55860037b14fb3893ba21cf2654c83d349cc1082 (diff) |
move 3rd-party librarys into libs/ and add built-in honeysuckle
Diffstat (limited to 'libs/assimp/code/AssetLib/3DS')
-rw-r--r-- | libs/assimp/code/AssetLib/3DS/3DSConverter.cpp | 807 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/3DS/3DSExporter.cpp | 584 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/3DS/3DSExporter.h | 98 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/3DS/3DSHelper.h | 702 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/3DS/3DSLoader.cpp | 1336 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/3DS/3DSLoader.h | 289 |
6 files changed, 3816 insertions, 0 deletions
diff --git a/libs/assimp/code/AssetLib/3DS/3DSConverter.cpp b/libs/assimp/code/AssetLib/3DS/3DSConverter.cpp new file mode 100644 index 0000000..5a01429 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSConverter.cpp @@ -0,0 +1,807 @@ +/* +--------------------------------------------------------------------------- +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 3ds importer class */ + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +// internal headers +#include "3DSLoader.h" +#include "Common/TargetAnimation.h" +#include <assimp/StringComparison.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <cctype> +#include <memory> + +using namespace Assimp; + +static const unsigned int NotSet = 0xcdcdcdcd; + +// ------------------------------------------------------------------------------------------------ +// Setup final material indices, generae a default material if necessary +void Discreet3DSImporter::ReplaceDefaultMaterial() { + // Try to find an existing material that matches the + // typical default material setting: + // - no textures + // - diffuse color (in grey!) + // NOTE: This is here to workaround the fact that some + // exporters are writing a default material, too. + unsigned int idx(NotSet); + for (unsigned int i = 0; i < mScene->mMaterials.size(); ++i) { + std::string s = mScene->mMaterials[i].mName; + for (char & it : s) { + it = static_cast<char>(::tolower(static_cast<unsigned char>(it))); + } + + if (std::string::npos == s.find("default")) continue; + + if (mScene->mMaterials[i].mDiffuse.r != + mScene->mMaterials[i].mDiffuse.g || + mScene->mMaterials[i].mDiffuse.r != + mScene->mMaterials[i].mDiffuse.b) continue; + + if (ContainsTextures(i)) { + continue; + } + idx = i; + } + if (NotSet == idx) { + idx = (unsigned int)mScene->mMaterials.size(); + } + + // now iterate through all meshes and through all faces and + // find all faces that are using the default material + unsigned int cnt = 0; + for (std::vector<D3DS::Mesh>::iterator + i = mScene->mMeshes.begin(); + i != mScene->mMeshes.end(); ++i) { + for (std::vector<unsigned int>::iterator + a = (*i).mFaceMaterials.begin(); + a != (*i).mFaceMaterials.end(); ++a) { + // NOTE: The additional check seems to be necessary, + // some exporters seem to generate invalid data here + if (0xcdcdcdcd == (*a)) { + (*a) = idx; + ++cnt; + } else if ((*a) >= mScene->mMaterials.size()) { + (*a) = idx; + ASSIMP_LOG_WARN("Material index overflow in 3DS file. Using default material"); + ++cnt; + } + } + } + if (cnt && idx == mScene->mMaterials.size()) { + // We need to create our own default material + D3DS::Material sMat("%%%DEFAULT"); + sMat.mDiffuse = aiColor3D(0.3f, 0.3f, 0.3f); + mScene->mMaterials.push_back(sMat); + + ASSIMP_LOG_INFO("3DS: Generating default material"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Check whether all indices are valid. Otherwise we'd crash before the validation step is reached +void Discreet3DSImporter::CheckIndices(D3DS::Mesh &sMesh) { + for (std::vector<D3DS::Face>::iterator i = sMesh.mFaces.begin(); i != sMesh.mFaces.end(); ++i) { + // check whether all indices are in range + for (unsigned int a = 0; a < 3; ++a) { + if ((*i).mIndices[a] >= sMesh.mPositions.size()) { + ASSIMP_LOG_WARN("3DS: Vertex index overflow)"); + (*i).mIndices[a] = (uint32_t)sMesh.mPositions.size() - 1; + } + if (!sMesh.mTexCoords.empty() && (*i).mIndices[a] >= sMesh.mTexCoords.size()) { + ASSIMP_LOG_WARN("3DS: Texture coordinate index overflow)"); + (*i).mIndices[a] = (uint32_t)sMesh.mTexCoords.size() - 1; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +// Generate out unique verbose format representation +void Discreet3DSImporter::MakeUnique(D3DS::Mesh &sMesh) { + // TODO: really necessary? I don't think. Just a waste of memory and time + // to do it now in a separate buffer. + + // Allocate output storage + std::vector<aiVector3D> vNew(sMesh.mFaces.size() * 3); + std::vector<aiVector3D> vNew2; + if (sMesh.mTexCoords.size()) + vNew2.resize(sMesh.mFaces.size() * 3); + + for (unsigned int i = 0, base = 0; i < sMesh.mFaces.size(); ++i) { + D3DS::Face &face = sMesh.mFaces[i]; + + // Positions + for (unsigned int a = 0; a < 3; ++a, ++base) { + vNew[base] = sMesh.mPositions[face.mIndices[a]]; + if (sMesh.mTexCoords.size()) + vNew2[base] = sMesh.mTexCoords[face.mIndices[a]]; + + face.mIndices[a] = base; + } + } + sMesh.mPositions = vNew; + sMesh.mTexCoords = vNew2; +} + +// ------------------------------------------------------------------------------------------------ +// Convert a 3DS texture to texture keys in an aiMaterial +void CopyTexture(aiMaterial &mat, D3DS::Texture &texture, aiTextureType type) { + // Setup the texture name + aiString tex; + tex.Set(texture.mMapName); + mat.AddProperty(&tex, AI_MATKEY_TEXTURE(type, 0)); + + // Setup the texture blend factor + if (is_not_qnan(texture.mTextureBlend)) + mat.AddProperty<ai_real>(&texture.mTextureBlend, 1, AI_MATKEY_TEXBLEND(type, 0)); + + // Setup the texture mapping mode + int mapMode = static_cast<int>(texture.mMapMode); + mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_U(type, 0)); + mat.AddProperty<int>(&mapMode, 1, AI_MATKEY_MAPPINGMODE_V(type, 0)); + + // Mirroring - double the scaling values + // FIXME: this is not really correct ... + if (texture.mMapMode == aiTextureMapMode_Mirror) { + texture.mScaleU *= 2.0; + texture.mScaleV *= 2.0; + texture.mOffsetU /= 2.0; + texture.mOffsetV /= 2.0; + } + + // Setup texture UV transformations + mat.AddProperty<ai_real>(&texture.mOffsetU, 5, AI_MATKEY_UVTRANSFORM(type, 0)); +} + +// ------------------------------------------------------------------------------------------------ +// Convert a 3DS material to an aiMaterial +void Discreet3DSImporter::ConvertMaterial(D3DS::Material &oldMat, + aiMaterial &mat) { + // NOTE: Pass the background image to the viewer by bypassing the + // material system. This is an evil hack, never do it again! + if (0 != mBackgroundImage.length() && bHasBG) { + aiString tex; + tex.Set(mBackgroundImage); + mat.AddProperty(&tex, AI_MATKEY_GLOBAL_BACKGROUND_IMAGE); + + // Be sure this is only done for the first material + mBackgroundImage = std::string(); + } + + // At first add the base ambient color of the scene to the material + oldMat.mAmbient.r += mClrAmbient.r; + oldMat.mAmbient.g += mClrAmbient.g; + oldMat.mAmbient.b += mClrAmbient.b; + + aiString name; + name.Set(oldMat.mName); + mat.AddProperty(&name, AI_MATKEY_NAME); + + // Material colors + mat.AddProperty(&oldMat.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT); + mat.AddProperty(&oldMat.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); + mat.AddProperty(&oldMat.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR); + mat.AddProperty(&oldMat.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); + + // Phong shininess and shininess strength + if (D3DS::Discreet3DS::Phong == oldMat.mShading || + D3DS::Discreet3DS::Metal == oldMat.mShading) { + if (!oldMat.mSpecularExponent || !oldMat.mShininessStrength) { + oldMat.mShading = D3DS::Discreet3DS::Gouraud; + } else { + mat.AddProperty(&oldMat.mSpecularExponent, 1, AI_MATKEY_SHININESS); + mat.AddProperty(&oldMat.mShininessStrength, 1, AI_MATKEY_SHININESS_STRENGTH); + } + } + + // Opacity + mat.AddProperty<ai_real>(&oldMat.mTransparency, 1, AI_MATKEY_OPACITY); + + // Bump height scaling + mat.AddProperty<ai_real>(&oldMat.mBumpHeight, 1, AI_MATKEY_BUMPSCALING); + + // Two sided rendering? + if (oldMat.mTwoSided) { + int i = 1; + mat.AddProperty<int>(&i, 1, AI_MATKEY_TWOSIDED); + } + + // Shading mode + aiShadingMode eShading = aiShadingMode_NoShading; + switch (oldMat.mShading) { + case D3DS::Discreet3DS::Flat: + eShading = aiShadingMode_Flat; + break; + + // I don't know what "Wire" shading should be, + // assume it is simple lambertian diffuse shading + case D3DS::Discreet3DS::Wire: { + // Set the wireframe flag + unsigned int iWire = 1; + mat.AddProperty<int>((int *)&iWire, 1, AI_MATKEY_ENABLE_WIREFRAME); + } + + case D3DS::Discreet3DS::Gouraud: + eShading = aiShadingMode_Gouraud; + break; + + // assume cook-torrance shading for metals. + case D3DS::Discreet3DS::Phong: + eShading = aiShadingMode_Phong; + break; + + case D3DS::Discreet3DS::Metal: + eShading = aiShadingMode_CookTorrance; + break; + + // FIX to workaround a warning with GCC 4 who complained + // about a missing case Blinn: here - Blinn isn't a valid + // value in the 3DS Loader, it is just needed for ASE + case D3DS::Discreet3DS::Blinn: + eShading = aiShadingMode_Blinn; + break; + } + int eShading_ = static_cast<int>(eShading); + mat.AddProperty<int>(&eShading_, 1, AI_MATKEY_SHADING_MODEL); + + // DIFFUSE texture + if (oldMat.sTexDiffuse.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexDiffuse, aiTextureType_DIFFUSE); + + // SPECULAR texture + if (oldMat.sTexSpecular.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexSpecular, aiTextureType_SPECULAR); + + // OPACITY texture + if (oldMat.sTexOpacity.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexOpacity, aiTextureType_OPACITY); + + // EMISSIVE texture + if (oldMat.sTexEmissive.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexEmissive, aiTextureType_EMISSIVE); + + // BUMP texture + if (oldMat.sTexBump.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexBump, aiTextureType_HEIGHT); + + // SHININESS texture + if (oldMat.sTexShininess.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexShininess, aiTextureType_SHININESS); + + // REFLECTION texture + if (oldMat.sTexReflective.mMapName.length() > 0) + CopyTexture(mat, oldMat.sTexReflective, aiTextureType_REFLECTION); + + // Store the name of the material itself, too + if (oldMat.mName.length()) { + aiString tex; + tex.Set(oldMat.mName); + mat.AddProperty(&tex, AI_MATKEY_NAME); + } +} + +// ------------------------------------------------------------------------------------------------ +// Split meshes by their materials and generate output aiMesh'es +void Discreet3DSImporter::ConvertMeshes(aiScene *pcOut) { + std::vector<aiMesh *> avOutMeshes; + avOutMeshes.reserve(mScene->mMeshes.size() * 2); + + unsigned int iFaceCnt = 0, num = 0; + aiString name; + + // we need to split all meshes by their materials + for (std::vector<D3DS::Mesh>::iterator i = mScene->mMeshes.begin(); i != mScene->mMeshes.end(); ++i) { + std::unique_ptr<std::vector<unsigned int>[]> aiSplit(new std::vector<unsigned int>[mScene->mMaterials.size()]); + + name.length = ASSIMP_itoa10(name.data, num++); + + unsigned int iNum = 0; + for (std::vector<unsigned int>::const_iterator a = (*i).mFaceMaterials.begin(); + a != (*i).mFaceMaterials.end(); ++a, ++iNum) { + aiSplit[*a].push_back(iNum); + } + // now generate submeshes + for (unsigned int p = 0; p < mScene->mMaterials.size(); ++p) { + if (aiSplit[p].empty()) { + continue; + } + aiMesh *meshOut = new aiMesh(); + meshOut->mName = name; + meshOut->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + + // be sure to setup the correct material index + meshOut->mMaterialIndex = p; + + // use the color data as temporary storage + meshOut->mColors[0] = (aiColor4D *)(&*i); + avOutMeshes.push_back(meshOut); + + // convert vertices + meshOut->mNumFaces = (unsigned int)aiSplit[p].size(); + meshOut->mNumVertices = meshOut->mNumFaces * 3; + + // allocate enough storage for faces + meshOut->mFaces = new aiFace[meshOut->mNumFaces]; + iFaceCnt += meshOut->mNumFaces; + + meshOut->mVertices = new aiVector3D[meshOut->mNumVertices]; + meshOut->mNormals = new aiVector3D[meshOut->mNumVertices]; + if ((*i).mTexCoords.size()) { + meshOut->mTextureCoords[0] = new aiVector3D[meshOut->mNumVertices]; + } + for (unsigned int q = 0, base = 0; q < aiSplit[p].size(); ++q) { + unsigned int index = aiSplit[p][q]; + aiFace &face = meshOut->mFaces[q]; + + face.mIndices = new unsigned int[3]; + face.mNumIndices = 3; + + for (unsigned int a = 0; a < 3; ++a, ++base) { + unsigned int idx = (*i).mFaces[index].mIndices[a]; + meshOut->mVertices[base] = (*i).mPositions[idx]; + meshOut->mNormals[base] = (*i).mNormals[idx]; + + if ((*i).mTexCoords.size()) + meshOut->mTextureCoords[0][base] = (*i).mTexCoords[idx]; + + face.mIndices[a] = base; + } + } + } + } + + // Copy them to the output array + pcOut->mNumMeshes = (unsigned int)avOutMeshes.size(); + pcOut->mMeshes = new aiMesh *[pcOut->mNumMeshes](); + for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) { + pcOut->mMeshes[a] = avOutMeshes[a]; + } + + // We should have at least one face here + if (!iFaceCnt) { + throw DeadlyImportError("No faces loaded. The mesh is empty"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Add a node to the scenegraph and setup its final transformation +void Discreet3DSImporter::AddNodeToGraph(aiScene *pcSOut, aiNode *pcOut, + D3DS::Node *pcIn, aiMatrix4x4 & /*absTrafo*/) { + std::vector<unsigned int> iArray; + iArray.reserve(3); + + aiMatrix4x4 abs; + + // Find all meshes with the same name as the node + for (unsigned int a = 0; a < pcSOut->mNumMeshes; ++a) { + const D3DS::Mesh *pcMesh = (const D3DS::Mesh *)pcSOut->mMeshes[a]->mColors[0]; + ai_assert(nullptr != pcMesh); + + if (pcIn->mName == pcMesh->mName) + iArray.push_back(a); + } + if (!iArray.empty()) { + // The matrix should be identical for all meshes with the + // same name. It HAS to be identical for all meshes ..... + D3DS::Mesh *imesh = ((D3DS::Mesh *)pcSOut->mMeshes[iArray[0]]->mColors[0]); + + // Compute the inverse of the transformation matrix to move the + // vertices back to their relative and local space + aiMatrix4x4 mInv = imesh->mMat, mInvTransposed = imesh->mMat; + mInv.Inverse(); + mInvTransposed.Transpose(); + aiVector3D pivot = pcIn->vPivot; + + pcOut->mNumMeshes = (unsigned int)iArray.size(); + pcOut->mMeshes = new unsigned int[iArray.size()]; + for (unsigned int i = 0; i < iArray.size(); ++i) { + const unsigned int iIndex = iArray[i]; + aiMesh *const mesh = pcSOut->mMeshes[iIndex]; + + if (mesh->mColors[1] == nullptr) { + // Transform the vertices back into their local space + // fixme: consider computing normals after this, so we don't need to transform them + const aiVector3D *const pvEnd = mesh->mVertices + mesh->mNumVertices; + aiVector3D *pvCurrent = mesh->mVertices, *t2 = mesh->mNormals; + + for (; pvCurrent != pvEnd; ++pvCurrent, ++t2) { + *pvCurrent = mInv * (*pvCurrent); + *t2 = mInvTransposed * (*t2); + } + + // Handle negative transformation matrix determinant -> invert vertex x + if (imesh->mMat.Determinant() < 0.0f) { + /* we *must* have normals */ + for (pvCurrent = mesh->mVertices, t2 = mesh->mNormals; pvCurrent != pvEnd; ++pvCurrent, ++t2) { + pvCurrent->x *= -1.f; + t2->x *= -1.f; + } + ASSIMP_LOG_INFO("3DS: Flipping mesh X-Axis"); + } + + // Handle pivot point + if (pivot.x || pivot.y || pivot.z) { + for (pvCurrent = mesh->mVertices; pvCurrent != pvEnd; ++pvCurrent) { + *pvCurrent -= pivot; + } + } + + mesh->mColors[1] = (aiColor4D *)1; + } else + mesh->mColors[1] = (aiColor4D *)1; + + // Setup the mesh index + pcOut->mMeshes[i] = iIndex; + } + } + + // Setup the name of the node + // First instance keeps its name otherwise something might break, all others will be postfixed with their instance number + if (pcIn->mInstanceNumber > 1) { + char tmp[12]; + ASSIMP_itoa10(tmp, pcIn->mInstanceNumber); + std::string tempStr = pcIn->mName + "_inst_"; + tempStr += tmp; + pcOut->mName.Set(tempStr); + } else + pcOut->mName.Set(pcIn->mName); + + // Now build the transformation matrix of the node + // ROTATION + if (pcIn->aRotationKeys.size()) { + + // FIX to get to Assimp's quaternion conventions + for (std::vector<aiQuatKey>::iterator it = pcIn->aRotationKeys.begin(); it != pcIn->aRotationKeys.end(); ++it) { + (*it).mValue.w *= -1.f; + } + + pcOut->mTransformation = aiMatrix4x4(pcIn->aRotationKeys[0].mValue.GetMatrix()); + } else if (pcIn->aCameraRollKeys.size()) { + aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(-pcIn->aCameraRollKeys[0].mValue), + pcOut->mTransformation); + } + + // SCALING + aiMatrix4x4 &m = pcOut->mTransformation; + if (pcIn->aScalingKeys.size()) { + const aiVector3D &v = pcIn->aScalingKeys[0].mValue; + m.a1 *= v.x; + m.b1 *= v.x; + m.c1 *= v.x; + m.a2 *= v.y; + m.b2 *= v.y; + m.c2 *= v.y; + m.a3 *= v.z; + m.b3 *= v.z; + m.c3 *= v.z; + } + + // TRANSLATION + if (pcIn->aPositionKeys.size()) { + const aiVector3D &v = pcIn->aPositionKeys[0].mValue; + m.a4 += v.x; + m.b4 += v.y; + m.c4 += v.z; + } + + // Generate animation channels for the node + if (pcIn->aPositionKeys.size() > 1 || pcIn->aRotationKeys.size() > 1 || + pcIn->aScalingKeys.size() > 1 || pcIn->aCameraRollKeys.size() > 1 || + pcIn->aTargetPositionKeys.size() > 1) { + aiAnimation *anim = pcSOut->mAnimations[0]; + ai_assert(nullptr != anim); + + if (pcIn->aCameraRollKeys.size() > 1) { + ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting camera roll track ..."); + + // Camera roll keys - in fact they're just rotations + // around the camera's z axis. The angles are given + // in degrees (and they're clockwise). + pcIn->aRotationKeys.resize(pcIn->aCameraRollKeys.size()); + for (unsigned int i = 0; i < pcIn->aCameraRollKeys.size(); ++i) { + aiQuatKey &q = pcIn->aRotationKeys[i]; + aiFloatKey &f = pcIn->aCameraRollKeys[i]; + + q.mTime = f.mTime; + + // FIX to get to Assimp quaternion conventions + q.mValue = aiQuaternion(0.f, 0.f, AI_DEG_TO_RAD(/*-*/ f.mValue)); + } + } +#if 0 + if (pcIn->aTargetPositionKeys.size() > 1) + { + ASSIMP_LOG_VERBOSE_DEBUG("3DS: Converting target track ..."); + + // Camera or spot light - need to convert the separate + // target position channel to our representation + TargetAnimationHelper helper; + + if (pcIn->aPositionKeys.empty()) + { + // We can just pass zero here ... + helper.SetFixedMainAnimationChannel(aiVector3D()); + } + else helper.SetMainAnimationChannel(&pcIn->aPositionKeys); + helper.SetTargetAnimationChannel(&pcIn->aTargetPositionKeys); + + // Do the conversion + std::vector<aiVectorKey> distanceTrack; + helper.Process(&distanceTrack); + + // Now add a new node as child, name it <ourName>.Target + // and assign the distance track to it. This is that the + // information where the target is and how it moves is + // not lost + D3DS::Node* nd = new D3DS::Node(); + pcIn->push_back(nd); + + nd->mName = pcIn->mName + ".Target"; + + aiNodeAnim* nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); + nda->mNodeName.Set(nd->mName); + + nda->mNumPositionKeys = (unsigned int)distanceTrack.size(); + nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys]; + ::memcpy(nda->mPositionKeys,&distanceTrack[0], + sizeof(aiVectorKey)*nda->mNumPositionKeys); + } +#endif + + // Cameras or lights define their transformation in their parent node and in the + // corresponding light or camera chunks. However, we read and process the latter + // to to be able to return valid cameras/lights even if no scenegraph is given. + for (unsigned int n = 0; n < pcSOut->mNumCameras; ++n) { + if (pcSOut->mCameras[n]->mName == pcOut->mName) { + pcSOut->mCameras[n]->mLookAt = aiVector3D(0.f, 0.f, 1.f); + } + } + for (unsigned int n = 0; n < pcSOut->mNumLights; ++n) { + if (pcSOut->mLights[n]->mName == pcOut->mName) { + pcSOut->mLights[n]->mDirection = aiVector3D(0.f, 0.f, 1.f); + } + } + + // Allocate a new node anim and setup its name + aiNodeAnim *nda = anim->mChannels[anim->mNumChannels++] = new aiNodeAnim(); + nda->mNodeName.Set(pcIn->mName); + + // POSITION keys + if (pcIn->aPositionKeys.size() > 0) { + nda->mNumPositionKeys = (unsigned int)pcIn->aPositionKeys.size(); + nda->mPositionKeys = new aiVectorKey[nda->mNumPositionKeys]; + ::memcpy(nda->mPositionKeys, &pcIn->aPositionKeys[0], + sizeof(aiVectorKey) * nda->mNumPositionKeys); + } + + // ROTATION keys + if (pcIn->aRotationKeys.size() > 0) { + nda->mNumRotationKeys = (unsigned int)pcIn->aRotationKeys.size(); + nda->mRotationKeys = new aiQuatKey[nda->mNumRotationKeys]; + + // Rotations are quaternion offsets + aiQuaternion abs1; + for (unsigned int n = 0; n < nda->mNumRotationKeys; ++n) { + const aiQuatKey &q = pcIn->aRotationKeys[n]; + + abs1 = (n ? abs1 * q.mValue : q.mValue); + nda->mRotationKeys[n].mTime = q.mTime; + nda->mRotationKeys[n].mValue = abs1.Normalize(); + } + } + + // SCALING keys + if (pcIn->aScalingKeys.size() > 0) { + nda->mNumScalingKeys = (unsigned int)pcIn->aScalingKeys.size(); + nda->mScalingKeys = new aiVectorKey[nda->mNumScalingKeys]; + ::memcpy(nda->mScalingKeys, &pcIn->aScalingKeys[0], + sizeof(aiVectorKey) * nda->mNumScalingKeys); + } + } + + // Allocate storage for children + pcOut->mNumChildren = (unsigned int)pcIn->mChildren.size(); + pcOut->mChildren = new aiNode *[pcIn->mChildren.size()]; + + // Recursively process all children + const unsigned int size = static_cast<unsigned int>(pcIn->mChildren.size()); + for (unsigned int i = 0; i < size; ++i) { + pcOut->mChildren[i] = new aiNode(); + pcOut->mChildren[i]->mParent = pcOut; + AddNodeToGraph(pcSOut, pcOut->mChildren[i], pcIn->mChildren[i], abs); + } +} + +// ------------------------------------------------------------------------------------------------ +// Find out how many node animation channels we'll have finally +void CountTracks(D3DS::Node *node, unsigned int &cnt) { + ////////////////////////////////////////////////////////////////////////////// + // We will never generate more than one channel for a node, so + // this is rather easy here. + + if (node->aPositionKeys.size() > 1 || node->aRotationKeys.size() > 1 || + node->aScalingKeys.size() > 1 || node->aCameraRollKeys.size() > 1 || + node->aTargetPositionKeys.size() > 1) { + ++cnt; + + // account for the additional channel for the camera/spotlight target position + if (node->aTargetPositionKeys.size() > 1) ++cnt; + } + + // Recursively process all children + for (unsigned int i = 0; i < node->mChildren.size(); ++i) + CountTracks(node->mChildren[i], cnt); +} + +// ------------------------------------------------------------------------------------------------ +// Generate the output node graph +void Discreet3DSImporter::GenerateNodeGraph(aiScene *pcOut) { + pcOut->mRootNode = new aiNode(); + if (0 == mRootNode->mChildren.size()) { + ////////////////////////////////////////////////////////////////////////////// + // It seems the file is so messed up that it has not even a hierarchy. + // generate a flat hiearachy which looks like this: + // + // ROOT_NODE + // | + // ---------------------------------------- + // | | | | | + // MESH_0 MESH_1 MESH_2 ... MESH_N CAMERA_0 .... + // + ASSIMP_LOG_WARN("No hierarchy information has been found in the file. "); + + pcOut->mRootNode->mNumChildren = pcOut->mNumMeshes + + static_cast<unsigned int>(mScene->mCameras.size() + mScene->mLights.size()); + + pcOut->mRootNode->mChildren = new aiNode *[pcOut->mRootNode->mNumChildren]; + pcOut->mRootNode->mName.Set("<3DSDummyRoot>"); + + // Build dummy nodes for all meshes + unsigned int a = 0; + for (unsigned int i = 0; i < pcOut->mNumMeshes; ++i, ++a) { + aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + pcNode->mMeshes = new unsigned int[1]; + pcNode->mMeshes[0] = i; + pcNode->mNumMeshes = 1; + + // Build a name for the node + pcNode->mName.length = ai_snprintf(pcNode->mName.data, MAXLEN, "3DSMesh_%u", i); + } + + // Build dummy nodes for all cameras + for (unsigned int i = 0; i < (unsigned int)mScene->mCameras.size(); ++i, ++a) { + aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + + // Build a name for the node + pcNode->mName = mScene->mCameras[i]->mName; + } + + // Build dummy nodes for all lights + for (unsigned int i = 0; i < (unsigned int)mScene->mLights.size(); ++i, ++a) { + aiNode *pcNode = pcOut->mRootNode->mChildren[a] = new aiNode(); + pcNode->mParent = pcOut->mRootNode; + + // Build a name for the node + pcNode->mName = mScene->mLights[i]->mName; + } + } else { + // First of all: find out how many scaling, rotation and translation + // animation tracks we'll have afterwards + unsigned int numChannel = 0; + CountTracks(mRootNode, numChannel); + + if (numChannel) { + // Allocate a primary animation channel + pcOut->mNumAnimations = 1; + pcOut->mAnimations = new aiAnimation *[1]; + aiAnimation *anim = pcOut->mAnimations[0] = new aiAnimation(); + + anim->mName.Set("3DSMasterAnim"); + + // Allocate enough storage for all node animation channels, + // but don't set the mNumChannels member - we'll use it to + // index into the array + anim->mChannels = new aiNodeAnim *[numChannel]; + } + + aiMatrix4x4 m; + AddNodeToGraph(pcOut, pcOut->mRootNode, mRootNode, m); + } + + // We used the first and second vertex color set to store some temporary values so we need to cleanup here + for (unsigned int a = 0; a < pcOut->mNumMeshes; ++a) { + pcOut->mMeshes[a]->mColors[0] = nullptr; + pcOut->mMeshes[a]->mColors[1] = nullptr; + } + + pcOut->mRootNode->mTransformation = aiMatrix4x4( + 1.f, 0.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + 0.f, -1.f, 0.f, 0.f, + 0.f, 0.f, 0.f, 1.f) * + pcOut->mRootNode->mTransformation; + + // If the root node is unnamed name it "<3DSRoot>" + if (::strstr(pcOut->mRootNode->mName.data, "UNNAMED") || + (pcOut->mRootNode->mName.data[0] == '$' && pcOut->mRootNode->mName.data[1] == '$')) { + pcOut->mRootNode->mName.Set("<3DSRoot>"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Convert all meshes in the scene and generate the final output scene. +void Discreet3DSImporter::ConvertScene(aiScene *pcOut) { + // Allocate enough storage for all output materials + pcOut->mNumMaterials = (unsigned int)mScene->mMaterials.size(); + pcOut->mMaterials = new aiMaterial *[pcOut->mNumMaterials]; + + // ... and convert the 3DS materials to aiMaterial's + for (unsigned int i = 0; i < pcOut->mNumMaterials; ++i) { + aiMaterial *pcNew = new aiMaterial(); + ConvertMaterial(mScene->mMaterials[i], *pcNew); + pcOut->mMaterials[i] = pcNew; + } + + // Generate the output mesh list + ConvertMeshes(pcOut); + + // Now copy all light sources to the output scene + pcOut->mNumLights = (unsigned int)mScene->mLights.size(); + if (pcOut->mNumLights) { + pcOut->mLights = new aiLight *[pcOut->mNumLights]; + ::memcpy(pcOut->mLights, &mScene->mLights[0], sizeof(void *) * pcOut->mNumLights); + } + + // Now copy all cameras to the output scene + pcOut->mNumCameras = (unsigned int)mScene->mCameras.size(); + if (pcOut->mNumCameras) { + pcOut->mCameras = new aiCamera *[pcOut->mNumCameras]; + ::memcpy(pcOut->mCameras, &mScene->mCameras[0], sizeof(void *) * pcOut->mNumCameras); + } +} + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/libs/assimp/code/AssetLib/3DS/3DSExporter.cpp b/libs/assimp/code/AssetLib/3DS/3DSExporter.cpp new file mode 100644 index 0000000..71588f9 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSExporter.cpp @@ -0,0 +1,584 @@ +/* +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. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER + +#include "AssetLib/3DS/3DSExporter.h" +#include "AssetLib/3DS/3DSHelper.h" +#include "AssetLib/3DS/3DSLoader.h" +#include "PostProcessing/SplitLargeMeshes.h" + +#include <assimp/SceneCombiner.h> +#include <assimp/StringComparison.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/Exporter.hpp> +#include <assimp/IOSystem.hpp> + +#include <memory> + +namespace Assimp { + +using namespace D3DS; + +namespace { + +////////////////////////////////////////////////////////////////////////////////////// +// Scope utility to write a 3DS file chunk. +// +// Upon construction, the chunk header is written with the chunk type (flags) +// filled out, but the chunk size left empty. Upon destruction, the correct chunk +// size based on the then-position of the output stream cursor is filled in. +class ChunkWriter { + enum { + CHUNK_SIZE_NOT_SET = 0xdeadbeef, + SIZE_OFFSET = 2 + }; + +public: + ChunkWriter(StreamWriterLE &writer, uint16_t chunk_type) : + writer(writer) { + chunk_start_pos = writer.GetCurrentPos(); + writer.PutU2(chunk_type); + writer.PutU4((uint32_t)CHUNK_SIZE_NOT_SET); + } + + ~ChunkWriter() { + std::size_t head_pos = writer.GetCurrentPos(); + + ai_assert(head_pos > chunk_start_pos); + const std::size_t chunk_size = head_pos - chunk_start_pos; + + writer.SetCurrentPos(chunk_start_pos + SIZE_OFFSET); + writer.PutU4(static_cast<uint32_t>(chunk_size)); + writer.SetCurrentPos(head_pos); + } + +private: + StreamWriterLE &writer; + std::size_t chunk_start_pos; +}; + +// Return an unique name for a given |mesh| attached to |node| that +// preserves the mesh's given name if it has one. |index| is the index +// of the mesh in |aiScene::mMeshes|. +std::string GetMeshName(const aiMesh &mesh, unsigned int index, const aiNode &node) { + static const char underscore = '_'; + char postfix[10] = { 0 }; + ASSIMP_itoa10(postfix, index); + + std::string result = node.mName.C_Str(); + if (mesh.mName.length > 0) { + result += underscore; + result += mesh.mName.C_Str(); + } + return result + underscore + postfix; +} + +// Return an unique name for a given |mat| with original position |index| +// in |aiScene::mMaterials|. The name preserves the original material +// name if possible. +std::string GetMaterialName(const aiMaterial &mat, unsigned int index) { + static const std::string underscore = "_"; + char postfix[10] = { 0 }; + ASSIMP_itoa10(postfix, index); + + aiString mat_name; + if (AI_SUCCESS == mat.Get(AI_MATKEY_NAME, mat_name)) { + return mat_name.C_Str() + underscore + postfix; + } + + return "Material" + underscore + postfix; +} + +// Collect world transformations for each node +void CollectTrafos(const aiNode *node, std::map<const aiNode *, aiMatrix4x4> &trafos) { + const aiMatrix4x4 &parent = node->mParent ? trafos[node->mParent] : aiMatrix4x4(); + trafos[node] = parent * node->mTransformation; + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + CollectTrafos(node->mChildren[i], trafos); + } +} + +// Generate a flat list of the meshes (by index) assigned to each node +void CollectMeshes(const aiNode *node, std::multimap<const aiNode *, unsigned int> &meshes) { + for (unsigned int i = 0; i < node->mNumMeshes; ++i) { + meshes.insert(std::make_pair(node, node->mMeshes[i])); + } + for (unsigned int i = 0; i < node->mNumChildren; ++i) { + CollectMeshes(node->mChildren[i], meshes); + } +} +} // namespace + +// ------------------------------------------------------------------------------------------------ +// Worker function for exporting a scene to 3DS. Prototyped and registered in Exporter.cpp +void ExportScene3DS(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene, const ExportProperties * /*pProperties*/) { + std::shared_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wb")); + if (!outfile) { + throw DeadlyExportError("Could not open output .3ds file: " + std::string(pFile)); + } + + // TODO: This extra copy should be avoided and all of this made a preprocess + // requirement of the 3DS exporter. + // + // 3DS meshes can be max 0xffff (16 Bit) vertices and faces, respectively. + // SplitLargeMeshes can do this, but it requires the correct limit to be set + // which is not possible with the current way of specifying preprocess steps + // in |Exporter::ExportFormatEntry|. + aiScene *scenecopy_tmp; + SceneCombiner::CopyScene(&scenecopy_tmp, pScene); + std::unique_ptr<aiScene> scenecopy(scenecopy_tmp); + + SplitLargeMeshesProcess_Triangle tri_splitter; + tri_splitter.SetLimit(0xffff); + tri_splitter.Execute(scenecopy.get()); + + SplitLargeMeshesProcess_Vertex vert_splitter; + vert_splitter.SetLimit(0xffff); + vert_splitter.Execute(scenecopy.get()); + + // Invoke the actual exporter + Discreet3DSExporter exporter(outfile, scenecopy.get()); +} + +} // end of namespace Assimp + +// ------------------------------------------------------------------------------------------------ +Discreet3DSExporter::Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene *scene) : + scene(scene), writer(outfile) { + CollectTrafos(scene->mRootNode, trafos); + CollectMeshes(scene->mRootNode, meshes); + + ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_MAIN); + + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_OBJMESH); + WriteMaterials(); + WriteMeshes(); + + { + ChunkWriter curChunk1(writer, Discreet3DS::CHUNK_MASTER_SCALE); + writer.PutF4(1.0f); + } + } + + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_KEYFRAMER); + WriteHierarchy(*scene->mRootNode, -1, -1); + } +} + +// ------------------------------------------------------------------------------------------------ +Discreet3DSExporter::~Discreet3DSExporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +int Discreet3DSExporter::WriteHierarchy(const aiNode &node, int seq, int sibling_level) { + // 3DS scene hierarchy is serialized as in http://www.martinreddy.net/gfx/3d/3DS.spec + { + ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_TRACKINFO); + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME); + + // Assimp node names are unique and distinct from all mesh-node + // names we generate; thus we can use them as-is + WriteString(node.mName); + + // Two unknown int16 values - it is even unclear if 0 is a safe value + // but luckily importers do not know better either. + writer.PutI4(0); + + int16_t hierarchy_pos = static_cast<int16_t>(seq); + if (sibling_level != -1) { + hierarchy_pos = (uint16_t)sibling_level; + } + + // Write the hierarchy position + writer.PutI2(hierarchy_pos); + } + } + + // TODO: write transformation chunks + + ++seq; + sibling_level = seq; + + // Write all children + for (unsigned int i = 0; i < node.mNumChildren; ++i) { + seq = WriteHierarchy(*node.mChildren[i], seq, i == 0 ? -1 : sibling_level); + } + + // Write all meshes as separate nodes to be able to reference the meshes by name + for (unsigned int i = 0; i < node.mNumMeshes; ++i) { + const bool first_child = node.mNumChildren == 0 && i == 0; + + const unsigned int mesh_idx = node.mMeshes[i]; + const aiMesh &mesh = *scene->mMeshes[mesh_idx]; + + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRACKINFO); + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_TRACKOBJNAME); + WriteString(GetMeshName(mesh, mesh_idx, node)); + + writer.PutI4(0); + writer.PutI2(static_cast<int16_t>(first_child ? seq : sibling_level)); + ++seq; + } + } + return seq; +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteMaterials() { + for (unsigned int i = 0; i < scene->mNumMaterials; ++i) { + ChunkWriter curRootChunk(writer, Discreet3DS::CHUNK_MAT_MATERIAL); + const aiMaterial &mat = *scene->mMaterials[i]; + + { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_MATNAME); + const std::string &name = GetMaterialName(mat, i); + WriteString(name); + } + + aiColor3D color; + if (mat.Get(AI_MATKEY_COLOR_DIFFUSE, color) == AI_SUCCESS) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_DIFFUSE); + WriteColor(color); + } + + if (mat.Get(AI_MATKEY_COLOR_SPECULAR, color) == AI_SUCCESS) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SPECULAR); + WriteColor(color); + } + + if (mat.Get(AI_MATKEY_COLOR_AMBIENT, color) == AI_SUCCESS) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_AMBIENT); + WriteColor(color); + } + + float f; + if (mat.Get(AI_MATKEY_OPACITY, f) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TRANSPARENCY); + WritePercentChunk(1.0f - f); + } + + if (mat.Get(AI_MATKEY_COLOR_EMISSIVE, color) == AI_SUCCESS) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_SELF_ILLUM); + WriteColor(color); + } + + aiShadingMode shading_mode = aiShadingMode_Flat; + if (mat.Get(AI_MATKEY_SHADING_MODEL, shading_mode) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHADING); + + Discreet3DS::shadetype3ds shading_mode_out; + switch (shading_mode) { + case aiShadingMode_Flat: + case aiShadingMode_NoShading: + shading_mode_out = Discreet3DS::Flat; + break; + + case aiShadingMode_Gouraud: + case aiShadingMode_Toon: + case aiShadingMode_OrenNayar: + case aiShadingMode_Minnaert: + shading_mode_out = Discreet3DS::Gouraud; + break; + + case aiShadingMode_Phong: + case aiShadingMode_Blinn: + case aiShadingMode_CookTorrance: + case aiShadingMode_Fresnel: + case aiShadingMode_PBR_BRDF: // Possibly should be Discreet3DS::Metal in some cases but this is undocumented + shading_mode_out = Discreet3DS::Phong; + break; + + default: + shading_mode_out = Discreet3DS::Flat; + ai_assert(false); + }; + writer.PutU2(static_cast<uint16_t>(shading_mode_out)); + } + + if (mat.Get(AI_MATKEY_SHININESS, f) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS); + WritePercentChunk(f); + } + + if (mat.Get(AI_MATKEY_SHININESS_STRENGTH, f) == AI_SUCCESS) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_SHININESS_PERCENT); + WritePercentChunk(f); + } + + int twosided; + if (mat.Get(AI_MATKEY_TWOSIDED, twosided) == AI_SUCCESS && twosided != 0) { + ChunkWriter chunk(writer, Discreet3DS::CHUNK_MAT_TWO_SIDE); + writer.PutI2(1); + } + + // Fallback to BASE_COLOR if no DIFFUSE + if (!WriteTexture(mat, aiTextureType_DIFFUSE, Discreet3DS::CHUNK_MAT_TEXTURE)) + WriteTexture(mat, aiTextureType_BASE_COLOR, Discreet3DS::CHUNK_MAT_TEXTURE); + + WriteTexture(mat, aiTextureType_HEIGHT, Discreet3DS::CHUNK_MAT_BUMPMAP); + WriteTexture(mat, aiTextureType_OPACITY, Discreet3DS::CHUNK_MAT_OPACMAP); + WriteTexture(mat, aiTextureType_SHININESS, Discreet3DS::CHUNK_MAT_MAT_SHINMAP); + WriteTexture(mat, aiTextureType_SPECULAR, Discreet3DS::CHUNK_MAT_SPECMAP); + WriteTexture(mat, aiTextureType_EMISSIVE, Discreet3DS::CHUNK_MAT_SELFIMAP); + WriteTexture(mat, aiTextureType_REFLECTION, Discreet3DS::CHUNK_MAT_REFLMAP); + } +} + +// ------------------------------------------------------------------------------------------------ +// returns true if the texture existed +bool Discreet3DSExporter::WriteTexture(const aiMaterial &mat, aiTextureType type, uint16_t chunk_flags) { + aiString path; + aiTextureMapMode map_mode[2] = { + aiTextureMapMode_Wrap, aiTextureMapMode_Wrap + }; + ai_real blend = 1.0; + if (mat.GetTexture(type, 0, &path, nullptr, nullptr, &blend, nullptr, map_mode) != AI_SUCCESS || !path.length) { + return false; + } + + // TODO: handle embedded textures properly + if (path.data[0] == '*') { + ASSIMP_LOG_ERROR("Ignoring embedded texture for export: ", path.C_Str()); + return false; + } + + ChunkWriter chunk(writer, chunk_flags); + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAPFILE); + WriteString(path); + } + + WritePercentChunk(blend); + + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAT_MAP_TILING); + uint16_t val = 0; // WRAP + if (map_mode[0] == aiTextureMapMode_Mirror) { + val = 0x2; + } else if (map_mode[0] == aiTextureMapMode_Decal) { + val = 0x10; + } + writer.PutU2(val); + } + // TODO: export texture transformation (i.e. UV offset, scale, rotation) + return true; +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteMeshes() { + // NOTE: 3DS allows for instances. However: + // i) not all importers support reading them + // ii) instances are not as flexible as they are in assimp, in particular, + // nodes can carry (and instance) only one mesh. + // + // This exporter currently deep clones all instanced meshes, i.e. for each mesh + // attached to a node a full TRIMESH chunk is written to the file. + // + // Furthermore, the TRIMESH is transformed into world space so that it will + // appear correctly if importers don't read the scene hierarchy at all. + for (MeshesByNodeMap::const_iterator it = meshes.begin(); it != meshes.end(); ++it) { + const aiNode &node = *(*it).first; + const unsigned int mesh_idx = (*it).second; + + const aiMesh &mesh = *scene->mMeshes[mesh_idx]; + + // This should not happen if the SLM step is correctly executed + // before the scene is handed to the exporter + ai_assert(mesh.mNumVertices <= 0xffff); + ai_assert(mesh.mNumFaces <= 0xffff); + + const aiMatrix4x4 &trafo = trafos[&node]; + + ChunkWriter chunk(writer, Discreet3DS::CHUNK_OBJBLOCK); + + // Mesh name is tied to the node it is attached to so it can later be referenced + const std::string &name = GetMeshName(mesh, mesh_idx, node); + WriteString(name); + + // TRIMESH chunk + ChunkWriter chunk2(writer, Discreet3DS::CHUNK_TRIMESH); + + // Vertices in world space + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_VERTLIST); + + const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices); + writer.PutU2(count); + for (unsigned int i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &v = mesh.mVertices[i]; + writer.PutF4(v.x); + writer.PutF4(v.y); + writer.PutF4(v.z); + } + } + + // UV coordinates + if (mesh.HasTextureCoords(0)) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_MAPLIST); + const uint16_t count = static_cast<uint16_t>(mesh.mNumVertices); + writer.PutU2(count); + + for (unsigned int i = 0; i < mesh.mNumVertices; ++i) { + const aiVector3D &v = mesh.mTextureCoords[0][i]; + writer.PutF4(v.x); + writer.PutF4(v.y); + } + } + + // Faces (indices) + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_FACELIST); + + ai_assert(mesh.mNumFaces <= 0xffff); + + // Count triangles, discard lines and points + uint16_t count = 0; + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + const aiFace &f = mesh.mFaces[i]; + if (f.mNumIndices < 3) { + continue; + } + // TRIANGULATE step is a pre-requisite so we should not see polys here + ai_assert(f.mNumIndices == 3); + ++count; + } + + writer.PutU2(count); + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + const aiFace &f = mesh.mFaces[i]; + if (f.mNumIndices < 3) { + continue; + } + + for (unsigned int j = 0; j < 3; ++j) { + ai_assert(f.mIndices[j] <= 0xffff); + writer.PutI2(static_cast<uint16_t>(f.mIndices[j])); + } + + // Edge visibility flag + writer.PutI2(0x0); + } + + // TODO: write smoothing groups (CHUNK_SMOOLIST) + + WriteFaceMaterialChunk(mesh); + } + + // Transformation matrix by which the mesh vertices have been pre-transformed with. + { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_TRMATRIX); + // Store rotation 3x3 matrix row wise + for (unsigned int r = 0; r < 3; ++r) { + for (unsigned int c = 0; c < 3; ++c) { + writer.PutF4(trafo[r][c]); + } + } + // Store translation sub vector column wise + for (unsigned int r = 0; r < 3; ++r) { + writer.PutF4(trafo[r][3]); + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteFaceMaterialChunk(const aiMesh &mesh) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_FACEMAT); + const std::string &name = GetMaterialName(*scene->mMaterials[mesh.mMaterialIndex], mesh.mMaterialIndex); + WriteString(name); + + // Because assimp splits meshes by material, only a single + // FACEMAT chunk needs to be written + ai_assert(mesh.mNumFaces <= 0xffff); + const uint16_t count = static_cast<uint16_t>(mesh.mNumFaces); + writer.PutU2(count); + + for (unsigned int i = 0; i < mesh.mNumFaces; ++i) { + writer.PutU2(static_cast<uint16_t>(i)); + } +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteString(const std::string &s) { + for (std::string::const_iterator it = s.begin(); it != s.end(); ++it) { + writer.PutI1(*it); + } + writer.PutI1('\0'); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteString(const aiString &s) { + for (std::size_t i = 0; i < s.length; ++i) { + writer.PutI1(s.data[i]); + } + writer.PutI1('\0'); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WriteColor(const aiColor3D &color) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_RGBF); + writer.PutF4(color.r); + writer.PutF4(color.g); + writer.PutF4(color.b); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WritePercentChunk(float f) { + ChunkWriter curChunk(writer, Discreet3DS::CHUNK_PERCENTF); + writer.PutF4(f); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSExporter::WritePercentChunk(double f) { + ChunkWriter ccurChunkhunk(writer, Discreet3DS::CHUNK_PERCENTD); + writer.PutF8(f); +} + +#endif // ASSIMP_BUILD_NO_3DS_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/libs/assimp/code/AssetLib/3DS/3DSExporter.h b/libs/assimp/code/AssetLib/3DS/3DSExporter.h new file mode 100644 index 0000000..82ec351 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSExporter.h @@ -0,0 +1,98 @@ +/* +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 3DSExporter.h + * 3DS Exporter Main Header + */ +#ifndef AI_3DSEXPORTER_H_INC +#define AI_3DSEXPORTER_H_INC + +#include <map> +#include <memory> + +#include <assimp/StreamWriter.h> +#include <assimp/material.h> + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiMesh; + +namespace Assimp +{ + +// ------------------------------------------------------------------------------------------------ +/** + * @brief Helper class to export a given scene to a 3DS file. + */ +// ------------------------------------------------------------------------------------------------ +class Discreet3DSExporter { +public: + Discreet3DSExporter(std::shared_ptr<IOStream> &outfile, const aiScene* pScene); + ~Discreet3DSExporter(); + +private: + void WriteMeshes(); + void WriteMaterials(); + bool WriteTexture(const aiMaterial& mat, aiTextureType type, uint16_t chunk_flags); + void WriteFaceMaterialChunk(const aiMesh& mesh); + int WriteHierarchy(const aiNode& node, int level, int sibling_level); + void WriteString(const std::string& s); + void WriteString(const aiString& s); + void WriteColor(const aiColor3D& color); + void WritePercentChunk(float f); + void WritePercentChunk(double f); + +private: + const aiScene* const scene; + StreamWriterLE writer; + + std::map<const aiNode*, aiMatrix4x4> trafos; + + typedef std::multimap<const aiNode*, unsigned int> MeshesByNodeMap; + MeshesByNodeMap meshes; + +}; + +} // Namespace Assimp + +#endif // AI_3DSEXPORTER_H_INC diff --git a/libs/assimp/code/AssetLib/3DS/3DSHelper.h b/libs/assimp/code/AssetLib/3DS/3DSHelper.h new file mode 100644 index 0000000..dc10980 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSHelper.h @@ -0,0 +1,702 @@ +/* +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 Defines helper data structures for the import of 3DS files */ + +#ifndef AI_3DSFILEHELPER_H_INC +#define AI_3DSFILEHELPER_H_INC + +#include <assimp/SmoothingGroups.h> +#include <assimp/SpatialSort.h> +#include <assimp/StringUtils.h> +#include <assimp/anim.h> +#include <assimp/camera.h> +#include <assimp/light.h> +#include <assimp/material.h> +#include <assimp/qnan.h> +#include <cstdio> //sprintf + +namespace Assimp { +namespace D3DS { + +#include <assimp/Compiler/pushpack1.h> + +// --------------------------------------------------------------------------- +/** Defines chunks and data structures. +*/ +namespace Discreet3DS { + + //! data structure for a single chunk in a .3ds file + struct Chunk { + uint16_t Flag; + uint32_t Size; + } PACK_STRUCT; + + //! Used for shading field in material3ds structure + //! From AutoDesk 3ds SDK + typedef enum { + // translated to gouraud shading with wireframe active + Wire = 0x0, + + // if this material is set, no vertex normals will + // be calculated for the model. Face normals + gouraud + Flat = 0x1, + + // standard gouraud shading + Gouraud = 0x2, + + // phong shading + Phong = 0x3, + + // cooktorrance or anistropic phong shading ... + // the exact meaning is unknown, if you know it + // feel free to tell me ;-) + Metal = 0x4, + + // required by the ASE loader + Blinn = 0x5 + } shadetype3ds; + + // Flags for animated keys + enum { + KEY_USE_TENS = 0x1, + KEY_USE_CONT = 0x2, + KEY_USE_BIAS = 0x4, + KEY_USE_EASE_TO = 0x8, + KEY_USE_EASE_FROM = 0x10 + }; + + enum { + + // ******************************************************************** + // Basic chunks which can be found everywhere in the file + CHUNK_VERSION = 0x0002, + CHUNK_RGBF = 0x0010, // float4 R; float4 G; float4 B + CHUNK_RGBB = 0x0011, // int1 R; int1 G; int B + + // Linear color values (gamma = 2.2?) + CHUNK_LINRGBF = 0x0013, // float4 R; float4 G; float4 B + CHUNK_LINRGBB = 0x0012, // int1 R; int1 G; int B + + CHUNK_PERCENTW = 0x0030, // int2 percentage + CHUNK_PERCENTF = 0x0031, // float4 percentage + CHUNK_PERCENTD = 0x0032, // float8 percentage + // ******************************************************************** + + // Prj master chunk + CHUNK_PRJ = 0xC23D, + + // MDLI master chunk + CHUNK_MLI = 0x3DAA, + + // Primary main chunk of the .3ds file + CHUNK_MAIN = 0x4D4D, + + // Mesh main chunk + CHUNK_OBJMESH = 0x3D3D, + + // Specifies the background color of the .3ds file + // This is passed through the material system for + // viewing purposes. + CHUNK_BKGCOLOR = 0x1200, + + // Specifies the ambient base color of the scene. + // This is added to all materials in the file + CHUNK_AMBCOLOR = 0x2100, + + // Specifies the background image for the whole scene + // This value is passed through the material system + // to the viewer + CHUNK_BIT_MAP = 0x1100, + CHUNK_BIT_MAP_EXISTS = 0x1101, + + // ******************************************************************** + // Viewport related stuff. Ignored + CHUNK_DEFAULT_VIEW = 0x3000, + CHUNK_VIEW_TOP = 0x3010, + CHUNK_VIEW_BOTTOM = 0x3020, + CHUNK_VIEW_LEFT = 0x3030, + CHUNK_VIEW_RIGHT = 0x3040, + CHUNK_VIEW_FRONT = 0x3050, + CHUNK_VIEW_BACK = 0x3060, + CHUNK_VIEW_USER = 0x3070, + CHUNK_VIEW_CAMERA = 0x3080, + // ******************************************************************** + + // Mesh chunks + CHUNK_OBJBLOCK = 0x4000, + CHUNK_TRIMESH = 0x4100, + CHUNK_VERTLIST = 0x4110, + CHUNK_VERTFLAGS = 0x4111, + CHUNK_FACELIST = 0x4120, + CHUNK_FACEMAT = 0x4130, + CHUNK_MAPLIST = 0x4140, + CHUNK_SMOOLIST = 0x4150, + CHUNK_TRMATRIX = 0x4160, + CHUNK_MESHCOLOR = 0x4165, + CHUNK_TXTINFO = 0x4170, + CHUNK_LIGHT = 0x4600, + CHUNK_CAMERA = 0x4700, + CHUNK_HIERARCHY = 0x4F00, + + // Specifies the global scaling factor. This is applied + // to the root node's transformation matrix + CHUNK_MASTER_SCALE = 0x0100, + + // ******************************************************************** + // Material chunks + CHUNK_MAT_MATERIAL = 0xAFFF, + + // asciiz containing the name of the material + CHUNK_MAT_MATNAME = 0xA000, + CHUNK_MAT_AMBIENT = 0xA010, // followed by color chunk + CHUNK_MAT_DIFFUSE = 0xA020, // followed by color chunk + CHUNK_MAT_SPECULAR = 0xA030, // followed by color chunk + + // Specifies the shininess of the material + // followed by percentage chunk + CHUNK_MAT_SHININESS = 0xA040, + CHUNK_MAT_SHININESS_PERCENT = 0xA041, + + // Specifies the shading mode to be used + // followed by a short + CHUNK_MAT_SHADING = 0xA100, + + // NOTE: Emissive color (self illumination) seems not + // to be a color but a single value, type is unknown. + // Make the parser accept both of them. + // followed by percentage chunk (?) + CHUNK_MAT_SELF_ILLUM = 0xA080, + + // Always followed by percentage chunk (?) + CHUNK_MAT_SELF_ILPCT = 0xA084, + + // Always followed by percentage chunk + CHUNK_MAT_TRANSPARENCY = 0xA050, + + // Diffuse texture channel 0 + CHUNK_MAT_TEXTURE = 0xA200, + + // Contains opacity information for each texel + CHUNK_MAT_OPACMAP = 0xA210, + + // Contains a reflection map to be used to reflect + // the environment. This is partially supported. + CHUNK_MAT_REFLMAP = 0xA220, + + // Self Illumination map (emissive colors) + CHUNK_MAT_SELFIMAP = 0xA33d, + + // Bumpmap. Not specified whether it is a heightmap + // or a normal map. Assme it is a heightmap since + // artist normally prefer this format. + CHUNK_MAT_BUMPMAP = 0xA230, + + // Specular map. Seems to influence the specular color + CHUNK_MAT_SPECMAP = 0xA204, + + // Holds shininess data. + CHUNK_MAT_MAT_SHINMAP = 0xA33C, + + // Scaling in U/V direction. + // (need to gen separate UV coordinate set + // and do this by hand) + CHUNK_MAT_MAP_USCALE = 0xA354, + CHUNK_MAT_MAP_VSCALE = 0xA356, + + // Translation in U/V direction. + // (need to gen separate UV coordinate set + // and do this by hand) + CHUNK_MAT_MAP_UOFFSET = 0xA358, + CHUNK_MAT_MAP_VOFFSET = 0xA35a, + + // UV-coordinates rotation around the z-axis + // Assumed to be in radians. + CHUNK_MAT_MAP_ANG = 0xA35C, + + // Tiling flags for 3DS files + CHUNK_MAT_MAP_TILING = 0xa351, + + // Specifies the file name of a texture + CHUNK_MAPFILE = 0xA300, + + // Specifies whether a material requires two-sided rendering + CHUNK_MAT_TWO_SIDE = 0xA081, + // ******************************************************************** + + // Main keyframer chunk. Contains translation/rotation/scaling data + CHUNK_KEYFRAMER = 0xB000, + + // Supported sub chunks + CHUNK_TRACKINFO = 0xB002, + CHUNK_TRACKOBJNAME = 0xB010, + CHUNK_TRACKDUMMYOBJNAME = 0xB011, + CHUNK_TRACKPIVOT = 0xB013, + CHUNK_TRACKPOS = 0xB020, + CHUNK_TRACKROTATE = 0xB021, + CHUNK_TRACKSCALE = 0xB022, + + // ******************************************************************** + // Keyframes for various other stuff in the file + // Partially ignored + CHUNK_AMBIENTKEY = 0xB001, + CHUNK_TRACKMORPH = 0xB026, + CHUNK_TRACKHIDE = 0xB029, + CHUNK_OBJNUMBER = 0xB030, + CHUNK_TRACKCAMERA = 0xB003, + CHUNK_TRACKFOV = 0xB023, + CHUNK_TRACKROLL = 0xB024, + CHUNK_TRACKCAMTGT = 0xB004, + CHUNK_TRACKLIGHT = 0xB005, + CHUNK_TRACKLIGTGT = 0xB006, + CHUNK_TRACKSPOTL = 0xB007, + CHUNK_FRAMES = 0xB008, + // ******************************************************************** + + // light sub-chunks + CHUNK_DL_OFF = 0x4620, + CHUNK_DL_OUTER_RANGE = 0x465A, + CHUNK_DL_INNER_RANGE = 0x4659, + CHUNK_DL_MULTIPLIER = 0x465B, + CHUNK_DL_EXCLUDE = 0x4654, + CHUNK_DL_ATTENUATE = 0x4625, + CHUNK_DL_SPOTLIGHT = 0x4610, + + // camera sub-chunks + CHUNK_CAM_RANGES = 0x4720 + }; +} + +// --------------------------------------------------------------------------- +/** Helper structure representing a 3ds mesh face */ +struct Face : public FaceWithSmoothingGroup { +}; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4315) +#endif // _MSC_VER + +// --------------------------------------------------------------------------- +/** Helper structure representing a texture */ +struct Texture { + //! Default constructor + Texture() AI_NO_EXCEPT + : mTextureBlend(0.0f), + mMapName(), + mOffsetU(0.0), + mOffsetV(0.0), + mScaleU(1.0), + mScaleV(1.0), + mRotation(0.0), + mMapMode(aiTextureMapMode_Wrap), + bPrivate(), + iUVSrc(0) { + mTextureBlend = get_qnan(); + } + + Texture(const Texture &other) : + mTextureBlend(other.mTextureBlend), + mMapName(other.mMapName), + mOffsetU(other.mOffsetU), + mOffsetV(other.mOffsetV), + mScaleU(other.mScaleU), + mScaleV(other.mScaleV), + mRotation(other.mRotation), + mMapMode(other.mMapMode), + bPrivate(other.bPrivate), + iUVSrc(other.iUVSrc) { + // empty + } + + Texture(Texture &&other) AI_NO_EXCEPT : mTextureBlend(other.mTextureBlend), + mMapName(std::move(other.mMapName)), + mOffsetU(other.mOffsetU), + mOffsetV(other.mOffsetV), + mScaleU(other.mScaleU), + mScaleV(other.mScaleV), + mRotation(other.mRotation), + mMapMode(other.mMapMode), + bPrivate(other.bPrivate), + iUVSrc(other.iUVSrc) { + // empty + } + + Texture &operator=(Texture &&other) AI_NO_EXCEPT { + if (this == &other) { + return *this; + } + + mTextureBlend = other.mTextureBlend; + mMapName = std::move(other.mMapName); + mOffsetU = other.mOffsetU; + mOffsetV = other.mOffsetV; + mScaleU = other.mScaleU; + mScaleV = other.mScaleV; + mRotation = other.mRotation; + mMapMode = other.mMapMode; + bPrivate = other.bPrivate; + iUVSrc = other.iUVSrc; + + return *this; + } + + //! Specifies the blend factor for the texture + ai_real mTextureBlend; + + //! Specifies the filename of the texture + std::string mMapName; + + //! Specifies texture coordinate offsets/scaling/rotations + ai_real mOffsetU; + ai_real mOffsetV; + ai_real mScaleU; + ai_real mScaleV; + ai_real mRotation; + + //! Specifies the mapping mode to be used for the texture + aiTextureMapMode mMapMode; + + //! Used internally + bool bPrivate; + int iUVSrc; +}; + +#include <assimp/Compiler/poppack1.h> + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +// --------------------------------------------------------------------------- +/** Helper structure representing a 3ds material */ +struct Material { + //! Default constructor has been deleted + Material() : + mName(), + mDiffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + mSpecularExponent(ai_real(0.0)), + mShininessStrength(ai_real(1.0)), + mShading(Discreet3DS::Gouraud), + mTransparency(ai_real(1.0)), + mBumpHeight(ai_real(1.0)), + mTwoSided(false) { + // empty + } + + //! Constructor with explicit name + explicit Material(const std::string &name) : + mName(name), + mDiffuse(ai_real(0.6), ai_real(0.6), ai_real(0.6)), + mSpecularExponent(ai_real(0.0)), + mShininessStrength(ai_real(1.0)), + mShading(Discreet3DS::Gouraud), + mTransparency(ai_real(1.0)), + mBumpHeight(ai_real(1.0)), + mTwoSided(false) { + // empty + } + + Material(const Material &other) : + mName(other.mName), + mDiffuse(other.mDiffuse), + mSpecularExponent(other.mSpecularExponent), + mShininessStrength(other.mShininessStrength), + mSpecular(other.mSpecular), + mAmbient(other.mAmbient), + mShading(other.mShading), + mTransparency(other.mTransparency), + sTexDiffuse(other.sTexDiffuse), + sTexOpacity(other.sTexOpacity), + sTexSpecular(other.sTexSpecular), + sTexReflective(other.sTexReflective), + sTexBump(other.sTexBump), + sTexEmissive(other.sTexEmissive), + sTexShininess(other.sTexShininess), + mBumpHeight(other.mBumpHeight), + mEmissive(other.mEmissive), + sTexAmbient(other.sTexAmbient), + mTwoSided(other.mTwoSided) { + // empty + } + + //! Move constructor. This is explicitly written because MSVC doesn't support defaulting it + Material(Material &&other) AI_NO_EXCEPT : mName(std::move(other.mName)), + mDiffuse(other.mDiffuse), + mSpecularExponent(other.mSpecularExponent), + mShininessStrength(other.mShininessStrength), + mSpecular(other.mSpecular), + mAmbient(other.mAmbient), + mShading(other.mShading), + mTransparency(other.mTransparency), + sTexDiffuse(std::move(other.sTexDiffuse)), + sTexOpacity(std::move(other.sTexOpacity)), + sTexSpecular(std::move(other.sTexSpecular)), + sTexReflective(std::move(other.sTexReflective)), + sTexBump(std::move(other.sTexBump)), + sTexEmissive(std::move(other.sTexEmissive)), + sTexShininess(std::move(other.sTexShininess)), + mBumpHeight(other.mBumpHeight), + mEmissive(other.mEmissive), + sTexAmbient(std::move(other.sTexAmbient)), + mTwoSided(other.mTwoSided) { + // empty + } + + Material &operator=(Material &&other) AI_NO_EXCEPT { + if (this == &other) { + return *this; + } + + mName = std::move(other.mName); + mDiffuse = other.mDiffuse; + mSpecularExponent = other.mSpecularExponent; + mShininessStrength = other.mShininessStrength, + mSpecular = other.mSpecular; + mAmbient = other.mAmbient; + mShading = other.mShading; + mTransparency = other.mTransparency; + sTexDiffuse = std::move(other.sTexDiffuse); + sTexOpacity = std::move(other.sTexOpacity); + sTexSpecular = std::move(other.sTexSpecular); + sTexReflective = std::move(other.sTexReflective); + sTexBump = std::move(other.sTexBump); + sTexEmissive = std::move(other.sTexEmissive); + sTexShininess = std::move(other.sTexShininess); + mBumpHeight = other.mBumpHeight; + mEmissive = other.mEmissive; + sTexAmbient = std::move(other.sTexAmbient); + mTwoSided = other.mTwoSided; + + return *this; + } + + virtual ~Material() { + // empty + } + + //! Name of the material + std::string mName; + //! Diffuse color of the material + aiColor3D mDiffuse; + //! Specular exponent + ai_real mSpecularExponent; + //! Shininess strength, in percent + ai_real mShininessStrength; + //! Specular color of the material + aiColor3D mSpecular; + //! Ambient color of the material + aiColor3D mAmbient; + //! Shading type to be used + Discreet3DS::shadetype3ds mShading; + //! Opacity of the material + ai_real mTransparency; + //! Diffuse texture channel + Texture sTexDiffuse; + //! Opacity texture channel + Texture sTexOpacity; + //! Specular texture channel + Texture sTexSpecular; + //! Reflective texture channel + Texture sTexReflective; + //! Bump texture channel + Texture sTexBump; + //! Emissive texture channel + Texture sTexEmissive; + //! Shininess texture channel + Texture sTexShininess; + //! Scaling factor for the bump values + ai_real mBumpHeight; + //! Emissive color + aiColor3D mEmissive; + //! Ambient texture channel + //! (used by the ASE format) + Texture sTexAmbient; + //! True if the material must be rendered from two sides + bool mTwoSided; +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent a 3ds file mesh */ +struct Mesh : public MeshWithSmoothingGroups<D3DS::Face> { + //! Default constructor has been deleted + Mesh() = delete; + + //! Constructor with explicit name + explicit Mesh(const std::string &name) : + mName(name) { + } + + //! Name of the mesh + std::string mName; + + //! Texture coordinates + std::vector<aiVector3D> mTexCoords; + + //! Face materials + std::vector<unsigned int> mFaceMaterials; + + //! Local transformation matrix + aiMatrix4x4 mMat; +}; + +// --------------------------------------------------------------------------- +/** Float key - quite similar to aiVectorKey and aiQuatKey. Both are in the + C-API, so it would be difficult to make them a template. */ +struct aiFloatKey { + double mTime; ///< The time of this key + ai_real mValue; ///< The value of this key + +#ifdef __cplusplus + + // time is not compared + bool operator==(const aiFloatKey &o) const { return o.mValue == this->mValue; } + + bool operator!=(const aiFloatKey &o) const { return o.mValue != this->mValue; } + + // Only time is compared. This operator is defined + // for use with std::sort + bool operator<(const aiFloatKey &o) const { return mTime < o.mTime; } + + bool operator>(const aiFloatKey &o) const { return mTime > o.mTime; } + +#endif +}; + +// --------------------------------------------------------------------------- +/** Helper structure to represent a 3ds file node */ +struct Node { + Node() = delete; + + explicit Node(const std::string &name) : + mParent(nullptr), + mName(name), + mInstanceNumber(0), + mHierarchyPos(0), + mHierarchyIndex(0), + mInstanceCount(1) { + aRotationKeys.reserve(20); + aPositionKeys.reserve(20); + aScalingKeys.reserve(20); + } + + ~Node() { + for (unsigned int i = 0; i < mChildren.size(); ++i) + delete mChildren[i]; + } + + //! Pointer to the parent node + Node *mParent; + + //! Holds all child nodes + std::vector<Node *> mChildren; + + //! Name of the node + std::string mName; + + //! InstanceNumber of the node + int32_t mInstanceNumber; + + //! Dummy nodes: real name to be combined with the $$$DUMMY + std::string mDummyName; + + //! Position of the node in the hierarchy (tree depth) + int16_t mHierarchyPos; + + //! Index of the node + int16_t mHierarchyIndex; + + //! Rotation keys loaded from the file + std::vector<aiQuatKey> aRotationKeys; + + //! Position keys loaded from the file + std::vector<aiVectorKey> aPositionKeys; + + //! Scaling keys loaded from the file + std::vector<aiVectorKey> aScalingKeys; + + // For target lights (spot lights and directional lights): + // The position of the target + std::vector<aiVectorKey> aTargetPositionKeys; + + // For cameras: the camera roll angle + std::vector<aiFloatKey> aCameraRollKeys; + + //! Pivot position loaded from the file + aiVector3D vPivot; + + //instance count, will be kept only for the first node + int32_t mInstanceCount; + + //! Add a child node, setup the right parent node for it + //! \param pc Node to be 'adopted' + inline Node &push_back(Node *pc) { + mChildren.push_back(pc); + pc->mParent = this; + return *this; + } +}; +// --------------------------------------------------------------------------- +/** Helper structure analogue to aiScene */ +struct Scene { + //! List of all materials loaded + //! NOTE: 3ds references materials globally + std::vector<Material> mMaterials; + + //! List of all meshes loaded + std::vector<Mesh> mMeshes; + + //! List of all cameras loaded + std::vector<aiCamera *> mCameras; + + //! List of all lights loaded + std::vector<aiLight *> mLights; + + //! Pointer to the root node of the scene + // --- moved to main class + // Node* pcRootNode; +}; + +} // end of namespace D3DS +} // end of namespace Assimp + +#endif // AI_XFILEHELPER_H_INC diff --git a/libs/assimp/code/AssetLib/3DS/3DSLoader.cpp b/libs/assimp/code/AssetLib/3DS/3DSLoader.cpp new file mode 100644 index 0000000..0ec8b87 --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSLoader.cpp @@ -0,0 +1,1336 @@ +/* +--------------------------------------------------------------------------- +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 3DSLoader.cpp + * @brief Implementation of the 3ds importer class + * + * http://www.the-labs.com/Blender/3DS-details.html + */ + +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +#include "3DSLoader.h" +#include <assimp/StringComparison.h> +#include <assimp/importerdesc.h> +#include <assimp/scene.h> +#include <assimp/DefaultLogger.hpp> +#include <assimp/IOSystem.hpp> + +using namespace Assimp; + +static const aiImporterDesc desc = { + "Discreet 3DS Importer", + "", + "", + "Limited animation support", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "3ds prj" +}; + +// ------------------------------------------------------------------------------------------------ +// Begins a new parsing block +// - Reads the current chunk and validates it +// - computes its length +#define ASSIMP_3DS_BEGIN_CHUNK() \ + while (true) { \ + if (stream->GetRemainingSizeToLimit() < sizeof(Discreet3DS::Chunk)) { \ + return; \ + } \ + Discreet3DS::Chunk chunk; \ + ReadChunk(&chunk); \ + int chunkSize = chunk.Size - sizeof(Discreet3DS::Chunk); \ + if (chunkSize <= 0) \ + continue; \ + const unsigned int oldReadLimit = stream->SetReadLimit( \ + stream->GetCurrentPos() + chunkSize); + +// ------------------------------------------------------------------------------------------------ +// End a parsing block +// Must follow at the end of each parsing block, reset chunk end marker to previous value +#define ASSIMP_3DS_END_CHUNK() \ + stream->SkipToReadLimit(); \ + stream->SetReadLimit(oldReadLimit); \ + if (stream->GetRemainingSizeToLimit() == 0) \ + return; \ + } + +// ------------------------------------------------------------------------------------------------ +// Constructor to be privately used by Importer +Discreet3DSImporter::Discreet3DSImporter() : + stream(), mLastNodeIndex(), mCurrentNode(), mRootNode(), mScene(), mMasterScale(), bHasBG(), bIsPrj() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Destructor, private as well +Discreet3DSImporter::~Discreet3DSImporter() { + // empty +} + +// ------------------------------------------------------------------------------------------------ +// Returns whether the class can handle the format of the given file. +bool Discreet3DSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const { + static const uint16_t token[] = { 0x4d4d, 0x3dc2 /*, 0x3daa */ }; + return CheckMagicToken(pIOHandler, pFile, token, AI_COUNT_OF(token), 0, sizeof token[0]); +} + +// ------------------------------------------------------------------------------------------------ +// Loader registry entry +const aiImporterDesc *Discreet3DSImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Setup configuration properties +void Discreet3DSImporter::SetupProperties(const Importer * /*pImp*/) { + // nothing to be done for the moment +} + +// ------------------------------------------------------------------------------------------------ +// Imports the given file into the given scene structure. +void Discreet3DSImporter::InternReadFile(const std::string &pFile, + aiScene *pScene, IOSystem *pIOHandler) { + + auto theFile = pIOHandler->Open(pFile, "rb"); + if (!theFile) { + throw DeadlyImportError("3DS: Could not open ", pFile); + } + + StreamReaderLE theStream(theFile); + + // We should have at least one chunk + if (theStream.GetRemainingSize() < 16) { + throw DeadlyImportError("3DS file is either empty or corrupt: ", pFile); + } + this->stream = &theStream; + + // Allocate our temporary 3DS representation + D3DS::Scene _scene; + mScene = &_scene; + + // Initialize members + D3DS::Node _rootNode("UNNAMED"); + mLastNodeIndex = -1; + mCurrentNode = &_rootNode; + mRootNode = mCurrentNode; + mRootNode->mHierarchyPos = -1; + mRootNode->mHierarchyIndex = -1; + mRootNode->mParent = nullptr; + mMasterScale = 1.0f; + mBackgroundImage = std::string(); + bHasBG = false; + bIsPrj = false; + + // Parse the file + ParseMainChunk(); + + // Process all meshes in the file. First check whether all + // face indices have valid values. The generate our + // internal verbose representation. Finally compute normal + // vectors from the smoothing groups we read from the + // file. + for (auto &mesh : mScene->mMeshes) { + if (mesh.mFaces.size() > 0 && mesh.mPositions.size() == 0) { + throw DeadlyImportError("3DS file contains faces but no vertices: ", pFile); + } + CheckIndices(mesh); + MakeUnique(mesh); + ComputeNormalsWithSmoothingsGroups<D3DS::Face>(mesh); + } + + // Replace all occurrences of the default material with a + // valid material. Generate it if no material containing + // DEFAULT in its name has been found in the file + ReplaceDefaultMaterial(); + + // Convert the scene from our internal representation to an + // aiScene object. This involves copying all meshes, lights + // and cameras to the scene + ConvertScene(pScene); + + // Generate the node graph for the scene. This is a little bit + // tricky since we'll need to split some meshes into sub-meshes + GenerateNodeGraph(pScene); + + // Now apply the master scaling factor to the scene + ApplyMasterScale(pScene); + + // Our internal scene representation and the root + // node will be automatically deleted, so the whole hierarchy will follow + + AI_DEBUG_INVALIDATE_PTR(mRootNode); + AI_DEBUG_INVALIDATE_PTR(mScene); + AI_DEBUG_INVALIDATE_PTR(this->stream); +} + +// ------------------------------------------------------------------------------------------------ +// Applies a master-scaling factor to the imported scene +void Discreet3DSImporter::ApplyMasterScale(aiScene *pScene) { + // There are some 3DS files with a zero scaling factor + if (!mMasterScale) + mMasterScale = 1.0f; + else + mMasterScale = 1.0f / mMasterScale; + + // Construct an uniform scaling matrix and multiply with it + pScene->mRootNode->mTransformation *= aiMatrix4x4( + mMasterScale, 0.0f, 0.0f, 0.0f, + 0.0f, mMasterScale, 0.0f, 0.0f, + 0.0f, 0.0f, mMasterScale, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f); + + // Check whether a scaling track is assigned to the root node. +} + +// ------------------------------------------------------------------------------------------------ +// Reads a new chunk from the file +void Discreet3DSImporter::ReadChunk(Discreet3DS::Chunk *pcOut) { + ai_assert(pcOut != nullptr); + + pcOut->Flag = stream->GetI2(); + pcOut->Size = stream->GetI4(); + + if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSize()) { + throw DeadlyImportError("Chunk is too large"); + } + + if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSizeToLimit()) { + ASSIMP_LOG_ERROR("3DS: Chunk overflow"); + } +} + +// ------------------------------------------------------------------------------------------------ +// Skip a chunk +void Discreet3DSImporter::SkipChunk() { + Discreet3DS::Chunk psChunk; + ReadChunk(&psChunk); + + stream->IncPtr(psChunk.Size - sizeof(Discreet3DS::Chunk)); + return; +} + +// ------------------------------------------------------------------------------------------------ +// Process the primary chunk of the file +void Discreet3DSImporter::ParseMainChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + + case Discreet3DS::CHUNK_PRJ: + bIsPrj = true; + break; + case Discreet3DS::CHUNK_MAIN: + ParseEditorChunk(); + break; + }; + + ASSIMP_3DS_END_CHUNK(); + // recursively continue processing this hierarchy level + return ParseMainChunk(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseEditorChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_OBJMESH: + + ParseObjectChunk(); + break; + + // NOTE: In several documentations in the internet this + // chunk appears at different locations + case Discreet3DS::CHUNK_KEYFRAMER: + + ParseKeyframeChunk(); + break; + + case Discreet3DS::CHUNK_VERSION: { + // print the version number + char buff[10]; + ASSIMP_itoa10(buff, stream->GetI2()); + ASSIMP_LOG_INFO("3DS file format version: ", buff); + } break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseObjectChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_OBJBLOCK: { + unsigned int cnt = 0; + const char *sz = (const char *)stream->GetPtr(); + + // Get the name of the geometry object + while (stream->GetI1()) + ++cnt; + ParseChunk(sz, cnt); + } break; + + case Discreet3DS::CHUNK_MAT_MATERIAL: + + // Add a new material to the list + mScene->mMaterials.push_back(D3DS::Material(std::string("UNNAMED_" + ai_to_string(mScene->mMaterials.size())))); + ParseMaterialChunk(); + break; + + case Discreet3DS::CHUNK_AMBCOLOR: + + // This is the ambient base color of the scene. + // We add it to the ambient color of all materials + ParseColorChunk(&mClrAmbient, true); + if (is_qnan(mClrAmbient.r)) { + // We failed to read the ambient base color. + ASSIMP_LOG_ERROR("3DS: Failed to read ambient base color"); + mClrAmbient.r = mClrAmbient.g = mClrAmbient.b = 0.0f; + } + break; + + case Discreet3DS::CHUNK_BIT_MAP: { + // Specifies the background image. The string should already be + // properly 0 terminated but we need to be sure + unsigned int cnt = 0; + const char *sz = (const char *)stream->GetPtr(); + while (stream->GetI1()) + ++cnt; + mBackgroundImage = std::string(sz, cnt); + } break; + + case Discreet3DS::CHUNK_BIT_MAP_EXISTS: + bHasBG = true; + break; + + case Discreet3DS::CHUNK_MASTER_SCALE: + // Scene master scaling factor + mMasterScale = stream->GetF4(); + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseChunk(const char *name, unsigned int num) { + ASSIMP_3DS_BEGIN_CHUNK(); + + // IMPLEMENTATION NOTE; + // Cameras or lights define their transformation in their parent node and in the + // corresponding light or camera chunks. However, we read and process the latter + // to to be able to return valid cameras/lights even if no scenegraph is given. + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_TRIMESH: { + // this starts a new triangle mesh + mScene->mMeshes.push_back(D3DS::Mesh(std::string(name, num))); + + // Read mesh chunks + ParseMeshChunk(); + } break; + + case Discreet3DS::CHUNK_LIGHT: { + // This starts a new light + aiLight *light = new aiLight(); + mScene->mLights.push_back(light); + + light->mName.Set(std::string(name, num)); + + // First read the position of the light + light->mPosition.x = stream->GetF4(); + light->mPosition.y = stream->GetF4(); + light->mPosition.z = stream->GetF4(); + + light->mColorDiffuse = aiColor3D(1.f, 1.f, 1.f); + + // Now check for further subchunks + if (!bIsPrj) /* fixme */ + ParseLightChunk(); + + // The specular light color is identical the the diffuse light color. The ambient light color + // is equal to the ambient base color of the whole scene. + light->mColorSpecular = light->mColorDiffuse; + light->mColorAmbient = mClrAmbient; + + if (light->mType == aiLightSource_UNDEFINED) { + // It must be a point light + light->mType = aiLightSource_POINT; + } + } break; + + case Discreet3DS::CHUNK_CAMERA: { + // This starts a new camera + aiCamera *camera = new aiCamera(); + mScene->mCameras.push_back(camera); + camera->mName.Set(std::string(name, num)); + + // First read the position of the camera + camera->mPosition.x = stream->GetF4(); + camera->mPosition.y = stream->GetF4(); + camera->mPosition.z = stream->GetF4(); + + // Then the camera target + camera->mLookAt.x = stream->GetF4() - camera->mPosition.x; + camera->mLookAt.y = stream->GetF4() - camera->mPosition.y; + camera->mLookAt.z = stream->GetF4() - camera->mPosition.z; + ai_real len = camera->mLookAt.Length(); + if (len < 1e-5) { + + // There are some files with lookat == position. Don't know why or whether it's ok or not. + ASSIMP_LOG_ERROR("3DS: Unable to read proper camera look-at vector"); + camera->mLookAt = aiVector3D(0.0, 1.0, 0.0); + + } else + camera->mLookAt /= len; + + // And finally - the camera rotation angle, in counter clockwise direction + const ai_real angle = AI_DEG_TO_RAD(stream->GetF4()); + aiQuaternion quat(camera->mLookAt, angle); + camera->mUp = quat.GetMatrix() * aiVector3D(0.0, 1.0, 0.0); + + // Read the lense angle + camera->mHorizontalFOV = AI_DEG_TO_RAD(stream->GetF4()); + if (camera->mHorizontalFOV < 0.001f) { + camera->mHorizontalFOV = float(AI_DEG_TO_RAD(45.f)); + } + + // Now check for further subchunks + if (!bIsPrj) /* fixme */ { + ParseCameraChunk(); + } + } break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseLightChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + aiLight *light = mScene->mLights.back(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_DL_SPOTLIGHT: + // Now we can be sure that the light is a spot light + light->mType = aiLightSource_SPOT; + + // We wouldn't need to normalize here, but we do it + light->mDirection.x = stream->GetF4() - light->mPosition.x; + light->mDirection.y = stream->GetF4() - light->mPosition.y; + light->mDirection.z = stream->GetF4() - light->mPosition.z; + light->mDirection.Normalize(); + + // Now the hotspot and falloff angles - in degrees + light->mAngleInnerCone = AI_DEG_TO_RAD(stream->GetF4()); + + // FIX: the falloff angle is just an offset + light->mAngleOuterCone = light->mAngleInnerCone + AI_DEG_TO_RAD(stream->GetF4()); + break; + + // intensity multiplier + case Discreet3DS::CHUNK_DL_MULTIPLIER: + light->mColorDiffuse = light->mColorDiffuse * stream->GetF4(); + break; + + // light color + case Discreet3DS::CHUNK_RGBF: + case Discreet3DS::CHUNK_LINRGBF: + light->mColorDiffuse.r *= stream->GetF4(); + light->mColorDiffuse.g *= stream->GetF4(); + light->mColorDiffuse.b *= stream->GetF4(); + break; + + // light attenuation + case Discreet3DS::CHUNK_DL_ATTENUATE: + light->mAttenuationLinear = stream->GetF4(); + break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseCameraChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + aiCamera *camera = mScene->mCameras.back(); + + // get chunk type + switch (chunk.Flag) { + // near and far clip plane + case Discreet3DS::CHUNK_CAM_RANGES: + camera->mClipPlaneNear = stream->GetF4(); + camera->mClipPlaneFar = stream->GetF4(); + break; + } + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseKeyframeChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_TRACKCAMTGT: + case Discreet3DS::CHUNK_TRACKSPOTL: + case Discreet3DS::CHUNK_TRACKCAMERA: + case Discreet3DS::CHUNK_TRACKINFO: + case Discreet3DS::CHUNK_TRACKLIGHT: + case Discreet3DS::CHUNK_TRACKLIGTGT: + + // this starts a new mesh hierarchy chunk + ParseHierarchyChunk(chunk.Flag); + break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Little helper function for ParseHierarchyChunk +void Discreet3DSImporter::InverseNodeSearch(D3DS::Node *pcNode, D3DS::Node *pcCurrent) { + if (!pcCurrent) { + mRootNode->push_back(pcNode); + return; + } + + if (pcCurrent->mHierarchyPos == pcNode->mHierarchyPos) { + if (pcCurrent->mParent) { + pcCurrent->mParent->push_back(pcNode); + } else + pcCurrent->push_back(pcNode); + return; + } + return InverseNodeSearch(pcNode, pcCurrent->mParent); +} + +// ------------------------------------------------------------------------------------------------ +// Find a node with a specific name in the import hierarchy +D3DS::Node *FindNode(D3DS::Node *root, const std::string &name) { + if (root->mName == name) { + return root; + } + + for (std::vector<D3DS::Node *>::iterator it = root->mChildren.begin(); it != root->mChildren.end(); ++it) { + D3DS::Node *nd = FindNode(*it, name); + if (nullptr != nd) { + return nd; + } + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// Binary predicate for std::unique() +template <class T> +bool KeyUniqueCompare(const T &first, const T &second) { + return first.mTime == second.mTime; +} + +// ------------------------------------------------------------------------------------------------ +// Skip some additional import data. +void Discreet3DSImporter::SkipTCBInfo() { + unsigned int flags = stream->GetI2(); + + if (!flags) { + // Currently we can't do anything with these values. They occur + // quite rare, so it wouldn't be worth the effort implementing + // them. 3DS is not really suitable for complex animations, + // so full support is not required. + ASSIMP_LOG_WARN("3DS: Skipping TCB animation info"); + } + + if (flags & Discreet3DS::KEY_USE_TENS) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_BIAS) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_CONT) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_EASE_FROM) { + stream->IncPtr(4); + } + if (flags & Discreet3DS::KEY_USE_EASE_TO) { + stream->IncPtr(4); + } +} + +// ------------------------------------------------------------------------------------------------ +// Read hierarchy and keyframe info +void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent) { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_TRACKOBJNAME: + + // This is the name of the object to which the track applies. The chunk also + // defines the position of this object in the hierarchy. + { + + // First of all: get the name of the object + unsigned int cnt = 0; + const char *sz = (const char *)stream->GetPtr(); + + while (stream->GetI1()) + ++cnt; + std::string name = std::string(sz, cnt); + + // Now find out whether we have this node already (target animation channels + // are stored with a separate object ID) + D3DS::Node *pcNode = FindNode(mRootNode, name); + int instanceNumber = 1; + + if (pcNode) { + // if the source is not a CHUNK_TRACKINFO block it won't be an object instance + if (parent != Discreet3DS::CHUNK_TRACKINFO) { + mCurrentNode = pcNode; + break; + } + pcNode->mInstanceCount++; + instanceNumber = pcNode->mInstanceCount; + } + pcNode = new D3DS::Node(name); + pcNode->mInstanceNumber = instanceNumber; + + // There are two unknown values which we can safely ignore + stream->IncPtr(4); + + // Now read the hierarchy position of the object + uint16_t hierarchy = stream->GetI2() + 1; + pcNode->mHierarchyPos = hierarchy; + pcNode->mHierarchyIndex = mLastNodeIndex; + + // And find a proper position in the graph for it + if (mCurrentNode && mCurrentNode->mHierarchyPos == hierarchy) { + + // add to the parent of the last touched node + mCurrentNode->mParent->push_back(pcNode); + mLastNodeIndex++; + } else if (hierarchy >= mLastNodeIndex) { + + // place it at the current position in the hierarchy + mCurrentNode->push_back(pcNode); + mLastNodeIndex = hierarchy; + } else { + // need to go back to the specified position in the hierarchy. + InverseNodeSearch(pcNode, mCurrentNode); + mLastNodeIndex++; + } + // Make this node the current node + mCurrentNode = pcNode; + } + break; + + case Discreet3DS::CHUNK_TRACKDUMMYOBJNAME: + + // This is the "real" name of a $$$DUMMY object + { + const char *sz = (const char *)stream->GetPtr(); + while (stream->GetI1()) + ; + + // If object name is DUMMY, take this one instead + if (mCurrentNode->mName == "$$$DUMMY") { + mCurrentNode->mName = std::string(sz); + break; + } + } + break; + + case Discreet3DS::CHUNK_TRACKPIVOT: + + if (Discreet3DS::CHUNK_TRACKINFO != parent) { + ASSIMP_LOG_WARN("3DS: Skipping pivot subchunk for non usual object"); + break; + } + + // Pivot = origin of rotation and scaling + mCurrentNode->vPivot.x = stream->GetF4(); + mCurrentNode->vPivot.y = stream->GetF4(); + mCurrentNode->vPivot.z = stream->GetF4(); + break; + + // //////////////////////////////////////////////////////////////////// + // POSITION KEYFRAME + case Discreet3DS::CHUNK_TRACKPOS: { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + bool sortKeys = false; + + // This could also be meant as the target position for + // (targeted) lights and cameras + std::vector<aiVectorKey> *l; + if (Discreet3DS::CHUNK_TRACKCAMTGT == parent || Discreet3DS::CHUNK_TRACKLIGTGT == parent) { + l = &mCurrentNode->aTargetPositionKeys; + } else + l = &mCurrentNode->aPositionKeys; + + l->reserve(numFrames); + for (unsigned int i = 0; i < numFrames; ++i) { + const unsigned int fidx = stream->GetI4(); + + // Setup a new position key + aiVectorKey v; + v.mTime = (double)fidx; + + SkipTCBInfo(); + v.mValue.x = stream->GetF4(); + v.mValue.y = stream->GetF4(); + v.mValue.z = stream->GetF4(); + + // check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Add the new keyframe to the list + l->push_back(v); + } + + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(), l->end()); + l->erase(std::unique(l->begin(), l->end(), &KeyUniqueCompare<aiVectorKey>), l->end()); + } + } + + break; + + // //////////////////////////////////////////////////////////////////// + // CAMERA ROLL KEYFRAME + case Discreet3DS::CHUNK_TRACKROLL: { + // roll keys are accepted for cameras only + if (parent != Discreet3DS::CHUNK_TRACKCAMERA) { + ASSIMP_LOG_WARN("3DS: Ignoring roll track for non-camera object"); + break; + } + bool sortKeys = false; + std::vector<aiFloatKey> *l = &mCurrentNode->aCameraRollKeys; + + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + l->reserve(numFrames); + for (unsigned int i = 0; i < numFrames; ++i) { + const unsigned int fidx = stream->GetI4(); + + // Setup a new position key + aiFloatKey v; + v.mTime = (double)fidx; + + // This is just a single float + SkipTCBInfo(); + v.mValue = stream->GetF4(); + + // Check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Add the new keyframe to the list + l->push_back(v); + } + + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(), l->end()); + l->erase(std::unique(l->begin(), l->end(), &KeyUniqueCompare<aiFloatKey>), l->end()); + } + } break; + + // //////////////////////////////////////////////////////////////////// + // CAMERA FOV KEYFRAME + case Discreet3DS::CHUNK_TRACKFOV: { + ASSIMP_LOG_ERROR("3DS: Skipping FOV animation track. " + "This is not supported"); + } break; + + // //////////////////////////////////////////////////////////////////// + // ROTATION KEYFRAME + case Discreet3DS::CHUNK_TRACKROTATE: { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI4(); + + bool sortKeys = false; + std::vector<aiQuatKey> *l = &mCurrentNode->aRotationKeys; + l->reserve(numFrames); + + for (unsigned int i = 0; i < numFrames; ++i) { + const unsigned int fidx = stream->GetI4(); + SkipTCBInfo(); + + aiQuatKey v; + v.mTime = (double)fidx; + + // The rotation keyframe is given as an axis-angle pair + const float rad = stream->GetF4(); + aiVector3D axis; + axis.x = stream->GetF4(); + axis.y = stream->GetF4(); + axis.z = stream->GetF4(); + + if (!axis.x && !axis.y && !axis.z) + axis.y = 1.f; + + // Construct a rotation quaternion from the axis-angle pair + v.mValue = aiQuaternion(axis, rad); + + // Check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // add the new keyframe to the list + l->push_back(v); + } + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(), l->end()); + l->erase(std::unique(l->begin(), l->end(), &KeyUniqueCompare<aiQuatKey>), l->end()); + } + } break; + + // //////////////////////////////////////////////////////////////////// + // SCALING KEYFRAME + case Discreet3DS::CHUNK_TRACKSCALE: { + stream->IncPtr(10); + const unsigned int numFrames = stream->GetI2(); + stream->IncPtr(2); + + bool sortKeys = false; + std::vector<aiVectorKey> *l = &mCurrentNode->aScalingKeys; + l->reserve(numFrames); + + for (unsigned int i = 0; i < numFrames; ++i) { + const unsigned int fidx = stream->GetI4(); + SkipTCBInfo(); + + // Setup a new key + aiVectorKey v; + v.mTime = (double)fidx; + + // ... and read its value + v.mValue.x = stream->GetF4(); + v.mValue.y = stream->GetF4(); + v.mValue.z = stream->GetF4(); + + // check whether we'll need to sort the keys + if (!l->empty() && v.mTime <= l->back().mTime) + sortKeys = true; + + // Remove zero-scalings on singular axes - they've been reported to be there erroneously in some strange files + if (!v.mValue.x) v.mValue.x = 1.f; + if (!v.mValue.y) v.mValue.y = 1.f; + if (!v.mValue.z) v.mValue.z = 1.f; + + l->push_back(v); + } + // Sort all keys with ascending time values and remove duplicates? + if (sortKeys) { + std::stable_sort(l->begin(), l->end()); + l->erase(std::unique(l->begin(), l->end(), &KeyUniqueCompare<aiVectorKey>), l->end()); + } + } break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a face chunk - it contains smoothing groups and material assignments +void Discreet3DSImporter::ParseFaceChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // Get the mesh we're currently working on + D3DS::Mesh &mMesh = mScene->mMeshes.back(); + + // Get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_SMOOLIST: { + // This is the list of smoothing groups - a bitfield for every face. + // Up to 32 smoothing groups assigned to a single face. + unsigned int num = chunkSize / 4, m = 0; + if (num > mMesh.mFaces.size()) { + throw DeadlyImportError("3DS: More smoothing groups than faces"); + } + for (std::vector<D3DS::Face>::iterator i = mMesh.mFaces.begin(); m != num; ++i, ++m) { + // nth bit is set for nth smoothing group + (*i).iSmoothGroup = stream->GetI4(); + } + } break; + + case Discreet3DS::CHUNK_FACEMAT: { + // at fist an asciiz with the material name + const char *sz = (const char *)stream->GetPtr(); + while (stream->GetI1()) + ; + + // find the index of the material + unsigned int idx = 0xcdcdcdcd, cnt = 0; + for (std::vector<D3DS::Material>::const_iterator i = mScene->mMaterials.begin(); i != mScene->mMaterials.end(); ++i, ++cnt) { + // use case independent comparisons. hopefully it will work. + if ((*i).mName.length() && !ASSIMP_stricmp(sz, (*i).mName.c_str())) { + idx = cnt; + break; + } + } + if (0xcdcdcdcd == idx) { + ASSIMP_LOG_ERROR("3DS: Unknown material: ", sz); + } + + // Now continue and read all material indices + cnt = (uint16_t)stream->GetI2(); + for (unsigned int i = 0; i < cnt; ++i) { + unsigned int fidx = (uint16_t)stream->GetI2(); + + // check range + if (fidx >= mMesh.mFaceMaterials.size()) { + ASSIMP_LOG_ERROR("3DS: Invalid face index in face material list"); + } else + mMesh.mFaceMaterials[fidx] = idx; + } + } break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a mesh chunk. Here's the actual mesh data +void Discreet3DSImporter::ParseMeshChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + + // Get the mesh we're currently working on + D3DS::Mesh &mMesh = mScene->mMeshes.back(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_VERTLIST: { + // This is the list of all vertices in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mPositions.reserve(num); + while (num-- > 0) { + aiVector3D v; + v.x = stream->GetF4(); + v.y = stream->GetF4(); + v.z = stream->GetF4(); + mMesh.mPositions.push_back(v); + } + } break; + case Discreet3DS::CHUNK_TRMATRIX: { + // This is the RLEATIVE transformation matrix of the current mesh. Vertices are + // pretransformed by this matrix wonder. + mMesh.mMat.a1 = stream->GetF4(); + mMesh.mMat.b1 = stream->GetF4(); + mMesh.mMat.c1 = stream->GetF4(); + mMesh.mMat.a2 = stream->GetF4(); + mMesh.mMat.b2 = stream->GetF4(); + mMesh.mMat.c2 = stream->GetF4(); + mMesh.mMat.a3 = stream->GetF4(); + mMesh.mMat.b3 = stream->GetF4(); + mMesh.mMat.c3 = stream->GetF4(); + mMesh.mMat.a4 = stream->GetF4(); + mMesh.mMat.b4 = stream->GetF4(); + mMesh.mMat.c4 = stream->GetF4(); + } break; + + case Discreet3DS::CHUNK_MAPLIST: { + // This is the list of all UV coords in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mTexCoords.reserve(num); + while (num-- > 0) { + aiVector3D v; + v.x = stream->GetF4(); + v.y = stream->GetF4(); + mMesh.mTexCoords.push_back(v); + } + } break; + + case Discreet3DS::CHUNK_FACELIST: { + // This is the list of all faces in the current mesh + int num = (int)(uint16_t)stream->GetI2(); + mMesh.mFaces.reserve(num); + while (num-- > 0) { + // 3DS faces are ALWAYS triangles + mMesh.mFaces.push_back(D3DS::Face()); + D3DS::Face &sFace = mMesh.mFaces.back(); + + sFace.mIndices[0] = (uint16_t)stream->GetI2(); + sFace.mIndices[1] = (uint16_t)stream->GetI2(); + sFace.mIndices[2] = (uint16_t)stream->GetI2(); + + stream->IncPtr(2); // skip edge visibility flag + } + + // Resize the material array (0xcdcdcdcd marks the default material; so if a face is + // not referenced by a material, $$DEFAULT will be assigned to it) + mMesh.mFaceMaterials.resize(mMesh.mFaces.size(), 0xcdcdcdcd); + + // Larger 3DS files could have multiple FACE chunks here + chunkSize = (int)stream->GetRemainingSizeToLimit(); + if (chunkSize > (int)sizeof(Discreet3DS::Chunk)) + ParseFaceChunk(); + } break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a 3DS material chunk +void Discreet3DSImporter::ParseMaterialChunk() { + ASSIMP_3DS_BEGIN_CHUNK(); + switch (chunk.Flag) { + case Discreet3DS::CHUNK_MAT_MATNAME: + + { + // The material name string is already zero-terminated, but we need to be sure ... + const char *sz = (const char *)stream->GetPtr(); + unsigned int cnt = 0; + while (stream->GetI1()) + ++cnt; + + if (!cnt) { + // This may not be, we use the default name instead + ASSIMP_LOG_ERROR("3DS: Empty material name"); + } else + mScene->mMaterials.back().mName = std::string(sz, cnt); + } break; + + case Discreet3DS::CHUNK_MAT_DIFFUSE: { + // This is the diffuse material color + aiColor3D *pc = &mScene->mMaterials.back().mDiffuse; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read DIFFUSE chunk"); + pc->r = pc->g = pc->b = 1.0f; + } + } break; + + case Discreet3DS::CHUNK_MAT_SPECULAR: { + // This is the specular material color + aiColor3D *pc = &mScene->mMaterials.back().mSpecular; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read SPECULAR chunk"); + pc->r = pc->g = pc->b = 1.0f; + } + } break; + + case Discreet3DS::CHUNK_MAT_AMBIENT: { + // This is the ambient material color + aiColor3D *pc = &mScene->mMaterials.back().mAmbient; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read AMBIENT chunk"); + pc->r = pc->g = pc->b = 0.0f; + } + } break; + + case Discreet3DS::CHUNK_MAT_SELF_ILLUM: { + // This is the emissive material color + aiColor3D *pc = &mScene->mMaterials.back().mEmissive; + ParseColorChunk(pc); + if (is_qnan(pc->r)) { + // color chunk is invalid. Simply ignore it + ASSIMP_LOG_ERROR("3DS: Unable to read EMISSIVE chunk"); + pc->r = pc->g = pc->b = 0.0f; + } + } break; + + case Discreet3DS::CHUNK_MAT_TRANSPARENCY: { + // This is the material's transparency + ai_real *pcf = &mScene->mMaterials.back().mTransparency; + *pcf = ParsePercentageChunk(); + + // NOTE: transparency, not opacity + if (is_qnan(*pcf)) + *pcf = ai_real(1.0); + else + *pcf = ai_real(1.0) - *pcf * (ai_real)0xFFFF / ai_real(100.0); + } break; + + case Discreet3DS::CHUNK_MAT_SHADING: + // This is the material shading mode + mScene->mMaterials.back().mShading = (D3DS::Discreet3DS::shadetype3ds)stream->GetI2(); + break; + + case Discreet3DS::CHUNK_MAT_TWO_SIDE: + // This is the two-sided flag + mScene->mMaterials.back().mTwoSided = true; + break; + + case Discreet3DS::CHUNK_MAT_SHININESS: { // This is the shininess of the material + ai_real *pcf = &mScene->mMaterials.back().mSpecularExponent; + *pcf = ParsePercentageChunk(); + if (is_qnan(*pcf)) + *pcf = 0.0; + else + *pcf *= (ai_real)0xFFFF; + } break; + + case Discreet3DS::CHUNK_MAT_SHININESS_PERCENT: { // This is the shininess strength of the material + ai_real *pcf = &mScene->mMaterials.back().mShininessStrength; + *pcf = ParsePercentageChunk(); + if (is_qnan(*pcf)) + *pcf = ai_real(0.0); + else + *pcf *= (ai_real)0xffff / ai_real(100.0); + } break; + + case Discreet3DS::CHUNK_MAT_SELF_ILPCT: { // This is the self illumination strength of the material + ai_real f = ParsePercentageChunk(); + if (is_qnan(f)) + f = ai_real(0.0); + else + f *= (ai_real)0xFFFF / ai_real(100.0); + mScene->mMaterials.back().mEmissive = aiColor3D(f, f, f); + } break; + + // Parse texture chunks + case Discreet3DS::CHUNK_MAT_TEXTURE: + // Diffuse texture + ParseTextureChunk(&mScene->mMaterials.back().sTexDiffuse); + break; + case Discreet3DS::CHUNK_MAT_BUMPMAP: + // Height map + ParseTextureChunk(&mScene->mMaterials.back().sTexBump); + break; + case Discreet3DS::CHUNK_MAT_OPACMAP: + // Opacity texture + ParseTextureChunk(&mScene->mMaterials.back().sTexOpacity); + break; + case Discreet3DS::CHUNK_MAT_MAT_SHINMAP: + // Shininess map + ParseTextureChunk(&mScene->mMaterials.back().sTexShininess); + break; + case Discreet3DS::CHUNK_MAT_SPECMAP: + // Specular map + ParseTextureChunk(&mScene->mMaterials.back().sTexSpecular); + break; + case Discreet3DS::CHUNK_MAT_SELFIMAP: + // Self-illumination (emissive) map + ParseTextureChunk(&mScene->mMaterials.back().sTexEmissive); + break; + case Discreet3DS::CHUNK_MAT_REFLMAP: + // Reflection map + ParseTextureChunk(&mScene->mMaterials.back().sTexReflective); + break; + }; + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +void Discreet3DSImporter::ParseTextureChunk(D3DS::Texture *pcOut) { + ASSIMP_3DS_BEGIN_CHUNK(); + + // get chunk type + switch (chunk.Flag) { + case Discreet3DS::CHUNK_MAPFILE: { + // The material name string is already zero-terminated, but we need to be sure ... + const char *sz = (const char *)stream->GetPtr(); + unsigned int cnt = 0; + while (stream->GetI1()) + ++cnt; + pcOut->mMapName = std::string(sz, cnt); + } break; + + case Discreet3DS::CHUNK_PERCENTD: + // Manually parse the blend factor + pcOut->mTextureBlend = ai_real(stream->GetF8()); + break; + + case Discreet3DS::CHUNK_PERCENTF: + // Manually parse the blend factor + pcOut->mTextureBlend = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_PERCENTW: + // Manually parse the blend factor + pcOut->mTextureBlend = (ai_real)((uint16_t)stream->GetI2()) / ai_real(100.0); + break; + + case Discreet3DS::CHUNK_MAT_MAP_USCALE: + // Texture coordinate scaling in the U direction + pcOut->mScaleU = stream->GetF4(); + if (0.0f == pcOut->mScaleU) { + ASSIMP_LOG_WARN("Texture coordinate scaling in the x direction is zero. Assuming 1."); + pcOut->mScaleU = 1.0f; + } + break; + case Discreet3DS::CHUNK_MAT_MAP_VSCALE: + // Texture coordinate scaling in the V direction + pcOut->mScaleV = stream->GetF4(); + if (0.0f == pcOut->mScaleV) { + ASSIMP_LOG_WARN("Texture coordinate scaling in the y direction is zero. Assuming 1."); + pcOut->mScaleV = 1.0f; + } + break; + + case Discreet3DS::CHUNK_MAT_MAP_UOFFSET: + // Texture coordinate offset in the U direction + pcOut->mOffsetU = -stream->GetF4(); + break; + + case Discreet3DS::CHUNK_MAT_MAP_VOFFSET: + // Texture coordinate offset in the V direction + pcOut->mOffsetV = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_MAT_MAP_ANG: + // Texture coordinate rotation, CCW in DEGREES + pcOut->mRotation = -AI_DEG_TO_RAD(stream->GetF4()); + break; + + case Discreet3DS::CHUNK_MAT_MAP_TILING: { + const uint16_t iFlags = stream->GetI2(); + + // Get the mapping mode (for both axes) + if (iFlags & 0x2u) + pcOut->mMapMode = aiTextureMapMode_Mirror; + + else if (iFlags & 0x10u) + pcOut->mMapMode = aiTextureMapMode_Decal; + + // wrapping in all remaining cases + else + pcOut->mMapMode = aiTextureMapMode_Wrap; + } break; + }; + + ASSIMP_3DS_END_CHUNK(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a percentage chunk +ai_real Discreet3DSImporter::ParsePercentageChunk() { + Discreet3DS::Chunk chunk; + ReadChunk(&chunk); + + if (Discreet3DS::CHUNK_PERCENTF == chunk.Flag) { + return stream->GetF4() * ai_real(100) / ai_real(0xFFFF); + } else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag) { + return (ai_real)((uint16_t)stream->GetI2()) / (ai_real)0xFFFF; + } + + return get_qnan(); +} + +// ------------------------------------------------------------------------------------------------ +// Read a color chunk. If a percentage chunk is found instead it is read as a grayscale color +void Discreet3DSImporter::ParseColorChunk(aiColor3D *out, bool acceptPercent) { + ai_assert(out != nullptr); + + // error return value + const ai_real qnan = get_qnan(); + static const aiColor3D clrError = aiColor3D(qnan, qnan, qnan); + + Discreet3DS::Chunk chunk; + ReadChunk(&chunk); + const unsigned int diff = chunk.Size - sizeof(Discreet3DS::Chunk); + + bool bGamma = false; + + // Get the type of the chunk + switch (chunk.Flag) { + case Discreet3DS::CHUNK_LINRGBF: + bGamma = true; + + case Discreet3DS::CHUNK_RGBF: + if (sizeof(float) * 3 > diff) { + *out = clrError; + return; + } + out->r = stream->GetF4(); + out->g = stream->GetF4(); + out->b = stream->GetF4(); + break; + + case Discreet3DS::CHUNK_LINRGBB: + bGamma = true; + case Discreet3DS::CHUNK_RGBB: { + if (sizeof(char) * 3 > diff) { + *out = clrError; + return; + } + const ai_real invVal = ai_real(1.0) / ai_real(255.0); + out->r = (ai_real)(uint8_t)stream->GetI1() * invVal; + out->g = (ai_real)(uint8_t)stream->GetI1() * invVal; + out->b = (ai_real)(uint8_t)stream->GetI1() * invVal; + } break; + + // Percentage chunks are accepted, too. + case Discreet3DS::CHUNK_PERCENTF: + if (acceptPercent && 4 <= diff) { + out->g = out->b = out->r = stream->GetF4(); + break; + } + *out = clrError; + return; + + case Discreet3DS::CHUNK_PERCENTW: + if (acceptPercent && 1 <= diff) { + out->g = out->b = out->r = (ai_real)(uint8_t)stream->GetI1() / ai_real(255.0); + break; + } + *out = clrError; + return; + + default: + stream->IncPtr(diff); + // Skip unknown chunks, hope this won't cause any problems. + return ParseColorChunk(out, acceptPercent); + }; + (void)bGamma; +} + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER diff --git a/libs/assimp/code/AssetLib/3DS/3DSLoader.h b/libs/assimp/code/AssetLib/3DS/3DSLoader.h new file mode 100644 index 0000000..f47fcfe --- /dev/null +++ b/libs/assimp/code/AssetLib/3DS/3DSLoader.h @@ -0,0 +1,289 @@ + +/* +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 3DSLoader.h + * @brief 3DS File format loader + */ +#ifndef AI_3DSIMPORTER_H_INC +#define AI_3DSIMPORTER_H_INC +#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER + +#include <assimp/BaseImporter.h> +#include <assimp/types.h> + + +#include "3DSHelper.h" +#include <assimp/StreamReader.h> + +struct aiNode; + +namespace Assimp { + + +using namespace D3DS; + +// --------------------------------------------------------------------------------- +/** Importer class for 3D Studio r3 and r4 3DS files + */ +class Discreet3DSImporter : public BaseImporter { +public: + Discreet3DSImporter(); + ~Discreet3DSImporter(); + + // ------------------------------------------------------------------- + /** 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: + + // ------------------------------------------------------------------- + /** Return importer meta information. + * See #BaseImporter::GetInfo for the details + */ + 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; + + // ------------------------------------------------------------------- + /** Converts a temporary material to the outer representation + */ + void ConvertMaterial(D3DS::Material& p_cMat, + aiMaterial& p_pcOut); + + // ------------------------------------------------------------------- + /** Read a chunk + * + * @param pcOut Receives the current chunk + */ + void ReadChunk(Discreet3DS::Chunk* pcOut); + + // ------------------------------------------------------------------- + /** Parse a percentage chunk. mCurrent will point to the next + * chunk behind afterwards. If no percentage chunk is found + * QNAN is returned. + */ + ai_real ParsePercentageChunk(); + + // ------------------------------------------------------------------- + /** Parse a color chunk. mCurrent will point to the next + * chunk behind afterwards. If no color chunk is found + * QNAN is returned in all members. + */ + void ParseColorChunk(aiColor3D* p_pcOut, + bool p_bAcceptPercent = true); + + + // ------------------------------------------------------------------- + /** Skip a chunk in the file + */ + void SkipChunk(); + + // ------------------------------------------------------------------- + /** Generate the nodegraph + */ + void GenerateNodeGraph(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** Parse a main top-level chunk in the file + */ + void ParseMainChunk(); + + // ------------------------------------------------------------------- + /** Parse a top-level chunk in the file + */ + void ParseChunk(const char* name, unsigned int num); + + // ------------------------------------------------------------------- + /** Parse a top-level editor chunk in the file + */ + void ParseEditorChunk(); + + // ------------------------------------------------------------------- + /** Parse a top-level object chunk in the file + */ + void ParseObjectChunk(); + + // ------------------------------------------------------------------- + /** Parse a material chunk in the file + */ + void ParseMaterialChunk(); + + // ------------------------------------------------------------------- + /** Parse a mesh chunk in the file + */ + void ParseMeshChunk(); + + // ------------------------------------------------------------------- + /** Parse a light chunk in the file + */ + void ParseLightChunk(); + + // ------------------------------------------------------------------- + /** Parse a camera chunk in the file + */ + void ParseCameraChunk(); + + // ------------------------------------------------------------------- + /** Parse a face list chunk in the file + */ + void ParseFaceChunk(); + + // ------------------------------------------------------------------- + /** Parse a keyframe chunk in the file + */ + void ParseKeyframeChunk(); + + // ------------------------------------------------------------------- + /** Parse a hierarchy chunk in the file + */ + void ParseHierarchyChunk(uint16_t parent); + + // ------------------------------------------------------------------- + /** Parse a texture chunk in the file + */ + void ParseTextureChunk(D3DS::Texture* pcOut); + + // ------------------------------------------------------------------- + /** Convert the meshes in the file + */ + void ConvertMeshes(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** Replace the default material in the scene + */ + void ReplaceDefaultMaterial(); + + bool ContainsTextures(unsigned int i) const { + return !mScene->mMaterials[i].sTexDiffuse.mMapName.empty() || + !mScene->mMaterials[i].sTexBump.mMapName.empty() || + !mScene->mMaterials[i].sTexOpacity.mMapName.empty() || + !mScene->mMaterials[i].sTexEmissive.mMapName.empty() || + !mScene->mMaterials[i].sTexSpecular.mMapName.empty() || + !mScene->mMaterials[i].sTexShininess.mMapName.empty() ; + } + + // ------------------------------------------------------------------- + /** Convert the whole scene + */ + void ConvertScene(aiScene* pcOut); + + // ------------------------------------------------------------------- + /** generate unique vertices for a mesh + */ + void MakeUnique(D3DS::Mesh& sMesh); + + // ------------------------------------------------------------------- + /** Add a node to the node graph + */ + void AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,D3DS::Node* pcIn, + aiMatrix4x4& absTrafo); + + // ------------------------------------------------------------------- + /** Search for a node in the graph. + * Called recursively + */ + void InverseNodeSearch(D3DS::Node* pcNode,D3DS::Node* pcCurrent); + + // ------------------------------------------------------------------- + /** Apply the master scaling factor to the mesh + */ + void ApplyMasterScale(aiScene* pScene); + + // ------------------------------------------------------------------- + /** Clamp all indices in the file to a valid range + */ + void CheckIndices(D3DS::Mesh& sMesh); + + // ------------------------------------------------------------------- + /** Skip the TCB info in a track key + */ + void SkipTCBInfo(); + +protected: + + /** Stream to read from */ + StreamReaderLE* stream; + + /** Last touched node index */ + short mLastNodeIndex; + + /** Current node, root node */ + D3DS::Node* mCurrentNode, *mRootNode; + + /** Scene under construction */ + D3DS::Scene* mScene; + + /** Ambient base color of the scene */ + aiColor3D mClrAmbient; + + /** Master scaling factor of the scene */ + ai_real mMasterScale; + + /** Path to the background image of the scene */ + std::string mBackgroundImage; + bool bHasBG; + + /** true if PRJ file */ + bool bIsPrj; +}; + +} // end of namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER + +#endif // AI_3DSIMPORTER_H_INC |