811 lines
32 KiB
C++
811 lines
32 KiB
C++
/*
|
|
---------------------------------------------------------------------------
|
|
Open Asset Import Library (assimp)
|
|
---------------------------------------------------------------------------
|
|
|
|
Copyright (c) 2006-2024, 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>
|
|
|
|
namespace Assimp {
|
|
|
|
static constexpr 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);
|
|
}
|
|
[[fallthrough]];
|
|
|
|
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 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, AI_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);
|
|
}
|
|
}
|
|
|
|
} // namespace Assimp
|
|
|
|
#endif // !! ASSIMP_BUILD_NO_3DS_IMPORTER
|