diff --git a/Assets/Content/Resources/Simple Toon/Shaders/ObjectIcon.shader b/Assets/Content/Resources/Simple Toon/Shaders/ObjectIcon.shader new file mode 100644 index 0000000000..bfd581d258 --- /dev/null +++ b/Assets/Content/Resources/Simple Toon/Shaders/ObjectIcon.shader @@ -0,0 +1,79 @@ +Shader "Unlit/ObjectIcon" +{ + Properties + { + _MainTex ("Base (RGB)", 2D) = "white" {} + _EmissionMap ("Emission Map", 2D) = "black" {} + [HDR]_EmissionColor ("Emission Color", Color) = (0,0,0,0) + + } + + SubShader + { + Pass + { + Tags{ "RenderType" = "Opaque" "Queue" = "Opaque" } + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma target 2.0 + #pragma multi_compile_fog + + #include "UnityCG.cginc" + + struct appdata_t { + float4 vertex : POSITION; + float2 texcoord : TEXCOORD0; + float3 normal : NORMAL; + }; + + struct v2f { + float4 vertex : SV_POSITION; + float2 texcoord : TEXCOORD0; + half3 worldNormal : TEXCOORD1; + float4 worldPos : TEXCOORD2; + float3 viewDir : TEXCOORD3; + }; + + sampler2D _MainTex; + float4 _MainTex_ST; + sampler2D _EmissionMap; + float4 _EmissionColor; + + v2f vert (appdata_t v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex); + o.worldNormal = UnityObjectToWorldNormal(v.normal); + o.worldPos = mul(unity_ObjectToWorld, v.vertex); + o.viewDir = WorldSpaceViewDir(v.vertex); + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + float3 viewDir = normalize(i.viewDir); + fixed4 texColor = tex2D(_MainTex, i.texcoord); + fixed4 emission = tex2D(_EmissionMap, i.texcoord) * _EmissionColor; + + float3 lightVector = (float3(-0.258, 0.22, 0.966)); + + float NdotL = dot(i.worldNormal, lightVector); + float light = (smoothstep(0, 0.75, NdotL) * 0.525 + 0.475); + float4 shadow = float4(0,0,0.04f,0) * (1 - light); + + +// float4 rimDot = (1 - dot(viewDir, i.worldNormal)); +// rimDot = (rimDot * rimDot * rimDot) * 0.15; + + fixed3 color = texColor.rgb * light + shadow + emission; + fixed4 outcolor; + outcolor.rgb = color.rgb; + outcolor.a = texColor.a; + return outcolor; + } + ENDCG + } + } +} \ No newline at end of file diff --git a/Assets/Content/Resources/Simple Toon/Shaders/ObjectIcon.shader.meta b/Assets/Content/Resources/Simple Toon/Shaders/ObjectIcon.shader.meta new file mode 100644 index 0000000000..c88368b544 --- /dev/null +++ b/Assets/Content/Resources/Simple Toon/Shaders/ObjectIcon.shader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 3370ec8ee4f6a4b45b61c825484a6a3d +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/SS3D/Systems/Inventory/Items/Item.cs b/Assets/Scripts/SS3D/Systems/Inventory/Items/Item.cs index 662fad162e..d1a35b1d75 100644 --- a/Assets/Scripts/SS3D/Systems/Inventory/Items/Item.cs +++ b/Assets/Scripts/SS3D/Systems/Inventory/Items/Item.cs @@ -195,13 +195,12 @@ public void SetVisibility(bool visible) { // TODO: Make this handle multiple renderers, with different states Renderer[] renderers = GetComponentsInChildren(); - foreach (Renderer childRenderer in renderers) { childRenderer.enabled = visible; } } - + public bool IsVisible() { // TODO: Make this handle multiple renderers @@ -305,15 +304,15 @@ public Sprite GenerateIcon() { storedItem.parent = null; } - + Transform previewObject = Instantiate(transform, null, false); previewObject.gameObject.hideFlags = HideFlags.HideAndDontSave; - + previewObject.GetComponent().SetVisibility(true); Sprite icon; try { Texture2D texture = RuntimePreviewGenerator.GenerateModelPreviewWithShader(previewObject, - Shader.Find("Legacy Shaders/Diffuse"), null, 128, 128, false, true); + Shader.Find("Legacy Shaders/Diffuse"), null, 128, 128); icon = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 100); icon.name = transform.name; diff --git a/Assets/Scripts/SS3D/Systems/Tile/TileResourceLoader.cs b/Assets/Scripts/SS3D/Systems/Tile/TileResourceLoader.cs index cdec3859d7..809413189f 100644 --- a/Assets/Scripts/SS3D/Systems/Tile/TileResourceLoader.cs +++ b/Assets/Scripts/SS3D/Systems/Tile/TileResourceLoader.cs @@ -1,10 +1,7 @@ -using SS3D.Core.Behaviours; using SS3D.Logging; -using System; using System.Collections; using System.Collections.Generic; using System.Linq; -using UnityEditor; using UnityEngine; namespace SS3D.Systems.Tile @@ -31,43 +28,33 @@ private void LoadAssets() GenericObjectSo[] tempAssets = Resources.LoadAll(""); StartCoroutine(LoadAssetsWithIcon(tempAssets)); + //LoadAssetsWithIcon(tempAssets); } private IEnumerator LoadAssetsWithIcon(GenericObjectSo[] assets) { List tempIcons = new List(); - -#if UNITY_EDITOR + RuntimePreviewGenerator.OrthographicMode = true; foreach (var asset in assets) { - Texture2D texture = AssetPreview.GetAssetPreview(asset.prefab); - yield return new WaitUntil(() => AssetPreview.IsLoadingAssetPreview(asset.GetInstanceID()) == false); - - - if (texture == null) - { - // Unity is dumb, so we need to reload generated textures... - texture = AssetPreview.GetAssetPreview(asset.prefab); - } + Texture2D texture = RuntimePreviewGenerator.GenerateModelPreviewWithShader(asset.prefab.transform, + Shader.Find("Unlit/ObjectIcon"), null, 128, 128, true, true); tempIcons.Add(texture); } -#endif - + for (int i = 0; i < assets.Length; i++) { -#if UNITY_EDITOR - - // If we reach this point... Give up and load a default texture instead if (tempIcons[i] != null) + { assets[i].icon = Sprite.Create(tempIcons[i], new Rect(0, 0, tempIcons[i].width, tempIcons[i].height), new Vector2(0.5f, 0.5f)); -#endif - if (assets[i].icon == null) + } + else + { assets[i].icon = _missingIcon; - + } _assets.Add(assets[i]); } - IsInitialized = true; yield return null; } diff --git a/Assets/Scripts/SS3D/Utils/RuntimePreviewGenerator.cs b/Assets/Scripts/SS3D/Utils/RuntimePreviewGenerator.cs index a9367b4563..43e9a1a121 100644 --- a/Assets/Scripts/SS3D/Utils/RuntimePreviewGenerator.cs +++ b/Assets/Scripts/SS3D/Utils/RuntimePreviewGenerator.cs @@ -3,552 +3,770 @@ using System; using System.Collections.Generic; using UnityEngine; +#if UNITY_2018_2_OR_NEWER +using UnityEngine.Rendering; +#endif using Object = UnityEngine.Object; -namespace SS3D.Utils +public static class RuntimePreviewGenerator { - // This handles generating icon previews - public static class RuntimePreviewGenerator + private class CameraSetup { - // Source: https://github.com/MattRix/UnityDecompiled/blob/master/UnityEngine/UnityEngine/Plane.cs - private struct ProjectionPlane + private Vector3 position; + private Quaternion rotation; + + private Color backgroundColor; + private bool orthographic; + private float orthographicSize; + private float nearClipPlane; + private float farClipPlane; + private float aspect; + private int cullingMask; + private CameraClearFlags clearFlags; + + private RenderTexture targetTexture; + + public void GetSetup( Camera camera ) { - private readonly Vector3 m_Normal; - private readonly float m_Distance; + position = camera.transform.position; + rotation = camera.transform.rotation; + + backgroundColor = camera.backgroundColor; + orthographic = camera.orthographic; + orthographicSize = camera.orthographicSize; + nearClipPlane = camera.nearClipPlane; + farClipPlane = camera.farClipPlane; + aspect = camera.aspect; + cullingMask = camera.cullingMask; + clearFlags = camera.clearFlags; + + targetTexture = camera.targetTexture; + } - public ProjectionPlane(Vector3 inNormal, Vector3 inPoint) + public void ApplySetup( Camera camera ) + { + camera.transform.position = position; + camera.transform.rotation = rotation; + + camera.backgroundColor = backgroundColor; + camera.orthographic = orthographic; + camera.orthographicSize = orthographicSize; + camera.aspect = aspect; + camera.cullingMask = cullingMask; + camera.clearFlags = clearFlags; + + // Assigning order or nearClipPlane and farClipPlane may matter because Unity clamps near to far and far to near + if( nearClipPlane < camera.farClipPlane ) { - m_Normal = Vector3.Normalize(inNormal); - m_Distance = -Vector3.Dot(inNormal, inPoint); + camera.nearClipPlane = nearClipPlane; + camera.farClipPlane = farClipPlane; } - - public Vector3 ClosestPointOnPlane(Vector3 point) + else { - float d = Vector3.Dot(m_Normal, point) + m_Distance; - return point - m_Normal * d; + camera.farClipPlane = farClipPlane; + camera.nearClipPlane = nearClipPlane; } - public float GetDistanceToPoint(Vector3 point) - { - float signedDistance = Vector3.Dot(m_Normal, point) + m_Distance; - if (signedDistance < 0f) - signedDistance = -signedDistance; - - return signedDistance; - } + camera.targetTexture = targetTexture; + targetTexture = null; } + } - private class CameraSetup - { - private Vector3 position; - private Quaternion rotation; + private const int PREVIEW_LAYER = 22; + private static Vector3 PREVIEW_POSITION = new Vector3( -250f, -250f, -250f ); + + private static Camera renderCamera; + private static readonly CameraSetup cameraSetup = new CameraSetup(); - private RenderTexture targetTexture; + private static readonly Vector3[] boundingBoxPoints = new Vector3[8]; + private static readonly Vector3[] localBoundsMinMax = new Vector3[2]; - private Color backgroundColor; - private bool orthographic; - private float orthographicSize; - private float nearClipPlane; - private float farClipPlane; - private float aspect; - private CameraClearFlags clearFlags; + private static readonly List renderersList = new List( 64 ); + private static readonly List layersList = new List( 64 ); + +#if DEBUG_BOUNDS + private static Material boundsDebugMaterial; +#endif - public void GetSetup(Camera camera) + private static Camera m_internalCamera = null; + private static Camera InternalCamera + { + get + { + if( m_internalCamera == null ) { - position = camera.transform.position; - rotation = camera.transform.rotation; - - targetTexture = camera.targetTexture; - - backgroundColor = camera.backgroundColor; - orthographic = camera.orthographic; - orthographicSize = camera.orthographicSize; - nearClipPlane = camera.nearClipPlane; - farClipPlane = camera.farClipPlane; - aspect = camera.aspect; - clearFlags = camera.clearFlags; + m_internalCamera = new GameObject( "ModelPreviewGeneratorCamera" ).AddComponent(); + m_internalCamera.enabled = false; + m_internalCamera.nearClipPlane = 0.01f; + m_internalCamera.cullingMask = 1 << PREVIEW_LAYER; + m_internalCamera.gameObject.hideFlags = HideFlags.HideAndDontSave; } - public void ApplySetup(Camera camera) - { - camera.transform.position = position; - camera.transform.rotation = rotation; + return m_internalCamera; + } + } - camera.targetTexture = targetTexture; + private static Camera m_previewRenderCamera; + public static Camera PreviewRenderCamera + { + get { return m_previewRenderCamera; } + set { m_previewRenderCamera = value; } + } - camera.backgroundColor = backgroundColor; - camera.orthographic = orthographic; - camera.orthographicSize = orthographicSize; - camera.nearClipPlane = nearClipPlane; - camera.farClipPlane = farClipPlane; - camera.aspect = aspect; - camera.clearFlags = clearFlags; + private static Vector3 m_previewDirection = new Vector3( -0.57735f, -0.57735f, -0.57735f ); // Normalized (-1,-1,-1) + public static Vector3 PreviewDirection + { + get { return m_previewDirection; } + set { m_previewDirection = value.normalized; } + } - targetTexture = null; - } - } + private static float m_padding; + public static float Padding + { + get { return m_padding; } + set { m_padding = Mathf.Clamp( value, -0.25f, 0.25f ); } + } - private const int PREVIEW_LAYER = 22; - private static Vector3 PREVIEW_POSITION = new Vector3(-1, 1, -1); + private static Color m_backgroundColor = new Color( 0.3f, 0.3f, 0.3f, 1f ); + public static Color BackgroundColor + { + get { return m_backgroundColor; } + set { m_backgroundColor = value; } + } - private static Camera renderCamera; - [SerializeField] - private static CameraSetup cameraSetup = new CameraSetup(); + private static bool m_orthographicMode = false; + public static bool OrthographicMode + { + get { return m_orthographicMode; } + set { m_orthographicMode = value; } + } - private static List renderersList = new List(64); - private static List layersList = new List(64); + private static bool m_useLocalBounds = false; + public static bool UseLocalBounds + { + get { return m_useLocalBounds; } + set { m_useLocalBounds = value; } + } - private static float aspect; - private static float minX, maxX, minY, maxY; - private static float maxDistance; + private static float m_renderSupersampling = 1f; + public static float RenderSupersampling + { + get { return m_renderSupersampling; } + set { m_renderSupersampling = Mathf.Max( value, 0.1f ); } + } - private static Vector3 boundsCenter; - private static ProjectionPlane projectionPlaneHorizontal, projectionPlaneVertical; + private static bool m_markTextureNonReadable = true; + public static bool MarkTextureNonReadable + { + get { return m_markTextureNonReadable; } + set { m_markTextureNonReadable = value; } + } -#if DEBUG_BOUNDS - private static List boundsDebugCubes = new List( 8 ); - private static Material boundsMaterial; -#endif + public static Texture2D GenerateMaterialPreview( Material material, PrimitiveType previewPrimitive, int width = 64, int height = 64 ) + { + return GenerateMaterialPreviewInternal( material, previewPrimitive, null, null, width, height ); + } - private static Camera m_internalCamera = null; - private static Camera InternalCamera - { - get - { - if (m_internalCamera == null) - { - m_internalCamera = new GameObject("ModelPreviewGeneratorCamera").AddComponent(); - m_internalCamera.enabled = false; - m_internalCamera.nearClipPlane = 0.01f; - m_internalCamera.cullingMask = 1 << PREVIEW_LAYER; - m_internalCamera.gameObject.hideFlags = HideFlags.HideAndDontSave; - } + public static Texture2D GenerateMaterialPreviewWithShader( Material material, PrimitiveType previewPrimitive, Shader shader, string replacementTag, int width = 64, int height = 64 ) + { + return GenerateMaterialPreviewInternal( material, previewPrimitive, shader, replacementTag, width, height ); + } - return m_internalCamera; - } - } +#if UNITY_2018_2_OR_NEWER + public static void GenerateMaterialPreviewAsync( Action callback, Material material, PrimitiveType previewPrimitive, int width = 64, int height = 64 ) + { + GenerateMaterialPreviewInternal( material, previewPrimitive, null, null, width, height, callback ); + } - private static Camera m_previewRenderCamera; - public static Camera PreviewRenderCamera - { - get { return m_previewRenderCamera; } - set { m_previewRenderCamera = value; } - } + public static void GenerateMaterialPreviewWithShaderAsync( Action callback, Material material, PrimitiveType previewPrimitive, Shader shader, string replacementTag, int width = 64, int height = 64 ) + { + GenerateMaterialPreviewInternal( material, previewPrimitive, shader, replacementTag, width, height, callback ); + } +#endif - private static Vector3 m_previewDirection; - public static Vector3 PreviewDirection - { - get { return m_previewDirection; } - set { m_previewDirection = value.normalized; } - } +#if UNITY_2018_2_OR_NEWER + private static Texture2D GenerateMaterialPreviewInternal( Material material, PrimitiveType previewPrimitive, Shader shader, string replacementTag, int width, int height, Action asyncCallback = null ) +#else + private static Texture2D GenerateMaterialPreviewInternal( Material material, PrimitiveType previewPrimitive, Shader shader, string replacementTag, int width, int height ) +#endif + { + GameObject previewModel = GameObject.CreatePrimitive( previewPrimitive ); + previewModel.gameObject.hideFlags = HideFlags.HideAndDontSave; + previewModel.GetComponent().sharedMaterial = material; - private static float m_padding; - public static float Padding + try { - get { return m_padding; } - set { m_padding = Mathf.Clamp(value, -0.25f, 0.25f); } +#if UNITY_2018_2_OR_NEWER + return GenerateModelPreviewInternal( previewModel.transform, shader, replacementTag, width, height, false, true, asyncCallback ); +#else + return GenerateModelPreviewInternal( previewModel.transform, shader, replacementTag, width, height, false, true ); +#endif } - - private static Color m_backgroundColor; - public static Color BackgroundColor + catch( Exception e ) { - get { return m_backgroundColor; } - set { m_backgroundColor = value; } + Debug.LogException( e ); } - - private static bool m_orthographicMode; - public static bool OrthographicMode + finally { - get { return m_orthographicMode; } - set { m_orthographicMode = value; } + Object.DestroyImmediate( previewModel ); } - private static bool m_markTextureNonReadable; - public static bool MarkTextureNonReadable - { - get { return m_markTextureNonReadable; } - set { m_markTextureNonReadable = value; } - } + return null; + } - static RuntimePreviewGenerator() - { - PreviewRenderCamera = null; - PreviewDirection = new Vector3(-1f, -1f, -1f); - Padding = 0f; - BackgroundColor = new Color(0.3f, 0.3f, 0.3f, 1f); - OrthographicMode = false; - MarkTextureNonReadable = true; + public static Texture2D GenerateModelPreview( Transform model, int width = 64, int height = 64, bool shouldCloneModel = false, bool shouldIgnoreParticleSystems = true ) + { + return GenerateModelPreviewInternal( model, null, null, width, height, shouldCloneModel, shouldIgnoreParticleSystems ); + } -#if DEBUG_BOUNDS - boundsMaterial = new Material( Shader.Find( "Legacy Shaders/Diffuse" ) ) - { - hideFlags = HideFlags.HideAndDontSave, - color = new Color( 0f, 1f, 1f, 1f ) - }; + public static Texture2D GenerateModelPreviewWithShader( Transform model, Shader shader, string replacementTag, int width = 64, int height = 64, bool shouldCloneModel = false, bool shouldIgnoreParticleSystems = true ) + { + return GenerateModelPreviewInternal( model, shader, replacementTag, width, height, shouldCloneModel, shouldIgnoreParticleSystems ); + } + +#if UNITY_2018_2_OR_NEWER + public static void GenerateModelPreviewAsync( Action callback, Transform model, int width = 64, int height = 64, bool shouldCloneModel = false, bool shouldIgnoreParticleSystems = true ) + { + GenerateModelPreviewInternal( model, null, null, width, height, shouldCloneModel, shouldIgnoreParticleSystems, callback ); + } + + public static void GenerateModelPreviewWithShaderAsync( Action callback, Transform model, Shader shader, string replacementTag, int width = 64, int height = 64, bool shouldCloneModel = false, bool shouldIgnoreParticleSystems = true ) + { + GenerateModelPreviewInternal( model, shader, replacementTag, width, height, shouldCloneModel, shouldIgnoreParticleSystems, callback ); + } #endif - } - public static Texture2D GenerateMaterialPreview(Material material, PrimitiveType previewObject, int width = 64, int height = 64) +#if UNITY_2018_2_OR_NEWER + private static Texture2D GenerateModelPreviewInternal( Transform model, Shader shader, string replacementTag, int width, int height, bool shouldCloneModel, bool shouldIgnoreParticleSystems, Action asyncCallback = null ) +#else + private static Texture2D GenerateModelPreviewInternal( Transform model, Shader shader, string replacementTag, int width, int height, bool shouldCloneModel, bool shouldIgnoreParticleSystems ) +#endif + { + if( !model ) { - return GenerateMaterialPreviewWithShader(material, previewObject, null, null, width, height); +#if UNITY_2018_2_OR_NEWER + if( asyncCallback != null ) + asyncCallback( null ); +#endif + + return null; } - public static Texture2D GenerateMaterialPreviewWithShader(Material material, PrimitiveType previewPrimitive, - Shader shader, string replacementTag, int width = 64, int height = 64) - { - GameObject previewModel = GameObject.CreatePrimitive(previewPrimitive); - previewModel.gameObject.hideFlags = HideFlags.HideAndDontSave; - previewModel.GetComponent().sharedMaterial = material; + Texture2D result = null; - try - { - return GenerateModelPreviewWithShader(previewModel.transform, shader, replacementTag, width, height, false); - } - catch (Exception e) - { - Debug.LogException(e); - } - finally - { - Object.DestroyImmediate(previewModel); - } + if( !model.gameObject.scene.IsValid() || !model.gameObject.scene.isLoaded ) + shouldCloneModel = true; - return null; + Transform previewObject; + if( shouldCloneModel ) + { + previewObject = (Transform) Object.Instantiate( model, null, false ); + previewObject.gameObject.hideFlags = HideFlags.HideAndDontSave; } - - public static Texture2D GenerateModelPreview(Transform model, int width = 64, int height = 64, bool shouldCloneModel = false) + else { + previewObject = model; - renderCamera = Camera.main; - return GenerateModelPreviewWithShader(model, null, null, width, height, shouldCloneModel); + layersList.Clear(); + GetLayerRecursively( previewObject ); } - /// - /// - /// - /// The transform of the game object we want to make a texture of. - /// The shader we want to apply on the model before generating the texture. - /// - /// Width of the texture. - /// Height of the texture. - /// If true, the clone can be freely changed without affecting the original model. - /// Activate all renderers on the gameObject before making a texture out of it. - /// - public static Texture2D GenerateModelPreviewWithShader(Transform model, Shader shader, string replacementTag, - int width = 64, int height = 64, bool shouldCloneModel = false, bool activateRenderers = false) - { - if (model == null || model.Equals(null)) - return null; + bool isStatic = IsStatic( model ); + bool wasActive = previewObject.gameObject.activeSelf; + Vector3 prevPos = previewObject.position; + Quaternion prevRot = previewObject.rotation; - Texture2D result = null; +#if UNITY_2018_2_OR_NEWER + bool asyncOperationStarted = false; +#endif - if (!model.gameObject.scene.IsValid() || !model.gameObject.scene.isLoaded) - shouldCloneModel = true; +#if DEBUG_BOUNDS + Transform boundsDebugCube = null; +#endif - Transform previewObject; - if (shouldCloneModel) + try + { + SetupCamera(); + SetLayerRecursively( previewObject ); + + if( !isStatic ) { - previewObject = (Transform)Object.Instantiate(model, null, false); - previewObject.gameObject.hideFlags = HideFlags.HideAndDontSave; + previewObject.position = PREVIEW_POSITION; + previewObject.rotation = Quaternion.identity; } - else + + if( !wasActive ) + previewObject.gameObject.SetActive( true ); + + Quaternion cameraRotation = Quaternion.LookRotation( previewObject.rotation * m_previewDirection, previewObject.up ); + Bounds previewBounds = new Bounds(); + if( !CalculateBounds( previewObject, shouldIgnoreParticleSystems, cameraRotation, out previewBounds ) ) { - previewObject = model; +#if UNITY_2018_2_OR_NEWER + if( asyncCallback != null ) + asyncCallback( null ); +#endif - layersList.Clear(); - GetLayerRecursively(previewObject); + return null; } - bool isStatic = IsStatic(model); - bool wasActive = previewObject.gameObject.activeSelf; - Vector3 prevPos = previewObject.position; - Quaternion prevRot = previewObject.rotation; +#if DEBUG_BOUNDS + if( !boundsDebugMaterial ) + { + boundsDebugMaterial = new Material( Shader.Find( "Sprites/Default" ) ) + { + hideFlags = HideFlags.HideAndDontSave, + color = new Color( 0.5f, 0.5f, 0.5f, 0.5f ) + }; + } + + boundsDebugCube = GameObject.CreatePrimitive( PrimitiveType.Cube ).transform; + boundsDebugCube.localPosition = previewBounds.center; + boundsDebugCube.localRotation = m_useLocalBounds ? cameraRotation : Quaternion.identity; + boundsDebugCube.localScale = previewBounds.size; + boundsDebugCube.gameObject.layer = PREVIEW_LAYER; + boundsDebugCube.gameObject.hideFlags = HideFlags.HideAndDontSave; + + boundsDebugCube.GetComponent().sharedMaterial = boundsDebugMaterial; +#endif + + renderCamera.aspect = (float) width / height; + renderCamera.transform.rotation = cameraRotation; + CalculateCameraPosition( renderCamera, previewBounds, m_padding ); + + renderCamera.farClipPlane = ( renderCamera.transform.position - previewBounds.center ).magnitude + ( m_useLocalBounds ? ( previewBounds.extents.z * 1.01f ) : previewBounds.size.magnitude ); + + RenderTexture activeRT = RenderTexture.active; + RenderTexture renderTexture = null; try { - SetupCamera(); - SetLayerRecursively(previewObject); + int supersampledWidth = Mathf.RoundToInt( width * m_renderSupersampling ); + int supersampledHeight = Mathf.RoundToInt( height * m_renderSupersampling ); - if (!isStatic) - { - previewObject.position = PREVIEW_POSITION; - previewObject.rotation = Quaternion.identity; - } + renderTexture = RenderTexture.GetTemporary( supersampledWidth, supersampledHeight, 16 ); + RenderTexture.active = renderTexture; + if( m_backgroundColor.a < 1f ) + GL.Clear( true, true, m_backgroundColor ); - if (!wasActive) - previewObject.gameObject.SetActive(true); + renderCamera.targetTexture = renderTexture; - Vector3 previewDir = previewObject.rotation * m_previewDirection; + if( !shader ) + renderCamera.Render(); + else + renderCamera.RenderWithShader( shader, replacementTag ?? string.Empty ); - renderersList.Clear(); - previewObject.GetComponentsInChildren(renderersList); + renderCamera.targetTexture = null; - Bounds previewBounds = new Bounds(); - bool init = false; - for (int i = 0; i < renderersList.Count; i++) + if( supersampledWidth != width || supersampledHeight != height ) { - if (!renderersList[i].enabled) + RenderTexture _renderTexture = null; + try { - if (activateRenderers) - { - renderersList[i].enabled = true; - } - else continue; - } + _renderTexture = RenderTexture.GetTemporary( width, height, 16 ); + RenderTexture.active = _renderTexture; + if( m_backgroundColor.a < 1f ) + GL.Clear( true, true, m_backgroundColor ); - if (!init) + Graphics.Blit( renderTexture, _renderTexture ); + } + finally { - previewBounds = renderersList[i].bounds; - init = true; + if( _renderTexture ) + { + RenderTexture.ReleaseTemporary( renderTexture ); + renderTexture = _renderTexture; + } } - else - previewBounds.Encapsulate(renderersList[i].bounds); } - if (!init) - return null; - - boundsCenter = previewBounds.center; - Vector3 boundsExtents = previewBounds.extents; - Vector3 boundsSize = 2f * boundsExtents; - - aspect = (float)width / height; - renderCamera.aspect = aspect; - renderCamera.transform.rotation = Quaternion.LookRotation(previewDir, previewObject.up); +#if UNITY_2018_2_OR_NEWER + if( asyncCallback != null ) + { + AsyncGPUReadback.Request( renderTexture, 0, m_backgroundColor.a < 1f ? TextureFormat.RGBA32 : TextureFormat.RGB24, ( asyncResult ) => + { + try + { + result = new Texture2D( width, height, m_backgroundColor.a < 1f ? TextureFormat.RGBA32 : TextureFormat.RGB24, false ); + if( !asyncResult.hasError ) + result.LoadRawTextureData( asyncResult.GetData() ); + else + { + Debug.LogWarning( "Async thumbnail request failed, falling back to conventional method" ); + + RenderTexture _activeRT = RenderTexture.active; + try + { + RenderTexture.active = renderTexture; + result.ReadPixels( new Rect( 0f, 0f, width, height ), 0, 0, false ); + } + finally + { + RenderTexture.active = _activeRT; + } + } + + result.Apply( false, m_markTextureNonReadable ); + asyncCallback( result ); + } + finally + { + if( renderTexture ) + RenderTexture.ReleaseTemporary( renderTexture ); + } + } ); -#if DEBUG_BOUNDS - boundsDebugCubes.Clear(); + asyncOperationStarted = true; + } + else #endif + { + result = new Texture2D( width, height, m_backgroundColor.a < 1f ? TextureFormat.RGBA32 : TextureFormat.RGB24, false ); + result.ReadPixels( new Rect( 0f, 0f, width, height ), 0, 0, false ); + result.Apply( false, m_markTextureNonReadable ); + } + } + finally + { + RenderTexture.active = activeRT; - float distance; - if (m_orthographicMode) + if( renderTexture ) { - renderCamera.transform.position = boundsCenter; - - minX = minY = Mathf.Infinity; - maxX = maxY = Mathf.NegativeInfinity; - - Vector3 point = boundsCenter + boundsExtents; - ProjectBoundingBoxMinMax(point); - point.x -= boundsSize.x; - ProjectBoundingBoxMinMax(point); - point.y -= boundsSize.y; - ProjectBoundingBoxMinMax(point); - point.x += boundsSize.x; - ProjectBoundingBoxMinMax(point); - point.z -= boundsSize.z; - ProjectBoundingBoxMinMax(point); - point.x -= boundsSize.x; - ProjectBoundingBoxMinMax(point); - point.y += boundsSize.y; - ProjectBoundingBoxMinMax(point); - point.x += boundsSize.x; - ProjectBoundingBoxMinMax(point); - - distance = boundsExtents.magnitude + 1f; - renderCamera.orthographicSize = (1f + m_padding * 2f) * Mathf.Max(maxY - minY, (maxX - minX) / aspect) * 0.5f; +#if UNITY_2018_2_OR_NEWER + if( !asyncOperationStarted ) +#endif + { + RenderTexture.ReleaseTemporary( renderTexture ); + } } - else + } + } + catch( Exception e ) + { + Debug.LogException( e ); + } + finally + { +#if DEBUG_BOUNDS + if( boundsDebugCube ) + Object.DestroyImmediate( boundsDebugCube.gameObject ); +#endif + + if( shouldCloneModel ) + Object.DestroyImmediate( previewObject.gameObject ); + else + { + if( !wasActive ) + previewObject.gameObject.SetActive( false ); + + if( !isStatic ) { - projectionPlaneHorizontal = new ProjectionPlane(renderCamera.transform.up, boundsCenter); - projectionPlaneVertical = new ProjectionPlane(renderCamera.transform.right, boundsCenter); - - maxDistance = Mathf.NegativeInfinity; - - Vector3 point = boundsCenter + boundsExtents; - CalculateMaxDistance(point); - point.x -= boundsSize.x; - CalculateMaxDistance(point); - point.y -= boundsSize.y; - CalculateMaxDistance(point); - point.x += boundsSize.x; - CalculateMaxDistance(point); - point.z -= boundsSize.z; - CalculateMaxDistance(point); - point.x -= boundsSize.x; - CalculateMaxDistance(point); - point.y += boundsSize.y; - CalculateMaxDistance(point); - point.x += boundsSize.x; - CalculateMaxDistance(point); - - distance = (1f + m_padding * 2f) * Mathf.Sqrt(maxDistance); + previewObject.position = prevPos; + previewObject.rotation = prevRot; } - renderCamera.transform.position = boundsCenter - previewDir * distance; - renderCamera.farClipPlane = distance * 4f; + int index = 0; + SetLayerRecursively( previewObject, ref index ); + } - RenderTexture temp = RenderTexture.active; - RenderTexture renderTex = RenderTexture.GetTemporary(width, height, 16); - RenderTexture.active = renderTex; - if (m_backgroundColor.a < 1f) - GL.Clear(false, true, m_backgroundColor); + if( renderCamera == m_previewRenderCamera ) + cameraSetup.ApplySetup( renderCamera ); + } - renderCamera.targetTexture = renderTex; +#if UNITY_2018_2_OR_NEWER + if( !asyncOperationStarted && asyncCallback != null ) + asyncCallback( null ); +#endif - if (shader == null) - renderCamera.Render(); - else - renderCamera.RenderWithShader(shader, replacementTag == null ? string.Empty : replacementTag); + return result; + } - renderCamera.targetTexture = null; + // Calculates AABB bounds of the target object (AABB will include its child objects) + public static bool CalculateBounds( Transform target, bool shouldIgnoreParticleSystems, Quaternion cameraRotation, out Bounds bounds ) + { + renderersList.Clear(); + target.GetComponentsInChildren( renderersList ); - int textureLimitBackup = QualitySettings.masterTextureLimit; //workaround for mipmap generation; - QualitySettings.masterTextureLimit = 0; - - result = new Texture2D(width, height, m_backgroundColor.a < 1f ? TextureFormat.RGBA32 : TextureFormat.RGB24, true); - result.ReadPixels(new Rect(0, 0, width, height), 0, 0, true); - result.filterMode = FilterMode.Trilinear; + Quaternion inverseCameraRotation = Quaternion.Inverse( cameraRotation ); + Vector3 localBoundsMin = new Vector3( float.MaxValue - 1f, float.MaxValue - 1f, float.MaxValue - 1f ); + Vector3 localBoundsMax = new Vector3( float.MinValue + 1f, float.MinValue + 1f, float.MinValue + 1f ); - result.Apply(false, m_markTextureNonReadable); - - QualitySettings.masterTextureLimit = textureLimitBackup; + bounds = new Bounds(); + bool hasBounds = false; + for( int i = 0; i < renderersList.Count; i++ ) + { + if( !renderersList[i].enabled ) + continue; - RenderTexture.active = temp; - RenderTexture.ReleaseTemporary(renderTex); - } - catch (Exception e) - { - Debug.LogException(e); - } - finally - { -#if DEBUG_BOUNDS - for( int i = 0; i < boundsDebugCubes.Count; i++ ) - Object.DestroyImmediate( boundsDebugCubes[i].gameObject ); + if( shouldIgnoreParticleSystems && renderersList[i] is ParticleSystemRenderer ) + continue; - boundsDebugCubes.Clear(); + // Local bounds calculation code taken from: https://github.com/Unity-Technologies/UnityCsReference/blob/0355e09029fa1212b7f2e821f41565df8e8814c7/Editor/Mono/InternalEditorUtility.bindings.cs#L710 + if( m_useLocalBounds ) + { +#if UNITY_2021_2_OR_NEWER + Bounds localBounds = renderersList[i].localBounds; +#else + MeshFilter meshFilter = renderersList[i].GetComponent(); + if( !meshFilter || !meshFilter.sharedMesh ) + continue; + + Bounds localBounds = meshFilter.sharedMesh.bounds; #endif - if (shouldCloneModel) - Object.DestroyImmediate(previewObject.gameObject); - else - { - if (!wasActive) - previewObject.gameObject.SetActive(false); + Transform transform = renderersList[i].transform; + localBoundsMinMax[0] = localBounds.min; + localBoundsMinMax[1] = localBounds.max; - if (!isStatic) + for( int x = 0; x < 2; x++ ) + { + for( int y = 0; y < 2; y++ ) { - previewObject.position = prevPos; - previewObject.rotation = prevRot; + for( int z = 0; z < 2; z++ ) + { + Vector3 point = inverseCameraRotation * transform.TransformPoint( new Vector3( localBoundsMinMax[x].x, localBoundsMinMax[y].y, localBoundsMinMax[z].z ) ); + localBoundsMin = Vector3.Min( localBoundsMin, point ); + localBoundsMax = Vector3.Max( localBoundsMax, point ); + } } - - int index = 0; - SetLayerRecursively(previewObject, ref index); } - if (renderCamera == m_previewRenderCamera) - cameraSetup.ApplySetup(renderCamera); + hasBounds = true; } - - return result; - } - - private static void SetupCamera() - { - if (m_previewRenderCamera != null && !m_previewRenderCamera.Equals(null)) + else if( !hasBounds ) { - cameraSetup.GetSetup(m_previewRenderCamera); - - renderCamera = m_previewRenderCamera; - renderCamera.nearClipPlane = 0.001f; + bounds = renderersList[i].bounds; + hasBounds = true; } else - renderCamera = InternalCamera; - - renderCamera.backgroundColor = m_backgroundColor; - renderCamera.orthographic = m_orthographicMode; - - renderCamera.clearFlags = CameraClearFlags.Nothing; - //renderCamera.clearFlags = m_backgroundColor.a < 1f ? CameraClearFlags.Depth : CameraClearFlags.Color; + bounds.Encapsulate( renderersList[i].bounds ); } - private static void ProjectBoundingBoxMinMax(Vector3 point) - { -#if DEBUG_BOUNDS - CreateDebugCube( point, Vector3.zero, new Vector3( 0.5f, 0.5f, 0.5f ) ); -#endif + if( m_useLocalBounds && hasBounds ) + bounds = new Bounds( cameraRotation * ( ( localBoundsMin + localBoundsMax ) * 0.5f ), localBoundsMax - localBoundsMin ); - Vector3 localPoint = renderCamera.transform.InverseTransformPoint(point); - if (localPoint.x < minX) - minX = localPoint.x; - if (localPoint.x > maxX) - maxX = localPoint.x; - if (localPoint.y < minY) - minY = localPoint.y; - if (localPoint.y > maxY) - maxY = localPoint.y; - } + return hasBounds; + } - private static void CalculateMaxDistance(Vector3 point) - { -#if DEBUG_BOUNDS - CreateDebugCube( point, Vector3.zero, new Vector3( 0.5f, 0.5f, 0.5f ) ); -#endif + // Moves camera in a way such that it will encapsulate bounds perfectly + public static void CalculateCameraPosition( Camera camera, Bounds bounds, float padding = 0f ) + { + Transform cameraTR = camera.transform; - Vector3 intersectionPoint = projectionPlaneHorizontal.ClosestPointOnPlane(point); + Vector3 cameraDirection = cameraTR.forward; + float aspect = camera.aspect; - float horizontalDistance = projectionPlaneHorizontal.GetDistanceToPoint(point); - float verticalDistance = projectionPlaneVertical.GetDistanceToPoint(point); + if( padding != 0f ) + bounds.size *= 1f + padding * 2f; // Padding applied to both edges, hence multiplied by 2 - // Credit: https://docs.unity3d.com/Manual/FrustumSizeAtDistance.html - float halfFrustumHeight = Mathf.Max(verticalDistance, horizontalDistance / aspect); - float distance = halfFrustumHeight / Mathf.Tan(renderCamera.fieldOfView * 0.5f * Mathf.Deg2Rad); + Vector3 boundsCenter = bounds.center; + Vector3 boundsExtents = bounds.extents; + Vector3 boundsSize = 2f * boundsExtents; - float distanceToCenter = (intersectionPoint - m_previewDirection * distance - boundsCenter).sqrMagnitude; - if (distanceToCenter > maxDistance) - maxDistance = distanceToCenter; + // Calculate corner points of the Bounds + if( m_useLocalBounds ) + { + Matrix4x4 localBoundsMatrix = Matrix4x4.TRS( boundsCenter, camera.transform.rotation, Vector3.one ); + Vector3 point = boundsExtents; + boundingBoxPoints[0] = localBoundsMatrix.MultiplyPoint3x4( point ); + point.x -= boundsSize.x; + boundingBoxPoints[1] = localBoundsMatrix.MultiplyPoint3x4( point ); + point.y -= boundsSize.y; + boundingBoxPoints[2] = localBoundsMatrix.MultiplyPoint3x4( point ); + point.x += boundsSize.x; + boundingBoxPoints[3] = localBoundsMatrix.MultiplyPoint3x4( point ); + point.z -= boundsSize.z; + boundingBoxPoints[4] = localBoundsMatrix.MultiplyPoint3x4( point ); + point.x -= boundsSize.x; + boundingBoxPoints[5] = localBoundsMatrix.MultiplyPoint3x4( point ); + point.y += boundsSize.y; + boundingBoxPoints[6] = localBoundsMatrix.MultiplyPoint3x4( point ); + point.x += boundsSize.x; + boundingBoxPoints[7] = localBoundsMatrix.MultiplyPoint3x4( point ); + } + else + { + Vector3 point = boundsCenter + boundsExtents; + boundingBoxPoints[0] = point; + point.x -= boundsSize.x; + boundingBoxPoints[1] = point; + point.y -= boundsSize.y; + boundingBoxPoints[2] = point; + point.x += boundsSize.x; + boundingBoxPoints[3] = point; + point.z -= boundsSize.z; + boundingBoxPoints[4] = point; + point.x -= boundsSize.x; + boundingBoxPoints[5] = point; + point.y += boundsSize.y; + boundingBoxPoints[6] = point; + point.x += boundsSize.x; + boundingBoxPoints[7] = point; } - private static bool IsStatic(Transform obj) + if( camera.orthographic ) { - if (obj.gameObject.isStatic) - return true; + cameraTR.position = boundsCenter; - for (int i = 0; i < obj.childCount; i++) + float minX = float.PositiveInfinity, minY = float.PositiveInfinity; + float maxX = float.NegativeInfinity, maxY = float.NegativeInfinity; + + for( int i = 0; i < boundingBoxPoints.Length; i++ ) { - if (IsStatic(obj.GetChild(i))) - return true; + Vector3 localPoint = cameraTR.InverseTransformPoint( boundingBoxPoints[i] ); + if( localPoint.x < minX ) + minX = localPoint.x; + if( localPoint.x > maxX ) + maxX = localPoint.x; + if( localPoint.y < minY ) + minY = localPoint.y; + if( localPoint.y > maxY ) + maxY = localPoint.y; } - return false; + float distance = boundsExtents.magnitude + 1f; + camera.orthographicSize = Mathf.Max( maxY - minY, ( maxX - minX ) / aspect ) * 0.5f; + cameraTR.position = boundsCenter - cameraDirection * distance; } - - private static void SetLayerRecursively(Transform obj) + else { - obj.gameObject.layer = PREVIEW_LAYER; - for (int i = 0; i < obj.childCount; i++) - SetLayerRecursively(obj.GetChild(i)); + Vector3 cameraUp = cameraTR.up, cameraRight = cameraTR.right; + + float verticalFOV = camera.fieldOfView * 0.5f; + float horizontalFOV = Mathf.Atan( Mathf.Tan( verticalFOV * Mathf.Deg2Rad ) * aspect ) * Mathf.Rad2Deg; + + // Normals of the camera's frustum planes + Vector3 topFrustumPlaneNormal = Quaternion.AngleAxis( 90f + verticalFOV, -cameraRight ) * cameraDirection; + Vector3 bottomFrustumPlaneNormal = Quaternion.AngleAxis( 90f + verticalFOV, cameraRight ) * cameraDirection; + Vector3 rightFrustumPlaneNormal = Quaternion.AngleAxis( 90f + horizontalFOV, cameraUp ) * cameraDirection; + Vector3 leftFrustumPlaneNormal = Quaternion.AngleAxis( 90f + horizontalFOV, -cameraUp ) * cameraDirection; + + // Credit for algorithm: https://stackoverflow.com/a/66113254/2373034 + // 1. Find edge points of the bounds using the camera's frustum planes + // 2. Create a plane for each edge point that goes through the point and has the corresponding frustum plane's normal + // 3. Find the intersection line of horizontal edge points' planes (horizontalIntersection) and vertical edge points' planes (verticalIntersection) + // If we move the camera along horizontalIntersection, the bounds will always with the camera's width perfectly (similar effect goes for verticalIntersection) + // 4. Find the closest line segment between these two lines (horizontalIntersection and verticalIntersection) and place the camera at the farthest point on that line + int leftmostPoint = -1, rightmostPoint = -1, topmostPoint = -1, bottommostPoint = -1; + for( int i = 0; i < boundingBoxPoints.Length; i++ ) + { + if( leftmostPoint < 0 && IsOutermostPointInDirection( i, leftFrustumPlaneNormal ) ) + leftmostPoint = i; + if( rightmostPoint < 0 && IsOutermostPointInDirection( i, rightFrustumPlaneNormal ) ) + rightmostPoint = i; + if( topmostPoint < 0 && IsOutermostPointInDirection( i, topFrustumPlaneNormal ) ) + topmostPoint = i; + if( bottommostPoint < 0 && IsOutermostPointInDirection( i, bottomFrustumPlaneNormal ) ) + bottommostPoint = i; + } + + Ray horizontalIntersection = GetPlanesIntersection( new Plane( leftFrustumPlaneNormal, boundingBoxPoints[leftmostPoint] ), new Plane( rightFrustumPlaneNormal, boundingBoxPoints[rightmostPoint] ) ); + Ray verticalIntersection = GetPlanesIntersection( new Plane( topFrustumPlaneNormal, boundingBoxPoints[topmostPoint] ), new Plane( bottomFrustumPlaneNormal, boundingBoxPoints[bottommostPoint] ) ); + + Vector3 closestPoint1, closestPoint2; + FindClosestPointsOnTwoLines( horizontalIntersection, verticalIntersection, out closestPoint1, out closestPoint2 ); + + cameraTR.position = Vector3.Dot( closestPoint1 - closestPoint2, cameraDirection ) < 0 ? closestPoint1 : closestPoint2; } + } - private static void GetLayerRecursively(Transform obj) + // Returns whether or not the given point is the outermost point in the given direction among all points of the bounds + private static bool IsOutermostPointInDirection( int pointIndex, Vector3 direction ) + { + Vector3 point = boundingBoxPoints[pointIndex]; + for( int i = 0; i < boundingBoxPoints.Length; i++ ) { - layersList.Add(obj.gameObject.layer); - for (int i = 0; i < obj.childCount; i++) - GetLayerRecursively(obj.GetChild(i)); + if( i != pointIndex && Vector3.Dot( direction, boundingBoxPoints[i] - point ) > 0 ) + return false; } - private static void SetLayerRecursively(Transform obj, ref int index) + return true; + } + + // Credit: https://stackoverflow.com/a/32410473/2373034 + // Returns the intersection line of the 2 planes + private static Ray GetPlanesIntersection( Plane p1, Plane p2 ) + { + Vector3 p3Normal = Vector3.Cross( p1.normal, p2.normal ); + float det = p3Normal.sqrMagnitude; + + return new Ray( ( ( Vector3.Cross( p3Normal, p2.normal ) * p1.distance ) + ( Vector3.Cross( p1.normal, p3Normal ) * p2.distance ) ) / det, p3Normal ); + } + + // Credit: http://wiki.unity3d.com/index.php/3d_Math_functions + // Returns the edge points of the closest line segment between 2 lines + private static void FindClosestPointsOnTwoLines( Ray line1, Ray line2, out Vector3 closestPointLine1, out Vector3 closestPointLine2 ) + { + Vector3 line1Direction = line1.direction; + Vector3 line2Direction = line2.direction; + + float a = Vector3.Dot( line1Direction, line1Direction ); + float b = Vector3.Dot( line1Direction, line2Direction ); + float e = Vector3.Dot( line2Direction, line2Direction ); + + float d = a * e - b * b; + + Vector3 r = line1.origin - line2.origin; + float c = Vector3.Dot( line1Direction, r ); + float f = Vector3.Dot( line2Direction, r ); + + float s = ( b * f - c * e ) / d; + float t = ( a * f - c * b ) / d; + + closestPointLine1 = line1.origin + line1Direction * s; + closestPointLine2 = line2.origin + line2Direction * t; + } + + private static void SetupCamera() + { + if( m_previewRenderCamera ) { - obj.gameObject.layer = layersList[index++]; - for (int i = 0; i < obj.childCount; i++) - SetLayerRecursively(obj.GetChild(i), ref index); + cameraSetup.GetSetup( m_previewRenderCamera ); + + renderCamera = m_previewRenderCamera; + renderCamera.nearClipPlane = 0.01f; + renderCamera.cullingMask = 1 << PREVIEW_LAYER; } + else + renderCamera = InternalCamera; -#if DEBUG_BOUNDS - private static void CreateDebugCube( Vector3 position, Vector3 rotation, Vector3 scale ) + renderCamera.backgroundColor = m_backgroundColor; + renderCamera.orthographic = m_orthographicMode; + renderCamera.clearFlags = m_backgroundColor.a < 1f ? CameraClearFlags.Depth : CameraClearFlags.Color; + } + + private static bool IsStatic( Transform obj ) { - Transform cube = GameObject.CreatePrimitive( PrimitiveType.Cube ).transform; - cube.localPosition = position; - cube.localEulerAngles = rotation; - cube.localScale = scale; - cube.gameObject.layer = PREVIEW_LAYER; - cube.gameObject.hideFlags = HideFlags.HideAndDontSave; + if( obj.gameObject.isStatic ) + return true; - cube.GetComponent().sharedMaterial = boundsMaterial; + for( int i = 0; i < obj.childCount; i++ ) + { + if( IsStatic( obj.GetChild( i ) ) ) + return true; + } - boundsDebugCubes.Add( cube ); + return false; } -#endif + + private static void SetLayerRecursively( Transform obj ) + { + obj.gameObject.layer = PREVIEW_LAYER; + for( int i = 0; i < obj.childCount; i++ ) + SetLayerRecursively( obj.GetChild( i ) ); + } + + private static void GetLayerRecursively( Transform obj ) + { + layersList.Add( obj.gameObject.layer ); + for( int i = 0; i < obj.childCount; i++ ) + GetLayerRecursively( obj.GetChild( i ) ); + } + + private static void SetLayerRecursively( Transform obj, ref int index ) + { + obj.gameObject.layer = layersList[index++]; + for( int i = 0; i < obj.childCount; i++ ) + SetLayerRecursively( obj.GetChild( i ), ref index ); } } \ No newline at end of file