diff --git a/Engine/lib/assimp/code/glTF2Asset.inl b/Engine/lib/assimp/code/glTF2Asset.inl index 196d664cb..b12a92f3a 100644 --- a/Engine/lib/assimp/code/glTF2Asset.inl +++ b/Engine/lib/assimp/code/glTF2Asset.inl @@ -405,8 +405,10 @@ inline void Buffer::Read(Value& obj, Asset& r) inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset) { byteLength = length ? length : stream.FileSize(); + //T3D_CHANGE_BEGIN if ((byteLength + baseOffset) > stream.FileSize()) byteLength = stream.FileSize() - baseOffset; + //T3D_CHANGE_END if (baseOffset) { stream.Seek(baseOffset, aiOrigin_SET); diff --git a/Engine/lib/assimp/code/glTF2Importer.cpp b/Engine/lib/assimp/code/glTF2Importer.cpp index 4228db23f..c2acdcd66 100644 --- a/Engine/lib/assimp/code/glTF2Importer.cpp +++ b/Engine/lib/assimp/code/glTF2Importer.cpp @@ -822,8 +822,6 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& if (node.skin) { for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) { aiMesh* mesh = pScene->mMeshes[meshOffsets[mesh_idx]+primitiveNo]; - mesh->mNumBones = static_cast(node.skin->jointNames.size()); - mesh->mBones = new aiBone*[mesh->mNumBones]; // GLTF and Assimp choose to store bone weights differently. // GLTF has each vertex specify which bones influence the vertex. @@ -834,39 +832,98 @@ aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& // both because it's somewhat slow and because, for many applications, // we then need to reconvert the data back into the vertex-to-bone // mapping which makes things doubly-slow. - std::vector> weighting(mesh->mNumBones); + + //T3D_CHANGE_BEGIN + // The following commented block has been completely replaced. + // Portions of the replacement code block have been taken from: + // https://github.com/ConfettiFX/The-Forge/blob/master/Common_3/ThirdParty/OpenSource/assimp/4.1.0/code/glTF2Importer.cpp#L823-L860 + //std::vector> weighting(mesh->mNumBones); + //BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting); + + //for (uint32_t i = 0; i < mesh->mNumBones; ++i) { + // aiBone* bone = new aiBone(); + + // Ref joint = node.skin->jointNames[i]; + // if (!joint->name.empty()) { + // bone->mName = joint->name; + // } else { + // // Assimp expects each bone to have a unique name. + // static const std::string kDefaultName = "bone_"; + // char postfix[10] = {0}; + // ASSIMP_itoa10(postfix, i); + // bone->mName = (kDefaultName + postfix); + // } + // GetNodeTransform(bone->mOffsetMatrix, *joint); + + // std::vector& weights = weighting[i]; + + // bone->mNumWeights = static_cast(weights.size()); + // if (bone->mNumWeights > 0) { + // bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + // memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); + // } else { + // // Assimp expects all bones to have at least 1 weight. + // bone->mWeights = new aiVertexWeight[1]; + // bone->mNumWeights = 1; + // bone->mWeights->mVertexId = 0; + // bone->mWeights->mWeight = 0.f; + // } + // mesh->mBones[i] = bone; + + std::vector> weighting(node.skin->jointNames.size()); BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting); - for (uint32_t i = 0; i < mesh->mNumBones; ++i) { - aiBone* bone = new aiBone(); - - Ref joint = node.skin->jointNames[i]; - if (!joint->name.empty()) { - bone->mName = joint->name; - } else { - // Assimp expects each bone to have a unique name. - static const std::string kDefaultName = "bone_"; - char postfix[10] = {0}; - ASSIMP_itoa10(postfix, i); - bone->mName = (kDefaultName + postfix); - } - GetNodeTransform(bone->mOffsetMatrix, *joint); - - std::vector& weights = weighting[i]; - - bone->mNumWeights = static_cast(weights.size()); - if (bone->mNumWeights > 0) { - bone->mWeights = new aiVertexWeight[bone->mNumWeights]; - memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); - } else { - // Assimp expects all bones to have at least 1 weight. - bone->mWeights = new aiVertexWeight[1]; - bone->mNumWeights = 1; - bone->mWeights->mVertexId = 0; - bone->mWeights->mWeight = 0.f; - } - mesh->mBones[i] = bone; + // CONFFX_BEGIN + // Assimp doesn't support bones with no weight. We have to count the + // number of bones that affect the mesh and limit it to just those bones. + int numBones = 0; + for (size_t i = 0; i < node.skin->jointNames.size(); ++i) { + if (!weighting[i].empty()) + ++numBones; } + + mesh->mNumBones = numBones; + if (numBones > 0) + { + mesh->mBones = new aiBone*[mesh->mNumBones]; + + int j = 0; + for (size_t i = 0; i < node.skin->jointNames.size(); ++i) { + if (!weighting[i].empty()) + { + aiBone* bone = new aiBone(); + + Ref joint = node.skin->jointNames[i]; + bone->mName = joint->name.empty() ? joint->id : joint->name; + + // Get the inverseBindMatrix for the joint, grab the position out of row 4, + // invert the matrix and put the position back as column 4. + aiMatrix4x4 *tmpMat; + uint8_t *matPtr = node.skin->inverseBindMatrices->GetPointer(); + tmpMat = (aiMatrix4x4*)matPtr; + bone->mOffsetMatrix = tmpMat[i]; + aiVector3D tmpPos(bone->mOffsetMatrix.d1, bone->mOffsetMatrix.d2, bone->mOffsetMatrix.d3); + bone->mOffsetMatrix.d1 = bone->mOffsetMatrix.d2 = bone->mOffsetMatrix.d3 = 0.0; + bone->mOffsetMatrix.Inverse(); + bone->mOffsetMatrix.a4 = tmpPos.x; + bone->mOffsetMatrix.b4 = tmpPos.y; + bone->mOffsetMatrix.c4 = tmpPos.z; + + std::vector& weights = weighting[i]; + + bone->mNumWeights = static_cast(weights.size()); + if (bone->mNumWeights > 0) { + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); + } + mesh->mBones[j++] = bone; + } + } + } + else + mesh->mBones = nullptr; + // CONFFX_END + //T3D_CHANGE_END } } @@ -921,7 +978,10 @@ struct AnimationSamplers { aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers) { aiNodeAnim* anim = new aiNodeAnim(); - anim->mNodeName = node.name; + //T3D_CHANGE_BEGIN + //anim->mNodeName = node.name; + anim->mNodeName = node.name.empty() ? node.id : node.name; + //T3D_CHANGE_END static const float kMillisecondsFromSeconds = 1000.f; @@ -1042,15 +1102,27 @@ void glTF2Importer::ImportAnimations(glTF2::Asset& r) std::unordered_map samplers = GatherSamplers(anim); - ai_anim->mNumChannels = static_cast(samplers.size()); + //T3D_CHANGE_BEGIN + //ai_anim->mNumChannels = static_cast(samplers.size()); + //if (ai_anim->mNumChannels > 0) { + // ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels]; + // int j = 0; + // for (auto& iter : samplers) { + // ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); + // ++j; + // } + //} + + ai_anim->mNumChannels = r.skins.Size() > 0 ? r.skins[0].jointNames.size() : 0; if (ai_anim->mNumChannels > 0) { ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels]; int j = 0; - for (auto& iter : samplers) { - ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); + for (auto& iter : r.skins[0].jointNames) { + ai_anim->mChannels[j] = CreateNodeAnim(r, *iter, samplers[iter.GetIndex()]); ++j; } } + //T3D_CHANGE_END // Use the latest keyframe for the duration of the animation double maxDuration = 0; diff --git a/Engine/lib/assimp/code/glTFAsset.inl b/Engine/lib/assimp/code/glTFAsset.inl index 5c65767d1..10bc4d1ef 100644 --- a/Engine/lib/assimp/code/glTFAsset.inl +++ b/Engine/lib/assimp/code/glTFAsset.inl @@ -345,8 +345,10 @@ inline void Buffer::Read(Value& obj, Asset& r) inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset) { byteLength = length ? length : stream.FileSize(); + //T3D_CHANGE_BEGIN if ((byteLength + baseOffset) > stream.FileSize()) byteLength = stream.FileSize() - baseOffset; + //T3D_CHANGE_END if (baseOffset) { stream.Seek(baseOffset, aiOrigin_SET); diff --git a/Engine/source/ts/assimp/assimpAppMaterial.cpp b/Engine/source/ts/assimp/assimpAppMaterial.cpp index 6fdc913e5..713ca9b87 100644 --- a/Engine/source/ts/assimp/assimpAppMaterial.cpp +++ b/Engine/source/ts/assimp/assimpAppMaterial.cpp @@ -19,6 +19,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//#define TORQUE_PBR_MATERIALS #include "platform/platform.h" #include "ts/loader/appSequence.h" @@ -38,7 +39,7 @@ String AppMaterial::cleanString(const String& str) String cleanStr(str); // Replace invalid characters with underscores - const String badChars(" -,.+=*/"); + const String badChars(" -,.+=*/[]%$~;:"); for (String::SizeType i = 0; i < badChars.length(); i++) cleanStr.replace(badChars[i], '_'); @@ -52,50 +53,27 @@ String AppMaterial::cleanString(const String& str) AssimpAppMaterial::AssimpAppMaterial(const char* matName) { name = matName; - diffuseColor = LinearColorF::ONE; - specularColor = LinearColorF::ONE; - specularPower = 0.8f; - doubleSided = false; // Set some defaults flags |= TSMaterialList::S_Wrap; flags |= TSMaterialList::T_Wrap; } -AssimpAppMaterial::AssimpAppMaterial(const struct aiMaterial* mtl) +AssimpAppMaterial::AssimpAppMaterial(aiMaterial* mtl) : + mAIMat(mtl) { aiString matName; mtl->Get(AI_MATKEY_NAME, matName); name = matName.C_Str(); - if ( name.isEmpty() ) - name = "defaultMaterial"; - Con::printf("[ASSIMP] Loaded Material: %s", matName.C_Str()); - - // Opacity - F32 opacity = 0.0f; - mtl->Get(AI_MATKEY_OPACITY, opacity); - - // Diffuse color - aiColor3D diff_color (0.f, 0.f, 0.f); - mtl->Get(AI_MATKEY_COLOR_DIFFUSE, diff_color); - diffuseColor = LinearColorF(diff_color.r, diff_color.g, diff_color.b, opacity); - - // Spec Color color - aiColor3D spec_color (0.f, 0.f, 0.f); - mtl->Get(AI_MATKEY_COLOR_DIFFUSE, spec_color ); - specularColor = LinearColorF(spec_color.r, spec_color.g, spec_color.b, 1.0f); - - // Specular Power - mtl->Get(AI_MATKEY_SHININESS_STRENGTH, specularPower); - - // Double-Sided - S32 dbl_sided = 0; - mtl->Get(AI_MATKEY_TWOSIDED, dbl_sided); - doubleSided = (dbl_sided != 0); - - // Set some defaults - flags |= TSMaterialList::S_Wrap; - flags |= TSMaterialList::T_Wrap; + if (name.isEmpty()) + { + name = cleanString(TSShapeLoader::getShapePath().getFileName());; + name += "_defMat"; + } + Con::printf("[ASSIMP] Loading Material: %s", name.c_str()); +#ifdef TORQUE_DEBUG + enumerateMaterialProperties(mtl); +#endif } Material* AssimpAppMaterial::createMaterial(const Torque::Path& path) const @@ -105,34 +83,264 @@ Material* AssimpAppMaterial::createMaterial(const Torque::Path& path) const String cleanFile = cleanString(TSShapeLoader::getShapePath().getFileName()); String cleanName = cleanString(getName()); - // Prefix the material name with the filename (if not done already by TSShapeConstructor prefix) - //if (!cleanName.startsWith(cleanFile)) - // cleanName = cleanFile + "_" + cleanName; - - // Determine the blend operation for this material - Material::BlendOp blendOp = (flags & TSMaterialList::Translucent) ? Material::LerpAlpha : Material::None; - if (flags & TSMaterialList::Additive) - blendOp = Material::Add; - else if (flags & TSMaterialList::Subtractive) - blendOp = Material::Sub; - // Create the Material definition const String oldScriptFile = Con::getVariable("$Con::File"); Con::setVariable("$Con::File", path.getFullPath()); // modify current script path so texture lookups are correct - Material *newMat = MATMGR->allocateAndRegister( cleanName, getName() ); + Material *newMat = MATMGR->allocateAndRegister(cleanName, getName()); Con::setVariable("$Con::File", oldScriptFile); // restore script path - newMat->mDiffuseMapFilename[0] = ""; - newMat->mNormalMapFilename[0] = ""; - newMat->mSpecularMapFilename[0] = ""; - - newMat->mDiffuse[0] = diffuseColor; - //newMat->mSpecular[0] = specularColor; - //newMat->mSpecularPower[0] = specularPower; - - newMat->mDoubleSided = doubleSided; - newMat->mTranslucent = (bool)(flags & TSMaterialList::Translucent); - newMat->mTranslucentBlendOp = blendOp; + initMaterial(path, newMat); return newMat; -} \ No newline at end of file +} + +void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) const +{ + String cleanFile = cleanString(TSShapeLoader::getShapePath().getFileName()); + String cleanName = cleanString(getName()); + + // Determine the blend mode and transparency for this material + Material::BlendOp blendOp = Material::None; + bool translucent = false; + float opacity = 1.0f; + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_OPACITY, opacity)) + { + if (opacity != 1.0f) + { + translucent = true; + int blendInt; + blendOp = Material::LerpAlpha; + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_BLEND_FUNC, blendInt)) + { + if (blendInt == aiBlendMode_Additive) + blendOp = Material::Add; + } + } + } + else + { // No opacity key, see if it's defined as a gltf property + aiString opacityMode; + if (AI_SUCCESS == mAIMat->Get("$mat.gltf.alphaMode", 0, 0, opacityMode)) + { + if (dStrcmp("MASK", opacityMode.C_Str()) == 0) + { + translucent = true; + blendOp = Material::LerpAlpha; + + float cutoff; + if (AI_SUCCESS == mAIMat->Get("$mat.gltf.alphaCutoff", 0, 0, cutoff)) + { + mat->mAlphaRef = (U32)(cutoff * 255); // alpha ref 0-255 + mat->mAlphaTest = true; + } + } + else if (dStrcmp("OPAQUE", opacityMode.C_Str()) != 0) + { + translucent = true; + blendOp = Material::LerpAlpha; + } + } + } + mat->mTranslucent = translucent; + mat->mTranslucentBlendOp = blendOp; + + // Assign color values. + LinearColorF diffuseColor(1.0f, 1.0f, 1.0f, 1.0f); + aiColor3D read_color(1.f, 1.f, 1.f); + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_COLOR_DIFFUSE, read_color)) + diffuseColor.set(read_color.r, read_color.g, read_color.b, opacity); + mat->mDiffuse[0] = diffuseColor; + + aiString texName; + String torquePath; + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0), texName)) + { + torquePath = texName.C_Str(); + if (!torquePath.isEmpty()) + mat->mDiffuseMapFilename[0] = cleanTextureName(torquePath, cleanFile); + } + + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0), texName)) + { + torquePath = texName.C_Str(); + if (!torquePath.isEmpty()) + mat->mNormalMapFilename[0] = cleanTextureName(torquePath, cleanFile); + } + +#ifdef TORQUE_PBR_MATERIALS + float floatVal; + if (AI_SUCCESS == mAIMat->Get("$mat.gltf.pbrMetallicRoughness.roughnessFactor", 0, 0, floatVal)) + { // The shape has pbr material definitions + String aoName, rmName; // occlusion and roughness/metalness maps + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0), texName)) + aoName = texName.C_Str(); + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_UNKNOWN, 0), texName)) + rmName = texName.C_Str(); + + //if (aoName.isNotEmpty() && (aoName == rmName)) + // mat->mOrmMapFilename[0] = cleanTextureName(aoName, cleanFile); // It's an ORM map + //else if (aoName.isNotEmpty() || rmName.isNotEmpty()) + if (aoName.isNotEmpty() || rmName.isNotEmpty()) + { // If we have either map, fill all three slots + if (rmName.isNotEmpty()) + { + mat->mRoughMapFilename[0] = cleanTextureName(rmName, cleanFile); // Roughness + mat->mSmoothnessChan[0] = 1.0f; + mat->mInvertSmoothness = (floatVal == 1.0f); + mat->mMetalMapFilename[0] = cleanTextureName(rmName, cleanFile); // Metallic + mat->mMetalChan[0] = 2.0f; + } + if (aoName.isNotEmpty()) + { + mat->mAOMapFilename[0] = cleanTextureName(aoName, cleanFile); // occlusion + mat->mAOChan[0] = 0.0f; + } + else + { + mat->mAOMapFilename[0] = cleanTextureName(rmName, cleanFile); // occlusion + mat->mAOChan[0] = 0.0f; + } + } + } +#else + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_SPECULAR, 0), texName)) + { + torquePath = texName.C_Str(); + if (!torquePath.isEmpty()) + mat->mSpecularMapFilename[0] = cleanTextureName(torquePath, cleanFile); + } + + LinearColorF specularColor(1.0f, 1.0f, 1.0f, 1.0f); + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_COLOR_SPECULAR, read_color)) + specularColor.set(read_color.r, read_color.g, read_color.b, opacity); + mat->mSpecular[0] = specularColor; + + // Specular Power + F32 specularPower = 1.0f; + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_SHININESS_STRENGTH, specularPower)) + mat->mSpecularPower[0] = specularPower; + + // Specular + F32 specularStrength = 0.0f; + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_SHININESS, specularStrength)) + mat->mSpecularStrength[0] = specularStrength; +#endif + + // Double-Sided + bool doubleSided = false; + S32 dbl_sided = 0; + if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TWOSIDED, dbl_sided)) + doubleSided = (dbl_sided != 0); + mat->mDoubleSided = doubleSided; +} + +String AssimpAppMaterial::cleanTextureName(String& texName, String& shapeName) +{ + String cleanStr; + + if (texName[0] == '*') + { + cleanStr = shapeName; + cleanStr += "_cachedTex"; + cleanStr += texName.substr(1); + } + else + { + cleanStr = texName; + cleanStr.replace('\\', '/'); + } + + return cleanStr; +} + +#ifdef TORQUE_DEBUG +void AssimpAppMaterial::enumerateMaterialProperties(aiMaterial* mtl) +{ + for (U32 i = 0; i < mtl->mNumProperties; ++i) + { + aiMaterialProperty* matProp = mtl->mProperties[i]; + String outText; + if (matProp) + { + outText = String::ToString(" Key: %s, Index: %d, Semantic: ", matProp->mKey.C_Str(), matProp->mIndex); + switch (matProp->mSemantic) + { + case aiTextureType_NONE: + outText += "aiTextureType_NONE"; + break; + case aiTextureType_DIFFUSE: + outText += "aiTextureType_DIFFUSE"; + break; + case aiTextureType_SPECULAR: + outText += "aiTextureType_SPECULAR"; + break; + case aiTextureType_AMBIENT: + outText += "aiTextureType_AMBIENT"; + break; + case aiTextureType_EMISSIVE: + outText += "aiTextureType_EMISSIVE"; + break; + case aiTextureType_HEIGHT: + outText += "aiTextureType_HEIGHT"; + break; + case aiTextureType_NORMALS: + outText += "aiTextureType_NORMALS"; + break; + case aiTextureType_SHININESS: + outText += "aiTextureType_SHININESS"; + break; + case aiTextureType_OPACITY: + outText += "aiTextureType_OPACITY"; + break; + case aiTextureType_DISPLACEMENT: + outText += "aiTextureType_DISPLACEMENT"; + break; + case aiTextureType_LIGHTMAP: + outText += "aiTextureType_LIGHTMAP"; + break; + case aiTextureType_REFLECTION: + outText += "aiTextureType_REFLECTION"; + break; + default: + outText += "aiTextureType_UNKNOWN"; + break; + } + + aiString stringProp; + F32* floatProp; + double* doubleProp; + S32* intProp; + + switch (matProp->mType) + { + case aiPTI_Float: + floatProp = (F32*)matProp->mData; + for (U32 j = 0; j < matProp->mDataLength / sizeof(F32); ++j) + outText += String::ToString(", %0.4f", floatProp[j]); + break; + case aiPTI_Double: + doubleProp = (double*)matProp->mData; + for (U32 j = 0; j < matProp->mDataLength / sizeof(double); ++j) + outText += String::ToString(", %0.4lf", doubleProp[j]); + break; + case aiPTI_String: + aiGetMaterialString(mtl, matProp->mKey.C_Str(), matProp->mSemantic, matProp->mIndex, &stringProp); + outText += String::ToString(", %s", stringProp.C_Str()); + break; + case aiPTI_Integer: + intProp = (S32*)matProp->mData; + for (U32 j = 0; j < matProp->mDataLength / sizeof(S32); ++j) + outText += String::ToString(", %d", intProp[j]); + break; + case aiPTI_Buffer: + outText += ", aiPTI_Buffer format data"; + break; + default: + outText += ", Unknown data type"; + } + + Con::printf("%s", outText.c_str()); + } + } +} +#endif \ No newline at end of file diff --git a/Engine/source/ts/assimp/assimpAppMaterial.h b/Engine/source/ts/assimp/assimpAppMaterial.h index 4f24873a6..64aaedf28 100644 --- a/Engine/source/ts/assimp/assimpAppMaterial.h +++ b/Engine/source/ts/assimp/assimpAppMaterial.h @@ -26,6 +26,7 @@ #ifndef _APPMATERIAL_H_ #include "ts/loader/appMaterial.h" #endif +#include class Material; @@ -34,18 +35,22 @@ class AssimpAppMaterial : public AppMaterial typedef AppMaterial Parent; String name; - LinearColorF diffuseColor; - LinearColorF specularColor; - F32 specularPower; - bool doubleSided; + aiMaterial* mAIMat; + +#ifdef TORQUE_DEBUG + void enumerateMaterialProperties(aiMaterial* mtl); +#endif + static String cleanTextureName(String& texName, String& shapeName); + public: AssimpAppMaterial(const char* matName); - AssimpAppMaterial(const struct aiMaterial* mtl); + AssimpAppMaterial(aiMaterial* mtl); ~AssimpAppMaterial() { } String getName() const { return name; } Material* createMaterial(const Torque::Path& path) const; + void initMaterial(const Torque::Path& path, Material* mat) const; }; #endif // _ASSIMP_APPMATERIAL_H_ diff --git a/Engine/source/ts/assimp/assimpAppMesh.cpp b/Engine/source/ts/assimp/assimpAppMesh.cpp index cd9c79bcb..3ed762e57 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.cpp +++ b/Engine/source/ts/assimp/assimpAppMesh.cpp @@ -202,6 +202,15 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) MatrixF boneTransform; AssimpAppNode::assimpToTorqueMat(mMeshData->mBones[b]->mOffsetMatrix, boneTransform); + Point3F boneScale = boneTransform.getScale(); + if (boneScale != Point3F::One) + { + Point3F scaleMult = Point3F::One / boneScale; + Point3F scalePos = boneTransform.getPosition(); + boneTransform.scale(scaleMult); + scalePos /= scaleMult; + boneTransform.setPosition(scalePos); + } initialTransforms.push_back(boneTransform); //Weights @@ -225,20 +234,32 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) vertexIndex.setSize(nonZeroWeights); boneIndex.setSize(nonZeroWeights); - // Copy the weights to our vectors in vertex order + // Copy the weights to our vectors in vertex order and + // normalize vertex weights (force weights for each vert to sum to 1) U32 nextWeight = 0; - for (U32 i = 0; i < mMeshData->mNumVertices; i++) + for (U32 i = 0; i < mMeshData->mNumVertices; ++i) { - for (U32 ind = 0; ind < nonZeroWeights; ind++) + U32 vertStart = nextWeight; + F32 invTotalWeight = 0; + for (U32 ind = 0; ind < nonZeroWeights; ++ind) { if (tmpVertexIndex[ind] == i) { weight[nextWeight] = tmpWeight[ind]; + invTotalWeight += tmpWeight[ind]; vertexIndex[nextWeight] = tmpVertexIndex[ind]; boneIndex[nextWeight] = tmpBoneIndex[ind]; nextWeight++; } } + + // Now normalize the vertex weights + if (invTotalWeight > 0.0) + { + invTotalWeight = 1.0f / invTotalWeight; + for (U32 ind = vertStart; ind < nextWeight; ++ind) + weight[ind] *= invTotalWeight; + } } if ( noUVFound ) diff --git a/Engine/source/ts/assimp/assimpAppNode.cpp b/Engine/source/ts/assimp/assimpAppNode.cpp index 686c5bfa2..ca87f9d0c 100644 --- a/Engine/source/ts/assimp/assimpAppNode.cpp +++ b/Engine/source/ts/assimp/assimpAppNode.cpp @@ -32,6 +32,7 @@ #include aiAnimation* AssimpAppNode::sActiveSequence = NULL; +F32 AssimpAppNode::sTimeMultiplier = 1.0f; AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* node, AssimpAppNode* parent) : mInvertMeshes(false), @@ -132,15 +133,15 @@ void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animS F32 lastT = 0.0; for (U32 key = 0; key < nodeAnim->mNumPositionKeys; ++key) { - F32 curT = (F32)nodeAnim->mPositionKeys[key].mTime; + F32 curT = sTimeMultiplier * (F32)nodeAnim->mPositionKeys[key].mTime; curPos.set(nodeAnim->mPositionKeys[key].mValue.x, nodeAnim->mPositionKeys[key].mValue.y, nodeAnim->mPositionKeys[key].mValue.z); - if (curT > t) + if ((curT > t) && (key > 0)) { F32 factor = (t - lastT) / (curT - lastT); trans.interpolate(lastPos, curPos, factor); break; } - else if ((curT == t) || (key == nodeAnim->mNumPositionKeys - 1)) + else if ((curT >= t) || (key == nodeAnim->mNumPositionKeys - 1)) { trans = curPos; break; @@ -161,16 +162,16 @@ void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animS F32 lastT = 0.0; for (U32 key = 0; key < nodeAnim->mNumRotationKeys; ++key) { - F32 curT = (F32)nodeAnim->mRotationKeys[key].mTime; + F32 curT = sTimeMultiplier * (F32)nodeAnim->mRotationKeys[key].mTime; curRot.set(nodeAnim->mRotationKeys[key].mValue.x, nodeAnim->mRotationKeys[key].mValue.y, nodeAnim->mRotationKeys[key].mValue.z, nodeAnim->mRotationKeys[key].mValue.w); - if (curT > t) + if ((curT > t) && (key > 0)) { F32 factor = (t - lastT) / (curT - lastT); rot.interpolate(lastRot, curRot, factor); break; } - else if ((curT == t) || (key == nodeAnim->mNumRotationKeys - 1)) + else if ((curT >= t) || (key == nodeAnim->mNumRotationKeys - 1)) { rot = curRot; break; @@ -190,15 +191,15 @@ void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animS F32 lastT = 0.0; for (U32 key = 0; key < nodeAnim->mNumScalingKeys; ++key) { - F32 curT = (F32)nodeAnim->mScalingKeys[key].mTime; + F32 curT = sTimeMultiplier * (F32)nodeAnim->mScalingKeys[key].mTime; curScale.set(nodeAnim->mScalingKeys[key].mValue.x, nodeAnim->mScalingKeys[key].mValue.y, nodeAnim->mScalingKeys[key].mValue.z); - if (curT > t) + if ((curT > t) && (key > 0)) { F32 factor = (t - lastT) / (curT - lastT); scale.interpolate(lastScale, curScale, factor); break; } - else if ((curT == t) || (key == nodeAnim->mNumScalingKeys - 1)) + else if ((curT >= t) || (key == nodeAnim->mNumScalingKeys - 1)) { scale = curScale; break; diff --git a/Engine/source/ts/assimp/assimpAppNode.h b/Engine/source/ts/assimp/assimpAppNode.h index 3fe04d43e..947cb894b 100644 --- a/Engine/source/ts/assimp/assimpAppNode.h +++ b/Engine/source/ts/assimp/assimpAppNode.h @@ -70,6 +70,7 @@ public: } static aiAnimation* sActiveSequence; + static F32 sTimeMultiplier; //----------------------------------------------------------------------- const char *getName() { return mName; } diff --git a/Engine/source/ts/assimp/assimpAppSequence.cpp b/Engine/source/ts/assimp/assimpAppSequence.cpp index a5a92460e..65a1f4b9e 100644 --- a/Engine/source/ts/assimp/assimpAppSequence.cpp +++ b/Engine/source/ts/assimp/assimpAppSequence.cpp @@ -16,19 +16,24 @@ AssimpAppSequence::AssimpAppSequence(aiAnimation *a) : seqStart(0.0f), mAnim(a) { - // From: http://sir-kimmi.de/assimp/lib_html/data.html#anims - // An aiAnimation has a duration. The duration as well as all time stamps are given in ticks. - // To get the correct timing, all time stamp thus have to be divided by aiAnimation::mTicksPerSecond. - // Beware, though, that certain combinations of file format and exporter don't always store this - // information in the exported file. In this case, mTicksPerSecond is set to 0 to indicate the lack of knowledge. + mSequenceName = mAnim->mName.C_Str(); + if (mSequenceName.isEmpty()) + mSequenceName = "ambient"; + Con::printf("\n[Assimp] Adding %s animation", mSequenceName.c_str()); + fps = (mAnim->mTicksPerSecond > 0) ? mAnim->mTicksPerSecond : 30.0f; + U32 maxKeys = 0; F32 maxEndTime = 0; - F32 minFrameTime = 1000.0f; + F32 minFrameTime = 100000.0f; // Detect the frame rate (minimum time between keyframes) and max sequence time for (U32 i = 0; i < mAnim->mNumChannels; ++i) { aiNodeAnim *nodeAnim = mAnim->mChannels[i]; + maxKeys = getMax(maxKeys, nodeAnim->mNumPositionKeys); + maxKeys = getMax(maxKeys, nodeAnim->mNumRotationKeys); + maxKeys = getMax(maxKeys, nodeAnim->mNumScalingKeys); + if (nodeAnim->mNumPositionKeys) maxEndTime = getMax(maxEndTime, (F32) nodeAnim->mPositionKeys[nodeAnim->mNumPositionKeys-1].mTime); if (nodeAnim->mNumRotationKeys) @@ -53,9 +58,31 @@ AssimpAppSequence::AssimpAppSequence(aiAnimation *a) : } } - fps = (minFrameTime > 0.0f) ? 1.0f / minFrameTime : fps; - fps = mClamp(fps, TSShapeLoader::MinFrameRate, TSShapeLoader::MaxFrameRate); - seqEnd = maxEndTime; + S32 timeFactor = Con::getIntVariable("$Assimp::AnimTiming", 1); + S32 fpsRequest = Con::getIntVariable("$Assimp::AnimFPS", 30); + if (timeFactor == 0) + { // Timing specified in frames + fps = mClamp(fpsRequest, 5 /*TSShapeLoader::MinFrameRate*/, TSShapeLoader::MaxFrameRate); + maxKeys = getMax(maxKeys, (U32)maxEndTime); // Keys won't be assigned for every frame. + seqEnd = maxKeys / fps; + mTimeMultiplier = 1.0f / fps; + } + else + { // Timing specified in seconds or ms depending on format + if (maxEndTime > 1000.0f || mAnim->mDuration > 1000.0f) + { + timeFactor = 1000.0f; // If it's more than 1000 seconds, assume it's ms. + Con::setIntVariable("$Assimp::AnimTiming", 1000); + } + + timeFactor = mClamp(timeFactor, 1, 1000); + minFrameTime /= (F32)timeFactor; + maxEndTime /= (F32)timeFactor; + fps = (minFrameTime > 0.0f) ? 1.0f / minFrameTime : fps; + fps = mClamp(fpsRequest, 5 /*TSShapeLoader::MinFrameRate*/, TSShapeLoader::MaxFrameRate); + seqEnd = maxEndTime; + mTimeMultiplier = 1.0f / timeFactor; + } } AssimpAppSequence::~AssimpAppSequence() @@ -65,7 +92,10 @@ AssimpAppSequence::~AssimpAppSequence() void AssimpAppSequence::setActive(bool active) { if (active) + { AssimpAppNode::sActiveSequence = mAnim; + AssimpAppNode::sTimeMultiplier = mTimeMultiplier; + } else { if (AssimpAppNode::sActiveSequence == mAnim) diff --git a/Engine/source/ts/assimp/assimpAppSequence.h b/Engine/source/ts/assimp/assimpAppSequence.h index 28072af7e..be5c11025 100644 --- a/Engine/source/ts/assimp/assimpAppSequence.h +++ b/Engine/source/ts/assimp/assimpAppSequence.h @@ -22,8 +22,10 @@ class AssimpAppSequence : public AppSequence { + String mSequenceName; F32 seqStart; F32 seqEnd; + F32 mTimeMultiplier; // The factor needed to convert the sequence data timestamp to seconds public: @@ -37,7 +39,7 @@ public: virtual S32 getNumTriggers() const { return 0; } virtual void getTrigger(S32 index, TSShape::Trigger& trigger) const { trigger.state = 0; } - virtual const char* getName() const { return mAnim->mName.C_Str(); } + virtual const char* getName() const { return mSequenceName.c_str(); } F32 getStart() const { return seqStart; } F32 getEnd() const { return seqEnd; } diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index f0e543704..570d4825f 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -200,6 +200,10 @@ void AssimpShapeLoader::enumerateScene() Con::printf("[ASSIMP] Mesh Count: %d", mScene->mNumMeshes); Con::printf("[ASSIMP] Material Count: %d", mScene->mNumMaterials); + // Extract embedded textures + for (U32 i = 0; i < mScene->mNumTextures; ++i) + extractTexture(i, mScene->mTextures[i]); + // Load all the materials. for ( U32 i = 0; i < mScene->mNumMaterials; i++ ) AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i])); @@ -302,8 +306,11 @@ void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path) if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) ) { // Only update existing materials if forced to - if ( ColladaUtils::getOptions().forceUpdateMaterials ) - persistMgr.setDirty( mappedMat ); + if (Con::getBoolVariable("$Assimp::ForceUpdateMats", false)) + { + mat->initMaterial(scriptPath, mappedMat); + persistMgr.setDirty(mappedMat); + } } else { @@ -322,8 +329,6 @@ void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path) /// Check if an up-to-date cached DTS is available for this DAE file bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path) { - return false; - // Generate the cached filename Torque::Path cachedPath(path); cachedPath.setExtension("cached.dts"); @@ -332,13 +337,13 @@ bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path) FileTime cachedModifyTime; if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) { - bool forceLoadDAE = Con::getBoolVariable("$assimp::forceLoad", false); + bool forceLoad = Con::getBoolVariable("$assimp::forceLoad", false); FileTime daeModifyTime; if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || - (!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0) )) + (!forceLoad && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0) )) { - // DAE not found, or cached DTS is newer + // Original file not found, or cached DTS is newer return true; } } @@ -393,6 +398,56 @@ void AssimpShapeLoader::detectDetails() AssimpAppMesh::fixDetailSize(singleDetail, Con::getIntVariable("$Assimp::singleDetailSize", 2)); } +void AssimpShapeLoader::extractTexture(U32 index, aiTexture* pTex) +{ // Cache an embedded texture to disk + updateProgress(Load_EnumerateScene, "Extracting Textures...", mScene->mNumTextures, index); + Con::printf("[Assimp] Extracting Texture %s, W: %d, H: %d, %d of %d, format hint: (%s)", pTex->mFilename.C_Str(), + pTex->mWidth, pTex->mHeight, index, mScene->mNumTextures, pTex->achFormatHint); + + // Create the texture filename + String cleanFile = AppMaterial::cleanString(TSShapeLoader::getShapePath().getFileName()); + String texName = String::ToString("%s_cachedTex%d", cleanFile.c_str(), index); + Torque::Path texPath = shapePath; + texPath.setFileName(texName); + + if (pTex->mHeight == 0) + { // Compressed format, write the data directly to disc + texPath.setExtension(pTex->achFormatHint); + FileStream *outputStream; + if ((outputStream = FileStream::createAndOpen(texPath.getFullPath(), Torque::FS::File::Write)) != NULL) + { + outputStream->setPosition(0); + outputStream->write(pTex->mWidth, pTex->pcData); + outputStream->close(); + delete outputStream; + } + } + else + { // Embedded pixel data, fill a bitmap and save it. + GFXTexHandle shapeTex; + shapeTex.set(pTex->mWidth, pTex->mHeight, GFXFormatR8G8B8A8_SRGB, &GFXDynamicTextureSRGBProfile, + String::ToString("AssimpShapeLoader (%s:%i)", __FILE__, __LINE__), 1, 0); + GFXLockedRect *rect = shapeTex.lock(); + + for (U32 y = 0; y < pTex->mHeight; ++y) + { + for (U32 x = 0; x < pTex->mWidth; ++x) + { + U32 targetIndex = (y * rect->pitch) + (x * 4); + U32 sourceIndex = ((y * pTex->mWidth) + x) * 4; + rect->bits[targetIndex] = pTex->pcData[sourceIndex].r; + rect->bits[targetIndex + 1] = pTex->pcData[sourceIndex].g; + rect->bits[targetIndex + 2] = pTex->pcData[sourceIndex].b; + rect->bits[targetIndex + 3] = pTex->pcData[sourceIndex].a; + } + } + shapeTex.unlock(); + + texPath.setExtension("png"); + shapeTex->dumpToDisk("PNG", texPath.getFullPath()); + } +} + //----------------------------------------------------------------------------- /// This function is invoked by the resource manager based on file extension. TSShape* assimpLoadShape(const Torque::Path &path) diff --git a/Engine/source/ts/assimp/assimpShapeLoader.h b/Engine/source/ts/assimp/assimpShapeLoader.h index 713e403cc..848baaaf9 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.h +++ b/Engine/source/ts/assimp/assimpShapeLoader.h @@ -26,6 +26,7 @@ #ifndef _TSSHAPELOADER_H_ #include "ts/loader/tsShapeLoader.h" #endif +#include //----------------------------------------------------------------------------- class AssimpShapeLoader : public TSShapeLoader @@ -37,6 +38,7 @@ protected: virtual bool ignoreNode(const String& name); void detectDetails(); + void extractTexture(U32 index, aiTexture* pTex); public: AssimpShapeLoader(); diff --git a/Templates/BaseGame/game/tools/gui/assimpImport.ed.gui b/Templates/BaseGame/game/tools/gui/assimpImport.ed.gui index e42d9ab9d..22794f3ad 100644 --- a/Templates/BaseGame/game/tools/gui/assimpImport.ed.gui +++ b/Templates/BaseGame/game/tools/gui/assimpImport.ed.gui @@ -393,6 +393,107 @@ canSaveDynamicFields = "0"; }; + new GuiTextCtrl() { + text = "Animation Timing:"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 311"; + Extent = "85 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiPopUpMenuCtrl() { + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiPopUpMenuProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "100 310"; + Extent = "86 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Select the timing units used in the animation data."; + hovertime = "1000"; + internalName = "animTiming"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "FPS:"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "200 311"; + Extent = "20 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl() { + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + passwordMask = "*"; + text = "2"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "225 310"; + Extent = "26 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Frames per second for all animations when Animation Timing type is Frames (5 - 60)"; + hovertime = "1000"; + internalName = "animFPS"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { text = "LOD"; maxLength = "1024"; @@ -582,6 +683,28 @@ canSaveDynamicFields = "0"; }; + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Force update materials.cs"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 150"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::ForceUpdateMats"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Forces update of materials.cs (even if Materials already exist)"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiButtonCtrl() { text = "OK"; groupNum = "-1"; @@ -643,6 +766,10 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd) $Assimp::FindDegenerates = true; $Assimp::FindInvalidData = true; $Assimp::JoinIdenticalVertices = true; + $Assimp::FlipNormals = false; + + $Assimp::AnimTiming = 1; // Seconds + $Assimp::AnimFPS = 30; // Framerate when timing is frames. } %this-->upAxis.clear(); @@ -656,9 +783,15 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd) %this-->lodType.add("SingleSize", 1); %this-->lodType.add("TrailingNumber", 2); %this-->lodType.setSelected($Assimp::lodType); - %this-->singleDetailSize.text = $Assimp::singleDetailSize; + %this-->animTiming.clear(); + %this-->animTiming.add("Frames", 0); + %this-->animTiming.add("Seconds", 1); + %this-->animTiming.add("Milliseconds", 1000); + %this-->animTiming.setSelected($Assimp::AnimTiming); + %this-->animFPS.text = $Assimp::AnimFPS; + //Triangulate is a default(currently mandatory) behavior $Assimp::Triangulate = true; @@ -681,6 +814,9 @@ function AssimpImportDlg::onOK(%this) $Assimp::lodType = %this-->lodType.getSelected(); $Assimp::singleDetailSize = %this-->singleDetailSize.getText(); + $Assimp::AnimTiming = %this-->animTiming.getSelected(); + $Assimp::AnimFPS = %this-->animFPS.getText(); + // Load the shape (always from the DAE) $assimp::forceLoad = true; eval(%this.cmd); diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.cs b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.cs index b9551c983..ea70f7363 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.cs +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.cs @@ -261,13 +261,13 @@ function ShapeEdSelectWindow::onSelect( %this, %path ) // Prompt user to save the old shape if it is dirty if ( ShapeEditor.isDirty() ) { - %cmd = "ColladaImportDlg.showDialog( \"" @ %path @ "\", \"ShapeEditor.selectShape( \\\"" @ %path @ "\\\", "; + %cmd = "showImportDialog( \"" @ %path @ "\", \"ShapeEditor.selectShape( \\\"" @ %path @ "\\\", "; MessageBoxYesNoCancel( "Shape Modified", "Would you like to save your changes?", %cmd @ "true );\" );", %cmd @ "false );\" );" ); } else { %cmd = "ShapeEditor.selectShape( \"" @ %path @ "\", false );"; - ColladaImportDlg.showDialog( %path, %cmd ); + showImportDialog( %path, %cmd ); } } diff --git a/Templates/Full/game/tools/gui/assimpImport.ed.gui b/Templates/Full/game/tools/gui/assimpImport.ed.gui index e42d9ab9d..22794f3ad 100644 --- a/Templates/Full/game/tools/gui/assimpImport.ed.gui +++ b/Templates/Full/game/tools/gui/assimpImport.ed.gui @@ -393,6 +393,107 @@ canSaveDynamicFields = "0"; }; + new GuiTextCtrl() { + text = "Animation Timing:"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 311"; + Extent = "85 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiPopUpMenuCtrl() { + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiPopUpMenuProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "100 310"; + Extent = "86 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Select the timing units used in the animation data."; + hovertime = "1000"; + internalName = "animTiming"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { + text = "FPS:"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "200 311"; + Extent = "20 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl() { + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + passwordMask = "*"; + text = "2"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "225 310"; + Extent = "26 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Frames per second for all animations when Animation Timing type is Frames (5 - 60)"; + hovertime = "1000"; + internalName = "animFPS"; + canSaveDynamicFields = "0"; + }; + new GuiTextCtrl() { text = "LOD"; maxLength = "1024"; @@ -582,6 +683,28 @@ canSaveDynamicFields = "0"; }; + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Force update materials.cs"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 150"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::ForceUpdateMats"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Forces update of materials.cs (even if Materials already exist)"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiButtonCtrl() { text = "OK"; groupNum = "-1"; @@ -643,6 +766,10 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd) $Assimp::FindDegenerates = true; $Assimp::FindInvalidData = true; $Assimp::JoinIdenticalVertices = true; + $Assimp::FlipNormals = false; + + $Assimp::AnimTiming = 1; // Seconds + $Assimp::AnimFPS = 30; // Framerate when timing is frames. } %this-->upAxis.clear(); @@ -656,9 +783,15 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd) %this-->lodType.add("SingleSize", 1); %this-->lodType.add("TrailingNumber", 2); %this-->lodType.setSelected($Assimp::lodType); - %this-->singleDetailSize.text = $Assimp::singleDetailSize; + %this-->animTiming.clear(); + %this-->animTiming.add("Frames", 0); + %this-->animTiming.add("Seconds", 1); + %this-->animTiming.add("Milliseconds", 1000); + %this-->animTiming.setSelected($Assimp::AnimTiming); + %this-->animFPS.text = $Assimp::AnimFPS; + //Triangulate is a default(currently mandatory) behavior $Assimp::Triangulate = true; @@ -681,6 +814,9 @@ function AssimpImportDlg::onOK(%this) $Assimp::lodType = %this-->lodType.getSelected(); $Assimp::singleDetailSize = %this-->singleDetailSize.getText(); + $Assimp::AnimTiming = %this-->animTiming.getSelected(); + $Assimp::AnimFPS = %this-->animFPS.getText(); + // Load the shape (always from the DAE) $assimp::forceLoad = true; eval(%this.cmd); diff --git a/Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs b/Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs index 1bada1ad7..1e5f896cb 100644 --- a/Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs +++ b/Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs @@ -261,7 +261,7 @@ function ShapeEdSelectWindow::onSelect( %this, %path ) // Prompt user to save the old shape if it is dirty if ( ShapeEditor.isDirty() ) { - %cmd = "ColladaImportDlg.showDialog( \"" @ %path @ "\", \"ShapeEditor.selectShape( \\\"" @ %path @ "\\\", "; + %cmd = "showImportDialog( \"" @ %path @ "\", \"ShapeEditor.selectShape( \\\"" @ %path @ "\\\", "; MessageBoxYesNoCancel( "Shape Modified", "Would you like to save your changes?", %cmd @ "true );\" );", %cmd @ "false );\" );" ); } else