Ejemplo n.º 1
0
        public static void CreateSource(Light light)
        {
            // ASSUME: All children of light are sources
            foreach (var source in light.gameObject.Children())
            {
                EP.Destroy(source);
            }

            // OPTION: Move light forward to prevent Z fighting when coplanar

            switch (light.type)
            {
            case LightType.Point:
                CreatePointSource(light);
                break;

            case LightType.Spot:
                CreateSpotSource(light);
                break;

            case LightType.Disc:
                CreateDiskSource(light);
                break;

            case LightType.Rectangle:
                CreateRectangleSource(light);
                break;

            default:             // Directional light has no position source
                break;
            }
        }
Ejemplo n.º 2
0
        // Derive transform information from a placeholder
        public static void ImportPlaceholder(MeshFilter meshFilter, bool rhinoBasis)
        {
            var sharedMesh = meshFilter.sharedMesh;
            var vertices   = sharedMesh?.vertices;

            if (vertices == null || vertices.Length != 4)
            {
                Debug.LogWarning($"Inconsistent placeholder mesh: {meshFilter.Path()}.{sharedMesh.name}");
                return;
            }
            var placeholder = meshFilter.transform;
            // OBSERVATION: At this stage of the import the layer parent transform is present
            // but on completion the layer may be absent, although the transform will persist.
            // NOTE: This also applies to the default import path.

            // Derive Rhino transform block basis in world coordinates
            var origin = placeholder.TransformPoint(vertices[0]);
            var basisX = placeholder.TransformPoint(vertices[1]) - origin;
            var basisY = placeholder.TransformPoint(vertices[2]) - origin;
            var basisZ = placeholder.TransformPoint(vertices[3]) - origin;

            if (rhinoBasis)
            {
                // Mesh basis describes transformation of Rhino basis
                var unityX = new Vector3(-basisX.x, basisX.y, -basisX.z);
                var unityY = new Vector3(-basisZ.x, basisZ.y, -basisZ.z);
                var unityZ = new Vector3(-basisY.x, basisY.y, -basisY.z);
                basisX = unityX;
                basisY = unityY;
                basisZ = unityZ;
            }

            // TODO: Use SVD to construct transform, which can include shear
            // TEMP: Assume transform is axial scaling followed by rotation only
            // NOTE: The origin and bases are simply the columns of an affine (3x4) transform matrix
            placeholder.localScale = new Vector3(basisX.magnitude, basisY.magnitude, basisZ.magnitude);
            placeholder.rotation   = Quaternion.LookRotation(basisZ, basisY);
            placeholder.position   = origin;

            // Remove meshes from placeholders
            // IMPORTANT: MeshRenderers must also be removed, in order to avoid
            // the application of renderer configations that could modify or remove the placeholder.
            var meshRenderer = meshFilter.GetComponent <MeshRenderer>();

            if (meshRenderer)
            {
                EP.Destroy(meshRenderer);
            }
            EP.Destroy(meshFilter);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Recursively configure all objects and components in hierarchy to be static
        /// </summary>
        /// <param name="gameObject">Root of hierarchy to be converted to static configuration</param>
        /// <param name="prefabs">Apply static conversion overrides to prefabs in hierarchy</param>
        public static void ApplyTo(GameObject gameObject, bool prefabs = false)
        {
            using (var editScope = new EP.EditGameObject(gameObject)) {
                var editObject = editScope.editObject;

                // Set static configurations
                GameObjectSetStatic(editObject);
                foreach (var renderer in editObject.GetComponents <Renderer>())
                {
                    RendererSetStatic(renderer);
                }
                foreach (var collider in editObject.GetComponents <Collider>())
                {
                    ColliderSetStatic(collider);
                }
                foreach (var light in editObject.GetComponents <Light>())
                {
                    LightSetStatic(light);
                }

                // Remove dynamic components
                foreach (var monoBehavior in editObject.GetComponents <MonoBehaviour>())
                {
                    EP.Destroy(monoBehavior);
                }
                foreach (var physics in editObject.GetComponents <Rigidbody>())
                {
                    EP.Destroy(physics);
                }

                foreach (var child in editObject.Children())
                {
                    // Limit prefab recursion
                    var prefabAssetType = PrefabUtility.GetPrefabAssetType(child);
                    if (prefabAssetType == PrefabAssetType.MissingAsset)
                    {
                        continue;
                    }
                    if (prefabAssetType != PrefabAssetType.NotAPrefab && !prefabs)
                    {
                        continue;
                    }

                    ApplyTo(child);
                }
            }
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Removes all empty branches in the hierarchy of a GameObject
        /// </summary>
        /// <remarks>
        /// Excluding cameras or lights from a model import removes components,
        /// but their associated GameObjects will persist in the hierarchy.
        /// </remarks>
        public static void RemoveEmpty(GameObject gameObject)
        {
            // IMPORTANT: Before counting children, apply RemoveEmpty to children
            // since their removal could result in children being empty
            var children = gameObject.transform.Children();

            foreach (var child in children)
            {
                RemoveEmpty(child.gameObject);
            }

            // IMPORTANT: Before counting components, remove empty meshes
            // since this could result in components being empty
            var meshFilter = gameObject.GetComponent <MeshFilter>();

            if (meshFilter)
            {
                var sharedMesh = meshFilter.sharedMesh;
                if (sharedMesh == null || sharedMesh.vertexCount == 0)
                {
                    var meshRenderer = gameObject.GetComponent <MeshRenderer>();
                    if (meshRenderer != null)
                    {
                        EP.Destroy(meshRenderer);
                    }
                    var meshCollider = gameObject.GetComponent <MeshCollider>();
                    if (meshCollider != null)
                    {
                        EP.Destroy(meshCollider);
                    }
                    EP.Destroy(meshFilter);
                }
            }

            // Remove if no children and no components other than transform
            if (
                gameObject.transform.childCount == 0 &&
                gameObject.GetComponents <Component>().Length == 1
                )
            {
                EP.Destroy(gameObject);
                return;
            }
        }
Ejemplo n.º 5
0
 // Find or make a prefab adjacent to the merged asset folder
 static void CreateMerged(string path, Dictionary <string, GameObject> mergedPrefabs)
 {
     if (!mergedPrefabs.ContainsKey(path))
     {
         // Find or make a merged prefab
         var mergedPath = path + ".prefab";
         var merged     = AssetDatabase.LoadAssetAtPath <GameObject>(mergedPath);
         if (!merged)
         {
             var empty = EP.Instantiate();
             empty.name = path.Substring((path.LastIndexOf('/') + 1));
             // WARNING: SaveAsPrefabAsset will return null while AssetDatabase.StartAssetEditing() pertains
             merged = PrefabUtility.SaveAsPrefabAsset(empty, mergedPath);
             EP.Destroy(empty);
             //Debug.Log($"Created empty merged object: {pathPart}.prefab");
         }
         mergedPrefabs.Add(path, merged);
     }
 }
Ejemplo n.º 6
0
        // WARNING: If there is a name override, PathName will not resolve!

        // TODO: Find a way to clone prefab with overrides intact.
        // QUESTION: Is there a way to accomplish instatiation using object serializations?
        // Ideally, this would handle the connection and override persistence.
        // https://docs.unity3d.com/ScriptReference/SerializedObject.html
        // Construct, then iterate & copy?

        static GameObject InstantiateChild(GameObject original, GameObject parent)
        {
            GameObject child = null;
            // IMPORTANT: PrefabUtility.InstantiatePrefab applies only to assets, not to instances
            // IMPORTANT: PrefabUtility.GetCorrespondingObjectFromSource applies only to instances, not to assets
            var        asset      = PrefabUtility.GetCorrespondingObjectFromSource(parent);
            GameObject copy_asset = null;

            if (asset)
            {
                copy_asset = asset;
            }
            else
            {
                copy_asset = original;
            }
            var copy = PrefabUtility.InstantiatePrefab(copy_asset) as GameObject;

            if (parent != original)
            {
                var path = new PathName(original, parent);
                var find = path.Find(copy.transform);
                if (find.Length == 1)
                {
                    child = find[0].gameObject;
                    // Unpack to enable orphaning, only once since nearest root was instantiated
                    var unpack = PrefabUtility.GetOutermostPrefabInstanceRoot(child);
                    while (unpack)
                    {
                        PrefabUtility.UnpackPrefabInstance(unpack, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
                        unpack = PrefabUtility.GetOutermostPrefabInstanceRoot(child);
                    }
                    child.transform.SetParent(null);
                }
                EP.Destroy(copy);
            }
            else
            {
                child = copy;
            }
            return(child);
        }
Ejemplo n.º 7
0
        public static void ApplyTo(GameObject mergeTarget, params GameObject[] mergeSources)
        {
            using (var editScope = new EP.EditGameObject(mergeTarget)) {
                var targetEdit = editScope.editObject;
                foreach (var mergeSource in mergeSources)
                {
                    var sourceCopy = EP.Instantiate(mergeSource);

                    // Unpack only root model prefab, constituent prefab links will be retained
                    // NOTE: Applying UnpackPrefabInstance to a non-prefab object results in a crash
                    if (PrefabUtility.GetPrefabAssetType(sourceCopy) != PrefabAssetType.NotAPrefab)
                    {
                        PrefabUtility.UnpackPrefabInstance(sourceCopy, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
                    }
                    Merge(sourceCopy.transform, targetEdit.transform);

                    EP.Destroy(sourceCopy);
                }
            }
        }
Ejemplo n.º 8
0
        public static string CreateScene(string path, params GameObject[] gameObjects)
        {
            var scenePath = path + ".unity";

            // PROBLEM: When using NewSceneMode.Single during import assertion "GetApplication().MayUpdate()" fails
            // SOLUTION: During import, using Additive loading works.
            // PROBLEM: InvalidOperationException: Cannot create a new scene additively with an untitled scene unsaved.
            // NOTE: This can occur when the previously opened scene has ceased to exist, in particular when a project is opened.
            var scene  = EditorSceneManager.GetActiveScene();
            var addNew = scene.name.Length > 0 && scene.path.Length > 0 && scene.path != scenePath;             // scene.IsValid() will be true even when path and name are empty

            if (addNew)
            {
                scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
            }
            else
            {
                // Remove all default scene objects
                foreach (var rootObject in scene.GetRootGameObjects())
                {
                    EP.Destroy(rootObject);
                }
            }
            EditorSceneManager.SetActiveScene(scene);

            // Add objects to scene
            foreach (var gameObject in gameObjects)
            {
                EP.Instantiate(gameObject);
            }
            // WARNING: If scene is created during asset import physics computations will not be initialized

            // PROBLEM: At end of import the open scene will have been modified, so a pop-up will appear.
            // SOLUTION: After loading the scene in additive mode, close it.
            EditorSceneManager.SaveScene(scene, scenePath);
            if (addNew)
            {
                EditorSceneManager.CloseScene(scene, true);
            }
            return(scenePath);
        }
Ejemplo n.º 9
0
        public static void ApplyTo(GameObject gameObject, string pathRoot)
        {
            var assetType = PrefabUtility.GetPrefabAssetType(gameObject);

            if (assetType == PrefabAssetType.MissingAsset)
            {
                return;
            }
            if (assetType == PrefabAssetType.Model)
            {
                // In the case of a model created an editable prefab
                var prefab = EP.Instantiate(gameObject);
                PrefabUtility.UnpackPrefabInstance(prefab, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
                gameObject = PrefabUtility.SaveAsPrefabAsset(prefab, pathRoot + ".prefab");
                EP.Destroy(prefab);
            }

            // NOTE: Calling CopyAssets will be extremely slow if each asset is imported individually.
            // Instead, all of the copying will be done in one batch, after which assets will be imported.
            // Then, a new instance of AssetGatherer will find those copies and make replacements in
            // a second batch.
            try {
                AssetDatabase.StartAssetEditing();
                var assertGatherer = new AssetGatherer(pathRoot);
                assertGatherer.CopyAssets(gameObject);
            } finally {
                AssetDatabase.StopAssetEditing();
                AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
            }
            try {
                AssetDatabase.StartAssetEditing();
                var assertGatherer = new AssetGatherer(pathRoot);
                assertGatherer.SwapAssets(gameObject);
            } finally {
                AssetDatabase.StopAssetEditing();
                AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
            }
        }
Ejemplo n.º 10
0
        static void ReplacePlaceholders(string prefabPath, GameObject gameObject)
        {
            Dictionary <string, CachedPrefab> prefabs = GetPrefabs(prefabPath);
            var children = gameObject.Children(true);

            foreach (var child in children)
            {
                // Do not modify existing child prefabs
                var childPrefab = PrefabUtility.GetNearestPrefabInstanceRoot(child);
                if (childPrefab != null && childPrefab != gameObject)
                {
                    continue;
                }

                // Placeholder names are constructed as "prefab_name=object_name"
                var name_parts = child.name.Split('=');
                if (name_parts.Length == 1)
                {
                    continue;
                }

                // Create an placeholder prefab that can be modified after import
                if (!prefabs.ContainsKey(name_parts[0]))
                {
                    var placeholder = EP.Instantiate();
                    placeholder.name = name_parts[0];
                    var placeholderPath  = prefabPath + "/" + placeholder.name + ".prefab";
                    var placeholderAsset = PrefabUtility.SaveAsPrefabAsset(placeholder, placeholderPath);
                    prefabs[name_parts[0]] = new CachedPrefab(placeholderAsset);
                    EP.Destroy(placeholder);
                    //Debug.Log($"Missing prefab in {gameObject.name} for {child.Path()} -> created placeholder");
                }

                ConfigurePrefab(child.transform, prefabs[name_parts[0]]);
                EP.Destroy(child);
            }
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Configure the light probe proxy volume for lower levels of detail in group
        /// </summary>
        public static void ConfigureLODGroup(LODGroup lodGroup)
        {
            // TODO: Check if group is static - if not, also configure the lowest level of detail.

            // Only lower levels of detail will use probes
            // IMPORTANT: Proxy volumes always update, so only generate them if they will be used.
            var lods = lodGroup.GetLODs();

            if (lods.Length < 2)
            {
                return;
            }

            // FIXME: Local bounds are needed
            var worldBounds = RendererWorldBounds(lodGroup.gameObject);
            // TEMP: Assume that only axis swaps pertain
            var localBounds = new Bounds();

            localBounds.center = lodGroup.transform.InverseTransformPoint(worldBounds.center);
            localBounds.size   = lodGroup.transform.InverseTransformDirection(worldBounds.size);
            localBounds.size   = new Vector3(Mathf.Abs(localBounds.size.x), Mathf.Abs(localBounds.size.y), Mathf.Abs(localBounds.size.z));

            // If object bounds > probe spacing in any dimension use a proxy volume
            var useProxy = false;

            for (var i = 0; i < 3; ++i)
            {
                useProxy |= localBounds.size[i] > probeSpaces[i];
            }
            var proxy = lodGroup.gameObject.GetComponent <LightProbeProxyVolume>();

            if (useProxy)
            {
                if (!proxy)
                {
                    proxy = EP.AddComponent <LightProbeProxyVolume>(lodGroup.gameObject);
                }

                // Configure proxy bounds
                proxy.boundingBoxMode = LightProbeProxyVolume.BoundingBoxMode.Custom;
                proxy.originCustom    = localBounds.center;
                proxy.sizeCustom      = localBounds.size;

                // Configure spacing
                proxy.probePositionMode = LightProbeProxyVolume.ProbePositionMode.CellCorner;
                proxy.resolutionMode    = LightProbeProxyVolume.ResolutionMode.Custom;
                proxy.gridResolutionX   = ProxyResolution(localBounds.size.x / probeSpaces.x);
                proxy.gridResolutionY   = ProxyResolution(localBounds.size.y / probeSpaces.y);
                proxy.gridResolutionZ   = ProxyResolution(localBounds.size.z / probeSpaces.z);

                // Remaining settings
                proxy.qualityMode = LightProbeProxyVolume.QualityMode.Normal;
                proxy.refreshMode = LightProbeProxyVolume.RefreshMode.Automatic;
            }
            else
            {
                if (proxy)
                {
                    EP.Destroy(proxy);
                }
                proxy = null;
            }

            // Configure all lower levels of detail to use probes
            for (var l = 1; l < lods.Length; ++l)
            {
                foreach (var renderer in lods[l].renderers)
                {
                    renderer.lightProbeUsage = useProxy ? UnityEngine.Rendering.LightProbeUsage.UseProxyVolume : UnityEngine.Rendering.LightProbeUsage.BlendProbes;
                    renderer.lightProbeProxyVolumeOverride = lodGroup.gameObject;

                    var meshRender = renderer as MeshRenderer;
                    if (!meshRender)
                    {
                        continue;
                    }
                    meshRender.receiveGI = ReceiveGI.LightProbes;
                }
            }
        }
Ejemplo n.º 12
0
        static void MergeGroup(string pathName, List <GameObject> group)
        {
            // Gather LODGroup and Renderers
            LODGroup lodGroup  = null;
            var      renderers = new List <RendererSort>();

            foreach (var gameObject in group)
            {
                var renderer = gameObject.GetComponent <Renderer>();
                if (renderer)
                {
                    renderers.Add(new RendererSort(renderer));
                }
                var isGroup = gameObject.GetComponent <LODGroup>();
                if (isGroup)
                {
                    var lods = isGroup.GetLODs();
                    foreach (var lod in lods)
                    {
                        foreach (var lodRenderer in lod.renderers)
                        {
                            // Renderers must begin as siblings of LODGroup
                            EP.SetParent(lodRenderer.transform, isGroup.transform.parent);
                            renderers.Add(new RendererSort(lodRenderer));
                        }
                    }

                    if (!!lodGroup || !!renderer)
                    {
                        // LODGroup manager cannot be duplicated, and cannot have renderer component
                        Debug.LogWarning($"Removing LODGroup found on {gameObject.Path()}");
                        EP.Destroy(isGroup);
                        continue;
                    }
                    lodGroup = isGroup;
                }
            }
            if (!lodGroup)
            {
                lodGroup = EP.AddComponent <LODGroup>(EP.Instantiate());
            }

            // renderers[0] has the lowest vertex count
            renderers.Sort((l, m) => l.vertexCount - m.vertexCount);
            // Remove missing meshes and duplicate levels of detail
            var vertexCount     = 0;
            var removeRenderers = new List <RendererSort>();

            foreach (var renderer in renderers)
            {
                if (vertexCount == renderer.vertexCount)
                {
                    removeRenderers.Add(renderer);
                    continue;
                }
                vertexCount = renderer.vertexCount;
            }
            foreach (var renderer in removeRenderers)
            {
                renderers.Remove(renderer);
                EP.Destroy(renderer.renderer.gameObject);
                // NOTE: Duplicate mesh asset could be removed
            }
            if (renderers.Count == 0)
            {
                EP.Destroy(lodGroup.gameObject);
                return;
            }
            // renderers[0] has the highest vertrex count
            renderers.Reverse();

            // Configure manager in hierarchy
            lodGroup.gameObject.name = pathName.Substring(pathName.LastIndexOf('/') + 1);
            EP.SetParent(lodGroup.transform, renderers[0].renderer.transform.parent);
            lodGroup.transform.localPosition = renderers[0].renderer.transform.localPosition;
            lodGroup.transform.localRotation = renderers[0].renderer.transform.localRotation;
            lodGroup.transform.localScale    = renderers[0].renderer.transform.localScale;
            for (var r = 0; r < renderers.Count; ++r)
            {
                var renderer = renderers[r].renderer;
                // TODO: Used PathNameExtension for this!
                var lodIndex = renderer.gameObject.name.LastIndexOf(lodSuffix);
                if (lodIndex >= 0)
                {
                    renderer.gameObject.name = renderer.gameObject.name.Substring(0, lodIndex);
                }
                renderer.gameObject.name += lodSuffix + r.ToString();
                EP.SetParent(renderer.transform, lodGroup.transform);
            }

            // Configure the group
            var lodList = new LOD[renderers.Count];

            for (var r = 0; r < renderers.Count; ++r)
            {
                lodList[r].renderers = new[] { renderers[r].renderer }
            }
            ;
            ConfigureLODGroup(lodGroup, lodList);

            // Configure the renderers and materials
            foreach (var lod in lodGroup.GetLODs())
            {
                foreach (var renderer in lod.renderers)
                {
                    SetLightmapScale(renderer);
                    foreach (var material in renderer.sharedMaterials)
                    {
                        UseFadingShader(material);
                    }
                }
            }
        }
Ejemplo n.º 13
0
        // Derive light configuration from a placeholder
        public static void ConfigureLight(Light light)
        {
            var       children    = light.transform.parent.Children();
            Transform placeholder = null;

            foreach (var child in children)
            {
                // NOTE: Use string.Contains since
                if (!child.name.Contains("=" + light.name))
                {
                    continue;
                }
                placeholder = child;
                break;
            }
            if (!placeholder)
            {
                Debug.LogWarning($"Missing placeholder for {light.Path()}");
                return;
            }

            // Apply rigid transform
            light.transform.localPosition = placeholder.localPosition;
            light.transform.localRotation = placeholder.localRotation;
            light.transform.localScale    = Vector3.one;

            // Configure using transform local scale parameters
            // NOTE: Disk Lights are not exported in Rhino
            // NOTE: Volume light type are not imported to Unity, and is not exported by Rhino
            var lightType = placeholder.name.Split('=')[0];

            switch (lightType)
            {
            case "DirectionalLight":
                light.type = LightType.Directional;
                break;

            case "PointLight":
                light.type = LightType.Point;
                break;

            case "SpotLight":
                light.type           = LightType.Spot;
                light.spotAngle      = Mathf.Atan2(placeholder.localScale.z, placeholder.localScale.x) * Mathf.Rad2Deg * 2f;
                light.innerSpotAngle = Mathf.Atan2(placeholder.localScale.z, placeholder.localScale.y) * Mathf.Rad2Deg * 2f;
                break;

            case "RectangularLight":
                light.type     = LightType.Rectangle;
                light.areaSize = new Vector2(placeholder.localScale.x * 2f, placeholder.localScale.y * 2f);
                break;

            case "LinearLight":
                // NOTE: Linear lights are not supported in Unity
                // A subsequent conversion to a collection of finite-size rectangular lights will be required
                light.type     = LightType.Rectangle;
                light.areaSize = new Vector2(placeholder.localScale.x * 2f, 0f);
                break;

            default:
                Debug.LogWarning($"Unsupported light type {lightType} -> configure as point light");
                light.type    = LightType.Point;
                light.enabled = false;
                break;
            }

            EP.Destroy(placeholder.gameObject);
        }
Ejemplo n.º 14
0
        /// <summary>
        /// Copies asset of type T to specified persistent path
        /// </summary>
        /// <remarks>
        /// If the specified path does not exist, it will be created using EP.CreatePersistentPath.
        /// The asset name will be a unique derivative of the name of the asset object.
        /// If assetSuffix is not specified the suffix corresponding to the asset type will be used.
        ///
        /// WARNING: When AssetDatabase.StartAssetEditing() has been called, AssetDatabase.CopyAsset
        /// and PrefabUtility.SaveAsPrefabAsset will not modify AssetDatabase, in which case the returned
        /// asset reference will be the asset argument.
        /// </remarks>
        /// <typeparam name="T">Asset type</typeparam>
        /// <param name="asset">Asset to be copied</param>
        /// <param name="assetPath">Path relative to persistent folder where asset will be created</param>
        /// <param name="assetSuffix">Optional override of default suffix for asset type</param>
        /// <returns>The asset copy, or the origin of AssetDatabase has not been updated</returns>
        public static T CopyAssetToPath <T>(T asset, string assetPath, string assetSuffix = null) where T : Object
        {
            // Ensure that the asset path folders exist
            EP.CreatePersistentPath(assetPath);
            T assetCopy = null;

            if (useEditorAction)
            {
#if UNITY_EDITOR
                // Match asset and file type, and ensure that asset will be unique
                if (assetSuffix == null)
                {
                    assetSuffix = ".asset";
                    var oldPath = AssetDatabase.GetAssetPath(asset);
                    if (oldPath != null)
                    {
                        // Preserve existing file type (important for images and models)
                        assetSuffix = oldPath.Substring(oldPath.LastIndexOf('.'));
                    }
                    else
                    {
                        // Use default file type for asset type
                        // https://docs.unity3d.com/ScriptReference/AssetDatabase.CreateAsset.html
                        if (asset is GameObject)
                        {
                            assetSuffix = ".prefab";
                        }
                        if (asset is Material)
                        {
                            assetSuffix = ".mat";
                        }
                        if (asset is Cubemap)
                        {
                            assetSuffix = ".cubemap";
                        }
                        if (asset is GUISkin)
                        {
                            assetSuffix = ".GUISkin";
                        }
                        if (asset is Animation)
                        {
                            assetSuffix = ".anim";
                        }
                        // TODO: Cover other specialized types such as giparams
                    }
                }
                var newPath = "Assets/" + assetPath + "/" + asset.name + assetSuffix;
                newPath = AssetDatabase.GenerateUniqueAssetPath(newPath);
                // WARNING: (Unity2019.4 undocumented) AssetDatabase.GenerateUniqueAssetPath will trim name spaces

                // Copy asset to new path
                // NOTE: The goal is to keep the original asset in place,
                // so AssetDatabase.ExtractAsset is not used.
                if (asset is GameObject)
                {
                    var gameObject = asset as GameObject;
                    if (PrefabUtility.GetPrefabAssetType(gameObject) == PrefabAssetType.NotAPrefab)
                    {
                        assetCopy = PrefabUtility.SaveAsPrefabAsset(gameObject, newPath) as T;
                    }
                    else
                    {
                        // NOTE: PrefabUtility.SaveAsPrefabAsset cannot save an asset reference as a prefab
                        var copyObject = PrefabUtility.InstantiatePrefab(gameObject) as GameObject;
                        // NOTE: Root must be unpacked, otherwise this will yield a prefab variant
                        PrefabUtility.UnpackPrefabInstance(copyObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
                        assetCopy = PrefabUtility.SaveAsPrefabAsset(copyObject, newPath) as T;
                        EP.Destroy(copyObject);
                    }
                }
                else
                {
                    // IMPORTANT: SubAssets must be instantiated, otherise AssetDatabase.CreateAsset(asset, newPath) will fail
                    // with error: "Couldn't add object to asset file because the Mesh [] is already an asset at [].fbx"
                    // NOTE: AssetDatabase.ExtractAsset will register as a modification of model import settings
                    if (AssetDatabase.IsSubAsset(asset))
                    {
                        AssetDatabase.CreateAsset(Object.Instantiate(asset), newPath);
                    }
                    else
                    {
                        AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(asset), newPath);
                    }
                }
                assetCopy = AssetDatabase.LoadAssetAtPath <T>(newPath);
#endif
            }
            else
            {
                Debug.LogWarning("Runtime asset copying is not implemented");
                // TODO: For runtime version simply copy the file!
            }

            if (!assetCopy)
            {
                return(asset);                        // Continue to use asset at previous path
            }
            return(assetCopy as T);
        }