public void CopyMeshes(MeshFilter meshFilter) { var mesh = meshFilter.sharedMesh; reduceMeshName(mesh); if ( meshAssets.ContainsKey(mesh.name) && meshAssets[mesh.name].ContainsKey(mesh.vertexCount) ) { return; } // IMPORTANT: Default meshes may be in use and are not in Assets path if (!AssetDatabase.GetAssetPath(mesh).StartsWith("Assets/")) { return; } expandMeshName(mesh); var copyMesh = EP.CopyAssetToPath(mesh, meshPath.Substring("Assets/".Length), ".asset"); reduceMeshName(mesh); if (!meshAssets.ContainsKey(mesh.name)) { meshAssets.Add(mesh.name, new Dictionary <int, Mesh>()); } meshAssets[mesh.name].Add(mesh.vertexCount, copyMesh); }
static void AddCollider(GameObject gameObject, bool hasPhysics) { // If no mesh is defined skip this GameObject var meshFilter = gameObject.GetComponent <MeshFilter>(); var sharedMesh = meshFilter ? meshFilter.sharedMesh : null; if (!sharedMesh) { return; } var colliderTarget = gameObject; var lodGroup = gameObject.GetComponentInParent <LODGroup>(); if (lodGroup) { colliderTarget = lodGroup.gameObject; } // If target already has colliders do not modify if (colliderTarget.GetComponentsInChildren <Collider>().Length > 0) { return; } // Add a mesh collider to this object var meshCollider = EP.AddComponent <MeshCollider>(colliderTarget); meshCollider.sharedMesh = sharedMesh; meshCollider.convex = hasPhysics; }
// PROBLEM: Light source meshes are generally small and could use a lower level of detail // in most cases. Prefabs with custom meshes could address this for sphere and cylinder // OPTION: Provide a component to monitor the light intensity and update the emissive material // accordingly - both static and dynamic. This could also tag actual child light sources. public static GameObject CreatePrimitiveSource(Light light, PrimitiveType primitiveType) { var source = GameObject.CreatePrimitive(primitiveType); source.SetActive(light.enabled); source.layer = light.gameObject.layer; // Make source constitent with search source.name = ConfigureName(light.name); EP.SetParent(source.transform, light.transform); // Position source around light source.transform.localPosition = Vector3.zero; source.transform.localRotation = Quaternion.identity; // Source scale depends on light type Object.DestroyImmediate(source.GetComponent <Collider>()); if (light.gameObject.isStatic) { var staticFlags = (StaticEditorFlags) ~0; staticFlags &= ~StaticEditorFlags.OccluderStatic; staticFlags &= ~StaticEditorFlags.ContributeGI; GameObjectUtility.SetStaticEditorFlags(source, staticFlags); } else { source.isStatic = false; } LightSourceMeshRenderer(light, source.GetComponent <MeshRenderer>()); Undo.RegisterCreatedObjectUndo(source, "Create Primitive Light Source"); return(source); }
// PROBLEM: If multiple intermediate levels of the hierarchy have the same name // then children will be randomly assigned. This could happen if Av0 has children, // Av1 does not and is added, but then Av2 has children, which could be parented to // either Av1 or Av0. Or, if the original model uses a name multiple times. // TODO: Identify problem and warn. static void Merge(Transform mergeFrom, Transform mergeTo) { // When names match, merge var mergeChildren = new List <Transform>(); foreach (var childFrom in mergeFrom.Children()) { var childTo = mergeTo.NameFindInChildren(childFrom.name); if (childTo.Length == 0) { // ChildFrom is not in the hierarchy mergeChildren.Add(childFrom); continue; } if (PrefabUtility.GetPrefabAssetType(childFrom) != PrefabAssetType.NotAPrefab) { // ChildFrom is a Prefab and is already present in hierarchy since childTo != null continue; } if (childFrom.transform.childCount == 0) { // ChildFrom is a distinct instance even if childTo is present mergeChildren.Add(childFrom); continue; } // ChildFrom and ChildTo match, so merge children instead Merge(childFrom, childTo[0].transform); } foreach (var childFrom in mergeChildren) { EP.SetParent(childFrom, mergeTo); } }
public static float playerPositionStep = 1f; // meters static void CreatePlayer(Bounds sceneBounds) { var playerAsset = AssetDatabase.LoadAssetAtPath <GameObject>(playerPrefabPath); if (!playerAsset) { Debug.LogWarning($"Missing asset: {playerPrefabPath}"); return; } var player = EP.Instantiate(playerAsset); // Place the player // NOTE: This could be managed by a configuration component var position = Vector3.zero; position.y = sceneBounds.max.y + playerPositionStep; for (position.x = sceneBounds.min.x + playerPositionStep; position.x < sceneBounds.max.x - playerPositionStep; position.x += playerPositionStep) { for (position.z = sceneBounds.min.z + playerPositionStep; position.z < sceneBounds.max.z - playerPositionStep; position.z += playerPositionStep) { if (Physics.Raycast(position, Vector3.down, out var hitInfo, sceneBounds.size.y + playerPositionStep * 2f)) { player.transform.position = hitInfo.point; position = sceneBounds.max; // Break from both loops } } } }
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; } }
public MeshGatherer(string pathRoot) { meshPath = pathRoot + "/" + meshFolder; if (EP.CreatePersistentPath(meshPath.Substring("Assets/".Length), false) > 0) { return; } var meshGUIDs = AssetDatabase.FindAssets("t:Mesh", new[] { meshPath }); foreach (var guid in meshGUIDs) { var assetPath = AssetDatabase.GUIDToAssetPath(guid); var mesh = AssetDatabase.LoadAssetAtPath <Mesh>(assetPath); reduceMeshName(mesh); if (!meshAssets.ContainsKey(mesh.name)) { meshAssets.Add(mesh.name, new Dictionary <int, Mesh>()); } if (meshAssets[mesh.name].ContainsKey(mesh.vertexCount)) { Debug.LogWarning($"MeshGatherer repeated asset name: {mesh.name}({mesh.vertexCount}) at {assetPath} in {meshPath}"); continue; } meshAssets[mesh.name][mesh.vertexCount] = mesh; } }
// Model export generates meshes in world coordinates // In order to retain information, each prefab is replaced with a transformed tetrahedron static void ConfigurePrefab(Transform placeholder, CachedPrefab cached) { var prefab = (PrefabUtility.InstantiatePrefab(cached.prefab) as GameObject).transform; EP.SetParent(prefab, placeholder.parent); prefab.localPosition = placeholder.localPosition; prefab.localRotation = placeholder.localRotation; prefab.localScale = placeholder.localScale; prefab.name = placeholder.name; }
// 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); }
// TODO: This, like a grid, is a standard layout // IDEA: When TransformData is included, provide utilities to generate layouts, and to replicate gameobjects over them static GameObject[] RotateCopies(GameObject original, Quaternion rotation, int count) { var copyList = new GameObject[count]; copyList[0] = original; for (int c = 1; c < count; ++c) { var copy = EP.Instantiate(original); EP.SetParent(copy.transform, original.transform.parent); copy.transform.localPosition = rotation * copyList[c - 1].transform.localPosition; copy.transform.localRotation = rotation * copyList[c - 1].transform.localRotation; copyList[c] = copy; } return(copyList); }
/// <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); } } }
// TODO: ExtractTextures should support list parameters for batched importing /// <summary> /// Extracts and remaps textures for use by materials /// </summary> /// <returns>True if reimport is requied</returns> /// <remarks> /// IMPORTANT: In order for extracted textures to be remapped to materials /// this must be called when AssetDatabase.StartAssetEditing() does not pertain /// so that textures can be synchronously imported for remapping. /// /// WARNING: In order to update model materials the model must be remiported: /// AssetDatabase.ImportAsset(modelPath) /// /// For the implementation of the "Extract Textures" button /// in the "Materials" tab of the "Import Settings" Inspector panel, see: /// https://github.com/Unity-Technologies/UnityCsReference/ /// Modules/AssetPipelineEditor/ImportSettings/ModelImporterMaterialEditor.cs /// private void ExtractTexturesGUI() /// </remarks> public static bool ExtractTextures(string modelPath) { var modelImporter = AssetImporter.GetAtPath(modelPath) as ModelImporter; if (modelImporter == null) { return(false); } // Extract textures var texturesPath = modelPath.Substring(0, modelPath.LastIndexOf('.')) + "/Textures"; var success = false; // success, not extraction count try { AssetDatabase.StartAssetEditing(); success = modelImporter.ExtractTextures(texturesPath); } finally { AssetDatabase.StopAssetEditing(); AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); // Import textures to AssetDatabase } // If no textures were imported remove folder & skip the reimport // NOTE: ExtractTextures will only create the texturesPath if there are textures to be extracted if (!success || EP.CreatePersistentPath(texturesPath.Substring("Assets/".Length), false) > 0) { return(false); } // Remap textures and reimport model // NOTE: Remapping will fail while StartAssetEditing() pertains (during model import) // since extracted textures will not be immediately imported, and so will not be found. var guids = AssetDatabase.FindAssets("t:Texture", new string[] { texturesPath }); foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); var texture = AssetDatabase.LoadAssetAtPath <Texture>(path); if (texture == null) { continue; } var identifier = new AssetImporter.SourceAssetIdentifier(texture); modelImporter.AddRemap(identifier, texture); } return(true); }
public static LightProbeGroup CreateLightProbes(GameObject gameObject) { // Light probe positions are evaluated relative to LightProbeGroup // https://docs.unity3d.com/ScriptReference/LightProbeGroup-probePositions.html var group = gameObject.GetComponent <LightProbeGroup>(); if (!group) { group = EP.AddComponent <LightProbeGroup>(gameObject); } // IDEA: Find child LightProbeGroups and exclude probes in those volumes // This would enable adjusting the probe density in different areas. // TODO: Enable configuration to use other placement strategies group.probePositions = LightProbeGrid(gameObject, probeSpaces); return(group); }
static void CreateLinearYSource(Light light) { // Create a self-illuminated cylinder var source = CreatePrimitiveSource(light, PrimitiveType.Cylinder); source.transform.localScale = new Vector3(pointDiameter, light.areaSize.y / 2f, pointDiameter); // Create planes covering all emission directions var side0 = MakeAreaCopy(light, new Vector2(pointDiameter, light.areaSize.y)); EP.SetParent(side0.transform, light.transform); var sideList = RotateCopies(side0, Quaternion.Euler(360f / linearSources, 0f, 0f), linearSources); for (int s = 0; s < sideList.Length; ++s) { sideList[s].name = light.name + "_Side" + s; } }
/// <summary> /// Make a copy of light source as area light, with equivalent illumination /// </summary> /// <remarks> /// Intensity is scaled relative to the area of the light /// </remarks> public static GameObject MakeAreaCopy(Light light, Vector2 areaSize) { var gameObject = EP.Instantiate(); GameObjectUtility.SetStaticEditorFlags(gameObject, (StaticEditorFlags) ~0); EP.SetParent(gameObject.transform, light.transform.parent); gameObject.transform.localPosition = light.transform.localPosition; gameObject.transform.localRotation = light.transform.localRotation; var areaLight = EP.AddComponent <Light>(gameObject); areaLight.lightmapBakeType = LightmapBakeType.Baked; areaLight.type = LightType.Rectangle; areaLight.areaSize = areaSize; areaLight.intensity = light.intensity / (areaSize.x * areaSize.y); areaLight.color = light.color; areaLight.range = light.range; return(gameObject); }
/// <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; } }
// 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); } }
// 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); }
static void CreateSun(Bounds sceneBounds) { var sunAsset = AssetDatabase.LoadAssetAtPath <GameObject>(sunPrefabPath); if (!sunAsset) { Debug.LogWarning($"Missing asset: {sunPrefabPath}"); return; } var sun = EP.Instantiate(sunAsset); var light = sun.GetComponentInChildren <Light>(); RenderSettings.sun = light; // Position the sun source outside of the model // NOTE: This could be managed by a configuration component sun.transform.position = sceneBounds.center; light.transform.localPosition = new Vector3(0f, 0f, -sceneBounds.extents.magnitude); }
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); } } }
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); }
public MaterialGatherer(string pathRoot) { materialPath = pathRoot + "/" + materialFolder; if (EP.CreatePersistentPath(materialPath.Substring("Assets/".Length), false) > 0) { return; } var materialGUIDs = AssetDatabase.FindAssets("t:Material", new[] { materialPath }); foreach (var guid in materialGUIDs) { var assetPath = AssetDatabase.GUIDToAssetPath(guid); var material = AssetDatabase.LoadAssetAtPath <Material>(assetPath); if (materialAssets.ContainsKey(material.name)) { Debug.LogWarning($"MaterialGatherer repeated asset name: {material.name} at {assetPath} in {materialPath}"); continue; } materialAssets.Add(material.name, material); } }
public TextureGatherer(string pathRoot) { texturePath = pathRoot + "/" + textureFolder; if (EP.CreatePersistentPath(texturePath.Substring("Assets/".Length), false) > 0) { return; } var textureGUIDs = AssetDatabase.FindAssets("t:Texture", new[] { texturePath }); foreach (var guid in textureGUIDs) { var assetPath = AssetDatabase.GUIDToAssetPath(guid); var texture = AssetDatabase.LoadAssetAtPath <Texture>(assetPath); if (textureAssets.ContainsKey(texture.name)) { Debug.LogWarning($"TextureGatherer repeated asset name: {texture.name} at {assetPath} in {texturePath}"); continue; } textureAssets.Add(texture.name, texture); } }
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); } }
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); } }
public void CopyMaterials(Renderer renderer) { var sharedMaterials = renderer.sharedMaterials; for (var m = 0; m < sharedMaterials.Length; ++m) { var material = sharedMaterials[m]; if (!material) { continue; } if (materialAssets.ContainsKey(material.name)) { continue; } // IMPORTANT: Default materials may be in use and are not in Assets path if (!AssetDatabase.GetAssetPath(material).StartsWith("Assets/")) { continue; } var copyMaterial = EP.CopyAssetToPath(material, materialPath.Substring("Assets/".Length), ".mat"); materialAssets.Add(material.name, copyMaterial); } }
public void CopyTextures(Material material) { if (material.shader.name != "Standard") { return; } var shaderTextures = GetStandardMaterialTextures(material); foreach (var shaderTexture in shaderTextures) { var texture = shaderTexture.Value; if (textureAssets.ContainsKey(texture.name)) { continue; } // IMPORTANT: Default textures may be in use and are not in Assets path if (!AssetDatabase.GetAssetPath(texture).StartsWith("Assets/")) { continue; } var copyTexture = EP.CopyAssetToPath(texture, texturePath.Substring("Assets/".Length)); textureAssets.Add(texture.name, copyTexture); } }
/// <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; } } }
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); } } } }
public RePort() { // Ensure that import path exists EP.CreatePersistentPath(importPath.Substring("Assets/".Length)); }