// ----------------------------------------------------------------------------------------- // // string[], List<string> <--> TokenArray // ----------------------------------------------------------------------------------------- // static public VtTokenArray ToVtArray(string[] input) { var output = new VtTokenArray((uint)input.Length); // PERFORMANCE: this is super inefficient. for (int i = 0; i < input.Length; i++) { output[i] = new pxr.TfToken(input[i]); } return(output); }
/// <summary> /// Retuns a dictionary of paths and the times at which each path has a keyframe for the given /// attribute. Only paths rooted under the given rootPath are considered. /// </summary> public Dictionary <string, double[]> ComputeKeyFrames(string rootPath, string attribute) { var keys = new Dictionary <string, double[]>(); var prim = GetUsdPrim(GetSdfPath(rootPath)); if (!prim) { throw new ArgumentException("rootPath does not exist"); } var sdfRootPath = GetSdfPath(rootPath); var tfAttrName = new pxr.TfToken(attribute); foreach (var child in Stage.GetAllPrims()) { if (child.GetPath() == SdfPath.AbsoluteRootPath()) { Console.WriteLine("Was abs: {0}", child.GetPath()); continue; } if (child.GetTypeName() != "Mesh") { continue; } Console.WriteLine(child.GetPath()); if (!child.GetPath().HasPrefix(sdfRootPath)) { continue; } var stdDoubleVector = child.GetAttribute(tfAttrName).GetTimeSamples(); if (stdDoubleVector.Count == 0) { continue; } double[] times = new double[stdDoubleVector.Count]; stdDoubleVector.CopyTo(times); keys.Add(child.GetPath(), times); } return(keys); }
/// <summary> /// Internal helper for reading data from USD. /// </summary> /// <param name="attrName">The USD attribute name.</param> /// <param name="csType">The C# type.</param> /// <param name="csValue">The C# value to populate.</param> /// <param name="usdTime">The time at which to sample key frames.</param> /// <param name="prim">The USD prim from which to read data.</param> /// <param name="memberInfo">The field/property providing serialization metadata.</param> /// <param name="usdNamespace">The optional USD namespace at which values live.</param> /// <param name="accessMap">A map of members to include when reading.</param> /// <param name="mayVary">When not null, is populated with variability.</param> /// <returns>True on success.</returns> /// <remarks> /// Note that "success" in the return value does not indicate data was read, rather it /// indicates that no unexpected states were encountered. E.g. calling ReadAttr on a field /// with no value stored in USD will not return false, since that is not considered a failure /// state. /// </remarks> bool ReadAttr(string attrName, Type csType, ref object csValue, pxr.UsdTimeCode usdTime, pxr.UsdPrim prim, MemberInfo memberInfo, HashSet <MemberInfo> accessMap, ref bool?mayVary, string usdNamespace) { bool isNewPrimvar = csValue != null && csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Primvar <>); // This is true for Primvar type only bool isPrimvar = Reflect.IsPrimvar(memberInfo) || isNewPrimvar; // This is true for VertexData + Primvar type... string ns = IntrinsicTypeConverter.JoinNamespace(usdNamespace, Reflect.GetNamespace(memberInfo)); // ----------------------------------------- // // Dictionaries, read, early exit, recurse. // ----------------------------------------- // // If holding a dictionary, immediately recurse and write keys as attributes. if (csValue != null && csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Dictionary <,>) && csType.GetGenericArguments()[0] == typeof(string)) { Type genericTypeDef = csType.GetGenericArguments()[1].IsGenericType ? csType.GetGenericArguments()[1].GetGenericTypeDefinition() : null; isNewPrimvar = genericTypeDef == typeof(Primvar <>); bool isRelationship = csType.GetGenericArguments()[1] == typeof(Relationship); bool isConnection = genericTypeDef == typeof(Connectable <>); // String dictionaries are unrolled directly into the object. // So the namespace is either the incoming namespace or empty, meaning each string value in // the dictionary becomes an attribute on the prim. // Ensure there is always a namespace immediately around this member. if (string.IsNullOrEmpty(Reflect.GetNamespace(memberInfo))) { ns = IntrinsicTypeConverter.JoinNamespace(ns, attrName); usdNamespace = IntrinsicTypeConverter.JoinNamespace(usdNamespace, attrName); } // Unfortunately, the primvars prefixing logic must be replicated here so we can discover // the dictionary member from USD. if (isPrimvar || isNewPrimvar) { ns = IntrinsicTypeConverter.JoinNamespace("primvars", ns); } var dict = csValue as System.Collections.IDictionary; ConstructorInfo ctor = (isNewPrimvar || isConnection || isRelationship) ? csType.GetGenericArguments()[1].GetConstructor(new Type[0]) : null; dict.Clear(); foreach (var prop in prim.GetAuthoredPropertiesInNamespace(ns)) { object value = null; if (ctor != null) { value = ctor.Invoke(new object[0]); } // The recursive call will also discover that this is a primvar and any associated namespace. if (ReadAttr(prop.GetBaseName(), csType.GetGenericArguments()[1], ref value, usdTime, prim, memberInfo, accessMap, ref mayVary, usdNamespace)) { if (value != null) { dict.Add(prop.GetBaseName().ToString(), value); } } } return(true); } pxr.TfToken sdfAttrName = sm_tokenCache[ns, attrName]; // ----------------------------------------- // // Relationship, read + early exit. // ----------------------------------------- // if (csType == typeof(Relationship)) { // mayVary is explicitly not set here because it has accumulation semantics: // mayVary = mayVary || false; // Which is equivalent to the no-op: // mayVary = mayVary; pxr.UsdRelationship rel = null; lock (m_stageLock) { rel = prim.GetRelationship(sm_tokenCache[sdfAttrName]); } var relationship = new Relationship(); csValue = relationship; if (rel == null || !rel.IsValid()) { return(true); } pxr.SdfPathVector paths = rel.GetTargets(); string[] result = new string[paths.Count]; for (int i = 0; i < paths.Count; i++) { result[i] = paths[i].ToString(); } relationship.targetPaths = result; return(true); } // ----------------------------------------- // // Connection Setup. // ----------------------------------------- // Connectable conn = null; if (csValue != null && csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Connectable <>)) { conn = csValue as Connectable; if (conn != null) { // Since this is a Connectable<T>, the held value T is what's being read from USD, // so replace csValue with the held T value itself. csValue must be restored before // returning. csValue = conn.GetValue(); // Same treatment for the type. csType = conn.GetValueType(); } } // ----------------------------------------- // // Primvar Setup. // ----------------------------------------- // ValueAccessor pvAccessor = null; PrimvarBase pvBase = null; if (isNewPrimvar) { pvAccessor = csValue as ValueAccessor; pvBase = (PrimvarBase)csValue; // Since this is a Primvar<T>, the held value T is what's being read from USD, // so replace csVAlue with the held T value itself. csValue must be restored before // returning. csValue = pvAccessor.GetValue(); // Same treatment for the type. csType = pvAccessor.GetValueType(); } // ----------------------------------------- // // Lookup Type Conversion Delegate. // ----------------------------------------- // UsdTypeBinding binding; if (!sm_bindings.GetBinding(csType, out binding) && !csType.IsEnum && csType != typeof(object)) { if (string.IsNullOrEmpty(ns)) { return(false); } var sample = csValue as SampleBase; if (csValue == null) { // This could attempt to automatically constuct the needed object, then nullable objects // could be used instead to drive deserialization. return(false); } else if (sample == null) { // In this case, csValue is not null, but also cannot be converted to SampleBase. throw new ArgumentException("Type does not inherit from SampleBase: " + attrName); } Deserialize((SampleBase)csValue, prim, usdTime, accessMap, ref mayVary, usdNamespace: ns); return(true); } // ----------------------------------------- // // Prep to Read. // ----------------------------------------- // // Restore C# value to the actual property value. if (conn != null) { csValue = conn; } else if (pvAccessor != null) { csValue = pvAccessor; } // Append "primvars:" namespace to primvars. if (isPrimvar) { var joinedName = IntrinsicTypeConverter.JoinNamespace(ns, attrName); sdfAttrName = sm_tokenCache["primvars", joinedName]; } // Adjust time for variability. pxr.SdfVariability variability = Reflect.GetVariability(memberInfo); pxr.UsdTimeCode time = variability == pxr.SdfVariability.SdfVariabilityUniform ? kDefaultUsdTime : usdTime; // Allocate a temp VtValue. pxr.VtValue vtValue = (pxr.VtValue)ArrayAllocator.MallocHandle(typeof(pxr.VtValue)); try { // ----------------------------------------- // // Read Connected Paths. // ----------------------------------------- // if (conn != null) { // Connection paths cannot be animated, so mayVary is not affected. var sources = new pxr.SdfPathVector(); if (prim.GetAttribute(sdfAttrName).GetConnections(sources)) { if (sources.Count > 0) { conn.SetConnectedPath(sources[0].ToString()); } } } // ----------------------------------------- // // Read Associated Primvar Data. // ----------------------------------------- // // If this is a Primvar<T>, read the associated primvar metadata and indices. if (pvBase != null) { UsdAttribute attr = null; if (Reflect.IsFusedDisplayColor(memberInfo)) { var gprim = new pxr.UsdGeomGprim(prim); if (gprim) { attr = gprim.GetDisplayColorAttr(); } } else { attr = prim.GetAttribute(sdfAttrName); } if (attr) { var pv = new pxr.UsdGeomPrimvar(attr); // ElementSize and Interpolation are not animatable, so they do not affect mayVary. pvBase.elementSize = pv.GetElementSize(); pvBase.SetInterpolationToken(pv.GetInterpolation()); // Primvars can be indexed and indices are a first class attribute and may vary over time. var indices = pv.GetIndicesAttr(); if (indices) { if (accessMap != null) { if (indices.GetVariability() == pxr.SdfVariability.SdfVariabilityVarying && indices.ValueMightBeTimeVarying()) { accessMap.Add(memberInfo); mayVary |= true; } } indices.Get(vtValue, time); if (!vtValue.IsEmpty()) { var vtIntArray = pxr.UsdCs.VtValueToVtIntArray(vtValue); pvBase.indices = IntrinsicTypeConverter.FromVtArray(vtIntArray); } } } } // ----------------------------------------- // // Read the value of csValue. // ----------------------------------------- // if (Reflect.IsMetadata(memberInfo)) { vtValue = prim.GetMetadata(sdfAttrName); // Metadata cannot vary over time. } else if (Reflect.IsCustomData(memberInfo)) { vtValue = prim.GetCustomDataByKey(sdfAttrName); // Custom data is metadata, which cannot vary over time. } else if (Reflect.IsFusedDisplayColor(memberInfo)) { vtValue = pxr.UsdCs.GetFusedDisplayColor(prim, time); if (accessMap != null) { // Display color is actually two attributes, primvars:displayColor and // primvars:displayOpacity. var gprim = new pxr.UsdGeomGprim(prim); if (gprim && gprim.GetDisplayColorAttr().ValueMightBeTimeVarying()) { accessMap.Add(memberInfo); mayVary |= true; } } } else if (Reflect.IsFusedTransform(memberInfo)) { vtValue = pxr.UsdCs.GetFusedTransform(prim, time); if (accessMap != null) { // Transforms are complicated :/ var xformable = new pxr.UsdGeomXformable(prim); if (xformable) { bool dummy; var orderAttr = xformable.GetXformOpOrderAttr(); if (orderAttr) { if (orderAttr.GetVariability() == pxr.SdfVariability.SdfVariabilityVarying && orderAttr.ValueMightBeTimeVarying()) { mayVary |= true; accessMap.Add(memberInfo); } else { foreach (var op in xformable.GetOrderedXformOps(out dummy)) { var opAttr = op.GetAttr(); if (!opAttr) { continue; } if (opAttr.GetVariability() == pxr.SdfVariability.SdfVariabilityVarying && opAttr.ValueMightBeTimeVarying()) { mayVary |= true; accessMap.Add(memberInfo); break; } } // foreach } } // orderAttr } // xformable } // mayVary } else { if (accessMap != null) { var attr = prim.GetAttribute(sdfAttrName); if (attr.GetVariability() == pxr.SdfVariability.SdfVariabilityVarying && attr.ValueMightBeTimeVarying()) { accessMap.Add(memberInfo); mayVary |= true; } } if (!prim.GetAttributeValue(sdfAttrName, vtValue, time)) { // Object has no value, still considered success. return(true); } } if (vtValue.IsEmpty()) { // Object has no value, still considered success. return(true); } // ------------------------------------------ // // Infer C# type from USD when Type == Object // ------------------------------------------ // if (csType == typeof(object)) { // Blind object serialization needs special handling, since we won't know the C# type a priori. // Instead, do a reverse lookup on the SdfTypeName and let USD dictate the C# type. pxr.UsdAttribute attr = prim.GetAttribute(sdfAttrName); if (attr != null && attr.IsValid()) { // TODO: Assuming the reverse lookup is successful for the binding, the caller may be // surprised by the result, since the USD <-> C# types are not 1-to-1. For example, // a List<Vector2> may have been serialized, but Vector2[] may be read. if (!sm_bindings.GetReverseBinding(attr.GetTypeName(), out binding)) { if (string.IsNullOrEmpty(ns)) { return(false); } // TODO: readback nested object declared as object -- maybe just disable this? //Deserialize(ref csValue, prim, usdTime, usdNamespace: ns); //return true; return(false); } } else { // TODO: Allow reading metadata declared as object in C# return(false); } } // ------------------------------------------ // // Convert USD's VtValue -> Strong C# Type. // ------------------------------------------ // csValue = binding.toCsObject(vtValue); // ------------------------------------------ // // Restore csValue. // ------------------------------------------ // if (conn != null && csValue != null) { conn.SetValue(csValue); csValue = conn; } if (pvAccessor != null) { pvAccessor.SetValue(csValue); csValue = pvAccessor; } } finally { // Would prefer RAII handle, but introduces garbage. ArrayAllocator.FreeHandle(vtValue); } return(true); }
/// <summary> /// Internal helper for serializing data to USD. /// </summary> /// <param name="attrName">The USD attribute name.</param> /// <param name="csType">The C# type.</param> /// <param name="csValue">The C# value.</param> /// <param name="usdTime">The time at which to sample key frames.</param> /// <param name="prim">The USD prim at which to write values.</param> /// <param name="imgble">The UsdGeomImagable attrbiute, used when writing PrimVars.</param> /// <param name="memberInfo">The field/property providing serialization metadata.</param> /// <param name="usdNamespace">The optional USD namespace at which values live.</param> /// <param name="srcObject">The source object name, used when remapping names.</param> /// <returns>True on success.</returns> /// <remarks> /// Note that "success" in the return value does not indicate data was written, rather it /// indicates that no unexpected states were encountered. E.g. calling WriteAttr on a field /// marked as [NotSerialized] does not cause this method to return false, since non-serialized /// fields are an expected state this function may encounter. /// </remarks> bool WriteAttr(string attrName, Type csType, object csValue, pxr.UsdTimeCode usdTime, pxr.UsdPrim prim, pxr.UsdGeomImageable imgble, MemberInfo memberInfo, string usdNamespace, string srcObject = null) { if (Reflect.IsNonSerialized(memberInfo)) { Console.WriteLine("Non serialized"); return(true); } // If serializing a Primvar<T>, extract the held value and save it in csValue, allowing the // all downstream logic to act as if it's operating on the held value itself. PrimvarBase pvBase = null; if (csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Primvar <>)) { if (csValue == null) { // Object not written, still considered success. return(true); } pvBase = (PrimvarBase)csValue; csValue = (csValue as ValueAccessor).GetValue(); if (csValue == null) { // Object not written, still considered success. return(true); } csType = csValue.GetType(); } bool isCustomData = Reflect.IsCustomData(memberInfo); bool isMetaData = Reflect.IsMetadata(memberInfo); bool isPrimvar = Reflect.IsPrimvar(memberInfo); bool isNewPrimvar = pvBase != null; int primvarElementSize = Reflect.GetPrimvarElementSize(memberInfo); string ns = IntrinsicTypeConverter.JoinNamespace(usdNamespace, Reflect.GetNamespace(memberInfo)); // If holding a dictionary, immediately recurse and write keys as attributes. if (csValue != null && csType.IsGenericType && csType.GetGenericTypeDefinition() == typeof(Dictionary <,>) && csType.GetGenericArguments()[0] == typeof(string)) { isNewPrimvar = csType.GetGenericArguments()[1].IsGenericType && csType.GetGenericArguments()[1].GetGenericTypeDefinition() == typeof(Primvar <>); // Ensure the immediate dictionary member is always namespaced. if (string.IsNullOrEmpty(Reflect.GetNamespace(memberInfo))) { usdNamespace = IntrinsicTypeConverter.JoinNamespace(usdNamespace, attrName); } var dict = csValue as System.Collections.IDictionary; foreach (System.Collections.DictionaryEntry kvp in dict) { object value = kvp.Value; WriteAttr((string)kvp.Key, value.GetType(), value, usdTime, prim, imgble, memberInfo, usdNamespace, srcObject: attrName); } return(true); } pxr.TfToken sdfAttrName = sm_tokenCache[attrName]; if (csType == typeof(Relationship) && csValue != null) { string[] targetStrings = ((Relationship)csValue).targetPaths; if (targetStrings != null) { // // Write Relationship // string[] arr = IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName).Split(':'); pxr.StdStringVector elts = new pxr.StdStringVector(arr.Length); foreach (var s in arr) { elts.Add(s); } pxr.UsdRelationship rel = null; lock (m_stageLock) { rel = prim.CreateRelationship(elts, custom: false); } if (!rel.IsValid()) { throw new ApplicationException("Failed to create relationship <" + prim.GetPath().AppendProperty( new pxr.TfToken( IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName))).ToString() + ">"); } var targets = new pxr.SdfPathVector(); foreach (var path in ((Relationship)csValue).targetPaths) { targets.Add(new pxr.SdfPath(path)); } lock (m_stageLock) { rel.SetTargets(targets); } } return(true); } // // Write Attribute // // FUTURE: When writing sparse overrides, if the csValue is null exit here and avoid // defining the target attribute. However, sparse authoring is not yet supported. UsdTypeBinding binding; // Extract the value and type from the connectable. var conn = csValue as Connectable; if (conn != null) { csType = conn.GetValueType(); csValue = conn.GetValue(); } // Get the binding for the value about to be serialized. if (!sm_bindings.GetBinding(csType, out binding) && !csType.IsEnum) { if (csValue == null) { return(true); } if (string.IsNullOrEmpty(ns)) { return(false); } var sample = csValue as SampleBase; if (sample == null && csValue != null) { throw new ArgumentException("Type does not inherit from SampleBase: " + attrName); } Serialize(csValue, prim, usdTime, usdNamespace: ns); return(true); } // Determine metadata for the attribtue, note that in the case of connections and primvars // these will be the attributes on the outter object, e.g. declared on the Connection<T> or // Primvar<T>. pxr.SdfVariability variability = Reflect.GetVariability(memberInfo); pxr.SdfValueTypeName sdfTypeName = binding.sdfTypeName; pxr.UsdTimeCode time = variability == pxr.SdfVariability.SdfVariabilityUniform ? pxr.UsdTimeCode.Default() : usdTime; bool custom = false; pxr.UsdAttribute attr; if (isCustomData || isMetaData) { // no-op attr = null; } else if (!isPrimvar && !isNewPrimvar) { if (string.IsNullOrEmpty(ns)) { // // Create non-namespaced attribute. // lock (m_stageLock) { attr = prim.CreateAttribute(sdfAttrName, csType.IsEnum ? SdfValueTypeNames.Token : sdfTypeName, custom, variability); } } else { // // Create namespaced attribute. // string[] arr = IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName).Split(':'); pxr.StdStringVector elts = new pxr.StdStringVector(arr.Length); foreach (var s in arr) { elts.Add(s); } lock (m_stageLock) { attr = prim.CreateAttribute(elts, sdfTypeName, custom, variability); } } } else { // // Create Primvar attribute. // lock (m_stageLock) { var fullAttrName = IntrinsicTypeConverter.JoinNamespace(ns, sdfAttrName); var primvar = imgble.CreatePrimvar(new pxr.TfToken(fullAttrName), sdfTypeName, VertexDataAttribute.Interpolation); if (isNewPrimvar) { primvar.SetElementSize(pvBase.elementSize); if (pvBase.indices != null) { var vtIndices = IntrinsicTypeConverter.ToVtArray(pvBase.indices); primvar.SetIndices(vtIndices, time); } primvar.SetInterpolation(pvBase.GetInterpolationToken()); } else { primvar.SetElementSize(primvarElementSize); } attr = primvar.GetAttr(); } } if (attr != null && conn != null && conn.GetConnectedPath() != null) { // TODO: Pool temp vector, possibly add a single item overload for SetConnections. var paths = new pxr.SdfPathVector(); var connPath = conn.GetConnectedPath(); if (connPath != string.Empty) { paths.Add(new pxr.SdfPath(conn.GetConnectedPath())); } attr.SetConnections(paths); } // This may happen when a connection is present, but has a null default value. // Because the connection is applied just before this point, this is the earliest possible // exit point. if (csValue == null) { return(true); } pxr.VtValue vtValue = binding.toVtValue(csValue); lock (m_stageLock) { if (isMetaData) { prim.SetMetadata(sdfAttrName, vtValue); } else if (isCustomData) { prim.SetCustomDataByKey(sdfAttrName, vtValue); } else if (Reflect.IsFusedDisplayColor(memberInfo)) { pxr.UsdCs.SetFusedDisplayColor(prim, vtValue, time); } else { // use the sparse attribute value writer, to skip redundant time samples m_sparseValueWriter.SetAttribute(attr, vtValue, time); } } if (!isCustomData && srcObject != null) { lock (m_stageLock) { attr.SetCustomDataByKey(sm_tokenCache["sourceMember"], srcObject); } } return(true); }
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 GameObject Import( USD.NET.Scene scene, GameObject rootObj, Dictionary <SdfPath, GameObject> objectMap, UpdateMask mask, out List <string> warnings, List <string> pathsToUpdate = null) { // TODO: generalize this to avoid having to dig down into USD for sparse reads. TfToken brushToken = new pxr.TfToken("brush"); TfToken faceVertexIndicesToken = new pxr.TfToken("faceVertexIndices"); warnings = new List <string>(); // Would be nice to find a way to kick this off automatically. // Redundant calls are ignored. if (!InitUsd.Initialize()) { return(null); } // PLAN: Process any UsdStage either constructing or updating GameObjects as needed. // This should include analysis of the time samples to see what attributes are // actually varying so they are updated minimally. UsdPrimVector prims = null; if (pathsToUpdate == null) { prims = scene.Stage.GetAllPrims(); } else { prims = new UsdPrimVector(); foreach (var path in pathsToUpdate) { prims.Add(scene.Stage.GetPrimAtPath(new pxr.SdfPath(path))); } } for (int p = 0; p < prims.Count; p++) { // TODO: prims[p] generates garbage. UsdPrim usdPrim = prims[p]; UsdGeomMesh usdMesh = new UsdGeomMesh(usdPrim); if (!usdMesh) { continue; } ExportUsd.BrushSample sample = new ExportUsd.BrushSample(); if (mask == UpdateMask.All) { scene.Read(usdPrim.GetPath(), sample); } else { // TODO: Generalize this as a reusable mechanism for sparse reads. if (mask == UpdateMask.Topology) { sample.brush = new Guid((string)usdPrim.GetCustomDataByKey(brushToken)); var fv = usdPrim.GetAttribute(faceVertexIndicesToken).Get(scene.Time); sample.faceVertexIndices = USD.NET.IntrinsicTypeConverter.FromVtArray((VtIntArray)fv); } else { throw new NotImplementedException(); } } GameObject strokeObj; Mesh unityMesh; // // Construct the GameObject if needed. // if (!objectMap.TryGetValue(usdPrim.GetPath(), out strokeObj)) { // On first import, we need to pull in all the data, regardless of what was requested. mask = UpdateMask.All; BrushDescriptor brush = BrushCatalog.m_Instance.GetBrush(sample.brush); if (brush == null) { Debug.LogWarningFormat("Invalid brush GUID at path: <{0}> guid: {1}", usdPrim.GetPath(), sample.brush); continue; } strokeObj = UnityEngine.Object.Instantiate(brush.m_BrushPrefab); // Register the Prim/Object mapping. objectMap.Add(usdPrim.GetPath(), strokeObj); // Init the game object. strokeObj.transform.parent = rootObj.transform; strokeObj.GetComponent <MeshRenderer>().material = brush.Material; strokeObj.GetComponent <MeshFilter>().sharedMesh = new Mesh(); strokeObj.AddComponent <BoxCollider>(); unityMesh = strokeObj.GetComponent <MeshFilter>().sharedMesh; } else { unityMesh = strokeObj.GetComponent <MeshFilter>().sharedMesh; } // // Points // Note that points must come first, before all other mesh data. // if ((mask & UpdateMask.Points) == UpdateMask.Points) { unityMesh.vertices = sample.points; } // // Bounds // if ((mask & UpdateMask.Bounds) == UpdateMask.Bounds) { var bc = strokeObj.GetComponent <BoxCollider>(); bc.center = sample.extent.center; bc.size = sample.extent.size; unityMesh.bounds = bc.bounds; } // // Topology // if ((mask & UpdateMask.Topology) == UpdateMask.Topology) { unityMesh.triangles = sample.faceVertexIndices; } // // Normals // if ((mask & UpdateMask.Normals) == UpdateMask.Normals) { unityMesh.normals = sample.normals; } // // Color & Opacity // if ((mask & UpdateMask.Colors) == UpdateMask.Colors && sample.colors != null) { unityMesh.colors = sample.colors; } // // Tangents // if ((mask & UpdateMask.Tangents) == UpdateMask.Tangents && sample.tangents != null) { unityMesh.tangents = sample.tangents; } // // UVs // if ((mask & UpdateMask.UVs) == UpdateMask.UVs) { SetUv(unityMesh, 0, sample.uv); SetUv(unityMesh, 1, sample.uv2); SetUv(unityMesh, 2, sample.uv3); SetUv(unityMesh, 3, sample.uv4); } } // For each prim return(rootObj); }