static Transform MergeBonesBelowAnimator(Transform animator, ExportContext context) { var toRemove = new Dictionary <Transform, Transform>(); Transform commonRoot = null; foreach (var sourceAndRoot in context.meshToSkelRoot) { var meshXf = sourceAndRoot.Key; var meshRootBone = sourceAndRoot.Value; if (!meshRootBone.IsChildOf(animator)) { continue; } toRemove.Add(meshXf, meshRootBone); if (commonRoot == null) { // We use the parent because the root bone is part of the skeleton and we're establishing // the skeleton root here. If the root bone is used as the skeleton root, its transform // will get applied twice after export to USD: once for the UsdPrim which is the skeleton // root and once for the bone which is in the skeleton itself. The root bone could be // excluded from the skeleton, but this seems simpler. commonRoot = meshRootBone.parent; } else if (meshRootBone.IsChildOf(commonRoot)) { // Nothing to do. } else if (commonRoot.IsChildOf(meshRootBone)) { // The new root is a parent of the current common root, use it as the root instead. commonRoot = meshRootBone.parent; } else { // We have an animator which is a common parent of two disjoint skeletons, this is not // desirable because it requires that the animator be the common root, however this // root will be tagged as a guide, which will cuase the geometry not to render, which // will be confusing. Another option would be to construct a new common parent in USD, // but this will cause the asset namespace to change, which is almost never a good idea. commonRoot = animator; } } if (toRemove.Count == 0) { return(null); } // At this point, some number of root bones have been aggregated under some potentially new // common root. Next, we need to merge all these root bones and preserve the requirement that // the bones are in "parent first" order. var allBones = new List <Transform>(); foreach (var kvp in toRemove) { Transform curMeshXf = kvp.Key; Transform rootBone = kvp.Value; allBones.AddRange(context.meshToBones[curMeshXf]); // Downstream code will have a root bone and need to know how to make bone paths relative // to the new, arbitrary, common root which we have chosen. context.boneToRoot[rootBone] = commonRoot; context.meshToSkelRoot.Remove(curMeshXf); context.meshToBones.Remove(curMeshXf); } // Maintain a sorted list of bone names to ensure "parent first" ordering for UsdSkel. var allNames = allBones.Select(boneXf => UnityTypeConverter.GetPath(boneXf)) .OrderBy(str => str) .Distinct() .ToList(); context.skelSortedMap[commonRoot] = allNames; return(commonRoot); }
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); } }
public static void SyncExportContext(GameObject exportRoot, ExportContext context) { context.exportRoot = exportRoot.transform.parent; Traverse(exportRoot, InitExportableObjects, context); Transform expRoot = context.exportRoot; var foundAnimators = new List <Transform>(); foreach (var rootBoneXf in context.meshToSkelRoot.Values.ToArray()) { bool alreadyProcessed = false; foreach (var xf in foundAnimators) { if (rootBoneXf.IsChildOf(xf)) { alreadyProcessed = true; break; } } if (alreadyProcessed) { continue; } var animatorXf = rootBoneXf; while (animatorXf != null) { // If there is an animator, assume this is the root of the rig. // This feels very ad hoc, it would be nice to not use a heuristic. var anim = animatorXf.GetComponent <Animator>(); if (anim != null) { // Any root bones under this animator will be merged into their most common ancestor, // which is returned here and becomes the skeleton root. Transform skeletonRoot = MergeBonesBelowAnimator(animatorXf, context); if (skeletonRoot == null) { animatorXf = animatorXf.parent; Debug.LogWarning("No children found under animator: " + UnityTypeConverter.GetPath(animatorXf) + " Root bone XF: " + UnityTypeConverter.GetPath(rootBoneXf)); continue; } foundAnimators.Add(anim.transform); // The skeleton is exported at the skeleton root and UsdSkelAnimation is nested under // this prim as a new prim called "_anim". SkelRootSample rootSample = CreateSample <SkelRootSample>(context); string skelRootPath = UnityTypeConverter.GetPath(animatorXf.transform, expRoot); string skelPath = UnityTypeConverter.GetPath(skeletonRoot, expRoot); string skelPathSuffix = ""; string skelAnimSuffix = "/_anim"; // When there is a collision between the SkelRoot and the Skeleton, make a new USD Prim // for the Skeleton object. The reason this is safe is as follows: if the object was // imported from USD, then the structure should already be correct and this code path will // not be hit (and hence overrides, etc, will work correctly). If the object was created // in Unity and there happened to be a collision, then we can safely create a new prim // for the Skeleton prim because there will be no existing USD skeleton for which // the namespace must match, hence adding a new prim is still safe. if (skelPath == skelRootPath) { Debug.LogWarning("SkelRoot and Skeleton have the same path, renaming Skeleton"); skelPathSuffix = "/_skel"; } rootSample.animationSource = skelPath + skelAnimSuffix; // For any skinned mesh exported under this SkelRoot, pass along the skeleton path in // the "additional data" member of the exporter. Note that this feels very ad hoc and // should probably be formalized in some way (perhaps as a separate export event for // which the SkinnedMesh exporter can explicitly register). // // While it is possible to bind the skel:skeleton relationship at the SkelRoot and // have it inherit down namespace, the Apple importer did not respect this inheritance // and it sometimes causes issues with geometry embedded in the bone hierarchy. foreach (var p in context.plans) { if (p.Key.transform.IsChildOf(animatorXf.transform)) { foreach (var e in p.Value.exporters) { if (e.exportFunc == MeshExporter.ExportSkinnedMesh) { e.data = skelPath + skelPathSuffix; } } } } CreateExportPlan( animatorXf.gameObject, rootSample, SkeletonExporter.ExportSkelRoot, context, insertFirst: true); CreateExportPlan( animatorXf.gameObject, rootSample, NativeExporter.ExportObject, context, insertFirst: false); CreateExportPlan( skeletonRoot.gameObject, CreateSample <SkeletonSample>(context), SkeletonExporter.ExportSkeleton, context, insertFirst: true, pathSuffix: skelPathSuffix); CreateExportPlan( skeletonRoot.gameObject, CreateSample <SkeletonSample>(context), NativeExporter.ExportObject, context, insertFirst: false, pathSuffix: skelPathSuffix); CreateExportPlan( skeletonRoot.gameObject, CreateSample <SkelAnimationSample>(context), SkeletonExporter.ExportSkelAnimation, context, insertFirst: true, pathSuffix: skelAnimSuffix); // Exporting animation is only possible while in-editor (in 2018 and earlier). #if UNITY_EDITOR #if false // Currently disabled, future work. if (anim.layerCount > 0) { for (int l = 0; l < anim.layerCount; l++) { int clipCount = anim.GetCurrentAnimatorClipInfoCount(l); var clipInfos = anim.GetCurrentAnimatorClipInfo(l); foreach (var clipInfo in clipInfos) { var bindings = UnityEditor.AnimationUtility.GetCurveBindings(clipInfo.clip); // Properties are expressed as individual values, for transforms this is: // m_LocalPosition.x,y,z // m_LocalScale.x,y,z // m_LocalRotation.x,y,z,w // Which means they must be reaggregated into matrices. foreach (var binding in bindings) { if (binding.type != typeof(Transform)) { continue; } Debug.Log(binding.path + "." + binding.propertyName); var knot = UnityEditor.AnimationUtility.GetEditorCurve(clipInfo.clip, binding); } } } } #endif // disabled. #endif // Editor only. break; } animatorXf = animatorXf.parent; } } }
private static void ExportImpl(GameObject root, ExportContext context) { var scene = context.scene; bool skipInactive = context.activePolicy == ActiveExportPolicy.DoNotExport; if (context.exportMaterials) { // TODO: should account for skipped objects and also skip their materials. UnityEngine.Profiling.Profiler.BeginSample("USD: Export Materials"); foreach (var kvp in context.matMap) { Material mat = kvp.Key; string usdPath = kvp.Value; if (!mat || usdPath == null) { continue; } try { MaterialExporter.ExportMaterial(scene, kvp.Key, kvp.Value); } catch (Exception ex) { Debug.LogException(new Exception("Error exporting material: " + kvp.Value, ex)); } } UnityEngine.Profiling.Profiler.EndSample(); } UnityEngine.Profiling.Profiler.BeginSample("USD: Process Export Plans"); foreach (var kvp in context.plans) { GameObject go = kvp.Key; ExportPlan exportPlan = kvp.Value; if (!go || exportPlan == null) { continue; } if (go != root && !go.transform.IsChildOf(root.transform)) { continue; } if (skipInactive && go.activeInHierarchy == false) { continue; } foreach (Exporter exporter in exportPlan.exporters) { string path = exporter.path; SampleBase sample = exporter.sample; var objCtx = new ObjectContext { gameObject = go, path = path, sample = sample, additionalData = exporter.data }; try { exporter.exportFunc(objCtx, context); } catch (Exception ex) { Debug.LogException(new Exception("Error exporting: " + path, ex)); continue; } UnityEngine.Profiling.Profiler.BeginSample("USD: Process Visibility"); try { if (!go.gameObject.activeSelf) { switch (context.activePolicy) { case ActiveExportPolicy.Ignore: // Nothing to see here. break; case ActiveExportPolicy.ExportAsVisibility: // Make the prim invisible. var im = new pxr.UsdGeomImageable(scene.GetPrimAtPath(path)); if (im) { im.CreateVisibilityAttr().Set(pxr.UsdGeomTokens.invisible); } break; case ActiveExportPolicy.ExportAsActive: // TODO: this may actually cause errors because exported prims will not exist in // the USD scene graph. Right now, that's too much responsibility on the caller, // because the error messages will be mysterious. // Make the prim inactive. scene.GetPrimAtPath(path).SetActive(false); break; } } } catch (Exception ex) { Debug.LogException(new Exception("Error setting visibility: " + path, ex)); continue; } UnityEngine.Profiling.Profiler.EndSample(); } // foreach exporter } // foreach plan UnityEngine.Profiling.Profiler.EndSample(); }
public static void ExportObject(ObjectContext objContext, ExportContext exportContext) { }
static void ExportMesh(ObjectContext objContext, ExportContext exportContext, Mesh mesh, Material sharedMaterial, Material[] sharedMaterials, bool exportMeshPose = true) { if (mesh.isReadable == false) { Debug.LogWarning("Mesh not readable: " + objContext.path); return; } string path = objContext.path; if (mesh == null) { Debug.LogWarning("Null mesh for: " + path); 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 ahve 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]); } } 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 materialBindToken = new pxr.TfToken("materialBind"); var vtIndices = UnityTypeConverter.ToVtArray(faceIndices); var subset = pxr.UsdGeomSubset.CreateUniqueGeomSubset( usdGeomMesh, // The object of which this subset belongs. "subMeshes", // An arbitrary name for the subset. pxr.UsdGeomTokens.face, // Indicator that these represent face indices vtIndices, // The actual face indices. materialBindToken // familyName = "materialBind" ); if (exportContext.exportMaterials) { if (si >= sharedMaterials.Length || !exportContext.matMap.TryGetValue(sharedMaterials[si], out usdMaterialPath)) { Debug.LogError("Invalid material bound for: " + path); } 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(); } }