private void ConformMeshBoneBindings(Mesh mesh, Mesh conformToMesh) { foreach (var conformBone in conformToMesh.BoneBindings) { BoneBinding inputBone = null; foreach (var bone in mesh.BoneBindings) { if (bone.BoneName == conformBone.BoneName) { inputBone = bone; break; } } if (inputBone == null) { // Create a new "dummy" binding if it does not exist in the new mesh inputBone = new BoneBinding(); inputBone.BoneName = conformBone.BoneName; mesh.BoneBindings.Add(inputBone); } // The bones match, copy relevant parameters from the conforming binding to the input. inputBone.OBBMin = conformBone.OBBMin; inputBone.OBBMax = conformBone.OBBMax; } }
private void GenerateDummySkeleton(Root root) { foreach (var model in root.Models) { if (model.Skeleton == null) { Utils.Info($"Generating dummy skeleton for model '{model.Name}'"); var skeleton = new Skeleton(); skeleton.Name = model.Name; skeleton.LODType = 1; skeleton.IsDummy = true; root.Skeletons.Add(skeleton); var bone = new Bone(); bone.Name = model.Name; bone.ParentIndex = -1; skeleton.Bones = new List <Bone> { bone }; bone.Transform = new Transform(); // TODO: Transform / IWT is not always identity on dummy bones! skeleton.UpdateWorldTransforms(); model.Skeleton = skeleton; foreach (var mesh in model.MeshBindings) { if (mesh.Mesh.BoneBindings != null && mesh.Mesh.BoneBindings.Count > 0) { throw new ParsingException("Failed to generate dummy skeleton: Mesh already has bone bindings."); } var binding = new BoneBinding(); binding.BoneName = bone.Name; // TODO: Calculate bounding box! // Use small bounding box values, as it interferes with object placement // in D:OS 2 (after the Gift Bag 2 update) binding.OBBMin = new float[] { -0.1f, -0.1f, -0.1f }; binding.OBBMax = new float[] { 0.1f, 0.1f, 0.1f }; mesh.Mesh.BoneBindings = new List <BoneBinding> { binding }; } } } }
private void GenerateDummySkeleton(Root root) { foreach (var model in root.Models) { if (model.Skeleton == null) { Utils.Info(String.Format("Generating dummy skeleton for model '{0}'", model.Name)); var skeleton = new Skeleton(); skeleton.Name = model.Name; skeleton.LODType = 0; skeleton.IsDummy = true; root.Skeletons.Add(skeleton); var bone = new Bone(); bone.Name = model.Name; bone.ParentIndex = -1; skeleton.Bones = new List <Bone> { bone }; bone.Transform = new Transform(); // TODO: Transform / IWT is not always identity on dummy bones! skeleton.UpdateInverseWorldTransforms(); model.Skeleton = skeleton; foreach (var mesh in model.MeshBindings) { if (mesh.Mesh.BoneBindings != null && mesh.Mesh.BoneBindings.Count > 0) { throw new ParsingException("Failed to generate dummy skeleton: Mesh already has bone bindings."); } var binding = new BoneBinding(); binding.BoneName = bone.Name; // TODO: Calculate bounding box! binding.OBBMin = new float[] { -10, -10, -10 }; binding.OBBMax = new float[] { 10, 10, 10 }; mesh.Mesh.BoneBindings = new List <BoneBinding> { binding }; } } } }
private void ImportSkin(Root root, skin skin) { if (skin.source1[0] != '#') { throw new ParsingException("Only ID references are supported for skin geometries"); } Mesh mesh = null; if (!ColladaGeometries.TryGetValue(skin.source1.Substring(1), out mesh)) { throw new ParsingException("Skin references nonexistent mesh: " + skin.source1); } if (!mesh.VertexFormat.HasBoneWeights) { var msg = String.Format("Tried to apply skin to mesh ({0}) with non-skinned vertices", mesh.Name); throw new ParsingException(msg); } var sources = new Dictionary <String, ColladaSource>(); foreach (var source in skin.source) { var src = ColladaSource.FromCollada(source); sources.Add(src.id, src); } List <Bone> joints = null; List <Matrix4> invBindMatrices = null; foreach (var input in skin.joints.input) { if (input.source[0] != '#') { throw new ParsingException("Only ID references are supported for joint input sources"); } ColladaSource inputSource = null; if (!sources.TryGetValue(input.source.Substring(1), out inputSource)) { throw new ParsingException("Joint input source does not exist: " + input.source); } if (input.semantic == "JOINT") { List <string> jointNames = inputSource.NameParams.Values.SingleOrDefault(); if (jointNames == null) { throw new ParsingException("Joint input source 'JOINT' must contain array of names."); } var skeleton = root.Skeletons[0]; joints = new List <Bone>(); foreach (var name in jointNames) { Bone bone = null; var lookupName = name.Replace("_x0020_", " "); if (!skeleton.BonesBySID.TryGetValue(lookupName, out bone)) { throw new ParsingException("Joint name list references nonexistent bone: " + lookupName); } joints.Add(bone); } } else if (input.semantic == "INV_BIND_MATRIX") { invBindMatrices = inputSource.MatrixParams.Values.SingleOrDefault(); if (invBindMatrices == null) { throw new ParsingException("Joint input source 'INV_BIND_MATRIX' must contain a single array of matrices."); } } else { throw new ParsingException("Unsupported joint semantic: " + input.semantic); } } if (joints == null) { throw new ParsingException("Required joint input semantic missing: JOINT"); } if (invBindMatrices == null) { throw new ParsingException("Required joint input semantic missing: INV_BIND_MATRIX"); } var influenceCounts = ColladaHelpers.StringsToIntegers(skin.vertex_weights.vcount); var influences = ColladaHelpers.StringsToIntegers(skin.vertex_weights.v); foreach (var count in influenceCounts) { if (count > 4) { throw new ParsingException("GR2 only supports at most 4 vertex influences"); } } // TODO if (influenceCounts.Count != mesh.OriginalToConsolidatedVertexIndexMap.Count) { Utils.Warn(String.Format("Vertex influence count ({0}) differs from vertex count ({1})", influenceCounts.Count, mesh.OriginalToConsolidatedVertexIndexMap.Count)); } List <Single> weights = null; int jointInputIndex = -1, weightInputIndex = -1; foreach (var input in skin.vertex_weights.input) { if (input.semantic == "JOINT") { jointInputIndex = (int)input.offset; } else if (input.semantic == "WEIGHT") { weightInputIndex = (int)input.offset; if (input.source[0] != '#') { throw new ParsingException("Only ID references are supported for weight input sources"); } ColladaSource inputSource = null; if (!sources.TryGetValue(input.source.Substring(1), out inputSource)) { throw new ParsingException("Weight input source does not exist: " + input.source); } if (!inputSource.FloatParams.TryGetValue("WEIGHT", out weights)) { weights = inputSource.FloatParams.Values.SingleOrDefault(); } if (weights == null) { throw new ParsingException("Weight input source " + input.source + " must have WEIGHT float attribute"); } } else { throw new ParsingException("Unsupported skin input semantic: " + input.semantic); } } if (jointInputIndex == -1) { throw new ParsingException("Required vertex weight input semantic missing: JOINT"); } if (weightInputIndex == -1) { throw new ParsingException("Required vertex weight input semantic missing: WEIGHT"); } // Remove bones that are not actually influenced from the binding list var boundBones = new HashSet <Bone>(); int offset = 0; int stride = skin.vertex_weights.input.Length; while (offset < influences.Count) { var jointIndex = influences[offset + jointInputIndex]; var weightIndex = influences[offset + weightInputIndex]; var joint = joints[jointIndex]; var weight = weights[weightIndex]; if (!boundBones.Contains(joint)) { boundBones.Add(joint); } offset += stride; } if (boundBones.Count > 127) { throw new ParsingException("D:OS supports at most 127 bound bones per mesh."); } mesh.BoneBindings = new List <BoneBinding>(); var boneToIndexMaps = new Dictionary <Bone, int>(); for (var i = 0; i < joints.Count; i++) { if (boundBones.Contains(joints[i])) { // Collada allows one inverse bind matrix for each skin, however Granny // only has one matrix for one bone, even if said bone is used from multiple meshes. // Hopefully the Collada ones are all equal ... var iwt = invBindMatrices[i]; // iwt.Transpose(); joints[i].InverseWorldTransform = new float[] { iwt[0, 0], iwt[1, 0], iwt[2, 0], iwt[3, 0], iwt[0, 1], iwt[1, 1], iwt[2, 1], iwt[3, 1], iwt[0, 2], iwt[1, 2], iwt[2, 2], iwt[3, 2], iwt[0, 3], iwt[1, 3], iwt[2, 3], iwt[3, 3] }; // Bind all bones that affect vertices to the mesh, so we can reference them // later from the vertexes BoneIndices. var binding = new BoneBinding(); binding.BoneName = joints[i].Name; // TODO // Use small bounding box values, as it interferes with object placement // in D:OS 2 (after the Gift Bag 2 update) binding.OBBMin = new float[] { -0.1f, -0.1f, -0.1f }; binding.OBBMax = new float[] { 0.1f, 0.1f, 0.1f }; mesh.BoneBindings.Add(binding); boneToIndexMaps.Add(joints[i], boneToIndexMaps.Count); } } offset = 0; for (var vertexIndex = 0; vertexIndex < influenceCounts.Count; vertexIndex++) { var influenceCount = influenceCounts[vertexIndex]; float influenceSum = 0.0f; for (var i = 0; i < influenceCount; i++) { var weightIndex = influences[offset + i * stride + weightInputIndex]; influenceSum += weights[weightIndex]; } for (var i = 0; i < influenceCount; i++) { var jointIndex = influences[offset + jointInputIndex]; var weightIndex = influences[offset + weightInputIndex]; var joint = joints[jointIndex]; var weight = weights[weightIndex] / influenceSum; // Not all vertices are actually used in triangles, we may have unused verts in the // source list (though this is rare) which won't show up in the consolidated vertex map. if (mesh.OriginalToConsolidatedVertexIndexMap.TryGetValue(vertexIndex, out List <int> consolidatedIndices)) { foreach (var consolidatedIndex in consolidatedIndices) { var vertex = mesh.PrimaryVertexData.Vertices[consolidatedIndex]; vertex.AddInfluence((byte)boneToIndexMaps[joint], weight); } } offset += stride; } } foreach (var vertex in mesh.PrimaryVertexData.Vertices) { vertex.FinalizeInfluences(); } // Warn if we have vertices that are not influenced by any bone int notInfluenced = 0; foreach (var vertex in mesh.PrimaryVertexData.Vertices) { if (vertex.BoneWeights[0] == 0) { notInfluenced++; } } if (notInfluenced > 0) { Utils.Warn(String.Format("{0} vertices are not influenced by any bone", notInfluenced)); } if (skin.bind_shape_matrix != null) { var bindShapeFloats = skin.bind_shape_matrix.Trim().Split(new char[] { ' ' }).Select(s => Single.Parse(s)).ToArray(); var bindShapeMat = ColladaHelpers.FloatsToMatrix(bindShapeFloats); bindShapeMat.Transpose(); // Deform geometries that were affected by our bind shape matrix mesh.PrimaryVertexData.Transform(bindShapeMat); } if (Options.RecalculateOBBs) { UpdateOBBs(root.Skeletons.Single(), mesh); } }