static void MergeBonesSimple(Transform source, Transform rootBone, Transform[] bones, Matrix4x4[] bindPoses, ExportContext context) { context.meshToSkelRoot.Add(source, rootBone); context.meshToBones.Add(source, bones); Matrix4x4 existingMatrix; for (int i = 0; i < bones.Length; i++) { Transform bone = bones[i]; if (bone == null) { var srcPath = UnityTypeConverter.GetPath(source); Debug.LogWarning("Null bone at in bones list at position (" + i + ") " + srcPath); continue; } var path = UnityTypeConverter.GetPath(bone); context.pathToBone[path] = bone; context.boneToRoot[bone] = rootBone; context.bindPoses[bone] = bindPoses[i]; if (context.bindPoses.TryGetValue(bone, out existingMatrix) && existingMatrix != bindPoses[i]) { Debug.LogWarning("Duplicate bone with different bind poses: " + path + "\n" + existingMatrix.ToString() + "\n" + bindPoses[i].ToString()); } } }
static void CreateExportPlan(GameObject go, SampleBase sample, ExportFunction exportFunc, ExportContext context, string pathSuffix = null, object data = null, bool insertFirst = true) { // This is an exportable object. Transform expRoot = context.exportRoot; string path = UnityTypeConverter.GetPath(go.transform, expRoot); if (!string.IsNullOrEmpty(pathSuffix)) { path += pathSuffix; } if (!context.plans.ContainsKey(go)) { context.plans.Add(go, new ExportPlan()); } var exp = new Exporter { exportFunc = exportFunc, sample = sample, path = path, data = data }; if (insertFirst) { context.plans[go].exporters.Insert(0, exp); } else { context.plans[go].exporters.Add(exp); } // Include the parent xform hierarchy. // Note that the parent hierarchy is memoised, so despite looking expensive, the time // complexity is linear. Transform xf = go.transform.parent; if (xf != context.exportRoot && !context.plans.ContainsKey(xf.gameObject)) { // Since all GameObjects have a Transform, export all un-exported parents as transform. CreateExportPlan(xf.gameObject, CreateSample<XformSample>(context), XformExporter.ExportXform, context); CreateExportPlan(xf.gameObject, CreateSample<XformSample>(context), NativeExporter.ExportObject, context, insertFirst: false); } }
public static void ExportSkeleton(ObjectContext objContext, ExportContext exportContext) { var scene = exportContext.scene; var sample = (SkeletonSample)objContext.sample; var boneNames = exportContext.skelSortedMap[objContext.gameObject.transform]; sample.joints = new string[boneNames.Count]; sample.bindTransforms = new Matrix4x4[boneNames.Count]; sample.restTransforms = new Matrix4x4[boneNames.Count]; string rootPath = UnityTypeConverter.GetPath(objContext.gameObject.transform); sample.transform = XformExporter.GetLocalTransformMatrix( objContext.gameObject.transform, scene.UpAxis == Scene.UpAxes.Z, new pxr.SdfPath(rootPath).IsRootPrimPath(), exportContext.basisTransform); int i = 0; foreach (string bonePath in boneNames) { if (string.IsNullOrEmpty(bonePath)) { sample.joints[i] = ""; i++; continue; } var bone = exportContext.pathToBone[bonePath]; if (bonePath == rootPath) { sample.joints[i] = "/"; } else { sample.joints[i] = bonePath.Replace(rootPath + "/", ""); } // TODO: When the bone bind transform contains the geomBindTransform from USD import, it // will be mixed into each bone. This transform should be saved in some way and removed // when exported as a skeleton. sample.bindTransforms[i] = exportContext.bindPoses[bone].inverse; sample.restTransforms[i] = XformExporter.GetLocalTransformMatrix( bone, false, false, exportContext.basisTransform); if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { sample.bindTransforms[i] = UnityTypeConverter.ChangeBasis(sample.bindTransforms[i]); // The restTransforms will get a change of basis from GetLocalTransformMatrix(). } i++; } scene.Write(objContext.path, sample); // Stop Skeleton from rendering bones in usdview by default. var im = new pxr.UsdGeomImageable(scene.GetPrimAtPath(objContext.path)); im.CreatePurposeAttr().Set(pxr.UsdGeomTokens.guide); }
/// <summary> /// Copyies the current sample values to the given camera. /// </summary> public void CopyToCamera(UnityEngine.Camera camera, bool setTransform) { // GfCamera is a gold mine of camera math. pxr.GfCamera c = new pxr.GfCamera(UnityTypeConverter.ToGfMatrix(transform), projection == ProjectionType.Perspective ? pxr.GfCamera.Projection.Perspective : pxr.GfCamera.Projection.Orthographic, this.horizontalAperture, this.verticalAperture, this.horizontalApertureOffset, this.verticalApertureOffset, this.focalLength); camera.orthographic = c.GetProjection() == pxr.GfCamera.Projection.Orthographic; camera.fieldOfView = c.GetFieldOfView(pxr.GfCamera.FOVDirection.FOVVertical); camera.aspect = c.GetAspectRatio(); camera.nearClipPlane = clippingRange.x; camera.farClipPlane = clippingRange.y; if (camera.orthographic) { // Note that USD default scale is cm and aperture is in mm. // Also Unity ortho size is the half aperture, so divide USD by 2. camera.orthographicSize = (verticalAperture / 10.0f) / 2.0f; } if (setTransform) { var tr = camera.transform; var xf = transform; UnityTypeConverter.SetTransform(xf, tr); } }
public static void BuildXform(Matrix4x4 xf, GameObject go, SceneImportOptions options) { UnityEngine.Profiling.Profiler.BeginSample("Change Handedness"); ImportXform(ref xf, options); UnityEngine.Profiling.Profiler.EndSample(); Vector3 localPos; Quaternion localRot; Vector3 localScale; UnityEngine.Profiling.Profiler.BeginSample("Decompose Matrix"); bool success = UnityTypeConverter.Decompose(xf, out localPos, out localRot, out localScale); UnityEngine.Profiling.Profiler.EndSample(); if (!success) { Debug.LogError("Non-decomposable transform matrix for " + go.name); return; } UnityEngine.Profiling.Profiler.BeginSample("Assign Values"); go.transform.localPosition = localPos; go.transform.localScale = localScale; go.transform.localRotation = localRot; UnityEngine.Profiling.Profiler.EndSample(); }
/// <summary> /// Imports a matrix transform, correctly handling the change of basis. /// </summary> public static void ImportXform(ref Matrix4x4 mat, SceneImportOptions options) { if (options.changeHandedness == BasisTransformation.FastWithNegativeScale) { return; } mat = UnityTypeConverter.ChangeBasis(mat); }
static void InitExportableObjects(GameObject go, ExportContext context) { var smr = go.GetComponent<SkinnedMeshRenderer>(); var mr = go.GetComponent<MeshRenderer>(); var mf = go.GetComponent<MeshFilter>(); var cam = go.GetComponent<Camera>(); Transform expRoot = context.exportRoot; var tmpPath = new pxr.SdfPath(UnityTypeConverter.GetPath(go.transform, expRoot)); while (!tmpPath.IsRootPrimPath()) { tmpPath = tmpPath.GetParentPath(); } // TODO: What if this path is in use? string materialBasePath = tmpPath.ToString() + "/Materials/"; // Ensure the "Materials" prim is defined with a valid prim type. context.scene.Write(materialBasePath.TrimEnd('/'), new ScopeSample()); if (smr != null) { foreach (var mat in smr.sharedMaterials) { if (!context.matMap.ContainsKey(mat)) { string usdPath = materialBasePath + pxr.UsdCs.TfMakeValidIdentifier(mat.name + "_" + mat.GetInstanceID().ToString()); context.matMap.Add(mat, usdPath); } } CreateExportPlan(go, CreateSample<MeshSample>(context), MeshExporter.ExportSkinnedMesh, context); CreateExportPlan(go, CreateSample<MeshSample>(context), NativeExporter.ExportObject, context, insertFirst: false); if (smr.rootBone == null) { Debug.LogWarning("No root bone at: " + UnityTypeConverter.GetPath(go.transform, expRoot)); } else if (smr.bones == null || smr.bones.Length == 0) { Debug.LogWarning("No bones at: " + UnityTypeConverter.GetPath(go.transform, expRoot)); } else { // Each mesh in a model may have a different root bone, which now must be merged into a // single skeleton for export to USD. try { MergeBonesSimple(smr.transform, smr.rootBone, smr.bones, smr.sharedMesh.bindposes, context); } catch (Exception ex) { Debug.LogException(new Exception("Failed to merge bones for " + UnityTypeConverter.GetPath(smr.transform), ex)); } } } else if (mf != null && mr != null) { foreach (var mat in mr.sharedMaterials) { if (mat == null) { continue; } if (!context.matMap.ContainsKey(mat)) { string usdPath = materialBasePath + pxr.UsdCs.TfMakeValidIdentifier(mat.name + "_" + mat.GetInstanceID().ToString()); context.matMap.Add(mat, usdPath); } } CreateExportPlan(go, CreateSample<MeshSample>(context), MeshExporter.ExportMesh, context); CreateExportPlan(go, CreateSample<MeshSample>(context), NativeExporter.ExportObject, context, insertFirst: false); } else if (cam) { CreateExportPlan(go, CreateSample<CameraSample>(context), CameraExporter.ExportCamera, context); CreateExportPlan(go, CreateSample<CameraSample>(context), NativeExporter.ExportObject, context, insertFirst: false); } }
/// <summary> /// Attempts to build a valid per-vertex UV set from the given object. If the type T is not /// the held type of the object "uv" argument, null is returned. If there is an error such /// that the type is correct, but the uv values are somehow incompatible, error messages /// will be generated and an empty array will be returned. /// </summary> /// <returns> /// An array of size > 0 on succes, an array of size 0 on failure, or null if the given object /// is not of the desired type T. /// </returns> private static T[] TryGetUVSet <T>(object uv, int[] uvIndices, int[] faceVertexCounts, int[] faceVertexIndices, int vertexCount, GameObject go) { if (uv.GetType() != typeof(T[])) { return(null); } var uvVec = (T[])uv; if (uvVec.Length == 0) { return(uvVec); } // Unroll UV indices if specified. if (uvIndices != null && uvIndices.Length > 0) { var newUvs = new T[uvIndices.Length]; for (int i = 0; i < uvIndices.Length; i++) { newUvs[i] = uvVec[uvIndices[i]]; } uvVec = newUvs; } // If there are more UVs than verts, the UVs must be face varying, e.g. each vertex for // each face has a unique UV value. These values must be collapsed such that verts shared // between faces also share a single UV value. if (uvVec.Length > vertexCount) { uvVec = UnrollFaceVarying(vertexCount, uvVec, faceVertexCounts, faceVertexIndices); if (uvVec == null) { return(new T[0]); } Debug.Assert(uvVec.Length == vertexCount); } // If there are fewer values, these must be "varying" / one value per face. // This is not yet supported. if (uvVec.Length < vertexCount) { Debug.LogWarning("Mesh UVs are constant or uniform, ignored " + UnityTypeConverter.GetPath(go.transform)); return(new T[0]); } return(uvVec); }
public static void Export(GameObject root, ExportContext context, bool zeroRootTransform) { // Remove parent transform effects while exporting. // This must be restored before returning from this function. var parent = root.transform.parent; if (zeroRootTransform) { root.transform.SetParent(null, worldPositionStays: false); } // Also zero out and restore local rotations on the root. var localPos = root.transform.localPosition; var localRot = root.transform.localRotation; var localScale = root.transform.localScale; if (zeroRootTransform) { root.transform.localPosition = Vector3.zero; root.transform.localRotation = Quaternion.identity; root.transform.localScale = Vector3.one; } // Scale overall scene for export (e.g. USDZ export needs scale 100) root.transform.localScale *= context.scale; UnityEngine.Profiling.Profiler.BeginSample("USD: Export"); try { ExportImpl(root, context); var path = new pxr.SdfPath(UnityTypeConverter.GetPath(root.transform)); var prim = context.scene.Stage.GetPrimAtPath(path); if (prim) { context.scene.Stage.SetDefaultPrim(prim); } } finally { if (zeroRootTransform) { root.transform.localPosition = localPos; root.transform.localRotation = localRot; root.transform.localScale = localScale; root.transform.SetParent(parent, worldPositionStays: false); } else { root.transform.localScale = localScale; } UnityEngine.Profiling.Profiler.EndSample(); } }
public static void ExportSkelRoot(ObjectContext objContext, ExportContext exportContext) { var sample = (SkelRootSample)objContext.sample; // Compute bounds for the root, required by USD. bool first = true; // Ensure the bounds are computed in root-local space. // This is required because USD expects the extent to be a local bound for the SkelRoot. var oldParent = objContext.gameObject.transform.parent; objContext.gameObject.transform.SetParent(null, worldPositionStays: false); try { foreach (var r in objContext.gameObject.GetComponentsInChildren <Renderer>()) { if (first) { // Ensure the bounds object starts growing from the first valid child bounds. first = false; sample.extent = r.bounds; } else { sample.extent.Encapsulate(r.bounds); } } } finally { // Restore the root parent. objContext.gameObject.transform.SetParent(oldParent, worldPositionStays: false); } // Convert handedness if needed. if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { sample.extent.center = UnityTypeConverter.ChangeBasis(sample.extent.center); } // Convert the transform var path = new pxr.SdfPath(objContext.path); // If exporting for Z-Up, rotate the world. bool correctZUp = exportContext.scene.UpAxis == Scene.UpAxes.Z; sample.transform = XformExporter.GetLocalTransformMatrix( objContext.gameObject.transform, correctZUp, path.IsRootPrimPath(), exportContext.basisTransform); exportContext.scene.Write(objContext.path, sample); }
/// <summary> /// Loads or unloads the given payload object. Throws an exception if game object deos not have /// a UsdPrimSource behaviour. /// </summary> public void SetPayloadState(GameObject go, bool isLoaded) { var primSrc = go.GetComponent <UsdPrimSource>(); if (!primSrc) { throw new Exception("UsdPrimSource not found: " + UnityTypeConverter.GetPath(go.transform)); } var usdPrimPath = primSrc.m_usdPrimPath; InitUsd.Initialize(); var scene = GetScene(); if (scene == null) { throw new Exception("Failed to open: " + usdFullPath); } var prim = scene.GetPrimAtPath(usdPrimPath); if (prim == null || !prim) { throw new Exception("Prim not found: " + usdPrimPath); } foreach (var child in go.transform.GetComponentsInChildren <UsdPrimSource>().ToList()) { if (!child || child.gameObject == go) { continue; } GameObject.DestroyImmediate(child.gameObject); } if (!isLoaded) { prim.Unload(); return; } else { prim.Load(); } SceneImportOptions importOptions = new SceneImportOptions(); this.StateToOptions(ref importOptions); importOptions.usdRootPath = prim.GetPath(); SceneImporter.ImportUsd(go, scene, new PrimMap(), true, importOptions); }
/// <summary> /// Reads geometry subsets if authored. If not authored, returns an empty dictionary. /// </summary> public static GeometrySubsets ReadGeomSubsets(Scene scene, string path) { var result = new GeometrySubsets(); var prim = scene.GetPrimAtPath(path); if (prim == null || prim.IsValid() == false) { return(result); } var im = new pxr.UsdGeomImageable(prim); if (im._IsValid() == false) { return(result); } pxr.UsdGeomSubsetVector subsets = pxr.UsdGeomSubset.GetGeomSubsets(im, pxr.UsdGeomTokens.face, new pxr.TfToken("materialBind")); // Cache these values to minimize garbage collector churn. var value = new pxr.VtValue(); int[] intValue = new int[0]; var defaultTime = pxr.UsdTimeCode.Default(); foreach (var subset in subsets) { if (!subset._IsValid()) { continue; } var indices = subset.GetIndicesAttr(); if (!indices.IsValid()) { continue; } if (!indices.Get(value, defaultTime)) { continue; } UnityTypeConverter.FromVtArray(value, ref intValue); result.Subsets.Add(subset.GetPath(), intValue); } return(result); }
public static void BuildDebugBindTransforms(SkeletonSample skelSample, GameObject goSkeleton, SceneImportOptions options) { var debugPrefix = "usdSkel_bindPose_debug_cube"; if (options.meshOptions.debugShowSkeletonBindPose) { int i = 0; foreach (var bindXf in skelSample.bindTransforms) { // Undo the bindXf inversion for visualization. var mat = bindXf.inverse; var cubeName = debugPrefix + i++; var cube = goSkeleton.transform.Find(cubeName); if (!cube) { cube = GameObject.CreatePrimitive(PrimitiveType.Cube).transform; cube.SetParent(goSkeleton.transform, worldPositionStays: false); cube.name = cubeName; } Vector3 t, s; Quaternion r; UnityTypeConverter.Decompose(mat, out t, out r, out s); cube.localPosition = t; cube.localScale = s; cube.localRotation = r; } } else { var zero = goSkeleton.transform.Find(debugPrefix + 0); if (zero) { var toDelete = new List <GameObject>(); foreach (Transform child in goSkeleton.transform) { if (child.name.StartsWith(debugPrefix)) { toDelete.Add(child.gameObject); } } foreach (var child in toDelete) { GameObject.DestroyImmediate(child); } } } }
/// <summary> /// Copyies the current sample values to the given camera. /// </summary> public void CopyToCamera(UnityEngine.Camera camera) { // GfCamera is a gold mine of camera math. pxr.GfCamera c = new pxr.GfCamera(UnityTypeConverter.ToGfMatrix(transform)); camera.orthographic = c.GetProjection() == pxr.GfCamera.Projection.Orthographic; camera.fieldOfView = c.GetFieldOfView(pxr.GfCamera.FOVDirection.FOVVertical); camera.aspect = c.GetAspectRatio(); camera.nearClipPlane = c.GetClippingRange().GetMin(); camera.farClipPlane = c.GetClippingRange().GetMax(); var tr = camera.transform; var xf = transform; UnityTypeConverter.SetTransform(xf, tr); }
public static void ExportSkelRoot(ObjectContext objContext, ExportContext exportContext) { var sample = (SkelRootSample)objContext.sample; var bindings = ((string[])objContext.additionalData); if (bindings != null) { sample.skeleton = bindings[0]; if (bindings.Length > 1) { sample.animationSource = bindings[1]; } } // Compute bounds for the root, required by USD. bool first = true; foreach (var r in objContext.gameObject.GetComponentsInChildren <Renderer>()) { if (first) { // Ensure the bounds object starts growing from the first valid child bounds. first = false; sample.extent = r.bounds; } else { sample.extent.Encapsulate(r.bounds); } } // Convert the bounds from worldspace to local space. // This is required because USD expects the extent to be a local bound for the SkelRoot. var xf = objContext.gameObject.transform; sample.extent.min = xf.worldToLocalMatrix.MultiplyPoint(sample.extent.min); sample.extent.max = xf.worldToLocalMatrix.MultiplyPoint(sample.extent.max); if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { sample.extent.min = UnityTypeConverter.ChangeBasis(sample.extent.min); sample.extent.max = UnityTypeConverter.ChangeBasis(sample.extent.max); } exportContext.scene.Write(objContext.path, sample); }
public UnityEngine.Matrix4x4[] ComputeInstanceMatrices(USD.NET.Scene scene, string primPath) { var prim = scene.GetPrimAtPath(primPath); var pi = new pxr.UsdGeomPointInstancer(prim); var xforms = new pxr.VtMatrix4dArray(); pi.ComputeInstanceTransformsAtTime(xforms, scene.Time == null ? pxr.UsdTimeCode.Default() : scene.Time, 0); // Slow, but works. var matrices = new UnityEngine.Matrix4x4[xforms.size()]; for (int i = 0; i < xforms.size(); i++) { matrices[i] = UnityTypeConverter.FromMatrix(xforms[i]); } return(matrices); }
public static void BuildSkeletonBone(string skelPath, GameObject go, Matrix4x4 restXform, VtTokenArray joints, SceneImportOptions importOptions) { // Perform change of basis, if needed. XformImporter.ImportXform(ref restXform, importOptions); // Decompose into TSR. Vector3 pos = Vector3.zero; Quaternion rot = Quaternion.identity; Vector3 scale = Vector3.one; if (!UnityTypeConverter.Decompose(restXform, out pos, out rot, out scale)) { throw new Exception("Failed to decompose bind transforms for <" + skelPath + ">"); } go.transform.localScale = scale; go.transform.localRotation = rot; go.transform.localPosition = pos; var cubeDebugName = "usdSkel_restPose_debug_cube"; if (importOptions.meshOptions.debugShowSkeletonRestPose) { var cube = go.transform.Find(cubeDebugName); if (!cube) { cube = GameObject.CreatePrimitive(PrimitiveType.Cube).transform; cube.name = cubeDebugName; cube.SetParent(go.transform, worldPositionStays: false); cube.localScale = Vector3.one * 2; } } else { var existing = go.transform.Find(cubeDebugName); if (existing) { GameObject.DestroyImmediate(existing.gameObject); } } }
public static void CameraTest2() { CameraSample sample = new CameraSample(); sample.transform = UnityEngine.Matrix4x4.identity; sample.clippingRange = new UnityEngine.Vector2(.01f, 10); // GfCamera is a gold mine of camera math. pxr.GfCamera c = new pxr.GfCamera(UnityTypeConverter.ToGfMatrix(UnityEngine.Matrix4x4.identity)); sample.focalLength = c.GetFocalLength(); sample.horizontalAperture = c.GetHorizontalAperture(); sample.verticalAperture = c.GetVerticalAperture(); var scene = USD.NET.Scene.Create(); scene.Write("/Foo/Bar", sample); }
public static void ExportSkinnedMesh(ObjectContext objContext, ExportContext exportContext) { var smr = objContext.gameObject.GetComponent <SkinnedMeshRenderer>(); UnityEngine.Profiling.Profiler.BeginSample("USD: Skinned Mesh"); ExportMesh(objContext, exportContext, smr.sharedMesh, smr.sharedMaterial, smr.sharedMaterials, exportMeshPose: exportContext.scene.Time == null); UnityEngine.Profiling.Profiler.EndSample(); if (exportContext.scene.Time != null) { return; } // Note that the baked mesh no longer has the bone weights, so here we switch back to the // shared SkinnedMeshRenderer mesh. Transform rootBone = null; if (smr.rootBone != null && !exportContext.boneToRoot.TryGetValue(smr.rootBone, out rootBone)) { Debug.LogWarning("Root bone not found in export context for " + UnityTypeConverter.GetPath(smr.rootBone)); return; } UnityEngine.Profiling.Profiler.BeginSample("USD: Skinning Weights"); // Skeleton path is stored in additionalData via the SceneExporter SyncExportContext(). It // would be nice to formalize this, rather than passing it as blind data. var skeletonPath = (string)objContext.additionalData; ExportSkelWeights(exportContext.scene, objContext.path, smr.sharedMesh, rootBone, smr.bones, skeletonPath); UnityEngine.Profiling.Profiler.EndSample(); }
public static Matrix4x4 GetLocalTransformMatrix( Transform tr, bool correctZUp, bool isRootPrim, BasisTransformation conversionType) { var localRot = tr.localRotation; bool fastConvert = conversionType == BasisTransformation.FastWithNegativeScale; if (correctZUp && isRootPrim) { float invert = fastConvert ? 1 : -1; localRot = localRot * Quaternion.AngleAxis(invert * 90, Vector3.right); } var mat = Matrix4x4.TRS(tr.localPosition, localRot, tr.localScale); // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change of // basis is required. There are shortcuts, but this is fully general. // // Here we can either put a partial conversion at the root (fast & dangerous) or convert the // entire hierarchy, along with the points, normals and triangle winding. The benefit of the // full conversion is that there are no negative scales left in the hierarchy. // // Note that this is the correct partial conversion for the root transforms, however the // camera and light matrices must contain the other half of the conversion // (e.g. mat * basisChangeInverse). if (fastConvert && isRootPrim) { // Partial change of basis. var basisChange = Matrix4x4.identity; // Invert the forward vector. basisChange[2, 2] = -1; mat = basisChange * mat; } else if (!fastConvert) { // Full change of basis. mat = UnityTypeConverter.ChangeBasis(mat); } return(mat); }
bool InitExportableParents(GameObject go) { if (m_primMap.ContainsKey(go)) { // Stop processing parents, this keeps the performance of the traversal linear. return(false); } // Any object we add will only be exported as an Xform. string path = UnityTypeConverter.GetPath(go.transform); SampleBase sample = new XformSample(); m_primMap.Add(go, new ExportPlan { path = path, sample = sample, exportFunc = ExportXform }); Debug.Log(path + " " + sample.GetType().Name); // Continue processing parents. return(true); }
public static void ExportSkinnedMesh(ObjectContext objContext, ExportContext exportContext) { var smr = objContext.gameObject.GetComponent <SkinnedMeshRenderer>(); UnityEngine.Profiling.Profiler.BeginSample("USD: Skinned Mesh"); ExportMesh(objContext, exportContext, smr.sharedMesh, smr.sharedMaterial, smr.sharedMaterials, exportMeshPose: exportContext.scene.Time == null); UnityEngine.Profiling.Profiler.EndSample(); if (exportContext.scene.Time != null) { return; } // Note that the baked mesh no longer has the bone weights, so here we switch back to the // shared SkinnedMeshRenderer mesh. Transform rootBone = null; if (smr.rootBone != null && !exportContext.boneToRoot.TryGetValue(smr.rootBone, out rootBone)) { Debug.LogWarning("Root bone not found in export context for " + UnityTypeConverter.GetPath(smr.rootBone)); return; } UnityEngine.Profiling.Profiler.BeginSample("USD: Skinning Weights"); ExportSkelWeights(exportContext.scene, objContext.path, smr.sharedMesh, rootBone, smr.bones); UnityEngine.Profiling.Profiler.EndSample(); }
static void ExportMesh(ObjectContext objContext, ExportContext exportContext, Mesh mesh, Material sharedMaterial, Material[] sharedMaterials, bool exportMeshPose = true) { string path = objContext.path; if (mesh == null) { Debug.LogWarning("Null mesh for: " + path, objContext.gameObject); return; } #if UNITY_EDITOR if (!CanReadMesh(mesh)) { #else if (!mesh.isReadable) { #endif Debug.LogError( "Mesh is not readable: " + objContext.path + ". To fix this, enable read/write in the inspector for the source asset that you are attempting to export.", objContext.gameObject); return; } var scene = exportContext.scene; bool unvarying = scene.Time == null; bool slowAndSafeConversion = exportContext.basisTransform == BasisTransformation.SlowAndSafe; var sample = (MeshSample)objContext.sample; var go = objContext.gameObject; if (mesh.bounds.center == Vector3.zero && mesh.bounds.extents == Vector3.zero) { mesh.RecalculateBounds(); } sample.extent = mesh.bounds; if (slowAndSafeConversion) { // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change of // basis is required. There are shortcuts, but this is fully general. sample.ConvertTransform(); sample.extent.center = UnityTypeConverter.ChangeBasis(sample.extent.center); } // Only export the mesh topology on the first frame. if (unvarying) { // TODO: Technically a mesh could be the root transform, which is not handled correctly here. // It should have the same logic for root prims as in ExportXform. sample.transform = XformExporter.GetLocalTransformMatrix( go.transform, scene.UpAxis == Scene.UpAxes.Z, new pxr.SdfPath(path).IsRootPrimPath(), exportContext.basisTransform); sample.normals = mesh.normals; sample.points = mesh.vertices; sample.tangents = mesh.tangents; sample.colors = mesh.colors; if (sample.colors != null && sample.colors.Length == 0) { sample.colors = null; } if ((sample.colors == null || sample.colors.Length == 0) && (sharedMaterial != null && sharedMaterial.HasProperty("_Color"))) { sample.colors = new Color[1]; sample.colors[0] = sharedMaterial.color.linear; } // Gah. There is no way to inspect a meshes UVs. sample.st = mesh.uv; // Set face vertex counts and indices. var tris = mesh.triangles; if (slowAndSafeConversion) { // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change // of basis is required. There are shortcuts, but this is fully general. for (int i = 0; i < sample.points.Length; i++) { sample.points[i] = UnityTypeConverter.ChangeBasis(sample.points[i]); if (sample.normals != null && sample.normals.Length == sample.points.Length) { sample.normals[i] = UnityTypeConverter.ChangeBasis(sample.normals[i]); } if (sample.tangents != null && sample.tangents.Length == sample.points.Length) { var w = sample.tangents[i].w; var t = UnityTypeConverter.ChangeBasis(sample.tangents[i]); sample.tangents[i] = new Vector4(t.x, t.y, t.z, w); } } for (int i = 0; i < tris.Length; i += 3) { var t = tris[i]; tris[i] = tris[i + 1]; tris[i + 1] = t; } } sample.SetTriangles(tris); UnityEngine.Profiling.Profiler.BeginSample("USD: Mesh Write"); scene.Write(path, sample); UnityEngine.Profiling.Profiler.EndSample(); // TODO: this is a bit of a half-measure, we need real support for primvar interpolation. // Set interpolation based on color count. if (sample.colors != null && sample.colors.Length == 1) { pxr.UsdPrim usdPrim = scene.GetPrimAtPath(path); var colorPrimvar = new pxr.UsdGeomPrimvar(usdPrim.GetAttribute(pxr.UsdGeomTokens.primvarsDisplayColor)); colorPrimvar.SetInterpolation(pxr.UsdGeomTokens.constant); var opacityPrimvar = new pxr.UsdGeomPrimvar(usdPrim.GetAttribute(pxr.UsdGeomTokens.primvarsDisplayOpacity)); opacityPrimvar.SetInterpolation(pxr.UsdGeomTokens.constant); } string usdMaterialPath; if (exportContext.exportMaterials && sharedMaterial != null) { if (!exportContext.matMap.TryGetValue(sharedMaterial, out usdMaterialPath)) { Debug.LogError("Invalid material bound for: " + path); } else { MaterialSample.Bind(scene, path, usdMaterialPath); } } // In USD subMeshes are represented as UsdGeomSubsets. // When there are multiple subMeshes, convert them into UsdGeomSubsets. if (mesh.subMeshCount > 1) { // Build a table of face indices, used to convert the subMesh triangles to face indices. var faceTable = new Dictionary <Vector3, int>(); for (int i = 0; i < tris.Length; i += 3) { if (!slowAndSafeConversion) { faceTable.Add(new Vector3(tris[i], tris[i + 1], tris[i + 2]), i / 3); } else { // Under slow and safe export, index 0 and 1 are swapped. // This swap will not be present in the subMesh indices, so must be undone here. faceTable.Add(new Vector3(tris[i + 1], tris[i], tris[i + 2]), i / 3); } } var usdPrim = scene.GetPrimAtPath(path); var usdGeomMesh = new pxr.UsdGeomMesh(usdPrim); // Process each subMesh and create a UsdGeomSubset of faces this subMesh targets. for (int si = 0; si < mesh.subMeshCount; si++) { int[] indices = mesh.GetTriangles(si); int[] faceIndices = new int[indices.Length / 3]; for (int i = 0; i < indices.Length; i += 3) { faceIndices[i / 3] = faceTable[new Vector3(indices[i], indices[i + 1], indices[i + 2])]; } var vtIndices = UnityTypeConverter.ToVtArray(faceIndices); var subset = pxr.UsdGeomSubset.CreateUniqueGeomSubset( usdGeomMesh, // The object of which this subset belongs. m_subMeshesToken, // An arbitrary name for the subset. pxr.UsdGeomTokens.face, // Indicator that these represent face indices vtIndices, // The actual face indices. m_materialBindToken // familyName = "materialBind" ); if (exportContext.exportMaterials) { if (si >= sharedMaterials.Length || !sharedMaterials[si] || !exportContext.matMap.TryGetValue(sharedMaterials[si], out usdMaterialPath)) { Debug.LogWarning("Invalid material bound for: " + path + "\n" + (si >= sharedMaterials.Length ? "More submeshes than materials assigned." : (!sharedMaterials[si] ? "Submesh " + si + " has null material" : "ExportMap can't map material"))); } else { MaterialSample.Bind(scene, subset.GetPath(), usdMaterialPath); } } } } } else { // Only write the transform when animating. var meshSample = new MeshSampleBase(); meshSample.extent = sample.extent; meshSample.transform = XformExporter.GetLocalTransformMatrix( go.transform, scene.UpAxis == Scene.UpAxes.Z, new pxr.SdfPath(path).IsRootPrimPath(), exportContext.basisTransform); if (exportMeshPose) { meshSample.points = mesh.vertices; // Set face vertex counts and indices. var tris = mesh.triangles; if (slowAndSafeConversion) { // Unity uses a forward vector that matches DirectX, but USD matches OpenGL, so a change // of basis is required. There are shortcuts, but this is fully general. for (int i = 0; i < meshSample.points.Length; i++) { meshSample.points[i] = UnityTypeConverter.ChangeBasis(meshSample.points[i]); } for (int i = 0; i < tris.Length; i += 3) { var t = tris[i]; tris[i] = tris[i + 1]; tris[i + 1] = t; } } sample.SetTriangles(tris); } UnityEngine.Profiling.Profiler.BeginSample("USD: Mesh Write"); scene.Write(path, meshSample); UnityEngine.Profiling.Profiler.EndSample(); } } }
public static void ExportSkelAnimation(ObjectContext objContext, ExportContext exportContext) { var scene = exportContext.scene; var sample = (SkelAnimationSample)objContext.sample; var go = objContext.gameObject; var boneNames = exportContext.skelSortedMap[go.transform]; var skelRoot = go.transform; sample.joints = new string[boneNames.Count]; var worldXf = new Matrix4x4[boneNames.Count]; var worldXfInv = new Matrix4x4[boneNames.Count]; string rootPath = UnityTypeConverter.GetPath(go.transform); var basisChange = Matrix4x4.identity; basisChange[2, 2] = -1; for (int i = 0; i < boneNames.Count; i++) { var bonePath = boneNames[i]; if (!exportContext.pathToBone.ContainsKey(bonePath)) { sample.joints[i] = ""; continue; } var bone = exportContext.pathToBone[bonePath]; sample.joints[i] = bonePath.Replace(rootPath + "/", ""); worldXf[i] = bone.localToWorldMatrix; if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { worldXf[i] = UnityTypeConverter.ChangeBasis(worldXf[i]); } worldXfInv[i] = worldXf[i].inverse; } var rootXf = skelRoot.localToWorldMatrix.inverse; if (exportContext.basisTransform == BasisTransformation.SlowAndSafe) { rootXf = UnityTypeConverter.ChangeBasis(rootXf); } var skelWorldTransform = UnityTypeConverter.ToGfMatrix(rootXf); pxr.VtMatrix4dArray vtJointsLS = new pxr.VtMatrix4dArray((uint)boneNames.Count); pxr.VtMatrix4dArray vtJointsWS = UnityTypeConverter.ToVtArray(worldXf); pxr.VtMatrix4dArray vtJointsWSInv = UnityTypeConverter.ToVtArray(worldXfInv); var translations = new pxr.VtVec3fArray(); var rotations = new pxr.VtQuatfArray(); sample.scales = new pxr.VtVec3hArray(); var topo = new pxr.UsdSkelTopology(UnityTypeConverter.ToVtArray(sample.joints)); pxr.UsdCs.UsdSkelComputeJointLocalTransforms(topo, vtJointsWS, vtJointsWSInv, vtJointsLS, skelWorldTransform); pxr.UsdCs.UsdSkelDecomposeTransforms( vtJointsLS, translations, rotations, sample.scales); sample.translations = UnityTypeConverter.FromVtArray(translations); sample.rotations = UnityTypeConverter.FromVtArray(rotations); scene.Write(objContext.path, sample); }
/// <summary> /// Converts a VtValue to a SerializedProperty, to reconstruct the USD scene in Unity. /// </summary> static public void VtValueToProp(SerializedProperty prop, pxr.VtValue val) { switch (prop.propertyType) { case SerializedPropertyType.AnimationCurve: // TODO: needs to be broken down into atoms. throw new System.NotImplementedException(); case SerializedPropertyType.ArraySize: //prop.intValue = (int)val; break; case SerializedPropertyType.Boolean: prop.boolValue = (bool)val; break; case SerializedPropertyType.Bounds: prop.boundsValue = UnityTypeConverter.BoundsFromVtArray(val); break; case SerializedPropertyType.BoundsInt: // TODO: add this to UnityTypeConverter. var bnds = UnityTypeConverter.BoundsFromVtArray(val); var center = new Vector3Int((int)bnds.center.x, (int)bnds.center.y, (int)bnds.center.z); var size = new Vector3Int((int)bnds.size.x, (int)bnds.size.y, (int)bnds.size.z); prop.boundsIntValue = new BoundsInt(center, size); break; case SerializedPropertyType.Character: prop.intValue = (int)val; break; case SerializedPropertyType.Color: prop.colorValue = UnityTypeConverter.Vec4fToColor(val); break; case SerializedPropertyType.Enum: prop.enumValueIndex = (int)val; break; case SerializedPropertyType.ExposedReference: // TODO. //prop.exposedReferenceValue; throw new System.NotImplementedException(); case SerializedPropertyType.FixedBufferSize: //prop.fixedBufferSize = (int)val; // TODO. throw new System.NotImplementedException(); case SerializedPropertyType.Float: prop.floatValue = (float)val; break; case SerializedPropertyType.Generic: throw new System.Exception(); case SerializedPropertyType.Gradient: // TODO: gradientValue accessor is not public. wat? throw new System.NotImplementedException(); case SerializedPropertyType.Integer: prop.intValue = (int)val; break; case SerializedPropertyType.LayerMask: prop.intValue = (int)val; break; case SerializedPropertyType.ObjectReference: /* * var v2i = (pxr.GfVec2i)val; * if (v2i[0] == 0 && v2i[1] == 0) { * break; * } * Debug.Log("FileID: " + v2i[0] + " PathID: " + v2i[1]); */ if (val.IsEmpty()) { break; } string strValue = pxr.UsdCs.VtValueTostring(val); if (string.IsNullOrEmpty(strValue)) { break; } string[] names = strValue.Split(':'); int pathId = int.Parse(names[0]); var guid = names[1]; int fileId = int.Parse(names[2]); string assetPath = AssetDatabase.GUIDToAssetPath(guid); Object[] objs = AssetDatabase.LoadAllAssetsAtPath(assetPath); Object obj = objs[pathId]; Debug.Log("pathId: " + pathId + " fileId: " + fileId + " guid: " + guid.ToString() + " obj: " + obj.ToString()); //break; /* TODO: * string expectedName = names[2]; * if (objs[index].name != expectedName) { * Debug.LogWarning("Expected name '" + expectedName + "' but found '" + objs[index].name + "'"); * } */ prop.FindPropertyRelative("m_PathID").intValue = pathId; prop.FindPropertyRelative("m_FileID").intValue = fileId; prop.objectReferenceValue = obj; break; case SerializedPropertyType.Quaternion: prop.quaternionValue = UnityTypeConverter.QuatfToQuaternion(val); break; case SerializedPropertyType.Rect: prop.rectValue = UnityTypeConverter.Vec4fToRect(val); break; case SerializedPropertyType.RectInt: var rect = UnityTypeConverter.Vec4fToRect(val); prop.rectIntValue = new RectInt((int)rect.xMin, (int)rect.yMin, (int)rect.width, (int)rect.height); break; case SerializedPropertyType.String: var s = (string)val; if (s == null) { break; } prop.stringValue = (string)val; break; case SerializedPropertyType.Vector2: prop.vector2Value = UnityTypeConverter.Vec2fToVector2(val); break; case SerializedPropertyType.Vector2Int: // TODO: add this to UnityTypeConverter. var v2 = (pxr.GfVec2i)val; prop.vector2IntValue = new Vector2Int(v2[0], v2[1]); break; case SerializedPropertyType.Vector3: prop.vector3Value = UnityTypeConverter.Vec3fToVector3(val); break; case SerializedPropertyType.Vector3Int: // TODO: add this to UnityTypeConverter. var v3 = (pxr.GfVec3i)val; prop.vector3IntValue = new Vector3Int(v3[0], v3[1], v3[2]); break; case SerializedPropertyType.Vector4: prop.vector4Value = UnityTypeConverter.Vec4fToVector4(val); break; } }
/// <summary> /// Converts a SerializedProperty to a VtValue, for writing to USD. /// </summary> static public pxr.VtValue PropToVtValue(SerializedProperty prop) { switch (prop.propertyType) { case SerializedPropertyType.AnimationCurve: // TODO: needs to be broken down into atoms. return(new pxr.VtValue()); case SerializedPropertyType.ArraySize: return(prop.intValue); case SerializedPropertyType.Boolean: return(prop.boolValue); case SerializedPropertyType.Bounds: return(UnityTypeConverter.BoundsToVtArray(prop.boundsValue)); case SerializedPropertyType.BoundsInt: // TODO: add this to UnityTypeConverter. var bi = prop.boundsIntValue; var bnds = new Bounds(bi.center, bi.size); return(UnityTypeConverter.BoundsToVtArray(bnds)); case SerializedPropertyType.Character: return(prop.intValue); case SerializedPropertyType.Color: return(UnityTypeConverter.ColorToVec4f(prop.colorValue)); case SerializedPropertyType.Enum: return(prop.enumDisplayNames[prop.enumValueIndex]); case SerializedPropertyType.ExposedReference: // TODO. //return prop.exposedReferenceValue; return(new pxr.VtValue()); case SerializedPropertyType.FixedBufferSize: return(prop.fixedBufferSize); case SerializedPropertyType.Float: return(prop.floatValue); case SerializedPropertyType.Generic: return("GENERIC"); case SerializedPropertyType.Gradient: // TODO: gradientValue accessor is not public. wat? return("Gradient"); case SerializedPropertyType.Integer: return(prop.intValue); case SerializedPropertyType.LayerMask: return(prop.intValue); case SerializedPropertyType.ObjectReference: var obj = prop.objectReferenceValue; if (obj == null) { return(new pxr.VtValue("")); } // For object references in the scene, the asset path will be empty/null. // However, for mesh and material instances in the scen, what do we want to do here? // They are serialized to .unity files with just a file id, rather than fileid, pathid, and guid. string assetPath = AssetDatabase.GetAssetPath(prop.objectReferenceValue); if (string.IsNullOrEmpty(assetPath)) { return(new pxr.VtValue("")); } var fileId = prop.FindPropertyRelative("m_FileID").intValue; var pathId = prop.FindPropertyRelative("m_PathID").intValue; string guid = AssetDatabase.AssetPathToGUID(assetPath); return(prop.FindPropertyRelative("m_PathID").intValue + ":" + guid + ":" + fileId); case SerializedPropertyType.Quaternion: return(UnityTypeConverter.QuaternionToQuatf(prop.quaternionValue)); case SerializedPropertyType.Rect: return(UnityTypeConverter.RectToVtVec4(prop.rectValue)); case SerializedPropertyType.RectInt: // TODO: add this to UnityTypeConverter. var ri = prop.rectIntValue; return(new pxr.GfVec4i(ri.x, ri.y, ri.width, ri.height)); case SerializedPropertyType.String: return(prop.stringValue); case SerializedPropertyType.Vector2: return(UnityTypeConverter.Vector2ToVec2f(prop.vector2Value)); case SerializedPropertyType.Vector2Int: // TODO: add this to UnityTypeConverter. return(new pxr.GfVec2i(prop.vector2IntValue.x, prop.vector2IntValue.y)); case SerializedPropertyType.Vector3: return(UnityTypeConverter.Vector3ToVec3f(prop.vector3Value)); case SerializedPropertyType.Vector3Int: var v3 = prop.vector3IntValue; // TODO: add this to UnityTypeConverter. return(new pxr.GfVec3i(v3.x, v3.y, v3.z)); case SerializedPropertyType.Vector4: return(UnityTypeConverter.Vector4ToVec4f(prop.vector4Value)); } return("UNKNOWN"); }
/// <summary> /// If HierInfo represents a UsdSkelRoot, reads the associated skelton joints into the /// skelJoints member. /// </summary> static void ReadSkeletonJoints(ref HierInfo skelRootInfo) { if (skelRootInfo.prim == null) { return; } var skelRoot = new UsdSkelRoot(skelRootInfo.prim); if (!skelRoot) { return; } var processed = new HashSet <SdfPath>(); foreach (UsdSkelBinding binding in skelRootInfo.skelBindings) { var skel = binding.GetSkeleton(); if (!skel) { continue; } // If the same skeleton is referenced multiple times, only process it once. if (processed.Contains(skel.GetPath())) { continue; } processed.Add(skel.GetPath()); var jointsAttr = skel.GetJointsAttr(); if (!jointsAttr) { continue; } var vtJoints = jointsAttr.Get(); if (vtJoints.IsEmpty()) { continue; } var vtStrings = UsdCs.VtValueToVtTokenArray(vtJoints); var joints = UnityTypeConverter.FromVtArray(vtStrings); var skelPath = skel.GetPath(); skelRootInfo.skelJoints = new SdfPath[joints.Length]; for (int i = 0; i < joints.Length; i++) { var jointPath = new SdfPath(joints[i]); if (joints[i] == "/") { skelRootInfo.skelJoints[i] = skelPath; continue; } else if (jointPath.IsAbsolutePath()) { Debug.LogException(new Exception("Unexpected absolute joint path: " + jointPath)); jointPath = new SdfPath(joints[i].TrimStart('/')); } skelRootInfo.skelJoints[i] = skelPath.AppendPath(jointPath); } } }
void OnEnable() { InitUsd.Initialize(); if (string.IsNullOrEmpty(m_usdMeshPath)) { m_usdMeshPath = UnityTypeConverter.GetPath(transform); } var scene = GetScene(); var binding = ReadUsdWeights(scene); string skelRootPath; var skeleton = ReadUsdSkeleton(scene, out skelRootPath); if (binding == null) { binding = new SkelBindingSample(); } var mesh = GetComponent <SkinnedMeshRenderer>().sharedMesh; // Process classic four-bone weights first. var sb = new System.Text.StringBuilder(); #if UNITY_2019_1_OR_NEWER var bonesPerVert = mesh.GetBonesPerVertex(); int weightsPerBone = 0; foreach (int count in bonesPerVert) { weightsPerBone = weightsPerBone > count ? weightsPerBone : count; } var boneWeights = mesh.GetAllBoneWeights(); sb.AppendLine("Many-bone indices: (" + boneWeights.Length + " * 4)"); int bone = 0; int bi = 0; int wi = 0; foreach (var weight in boneWeights) { if (wi == 0) { sb.Append("i: " + bone + " ["); } sb.Append(weight.boneIndex + GetUsdBoneData(bi, wi, binding.jointIndices) + ","); wi++; if (wi == weightsPerBone) { sb.Append("]\n"); bi++; wi = 0; } if (bonesPerVert[bi] != weightsPerBone) { // TODO: Unity supports a variable number of weights per bone, but USD does not. // Therefore, the number of weights may be greater in USD than in Unity. Currently // the way this works does not correctly handle that case. Debug.LogWarning("Unity bone count issue, see code comment for details."); } bone++; } Debug.Log(sb.ToString()); bone = 0; bi = 0; wi = 0; sb = new System.Text.StringBuilder(); sb.AppendLine("Many-bone weights: (" + boneWeights.Length + " * 4)"); foreach (var weight in boneWeights) { if (wi == 0) { sb.Append("i: " + bone + " ["); } sb.Append(weight.weight + GetUsdBoneData(bi, wi, binding.jointWeights) + ","); wi++; if (wi == weightsPerBone) { sb.Append("]\n"); bi++; wi = 0; } bone++; } Debug.Log(sb.ToString()); #else sb.AppendLine("Legacy 4-bone indices: (" + mesh.boneWeights.Length + " * 4)"); int bone = 0; foreach (var weight in mesh.boneWeights) { sb.Append("["); sb.Append(weight.boneIndex0 + GetUsdBoneData(bone, 0, binding.jointIndices) + ","); sb.Append(weight.boneIndex1 + GetUsdBoneData(bone, 1, binding.jointIndices) + ","); sb.Append(weight.boneIndex2 + GetUsdBoneData(bone, 2, binding.jointIndices) + ","); sb.Append(weight.boneIndex3 + GetUsdBoneData(bone, 3, binding.jointIndices) + "]\n"); bone++; } Debug.Log(sb.ToString()); bone = 0; sb = new System.Text.StringBuilder(); sb.AppendLine("Legacy 4-bone weights: (" + mesh.boneWeights.Length + " * 4)"); foreach (var weight in mesh.boneWeights) { sb.Append("["); sb.Append(weight.weight0 + GetUsdBoneData(bone, 0, binding.jointWeights) + ","); sb.Append(weight.weight1 + GetUsdBoneData(bone, 1, binding.jointWeights) + ","); sb.Append(weight.weight2 + GetUsdBoneData(bone, 2, binding.jointWeights) + ","); sb.Append(weight.weight3 + GetUsdBoneData(bone, 3, binding.jointWeights) + "]\n"); bone++; } Debug.Log(sb.ToString()); #endif sb = new System.Text.StringBuilder(); var bones = GetComponent <SkinnedMeshRenderer>().bones; var rootBone = GetComponent <SkinnedMeshRenderer>().rootBone; var root = UnityTypeConverter.GetPath(rootBone); sb.AppendLine("Bones: (" + bones.Length + ")"); sb.AppendLine("Root Bone: " + root); int i = 0; foreach (var boneXf in bones) { sb.AppendLine(UnityTypeConverter.GetPath(boneXf)); if (binding.joints != null) { sb.AppendLine(root + "\\" + binding.joints[i++] + "\n"); } } Debug.Log(sb.ToString()); sb = new System.Text.StringBuilder(); sb.AppendLine("Bind Transforms: (" + mesh.bindposes.Length + ")"); i = -1; var options = new SceneImportOptions(); options.changeHandedness = m_basisTransform; foreach (var boneXf in bones) { i++; var bindPose = mesh.bindposes[i]; var bonePath = UnityTypeConverter.GetPath(boneXf); sb.AppendLine("Pose[" + i + "] " + bonePath); sb.AppendLine(bindPose.ToString()); if (skeleton.bindTransforms != null) { if (string.IsNullOrEmpty(skelRootPath)) { continue; } bonePath = bonePath.Substring(skelRootPath.Length); bonePath = bonePath.TrimStart('/'); foreach (var joint in skeleton.joints) { if (joint == bonePath) { var usdMat = skeleton.bindTransforms[i]; XformImporter.ImportXform(ref usdMat, options); sb.AppendLine(usdMat.ToString() + "\n"); bonePath = null; break; } } if (string.IsNullOrEmpty(bonePath)) { continue; } sb.Append("Bone not found in USD: " + bonePath + "\n\n"); } } Debug.Log(sb.ToString()); }
/// <summary> /// Applies the contents of this USD file to a foreign root object. /// </summary> /// <remarks> /// The idea here is that one may have many animation clips, but only a single GameObject in /// the Unity scenegraph. /// </remarks> public void SetTime(double time, UsdAsset foreignRoot, bool saveMeshUpdates) { var scene = GetScene(); if (scene == null) { Debug.LogWarning("Null scene from GetScene() at " + UnityTypeConverter.GetPath(transform)); return; } // Careful not to update any local members here, if this data is driven from a prefab, we // dont want those changes to be baked back into the asset. time += foreignRoot.m_usdTimeOffset; float usdTime = (float)(scene.StartTime + time * scene.Stage.GetTimeCodesPerSecond()); if (usdTime > scene.EndTime) { return; } if (usdTime < scene.StartTime) { return; } scene.Time = usdTime; var options = new SceneImportOptions(); foreignRoot.StateToOptions(ref options); PrepOptionsForTimeChange(ref options); if (foreignRoot.m_lastPrimMap == null) { foreignRoot.m_lastPrimMap = new PrimMap(); options.importHierarchy = true; } if (m_usdVariabilityCache) { if (m_lastAccessMask == null) { m_lastAccessMask = new AccessMask(); scene.IsPopulatingAccessMask = true; } } else { m_lastAccessMask = null; } if (m_debugPrintVariabilityCache && m_lastAccessMask != null && !scene.IsPopulatingAccessMask) { var sb = new System.Text.StringBuilder(); foreach (var kvp in m_lastAccessMask.Included) { sb.AppendLine(kvp.Key); foreach (var member in kvp.Value) { sb.AppendLine(" ." + member.Name); } sb.AppendLine(); } Debug.Log(sb.ToString()); } scene.AccessMask = m_lastAccessMask; SceneImporter.ImportUsd(foreignRoot.gameObject, scene, foreignRoot.m_lastPrimMap, options); scene.AccessMask = null; if (m_lastAccessMask != null) { scene.IsPopulatingAccessMask = false; } }
static void ExportSkelWeights(Scene scene, string path, Mesh unityMesh, Transform rootBone, Transform[] bones, string skeletonPath) { var sample = new SkelBindingSample(); sample.geomBindTransform.value = Matrix4x4.identity; sample.joints = new string[bones.Length]; if (!string.IsNullOrEmpty(skeletonPath)) { sample.skeleton.targetPaths = new string[] { skeletonPath }; } int b = 0; var rootPath = UnityTypeConverter.GetPath(rootBone); foreach (Transform bone in bones) { var bonePath = UnityTypeConverter.GetPath(bone); if (bonePath == rootPath) { sample.joints[b++] = "/"; } else { sample.joints[b++] = bonePath.Replace(rootPath + "/", ""); } } int i = 0; int w = 0; b = 0; #if UNITY_2019 var bonesPerVertex = unityMesh.GetBonesPerVertex(); var unityBoneWeights = unityMesh.GetAllBoneWeights(); byte maxWeightCount = 0; foreach (var c in bonesPerVertex) { maxWeightCount = maxWeightCount > c ? maxWeightCount : c; } sample.jointIndices.value = new int[bonesPerVertex.Length * maxWeightCount]; sample.jointIndices.elementSize = maxWeightCount; sample.jointIndices.interpolation = PrimvarInterpolation.Vertex; sample.jointWeights.value = new float[bonesPerVertex.Length * maxWeightCount]; sample.jointWeights.elementSize = maxWeightCount; sample.jointWeights.interpolation = PrimvarInterpolation.Vertex; foreach (var weightCount in bonesPerVertex) { for (int j = 0; j < weightCount; j++) { var bw = unityBoneWeights[b++]; sample.jointIndices.value[i++] = bw.boneIndex; sample.jointWeights.value[w++] = bw.weight; } // Unity allows a variable number of weights per bone, but we've made the array square, // which means we may need to skip a few indicies, if this vert doesn't use the max number // of weights. i += maxWeightCount - weightCount; w += maxWeightCount - weightCount; } #else var unityBoneWeights = unityMesh.boneWeights; if (unityBoneWeights.Length == 0) { Debug.LogWarning("Found zero bone weights at: " + path); return; } sample.jointIndices.value = new int[unityBoneWeights.Length * 4]; sample.jointIndices.elementSize = 4; sample.jointIndices.interpolation = PrimvarInterpolation.Vertex; sample.jointWeights.value = new float[unityBoneWeights.Length * 4]; sample.jointWeights.elementSize = 4; sample.jointWeights.interpolation = PrimvarInterpolation.Vertex; foreach (var bone in unityBoneWeights) { sample.jointIndices.value[i++] = bone.boneIndex0; sample.jointIndices.value[i++] = bone.boneIndex1; sample.jointIndices.value[i++] = bone.boneIndex2; sample.jointIndices.value[i++] = bone.boneIndex3; sample.jointWeights.value[w++] = bone.weight0; sample.jointWeights.value[w++] = bone.weight1; sample.jointWeights.value[w++] = bone.weight2; sample.jointWeights.value[w++] = bone.weight3; } #endif scene.Write(path, sample); }