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();
        }
Example #5
0
 public static void ExportObject(ObjectContext objContext, ExportContext exportContext)
 {
 }
Example #6
0
        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();
            }
        }