public static void BuildSkinnedMesh(string meshPath, string skelPath, SkeletonSample skeleton, UsdSkelSkinningQuery skinningQuery, GameObject go, PrimMap primMap, SceneImportOptions options) { // The mesh renderer must already exist, since hte mesh also must already exist. var smr = go.GetComponent <SkinnedMeshRenderer>(); if (!smr) { throw new Exception( "Error importing " + meshPath + " SkinnnedMeshRenderer not present on GameObject" ); } // Get and validate the joint weights and indices informations. UsdGeomPrimvar jointWeights = skinningQuery.GetJointWeightsPrimvar(); UsdGeomPrimvar jointIndices = skinningQuery.GetJointIndicesPrimvar(); if (!jointWeights.IsDefined() || !jointIndices.IsDefined()) { throw new Exception("Joints information (indices and/or weights) are missing for: " + meshPath); } // TODO: Both indices and weights attributes can be animated. It's not handled yet. // TODO: Having something that convert a UsdGeomPrimvar into a PrimvarSample could help simplify this code. int[] indices = IntrinsicTypeConverter.FromVtArray((VtIntArray)jointIndices.GetAttr().Get()); int indicesElementSize = jointIndices.GetElementSize(); pxr.TfToken indicesInterpolation = jointIndices.GetInterpolation(); if (indices.Length == 0 || indicesElementSize == 0 || indices.Length % indicesElementSize != 0 || !pxr.UsdGeomPrimvar.IsValidInterpolation(indicesInterpolation)) { throw new Exception("Joint indices information are invalid or empty for: " + meshPath); } float[] weights = IntrinsicTypeConverter.FromVtArray((VtFloatArray)jointWeights.GetAttr().Get()); int weightsElementSize = jointWeights.GetElementSize(); pxr.TfToken weightsInterpolation = jointWeights.GetInterpolation(); if (weights.Length == 0 || weightsElementSize == 0 || weights.Length % weightsElementSize != 0 || !pxr.UsdGeomPrimvar.IsValidInterpolation(weightsInterpolation)) { throw new Exception("Joints weights information are invalid or empty for: " + meshPath); } // Get and validate the local list of joints. VtTokenArray jointsAttr = new VtTokenArray(); skinningQuery.GetJointOrder(jointsAttr); // If jointsAttr wasn't define, GetJointOrder return an empty array and FromVtArray as well. string[] joints = IntrinsicTypeConverter.FromVtArray(jointsAttr); // WARNING: Do not mutate skeleton values. string[] skelJoints = skeleton.joints; if (joints == null || joints.Length == 0) { if (skelJoints == null || skelJoints.Length == 0) { throw new Exception("Joints array empty: " + meshPath); } else { joints = skelJoints; } } var mesh = smr.sharedMesh; // TODO: bind transform attribute can be animated. It's not handled yet. Matrix4x4 geomXf = UnityTypeConverter.FromMatrix(skinningQuery.GetGeomBindTransform()); // If the joints list is a different length than the bind transforms, then this is likely // a mesh using a subset of the total bones in the skeleton and the bindTransforms must be // reconstructed. var bindPoses = skeleton.bindTransforms; if (!JointsMatch(skeleton.joints, joints)) { var boneToPose = new Dictionary <string, Matrix4x4>(); bindPoses = new Matrix4x4[joints.Length]; for (int i = 0; i < skelJoints.Length; i++) { boneToPose[skelJoints[i]] = skeleton.bindTransforms[i]; } for (int i = 0; i < joints.Length; i++) { bindPoses[i] = boneToPose[joints[i]]; } } // When geomXf is identity, we can take a shortcut and just use the exact skeleton bindPoses. if (!ImporterBase.ApproximatelyEqual(geomXf, Matrix4x4.identity)) { // Note that the bind poses were transformed when the skeleton was imported, but the // geomBindTransform is per-mesh, so it must be transformed here so it is in the same space // as the bind pose. XformImporter.ImportXform(ref geomXf, options); // Make a copy only if we haven't already copied the bind poses earlier. if (bindPoses == skeleton.bindTransforms) { var newBindPoses = new Matrix4x4[skeleton.bindTransforms.Length]; Array.Copy(bindPoses, newBindPoses, bindPoses.Length); bindPoses = newBindPoses; } // Concatenate the geometry bind transform with the skeleton bind poses. for (int i = 0; i < bindPoses.Length; i++) { // The geometry transform should be applied to the points before any other transform, // hence the right hand multiply here. bindPoses[i] = bindPoses[i] * geomXf; } } mesh.bindposes = bindPoses; var bones = new Transform[joints.Length]; var sdfSkelPath = new SdfPath(skelPath); for (int i = 0; i < joints.Length; i++) { var jointPath = new SdfPath(joints[i]); if (joints[i] == "/") { jointPath = sdfSkelPath; } else if (jointPath.IsAbsolutePath()) { Debug.LogException(new Exception("Unexpected absolute joint path: " + jointPath)); jointPath = new SdfPath(joints[i].TrimStart('/')); jointPath = sdfSkelPath.AppendPath(jointPath); } else { jointPath = sdfSkelPath.AppendPath(jointPath); } var jointGo = primMap[jointPath]; if (!jointGo) { Debug.LogError("Error importing " + meshPath + " " + "Joint not found: " + joints[i]); continue; } bones[i] = jointGo.transform; } smr.bones = bones; bool isConstant = weightsInterpolation.GetString() == pxr.UsdGeomTokens.constant; // Unity 2019 supports many-bone rigs, older versions of Unity only support four bones. #if UNITY_2019 var bonesPerVertex = new NativeArray <byte>(mesh.vertexCount, Allocator.Persistent); var boneWeights1 = new NativeArray <BoneWeight1>(mesh.vertexCount * weightsElementSize, Allocator.Persistent); for (int i = 0; i < mesh.vertexCount; i++) { int unityIndex = i * weightsElementSize; int usdIndex = isConstant ? 0 : unityIndex; bonesPerVertex[i] = (byte)weightsElementSize; for (int wi = 0; wi < weightsElementSize; wi++) { var bw = boneWeights1[unityIndex + wi]; bw.boneIndex = indices[usdIndex + wi]; bw.weight = weights[usdIndex + wi]; boneWeights1[unityIndex + wi] = bw; } } // TODO: Investigate if bone weights should be normalized before this line. mesh.SetBoneWeights(bonesPerVertex, boneWeights1); bonesPerVertex.Dispose(); boneWeights1.Dispose(); #else var boneWeights = new BoneWeight[mesh.vertexCount]; for (int i = 0; i < boneWeights.Length; i++) { // When interpolation is constant, the base usdIndex should always be zero. // When non-constant, the offset is the index times the number of weights per vertex. int usdIndex = isConstant ? 0 : i * weightsElementSize; var boneWeight = boneWeights[i]; if (usdIndex >= indices.Length) { Debug.Log("UsdIndex out of bounds: " + usdIndex + " indices.Length: " + indices.Length + " boneWeights.Length: " + boneWeights.Length + " mesh: " + meshPath); } boneWeight.boneIndex0 = indices[usdIndex]; boneWeight.weight0 = weights[usdIndex]; if (indicesElementSize >= 2) { boneWeight.boneIndex1 = indices[usdIndex + 1]; boneWeight.weight1 = weights[usdIndex + 1]; } if (indicesElementSize >= 3) { boneWeight.boneIndex2 = indices[usdIndex + 2]; boneWeight.weight2 = weights[usdIndex + 2]; } if (indicesElementSize >= 4) { boneWeight.boneIndex3 = indices[usdIndex + 3]; boneWeight.weight3 = weights[usdIndex + 3]; } // If weights are less than 1, Unity will not automatically renormalize. // If weights are greater than 1, Unity will renormalize. // Only normalize when less than one to make it easier to diff bone weights which were // round-tripped and were being normalized by Unity. float sum = boneWeight.weight0 + boneWeight.weight1 + boneWeight.weight2 + boneWeight.weight3; if (sum < 1) { boneWeight.weight0 /= sum; boneWeight.weight1 /= sum; boneWeight.weight2 /= sum; boneWeight.weight3 /= sum; } boneWeights[i] = boneWeight; } mesh.boneWeights = boneWeights; #endif }
public static void CurvesTest() { UsdStage stage = UsdStage.CreateInMemory(); var path = new SdfPath("/Parent/Curves"); var curvesGprim = UsdGeomBasisCurves.Define(new UsdStageWeakPtr(stage), path); var vertCounts = IntrinsicTypeConverter.ToVtArray(new int[] { 4 }); var basisAttr = curvesGprim.CreateBasisAttr(UsdGeomTokens.bezier); curvesGprim.CreateCurveVertexCountsAttr(vertCounts); var basisCurves = new BasisCurvesSample(); basisCurves.basis = BasisCurvesSample.Basis.Bspline; basisCurves.type = BasisCurvesSample.CurveType.Cubic; basisCurves.wrap = BasisCurvesSample.WrapMode.Nonperiodic; basisCurves.colors = new UnityEngine.Color[4]; basisCurves.colors[0] = new UnityEngine.Color(1, 2, 3, 4); basisCurves.colors[3] = new UnityEngine.Color(6, 7, 8, 9); basisCurves.curveVertexCounts = new int[1] { 4 }; basisCurves.doubleSided = true; basisCurves.normals = new UnityEngine.Vector3[4]; basisCurves.normals[0] = new UnityEngine.Vector3(1, 0, 0); basisCurves.normals[3] = new UnityEngine.Vector3(0, 0, 1); basisCurves.widths = new float[4]; basisCurves.widths[0] = .5f; basisCurves.widths[1] = 1f; basisCurves.widths[2] = .2f; basisCurves.widths[3] = 2f; basisCurves.orientation = Orientation.RightHanded; basisCurves.points = new UnityEngine.Vector3[4]; basisCurves.points[0] = new UnityEngine.Vector3(1, 2, 3); basisCurves.points[3] = new UnityEngine.Vector3(7, 8, 9); basisCurves.velocities = new UnityEngine.Vector3[4]; basisCurves.velocities[0] = new UnityEngine.Vector3(11, 22, 33); basisCurves.velocities[3] = new UnityEngine.Vector3(77, 88, 99); basisCurves.wrap = BasisCurvesSample.WrapMode.Periodic; basisCurves.transform = UnityEngine.Matrix4x4.identity; var basisCurves2 = new BasisCurvesSample(); WriteAndRead(ref basisCurves, ref basisCurves2, true); AssertEqual(basisCurves.basis, basisCurves2.basis); AssertEqual(basisCurves.colors, basisCurves2.colors); AssertEqual(basisCurves.curveVertexCounts, basisCurves2.curveVertexCounts); AssertEqual(basisCurves.doubleSided, basisCurves2.doubleSided); AssertEqual(basisCurves.normals, basisCurves2.normals); AssertEqual(basisCurves.orientation, basisCurves2.orientation); AssertEqual(basisCurves.points, basisCurves2.points); AssertEqual(basisCurves.type, basisCurves2.type); AssertEqual(basisCurves.velocities, basisCurves2.velocities); AssertEqual(basisCurves.widths, basisCurves2.widths); AssertEqual(basisCurves.wrap, basisCurves2.wrap); AssertEqual(basisCurves.transform, basisCurves2.transform); AssertEqual(basisCurves.xformOpOrder, basisCurves2.xformOpOrder); }