using UnityEditor;
using UnityEngine;
namespace MTE
{
internal class Preview
{
public bool IsReady = false;
private bool IsArray = false;
private Shader shader;
private RenderPipeline renderPipeline = RenderPipeline.NotDetermined;
private GameObject previewObj;
private Texture2D UpDirectionNormalTexture;
public Preview(bool isArray)
{
this.IsArray = isArray;
}
///
/// Load preview from target GameObjects
///
public void LoadPreview(Texture texture, float brushSizeInU3D, int brushIndex)
{
LoadShader();
if (IsArray)
{
Material material = null;
foreach (var target in MTEContext.Targets)
{
material = FindMaterialInObject(target, texture, out _);
if(material)
{
break;
}
}
if (material == null)
{
throw new MTEEditException(
"Failed to load texture in to preview: " +
"selected texture isn't a source texture slice of any texture array used in any targets' material. " +
"Please refresh the filter to reload the texture list.\n\n" +
$"Note: MTE finds source texture slice via the {nameof(TextureArraySettings)} asset next to the texture array being used.");
}
UnLoadPreview();
CreatePreviewObject();
previewObj.hideFlags = HideFlags.HideAndDontSave;
var textureScale = GetPreviewSplatTextureScale(material, brushIndex);
//TODO consider normal roughness and AO
SetPreviewTexture(textureScale, texture, null);
SetPreviewSize(brushSizeInU3D / 2);
SetPreviewMaskTexture(brushIndex);
}
else
{
int splatIndex = -1;
Material material = null;
foreach (var target in MTEContext.Targets)
{
material = FindMaterialInObject(target, texture, out _);
if (material)
{
break;
}
}
if (material == null)
{
throw new MTEEditException(
"Failed to load texture in to preview: " +
"selected texture isn't used in any GameObject's material. " +
"Please refresh the filter to reload the texture list.");
}
UnLoadPreview();
CreatePreviewObject();
previewObj.hideFlags = HideFlags.HideAndDontSave;
var textureScale = GetPreviewSplatTextureScale(material, splatIndex);
Texture normalTexture = null;
if (material.HasProperty("_Normal" + splatIndex))
{
normalTexture = material.GetTexture("_Normal" + splatIndex);
}
SetPreviewTexture(textureScale, texture, normalTexture);
SetPreviewSize(brushSizeInU3D / 2);
SetPreviewMaskTexture(brushIndex);
}
IsReady = true;
}
///
/// Load preview from a GameObject
///
public void LoadPreviewFromObject(Texture texture, float brushSizeInU3D,
int brushIndex, GameObject obj)
{
if (obj == null)
{
MTEDebug.LogError("Cannot load preview from an invalid GameObject.");
return;
}
LoadShader();
if (IsArray)
{
Material material = null;
var target = obj;
material = FindMaterialInObject(target, texture, out _);
if (material == null)
{
throw new MTEEditException(
"Failed to load texture in to preview: " +
"selected texture isn't a source texture slice of any texture array used in any targets' material. " +
"Please refresh the filter to reload the texture list.\n\n" +
$"Note: MTE finds source texture slice via the {nameof(TextureArraySettings)} asset next to the texture array being used.");
}
UnLoadPreview();
CreatePreviewObject();
previewObj.hideFlags = HideFlags.HideAndDontSave;
var textureScale = GetPreviewSplatTextureScale(material, brushIndex);
//TODO consider normal roughness and AO
SetPreviewTexture(textureScale, texture, null);
SetPreviewSize(brushSizeInU3D / 2);
SetPreviewMaskTexture(brushIndex);
}
else
{
int splatIndex = -1;
Material material = null;
var target = obj;
material = FindMaterialInObject(target, texture, out splatIndex);
if (material == null)
{
throw new MTEEditException(
"Failed to load texture in to preview: " +
"selected texture isn't used in any GameObject's material. " +
"Please refresh the filter to reload the texture list.");
}
UnLoadPreview();
CreatePreviewObject();
previewObj.hideFlags = HideFlags.HideAndDontSave;
var textureScale = GetPreviewSplatTextureScale(material, splatIndex);
Texture normalTexture = null;
if (material.HasProperty("_Normal" + splatIndex))
{
normalTexture = material.GetTexture("_Normal" + splatIndex);
}
SetPreviewTexture(textureScale, texture, normalTexture);
SetPreviewSize(brushSizeInU3D / 2);
SetPreviewMaskTexture(brushIndex);
}
IsReady = true;
}
///
/// Destory the preview.
///
public void UnLoadPreview()
{
if (previewObj != null)
{
UnityEngine.Object.DestroyImmediate(previewObj);
previewObj = null;
}
IsReady = false;
}
public void SetPreviewTexture(Vector2 textureScale,
Texture texture, Texture normalTexture)
{
if(!previewObj)
{
return;
}
if(renderPipeline != RenderPipeline.Builtin)
{
var renderer = previewObj.GetComponent();
renderer.sharedMaterial.SetTexture("_MainTex", texture);
renderer.sharedMaterial.SetTextureScale("_MainTex", textureScale);
if (!normalTexture)
{//Default "bump" direction is (0, 0, 1), encoded as(0.5, 0.5, 1.0);
// not expected up-direction (0, 1, 0), encoded as(0.5, 1.0, 0.5).
//So a default up-direction normal texture is created here lazily to replaced the "bump".
if (!UpDirectionNormalTexture)
{
UpDirectionNormalTexture = new Texture2D(1, 1, TextureFormat.RGB24, false);
UpDirectionNormalTexture.SetPixel(0, 0, new Color(0.5f, 1.0f, 0.5f));
UpDirectionNormalTexture.Apply();
}
normalTexture = UpDirectionNormalTexture;
}
renderer.sharedMaterial.SetTexture("_NormalTex", normalTexture);
}
else
{
var projector = previewObj.GetComponent();
projector.material.SetTexture("_MainTex", texture);
projector.material.SetTextureScale("_MainTex", textureScale);
projector.material.SetTexture("_NormalTex", normalTexture);
}
SceneView.RepaintAll();
}
public void SetPreviewMaskTexture(int maskIndex)
{
if(!previewObj)
{
return;
}
if (renderPipeline == RenderPipeline.Builtin)
{
var projector = previewObj.GetComponent();
projector.material.SetTexture("_MaskTex", MTEStyles.brushTextures[maskIndex]);
projector.material.SetTextureScale("_MaskTex", Vector2.one);
}
else
{
var renderer = previewObj.GetComponent();
renderer.sharedMaterial.SetTexture("_MaskTex", MTEStyles.brushTextures[maskIndex]);
renderer.sharedMaterial.SetTextureScale("_MaskTex", Vector2.one);
}
SceneView.RepaintAll();
}
public void SetPreviewSize(float value)
{
if(!previewObj)
{
return;
}
if (renderPipeline == RenderPipeline.Builtin)
{
var projector = previewObj.GetComponent();
projector.orthographicSize = value;
}
else
{
var halfBrushSizeInUnityUnit = value;
previewObj.transform.localScale = new Vector3(
halfBrushSizeInUnityUnit*2,
halfBrushSizeInUnityUnit*2, 10000);
}
SceneView.RepaintAll();
}
public void MoveTo(Vector3 worldPosition)
{
if(!previewObj)
{
return;
}
previewObj.transform.position = worldPosition;
}
public void SetNormalizedBrushCenter(Vector2 normalizedBrushCenter)
{
if (renderPipeline != RenderPipeline.Builtin)
{
var renderer = previewObj.GetComponent();
renderer.sharedMaterial.SetVector("_BrushCenter", normalizedBrushCenter);
}
else
{
//nothing
}
}
public void SetNormalizedBrushSize(float normalizeBrushSize)
{
if (renderPipeline != RenderPipeline.Builtin)
{
var renderer = previewObj.GetComponent();
renderer.sharedMaterial.SetFloat("_NormalizedBrushSize", normalizeBrushSize);
}
else
{
//nothing
}
}
private void LoadShader()
{
if (shader != null && RenderPipelineUtil.Current == renderPipeline)
{
return;
}
renderPipeline = RenderPipelineUtil.Current;
var urpShaderRelativePath = Utility.GetUnityPath(Res.ShaderDir + "PaintTexturePreview_URP.shader");
switch (RenderPipelineUtil.Current)
{
case RenderPipeline.Builtin:
shader = Shader.Find("Hidden/MTE/PaintTexturePreview");
break;
case RenderPipeline.URP:
this.shader = AssetDatabase.LoadAssetAtPath(urpShaderRelativePath);
if (shader == null)
{
MTEDebug.LogError("MTE Preview shader for URP is not found.");
}
else
{
MTEDebug.Log("Loaded Preview shader for URP.");
}
break;
//fallback to URP
case RenderPipeline.HDRP://HDRP is not supported yet.
default:
this.shader = AssetDatabase.LoadAssetAtPath(urpShaderRelativePath);
if (shader == null)
{
MTEDebug.LogError("MTE Preview shader for URP (fallback) is not found.");
}
else
{
MTEDebug.Log("Loaded Preview shader for URP (fallback).");
}
break;
}
}
private void CreatePreviewObject()
{
if (renderPipeline != RenderPipeline.Builtin)
{
previewObj = GameObject.CreatePrimitive(PrimitiveType.Cube);
var boxCollider = previewObj.GetComponent();
Object.DestroyImmediate(boxCollider);
previewObj.name = "MTEPreview";
var meshRenderer = previewObj.GetComponent();
var material = new Material(shader);
meshRenderer.sharedMaterial = material;
previewObj.transform.eulerAngles = new Vector3(90, 0, 0);
}
else
{
previewObj = new GameObject("MTEPreview");
var projector = previewObj.AddComponent();
projector.material = new Material(shader);
projector.orthographic = true;
projector.nearClipPlane = -1000;
projector.farClipPlane = 1000;
projector.transform.Rotate(90, 0, 0);
}
}
private Material FindMaterialInObject(GameObject obj, Texture texture, out int layerIndexBuiltinOnly)
{
layerIndexBuiltinOnly = -1;
if (IsArray)
{
var meshRenderer = obj.GetComponent();
if (meshRenderer == null)
{
return null;
}
var m = meshRenderer.sharedMaterial;
if (m == null)
{
return null;
}
var runtimeTextureArrayLoader = meshRenderer.GetComponent();
if (runtimeTextureArrayLoader)
{
runtimeTextureArrayLoader.LoadInEditor();
var settings = runtimeTextureArrayLoader.settings;
var texturePropertyValue = m.GetTexture(
TextureArrayShaderPropertyNames.AlbedoArrayPropertyName);
if (!texturePropertyValue)
{
return null;
}
var textureArray = texturePropertyValue as Texture2DArray;
if (textureArray == null)
{
return null;
}
TextureArrayManager.Instance.AddOrUpdate(textureArray, settings);
}
else
{
var texturePropertyValue = m.GetTexture(
TextureArrayShaderPropertyNames.AlbedoArrayPropertyName);
if (!texturePropertyValue)
{
return null;
}
var textureArray = texturePropertyValue as Texture2DArray;
if (textureArray == null)
{
return null;
}
if (!TextureArrayManager.Instance.IsCached(textureArray))
{
return null;
}
if (TextureArrayManager.Instance.GetTextureSliceIndex(textureArray, texture) < 0)
{
return null;
}
}
return m;
}
else
{
var meshRenderer = obj.GetComponent();
if (meshRenderer == null)
{
return null;
}
var m = meshRenderer.sharedMaterial;
if (m == null)
{
return null;
}
layerIndexBuiltinOnly = m.FindSplatTexture(texture);
if (layerIndexBuiltinOnly < 0)
{
return null;
}
return m;
}
}
private Vector2 GetPreviewSplatTextureScale(Material material, int splatIndex)
{
if(IsArray)
{
//We use unique uv scale offset for all layers in array shaders.
//So splatIndex is only valid for non-array shaders.
var UVScaleOffset = TextureArrayShaderPropertyNames.UVScaleOffsetPropertyName;
if (material.HasProperty(UVScaleOffset))
{
var scaleOffset = material.GetVector(UVScaleOffset);
return new Vector2(scaleOffset.x, scaleOffset.y);
}
MTEDebug.LogWarning($"No {UVScaleOffset} property found in texture array shader " +
material.shader.name);
return new Vector2(15, 15);
}
else
{
var splatXName = "_Splat" + splatIndex;
if (material.HasProperty(splatXName))
{
return material.GetTextureScale(splatXName);
}
if (0 <= splatIndex && splatIndex <= 3)
{
var packedSplatName = "_PackedSplat0";
if (material.HasProperty(packedSplatName))
{
return material.GetTextureScale(packedSplatName);
}
}
else if (4 <= splatIndex && splatIndex <= 7)
{
var packedSplatName = "_PackedSplat3";
if (material.HasProperty(packedSplatName))
{
return material.GetTextureScale(packedSplatName);
}
}
return new Vector2(10, 10);
}
}
}
}