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); }
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); }
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(); } } }