// // Copyright (c) 2008-2015 the Urho3D project. // // 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 #include #include "OpenAssetUtils.h" namespace ToolCore { void CollectMeshes(const aiScene* scene, OutModel& model, aiNode* node) { for (unsigned i = 0; i < node->mNumMeshes; ++i) { aiMesh* mesh = scene->mMeshes[node->mMeshes[i]]; for (unsigned j = 0; j < model.meshes_.Size(); ++j) { if (mesh == model.meshes_[j]) { PrintLine("Warning: same mesh found multiple times"); break; } } model.meshIndices_.Insert(node->mMeshes[i]); model.meshes_.Push(mesh); model.meshNodes_.Push(node); model.totalVertices_ += mesh->mNumVertices; model.totalIndices_ += GetNumValidFaces(mesh) * 3; } for (unsigned i = 0; i < node->mNumChildren; ++i) CollectMeshes(scene, model, node->mChildren[i]); } void GetMeshesUnderNode(const aiScene* scene, Vector >& dest, aiNode* node) { for (unsigned i = 0; i < node->mNumMeshes; ++i) dest.Push(MakePair(node, scene->mMeshes[node->mMeshes[i]])); } unsigned GetMeshIndex(const aiScene* scene, aiMesh* mesh) { for (unsigned i = 0; i < scene->mNumMeshes; ++i) { if (scene->mMeshes[i] == mesh) return i; } return M_MAX_UNSIGNED; } unsigned GetBoneIndex(OutModel& model, const String& boneName) { for (unsigned i = 0; i < model.bones_.Size(); ++i) { if (boneName == model.bones_[i]->mName.data) return i; } return M_MAX_UNSIGNED; } aiBone* GetMeshBone(OutModel& model, const String& boneName) { for (unsigned i = 0; i < model.meshes_.Size(); ++i) { aiMesh* mesh = model.meshes_[i]; for (unsigned j = 0; j < mesh->mNumBones; ++j) { aiBone* bone = mesh->mBones[j]; if (boneName == bone->mName.data) return bone; } } return 0; } Matrix3x4 GetOffsetMatrix(OutModel& model, const String& boneName) { for (unsigned i = 0; i < model.meshes_.Size(); ++i) { aiMesh* mesh = model.meshes_[i]; aiNode* node = model.meshNodes_[i]; for (unsigned j = 0; j < mesh->mNumBones; ++j) { aiBone* bone = mesh->mBones[j]; if (boneName == bone->mName.data) { aiMatrix4x4 offset = bone->mOffsetMatrix; aiMatrix4x4 nodeDerivedInverse = GetMeshBakingTransform(node, model.rootNode_); nodeDerivedInverse.Inverse(); offset *= nodeDerivedInverse; return ToMatrix3x4(offset); } } } return Matrix3x4::IDENTITY; } unsigned GetNumValidFaces(aiMesh* mesh) { unsigned ret = 0; for (unsigned j = 0; j < mesh->mNumFaces; ++j) { if (mesh->mFaces[j].mNumIndices == 3) ++ret; } return ret; } void WriteShortIndices(unsigned short*& dest, aiMesh* mesh, unsigned index, unsigned offset) { if (mesh->mFaces[index].mNumIndices == 3) { *dest++ = mesh->mFaces[index].mIndices[0] + offset; *dest++ = mesh->mFaces[index].mIndices[1] + offset; *dest++ = mesh->mFaces[index].mIndices[2] + offset; } } void WriteLargeIndices(unsigned*& dest, aiMesh* mesh, unsigned index, unsigned offset) { if (mesh->mFaces[index].mNumIndices == 3) { *dest++ = mesh->mFaces[index].mIndices[0] + offset; *dest++ = mesh->mFaces[index].mIndices[1] + offset; *dest++ = mesh->mFaces[index].mIndices[2] + offset; } } void WriteVertex(float*& dest, aiMesh* mesh, unsigned index, unsigned elementMask, BoundingBox& box, const Matrix3x4& vertexTransform, const Matrix3& normalTransform, Vector >& blendIndices, Vector >& blendWeights) { Vector3 vertex = vertexTransform * ToVector3(mesh->mVertices[index]); box.Merge(vertex); *dest++ = vertex.x_; *dest++ = vertex.y_; *dest++ = vertex.z_; if (elementMask & MASK_NORMAL) { Vector3 normal = normalTransform * ToVector3(mesh->mNormals[index]); *dest++ = normal.x_; *dest++ = normal.y_; *dest++ = normal.z_; } if (elementMask & MASK_COLOR) { *((unsigned*)dest) = Color(mesh->mColors[0][index].r, mesh->mColors[0][index].g, mesh->mColors[0][index].b, mesh->mColors[0][index].a).ToUInt(); ++dest; } if (elementMask & MASK_TEXCOORD1) { Vector3 texCoord = ToVector3(mesh->mTextureCoords[0][index]); *dest++ = texCoord.x_; *dest++ = texCoord.y_; } if (elementMask & MASK_TEXCOORD2) { Vector3 texCoord = ToVector3(mesh->mTextureCoords[1][index]); *dest++ = texCoord.x_; *dest++ = texCoord.y_; } if (elementMask & MASK_TANGENT) { Vector3 tangent = normalTransform * ToVector3(mesh->mTangents[index]); Vector3 normal = normalTransform * ToVector3(mesh->mNormals[index]); Vector3 bitangent = normalTransform * ToVector3(mesh->mBitangents[index]); // Check handedness float w = 1.0f; if ((tangent.CrossProduct(normal)).DotProduct(bitangent) < 0.5f) w = -1.0f; *dest++ = tangent.x_; *dest++ = tangent.y_; *dest++ = tangent.z_; *dest++ = w; } if (elementMask & MASK_BLENDWEIGHTS) { for (unsigned i = 0; i < 4; ++i) { if (i < blendWeights[index].Size()) *dest++ = blendWeights[index][i]; else *dest++ = 0.0f; } } if (elementMask & MASK_BLENDINDICES) { unsigned char* destBytes = (unsigned char*)dest; ++dest; for (unsigned i = 0; i < 4; ++i) { if (i < blendIndices[index].Size()) *destBytes++ = blendIndices[index][i]; else *destBytes++ = 0; } } } unsigned GetElementMask(aiMesh* mesh) { unsigned elementMask = MASK_POSITION; if (mesh->HasNormals()) elementMask |= MASK_NORMAL; if (mesh->HasTangentsAndBitangents()) elementMask |= MASK_TANGENT; if (mesh->GetNumColorChannels() > 0) elementMask |= MASK_COLOR; if (mesh->GetNumUVChannels() > 0) elementMask |= MASK_TEXCOORD1; if (mesh->GetNumUVChannels() > 1) elementMask |= MASK_TEXCOORD2; if (mesh->HasBones()) elementMask |= (MASK_BLENDWEIGHTS | MASK_BLENDINDICES); return elementMask; } aiNode* GetNode(const String& name, aiNode* rootNode, bool caseSensitive) { if (!rootNode) return 0; if (!name.Compare(rootNode->mName.data, caseSensitive)) return rootNode; for (unsigned i = 0; i < rootNode->mNumChildren; ++i) { aiNode* found = GetNode(name, rootNode->mChildren[i], caseSensitive); if (found) return found; } return 0; } aiMatrix4x4 GetDerivedTransform(aiNode* node, aiNode* rootNode, bool rootInclusive) { return GetDerivedTransform(node->mTransformation, node, rootNode, rootInclusive); } aiMatrix4x4 GetDerivedTransform(aiMatrix4x4 transform, aiNode* node, aiNode* rootNode, bool rootInclusive) { // If basenode is defined, go only up to it in the parent chain while (node && node != rootNode) { node = node->mParent; if (!rootInclusive && node == rootNode) break; if (node) transform = node->mTransformation * transform; } return transform; } aiMatrix4x4 GetMeshBakingTransform(aiNode* meshNode, aiNode* modelRootNode) { if (meshNode == modelRootNode) return aiMatrix4x4(); else return GetDerivedTransform(meshNode, modelRootNode); } void GetPosRotScale(const aiMatrix4x4& transform, Vector3& pos, Quaternion& rot, Vector3& scale) { aiVector3D aiPos; aiQuaternion aiRot; aiVector3D aiScale; transform.Decompose(aiScale, aiRot, aiPos); pos = ToVector3(aiPos); rot = ToQuaternion(aiRot); scale = ToVector3(aiScale); } void GetBlendData(OutModel& model, aiMesh* mesh, PODVector& boneMappings, Vector >& blendIndices, Vector >& blendWeights, unsigned maxBones) { blendIndices.Resize(mesh->mNumVertices); blendWeights.Resize(mesh->mNumVertices); boneMappings.Clear(); // If model has more bones than can fit vertex shader parameters, write the per-geometry mappings if (model.bones_.Size() > maxBones) { if (mesh->mNumBones > maxBones) { ErrorExit( "Geometry (submesh) has over " + String(maxBones) + " bone influences. Try splitting to more submeshes\n" "that each stay at " + String(maxBones) + " bones or below." ); } boneMappings.Resize(mesh->mNumBones); for (unsigned i = 0; i < mesh->mNumBones; ++i) { aiBone* bone = mesh->mBones[i]; String boneName = FromAIString(bone->mName); unsigned globalIndex = GetBoneIndex(model, boneName); if (globalIndex == M_MAX_UNSIGNED) ErrorExit("Bone " + boneName + " not found"); boneMappings[i] = globalIndex; for (unsigned j = 0; j < bone->mNumWeights; ++j) { unsigned vertex = bone->mWeights[j].mVertexId; blendIndices[vertex].Push(i); blendWeights[vertex].Push(bone->mWeights[j].mWeight); if (blendWeights[vertex].Size() > 4) ErrorExit("More than 4 bone influences on vertex"); } } } else { for (unsigned i = 0; i < mesh->mNumBones; ++i) { aiBone* bone = mesh->mBones[i]; String boneName = FromAIString(bone->mName); unsigned globalIndex = GetBoneIndex(model, boneName); if (globalIndex == M_MAX_UNSIGNED) ErrorExit("Bone " + boneName + " not found"); for (unsigned j = 0; j < bone->mNumWeights; ++j) { unsigned vertex = bone->mWeights[j].mVertexId; blendIndices[vertex].Push(globalIndex); blendWeights[vertex].Push(bone->mWeights[j].mWeight); if (blendWeights[vertex].Size() > 4) ErrorExit("More than 4 bone influences on vertex"); } } } } String FromAIString(const aiString& str) { return String(str.data); } Vector3 ToVector3(const aiVector3D& vec) { return Vector3(vec.x, vec.y, vec.z); } Vector2 ToVector2(const aiVector2D& vec) { return Vector2(vec.x, vec.y); } Quaternion ToQuaternion(const aiQuaternion& quat) { return Quaternion(quat.w, quat.x, quat.y, quat.z); } Matrix3x4 ToMatrix3x4(const aiMatrix4x4& mat) { Matrix3x4 ret; memcpy(&ret.m00_, &mat.a1, sizeof(Matrix3x4)); return ret; } String SanitateAssetName(const String& name) { String fixedName = name; fixedName.Replace("<", ""); fixedName.Replace(">", ""); fixedName.Replace("?", ""); fixedName.Replace("*", ""); fixedName.Replace(":", ""); fixedName.Replace("\"", ""); fixedName.Replace("/", ""); fixedName.Replace("\\", ""); fixedName.Replace("|", ""); return fixedName; } }