//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "ts/loader/appSequence.h" #include "ts/assimp/assimpAppNode.h" #include "ts/assimp/assimpAppMesh.h" // assimp include files. #include #include #include #include aiAnimation* AssimpAppNode::sActiveSequence = NULL; F32 AssimpAppNode::sTimeMultiplier = 1.0f; AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* node, AssimpAppNode* parent) : mScene(scene), mNode(node ? node : scene->mRootNode), appParent(parent), mInvertMeshes(false), mLastTransformTime(TSShapeLoader::DefaultTime - 1), mDefaultTransformValid(false) { mScene = scene; mNode = node ? node : scene->mRootNode; // Initialize node and parent names. mName = dStrdup(mNode->mName.C_Str()); if ( dStrlen(mName) == 0 ) { const char* defaultName = "null"; mName = dStrdup(defaultName); } mParentName = dStrdup(parent ? parent->getName() : "ROOT"); // Convert transformation matrix assimpToTorqueMat(node->mTransformation, mNodeTransform); Con::printf("[ASSIMP] Node Created: %s, Parent: %s", mName, mParentName); } // Get all child nodes void AssimpAppNode::buildChildList() { // Ensure mNode is valid if (!mNode) { Con::errorf("[ASSIMP] Error: mNode is null in buildChildList"); return; } if (!mNode->mChildren) return; for (U32 n = 0; n < mNode->mNumChildren; ++n) { if (!mNode->mChildren[n]) { Con::errorf("[ASSIMP] Warning: Null child node at index %d", n); continue; } mChildNodes.push_back(new AssimpAppNode(mScene, mNode->mChildren[n], this)); } } // Get all geometry attached to this node void AssimpAppNode::buildMeshList() { for (U32 n = 0; n < mNode->mNumMeshes; ++n) { const struct aiMesh* mesh = mScene->mMeshes[mNode->mMeshes[n]]; mMeshes.push_back(new AssimpAppMesh(mesh, this)); } } MatrixF AssimpAppNode::getTransform(F32 time) { // Check if we can use the last computed transform if (time == mLastTransformTime) return mLastTransform; if (appParent) { // Get parent node's transform mLastTransform = appParent->getTransform(time); } else { // no parent (ie. root level) => scale by global shape mLastTransform.identity(); mLastTransform.scale(ColladaUtils::getOptions().unit * ColladaUtils::getOptions().formatScaleFactor); if (!isBounds()) convertMat(mLastTransform); } // If this node is animated in the active sequence, fetch the animated transform MatrixF mat(true); if (sActiveSequence) getAnimatedTransform(mat, time, sActiveSequence); else mat = mNodeTransform; // Remove node scaling? Point3F nodeScale = mat.getScale(); if (nodeScale != Point3F::One && appParent && ColladaUtils::getOptions().ignoreNodeScale) { nodeScale.x = nodeScale.x ? (1.0f / nodeScale.x) : 0; nodeScale.y = nodeScale.y ? (1.0f / nodeScale.y) : 0; nodeScale.z = nodeScale.z ? (1.0f / nodeScale.z) : 0; mat.scale(nodeScale); } mLastTransform.mul(mat); mLastTransformTime = time; return mLastTransform; } void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animSeq) { // Convert time `t` (in seconds) to a frame index const F32 frameTime = (t * animSeq->mTicksPerSecond + 0.5f) + 1.0f; // Loop through animation channels to find the matching node for (U32 k = 0; k < animSeq->mNumChannels; ++k) { const aiNodeAnim* nodeAnim = animSeq->mChannels[k]; if (dStrcmp(mName, nodeAnim->mNodeName.C_Str()) != 0) continue; Point3F translation(Point3F::Zero); QuatF rotation(QuatF::Identity); Point3F scale(Point3F::One); // Interpolate Translation Keys if (nodeAnim->mNumPositionKeys > 0) { translation = interpolateVectorKey(nodeAnim->mPositionKeys, nodeAnim->mNumPositionKeys, frameTime); } // Interpolate Rotation Keys if (nodeAnim->mNumRotationKeys > 0) { rotation = interpolateQuaternionKey(nodeAnim->mRotationKeys, nodeAnim->mNumRotationKeys, frameTime); } // Interpolate Scaling Keys if (nodeAnim->mNumScalingKeys > 0) { scale = interpolateVectorKey(nodeAnim->mScalingKeys, nodeAnim->mNumScalingKeys, frameTime); } // Apply the interpolated transform components to the matrix rotation.setMatrix(&mat); mat.inverse(); mat.setPosition(translation); mat.scale(scale); return; // Exit after processing the matching node } // Default to the static node transformation if no animation data is found mat = mNodeTransform; } Point3F AssimpAppNode::interpolateVectorKey(const aiVectorKey* keys, U32 numKeys, F32 frameTime) { if (numKeys == 1) // Single keyframe: use it directly return Point3F(keys[0].mValue.x, keys[0].mValue.y, keys[0].mValue.z); // Clamp frameTime to the bounds of the keyframes if (frameTime <= keys[0].mTime) { // Before the first keyframe, return the first key return Point3F(keys[0].mValue.x, keys[0].mValue.y, keys[0].mValue.z); } if (frameTime >= keys[numKeys - 1].mTime) { // After the last keyframe, return the last key return Point3F(keys[numKeys - 1].mValue.x, keys[numKeys - 1].mValue.y, keys[numKeys - 1].mValue.z); } // Interpolate between the two nearest keyframes for (U32 i = 1; i < numKeys; ++i) { if (frameTime < keys[i].mTime) { const F32 factor = (frameTime - keys[i - 1].mTime) / (keys[i].mTime - keys[i - 1].mTime); Point3F start(keys[i - 1].mValue.x, keys[i - 1].mValue.y, keys[i - 1].mValue.z); Point3F end(keys[i].mValue.x, keys[i].mValue.y, keys[i].mValue.z); Point3F result; result.interpolate(start, end, factor); return result; } } // Default to the last keyframe return Point3F(keys[numKeys - 1].mValue.x, keys[numKeys - 1].mValue.y, keys[numKeys - 1].mValue.z); } QuatF AssimpAppNode::interpolateQuaternionKey(const aiQuatKey* keys, U32 numKeys, F32 frameTime) { if (numKeys == 1) // Single keyframe: use it directly return QuatF(keys[0].mValue.x, keys[0].mValue.y, keys[0].mValue.z, keys[0].mValue.w); for (U32 i = 1; i < numKeys; ++i) { if (frameTime < keys[i].mTime) { const F32 factor = (frameTime - keys[i - 1].mTime) / (keys[i].mTime - keys[i - 1].mTime); QuatF start(keys[i - 1].mValue.x, keys[i - 1].mValue.y, keys[i - 1].mValue.z, keys[i - 1].mValue.w); QuatF end(keys[i].mValue.x, keys[i].mValue.y, keys[i].mValue.z, keys[i].mValue.w); QuatF result; result.interpolate(start, end, factor); return result; } } // Default to the last keyframe return QuatF(keys[numKeys - 1].mValue.x, keys[numKeys - 1].mValue.y, keys[numKeys - 1].mValue.z, keys[numKeys - 1].mValue.w); } bool AssimpAppNode::animatesTransform(const AppSequence* appSeq) { return false; } /// Get the world transform of the node at the specified time MatrixF AssimpAppNode::getNodeTransform(F32 time) { // Avoid re-computing the default transform if possible if (mDefaultTransformValid && time == TSShapeLoader::DefaultTime) { return mDefaultNodeTransform; } else { MatrixF nodeTransform = getTransform(time); // Check for inverted node coordinate spaces => can happen when modelers // use the 'mirror' tool in their 3d app. Shows up as negative // transforms in the collada model. if (m_matF_determinant(nodeTransform) < 0.0f) { // Mark this node as inverted so we can mirror mesh geometry, then // de-invert the transform matrix mInvertMeshes = true; nodeTransform.scale(Point3F(1, 1, -1)); } // Cache the default transform if (time == TSShapeLoader::DefaultTime) { mDefaultTransformValid = true; mDefaultNodeTransform = nodeTransform; } return nodeTransform; } } void AssimpAppNode::assimpToTorqueMat(const aiMatrix4x4& inAssimpMat, MatrixF& outMat) { outMat.setRow(0, Point4F((F32)inAssimpMat.a1, (F32)inAssimpMat.a2, (F32)inAssimpMat.a3, (F32)inAssimpMat.a4)); outMat.setRow(1, Point4F((F32)inAssimpMat.b1, (F32)inAssimpMat.b2, (F32)inAssimpMat.b3, (F32)inAssimpMat.b4)); outMat.setRow(2, Point4F((F32)inAssimpMat.c1, (F32)inAssimpMat.c2, (F32)inAssimpMat.c3, (F32)inAssimpMat.c4)); outMat.setRow(3, Point4F((F32)inAssimpMat.d1, (F32)inAssimpMat.d2, (F32)inAssimpMat.d3, ColladaUtils::getOptions().formatScaleFactor));// (F32)inAssimpMat.d4)); } void AssimpAppNode::convertMat(MatrixF& outMat) { MatrixF rot(true); switch (ColladaUtils::getOptions().upAxis) { case UPAXISTYPE_X_UP: // rotate 90 around Y-axis, then 90 around Z-axis rot(0, 0) = 0.0f; rot(1, 0) = 1.0f; rot(1, 1) = 0.0f; rot(2, 1) = 1.0f; rot(0, 2) = 1.0f; rot(2, 2) = 0.0f; // pre-multiply the transform by the rotation matrix outMat.mulL(rot); break; case UPAXISTYPE_Y_UP: // rotate 180 around Y-axis, then 90 around X-axis rot(0, 0) = -1.0f; rot(1, 1) = 0.0f; rot(2, 1) = 1.0f; rot(1, 2) = 1.0f; rot(2, 2) = 0.0f; // pre-multiply the transform by the rotation matrix outMat.mulL(rot); break; case UPAXISTYPE_Z_UP: default: // nothing to do break; } } aiNode* AssimpAppNode::findChildNodeByName(const char* nodeName, aiNode* rootNode) { aiNode* retNode = NULL; if (strcmp(nodeName, rootNode->mName.C_Str()) == 0) return rootNode; for (U32 i = 0; i < rootNode->mNumChildren; ++i) { retNode = findChildNodeByName(nodeName, rootNode->mChildren[i]); if (retNode) return retNode; } return nullptr; }