From 07ab38cdcbc0d01a4c754e5fc815b2dd6c1a36aa Mon Sep 17 00:00:00 2001 From: historic_bruno Date: Mon, 5 Mar 2012 00:42:34 +0000 Subject: [PATCH] Implements COLLADA -1 vertex weight support, using simpler initial solution. Fixes #1012. This was SVN commit r11242. --- source/collada/PMDConvert.cpp | 97 +++++++++++++++++++++++++---------- source/graphics/Model.cpp | 13 ++++- source/graphics/ModelDef.cpp | 17 ++++-- 3 files changed, 95 insertions(+), 32 deletions(-) diff --git a/source/collada/PMDConvert.cpp b/source/collada/PMDConvert.cpp index 4a21585b4b..5cbcd88243 100644 --- a/source/collada/PMDConvert.cpp +++ b/source/collada/PMDConvert.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -252,39 +252,74 @@ public: // guaranteed by ReduceInfluences; necessary for avoiding // out-of-bounds writes to the VertexBlend + if (vertexInfluences[i].GetPairCount() == 0) + { + // Blender exports some models with vertices that have no influences, + // which I've not found details about in the COLLADA spec, however, + // it seems to work OK to treat these vertices the same as if they + // were only influenced by the bind-shape matrix (see comment below), + // so we use the same special case here. + influences.bones[0] = (uint8)jointCount; + influences.weights[0] = 1.0f; + } + for (size_t j = 0; j < vertexInfluences[i].GetPairCount(); ++j) { - uint32 jointIdx = vertexInfluences[i].GetPair(j)->jointIndex; - REQUIRE(jointIdx <= 0xFF, "sensible number of joints (<256)"); // because we only have a u8 to store them in - - // Find the joint on the skeleton, after checking it really exists - FCDSceneNode* joint = NULL; - if (jointIdx < controllerInstance.GetJointCount()) - joint = controllerInstance.GetJoint(jointIdx); - - // Complain on error - if (! joint) + if (vertexInfluences[i].GetPair(j)->jointIndex == -1) { - if (! hasComplainedAboutNonexistentJoints) + // This is a special case we must handle, according to the COLLADA spec: + // "An index of -1 into the array of joints refers to the bind shape" + // + // which basically means when skinning the vertex it's relative to the + // bind-shape transform instead of an animated bone. Since our skinning + // is in world space, we will have already applied the bind-shape transform, + // so we don't have to worry about that, though we DO have to apply the + // world space transform of the model for the indicated vertex. + // + // To indicate this special case, we use a bone ID set to the total number + // of bones in the model, which will have a special "bone matrix" reserved + // that contains the world space transform of the model during skinning. + // (see http://trac.wildfiregames.com/ticket/1012) + influences.bones[j] = (uint8)jointCount; + influences.weights[j] = vertexInfluences[i].GetPair(j)->weight; + } + else + { + // Check for less than 254 joints because we store them in a u8, + // 0xFF is a reserved value (no influence), and we reserve one slot + // for the above special case. + uint32 jointIdx = vertexInfluences[i].GetPair(j)->jointIndex; + REQUIRE(jointIdx < 0xFE, "sensible number of joints (<254)"); + + // Find the joint on the skeleton, after checking it really exists + FCDSceneNode* joint = NULL; + if (jointIdx < controllerInstance.GetJointCount()) + joint = controllerInstance.GetJoint(jointIdx); + + // Complain on error + if (! joint) { - Log(LOG_WARNING, "Vertexes influenced by nonexistent joint"); - hasComplainedAboutNonexistentJoints = true; + if (! hasComplainedAboutNonexistentJoints) + { + Log(LOG_WARNING, "Vertexes influenced by nonexistent joint"); + hasComplainedAboutNonexistentJoints = true; + } + continue; } - continue; - } - // Store into the VertexBlend - int boneId = skeleton.GetBoneID(joint->GetName().c_str()); - if (boneId < 0) - { - // The relevant joint does exist, but it's not a recognised - // bone in our chosen skeleton structure - Log(LOG_ERROR, "Vertex influenced by unrecognised bone '%s'", joint->GetName().c_str()); - continue; - } + // Store into the VertexBlend + int boneId = skeleton.GetBoneID(joint->GetName().c_str()); + if (boneId < 0) + { + // The relevant joint does exist, but it's not a recognised + // bone in our chosen skeleton structure + Log(LOG_ERROR, "Vertex influenced by unrecognised bone '%s'", joint->GetName().c_str()); + continue; + } - influences.bones[j] = (uint8)boneId; - influences.weights[j] = vertexInfluences[i].GetPair(j)->weight; + influences.bones[j] = (uint8)boneId; + influences.weights[j] = vertexInfluences[i].GetPair(j)->weight; + } } boneWeights.push_back(influences); @@ -522,6 +557,10 @@ public: /** * Applies world-space transform to vertex data and transforms Collada's right-handed * Y-up / Z-up coordinates to the game's left-handed Y-up coordinate system + * + * TODO: Maybe we should use FCDocumentTools::StandardizeUpAxisAndLength in addition + * to this, so we'd only have one up-axis case to worry about, but it doesn't seem to + * correctly adjust the prop points in Y_UP models. */ static void TransformStaticModel(float* position, float* normal, size_t vertexCount, const FMMatrix44& transform, bool yUp) @@ -563,6 +602,10 @@ public: /** * Applies world-space transform to vertex data and transforms Collada's right-handed * Y-up / Z-up coordinates to the game's left-handed Y-up coordinate system + * + * TODO: Maybe we should use FCDocumentTools::StandardizeUpAxisAndLength in addition + * to this, so we'd only have one up-axis case to worry about, but it doesn't seem to + * correctly adjust the prop points in Y_UP models. */ static void TransformSkinnedModel(float* position, float* normal, size_t vertexCount, std::vector& bones, std::vector& propPoints, diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index 226ee339ba..728deea55d 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -84,7 +84,8 @@ bool CModel::InitModel(const CModelDefPtr& modeldef) size_t numBlends = modeldef->GetNumBlends(); // allocate matrices for bone transformations - m_BoneMatrices = (CMatrix3D*)rtl_AllocateAligned(sizeof(CMatrix3D) * (numBones + numBlends), 16); + // (one extra matrix is used for the special case of bind-shape relative weighting) + m_BoneMatrices = (CMatrix3D*)rtl_AllocateAligned(sizeof(CMatrix3D) * (numBones + 1 + numBlends), 16); for (size_t i = 0; i < numBones + numBlends; ++i) { m_BoneMatrices[i].SetIdentity(); @@ -398,6 +399,14 @@ void CModel::ValidatePosition() m_BoneMatrices[i] = m_BoneMatrices[i] * m_InverseBindBoneMatrices[i]; } + // Note: there is a special case of joint influence, in which the vertex + // is influenced by the bind-shape transform instead of a particular bone, + // which we indicate with the blending bone ID set to the total number + // of bones. But since we're skinning in world space, we use the model's + // world space transform and store that matrix in this special index. + // (see http://trac.wildfiregames.com/ticket/1012) + m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform; + m_pModelDef->BlendBoneMatrices(m_BoneMatrices); } } diff --git a/source/graphics/ModelDef.cpp b/source/graphics/ModelDef.cpp index a2db6c87fc..cf4818d48a 100644 --- a/source/graphics/ModelDef.cpp +++ b/source/graphics/ModelDef.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -209,7 +209,15 @@ void CModelDef::BlendBoneMatrices( for (size_t i = 0; i < m_NumBlends; ++i) { const SVertexBlend& blend = m_pBlends[i]; - CMatrix3D& boneMatrix = boneMatrices[m_NumBones + i]; + CMatrix3D& boneMatrix = boneMatrices[m_NumBones + 1 + i]; + + // Note: there is a special case of joint influence, in which the vertex + // is influenced by the bind-shape matrix instead of a particular bone, + // which we indicate by setting the bone ID to the total number of bones. + // It should be blended with the world space transform and we have already + // set up this matrix in boneMatrices. + // (see http://trac.wildfiregames.com/ticket/1012) + boneMatrix.Blend(boneMatrices[blend.m_Bone[0]], blend.m_Weight[0]); boneMatrix.AddBlend(boneMatrices[blend.m_Bone[1]], blend.m_Weight[1]); for (size_t j = 2; j < SVertexBlend::SIZE && blend.m_Bone[j] != 0xFF; ++j) @@ -301,7 +309,10 @@ CModelDef* CModelDef::Load(const VfsPath& filename, const VfsPath& name) } if (j >= blends.size()) blends.push_back(blend); - mdef->m_pBlendIndices[i] = mdef->m_NumBones + j; + // This index is offset by one to allow the special case of a + // weighted influence relative to the bind-shape rather than + // a particular bone. See comment in BlendBoneMatrices. + mdef->m_pBlendIndices[i] = mdef->m_NumBones + 1 + j; } }