diff --git a/Assets/Shaders/Dependencies/GetDepth.prefab b/Dependencies/GetDepth.prefab similarity index 100% rename from Assets/Shaders/Dependencies/GetDepth.prefab rename to Dependencies/GetDepth.prefab diff --git a/Assets/Shaders/PrimoToon-helpers.hlsl b/PrimoToon-helpers.hlsl similarity index 100% rename from Assets/Shaders/PrimoToon-helpers.hlsl rename to PrimoToon-helpers.hlsl diff --git a/Assets/Shaders/PrimoToon-inputs.hlsli b/PrimoToon-inputs.hlsli similarity index 100% rename from Assets/Shaders/PrimoToon-inputs.hlsli rename to PrimoToon-inputs.hlsli diff --git a/Assets/Shaders/PrimoToon-main.hlsl b/PrimoToon-main.hlsl similarity index 99% rename from Assets/Shaders/PrimoToon-main.hlsl rename to PrimoToon-main.hlsl index 087924e..4197912 100644 --- a/Assets/Shaders/PrimoToon-main.hlsl +++ b/PrimoToon-main.hlsl @@ -699,4 +699,4 @@ vector frag(vsOut i, bool frontFacing : SV_IsFrontFace) : SV_Target{ /* END OF DEBUGGING */ return finalColor; -} +} \ No newline at end of file diff --git a/Assets/Shaders/PrimoToon-outlines.hlsl b/PrimoToon-outlines.hlsl similarity index 100% rename from Assets/Shaders/PrimoToon-outlines.hlsl rename to PrimoToon-outlines.hlsl diff --git a/Assets/Shaders/PrimoToon-shadows.hlsl b/PrimoToon-shadows.hlsl similarity index 100% rename from Assets/Shaders/PrimoToon-shadows.hlsl rename to PrimoToon-shadows.hlsl diff --git a/Assets/Shaders/PrimoToon.shader b/PrimoToon.shader similarity index 60% rename from Assets/Shaders/PrimoToon.shader rename to PrimoToon.shader index 2431a39..32a4009 100644 --- a/Assets/Shaders/PrimoToon.shader +++ b/PrimoToon.shader @@ -1,43 +1,86 @@ -Shader ".festivity/PrimoToon/PrimoToon"{ - Properties{ - [Header(Textures)] [MainTex] [NoScaleOffset] [HDR] [Space(10)] _MainTex ("Diffuse", 2D) = "white"{} - [NoScaleOffset] _LightMapTex ("Lightmap", 2D) = "white"{} - [NoScaleOffset] _FaceMap ("Face Shadow (only if face shader is used)", 2D) = "white"{} - [NoScaleOffset] _BumpMap ("Bump Map", 2D) = "bump"{} - [NoScaleOffset] [HDR] _PackedShadowRampTex ("Shadow Ramp", 2D) = "white"{} - [NoScaleOffset] _MTSpecularRamp ("Specular Ramp", 2D) = "white"{} - [NoScaleOffset] [HDR] _MTMap ("Metallic Matcap", 2D) = "white"{} - [NoScaleOffset] _WeaponDissolveTex ("Weapon Dissolve", 2D) = "white"{} - [NoScaleOffset] _WeaponPatternTex ("Weapon Pattern", 2D) = "white"{} - [NoScaleOffset] _ScanPatternTex ("Scan Pattern", 2D) = "black"{} - - [Header(Miscellaneous and Lighting Options)] [Space(10)] _DayOrNight ("Nighttime?", Range(0.0, 1.0)) = 0.0 - _EnvironmentLightingStrength ("Environment Lighting Strength", Range(0.0, 1.0)) = 1.0 - [KeywordEnum(Add, Color Dodge)] _RimLightType ("Rim Light Blend Mode", Float) = 0.0 - _RimLightIntensity ("Rim Light Intensity", Float) = 1.0 - _RimLightThickness ("Rim Light Thickness", Range(0.0, 10.0)) = 1.0 - [Toggle] _VertexColorLinear ("Linear Vertex Colors?", Range(0.0, 1.0)) = 0.0 - - [Header(Fresnel Options)] [Space(10)] [Toggle] _UseFresnel ("Use Fresnel?", Range(0.0, 1.0)) = 1.0 - [Gamma] _HitColor ("Fresnel Color", Color) = (0.0, 0.0, 0.0, 1.0) - [Gamma] [HideInInspector] _ElementRimColor ("Element Rim Color", Color) = (0.0, 0.0, 0.0, 1.0) - _HitColorScaler ("Fresnel Color Scaler", Float) = 6 - _HitColorFresnelPower ("Fresnel Power", Float) = 1.5 - - [Header(Face Shader Specific Settings)] [Space(10)] [Toggle] _UseFaceMapNew ("Use Face Shader?", Range(0.0, 1.0)) = 0.0 - _headForwardVector ("Forward Vector, ignore the last element", Vector) = (0, 1, 0, 0) - _headRightVector ("Right Vector, ignore the last element", Vector) = (0, 0, -1, 0) +Shader ".festivity/PrimoToon"{ + Properties + { + //Header + [HideInInspector] shader_master_label ("✧PrimoToon 2.0✧", Float) = 0 + [HideInInspector] shader_is_using_thry_editor ("", Float) = 0 + [HideInInspector] footer_github ("{texture:{name:github},action:{type:URL,data:https://github.com/festivities/PrimoToon},hover:GITHUB}", Float) = 0 + [HideInInspector] footer_discord ("{texture:{name:discord},action:{type:URL,data:https://discord.gg/85rP9SpAkF},hover:GITHUB}", Float) = 0 + //Header End + + + //Material Type + [ThryWideEnum(Base, 0, Face, 1, Weapon, 2)]variant_selector("Material Type--{on_value_actions:[ + {value:0,actions:[{type:SET_PROPERTY,data:_UseFaceMapNew=0.0}, {type:SET_PROPERTY,data:_UseWeapon=0.0}]}, + {value:1,actions:[{type:SET_PROPERTY,data:_UseFaceMapNew=1.0}, {type:SET_PROPERTY,data:_UseWeapon=0.0}]}, + {value:2,actions:[{type:SET_PROPERTY,data:_UseFaceMapNew=0.0}, {type:SET_PROPERTY,data:_UseWeapon=1.0} + }]}]}", Int) = 0 + //Material Type End + + + //Main + [HideInInspector] m_start_main ("Main", Float) = 0 + [SmallTexture]_MainTex("Main Texture",2D)= "white" { } + [SmallTexture]_LightMapTex("Lightmap",2D)= "white" { } + [Enum(UV0, 0, UV1, 1)] _UseBackFaceUV2("Backface UV", int) = 1.0 + [Toggle] _VertexColorLinear ("Enable Linear Vertex Colors", Range(0.0, 1.0)) = 0.0 + [Toggle]_DayOrNight ("Enable Nighttime", Range(0.0, 1.0)) = 0.0 + [HideInInspector] m_start_mainalpha ("Alpha Options", Float) = 0 + [Enum(Off, 0, Transparency, 1, Glow, 2)] _MainTexAlphaUse("Diffuse Alpha Channel", Int) = 0 + _MainTexAlphaCutoff("Alpha Cuttoff", Range(0, 1.0)) = 0.5 + [HideInInspector] m_end_mainalpha ("", Float) = 0 + [HideInInspector] m_start_maindetail ("Details", Float) = 0 + [Toggle] _TextureLineUse ("Texture Line", Range(0.0, 1.0)) = 0.0 + _TextureLineSmoothness ("Texture Line Smoothness", Range(0.0, 1.0)) = 0.15 + _TextureLineThickness ("Texture Line Thickness", Range(0.0, 1.0)) = 0.55 + _TextureLineDistanceControl ("Texture Line Distance Control", Vector) = (0.1, 0.6, 1.0, 1.0) + [Gamma] [HDR] _TextureLineMultiplier ("Texture Line Multiplier", Color) = (0.6, 0.6, 0.6, 1.0) + [HideInInspector] _TextureBiasWhenDithering ("Texture Dithering Bias", Float) = -1.0 + [HideInInspector] m_end_maindetail ("", Float) = 0 + [HideInInspector] m_start_matid ("Material IDs", Float) = 0 + [Toggle] _UseMaterial2 ("Enable Material 2", Float) = 1.0 + [Toggle] _UseMaterial3 ("Enable Material 3", Float) = 1.0 + [Toggle] _UseMaterial4 ("Enable Material 4", Float) = 1.0 + [Toggle] _UseMaterial5 ("Enable Material 5", Float) = 1.0 + [HideInInspector] m_end_matid ("", Float) = 0 + [HideInInspector] m_end_main ("", Float) = 0 + //Main End + + //Normal Map + [HideInInspector] m_start_normalmap ("Normal Map", Float) = 0 + [Toggle] _UseBumpMap("Normal Map", Float) = 0.0 + [SmallTexture]_BumpMap("Normal Map",2D)= "white"{ } + _BumpScale ("Normal Map Scale", Range(0.0, 1.0)) = 0.2 + [HideInInspector] m_end_normalmap ("", Float) = 0 + //Normal Map End + + + //Face Shading + [HideInInspector] m_start_faceshading("Face--{condition_show:{type:PROPERTY_BOOL,data:_UseFaceMapNew==1.0}}", Float) = 0 + [Toggle] _flipFaceLighting ("Flip Face Lighting", Range(0.0, 1.0)) = 0.0 + [SmallTexture]_FaceMap ("Face Shadow Ramp",2D)= "white"{ } + [HideInInspector] _UseFaceMapNew ("Enable Face Shader", Range(0.0, 1.0)) = 0.0 _FaceMapSoftness ("Face Lighting Softness", Range(0.0, 1.0)) = 0.001 - [Toggle] _flipFaceLighting ("Flip Face Lighting?", Range(0.0, 1.0)) = 0.0 - [IntRange] _MaterialID ("Material ID", Range(1.0, 5.0)) = 2.0 + [IntRange] _MaterialID ("Face Material ID", Range(1.0, 5.0)) = 2.0 + _headForwardVector ("Forward Vector", Vector) = (0, 1, 0, 0) + _headRightVector ("Right Vector, ignore the last element", Vector) = (0, 0, -1, 0) + [HideInInspector] m_start_faceblush ("Blush", Float) = 0 _NoseBlushStrength ("Nose Blush Strength", Range(0.0, 1.0)) = 0.0 - [Gamma] _NoseBlushColor ("Nose Blush Color", Color) = (1.0, 0.8, 0.7, 1.0) _FaceBlushStrength ("Face Blush Strength", Range(0.0, 1.0)) = 0.0 + [Gamma] _NoseBlushColor ("Nose Blush Color", Color) = (1.0, 0.8, 0.7, 1.0) [Gamma] _FaceBlushColor ("Face Blush Color", Color) = (1.0, 0.8, 0.7, 1.0) - - [Header(Weapon Specific Settings)] [Space(10)] [Toggle] _UseWeapon ("Use Weapon Shader?", Range(0.0, 1.0)) = 0.0 - [Toggle] _UsePattern ("Use Weapon Pattern?", Range(0.0, 1.0)) = 1.0 - [Toggle] _ProceduralUVs ("No UV1?", Range(0.0, 1.0)) = 0.0 + [HideInInspector] m_end_faceblush ("", Float) = 0 + [HideInInspector] m_end_faceshading ("", Float) = 0 + //Face Shading End + + //Weapon Shading + [HideInInspector] m_start_weaponshading("Weapon--{condition_show:{type:PROPERTY_BOOL,data:_UseWeapon==1.0}}", Float) = 0 + [HideInInspector]_UseWeapon ("Weapon Shader", Range(0.0, 1.0)) = 0.0 + [Toggle] _UsePattern ("Enable Weapon Pattern", Range(0.0, 1.0)) = 1.0 + [Toggle] _ProceduralUVs ("Disable UV1", Range(0.0, 1.0)) = 0.0 + [SmallTexture]_WeaponDissolveTex("Weapon Dissolve",2D)= "white"{ } + [SmallTexture]_WeaponPatternTex("Weapon Pattern",2D)= "white"{ } + [SmallTexture]_ScanPatternTex("Scan Pattern",2D)= "black"{ } _ClipAlphaThreshold ("Dissolve Clipping Threshold", Range(0, 1)) = 1.0 _WeaponDissolveValue ("Weapon Dissolve Value", Range(-1.0, 2.0)) = 1.0 [Toggle] _DissolveDirection_Toggle ("Dissolve Direction Toggle", Range(0.0, 1.0)) = 0.0 @@ -46,30 +89,29 @@ [HideInInspector] _SkillEmisssionPower ("Skill Emisssion Power", Float) = 0.6 [Gamma] [HideInInspector] _SkillEmisssionColor ("Skill Emisssion Color", Vector) = (0.0, 0.0, 0.0, 0.0) [HideInInspector] _SkillEmissionScaler ("Skill Emission Scaler", Float) = 3.2 + [HideInInspector] m_start_weaponscan ("Scan", Float) = 0 _ScanColorScaler ("Scan Color Scaler", Float) = 0.0 [Gamma] _ScanColor ("Scan Color", Color) = (0.8970588, 0.8970588, 0.8970588, 1.0) [Toggle] _ScanDirection_Switch ("Scan Direction Switch", Range(0.0, 1.0)) = 0.0 _ScanSpeed ("Scan Speed", Float) = 0.8 + [HideInInspector] m_end_weaponscan ("", Float) = 0 + [HideInInspector] m_end_weaponshading ("", Float) = 0 + //Weapon Shading End - [Header(Alpha Options)] [Space(10)] [IntRange] _MainTexAlphaUse ("Use diffuse alpha channel for nothing, transparency, or glow?", Range(0.0, 2.0)) = 0.0 - - [Header(Emission Options)] [Space(10)] [Toggle] _ToggleEyeGlow ("Toggle Eye Glow?", Range(0.0, 1.0)) = 1.0 - [KeywordEnum(Default, Custom)] _EmissionType ("Emission Type", Float) = 0.0 - [NoScaleOffset] [HDR] _CustomEmissionTex ("Custom Emission Texture", 2D) = "black"{} - [NoScaleOffset] _CustomEmissionAOTex ("Custom Emission AO", 2D) = "white"{} - [Gamma] _EmissionColor ("Emission Tint", Color) = (1.0, 1.0, 1.0, 1.0) - _EyeGlowStrength ("Eye Glow Strength", Float) = 1.0 - _EmissionStrength ("Emission Strength", Float) = 1.0 - [Toggle] _TogglePulse ("Toggle Pulse?", Range(0.0, 1.0)) = 0.0 - _PulseSpeed ("Pulse Speed", Float) = 1.0 - _PulseMinStrength ("Minimum Pulse Strength", Range(0.0, 1.0)) = 0.0 - _PulseMaxStrength ("Maximum Pulse Strength", Range(0.0, 1.0)) = 1.0 - [Header(Cutout Transparency)] [Space(10)] _MainTexAlphaCutoff ("Cutoff", Range(0.0, 1.0)) = 0.5 + //Lightning Options + [HideInInspector] m_start_lighting("Lighting Options", Float) = 0 + [HideInInspector] g_start_light("", Int) = 0 + [HideInInspector] m_start_lightandshadow("Shadow", Float) = 0 + [SmallTexture]_PackedShadowRampTex("Shadow Ramp",2D)= "white"{ } + [Toggle] _UseLightMapColorAO ("Use Lightmap Ambient Occlusion?", Range(0.0, 1.0)) = 1.0 + [Toggle] _UseShadowRamp ("Use Shadow Ramp Texture?", Float) = 1.0 + [Toggle] _UseVertexColorAO ("Use Vertex Color Ambient Occlusion?", Range(0.0, 1.0)) = 1.0 + _EnvironmentLightingStrength ("Environment Lighting Strength", Range(0.0, 1.0)) = 1.0 - [Header(Diffuse or Lighting Options)] [Space(10)] _BumpScale ("Bump Scale", Range(0.0, 1.0)) = 0.2 _LightArea ("Shadow Position", Range(0.0, 2.0)) = 0.55 _ShadowRampWidth ("Ramp Width", Range(0.2, 3.0)) = 1.0 + [HideInInspector] m_start_shadowtransitions("Shadow Transition", Float) = 0 _ShadowTransitionRange ("Shadow Transition Range 1", Range(0.0, 1.0)) = 0.01 _ShadowTransitionRange2 ("Shadow Transition Range 2", Range(0.0, 1.0)) = 0.01 _ShadowTransitionRange3 ("Shadow Transition Range 3", Range(0.0, 1.0)) = 0.01 @@ -80,76 +122,132 @@ _ShadowTransitionSoftness3 ("Shadow Transition Softness 3", Range(0.0, 1.0)) = 0.5 _ShadowTransitionSoftness4 ("Shadow Transition Softness 4", Range(0.0, 1.0)) = 0.5 _ShadowTransitionSoftness5 ("Shadow Transition Softness 5", Range(0.0, 1.0)) = 0.5 - [Toggle] _UseBackFaceUV2 ("Use second UV for backfaces?", Float) = 1.0 - [Toggle] _UseBumpMap ("Use Normal Map?", Float) = 1.0 - [Toggle] _UseLightMapColorAO ("Use Lightmap Ambient Occlusion?", Range(0.0, 1.0)) = 1.0 - [Toggle] _UseMaterial2 ("Toggle Material 2", Float) = 1.0 - [Toggle] _UseMaterial3 ("Toggle Material 3", Float) = 1.0 - [Toggle] _UseMaterial4 ("Toggle Material 4", Float) = 1.0 - [Toggle] _UseMaterial5 ("Toggle Material 5", Float) = 1.0 - [Toggle] _UseShadowRamp ("Use Shadow Ramp Texture?", Float) = 1.0 - [Toggle] _UseVertexColorAO ("Use Vertex Color Ambient Occlusion?", Range(0.0, 1.0)) = 1.0 - [Gamma] _CoolShadowMultColor ("Nighttime Shadow Color 1", Color) = (0.9, 0.7, 0.75, 1) - [Gamma] _CoolShadowMultColor2 ("Nighttime Shadow Color 2", Color) = (0.9, 0.7, 0.75, 1) - [Gamma] _CoolShadowMultColor3 ("Nighttime Shadow Color 3", Color) = (0.9, 0.7, 0.75, 1) - [Gamma] _CoolShadowMultColor4 ("Nighttime Shadow Color 4", Color) = (0.9, 0.7, 0.75, 1) - [Gamma] _CoolShadowMultColor5 ("Nighttime Shadow Color 5", Color) = (0.9, 0.7, 0.75, 1) + [HideInInspector] m_end_shadowtransitions ("", Float) = 0 + [HideInInspector] m_start_shadowcolorsday("DayTime Colors", Float) = 0 [Gamma] _FirstShadowMultColor ("Daytime Shadow Color 1", Color) = (0.9, 0.7, 0.75, 1) [Gamma] _FirstShadowMultColor2 ("Daytime Shadow Color 2", Color) = (0.9, 0.7, 0.75, 1) [Gamma] _FirstShadowMultColor3 ("Daytime Shadow Color 3", Color) = (0.9, 0.7, 0.75, 1) [Gamma] _FirstShadowMultColor4 ("Daytime Shadow Color 4", Color) = (0.9, 0.7, 0.75, 1) [Gamma] _FirstShadowMultColor5 ("Daytime Shadow Color 5", Color) = (0.9, 0.7, 0.75, 1) - - [Header(Specular Options)] [Space(10)] _Shininess ("Shininess 1", Float) = 10 - _Shininess2 ("Shininess 2", Float) = 10 - _Shininess3 ("Shininess 3", Float) = 10 - _Shininess4 ("Shininess 4", Float) = 10 - _Shininess5 ("Shininess 5", Float) = 10 - _SpecMulti ("Specular Multiplier 1", Float) = 0.1 - _SpecMulti2 ("Specular Multiplier 2", Float) = 0.1 - _SpecMulti3 ("Specular Multiplier 3", Float) = 0.1 - _SpecMulti4 ("Specular Multiplier 4", Float) = 0.1 - _SpecMulti5 ("Specular Multiplier 5", Float) = 0.1 - [Gamma] _SpecularColor ("Specular Color", Color) = (1.0, 1.0, 1.0, 1.0) - - [Header(Metallic Options)] [Space(10)] _MTMapBrightness ("Metallic Matcap Brightness", Float) = 3.0 - _MTMapTileScale ("Metallic Matcap Tile Scale", Range(0.0, 2.0)) = 1.0 - _MTShininess ("Metallic Specular Shininess", Float) = 90.0 - _MTSpecularAttenInShadow ("Metallic Specular Attenuation in Shadow", Range(0.0, 1.0)) = 0.2 - _MTSpecularScale ("Metallic Specular Scale", Float) = 15.0 - _MTSharpLayerOffset ("Metallic Sharp Layer Offset", Range(0.001, 1.0)) = 1.0 - [Toggle] _MTUseSpecularRamp ("Use Specular Ramp Texture?", Float) = 0.0 - [Toggle] _MetalMaterial ("Enable Metallic?", Range(0.0, 1.0)) = 1.0 + [HideInInspector] m_end_shadowcolorsday ("", Float) = 0 + [HideInInspector] m_start_shadowcolorsnight("NightTime Colors", Float) = 0 + [Gamma] _CoolShadowMultColor ("Nighttime Shadow Color 1", Color) = (0.9, 0.7, 0.75, 1) + [Gamma] _CoolShadowMultColor2 ("Nighttime Shadow Color 2", Color) = (0.9, 0.7, 0.75, 1) + [Gamma] _CoolShadowMultColor3 ("Nighttime Shadow Color 3", Color) = (0.9, 0.7, 0.75, 1) + [Gamma] _CoolShadowMultColor4 ("Nighttime Shadow Color 4", Color) = (0.9, 0.7, 0.75, 1) + [Gamma] _CoolShadowMultColor5 ("Nighttime Shadow Color 5", Color) = (0.9, 0.7, 0.75, 1) + [HideInInspector] m_end_shadowcolorsnight ("", Float) = 0 + [HideInInspector] m_end_lightandshadow ("", Float) = 0 + [HideInInspector] m_start_rimlight("Rim Light", Float) = 0 + [Enum(Add, 0, Color Dodge, 1)] _RimLightType ("Rim Light Blend Mode", Float) = 0.0 + _RimLightIntensity ("Rim Light Intensity", Float) = 1.0 + _RimLightThickness ("Rim Light Thickness", Range(0.0, 10.0)) = 1.0 + [HideInInspector] m_end_rimlight ("", Float) = 0 + [HideInInspector] g_end_light("", Int) = 0 + [HideInInspector] m_end_lightning ("", Float) = 0 + //Lightning Options End + + + //Reflections + [HideInInspector] m_start_reflections("Reflections", Float) = 0 + [HideInInspector] m_start_metallics("Metallics", Int) = 0 + [Toggle] _MetalMaterial ("Enable Metallic", Range(0.0, 1.0)) = 1.0 + [SmallTexture]_MTMap("Metallic Matcap--{condition_show:{type:PROPERTY_BOOL,data:_MetalMaterial==1.0}}",2D)= "white"{ } + _MTMapBrightness ("Metallic Matcap Brightness--{condition_show:{type:PROPERTY_BOOL,data:_MetalMaterial==1.0}}", Float) = 3.0 + _MTShininess ("Metallic Specular Shininess--{condition_show:{type:PROPERTY_BOOL,data:_MetalMaterial==1.0}}", Float) = 90.0 + _MTSpecularScale ("Metallic Specular Scale--{condition_show:{type:PROPERTY_BOOL,data:_MetalMaterial==1.0}}", Float) = 15.0 + _MTMapTileScale ("Metallic Matcap Tile Scale--{condition_show:{type:PROPERTY_BOOL,data:_MetalMaterial==1.0}}", Range(0.0, 2.0)) = 1.0 + _MTSpecularAttenInShadow ("Metallic Specular Attenuation in Shadow--{condition_show:{type:PROPERTY_BOOL,data:_MetalMaterial==1.0}}", Range(0.0, 1.0)) = 0.2 + _MTSharpLayerOffset ("Metallic Sharp Layer Offset--{condition_show:{type:PROPERTY_BOOL,data:_MetalMaterial==1.0}}", Range(0.001, 1.0)) = 1.0 + [HideInInspector] m_start_metallicscolor("Metallic Colors--{condition_show:{type:PROPERTY_BOOL,data:_MetalMaterial==1.0}}", Int) = 0 [Gamma] [HDR] _MTMapDarkColor ("Metallic Matcap Dark Color", Color) = (0.51, 0.3, 0.19, 1.0) [Gamma] [HDR] _MTMapLightColor ("Metallic Matcap Light Color", Color) = (1.0, 1.0, 1.0, 1.0) [Gamma] _MTShadowMultiColor ("Metallic Matcap Shadow Multiply Color", Color) = (0.78, 0.77, 0.82, 1.0) [Gamma] [HDR] _MTSpecularColor ("Metallic Specular Color", Color) = (1.0, 1.0, 1.0, 1.0) [Gamma] _MTSharpLayerColor ("Metallic Sharp Layer Color", Color) = (1.0, 1.0, 1.0, 1.0) - - [Header(Texture Line Options)] [Space(10)] _TextureLineSmoothness ("Texture Line Smoothness", Range(0.0, 1.0)) = 0.15 - _TextureLineThickness ("Texture Line Thickness", Range(0.0, 1.0)) = 0.55 - [Toggle] _TextureLineUse ("Use Texture Line?", Range(0.0, 1.0)) = 0.0 - _TextureLineDistanceControl ("Texture Line Distance Control", Vector) = (0.1, 0.6, 1.0, 1.0) - [Gamma] [HDR] _TextureLineMultiplier ("Texture Line Multiplier", Color) = (0.6, 0.6, 0.6, 1.0) - [HideInInspector] _TextureBiasWhenDithering ("Texture Dithering Bias", Float) = -1.0 - - [Header(Outline Options)] [Space(10)] _MaxOutlineZOffset ("Max Z-Offset", Float) = 1.0 + [HideInInspector] m_end_metallicscolor ("", Int) = 0 + [HideInInspector] m_end_metallics("", Int) = 0 + [HideInInspector] m_start_specular("Specular Reflections", Int) = 0 + [Toggle] _MTUseSpecularRamp ("Enable Specular", Float) = 0.0 + [SmallTexture]_MTSpecularRamp("Specular Ramp--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}",2D)= "white"{ } + _Shininess ("Shininess 1--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 10 + _Shininess2 ("Shininess 2--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 10 + _Shininess3 ("Shininess 3--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 10 + _Shininess4 ("Shininess 4--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 10 + _Shininess5 ("Shininess 5--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 10 + _SpecMulti ("Specular Multiplier 1--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 0.1 + _SpecMulti2 ("Specular Multiplier 2--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 0.1 + _SpecMulti3 ("Specular Multiplier 3--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 0.1 + _SpecMulti4 ("Specular Multiplier 4--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 0.1 + _SpecMulti5 ("Specular Multiplier 5--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Float) = 0.1 + [Gamma] _SpecularColor ("Specular Color--{condition_show:{type:PROPERTY_BOOL,data:_MTUseSpecularRamp==1.0}}", Color) = (1.0, 1.0, 1.0, 1.0) + [HideInInspector] m_end_specular("", Int) = 0 + [HideInInspector] m_end_reflections ("", Float) = 0 + //Reflections End + + //Outlines + [HideInInspector] m_start_outlines("Outlines", Float) = 0 + [Enum(None, 0, Normal, 1, Tangent, 2)] _OutlineType ("Outline Type", Float) = 1.0 + [Toggle] _FallbackOutlines ("Enable Fallback Outlines", Range(0.0, 1.0)) = 1.0 [Toggle] [HideInInspector] _ClipPlaneWorld ("Clip Plane World", Range(0.0, 1.0)) = 1.0 - [KeywordEnum(None, Normal, Tangent)] _OutlineType ("Outline Type", Float) = 1.0 - [Toggle] _FallbackOutlines ("Fallback Outlines? (default is on temporarily)", Range(0.0, 1.0)) = 1.0 _OutlineWidth ("Outline Width", Float) = 0.03 _Scale ("Outline Scale", Float) = 0.01 [Toggle] [HideInInspector] _UseClipPlane ("Use Clip Plane?", Range(0.0, 1.0)) = 0.0 [HideInInspector] _ClipPlane ("Clip Plane", Vector) = (0.0, 0.0, 0.0, 0.0) + [HideInInspector] m_start_outlinescolor("Outline Colors", Float) = 0 [Gamma] _OutlineColor ("Outline Color 1", Color) = (0.0, 0.0, 0.0, 1.0) [Gamma] _OutlineColor2 ("Outline Color 2", Color) = (0.0, 0.0, 0.0, 1.0) [Gamma] _OutlineColor3 ("Outline Color 3", Color) = (0.0, 0.0, 0.0, 1.0) [Gamma] _OutlineColor4 ("Outline Color 4", Color) = (0.0, 0.0, 0.0, 1.0) [Gamma] _OutlineColor5 ("Outline Color 5", Color) = (0.0, 0.0, 0.0, 1.0) + [HideInInspector] m_end_outlinescolor ("", Float) = 0 + [HideInInspector] m_start_outlinesoffset("Outline Offset & Adjustments", Float) = 0 _OutlineWidthAdjustScales ("Outline Width Adjust Scales", Vector) = (0.01, 0.245, 0.6, 0.0) _OutlineWidthAdjustZs ("Outline Width Adjust Zs", Vector) = (0.001, 2.0, 6.0, 0.0) + _MaxOutlineZOffset ("Max Z-Offset", Float) = 1.0 + [HideInInspector] m_end_outlinesoffset ("", Float) = 0 + [HideInInspector] m_end_outlines ("", Float) = 0 + //Outlines End + + //Special Effects + [HideInInspector] m_start_specialeffects("Special Effects", Float) = 0 + [HideInInspector] m_start_emissionglow("Emission / Archon Glow", Float) = 0 + [Enum(Default, 0, Custom, 1)] _EmissionType("Emission Type", Float) = 1.0 + [Gamma] _EmissionColor ("Emission Tint", Color) = (1.0, 1.0, 1.0, 1.0) + [NoScaleOffset] [HDR] _CustomEmissionTex ("Custom Emission Texture--{condition_show:{type:PROPERTY_BOOL,data:_EmissionType==1}}", 2D) = "black"{} + [NoScaleOffset] _CustomEmissionAOTex ("Custom Emission AO--{condition_show:{type:PROPERTY_BOOL,data:_EmissionType==1}}", 2D) = "white"{} + _EmissionStrength ("Emission Strength", Float) = 1.0 + [HideInInspector] m_start_emissioneyeglow("Eye & Archon Glow", Float) = 0 + [Toggle] _ToggleEyeGlow ("Enable Eye & Archon Glow", Range(0.0, 1.0)) = 1.0 + _EyeGlowStrength ("Eye & Archon Glow Strength", Float) = 1.0 + [HideInInspector] m_end_emissioneyeglow ("", Float) = 0 + [HideInInspector] m_start_emissionpulse("Pulsing Emission", Float) = 0 + [Toggle] _TogglePulse ("Enable Pulse", Range(0.0, 1.0)) = 0.0 + _PulseSpeed ("Pulse Speed", Float) = 1.0 + _PulseMinStrength ("Minimum Pulse Strength", Range(0.0, 1.0)) = 0.0 + _PulseMaxStrength ("Maximum Pulse Strength", Range(0.0, 1.0)) = 1.0 + [HideInInspector] m_end_emissionpulse ("", Float) = 0 + [HideInInspector] m_end_emissionglow ("", Float) = 0 - [Header(Debugging)] [Space(10)] [Toggle] _ReturnDiffuseRGB ("Show Diffuse", Range(0.0, 1.0)) = 0.0 + [HideInInspector] m_start_fresnel("Fresnel", Float) = 0 + [Toggle] _UseFresnel ("Enable Fresnel", Range(0.0, 1.0)) = 1.0 + [Gamma] _HitColor ("Fresnel Color", Color) = (0.0, 0.0, 0.0, 1.0) + [Gamma] _ElementRimColor ("Element Rim Color", Color) = (0.0, 0.0, 0.0, 1.0) + _HitColorScaler ("Fresnel Color Scaler", Float) = 6 + _HitColorFresnelPower ("Fresnel Power", Float) = 1.5 + [HideInInspector] m_end_fresnel ("", Float) = 0 + [HideInInspector] m_end_specialeffects ("", Float) = 0 + //Special Effects End + + //Rendering Options + [HideInInspector] m_start_renderingOptions("Rendering Options", Float) = 0 + [Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", Float) = 0 + [Enum(Off, 0, On, 1)] _ZWrite("ZWrite", Int) = 1 + [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4 + [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Source Blend", Int) = 1 + [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Destination Blend", Int) = 0 + [HideInInspector] m_start_debugOptions("Debug", Float) = 0 + [Toggle] _ReturnDiffuseRGB ("Show Diffuse", Range(0.0, 1.0)) = 0.0 [Toggle] _ReturnDiffuseA ("Show Diffuse Alpha", Range(0.0, 1.0)) = 0.0 [Toggle] _ReturnLightmapR ("Show Lightmap Red", Range(0.0, 1.0)) = 0.0 [Toggle] _ReturnLightmapG ("Show Lightmap Green", Range(0.0, 1.0)) = 0.0 @@ -170,26 +268,22 @@ [Toggle] _ReturnEmissionFactor ("Show Emission Factor", Range(0.0, 1.0)) = 0.0 [Toggle] _ReturnForwardVector ("Show Forward Vector (it should look blue)", Range(0.0, 1.0)) = 0.0 [Toggle] _ReturnRightVector ("Show Right Vector (it should look red)", Range(0.0, 1.0)) = 0.0 - - [Header(Rendering Options)] [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0 - //[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 4 - [Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Int) = 1 + [HideInInspector] m_end_debugOptions("Debug", Float) = 0 //[Enum(Thry.ColorMask)] _ColorMask ("Color Mask", Int) = 15 //_OffsetFactor ("Offset Factor", Float) = 0.0 //_OffsetUnits ("Offset Units", Float) = 0.0 //[ToggleUI]_RenderingReduceClipDistance ("Reduce Clip Distance", Float) = 0 //[ToggleUI]_IgnoreFog ("Ignore Fog", Float) = 0 //[HideInInspector] Instancing ("Instancing", Float) = 0 //add this property for instancing variants settings to be shown - - [Header(Blending Options)] //[Enum(Thry.BlendOp)]_BlendOp ("RGB Blend Op", Int) = 0 + //[Enum(Thry.BlendOp)]_BlendOpAlpha ("Alpha Blend Op", Int) = 0 - [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Source Blend", Int) = 1 - [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Destination Blend", Int) = 0 //[Space][ThryHeaderLabel(Additive Blending, 13)] //[Enum(Thry.BlendOp)]_AddBlendOp ("RGB Blend Op", Int) = 0 //[Enum(Thry.BlendOp)]_AddBlendOpAlpha ("Alpha Blend Op", Int) = 0 //[Enum(UnityEngine.Rendering.BlendMode)] _AddSrcBlend ("Source Blend", Int) = 1 //[Enum(UnityEngine.Rendering.BlendMode)] _AddDstBlend ("Destination Blend", Int) = 1 + [HideInInspector] m_end_renderingOptions("Rendering Options", Float) = 0 + //Rendering Options End } SubShader{ Tags{ "RenderType"="Opaque" "Queue"="Geometry" } @@ -444,4 +538,5 @@ ENDHLSL } } + CustomEditor "Thry.ShaderEditor" } diff --git a/Resources/Avatar_Tex_Body_Normalmap.png b/Resources/Avatar_Tex_Body_Normalmap.png new file mode 100644 index 0000000..9b858b6 Binary files /dev/null and b/Resources/Avatar_Tex_Body_Normalmap.png differ diff --git a/Resources/Avatar_Tex_MetalMap.png b/Resources/Avatar_Tex_MetalMap.png new file mode 100644 index 0000000..48be26b Binary files /dev/null and b/Resources/Avatar_Tex_MetalMap.png differ diff --git a/Resources/Avatar_Tex_Specular_Ramp.png b/Resources/Avatar_Tex_Specular_Ramp.png new file mode 100644 index 0000000..a888b0b Binary files /dev/null and b/Resources/Avatar_Tex_Specular_Ramp.png differ diff --git a/Resources/Eff_Gradient_Repeat_01.png b/Resources/Eff_Gradient_Repeat_01.png new file mode 100644 index 0000000..c586de7 Binary files /dev/null and b/Resources/Eff_Gradient_Repeat_01.png differ diff --git a/Resources/Eff_WeaponsTotem_Dissolve_00.png b/Resources/Eff_WeaponsTotem_Dissolve_00.png new file mode 100644 index 0000000..57d8ee5 Binary files /dev/null and b/Resources/Eff_WeaponsTotem_Dissolve_00.png differ diff --git a/Resources/Eff_WeaponsTotem_Grain_00.png b/Resources/Eff_WeaponsTotem_Grain_00.png new file mode 100644 index 0000000..c862f47 Binary files /dev/null and b/Resources/Eff_WeaponsTotem_Grain_00.png differ diff --git a/Resources/discord.png b/Resources/discord.png new file mode 100644 index 0000000..5f25e5e Binary files /dev/null and b/Resources/discord.png differ diff --git a/Resources/github.png b/Resources/github.png new file mode 100644 index 0000000..6cb3b70 Binary files /dev/null and b/Resources/github.png differ diff --git a/Resources/primobig.jpg b/Resources/primobig.jpg new file mode 100644 index 0000000..912ebed Binary files /dev/null and b/Resources/primobig.jpg differ diff --git a/Resources/primosmall.png b/Resources/primosmall.png new file mode 100644 index 0000000..19563f5 Binary files /dev/null and b/Resources/primosmall.png differ diff --git a/Assets/Scripts/AverageNormals.cs b/Scripts/Festivity Scripts/Editor/AverageNormals.cs similarity index 100% rename from Assets/Scripts/AverageNormals.cs rename to Scripts/Festivity Scripts/Editor/AverageNormals.cs diff --git a/Assets/Scripts/ImportMat.cs b/Scripts/Festivity Scripts/Editor/ImportMat.cs similarity index 100% rename from Assets/Scripts/ImportMat.cs rename to Scripts/Festivity Scripts/Editor/ImportMat.cs diff --git a/Scripts/ThryEditor/.gitattributes b/Scripts/ThryEditor/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/Scripts/ThryEditor/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/Scripts/ThryEditor/CREDITS.txt b/Scripts/ThryEditor/CREDITS.txt new file mode 100644 index 0000000..8d85929 --- /dev/null +++ b/Scripts/ThryEditor/CREDITS.txt @@ -0,0 +1,6 @@ +Shader Optimizer : Kaj (https://github.com/DarthShader) +sRGBWarningDrawer : z3y (https://github.com/z3y) +Auto Anchor Feature : Pumkin (https://github.com/rurre) + +Shader Optimizer - Property Renaming : RequiDev (https://github.com/RequiDev) +Shader Optimizer - Shared Locked Shaders : 3 (https://github.com/Float3) \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/ChannelPreview.shader b/Scripts/ThryEditor/Editor/ChannelPreview.shader new file mode 100644 index 0000000..cfe700f --- /dev/null +++ b/Scripts/ThryEditor/Editor/ChannelPreview.shader @@ -0,0 +1,64 @@ +Shader "Hidden/Thry/ChannelPreview" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _Channel ("Channel", Float) = 0.0 + } + SubShader + { + Tags { "RenderType"="Transparent" } + LOD 100 + + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv : TEXCOORD0; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + float4 vertex : SV_POSITION; + }; + + sampler2D _MainTex; + float4 _MainTex_ST; + float _Channel; + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = v.uv; + return o; + } + + fixed4 frag (v2f i) : SV_Target + { + // sample the texture + fixed4 col = tex2D(_MainTex, i.uv); + if(_Channel == -1.0) return fixed4(col.r, col.g, col.b, col.a); + if(_Channel == 0.0) return fixed4(col.r, 0, 0, 1); + if(_Channel == 1.0) return fixed4(0, col.g, 0, 1); + if(_Channel == 2.0) return fixed4(0, 0, col.b, 1); + if(_Channel == 3.0) return fixed4(col.a, col.a, col.a, 1); + if(_Channel == 4.0) + { + float val = max(col.r, max(col.g, col.b)); + return fixed4(val, val, val, 1); + } + return fixed4(0, 0, 0, 1); + } + ENDCG + } + } +} diff --git a/Scripts/ThryEditor/Editor/Config.cs b/Scripts/ThryEditor/Editor/Config.cs new file mode 100644 index 0000000..c6c69de --- /dev/null +++ b/Scripts/ThryEditor/Editor/Config.cs @@ -0,0 +1,199 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class Config + { + // consts + private const string PATH_CONFIG_FILE = "Thry/Config.json"; + private const string VERSION = "2.42.2"; + + // static + private static Config config; + + public static void OnCompile() + { + if (!File.Exists(PATH_CONFIG_FILE)) + { + //Settings.firstTimePopup(); + } + else + { + string prevVersion = Singleton.verion; + string installedVersion = VERSION; + int versionComparision = Helper.CompareVersions(installedVersion, prevVersion); + if (versionComparision != 0) + { + config.verion = VERSION; + config.Save(); + } + if (versionComparision == 1) + { + Settings.updatedPopup(versionComparision); + } + else if (versionComparision == -1) + { + config.OnUpgrade(prevVersion); + Debug.Log(">>> Thry Editor has been updated to version " + installedVersion); + } + } + } + + public static Config Singleton + { + get + { + if (config == null) + { + if (File.Exists(PATH_CONFIG_FILE)) + config = JsonUtility.FromJson(FileHelper.ReadFileIntoString(PATH_CONFIG_FILE)); + else + config = new Config().Save(); + } + return config; + } + } + + //actual config class + public TextureDisplayType default_texture_type = TextureDisplayType.small; + public bool showRenderQueue = true; + public bool showManualReloadButton = false; + public bool allowCustomLockingRenaming = false; + public bool autoMarkPropertiesAnimated = true; + public TextureImporterFormat texturePackerCompressionWithAlphaOverwrite = TextureImporterFormat.Automatic; + public TextureImporterFormat texturePackerCompressionNoAlphaOverwrite = TextureImporterFormat.Automatic; + public TextureImporterFormat gradientEditorCompressionOverwrite = TextureImporterFormat.Automatic; + + public string locale = "English"; + + public string gradient_name = "gradient_.png"; + + public bool autoSetAnchorOverride = true; + public HumanBodyBones humanBoneAnchor = HumanBodyBones.Spine; + public string anchorOverrideObjectName = "AutoAnchorObject"; + public bool autoSetAnchorAskedOnce = false; + public bool enableDeveloperMode = false; + public bool disableUnlockedShaderStrippingOnBuild = false; + public bool forceAsyncCompilationPreview = true; + public bool fixKeywordsWhenLocking = true; + public bool saveAfterLockUnlock = true; + + public string verion = VERSION; + + public Config Save() + { + FileHelper.WriteStringToFile(JsonUtility.ToJson(this, true), PATH_CONFIG_FILE); + return this; + } + + private void OnUpgrade(string oldVersionString) + { + Version newVersion = new Version(VERSION); + Version oldVersion = new Version(oldVersionString); + + //Upgrade locking valuesd from Animated property to tags + if (newVersion >= "2.11.0" && oldVersion > "2.0" && oldVersion < "2.11.0") + { + ShaderOptimizer.UpgradeAnimatedPropertiesToTagsOnAllMaterials(); + } + } + } + + public class Version + { + private string value; + + public Version(string s) + { + if (string.IsNullOrEmpty(s)) s = "0"; + this.value = s; + } + + public static bool operator ==(Version x, Version y) + { + return Helper.CompareVersions(x.value, y.value) == 0; + } + + public static bool operator !=(Version x, Version y) + { + return Helper.CompareVersions(x.value, y.value) != 0; + } + + public static bool operator >(Version x, Version y) + { + return Helper.CompareVersions(x.value, y.value) == -1; + } + + public static bool operator <(Version x, Version y) + { + return Helper.CompareVersions(x.value, y.value) == 1; + } + + public static bool operator >=(Version x, Version y) + { + return Helper.CompareVersions(x.value, y.value) < 1; + } + + public static bool operator <=(Version x, Version y) + { + return Helper.CompareVersions(x.value, y.value) > -1; + } + + public static bool operator ==(Version x, string y) + { + return Helper.CompareVersions(x.value, y) == 0; + } + + public static bool operator !=(Version x, string y) + { + return Helper.CompareVersions(x.value, y) != 0; + } + + public static bool operator >(Version x, string y) + { + return Helper.CompareVersions(x.value, y) == -1; + } + + public static bool operator <(Version x, string y) + { + return Helper.CompareVersions(x.value, y) == 1; + } + + public static bool operator >=(Version x, string y) + { + return Helper.CompareVersions(x.value, y) < 1; + } + + public static bool operator <=(Version x, string y) + { + return Helper.CompareVersions(x.value, y) > -1; + } + + public override bool Equals(object o) + { + if (o is Version) return this == (o as Version); + if (o is string) return this == (o as string); + return false; + } + + public override string ToString() + { + return value; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/Scripts/ThryEditor/Editor/DataStructs.cs b/Scripts/ThryEditor/Editor/DataStructs.cs new file mode 100644 index 0000000..54886a2 --- /dev/null +++ b/Scripts/ThryEditor/Editor/DataStructs.cs @@ -0,0 +1,804 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; + +namespace Thry +{ + #region Constants + public class PATH + { + public const string TEXTURES_DIR = "Assets/textures"; + public const string RSP_NEEDED_PATH = "Assets/"; + + public const string DELETING_DIR = "Thry/trash"; + + public const string PERSISTENT_DATA = "Thry/persistent_data"; + + public const string GRADIENT_INFO_FILE = "Thry/gradients"; + + public const string LINKED_MATERIALS_FILE = "Thry/linked_materials.json"; + } + + public class URL + { + public const string MODULE_COLLECTION = "https://raw.githubusercontent.com/Thryrallo/ThryEditorStreamedResources/main/packages.json"; + public const string SETTINGS_MESSAGE_URL = "https://raw.githubusercontent.com/Thryrallo/ThryEditorStreamedResources/main/Messages/settingsWindow.json"; + public const string COUNT_PROJECT = "http://thryeditor.thryrallo.de/count_project.php"; + public const string COUNT_USER = "http://thryeditor.thryrallo.de/count_user.php"; + } + + public class DEFINE_SYMBOLS + { + public const string IMAGING_EXISTS = "IMAGING_DLL_EXISTS"; + } + + public class RESOURCE_GUID + { + public const string RECT = "2329f8696fd09a743a5baf2a5f4986af"; + public const string ICON_LINK = "e85fd0a0e4e4fea46bb3fdeab5c3fb07"; + public const string ICON_THRY = "693aa4c2cdc578346a196469a06ddbba"; + } + #endregion + + public class DrawingData + { + public static TextureProperty CurrentTextureProperty; + public static Rect LastGuiObjectRect; + public static Rect LastGuiObjectHeaderRect; + public static Rect TooltipCheckRect; + public static float IconsPositioningHeight; + public static bool LastPropertyUsedCustomDrawer; + public static bool LastPropertyDoesntAllowAnimation; + public static DrawerType LastPropertyDrawerType; + public static MaterialPropertyDrawer LastPropertyDrawer; + public static List LastPropertyDecorators = new List(); + public static bool IsEnabled = true; + public static bool IsCollectingProperties = false; + + public static ShaderPart LastInitiatedPart; + + public static void ResetLastDrawerData() + { + LastPropertyUsedCustomDrawer = false; + LastPropertyDoesntAllowAnimation = false; + LastPropertyDrawer = null; + LastPropertyDrawerType = DrawerType.None; + LastPropertyDecorators.Clear(); + } + + public static void RegisterDecorator(MaterialPropertyDrawer drawer) + { + if(IsCollectingProperties) + { + LastPropertyDecorators.Add(drawer); + } + } + } + + public enum DrawerType + { + None + } + + public class GradientData + { + public Texture PreviewTexture; + public Gradient Gradient; + } + + public enum TextureDisplayType + { + small, big, big_basic + } + + //--------------Shader Data Structs-------------------- + + #region In Shader Data + public class PropertyOptions + { + public int offset = 0; + public string tooltip = ""; + public DefineableAction altClick; + public DefineableAction onClick; + public DefineableCondition condition_show = new DefineableCondition(); + public string condition_showS; + public DefineableCondition condition_enable = null; + public PropertyValueAction[] on_value_actions; + public string on_value; + public DefineableAction[] actions; + public ButtonData button_help; + public TextureData texture; + public string[] reference_properties; + public string reference_property; + public bool force_texture_options = false; + public bool is_visible_simple = false; + public string file_name; + public string remote_version_url; + public string generic_string; + public bool never_lock; + public bool draw_border; + + public static PropertyOptions Deserialize(string s) + { + if(s == null) return new PropertyOptions(); + s = s.Replace("''", "\""); + PropertyOptions options = Parser.Deserialize(s); + if (options == null) return new PropertyOptions(); + // The following could be removed since the parser can now handle it. leaving it in for now /shrug + if (options.condition_showS != null) + { + options.condition_show = DefineableCondition.Parse(options.condition_showS); + } + if (options.on_value != null) + { + options.on_value_actions = PropertyValueAction.ParseToArray(options.on_value); + } + return options; + } + } + + public class ButtonData + { + public string text = ""; + public TextureData texture = null; + public DefineableAction action = new DefineableAction(); + public string hover = ""; + public bool center_position = false; + public DefineableCondition condition_show = new DefineableCondition(); + } + + public class TextureData + { + public string name = null; + public string guid = null; + public int width = 128; + public int height = 128; + + public char channel = 'r'; + + public int ansioLevel = 1; + public FilterMode filterMode = FilterMode.Bilinear; + public TextureWrapMode wrapMode = TextureWrapMode.Repeat; + public bool center_position = false; + bool _isLoading; + + public void ApplyModes(Texture texture) + { + texture.filterMode = filterMode; + texture.wrapMode = wrapMode; + texture.anisoLevel = ansioLevel; + } + public void ApplyModes(string path) + { + TextureImporter importer = (TextureImporter)AssetImporter.GetAtPath(path); + importer.filterMode = filterMode; + importer.wrapMode = wrapMode; + importer.anisoLevel = ansioLevel; + importer.SaveAndReimport(); + } + + static Dictionary s_loaded_textures = new Dictionary(); + public Texture loaded_texture + { + get + { + if(guid != null) + { + if(!s_loaded_textures.ContainsKey(guid) || s_loaded_textures[guid] == null) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + if (path != null) + s_loaded_textures[guid] = AssetDatabase.LoadAssetAtPath(path); + else + s_loaded_textures[guid] = Texture2D.whiteTexture; + } + return s_loaded_textures[guid]; + }else if(name != null) + { + if(!s_loaded_textures.ContainsKey(name) || s_loaded_textures[name] == null) + { + // Retrieve downloaded image from sessionstate (base64 encoded) + if(SessionState.GetString(name, "") != "") + { + s_loaded_textures[name] = new Texture2D(1,1, TextureFormat.ARGB32, false); + ImageConversion.LoadImage((Texture2D)s_loaded_textures[name], Convert.FromBase64String(SessionState.GetString(name, "")), false); + return s_loaded_textures[name]; + } + + if(IsUrl()) + { + if(!_isLoading) + { + s_loaded_textures[name] = Texture2D.whiteTexture; + WebHelper.DownloadBytesASync(name, (byte[] b) => + { + _isLoading = false; + Texture2D tex = new Texture2D(1,1, TextureFormat.ARGB32, false); + ImageConversion.LoadImage(tex, b, false); + s_loaded_textures[name] = tex; + SessionState.SetString(name, Convert.ToBase64String(((Texture2D)s_loaded_textures[name]).EncodeToPNG())); + }); + _isLoading = true; + } + }else + { + string path = FileHelper.FindFile(name, "texture"); + if (path != null) + s_loaded_textures[name] = AssetDatabase.LoadAssetAtPath(path); + else + s_loaded_textures[name] = Texture2D.whiteTexture; + } + } + return s_loaded_textures[name]; + } + return Texture2D.whiteTexture; + } + } + + private static TextureData ParseForThryParser(string s) + { + if(s.StartsWith("{") == false) + { + return new TextureData() + { + name = s + }; + } + return Parser.Deserialize(s); + } + + bool IsUrl() + { + return name.StartsWith("http") && (name.EndsWith(".jpg") || name.EndsWith(".png")); + } + } + + public class PropertyValueAction + { + public string value; + public DefineableAction[] actions; + + public bool Execute(MaterialProperty p, Material[] targets) + { + if( + (p.type == MaterialProperty.PropType.Float && p.floatValue.ToString() == value) || + (p.type == MaterialProperty.PropType.Range && p.floatValue.ToString() == value) || + (p.type == MaterialProperty.PropType.Color && p.colorValue.ToString() == value) || + (p.type == MaterialProperty.PropType.Vector && p.vectorValue.ToString() == value) || + (p.type == MaterialProperty.PropType.Texture && ((p.textureValue == null) == (value == "0"))) || + (p.type == MaterialProperty.PropType.Texture && ((p.textureValue != null) == (value == "1"))) || + (p.type == MaterialProperty.PropType.Texture && (p.textureValue != null && p.textureValue.name == value)) + ) + {; + foreach (DefineableAction a in actions) + a.Perform(targets); + return true; + } + return false; + } + + private static PropertyValueAction ParseForThryParser(string s) + { + return Parse(s); + } + + // value,property1=value1,property2=value2 + public static PropertyValueAction Parse(string s) + { + s = s.Trim(); + string[] valueAndActions = s.Split(new string[]{"=>"}, System.StringSplitOptions.RemoveEmptyEntries); + if (valueAndActions.Length > 1) + { + PropertyValueAction propaction = new PropertyValueAction(); + propaction.value = valueAndActions[0]; + List actions = new List(); + string[] actionStrings = valueAndActions[1].Split(';'); + for (int i = 0; i < actionStrings.Length; i++) + { + if(string.IsNullOrWhiteSpace(actionStrings[i])) + continue; + actions.Add(DefineableAction.Parse(actionStrings[i])); + } + propaction.actions = actions.ToArray(); + return propaction; + } + return null; + } + + private static PropertyValueAction[] ParseToArrayForThryParser(string s) + { + return ParseToArray(s); + } + + public static PropertyValueAction[] ParseToArray(string s) + { + //s := 0=>p1=v1;p2=v2;1=>p1=v3... + List propactions = new List(); + string[] valueAndActionMatches = Regex.Matches(s, @"[^;]+=>.+?(?=(;[^;]+=>)|$)", RegexOptions.Multiline).Cast().Select(m => m.Value).ToArray(); + foreach (string p in valueAndActionMatches) + { + PropertyValueAction propertyValueAction = PropertyValueAction.Parse(p); + if (propertyValueAction != null) + propactions.Add(propertyValueAction); + } + return propactions.ToArray(); + } + } + + public class DefineableAction + { + public DefineableActionType type = DefineableActionType.NONE; + public string data = ""; + public void Perform(Material[] targets) + { + switch (type) + { + case DefineableActionType.URL: + Application.OpenURL(data); + break; + case DefineableActionType.SET_PROPERTY: + string[] set = Regex.Split(data, @"="); + if (set.Length > 1) + MaterialHelper.SetMaterialValue(set[0].Trim(), set[1].Trim()); + break; + case DefineableActionType.SET_TAG: + string[] keyValue = Regex.Split(data, @"="); + foreach (Material m in targets) + m.SetOverrideTag(keyValue[0].Trim(), keyValue[1].Trim()); + break; + case DefineableActionType.SET_SHADER: + Shader shader = Shader.Find(data); + if (shader != null) + { + foreach (Material m in targets) + m.shader = shader; + } + break; + case DefineableActionType.OPEN_EDITOR: + System.Type t = Helper.FindTypeByFullName(data); + if (t != null) + { + try + { + EditorWindow window = EditorWindow.GetWindow(t); + window.titleContent = new GUIContent("TPS Setup Wizard"); + window.Show(); + }catch(System.Exception e) + { + Debug.LogError("[Thry] Couldn't open Editor Window of type" + data); + Debug.LogException(e); + } + } + break; + } + } + + private static DefineableAction ParseForThryParser(string s) + { + return Parse(s); + } + public static DefineableAction Parse(string s) + { + s = s.Trim(); + DefineableAction action = new DefineableAction(); + if (s.StartsWith("http", StringComparison.Ordinal) || s.StartsWith("www", StringComparison.Ordinal)) + { + action.type = DefineableActionType.URL; + action.data = s; + } + else if (s.StartsWith("tag::", StringComparison.Ordinal)) + { + action.type = DefineableActionType.SET_TAG; + action.data = s.Replace("tag::", ""); + } + else if (s.StartsWith("shader=", StringComparison.Ordinal)) + { + action.type = DefineableActionType.SET_SHADER; + action.data = s.Replace("shader=", ""); + } + else if (s.Contains("=")) + { + action.type = DefineableActionType.SET_PROPERTY; + action.data = s; + } + else if (Regex.IsMatch(s, @"\w+(\.\w+)")) + { + action.type = DefineableActionType.OPEN_EDITOR; + action.data = s; + } + return action; + } + + public static DefineableAction ParseDrawerParameter(string s) + { + s = s.Trim(); + DefineableAction action = new DefineableAction(); + if (s.StartsWith("youtube#", StringComparison.Ordinal)) + { + action.type = DefineableActionType.URL; + action.data = "https://www.youtube.com/watch?v="+s.Substring(8); + } + return action; + } + + public override string ToString() + { + return $"{{{type},{data}}}"; + } + } + + public enum DefineableActionType + { + NONE, + URL, + SET_PROPERTY, + SET_SHADER, + SET_TAG, + OPEN_EDITOR, + } + + public class DefineableCondition + { + public DefineableConditionType type = DefineableConditionType.NONE; + public string data = ""; + public DefineableCondition condition1; + public DefineableCondition condition2; + + CompareType _compareType; + string _obj; + ShaderProperty _propertyObj; + Material _materialInsteadOfEditor; + + string _value; + float _floatValue; + + bool _hasConstantValue; + bool _constantValue; + + bool _isInit = false; + public void Init() + { + if (_isInit) return; + _hasConstantValue = true; + if (type == DefineableConditionType.NONE) { _constantValue = true; } + else if (type == DefineableConditionType.TRUE) { _constantValue = true; } + else if (type == DefineableConditionType.FALSE) { _constantValue = false; } + else + { + var (compareType, compareString) = GetComparetor(); + _compareType = compareType; + + string[] parts = Regex.Split(data, compareString); + _obj = parts[0].Trim(); + _value = parts[parts.Length - 1].Trim(); + + _floatValue = Parser.ParseFloat(_value); + if (ShaderEditor.Active != null && ShaderEditor.Active.PropertyDictionary.ContainsKey(_obj)) + _propertyObj = ShaderEditor.Active.PropertyDictionary[_obj]; + + if (type == DefineableConditionType.EDITOR_VERSION) InitEditorVersion(); + else if (type == DefineableConditionType.VRC_SDK_VERSION) InitVRCSDKVersion(); + else _hasConstantValue = false; + } + + _isInit = true; + } + + void InitEditorVersion() + { + int c_ev = Helper.CompareVersions(Config.Singleton.verion, _value); + if (_compareType == CompareType.EQUAL) _constantValue = c_ev == 0; + if (_compareType == CompareType.NOT_EQUAL) _constantValue = c_ev != 0; + if (_compareType == CompareType.SMALLER) _constantValue = c_ev == 1; + if (_compareType == CompareType.BIGGER) _constantValue = c_ev == -1; + if (_compareType == CompareType.BIGGER_EQ) _constantValue = c_ev == -1 || c_ev == 0; + if (_compareType == CompareType.SMALLER_EQ) _constantValue = c_ev == 1 || c_ev == 0; + } + + void InitVRCSDKVersion() + { + if (VRCInterface.Get().Sdk_information.type == VRCInterface.VRC_SDK_Type.NONE) + { + _constantValue = false; + return; + } + int c_vrc = Helper.CompareVersions(VRCInterface.Get().Sdk_information.installed_version, _value); + if (_compareType == CompareType.EQUAL) _constantValue = c_vrc == 0; + if (_compareType == CompareType.NOT_EQUAL) _constantValue = c_vrc != 0; + if (_compareType == CompareType.SMALLER) _constantValue = c_vrc == 1; + if (_compareType == CompareType.BIGGER) _constantValue = c_vrc == -1; + if (_compareType == CompareType.BIGGER_EQ) _constantValue = c_vrc == -1 || c_vrc == 0; + if (_compareType == CompareType.SMALLER_EQ) _constantValue = c_vrc == 1 || c_vrc == 0; + } + + public bool Test() + { + Init(); + if (_hasConstantValue) return _constantValue; + + MaterialProperty materialProperty = null; + switch (type) + { + case DefineableConditionType.PROPERTY_BOOL: + materialProperty = GetMaterialProperty(); + if (materialProperty == null) return false; + if (_compareType == CompareType.NONE) return materialProperty.floatValue == 1; + if (_compareType == CompareType.EQUAL) return materialProperty.floatValue == _floatValue; + if (_compareType == CompareType.NOT_EQUAL) return materialProperty.floatValue != _floatValue; + if (_compareType == CompareType.SMALLER) return materialProperty.floatValue < _floatValue; + if (_compareType == CompareType.BIGGER) return materialProperty.floatValue > _floatValue; + if (_compareType == CompareType.BIGGER_EQ) return materialProperty.floatValue >= _floatValue; + if (_compareType == CompareType.SMALLER_EQ) return materialProperty.floatValue <= _floatValue; + break; + case DefineableConditionType.TEXTURE_SET: + materialProperty = GetMaterialProperty(); + if (materialProperty == null) return false; + return (materialProperty.textureValue == null) == (_compareType == CompareType.EQUAL); + case DefineableConditionType.DROPDOWN: + materialProperty = GetMaterialProperty(); + if (materialProperty == null) return false; + if (_compareType == CompareType.NONE) return materialProperty.floatValue == 1; + if (_compareType == CompareType.EQUAL) return "" + materialProperty.floatValue == _value; + if (_compareType == CompareType.NOT_EQUAL) return "" + materialProperty.floatValue != _value; + break; + case DefineableConditionType.PROPERTY_IS_ANIMATED: + return ShaderOptimizer.IsAnimated(_materialInsteadOfEditor, _obj); + case DefineableConditionType.PROPERTY_IS_NOT_ANIMATED: + return !ShaderOptimizer.IsAnimated(_materialInsteadOfEditor, _obj); + case DefineableConditionType.AND: + if(condition1!=null&&condition2!=null) return condition1.Test() && condition2.Test(); + break; + case DefineableConditionType.OR: + if(condition1 != null && condition2 != null) return condition1.Test() || condition2.Test(); + break; + case DefineableConditionType.NOT: + if(condition1 != null) return !condition1.Test(); + break; + } + + return true; + } + + private MaterialProperty GetMaterialProperty() + { + if(_materialInsteadOfEditor) return MaterialEditor.GetMaterialProperty(new Material[]{_materialInsteadOfEditor}, _obj); + if(_propertyObj != null) return _propertyObj.MaterialProperty; + return null; + } + private (CompareType,string) GetComparetor() + { + if (data.Contains("==")) + return (CompareType.EQUAL,"=="); + if (data.Contains("!=")) + return (CompareType.NOT_EQUAL,"!="); + if (data.Contains(">=")) + return (CompareType.BIGGER_EQ,">="); + if (data.Contains("<=")) + return (CompareType.SMALLER_EQ,"<="); + if (data.Contains(">")) + return (CompareType.BIGGER,">"); + if (data.Contains("<")) + return (CompareType.SMALLER,"<"); + return (CompareType.NONE,"##"); + } + + public override string ToString() + { + switch (type) + { + case DefineableConditionType.PROPERTY_BOOL: + return data; + case DefineableConditionType.EDITOR_VERSION: + return "EDITOR_VERSION" + data; + case DefineableConditionType.VRC_SDK_VERSION: + return "VRC_SDK_VERSION" + data; + case DefineableConditionType.TEXTURE_SET: + return "TEXTURE_SET" + data; + case DefineableConditionType.DROPDOWN: + return "DROPDOWN" + data; + case DefineableConditionType.PROPERTY_IS_ANIMATED: + return $"isAnimated({data})"; + case DefineableConditionType.PROPERTY_IS_NOT_ANIMATED: + return $"isNotAnimated({data})"; + case DefineableConditionType.AND: + if (condition1 != null && condition2 != null) return "("+condition1.ToString() + "&&" + condition2.ToString()+")"; + break; + case DefineableConditionType.OR: + if (condition1 != null && condition2 != null) return "("+condition1.ToString()+"||"+condition2.ToString()+")"; + break; + case DefineableConditionType.NOT: + if (condition1 != null) return "!"+condition1.ToString(); + break; + } + return ""; + } + + private static DefineableCondition ParseForThryParser(string s) + { + return Parse(s); + } + + private static readonly char[] ComparissionLiteralsToCheckFor = "*><=".ToCharArray(); + public static DefineableCondition Parse(string s, Material useThisMaterialInsteadOfOpenEditor = null, int start = 0, int end = -1) + { + if(end == -1) end = s.Length; + DefineableCondition con; + + // Debug.Log("Parsing: " + s.Substring(start, end - start)); + + int depth = 0; + int bracketStart = -1; + int bracketEnd = -1; + for(int i = start; i < end; i++) + { + char c = s[i]; + if(c == '(') + { + depth += 1; + if(depth == 1) + { + bracketStart = i; + } + }else if(c == ')') + { + if(depth == 1) + { + bracketEnd = i; + } + depth -= 1; + }else if(depth == 0) + { + if(c == '&') + { + con = new DefineableCondition(); + con._materialInsteadOfEditor = useThisMaterialInsteadOfOpenEditor; + + con.type = DefineableConditionType.AND; + con.condition1 = Parse(s, useThisMaterialInsteadOfOpenEditor, start, i); + con.condition2 = Parse(s, useThisMaterialInsteadOfOpenEditor, i + (s[i+1] == '&' ? 2 : 1), end); + return con; + }else if(c == '|') + { + + con = new DefineableCondition(); + con._materialInsteadOfEditor = useThisMaterialInsteadOfOpenEditor; + + con.type = DefineableConditionType.OR; + con.condition1 = Parse(s, useThisMaterialInsteadOfOpenEditor, start, i); + con.condition2 = Parse(s, useThisMaterialInsteadOfOpenEditor, i + (s[i + 1] == '|' ? 2 : 1), end); + return con; + } + } + } + + + bool isInverted = IsInverted(s, ref start); + + // if no AND or OR was found, check for brackets + if(bracketStart != -1 && bracketEnd != -1) + { + con = Parse(s, useThisMaterialInsteadOfOpenEditor, bracketStart + 1, bracketEnd); + }else + { + con = ParseSingle(s.Substring(start, end - start), useThisMaterialInsteadOfOpenEditor); + } + + if(isInverted) + { + DefineableCondition inverted = new DefineableCondition(); + inverted._materialInsteadOfEditor = useThisMaterialInsteadOfOpenEditor; + inverted.type = DefineableConditionType.NOT; + inverted.condition1 = con; + return inverted; + } + + return con; + } + + static bool IsInverted(string s, ref int start) + { + for(int i = start; i < s.Length; i++) + { + if(s[i] == '!') + { + start += 1; + return true; + } + if(s[i] != ' ') + return false; + } + return false; + } + + static DefineableCondition ParseSingle(string s, Material useThisMaterialInsteadOfOpenEditor = null) + { + // Debug.Log("Parsing single: " + s); + + DefineableCondition con = new DefineableCondition(); + con._materialInsteadOfEditor = useThisMaterialInsteadOfOpenEditor; + + if(s.IndexOfAny(ComparissionLiteralsToCheckFor) != -1) + { + //is a comparission + con.data = s; + con.type = DefineableConditionType.PROPERTY_BOOL; + if (s.StartsWith("VRCSDK", StringComparison.Ordinal)) + { + con.type = DefineableConditionType.VRC_SDK_VERSION; + con.data = s.Replace("VRCSDK", ""); + }else if (s.StartsWith("ThryEditor", StringComparison.Ordinal)) + { + con.type = DefineableConditionType.EDITOR_VERSION; + con.data = s.Replace("ThryEditor", ""); + }else if(IsTextureNullComparission(s, useThisMaterialInsteadOfOpenEditor)) + { + con.type = DefineableConditionType.TEXTURE_SET; + con.data = s.Replace("TEXTURE_SET", ""); + } + return con; + } + if(s.StartsWith("isNotAnimated(", StringComparison.Ordinal)) + { + con.type = DefineableConditionType.PROPERTY_IS_NOT_ANIMATED; + con.data = s.Replace("isNotAnimated(", "").TrimEnd(')'); + return con; + } + if(s.StartsWith("isAnimated(", StringComparison.Ordinal)) + { + con.type = DefineableConditionType.PROPERTY_IS_ANIMATED; + con.data = s.Replace("isAnimated(", "").TrimEnd(')'); + return con; + } + return con; + } + + static bool IsTextureNullComparission(string data, Material useThisMaterialInsteadOfOpenEditor = null) + { + // Check if property is a texture property && is checking for null + Material m = GetReferencedMaterial(useThisMaterialInsteadOfOpenEditor); + if( m == null) return false; + if(data.Length < 7) return false; + if(data.EndsWith("null") == false) return false; + string propertyName = data.Substring(0, data.Length - 6); + if(m.HasProperty(propertyName) == false) return false; + MaterialProperty p = MaterialEditor.GetMaterialProperty(new Material[]{m}, propertyName); + return p.type == MaterialProperty.PropType.Texture; + } + + static Material GetReferencedMaterial(Material useThisMaterialInsteadOfOpenEditor = null) + { + if( useThisMaterialInsteadOfOpenEditor != null ) return useThisMaterialInsteadOfOpenEditor; + if( ShaderEditor.Active != null ) return ShaderEditor.Active.Materials[0]; + return null; + } + } + + enum CompareType { NONE,BIGGER,SMALLER,EQUAL,NOT_EQUAL,BIGGER_EQ,SMALLER_EQ } + + public enum DefineableConditionType + { + NONE, + TRUE, + FALSE, + PROPERTY_BOOL, + PROPERTY_IS_ANIMATED, + PROPERTY_IS_NOT_ANIMATED, + EDITOR_VERSION, + VRC_SDK_VERSION, + TEXTURE_SET, + DROPDOWN, + AND, + OR, + NOT + } + + #endregion +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/DecalGizmos.shader b/Scripts/ThryEditor/Editor/DecalGizmos.shader new file mode 100644 index 0000000..df36d93 --- /dev/null +++ b/Scripts/ThryEditor/Editor/DecalGizmos.shader @@ -0,0 +1,136 @@ +Shader "Unlit/DecalGizmos" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _Color("Color", Color) = (1,1,1,1) + _Position("Position", Vector) = (0.5,0.5,0,0) + _Rotation("Rotation", Float) = 0 + _Scale("Scale", Vector) = (1,1,0,0) + _Offset("Scale Offset", Vector) = (0,0,0,0) + _DecalTex("Decal Texture", 2D) = "white" {} + _UVChannel("UV Channel", Int) = 0 + } + SubShader + { + Tags { "RenderType"="Transparent" "Queue"="Transparent" } + LOD 100 + + Pass + { + Blend SrcAlpha OneMinusSrcAlpha + + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + // make fog work + #pragma multi_compile_fog + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float2 uv0 : TEXCOORD0; + float2 uv1 : TEXCOORD1; + float2 uv2 : TEXCOORD2; + float2 uv3 : TEXCOORD3; + }; + + struct v2f + { + float2 uv : TEXCOORD0; + UNITY_FOG_COORDS(1) + float4 vertex : SV_POSITION; + }; + + sampler2D _MainTex; + float4 _MainTex_ST; + float4 _Color; + + sampler2D _DecalTex; + + float4 _Position; + float _Rotation; + float4 _Scale; + float4 _Offset; + int _UVChannel; + + float2 remap(float2 x, float2 minOld, float2 maxOld, float2 minNew = 0, float2 maxNew = 1) + { + return minNew + (x - minOld) * (maxNew - minNew) / (maxOld - minOld); + } + + float2 decalUV(float2 uv, float2 position, half rotation, half rotationSpeed, half2 scale, float4 scaleOffset, float depth) + { + scaleOffset = float4(-scaleOffset.x, scaleOffset.y, -scaleOffset.z, scaleOffset.w); + float2 centerOffset = float2((scaleOffset.x + scaleOffset.y)/2, (scaleOffset.z + scaleOffset.w)/2); + float2 decalCenter = position + centerOffset; + float theta = radians(rotation + _Time.z * rotationSpeed); + float cs = cos(theta); + float sn = sin(theta); + uv = float2((uv.x - decalCenter.x) * cs - (uv.y - decalCenter.y) * sn + decalCenter.x, (uv.x - decalCenter.x) * sn + (uv.y - decalCenter.y) * cs + decalCenter.y); + uv = remap(uv, float2(0, 0) - scale / 2 + position + scaleOffset.xz, scale / 2 + position + scaleOffset.yw, float2(0, 0), float2(1, 1)); + return uv; + } + + v2f vert (appdata v) + { + v2f o; + o.vertex = UnityObjectToClipPos(v.vertex); + o.uv = decalUV(v.uv0, _Position.xy, _Rotation, 0, _Scale.xy, _Offset, 0); + return o; + } + + float2 isBox(float2 uv, float2 p1, float2 p2, float2 width) + { + if (uv.x >= p1.x - width.x && uv.x <= p2.x + width.x && + uv.y >= p1.y - width.y && uv.y <= p2.y + width.y) + { + bool isInside = uv.x >= p1.x + width.x && uv.x <= p2.x - width.x && + uv.y >= p1.y + width.y && uv.y <= p2.y - width.y; + return float2(1, !isInside ? 1 : 0); + } + return float2(0, 0); + } + + #define LINE_WIDTH 0.003 + #define CORNER_BOX_SIZE 0.02 + #define EDGE_BOX_SIZE 0.01 + + fixed4 frag (v2f i) : SV_Target + { + fixed4 col = _Color; + col.a = 0; + + float2 aspectAdjust = float2(_Scale.x + _Offset.x + _Offset.y, _Scale.y + _Offset.z + _Offset.w); + float2 smallBoxSizeScaled = float2(CORNER_BOX_SIZE / aspectAdjust.x, CORNER_BOX_SIZE / aspectAdjust.y); + float2 edgeBoxSizeScaled = float2(EDGE_BOX_SIZE / aspectAdjust.x, EDGE_BOX_SIZE / aspectAdjust.y); + float2 lineWidthScaled = float2(LINE_WIDTH / aspectAdjust.x, LINE_WIDTH / aspectAdjust.y); + + float2 isSmallBox = isBox(i.uv, -smallBoxSizeScaled + float2(0,0), smallBoxSizeScaled + float2(0,0), lineWidthScaled); + isSmallBox += isBox(i.uv, -smallBoxSizeScaled + float2(0,1), smallBoxSizeScaled + float2(0,1), lineWidthScaled); + isSmallBox += isBox(i.uv, -smallBoxSizeScaled + float2(1,0), smallBoxSizeScaled + float2(1,0), lineWidthScaled); + isSmallBox += isBox(i.uv, -smallBoxSizeScaled + float2(1,1), smallBoxSizeScaled + float2(1,1), lineWidthScaled); + + float2 isEdgeBox = isBox(i.uv, -edgeBoxSizeScaled + float2(0.5,0), edgeBoxSizeScaled + float2(0.5,0), lineWidthScaled); + isEdgeBox += isBox(i.uv, -edgeBoxSizeScaled + float2(0,0.5), edgeBoxSizeScaled + float2(0,0.5), lineWidthScaled); + isEdgeBox += isBox(i.uv, -edgeBoxSizeScaled + float2(1,0.5), edgeBoxSizeScaled + float2(1,0.5), lineWidthScaled); + isEdgeBox += isBox(i.uv, -edgeBoxSizeScaled + float2(0.5,1), edgeBoxSizeScaled + float2(0.5,1), lineWidthScaled); + + if (isSmallBox.y > 0) + col.a = 1; + else if (isEdgeBox.x > 0) + col.a = 1; + else if (isSmallBox.x == 0 && isBox(i.uv, float2(0, 0), float2(1, 1), lineWidthScaled).y) + col.a = 1; + + if(col.a == 0 && i.uv.x >= 0 && i.uv.x <= 1 && i.uv.y >= 0 && i.uv.y <= 1) + col = tex2D(_DecalTex, i.uv); + + return col; + } + ENDCG + } + } +} diff --git a/Scripts/ThryEditor/Editor/DecalSceneTool.cs b/Scripts/ThryEditor/Editor/DecalSceneTool.cs new file mode 100644 index 0000000..b123f6e --- /dev/null +++ b/Scripts/ThryEditor/Editor/DecalSceneTool.cs @@ -0,0 +1,411 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class DecalSceneTool + { + private MaterialProperty _propPosition; + private MaterialProperty _propRotation; + private MaterialProperty _propScale; + private MaterialProperty _propOffset; + private Material _material; + private Renderer _renderer; + private int _uvIndex; + private Mesh _mesh; + private Vector2[][] _uvTriangles; + private Vector3[][] _worldTriangles; + private bool _isActive; + private Mode _mode = Mode.None; + private HandleMode _handleMode = HandleMode.Position; + private Tool _previousTool; + + public enum Mode + { + None, Raycast, Handles + } + + public enum HandleMode + { + Position, Rotation, Scale, Offset + } + + private DecalSceneTool() {} + + public static DecalSceneTool Create(Renderer renderer, Material m, int uvIndex, MaterialProperty propPosition, MaterialProperty propRotation, MaterialProperty propScale, MaterialProperty propOffset) + { + var tool = new DecalSceneTool(); + tool._material = m; + tool._uvIndex = uvIndex; + tool._propPosition = propPosition; + tool._propRotation = propRotation; + tool._propScale = propScale; + tool._propOffset = propOffset; + tool._renderer = renderer; + tool.Init(); + return tool; + } + + public void SetMaterialProperties(MaterialProperty propPosition, MaterialProperty propRotation, MaterialProperty propScale, MaterialProperty propOffset) + { + _propPosition = propPosition; + _propRotation = propRotation; + _propScale = propScale; + _propOffset = propOffset; + } + + public void StartRaycastMode() + { + _mode = Mode.Raycast; + this.Activate(); + } + + public void StartHandleMode() + { + _mode = Mode.Handles; + this.Activate(); + } + + public void Activate() + { + if(_isActive) return; + _previousTool = Tools.current; + Tools.current = Tool.None; + SceneView.duringSceneGui += OnSceneGUI; + Selection.selectionChanged += OnSelectionChange; + _isActive = true; + } + + public void Deactivate() + { + if(!_isActive) return; + SceneView.duringSceneGui -= OnSceneGUI; + Selection.selectionChanged -= OnSelectionChange; + Tools.current = _previousTool; + _isActive = false; + _mode = Mode.None; + + } + + public Mode GetMode() + { + return _mode; + } + + void OnSelectionChange() + { + this.Deactivate(); + } + + void Init() + { + // EditorUtility.DisplayProgressBar("Decal Tool", "Loading Mesh...", 0.0f); + GetMesh(); + _uvTriangles = new Vector2[_mesh.triangles.Length / 3][]; + _worldTriangles = new Vector3[_mesh.triangles.Length / 3][]; + int[] triangles = _mesh.triangles; + Vector2[] uvs; + if(_uvIndex == 1) uvs = _mesh.uv2; + else if(_uvIndex == 2) uvs = _mesh.uv3; + else if(_uvIndex == 3) uvs = _mesh.uv4; + else uvs = _mesh.uv; + Vector3[] vertices = _mesh.vertices; + Transform root = _renderer.transform; + Vector3 inverseScale = new Vector3(1.0f / root.lossyScale.x, 1.0f / root.lossyScale.y, 1.0f / root.lossyScale.z); + bool isSMR = _renderer is SkinnedMeshRenderer; + for(int i = 0; i < triangles.Length; i += 3) + { + // if(i%100 == 0) EditorUtility.DisplayProgressBar("Decal Tool", "Loading Mesh...", (float)i / triangles.Length); + _uvTriangles[i / 3] = new Vector2[3]; + _worldTriangles[i / 3] = new Vector3[3]; + for(int j = 0; j < 3; j++) + { + _uvTriangles[i / 3][j] = uvs[triangles[i + j]]; + if(isSMR) + _worldTriangles[i / 3][j] = root.TransformPoint(Vector3.Scale(vertices[triangles[i + j]], inverseScale)); + else + _worldTriangles[i / 3][j] = root.TransformPoint(vertices[triangles[i + j]]); + // _worldTriangles[i / 3][j] = vertices[triangles[i + j]]; + } + } + // EditorUtility.ClearProgressBar(); + } + + private void OnSceneGUI(SceneView sceneView) + { + switch(_mode) + { + case Mode.Raycast: + RaycastMode(sceneView); + break; + case Mode.Handles: + HandlesMode(sceneView); + break; + } + } + + void RaycastMode(SceneView sceneView) + { + if(Tools.current != Tool.View) + { + Tools.current = Tool.View; + } + + Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); + + Vector2 uv = _propPosition.vectorValue; + if(RaycastToClosestUV(ray, ref uv)) + { + _propPosition.vectorValue = uv; + } + + if(Event.current.type == EventType.MouseDown && Event.current.button == 0) + { + Event.current.Use(); + Deactivate(); + } + } + + void HandlesMode(SceneView sceneView) + { + switch(_handleMode) + { + case HandleMode.Position: + PositionMode(sceneView); + break; + case HandleMode.Rotation: + RotationMode(sceneView); + break; + case HandleMode.Scale: + ScaleMode(sceneView); + break; + case HandleMode.Offset: + OffsetMode(sceneView); + break; + } + + if(Tools.current != Tool.None) + { + switch(Tools.current) + { + case Tool.Move: + _handleMode = HandleMode.Position; + break; + case Tool.Rotate: + _handleMode = HandleMode.Rotation; + break; + case Tool.Scale: + _handleMode = HandleMode.Scale; + break; + case Tool.Rect: + _handleMode = HandleMode.Offset; + break; + } + Tools.current = Tool.None; + } + } + + void PositionMode(SceneView sceneView) + { + GetPivot(); + Vector3 gizmoNormal = _pivotNormal; + if(Vector3.Dot(sceneView.camera.transform.forward, _pivotNormal) < 0) + { + gizmoNormal = -_pivotNormal; + } + Quaternion rotation = Quaternion.LookRotation(gizmoNormal, _pivotUp); + + if(Tools.pivotRotation == PivotRotation.Local) + { + rotation *= Quaternion.Euler(0, 0, -_propRotation.floatValue); + } + + Vector3 moved = Handles.PositionHandle(_pivotPoint, rotation); + if(moved != _pivotPoint) + { + Vector2 uv = Vector2.zero; + Ray ray = new Ray(moved - _pivotNormal * 0.1f, _pivotNormal); + if(RaycastToClosestUV(ray, ref uv)) + { + _propPosition.vectorValue = uv; + } + } + } + + void RotationMode(SceneView sceneView) + { + GetPivot(); + Vector3 gizmoNormal = _pivotNormal; + if(Vector3.Dot(sceneView.camera.transform.forward, _pivotNormal) < 0) + { + gizmoNormal = -_pivotNormal; + } + Quaternion rotation = Quaternion.LookRotation(gizmoNormal, _pivotUp); + rotation *= Quaternion.Euler(0, 0, -_propRotation.floatValue); + + Quaternion moved = Handles.RotationHandle(rotation, _pivotPoint); + if(moved != rotation) + { + Quaternion delta = Quaternion.Inverse(rotation) * moved; + float deltaAngle = delta.eulerAngles.z; + DecalTool.SetClampedRotation(_propRotation, _propRotation.floatValue - deltaAngle); + } + } + + Vector3 _initalScale; + void ScaleMode(SceneView sceneView) + { + GetPivot(); + Vector3 gizmoNormal = _pivotNormal; + if(Vector3.Dot(sceneView.camera.transform.forward, _pivotNormal) < 0) + { + gizmoNormal = -_pivotNormal; + } + Quaternion rotation = Quaternion.LookRotation(gizmoNormal, _pivotUp); + rotation *= Quaternion.Euler(0, 0, -_propRotation.floatValue); + + if(Event.current.type == EventType.MouseDown && Event.current.button == 0) + { + _initalScale = _propScale.vectorValue; + } + + Vector3 moved = Handles.ScaleHandle(Vector3.one, _pivotPoint, rotation, HandleUtility.GetHandleSize(_pivotPoint)); + if(moved != Vector3.one) + { + Vector4 scale = _initalScale; + scale.x *= moved.x; + scale.y *= moved.y; + _propScale.vectorValue = scale; + } + } + + Vector4 _initalOffset; + void OffsetMode(SceneView sceneView) + { + GetPivot(); + Vector3 normal = _pivotNormal; + if(Vector3.Dot(sceneView.camera.transform.forward, _pivotNormal) < 0) + { + _pivotNormal = -_pivotNormal; + } + Quaternion rotation = Quaternion.LookRotation(_pivotNormal, _pivotUp); + rotation *= Quaternion.Euler(0, 0, -_propRotation.floatValue); + + if(Event.current.type == EventType.MouseDown && Event.current.button == 0) + { + _initalOffset = _propOffset.vectorValue; + } + + float size = HandleUtility.GetHandleSize(_pivotPoint); + float left = Handles.ScaleValueHandle(1, _pivotPoint + rotation * Vector3.left * size, rotation * Quaternion.Euler(0, -90, 0), size * 5, Handles.ArrowHandleCap, 0); + float right = Handles.ScaleValueHandle(1, _pivotPoint + rotation * Vector3.right * size, rotation * Quaternion.Euler(0, 90, 0), size * 5, Handles.ArrowHandleCap, 0); + float down = Handles.ScaleValueHandle(1, _pivotPoint + rotation * Vector3.down * size, rotation * Quaternion.Euler(90, 0, 0), size * 5, Handles.ArrowHandleCap, 0); + float up = Handles.ScaleValueHandle(1, _pivotPoint + rotation * Vector3.up * size, rotation * Quaternion.Euler(-90, 0, 0), size * 5, Handles.ArrowHandleCap, 0); + if(left != 1 || right != 1 || down != 1 || up != 1) + { + Vector4 offset = _initalOffset; + offset.x -= (left - 1) * _propScale.vectorValue.x * 0.25f; + offset.y += (right - 1) * _propScale.vectorValue.x * 0.25f; + offset.z -= (down - 1) * _propScale.vectorValue.y * 0.25f; + offset.w += (up - 1) * _propScale.vectorValue.y * 0.25f; + _propOffset.vectorValue = offset; + } + } + + Vector3 _pivotPoint; + Vector3 _pivotNormal; + Vector3 _pivotUp; + void GetPivot() + { + _pivotPoint = Vector3.zero; + _pivotNormal = Vector3.zero; + + Vector2 uv = _propPosition.vectorValue; + Vector2 uvUp = uv + Vector2.up * 0.0001f; + // uv position to world position using renderer mesh + for(int i=0; i<_worldTriangles.Length;i++) + { + Vector2[] uvTriangle = _uvTriangles[i]; + float a = TriangleArea(uvTriangle[0], uvTriangle[1], uvTriangle[2]); + if(a == 0) continue; + // check if uv is inside uvTriangle + float a1 = TriangleArea(uvTriangle[1], uvTriangle[2], uv) / a; + if(a1 < 0) continue; + float a2 = TriangleArea(uvTriangle[2], uvTriangle[0], uv) / a; + if(a2 < 0) continue; + float a3 = TriangleArea(uvTriangle[0], uvTriangle[1], uv) / a; + if(a3 < 0) continue; + // get a1, a2, a3 of uv up + float a1Up = TriangleArea(uvTriangle[1], uvTriangle[2], uvUp) / a; + float a2Up = TriangleArea(uvTriangle[2], uvTriangle[0], uvUp) / a; + float a3Up = TriangleArea(uvTriangle[0], uvTriangle[1], uvUp) / a; + // point inside the triangle - find mesh position by interpolation + Vector3[] triangle = _worldTriangles[i]; + _pivotPoint = triangle[0] * a1 + triangle[1] * a2 + triangle[2] * a3; + _pivotNormal = Vector3.Cross(triangle[1] - triangle[0], triangle[2] - triangle[0]).normalized; + _pivotUp = (triangle[0] * a1Up + triangle[1] * a2Up + triangle[2] * a3Up - _pivotPoint).normalized; + return; + } + } + + bool RaycastToClosestUV(Ray ray, ref Vector2 uv) + { + Vector4 scaleOffset = _propOffset.vectorValue; + scaleOffset = new Vector4(-scaleOffset.x, scaleOffset.y, -scaleOffset.z, scaleOffset.w); + Vector2 centerOffset = new Vector2((scaleOffset.x + scaleOffset.y)/2, (scaleOffset.z + scaleOffset.w)/2); + + float minDistance = float.MaxValue; + for(int i=0; i<_worldTriangles.Length;i++) + { + Vector3[] triangle = _worldTriangles[i]; + // raycast to triangle + Plane plane = new Plane(triangle[0], triangle[1], triangle[2]); + float distance; + if(plane.Raycast(ray, out distance)) + { + Vector3 hitPoint = ray.GetPoint(distance); + // check if hitPoint is inside triangle + float a = TriangleArea(triangle[0], triangle[1], triangle[2]); + if(a == 0) continue; + float a1 = TriangleArea(triangle[1], triangle[2], hitPoint) / a; + if(a1 < 0) continue; + float a2 = TriangleArea(triangle[2], triangle[0], hitPoint) / a; + if(a2 < 0) continue; + float a3 = TriangleArea(triangle[0], triangle[1], hitPoint) / a; + if(a3 < 0) continue; + if(distance < minDistance) + { + minDistance = distance; + // point inside the triangle - find uv by interpolation + Vector2[] uvTriangle = _uvTriangles[i]; + uv = uvTriangle[0] * a1 + uvTriangle[1] * a2 + uvTriangle[2] * a3; + uv = uv - centerOffset; + } + } + } + return minDistance != float.MaxValue; + } + + float TriangleArea(Vector2 a, Vector2 b, Vector2 c) + { + var v1 = a - c; + var v2 = b - c; + return (v1.x * v2.y - v1.y * v2.x) / 2; + } + + void GetMesh() + { + if(_renderer is MeshRenderer) + { + _mesh = _renderer.GetComponent().sharedMesh; + } + else if(_renderer is SkinnedMeshRenderer) + { + _mesh = new Mesh(); + (_renderer as SkinnedMeshRenderer).BakeMesh(_mesh); + } + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/DecalTool.cs b/Scripts/ThryEditor/Editor/DecalTool.cs new file mode 100644 index 0000000..3ac40ef --- /dev/null +++ b/Scripts/ThryEditor/Editor/DecalTool.cs @@ -0,0 +1,177 @@ +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class DecalTool : EditorWindow + { + const string GUID_GIZMO_SHADER = "94a63a84353e517488db284dc8fab0ca"; + + private MaterialProperty _propPosition; + private MaterialProperty _propRotation; + private MaterialProperty _propScale; + private MaterialProperty _propOffset; + private MaterialProperty _propUVChannel; + private Material _material; + private Material _gizmoMaterial; + + public static DecalTool OpenDecalTool(Material m) + { + var window = EditorWindow.GetWindow("Decal Tool"); + window._material = m; + window._gizmoMaterial = new Material(AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(GUID_GIZMO_SHADER))); + window._gizmoMaterial.color = Color.white; + return window; + } + + public void SetMaterialProperties(MaterialProperty decalProp, MaterialProperty uvProp, MaterialProperty positionProp, MaterialProperty rotationProp, MaterialProperty scaleProp, MaterialProperty offsetProp) + { + _propPosition = positionProp; + _propRotation = rotationProp; + _propScale = scaleProp; + _propOffset = offsetProp; + _propUVChannel = uvProp; + _gizmoMaterial.SetTexture("_DecalTex", decalProp.textureValue); + this.Repaint(); + } + + private void OnGUI() + { + if(_propPosition == null) + { + return; + } + HandleInput(); + // EditorGUI.DrawPreviewTexture(new Rect(0, 0, position.width, position.height), _material.mainTexture, _material); + EditorGUI.DrawTextureTransparent(new Rect(0, 0, position.width, position.height), _material.mainTexture, ScaleMode.StretchToFill); + _gizmoMaterial.SetVector("_Position", _propPosition.vectorValue); + _gizmoMaterial.SetVector("_Scale", _propScale.vectorValue); + _gizmoMaterial.SetFloat("_Rotation", _propRotation.floatValue); + _gizmoMaterial.SetVector("_Offset", _propOffset.vectorValue); + _gizmoMaterial.SetFloat("_UVChannel", _propUVChannel.floatValue); + EditorGUI.DrawPreviewTexture(new Rect(0, 0, position.width, position.height), Texture2D.whiteTexture, _gizmoMaterial); + } + + private Vector2 _lastMousePosition; + private Vector2 _initialMousePosition; + private Vector4 _initalScale; + private float _initialRotation; + private bool _isInsideAction; + private bool _isOutsideAction; + private Vector2 _grabbedSnappoint; + private Vector2 _lastDecalMouseUV; + private Vector2 _initalDecalMouseUV; + private Vector2 _initalMouseUV; + private void HandleInput() + { + Vector2 pivot = _propPosition.vectorValue * position.size; + Vector2 pivotUV = pivot / position.size; + + Vector2 mouseUV = Event.current.mousePosition / position.size; + mouseUV.y = 1 - mouseUV.y; + Vector2 decalMouseUV = DecalUV(mouseUV, _propPosition.vectorValue, _propRotation.floatValue, _propScale.vectorValue, _propOffset.vectorValue); + + Event e = Event.current; + bool isMouseDrag = e.type == EventType.MouseDrag; + + Vector2 delta = e.mousePosition - _lastMousePosition; + Vector2 deltaDecalUV = decalMouseUV - _lastDecalMouseUV; + if(isMouseDrag) + { + _lastMousePosition = e.mousePosition; + } + + + if (e.type == EventType.MouseDown && e.button == 0) + { + _initialMousePosition = e.mousePosition; + _lastMousePosition = e.mousePosition; + + _initalMouseUV = mouseUV; + + _initalDecalMouseUV = decalMouseUV; + _lastDecalMouseUV = decalMouseUV; + + _initalScale = _propScale.vectorValue; + _initialRotation = _propRotation.floatValue; + _isInsideAction = decalMouseUV.x >= 0.1f && decalMouseUV.x <= 0.9f && decalMouseUV.y >= 0.1f && decalMouseUV.y <= 0.9f; + _isOutsideAction = decalMouseUV.x < -0.1f || decalMouseUV.x > 1.1f || decalMouseUV.y < -0.1f || decalMouseUV.y > 1.1f; + _grabbedSnappoint = new Vector2(Mathf.Round(decalMouseUV.x * 2), Mathf.Round(decalMouseUV.y * 2)) / 2; + } + // Translate + if (_isInsideAction && isMouseDrag && !e.alt) + { + delta = delta / position.size; + Vector2 pos = _propPosition.vectorValue; + pos.x += delta.x; + pos.y -= delta.y; + _propPosition.vectorValue = pos; + this.Repaint(); + } + // Rotate + else if(_isOutsideAction && isMouseDrag && !e.alt) + { + Vector2 vecInital = _initalMouseUV - pivotUV; + Vector2 vecLast = mouseUV - pivotUV; + float angle = Vector2.SignedAngle(vecInital, vecLast); + SetClampedRotation(_propRotation, _initialRotation - angle); + this.Repaint(); + } + // Scale + else if(isMouseDrag && e.alt) + { + Vector2 vecInital = _initalMouseUV - pivotUV; + Vector2 vecLast = mouseUV - pivotUV; + float vecLastIntoInitalDir = Vector2.Dot(vecLast, vecInital.normalized); + float uniform = vecInital.magnitude / vecLastIntoInitalDir; + _propScale.vectorValue = _initalScale / uniform; + this.Repaint(); + } + // Offset + else if(isMouseDrag && !_isInsideAction && !_isOutsideAction) + { + Vector4 offset = _propOffset.vectorValue; + if(_grabbedSnappoint.x == 0) + offset.x = Mathf.Max(-_propScale.vectorValue.x / 2, offset.x - deltaDecalUV.x * _propScale.vectorValue.y); + if(_grabbedSnappoint.x == 1) + offset.y = Mathf.Max(-_propScale.vectorValue.x / 2, offset.y + deltaDecalUV.x * _propScale.vectorValue.y); + if(_grabbedSnappoint.y == 0) + offset.z = Mathf.Max(-_propScale.vectorValue.y / 2, offset.z - deltaDecalUV.y * _propScale.vectorValue.y); + if(_grabbedSnappoint.y == 1) + offset.w = Mathf.Max(-_propScale.vectorValue.y / 2, offset.w + deltaDecalUV.y * _propScale.vectorValue.y); + _propOffset.vectorValue = offset; + this.Repaint(); + } + + if(isMouseDrag) + { + _lastDecalMouseUV = DecalUV(mouseUV, _propPosition.vectorValue, _propRotation.floatValue, _propScale.vectorValue, _propOffset.vectorValue); + } + } + + static Vector2 Remap(Vector2 x, Vector2 minOld, Vector2 maxOld, Vector2 minNew, Vector2 maxNew) + { + return minNew + (x - minOld) * (maxNew - minNew) / (maxOld - minOld); + } + + static Vector2 DecalUV(Vector2 uv, Vector2 position, float rotation, Vector2 scale, Vector4 scaleOffset) + { + scaleOffset = new Vector4(-scaleOffset.x, scaleOffset.y, -scaleOffset.z, scaleOffset.w); + Vector2 centerOffset = new Vector2((scaleOffset.x + scaleOffset.y)/2, (scaleOffset.z + scaleOffset.w)/2); + Vector2 decalCenter = position + centerOffset; + float theta = Mathf.Deg2Rad * rotation; + float cs = Mathf.Cos(theta); + float sn = Mathf.Sin(theta); + uv = new Vector2((uv.x - decalCenter.x) * cs - (uv.y - decalCenter.y) * sn + decalCenter.x, (uv.x - decalCenter.x) * sn + (uv.y - decalCenter.y) * cs + decalCenter.y); + uv = Remap(uv, new Vector2(0, 0) - scale / 2 + position + new Vector2(scaleOffset.x, scaleOffset.z), scale / 2 + position + new Vector2(scaleOffset.y,scaleOffset.w), Vector2.zero, Vector2.one); + return uv; + } + + public static void SetClampedRotation(MaterialProperty property, float value) + { + Vector2 limits = property.rangeLimits; + value = Helper.Mod(value - limits.x, limits.y - limits.x) + limits.x; + property.floatValue = value; + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Drawers.cs b/Scripts/ThryEditor/Editor/Drawers.cs new file mode 100644 index 0000000..8cdf0ee --- /dev/null +++ b/Scripts/ThryEditor/Editor/Drawers.cs @@ -0,0 +1,2024 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + #region Texture Drawers + public class ThryTextureDrawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + GuiHelper.ConfigTextureProperty(position, prop, label, editor, ((TextureProperty)ShaderEditor.Active.CurrentProperty).hasScaleOffset); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class SmallTextureDrawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + GuiHelper.SmallTextureProperty(position, prop, label, editor, ((TextureProperty)ShaderEditor.Active.CurrentProperty).hasScaleOffset); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + // For backwards compatibility + public class BigTextureDrawer : SimpleLargeTextureDrawer + { + + } + + // For backwards compatibility + public class StylizedBigTextureDrawer : StylizedLargeTextureDrawer + { + + } + + public class SimpleLargeTextureDrawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + GuiHelper.BigTexturePropertyBasic(position, prop, label, editor, ((TextureProperty)ShaderEditor.Active.CurrentProperty).hasScaleOffset); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class StylizedLargeTextureDrawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + GuiHelper.StylizedBigTextureProperty(position, prop, label, editor, ((TextureProperty)ShaderEditor.Active.CurrentProperty).hasScaleOffset); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + #endregion + + #region Special Texture Drawers + public class CurveDrawer : MaterialPropertyDrawer + { + public AnimationCurve curve; + public EditorWindow window; + public Texture2D texture; + public bool saved = true; + public TextureData imageData; + + public CurveDrawer() + { + curve = new AnimationCurve(); + } + + private void Init() + { + if (imageData == null) + { + if (ShaderEditor.Active.CurrentProperty.Options.texture == null) + imageData = new TextureData(); + else + imageData = ShaderEditor.Active.CurrentProperty.Options.texture; + } + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + Init(); + Rect border_position = new Rect(position.x + EditorGUIUtility.labelWidth - 15, position.y, position.width - EditorGUIUtility.labelWidth + 15 - GuiHelper.GetSmallTextureVRAMWidth(prop), position.height); + + EditorGUI.BeginChangeCheck(); + curve = EditorGUI.CurveField(border_position, curve); + if (EditorGUI.EndChangeCheck()) + { + UpdateCurveTexture(prop); + } + + GuiHelper.SmallTextureProperty(position, prop, label, editor, DrawingData.CurrentTextureProperty.hasFoldoutProperties); + + CheckWindowForCurveEditor(); + + if (window == null && !saved) + Save(prop); + } + + private void UpdateCurveTexture(MaterialProperty prop) + { + texture = Converter.CurveToTexture(curve, imageData); + prop.textureValue = texture; + saved = false; + } + + private void CheckWindowForCurveEditor() + { + string windowName = ""; + if (EditorWindow.focusedWindow != null) + windowName = EditorWindow.focusedWindow.titleContent.text; + bool isCurveEditor = windowName == "Curve"; + if (isCurveEditor) + window = EditorWindow.focusedWindow; + } + + private void Save(MaterialProperty prop) + { + Debug.Log(prop.textureValue.ToString()); + Texture saved_texture = TextureHelper.SaveTextureAsPNG(texture, PATH.TEXTURES_DIR + "curves/" + curve.GetHashCode() + ".png", null); + prop.textureValue = saved_texture; + saved = true; + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + public class ThryExternalTextureToolDrawer : MaterialPropertyDrawer + { + string _toolTypeName; + string _toolHeader; + + Type t_ExternalToolType; + MethodInfo _onGui; + object _externalTool; + MaterialProperty _prop; + + bool _isTypeLoaded; + bool _doesExternalTypeExist; + bool _isInit; + bool _showTool; + + public ThryExternalTextureToolDrawer(string toolHeader, string toolTypeName) + { + this._toolTypeName = toolTypeName; + this._toolHeader = toolHeader; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + LoadType(); + if (_doesExternalTypeExist) + { + _prop = prop; + GuiHelper.SmallTextureProperty(position, prop, label, editor, DrawingData.CurrentTextureProperty.hasFoldoutProperties, ExternalGUI); + } + else + { + GuiHelper.SmallTextureProperty(position, prop, label, editor, DrawingData.CurrentTextureProperty.hasFoldoutProperties); + } + } + + void ExternalGUI() + { + if (GUI.Button(EditorGUI.IndentedRect(EditorGUILayout.GetControlRect()), _toolHeader)) _showTool = !_showTool; + if (_showTool) + { + Init(); + + int indent = EditorGUI.indentLevel; + GuiHelper.BeginCustomIndentLevel(0); + GUILayout.BeginHorizontal(); + GUILayout.Space(indent * 15); + GUILayout.BeginVertical(); + _onGui.Invoke(_externalTool, new object[0]); + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + GuiHelper.EndCustomIndentLevel(); + } + } + + public void LoadType() + { + if (_isTypeLoaded) return; + t_ExternalToolType = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(_toolTypeName)).Where(t => t != null).FirstOrDefault(); + _doesExternalTypeExist = t_ExternalToolType != null; + _isTypeLoaded = true; + } + + public void Init() + { + if (_isInit) return; + if (_isTypeLoaded && _doesExternalTypeExist) + { + _onGui = t_ExternalToolType.GetMethod("OnGUI", BindingFlags.NonPublic | BindingFlags.Instance); + _externalTool = ScriptableObject.CreateInstance(t_ExternalToolType); + EventInfo eventTextureGenerated = t_ExternalToolType.GetEvent("TextureGenerated"); + if (eventTextureGenerated != null) + eventTextureGenerated.AddEventHandler(_externalTool, new EventHandler(TextureGenerated)); + } + _isInit = true; + } + + void TextureGenerated(object sender, EventArgs args) + { + if (args != null && args.GetType().GetField("generated_texture") != null) + { + Texture2D generated = args.GetType().GetField("generated_texture").GetValue(args) as Texture2D; + _prop.textureValue = generated; + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class ThryRGBAPackerDrawer : MaterialPropertyDrawer + { + // TODO : Load lacale by property name in the future: propname_r, propname_g, propname_b, propname_a + class ThryRGBAPackerData + { + public Texture _previousTexture; + public Texture2D _packedTexture; + + public InlinePackerChannelConfig _input_r; + public InlinePackerChannelConfig _input_g; + public InlinePackerChannelConfig _input_b; + public InlinePackerChannelConfig _input_a; + + public bool _isInit; + public bool _hasConfigChanged; + public bool _hasTextureChanged; + public long _lastConfirmTime; + + public TexturePacker.Connection[] _connections; + public TexturePacker.TextureSource[] _sources; + } + + Dictionary materialPackerData = new Dictionary(); + + MaterialProperty _prop; + ThryRGBAPackerData _current; + + string _label1; + string _label2; + string _label3; + string _label4; + bool _firstTextureIsRGB; + bool _makeSRGB = true; + + // for locale changing + // i tried using an array to save the default labels, but the data just got lost somewhere. not sure why + string _defaultLabel1; + string _defaultLabel2; + string _defaultLabel3; + string _defaultLabel4; + int _reloadCount = -1; + static int _reloadCountStatic; + + public static void Reload() + { + _reloadCountStatic++; + } + + void LoadLabels() + { + if (_reloadCount == _reloadCountStatic) return; + // using the string itself as a key for reuse in other places. this might cause issues, if it does in the future + // we can add the class name as a prefix to the key + _label1 = ShaderEditor.Active.Locale.Get(_defaultLabel1, _defaultLabel1); + _label2 = ShaderEditor.Active.Locale.Get(_defaultLabel2, _defaultLabel2); + _label3 = ShaderEditor.Active.Locale.Get(_defaultLabel3, _defaultLabel3); + _label4 = ShaderEditor.Active.Locale.Get(_defaultLabel4, _defaultLabel4); + _reloadCount = _reloadCountStatic; + } + + // end locale changing + + public ThryRGBAPackerDrawer(string label1, string label2, string label3, string label4, float sRGB) + { + _defaultLabel1 = label1; + _defaultLabel2 = label2; + _defaultLabel3 = label3; + _defaultLabel4 = label4; + _label1 = label1; + _label2 = label2; + _label3 = label3; + _label4 = label4; + _makeSRGB = sRGB == 1; + } + + public ThryRGBAPackerDrawer(string label1, string label2, float sRGB) : this(label1, label2, null, null, sRGB) { } + public ThryRGBAPackerDrawer(string label1, string label2, string label3, float sRGB) : this(label1, label2, label3, null, sRGB) { } + + public ThryRGBAPackerDrawer(string label1, string label2) : this(label1, label2, null, null, 0) { } + public ThryRGBAPackerDrawer(string label1, string label2, string label3) : this(label1, label2, label3, null, 0) { } + public ThryRGBAPackerDrawer(string label1, string label2, string label3, string label4) : this(label1, label2, label3, label4, 0) { } + + public ThryRGBAPackerDrawer(float firstTextureIsRGB, string label1, string label2) : this(label1, label2, null, null, 0) + { + _firstTextureIsRGB = firstTextureIsRGB == 1; + } + public ThryRGBAPackerDrawer(float firstTextureIsRGB, string label1, string label2, float sRGB) : this(label1, label2, null, null, sRGB) + { + _firstTextureIsRGB = firstTextureIsRGB == 1; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + _prop = prop; + if (materialPackerData.ContainsKey(_prop.targets[0]) == false) materialPackerData[_prop.targets[0]] = new ThryRGBAPackerData(); + _current = materialPackerData[_prop.targets[0]]; + GuiHelper.SmallTextureProperty(position, prop, label, editor, true, TexturePackerGUI); + if (_prop.textureValue != _current._packedTexture) _current._previousTexture = _prop.textureValue; + } + + bool DidTextureGetEdit(InlinePackerChannelConfig data) + { + if (data.TextureSource.Texture == null) return false; + string path = AssetDatabase.GetAssetPath(data.TextureSource.Texture); + if (System.IO.File.Exists(path) == false) return false; + long lastEditTime = Helper.DatetimeToUnixSeconds(System.IO.File.GetLastWriteTime(path)); + bool hasBeenEdited = lastEditTime > _current._lastConfirmTime && lastEditTime != data.TextureSource.LastHandledTextureEditTime; + data.TextureSource.LastHandledTextureEditTime = lastEditTime; + if (hasBeenEdited) TexturePacker.TextureSource.SetUncompressedTextureDirty(data.TextureSource.Texture); + return hasBeenEdited; + } + + void TexturePackerGUI() + { + Init(); + LoadLabels(); + EditorGUI.BeginChangeCheck(); + _current._input_r = TexturePackerSlotGUI(_current._input_r, _label1); + _current._input_g = TexturePackerSlotGUI(_current._input_g, _label2); + if (_label3 != null) _current._input_b = TexturePackerSlotGUI(_current._input_b, _label3); + if (_label4 != null) _current._input_a = TexturePackerSlotGUI(_current._input_a, _label4); + bool changeCheck = EditorGUI.EndChangeCheck(); + changeCheck |= DidTextureGetEdit(_current._input_r); + changeCheck |= DidTextureGetEdit(_current._input_g); + changeCheck |= DidTextureGetEdit(_current._input_b); + changeCheck |= DidTextureGetEdit(_current._input_a); + if (changeCheck) + { + _current._hasConfigChanged = true; + Save(); + Pack(); + } + + Rect buttonRect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect()); + buttonRect.width /= 3; + EditorGUI.BeginDisabledGroup(!_current._hasConfigChanged); + if (GUI.Button(buttonRect, "Confirm Merge")) Confirm(); + buttonRect.x += buttonRect.width; + EditorGUI.EndDisabledGroup(); + EditorGUI.BeginDisabledGroup(!_current._hasTextureChanged); + if (GUI.Button(buttonRect, "Revert")) Revert(); + EditorGUI.EndDisabledGroup(); + buttonRect.x += buttonRect.width; + if (GUI.Button(buttonRect, "Advanced")) OpenFullTexturePacker(); + } + + InlinePackerChannelConfig TexturePackerSlotGUI(InlinePackerChannelConfig input, string label) + { + Rect totalRect = EditorGUILayout.GetControlRect(false); + totalRect = EditorGUI.IndentedRect(totalRect); + Rect r = totalRect; + + int ind = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + + float texWidth = Math.Max(50, r.width - 130 - 30) - 5; + r.x = totalRect.x; + r.width = 30; + EditorGUI.BeginChangeCheck(); + Texture2D changed = EditorGUI.ObjectField(r, input.TextureSource.Texture, typeof(Texture2D), false) as Texture2D; + if (EditorGUI.EndChangeCheck()) + { + input.TextureSource.SetTexture(changed); + } + + r.x += r.width + 5; + r.width = texWidth - 5; + EditorGUI.LabelField(r, label); + + if (input.TextureSource.Texture == null) + { + r.width = 70; + r.x = totalRect.x + totalRect.width - r.width; + input.Fallback = EditorGUI.FloatField(r, input.Fallback); + + r.width = 60; + r.x -= r.width; + EditorGUI.LabelField(r, "Fallback:"); + } + else + { + r.width = 50; + r.x = totalRect.x + totalRect.width - r.width; + if (!_firstTextureIsRGB || input != _current._input_r) + input.Channel = (TexturePacker.TextureChannelIn)EditorGUI.EnumPopup(r, input.Channel); + + r.width = 20; + r.x -= r.width; + input.Invert = EditorGUI.Toggle(r, input.Invert); + + r.width = 60; + r.x -= r.width; + EditorGUI.LabelField(r, "Inverted:"); + } + + EditorGUI.indentLevel = ind; + + return input; + } + + void Init() + { + if (_current._isInit) return; + _current._input_r = LoadForChannel(ShaderEditor.Active.Materials[0], _prop.name, "r"); + _current._input_g = LoadForChannel(ShaderEditor.Active.Materials[0], _prop.name, "g"); + _current._input_b = LoadForChannel(ShaderEditor.Active.Materials[0], _prop.name, "b"); + _current._input_a = LoadForChannel(ShaderEditor.Active.Materials[0], _prop.name, "a"); + _current._lastConfirmTime = long.Parse(ShaderEditor.Active.Materials[0].GetTag(_prop.name + "_texPack_lastConfirmTime", false, "" + Helper.DatetimeToUnixSeconds(DateTime.Now))); + _current._previousTexture = _prop.textureValue; + _current._isInit = true; + } + + void Save() + { + SaveForChannel(_current._input_r, _prop.name, "r"); + SaveForChannel(_current._input_g, _prop.name, "g"); + SaveForChannel(_current._input_b, _prop.name, "b"); + SaveForChannel(_current._input_a, _prop.name, "a"); + foreach (Material m in ShaderEditor.Active.Materials) + { + m.SetOverrideTag(_prop.name + "_texPack_lastConfirmTime", "" + _current._lastConfirmTime); + } + } + + void SaveForChannel(InlinePackerChannelConfig input, string id, string channel) + { + foreach (Material m in ShaderEditor.Active.Materials) + { + if (input.TextureSource.Texture != null) m.SetOverrideTag(id + "_texPack_" + channel + "_guid", AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(input.TextureSource.Texture))); + else m.SetOverrideTag(id + "_texPack_" + channel + "_guid", ""); + m.SetOverrideTag(id + "_texPack_" + channel + "_fallback", input.Fallback.ToString()); + m.SetOverrideTag(id + "_texPack_" + channel + "_inverted", input.Invert.ToString()); + m.SetOverrideTag(id + "_texPack_" + channel + "_channel", ((int)input.Channel).ToString()); + } + } + + InlinePackerChannelConfig LoadForChannel(Material m, string id, string channel) + { + InlinePackerChannelConfig packerChannelConfig = new InlinePackerChannelConfig(); + packerChannelConfig.Fallback = float.Parse(m.GetTag(id + "_texPack_" + channel + "_fallback", false, "1")); + packerChannelConfig.Invert = bool.Parse(m.GetTag(id + "_texPack_" + channel + "_inverted", false, "false")); + packerChannelConfig.Channel = (TexturePacker.TextureChannelIn)int.Parse(m.GetTag(id + "_texPack_" + channel + "_channel", false, "4")); + string guid = m.GetTag(id + "_texPack_" + channel + "_guid", false, ""); + if (string.IsNullOrEmpty(guid) == false) + { + string p = AssetDatabase.GUIDToAssetPath(guid); + if (p != null) + packerChannelConfig.TextureSource.SetTexture(AssetDatabase.LoadAssetAtPath(p)); + } + return packerChannelConfig; + } + + void Pack() + { + _current._packedTexture = TexturePacker.Pack(GetTextureSources(), GetOutputConfigs(), GetConnections(), GetFiltermode(), _makeSRGB ? ColorSpace.Gamma : ColorSpace.Linear); + _prop.textureValue = _current._packedTexture; + + _current._hasTextureChanged = true; + } + + TexturePacker.TextureSource[] GetTextureSources() + { + // build sources array + return new TexturePacker.TextureSource[4]{ + _current._input_r.TextureSource, _current._input_g.TextureSource, _current._input_b.TextureSource, _current._input_a.TextureSource }; + } + + TexturePacker.OutputConfig[] GetOutputConfigs() + { + // Build OutputConfig Array + TexturePacker.OutputConfig[] outputConfigs = new TexturePacker.OutputConfig[4]; + + if (_firstTextureIsRGB) + { + outputConfigs[0] = _current._input_r.ToOutputConfig(); + outputConfigs[1] = _current._input_r.ToOutputConfig(); + outputConfigs[2] = _current._input_r.ToOutputConfig(); + outputConfigs[3] = _current._input_g.ToOutputConfig(); + } + else + { + outputConfigs[0] = _current._input_r.ToOutputConfig(); + outputConfigs[1] = _current._input_g.ToOutputConfig(); + outputConfigs[2] = _current._input_b.ToOutputConfig(); + outputConfigs[3] = _current._input_a.ToOutputConfig(); + } + return outputConfigs; + } + + TexturePacker.Connection[] GetConnections() + { + // Build connections array + TexturePacker.Connection[] connections = new TexturePacker.Connection[4]; + if (_firstTextureIsRGB) + { + connections[0] = TexturePacker.Connection.CreateFull(0, TexturePacker.TextureChannelIn.R, TexturePacker.TextureChannelOut.R); + connections[1] = TexturePacker.Connection.CreateFull(0, TexturePacker.TextureChannelIn.G, TexturePacker.TextureChannelOut.G); + connections[2] = TexturePacker.Connection.CreateFull(0, TexturePacker.TextureChannelIn.B, TexturePacker.TextureChannelOut.B); + connections[3] = TexturePacker.Connection.CreateFull(1, _current._input_g.Channel, TexturePacker.TextureChannelOut.A); + } + else + { + connections[0] = TexturePacker.Connection.CreateFull(0, _current._input_r.Channel, TexturePacker.TextureChannelOut.R); + connections[1] = TexturePacker.Connection.CreateFull(1, _current._input_g.Channel, TexturePacker.TextureChannelOut.G); + connections[2] = TexturePacker.Connection.CreateFull(2, _current._input_b.Channel, TexturePacker.TextureChannelOut.B); + connections[3] = TexturePacker.Connection.CreateFull(3, _current._input_a.Channel, TexturePacker.TextureChannelOut.A); + } + return connections; + } + + void OpenFullTexturePacker() + { + TexturePacker packer = TexturePacker.ShowWindow(); + packer.InitilizeWithData(GetTextureSources(), GetOutputConfigs(), GetConnections(), GetFiltermode(), _makeSRGB ? ColorSpace.Gamma : ColorSpace.Linear); + packer.OnChange += FullTexturePackerOnChange; + packer.OnSave += FullTexturePackerOnSave; + } + + void FullTexturePackerOnSave(Texture2D tex) + { + _current._packedTexture = tex; + _prop.textureValue = _current._packedTexture; + _current._hasTextureChanged = false; + } + + void FullTexturePackerOnChange(Texture2D tex, TexturePacker.TextureSource[] sources, TexturePacker.OutputConfig[] configs, TexturePacker.Connection[] connections) + { + _current._input_r.TextureSource = sources[0]; + _current._input_g.TextureSource = sources[1]; + _current._input_b.TextureSource = sources[2]; + _current._input_a.TextureSource = sources[3]; + + _current._input_r.FromOutputConfig(configs[0]); + _current._input_g.FromOutputConfig(configs[1]); + _current._input_b.FromOutputConfig(configs[2]); + _current._input_a.FromOutputConfig(configs[3]); + + _current._input_r.Channel = connections.Length > 0 ? connections[0].FromChannel : TexturePacker.TextureChannelIn.Max; + _current._input_g.Channel = connections.Length > 1 ? connections[1].FromChannel : TexturePacker.TextureChannelIn.Max; + _current._input_b.Channel = connections.Length > 2 ? connections[2].FromChannel : TexturePacker.TextureChannelIn.Max; + _current._input_a.Channel = connections.Length > 3 ? connections[3].FromChannel : TexturePacker.TextureChannelIn.Max; + + _current._packedTexture = tex; + _prop.textureValue = _current._packedTexture; + _current._hasTextureChanged = true; + } + + FilterMode GetFiltermode() + { + if (_current._input_r.GetTexture() != null) return _current._input_r.GetTexture().filterMode; + if (_current._input_g.GetTexture() != null) return _current._input_g.GetTexture().filterMode; + if (_current._input_b.GetTexture() != null) return _current._input_b.GetTexture().filterMode; + if (_current._input_a.GetTexture() != null) return _current._input_a.GetTexture().filterMode; + return FilterMode.Bilinear; + } + + void Confirm() + { + if (_current._packedTexture == null) Pack(); + string path = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(ShaderEditor.Active.Materials[0])); + path = path + "/" + ShaderEditor.Active.Materials[0].name + _prop.name + ".png"; + _prop.textureValue = TextureHelper.SaveTextureAsPNG(_current._packedTexture, path); + TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; + importer.streamingMipmaps = true; + importer.crunchedCompression = true; + importer.sRGBTexture = _makeSRGB; + importer.filterMode = GetFiltermode(); + importer.alphaIsTransparency = _current._packedTexture.alphaIsTransparency; + importer.SaveAndReimport(); + + _current._hasConfigChanged = false; + _current._hasTextureChanged = false; + _current._lastConfirmTime = Helper.DatetimeToUnixSeconds(DateTime.Now); + } + + void Revert() + { + _prop.textureValue = _current._previousTexture; + _current._hasTextureChanged = false; + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + + class InlinePackerChannelConfig + { + public TexturePacker.TextureSource TextureSource = new TexturePacker.TextureSource(); + public bool Invert; + public float Fallback; + public TexturePacker.TextureChannelIn Channel = TexturePacker.TextureChannelIn.Max; + + public TexturePacker.OutputConfig ToOutputConfig() + { + TexturePacker.OutputConfig outputConfig = new TexturePacker.OutputConfig(); + outputConfig.BlendMode = TexturePacker.BlendMode.Add; + outputConfig.Invert = Invert ? TexturePacker.InvertMode.Invert : TexturePacker.InvertMode.None; + outputConfig.Fallback = Fallback; + return outputConfig; + } + + public void FromOutputConfig(TexturePacker.OutputConfig config) + { + Invert = config.Invert == TexturePacker.InvertMode.Invert; + Fallback = config.Fallback; + } + + public Texture2D GetTexture() + { + if (TextureSource == null) return null; + return TextureSource.Texture; + } + } + } + + public class GradientDrawer : MaterialPropertyDrawer + { + GradientData data; + + Rect border_position; + Rect gradient_position; + + Dictionary _gradient_data = new Dictionary(); + + private void Init(MaterialProperty prop, bool replace = false) + { + if (!replace && _gradient_data.ContainsKey(prop.targets[0])) + { + data = _gradient_data[prop.targets[0]]; + return; + } + data = new GradientData(); + data.PreviewTexture = prop.textureValue; + _gradient_data[prop.targets[0]] = data; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + Init(prop); + + EditorGUI.BeginChangeCheck(); + if (EditorGUI.EndChangeCheck()) + Init(prop, true); + + if (Config.Singleton.default_texture_type == TextureDisplayType.small) + { + UpdateRects(position, prop); + if (ShaderEditor.Input.Click && border_position.Contains(Event.current.mousePosition)) + Open(prop); + GuiHelper.SmallTextureProperty(position, prop, label, editor, DrawingData.CurrentTextureProperty.hasFoldoutProperties); + GradientField(); + } + else + { + position = new RectOffset(-30, 0, 0, 0).Add(position); + Rect top_bg_rect = new Rect(position); + Rect label_rect = new Rect(position); + Rect button_select = new Rect(position); + top_bg_rect = new RectOffset(0, 0, 0, 25).Add(top_bg_rect); + label_rect = new RectOffset(-5, 5, -3, 3).Add(label_rect); + button_select = new RectOffset((int)button_select.width - 120, 20, 2, 0).Remove(button_select); + + GUILayoutUtility.GetRect(position.width, 30); // get space for gradient + border_position = new Rect(position.x, position.y + position.height, position.width, 30); + border_position = new RectOffset(3, 3, 0, 0).Remove(border_position); + gradient_position = new RectOffset(1, 1, 1, 1).Remove(border_position); + if (ShaderEditor.Input.Click && border_position.Contains(Event.current.mousePosition)) + Open(prop); + + GUI.DrawTexture(top_bg_rect, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, Styles.COLOR_BACKGROUND_1, 3, 10); + + if (DrawingData.CurrentTextureProperty.hasScaleOffset || DrawingData.CurrentTextureProperty.Options.reference_properties != null) + { + Rect extraPropsBackground = EditorGUILayout.BeginVertical(); + extraPropsBackground.x = position.x; + extraPropsBackground.width = position.width; + extraPropsBackground.y = extraPropsBackground.y - 25; + extraPropsBackground.height = extraPropsBackground.height + 25; + float propertyX = extraPropsBackground.x + 15; + float propertyWidth = extraPropsBackground.width - 30; + GUI.DrawTexture(extraPropsBackground, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, Styles.COLOR_BACKGROUND_1, 3, 10); + Rect r; + if (DrawingData.CurrentTextureProperty.hasScaleOffset) + { + r = GUILayoutUtility.GetRect(propertyWidth, 48); + r.x = propertyX; + r.y -= 8; + r.width = propertyWidth; + editor.TextureScaleOffsetProperty(r, prop); + } + if (DrawingData.CurrentTextureProperty.Options.reference_properties != null) + { + float labelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 100; + propertyX -= 30; + foreach (string pName in DrawingData.CurrentTextureProperty.Options.reference_properties) + { + ShaderProperty property = ShaderEditor.Active.PropertyDictionary[pName]; + if (property != null) + { + r = GUILayoutUtility.GetRect(propertyWidth, editor.GetPropertyHeight(property.MaterialProperty, property.Content.text) + 3); + r.x = propertyX; + r.width = propertyWidth; + property.Draw(new CRect(r)); + } + } + EditorGUIUtility.labelWidth = labelWidth; + } + EditorGUILayout.EndVertical(); + } + else + { + GUILayoutUtility.GetRect(0, 5); + Rect backgroundBottom = new RectOffset(3, 3, -5, 10).Add(border_position); + GUI.DrawTexture(backgroundBottom, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, Styles.COLOR_BACKGROUND_1, 3, 10); + } + + bool changed = GuiHelper.HandleTexturePicker(prop); + changed |= GuiHelper.AcceptDragAndDrop(border_position, prop); + if (changed) + Init(prop, true); + if (GUI.Button(button_select, "Select", EditorStyles.miniButton)) + { + GuiHelper.OpenTexturePicker(prop); + } + + GradientField(); + GUI.Label(label_rect, label); + + GUILayoutUtility.GetRect(0, 5); + } + } + + private void Open(MaterialProperty prop) + { + ShaderEditor.Input.Use(); + PropertyOptions options = ShaderEditor.Active.CurrentProperty.Options; + GradientEditor.Open(data, prop, options.texture, options.force_texture_options, !options.force_texture_options); + } + + private void UpdateRects(Rect position, MaterialProperty prop) + { + border_position = new Rect(position.x + EditorGUIUtility.labelWidth, position.y, position.width - EditorGUIUtility.labelWidth - GuiHelper.GetSmallTextureVRAMWidth(prop), position.height); + gradient_position = new Rect(border_position.x + 1, border_position.y + 1, border_position.width - 2, border_position.height - 2); + } + + private void GradientField() + { + DrawBackgroundTexture(); + if (data.PreviewTexture != null) + DrawGradientTexture(); + else + GUI.DrawTexture(border_position, Texture2D.whiteTexture, ScaleMode.StretchToFill, false, 0, Color.grey, 1, 1); + } + + private void DrawBackgroundTexture() + { + Texture2D backgroundTexture = TextureHelper.GetBackgroundTexture(); + Rect texCoordsRect = new Rect(0, 0, gradient_position.width / backgroundTexture.width, gradient_position.height / backgroundTexture.height); + GUI.DrawTextureWithTexCoords(gradient_position, backgroundTexture, texCoordsRect, false); + } + + private void DrawGradientTexture() + { + TextureWrapMode wrap_mode = data.PreviewTexture.wrapMode; + data.PreviewTexture.wrapMode = TextureWrapMode.Clamp; + bool vertical = data.PreviewTexture.height > data.PreviewTexture.width; + Vector2 pivot = new Vector2(); + if (vertical) + { + pivot = new Vector2(gradient_position.x, gradient_position.y + gradient_position.height); + GUIUtility.RotateAroundPivot(-90, pivot); + gradient_position.y += gradient_position.height; + float h = gradient_position.width; + gradient_position.width = gradient_position.height; + gradient_position.y += h; + gradient_position.height = -h; + } + GUI.DrawTexture(gradient_position, data.PreviewTexture, ScaleMode.StretchToFill, true); + if (vertical) + { + GUIUtility.RotateAroundPivot(90, pivot); + } + GUI.DrawTexture(border_position, data.PreviewTexture, ScaleMode.StretchToFill, false, 0, Color.grey, 1, 1); + data.PreviewTexture.wrapMode = wrap_mode; + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class TextureArrayDrawer : MaterialPropertyDrawer + { + private string framesProperty; + + public TextureArrayDrawer() { } + + public TextureArrayDrawer(string framesProperty) + { + this.framesProperty = framesProperty; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + ShaderProperty shaderProperty = (ShaderProperty)ShaderEditor.Active.CurrentProperty; + GuiHelper.ConfigTextureProperty(position, prop, label, editor, true, true); + + if ((ShaderEditor.Input.is_drag_drop_event) && position.Contains(ShaderEditor.Input.mouse_position)) + { + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + if (ShaderEditor.Input.is_drop_event) + { + DragAndDrop.AcceptDrag(); + HanldeDropEvent(prop, shaderProperty); + } + } + if (ShaderEditor.Active.IsFirstCall) + ShaderEditor.Active.TextureArrayProperties.Add(shaderProperty); + } + + public void HanldeDropEvent(MaterialProperty prop, ShaderProperty shaderProperty) + { + string[] paths = DragAndDrop.paths; + Texture2DArray tex; + if (AssetDatabase.GetMainAssetTypeAtPath(paths[0]) != typeof(Texture2DArray)) + tex = Converter.PathsToTexture2DArray(paths); + else + tex = AssetDatabase.LoadAssetAtPath(paths[0]); + prop.textureValue = tex; + UpdateFramesProperty(prop, shaderProperty, tex); + EditorGUIUtility.ExitGUI(); + } + + private void UpdateFramesProperty(MaterialProperty prop, ShaderProperty shaderProperty, Texture2DArray tex) + { + if (framesProperty == null) + framesProperty = shaderProperty.Options.reference_property; + + if (framesProperty != null) + { + if (ShaderEditor.Active.PropertyDictionary.ContainsKey(framesProperty)) + ShaderEditor.Active.PropertyDictionary[framesProperty].MaterialProperty.floatValue = tex.depth; + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + #endregion + + #region Decorators + public class NoAnimateDecorator : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyDoesntAllowAnimation = true; + return 0; + } + } + + public class ThrySeperatorDecorator : MaterialPropertyDrawer + { + Color _color = Styles.COLOR_FG; + + public ThrySeperatorDecorator() { } + public ThrySeperatorDecorator(string c) + { + ColorUtility.TryParseHtmlString(c, out _color); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.RegisterDecorator(this); + return 1; + } + + public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) + { + position = EditorGUI.IndentedRect(position); + EditorGUI.DrawRect(position, _color); + } + } + + public class ThryHeaderLabelDecorator : MaterialPropertyDrawer + { + readonly string text; + readonly int size; + GUIStyle style; + + public ThryHeaderLabelDecorator(string text) : this(text, EditorStyles.standardFont.fontSize) + { + } + public ThryHeaderLabelDecorator(string text, float size) + { + this.text = text; + this.size = (int)size; + style = new GUIStyle(EditorStyles.boldLabel); + style.fontSize = this.size; + } + + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.RegisterDecorator(this); + return size + 6; + } + + public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) + { + float offst = position.height; + position = EditorGUI.IndentedRect(position); + GUI.Label(position, text, style); + } + } + + public class ThryRichLabelDrawer : MaterialPropertyDrawer + { + readonly int size; + GUIStyle style; + + public ThryRichLabelDrawer(float size) + { + this.size = (int)size; + style = new GUIStyle(EditorStyles.boldLabel); + style.richText = true; + style.fontSize = this.size; + } + + public ThryRichLabelDrawer() : this(EditorStyles.standardFont.fontSize) { } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + return size + 4; + } + + public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) + { + float offst = position.height; + position = EditorGUI.IndentedRect(position); + GUI.Label(position, label, style); + } + } + #endregion + + #region Vector Drawers + public class ThryToggleDrawer : MaterialPropertyDrawer + { + public string keyword; + private bool isFirstGUICall = true; + public bool left = false; + private bool hasKeyword = false; + + public ThryToggleDrawer() + { + } + + //the reason for weird string thing here is that you cant have bools as params for drawers + public ThryToggleDrawer(string keywordLeft) + { + if (keywordLeft == "true") left = true; + else if (keywordLeft == "false") left = false; + else keyword = keywordLeft; + hasKeyword = keyword != null; + } + + public ThryToggleDrawer(string keyword, string left) + { + this.keyword = keyword; + this.left = left == "true"; + hasKeyword = keyword != null; + } + + protected void SetKeyword(MaterialProperty prop, bool on) + { + if (ShaderOptimizer.IsMaterialLocked(prop.targets[0] as Material)) return; + SetKeywordInternal(prop, on, "_ON"); + } + + protected void CheckKeyword(MaterialProperty prop) + { + if (ShaderEditor.Active != null && ShaderOptimizer.IsMaterialLocked(prop.targets[0] as Material)) return; + if (prop.hasMixedValue) + { + foreach (Material m in prop.targets) + { + if (m.GetFloat(prop.name) == 1) + m.EnableKeyword(keyword); + else + m.DisableKeyword(keyword); + } + } + else + { + foreach (Material m in prop.targets) + { + if (prop.floatValue == 1) + m.EnableKeyword(keyword); + else + m.DisableKeyword(keyword); + } + } + } + + static bool IsPropertyTypeSuitable(MaterialProperty prop) + { + return prop.type == MaterialProperty.PropType.Float || prop.type == MaterialProperty.PropType.Range; + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + if (!IsPropertyTypeSuitable(prop)) + { + return EditorGUIUtility.singleLineHeight * 2.5f; + } + if (hasKeyword) + { + CheckKeyword(prop); + DrawingData.LastPropertyDoesntAllowAnimation = true; + } + return base.GetPropertyHeight(prop, label, editor); + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + if (!IsPropertyTypeSuitable(prop)) + { + return; + } + if (isFirstGUICall && !ShaderEditor.Active.IsLockedMaterial) + { + if (hasKeyword) CheckKeyword(prop); + isFirstGUICall = false; + } + //why is this not inFirstGUICall ? cause it seems drawers are kept between different openings of the shader editor, so this needs to be set again every time the shader editor is reopened for that material + (ShaderEditor.Active.PropertyDictionary[prop.name] as ShaderProperty).keyword = keyword; + + EditorGUI.BeginChangeCheck(); + + bool value = (Math.Abs(prop.floatValue) > 0.001f); + EditorGUI.showMixedValue = prop.hasMixedValue; + if (left) value = EditorGUI.ToggleLeft(position, label, value, Styles.style_toggle_left_richtext); + else value = EditorGUI.Toggle(position, label, value); + EditorGUI.showMixedValue = false; + if (EditorGUI.EndChangeCheck()) + { + prop.floatValue = value ? 1.0f : 0.0f; + if (hasKeyword) SetKeyword(prop, value); + } + } + + public override void Apply(MaterialProperty prop) + { + base.Apply(prop); + if (!IsPropertyTypeSuitable(prop)) + return; + + if (prop.hasMixedValue) + return; + + if (hasKeyword) SetKeyword(prop, (Math.Abs(prop.floatValue) > 0.001f)); + } + + protected void SetKeywordInternal(MaterialProperty prop, bool on, string defaultKeywordSuffix) + { + // if no keyword is provided, use + defaultKeywordSuffix + string kw = string.IsNullOrEmpty(keyword) ? prop.name.ToUpperInvariant() + defaultKeywordSuffix : keyword; + // set or clear the keyword + foreach (Material material in prop.targets) + { + if (on) + material.EnableKeyword(kw); + else + material.DisableKeyword(kw); + } + } + } + + //This class only exists for backward compatibility + public class ThryToggleUIDrawer : ThryToggleDrawer + { + public ThryToggleUIDrawer() + { + } + + //the reason for weird string thing here is that you cant have bools as params for drawers + public ThryToggleUIDrawer(string keywordLeft) + { + if (keywordLeft == "true") left = true; + else if (keywordLeft == "false") left = false; + else keyword = keywordLeft; + } + + public ThryToggleUIDrawer(string keyword, string left) + { + this.keyword = keyword; + this.left = left == "true"; + } + } + + public class MultiSliderDrawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + GuiHelper.MinMaxSlider(position, label, prop); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class VectorToSlidersDrawer : MaterialPropertyDrawer + { + class SliderConfig + { + public string Label; + public float Min; + public float Max; + + public SliderConfig(string l, float min, float max) + { + Label = l; + Min = min; + Max = max; + } + } + + SliderConfig _slider1; + SliderConfig _slider2; + SliderConfig _slider3; + SliderConfig _slider4; + bool _twoMinMaxDrawers; + + VectorToSlidersDrawer(SliderConfig slider1, SliderConfig slider2, SliderConfig slider3, SliderConfig slider4, float twoMinMaxDrawers) + { + _slider1 = slider1; + _slider2 = slider2; + _slider3 = slider3; + _slider4 = slider4; + _twoMinMaxDrawers = twoMinMaxDrawers == 1; + } + + public VectorToSlidersDrawer(string label1, float min1, float max1, string label2, float min2, float max2, string label3, float min3, float max3, string label4, float min4, float max4) : + this(new SliderConfig(label1, min1, max1), new SliderConfig(label2, min2, max2), new SliderConfig(label3, min3, max3), new SliderConfig(label4, min4, max4), 0) + { } + public VectorToSlidersDrawer(string label1, float min1, float max1, string label2, float min2, float max2, string label3, float min3, float max3) : + this(new SliderConfig(label1, min1, max1), new SliderConfig(label2, min2, max2), new SliderConfig(label3, min3, max3), null, 0) + { } + public VectorToSlidersDrawer(string label1, float min1, float max1, string label2, float min2, float max2) : + this(new SliderConfig(label1, min1, max1), new SliderConfig(label2, min2, max2), null, null, 0) + { } + public VectorToSlidersDrawer(float twoMinMaxDrawers, string label1, float min1, float max1, string label2, float min2, float max2) : + this(new SliderConfig(label1, min1, max1), new SliderConfig(label2, min2, max2), null, null, twoMinMaxDrawers) + { } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + Vector4 vector = prop.vectorValue; + EditorGUI.BeginChangeCheck(); + if (_twoMinMaxDrawers) + { + float min1 = vector.x; + float max1 = vector.y; + float min2 = vector.z; + float max2 = vector.w; + EditorGUI.showMixedValue = prop.hasMixedValue; + EditorGUILayout.MinMaxSlider(_slider1.Label, ref min1, ref max1, _slider1.Min, _slider1.Max); + EditorGUI.showMixedValue = prop.hasMixedValue; + EditorGUILayout.MinMaxSlider(_slider2.Label, ref min2, ref max2, _slider2.Min, _slider2.Max); + vector = new Vector4(min1, max1, min2, max2); + } + else + { + EditorGUI.showMixedValue = prop.hasMixedValue; + vector.x = EditorGUILayout.Slider(_slider1.Label, vector.x, _slider1.Min, _slider1.Max); + EditorGUI.showMixedValue = prop.hasMixedValue; + vector.y = EditorGUILayout.Slider(_slider2.Label, vector.y, _slider2.Min, _slider2.Max); + if (_slider3 != null) + { + EditorGUI.showMixedValue = prop.hasMixedValue; + vector.z = EditorGUILayout.Slider(_slider3.Label, vector.z, _slider3.Min, _slider3.Max); + } + if (_slider4 != null) + { + EditorGUI.showMixedValue = prop.hasMixedValue; + vector.w = EditorGUILayout.Slider(_slider4.Label, vector.w, _slider4.Min, _slider4.Max); + } + } + if (EditorGUI.EndChangeCheck()) + prop.vectorValue = vector; + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor) - EditorGUIUtility.singleLineHeight; + } + } + + public class Vector4TogglesDrawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = prop.hasMixedValue; + EditorGUI.LabelField(position, label); + position.x += EditorGUIUtility.labelWidth; + position.width = (position.width - EditorGUIUtility.labelWidth) / 4; + bool b1 = GUI.Toggle(position, prop.vectorValue.x == 1, ""); + position.x += position.width; + bool b2 = GUI.Toggle(position, prop.vectorValue.y == 1, ""); + position.x += position.width; + bool b3 = GUI.Toggle(position, prop.vectorValue.z == 1, ""); + position.x += position.width; + bool b4 = GUI.Toggle(position, prop.vectorValue.w == 1, ""); + if (EditorGUI.EndChangeCheck()) + { + prop.vectorValue = new Vector4(b1 ? 1 : 0, b2 ? 1 : 0, b3 ? 1 : 0, b4 ? 1 : 0); + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class ThryMultiFloatsDrawer : MaterialPropertyDrawer + { + string[] _otherProperties; + MaterialProperty[] _otherMaterialProps; + bool _displayAsToggles; + + public ThryMultiFloatsDrawer(string displayAsToggles, string p1, string p2, string p3, string p4, string p5, string p6, string p7) : this(displayAsToggles, new string[] { p1, p2, p3, p4, p5, p6, p7 }) { } + public ThryMultiFloatsDrawer(string displayAsToggles, string p1, string p2, string p3, string p4, string p5, string p6) : this(displayAsToggles, new string[] { p1, p2, p3, p4, p5, p6 }) { } + public ThryMultiFloatsDrawer(string displayAsToggles, string p1, string p2, string p3, string p4, string p5) : this(displayAsToggles, new string[] { p1, p2, p3, p4, p5 }) { } + public ThryMultiFloatsDrawer(string displayAsToggles, string p1, string p2, string p3, string p4) : this(displayAsToggles, new string[] { p1, p2, p3, p4 }) { } + public ThryMultiFloatsDrawer(string displayAsToggles, string p1, string p2, string p3) : this(displayAsToggles, new string[] { p1, p2, p3 }) { } + public ThryMultiFloatsDrawer(string displayAsToggles, string p1, string p2) : this(displayAsToggles, new string[] { p1, p2 }) { } + public ThryMultiFloatsDrawer(string displayAsToggles, string p1) : this(displayAsToggles, new string[] { p1 }) { } + + public ThryMultiFloatsDrawer(string displayAsToggles, params string[] extraProperties) + { + _displayAsToggles = displayAsToggles.ToLower() == "true" || displayAsToggles == "1"; + _otherProperties = extraProperties; + _otherMaterialProps = new MaterialProperty[extraProperties.Length]; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + Rect labelR = new Rect(position); + labelR.width = EditorGUIUtility.labelWidth; + Rect contentR = new Rect(position); + contentR.width = (contentR.width - labelR.width) / (_otherProperties.Length + 1); + contentR.x += labelR.width; + + for (int i = 0; i < _otherProperties.Length; i++) + _otherMaterialProps[i] = ShaderEditor.Active.PropertyDictionary[_otherProperties[i]].MaterialProperty; + EditorGUI.BeginChangeCheck(); + + EditorGUI.LabelField(labelR, label); + int indentLevel = EditorGUI.indentLevel; //else it double indents + EditorGUI.indentLevel = 0; + PropGUI(prop, contentR, 0); + if (ShaderEditor.Active.IsInAnimationMode) + MaterialEditor.PrepareMaterialPropertiesForAnimationMode(_otherMaterialProps, true); + for (int i = 0; i < _otherProperties.Length; i++) + { + PropGUI(_otherMaterialProps[i], contentR, i + 1); + } + EditorGUI.indentLevel = indentLevel; + + //If edited in animation mode mark as animated (needed cause other properties isnt checked in draw) + if (EditorGUI.EndChangeCheck() && ShaderEditor.Active.IsInAnimationMode && !ShaderEditor.Active.CurrentProperty.IsAnimated) + ShaderEditor.Active.CurrentProperty.SetAnimated(true, false); + //make sure all are animated together + bool animated = ShaderEditor.Active.CurrentProperty.IsAnimated; + bool renamed = ShaderEditor.Active.CurrentProperty.IsRenaming; + for (int i = 0; i < _otherProperties.Length; i++) + ShaderEditor.Active.PropertyDictionary[_otherProperties[i]].SetAnimated(animated, renamed); + } + + void PropGUI(MaterialProperty prop, Rect contentRect, int index) + { + contentRect.x += contentRect.width * index; + contentRect.width -= 5; + + float val = prop.floatValue; + EditorGUI.showMixedValue = prop.hasMixedValue; + EditorGUI.BeginChangeCheck(); + if (_displayAsToggles) val = EditorGUI.Toggle(contentRect, val == 1) ? 1 : 0; + else val = EditorGUI.FloatField(contentRect, val); + if (EditorGUI.EndChangeCheck()) + { + prop.floatValue = val; + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class Vector3Drawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = prop.hasMixedValue; + Vector4 vec = EditorGUI.Vector3Field(position, label, prop.vectorValue); + if (EditorGUI.EndChangeCheck()) + { + prop.vectorValue = new Vector4(vec.x, vec.y, vec.z, prop.vectorValue.w); + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class Vector2Drawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = prop.hasMixedValue; + Vector4 vec = EditorGUI.Vector2Field(position, label, prop.vectorValue); + if (EditorGUI.EndChangeCheck()) + { + prop.vectorValue = new Vector4(vec.x, vec.y, prop.vectorValue.z, prop.vectorValue.w); + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + public class VectorLabelDrawer : MaterialPropertyDrawer + { + string[] _labelStrings = new string[4] { "X", "Y", "Z", "W" }; + int vectorChannels = 0; + + public VectorLabelDrawer(string labelX, string labelY) + { + _labelStrings[0] = labelX; + _labelStrings[1] = labelY; + vectorChannels = 2; + } + + public VectorLabelDrawer(string labelX, string labelY, string labelZ) + { + _labelStrings[0] = labelX; + _labelStrings[1] = labelY; + _labelStrings[2] = labelZ; + vectorChannels = 3; + } + + public VectorLabelDrawer(string labelX, string labelY, string labelZ, string labelW) + { + _labelStrings[0] = labelX; + _labelStrings[1] = labelY; + _labelStrings[2] = labelZ; + _labelStrings[3] = labelW; + vectorChannels = 4; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = prop.hasMixedValue; + + Rect labelR = new Rect(position.x, position.y, position.width * 0.41f, position.height); + Rect contentR = new Rect(position.x + labelR.width, position.y, position.width - labelR.width, position.height); + + float[] values = new float[vectorChannels]; + GUIContent[] labels = new GUIContent[vectorChannels]; + + for (int i = 0; i < vectorChannels; i++) + { + values[i] = prop.vectorValue[i]; + labels[i] = new GUIContent(_labelStrings[i]); + } + + EditorGUI.LabelField(labelR, label); + EditorGUI.MultiFloatField(contentR, labels, values); + + if (EditorGUI.EndChangeCheck()) + { + switch (vectorChannels) + { + case 2: + prop.vectorValue = new Vector4(values[0], values[1], prop.vectorValue.z, prop.vectorValue.w); + break; + case 3: + prop.vectorValue = new Vector4(values[0], values[1], values[2], prop.vectorValue.w); + break; + case 4: + prop.vectorValue = new Vector4(values[0], values[1], values[2], values[3]); + break; + default: + break; + } + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + // Enum with normal editor width, rather than MaterialEditor Default GUI widths + // Would be nice if Decorators could access Drawers too so this wouldn't be necessary for something to trivial + // Adapted from Unity interal MaterialEnumDrawer https://github.com/Unity-Technologies/UnityCsReference/ + public class ThryWideEnumDrawer : MaterialPropertyDrawer + { + // TODO: Consider Load locale by property name in the future (maybe, could have drawbacks) + private GUIContent[] names; + private readonly string[] defaultNames; + private readonly float[] values; + private int _reloadCount = -1; + private static int _reloadCountStatic; + + // internal Unity AssemblyHelper can't be accessed + private Type[] TypesFromAssembly(Assembly a) + { + if (a == null) + return new Type[0]; + try + { + return a.GetTypes(); + } + catch (ReflectionTypeLoadException) + { + return new Type[0]; + } + } + public ThryWideEnumDrawer(string enumName, int j) + { + var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany( + x => TypesFromAssembly(x)).ToArray(); + try + { + var enumType = types.FirstOrDefault( + x => x.IsEnum && (x.Name == enumName || x.FullName == enumName) + ); + var enumNames = Enum.GetNames(enumType); + names = new GUIContent[enumNames.Length]; + for (int i = 0; i < enumNames.Length; ++i) + names[i] = new GUIContent(enumNames[i]); + + var enumVals = Enum.GetValues(enumType); + values = new float[enumVals.Length]; + for (int i = 0; i < enumVals.Length; ++i) + values[i] = (int)enumVals.GetValue(i); + } + catch (Exception) + { + Debug.LogWarningFormat("Failed to create WideEnum, enum {0} not found", enumName); + throw; + } + + } + + public ThryWideEnumDrawer(string n1, float v1) : this(new[] { n1 }, new[] { v1 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2) : this(new[] { n1, n2 }, new[] { v1, v2 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3) : this(new[] { n1, n2, n3 }, new[] { v1, v2, v3 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4) : this(new[] { n1, n2, n3, n4 }, new[] { v1, v2, v3, v4 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5) : this(new[] { n1, n2, n3, n4, n5 }, new[] { v1, v2, v3, v4, v5 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6) : this(new[] { n1, n2, n3, n4, n5, n6 }, new[] { v1, v2, v3, v4, v5, v6 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7) : this(new[] { n1, n2, n3, n4, n5, n6, n7 }, new[] { v1, v2, v3, v4, v5, v6, v7 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12, string n13, float v13) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12, string n13, float v13, string n14, float v14) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12, string n13, float v13, string n14, float v14, string n15, float v15) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12, string n13, float v13, string n14, float v14, string n15, float v15, string n16, float v16) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12, string n13, float v13, string n14, float v14, string n15, float v15, string n16, float v16, string n17, float v17) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12, string n13, float v13, string n14, float v14, string n15, float v15, string n16, float v16, string n17, float v17, string n18, float v18) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12, string n13, float v13, string n14, float v14, string n15, float v15, string n16, float v16, string n17, float v17, string n18, float v18, string n19, float v19) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18, n19 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19 }) { } + public ThryWideEnumDrawer(string n1, float v1, string n2, float v2, string n3, float v3, string n4, float v4, string n5, float v5, string n6, float v6, string n7, float v7, string n8, float v8, string n9, float v9, string n10, float v10, string n11, float v11, string n12, float v12, string n13, float v13, string n14, float v14, string n15, float v15, string n16, float v16, string n17, float v17, string n18, float v18, string n19, float v19, string n20, float v20) : this(new[] { n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18, n19, n20 }, new[] { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20 }) { } + public ThryWideEnumDrawer(string[] enumNames, float[] vals) + { + defaultNames = enumNames; + + // Init without Locale to prevent errors + names = new GUIContent[enumNames.Length]; + for (int i = 0; i < enumNames.Length; ++i) + names[i] = new GUIContent(enumNames[i]); + + values = new float[vals.Length]; + for (int i = 0; i < vals.Length; ++i) + values[i] = vals[i]; + } + + void LoadNames() + { + names = new GUIContent[defaultNames.Length]; + for (int i = 0; i < defaultNames.Length; ++i) + { + names[i] = new GUIContent(ShaderEditor.Active.Locale.Get(defaultNames[i], defaultNames[i])); + } + } + public static void Reload() + { + _reloadCountStatic++; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + EditorGUI.showMixedValue = prop.hasMixedValue; + EditorGUI.BeginChangeCheck(); + var value = prop.floatValue; + int selectedIndex = Array.IndexOf(values, value); + + if (_reloadCount != _reloadCountStatic) + { + _reloadCount = _reloadCountStatic; + LoadNames(); + } + + var selIndex = EditorGUI.Popup(position, label, selectedIndex, names); + EditorGUI.showMixedValue = false; + if (EditorGUI.EndChangeCheck()) + prop.floatValue = values[selIndex]; + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return base.GetPropertyHeight(prop, label, editor); + } + } + + #endregion + + #region UI Drawers + public class HelpboxDrawer : MaterialPropertyDrawer + { + readonly MessageType type; + + public HelpboxDrawer() + { + type = MessageType.Info; + } + + public HelpboxDrawer(float f) + { + type = (MessageType)(int)f; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + EditorGUILayout.HelpBox(label.text, type); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return 0; + } + } + + public class sRGBWarningDecorator : MaterialPropertyDrawer + { + bool _isSRGB = true; + + public sRGBWarningDecorator() + { + _isSRGB = false; + } + + public sRGBWarningDecorator(string shouldHaveSRGB) + { + this._isSRGB = shouldHaveSRGB.ToLower() == "true"; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + GuiHelper.ColorspaceWarning(prop, _isSRGB); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.RegisterDecorator(this); + return 0; + } + } + + public class LocalMessageDrawer : MaterialPropertyDrawer + { + protected ButtonData _buttonData; + protected bool _isInit; + protected virtual void Init(string s) + { + if (_isInit) return; + _buttonData = Parser.Deserialize(s); + _isInit = true; + } + + public override void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + Init(prop.displayName); + if (_buttonData == null) return; + if (_buttonData.text.Length > 0) + { + GUILayout.Label(new GUIContent(_buttonData.text, _buttonData.hover), _buttonData.center_position ? Styles.richtext_center : Styles.richtext); + Rect r = GUILayoutUtility.GetLastRect(); + if (Event.current.type == EventType.MouseDown && r.Contains(Event.current.mousePosition)) + _buttonData.action.Perform(ShaderEditor.Active?.Materials); + } + if (_buttonData.texture != null) + { + if (_buttonData.center_position) GUILayout.Label(new GUIContent(_buttonData.texture.loaded_texture, _buttonData.hover), EditorStyles.centeredGreyMiniLabel, GUILayout.MaxHeight(_buttonData.texture.height)); + else GUILayout.Label(new GUIContent(_buttonData.texture.loaded_texture, _buttonData.hover), GUILayout.MaxHeight(_buttonData.texture.height)); + Rect r = GUILayoutUtility.GetLastRect(); + if (Event.current.type == EventType.MouseDown && r.Contains(Event.current.mousePosition)) + _buttonData.action.Perform(ShaderEditor.Active?.Materials); + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + return 0; + } + } + + public class RemoteMessageDrawer : LocalMessageDrawer + { + + protected override void Init(string s) + { + if (_isInit) return; + WebHelper.DownloadStringASync(s, (Action)((string data) => + { + _buttonData = Parser.Deserialize(data); + })); + _isInit = true; + } + } + #endregion + + #region enums + public enum ColorMask + { + None, + Alpha, + Blue, + BA, + Green, + GA, + GB, + GBA, + Red, + RA, + RB, + RBA, + RG, + RGA, + RGB, + RGBA + } + + // DX11 only blend operations + public enum BlendOp + { + Add, + Subtract, + ReverseSubtract, + Min, + Max, + LogicalClear, + LogicalSet, + LogicalCopy, + LogicalCopyInverted, + LogicalNoop, + LogicalInvert, + LogicalAnd, + LogicalNand, + LogicalOr, + LogicalNor, + LogicalXor, + LogicalEquivalence, + LogicalAndReverse, + LogicalAndInverted, + LogicalOrReverse, + LogicalOrInverted + } + #endregion + + public class ThryShaderOptimizerLockButtonDrawer : MaterialPropertyDrawer + { + public override void OnGUI(Rect position, MaterialProperty shaderOptimizer, string label, MaterialEditor materialEditor) + { + Material material = shaderOptimizer.targets[0] as Material; + Shader shader = material.shader; + // The GetPropertyDefaultFloatValue is changed from 0 to 1 when the shader is locked in + bool isLocked = shader.name.StartsWith("Hidden/Locked/") || + (shader.name.StartsWith("Hidden/") && material.GetTag("OriginalShader", false, "") != "" && shader.GetPropertyDefaultFloatValue(shader.FindPropertyIndex(shaderOptimizer.name)) == 1); + //this will make sure the button is unlocked if you manually swap to an unlocked shader + //shaders that have the ability to be locked shouldnt really be hidden themself. at least it wouldnt make too much sense + if (shaderOptimizer.hasMixedValue == false && shaderOptimizer.floatValue == 1 && isLocked == false) + { + shaderOptimizer.floatValue = 0; + } + else if (shaderOptimizer.hasMixedValue == false && shaderOptimizer.floatValue == 0 && isLocked) + { + shaderOptimizer.floatValue = 1; + } + + // Theoretically this shouldn't ever happen since locked in materials have different shaders. + // But in a case where the material property says its locked in but the material really isn't, this + // will display and allow users to fix the property/lock in + ShaderEditor.Active.IsLockedMaterial = shaderOptimizer.floatValue == 1; + if (shaderOptimizer.hasMixedValue) + { + EditorGUI.BeginChangeCheck(); + GUILayout.Button(EditorLocale.editor.Get("lockin_button_multi").ReplaceVariables(materialEditor.targets.Length)); + if (EditorGUI.EndChangeCheck()) + { + SaveChangeStack(); + ShaderOptimizer.SetLockedForAllMaterials(shaderOptimizer.targets.Select(t => t as Material), shaderOptimizer.floatValue == 1 ? 0 : 1, true, false, false, shaderOptimizer); + RestoreChangeStack(); + } + } + else + { + EditorGUI.BeginChangeCheck(); + if (shaderOptimizer.floatValue == 0) + { + if (materialEditor.targets.Length == 1) + GUILayout.Button(EditorLocale.editor.Get("lockin_button_single")); + else GUILayout.Button(EditorLocale.editor.Get("lockin_button_multi").ReplaceVariables(materialEditor.targets.Length)); + } + else + { + if (materialEditor.targets.Length == 1) + GUILayout.Button(EditorLocale.editor.Get("unlock_button_single")); + else GUILayout.Button(EditorLocale.editor.Get("unlock_button_multi").ReplaceVariables(materialEditor.targets.Length)); + } + if (EditorGUI.EndChangeCheck()) + { + SaveChangeStack(); + ShaderOptimizer.SetLockedForAllMaterials(shaderOptimizer.targets.Select(t => t as Material), shaderOptimizer.floatValue == 1 ? 0 : 1, true, false, false, shaderOptimizer); + RestoreChangeStack(); + } + } + if (Config.Singleton.allowCustomLockingRenaming || ShaderEditor.Active.HasCustomRenameSuffix) + { + EditorGUI.BeginDisabledGroup(!Config.Singleton.allowCustomLockingRenaming || ShaderEditor.Active.IsLockedMaterial); + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = ShaderEditor.Active.HasMixedCustomPropertySuffix; + ShaderEditor.Active.RenamedPropertySuffix = EditorGUILayout.TextField("Locked property suffix: ", ShaderEditor.Active.RenamedPropertySuffix); + if (EditorGUI.EndChangeCheck()) + { + // Make sure suffix that is saved is valid + ShaderEditor.Active.RenamedPropertySuffix = ShaderOptimizer.CleanStringForPropertyNames(ShaderEditor.Active.RenamedPropertySuffix.Replace(" ", "_")); + foreach (Material m in ShaderEditor.Active.Materials) + m.SetOverrideTag("thry_rename_suffix", ShaderEditor.Active.RenamedPropertySuffix); + if (ShaderEditor.Active.RenamedPropertySuffix == "") + ShaderEditor.Active.RenamedPropertySuffix = ShaderOptimizer.GetRenamedPropertySuffix(ShaderEditor.Active.Materials[0]); + ShaderEditor.Active.HasCustomRenameSuffix = ShaderOptimizer.HasCustomRenameSuffix(ShaderEditor.Active.Materials[0]); + } + if (!Config.Singleton.allowCustomLockingRenaming) + { + EditorGUILayout.HelpBox("This feature is disabled in the config file. You can enable it by setting allowCustomLockingRenaming to true.", MessageType.Info); + } + EditorGUI.EndDisabledGroup(); + } + } + + //This code purly exists cause Unity 2019 is a piece of shit that looses it's internal change stack on locking CAUSE FUCK IF I KNOW + static System.Reflection.FieldInfo changeStack = typeof(EditorGUI).GetField("s_ChangedStack", BindingFlags.Static | BindingFlags.NonPublic); + static int preLockStackSize = 0; + private static void SaveChangeStack() + { + if (changeStack != null) + { + Stack stack = (Stack)changeStack.GetValue(null); + if (stack != null) + { + preLockStackSize = stack.Count(); + } + } + } + + private static void RestoreChangeStack() + { + if (changeStack != null) + { + Stack stack = (Stack)changeStack.GetValue(null); + if (stack != null) + { + int postLockStackSize = stack.Count(); + //Restore change stack from before lock / unlocking + for (int i = postLockStackSize; i < preLockStackSize; i++) + { + EditorGUI.BeginChangeCheck(); + } + } + } + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + DrawingData.LastPropertyDoesntAllowAnimation = true; + ShaderEditor.Active.DoUseShaderOptimizer = true; + return -2; + } + } + + public class ThryDecalPositioningDecorator : MaterialPropertyDrawer + { + string _texturePropertyName; + string _uvIndexPropertyName; + string _positionPropertyName; + string _rotationPropertyName; + string _scalePropertyName; + string _offsetPropertyName; + DecalSceneTool _sceneTool; + DecalTool _tool; + + public ThryDecalPositioningDecorator(string textureProp, string uvIndexPropertyName, string positionProp, string rotationProp, string scaleProp, string offsetProp) + { + _texturePropertyName = textureProp; + _uvIndexPropertyName = uvIndexPropertyName; + _positionPropertyName = positionProp; + _rotationPropertyName = rotationProp; + _offsetPropertyName = offsetProp; + _scalePropertyName = scaleProp; + } + + void CreateSceneTool() + { + DiscardSceneTool(); + _sceneTool = DecalSceneTool.Create( + Selection.activeTransform.GetComponent(), + ShaderEditor.Active.Materials[0], + (int)ShaderEditor.Active.PropertyDictionary[_uvIndexPropertyName].MaterialProperty.floatValue, + ShaderEditor.Active.PropertyDictionary[_positionPropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_rotationPropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_scalePropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_offsetPropertyName].MaterialProperty); + } + + void DiscardSceneTool() + { + if(_sceneTool != null) + { + _sceneTool.Deactivate(); + _sceneTool = null; + } + } + + public override void OnGUI(Rect position, MaterialProperty prop, string label, MaterialEditor editor) + { + position = new RectOffset(0, 0, 0, 3).Remove(EditorGUI.IndentedRect(position)); + bool isInScene = Selection.activeTransform != null && Selection.activeTransform.GetComponent() != null; + if(isInScene) + { + position.width /= 3; + ButtonGUI(position); + position.x += position.width; + ButtonRaycast(position); + position.x += position.width; + ButtonSceneTools(position); + if(_sceneTool != null) + { + _sceneTool.SetMaterialProperties( + ShaderEditor.Active.PropertyDictionary[_positionPropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_rotationPropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_scalePropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_offsetPropertyName].MaterialProperty); + } + }else + { + ButtonGUI(position); + } + } + + void ButtonGUI(Rect r) + { + if(GUI.Button(r, "Open Positioning Tool")) + { + _tool = DecalTool.OpenDecalTool(ShaderEditor.Active.Materials[0]); + } + // This is done because the tool didnt want to update if the data was changed from the outside + if(_tool != null) + { + _tool.SetMaterialProperties( + ShaderEditor.Active.PropertyDictionary[_texturePropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_uvIndexPropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_positionPropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_rotationPropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_scalePropertyName].MaterialProperty, + ShaderEditor.Active.PropertyDictionary[_offsetPropertyName].MaterialProperty); + } + } + + void ButtonRaycast(Rect r) + { + if (GUI.Button(r, "Raycast")) + { + if(_sceneTool != null && _sceneTool.GetMode() == DecalSceneTool.Mode.Raycast) + { + DiscardSceneTool(); + } + else + { + CreateSceneTool(); + _sceneTool.StartRaycastMode(); + } + } + if(_sceneTool != null && _sceneTool.GetMode() == DecalSceneTool.Mode.Raycast) + GUI.DrawTexture(r, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, new Color(0.5f, 0.5f, 0.5f, 0.5f), 0, 3); + } + + void ButtonSceneTools(Rect r) + { + if (GUI.Button(r, "Scene Tools")) + { + if(_sceneTool != null && _sceneTool.GetMode() == DecalSceneTool.Mode.Handles) + { + DiscardSceneTool(); + } + else + { + CreateSceneTool(); + _sceneTool.StartHandleMode(); + } + } + if(_sceneTool != null && _sceneTool.GetMode() == DecalSceneTool.Mode.Handles) + GUI.DrawTexture(r, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, new Color(0.5f, 0.5f, 0.5f, 0.5f), 0, 3); + } + + public override float GetPropertyHeight(MaterialProperty prop, string label, MaterialEditor editor) + { + DrawingData.LastPropertyUsedCustomDrawer = true; + DrawingData.LastPropertyDoesntAllowAnimation = false; + return EditorGUIUtility.singleLineHeight + 6; + } + } +} diff --git a/Scripts/ThryEditor/Editor/EditorLocale.cs b/Scripts/ThryEditor/Editor/EditorLocale.cs new file mode 100644 index 0000000..da40469 --- /dev/null +++ b/Scripts/ThryEditor/Editor/EditorLocale.cs @@ -0,0 +1,162 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class EditorLocale + { + const string EDITOR_LOCALE_NAME = "thry_editor_locale"; + + private string[] languages; + public int selected_locale_index = 0; + private Dictionary dictionary; + + public EditorLocale(string file_name) + { + LoadCSV(file_name); + } + + public EditorLocale(string file_name, string selected_name) + { + LoadCSV(file_name); + SetSelectedLocale(selected_name); + } + + public void SetSelectedLocale(string name) + { + for (int i = 0; i < languages.Length; i++) + if (languages[i].Equals(name)) + selected_locale_index = i; + } + + public string Get(string key) + { + if(dictionary.ContainsKey(key)) return dictionary[key][selected_locale_index]; + Debug.LogWarning("Locale[key] could not be found."); + return key; + } + + public bool Constains(string key) + { + return dictionary.ContainsKey(key) && string.IsNullOrEmpty(dictionary[key][selected_locale_index]) == false; + } + + public string[] available_locales + { + get + { + return languages; + } + } + + public Dictionary.KeyCollection GetAllKeys() + { + return dictionary.Keys; + } + + public void LoadCSV(string file_name) + { + List files = UnityHelper.FindAssetsWithFilename(file_name + ".csv"); + if (files.Count > 0) + ParseCSV(FileHelper.ReadFileIntoString(files[0])); + else + throw new System.Exception("CVS File with name \"" + file_name + "\" could not be found."); + } + + private static EditorLocale p_editor; + public static EditorLocale editor + { + get + { + if (p_editor == null) + p_editor = new EditorLocale(EDITOR_LOCALE_NAME); + return p_editor; + } + } + + private void ParseCSV(string text) + { + List> lines = GetCVSFields(text); + InitLanguages(lines); + lines.RemoveAt(0); + InitDictionary(lines); + } + + private void InitLanguages(List> lines) + { + languages = new string[lines[0].Count - 1]; + for (int i = 0; i < languages.Length; i++) + languages[i] = lines[0][i + 1]; + } + + private void InitDictionary(List> lines) + { + dictionary = new Dictionary(); + foreach(List line in lines) + { + string key = line[0]; + if (key == "") + continue; + string[] value = new string[languages.Length]; + value[0] = ""; + for(int i = 0; i < value.Length; i++) + { + if (line.Count > i + 1 && line[i + 1] != "") + value[i] = line[i + 1]; + else + value[i] = value[0]; + value[i] = value[i].Replace("\\n", "\n"); + } + dictionary.Add(key, value); + } + } + + private static List> GetCVSFields(string text) + { + char[] array = text.ToCharArray(); + List> lines = new List>(); + List current_line = new List(); + lines.Add(current_line); + string current_value = ""; + bool in_apostrpoh = false; + for (int i = 0; i < array.Length; i++) + { + if (!in_apostrpoh && (array[i] == '\r') && i + 1 < array.Length && (array[i + 1] == '\n')) + i += 1; + if (!in_apostrpoh && (array[i] == '\n')) + { + current_line.Add(current_value); + current_line = new List(); + lines.Add(current_line); + current_value = ""; + } + else if (!in_apostrpoh && array[i] == ',') + { + current_line.Add(current_value); + current_value = ""; + } + else if (!in_apostrpoh && array[i] == '"') + { + in_apostrpoh = true; + } + else if (in_apostrpoh && array[i] == '"' && (i == array.Length - 1 || array[i + 1] != '"')) + { + in_apostrpoh = false; + } + else if (in_apostrpoh && array[i] == '"' && array[i + 1] == '"') + { + current_value += '"'; + i += 1; + } + else + { + current_value += array[i]; + } + } + current_line.Add(current_value); + return lines; + } + } +} diff --git a/Scripts/ThryEditor/Editor/EditorStructs.cs b/Scripts/ThryEditor/Editor/EditorStructs.cs new file mode 100644 index 0000000..042706f --- /dev/null +++ b/Scripts/ThryEditor/Editor/EditorStructs.cs @@ -0,0 +1,1215 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Thry.ThryEditor; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class CRect + { + public Rect r; + public CRect(Rect r) + { + this.r = r; + } + } + + public class InputEvent + { + public bool HadMouseDownRepaint; + public bool HadMouseDown; + int _button; + bool _MouseClick; + bool _MouseLeftClickIgnoreLocked; + bool _MouseRightClickIgnoreLocked; + bool _MouseLeftClick; + bool _MouseRightClick; + + public bool is_alt_down; + + public bool is_drag_drop_event; + public bool is_drop_event; + + public Vector2 mouse_position; + public Vector2 screen_mouse_position; + + public void Update(bool isLockedMaterial) + { + Event e = Event.current; + _button = e.button; + _MouseClick = e.type == EventType.MouseDown && !isLockedMaterial; + _MouseLeftClick = _MouseClick && _button == 0; + _MouseRightClick = _MouseClick && _button == 1; + _MouseLeftClickIgnoreLocked = e.type == EventType.MouseDown && _button == 0; + _MouseRightClickIgnoreLocked = e.type == EventType.MouseDown && _button == 1; + if (_MouseClick) HadMouseDown = _MouseClick; + if (HadMouseDown && e.type == EventType.Repaint) + { + HadMouseDownRepaint = true; + HadMouseDown = false; + } + is_alt_down = e.alt; + mouse_position = e.mousePosition; + screen_mouse_position = GUIUtility.GUIToScreenPoint(e.mousePosition); + is_drop_event = e.type == EventType.DragPerform; + is_drag_drop_event = is_drop_event || e.type == EventType.DragUpdated; + } + + public void Use() + { + _MouseClick = false; + _MouseLeftClick = false; + _MouseRightClick = false; + Event.current.Use(); + } + + public bool LeftClick_IgnoreUnityUses + { + get { return _MouseLeftClick; } + } + + public bool RightClick_IgnoreUnityUses + { + get { return _MouseRightClick; } + } + + public bool LeftClick_IgnoreLocked + { + get { return _MouseLeftClickIgnoreLocked && Event.current.type != EventType.Used; } + } + + public bool RightClick_IgnoreLocked + { + get { return _MouseRightClickIgnoreLocked && Event.current.type != EventType.Used; } + } + + public bool LeftClick_IgnoreLockedAndUnityUses + { + get { return _MouseLeftClickIgnoreLocked; } + } + + public bool RightClick_IgnoreLockedAndUnityUses + { + get { return _MouseRightClickIgnoreLocked; } + } + + public bool Click + { + get { return _MouseClick && Event.current.type != EventType.Used; } + } + + public bool RightClick + { + get { return _MouseRightClick && Event.current.type != EventType.Used; } + } + + public bool LeftClick + { + get { return _MouseLeftClick && Event.current.type != EventType.Used; } + } + } + + public abstract class ShaderPart + { + public ShaderEditor ActiveShaderEditor; + + public int XOffset = 0; + public GUIContent Content; + public MaterialProperty MaterialProperty; + public string PropertyIdentifier; + public System.Object PropertyData = null; + public bool DoReferencePropertiesExist = false; + public bool DoesReferencePropertyExist = false; + public bool IsHidden = false; + public bool IsAnimatable = false; + public bool IsPreset = false; + public bool ExemptFromLockedDisabling = false; + public bool IsAnimated = false; + public bool IsRenaming = false; + + public BetterTooltips.Tooltip tooltip; + + public bool has_not_searchedFor = false; //used for property search + + protected string _optionsRaw; + private PropertyOptions _options; + private bool _doOptionsNeedInitilization = true; + public PropertyOptions Options + { + get + { + if (_options == null) + { + _options = PropertyOptions.Deserialize(_optionsRaw); + } + return _options; + } + } + + GenericMenu contextMenu; + + public ShaderPart(string propertyIdentifier, int xOffset, string displayName, string tooltip, ShaderEditor shaderEditor) + { + this._optionsRaw = null; + this.ActiveShaderEditor = shaderEditor; + this.PropertyIdentifier = propertyIdentifier; + this.XOffset = xOffset; + this.Content = new GUIContent(displayName); + this.tooltip = new BetterTooltips.Tooltip(tooltip); + this.IsPreset = shaderEditor.IsPresetEditor && Presets.IsPreset(shaderEditor.Materials[0], this); + } + + public ShaderPart(ShaderEditor shaderEditor, MaterialProperty prop, int xOffset, string displayName, string optionsRaw) + { + this._optionsRaw = optionsRaw; + this.ActiveShaderEditor = shaderEditor; + this.MaterialProperty = prop; + this.XOffset = xOffset; + this.Content = new GUIContent(displayName); + this.IsPreset = shaderEditor.IsPresetEditor && Presets.IsPreset(shaderEditor.Materials[0], this); + + if (prop == null) + return; + + this.ExemptFromLockedDisabling |= ShaderOptimizer.IsPropertyExcemptFromLocking(prop); + } + + protected virtual void InitOptions() + { + this.tooltip = new BetterTooltips.Tooltip(Options.tooltip); + this.DoReferencePropertiesExist = Options.reference_properties != null && Options.reference_properties.Length > 0; + this.DoesReferencePropertyExist = Options.reference_property != null; + this.XOffset += Options.offset; + } + + public void SetReferenceProperty(string s) + { + Options.reference_property = s; + this.DoesReferencePropertyExist = Options.reference_property != null; + } + + public void SetReferenceProperties(string[] properties) + { + Options.reference_properties = properties; + this.DoReferencePropertiesExist = Options.reference_properties != null && Options.reference_properties.Length > 0; + } + + public void SetTooltip(string tooltip) + { + this.tooltip.SetText(tooltip); + } + + public abstract void DrawInternal(GUIContent content, CRect rect = null, bool useEditorIndent = false, bool isInHeader = false); + public abstract void CopyFromMaterial(Material m, bool isTopCall = false); + public abstract void CopyToMaterial(Material m, bool isTopCall = false); + + public abstract void TransferFromMaterialAndGroup(Material m, ShaderPart g, bool isTopCall = false); + + bool hasAddedDisabledGroup = false; + public void Draw(CRect rect = null, GUIContent content = null, bool useEditorIndent = false, bool isInHeader = false) + { + if(_doOptionsNeedInitilization) + { + InitOptions(); + _doOptionsNeedInitilization = false; + } + + if (has_not_searchedFor) + return; + if (DrawingData.IsEnabled && Options.condition_enable != null) + { + hasAddedDisabledGroup = !Options.condition_enable.Test(); + if(hasAddedDisabledGroup) + { + DrawingData.IsEnabled = !hasAddedDisabledGroup; + EditorGUI.BeginDisabledGroup(true); + } + } + if (Options.condition_show.Test()) + { + PerformDraw(content, rect, useEditorIndent, isInHeader); + } + if (hasAddedDisabledGroup) + { + hasAddedDisabledGroup = false; + DrawingData.IsEnabled = true; + EditorGUI.EndDisabledGroup(); + } + } + + public virtual void HandleRightClickToggles(bool isInHeader) + { + if (this is ShaderGroup) return; + if (ShaderEditor.Input.RightClick_IgnoreLockedAndUnityUses && DrawingData.TooltipCheckRect.Contains(Event.current.mousePosition)) + { + //Context menu + //Show context menu, if not open. + //If locked material only show menu for animated materials. Only show data retieving options in locked state + if ( (!ShaderEditor.Active.IsLockedMaterial || IsAnimated)) { + contextMenu = new GenericMenu(); + if (IsAnimatable && !ShaderEditor.Active.IsLockedMaterial) + { + contextMenu.AddItem(new GUIContent("Animated (when locked)"), IsAnimated, () => { SetAnimated(!IsAnimated, false); }); + contextMenu.AddItem(new GUIContent("Renamed (when locked)"), IsAnimated && IsRenaming, () => { SetAnimated(true, !IsRenaming); }); + contextMenu.AddItem(new GUIContent("Locking Explanation"), false, () => { Application.OpenURL("https://www.youtube.com/watch?v=asWeDJb5LAo&ab_channel=poiyomi"); }); + contextMenu.AddSeparator(""); + } + if (ShaderEditor.Active.IsPresetEditor ) + { + contextMenu.AddItem(new GUIContent("Is part of preset"), IsPreset, ToggleIsPreset); + contextMenu.AddSeparator(""); + } + contextMenu.AddItem(new GUIContent("Copy Property Name"), false, () => { EditorGUIUtility.systemCopyBuffer = MaterialProperty.name; }); + contextMenu.AddItem(new GUIContent("Copy Animated Property Name"), false, () => { EditorGUIUtility.systemCopyBuffer = GetAnimatedPropertyName(); }); + contextMenu.AddItem(new GUIContent("Copy Animated Property Path"), false, CopyPropertyPath ); + contextMenu.AddItem(new GUIContent("Copy Property as Keyframe"), false, CopyPropertyAsKeyframe); + contextMenu.ShowAsContext(); + } + } + } + + void ToggleIsPreset() + { + IsPreset = !IsPreset; + if(MaterialProperty != null) Presets.SetProperty(ActiveShaderEditor.Materials[0], this, IsPreset); + ShaderEditor.RepaintActive(); + } + + void CopyPropertyPath() + { + string path = GetAnimatedPropertyName(); + Transform selected = Selection.activeTransform; + Transform root = selected; + while(root != null && root.GetComponent() == null) + root = root.parent; + if (selected != null && root != null && selected != root) + path = AnimationUtility.CalculateTransformPath(selected, root) + "/" + path; + EditorGUIUtility.systemCopyBuffer = path; + } + + string GetAnimatedPropertyName() + { + string propName = MaterialProperty.name; + if (IsRenaming && !ShaderEditor.Active.IsLockedMaterial) propName = propName + "_" + ShaderEditor.Active.RenamedPropertySuffix; + if (MaterialProperty.type == MaterialProperty.PropType.Texture) propName = propName + "_ST"; + return propName; + } + + void CopyPropertyAsKeyframe() + { + string path = ""; + Transform selected = Selection.activeTransform; + Transform root = selected; + while (root != null && root.GetComponent() == null) + root = root.parent; + if (selected != null && root != null && selected != root) + path = AnimationUtility.CalculateTransformPath(selected, root); + if(selected == null && root == null) + return; + + Type rendererType = typeof(Renderer); + if (selected.GetComponent()) rendererType = typeof(SkinnedMeshRenderer); + if (selected.GetComponent()) rendererType = typeof(MeshRenderer); + + Type animationStateType = typeof(AnimationUtility).Assembly.GetType("UnityEditorInternal.AnimationWindowState"); + Type animationKeyframeType = typeof(AnimationUtility).Assembly.GetType("UnityEditorInternal.AnimationWindowKeyframe"); + Type animationCurveType = typeof(AnimationUtility).Assembly.GetType("UnityEditorInternal.AnimationWindowCurve"); + + FieldInfo clipboardField = animationStateType.GetField("s_KeyframeClipboard", BindingFlags.NonPublic | BindingFlags.Static); + + Type keyframeListType = typeof(List<>).MakeGenericType(animationKeyframeType); + IList keyframeList = (IList)Activator.CreateInstance(keyframeListType); + + AnimationClip clip = new AnimationClip(); + + string propertyname = "material." + GetAnimatedPropertyName(); + if (MaterialProperty.type == MaterialProperty.PropType.Float || MaterialProperty.type == MaterialProperty.PropType.Range) + { + clip.SetCurve(path, rendererType, propertyname, new AnimationCurve(new Keyframe(0, MaterialProperty.floatValue))); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, "", rendererType)); + } + else if(MaterialProperty.type == MaterialProperty.PropType.Color) + { + clip.SetCurve(path, rendererType, propertyname + ".r", new AnimationCurve(new Keyframe(0, MaterialProperty.colorValue.r))); + clip.SetCurve(path, rendererType, propertyname + ".g", new AnimationCurve(new Keyframe(0, MaterialProperty.colorValue.g))); + clip.SetCurve(path, rendererType, propertyname + ".b", new AnimationCurve(new Keyframe(0, MaterialProperty.colorValue.b))); + clip.SetCurve(path, rendererType, propertyname + ".a", new AnimationCurve(new Keyframe(0, MaterialProperty.colorValue.a))); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".r", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".g", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".b", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".a", rendererType)); + }else if(MaterialProperty.type == MaterialProperty.PropType.Vector) + { + clip.SetCurve(path, rendererType, propertyname + ".x", new AnimationCurve(new Keyframe(0, MaterialProperty.vectorValue.x))); + clip.SetCurve(path, rendererType, propertyname + ".y", new AnimationCurve(new Keyframe(0, MaterialProperty.vectorValue.y))); + clip.SetCurve(path, rendererType, propertyname + ".z", new AnimationCurve(new Keyframe(0, MaterialProperty.vectorValue.z))); + clip.SetCurve(path, rendererType, propertyname + ".w", new AnimationCurve(new Keyframe(0, MaterialProperty.vectorValue.w))); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".x", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".y", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".z", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".w", rendererType)); + }else if(MaterialProperty.type == MaterialProperty.PropType.Texture) + { + clip.SetCurve(path, rendererType, propertyname + ".x", new AnimationCurve(new Keyframe(0, MaterialProperty.textureScaleAndOffset.x))); + clip.SetCurve(path, rendererType, propertyname + ".y", new AnimationCurve(new Keyframe(0, MaterialProperty.textureScaleAndOffset.y))); + clip.SetCurve(path, rendererType, propertyname + ".z", new AnimationCurve(new Keyframe(0, MaterialProperty.textureScaleAndOffset.z))); + clip.SetCurve(path, rendererType, propertyname + ".w", new AnimationCurve(new Keyframe(0, MaterialProperty.textureScaleAndOffset.w))); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".x", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".y", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".z", rendererType)); + keyframeList.Add(ClipToKeyFrame(animationCurveType, clip, path, ".w", rendererType)); + } + clipboardField.SetValue(null, keyframeList); + } + + object ClipToKeyFrame(Type animationCurveType, AnimationClip clip, string path, string propertyPostFix, Type rendererType) + { + FieldInfo curvesField = animationCurveType.GetField("m_Keyframes", BindingFlags.Instance | BindingFlags.Public); + + object windowCurve = Activator.CreateInstance(animationCurveType, clip, + EditorCurveBinding.FloatCurve(path, rendererType, "material." + GetAnimatedPropertyName() + propertyPostFix), typeof(float)); + IEnumerator enumerator = (curvesField.GetValue(windowCurve) as IList).GetEnumerator(); + enumerator.MoveNext(); + return enumerator.Current; + } + + public void SetAnimated(bool animated, bool renamed) + { + if (IsAnimated == animated && IsRenaming == renamed) return; + IsAnimated = animated; + IsRenaming = renamed; + ShaderOptimizer.SetAnimatedTag(MaterialProperty, IsAnimated ? (IsRenaming ? "2" : "1") : ""); + } + + private void PerformDraw(GUIContent content, CRect rect, bool useEditorIndent, bool isInHeader = false) + { + if (content == null) + content = this.Content; + EditorGUI.BeginChangeCheck(); + DrawInternal(content, rect, useEditorIndent, isInHeader); + + if(this is TextureProperty == false) + { + DrawingData.TooltipCheckRect = DrawingData.LastGuiObjectRect; + DrawingData.IconsPositioningHeight = DrawingData.LastGuiObjectRect.y + DrawingData.LastGuiObjectRect.height - 14; + } + DrawingData.TooltipCheckRect.width = EditorGUIUtility.labelWidth; + + HandleRightClickToggles(isInHeader); + + if (EditorGUI.EndChangeCheck()) + { + OnPropertyValueChanged(); + ExecuteOnValueActions(ShaderEditor.Active.Materials); + //Check if property is being animated + if (this is ShaderProperty && ActiveShaderEditor.ActiveRenderer != null && ActiveShaderEditor.IsInAnimationMode && IsAnimatable && !IsAnimated) + { + if (MaterialProperty.type == MaterialProperty.PropType.Texture ? + AnimationMode.IsPropertyAnimated(ActiveShaderEditor.ActiveRenderer, "material." + MaterialProperty.name + "_ST.x" ) : + AnimationMode.IsPropertyAnimated(ActiveShaderEditor.ActiveRenderer, "material." + MaterialProperty.name)) + SetAnimated(true, false); + } + } + + if (IsAnimatable && IsAnimated) DrawLockedAnimated(); + if (IsPreset) DrawPresetProperty(); + + tooltip.ConditionalDraw(DrawingData.TooltipCheckRect); + + //Click testing + if(Event.current.type == EventType.MouseDown && DrawingData.LastGuiObjectRect.Contains(ShaderEditor.Input.mouse_position)) + { + if ((ShaderEditor.Input.is_alt_down && Options.altClick != null)) Options.altClick.Perform(ShaderEditor.Active.Materials); + else if (Options.onClick != null) Options.onClick.Perform(ShaderEditor.Active.Materials); + } + } + + protected virtual void OnPropertyValueChanged() + { + + } + + private void DrawLockedAnimated() + { + Rect r = new Rect(14, DrawingData.IconsPositioningHeight, 16, 16); + //GUI.DrawTexture(r, is_renaming ? Styles.texture_animated_renamed : Styles.texture_animated, ScaleMode.StretchToFill, true); + if (IsRenaming) GUI.Label(r, "RA", Styles.animatedIndicatorStyle); + else GUI.Label(r, "A", Styles.animatedIndicatorStyle); + } + + private void DrawPresetProperty() + { + Rect r = new Rect(3, DrawingData.IconsPositioningHeight, 8, 16); + //GUI.DrawTexture(r, Styles.texture_preset, ScaleMode.StretchToFill, true); + GUI.Label(r, "P", Styles.presetIndicatorStyle); + } + + protected void ExecuteOnValueActions(Material[] targets) + { + if (Options.on_value_actions != null) + foreach (PropertyValueAction action in Options.on_value_actions) + { + action.Execute(MaterialProperty, targets); + } + } + + public abstract void FindUnusedTextures(List unusedList, bool isEnabled); + } + + public class ShaderGroup : ShaderPart + { + public List parts = new List(); + + public ShaderGroup(ShaderEditor shaderEditor) : base(null, 0, "", null, shaderEditor) + { + + } + + public ShaderGroup(ShaderEditor shaderEditor, string optionsRaw) : base(null, 0, "", null, shaderEditor) + { + this._optionsRaw = optionsRaw; + } + + public ShaderGroup(ShaderEditor shaderEditor, MaterialProperty prop, MaterialEditor materialEditor, string displayName, int xOffset, string optionsRaw) : base(shaderEditor, prop, xOffset, displayName, optionsRaw) + { + + } + + public void addPart(ShaderPart part) + { + parts.Add(part); + } + + public override void CopyFromMaterial(Material m, bool isTopCall = false) + { + if (Options.reference_property != null) + ActiveShaderEditor.PropertyDictionary[Options.reference_property].CopyFromMaterial(m); + foreach (ShaderPart p in parts) + p.CopyFromMaterial(m); + if (isTopCall) ActiveShaderEditor.ApplyDrawers(); + } + + public override void CopyToMaterial(Material m, bool isTopCall = false) + { + if (Options.reference_property != null) + ActiveShaderEditor.PropertyDictionary[Options.reference_property].CopyToMaterial(m); + foreach (ShaderPart p in parts) + p.CopyToMaterial(m); + if (isTopCall) MaterialEditor.ApplyMaterialPropertyDrawers(m); + } + + public override void DrawInternal(GUIContent content, CRect rect = null, bool useEditorIndent = false, bool isInHeader = false) + { + if(Options.draw_border) + { + bool has_header = string.IsNullOrWhiteSpace(this.Content.text) == false; + Rect border = EditorGUILayout.BeginVertical(); + GUILayoutUtility.GetRect(0, 5 + (has_header ? 20 : 0)); + border = new RectOffset(this.XOffset * -15 - 12, 3, -2, -2).Add(border); + Vector4 borderWidths = new Vector4(3, (has_header ? 22 : 3), 3, 3); + GUI.DrawTexture(border, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, Styles.COLOR_BACKGROUND_1, borderWidths, 10); + if(has_header) + { + Rect header = new Rect(border.x + 16, border.y, border.width - 16, 22); + GUI.Label(header, this.Content, EditorStyles.boldLabel); + } + } + foreach (ShaderPart part in parts) + { + part.Draw(); + } + if (Options.draw_border) + { + GUILayoutUtility.GetRect(0, 5); + EditorGUILayout.EndVertical(); + } + } + + public override void TransferFromMaterialAndGroup(Material m, ShaderPart p, bool isTopCall = false) + { + if (p is ShaderGroup == false) return; + ShaderGroup group = p as ShaderGroup; + if (Options.reference_property != null && group.Options.reference_property != null) + ActiveShaderEditor.PropertyDictionary[Options.reference_property].TransferFromMaterialAndGroup(m, group.ActiveShaderEditor.PropertyDictionary[group.Options.reference_property]); + for(int i=0;i unusedList, bool isEnabled) + { + if (isEnabled && Options.condition_enable != null) + { + isEnabled &= Options.condition_enable.Test(); + } + foreach (ShaderPart p in (this as ShaderGroup).parts) + p.FindUnusedTextures(unusedList, isEnabled); + } + } + + public class ShaderHeader : ShaderGroup + { + private ThryHeaderHandler _headerDrawer; + + public ShaderHeader(ShaderEditor shaderEditor) : base(shaderEditor) + { + this._headerDrawer = new ThryHeaderHandler(); + } + + public ShaderHeader(ShaderEditor shaderEditor, MaterialProperty prop, MaterialEditor materialEditor, string displayName, int xOffset, string optionsRaw) : base(shaderEditor, prop, materialEditor, displayName, xOffset, optionsRaw) + { + this._headerDrawer = new ThryHeaderHandler(); + this._headerDrawer.xOffset = xOffset; + } + + public string GetEndProperty() + { + return _headerDrawer.GetEndProperty(); + } + + public override void DrawInternal(GUIContent content, CRect rect = null, bool useEditorIndent = false, bool isInHeader = false) + { + ActiveShaderEditor.CurrentProperty = this; + EditorGUI.BeginChangeCheck(); + Rect position = GUILayoutUtility.GetRect(content, Styles.dropDownHeader); + _headerDrawer.OnGUI(position, this.MaterialProperty, content, ActiveShaderEditor.Editor); + Rect headerRect = DrawingData.LastGuiObjectHeaderRect; + if (this._headerDrawer.IsExpanded) + { + EditorGUILayout.Space(); + EditorGUI.BeginDisabledGroup(_headerDrawer.DisableContent); + foreach (ShaderPart part in parts) + { + part.Draw(); + } + EditorGUI.EndDisabledGroup(); + EditorGUILayout.Space(); + } + if (EditorGUI.EndChangeCheck()) + HandleLinkedMaterials(); + DrawingData.LastGuiObjectHeaderRect = headerRect; + DrawingData.LastGuiObjectRect = headerRect; + } + + private void HandleLinkedMaterials() + { + List linked_materials = MaterialLinker.GetLinked(MaterialProperty); + if (linked_materials != null) + foreach (Material m in linked_materials) + this.CopyToMaterial(m); + } + + public override void FindUnusedTextures(List unusedList, bool isEnabled) + { + if (isEnabled && Options.condition_enable != null) + { + isEnabled &= Options.condition_enable.Test(); + } + isEnabled &= !_headerDrawer.DisableContent; + foreach (ShaderPart p in (this as ShaderGroup).parts) + p.FindUnusedTextures(unusedList, isEnabled); + } + } + + public class ShaderProperty : ShaderPart + { + public float setFloat; + public bool updateFloat; + + public bool doCustomDrawLogic = false; + public bool doForceIntoOneLine = false; + public bool doDrawTwoFields = false; + + //Done for e.g. Vectors cause they draw in 2 lines for some fucking reasons + public bool doCustomHeightOffset = false; + public float customHeightOffset = 0; + + private int property_index = 0; + + public string keyword; + + protected MaterialPropertyDrawer[] _customDecorators; + protected Rect[] _customDecoratorRects; + protected bool _hasDrawer = false; + + bool _needsDrawerInitlization = true; + + public ShaderProperty(ShaderEditor shaderEditor, string propertyIdentifier, int xOffset, string displayName, string tooltip) : base(propertyIdentifier, xOffset, displayName, tooltip, shaderEditor) + { + + } + + public ShaderProperty(ShaderEditor shaderEditor, MaterialProperty materialProperty, string displayName, int xOffset, string optionsRaw, bool forceOneLine, int property_index) : base(shaderEditor, materialProperty, xOffset, displayName, optionsRaw) + { + this.doCustomDrawLogic = false; + this.doForceIntoOneLine = forceOneLine; + + this.property_index = property_index; + } + + protected override void InitOptions() + { + base.InitOptions(); + this.doDrawTwoFields = Options.reference_property != null; + } + + public override void CopyFromMaterial(Material m, bool isTopCall = false) + { + MaterialHelper.CopyPropertyValueFromMaterial(MaterialProperty, m); + if (keyword != null) SetKeyword(ActiveShaderEditor.Materials, m.GetFloat(MaterialProperty.name)==1); + if (IsAnimatable) + { + ShaderOptimizer.CopyAnimatedTagFromMaterial(m, MaterialProperty); + } + this.IsAnimated = IsAnimatable && ShaderOptimizer.GetAnimatedTag(MaterialProperty) != ""; + this.IsRenaming = IsAnimatable && ShaderOptimizer.GetAnimatedTag(MaterialProperty) == "2"; + + ExecuteOnValueActions(ShaderEditor.Active.Materials); + + if (isTopCall) ActiveShaderEditor.ApplyDrawers(); + } + + public override void CopyToMaterial(Material m, bool isTopCall = false) + { + MaterialHelper.CopyPropertyValueToMaterial(MaterialProperty, m); + if (keyword != null) SetKeyword(m, MaterialProperty.floatValue == 1); + if (IsAnimatable) + ShaderOptimizer.CopyAnimatedTagToMaterials(new Material[] { m }, MaterialProperty); + + ExecuteOnValueActions(new Material[] { m }); + + if (isTopCall) MaterialEditor.ApplyMaterialPropertyDrawers(m); + } + + private void SetKeyword(Material[] materials, bool enabled) + { + if (enabled) foreach (Material m in materials) m.EnableKeyword(keyword); + else foreach (Material m in materials) m.DisableKeyword(keyword); + } + + private void SetKeyword(Material m, bool enabled) + { + if (enabled) m.EnableKeyword(keyword); + else m.DisableKeyword(keyword); + } + + public void UpdateKeywordFromValue() + { + if (keyword != null) SetKeyword(ActiveShaderEditor.Materials, MaterialProperty.floatValue == 1); + } + + void InitializeDrawers() + { + DrawingData.ResetLastDrawerData(); + ShaderEditor.Active.Editor.GetPropertyHeight(MaterialProperty, MaterialProperty.displayName); + + this.IsAnimatable = !DrawingData.LastPropertyDoesntAllowAnimation; + this._hasDrawer = DrawingData.LastPropertyUsedCustomDrawer; + + if (MaterialProperty.type == MaterialProperty.PropType.Vector && doForceIntoOneLine == false) + { + this.doCustomHeightOffset = !DrawingData.LastPropertyUsedCustomDrawer; + this.customHeightOffset = -EditorGUIUtility.singleLineHeight; + } + if(DrawingData.LastPropertyDecorators.Count > 0) + { + _customDecorators = DrawingData.LastPropertyDecorators.ToArray(); + _customDecoratorRects = new Rect[DrawingData.LastPropertyDecorators.Count]; + } + + // Animatable Stuff + bool propHasDuplicate = ShaderEditor.Active.GetMaterialProperty(MaterialProperty.name + "_" + ShaderEditor.Active.RenamedPropertySuffix) != null; + string tag = null; + //If prop is og, but is duplicated (locked) dont have it animateable + if (propHasDuplicate) + { + this.IsAnimatable = false; + } + else + { + //if prop is a duplicated or renamed get og property to check for animted status + if (MaterialProperty.name.Contains(ShaderEditor.Active.RenamedPropertySuffix)) + { + string ogName = MaterialProperty.name.Substring(0, MaterialProperty.name.Length - ShaderEditor.Active.RenamedPropertySuffix.Length - 1); + tag = ShaderOptimizer.GetAnimatedTag(MaterialProperty.targets[0] as Material, ogName); + } + else + { + tag = ShaderOptimizer.GetAnimatedTag(MaterialProperty); + } + } + + this.IsAnimated = IsAnimatable && tag != ""; + this.IsRenaming = IsAnimatable && tag == "2"; + } + + public override void DrawInternal(GUIContent content, CRect rect = null, bool useEditorIndent = false, bool isInHeader = false) + { + ActiveShaderEditor.CurrentProperty = this; + this.MaterialProperty = ActiveShaderEditor.Properties[property_index]; + + if(_needsDrawerInitlization) + { + InitializeDrawers(); + _needsDrawerInitlization = false; + } + + PreDraw(); + if (ActiveShaderEditor.IsLockedMaterial) + EditorGUI.BeginDisabledGroup(!(IsAnimatable && (IsAnimated || IsRenaming)) && !ExemptFromLockedDisabling); + int oldIndentLevel = EditorGUI.indentLevel; + if (!useEditorIndent) + EditorGUI.indentLevel = XOffset + 1; + + if(_customDecorators != null && doCustomDrawLogic) + { + for(int i= 0;i<_customDecorators.Length;i++) + { + _customDecoratorRects[i] = EditorGUILayout.GetControlRect(false, GUILayout.Height(_customDecorators[i].GetPropertyHeight(MaterialProperty, content.text, ActiveShaderEditor.Editor))); + } + } + + if (doCustomDrawLogic) + { + DrawDefault(); + } + else if (doDrawTwoFields) + { + Rect r = GUILayoutUtility.GetRect(content, Styles.vectorPropertyStyle); + float labelWidth = (r.width - EditorGUIUtility.labelWidth) / 2; ; + r.width -= labelWidth; + ActiveShaderEditor.Editor.ShaderProperty(r, this.MaterialProperty, content); + + r.x += r.width; + r.width = labelWidth; + float prevLabelW = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 0; + ActiveShaderEditor.PropertyDictionary[Options.reference_property].Draw(new CRect(r), new GUIContent()); + EditorGUIUtility.labelWidth = prevLabelW; + } + else if (doForceIntoOneLine) + { + ActiveShaderEditor.Editor.ShaderProperty(GUILayoutUtility.GetRect(content, Styles.vectorPropertyStyle), this.MaterialProperty, content); + }else if (doCustomHeightOffset) + { + ActiveShaderEditor.Editor.ShaderProperty( + GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, ActiveShaderEditor.Editor.GetPropertyHeight(this.MaterialProperty, content.text) + customHeightOffset) + , this.MaterialProperty, content); + } + else if (rect != null) + { + // Custom Drawing for Range, because it doesnt draw correctly if inside the big texture property + if(!_hasDrawer && MaterialProperty.type == MaterialProperty.PropType.Range) + { + MaterialProperty.floatValue = EditorGUI.Slider(rect.r, content, MaterialProperty.floatValue, 0, MaterialProperty.rangeLimits.y); + }else + { + ActiveShaderEditor.Editor.ShaderProperty(rect.r, this.MaterialProperty, content); + } + } + else + { + ActiveShaderEditor.Editor.ShaderProperty(this.MaterialProperty, content); + } + + if(_customDecorators != null && doCustomDrawLogic) + { + for(int i= 0;i<_customDecorators.Length;i++) + { + _customDecorators[i].OnGUI(_customDecoratorRects[i], MaterialProperty, content, ShaderEditor.Active.Editor); + } + } + + EditorGUI.indentLevel = oldIndentLevel; + if (rect == null) DrawingData.LastGuiObjectRect = GUILayoutUtility.GetLastRect(); + else DrawingData.LastGuiObjectRect = rect.r; + if (ActiveShaderEditor.IsLockedMaterial) + EditorGUI.EndDisabledGroup(); + } + + public virtual void PreDraw() { } + + public virtual void DrawDefault() { } + + public override void TransferFromMaterialAndGroup(Material m, ShaderPart p, bool isTopCall = false) + { + if (MaterialProperty.type != p.MaterialProperty.type) return; + MaterialHelper.CopyMaterialValueFromProperty(MaterialProperty, p.MaterialProperty); + if (keyword != null) SetKeyword(ActiveShaderEditor.Materials, m.GetFloat(p.MaterialProperty.name) == 1); + if (IsAnimatable && p.IsAnimatable) + ShaderOptimizer.CopyAnimatedTagFromProperty(p.MaterialProperty, MaterialProperty); + this.IsAnimated = IsAnimatable && ShaderOptimizer.GetAnimatedTag(MaterialProperty) != ""; + this.IsRenaming = IsAnimatable && ShaderOptimizer.GetAnimatedTag(MaterialProperty) == "2"; + + if (isTopCall) ActiveShaderEditor.ApplyDrawers(); + } + + public override void FindUnusedTextures(List unusedList, bool isEnabled) + { + if (isEnabled && Options.condition_enable != null) + { + isEnabled &= Options.condition_enable.Test(); + } + if (!isEnabled && MaterialProperty != null && MaterialProperty.type == MaterialProperty.PropType.Texture && MaterialProperty.textureValue != null) + { + unusedList.Add(MaterialProperty.name); + } + } + } + + public class TextureProperty : ShaderProperty + { + public bool showFoldoutProperties = false; + public bool hasFoldoutProperties = false; + public bool hasScaleOffset = false; + public string VRAMString = ""; + bool _isVRAMDirty = true; + + public TextureProperty(ShaderEditor shaderEditor, MaterialProperty materialProperty, string displayName, int xOffset, string optionsRaw, bool hasScaleOffset, bool forceThryUI, int property_index) : base(shaderEditor, materialProperty, displayName, xOffset, optionsRaw, false, property_index) + { + doCustomDrawLogic = forceThryUI; + this.hasScaleOffset = hasScaleOffset; + } + + protected override void InitOptions() + { + base.InitOptions(); + this.hasFoldoutProperties = hasScaleOffset || DoReferencePropertiesExist; + } + + void UpdateVRAM() + { + if (MaterialProperty.textureValue != null) + { + var details = TextureHelper.VRAM.CalcSize(MaterialProperty.textureValue); + this.VRAMString = $"{TextureHelper.VRAM.ToByteString(details.size)}"; + } + else + { + VRAMString = null; + } + } + + protected override void OnPropertyValueChanged() + { + base.OnPropertyValueChanged(); + _isVRAMDirty = true; + } + + public override void PreDraw() + { + DrawingData.CurrentTextureProperty = this; + this.doCustomDrawLogic = !this._hasDrawer; + if (this._isVRAMDirty) + { + UpdateVRAM(); + _isVRAMDirty = false; + } + } + + public override void DrawDefault() + { + Rect pos = GUILayoutUtility.GetRect(Content, Styles.vectorPropertyStyle); + GuiHelper.ConfigTextureProperty(pos, MaterialProperty, Content, ActiveShaderEditor.Editor, hasFoldoutProperties); + DrawingData.LastGuiObjectRect = pos; + } + + public override void CopyFromMaterial(Material m, bool isTopCall = false) + { + MaterialHelper.CopyPropertyValueFromMaterial(MaterialProperty, m); + CopyReferencePropertiesFromMaterial(m); + + if (isTopCall) ActiveShaderEditor.ApplyDrawers(); + } + + public override void CopyToMaterial(Material m, bool isTopCall = false) + { + MaterialHelper.CopyPropertyValueToMaterial(MaterialProperty, m); + CopyReferencePropertiesToMaterial(m); + + if (isTopCall) MaterialEditor.ApplyMaterialPropertyDrawers(m); + } + + public override void TransferFromMaterialAndGroup(Material m, ShaderPart p, bool isTopCall = false) + { + if (MaterialProperty.type != p.MaterialProperty.type) return; + MaterialHelper.CopyMaterialValueFromProperty(MaterialProperty, p.MaterialProperty); + TransferReferencePropertiesToMaterial(m, p); + } + private void TransferReferencePropertiesToMaterial(Material target, ShaderPart p) + { + if (p.Options.reference_properties == null || this.Options.reference_properties == null) return; + for (int i = 0; i < p.Options.reference_properties.Length && i < Options.reference_properties.Length; i++) + { + if (ActiveShaderEditor.PropertyDictionary.ContainsKey(this.Options.reference_properties[i]) == false) continue; + + ShaderProperty targetP = ActiveShaderEditor.PropertyDictionary[this.Options.reference_properties[i]]; + ShaderProperty sourceP = p.ActiveShaderEditor.PropertyDictionary[p.Options.reference_properties[i]]; + MaterialHelper.CopyMaterialValueFromProperty(targetP.MaterialProperty, sourceP.MaterialProperty); + } + } + + private void CopyReferencePropertiesToMaterial(Material target) + { + if (Options.reference_properties != null) + foreach (string r_property in Options.reference_properties) + { + ShaderProperty property = ActiveShaderEditor.PropertyDictionary[r_property]; + MaterialHelper.CopyPropertyValueToMaterial(property.MaterialProperty, target); + } + } + + private void CopyReferencePropertiesFromMaterial(Material source) + { + if (Options.reference_properties != null) + foreach (string r_property in Options.reference_properties) + { + ShaderProperty property = ActiveShaderEditor.PropertyDictionary[r_property]; + MaterialHelper.CopyPropertyValueFromMaterial(property.MaterialProperty, source); + } + } + } + + public class ShaderHeaderProperty : ShaderPart + { + public ShaderHeaderProperty(ShaderEditor shaderEditor, MaterialProperty materialProperty, string displayName, int xOffset, string optionsRaw, bool forceOneLine) : base(shaderEditor, materialProperty, xOffset, displayName, optionsRaw) + { + // guid is defined as + if(displayName.Contains("", start); + string guid = displayName.Substring(start + 6, end - start - 6); + string path = AssetDatabase.GUIDToAssetPath(guid); + string replacement = ""; + if (path != null && System.IO.File.Exists(path)) + { + replacement = System.IO.File.ReadAllText(path); + } + Content.text = displayName.Replace($"", replacement); + } + } + + public override void HandleRightClickToggles(bool isInHeader) + { + + } + + public override void DrawInternal(GUIContent content, CRect rect = null, bool useEditorIndent = false, bool isInHeader = false) + { + if (rect == null) + { + if (Options.texture != null && Options.texture.name != null) + { + //is texutre draw + content = new GUIContent(Options.texture.loaded_texture, content.tooltip); + int height = Options.texture.height; + int width = (int)((float)Options.texture.loaded_texture.width / Options.texture.loaded_texture.height * height); + Rect control = EditorGUILayout.GetControlRect(false, height-18); + Rect r = new Rect((control.width-width)/2,control.y,width, height); + GUI.DrawTexture(r, Options.texture.loaded_texture); + } + } + else + { + //is text draw + Rect headerrect = new Rect(0, rect.r.y, rect.r.width, 18); + EditorGUI.LabelField(headerrect, "" + this.Content.text + "", Styles.masterLabel); + DrawingData.LastGuiObjectRect = headerrect; + } + } + + public override void CopyFromMaterial(Material m, bool isTopCall = false) + { + throw new System.NotImplementedException(); + } + + public override void CopyToMaterial(Material m, bool isTopCall = false) + { + throw new System.NotImplementedException(); + } + + public override void TransferFromMaterialAndGroup(Material m, ShaderPart p, bool isTopCall = false) + { + throw new System.NotImplementedException(); + } + + public override void FindUnusedTextures(List unusedList, bool isEnabled) + { + } + } + + public class RenderQueueProperty : ShaderProperty + { + public RenderQueueProperty(ShaderEditor shaderEditor) : base(shaderEditor, "RenderQueue", 0, "", "Change the Queue at which the material is rendered.") + { + doCustomDrawLogic = true; + } + + public override void DrawDefault() + { + ActiveShaderEditor.Editor.RenderQueueField(); + } + + public override void CopyFromMaterial(Material sourceM, bool isTopCall = false) + { + foreach (Material m in ActiveShaderEditor.Materials) m.renderQueue = sourceM.renderQueue; + } + public override void CopyToMaterial(Material targetM, bool isTopCall = false) + { + targetM.renderQueue = ActiveShaderEditor.Materials[0].renderQueue; + } + } + public class VRCFallbackProperty : ShaderProperty + { + static string[] s_fallbackShaderTypes = { "Standard", "Toon", "Unlit", "VertexLit", "Particle", "Sprite", "Matcap", "MobileToon" }; + static string[] s_fallbackRenderTypes = { "Opaque", "Cutout", "Transparent", "Fade" }; + static string[] s_fallbackRenderTypesValues = { "", "Cutout", "Transparent", "Fade" }; + static string[] s_fallbackCullTypes = { "OneSided", "DoubleSided" }; + static string[] s_fallbackCullTypesValues = { "", "DoubleSided" }; + static string[] s_fallbackNoTypes = { "None", "Hidden" }; + static string[] s_fallbackNoTypesValues = { "", "Hidden" }; + static string[] s_vRCFallbackOptionsPopup = s_fallbackNoTypes.Union(s_fallbackShaderTypes.SelectMany(s => s_fallbackRenderTypes.SelectMany(r => s_fallbackCullTypes.Select(c => r + "/" + c).Select(rc => s + "/" + rc)))).ToArray(); + static string[] s_vRCFallbackOptionsValues = s_fallbackNoTypes.Union(s_fallbackShaderTypes.SelectMany(s => s_fallbackRenderTypesValues.SelectMany(r => s_fallbackCullTypesValues.Select(c => r + c).Select(rc => s + rc)))).ToArray(); + + public VRCFallbackProperty(ShaderEditor shaderEditor) : base(shaderEditor, "VRCFallback", 0, "", "Select the shader VRChat should use when your shaders are being hidden.") + { + doCustomDrawLogic = true; + } + + public override void DrawDefault() + { + string current = ActiveShaderEditor.Materials[0].GetTag("VRCFallback", false, "None"); + EditorGUI.BeginChangeCheck(); + int selected = EditorGUILayout.Popup("VRChat Fallback Shader", s_vRCFallbackOptionsValues.Select((f, i) => (f, i)).FirstOrDefault(f => f.f == current).i, s_vRCFallbackOptionsPopup); + if (EditorGUI.EndChangeCheck()) + ActiveShaderEditor.Materials[0].SetOverrideTag("VRCFallback", s_vRCFallbackOptionsValues[selected]); + } + + public override void CopyFromMaterial(Material sourceM, bool isTopCall = false) + { + string value = sourceM.GetTag("VRCFallback", false, "None"); + foreach (Material m in ActiveShaderEditor.Materials) m.SetOverrideTag("VRCFallback", value); + } + public override void CopyToMaterial(Material targetM, bool isTopCall = false) + { + string value = ActiveShaderEditor.Materials[0].GetTag("VRCFallback", false, "None"); + targetM.SetOverrideTag("VRCFallback", value); + } + } + public class InstancingProperty : ShaderProperty + { + public InstancingProperty(ShaderEditor shaderEditor, MaterialProperty materialProperty, string displayName, int xOffset, string optionsRaw, bool forceOneLine) : base(shaderEditor, materialProperty, displayName, xOffset, optionsRaw, forceOneLine, 0) + { + doCustomDrawLogic = true; + } + + public override void DrawDefault() + { + ActiveShaderEditor.Editor.EnableInstancingField(); + } + } + public class GIProperty : ShaderProperty + { + public GIProperty(ShaderEditor shaderEditor, MaterialProperty materialProperty, string displayName, int xOffset, string optionsRaw, bool forceOneLine) : base(shaderEditor, materialProperty, displayName, xOffset, optionsRaw, forceOneLine, 0) + { + doCustomDrawLogic = true; + } + + public override void DrawDefault() + { + LightmapEmissionFlagsProperty(XOffset, false); + } + + public static readonly GUIContent lightmapEmissiveLabel = EditorGUIUtility.TrTextContent("Global Illumination", "Controls if the emission is baked or realtime.\n\nBaked only has effect in scenes where baked global illumination is enabled.\n\nRealtime uses realtime global illumination if enabled in the scene. Otherwise the emission won't light up other objects."); + public static GUIContent[] lightmapEmissiveStrings = { EditorGUIUtility.TrTextContent("Realtime"), EditorGUIUtility.TrTextContent("Baked"), EditorGUIUtility.TrTextContent("None") }; + public static int[] lightmapEmissiveValues = { (int)MaterialGlobalIlluminationFlags.RealtimeEmissive, (int)MaterialGlobalIlluminationFlags.BakedEmissive, (int)MaterialGlobalIlluminationFlags.None }; + + public static void FixupEmissiveFlag(Material mat) + { + if (mat == null) + throw new System.ArgumentNullException("mat"); + + mat.globalIlluminationFlags = FixupEmissiveFlag(mat.GetColor("_EmissionColor"), mat.globalIlluminationFlags); + } + + public static MaterialGlobalIlluminationFlags FixupEmissiveFlag(Color col, MaterialGlobalIlluminationFlags flags) + { + if ((flags & MaterialGlobalIlluminationFlags.BakedEmissive) != 0 && col.maxColorComponent == 0.0f) // flag black baked + flags |= MaterialGlobalIlluminationFlags.EmissiveIsBlack; + else if (flags != MaterialGlobalIlluminationFlags.EmissiveIsBlack) // clear baked flag on everything else, unless it's explicity disabled + flags &= MaterialGlobalIlluminationFlags.AnyEmissive; + return flags; + } + + public void LightmapEmissionFlagsProperty(int indent, bool enabled) + { + LightmapEmissionFlagsProperty(indent, enabled, false); + } + + public void LightmapEmissionFlagsProperty(int indent, bool enabled, bool ignoreEmissionColor) + { + // Calculate isMixed + MaterialGlobalIlluminationFlags any_em = MaterialGlobalIlluminationFlags.AnyEmissive; + MaterialGlobalIlluminationFlags giFlags = ActiveShaderEditor.Materials[0].globalIlluminationFlags & any_em; + bool isMixed = false; + for (int i = 1; i < ActiveShaderEditor.Materials.Length; i++) + { + if((ActiveShaderEditor.Materials[i].globalIlluminationFlags & any_em) != giFlags) + { + isMixed = true; + break; + } + } + + EditorGUI.BeginChangeCheck(); + + // Show popup + EditorGUI.showMixedValue = isMixed; + giFlags = (MaterialGlobalIlluminationFlags)EditorGUILayout.IntPopup(lightmapEmissiveLabel, (int)giFlags, lightmapEmissiveStrings, lightmapEmissiveValues); + EditorGUI.showMixedValue = false; + + // Apply flags. But only the part that this tool modifies (RealtimeEmissive, BakedEmissive, None) + bool applyFlags = EditorGUI.EndChangeCheck(); + foreach (Material mat in ActiveShaderEditor.Materials) + { + mat.globalIlluminationFlags = applyFlags ? giFlags : mat.globalIlluminationFlags; + if (!ignoreEmissionColor) + { + FixupEmissiveFlag(mat); + } + } + } + } + public class DSGIProperty : ShaderProperty + { + public DSGIProperty(ShaderEditor shaderEditor, MaterialProperty materialProperty, string displayName, int xOffset, string optionsRaw, bool forceOneLine) : base(shaderEditor, materialProperty, displayName, xOffset, optionsRaw, forceOneLine, 0) + { + doCustomDrawLogic = true; + } + + public override void DrawDefault() + { + ActiveShaderEditor.Editor.DoubleSidedGIField(); + } + } + public class LocaleProperty : ShaderProperty + { + public LocaleProperty(ShaderEditor shaderEditor, MaterialProperty materialProperty, string displayName, int xOffset, string optionsRaw, bool forceOneLine) : base(shaderEditor, materialProperty, displayName, xOffset, optionsRaw, forceOneLine, 0) + { + doCustomDrawLogic = true; + } + + public override void DrawDefault() + { + ShaderEditor.Active.Locale.DrawDropdown(); + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/GUIHelper.cs b/Scripts/ThryEditor/Editor/GUIHelper.cs new file mode 100644 index 0000000..7ed1dae --- /dev/null +++ b/Scripts/ThryEditor/Editor/GUIHelper.cs @@ -0,0 +1,1134 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class GuiHelper + { + public const float SMALL_TEXTURE_VRAM_DISPLAY_WIDTH = 80; + + public static void ConfigTextureProperty(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor, bool hasFoldoutProperties, bool skip_drag_and_drop_handling = false) + { + switch (Config.Singleton.default_texture_type) + { + case TextureDisplayType.small: + SmallTextureProperty(position, prop, label, editor, hasFoldoutProperties); + break; + case TextureDisplayType.big: + StylizedBigTextureProperty(position, prop, label, editor, hasFoldoutProperties, skip_drag_and_drop_handling); + break; + case TextureDisplayType.big_basic: + BigTexturePropertyBasic(position, prop, label, editor, hasFoldoutProperties, skip_drag_and_drop_handling); + break; + } + } + + public static float GetSmallTextureVRAMWidth(MaterialProperty textureProperty) + { + if (textureProperty.textureValue != null) return SMALL_TEXTURE_VRAM_DISPLAY_WIDTH; + return 0; + } + + public static void SmallTextureProperty(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor, bool hasFoldoutProperties, Action extraFoldoutGUI = null) + { + // Border Code start + bool isFoldedOut = hasFoldoutProperties && DrawingData.IsEnabled && DrawingData.CurrentTextureProperty.showFoldoutProperties; + if(isFoldedOut) + { + Rect border = EditorGUILayout.BeginVertical(); + GUILayoutUtility.GetRect(0, 5); + border = new RectOffset(EditorGUI.indentLevel * -15 - 26, 3, -3, -3).Add(border); + GUI.DrawTexture(border, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, Styles.COLOR_BACKGROUND_1, 3, 10); + } + // Border Code end + + + Rect thumbnailPos = position; + Rect foloutClickCheck = position; + Rect tooltipRect = position; + if (hasFoldoutProperties) + { + thumbnailPos.x += 20; + thumbnailPos.width -= 20; + } + editor.TexturePropertyMiniThumbnail(thumbnailPos, prop, label.text, label.tooltip); + float iconsPositioningHeight = thumbnailPos.y; + //VRAM + Rect vramPos = Rect.zero; + if (DrawingData.CurrentTextureProperty.MaterialProperty.textureValue != null) + { + GUIContent content = new GUIContent(DrawingData.CurrentTextureProperty.VRAMString); + vramPos = thumbnailPos; + vramPos.x += thumbnailPos.width - SMALL_TEXTURE_VRAM_DISPLAY_WIDTH; + vramPos.width = SMALL_TEXTURE_VRAM_DISPLAY_WIDTH; + GUI.Label(vramPos, content, Styles.label_align_right); + } + //Prop right next to texture + if (DrawingData.CurrentTextureProperty.DoesReferencePropertyExist) + { + ShaderProperty property = ShaderEditor.Active.PropertyDictionary[DrawingData.CurrentTextureProperty.Options.reference_property]; + Rect r = position; + r.x += EditorGUIUtility.labelWidth - CurrentIndentWidth(); + r.width -= EditorGUIUtility.labelWidth - CurrentIndentWidth(); + r.width -= vramPos.width; + foloutClickCheck.width -= r.width; + property.Draw(new CRect(r), new GUIContent()); + property.tooltip.ConditionalDraw(r); + } + //Foldouts + if (hasFoldoutProperties && DrawingData.CurrentTextureProperty != null) + { + //draw dropdown triangle + Rect trianglePos = thumbnailPos; + trianglePos.x += DrawingData.CurrentTextureProperty.XOffset * 15 - 2; + //This is an invisible button with zero functionality. But it needs to be here so that the triangle click reacts fast + if (GUI.Button(trianglePos, "", GUIStyle.none)) { } + if (Event.current.type == EventType.Repaint) + EditorStyles.foldout.Draw(trianglePos, false, false, DrawingData.CurrentTextureProperty.showFoldoutProperties, false); + + if (DrawingData.IsEnabled) + { + //sub properties + if (DrawingData.CurrentTextureProperty.showFoldoutProperties) + { + EditorGUI.indentLevel += 2; + extraFoldoutGUI?.Invoke(); + if (DrawingData.CurrentTextureProperty.hasScaleOffset) + { + EditorGUI.showMixedValue = ShaderEditor.Active.Materials.Select(m => m.GetTextureScale(prop.name)).Distinct().Count() > 1 || ShaderEditor.Active.Materials.Select(m => m.GetTextureOffset(prop.name)).Distinct().Count() > 1; + ShaderEditor.Active.Editor.TextureScaleOffsetProperty(prop); + Rect lastRect = GUILayoutUtility.GetLastRect(); + tooltipRect.height = (lastRect.y - tooltipRect.y) + lastRect.height; + iconsPositioningHeight = lastRect.y; + } + //In case of locked material end disabled group here to allow editing of sub properties + if (ShaderEditor.Active.IsLockedMaterial) EditorGUI.EndDisabledGroup(); + + PropertyOptions options = DrawingData.CurrentTextureProperty.Options; + if (options.reference_properties != null) + foreach (string r_property in options.reference_properties) + { + ShaderProperty property = ShaderEditor.Active.PropertyDictionary[r_property]; + property.Draw(useEditorIndent: true); + } + + //readd disabled group + if (ShaderEditor.Active.IsLockedMaterial) EditorGUI.BeginDisabledGroup(false); + + EditorGUI.indentLevel -= 2; + } + if (ShaderEditor.Input.LeftClick_IgnoreLockedAndUnityUses && foloutClickCheck.Contains(Event.current.mousePosition)) + { + ShaderEditor.Input.Use(); + DrawingData.CurrentTextureProperty.showFoldoutProperties = !DrawingData.CurrentTextureProperty.showFoldoutProperties; + } + } + } + + Rect object_rect = new Rect(position); + object_rect.height = GUILayoutUtility.GetLastRect().y - object_rect.y + GUILayoutUtility.GetLastRect().height; + DrawingData.LastGuiObjectRect = object_rect; + DrawingData.TooltipCheckRect = tooltipRect; + DrawingData.IconsPositioningHeight = iconsPositioningHeight; + + // Border Code start + if(isFoldedOut) + { + GUILayoutUtility.GetRect(0, 5); + EditorGUILayout.EndVertical(); + } + // Border Code end + } + + + static int s_texturePickerWindow = -1; + static MaterialProperty s_texturePickerWindowProperty = null; + public static void StylizedBigTextureProperty(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor, bool hasFoldoutProperties, bool skip_drag_and_drop_handling = false) + { + // add some padding at the top + position.y += 5; + + position.x += (EditorGUI.indentLevel) * 15; + position.width -= (EditorGUI.indentLevel) * 15; + Rect border = new Rect(position); + border.height = 80; // for texture & offset + + Rect[] additionRects = new Rect[(DrawingData.CurrentTextureProperty.DoesReferencePropertyExist ? 1 : 0) + + (DrawingData.CurrentTextureProperty.DoReferencePropertiesExist ? DrawingData.CurrentTextureProperty.Options.reference_properties.Length : 0)]; + int i = 0; + + if (DrawingData.CurrentTextureProperty.DoReferencePropertiesExist) + { + foreach (string r_property in DrawingData.CurrentTextureProperty.Options.reference_properties) + { + float height = editor.GetPropertyHeight(ShaderEditor.Active.PropertyDictionary[r_property].MaterialProperty); + additionRects[i++] = new Rect(border.x + 15, border.y + border.height - 8, border.width - 15, height); + border.height += height + 3; // add a little padding + } + } + if (DrawingData.CurrentTextureProperty.DoesReferencePropertyExist) + { + float height = editor.GetPropertyHeight(ShaderEditor.Active.PropertyDictionary[DrawingData.CurrentTextureProperty.Options.reference_property].MaterialProperty); + additionRects[i++] = new Rect(border.x + 15, border.y + border.height, border.width - 15, height); + border.height += height + 3; // add a little padding + } + Rect vramRect = new Rect(border.x + 30, border.y + border.height - 6, border.width - 15, EditorStyles.label.lineHeight); + border.height += EditorStyles.label.lineHeight; + + // Reserve space + GUILayoutUtility.GetRect(0, border.height - position.height - 5); + + GUI.DrawTexture(border, Texture2D.whiteTexture, ScaleMode.StretchToFill, false, 0, Styles.COLOR_BACKGROUND_1, 3, 10); + + Rect previewSide = new Rect(border); + Rect optionsSide = new Rect(border); + previewSide.width = Mathf.Max(50, Mathf.Min(previewSide.height, previewSide.width - EditorGUIUtility.labelWidth - 50)); + previewSide.x += optionsSide.width - previewSide.width; + optionsSide.width -= previewSide.width; + + Rect previewRectBorder = new Rect(previewSide); + previewRectBorder.height = previewRectBorder.width; + Rect previewRect = new RectOffset(3, 3, 3, 3).Remove(previewRectBorder); + + Rect buttonSelectRect = new RectOffset(20, 20, 0, 0).Remove(previewRectBorder); + buttonSelectRect.height = 20; + buttonSelectRect.y = previewRect.y + previewRect.height - buttonSelectRect.height + 2; + + if (prop.hasMixedValue) + { + Rect mixedRect = new Rect(previewRect); + mixedRect.y -= 5; + mixedRect.x += mixedRect.width / 2 - 4; + GUI.Label(mixedRect, "_"); + } + else if (prop.textureValue != null) + { + if(prop.textureValue is Cubemap) + { + editor.TextureProperty(previewRect, prop, "", false); + } + else + { + GUI.DrawTexture(previewRect, prop.textureValue); + } + } + GUI.DrawTexture(previewRectBorder, Texture2D.whiteTexture, ScaleMode.StretchToFill, false, 0, Styles.COLOR_BACKGROUND_1, 3, 10); + + //selection button and pinging + if (GUI.Button(buttonSelectRect, "Select", EditorStyles.miniButton)) + { + OpenTexturePicker(prop); + } + else if (Event.current.type == EventType.MouseDown && previewRect.Contains(Event.current.mousePosition)) + { + EditorGUIUtility.PingObject(prop.textureValue); + } + HandleTexturePicker(prop); + + if (!skip_drag_and_drop_handling) + AcceptDragAndDrop(previewRect, prop); + + //Change indent & label width + EditorGUI.indentLevel += 2; + float oldLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 80; + + + //scale offset rect + foldout properties + Rect scale_offset_rect = new Rect(); + scale_offset_rect = new RectOffset(30, 5, 37, 0).Remove(optionsSide); + scale_offset_rect.height = position.height; + if (hasFoldoutProperties || DrawingData.CurrentTextureProperty.Options.reference_property != null) + { + if (DrawingData.CurrentTextureProperty.hasScaleOffset) + { + EditorGUI.showMixedValue = ShaderEditor.Active.Materials.Select(m => m.GetTextureScale(prop.name)).Distinct().Count() > 1 || ShaderEditor.Active.Materials.Select(m => m.GetTextureOffset(prop.name)).Distinct().Count() > 1; + editor.TextureScaleOffsetProperty(scale_offset_rect, prop); + } + + //In case of locked material end disabled group here to allow editing of sub properties + if (ShaderEditor.Active.IsLockedMaterial) EditorGUI.EndDisabledGroup(); + + PropertyOptions options = DrawingData.CurrentTextureProperty.Options; + if (options.reference_property != null) + { + ShaderProperty property = ShaderEditor.Active.PropertyDictionary[options.reference_property]; + Rect r = additionRects[additionRects.Length - 1]; + r.width = optionsSide.width - 20; + property.Draw(new CRect(r)); + } + if (options.reference_properties != null) + { + i = 0; + foreach (string r_property in options.reference_properties) + { + ShaderProperty property = ShaderEditor.Active.PropertyDictionary[r_property]; + Rect r = additionRects[i++]; + r.width = optionsSide.width - 20; + property.Draw(new CRect(r)); + } + } + + //readd disabled group + if (ShaderEditor.Active.IsLockedMaterial) EditorGUI.BeginDisabledGroup(false); + } + + //VRAM + if (DrawingData.CurrentTextureProperty.MaterialProperty.textureValue != null) + { + GUI.Label(vramRect, "VRAM:"); + vramRect.x += EditorGUIUtility.labelWidth - 15; + GUI.Label(vramRect, DrawingData.CurrentTextureProperty.VRAMString); + } + + //reset indent + label width + EditorGUI.indentLevel -= 2; + EditorGUIUtility.labelWidth = oldLabelWidth; + + Rect label_rect = new RectOffset(-5, 0, -2, 0).Add(position); + GUI.Label(label_rect, label); + + DrawingData.LastGuiObjectRect = border; + DrawingData.TooltipCheckRect = Rect.MinMaxRect(position.x, position.y, scale_offset_rect.xMax, scale_offset_rect.yMax); + DrawingData.IconsPositioningHeight = scale_offset_rect.y; + } + + public static void BigTexturePropertyBasic(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor, bool hasFoldoutProperties, bool skip_drag_and_drop_handling = false) + { + string text = label.text; + //VRAM + if (DrawingData.CurrentTextureProperty.MaterialProperty.textureValue != null) + { + text += " (VRAM: " + DrawingData.CurrentTextureProperty.VRAMString + ")"; + } + GUILayoutUtility.GetRect(0, EditorGUIUtility.singleLineHeight * 3 - 5); + editor.TextureProperty(position, prop, text); + + Rect tooltipCheckRect = position; + tooltipCheckRect.height += EditorGUIUtility.singleLineHeight * 3 - 5; + + float iconsPositioningHeight = position.y; + if(DrawingData.CurrentTextureProperty.hasScaleOffset) + iconsPositioningHeight += position.height + EditorGUIUtility.singleLineHeight - 5; + + + // Reference properties + EditorGUI.indentLevel += 1; + PropertyOptions options = DrawingData.CurrentTextureProperty.Options; + if (options.reference_property != null) + { + ShaderProperty property = ShaderEditor.Active.PropertyDictionary[options.reference_property]; + property.Draw(useEditorIndent: true); + } + if (options.reference_properties != null) + foreach (string r_property in options.reference_properties) + { + ShaderProperty property = ShaderEditor.Active.PropertyDictionary[r_property]; + property.Draw(useEditorIndent: true); + } + EditorGUI.indentLevel -= 1; + + DrawingData.LastGuiObjectRect = position; + DrawingData.TooltipCheckRect = tooltipCheckRect; + DrawingData.IconsPositioningHeight = iconsPositioningHeight; + } + + public static void OpenTexturePicker(MaterialProperty prop) + { + EditorGUIUtility.ShowObjectPicker(prop.textureValue, false, "", 0); + s_texturePickerWindow = EditorGUIUtility.GetObjectPickerControlID(); + s_texturePickerWindowProperty = prop; + } + + public static bool HandleTexturePicker(MaterialProperty prop) + { + if (Event.current.commandName == "ObjectSelectorUpdated" && EditorGUIUtility.GetObjectPickerControlID() == s_texturePickerWindow && s_texturePickerWindowProperty.name == prop.name) + { + prop.textureValue = (Texture)EditorGUIUtility.GetObjectPickerObject(); + ShaderEditor.RepaintActive(); + return true; + } + if (Event.current.commandName == "ObjectSelectorClosed" && EditorGUIUtility.GetObjectPickerControlID() == s_texturePickerWindow) + { + s_texturePickerWindow = -1; + s_texturePickerWindowProperty = null; + } + return false; + } + + public static bool AcceptDragAndDrop(Rect r, MaterialProperty prop) + { + if ((ShaderEditor.Input.is_drag_drop_event) && r.Contains(ShaderEditor.Input.mouse_position) && DragAndDrop.objectReferences[0] is Texture) + { + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + if (ShaderEditor.Input.is_drop_event) + { + DragAndDrop.AcceptDrag(); + prop.textureValue = (Texture)DragAndDrop.objectReferences[0]; + return true; + } + } + return false; + } + + static Stack s_previousIndentLevels = new Stack(); + public static void BeginCustomIndentLevel(int indent) + { + s_previousIndentLevels.Push(EditorGUI.indentLevel); + EditorGUI.indentLevel = indent; + } + + public static void EndCustomIndentLevel() + { + EditorGUI.indentLevel = s_previousIndentLevels.Pop(); + } + + public static void MinMaxSlider(Rect settingsRect, GUIContent content, MaterialProperty prop) + { + bool changed = false; + Vector4 vec = prop.vectorValue; + Rect sliderRect = settingsRect; + + EditorGUI.LabelField(settingsRect, content); + + if (settingsRect.width > 160) + { + Rect numberRect = settingsRect; + numberRect.width = 65 + (EditorGUI.indentLevel - 1) * 15; + + numberRect.x = EditorGUIUtility.labelWidth - (EditorGUI.indentLevel - 1) * 15; + + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = prop.hasMixedValue; + vec.x = EditorGUI.FloatField(numberRect, vec.x, EditorStyles.textField); + changed |= EditorGUI.EndChangeCheck(); + + numberRect.x = settingsRect.xMax - numberRect.width; + + EditorGUI.BeginChangeCheck(); + EditorGUI.showMixedValue = prop.hasMixedValue; + vec.y = EditorGUI.FloatField(numberRect, vec.y); + changed |= EditorGUI.EndChangeCheck(); + + sliderRect.xMin = EditorGUIUtility.labelWidth - (EditorGUI.indentLevel - 1) * 15; + sliderRect.xMin += (65 + -8); + sliderRect.xMax -= (65 + -8); + } + + vec.x = Mathf.Clamp(vec.x, vec.z, vec.y); + vec.y = Mathf.Clamp(vec.y, vec.x, vec.w); + + EditorGUI.BeginChangeCheck(); + EditorGUI.MinMaxSlider(sliderRect, ref vec.x, ref vec.y, vec.z, vec.w); + changed |= EditorGUI.EndChangeCheck(); + + if (changed) + { + prop.vectorValue = vec; + } + } + + public static bool DrawListField(List list) where type : UnityEngine.Object + { + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Add", EditorStyles.miniButton)) + list.Add(null); + if (GUILayout.Button("Remove", EditorStyles.miniButton)) + if (list.Count > 0) + list.RemoveAt(list.Count - 1); + GUILayout.EndHorizontal(); + + for (int i = 0; i < list.Count; i++) + { + list[i] = (type)EditorGUILayout.ObjectField(list[i], typeof(type), false); + } + return false; + } + public static bool DrawListField(List list, float maxHeight, ref Vector2 scrollPosition) where type : UnityEngine.Object + { + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Add", EditorStyles.miniButton)) + list.Add(null); + if (GUILayout.Button("Remove", EditorStyles.miniButton)) + if (list.Count > 0) + list.RemoveAt(list.Count - 1); + GUILayout.EndHorizontal(); + + scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.MaxHeight(maxHeight)); + for (int i = 0; i < list.Count; i++) + { + list[i] = (type)EditorGUILayout.ObjectField(list[i], typeof(type), false); + } + GUILayout.EndScrollView(); + return false; + } + + public static bool GUIDataStruct(t data) + { + return GUIDataStruct(data, new string[] { }); + } + + public static bool GUIDataStruct(t data, string[] exclude) + { + Type type = data.GetType(); + bool changed = false; + foreach (FieldInfo f in type.GetFields()) + { + bool skip = false; + foreach (string s in exclude) + if (s == f.Name) + skip = true; + if (skip) + continue; + + if (f.FieldType.IsEnum) + changed |= GUIEnum(f, data); + else if (f.FieldType == typeof(string)) + changed |= GUIString(f, data); + else if (f.FieldType == typeof(int)) + changed |= GUIInt(f, data); + else if (f.FieldType == typeof(float)) + changed |= GUIFloat(f, data); + } + return changed; + } + + private static bool GUIEnum(FieldInfo f, object o) + { + EditorGUI.BeginChangeCheck(); + Enum e = EditorGUILayout.EnumPopup(f.Name, (Enum)f.GetValue(o), GUILayout.ExpandWidth(false)); + bool changed = EditorGUI.EndChangeCheck(); + if (changed) + f.SetValue(o, e); + return changed; + } + + private static bool GUIString(FieldInfo f, object o) + { + EditorGUI.BeginChangeCheck(); + string s = EditorGUILayout.TextField(f.Name, (string)f.GetValue(o), GUILayout.ExpandWidth(false)); + bool changed = EditorGUI.EndChangeCheck(); + if (changed) + f.SetValue(o, s); + return changed; + } + + private static bool GUIInt(FieldInfo f, object o) + { + EditorGUI.BeginChangeCheck(); + int i = EditorGUILayout.IntField(f.Name, (int)f.GetValue(o), GUILayout.ExpandWidth(false)); + bool changed = EditorGUI.EndChangeCheck(); + if (changed) + f.SetValue(o, i); + return changed; + } + + private static bool GUIFloat(FieldInfo f, object o) + { + EditorGUI.BeginChangeCheck(); + float i = EditorGUILayout.FloatField(f.Name, (float)f.GetValue(o), GUILayout.ExpandWidth(false)); + bool changed = EditorGUI.EndChangeCheck(); + if (changed) + f.SetValue(o, i); + return changed; + } + + public static void DrawLocaleSelection(GUIContent label, string[] locales, int selected) + { + EditorGUI.BeginChangeCheck(); + selected = EditorGUILayout.Popup(label.text, selected, locales); + if (EditorGUI.EndChangeCheck()) + { + ShaderEditor.Active.PropertyDictionary[ShaderEditor.PROPERTY_NAME_LOCALE].MaterialProperty.floatValue = selected; + ShaderEditor.ReloadActive(); + } + } + + public static void DrawHeader(ref bool enabled, ref bool options, GUIContent name) + { + var r = EditorGUILayout.BeginHorizontal("box"); + enabled = EditorGUILayout.Toggle(enabled, EditorStyles.radioButton, GUILayout.MaxWidth(15.0f)); + options = GUI.Toggle(r, options, GUIContent.none, new GUIStyle()); + EditorGUILayout.LabelField(name, Styles.dropDownHeaderLabel); + EditorGUILayout.EndHorizontal(); + } + + public static void DrawMasterLabel(string shaderName, Rect parent) + { + Rect rect = new Rect(0, parent.y, parent.width, 18); + EditorGUI.LabelField(rect, "" + shaderName + "", Styles.masterLabel); + } + + public static float CurrentIndentWidth() + { + return EditorGUI.indentLevel * 15; + } + // Mimics the normal map import warning - written by Orels1 + static bool TextureImportWarningBox(string message) { + GUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)); + GUILayout.Label(message, new GUIStyle(EditorStyles.label) { + fontSize = 10, wordWrap = true + }); + EditorGUILayout.BeginHorizontal(new GUIStyle() { + alignment = TextAnchor.MiddleRight + }, GUILayout.Height(24)); + EditorGUILayout.Space(); + bool buttonPress = GUILayout.Button("Fix Now", new GUIStyle("button") { + stretchWidth = false, + margin = new RectOffset(0, 0, 0, 0), + padding = new RectOffset(9, 9, 0, 0) + }, GUILayout.Height(22)); + EditorGUILayout.EndHorizontal(); + GUILayout.EndVertical(); + return buttonPress; + } + + public static void ColorspaceWarning(MaterialProperty tex, bool shouldHaveSRGB) { + if (tex.textureValue) { + string texPath = AssetDatabase.GetAssetPath(tex.textureValue); + TextureImporter texImporter; + var importer = TextureImporter.GetAtPath(texPath) as TextureImporter; + if (importer != null) { + texImporter = (TextureImporter)importer; + if (texImporter.sRGBTexture != shouldHaveSRGB) { + if (TextureImportWarningBox(shouldHaveSRGB ? EditorLocale.editor.Get("colorSpaceWarningSRGB") : EditorLocale.editor.Get("colorSpaceWarningLinear"))) { + texImporter.sRGBTexture = shouldHaveSRGB; + texImporter.SaveAndReimport(); + } + } + } + } + } + + public class CustomGUIColor : IDisposable + { + Color _prev; + public CustomGUIColor(Color color) + { + _prev = GUI.color; + GUI.color = color; + } + + public void Dispose() + { + GUI.color = _prev; + } + } + + public static bool Button(Rect r, GUIStyle style) + { + return GUI.Button(r, GUIContent.none, style); + } + + public static bool Button(Rect r, string tooltip, GUIStyle style) + { + return GUI.Button(r, new GUIContent("", tooltip), style); + } + + public static bool Button(GUIStyle style, int width, int height) + { + Rect r = GUILayoutUtility.GetRect(width, height); + return Button(r, style); + } + + public static bool ButtonWithCursor(GUIStyle style, int width, int height) + { + Rect r = GUILayoutUtility.GetRect(width, height); + EditorGUIUtility.AddCursorRect(r, MouseCursor.Link); + return Button(r, style); + } + + public static bool ButtonWithCursor(GUIStyle style, string tooltip, int width, int height) + { + Rect r = GUILayoutUtility.GetRect(width, height); + EditorGUIUtility.AddCursorRect(r, MouseCursor.Link); + return Button(r, tooltip, style); + } + + public static bool ButtonWithCursor(GUIStyle style, string tooltip, int width, int height, out Rect r) + { + r = GUILayoutUtility.GetRect(width, height); + EditorGUIUtility.AddCursorRect(r, MouseCursor.Link); + return Button(r, tooltip, style); + } + + public static bool Button(Rect r, string tooltip, GUIStyle style, Color c) + { + Color prevColor = GUI.backgroundColor; + GUI.backgroundColor = c; + bool b = GuiHelper.Button(r, tooltip, style); + GUI.backgroundColor = prevColor; + return b; + } + + public static bool Button(GUIStyle style, int width, int height, Color c) + { + Color prevColor = GUI.backgroundColor; + GUI.backgroundColor = c; + bool b = GuiHelper.Button(style, width, height); + GUI.backgroundColor = prevColor; + return b; + } + + public static bool Button(Rect r, GUIStyle style, Color c, bool doColor) + { + Color prevColor = GUI.backgroundColor; + if(doColor) GUI.backgroundColor = c; + bool b = GuiHelper.Button(r, style); + GUI.backgroundColor = prevColor; + return b; + } + + #region SearchableEnumPopup + public class SearchableEnumPopup : EditorWindow + { + private static SearchableEnumPopup window; + public static void CreateSearchableEnumPopup(string[] options, string selected, Action setter) + { + Vector2 pos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); + pos.x = Mathf.Min(EditorWindow.focusedWindow.position.x + EditorWindow.focusedWindow.position.width - 250, pos.x); + pos.y = Mathf.Min(EditorWindow.focusedWindow.position.y + EditorWindow.focusedWindow.position.height - 200, pos.y); + + if (window != null) + window.Close(); + window = ScriptableObject.CreateInstance(); + window.position = new Rect(pos.x, pos.y, 250, 200); + window._options = options; + window._selected = selected; + window._setter = setter; + window._searchedFor = ""; + window.ShowPopup(); + } + + private SearchableEnumPopup() { } + + string[] _options; + string _selected; + string _searchedFor; + Action _setter; + + bool first = true; + + private void OnGUI() + { + if (GUILayout.Button("Close")) this.Close(); + GUI.SetNextControlName("SearchTextField"); + _searchedFor = GUILayout.TextField(_searchedFor); + string seachTerm = _searchedFor.ToLowerInvariant().TrimStart('_'); + string[] filteredOptions = _options.Where(o => o.TrimStart('_').ToLowerInvariant().StartsWith(seachTerm)).ToArray(); + for (int i = 0; i < 7 && i < filteredOptions.Length; i++) + { + if (GUILayout.Button(filteredOptions[i])) + { + _selected = filteredOptions[i]; + _setter.Invoke(_selected); + this.Close(); + } + } + if (filteredOptions.Length > 7) + { + GUILayout.Label("... More"); + } + if (first) + { + GUI.FocusControl("SearchTextField"); + first = false; + } + } + } + + #endregion + } + + public class BetterTooltips + { + private static Tooltip activeTooltip; + + public class Tooltip + { + private GUIContent content; + private bool empty; + + public bool isSelected { get; private set; } = false; + + private Rect containerRect; + private Rect contentRect; + + readonly static Vector2 PADDING = new Vector2(10, 10); + + public Tooltip(string text) + { + content = new GUIContent(text); + empty = string.IsNullOrWhiteSpace(text); + } + + public Tooltip(string text, Texture texture) + { + content = new GUIContent(text, texture); + empty = string.IsNullOrWhiteSpace(text) && texture == null; + } + + public void SetText(string text) + { + content.text = text; + empty &= string.IsNullOrWhiteSpace(text); + } + + public void ConditionalDraw(Rect hoverOverRect) + { + if (empty) return; + bool isSelected = hoverOverRect.Contains(Event.current.mousePosition); + if (isSelected ) + { + CalculatePositions(hoverOverRect); + activeTooltip = this; + this.isSelected = true; + } + } + + private void CalculatePositions(Rect hoverOverRect) + { + Vector2 contentSize = EditorStyles.label.CalcSize(content); + Vector2 containerPosition = new Vector2(Event.current.mousePosition.x - contentSize.x / 2 - PADDING.x / 2, hoverOverRect.y - contentSize.y - PADDING.y - 3); + + containerPosition.x = Mathf.Max(0, containerPosition.x); + containerPosition.x = Mathf.Min(EditorGUIUtility.currentViewWidth - contentSize.x - PADDING.x, containerPosition.x); + + contentRect = new Rect(containerPosition + new Vector2(PADDING.x/2, PADDING.y/2), contentSize); + containerRect = new Rect(containerPosition, contentSize + new Vector2(PADDING.x, PADDING.y)); + } + + public void Draw() + { + EditorGUI.DrawRect(containerRect, Styles.COLOR_BG); + EditorGUI.LabelField(contentRect, content); + isSelected = false; + } + } + + public static void DrawActive() + { + if(activeTooltip != null) + { + if (activeTooltip.isSelected) + { + activeTooltip.Draw(); + } + else + { + activeTooltip = null; + } + } + } + } + + public class FooterButton + { + private GUIContent content; + private bool isTextureContent; + const int texture_height = 40; + int texture_width; + private ButtonData data; + + public FooterButton(ButtonData data) + { + this.data = data; + if (data != null) + { + if (data.texture == null) + { + content = new GUIContent(data.text, data.hover); + isTextureContent = false; + } + else + { + texture_width = (int)((float)data.texture.loaded_texture.width / data.texture.loaded_texture.height * texture_height); + content = new GUIContent(data.texture.loaded_texture, data.hover); + isTextureContent = true; + } + } + else + { + content = new GUIContent(); + } + } + + public void Draw() + { + Rect cursorRect; + if (isTextureContent) + { + if(GUILayout.Button(content, new GUIStyle(), GUILayout.MaxWidth(texture_width), GUILayout.Height(texture_height))){ + data.action.Perform(ShaderEditor.Active?.Materials); + } + cursorRect = GUILayoutUtility.GetLastRect(); + GUILayout.Space(8); + } + else + { + if (GUILayout.Button(content, GUILayout.ExpandWidth(false), GUILayout.Height(texture_height))) + data.action.Perform(ShaderEditor.Active?.Materials); + cursorRect = GUILayoutUtility.GetLastRect(); + GUILayout.Space(2); + } + EditorGUIUtility.AddCursorRect(cursorRect, MouseCursor.Link); + } + + public static void DrawList(List list) + { + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + GUILayout.Space(2); + foreach (FooterButton b in list) + { + b.Draw(); + } + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + } + } + + public class ThryHeaderHandler + { + private MaterialProperty property; + + private bool _isExpanded; + private string keyword; + private string end; + + private bool _disableContentWhenKeywordOff; + + int p_xOffset; + int p_xOffset_total; + public int xOffset + { + set + { + p_xOffset = value; + p_xOffset_total = value * 15 + 15; + } + } + + private ButtonData button; + + public ThryHeaderHandler(string end, string keyword, float disableContentWhenKeywordOff) + { + this.end = end; + this.keyword = keyword; + this._disableContentWhenKeywordOff = disableContentWhenKeywordOff == 1; + } + + public ThryHeaderHandler(string end, string keyword) : this(end, keyword, 0) { } + public ThryHeaderHandler(string end) : this(end, null, 0) { } + public ThryHeaderHandler() : this(null, null, 0) { } + + public string GetEndProperty() + { + return end; + } + + public bool IsExpanded + { + get + { + return _isExpanded; + } + } + + public bool DisableContent + { + get + { + return _disableContentWhenKeywordOff && !ShaderEditor.Active.Materials[0].IsKeywordEnabled(keyword); + } + } + + public void Toggle() + { + _isExpanded = !_isExpanded; + foreach (Material m in ShaderEditor.Active.Materials) m.SetFloat(property.name, _isExpanded ? 1 : 0); + } + + public void OnGUI(Rect position, MaterialProperty prop, GUIContent label, MaterialEditor editor) + { + if (this.property == null) + { + this.property = prop; + this._isExpanded = prop.floatValue == 1; + } + + PropertyOptions options = ShaderEditor.Active.CurrentProperty.Options; + Event e = Event.current; + + position.width -= p_xOffset_total - position.x; + position.x = p_xOffset_total; + + DrawingData.LastGuiObjectHeaderRect = position; + + DrawBoxAndContent(position, e, label, options); + + DrawSmallArrow(position, e); + HandleToggleInput(position); + } + + private void DrawBoxAndContent(Rect rect, Event e, GUIContent content, PropertyOptions options) + { + if (options.reference_property != null && ShaderEditor.Active.PropertyDictionary.ContainsKey(options.reference_property)) + { + GUI.Box(rect, new GUIContent(" " + content.text, content.tooltip), Styles.dropDownHeader); + DrawIcons(rect, options, e); + + Rect togglePropertyRect = new Rect(rect); + togglePropertyRect.x += 5; + togglePropertyRect.y += 1; + togglePropertyRect.height -= 4; + togglePropertyRect.width = GUI.skin.font.fontSize * 3; + float fieldWidth = EditorGUIUtility.fieldWidth; + EditorGUIUtility.fieldWidth = 20; + ShaderProperty prop = ShaderEditor.Active.PropertyDictionary[options.reference_property]; + + int xOffset = prop.XOffset; + prop.XOffset = 0; + prop.Draw(new CRect(togglePropertyRect), new GUIContent(), isInHeader: true); + prop.XOffset = xOffset; + EditorGUIUtility.fieldWidth = fieldWidth; + }else if(keyword != null) + { + GUI.Box(rect, " " + content.text, Styles.dropDownHeader); + DrawIcons(rect, options, e); + + Rect togglePropertyRect = new Rect(rect); + togglePropertyRect.x += 20; + togglePropertyRect.width = 20; + + EditorGUI.BeginChangeCheck(); + bool keywordOn = EditorGUI.Toggle(togglePropertyRect, "", ShaderEditor.Active.Materials[0].IsKeywordEnabled(keyword)); + if (EditorGUI.EndChangeCheck()) + { + MaterialHelper.ToggleKeyword(ShaderEditor.Active.Materials, keyword, keywordOn); + } + } + else + { + GUI.Box(rect, content, Styles.dropDownHeader); + DrawIcons(rect, options, e); + } + + } + + /// + /// Draws the icons for ShaderEditor features like linking and copying + /// + /// + /// + private void DrawIcons(Rect rect, PropertyOptions options, Event e) + { + Rect buttonRect = new Rect(rect); + buttonRect.y += 1; + buttonRect.height -= 4; + buttonRect.width = buttonRect.height; + + float right = rect.x + rect.width; + buttonRect.x = right - 56; + DrawHelpButton(buttonRect, options, e); + buttonRect.x = right - 38; + DrawLinkSettings(buttonRect, e); + buttonRect.x = right - 20; + DrawDowdownSettings(buttonRect, e); + } + + private void DrawHelpButton(Rect rect, PropertyOptions options, Event e) + { + ButtonData button = this.button != null ? this.button : options.button_help; + if (button != null && button.condition_show.Test()) + { + if (GuiHelper.Button(rect, Styles.icon_style_help)) + { + ShaderEditor.Input.Use(); + if (button.action != null) button.action.Perform(ShaderEditor.Active?.Materials); + } + } + } + + private void DrawDowdownSettings(Rect rect, Event e) + { + if (GuiHelper.Button(rect, Styles.icon_style_menu)) + { + ShaderEditor.Input.Use(); + Rect buttonRect = new Rect(rect); + buttonRect.width = 150; + buttonRect.x = Mathf.Min(Screen.width - buttonRect.width, buttonRect.x); + buttonRect.height = 60; + float maxY = GUIUtility.ScreenToGUIPoint(new Vector2(0, EditorWindow.focusedWindow.position.y + Screen.height)).y - 2.5f * buttonRect.height; + buttonRect.y = Mathf.Min(buttonRect.y - buttonRect.height / 2, maxY); + + ShowHeaderContextMenu(buttonRect, ShaderEditor.Active.CurrentProperty, ShaderEditor.Active.Materials[0]); + } + } + + private void DrawLinkSettings(Rect rect, Event e) + { + if (GuiHelper.Button(rect, Styles.icon_style_linked, Styles.COLOR_ICON_ACTIVE_CYAN, MaterialLinker.IsLinked(ShaderEditor.Active.CurrentProperty.MaterialProperty))) + { + ShaderEditor.Input.Use(); + List linked_materials = MaterialLinker.GetLinked(ShaderEditor.Active.CurrentProperty.MaterialProperty); + MaterialLinker.Popup(rect, linked_materials, ShaderEditor.Active.CurrentProperty.MaterialProperty); + } + } + + void ShowHeaderContextMenu(Rect position, ShaderPart property, Material material) + { + var menu = new GenericMenu(); + menu.AddItem(new GUIContent("Reset"), false, delegate () + { + property.CopyFromMaterial(new Material(material.shader), true); + List linked_materials = MaterialLinker.GetLinked(property.MaterialProperty); + if (linked_materials != null) + foreach (Material m in linked_materials) + property.CopyToMaterial(m, true); + }); + menu.AddItem(new GUIContent("Copy"), false, delegate () + { + Mediator.copy_material = new Material(material); + Mediator.transfer_group = property; + }); + menu.AddItem(new GUIContent("Paste"), false, delegate () + { + if (Mediator.copy_material != null || Mediator.transfer_group != null) + { + property.TransferFromMaterialAndGroup(Mediator.copy_material, Mediator.transfer_group, true); + List linked_materials = MaterialLinker.GetLinked(property.MaterialProperty); + if (linked_materials != null) + foreach (Material m in linked_materials) + property.CopyToMaterial(m, true); + } + }); + menu.DropDown(position); + } + + private void DrawSmallArrow(Rect rect, Event e) + { + if (e.type == EventType.Repaint) + { + var toggleRect = new Rect(rect.x + 4f, rect.y + 2f, 13f, 13f); + EditorStyles.foldout.Draw(toggleRect, false, false, _isExpanded, false); + } + } + + private void HandleToggleInput(Rect rect) + { + //Ignore unity uses is cause disabled will use the event to prevent toggling + if (ShaderEditor.Input.LeftClick_IgnoreLocked && rect.Contains(ShaderEditor.Input.mouse_position) && !ShaderEditor.Input.is_alt_down) + { + this.Toggle(); + ShaderEditor.Input.Use(); + } + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/GradiantEditor.cs b/Scripts/ThryEditor/Editor/GradiantEditor.cs new file mode 100644 index 0000000..b33b401 --- /dev/null +++ b/Scripts/ThryEditor/Editor/GradiantEditor.cs @@ -0,0 +1,273 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class GradientEditor : EditorWindow + { + + public static void Open(GradientData data, MaterialProperty prop, TextureData predefinedTextureSettings, bool force_texture_options = false, bool show_texture_options=true) + { + texture_settings_data = LoadTextureSettings(prop, predefinedTextureSettings, force_texture_options); + data.Gradient = TextureHelper.GetGradient(prop.textureValue); + GradientEditor window = (GradientEditor)EditorWindow.GetWindow(typeof(GradientEditor)); + window.titleContent = new GUIContent("Gradient '" +prop.name +"' of '"+ prop.targets[0].name + "'"); + window.privious_preview_texture = prop.textureValue; + window.prop = prop; + window.data = data; + window.show_texture_options = show_texture_options; + window.minSize = new Vector2(350, 350); + window.Show(); + } + + GradientData data; + MaterialProperty prop; + + object gradient_editor; + MethodInfo ongui; + MethodInfo gradient_editor_init; + + object preset_libary_editor; + MethodInfo preset_libary_onGUI; + object preset_libary_editor_state; + + private bool inited = false; + + private bool show_texture_options = true; + + private bool gradient_has_been_edited = false; + private Texture privious_preview_texture; + + private static TextureData LoadTextureSettings(MaterialProperty prop, TextureData predefinedTextureSettings, bool force_texture_options) + { + if (force_texture_options && predefinedTextureSettings != null) + return predefinedTextureSettings; + string json_texture_settings = FileHelper.LoadValueFromFile("gradient_texture_options_"+prop.name, PATH.PERSISTENT_DATA); + if (json_texture_settings != null) + return Parser.Deserialize(json_texture_settings); + else if (predefinedTextureSettings != null) + return predefinedTextureSettings; + else + return new TextureData(); + } + private static TextureData texture_settings_data; + private TextureData textureSettings + { + get + { + return texture_settings_data; + } + } + + public void Awake() + { + Type gradient_editor_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GradientEditor"); + gradient_editor = Activator.CreateInstance(gradient_editor_type); + gradient_editor_init = gradient_editor_type.GetMethod("Init"); + + ongui = gradient_editor_type.GetMethod("OnGUI"); + } + + public void OnDestroy() + { + if (gradient_has_been_edited) + { + if (data.PreviewTexture.GetType() == typeof(Texture2D)) + { + string file_name = GradientFileName(data.Gradient, prop.targets[0].name); + Texture saved = TextureHelper.SaveTextureAsPNG((Texture2D)data.PreviewTexture, PATH.TEXTURES_DIR+"/Gradients/" + file_name, textureSettings); + file_name = Regex.Replace(file_name, @"\.((png)|(jpg))$", ""); + FileHelper.SaveValueToFile(AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(saved)), Parser.ObjectToString(data.Gradient), PATH.GRADIENT_INFO_FILE); + prop.textureValue = saved; + // change importer settings + TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(AssetDatabase.GetAssetPath(saved)); + importer.textureCompression = TextureImporterCompression.CompressedHQ; + if(Config.Singleton.gradientEditorCompressionOverwrite != TextureImporterFormat.Automatic) + { + importer.SetPlatformTextureSettings(new TextureImporterPlatformSettings() + { + name = "PC", + overridden = true, + maxTextureSize = 2048, + format = Config.Singleton.gradientEditorCompressionOverwrite + }); + } + importer.SaveAndReimport(); + } + } + else + { + UpdatePreviewTexture(privious_preview_texture); + } + } + + private string GradientFileName(Gradient gradient, string material_name) + { + string hash = "" + gradient.GetHashCode(); + return GradientFileName(hash, material_name); + } + + private string GradientFileName(string hash, string material_name) + { + Config config = Config.Singleton; + string ret = config.gradient_name; + ret = Regex.Replace(ret, "", hash); + ret = Regex.Replace(ret, "", material_name); + return ret; + } + + private void InitSomeStuff() + { + Type presetLibraryEditorState_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PresetLibraryEditorState"); + preset_libary_editor_state = Activator.CreateInstance(presetLibraryEditorState_type, "Gradient"); + MethodInfo transfer_editor_prefs_state = presetLibraryEditorState_type.GetMethod("TransferEditorPrefsState"); + transfer_editor_prefs_state.Invoke(preset_libary_editor_state, new object[] { true }); + + Type scriptable_save_load_helper_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ScriptableObjectSaveLoadHelper`1"); + Type gradient_preset_libary_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GradientPresetLibrary"); + Type preset_libary_editor_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PresetLibraryEditor`1"); + Type save_load_helper_type = scriptable_save_load_helper_type.MakeGenericType(gradient_preset_libary_type); + Type gradient_preset_libary_editor_type = preset_libary_editor_type.MakeGenericType(gradient_preset_libary_type); + + object saveLoadHelper = Activator.CreateInstance(save_load_helper_type, "gradients", SaveType.Text); + + Action preset_libary_editor_callback = PresetClickedCallback; + preset_libary_editor = Activator.CreateInstance(gradient_preset_libary_editor_type, saveLoadHelper, preset_libary_editor_state, preset_libary_editor_callback); + PropertyInfo show_header = gradient_preset_libary_editor_type.GetProperty("showHeader"); + show_header.SetValue(preset_libary_editor, true, null); + PropertyInfo minMaxPreviewHeight = gradient_preset_libary_editor_type.GetProperty("minMaxPreviewHeight"); + minMaxPreviewHeight.SetValue(preset_libary_editor, new Vector2(14f, 14f), null); + + preset_libary_onGUI = gradient_preset_libary_editor_type.GetMethod("OnGUI"); + + SetGradient(data.Gradient); + gradient_has_been_edited = false; + + inited = true; + } + + public void PresetClickedCallback(int clickCount, object presetObject) + { + Gradient gradient = presetObject as Gradient; + if (gradient == null) + Debug.LogError("Incorrect object passed " + presetObject); + SetGradient(gradient); + } + + void SetGradient(Gradient gradient) + { + data.Gradient = gradient; +#if UNITY_2020_1_OR_NEWER + gradient_editor_init.Invoke(gradient_editor, new object[] { gradient, 0, true, ColorSpace.Linear }); +#else + gradient_editor_init.Invoke(gradient_editor, new object[] { gradient, 0, true }); +#endif + UpdateGradientPreviewTexture(); + } + + void OnGUI() + { + if (!inited) + InitSomeStuff(); + float gradientEditorHeight = Mathf.Min(position.height, 146); + float distBetween = 10f; + float presetLibraryHeight = Mathf.Min(position.height - gradientEditorHeight - distBetween-135,130); + + Rect gradientEditorRect = new Rect(10, 10, position.width - 20, gradientEditorHeight - 20); + Rect gradientLibraryRect = new Rect(0, gradientEditorHeight + distBetween, position.width, presetLibraryHeight); + + EditorGUI.BeginChangeCheck(); + ongui.Invoke(gradient_editor, new object[] { gradientEditorRect }); + if (EditorGUI.EndChangeCheck()) + UpdateGradientPreviewTexture(); + + OverrideGradientTexture(gradientEditorRect); + + preset_libary_onGUI.Invoke(preset_libary_editor, new object[] { gradientLibraryRect, data.Gradient }); + + GUILayout.BeginVertical(); + GUILayout.Space(gradientEditorHeight+ presetLibraryHeight+ distBetween); + GUILayout.EndVertical(); + + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if(GUILayout.Button("Discard Changes",GUILayout.ExpandWidth(false))) + DiscardChanges(); + GUILayout.EndHorizontal(); + if(show_texture_options) + TextureSettingsGUI(); + } + + private void DiscardChanges() + { + prop.textureValue = privious_preview_texture; + SetGradient(TextureHelper.GetGradient(privious_preview_texture)); + gradient_has_been_edited = false; + ShaderEditor.RepaintActive(); + } + + private void TextureSettingsGUI() + { + EditorGUIUtility.labelWidth = 100; + EditorGUIUtility.fieldWidth = 150; + EditorGUILayout.LabelField("Texture options:",EditorStyles.boldLabel); + bool changed = GuiHelper.GUIDataStruct(textureSettings, new string[]{"name"}); + if (changed) + { + FileHelper.SaveValueToFile("gradient_texture_options_" + prop.name, Parser.ObjectToString(textureSettings), PATH.PERSISTENT_DATA); + UpdateGradientPreviewTexture(); + } + } + + private void UpdateGradientPreviewTexture() + { + data.PreviewTexture = Converter.GradientToTexture(data.Gradient, textureSettings.width, textureSettings.height); + textureSettings.ApplyModes(data.PreviewTexture); + prop.textureValue = data.PreviewTexture; + gradient_has_been_edited = true; + ShaderEditor.RepaintActive(); + } + + private void UpdatePreviewTexture(Texture texture) + { + data.PreviewTexture = texture; + prop.textureValue = texture; + ShaderEditor.RepaintActive(); + } + + private void OverrideGradientTexture(Rect position) + { + Rect gradient_texture_position = new Rect(position); + + float modeHeight = 24f; + float swatchHeight = 16f; + float editSectionHeight = 26f; + float gradientTextureHeight = gradient_texture_position.height - 2 * swatchHeight - editSectionHeight - modeHeight; + gradient_texture_position.y += modeHeight; + gradient_texture_position.y += swatchHeight; + gradient_texture_position.height = gradientTextureHeight; + + + Rect r2 = new Rect(gradient_texture_position.x + 1, gradient_texture_position.y + 1, gradient_texture_position.width - 2, gradient_texture_position.height - 2); + + Texture2D backgroundTexture = TextureHelper.GetBackgroundTexture(); + Rect texCoordsRect = new Rect(0, 0, r2.width / backgroundTexture.width, r2.height / backgroundTexture.height); + GUI.DrawTextureWithTexCoords(r2, backgroundTexture, texCoordsRect, false); + + TextureWrapMode wrap_mode = data.PreviewTexture.wrapMode; + data.PreviewTexture.wrapMode = TextureWrapMode.Clamp; + GUI.DrawTexture(r2, data.PreviewTexture, ScaleMode.StretchToFill, true); + GUI.DrawTexture(gradient_texture_position, data.PreviewTexture, ScaleMode.StretchToFill, false, 0, Color.grey, 1, 1); + data.PreviewTexture.wrapMode = wrap_mode; + } + + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/GradientEditor2.cs b/Scripts/ThryEditor/Editor/GradientEditor2.cs new file mode 100644 index 0000000..52118e0 --- /dev/null +++ b/Scripts/ThryEditor/Editor/GradientEditor2.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class GradientEditor2 : EditorWindow + { + private Gradient _gradient; + private bool _allowSizeSelection; + private Vector2Int _textureSizeMin; + private Vector2Int _textureSizeMax; + private Vector2Int _textureSize; + private bool _makeTextureVertical; + private Action _onGradientChanged; + private object _gradientEditor; + private object _gradientLibary; + + public static void Open(Gradient gradient, Action onGradientChanged, bool textureVertical, bool allowSizeSelection, Vector2Int minTextureSize, Vector2Int maxTextureSize) + { + var window = GetWindow(); + window._gradient = gradient; + window._allowSizeSelection = allowSizeSelection; + window._textureSizeMin = minTextureSize; + window._textureSizeMax = maxTextureSize; + window._textureSize = minTextureSize; + window._makeTextureVertical = textureVertical; + window._onGradientChanged = onGradientChanged; + window.titleContent = new GUIContent("Gradient Editor"); + // show in center of screen + float width = 500; + float height = 400; + window.position = new Rect(Screen.width / 2 - width / 2, Screen.height / 2 - height / 2, width, height); + window.Show(); + } + + static MethodInfo s_gradientEditorGUIMethodInfo = null; + static MethodInfo GradientEditorGUI { + get + { + if(s_gradientEditorGUIMethodInfo == null) + { + Type gradient_editor_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GradientEditor"); + s_gradientEditorGUIMethodInfo = gradient_editor_type.GetMethod("OnGUI"); + } + return s_gradientEditorGUIMethodInfo; + } + } + + static MethodInfo s_presetLibraryOnGUIMethodInfo = null; + static MethodInfo PresetLibraryOnGUI + { + get + { + if (s_presetLibraryOnGUIMethodInfo == null) + { + Type gradient_preset_libary_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GradientPresetLibrary"); + Type preset_libary_editor_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PresetLibraryEditor`1"); + Type gradient_preset_libary_editor_type = preset_libary_editor_type.MakeGenericType(gradient_preset_libary_type); + s_presetLibraryOnGUIMethodInfo = gradient_preset_libary_editor_type.GetMethod("OnGUI"); + } + return s_presetLibraryOnGUIMethodInfo; + } + } + + static object GetGradientEditor(Gradient gradient) + { + Type gradientEditorType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GradientEditor"); + var gradientEditor = Activator.CreateInstance(gradientEditorType); + SetGradient(gradientEditor, gradient); + return gradientEditor; + } + + static void SetGradient(object gradientEditor, Gradient gradient) + { + Type gradientEditorType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GradientEditor"); + var gradientEditorInit = gradientEditorType.GetMethod("Init"); + +#if UNITY_2020_1_OR_NEWER + gradientEditorInit.Invoke(gradientEditor, new object[] { gradient, 0, true, ColorSpace.Linear }); +#else + gradientEditorInit.Invoke(gradientEditor, new object[] { gradient, 0, true }); +#endif + } + + public static object GetGradientLibary(Action presetSelectedCallback) + { + Type presetLibraryEditorState_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PresetLibraryEditorState"); + var preset_libary_editor_state = Activator.CreateInstance(presetLibraryEditorState_type, "Gradient"); + MethodInfo transfer_editor_prefs_state = presetLibraryEditorState_type.GetMethod("TransferEditorPrefsState"); + transfer_editor_prefs_state.Invoke(preset_libary_editor_state, new object[] { true }); + + Type scriptable_save_load_helper_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ScriptableObjectSaveLoadHelper`1"); + Type gradient_preset_libary_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.GradientPresetLibrary"); + Type preset_libary_editor_type = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PresetLibraryEditor`1"); + Type save_load_helper_type = scriptable_save_load_helper_type.MakeGenericType(gradient_preset_libary_type); + Type gradient_preset_libary_editor_type = preset_libary_editor_type.MakeGenericType(gradient_preset_libary_type); + + object saveLoadHelper = Activator.CreateInstance(save_load_helper_type, "gradients", SaveType.Text); + + var preset_libary_editor = Activator.CreateInstance(gradient_preset_libary_editor_type, saveLoadHelper, preset_libary_editor_state, presetSelectedCallback); + PropertyInfo show_header = gradient_preset_libary_editor_type.GetProperty("showHeader"); + show_header.SetValue(preset_libary_editor, true, null); + PropertyInfo minMaxPreviewHeight = gradient_preset_libary_editor_type.GetProperty("minMaxPreviewHeight"); + minMaxPreviewHeight.SetValue(preset_libary_editor, new Vector2(14f, 14f), null); + + return preset_libary_editor; + } + + private void OnGUI() + { + if(_gradientEditor == null) _gradientEditor = GetGradientEditor(_gradient); + if(_gradientLibary == null) _gradientLibary = GetGradientLibary(PresetHasBeenSelected); + + int settingsHeight = 70; + if(_allowSizeSelection) settingsHeight += 50; + + Rect mainUIRect = new Rect(20, 20, position.width - 40, position.height - settingsHeight); + Rect gradientRect = new Rect(mainUIRect.x, mainUIRect.y, mainUIRect.width, mainUIRect.height * 0.6f); + Rect presetRect = new Rect(mainUIRect.x, gradientRect.yMax + 10, mainUIRect.width, mainUIRect.height * 0.4f - 10); + + GradientEditorGUI.Invoke(_gradientEditor, new object[] { gradientRect }); + PresetLibraryOnGUI.Invoke(_gradientLibary, new object[] { presetRect, _gradient }); + + Rect settingsRect = new Rect(20, position.height - settingsHeight, position.width - 40, settingsHeight); + Rect buttonRect = new Rect(settingsRect.x + 20, settingsRect.yMax - 40, settingsRect.width - 20, 30); + if(_allowSizeSelection) + { + Rect sizeRect = new Rect(settingsRect.x + 20, settingsRect.y + 30, settingsRect.width - 20, 30); + _textureSize = EditorGUI.Vector2IntField(sizeRect, "Texture Size", _textureSize); + } + if (GUI.Button(buttonRect, "Apply")) + { + Apply(); + } + } + + private void PresetHasBeenSelected(int index, object preset) + { + Gradient gradient = preset as Gradient; + if (gradient == null) + Debug.LogError("Incorrect object passed " + preset); + // copy the data from the preset to the gradient + _gradient.SetKeys(gradient.colorKeys, gradient.alphaKeys); + _gradient.mode = gradient.mode; + SetGradient(_gradientEditor, _gradient); + Apply(); + } + + private void OnDestroy() + { + Apply(); + } + + void Apply() + { + Texture2D gradientTexture = Converter.GradientToTexture(_gradient, _textureSize.x, _textureSize.y, _makeTextureVertical); + _onGradientChanged?.Invoke(_gradient, gradientTexture); + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Helper.cs b/Scripts/ThryEditor/Editor/Helper.cs new file mode 100644 index 0000000..3fe0d07 --- /dev/null +++ b/Scripts/ThryEditor/Editor/Helper.cs @@ -0,0 +1,1949 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; +using UnityEngine.Networking; +using UnityEngine.Profiling; + +namespace Thry +{ + static class StringExtensions + { + public static string ReplaceVariables(this string s, params object[] values) + { + for(int i = 0; i < values.Length;i++) + { + s = s.Replace("{" + i + "}", values[i].ToString()); + } + return s; + } + } + + public class Helper + { + static bool s_didTryRegsiterThisSession = false; + + public static bool ClassWithNamespaceExists(string classname) + { + return (from assembly in AppDomain.CurrentDomain.GetAssemblies() + from type in assembly.GetTypes() + where type.FullName == classname + select type).Count() > 0; + } + + public static Type FindTypeByFullName(string fullname) + { + return (from assembly in AppDomain.CurrentDomain.GetAssemblies() + from type in assembly.GetTypes() + where type.FullName == fullname + select type).FirstOrDefault(); + } + + private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static long GetCurrentUnixTimestampMillis() + { + return (long)(DateTime.UtcNow - UnixEpoch).TotalMilliseconds; + } + + public static long DatetimeToUnixSeconds(DateTime time) + { + return (long)(time - UnixEpoch).TotalSeconds; + } + + public static long GetUnityStartUpTimeStamp() + { + return GetCurrentUnixTimestampMillis() - (long)EditorApplication.timeSinceStartup * 1000; + } + + public static void RegisterEditorUse() + { + if (s_didTryRegsiterThisSession) return; + if (!EditorPrefs.GetBool("thry_has_counted_user", false)) + { + WebHelper.DownloadStringASync(URL.COUNT_USER, delegate (string s) + { + if (s == "true") + EditorPrefs.SetBool("thry_has_counted_user", true); + }); + } + + string projectPrefix = PlayerSettings.companyName + "." + PlayerSettings.productName; + if (!EditorPrefs.GetBool(projectPrefix + "_thry_has_counted_project", false)) + { + WebHelper.DownloadStringASync(URL.COUNT_PROJECT, delegate (string s) + { + if (s == "true") + EditorPrefs.SetBool(projectPrefix + "_thry_has_counted_project", true); + }); + } + s_didTryRegsiterThisSession = true; + } + + //-------------------Comparetors---------------------- + + public static int CompareVersions(string v1, string v2) + { + //fix the string + v1 = v1.Replace(",", "."); + v2 = v2.Replace(",", "."); + Match v1_match = Regex.Match(v1, @"(a|b)?\d+((\.|a|b)\d+)*(a|b)?"); + Match v2_match = Regex.Match(v2, @"(a|b)?\d+((\.|a|b)\d+)*(a|b)?"); + if (!v1_match.Success && !v2_match.Success) return 0; + else if (!v1_match.Success) return 1; + else if (!v2_match.Success) return -1; + v1 = v1_match.Value; + v2 = v2_match.Value; + + int index_v1 = 0; + int index_v2 = 0; + string chunk_v1; + string chunk_v2; + while (index_v1 < v1.Length || index_v2 < v2.Length) + { + //get a chunk of the strings + if (index_v1 < v1.Length) + { + chunk_v1 = ""; + if (v1[index_v1] == 'a') + chunk_v1 = "-2"; + else if (v1[index_v1] == 'b') + chunk_v1 = "-1"; + else + { + while (index_v1 < v1.Length && v1[index_v1] != 'a' && v1[index_v1] != 'b' && v1[index_v1] != '.') + chunk_v1 += v1[index_v1++]; + if (index_v1 < v1.Length && (v1[index_v1] == 'a' || v1[index_v1] == 'b')) + index_v1--; + } + index_v1++; + } + else + chunk_v1 = "0"; + + if (index_v2 < v2.Length) + { + chunk_v2 = ""; + if (v2[index_v2] == 'a') + chunk_v2 = "-2"; + else if (v2[index_v2] == 'b') + chunk_v2 = "-1"; + else + { + while (index_v2 < v2.Length && v2[index_v2] != 'a' && v2[index_v2] != 'b' && v2[index_v2] != '.') + chunk_v2 += v2[index_v2++]; + if (index_v2 < v2.Length && (v2[index_v2] == 'a' || v2[index_v2] == 'b')) + index_v2--; + } + index_v2++; + } + else + chunk_v2 = "0"; + + //compare chunks + int v1P = int.Parse(chunk_v1); + int v2P = int.Parse(chunk_v2); + if (v1P > v2P) return -1; + else if (v1P < v2P) return 1; + } + return 0; + } + + public static bool IsPrimitive(Type t) + { + return t.IsPrimitive || t == typeof(Decimal) || t == typeof(String); + } + + public static string GetStringBetweenBracketsAndAfterId(string input, string id, char[] brackets) + { + string[] parts = Regex.Split(input, id); + if (parts.Length > 1) + { + char[] behind_id = parts[1].ToCharArray(); + int i = 0; + int begin = 0; + int end = behind_id.Length - 1; + int depth = 0; + bool escaped = false; + while (i < behind_id.Length) + { + if (behind_id[i] == brackets[0] && !escaped) + { + if (depth == 0) + begin = i; + depth++; + } + else if (behind_id[i] == brackets[1] && !escaped) + { + depth--; + if (depth == 0) + { + end = i; + break; + } + } + + if (behind_id[i] == '\\') + escaped = !escaped; + else + escaped = false; + i++; + } + return parts[1].Substring(begin, end); + } + return input; + } + + public static float SolveMath(string exp, float parameter) + { + exp = exp.Replace("x", parameter.ToString(CultureInfo.InvariantCulture)); + exp = exp.Replace(" ", ""); + float f; + if (ExpressionEvaluator.Evaluate(exp, out f)) return f; + return 0; + } + + public static float Mod(float a, float b) + { + return a - b * Mathf.Floor(a / b); + } + + // This code is an implementation of the pseudocode from the Wikipedia, + // showing a naive implementation. + // You should research an algorithm with better space complexity. + public static int LevenshteinDistance(string s, string t) + { + int n = s.Length; + int m = t.Length; + int[,] d = new int[n + 1, m + 1]; + if (n == 0) + { + return m; + } + if (m == 0) + { + return n; + } + for (int i = 0; i <= n; d[i, 0] = i++) + ; + for (int j = 0; j <= m; d[0, j] = j++) + ; + for (int i = 1; i <= n; i++) + { + for (int j = 1; j <= m; j++) + { + int cost = (t[j - 1] == s[i - 1]) ? 0 : 1; + d[i, j] = Math.Min( + Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), + d[i - 1, j - 1] + cost); + } + } + return d[n, m]; + } + + // Start of Detour methods + // Modified from: https://github.com/apkd/UnityStaticBatchingSortingPatch/blob/e83bed8cf31fc98097586c4e47af77fa79d9bed5/StaticBatchingSortingPatch.cs + // Modified by Behemoth/hill + static Dictionary s_patchedData = new Dictionary(); + public static unsafe void TryDetourFromTo(MethodInfo src, MethodInfo dst) + { + try + { + if (IntPtr.Size == sizeof(Int64)) + { + // 64-bit systems use 64-bit absolute address and jumps + // 12 byte destructive + + // Get function pointers + long Source_Base = src .MethodHandle.GetFunctionPointer().ToInt64(); + long Destination_Base = dst.MethodHandle.GetFunctionPointer().ToInt64(); + + // Backup Source Data + IntPtr Source_IntPtr = src.MethodHandle.GetFunctionPointer(); + var backup = new byte[0xC]; + Marshal.Copy(Source_IntPtr, backup, 0, 0xC); + s_patchedData.Add(src, backup); + + // Native source address + byte* Pointer_Raw_Source = (byte*)Source_Base; + + // Pointer to insert jump address into native code + long* Pointer_Raw_Address = (long*)( Pointer_Raw_Source + 0x02 ); + + // Insert 64-bit absolute jump into native code (address in rax) + // mov rax, immediate64 + // jmp [rax] + *( Pointer_Raw_Source + 0x00 ) = 0x48; + *( Pointer_Raw_Source + 0x01 ) = 0xB8; + *Pointer_Raw_Address = Destination_Base; // ( Pointer_Raw_Source + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 ) + *( Pointer_Raw_Source + 0x0A ) = 0xFF; + *( Pointer_Raw_Source + 0x0B ) = 0xE0; + } + else + { + // 32-bit systems use 32-bit relative offset and jump + // 5 byte destructive + + // Get function pointers + int Source_Base = src .MethodHandle.GetFunctionPointer().ToInt32(); + int Destination_Base = dst.MethodHandle.GetFunctionPointer().ToInt32(); + + // Backup Source Data + IntPtr Source_IntPtr = src.MethodHandle.GetFunctionPointer(); + var backup = new byte[0x5]; + Marshal.Copy(Source_IntPtr, backup, 0, 0x5); + s_patchedData.Add(src, backup); + + // Native source address + byte* Pointer_Raw_Source = (byte*)Source_Base; + + // Pointer to insert jump address into native code + int* Pointer_Raw_Address = (int*)( Pointer_Raw_Source + 1 ); + + // Jump offset (less instruction size) + int offset = ( Destination_Base - Source_Base ) - 5; + + // Insert 32-bit relative jump into native code + *Pointer_Raw_Source = 0xE9; + *Pointer_Raw_Address = offset; + } + } + catch (Exception ex) + { + Debug.LogError($"Unable to detour: {src?.Name ?? "UnknownSrc"} -> {dst?.Name ?? "UnknownDst"}\n{ex}"); + throw; + } + } + + public static unsafe void RestoreDetour(MethodInfo src) { + var Source_IntPtr = src.MethodHandle.GetFunctionPointer(); + var backup = s_patchedData[src]; + Marshal.Copy(backup, 0, Source_IntPtr, backup.Length); + s_patchedData.Remove(src); + } + // End of Detour Methods + } + + public class PersistentData + { + public static string Get(string key) + { + return FileHelper.LoadValueFromFile(key, PATH.PERSISTENT_DATA); + } + + public static void Set(string key, string value) + { + FileHelper.SaveValueToFile(key, value, PATH.PERSISTENT_DATA); + } + + public static T Get(string key, T defaultValue) + { + string s = FileHelper.LoadValueFromFile(key, PATH.PERSISTENT_DATA); + if (string.IsNullOrEmpty(s)) return defaultValue; + T obj = Parser.Deserialize(s); + if (obj == null) return defaultValue; + return obj; + } + + public static void Set(string key, object value) + { + FileHelper.SaveValueToFile(key, Parser.Serialize(value), PATH.PERSISTENT_DATA); + } + } + + public class FileHelper + { + public static string FindFile(string name, string type=null) + { + string[] guids; + if (type != null) + guids = AssetDatabase.FindAssets(name + " t:" + type); + else + guids = AssetDatabase.FindAssets(name); + if (guids.Length == 0) + return null; + return AssetDatabase.GUIDToAssetPath(guids[0]); + } + + //-----------------------Value To File Saver---------------------- + + private static Dictionary> s_textFileData = new Dictionary>(); + + public static string LoadValueFromFile(string key, string path) + { + if (!s_textFileData.ContainsKey(path)) ReadFileIntoTextFileData(path); + if (s_textFileData[path].ContainsKey(key)) + return s_textFileData[path][key]; + return null; + } + + private static void ReadFileIntoTextFileData(string path) + { + string data = ReadFileIntoString(path); + Dictionary dictionary = new Dictionary(); + MatchCollection matchCollection = Regex.Matches(data, @".*\s*:=.*(?=\r?\n)"); + foreach(Match m in matchCollection) + { + string[] keyvalue = m.Value.Split(new string[] { ":=" }, 2, StringSplitOptions.RemoveEmptyEntries); + if(keyvalue.Length>1) + dictionary[keyvalue[0]] = keyvalue[1]; + } + s_textFileData[path] = dictionary; + } + + public static bool SaveValueToFile(string key, string value, string path) + { + if (!s_textFileData.ContainsKey(path)) ReadFileIntoTextFileData(path); + s_textFileData[path][key] = value; + return SaveDictionaryToFile(path, s_textFileData[path]); + } + + public static void RemoveValueFromFile(string key, string path) + { + if (!s_textFileData.ContainsKey(path)) ReadFileIntoTextFileData(path); + if (s_textFileData[path].ContainsKey(key)) s_textFileData[path].Remove(key); + } + + private static bool SaveDictionaryToFile(string path, Dictionary dictionary) + { + s_textFileData[path] = dictionary; + string data = s_textFileData[path].Aggregate("", (d1, d2) => d1 + d2.Key + ":=" + d2.Value + "\n"); + WriteStringToFile(data, path); + return true; + } + + //-----------------------File Interaction--------------------- + + public static string FindFileAndReadIntoString(string fileName) + { + string[] guids = AssetDatabase.FindAssets(fileName); + if (guids.Length > 0) + return ReadFileIntoString(AssetDatabase.GUIDToAssetPath(guids[0])); + else return ""; + } + + public static void FindFileAndWriteString(string fileName, string s) + { + string[] guids = AssetDatabase.FindAssets(fileName); + if (guids.Length > 0) + WriteStringToFile(s, AssetDatabase.GUIDToAssetPath(guids[0])); + } + + public static string ReadFileIntoString(string path) + { + if (!File.Exists(path)) + { + CreateFileWithDirectories(path); + return ""; + } + StreamReader reader = new StreamReader(path); + string ret = reader.ReadToEnd(); + reader.Close(); + return ret; + } + + public static void WriteStringToFile(string s, string path) + { + if (!File.Exists(path)) CreateFileWithDirectories(path); + StreamWriter writer = new StreamWriter(path, false); + writer.Write(s); + writer.Close(); + } + + public static bool WriteBytesToFile(byte[] bytes, string path) + { + if (!File.Exists(path)) CreateFileWithDirectories(path); + try + { + using (var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write)) + { + fs.Write(bytes, 0, bytes.Length); + return true; + } + } + catch (Exception ex) + { + Debug.Log("Exception caught in process: " + ex.ToString()); + return false; + } + } + + public static void CreateFileWithDirectories(string path) + { + string dir_path = Path.GetDirectoryName(path); + if (dir_path != "") + Directory.CreateDirectory(dir_path); + File.Create(path).Close(); + } + } + + public class TrashHandler + { + public static void EmptyThryTrash() + { + if (Directory.Exists(PATH.DELETING_DIR)) + { + DeleteDirectory(PATH.DELETING_DIR); + } + } + + public static void MoveDirectoryToTrash(string path) + { + string name = Path.GetFileName(path); + if (!Directory.Exists(PATH.DELETING_DIR)) + Directory.CreateDirectory(PATH.DELETING_DIR); + int i = 0; + string newpath = PATH.DELETING_DIR + "/" + name + i; + while (Directory.Exists(newpath)) + newpath = PATH.DELETING_DIR + "/" + name + (++i); + Directory.Move(path, newpath); + } + + static void DeleteDirectory(string path) + { + foreach (string f in Directory.GetFiles(path)) + DeleteFile(f); + foreach (string d in Directory.GetDirectories(path)) + DeleteDirectory(d); + if (Directory.GetFiles(path).Length + Directory.GetDirectories(path).Length == 0) + Directory.Delete(path); + } + static void DeleteFile(string path) + { + try + { + File.Delete(path); + } + catch (Exception e) + { + e.GetType(); + } + } + } + + public class TextureHelper + { + public static Gradient GetGradient(Texture texture) + { + if (texture != null) + { + string path = AssetDatabase.GetAssetPath(texture); + string gradient_data_string = null; + if(path != null) gradient_data_string = FileHelper.LoadValueFromFile(AssetDatabase.AssetPathToGUID(path), PATH.GRADIENT_INFO_FILE); + //For Backwards compatibility check old id (name) if guid cant be found + if(gradient_data_string == null) gradient_data_string = FileHelper.LoadValueFromFile(texture.name, PATH.GRADIENT_INFO_FILE); + if (gradient_data_string != null) + { + Debug.Log(texture.name + " Gradient loaded from file."); + Gradient g = Parser.Deserialize(gradient_data_string); + return g; + } + Debug.Log(texture.name + " Converted into Gradient."); + return Converter.TextureToGradient(GetReadableTexture(texture)); + } + return new Gradient(); + } + + private static Texture2D s_BackgroundTexture; + + public static Texture2D GetBackgroundTexture() + { + if (s_BackgroundTexture == null) + s_BackgroundTexture = CreateCheckerTexture(32, 4, 4, Color.white, new Color(0.7f, 0.7f, 0.7f)); + return s_BackgroundTexture; + } + + public static Texture2D CreateCheckerTexture(int numCols, int numRows, int cellPixelWidth, Color col1, Color col2) + { + int height = numRows * cellPixelWidth; + int width = numCols * cellPixelWidth; + + Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false); + texture.hideFlags = HideFlags.HideAndDontSave; + Color[] pixels = new Color[width * height]; + + for (int i = 0; i < numRows; i++) + for (int j = 0; j < numCols; j++) + for (int ci = 0; ci < cellPixelWidth; ci++) + for (int cj = 0; cj < cellPixelWidth; cj++) + pixels[(i * cellPixelWidth + ci) * width + j * cellPixelWidth + cj] = ((i + j) % 2 == 0) ? col1 : col2; + + texture.SetPixels(pixels); + texture.Apply(); + return texture; + } + + public static Texture SaveTextureAsPNG(Texture2D texture, string path, TextureData settings = null) + { + if (!path.EndsWith(".png")) + path += ".png"; + byte[] encoding = texture.EncodeToPNG(); + Debug.Log("Texture saved at \"" + path + "\"."); + FileHelper.WriteBytesToFile(encoding, path); + + AssetDatabase.ImportAsset(path); + if (settings != null) + settings.ApplyModes(path); + Texture saved = AssetDatabase.LoadAssetAtPath(path); + return saved; + } + + public static void MakeTextureReadible(string path) + { + TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(path); + if (!importer.isReadable) + { + importer.isReadable = true; + importer.SaveAndReimport(); + } + } + + public static Texture2D GetReadableTexture(Texture texture) + { + RenderTexture temp = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear); + Graphics.Blit(texture, temp); + RenderTexture previous = RenderTexture.active; + RenderTexture.active = temp; + Texture2D ret = new Texture2D(texture.width, texture.height); + ret.ReadPixels(new Rect(0, 0, temp.width, temp.height), 0, 0); + ret.Apply(); + RenderTexture.active = previous; + RenderTexture.ReleaseTemporary(temp); + return ret; + } + + public static Texture2D Resize(Texture2D texture, int width, int height) + { + Texture2D ret = new Texture2D(width, height, texture.format, texture.mipmapCount > 0); + float scaleX = ((float)texture.width) / width; + float scaleY = ((float)texture.height) / height; + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + ret.SetPixel(x, y, texture.GetPixel((int)(scaleX * x), (int)(scaleY * y))); + } + } + ret.Apply(); + return ret; + } + + //===============TGA Loader by aaro4130 https://forum.unity.com/threads/tga-loader-for-unity3d.172291/============== + + public static Texture2D LoadTGA(string TGAFile, bool displayProgressbar = false) + { + using (BinaryReader r = new BinaryReader(File.Open(TGAFile, FileMode.Open))) + { + byte IDLength = r.ReadByte(); + byte ColorMapType = r.ReadByte(); + byte ImageType = r.ReadByte(); + Int16 CMapStart = r.ReadInt16(); + Int16 CMapLength = r.ReadInt16(); + byte CMapDepth = r.ReadByte(); + Int16 XOffset = r.ReadInt16(); + Int16 YOffset = r.ReadInt16(); + Int16 Width = r.ReadInt16(); + Int16 Height = r.ReadInt16(); + byte PixelDepth = r.ReadByte(); + byte ImageDescriptor = r.ReadByte(); + if (ImageType == 0) + { + EditorUtility.DisplayDialog("Error", "Unsupported TGA file! No image data", "OK"); + Debug.LogError("Unsupported TGA file! No image data"); + } + else if (ImageType == 3 | ImageType == 11) + { + EditorUtility.DisplayDialog("Error", "Unsupported TGA file! 8-bit grayscale images are not supported", "OK"); + Debug.LogError("Unsupported TGA file! Not truecolor"); + } + else if (ImageType == 9 | ImageType == 10) + { + EditorUtility.DisplayDialog("Error", "Unsupported TGA file! Run-length encoded images are not supported", "OK"); + Debug.LogError("Unsupported TGA file! Colormapped"); + + } + bool startsAtTop = (ImageDescriptor & 1 << 5) >> 5 == 1; + bool startsAtRight = (ImageDescriptor & 1 << 4) >> 4 == 1; + // MsgBox("Dimensions are " Width "," Height) + Texture2D b = new Texture2D(Width, Height, TextureFormat.ARGB32, false); + Color[] colors = new Color[Width * Height]; + int texX = 0; + int texY = 0; + int index = 0; + float red = 0, green = 0, blue = 0, alpha = 0; + Byte[] bytes = r.ReadBytes((PixelDepth == 32 ? 4 : 3) * Width * Height); + + int byteIndex = 0; + for (int y = 0; y < b.height; y++) + { + if(displayProgressbar && y % 50 == 0) EditorUtility.DisplayProgressBar("Loading Raw TGA", "Loading " + TGAFile, (float)y / b.height); + for (int x = 0; x < b.width; x++) + { + texX = x; + texY = y; + if(startsAtRight) texX = b.width - x - 1; + if(startsAtTop) texY = b.height - y - 1; + index = texX + texY * b.width; + + blue = Convert.ToSingle(bytes[byteIndex++]); + green = Convert.ToSingle(bytes[byteIndex++]); + red = Convert.ToSingle(bytes[byteIndex++]); + + blue = Mathf.Pow(blue / 255, 0.45454545454f); + green = Mathf.Pow(green / 255, 0.45454545454f); + red = Mathf.Pow(red / 255, 0.45454545454f); + + // blue /= 255; + // green /= 255; + // red /= 255; + + colors[index].r = red; + colors[index].g = green; + colors[index].b = blue; + + if (PixelDepth == 32) + { + alpha = Convert.ToSingle(bytes[byteIndex++]); + alpha /= 255; + colors[index].a = alpha; + } + else + { + colors[index].a = 1; + } + } + } + b.SetPixels(colors); + b.Apply(); + if(displayProgressbar) EditorUtility.ClearProgressBar(); + + return b; + } + } + + public class VRAM + { + static Dictionary BPP = new Dictionary() + { + { TextureImporterFormat.BC7 , 8 }, + { TextureImporterFormat.DXT5 , 8 }, + { TextureImporterFormat.DXT5Crunched , 8 }, + { TextureImporterFormat.RGBA32 , 32 }, + { TextureImporterFormat.RGBA16 , 16 }, + { TextureImporterFormat.DXT1 , 4 }, + { TextureImporterFormat.DXT1Crunched , 4 }, + { TextureImporterFormat.RGB24 , 32 }, + { TextureImporterFormat.RGB16 , 16 }, + { TextureImporterFormat.BC5 , 8 }, + { TextureImporterFormat.BC4 , 4 }, + { TextureImporterFormat.R8 , 8 }, + { TextureImporterFormat.R16 , 16 }, + { TextureImporterFormat.Alpha8 , 8 }, + { TextureImporterFormat.RGBAHalf , 64 }, + { TextureImporterFormat.BC6H , 8 }, + { TextureImporterFormat.RGB9E5 , 32 }, + { TextureImporterFormat.ETC2_RGBA8Crunched , 8 }, + { TextureImporterFormat.ETC2_RGB4 , 4 }, + { TextureImporterFormat.ETC2_RGBA8 , 8 }, + { TextureImporterFormat.ETC2_RGB4_PUNCHTHROUGH_ALPHA , 4 }, + { TextureImporterFormat.PVRTC_RGB2 , 2 }, + { TextureImporterFormat.PVRTC_RGB4 , 4 }, + { TextureImporterFormat.ARGB32 , 32 }, + { TextureImporterFormat.ARGB16 , 16 }, + #if (UNITY_2020_1_OR_NEWER || UNITY_2019_4_23 || UNITY_2019_4_24 || UNITY_2019_4_25 || UNITY_2019_4_26 || UNITY_2019_4_27 || UNITY_2019_4_28 || UNITY_2019_4_29 || UNITY_2019_4_30 || UNITY_2019_4_31 || UNITY_2019_4_32 || UNITY_2019_4_33 || UNITY_2019_4_34 || UNITY_2019_4_35 || UNITY_2019_4_36 || UNITY_2019_4_37 || UNITY_2019_4_38 || UNITY_2019_4_39 || UNITY_2019_4_40) + { TextureImporterFormat.RGBA64 , 64 }, + { TextureImporterFormat.RGB48 , 64 }, + { TextureImporterFormat.RG32 , 32 }, + #endif + }; + + static Dictionary RT_BPP = new Dictionary() + { + { RenderTextureFormat.ARGB32 , 32 }, + { RenderTextureFormat.Depth , 0 }, + { RenderTextureFormat.ARGBHalf , 64 }, + { RenderTextureFormat.Shadowmap , 8 }, //guessed bpp + { RenderTextureFormat.RGB565 , 32 }, //guessed bpp + { RenderTextureFormat.ARGB4444 , 16 }, + { RenderTextureFormat.ARGB1555 , 16 }, + { RenderTextureFormat.Default , 32 }, + { RenderTextureFormat.ARGB2101010 , 32 }, + { RenderTextureFormat.DefaultHDR , 128 }, + { RenderTextureFormat.ARGB64 , 64 }, + { RenderTextureFormat.ARGBFloat , 128 }, + { RenderTextureFormat.RGFloat , 64 }, + { RenderTextureFormat.RGHalf , 32 }, + { RenderTextureFormat.RFloat , 32 }, + { RenderTextureFormat.RHalf , 16 }, + { RenderTextureFormat.R8 , 8 }, + { RenderTextureFormat.ARGBInt , 128 }, + { RenderTextureFormat.RGInt , 64 }, + { RenderTextureFormat.RInt , 32 }, + { RenderTextureFormat.BGRA32 , 32 }, + { RenderTextureFormat.RGB111110Float , 32 }, + { RenderTextureFormat.RG32 , 32 }, + { RenderTextureFormat.RGBAUShort , 64 }, + { RenderTextureFormat.RG16 , 16 }, + { RenderTextureFormat.BGRA10101010_XR , 40 }, + { RenderTextureFormat.BGR101010_XR , 30 }, + { RenderTextureFormat.R16 , 16 } + }; + + public static string ToByteString(long l) + { + if (l < 1000) return l + " B"; + if (l < 1000000) return (l / 1000f).ToString("n2") + " KB"; + if (l < 1000000000) return (l / 1000000f).ToString("n2") + " MB"; + else return (l / 1000000000f).ToString("n2") + " GB"; + } + + public static (long size, string format) CalcSize(Texture t) + { + string add = ""; + long bytesCount = 0; + + string path = AssetDatabase.GetAssetPath(t); + if (t != null && path != null && t is RenderTexture == false && t.dimension == UnityEngine.Rendering.TextureDimension.Tex2D) + { + AssetImporter assetImporter = AssetImporter.GetAtPath(path); + if (assetImporter is TextureImporter) + { + TextureImporter textureImporter = (TextureImporter)assetImporter; + TextureImporterFormat textureFormat = textureImporter.GetPlatformTextureSettings("PC").format; +#pragma warning disable CS0618 + if (textureFormat == TextureImporterFormat.AutomaticCompressed) textureFormat = textureImporter.GetAutomaticFormat("PC"); +#pragma warning restore CS0618 + + if (BPP.ContainsKey(textureFormat)) + { + add = textureFormat.ToString(); + double mipmaps = 1; + for (int i = 0; i < t.mipmapCount; i++) mipmaps += Math.Pow(0.25, i + 1); + bytesCount = (long)(BPP[textureFormat] * t.width * t.height * (textureImporter.mipmapEnabled ? mipmaps : 1) / 8); + //Debug.Log(bytesCount); + } + else + { + Debug.LogWarning("[Thry][VRAM] Does not have BPP for " + textureFormat); + } + } + else + { + bytesCount = Profiler.GetRuntimeMemorySizeLong(t); + } + } + else if (t is RenderTexture) + { + RenderTexture rt = t as RenderTexture; + double mipmaps = 1; + for (int i = 0; i < rt.mipmapCount; i++) mipmaps += Math.Pow(0.25, i + 1); + bytesCount = (long)((RT_BPP[rt.format] + rt.depth) * rt.width * rt.height * (rt.useMipMap ? mipmaps : 1) / 8); + } + else + { + bytesCount = Profiler.GetRuntimeMemorySizeLong(t); + } + + return (bytesCount, add); + } + } + } + + public class MaterialHelper + { + public static void ToggleKeyword(Material material, string keyword, bool turn_on) + { + bool is_on = material.IsKeywordEnabled(keyword); + if (is_on && !turn_on) + material.DisableKeyword(keyword); + else if (!is_on && turn_on) + material.EnableKeyword(keyword); + } + + public static void ToggleKeyword(Material[] materials, string keyword, bool on) + { + foreach (Material m in materials) + ToggleKeyword(m, keyword, on); + } + + public static void ToggleKeyword(MaterialProperty p, string keyword, bool on) + { + ToggleKeyword(p.targets as Material[], keyword, on); + } + + /// + /// Set Material Property value or Renderqueue of current Editor. + /// + /// Property Name or "render_queue" + /// + public static void SetMaterialValue(string key, string value) + { + Material[] materials = ShaderEditor.Active.Materials; + if (ShaderEditor.Active.PropertyDictionary.TryGetValue(key, out ShaderProperty p)) + { + MaterialHelper.SetMaterialPropertyValue(p.MaterialProperty, value); + p.UpdateKeywordFromValue(); + } + else if (key == "render_queue") + { + int q = 0; + if (int.TryParse(value, out q)) + { + foreach (Material m in materials) m.renderQueue = q; + } + } + else if (key == "render_type") + { + foreach (Material m in materials) m.SetOverrideTag("RenderType", value); + } + else if (key == "preview_type") + { + foreach (Material m in materials) m.SetOverrideTag("PreviewType", value); + } + else if (key == "ignore_projector") + { + foreach (Material m in materials) m.SetOverrideTag("IgnoreProjector", value); + } + } + + public static void SetMaterialPropertyValue(MaterialProperty p, string value) + { + object prev = null; + if (p.type == MaterialProperty.PropType.Texture) + { + prev = p.textureValue; + p.textureValue = AssetDatabase.LoadAssetAtPath(value); + } + else if (p.type == MaterialProperty.PropType.Float || p.type == MaterialProperty.PropType.Range) + { + prev = p.floatValue; + p.floatValue = Parser.ParseFloat(value, p.floatValue); + } + else if (p.type == MaterialProperty.PropType.Vector) + { + prev = p.vectorValue; + p.vectorValue = Converter.StringToVector(value); + } + else if (p.type == MaterialProperty.PropType.Color) + { + prev = p.colorValue; + p.colorValue = Converter.StringToColor(value); + } + if (p.applyPropertyCallback != null) + p.applyPropertyCallback.Invoke(p, 1, prev); + } + + public static void CopyPropertyValueFromMaterial(MaterialProperty p, Material source) + { + if (!source.HasProperty(p.name)) return; + object prev = null; + switch (p.type) + { + case MaterialProperty.PropType.Float: + case MaterialProperty.PropType.Range: + prev = p.floatValue; + p.floatValue = source.GetFloat(p.name); + break; + case MaterialProperty.PropType.Color: + prev = p.colorValue; + p.colorValue = source.GetColor(p.name); + break; + case MaterialProperty.PropType.Vector: + prev = p.vectorValue; + p.vectorValue = source.GetVector(p.name); + break; + case MaterialProperty.PropType.Texture: + prev = p.textureValue; + p.textureValue = source.GetTexture(p.name); + Vector2 offset = source.GetTextureOffset(p.name); + Vector2 scale = source.GetTextureScale(p.name); + p.textureScaleAndOffset = new Vector4(scale.x, scale.y, offset.x, offset.y); + break; + } + if (p.applyPropertyCallback != null) + p.applyPropertyCallback.Invoke(p, 1, prev); + } + + public static void CopyMaterialValueFromProperty(MaterialProperty target, MaterialProperty source) + { + object prev = null; + switch (target.type) + { + case MaterialProperty.PropType.Float: + case MaterialProperty.PropType.Range: + prev = target.floatValue; + target.floatValue = source.floatValue; + break; + case MaterialProperty.PropType.Color: + prev = target.colorValue; + target.colorValue = source.colorValue; + break; + case MaterialProperty.PropType.Vector: + prev = target.vectorValue; + target.vectorValue = source.vectorValue; + break; + case MaterialProperty.PropType.Texture: + prev = target.textureValue; + target.textureValue = source.textureValue; + target.textureScaleAndOffset = source.textureScaleAndOffset; + break; + } + if (target.applyPropertyCallback != null) + target.applyPropertyCallback.Invoke(target, 1, prev); + } + + public static void CopyPropertyValueToMaterial(MaterialProperty source, Material target) + { + CopyMaterialValueFromProperty(MaterialEditor.GetMaterialProperty(new Material[] { target }, source.name), source); + } + } + + public class ColorHelper + { + public static Color Subtract(Color col1, Color col2) + { + return ColorMath(col1, col2, 1, -1); + } + + public static Color ColorMath(Color col1, Color col2, float multiplier1, float multiplier2) + { + return new Color(col1.r * multiplier1 + col2.r * multiplier2, col1.g * multiplier1 + col2.g * multiplier2, col1.b * multiplier1 + col2.b * multiplier2); + } + + public static float ColorDifference(Color col1, Color col2) + { + return Math.Abs(col1.r - col2.r) + Math.Abs(col1.g - col2.g) + Math.Abs(col1.b - col2.b) + Math.Abs(col1.a - col2.a); + } + } + + public class Converter + { + + public static Color StringToColor(string s) + { + s = s.Trim(new char[] { '(', ')' }); + string[] split = s.Split(",".ToCharArray()); + float[] rgba = new float[4] { 1, 1, 1, 1 }; + for (int i = 0; i < split.Length; i++) if (string.IsNullOrWhiteSpace(split[i]) == false) rgba[i] = float.Parse(split[i]); + return new Color(rgba[0], rgba[1], rgba[2], rgba[3]); + + } + + public static Vector4 StringToVector(string s) + { + s = s.Trim(new char[] { '(', ')' }); + string[] split = s.Split(",".ToCharArray()); + float[] xyzw = new float[4]; + for (int i = 0; i < 4 && i < split.Length; i++) if (string.IsNullOrWhiteSpace(split[i]) == false) xyzw[i] = float.Parse(split[i]); else xyzw[i] = 0; + return new Vector4(xyzw[0], xyzw[1], xyzw[2], xyzw[3]); + } + + public static string ArrayToString(object[] a) + { + string ret = ""; + foreach (object o in a) + ret += o.ToString() + ","; + return ret.TrimEnd(new char[] { ',' }); + } + + public static string ArrayToString(Array a) + { + string ret = ""; + foreach (object o in a) + ret += o.ToString() + ","; + return ret.TrimEnd(new char[] { ',' }); + } + + //--Start--Gradient + public static Gradient TextureToGradient(Texture2D texture) + { + texture = Gradient_Resize(texture); + Color[] values = Gradient_Sample(texture); + //values = Gradient_Smooth(values); + Color[] delta = CalcDelta(values); + delta[0] = delta[1]; + Color[] delta_delta = CalcDelta(delta); + //PrintColorArray(delta_delta); + List changes = DeltaDeltaToChanges(delta_delta, values); + changes = RemoveChangesUnderDistanceThreshold(changes); + SortChanges(changes); + //PrintColorList(changes); + return ConstructGradient(changes, values); + } + + private static Texture2D Gradient_Resize(Texture2D texture) + { + return TextureHelper.Resize(texture, 512, 512); + } + + private static Color[] Gradient_Sample(Texture2D texture) + { + texture.wrapMode = TextureWrapMode.Clamp; + int length = texture.width; + Color[] ar = new Color[length]; + for (int i = 0; i < length; i++) + { + ar[i] = texture.GetPixel(i, i); + } + return ar; + } + + private static Color[] Gradient_Smooth(Color[] values) + { + Color[] ar = new Color[values.Length]; + ar[0] = values[0]; + ar[ar.Length - 1] = values[ar.Length - 1]; + for (int i = 1; i < values.Length - 1; i++) + { + ar[i] = new Color(); + ar[i].r = (values[i - 1].r + values[i].r + values[i + 1].r) / 3; + ar[i].g = (values[i - 1].g + values[i].g + values[i + 1].g) / 3; + ar[i].b = (values[i - 1].b + values[i].b + values[i + 1].b) / 3; + } + return ar; + } + + private static Color[] CalcDelta(Color[] values) + { + Color[] delta = new Color[values.Length]; + delta[0] = new Color(0, 0, 0); + for (int i = 1; i < values.Length; i++) + { + delta[i] = ColorSubtract(values[i - 1], values[i]); + } + return delta; + } + + private static List DeltaDeltaToChanges(Color[] deltadelta, Color[] values) + { + List changes = new List(); + for (int i = 0; i < deltadelta.Length; i++) + { + if (deltadelta[i].r != 0 || deltadelta[i].g != 0 || deltadelta[i].b != 0) + { + deltadelta[i].a = i / 512.0f; + Color[] new_change = new Color[2]; + new_change[0] = deltadelta[i]; + new_change[1] = values[i]; + changes.Add(new_change); + } + } + return changes; + } + + const float GRADIENT_DISTANCE_THRESHOLD = 0.05f; + private static List RemoveChangesUnderDistanceThreshold(List changes) + { + List new_changes = new List(); + new_changes.Add(changes[0]); + for (int i = 1; i < changes.Count; i++) + { + + if (changes[i][0].a - new_changes[new_changes.Count - 1][0].a < GRADIENT_DISTANCE_THRESHOLD) + { + if (ColorValueForDelta(new_changes[new_changes.Count - 1][0]) < ColorValueForDelta(changes[i][0])) + { + new_changes.RemoveAt(new_changes.Count - 1); + new_changes.Add(changes[i]); + } + } + else + { + new_changes.Add(changes[i]); + } + } + return new_changes; + } + + private static void SortChanges(List changes) + { + changes.Sort(delegate (Color[] x, Color[] y) + { + float sizeX = ColorValueForDelta(x[0]); + float sizeY = ColorValueForDelta(y[0]); + if (sizeX < sizeY) return 1; + else if (sizeY < sizeX) return -1; + return 0; + }); + } + + private static Gradient ConstructGradient(List changes, Color[] values) + { + List alphas = new List(); + List colors = new List(); + for (int i = 0; i < 6 && i < changes.Count; i++) + { + colors.Add(new GradientColorKey(changes[i][1], changes[i][0].a)); + //Debug.Log("key " + changes[i][0].a); + } + colors.Add(new GradientColorKey(values[0], 0)); + colors.Add(new GradientColorKey(values[values.Length - 1], 1)); + alphas.Add(new GradientAlphaKey(1, 0)); + alphas.Add(new GradientAlphaKey(1, 1)); + Gradient gradient = new Gradient(); + gradient.SetKeys(colors.ToArray(), alphas.ToArray()); + return gradient; + } + + private static void PrintColorArray(Color[] ar) + { + foreach (Color c in ar) + Debug.Log(c.ToString()); + } + private static void PrintColorList(List ar) + { + foreach (Color[] x in ar) + Debug.Log(ColorValueForDelta(x[0]) + ":" + x[0].ToString()); + } + + private static float ColorValueForDelta(Color col) + { + return Mathf.Abs(col.r) + Mathf.Abs(col.g) + Mathf.Abs(col.b); + } + + private static Color ColorAdd(Color col1, Color col2) + { + return new Color(col1.r + col2.r, col1.g + col2.g, col1.b + col2.b); + } + private static Color ColorSubtract(Color col1, Color col2) + { + return new Color(col1.r - col2.r, col1.g - col2.g, col1.b - col2.b); + } + + public static Texture2D ColorToTexture(Color color, int width, int height) + { + width = Mathf.Max(0, Mathf.Min(8192, width)); + height = Mathf.Max(0, Mathf.Min(8192, height)); + Texture2D texture = new Texture2D(width, height); + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + texture.SetPixel(x, y, color); + } + } + texture.Apply(); + return texture; + } + + public static Texture2D GradientToTexture(Gradient gradient, int width, int height, bool vertical = false) + { + width = Mathf.Max(0, Mathf.Min(8192, width)); + height = Mathf.Max(0, Mathf.Min(8192, height)); + Texture2D texture = new Texture2D(width, height); + Color col; + if(vertical) + { + for (int y = 0; y < height; y++) + { + col = gradient.Evaluate((float)y / height); + for (int x = 0; x < width; x++) texture.SetPixel(x, y, col); + } + }else + { + for (int x = 0; x < width; x++) + { + col = gradient.Evaluate((float)x / width); + for (int y = 0; y < height; y++) texture.SetPixel(x, y, col); + } + } + texture.wrapMode = TextureWrapMode.Clamp; + texture.filterMode = FilterMode.Bilinear; + texture.Apply(); + return texture; + } + + //--End--Gradient + + public static Texture2D CurveToTexture(AnimationCurve curve, TextureData texture_settings) + { + Texture2D texture = new Texture2D(texture_settings.width, texture_settings.height); + for (int i = 0; i < texture_settings.width; i++) + { + Color color = new Color(); + float value = curve.Evaluate((float)i / texture_settings.width); + value = Mathf.Clamp01(value); + if (texture_settings.channel == 'r') + color.r = value; + else if (texture_settings.channel == 'g') + color.g = value; + else if (texture_settings.channel == 'b') + color.b = value; + else if (texture_settings.channel == 'a') + color.a = value; + if (texture_settings.channel != 'a') + color.a = 1; + for (int y = 0; y < texture_settings.height; y++) + texture.SetPixel(i, y, color); + } + texture.Apply(); + texture_settings.ApplyModes(texture); + return texture; + } + + //==============Texture Array================= + + [MenuItem("Assets/Thry/Flipbooks/Images 2 TextureArray",false, 303)] + static void SelectionImagesToTextureArray() + { + string[] paths = Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)).ToArray(); + PathsToTexture2DArray(paths); + } + + [MenuItem("Assets/Thry/Flipbooks/Images 2 TextureArray", true)] + static bool SelectionImagesToTextureArrayValidator() + { + if (Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 0) + { + return Selection.assetGUIDs.All(g => Regex.IsMatch(AssetDatabase.GUIDToAssetPath(g), @".*\.(png)|(jpg)")); + } + return false; + } + + public static Texture2DArray PathsToTexture2DArray(string[] paths) + { + if (paths.Length == 0) + return null; + if (paths[0].EndsWith(".gif")) + { + return Converter.GifToTextureArray(paths[0]); + } + else + { +#if SYSTEM_DRAWING + Texture2D[] wew = paths.Where(p=> AssetDatabase.GetMainAssetTypeAtPath(p).IsAssignableFrom(typeof(Texture2D))).Select(p => AssetDatabase.LoadAssetAtPath(p)).ToArray(); + Array.Sort(wew, (UnityEngine.Object one, UnityEngine.Object two) => one.name.CompareTo(two.name)); + Selection.objects = wew; + Texture2DArray texture2DArray = new Texture2DArray(wew[0].width, wew[0].height, wew.Length, wew[0].format, true); + + string assetPath = AssetDatabase.GetAssetPath(wew[0]); + assetPath = assetPath.Remove(assetPath.LastIndexOf('/')) + "/Texture2DArray.asset"; + + for (int i = 0; i < wew.Length; i++) + { + for (int m = 0; m < wew[i].mipmapCount; m++) + { + Graphics.CopyTexture(wew[i], 0, m, texture2DArray, i, m); + } + } + + texture2DArray.anisoLevel = wew[0].anisoLevel; + texture2DArray.wrapModeU = wew[0].wrapModeU; + texture2DArray.wrapModeV = wew[0].wrapModeV; + texture2DArray.Apply(false, true); + + AssetDatabase.CreateAsset(texture2DArray, assetPath); + AssetDatabase.SaveAssets(); + + Selection.activeObject = texture2DArray; + return texture2DArray; +#else + return null; +#endif + } + } + + [MenuItem("Assets/Thry/Flipbooks/Gif 2 TextureArray",false, 303)] + static void SelectionGifToTextureArray() + { + GifToTextureArray(AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0])); + } + + [MenuItem("Assets/Thry/Flipbooks/Gif 2 TextureArray", true)] + static bool SelectionGifToTextureArrayValidator() + { + if (Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 0) + { + return AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]).EndsWith(".gif"); + } + return false; + } + + public static Texture2DArray GifToTextureArray(string path) + { + List array = GetGifFrames(path); + if (array == null) return null; + if (array.Count == 0) + { + Debug.LogError("Gif is empty or System.Drawing is not working. Try right clicking and reimporting the \"Thry Editor\" Folder!"); + return null; + } + Texture2DArray arrayTexture = Textre2DArrayToAsset(array.ToArray()); + AssetDatabase.CreateAsset(arrayTexture, path.Replace(".gif", ".asset")); + AssetDatabase.SaveAssets(); + return arrayTexture; + } + + public static List GetGifFrames(string path) + { + List gifFrames = new List(); +#if SYSTEM_DRAWING + var gifImage = System.Drawing.Image.FromFile(path); + var dimension = new System.Drawing.Imaging.FrameDimension(gifImage.FrameDimensionsList[0]); + + int width = Mathf.ClosestPowerOfTwo(gifImage.Width - 1); + int height = Mathf.ClosestPowerOfTwo(gifImage.Height - 1); + + bool hasAlpha = false; + + int frameCount = gifImage.GetFrameCount(dimension); + + float totalProgress = frameCount * width; + for (int i = 0; i < frameCount; i++) + { + gifImage.SelectActiveFrame(dimension, i); + var ogframe = new System.Drawing.Bitmap(gifImage.Width, gifImage.Height); + System.Drawing.Graphics.FromImage(ogframe).DrawImage(gifImage, System.Drawing.Point.Empty); + var frame = ResizeBitmap(ogframe, width, height); + + Texture2D frameTexture = new Texture2D(frame.Width, frame.Height); + + float doneProgress = i * width; + for (int x = 0; x < frame.Width; x++) + { + if (x % 20 == 0) + if (EditorUtility.DisplayCancelableProgressBar("From GIF", "Frame " + i + ": " + (int)((float)x / width * 100) + "%", (doneProgress + x + 1) / totalProgress)) + { + EditorUtility.ClearProgressBar(); + return null; + } + + for (int y = 0; y < frame.Height; y++) + { + System.Drawing.Color sourceColor = frame.GetPixel(x, y); + frameTexture.SetPixel(x, frame.Height - 1 - y, new UnityEngine.Color32(sourceColor.R, sourceColor.G, sourceColor.B, sourceColor.A)); + if (sourceColor.A < 255.0f) + { + hasAlpha = true; + } + } + } + + frameTexture.Apply(); + gifFrames.Add(frameTexture); + } + EditorUtility.ClearProgressBar(); + //Debug.Log("has alpha? " + hasAlpha); + for (int i = 0; i < frameCount; i++) + { + EditorUtility.CompressTexture(gifFrames[i], hasAlpha ? TextureFormat.DXT5 : TextureFormat.DXT1, UnityEditor.TextureCompressionQuality.Normal); + gifFrames[i].Apply(true, false); + } +#endif + return gifFrames; + } + +#if SYSTEM_DRAWING + public static System.Drawing.Bitmap ResizeBitmap(System.Drawing.Image image, int width, int height) + { + var destRect = new System.Drawing.Rectangle(0, 0, width, height); + var destImage = new System.Drawing.Bitmap(width, height); + + destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + using (var graphics = System.Drawing.Graphics.FromImage(destImage)) + { + graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy; + graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + + using (var wrapMode = new System.Drawing.Imaging.ImageAttributes()) + { + wrapMode.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY); + graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, System.Drawing.GraphicsUnit.Pixel, wrapMode); + } + } + + return destImage; + } +#endif + + private static Texture2DArray Textre2DArrayToAsset(Texture2D[] array) + { + Texture2DArray texture2DArray = new Texture2DArray(array[0].width, array[0].height, array.Length, array[0].format, true); + +#if SYSTEM_DRAWING + for (int i = 0; i < array.Length; i++) + { + for (int m = 0; m < array[i].mipmapCount; m++) + { + UnityEngine.Graphics.CopyTexture(array[i], 0, m, texture2DArray, i, m); + } + } +#endif + + texture2DArray.anisoLevel = array[0].anisoLevel; + texture2DArray.wrapModeU = array[0].wrapModeU; + texture2DArray.wrapModeV = array[0].wrapModeV; + + texture2DArray.Apply(false, true); + + return texture2DArray; + } + } + + public class ShaderHelper + { + + private static Dictionary> shader_property_drawers = new Dictionary>(); + public static string[] GetDrawer(MaterialProperty property) + { + Shader shader = ((Material)property.targets[0]).shader; + + if (!shader_property_drawers.ContainsKey(shader)) + LoadShaderPropertyDrawers(shader); + + Dictionary property_drawers = shader_property_drawers[shader]; + if (property_drawers.ContainsKey(property.name)) + return property_drawers[property.name]; + return null; + } + + public static void LoadShaderPropertyDrawers(Shader shader) + { + string path = AssetDatabase.GetAssetPath(shader); + string code = FileHelper.ReadFileIntoString(path); + code = Helper.GetStringBetweenBracketsAndAfterId(code, "Properties", new char[] { '{', '}' }); + MatchCollection matchCollection = Regex.Matches(code, @"\[.*\].*(?=\()"); + Dictionary property_drawers = new Dictionary(); + foreach (Match match in matchCollection) + { + string[] drawers_or_flag_code = GetDrawersFlagsCode(match.Value); + string drawer_code = GetNonFlagDrawer(drawers_or_flag_code); + if (drawer_code == null) + continue; + + string property_name = Regex.Match(match.Value, @"(?<=\])[^\[]*$").Value.Trim(); + + List drawer_and_parameters = new List(); + drawer_and_parameters.Add(Regex.Split(drawer_code, @"\(")[0]); + + GetDrawerParameters(drawer_code, drawer_and_parameters); + + property_drawers[property_name] = drawer_and_parameters.ToArray(); + } + shader_property_drawers[shader] = property_drawers; + } + + private static void GetDrawerParameters(string code, List list) + { + MatchCollection matchCollection = Regex.Matches(code, @"(?<=\(|,).*?(?=\)|,)"); + foreach (Match m in matchCollection) + list.Add(m.Value); + } + + private static string GetNonFlagDrawer(string[] codes) + { + foreach (string c in codes) + if (!DrawerIsFlag(c)) + return c; + return null; + } + + private static bool DrawerIsFlag(string code) + { + return (code == "HideInInspector" || code == "NoScaleOffset" || code == "Normal" || code == "HDR" || code == "Gamma" || code == "PerRendererData"); + } + + private static string[] GetDrawersFlagsCode(string line) + { + MatchCollection matchCollection = Regex.Matches(line, @"(?<=\[).*?(?=\])"); + string[] codes = new string[matchCollection.Count]; + int i = 0; + foreach (Match m in matchCollection) + codes[i++] = m.Value; + return codes; + } + //------------Track ShaderEditor shaders------------------- + + // [MenuItem("Thry/Shader Editor/Test")] + // public static void Test() + // { + // Shader shader = Shader.Find(".poiyomi/Poiyomi 8.1/Poiyomi Pro"); + // Debug.Log(IsShaderUsingThryEditor(shader)); + // } + + public static bool IsShaderUsingThryEditor(Shader shader) + { + return IsShaderUsingThryEditor(new Material(shader)); + } + public static bool IsShaderUsingThryEditor(Material material) + { + return IsShaderUsingThryEditor(MaterialEditor.CreateEditor(material) as MaterialEditor); + } + public static bool IsShaderUsingThryEditor(MaterialEditor materialEditor) + { + PropertyInfo shaderGUIProperty = typeof(MaterialEditor).GetProperty("customShaderGUI"); + var gui = shaderGUIProperty.GetValue(materialEditor); + // gui is null for some shaders. I think it has to do with packages maybe + return (gui != null) && gui.GetType() == typeof(ShaderEditor); + } + + internal static List<(string prop, List keywords)> GetPropertyKeywordsForShader(Shader s) + { + List<(string prop, List keywords)> list = new List<(string prop, List keywords)>(); + + for (int i = 0; i < s.GetPropertyCount(); i++) + { + if (s.GetPropertyType(i) == UnityEngine.Rendering.ShaderPropertyType.Float) + { + string prop = s.GetPropertyName(i); + List keywords = null; + keywords = GetKeywordsFromShaderProperty(s, prop); + + if(keywords.Count == 0) + continue; + else + list.Add((prop, keywords)); + } + } + + return list; + } + + // Logic Adapted from unity's reference implementation + /// Returns a list of keywords for a given shader property. + internal static List GetKeywordsFromShaderProperty(Shader shader, string propertyName) + { + List keywords = new List(); + if (string.IsNullOrEmpty(propertyName)) + return keywords; + + int propertyIndex = shader.FindPropertyIndex(propertyName); + if (propertyIndex < 0) + return keywords; + + string[] attributes = shader.GetPropertyAttributes(propertyIndex); + if (attributes.Length == 0 || attributes == null) + return keywords; + + foreach (string attribute in attributes) + { + string args = ""; + // Regex based on Unity's reference implementation: Match a string of the form Keyword(Argument) and capture its components + // (\w+) - Match a word (keyword name) + // \s*\(\s* - Match an opening parenthesis, allowing whitespace before and after + // ([^)]*) - Match any number of characters that are not a closing parenthesis, and capture them into a group + // \s*\) - Match a closing parenthesis, allowing whitespace before and after + const string propertyDrawerRegex = @"(\w+)\s*\(\s*([^)]*)\s*\)"; + + Match regexMatch = Regex.Match(attribute, propertyDrawerRegex); + if (regexMatch.Success) + { + string className = regexMatch.Groups[1].Value; + args = regexMatch.Groups[2].Value.Trim(); + + // Note that we don't handle ToggleOff as it would require extra logic to differentiate + if(className == "Toggle") // Unity Toggle drawer, toggles a keyword directly if provided as [Toggle(KEYWORD)] and toggles PropertyName + { + if(string.IsNullOrEmpty(args)) + keywords.Add(GetUnityKeywordName(propertyName, "ON")); + else + keywords.Add(args); + break; + } + else if(className == "ThryToggle") // Thry Toggle drawer, toggles a keyword directly if provided as [Toggle(KEYWORD)] + { + // We only care about the first argument, the second is for UI + if(args.Contains(",")) + args = args.Split(',')[0]; + + // Ignore ThryToggle's bools, since otherwise we get keywords that have the same name as HLSL language keywords + if(args != "false" && args != "true") + keywords.Add(args); + + break; + } + else if(className == "KeywordEnum") // Keyword enum, enables one keyword out of a list of keywords provided as [KeywordEnum(KEYWORD1,KEYWORD2,KEYWORD3)] + { + string[] enumArgs = args.Split(','); + foreach(var enumArg in enumArgs) + { + keywords.Add(GetUnityKeywordName(propertyName, enumArg.Trim())); + } + + break; + } + } + } + return keywords; + } + + // Logic from Unity defaults + /// Gets a formatted Keyword name from a shader property name and a keyword name. + private static string GetUnityKeywordName(string propertyName, string keywordName) => $"{propertyName}_{keywordName}".Replace(' ', '_').ToUpperInvariant(); + } + + public class StringHelper + { + public static string GetBetween(string value, string prefix, string postfix) + { + return GetBetween(value, prefix, postfix, value); + } + + public static string GetBetween(string value, string prefix, string postfix, string fallback) + { + string pattern = @"(?<=" + prefix + ").*?(?=" + postfix + ")"; + Match m = Regex.Match(value, pattern); + if (m.Success) + return m.Value; + return fallback; + } + + //returns data for name:{data} even if data containss brakets + public static string GetBracket(string data, string bracketName) + { + Match m = Regex.Match(data, bracketName + ":"); + if (m.Success) + { + int startIndex = m.Index + bracketName.Length + 2; + int i = startIndex; + int depth = 0; + while (++i < data.Length) + { + if (data[i] == '{') + depth++; + else if (data[i] == '}') + { + if (depth == 0) + break; + depth--; + } + } + return data.Substring(startIndex, i - startIndex); + } + return data; + } + } + + public class VRCInterface + { + private static VRCInterface _Instance; + public static VRCInterface Get() + { + if (_Instance == null) _Instance = new VRCInterface(); + return _Instance; + } + public static void Update() + { + _Instance = new VRCInterface(); + } + + public SDK_Information Sdk_information; + + public class SDK_Information + { + public VRC_SDK_Type type; + public string installed_version = "0"; + } + + public enum VRC_SDK_Type + { + NONE = 0, + SDK_2 = 1, + SDK_3_Avatar = 2, + SDK_3_World = 3 + } + + private VRCInterface() + { + Sdk_information = new SDK_Information(); + Sdk_information.type = GetInstalledSDKType(); + InitInstalledSDKVersionAndPaths(); + } + + private void InitInstalledSDKVersionAndPaths() + { + string[] guids = AssetDatabase.FindAssets("version"); + string path = null; + foreach (string guid in guids) + { + string p = AssetDatabase.GUIDToAssetPath(guid); + if (p.Contains("VRCSDK/version")) + path = p; + } + if (path == null || !File.Exists(path)) + return; + string persistent = PersistentData.Get("vrc_sdk_version"); + if (persistent != null) + Sdk_information.installed_version = persistent; + else + Sdk_information.installed_version = Regex.Replace(FileHelper.ReadFileIntoString(path), @"\n?\r", ""); + } + + public static VRC_SDK_Type GetInstalledSDKType() + { +#if VRC_SDK_VRCSDK3 && UDON + return VRC_SDK_Type.SDK_3_World; +#elif VRC_SDK_VRCSDK3 + return VRC_SDK_Type.SDK_3_Avatar; +#elif VRC_SDK_VRCSDK2 + return VRC_SDK_Type.SDK_2; +#else + return VRC_SDK_Type.NONE; +#endif + } + + public static bool IsVRCSDKInstalled() + { +#if VRC_SDK_VRCSDK3 + return true; +#elif VRC_SDK_VRCSDK2 + return true; +#else + return false; +#endif + } + } + + //Adapted from https://github.com/lukis101/VRCUnityStuffs/blob/master/Scripts/Editor/MaterialCleaner.cs + //MIT License + + //Copyright (c) 2019 Dj Lukis.LT + + //Permission is hereby granted, free of charge, to any person obtaining a copy + //of this software and associated documentation files (the "Software"), to deal + //in the Software without restriction, including without limitation the rights + //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + //copies of the Software, and to permit persons to whom the Software is + //furnished to do so, subject to the following conditions: + + //The above copyright notice and this permission notice shall be included in all + //copies or substantial portions of the Software. + + //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + //SOFTWARE. + public class MaterialCleaner + { + public enum CleanPropertyType { Texture,Float,Color } + + private const string PropPath_Tex = "m_SavedProperties.m_TexEnvs"; + private const string PropPath_Float = "m_SavedProperties.m_Floats"; + private const string PropPath_Col = "m_SavedProperties.m_Colors"; + + static string GetPath(CleanPropertyType type) + { + if (type == CleanPropertyType.Float) return PropPath_Float; + if (type == CleanPropertyType.Color) return PropPath_Col; + return PropPath_Tex; + } + public static int CountAllUnusedProperties(params Material[] materials) + {; + return materials.Sum(m => + { + int count = 0; + SerializedObject serObj = new SerializedObject(m); + count += CountUnusedProperties(m, serObj, CleanPropertyType.Texture); + count += CountUnusedProperties(m, serObj, CleanPropertyType.Float); + count += CountUnusedProperties(m, serObj, CleanPropertyType.Color); + return count; + }); + } + private static int CountUnusedProperties(Material mat, SerializedObject serObj, CleanPropertyType type, List list = null) + { + var properties = serObj.FindProperty(GetPath(type)); + int count = 0; + if (properties != null && properties.isArray) + { + for (int i = 0; i < properties.arraySize; i++) + { + string propName = properties.GetArrayElementAtIndex(i).displayName; + if (!mat.HasProperty(propName)) + { + if (list!=null) list.Add(propName); + count++; + } + } + } + return count; + } + public static int ListUnusedProperties(CleanPropertyType type, params Material[] materials) + { + List list = new List(); + int count = materials.Sum(m => CountUnusedProperties(m, new SerializedObject(m), type, list)); + if(count > 0) ShaderEditor.Out($"Unbound properties of type {type}", list.Distinct().Select(s => $"↳{s}")); + return count; + } + public static int CountUnusedProperties(CleanPropertyType type, params Material[] materials) + { + return materials.Sum(m => CountUnusedProperties(m, new SerializedObject(m), type)); + } + + private static int RemoveUnusedProperties(Material mat, SerializedObject serObj, CleanPropertyType type) + { + if (!mat.shader.isSupported) + { + Debug.LogWarning("Skipping \"" + mat.name + "\" cleanup because shader is unsupported!"); + return 0; + } + Undo.RecordObject(mat, "Material property cleanup"); + int removedprops = 0; + string path = PropPath_Tex; + if (type == CleanPropertyType.Float) path = PropPath_Float; + if (type == CleanPropertyType.Color) path = PropPath_Col; + var properties = serObj.FindProperty(path); + if (properties != null && properties.isArray) + { + int amount = properties.arraySize; + for (int i = amount - 1; i >= 0; i--) // reverse loop because array gets modified + { + string propName = properties.GetArrayElementAtIndex(i).displayName; + if (!mat.HasProperty(propName)) + { + properties.DeleteArrayElementAtIndex(i); + removedprops++; + } + } + if (removedprops > 0) + serObj.ApplyModifiedProperties(); + } + return removedprops; + } + public static int RemoveUnusedProperties(CleanPropertyType type, params Material[] materials) + { + return materials.Sum(m => RemoveUnusedProperties(m, new SerializedObject(m), type)); + } + private static int RemoveAllUnusedProperties(Material mat, SerializedObject serObj) + { + int removedprops = 0; + removedprops += RemoveUnusedProperties(mat, serObj, CleanPropertyType.Texture); + removedprops += RemoveUnusedProperties(mat, serObj, CleanPropertyType.Float); + removedprops += RemoveUnusedProperties(mat, serObj, CleanPropertyType.Color); + + Debug.Log("Removed " + removedprops + " unused properties from " + mat.name); + return removedprops; + } + public static int RemoveAllUnusedProperties(CleanPropertyType type, params Material[] materials) + { + return materials.Sum(m => RemoveAllUnusedProperties(m, new SerializedObject(m))); + } + private static void ClearKeywords(Material mat) + { + Undo.RecordObject(mat, "Material keyword clear"); + string[] keywords = mat.shaderKeywords; + mat.shaderKeywords = new string[0]; + } + } +} diff --git a/Scripts/ThryEditor/Editor/HelperWeb.cs b/Scripts/ThryEditor/Editor/HelperWeb.cs new file mode 100644 index 0000000..8a5a69a --- /dev/null +++ b/Scripts/ThryEditor/Editor/HelperWeb.cs @@ -0,0 +1,283 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using UnityEditor; +using UnityEngine; +using UnityEngine.Networking; + +namespace Thry +{ + public class WebHelper + { + public static string FixUrl(string url) + { + if (!url.StartsWith("http")) + url = "http://" + url; + url = url.Replace("\\","/"); + if (System.Text.RegularExpressions.Regex.IsMatch(url, @"^https?:\/[^\/].*")) + url = url.Replace(":/", "://"); + return url; + } + + public static string GetFinalRedirect(string url) + { + if (string.IsNullOrEmpty(url)) + return url; + try + { + UnityWebRequest request = new UnityWebRequest(url); + request.method = UnityWebRequest.kHttpVerbHEAD; + DownloadHandlerBuffer response = new DownloadHandlerBuffer(); + request.downloadHandler = response; + request.SendWebRequest(); + bool fetching = true; + while (fetching) + { + if (request.isHttpError || request.isNetworkError) + { + fetching = false; + Debug.Log(request.error); + } + if (request.isDone) + { + fetching = false; + } + } + return request.url; + } + catch (Exception ex) + { + ex.ToString(); + return null; + } + } + + private static Dictionary fileCache = new Dictionary(); + public static string GetCachedString(string url) + { + if (fileCache.ContainsKey(url) == false) fileCache[url] = DownloadString(url); + return fileCache[url]; + } + + public static string Translate(string text, string targetLanguage) + { + string sourceLang = "auto"; + string url = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=" + + sourceLang + "&tl=" + targetLanguage + "&dt=t&q=" + UnityWebRequest.EscapeURL(text); + string resp = DownloadAsString(url); + + // parse response. it is a json array with the first elements being the translation. but not good json that my existing parser can parse + List parts = new List(); + int indent = 0; + bool inString = false; + for(int i = 0; i< resp.Length; i++) + { + if (resp[i] == '[') indent++; + if (resp[i] == ']') indent--; + if (resp[i] == '"' && resp[i - 1] != '\\') + { + inString = !inString; + if(inString && indent == 3) + parts.Add(""); + if(!inString && indent == 3 && resp[i+1] != ',') + parts.RemoveAt(parts.Count - 1); + }else + if (inString && indent == 3) + { + parts[parts.Count - 1] += resp[i]; + } + } + string result = ""; + for(int i = 0; i< parts.Count; i++) + { + if (i % 2 == 0) + result += parts[i]; + } + + // double unescape to fix some weird characters + result = System.Text.RegularExpressions.Regex.Unescape(result); + result = UnityWebRequest.UnEscapeURL(result, System.Text.Encoding.UTF8); + return result; + } + + //-------------------Downloaders----------------------------- + + [InitializeOnLoad] + public class MainThreader + { + private struct CallData + { + public object action; + public object[] arguments; + } + static List queue; + + static MainThreader() + { + queue = new List(); + EditorApplication.update += Update; + } + + public static void Call(object action, params object[] args) + { + if (action == null) + return; + CallData data = new CallData(); + data.action = action; + data.arguments = args; + if (args == null || args.Length == 0 || args[0] == null) + data.arguments = new object[] { "" }; + else + data.arguments = args; + queue.Add(data); + } + + public static void Update() + { + if (queue.Count > 0) + { + try + { + if(queue[0].action is Action) ((Action)queue[0].action).DynamicInvoke(queue[0].arguments); + if(queue[0].action is Action) ((Action)queue[0].action).DynamicInvoke(queue[0].arguments); + } + catch(Exception e) { + Debug.LogWarning("[Thry] Error during WebRequest: " + e.ToString()); + } + queue.RemoveAt(0); + } + } + } + + public static void DownloadFile(string url, string path) + { + DownloadAsFile(url, path); + } + + public static void DownloadFileASync(string url, string path, Action callback) + { + DownloadAsBytesASync(url, delegate (object o, DownloadDataCompletedEventArgs a) + { + if (a.Cancelled || a.Error != null) + MainThreader.Call(callback, null); + else + { + FileHelper.WriteBytesToFile(a.Result, path); + MainThreader.Call(callback, path); + } + }); + } + + public static string DownloadString(string url) + { + return DownloadAsString(url); + } + + public static void DownloadStringASync(string url, Action callback) + { + DownloadAsStringASync(url, delegate (object o, DownloadStringCompletedEventArgs e) + { + if (e.Cancelled || e.Error != null) + { + Debug.LogWarning(e.Error); + MainThreader.Call(callback, null); + } + else + MainThreader.Call(callback, e.Result); + }); + } + + public static void DownloadBytesASync(string url, Action callback) + { + DownloadAsBytesASync(url, delegate (object o, DownloadDataCompletedEventArgs e) + { + if (e.Cancelled || e.Error != null) + { + Debug.LogWarning(e.Error); + MainThreader.Call(callback, null); + } + else + MainThreader.Call(callback, e.Result); + }); + } + + private static void SetCertificate() + { + ServicePointManager.ServerCertificateValidationCallback = + delegate (object s, X509Certificate certificate, + X509Chain chain, SslPolicyErrors sslPolicyErrors) + { return true; }; + } + + private static string DownloadAsString(string url) + { + SetCertificate(); + string contents = null; + try + { + using (var wc = new System.Net.WebClient()) + contents = wc.DownloadString(url); + }catch(WebException e) + { + Debug.LogError(e); + } + return contents; + } + + private static void DownloadAsStringASync(string url, Action callback) + { + SetCertificate(); + using (var wc = new System.Net.WebClient()) + { + wc.Headers["User-Agent"] = "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0)"; + wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(callback); + wc.DownloadStringAsync(new Uri(url)); + } + } + + private static void DownloadAsFileASync(string url, string path, Action callback) + { + SetCertificate(); + using (var wc = new System.Net.WebClient()) + { + wc.DownloadFileCompleted += new AsyncCompletedEventHandler(callback); + wc.DownloadFileAsync(new Uri(url), path); + } + } + + private static void DownloadAsFile(string url, string path) + { + SetCertificate(); + using (var wc = new System.Net.WebClient()) + wc.DownloadFile(url, path); + } + + private static byte[] DownloadAsBytes(string url) + { + SetCertificate(); + byte[] contents = null; + using (var wc = new System.Net.WebClient()) + contents = wc.DownloadData(url); + return contents; + } + + private static void DownloadAsBytesASync(string url, Action callback) + { + SetCertificate(); + using (var wc = new System.Net.WebClient()) + { + wc.DownloadDataCompleted += new DownloadDataCompletedEventHandler(callback); + url = FixUrl(url); + wc.DownloadDataAsync(new Uri(url)); + } + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Localization.cs b/Scripts/ThryEditor/Editor/Localization.cs new file mode 100644 index 0000000..b3722c0 --- /dev/null +++ b/Scripts/ThryEditor/Editor/Localization.cs @@ -0,0 +1,564 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace Thry{ + public class Localization : ScriptableObject + { + [SerializeField] Shader[] ValidateWithShaders; + [SerializeField] string DefaultLanguage = "English"; + [SerializeField] string[] Languages = new string[0]; + [SerializeField]int SelectedLanguage = -1; + [SerializeField] + string[] _keys = new string[0]; + [SerializeField] + string[] _values = new string[0]; + [SerializeField] + string[] _defaultKeys = new string[0]; + [SerializeField] + string[] _defaultValues = new string[0]; + + Dictionary _localizedStrings = new Dictionary(); + Dictionary _defaultKeyValues = new Dictionary(); + string[] _allLanguages; + bool _isLoaded = false; + bool _couldNotLoad = false; + + // Use + public static Localization Load(string guid) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + Localization l = AssetDatabase.LoadAssetAtPath(path); + if(l == null) + { + l = ScriptableObject.CreateInstance(); + l._couldNotLoad = true; + return l; + } + l.Load(); + return l; + } + + void Load() + { + _allLanguages = new string[Languages.Length + 1]; + _allLanguages[0] = DefaultLanguage; + Array.Copy(Languages, 0, _allLanguages, 1, Languages.Length); + _localizedStrings = new Dictionary(); + for (int i = 0; i < _keys.Length; i++) + { + string[] ar = new string[Languages.Length]; + Array.Copy(_values, i * Languages.Length , ar, 0, Languages.Length); + _localizedStrings[_keys[i]] = ar; + } + _isLoaded = true; + } + + public static Localization Create() + { + Localization l = ScriptableObject.CreateInstance(); + l._allLanguages = new string[l.Languages.Length + 1]; + l._allLanguages[0] = l.DefaultLanguage; + Array.Copy(l.Languages, 0, l._allLanguages, 1, l.Languages.Length); + l._localizedStrings = new Dictionary(); + return l; + } + + public void DrawDropdown() + { + if(_couldNotLoad) + { + EditorGUILayout.HelpBox("Could not load localization file", MessageType.Warning); + return; + } + EditorGUI.BeginChangeCheck(); + SelectedLanguage = EditorGUILayout.Popup(SelectedLanguage + 1, _allLanguages) - 1; + if(EditorGUI.EndChangeCheck()) + { + ShaderEditor.Active.Reload(); + EditorUtility.SetDirty(this); + AssetDatabase.SaveAssets(); + } + } + + public string Get(MaterialProperty prop, string defaultValue) + { + if(_localizedStrings.ContainsKey(prop.name)) + { + string[] ar = _localizedStrings[prop.name]; + if(ar.Length > SelectedLanguage && SelectedLanguage > -1) + { + return ar[SelectedLanguage] ?? defaultValue; + } + } + _defaultKeyValues[prop.name] = defaultValue; + return defaultValue; + } + + public string Get(MaterialProperty prop, FieldInfo field, string defaultValue) + { + string id = prop.name + "." + field.DeclaringType + "." + field.Name; + return Get(id, defaultValue); + } + + public string Get(string id, string defaultValue) + { + if(id == null) return defaultValue; + if (_localizedStrings.ContainsKey(id)) + { + string[] ar = _localizedStrings[id]; + if (ar.Length > SelectedLanguage && SelectedLanguage > -1) + { + return ar[SelectedLanguage] ?? defaultValue; + } + } + _defaultKeyValues[id] = defaultValue; + return defaultValue; + } + + // Managment + + void AddLanguage(string language) + { + if (System.Array.IndexOf(Languages, language) == -1) + { + System.Array.Resize(ref Languages, Languages.Length + 1); + Languages[Languages.Length - 1] = language; + string[] keys = _localizedStrings.Keys.ToArray(); + foreach(string key in keys) + { + string[] ar = _localizedStrings[key]; + System.Array.Resize(ref ar, ar.Length + 1); + ar[ar.Length - 1] = null; + _localizedStrings[key] = ar; + } + Save(); + } + } + + void RemoveLanguage(string language) + { + int index = System.Array.IndexOf(Languages, language); + if (index != -1) + { + if(Languages.Length > 1) + { + for (int i = index; i < Languages.Length - 1; i++) + { + Languages[i] = Languages[i + 1]; + } + System.Array.Resize(ref Languages, Languages.Length - 1); + string[] keys = _localizedStrings.Keys.ToArray(); + foreach (string key in keys) + { + string[] ar = _localizedStrings[key]; + for (int i = index; i < ar.Length - 1; i++) + { + ar[i] = ar[i + 1]; + } + System.Array.Resize(ref ar, ar.Length - 1); + _localizedStrings[key] = ar; + } + }else + { + Languages = new string[0]; + _localizedStrings = new Dictionary(); + } + Save(); + } + } + + void Save() + { + _defaultKeys = _defaultKeyValues.Keys.ToArray(); + _defaultValues = _defaultKeyValues.Values.ToArray(); + _keys = _localizedStrings.Keys.ToArray(); + _values = new string[_keys.Length * Languages.Length]; + for (int i = 0; i < _keys.Length; i++) + { + string[] ar = _localizedStrings[_keys[i]]; + Array.Copy(ar, 0, _values, i * Languages.Length, ar.Length); + } + EditorUtility.SetDirty(this); + AssetDatabase.SaveAssets(); + } + + void Clear() + { + _defaultKeys = new string[0]; + _defaultValues = new string[0]; + _keys = new string[0]; + _values = new string[0]; + Languages = new string[0]; + } + + [MenuItem("Assets/Thry/Shaders/Create Locale File", false)] + static void CreateLocale() + { + Localization locale = ScriptableObject.CreateInstance(); + Shader[] shaders = Selection.objects.Select(o => o as Shader).ToArray(); + string fileName = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(shaders[0])); + string folderPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(shaders[0])); + locale.ValidateWithShaders = shaders; + AssetDatabase.CreateAsset(locale, folderPath + "/" + fileName + "_Locale.asset"); + AssetDatabase.SaveAssets(); + } + + [MenuItem("Assets/Thry/Shaders/Create Locale File", true)] + static bool ValidateCreateLocale() + { + return Selection.objects.All(o => o is Shader); + } + + [MenuItem("Assets/Thry/Shaders/Locale Property", false)] + static void CreateShaderProperty() + { + Localization l = Selection.activeObject as Localization; + string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(l)); + string outS = $"[HideInInspector] {ShaderEditor.PROPERTY_NAME_LOCALE} (\"{guid}\", Float) = 0"; + EditorGUIUtility.systemCopyBuffer = outS; + } + + [MenuItem("Assets/Thry/Shaders/Locale Property", true)] + static bool ValidateCreateShaderProperty() + { + return Selection.activeObject is Localization; + } + + [CustomEditor(typeof(Localization))] + public class LocaleEditor : Editor + { + List<(string key, string defaultValue, string newValue)> _missingKeys = new List<(string key, string defaultValue, string newValue)>(); + int _selectedLanguageIndex = 0; + string _searchById = ""; + string _searchByTranslation = ""; + string[] _searchResults = new string[0]; + + string _translateByValueIn = ""; + string _translateByValueOut = ""; + string _autoTranslateLanguageShortCode = "EN"; + + string ToCSVString(string s) + { + return "\"" + s.Replace("\"", "“") + "\""; + } + + string FromCSVString(string s) + { + return s.Trim('"').Replace("“", "\""); + } + + void ExportAsCSV(Localization locale) + { + string path = EditorUtility.SaveFilePanel("Export as CSV", "", locale.name, "csv"); + if (string.IsNullOrEmpty(path) == false) + { + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + foreach (string language in locale.Languages) + { + sb.Append("," + ToCSVString(language)); + } + sb.AppendLine(); + for(int i = 0;i < locale._keys.Length; i++) + { + sb.Append(ToCSVString(locale._keys[i])); + for(int j = 0; j < locale.Languages.Length; j++) + { + sb.Append("," + ToCSVString(locale._values[i * locale.Languages.Length + j])); + } + sb.AppendLine(); + } + File.WriteAllText(path, sb.ToString()); + } + } + + void LoadFromCSV(Localization locale) + { + string path = EditorUtility.OpenFilePanel("Load from CSV", "", "csv"); + if (string.IsNullOrEmpty(path) == false) + { + string[] lines = File.ReadAllLines(path); + if (lines.Length > 0) + { + locale.Clear(); + string[] languages = lines[0].Split(','); + for (int i = 1; i < languages.Length; i++) + { + locale.AddLanguage(FromCSVString(languages[i])); + } + for (int i = 1; i < lines.Length; i++) + { + string[] values = lines[i].Split(','); + if (values.Length > 0) + { + string key = FromCSVString(values[0]); + for (int j = 1; j < values.Length; j++) + { + locale._values[(i - 1) * (languages.Length - 1) + j - 1] = FromCSVString(values[j]); + } + } + } + locale.Save(); + } + } + } + + void UpdateMissing(Localization locale) + { + _missingKeys.Clear(); + foreach(string key in locale._localizedStrings.Keys) + { + if (string.IsNullOrEmpty(locale._localizedStrings[key][_selectedLanguageIndex])) + { + _missingKeys.Add((key, locale._defaultKeyValues[key], locale._defaultKeyValues[key])); + } + } + } + + void UpdateData(Localization locale) + { + locale.Load(); + // create _defaultKeyValues + if(locale._defaultKeyValues == null) + { + locale._defaultKeyValues = new Dictionary(); + } + for(int i = 0; i < locale._defaultKeys.Length; i++) + { + if(locale._defaultKeyValues.ContainsKey(locale._defaultKeys[i]) == false) + locale._defaultKeyValues.Add(locale._defaultKeys[i], locale._defaultValues[i]); + } + // add all keys from shader + foreach(var kv in locale._defaultKeyValues) + { + string key = kv.Key; + if(key.StartsWith("footer_")) continue; + if(key == ShaderEditor.PROPERTY_NAME_MASTER_LABEL) continue; + if(key == ShaderEditor.PROPERTY_NAME_LABEL_FILE) continue; + if(key == ShaderEditor.PROPERTY_NAME_LOCALE) continue; + if(key == ShaderEditor.PROPERTY_NAME_ON_SWAP_TO_ACTIONS) continue; + if(key == ShaderEditor.PROPERTY_NAME_SHADER_VERSION) continue; + if(key == ShaderEditor.PROPERTY_NAME_EDITOR_DETECT) continue; + if (string.IsNullOrEmpty(kv.Value) == false && !locale._localizedStrings.ContainsKey(kv.Key)) + { + locale._localizedStrings.Add(kv.Key, new string[locale.Languages.Length]); + } + } + // make missing keys a list of all keys that have an empty string in the selected language + UpdateMissing(locale); + } + + private void OnEnable() + { + Localization locale = (Localization)target; + locale.Load(); + UpdateData(locale); + } + + private void Awake() { + Localization locale = (Localization)target; + locale.Load(); + UpdateData(locale); + } + + public override void OnInspectorGUI() + { + Localization locale = (Localization)target; + if(!locale._isLoaded) + { + UpdateData(locale); + } + + if(GUILayout.Button("Save")) + { + locale.Save(); + } + + EditorGUILayout.PropertyField(serializedObject.FindProperty("ValidateWithShaders")); + if(GUILayout.Button("Load Properties from Shaders")) + { + // for each shader create a material & material editor so that the data is loaded into the localization object + foreach(Shader s in locale.ValidateWithShaders) + { + ShaderEditor se = new ShaderEditor(); + se.FakePartialInitilizationForLocaleGathering(s); + } + } + locale.DefaultLanguage = EditorGUILayout.TextField("Default Language", locale.DefaultLanguage); + + EditorGUILayout.LabelField("Languages"); + for (int i = 0; i < locale.Languages.Length; i++) + { + EditorGUILayout.BeginHorizontal(); + locale.Languages[i] = EditorGUILayout.TextField(locale.Languages[i]); + if (GUILayout.Button("Remove")) + { + locale.RemoveLanguage(locale.Languages[i]); + } + EditorGUILayout.EndHorizontal(); + } + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Add")) + { + locale.AddLanguage("New Language"); + } + EditorGUILayout.EndHorizontal(); + + // popup for selecting language + EditorGUI.BeginChangeCheck(); + _selectedLanguageIndex = EditorGUILayout.Popup("Language to edit", _selectedLanguageIndex, locale.Languages); + if(EditorGUI.EndChangeCheck()) + { + _missingKeys.Clear(); + } + + if(GUILayout.Button("Update")) + { + UpdateData(locale); + } + + EditorGUILayout.Space(20); + EditorGUILayout.LabelField("Import / Export", EditorStyles.boldLabel); + if(GUILayout.Button("Import from CSV")) + LoadFromCSV(locale); + + if(locale.Languages.Length == 0) return; + + if(GUILayout.Button("Export to CSV")) + ExportAsCSV(locale); + + EditorGUILayout.Space(20); + EditorGUILayout.LabelField("Missing Entries", EditorStyles.boldLabel); + (string,string,string) kvToRemove = default; + for(int i = 0; i < _missingKeys.Count && i < 10; i++) + { + var kv = _missingKeys[i]; + EditorGUILayout.BeginHorizontal(); + kv.newValue = EditorGUILayout.DelayedTextField(kv.key, kv.newValue); + if(GUILayout.Button("Skip", GUILayout.Width(50))) + { + kvToRemove = kv; + } + if(GUILayout.Button("Apply", GUILayout.Width(50))) + { + if (!locale._localizedStrings.ContainsKey(kv.key)) + { + locale._localizedStrings.Add(kv.key, new string[locale.Languages.Length]); + } + locale._localizedStrings[kv.key][_selectedLanguageIndex] = kv.newValue; + kvToRemove = kv; + } + _missingKeys[i] = kv; + EditorGUILayout.EndHorizontal(); + } + if(_missingKeys.Count > 10) + { + EditorGUILayout.LabelField("..."); + } + if(kvToRemove != default) + { + _missingKeys.Remove(kvToRemove); + } + + EditorGUILayout.Space(20); + EditorGUILayout.LabelField("Automatic Translation using Google", EditorStyles.boldLabel); + _autoTranslateLanguageShortCode = EditorGUILayout.TextField("Language Short Code", _autoTranslateLanguageShortCode); + EditorGUILayout.HelpBox("Short code must be valid short code. See https://cloud.google.com/translate/docs/languages for a list of valid short codes.", MessageType.Info); + if(Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) + { + Application.OpenURL("https://cloud.google.com/translate/docs/languages"); + } + if(GUILayout.Button("Auto Translate")) + { + int _missingKeysCount = _missingKeys.Count; + int i = 0; + foreach((string key, string defaultValue, string newValue) in _missingKeys) + { + EditorUtility.DisplayProgressBar("Auto Translate", $"Translating {i}/{_missingKeysCount}", (float)i / _missingKeysCount); + try + { + if (!locale._localizedStrings.ContainsKey(key)) + { + locale._localizedStrings.Add(key, new string[locale.Languages.Length]); + } + locale._localizedStrings[key][_selectedLanguageIndex] = WebHelper.Translate(defaultValue, _autoTranslateLanguageShortCode); + } + catch(Exception e) + { + Debug.LogError(e); + } + i += 1; + } + EditorUtility.ClearProgressBar(); + locale.Save(); + } + + EditorGUILayout.Space(20); + EditorGUILayout.LabelField("Translate entries by value", EditorStyles.boldLabel); + EditorGUILayout.HelpBox("This will search all properties and translate all that have the exact display name with the selected value. Suggested usecase: Panning, UV", MessageType.Info); + _translateByValueIn = EditorGUILayout.TextField("Search for", _translateByValueIn); + _translateByValueOut = EditorGUILayout.TextField("Translate with", _translateByValueOut); + if(GUILayout.Button("Execute")) + { + foreach(var kv in locale._defaultKeyValues) + { + if(kv.Value == _translateByValueIn) + { + locale._localizedStrings[kv.Key][_selectedLanguageIndex] = _translateByValueOut; + } + } + UpdateMissing(locale); + } + + EditorGUILayout.Space(20); + EditorGUILayout.LabelField("Existing Entries", EditorStyles.boldLabel); + EditorGUI.BeginChangeCheck(); + _searchById = EditorGUILayout.TextField("Search by id", _searchById); + _searchByTranslation = EditorGUILayout.TextField("Search by translation", _searchByTranslation); + if(EditorGUI.EndChangeCheck()) + { + List res = new List(); + foreach (string key in locale._localizedStrings.Keys) + { + if(locale._localizedStrings[key][_selectedLanguageIndex] == null) continue; + if(locale._localizedStrings[key][_selectedLanguageIndex].IndexOf(_searchByTranslation, StringComparison.OrdinalIgnoreCase) != -1 + && key.IndexOf(_searchById, StringComparison.OrdinalIgnoreCase) != -1) + { + res.Add(key); + } + } + _searchResults = res.ToArray(); + } + EditorGUILayout.Space(5); + if(_searchById.Length > 0 || _searchByTranslation.Length > 0) + { + int count = 0; + foreach (string key in _searchResults) + { + if(count > 50) + { + EditorGUILayout.LabelField("..."); + break; + } + EditorGUILayout.BeginHorizontal(); + string value = EditorGUILayout.DelayedTextField(key, locale._localizedStrings[key][_selectedLanguageIndex]); + if (GUILayout.Button("Remove", GUILayout.Width(65))) + { + locale._localizedStrings[key][_selectedLanguageIndex] = ""; + } + EditorGUILayout.EndHorizontal(); + locale._localizedStrings[key][_selectedLanguageIndex] = value; + count++; + } + } + + } + } + } +} + diff --git a/Scripts/ThryEditor/Editor/MaterialLinker.cs b/Scripts/ThryEditor/Editor/MaterialLinker.cs new file mode 100644 index 0000000..2d88a61 --- /dev/null +++ b/Scripts/ThryEditor/Editor/MaterialLinker.cs @@ -0,0 +1,260 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class MaterialLinker + { + private static Dictionary<(Material,string), List> linked_materials; + + private static void Load() + { + if (linked_materials == null) + { + linked_materials = new Dictionary<(Material,string), List>(); + string raw = FileHelper.ReadFileIntoString(PATH.LINKED_MATERIALS_FILE); + string[][] parsed = Parser.Deserialize(raw); + if(parsed!=null) + foreach (string[] material_cloud in parsed) + { + List materials = new List(); + for (int i = 1; i < material_cloud.Length; i++) + { + string path = AssetDatabase.GUIDToAssetPath(material_cloud[i]); + Material m = AssetDatabase.LoadAssetAtPath(path); + if (m != null) + materials.Add(m); + } + foreach (Material m in materials) + if(linked_materials.ContainsKey((m, material_cloud[0])) == false) + linked_materials.Add((m, material_cloud[0]), materials); + } + } + } + + private static void Save() + { + List save_structre = new List(); + HashSet<(Material,string)> has_already_been_saved = new HashSet<(Material,string)>(); + foreach (KeyValuePair<(Material,string),List> link in linked_materials) + { + if (has_already_been_saved.Contains(link.Key)) continue; + string[] value = new string[link.Value.Count + 1]; + value[0] = link.Key.Item2; + int i = 1; + foreach (Material m in link.Value) { + has_already_been_saved.Add((m, link.Key.Item2)); + value[i++] = UnityHelper.GetGUID(m); + } + save_structre.Add(value); + } + FileHelper.WriteStringToFile(Parser.ObjectToString(save_structre),PATH.LINKED_MATERIALS_FILE); + } + + public static bool IsLinked(MaterialProperty p) + { + Load(); + return linked_materials.ContainsKey(((Material)p.targets[0], p.name)); + } + + public static List GetLinked(MaterialProperty p) + { + return GetLinked((Material)p.targets[0], p); + } + + public static List GetLinked(Material m, MaterialProperty p) + { + Load(); + if (linked_materials.ContainsKey((m,p.name))) + return linked_materials[(m,p.name)]; + return null; + } + + public static void Link(Material master, Material add_to, MaterialProperty p) + { + Load(); + Debug.Log("link " + master.name + "," + add_to.name); + bool containes_key1 = linked_materials.ContainsKey((master,p.name)); + bool containes_key2 = linked_materials.ContainsKey((add_to,p.name)); + + if(containes_key1 && containes_key2) + { + Unlink(add_to, p); + Link(master, add_to, p); + return; + } + else if (containes_key1) + AddToListIfMaterialAlreadyLinked(master, add_to, p); + else if (containes_key2) + AddToListIfMaterialAlreadyLinked(add_to, master, p); + else + { + List value = new List(); + value.Add(master); + value.Add(add_to); + linked_materials[(master,p.name)] = value; + linked_materials[(add_to,p.name)] = value; + } + } + + private static void AddToListIfMaterialAlreadyLinked(Material existing, Material add, MaterialProperty p) + { + List value = linked_materials[(existing,p.name)]; + value.Add(add); + linked_materials[(add,p.name)] = value; + } + + public static void Unlink(Material m, MaterialProperty p) + { + Load(); + List value = linked_materials[(m,p.name)]; + value.Remove(m); + linked_materials.Remove((m,p.name)); + } + + private static void UpdateLinkList(List new_linked_materials, MaterialProperty p) + { + var key = (p.targets[0] as Material, p.name); + if (linked_materials.ContainsKey(key)) + { + List old_materials = linked_materials[key]; + foreach (Material m in old_materials) + linked_materials.Remove((m, p.name)); + } + foreach (Material m in new_linked_materials) + linked_materials[(m, p.name)] = new_linked_materials; + } + + public static void UnlinkAll(Material m) + { + List<(Material, string)> remove_keys = new List<(Material, string)>(); + foreach (KeyValuePair<(Material,string), List> link_cloud in linked_materials) + { + if (link_cloud.Key.Item1 == m) + { + link_cloud.Value.Remove(m); + remove_keys.Add(link_cloud.Key); + } + } + foreach ((Material, string) k in remove_keys) + linked_materials.Remove(k); + RemoveEmptyLinks(); + Save(); + } + + private static void RemoveEmptyLinks() + { + List<(Material, string)> remove_keys = new List<(Material, string)>(); + foreach (KeyValuePair<(Material,string), List> link_cloud in linked_materials) + { + if (link_cloud.Value.Count < 2) + { + link_cloud.Value.Clear(); + remove_keys.Add(link_cloud.Key); + } + } + foreach ((Material, string) k in remove_keys) + linked_materials.Remove(k); + } + + private static MaterialLinkerPopupWindow window; + public static void Popup(Rect activeation_rect, List linked_materials, MaterialProperty p) + { + Vector2 pos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); + pos.x = Mathf.Min(EditorWindow.focusedWindow.position.x + EditorWindow.focusedWindow.position.width - 250, pos.x); + pos.y = Mathf.Min(EditorWindow.focusedWindow.position.y + EditorWindow.focusedWindow.position.height - 200, pos.y); + + Load(); + if (window != null) + window.Close(); + window = ScriptableObject.CreateInstance(); + window.position = new Rect(pos.x, pos.y, 250, 200); + window.Init(linked_materials, p); + window.ShowPopup(); + } + + private class MaterialLinkerPopupWindow : EditorWindow + { + private Vector2 scrollPos; + private List linked_materials; + private MaterialProperty materialProperty; + + public void Init(List linked_materials, MaterialProperty p) + { + if (linked_materials == null) + linked_materials = new List(); + this.linked_materials = new List(linked_materials); + + string self_guid = UnityHelper.GetGUID((Material)p.targets[0]); + for (int i = this.linked_materials.Count - 1; i >= 0; i--) + { + if (UnityHelper.GetGUID(this.linked_materials[i]) == self_guid) + this.linked_materials.RemoveAt(i); + } + this.materialProperty = p; + } + + public new Vector2 minSize = new Vector2(250, 200); + + void OnGUI() + { + GUILayout.Label("Linked Materials", EditorStyles.boldLabel); + float listMaxHeight = this.position.height - 110; + GuiHelper.DrawListField(linked_materials, listMaxHeight, ref scrollPos); + GUILayout.Box("Drag and Drop new Material", EditorStyles.helpBox, GUILayout.MinHeight(30)); + //Rect drag_rect = GUILayoutUtility.GetLastRect(); + Rect lastRect = GUILayoutUtility.GetLastRect(); + Rect drag_rect = new Rect(0, lastRect.y, Screen.width, Screen.height - lastRect.y - 30); + Event e = Event.current; + if ((e.type == EventType.DragPerform || e.type == EventType.DragUpdated) && drag_rect.Contains(e.mousePosition)) + { + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + if (e.type == EventType.DragPerform) + { + DragAndDrop.AcceptDrag(); + HanldeDropEvent(); + } + } + if (GUI.Button(new Rect(0,this.position.height-30,this.position.width,30),"Done")) + this.Close(); + } + + public void HanldeDropEvent() + { + foreach (string path in DragAndDrop.paths) + { + if (AssetDatabase.GetMainAssetTypeAtPath(path) == typeof(Material)) + { + linked_materials.Add(AssetDatabase.LoadAssetAtPath(path)); + } + } + } + + void Awake() + { + + } + + void OnDestroy() + { + //add itself + bool contains_itself = false; + string self_guid = UnityHelper.GetGUID((Material)materialProperty.targets[0]); + for (int i = linked_materials.Count - 1; i >= 0; i--) + { + if (UnityHelper.GetGUID(linked_materials[i]) == self_guid) + contains_itself = true; + if (linked_materials[i] == null) + linked_materials.RemoveAt(i); + } + if (linked_materials.Count>0 && !contains_itself) + linked_materials.Add((Material)materialProperty.targets[0]); + + UpdateLinkList(linked_materials, materialProperty); + Save(); + } + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Mediator.cs b/Scripts/ThryEditor/Editor/Mediator.cs new file mode 100644 index 0000000..78f877c --- /dev/null +++ b/Scripts/ThryEditor/Editor/Mediator.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Thry +{ + public class Mediator + { + + private static Material m_copy; + public static Material copy_material + { + set + { + m_copy = value; + } + get + { + return m_copy; + } + } + + public static ShaderPart transfer_group; + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Parser.cs b/Scripts/ThryEditor/Editor/Parser.cs new file mode 100644 index 0000000..1a1c12f --- /dev/null +++ b/Scripts/ThryEditor/Editor/Parser.cs @@ -0,0 +1,470 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class Parser + { + + public static string Serialize(object o) + { + return Parser.ObjectToString(o); + } + + public static T Deserialize(string s) + { + return DeserializeInternal(s); + } + + public static object Deserialize(string s, Type t) + { + return DeserializeInternal(s, t); + } + + public static string ObjectToString(object obj) + { + if (obj == null) return "null"; + if (Helper.IsPrimitive(obj.GetType())) return SerializePrimitive(obj); + if (obj is IList) return SerializeList(obj); + if (obj.GetType().IsGenericType && obj.GetType().GetGenericTypeDefinition() == typeof(Dictionary<,>)) return SerializeDictionary(obj); + if (obj.GetType().IsArray) return SerializeList(obj); + if (obj.GetType().IsEnum) return obj.ToString(); + if (obj.GetType().IsClass) return SerializeClass(obj); + if (obj.GetType().IsValueType && !obj.GetType().IsEnum) return SerializeClass(obj); + return ""; + } + + private static T DeserializeInternal(string s) + { + object parsed = ParseJson(s); + object ret = null; + try + { + ret = (T)ParsedToObject(parsed, typeof(T)); + } + catch (Exception e) + { + Debug.LogWarning(e.ToString()); + Debug.LogWarning(s + " cannot be parsed to object of type " + typeof(T).ToString()); + ret = Activator.CreateInstance(typeof(T)); + } + return (T)ret; + } + + private static object DeserializeInternal(string s, Type t) + { + object parsed = ParseJson(s); + object ret = null; + try + { + ret = ParsedToObject(parsed, t); + } + catch (Exception e) + { + Debug.LogWarning(e.ToString()); + Debug.LogWarning(s + " cannot be parsed to object of type " + t.ToString()); + ret = Activator.CreateInstance(t); + } + return ret; + } + + //Parser methods + + public static object ParseJson(string input) + { + return ParseJsonPart(input, 0, input.Length); + } + + private static object ParseJsonPart(string input, int start, int end) + { + int rawStart = start; + int rawEnd = end; + + while (start < end && (input[start] == ' ' || input[start] == '\t' || input[start] == '\n' || input[start] == '\r')) + start++; + if (start == end) + return input; // empty string + if (input[start] == '{') + { + start++; + end--; + while (end > start && (input[end] == ' ' || input[end] == '\t' || input[end] == '\n' || input[end] == '\r')) + end--; + if (input[end] == '}') + { + return ParseObject(input, start, end); + }else + { + Debug.LogWarning("Invalid json object: " + input.Substring(rawStart, rawEnd - rawStart)); + return null; + } + } + if (input[start] == '[') + { + start++; + end--; + while (end > start && (input[end] == ' ' || input[end] == '\t' || input[end] == '\n' || input[end] == '\r')) + end--; + if (input[end] == ']') + { + return ParseArray(input, start, end); + } + else + { + Debug.LogWarning("Invalid json array: " + input); + return null; + } + } + return ParsePrimitive(input.Substring(start, end - start)); + } + + private static Dictionary ParseObject(string input, int start, int end) + { + // Debug.Log("Parse Object: "+ input.Substring(start, end - start)); + int depth = 0; + int variableStart = start; + bool isString = false; + Dictionary variables = new Dictionary(); + for (int i = start; i < end; i++) + { + bool escaped = i != 0 && input[i - 1] == '\\'; + if (input[i] == '\"' && !escaped) + isString = !isString; + if (!isString) + { + if ((depth == 0 && input[i] == ',' && !escaped) || (!escaped && depth == 0 && input[i] == '}')) + { + int seperatorIndex = input.IndexOf(':', variableStart, i - variableStart); + if (seperatorIndex == -1) + break; + string key = "" + ParseJsonPart(input, variableStart, seperatorIndex); + object value = ParseJsonPart(input, seperatorIndex + 1, i); + variables.Add(key, value); + variableStart = i + 1; + }else if(i == end - 1) + { + int seperatorIndex = input.IndexOf(':', variableStart, i - variableStart); + if (seperatorIndex == -1) + break; + string key = "" + ParseJsonPart(input, variableStart, seperatorIndex); + object value = ParseJsonPart(input, seperatorIndex + 1, i + 1); + variables.Add(key, value); + } + else if ((input[i] == '{' || input[i] == '[') && !escaped) + depth++; + else if ((input[i] == '}' || input[i] == ']') && !escaped) + depth--; + } + + } + return variables; + } + + private static List ParseArray(string input, int start, int end) + { + // Debug.Log("Parse Array: " + input.Substring(start, end - start)); + int depth = 0; + int variableStart = start; + List variables = new List(); + for (int i = start; i < end; i++) + { + if(depth == 0 && input[i] == ',' && (i == 0 || input[i - 1] != '\\')) + { + variables.Add(ParseJsonPart(input, variableStart, i)); + variableStart = i + 1; + }else if(i == end - 1) + { + variables.Add(ParseJsonPart(input, variableStart, i + 1)); + } + else if (input[i] == '{' || input[i] == '[') + depth++; + else if (input[i] == '}' || input[i] == ']') + depth--; + } + return variables; + } + + private static object ParsePrimitive(string input) + { + // Debug.Log("Parse Primitive: " + input); + // string + if (input.StartsWith("\"", StringComparison.Ordinal)) + return input.Trim(new char[] { '"' }); + + // boolean + // StartsWith ordinal, because it's faster than toLower and trim (in case of spaces after) + if (input.StartsWith("true", StringComparison.OrdinalIgnoreCase)) + return true; + if (input.StartsWith("false", StringComparison.OrdinalIgnoreCase)) + return false; + // null + if (input == "null" || input == "NULL" || input == "Null") + return null; + + // number + float floatValue; + // parse float invariant + if(float.TryParse(input, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out floatValue)) + { + if ((int)floatValue == floatValue) + return (int)floatValue; + return floatValue; + } + + return input; + } + + //converter methods + + public static float ParseFloat(string s, float defaultF = 0) + { + float f; + if(float.TryParse(s, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out f)) + { + return f; + } + return defaultF; + } + + public static type ConvertParsedToObject(object parsed) + { + return (type)ParsedToObject(parsed, typeof(type)); + } + + private static object ParsedToObject(object parsed,Type objtype) + { + if (parsed == null) return null; + if (Helper.IsPrimitive(objtype)) return ConvertToPrimitive(parsed, objtype); + if (objtype.IsGenericType && objtype.GetInterfaces().Contains(typeof(IList))) return ConvertToList(parsed, objtype); + if (objtype.IsGenericType && objtype.GetGenericTypeDefinition() == typeof(Dictionary<,>)) return ConvertToDictionary(parsed,objtype); + if (objtype.IsArray) return ConvertToArray(parsed, objtype); + if (objtype.IsEnum) return ConvertToEnum(parsed, objtype); + if (objtype.IsClass) return ConvertToObject(parsed, objtype); + if (objtype.IsValueType && !objtype.IsEnum) return ConvertToObject(parsed, objtype); + return null; + } + + private static object ConvertToDictionary(object parsed, Type objtype) + { + var returnObject = (dynamic)Activator.CreateInstance(objtype); + Dictionary dict = (Dictionary)parsed; + foreach (KeyValuePair keyvalue in dict) + { + dynamic key = ParsedToObject(keyvalue.Key, objtype.GetGenericArguments()[0]); + dynamic value = ParsedToObject(keyvalue.Value, objtype.GetGenericArguments()[1]); + returnObject.Add(key , value ); + } + return returnObject; + } + + private static object ConvertToObject(object parsed, Type objtype) + { + if (parsed.GetType() == typeof(string) && objtype.GetMethod("ParseForThryParser", BindingFlags.Static | BindingFlags.NonPublic) != null) + return objtype.GetMethod("ParseForThryParser", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { parsed }); + if (parsed.GetType() != typeof(Dictionary)) return null; + object returnObject = Activator.CreateInstance(objtype); + Dictionary dict = (Dictionary)parsed; + foreach (FieldInfo field in objtype.GetFields()) + { + if (dict.ContainsKey(field.Name)) + { + field.SetValue(returnObject, ParsedToObject(dict[field.Name], field.FieldType)); + } + } + foreach (PropertyInfo property in objtype.GetProperties()) + { + if (property.CanWrite && property.CanRead && property.GetIndexParameters().Length == 0 && dict.ContainsKey(property.Name)) + { + property.SetValue(returnObject, ParsedToObject(dict[property.Name], property.PropertyType), null); + } + } + return returnObject; + } + + private static object ConvertToList(object parsed, Type objtype) + { + Type list_obj_type = objtype.GetGenericArguments()[0]; + List list_strings = (List)parsed; + IList return_list = (IList)Activator.CreateInstance(objtype); + foreach (object s in list_strings) + return_list.Add(ParsedToObject(s, list_obj_type)); + return return_list; + } + + private static object ConvertToArray(object parsed, Type objtype) + { + if (objtype.BaseType == typeof(System.Array) && parsed.GetType() == typeof(string) && objtype.GetElementType().GetMethod("ParseToArrayForThryParser", BindingFlags.Static | BindingFlags.NonPublic) != null) + return objtype.GetElementType().GetMethod("ParseToArrayForThryParser", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { parsed }); + if (parsed == null || (parsed is string && (string)parsed == "")) + return null; + Type array_obj_type = objtype.GetElementType(); + List list_strings = (List)parsed; + IList return_list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(array_obj_type)); + foreach (object s in list_strings) + { + object o = ParsedToObject(s, array_obj_type); + if(o!=null) + return_list.Add(o); + } + object return_array = Activator.CreateInstance(objtype, return_list.Count); + return_list.CopyTo(return_array as Array, 0); + return return_array; + } + + private static object ConvertToEnum(object parsed, Type objtype) + { + if (Enum.IsDefined(objtype, (string)parsed)) + return Enum.Parse(objtype, (string)parsed); + Debug.LogWarning("The specified enum for " + objtype.Name + " does not exist. Existing Values are: " + Converter.ArrayToString(Enum.GetValues(objtype))); + return Enum.GetValues(objtype).GetValue(0); + } + + private static object ConvertToPrimitive(object parsed, Type objtype) + { + if (typeof(String) == objtype) + return parsed!=null?parsed.ToString():null; + if (typeof(char) == objtype) + return ((string)parsed)[0]; + return parsed; + } + + //Serilizer + + private static string SerializeDictionary(object obj) + { + string ret = "{"; + foreach (var item in (dynamic)obj) + { + object key = item.Key; + object val = item.Value; + ret += Serialize(key) + ":" + Serialize(val)+","; + } + ret = ret.TrimEnd(new char[] { ',' }); + ret += "}"; + return ret; + } + + private static string SerializeClass(object obj) + { + string ret = "{"; + foreach(FieldInfo field in obj.GetType().GetFields()) + { + if(field.IsPublic) + ret += "\""+field.Name + "\"" + ":" + ObjectToString(field.GetValue(obj)) + ","; + } + foreach (PropertyInfo property in obj.GetType().GetProperties()) + { + if(property.CanWrite && property.CanRead && property.GetIndexParameters().Length==0) + ret += "\""+ property.Name + "\"" + ":" + ObjectToString(property.GetValue(obj,null)) + ","; + } + ret = ret.TrimEnd(new char[] { ',' }); + ret += "}"; + return ret; + } + + private static string SerializeList(object obj) + { + string ret = "["; + foreach (object o in obj as IEnumerable) + { + ret += ObjectToString(o) + ","; + } + ret = ret.TrimEnd(new char[] { ',' }); + ret += "]"; + return ret; + } + + private static string SerializePrimitive(object obj) + { + if (obj.GetType() == typeof(string)) + return "\"" + obj + "\""; + return obj.ToString().Replace(",", "."); ; + } + } + + public class AnimationParser + { + public class Animation + { + public PPtrCurve[] pPtrCurves; + } + + public class PPtrCurve + { + public PPtrType curveType; + public PPtrKeyframe[] keyframes; + } + + public enum PPtrType + { + None,Material + } + + public class PPtrKeyframe + { + public float time; + public string guid; + public int type; + } + + public static Animation Parse(AnimationClip clip) + { + return Parse(AssetDatabase.GetAssetPath(clip)); + } + + public static Animation Parse(string path) + { + string data = FileHelper.ReadFileIntoString(path); + + List pPtrCurves = new List(); + int pptrIndex; + int lastIndex = 0; + while ((pptrIndex = data.IndexOf("m_PPtrCurves", lastIndex)) != -1) + { + lastIndex = pptrIndex + 1; + int pptrEndIndex = data.IndexOf(" m_", pptrIndex); + + int curveIndex; + int lastCurveIndex = pptrIndex; + //find all curves + while((curveIndex = data.IndexOf(" - curve:", lastCurveIndex, pptrEndIndex- lastCurveIndex)) != -1) + { + lastCurveIndex = curveIndex + 1; + int curveEndIndex = data.IndexOf(" script: ", curveIndex); + + PPtrCurve curve = new PPtrCurve(); + List keyframes = new List(); + + int keyFrameIndex; + int lastKeyFrameIndex = curveIndex; + while((keyFrameIndex = data.IndexOf(" - time:", lastKeyFrameIndex, curveEndIndex - lastKeyFrameIndex)) != -1) + { + lastKeyFrameIndex = keyFrameIndex + 1; + int keyFrameEndIndex = data.IndexOf("}", keyFrameIndex); + + PPtrKeyframe keyframe = new PPtrKeyframe(); + keyframe.time = float.Parse(data.Substring(keyFrameIndex, data.IndexOf("\n", keyFrameIndex, keyFrameEndIndex))); + keyframes.Add(keyframe); + } + + curve.curveType = data.IndexOf(" attribute: m_Materials", lastKeyFrameIndex, curveEndIndex - lastKeyFrameIndex) != -1 ? PPtrType.Material : PPtrType.None; + curve.keyframes = keyframes.ToArray(); + pPtrCurves.Add(curve); + } + } + Animation animation = new Animation(); + animation.pPtrCurves = pPtrCurves.ToArray(); + Debug.Log(Parser.Serialize(animation)); + return animation; + } + } +} diff --git a/Scripts/ThryEditor/Editor/ParserTests.txt b/Scripts/ThryEditor/Editor/ParserTests.txt new file mode 100644 index 0000000..8b09cf9 --- /dev/null +++ b/Scripts/ThryEditor/Editor/ParserTests.txt @@ -0,0 +1,21 @@ +##Thry.PropertyOptions +{reference_properties:[_LightingShadowMasksPan, _LightingShadowMasksUV,_LightingShadowMaskStrengthR,_LightingShadowMaskStrengthG,_LightingShadowMaskStrengthB,_LightingShadowMaskStrengthA]} +##Thry.PropertyOptions +{on_value_actions:[ +{value:0,actions:[{type:SET_PROPERTY,data:render_queue=2000}, {type:SET_PROPERTY,data:render_type=Opaque}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=0}, {type:SET_PROPERTY,data:_Cutoff=0}, {type:SET_PROPERTY,data:_SrcBlend=1}, {type:SET_PROPERTY,data:_DstBlend=0}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=1}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=1}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=0}, {type:SET_PROPERTY,data:_OutlineSrcBlend=1}, {type:SET_PROPERTY,data:_OutlineDstBlend=0}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=0}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=0},]}, +{value:1,actions:[{type:SET_PROPERTY,data:render_queue=2450}, {type:SET_PROPERTY,data:render_type=TransparentCutout}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=4}, {type:SET_PROPERTY,data:_Cutoff=.5}, {type:SET_PROPERTY,data:_SrcBlend=1}, {type:SET_PROPERTY,data:_DstBlend=0}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=1}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=1}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=0}, {type:SET_PROPERTY,data:_OutlineSrcBlend=1}, {type:SET_PROPERTY,data:_OutlineDstBlend=0}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=4},]}, +{value:9,actions:[{type:SET_PROPERTY,data:render_queue=2450}, {type:SET_PROPERTY,data:render_type=TransparentCutout}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=4}, {type:SET_PROPERTY,data:_Cutoff=0}, {type:SET_PROPERTY,data:_SrcBlend=5}, {type:SET_PROPERTY,data:_DstBlend=10}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=5}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=1}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=0}, {type:SET_PROPERTY,data:_OutlineSrcBlend=5}, {type:SET_PROPERTY,data:_OutlineDstBlend=10}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=4},]}, +{value:2,actions:[{type:SET_PROPERTY,data:render_queue=3000}, {type:SET_PROPERTY,data:render_type=Transparent}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=4}, {type:SET_PROPERTY,data:_Cutoff=0}, {type:SET_PROPERTY,data:_SrcBlend=5}, {type:SET_PROPERTY,data:_DstBlend=10}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=5}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=0}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=0}, {type:SET_PROPERTY,data:_OutlineSrcBlend=5}, {type:SET_PROPERTY,data:_OutlineDstBlend=10}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=4},]}, +{value:3,actions:[{type:SET_PROPERTY,data:render_queue=3000}, {type:SET_PROPERTY,data:render_type=Transparent}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=4}, {type:SET_PROPERTY,data:_Cutoff=0}, {type:SET_PROPERTY,data:_SrcBlend=1}, {type:SET_PROPERTY,data:_DstBlend=10}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=1}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=0}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=1}, {type:SET_PROPERTY,data:_OutlineSrcBlend=1}, {type:SET_PROPERTY,data:_OutlineDstBlend=10}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=4},]}, +{value:4,actions:[{type:SET_PROPERTY,data:render_queue=3000}, {type:SET_PROPERTY,data:render_type=Transparent}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=4}, {type:SET_PROPERTY,data:_Cutoff=0}, {type:SET_PROPERTY,data:_SrcBlend=1}, {type:SET_PROPERTY,data:_DstBlend=1}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=1}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=0}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=0}, {type:SET_PROPERTY,data:_OutlineSrcBlend=1}, {type:SET_PROPERTY,data:_OutlineDstBlend=1}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=4},]}, +{value:5,actions:[{type:SET_PROPERTY,data:render_queue=3000}, {type:SET_PROPERTY,data:render_type=Transparent}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=4}, {type:SET_PROPERTY,data:_Cutoff=0}, {type:SET_PROPERTY,data:_SrcBlend=4}, {type:SET_PROPERTY,data:_DstBlend=1}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=4}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=0}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=0}, {type:SET_PROPERTY,data:_OutlineSrcBlend=4}, {type:SET_PROPERTY,data:_OutlineDstBlend=1}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=4},]}, +{value:6,actions:[{type:SET_PROPERTY,data:render_queue=3000}, {type:SET_PROPERTY,data:render_type=Transparent}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=4}, {type:SET_PROPERTY,data:_Cutoff=0}, {type:SET_PROPERTY,data:_SrcBlend=2}, {type:SET_PROPERTY,data:_DstBlend=0}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=2}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=0}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=0}, {type:SET_PROPERTY,data:_OutlineSrcBlend=2}, {type:SET_PROPERTY,data:_OutlineDstBlend=0}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=4},]}, +{value:7,actions:[{type:SET_PROPERTY,data:render_queue=3000}, {type:SET_PROPERTY,data:render_type=Transparent}, {type:SET_PROPERTY,data:_BlendOp=0}, {type:SET_PROPERTY,data:_BlendOpAlpha=4}, {type:SET_PROPERTY,data:_Cutoff=0}, {type:SET_PROPERTY,data:_SrcBlend=2}, {type:SET_PROPERTY,data:_DstBlend=3}, {type:SET_PROPERTY,data:_SrcBlendAlpha=1}, {type:SET_PROPERTY,data:_DstBlendAlpha=1}, {type:SET_PROPERTY,data:_AddSrcBlend=2}, {type:SET_PROPERTY,data:_AddDstBlend=1}, {type:SET_PROPERTY,data:_AddSrcBlendAlpha=0}, {type:SET_PROPERTY,data:_AddDstBlendAlpha=1}, {type:SET_PROPERTY,data:_AlphaToCoverage=0}, {type:SET_PROPERTY,data:_ZWrite=0}, {type:SET_PROPERTY,data:_ZTest=4}, {type:SET_PROPERTY,data:_AlphaPremultiply=0}, {type:SET_PROPERTY,data:_OutlineSrcBlend=2}, {type:SET_PROPERTY,data:_OutlineDstBlend=3}, {type:SET_PROPERTY,data:_OutlineSrcBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineDstBlendAlpha=1}, {type:SET_PROPERTY,data:_OutlineBlendOp=0}, {type:SET_PROPERTY,data:_OutlineBlendOpAlpha=4},]} +}]}]} +##Thry.ButtonData +{ + "text": "Click here & follow thry on twitter !", + "texture": {"name": "https://i.kym-cdn.com/entries/icons/facebook/000/037/349/Screenshot_14.jpg", "height": 100}, + "action": "https://twitter.com/thryrallo", + "center_position": true +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Presets.cs b/Scripts/ThryEditor/Editor/Presets.cs new file mode 100644 index 0000000..e227421 --- /dev/null +++ b/Scripts/ThryEditor/Editor/Presets.cs @@ -0,0 +1,548 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace Thry.ThryEditor +{ + public class Presets : AssetPostprocessor + { + const string TAG_IS_PRESET = "isPreset"; + const string TAG_POSTFIX_IS_PRESET = "_isPreset"; + const string TAG_PRESET_NAME = "presetName"; + const string FILE_NAME_CACHE = "Thry/preset_cache.txt"; + + static Dictionary s_appliedPresets = new Dictionary(); + + static string[] p_presetNames; + static Dictionary s_presetGuids; + static Dictionary s_presetMaterials; + static string[] s_presetNames { get + { + if (p_presetNames == null) + { + // Get current time + var time = System.DateTime.Now; + // Check if cache exists + if(File.Exists(FILE_NAME_CACHE)) + { + string raw = File.ReadAllText(FILE_NAME_CACHE); + // If file is empty (no presets), create empty, parsing would throw error + if(string.IsNullOrWhiteSpace(raw)) + { + s_presetGuids = new Dictionary(); + p_presetNames = new string[0]; + s_presetMaterials = new Dictionary(); + } + else + { + // Load from cache + string[][] lines = raw.Split('\n').Select(s => s.Split(';')).ToArray(); + // Split into lines + s_presetGuids = lines.Select(l => (l[0], l[1])).ToDictionary(t => t.Item1, t => t.Item2); + p_presetNames = lines.Select(l => l[0]).Prepend("").ToArray(); + s_presetMaterials = new Dictionary(); + } + }else + { + CreatePresetCache(); + } + // Log time + // Debug.Log($"Presets: {p_presetNames.Length} presets found in {System.DateTime.Now - time}"); + } + return p_presetNames; + } + } + + static void CreatePresetCache() + { + // Create cache + // Find all materials + string[] guids = AssetDatabase.FindAssets("t:material"); + List presetMaterials = new List(); + for(int guid = 0; guid < guids.Length; guid++) + { + EditorUtility.DisplayProgressBar("Creating Preset Cache", $"Loading material {guid + 1}/{guids.Length}", (float)guid / guids.Length); + // Load material + Material material = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[guid])); + // Check if material is preset + if (IsPreset(material)) + { + // Add to list + presetMaterials.Add(material); + } + } + EditorUtility.ClearProgressBar(); + // Create data + p_presetNames = new string[presetMaterials.Count]; + s_presetMaterials = new Dictionary(); + s_presetGuids = new Dictionary(); + for(int i = 0; i < presetMaterials.Count; i++) + { + p_presetNames[i] = presetMaterials[i].GetTag(TAG_PRESET_NAME, false, presetMaterials[i].name).Replace(';', '_'); + s_presetMaterials[p_presetNames[i]] = presetMaterials[i]; + s_presetGuids[p_presetNames[i]] = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(presetMaterials[i])); + } + // Save cache + Save(); + } + + static void Save() + { + // Save cache + FileHelper.CreateFileWithDirectories(FILE_NAME_CACHE); + File.WriteAllText(FILE_NAME_CACHE, string.Join("\n", s_presetGuids.Select(kvp => $"{kvp.Key};{kvp.Value}"))); + } + + // On Asset Delete remove presets from cache + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) + { + if(s_presetNames == null) return; // should init stuff + + if(importedAssets.Length > 0) + { + // Check if any presets were imported + foreach (string asset in importedAssets) + { + // Check if asset is material + if (asset.EndsWith(".mat")) + { + Material material = AssetDatabase.LoadAssetAtPath(asset); + // Check if asset is preset + if (IsPreset(material)) + { + // Add preset + AddPreset(material); + } + } + } + } + + if(deletedAssets.Length > 0) + { + Dictionary presetPaths = new Dictionary(); + // get all paths from guids + foreach (var kvp in s_presetGuids) + { + presetPaths.Add(AssetDatabase.GUIDToAssetPath(kvp.Value), kvp.Key); + } + // Check if any presets were deleted + foreach (string asset in deletedAssets) + { + // Check if asset is material + if (asset.EndsWith(".mat")) + { + // Check if asset is preset + if (presetPaths.ContainsKey(asset)) + { + // Remove preset + RemovePreset(presetPaths[asset]); + } + } + } + } + } + + static void AddPreset(Material material) + { + Debug.Log($"AddPreset: {material.name}"); + // Get preset name + string presetName = material.GetTag(TAG_PRESET_NAME, false, material.name).Replace(';', '_'); + // Get preset guid + string presetGuid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(material)); + // Add to cache + p_presetNames = s_presetNames.Append(presetName).ToArray(); + s_presetGuids[presetName] = presetGuid; + s_presetMaterials[presetName] = material; + // Save cache + Save(); + } + + static void RemovePreset(Material material) + { + // Get preset name + string presetName = material.GetTag(TAG_PRESET_NAME, false, material.name).Replace(';', '_'); + // Remove from cache + p_presetNames = s_presetNames.Where(n => n != presetName).ToArray(); + s_presetGuids.Remove(presetName); + s_presetMaterials.Remove(presetName); + // Save cache + Save(); + } + + static void RemovePreset(string name) + { + Debug.Log($"RemovePreset: {name}"); + // Get preset name + string presetName = name.Replace(';', '_'); + // Remove from cache + p_presetNames = s_presetNames.Where(n => n != presetName).ToArray(); + s_presetGuids.Remove(presetName); + s_presetMaterials.Remove(presetName); + // Save cache + Save(); + } + + public static Material GetPresetMaterial(string presetName) + { + if (s_presetMaterials.ContainsKey(presetName)) + { + return s_presetMaterials[presetName]; + } + else if(s_presetGuids.ContainsKey(presetName)) + { + Material m = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(s_presetGuids[presetName])); + s_presetMaterials[presetName] = m; + return m; + } + return null; + } + + public static bool DoesPresetExist(string presetName) + { + return s_presetGuids.ContainsKey(presetName); + } + + private static PresetsPopupGUI window; + public static void OpenPresetsMenu(Rect r, ShaderEditor shaderEditor) + { + Event.current.Use(); + if (Event.current.button == 0) + { + Vector2 pos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); + pos.x = Mathf.Min(EditorWindow.focusedWindow.position.x + EditorWindow.focusedWindow.position.width - 250, pos.x); + pos.y = Mathf.Min(EditorWindow.focusedWindow.position.y + EditorWindow.focusedWindow.position.height - 200, pos.y); + if (window != null) + window.Close(); + window = ScriptableObject.CreateInstance(); + window.position = new Rect(pos.x, pos.y, 250, 200); + window.Init(s_presetNames, shaderEditor); + window.titleContent = new GUIContent("Preset List"); + window.ShowUtility(); + } + else + { + EditorUtility.DisplayCustomMenu(GUILayoutUtility.GetLastRect(), s_presetNames.Select(s => new GUIContent(s)).ToArray(), 0, ApplyQuickPreset, shaderEditor); + } + } + + static void ApplyQuickPreset(object userData, string[] options, int selected) + { + Apply(GetPresetMaterial(p_presetNames[selected - 1]), userData as ShaderEditor); + } + + public static void PresetEditorGUI(ShaderEditor shaderEditor) + { + if (shaderEditor.IsPresetEditor) + { + EditorGUILayout.LabelField(EditorLocale.editor.Get("preset_material_notify"), Styles.greenStyle); + string name = shaderEditor.Materials[0].GetTag(TAG_PRESET_NAME, false, ""); + EditorGUI.BeginChangeCheck(); + name = EditorGUILayout.TextField(EditorLocale.editor.Get("preset_name"), name); + if (EditorGUI.EndChangeCheck()) + { + shaderEditor.Materials[0].SetOverrideTag(TAG_PRESET_NAME, name); + p_presetNames = null; + } + } + if (s_appliedPresets.ContainsKey(shaderEditor.Materials[0])) + { + if(GUILayout.Button(EditorLocale.editor.Get("preset_revert")+s_appliedPresets[shaderEditor.Materials[0]].Item1.name)) + { + Revert(shaderEditor); + } + } + } + + public static void Apply(Material preset, ShaderEditor shaderEditor) + { + s_appliedPresets[shaderEditor.Materials[0]] = (preset, new Material(shaderEditor.Materials[0])); + foreach (ShaderPart prop in shaderEditor.ShaderParts) + { + if (IsPreset(preset, prop)) + { + prop.CopyFromMaterial(preset); + } + } + foreach (Material m in shaderEditor.Materials) + MaterialEditor.ApplyMaterialPropertyDrawers(m); + } + + static void Revert(ShaderEditor shaderEditor) + { + Material key = shaderEditor.Materials[0]; + Material preset = s_appliedPresets[key].Item1; + Material prePreset = s_appliedPresets[key].Item2; + foreach (ShaderPart prop in shaderEditor.ShaderParts) + { + if (IsPreset(preset, prop)) + { + prop.CopyFromMaterial(prePreset); + } + } + foreach (Material m in shaderEditor.Materials) + MaterialEditor.ApplyMaterialPropertyDrawers(m); + s_appliedPresets.Remove(key); + } + + public static void ApplyList(ShaderEditor shaderEditor, Material[] originals, List presets) + { + for(int i=0;i IsPreset(m)); + } + + public static bool IsPreset(Material m) + { + return m.GetTag(TAG_IS_PRESET, false, "false") == "true"; + } + + [MenuItem("Assets/Thry/Mark as preset")] + static void MarkAsPreset() + { + IEnumerable mats = Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)). + Where(p => AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Material)).Select(p => AssetDatabase.LoadAssetAtPath(p)); + foreach (Material m in mats) + { + m.SetOverrideTag(TAG_IS_PRESET, "true"); + if (m.GetTag("presetName", false, "") == "") m.SetOverrideTag("presetName", m.name); + Presets.AddPreset(m); + } + p_presetNames = null; + } + + [MenuItem("Assets/Thry/Mark as preset", true)] + static bool MarkAsPresetValid() + { + return Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)). + All(p => AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Material)); + } + + [MenuItem("Assets/Thry/Remove as preset")] + static void RemoveAsPreset() + { + IEnumerable mats = Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)). + Where(p => AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Material)).Select(p => AssetDatabase.LoadAssetAtPath(p)); + foreach (Material m in mats) + { + m.SetOverrideTag(TAG_IS_PRESET, ""); + Presets.RemovePreset(m); + } + p_presetNames = null; + } + + [MenuItem("Assets/Thry/Remove as preset", true)] + static bool RemoveAsPresetValid() + { + return Selection.assetGUIDs.Select(g => AssetDatabase.GUIDToAssetPath(g)). + All(p => AssetDatabase.GetMainAssetTypeAtPath(p) == typeof(Material)); + } + + [MenuItem("Thry/Presets/Rebuild Cache", priority = 100)] + static void RebuildCache() + { + Presets.CreatePresetCache(); + } + } + + public class PresetsPopupGUI : EditorWindow + { + class PresetStruct + { + public Dictionary dict; + string name; + string fullName; + bool hasPreset; + bool isOpen = false; + bool isOn; + public PresetStruct(string name) + { + this.name = name; + dict = new Dictionary(); + } + + public PresetStruct GetSubStruct(string name) + { + name = name.Trim(); + if (dict.ContainsKey(name) == false) + dict.Add(name, new PresetStruct(name)); + return dict[name]; + } + public void SetHasPreset(bool b, string fullName) + { + this.hasPreset = b; + this.fullName = fullName; + } + public void StructGUI(PresetsPopupGUI popupGUI) + { + if(hasPreset) + { + EditorGUI.BeginChangeCheck(); + isOn = EditorGUILayout.ToggleLeft(name, isOn); + if (EditorGUI.EndChangeCheck()) + { + popupGUI.ToggelPreset(Presets.GetPresetMaterial(fullName), isOn); + } + } + if(dict.Count > 0) + { + Rect r = GUILayoutUtility.GetRect(new GUIContent(), Styles.dropDownHeader); + r.x = EditorGUI.indentLevel * 15; + r.width -= r.x; + GUI.Box(r, name, Styles.dropDownHeader); + if (Event.current.type == EventType.Repaint) + { + var toggleRect = new Rect(r.x + 4f, r.y + 2f, 13f, 13f); + EditorStyles.foldout.Draw(toggleRect, false, false, isOpen, false); + } + if (Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition)) + { + isOpen = !isOpen; + ShaderEditor.Input.Use(); + } + if (isOpen) + { + EditorGUI.indentLevel += 1; + foreach (KeyValuePair struc in dict) + { + struc.Value.StructGUI(popupGUI); + } + EditorGUI.indentLevel -= 1; + } + } + + } + + public void Reset() + { + isOn = false; + foreach (KeyValuePair struc in dict) + struc.Value.Reset(); + } + } + + Material[] beforePreset; + List tickedPresets = new List(); + PresetStruct mainStruct; + ShaderEditor shaderEditor; + public void Init(string[] names, ShaderEditor shaderEditor) + { + this.shaderEditor = shaderEditor; + ShaderOptimizer.DetourApplyMaterialPropertyDrawers(); + this.beforePreset = shaderEditor.Materials.Select(m => new Material(m)).ToArray(); + ShaderOptimizer.RestoreApplyMaterialPropertyDrawers(); + mainStruct = new PresetStruct(""); + backgroundTextrure = new Texture2D(1,1); + if (EditorGUIUtility.isProSkin) backgroundTextrure.SetPixel(0, 0, new Color(0.18f, 0.18f, 0.18f, 1)); + else backgroundTextrure.SetPixel(0, 0, new Color(0.9f, 0.9f, 0.9f, 1)); + backgroundTextrure.Apply(); + for (int i = 1; i < names.Length; i++) + { + string[] path = names[i].Split('/'); + PresetStruct addUnder = mainStruct; + for (int j=0;j struc in mainStruct.dict) + { + struc.Value.StructGUI(this); + } + } + + void Revert() + { + EditorUtility.DisplayProgressBar("Reverting", "Reverting", 0); + for (int i = 0; i < shaderEditor.Materials.Length; i++) + { + EditorUtility.DisplayProgressBar("Reverting", "Reverting", (float)i / shaderEditor.Materials.Length); + shaderEditor.Materials[i].CopyPropertiesFromMaterial(beforePreset[i]); + MaterialEditor.ApplyMaterialPropertyDrawers(shaderEditor.Materials[i]); + } + EditorUtility.ClearProgressBar(); + mainStruct.Reset(); + shaderEditor.Repaint(); + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Settings.cs b/Scripts/ThryEditor/Editor/Settings.cs new file mode 100644 index 0000000..efcd8de --- /dev/null +++ b/Scripts/ThryEditor/Editor/Settings.cs @@ -0,0 +1,401 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEditor.PackageManager; +using UnityEngine; + +namespace Thry +{ + public abstract class ModuleSettings + { + public const string MODULES_CONFIG = "Thry/modules_config"; + + public abstract void Draw(); + } + + public class Settings : EditorWindow + { + + public static void firstTimePopup() + { + Settings window = (Settings)EditorWindow.GetWindow(typeof(Settings)); + window._isFirstPopop = true; + window.Show(); + } + + public static void updatedPopup(int compare) + { + Settings window = (Settings)EditorWindow.GetWindow(typeof(Settings)); + window._updatedVersion = compare; + window.Show(); + } + + public new void Show() + { + base.Show(); + this.titleContent = new GUIContent("Thry Settings"); + } + + public ModuleSettings[] moduleSettings; + + private bool _isFirstPopop = false; + private int _updatedVersion = 0; + + private bool _is_init = false; + private bool _isInstallingVAI = false; + Vector2 _scrollPosition; + + public static ButtonData thry_message = null; + + //---------------------Stuff checkers and fixers------------------- + + public void Awake() + { + InitVariables(); + } + + private void InitVariables() + { + List subclasses = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(type => type.IsSubclassOf(typeof(ModuleSettings))).ToList(); + moduleSettings = new ModuleSettings[subclasses.Count]; + int i = 0; + foreach(Type classtype in subclasses) + { + moduleSettings[i++] = (ModuleSettings)Activator.CreateInstance(classtype); + } + + _is_init = true; + + if (thry_message == null) + WebHelper.DownloadStringASync(Thry.URL.SETTINGS_MESSAGE_URL, (Action)delegate (string s) { thry_message = Parser.Deserialize(s); }); + } + + //------------------Main GUI + void OnGUI() + { + if (!_is_init || moduleSettings==null) InitVariables(); + GUILayout.Label("ThryEditor v" + Config.Singleton.verion); + + _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); + GUINotification(); + DrawHorizontalLine(); + GUIMessage(); + LocaleDropdown(); + GUIEditor(); + DrawHorizontalLine(); + foreach(ModuleSettings s in moduleSettings) + { + s.Draw(); + DrawHorizontalLine(); + } + GUIVRChatAssetInstaller(); + EditorGUILayout.EndScrollView(); + } + + //--------------------------GUI Helpers----------------------------- + + private static void DrawHorizontalLine() + { + Rect rect = EditorGUILayout.GetControlRect(false, 1); + rect.height = 1; + EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 1)); + } + + private void GUINotification() + { + if (_isFirstPopop) + GUILayout.Label(" " + EditorLocale.editor.Get("first_install_message"), Styles.greenStyle); + else if (_updatedVersion == -1) + GUILayout.Label(" " + EditorLocale.editor.Get("update_message"), Styles.greenStyle); + else if (_updatedVersion == 1) + GUILayout.Label(" " + EditorLocale.editor.Get("downgrade_message"), Styles.orangeStyle); + } + + private void GUIMessage() + { + if(thry_message!=null) + { + bool doDrawLine = false; + if(thry_message.text.Length > 0) + { + doDrawLine = true; + GUILayout.Label(new GUIContent(thry_message.text,thry_message.hover), thry_message.center_position?Styles.richtext_center: Styles.richtext); + Rect r = GUILayoutUtility.GetLastRect(); + if(thry_message.action.type != DefineableActionType.NONE) + EditorGUIUtility.AddCursorRect(r, MouseCursor.Link); + if (Event.current.type == EventType.MouseDown && r.Contains(Event.current.mousePosition)) + thry_message.action.Perform(ShaderEditor.Active?.Materials); + } + if(thry_message.texture != null) + { + doDrawLine = true; + if(thry_message.center_position) GUILayout.Label(new GUIContent(thry_message.texture.loaded_texture, thry_message.hover), EditorStyles.centeredGreyMiniLabel, GUILayout.MaxHeight(thry_message.texture.height)); + else GUILayout.Label(new GUIContent(thry_message.texture.loaded_texture, thry_message.hover), GUILayout.MaxHeight(thry_message.texture.height)); + Rect r = GUILayoutUtility.GetLastRect(); + if(thry_message.action.type != DefineableActionType.NONE) + EditorGUIUtility.AddCursorRect(r, MouseCursor.Link); + if (Event.current.type == EventType.MouseDown && r.Contains(Event.current.mousePosition)) + thry_message.action.Perform(ShaderEditor.Active?.Materials); + } + if(doDrawLine) + DrawHorizontalLine(); + } + } + + private void GUIEditor() + { + EditorGUILayout.Space(); + GUILayout.Label(EditorLocale.editor.Get("shader_ui_design_header"), EditorStyles.boldLabel); + Dropdown("default_texture_type"); + Toggle("showRenderQueue"); + + EditorGUILayout.Space(); + GUILayout.Label(EditorLocale.editor.Get("shader_ui_features_header"), EditorStyles.boldLabel); + EditorGUILayout.Space(); + Toggle("autoMarkPropertiesAnimated"); + Toggle("allowCustomLockingRenaming"); + GUIGradients(); + + EditorGUILayout.Space(); + GUILayout.Label(EditorLocale.editor.Get("avatar_fixes_header"), EditorStyles.boldLabel); + Toggle("autoSetAnchorOverride"); + Dropdown("humanBoneAnchor"); + Text("anchorOverrideObjectName"); + + EditorGUILayout.Space(); + GUILayout.Label(EditorLocale.editor.Get("textures_header"), EditorStyles.boldLabel); + Dropdown("texturePackerCompressionWithAlphaOverwrite"); + Dropdown("texturePackerCompressionNoAlphaOverwrite"); + Dropdown("gradientEditorCompressionOverwrite"); + + EditorGUILayout.Space(); + GUILayout.Label(EditorLocale.editor.Get("technical_header"), EditorStyles.boldLabel); + Toggle("forceAsyncCompilationPreview"); + Toggle("saveAfterLockUnlock"); + Toggle("fixKeywordsWhenLocking"); + + EditorGUILayout.Space(); + GUILayout.Label(EditorLocale.editor.Get("developer_header"), EditorStyles.boldLabel); + Toggle("showManualReloadButton"); + Toggle("enableDeveloperMode"); + if(Config.Singleton.enableDeveloperMode) + { + Toggle("disableUnlockedShaderStrippingOnBuild"); + } + } + + private static void GUIGradients() + { + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(false)); + Text("gradient_name", false); + string gradient_name = Config.Singleton.gradient_name; + if (gradient_name.Contains("")) + GUILayout.Label(EditorLocale.editor.Get("gradient_good_naming"), Styles.greenStyle, GUILayout.ExpandWidth(false)); + else if (gradient_name.Contains("")) + if (gradient_name.Contains("")) + GUILayout.Label(EditorLocale.editor.Get("gradient_good_naming"), Styles.greenStyle, GUILayout.ExpandWidth(false)); + else + GUILayout.Label(EditorLocale.editor.Get("gradient_add_hash_or_prop"), Styles.orangeStyle, GUILayout.ExpandWidth(false)); + else if (gradient_name.Contains("")) + GUILayout.Label(EditorLocale.editor.Get("gradient_add_material"), Styles.orangeStyle, GUILayout.ExpandWidth(false)); + else + GUILayout.Label(EditorLocale.editor.Get("gradient_add_material_or_prop"), Styles.redStyle, GUILayout.ExpandWidth(false)); + GUILayout.EndHorizontal(); + } + + private class TextPopup : EditorWindow + { + public string text = ""; + private Vector2 scroll; + void OnGUI() + { + EditorGUILayout.SelectableLabel(EditorLocale.editor.Get("my_data_header"), EditorStyles.boldLabel); + Rect last = GUILayoutUtility.GetLastRect(); + + Rect data_rect = new Rect(0, last.height, Screen.width, Screen.height - last.height); + scroll = EditorGUILayout.BeginScrollView(scroll, GUILayout.Width(data_rect.width), GUILayout.Height(data_rect.height)); + GUILayout.TextArea(text); + EditorGUILayout.EndScrollView(); + } + } + + static Type s_vrchatAssetInstallerUIType {get; set;} = Helper.FindTypeByFullName("Thry.VRChatAssetInstaller.VAI_UI"); + + private void GUIVRChatAssetInstaller() + { + // check if Thry.VRChatAssetInstaller.VAI_UI exists + if(s_vrchatAssetInstallerUIType != null) + { + if(GUILayout.Button("Open VRChat Asset Installer")) + { + s_vrchatAssetInstallerUIType.GetMethod("ShowWindow").Invoke(null, null); + } + }else + { + EditorGUILayout.HelpBox("VRChat Asset Installer is an external asset that allows you to easily find and install assets for VRChat into your project. It has various community prefabs and tools availabe for one click installation. It is not an alternative to VCC, but an addition as it uses unitypackages and UPM instead of VPM.", MessageType.Info); + EditorGUI.BeginDisabledGroup(_isInstallingVAI); + if(GUILayout.Button("Install VRChat Asset Installer")) + { + _isInstallingVAI = true; + Client.Add("https://github.com/Thryrallo/VRChat-Assets-Installer.git"); + } + EditorGUI.EndDisabledGroup(); + } + } + + private static void Text(string configField, bool createHorizontal = true) + { + Text(configField, EditorLocale.editor.Get(configField), EditorLocale.editor.Get(configField + "_tooltip"), createHorizontal); + } + + private static void Text(string configField, string[] content, bool createHorizontal=true) + { + Text(configField, content[0], content[1], createHorizontal); + } + + private static void Text(string configField, string text, string tooltip, bool createHorizontal) + { + Config config = Config.Singleton; + System.Reflection.FieldInfo field = typeof(Config).GetField(configField); + if (field != null) + { + string value = (string)field.GetValue(config); + if (createHorizontal) + GUILayout.BeginHorizontal(GUILayout.ExpandWidth(false)); + GUILayout.Space(57); + GUILayout.Label(new GUIContent(text, tooltip), GUILayout.ExpandWidth(false)); + EditorGUI.BeginChangeCheck(); + value = EditorGUILayout.DelayedTextField("", value, GUILayout.ExpandWidth(false)); + if (EditorGUI.EndChangeCheck()) + { + field.SetValue(config, value); + config.Save(); + } + if (createHorizontal) + GUILayout.EndHorizontal(); + } + } + + private static void Toggle(string configField, GUIStyle label_style = null) + { + Toggle(configField, EditorLocale.editor.Get(configField), EditorLocale.editor.Get(configField + "_tooltip"), label_style); + } + + private static void Toggle(string configField, string[] content, GUIStyle label_style = null) + { + Toggle(configField, content[0], content[1], label_style); + } + + private static void Toggle(string configField, string label, string hover, GUIStyle label_style = null) + { + Config config = Config.Singleton; + System.Reflection.FieldInfo field = typeof(Config).GetField(configField); + if (field != null) + { + bool value = (bool)field.GetValue(config); + if (Toggle(value, label, hover, label_style) != value) + { + field.SetValue(config, !value); + config.Save(); + ShaderEditor.RepaintActive(); + } + } + } + + private static void Dropdown(string configField) + { + Dropdown(configField, EditorLocale.editor.Get(configField),EditorLocale.editor.Get(configField+"_tooltip")); + } + + private static void Dropdown(string configField, string[] content) + { + Dropdown(configField, content[0], content[1]); + } + + private static void Dropdown(string configField, string label, string hover, GUIStyle label_style = null) + { + Config config = Config.Singleton; + System.Reflection.FieldInfo field = typeof(Config).GetField(configField); + if (field != null) + { + Enum value = (Enum)field.GetValue(config); + EditorGUI.BeginChangeCheck(); + EditorGUILayout.BeginHorizontal(); + GUILayout.Space(57); + GUILayout.Label(new GUIContent(label, hover), GUILayout.ExpandWidth(false)); + value = EditorGUILayout.EnumPopup(value,GUILayout.ExpandWidth(false)); + EditorGUILayout.EndHorizontal(); + if(EditorGUI.EndChangeCheck()) + { + field.SetValue(config, value); + config.Save(); + ShaderEditor.RepaintActive(); + } + } + } + + private static void LocaleDropdown() + { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.BeginHorizontal(); + GUILayout.Label(new GUIContent(EditorLocale.editor.Get("locale"), EditorLocale.editor.Get("locale_tooltip")), GUILayout.ExpandWidth(false)); + EditorLocale.editor.selected_locale_index = EditorGUILayout.Popup(EditorLocale.editor.selected_locale_index, EditorLocale.editor.available_locales, GUILayout.ExpandWidth(false)); + if(EditorLocale.editor.Get("translator").Length>0) + GUILayout.Label(EditorLocale.editor.Get("translation") +": "+EditorLocale.editor.Get("translator"), GUILayout.ExpandWidth(false)); + EditorGUILayout.EndHorizontal(); + if(EditorGUI.EndChangeCheck()) + { + Config.Singleton.locale = EditorLocale.editor.available_locales[EditorLocale.editor.selected_locale_index]; + Config.Singleton.Save(); + ShaderEditor.ReloadActive(); + } + } + + private static bool Toggle(bool val, string text, GUIStyle label_style = null) + { + return Toggle(val, text, "",label_style); + } + + private static bool Toggle(bool val, string text, string tooltip, GUIStyle label_style=null) + { + GUILayout.BeginHorizontal(); + GUILayout.Space(35); + val = GUILayout.Toggle(val, new GUIContent("", tooltip), GUILayout.ExpandWidth(false)); + if(label_style==null) + GUILayout.Label(new GUIContent(text, tooltip)); + else + GUILayout.Label(new GUIContent(text, tooltip),label_style); + GUILayout.EndHorizontal(); + return val; + } + + private static bool Foldout(string text, bool expanded) + { + return Foldout(new GUIContent(text), expanded); + } + + private static bool Foldout(GUIContent content, bool expanded) + { + var rect = GUILayoutUtility.GetRect(16f + 20f, 22f, Styles.dropDownHeader); + rect = EditorGUI.IndentedRect(rect); + GUI.Box(rect, content, Styles.dropDownHeader); + var toggleRect = new Rect(rect.x + 4f, rect.y + 2f, 13f, 13f); + Event e = Event.current; + if (e.type == EventType.Repaint) + EditorStyles.foldout.Draw(toggleRect, false, false, expanded, false); + if (e.type == EventType.MouseDown && toggleRect.Contains(e.mousePosition) && !e.alt) + { + expanded = !expanded; + e.Use(); + } + return expanded; + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/ShaderOptimizer.cs b/Scripts/ThryEditor/Editor/ShaderOptimizer.cs new file mode 100644 index 0000000..7c97625 --- /dev/null +++ b/Scripts/ThryEditor/Editor/ShaderOptimizer.cs @@ -0,0 +1,2549 @@ +//Original Code from https://github.com/DarthShader/Kaj-Unity-Shaders +/**MIT License + +Copyright (c) 2020 DarthShader + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.**/ + +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using System; +using System.IO; +using System.Text.RegularExpressions; +using System.Text; +using System.Globalization; +using System.Linq; +using System.Security.Cryptography; +using Object = UnityEngine.Object; +using System.Reflection; +#if VRC_SDK_VRCSDK3 +using VRC.SDKBase; +#endif +#if VRC_SDK_VRCSDK2 +using VRCSDK2; +#endif +#if VRC_SDK_VRCSDK2 || VRC_SDK_VRCSDK3 +using VRC.SDKBase.Editor.BuildPipeline; +#endif +#if VRC_SDK_VRCSDK3 && !UDON +using static VRC.SDK3.Avatars.Components.VRCAvatarDescriptor; +using VRC.SDK3.Avatars.Components; +#endif + +namespace Thry +{ + + public enum LightMode + { + Always=1, + ForwardBase=2, + ForwardAdd=4, + Deferred=8, + ShadowCaster=16, + MotionVectors=32, + PrepassBase=64, + PrepassFinal=128, + Vertex=256, + VertexLMRGBM=512, + VertexLM=1024 + } + + // Static methods to generate new shader files with in-place constants based on a material's properties + // and link that new shader to the material automatically + public class ShaderOptimizer + { + // Tags + public const string TAG_ORIGINAL_SHADER = "OriginalShader"; + public const string TAG_ALL_MATERIALS_GUIDS_USING_THIS_LOCKED_SHADER = "AllLockedGUIDS"; + //When locking don't include code from define blocks that are not enabled + const bool REMOVE_UNUSED_IF_DEFS = true; + + // For some reason, 'if' statements with replaced constant (literal) conditions cause some compilation error + // So until that is figured out, branches will be removed by default + // Set to false if you want to keep UNITY_BRANCH and [branch] + public static bool RemoveUnityBranches = true; + + // LOD Crossfade Dithing doesn't have multi_compile keyword correctly toggled at build time (its always included) so + // this hard-coded material property will uncomment //#pragma multi_compile _ LOD_FADE_CROSSFADE in optimized .shader files + public static readonly string LODCrossFadePropertyName = "_LODCrossfade"; + + // IgnoreProjector and ForceNoShadowCasting don't work as override tags, so material properties by these names + // will determine whether or not //"IgnoreProjector"="True" etc. will be uncommented in optimized .shader files + public static readonly string IgnoreProjectorPropertyName = "_IgnoreProjector"; + public static readonly string ForceNoShadowCastingPropertyName = "_ForceNoShadowCasting"; + + // Material property suffix that controls whether the property of the same name gets baked into the optimized shader + // e.g. if _Color exists and _ColorAnimated = 1, _Color will not be baked in + public static readonly string AnimatedPropertySuffix = "Animated"; + public static readonly string AnimatedTagSuffix = "Animated"; + public static readonly string ExemptFromLockingSuffix = "NL"; + + // Currently, Material.SetShaderPassEnabled doesn't work on "ShadowCaster" lightmodes, + // and doesn't let "ForwardAdd" lights get turned into vertex lights if "ForwardAdd" is simply disabled + // vs. if the pases didn't exist at all in the shader. + // The Optimizer will take a mask property by this name and attempt to correct these issues + // by hard-removing the shadowcaster and fwdadd passes from the shader being optimized. + public static readonly string DisabledLightModesPropertyName = "_LightModes"; + + // Property that determines whether or not to evaluate KSOInlineSamplerState comments. + // Inline samplers can be used to get a wider variety of wrap/filter combinations at the cost + // of only having 1x anisotropic filtering on all textures + public static readonly string UseInlineSamplerStatesPropertyName = "_InlineSamplerStates"; + private static bool UseInlineSamplerStates = true; + + // Material properties are put into each CGPROGRAM as preprocessor defines when the optimizer is run. + // This is mainly targeted at culling interpolators and lines that rely on those interpolators. + // (The compiler is not smart enough to cull VS output that isn't used anywhere in the PS) + // Additionally, simply enabling the optimizer can define a keyword, whose name is stored here. + // This keyword is added to the beginning of all passes, right after CGPROGRAM + public static readonly string OptimizerEnabledKeyword = "OPTIMIZER_ENABLED"; + + // Mega shaders are expected to have geometry and tessellation shaders enabled by default, + // but with the ability to be disabled by convention property names when the optimizer is run. + // Additionally, they can be removed per-lightmode by the given property name plus + // the lightmode name as a suffix (e.g. group_toggle_GeometryShadowCaster) + // Geometry and Tessellation shaders are REMOVED by default, but if the main gorups + // are enabled certain pass types are assumed to be ENABLED + public static readonly string GeometryShaderEnabledPropertyName = "GeometryShader_Enabled"; + public static readonly string TessellationEnabledPropertyName = "Tessellation_Enabled"; + private static bool UseGeometry = false; + private static bool UseGeometryForwardBase = true; + private static bool UseGeometryForwardAdd = true; + private static bool UseGeometryShadowCaster = true; + private static bool UseGeometryMeta = true; + private static bool UseTessellation = false; + private static bool UseTessellationForwardBase = true; + private static bool UseTessellationForwardAdd = true; + private static bool UseTessellationShadowCaster = true; + private static bool UseTessellationMeta = false; + + // Tessellation can be slightly optimized with a constant max tessellation factor attribute + // on the hull shader. A non-animated property by this name will replace the argument of said + // attribute if it exists. + public static readonly string TessellationMaxFactorPropertyName = "_TessellationFactorMax"; + + enum LightModeType { None, ForwardBase, ForwardAdd, ShadowCaster, Meta }; + private static LightModeType CurrentLightmode = LightModeType.None; + + // In-order list of inline sampler state names that will be replaced by InlineSamplerState() lines + public static readonly string[] InlineSamplerStateNames = new string[] + { + "_linear_repeat", + "_linear_clamp", + "_linear_mirror", + "_linear_mirroronce", + "_point_repeat", + "_point_clamp", + "_point_mirror", + "_point_mirroronce", + "_trilinear_repeat", + "_trilinear_clamp", + "_trilinear_mirror", + "_trilinear_mirroronce" + }; + + // Would be better to dynamically parse the "C:\Program Files\UnityXXXX\Editor\Data\CGIncludes\" folder + // to get version specific includes but eh + public static readonly HashSet DefaultUnityShaderIncludes = new HashSet() + { + "UnityUI.cginc", + "AutoLight.cginc", + "GLSLSupport.glslinc", + "HLSLSupport.cginc", + "Lighting.cginc", + "SpeedTreeBillboardCommon.cginc", + "SpeedTreeCommon.cginc", + "SpeedTreeVertex.cginc", + "SpeedTreeWind.cginc", + "TerrainEngine.cginc", + "TerrainSplatmapCommon.cginc", + "Tessellation.cginc", + "UnityBuiltin2xTreeLibrary.cginc", + "UnityBuiltin3xTreeLibrary.cginc", + "UnityCG.cginc", + "UnityCG.glslinc", + "UnityCustomRenderTexture.cginc", + "UnityDeferredLibrary.cginc", + "UnityDeprecated.cginc", + "UnityGBuffer.cginc", + "UnityGlobalIllumination.cginc", + "UnityImageBasedLighting.cginc", + "UnityInstancing.cginc", + "UnityLightingCommon.cginc", + "UnityMetaPass.cginc", + "UnityPBSLighting.cginc", + "UnityShaderUtilities.cginc", + "UnityShaderVariables.cginc", + "UnityShadowLibrary.cginc", + "UnitySprites.cginc", + "UnityStandardBRDF.cginc", + "UnityStandardConfig.cginc", + "UnityStandardCore.cginc", + "UnityStandardCoreForward.cginc", + "UnityStandardCoreForwardSimple.cginc", + "UnityStandardInput.cginc", + "UnityStandardMeta.cginc", + "UnityStandardParticleInstancing.cginc", + "UnityStandardParticles.cginc", + "UnityStandardParticleShadow.cginc", + "UnityStandardShadow.cginc", + "UnityStandardUtils.cginc" + }; + + public static readonly HashSet ValidSeparators = new HashSet() { ' ', '\t', '\r', '\n', ';', ',', '.', '(', ')', '[', ']', '{', '}', '>', '<', '=', '!', '&', '|', '^', '+', '-', '*', '/', '#' }; + + public static readonly HashSet DontRemoveIfBranchesKeywords = new HashSet() { "UNITY_SINGLE_PASS_STEREO", "FORWARD_BASE_PASS", "FORWARD_ADD_PASS", "POINT", "SPOT" }; + public static readonly HashSet KeywordsUsedByPragmas = new HashSet() { }; + + public static readonly string[] ValidPropertyDataTypes = new string[] + { + "int", + "float", + "float2", + "float3", + "float4", + "half", + "half2", + "half3", + "half4", + "fixed", + "fixed2", + "fixed3", + "fixed4" + }; + + public static readonly HashSet IllegalPropertyRenames = new HashSet() + { + "_MainTex", + "_Color", + "_EmissionColor", + "_BumpScale", + "_Cutoff", + "_DetailNormalMapScale", + "_DstBlend", + "_GlossMapScale", + "_Glossiness", + "_GlossyReflections", + "_Metallic", + "_Mode", + "_OcclusionStrength", + "_Parallax", + "_SmoothnessTextureChannel", + "_SpecularHighlights", + "_SrcBlend", + "_UVSec", + "_ZWrite" + }; + + public static readonly HashSet PropertiesToSkipInMaterialEquallityComparission = new HashSet + { + "shader_master_label", + "shader_is_using_thry_editor" + }; + + public enum PropertyType + { + Vector, + Float + } + + public class PropertyData + { + public PropertyType type; + public string name; + public Vector4 value; + public string lastDeclarationType; + } + + public class Macro + { + public string name; + public string[] args; + public string contents; + } + + public class ParsedShaderFile + { + public string filePath; + public string[] lines; + } + + public class TextureProperty + { + public string name; + public Texture texture; + public int uv; + public Vector2 scale; + public Vector2 offset; + } + + public class GrabPassReplacement + { + public string originalName; + public string newName; + } + + public static void CopyAnimatedTagToMaterials(Material[] targets, MaterialProperty source) + { + string val = (source.targets[0] as Material).GetTag(source.name + AnimatedTagSuffix, false, ""); + foreach (Material m in targets) + { + m.SetOverrideTag(source.name+ AnimatedTagSuffix, val); + } + } + + public static void CopyAnimatedTagFromMaterial(Material source, MaterialProperty target) + { + string val = source.GetTag(target.name + AnimatedTagSuffix, false, ""); + foreach (Material m in target.targets) + { + m.SetOverrideTag(target.name + AnimatedTagSuffix, val); + } + } + + public static void CopyAnimatedTagFromProperty(MaterialProperty source, MaterialProperty target) + { + string val = (source.targets[0] as Material).GetTag(source.name + AnimatedTagSuffix, false, ""); + foreach (Material m in target.targets) + { + m.SetOverrideTag(target.name + AnimatedTagSuffix, val); + } + } + + public static void SetAnimatedTag(MaterialProperty prop, string value) + { + foreach (Material m in prop.targets) + { + m.SetOverrideTag(prop.name + AnimatedTagSuffix, value); + } + } + + public static string GetAnimatedTag(MaterialProperty prop) + { + return (prop.targets[0] as Material).GetTag(prop.name + AnimatedTagSuffix, false, ""); + } + + public static string GetAnimatedTag(Material m, string prop) + { + return m.GetTag(prop + AnimatedTagSuffix, false, ""); + } + + public static bool IsAnimated(Material m, string prop) + { + return m.GetTag(prop + AnimatedTagSuffix, false, "0") != "0"; + } + + public static string CleanStringForPropertyNames(string s) + { + s = s.Trim().Replace(" ", ""); + var nameByteArray = System.Text.Encoding.UTF8.GetBytes(s); + string cleaned = ""; + for (var i = 0; i < nameByteArray.Length; i++) + { + if ((nameByteArray[i] >= 65 && nameByteArray[i] <= 122 && nameByteArray[i] != 91 && nameByteArray[i] != 92 && nameByteArray[i] != 93 && nameByteArray[i] != 94 && nameByteArray[i] != 96) || // word characters + (nameByteArray[i] >= 48 && nameByteArray[i] <= 57)) // numbers + { + cleaned += System.Text.Encoding.UTF8.GetString(new byte[] { nameByteArray[i] }); + } + else + { + cleaned += nameByteArray[i].ToString("X2"); + } + } + return cleaned; + } + + + public static string GetRenamedPropertySuffix(Material m) + { + return CleanStringForPropertyNames(m.GetTag("thry_rename_suffix", false, m.name)); + } + + public static bool HasCustomRenameSuffix(Material m) + { + string cleanedMaterialName = CleanStringForPropertyNames(m.name); + string suffix = m.GetTag("thry_rename_suffix", false, cleanedMaterialName); + return suffix != cleanedMaterialName; + } + + struct RenamingProperty + { + public MaterialProperty Prop; + public string Keyword; + public string Replace; + public RenamingProperty(MaterialProperty prop, string keyword, string replace) + { + this.Prop = prop; + this.Keyword = keyword; + this.Replace = replace; + } + } + + public static bool IsPropertyExcemptFromLocking(MaterialProperty prop) + { + // if not a texture, but has non-modifiable texture data flag, is used as indicator to prevent locking + return prop.displayName.EndsWith(ExemptFromLockingSuffix, StringComparison.Ordinal) || (prop.type != MaterialProperty.PropType.Texture && prop.flags.HasFlag(MaterialProperty.PropFlags.NonModifiableTextureData)); + } + + private static bool Lock(Material material, MaterialProperty[] props, bool applyShaderLater = false) + { + // File filepaths and names + Shader shader = material.shader; + string shaderFilePath = AssetDatabase.GetAssetPath(shader); + string materialFilePath = AssetDatabase.GetAssetPath(material); + string materialFolder = Path.GetDirectoryName(materialFilePath); + string guid = AssetDatabase.AssetPathToGUID(materialFilePath); + string newShaderName = "Hidden/Locked/" + shader.name + "/" + guid; + string shaderOptimizerButtonDrawerName = $"[{nameof(ThryShaderOptimizerLockButtonDrawer).Replace("Drawer", "")}]"; + //string newShaderDirectory = materialFolder + "/OptimizedShaders/" + material.name + "-" + smallguid + "/"; + // unity path stuff (https://docs.unity3d.com/Manual/SpecialFolders.html) + // ~ & . hides the folder in the editor and unity will not be able to find the shader + string subfoldername = material.name; + while(subfoldername.StartsWith(".")) + subfoldername = subfoldername.Substring(1) + "_dot_"; + while(subfoldername.EndsWith("~")) + subfoldername = subfoldername.Substring(0, subfoldername.Length - 1) + "_tilde_"; + string newShaderDirectory = materialFolder + "/OptimizedShaders/" + subfoldername + "/"; + + // if directory already exists swap to using the guid + if (Directory.Exists(newShaderDirectory)) + { + newShaderDirectory = materialFolder + "/OptimizedShaders/" + guid + "/"; + } + + + // suffix for animated properties when renaming is enabled + string animPropertySuffix = GetRenamedPropertySuffix(material); + + // Get collection of all properties to replace + // Simultaneously build a string of #defines for each CGPROGRAM + List<(string name,string value)> defines = new List<(string,string)>(); + // Append all keywords active on the material + foreach (string keyword in material.shaderKeywords) + { + if (keyword == "") continue; // idk why but null keywords exist if _ keyword is used and not removed by the editor at some point + defines.Add((keyword,"")); + } + + KeywordsUsedByPragmas.Clear(); + + List constantProps = new List(); + List animatedPropsToRename = new List(); + List animatedPropsToDuplicate = new List(); + foreach (MaterialProperty prop in props) + { + if (prop == null) continue; + // Every property gets turned into a preprocessor variable + switch (prop.type) + { + case MaterialProperty.PropType.Texture: + if (prop.textureValue != null) + { + defines.Add(($"PROP{prop.name.ToUpperInvariant()}", "")); + } + break; + } + + if (prop.name.EndsWith(AnimatedPropertySuffix, StringComparison.Ordinal)) continue; + else if (prop.name == UseInlineSamplerStatesPropertyName) + { + UseInlineSamplerStates = (prop.floatValue == 1); + continue; + } + else if (prop.name.StartsWith(GeometryShaderEnabledPropertyName, StringComparison.Ordinal)) + { + if (prop.name == GeometryShaderEnabledPropertyName) + UseGeometry = (prop.floatValue == 1); + else if (prop.name == GeometryShaderEnabledPropertyName + "ForwardBase") + UseGeometryForwardBase = (prop.floatValue == 1); + else if (prop.name == GeometryShaderEnabledPropertyName + "ForwardAdd") + UseGeometryForwardAdd = (prop.floatValue == 1); + else if (prop.name == GeometryShaderEnabledPropertyName + "ShadowCaster") + UseGeometryShadowCaster = (prop.floatValue == 1); + else if (prop.name == GeometryShaderEnabledPropertyName + "Meta") + UseGeometryMeta = (prop.floatValue == 1); + } + else if (prop.name.StartsWith(TessellationEnabledPropertyName, StringComparison.Ordinal)) + { + if (prop.name == TessellationEnabledPropertyName) + UseTessellation = (prop.floatValue == 1); + else if (prop.name == TessellationEnabledPropertyName + "ForwardBase") + UseTessellationForwardBase = (prop.floatValue == 1); + else if (prop.name == TessellationEnabledPropertyName + "ForwardAdd") + UseTessellationForwardAdd = (prop.floatValue == 1); + else if (prop.name == TessellationEnabledPropertyName + "ShadowCaster") + UseTessellationShadowCaster = (prop.floatValue == 1); + else if (prop.name == TessellationEnabledPropertyName + "Meta") + UseTessellationMeta = (prop.floatValue == 1); + } + + string animateTag = material.GetTag(prop.name + AnimatedTagSuffix, false, ""); + if(string.IsNullOrEmpty(animateTag) == false) + { + // check if we're renaming the property as well + if (animateTag == "2") + { + if (!prop.name.EndsWith("UV", StringComparison.Ordinal) && !prop.name.EndsWith("Pan", StringComparison.Ordinal)) // this property might be animated, but we're not allowed to rename it. this will break things. + { + if (IllegalPropertyRenames.Contains(prop.name)) + animatedPropsToDuplicate.Add(new RenamingProperty(prop, prop.name, prop.name + "_" + animPropertySuffix)); + else + animatedPropsToRename.Add(new RenamingProperty(prop, prop.name, prop.name + "_" + animPropertySuffix)); + if (prop.type == MaterialProperty.PropType.Texture) + { + animatedPropsToRename.Add(new RenamingProperty(prop, prop.name + "_ST", prop.name + "_" + animPropertySuffix + "_ST")); + animatedPropsToRename.Add(new RenamingProperty(prop, prop.name + "_TexelSize", prop.name + "_" + animPropertySuffix + "_TexelSize")); + } + } + } + + continue; + } + + if (IsPropertyExcemptFromLocking(prop)) continue; + + PropertyData propData; + switch(prop.type) + { + case MaterialProperty.PropType.Color: + propData = new PropertyData(); + propData.type = PropertyType.Vector; + propData.name = prop.name; + if ((prop.flags & MaterialProperty.PropFlags.HDR) != 0) + { + if ((prop.flags & MaterialProperty.PropFlags.Gamma) != 0) + propData.value = prop.colorValue.linear; + else propData.value = prop.colorValue; + } + else if ((prop.flags & MaterialProperty.PropFlags.Gamma) != 0) + propData.value = prop.colorValue; + else propData.value = prop.colorValue.linear; + if (PlayerSettings.colorSpace == ColorSpace.Gamma) propData.value = prop.colorValue; + constantProps.Add(propData); + break; + case MaterialProperty.PropType.Vector: + propData = new PropertyData(); + propData.type = PropertyType.Vector; + propData.name = prop.name; + propData.value = prop.vectorValue; + constantProps.Add(propData); + break; + case MaterialProperty.PropType.Float: + case MaterialProperty.PropType.Range: + propData = new PropertyData(); + propData.type = PropertyType.Float; + propData.name = prop.name; + propData.value = new Vector4(prop.floatValue, 0, 0, 0); + constantProps.Add(propData); + break; + case MaterialProperty.PropType.Texture: + PropertyData ST = new PropertyData(); + ST.type = PropertyType.Vector; + ST.name = prop.name + "_ST"; + Vector2 offset = material.GetTextureOffset(prop.name); + Vector2 scale = material.GetTextureScale(prop.name); + ST.value = new Vector4(scale.x, scale.y, offset.x, offset.y); + constantProps.Add(ST); + + PropertyData TexelSize = new PropertyData(); + TexelSize.type = PropertyType.Vector; + TexelSize.name = prop.name + "_TexelSize"; + Texture t = prop.textureValue; + if (t != null) + TexelSize.value = new Vector4(1.0f / t.width, 1.0f / t.height, t.width, t.height); + else TexelSize.value = new Vector4(1.0f, 1.0f, 1.0f, 1.0f); + constantProps.Add(TexelSize); + break; + } + } + + // Get list of lightmode passes to delete + List disabledLightModes = new List(); + var disabledLightModesProperty = Array.Find(props, x => x.name == DisabledLightModesPropertyName); + if (disabledLightModesProperty != null) + { + int lightModesMask = (int)disabledLightModesProperty.floatValue; + if ((lightModesMask & (int)LightMode.ForwardAdd) != 0) + disabledLightModes.Add("ForwardAdd"); + if ((lightModesMask & (int)LightMode.ShadowCaster) != 0) + disabledLightModes.Add("ShadowCaster"); + } + + // Parse shader and cginc files, also gets preprocessor macros + List shaderFiles = new List(); + List macros = new List(); + if (!ParseShaderFilesRecursive(shaderFiles, newShaderDirectory, shaderFilePath, macros, material)) + return false; + + // Remove all defines where name if not in shader files + List<(string,string)> definesToRemove = new List<(string,string)>(); + foreach((string name,string) def in defines) + { + if (shaderFiles.All(x => x.lines.Any(l => l.Contains(def.name)) == false)) + definesToRemove.Add(def); + } + defines.RemoveAll(x => definesToRemove.Contains(x)); + // Append convention OPTIMIZER_ENABLED keyword + defines.Add((OptimizerEnabledKeyword,"")); + string optimizerDefines = ""; + if(defines.Count > 0) + optimizerDefines = defines.Select(m => $"\r\n #define {m.name} {m.value}").Aggregate((s1, s2) => s1 + s2); + + int commentKeywords = 0; + + Dictionary constantPropsDictionary = constantProps.GroupBy(x => x.name).Select(g => g.First()).ToDictionary(x => x.name); + Macro[] macrosArray = macros.ToArray(); + + List grabPassVariables = new List(); + // Loop back through and do macros, props, and all other things line by line as to save string ops + // Will still be a massive n2 operation from each line * each property + foreach (ParsedShaderFile psf in shaderFiles) + { + // replace property names when prop is animated + for (int i = 0; i < psf.lines.Length; i++) + { + foreach (var animProp in animatedPropsToRename) + { + // don't have to match if that prop does not even exist in that line + if (psf.lines[i].Contains(animProp.Keyword)) + { + string pattern = animProp.Keyword + @"(?!(\w|\d))"; + psf.lines[i] = Regex.Replace(psf.lines[i], pattern, animProp.Replace, RegexOptions.Multiline); + } + } + foreach (var animProp in animatedPropsToDuplicate) + { + if (psf.lines[i].Contains(animProp.Keyword)) + { + //if Line is property definition duplicate it + bool isDefinition = Regex.Match(psf.lines[i], animProp.Keyword + @"\s*\(""[^""]+""\s*,\s*\w+\)\s*=").Success; + string og = null; + if (isDefinition) + og = psf.lines[i]; + + string pattern = animProp.Keyword + @"(?!(\w|\d))"; + psf.lines[i] = Regex.Replace(psf.lines[i], pattern, animProp.Replace, RegexOptions.Multiline); + + if (isDefinition) + psf.lines[i] = og + "\r\n" + psf.lines[i]; + } + } + } + + + // Shader file specific stuff + if (psf.filePath.EndsWith(".shader", StringComparison.Ordinal)) + { + for (int i=0; i x.name == LODCrossFadePropertyName); + if (crossfadeProp != null && crossfadeProp.floatValue == 1) + psf.lines[i] = psf.lines[i].Replace("//#pragma", "#pragma"); + } + else if (trimmedLine.StartsWith("//\"IgnoreProjector\"=\"True\"", StringComparison.Ordinal)) + { + MaterialProperty projProp = Array.Find(props, x => x.name == IgnoreProjectorPropertyName); + if (projProp != null && projProp.floatValue == 1) + psf.lines[i] = psf.lines[i].Replace("//\"IgnoreProjector", "\"IgnoreProjector"); + } + else if (trimmedLine.StartsWith("//\"ForceNoShadowCasting\"=\"True\"", StringComparison.Ordinal)) + { + MaterialProperty forceNoShadowsProp = Array.Find(props, x => x.name == ForceNoShadowCastingPropertyName); + if (forceNoShadowsProp != null && forceNoShadowsProp.floatValue == 1) + psf.lines[i] = psf.lines[i].Replace("//\"ForceNoShadowCasting", "\"ForceNoShadowCasting"); + } + else if (trimmedLine.StartsWith("GrabPass {", StringComparison.Ordinal)) + { + GrabPassReplacement gpr = new GrabPassReplacement(); + string[] splitLine = trimmedLine.Split('\"'); + if (splitLine.Length == 1) + gpr.originalName = "_GrabTexture"; + else + gpr.originalName = splitLine[1]; + gpr.newName = material.GetTag("GrabPass" + grabPassVariables.Count, false, "_GrabTexture"); + psf.lines[i] = "GrabPass { \"" + gpr.newName + "\" }"; + grabPassVariables.Add(gpr); + } + else if (trimmedLine.StartsWith("CGINCLUDE", StringComparison.Ordinal)) + { + for (int j=i+1; j=0;j--) + if (psf.lines[j].Replace(" ", "").Replace("\t", "") == "Pass") + break; + // then delete each line until a standalone ENDCG line is found + for (;j applyStructsLater = new Dictionary(); + + private struct ApplyStruct + { + public Material material; + public Shader shader; + public string smallguid; + public string newShaderName; + public List animatedPropsToRename; + public List animatedPropsToDuplicate; + public string animPropertySuffix; + public bool shared; + } + + private static bool LockApplyShader(Material material) + { + if (applyStructsLater.ContainsKey(material) == false) return false; + ApplyStruct applyStruct = applyStructsLater[material]; + if (applyStruct.shared) + { + material.shader = applyStruct.material.shader; + return true; + } + //applyStructsLater.Remove(material); + return LockApplyShader(applyStruct); + } + + public static void ApplyMaterialPropertyDrawersPatch(Material material) {} + static MethodInfo ApplyMaterialPropertyDrawersOriginalMethodInfo = typeof(MaterialEditor).GetMethod("ApplyMaterialPropertyDrawers", new Type[] {typeof(Material)}); + static MethodInfo ApplyMaterialPropertyDrawersPatchMethodInfo = typeof(ShaderOptimizer).GetMethod(nameof(ApplyMaterialPropertyDrawersPatch), BindingFlags.Public | BindingFlags.Static); + + public static void DetourApplyMaterialPropertyDrawers() + { + Helper.TryDetourFromTo(ApplyMaterialPropertyDrawersOriginalMethodInfo, ApplyMaterialPropertyDrawersPatchMethodInfo); + } + + public static void RestoreApplyMaterialPropertyDrawers() + { + Helper.RestoreDetour(ApplyMaterialPropertyDrawersOriginalMethodInfo); + } + + private static bool LockApplyShader(ApplyStruct applyStruct) + { + Material material = applyStruct.material; + Shader shader = applyStruct.shader; + string newShaderName = applyStruct.newShaderName; + List animatedPropsToRename = applyStruct.animatedPropsToRename; + List animatedPropsToDuplicate = applyStruct.animatedPropsToDuplicate; + string animPropertySuffix = applyStruct.animPropertySuffix; + + // Write original shader to override tag + material.SetOverrideTag(TAG_ORIGINAL_SHADER, shader.name); + // Write the new shader folder name in an override tag so it will be deleted + + // For some reason when shaders are swapped on a material the RenderType override tag gets completely deleted and render queue set back to -1 + // So these are saved as temp values and reassigned after switching shaders + string renderType = material.GetTag("RenderType", false, ""); + int renderQueue = material.renderQueue; + + // Actually switch the shader + Shader newShader = Shader.Find(newShaderName); + if (newShader == null) + { + Debug.LogError("[Shader Optimizer] Generated shader " + newShaderName + " could not be found"); + return false; + } + DetourApplyMaterialPropertyDrawers(); + material.shader = newShader; + RestoreApplyMaterialPropertyDrawers(); + //ShaderEditor.reload(); + material.SetOverrideTag("RenderType", renderType); + material.renderQueue = renderQueue; + + material.SetOverrideTag("OriginalKeywords", string.Join(" ", material.shaderKeywords)); + // Remove ALL keywords + foreach (string keyword in material.shaderKeywords) + if(material.IsKeywordEnabled(keyword)) material.DisableKeyword(keyword); + + foreach (var animProp in animatedPropsToRename) + { + var newName = animProp.Prop.name + "_" + animPropertySuffix; + switch (animProp.Prop.type) + { + case MaterialProperty.PropType.Color: + material.SetColor(newName, animProp.Prop.colorValue); + break; + case MaterialProperty.PropType.Vector: + material.SetVector(newName, animProp.Prop.vectorValue); + break; + case MaterialProperty.PropType.Float: + material.SetFloat(newName, animProp.Prop.floatValue); + break; + case MaterialProperty.PropType.Range: + material.SetFloat(newName, animProp.Prop.floatValue); + break; + case MaterialProperty.PropType.Texture: + material.SetTexture(newName, animProp.Prop.textureValue); + material.SetTextureScale(newName, new Vector2(animProp.Prop.textureScaleAndOffset.x, animProp.Prop.textureScaleAndOffset.y)); + material.SetTextureOffset(newName, new Vector2(animProp.Prop.textureScaleAndOffset.z, animProp.Prop.textureScaleAndOffset.w)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(material), "This property type should not be renamed and can not be set."); + } + } + + foreach (var animProp in animatedPropsToDuplicate) + { + var newName = animProp.Prop.name + "_" + animPropertySuffix; + switch (animProp.Prop.type) + { + case MaterialProperty.PropType.Color: + material.SetColor(newName, animProp.Prop.colorValue); + break; + case MaterialProperty.PropType.Vector: + material.SetVector(newName, animProp.Prop.vectorValue); + break; + case MaterialProperty.PropType.Float: + material.SetFloat(newName, animProp.Prop.floatValue); + break; + case MaterialProperty.PropType.Range: + material.SetFloat(newName, animProp.Prop.floatValue); + break; + case MaterialProperty.PropType.Texture: + material.SetTexture(newName, animProp.Prop.textureValue); + material.SetTextureScale(newName, new Vector2(animProp.Prop.textureScaleAndOffset.x, animProp.Prop.textureScaleAndOffset.y)); + material.SetTextureOffset(newName, new Vector2(animProp.Prop.textureScaleAndOffset.z, animProp.Prop.textureScaleAndOffset.w)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(material), "This property type should not be renamed and can not be set."); + } + } + return true; + } + + /** Find longest common directoy */ + public static int GetLongestCommonDirectoryLength(string[] s) + { + int k = s[0].Length; + for (int i = 1; i < s.Length; i++) + { + k = Math.Min(k, s[i].Length); + for (int j = 0; j < k; j++) + if ( AreCharsInPathEqual(s[i][j] , s[0][j]) == false) + { + k = j; + break; + } + } + string p = s[0].Substring(0, k); + if (Directory.Exists(p)) return p.Length; + else return Path.GetDirectoryName(p).Length; + } + + private static bool AreCharsInPathEqual(char c1, char c2) + { + return (c1 == c2) || ((c1 == '/' || c1 == '\\') && (c2 == '/' || c2 == '\\')); + } + + // Preprocess each file for macros and includes + // Save each file as string[], parse each macro with //KSOEvaluateMacro + // Only editing done is replacing #include "X" filepaths where necessary + // most of these args could be private static members of the class + private static bool ParseShaderFilesRecursive(List filesParsed, string newTopLevelDirectory, string filePath, List macros, Material material) + { + // Infinite recursion check + if (filesParsed.Exists(x => x.filePath == filePath)) return true; + + ParsedShaderFile psf = new ParsedShaderFile(); + psf.filePath = filePath; + filesParsed.Add(psf); + + // Read file + string fileContents = null; + try + { + StreamReader sr = new StreamReader(filePath); + fileContents = sr.ReadToEnd(); + sr.Close(); + } + catch (FileNotFoundException e) + { + Debug.LogError("[Shader Optimizer] Shader file " + filePath + " not found. " + e.ToString()); + return false; + } + catch (IOException e) + { + Debug.LogError("[Shader Optimizer] Error reading shader file. " + e.ToString()); + return false; + } + + // Parse file line by line + List macrosList = new List(); + string[] fileLines = fileContents.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + + List includedLines = new List(); + + bool isIncluded = true; + int isNotIncludedAtDepth = 0; + int ifStacking = 0; + Stack removeEndifStack = new Stack(); + + bool isCommentedOut = false; + + int currentExcludeDepth = 0; + bool doExclude = false; + int excludeStartDepth = 0; + + for (int i=0; i constants, Macro[] macros, GrabPassReplacement[] grabPassVariables) + { + List uniqueSampledTextures = new List(); + + // Outside loop is each line + for (int i=startLine;i x.name == args[1]); + if (texProp != null) + { + Texture t = texProp.textureValue; + int inlineSamplerIndex = 0; + if (t != null) + { + switch (t.filterMode) + { + case FilterMode.Bilinear: + break; + case FilterMode.Point: + inlineSamplerIndex += 1 * 4; + break; + case FilterMode.Trilinear: + inlineSamplerIndex += 2 * 4; + break; + } + switch (t.wrapMode) + { + case TextureWrapMode.Repeat: + break; + case TextureWrapMode.Clamp: + inlineSamplerIndex += 1; + break; + case TextureWrapMode.Mirror: + inlineSamplerIndex += 2; + break; + case TextureWrapMode.MirrorOnce: + inlineSamplerIndex += 3; + break; + } + } + + // Replace the token on the following line + lines[i+1] = lines[i+1].Replace(args[0], InlineSamplerStateNames[inlineSamplerIndex]); + } + } + else if (lineTrimmed.StartsWith("//KSODuplicateTextureCheckStart", StringComparison.Ordinal)) + { + // Since files are not fully parsed and instead loosely processed, each shader function needs to have + // its sampled texture list reset somewhere before KSODuplicateTextureChecks are made. + // As long as textures are sampled in-order inside a single function, this method will work. + uniqueSampledTextures = new List(); + } + else if (lineTrimmed.StartsWith("//KSODuplicateTextureCheck", StringComparison.Ordinal)) + { + // Each KSODuplicateTextureCheck line gets evaluated when the shader is optimized + // If the texture given has already been sampled as another texture (i.e. one texture is used in two slots) + // AND has been sampled with the same UV mode - as indicated by a convention UV property, + // AND has been sampled with the exact same Tiling/Offset values + // AND has been logged by KSODuplicateTextureCheck, + // then the variable corresponding to the first instance of that texture being + // sampled will be assigned to the variable corresponding to the given texture. + // The compiler will then skip the duplicate texture sample since its variable is overwritten before being used + + // Parse line for argument texture property name + string lineParsed = lineTrimmed.Replace(" ", "").Replace("\t", ""); + int firstParenthesis = lineParsed.IndexOf('('); + int lastParenthesis = lineParsed.IndexOf(')'); + string argName = lineParsed.Substring(firstParenthesis+1, lastParenthesis-firstParenthesis-1); + // Check if texture property by argument name exists and has a texture assigned + if (Array.Exists(props, x => x.name == argName)) + { + MaterialProperty argProp = Array.Find(props, x => x.name == argName); + if (argProp.textureValue != null) + { + // If no convention UV property exists, sampled UV mode is assumed to be 0 + // Any UV enum or mode indicator can be used for this + int UV = 0; + if (Array.Exists(props, x => x.name == argName + "UV")) + UV = (int)(Array.Find(props, x => x.name == argName + "UV").floatValue); + + Vector2 texScale = material.GetTextureScale(argName); + Vector2 texOffset = material.GetTextureOffset(argName); + + // Check if this texture has already been sampled + if (uniqueSampledTextures.Exists(x => (x.texture == argProp.textureValue) + && (x.uv == UV) + && (x.scale == texScale) + && x.offset == texOffset)) + { + string texName = uniqueSampledTextures.Find(x => (x.texture == argProp.textureValue) && (x.uv == UV)).name; + // convention _var variables requried. i.e. _MainTex_var and _CoverageMap_var + lines[i] = argName + "_var = " + texName + "_var;"; + } + else + { + // Texture/UV/ST combo hasn't been sampled yet, add it to the list + TextureProperty tp = new TextureProperty(); + tp.name = argName; + tp.texture = argProp.textureValue; + tp.uv = UV; + tp.scale = texScale; + tp.offset = texOffset; + uniqueSampledTextures.Add(tp); + } + } + } + } + else if (lineTrimmed.StartsWith("[maxtessfactor(", StringComparison.Ordinal)) + { + MaterialProperty maxTessFactorProperty = Array.Find(props, x => x.name == TessellationMaxFactorPropertyName); + if (maxTessFactorProperty != null) + { + float maxTessellation = maxTessFactorProperty.floatValue; + string animateTag = material.GetTag(TessellationMaxFactorPropertyName + AnimatedTagSuffix, false, "0"); + if (animateTag != "" && animateTag == "1") + maxTessellation = 64.0f; + lines[i] = "[maxtessfactor(" + maxTessellation.ToString(".0######") + ")]"; + } + } + + // then replace macros + foreach (Macro macro in macros) + { + // Expects only one instance of a macro per line! + int macroIndex; + if ((macroIndex = lines[i].IndexOf(macro.name + "(", StringComparison.Ordinal)) != -1) + { + // Macro exists on this line, make sure its not the definition + string lineParsed = lineTrimmed.Replace(" ","").Replace("\t",""); + if (lineParsed.StartsWith("#define", StringComparison.Ordinal)) continue; + + // parse args between first '(' and first ')' + int firstParenthesis = macroIndex + macro.name.Length; + int lastParenthesis = lines[i].IndexOf(')', macroIndex + macro.name.Length+1); + string allArgs = lines[i].Substring(firstParenthesis+1, lastParenthesis-firstParenthesis-1); + string[] args = allArgs.Split(','); + + // Replace macro parts + string newContents = macro.contents; + for (int j=0; j= 0) + charLeft = newContents[argIndex-1]; + char charRight = ' '; + if (argIndex+macro.args[j].Length < newContents.Length) + charRight = newContents[argIndex+macro.args[j].Length]; + if (ValidSeparators.Contains(charLeft) && ValidSeparators.Contains(charRight)) + { + // Replcae the arg! + StringBuilder sbm = new StringBuilder(newContents.Length - macro.args[j].Length + args[j].Length); + sbm.Append(newContents, 0, argIndex); + sbm.Append(args[j]); + sbm.Append(newContents, argIndex + macro.args[j].Length, newContents.Length - argIndex - macro.args[j].Length); + newContents = sbm.ToString(); + } + } + } + newContents = newContents.Replace("##", ""); // Remove token pasting separators + // Replace the line with the evaluated macro + StringBuilder sb = new StringBuilder(lines[i].Length + newContents.Length); + sb.Append(lines[i], 0, macroIndex); + sb.Append(newContents); + sb.Append(lines[i], lastParenthesis+1, lines[i].Length - lastParenthesis-1); + lines[i] = sb.ToString(); + } + } + + foreach(string token in tokens) + { + if(constants.ContainsKey(token)) + { + PropertyData constant = constants[token]; + + int constantIndex; + int lastIndex = 0; + bool declarationFound = false; + while ((constantIndex = lines[i].IndexOf(constant.name, lastIndex, StringComparison.Ordinal)) != -1) + { + lastIndex = constantIndex+1; + char charLeft = ' '; + if (constantIndex-1 >= 0) + charLeft = lines[i][constantIndex-1]; + char charRight = ' '; + if (constantIndex + constant.name.Length < lines[i].Length) + charRight = lines[i][constantIndex + constant.name.Length]; + // Skip invalid matches (probably a subname of another symbol) + if (!(ValidSeparators.Contains(charLeft) && ValidSeparators.Contains(charRight))) + continue; + // Skip inline comments + if (charLeft == '*' && charRight == '*' && constantIndex >= 2 && lines[i][constantIndex - 2] == '/') + continue; + + // Skip basic declarations of unity shader properties i.e. "uniform float4 _Color;" + if (!declarationFound) + { + string precedingText = lines[i].Substring(0, constantIndex-1).TrimEnd(); // whitespace removed string immediately to the left should be float or float4 + string restOftheFile = lines[i].Substring(constantIndex + constant.name.Length).TrimStart(); // whitespace removed character immediately to the right should be ; + if (Array.Exists(ValidPropertyDataTypes, x => precedingText.EndsWith(x, StringComparison.Ordinal)) && restOftheFile.StartsWith(";", StringComparison.Ordinal)) + { + constant.lastDeclarationType = precedingText.TrimStart(); + declarationFound = true; + continue; + } + } + + // Replace with constant! + // This could technically be more efficient by being outside the IndexOf loop + StringBuilder sb = new StringBuilder(lines[i].Length * 2); + sb.Append(lines[i], 0, constantIndex); + switch (constant.type) + { + case PropertyType.Float: + string constantValue; + // Special Handling for ints + if(constant.lastDeclarationType == "int") + constantValue = constant.value.x.ToString("F0", CultureInfo.InvariantCulture); + else + constantValue = constant.value.x.ToString("0.0####################", CultureInfo.InvariantCulture); + + // Add comment with property name, for easier debug + sb.Append($"({constantValue} /*{constant.name}*/)"); + break; + case PropertyType.Vector: + sb.Append("float4("+constant.value.x.ToString(CultureInfo.InvariantCulture)+"," + +constant.value.y.ToString(CultureInfo.InvariantCulture)+"," + +constant.value.z.ToString(CultureInfo.InvariantCulture)+"," + +constant.value.w.ToString(CultureInfo.InvariantCulture)+")"); + break; + } + sb.Append(lines[i], constantIndex+constant.name.Length, lines[i].Length-constantIndex-constant.name.Length); + lines[i] = sb.ToString(); + + // Check for Unity branches on previous line here? + } + } + } + + // Then replace grabpass variable names + foreach (GrabPassReplacement gpr in grabPassVariables) + { + // find indexes of all instances of gpr.originalName that exist on this line + int lastIndex = 0; + int gbIndex; + while ((gbIndex = lines[i].IndexOf(gpr.originalName, lastIndex, StringComparison.Ordinal)) != -1) + { + lastIndex = gbIndex+1; + char charLeft = ' '; + if (gbIndex-1 >= 0) + charLeft = lines[i][gbIndex-1]; + char charRight = ' '; + if (gbIndex + gpr.originalName.Length < lines[i].Length) + charRight = lines[i][gbIndex + gpr.originalName.Length]; + // Skip invalid matches (probably a subname of another symbol) + if (!(ValidSeparators.Contains(charLeft) && ValidSeparators.Contains(charRight))) + continue; + + // Replace with new variable name + // This could technically be more efficient by being outside the IndexOf loop + StringBuilder sb = new StringBuilder(lines[i].Length * 2); + sb.Append(lines[i], 0, gbIndex); + sb.Append(gpr.newName); + sb.Append(lines[i], gbIndex+gpr.originalName.Length, lines[i].Length-gbIndex-gpr.originalName.Length); + lines[i] = sb.ToString(); + } + } + + // Then remove Unity branches + if (RemoveUnityBranches) + lines[i] = lines[i].Replace("UNITY_BRANCH", "").Replace("[branch]", ""); + } + } + + public enum UnlockSuccess { hasNoSavedShader, wasNotLocked, couldNotFindOriginalShader, couldNotDeleteLockedShader, + success} + private static void Unlock(Material material, MaterialProperty shaderOptimizer = null) + { + //if unlock success set floats. not done for locking cause the sucess is checked later when applying the shaders + UnlockSuccess success = ShaderOptimizer.UnlockConcrete(material); + if (success == UnlockSuccess.success || success == UnlockSuccess.wasNotLocked + || success == UnlockSuccess.couldNotDeleteLockedShader) + { + if (shaderOptimizer != null) shaderOptimizer.floatValue = 0; + else material.SetFloat(GetOptimizerPropertyName(material.shader), 0); + } + } + public static bool GuessShader(Shader locked, out Shader shader) + { + string name = locked.name; + name = Regex.Match(name.Substring(7), @".*(?=\/)").Value; + ShaderInfo[] allShaders = ShaderUtil.GetAllShaderInfo(); + int closestDistance = int.MaxValue; + string closestShaderName = null; + foreach (ShaderInfo s in allShaders) + { + if (!s.supported) continue; + int d = Helper.LevenshteinDistance(s.name, name); + if(d < closestDistance) + { + closestDistance = d; + closestShaderName = s.name; + } + } + shader = Shader.Find(closestShaderName); + return shader != null && closestDistance < name.Length / 2; + } + private static UnlockSuccess UnlockConcrete (Material material) + { + Shader lockedShader = material.shader; + // Revert to original shader + string originalShaderName = material.GetTag(TAG_ORIGINAL_SHADER, false, ""); + Shader orignalShader = null; + if (originalShaderName == "" && !GuessShader(lockedShader, out orignalShader)) + { + if (material.shader.name.StartsWith("Hidden/")) + { + if (EditorUtility.DisplayDialog("Unlock Material", $"The original shader for {material.name} could not be resolved.\nPlease select a shader manually.", "Ok")) { } + Debug.LogError("[Shader Optimizer] Original shader not saved to material, could not unlock shader"); + return UnlockSuccess.hasNoSavedShader; + } + else + { + Debug.LogWarning("[Shader Optimizer] Original shader not saved to material, but material also doesnt seem to be locked."); + return UnlockSuccess.wasNotLocked; + } + + } + if(orignalShader == null) orignalShader = Shader.Find(originalShaderName); + if (orignalShader == null && !GuessShader(lockedShader, out orignalShader)) + { + if (material.shader.name.StartsWith("Hidden/")) + { + if (EditorUtility.DisplayDialog("Unlock Material", $"The original shader for {material.name} could not be resolved.\nPlease select a shader manually.", "Ok")) { } + Debug.LogError("[Shader Optimizer] Original shader " + originalShaderName + " could not be found"); + return UnlockSuccess.couldNotFindOriginalShader; + } + else + { + Debug.LogWarning("[Shader Optimizer] Original shader not saved to material, but material also doesnt seem to be locked."); + return UnlockSuccess.wasNotLocked; + } + } + + // For some reason when shaders are swapped on a material the RenderType override tag gets completely deleted and render queue set back to -1 + // So these are saved as temp values and reassigned after switching shaders + string renderType = material.GetTag("RenderType", false, ""); + int renderQueue = material.renderQueue; + string unlockedMaterialGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(material)); + DetourApplyMaterialPropertyDrawers(); + material.shader = orignalShader; + RestoreApplyMaterialPropertyDrawers(); + material.SetOverrideTag("RenderType", renderType); + material.renderQueue = renderQueue; + material.shaderKeywords = material.GetTag("OriginalKeywords", false, string.Join(" ", material.shaderKeywords)).Split(' '); + + // Delete the variants folder and all files in it, as to not orhpan files and inflate Unity project + // But only if no other material is using the locked shader + string[] lockedMaterials = material.GetTag(TAG_ALL_MATERIALS_GUIDS_USING_THIS_LOCKED_SHADER, false, "").Split(','); + string newTag = string.Join(",", lockedMaterials.Where(guid => guid != unlockedMaterialGUID).ToArray()); + bool isOtherMaterialUsingLockedShader = false; + foreach(string guid in lockedMaterials) + { + if (string.IsNullOrWhiteSpace(guid)) continue; + Material m = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); + if (m != null) + { + isOtherMaterialUsingLockedShader |= m.shader == lockedShader; + m.SetOverrideTag(TAG_ALL_MATERIALS_GUIDS_USING_THIS_LOCKED_SHADER, newTag); + } + } + if (!isOtherMaterialUsingLockedShader) + { + string materialFilePath = AssetDatabase.GetAssetPath(lockedShader); + string lockedFolder = Path.GetDirectoryName(materialFilePath); + FileUtil.DeleteFileOrDirectory(lockedFolder); + FileUtil.DeleteFileOrDirectory(lockedFolder + ".meta"); + } + //AssetDatabase.Refresh(); + + return UnlockSuccess.success; + } + + public static void DeleteTags(Material[] materials) + { + foreach(Material m in materials) + { + var it = new SerializedObject(m).GetIterator(); + while (it.Next(true)) + { + if (it.name == "stringTagMap") + { + for (int i = 0; i < it.arraySize; i++) + { + string tagName = it.GetArrayElementAtIndex(i).displayName; + if (tagName.EndsWith(AnimatedTagSuffix)) + { + m.SetOverrideTag(tagName, ""); + } + } + } + } + } + } + + #region Upgrade + + public static void UpgradeAnimatedPropertiesToTagsOnAllMaterials() + { + IEnumerable materials = Resources.FindObjectsOfTypeAll(); + UpgradeAnimatedPropertiesToTags(materials); + Debug.Log("[Thry][Optimizer] Update animated properties of all materials to tags."); + } + + public static void UpgradeAnimatedPropertiesToTags(IEnumerable iMaterials) + { + IEnumerable materialsToChange = iMaterials.Where(m => m != null && + string.IsNullOrEmpty(AssetDatabase.GetAssetPath(m)) == false && string.IsNullOrEmpty(AssetDatabase.GetAssetPath(m.shader)) == false + && IsShaderUsingThryOptimizer(m.shader)).Distinct().OrderBy(m => m.shader.name); + + int i = 0; + foreach (Material m in materialsToChange) + { + if(EditorUtility.DisplayCancelableProgressBar("Upgrading Materials", "Upgrading animated tags of " + m.name, (float)i / materialsToChange.Count())) + { + break; + } + + string path = AssetDatabase.GetAssetPath(m); + StreamReader reader = new StreamReader(path); + string line; + while((line = reader.ReadLine()) != null) + { + if (line.Contains(AnimatedPropertySuffix) && line.Length > 6) + { + string[] parts = line.Substring(6, line.Length - 6).Split(':'); + float f; + if (float.TryParse(parts[1], out f)) + { + if( f != 0) + { + string name = parts[0].Substring(0, parts[0].Length - AnimatedPropertySuffix.Length); + m.SetOverrideTag(name + AnimatedTagSuffix, "" + f); + } + } + } + } + reader.Close(); + i++; + } + + EditorUtility.ClearProgressBar(); + } + + static void ClearConsole() + { + var logEntries = System.Type.GetType("UnityEditor.LogEntries, UnityEditor.dll"); + + var clearMethod = logEntries.GetMethod("Clear", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + + clearMethod.Invoke(null, null); + } + + #endregion + + // ifex indenting + [MenuItem("Assets/Thry/Shaders/Ifex Indenting", false, 303)] + static void IfExIndenting() + { + Shader s = Selection.objects[0] as Shader; + if (s == null) return; + string path = AssetDatabase.GetAssetPath(s); + if(string.IsNullOrEmpty(path)) return; + // Load the shader file + string[] lines = File.ReadAllLines(path); + int indent = 0; + for (int i = 0; i < lines.Length; i++) + { + string line = lines[i].Trim(); + if (line.Contains("//endex")) indent = Mathf.Max(0,indent - 1); + lines[i] = new string(' ', indent * 4) + line; + if (line.StartsWith("//ifex")) indent++; + } + GUIUtility.systemCopyBuffer = string.Join("\n", lines); + } + + [MenuItem("Assets/Thry/Shaders/Ifex Indenting", true)] + static bool IfExIndentingValidator() + { + return Selection.objects.Length == 1 && Selection.objects[0] is Shader; + } + + //---GameObject + Children Locking + + [MenuItem("GameObject/Thry/Materials/Unlock All", false,0)] + static void UnlockAllChildren() + { + SetLockForAllChildren(Selection.gameObjects, 0, true); + } + + [MenuItem("GameObject/Thry/Materials/Lock All", false,0)] + static void LockAllChildren() + { + SetLockForAllChildren(Selection.gameObjects, 1, true); + } + + //---Asset Unlocking + + [MenuItem("Assets/Thry/Materials/Unlock All", false, 303)] + static void UnlockAllMaterials() + { + IEnumerable mats = Selection.assetGUIDs.Select(g => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g))); + SetLockedForAllMaterials(mats, 0, true); + } + + [MenuItem("Assets/Thry/Materials/Unlock All", true)] + static bool UnlockAllMaterialsValidator() + { + return SelectedObjectsAreLockableMaterials(); + } + + //---Asset Locking + + [MenuItem("Assets/Thry/Materials/Lock All", false, 303)] + static void LockAllMaterials() + { + IEnumerable mats = Selection.assetGUIDs.Select(g => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g))); + SetLockedForAllMaterials(mats, 1, true); + } + + [MenuItem("Assets/Thry/Materials/Lock All", true)] + static bool LockAllMaterialsValidator() + { + return SelectedObjectsAreLockableMaterials(); + } + + //----Folder Lock + + //This does not work for folders on the left side of the project explorer, because they are not exposed to Selection + [MenuItem("Assets/Thry/Materials/Lock Folder", false, 303)] + static void LockFolder() + { + IEnumerable folderPaths = Selection.objects.Select(o => AssetDatabase.GetAssetPath(o)).Where(p => Directory.Exists(p)); + List materials = new List(); + foreach (string f in folderPaths) FindMaterialsRecursive(f, materials); + SetLockedForAllMaterials(materials, 1, true); + } + + [MenuItem("Assets/Thry/Materials/Lock Folder", true)] + static bool LockFolderValidator() + { + return Selection.objects.Select(o => AssetDatabase.GetAssetPath(o)).Where(p => Directory.Exists(p)).Count() == Selection.objects.Length; + } + + //-----Folder Unlock + + [MenuItem("Assets/Thry/Materials/Unlock Folder", false, 303)] + static void UnLockFolder() + { + IEnumerable folderPaths = Selection.objects.Select(o => AssetDatabase.GetAssetPath(o)).Where(p => Directory.Exists(p)); + List materials = new List(); + foreach (string f in folderPaths) FindMaterialsRecursive(f, materials); + SetLockedForAllMaterials(materials, 0, true); + } + + [MenuItem("Assets/Thry/Materials/Unlock Folder", true)] + static bool UnLockFolderValidator() + { + return Selection.objects.Select(o => AssetDatabase.GetAssetPath(o)).Where(p => Directory.Exists(p)).Count() == Selection.objects.Length; + } + + private static void FindMaterialsRecursive(string folderPath, List materials) + { + foreach(string f in Directory.GetFiles(folderPath)) + { + if(AssetDatabase.GetMainAssetTypeAtPath(f) == typeof(Material)) + { + materials.Add(AssetDatabase.LoadAssetAtPath(f)); + } + } + foreach(string f in Directory.GetDirectories(folderPath)){ + FindMaterialsRecursive(f, materials); + } + } + + //----Folder Unlock + + static bool SelectedObjectsAreLockableMaterials() + { + if (Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 0) + { + return Selection.assetGUIDs.All(g => + { + if (AssetDatabase.GetMainAssetTypeAtPath(AssetDatabase.GUIDToAssetPath(g)) != typeof(Material)) + return false; + Material m = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g)); + return IsShaderUsingThryOptimizer(m.shader); + }); + } + return false; + } + + //----VRChat Callback to force Locking on upload + +#if VRC_SDK_VRCSDK2 || VRC_SDK_VRCSDK3 + public class LockMaterialsOnUpload : IVRCSDKPreprocessAvatarCallback + { + public int callbackOrder => 100; + + public bool OnPreprocessAvatar(GameObject avatarGameObject) + { + List materials = avatarGameObject.GetComponentsInChildren(true).SelectMany(r => r.sharedMaterials).ToList(); +#if VRC_SDK_VRCSDK3 && !UDON + VRCAvatarDescriptor descriptor = avatarGameObject.GetComponent(); + if(descriptor != null) + { + IEnumerable clips = descriptor.baseAnimationLayers.Select(l => l.animatorController).Where(a => a != null).SelectMany(a => a.animationClips).Distinct(); + foreach (AnimationClip clip in clips) + { + IEnumerable clipMaterials = AnimationUtility.GetObjectReferenceCurveBindings(clip).Where(b => b.isPPtrCurve && b.type.IsSubclassOf(typeof(Renderer)) && b.propertyName.StartsWith("m_Materials")) + .SelectMany(b => AnimationUtility.GetObjectReferenceCurve(clip, b)).Select(r => r.value as Material); + materials.AddRange(clipMaterials); + } + } + +#endif + if(SetLockedForAllMaterials(materials, 1, showProgressbar: true, showDialog: PersistentData.Get("ShowLockInDialog", true), allowCancel: false) == false) + return false; + //returning true all the time, because build process cant be stopped it seems + return true; + } + } +#endif + +#if VRC_SDK_VRCSDK2 || VRC_SDK_VRCSDK3 + public class LockMaterialsOnWorldUpload : IVRCSDKBuildRequestedCallback + { + public int callbackOrder => 100; + + bool IVRCSDKBuildRequestedCallback.OnBuildRequested(VRCSDKRequestedBuildType requestedBuildType) + { + List materials = new List(); + if (requestedBuildType == VRCSDKRequestedBuildType.Scene) + { + if (UnityEngine.Object.FindObjectsOfType(typeof(VRC_SceneDescriptor)) is VRC_SceneDescriptor[] descriptors && descriptors.Length > 0){ + var renderers = UnityEngine.Object.FindObjectsOfType(); + foreach (var rend in renderers) + { + foreach (var mat in rend.sharedMaterials){ + materials.Add(mat); + } + } + } + SetLockedForAllMaterials(materials, 1, showProgressbar: true, showDialog: PersistentData.Get("ShowLockInDialog", true), allowCancel: false); + } + return true; + } + } +#endif + + const string DidStripUnlockedShadersSessionStateKey = "ShaderOptimizerDidStripUnlockedShaders"; + public class StripUnlockedShadersFromBuild : UnityEditor.Build.IPreprocessShaders + { + // Thanks to z3y for this function + public int callbackOrder => 4; + + public void OnProcessShader(Shader shader, UnityEditor.Rendering.ShaderSnippetData snippet, IList data) + { + // Don't strip if the user disabled it (developer mode only) + if(Config.Singleton.enableDeveloperMode && Config.Singleton.disableUnlockedShaderStrippingOnBuild) + return; + + // Strip shaders from the build under the following conditions: + // - Has the property "shader_is_using_thry_editor", which should be present on all shaders using ThryEditor (even if it's not using the optimizer) + // - Has the property "_ShaderOptimizerEnabled", indicating the shader is using the optimizer + // - Doesn't have a name starting with "Hidden/Locked/", indicating the shader is unlocked + bool shouldStrip = shader.FindPropertyIndex("shader_is_using_thry_editor") >= 0 && shader.FindPropertyIndex("_ShaderOptimizerEnabled") >= 0 && !shader.name.StartsWith("Hidden/Locked/"); + + if (shouldStrip) + { + // Try to warn the user if there's an unlocked shader + if (!SessionState.GetBool(DidStripUnlockedShadersSessionStateKey, false)) + { + EditorUtility.DisplayDialog("Shader Optimizer: Unlocked Shader", + "An Unlocked shader was found, and will not be included in the build (this will cause pink materials).\n" + + "This shouldn't happen. Make sure all lockable materials are Locked, and try again.\n" + + "If it happens again, please report the issue via GitHub or Discord!" + , "OK"); + SessionState.SetBool(DidStripUnlockedShadersSessionStateKey, true); + } + + data.Clear(); + } + } + } + + [InitializeOnLoad] + public static class ResetStrippedShaderWarning + { + static ResetStrippedShaderWarning() + { + EditorApplication.update -= ResetWarning; + EditorApplication.update += ResetWarning; + } + + private static void ResetWarning() + { + if(SessionState.GetBool(DidStripUnlockedShadersSessionStateKey, false)) + { + Debug.LogError($"[Shader Optimizer] Unlocked shaders were removed from build. Materials will be pink. Use Thry -> Lock All on hierarchy items to ensure materials are locked."); + SessionState.SetBool(DidStripUnlockedShadersSessionStateKey, false); + } + } + } + + static string MaterialToShaderPropertyHash(Material m) + { + StringBuilder stringBuilder = new StringBuilder(m.shader.name); + + foreach (MaterialProperty prop in + MaterialEditor.GetMaterialProperties(new Object[] { m })) + { + string propName = prop.name; + + if (PropertiesToSkipInMaterialEquallityComparission.Contains(propName)) continue; + + string isAnimated = GetAnimatedTag(m, propName); + + if (isAnimated == "1") + { + stringBuilder.Append(isAnimated); + } + else if(isAnimated == "2") + { + //This is because materials with renaming should not share shaders + stringBuilder.Append(m.name); + } + else + { + + switch (prop.type) + { + case MaterialProperty.PropType.Color: + stringBuilder.Append(m.GetColor(propName).ToString()); + break; + case MaterialProperty.PropType.Vector: + stringBuilder.Append(m.GetVector(propName).ToString()); + break; + case MaterialProperty.PropType.Range: + case MaterialProperty.PropType.Float: + stringBuilder.Append(m.GetFloat(propName) + .ToString(CultureInfo.InvariantCulture)); + break; + case MaterialProperty.PropType.Texture: + Texture t = m.GetTexture(propName); + Vector4 texelSize = new Vector4(1.0f, 1.0f, 1.0f, 1.0f); + if (t != null) + texelSize = new Vector4(1.0f / t.width, 1.0f / t.height, t.width, t.height); + + stringBuilder.Append(m.GetTextureOffset(propName).ToString()); + stringBuilder.Append(m.GetTextureScale(propName).ToString()); + break; + } + } + } + + // https://forum.unity.com/threads/hash-function-for-game.452779/ + ASCIIEncoding encoding = new ASCIIEncoding(); + byte[] bytes = encoding.GetBytes(stringBuilder.ToString()); + var sha = new MD5CryptoServiceProvider(); + return BitConverter.ToString(sha.ComputeHash(bytes)).Replace("-", "").ToLower(); + } + + public static bool SetLockForAllChildren(GameObject[] objects, int lockState, bool showProgressbar = false, bool showDialog = false, bool allowCancel = true) + { + IEnumerable materials = objects.Select(o => o.GetComponentsInChildren(true)).SelectMany(rA => rA.SelectMany(r => r.sharedMaterials)); + return SetLockedForAllMaterials(materials, lockState, showProgressbar, showDialog); + } + + static Dictionary> s_shaderPropertyCombinations = new Dictionary>(); + public static bool SetLockedForAllMaterials(IEnumerable materials, int lockState, bool showProgressbar = false, bool showDialog = false, bool allowCancel = true, MaterialProperty shaderOptimizer = null) + { + Helper.RegisterEditorUse(); + //first the shaders are created. compiling is suppressed with start asset editing + AssetDatabase.StartAssetEditing(); + + bool isLocking = lockState == 1; + + //Get cleaned materia list + // The GetPropertyDefaultFloatValue is changed from 0 to 1 when the shader is locked in + IEnumerable materialsToChangeLock = materials.Where(m => m != null + && string.IsNullOrEmpty(AssetDatabase.GetAssetPath(m)) == false + && string.IsNullOrEmpty(AssetDatabase.GetAssetPath(m.shader)) == false + && IsShaderUsingThryOptimizer(m.shader) + && ( m.shader.name.StartsWith("Hidden/Locked/") + || (m.shader.name.StartsWith("Hidden/") && m.GetTag("OriginalShader",false,"") != "" + && m.shader.GetPropertyDefaultFloatValue(m.shader.FindPropertyIndex(GetOptimizerPropertyName(m.shader))) == 1) + ) != isLocking) + .Distinct(); + + // Make sure keywords are set correctly for materials to be locked. If unlocking, do this after the shaders are unlocked + if(isLocking && Config.Singleton.fixKeywordsWhenLocking) + ShaderEditor.FixKeywords(materialsToChangeLock); + + float i = 0; + float length = materialsToChangeLock.Count(); + + //show popup dialog if defined + if (showDialog && length > 0) + { + if(EditorUtility.DisplayDialog("Locking Materials", EditorLocale.editor.Get("auto_lock_dialog").ReplaceVariables(length), "More information","OK")) + { + Application.OpenURL("https://www.youtube.com/watch?v=asWeDJb5LAo"); + } + PersistentData.Set("ShowLockInDialog", false); + } + + //Create shader assets + foreach (Material m in materialsToChangeLock.ToList()) //have to call ToList() here otherwise the Unlock Shader button in the ShaderGUI doesn't work + { + //do progress bar + if (showProgressbar) + { + if (allowCancel) + { + if (EditorUtility.DisplayCancelableProgressBar(isLocking ? "Locking Materials" : "Unlocking Materials", m.name, i / length)) break; + } + else + { + EditorUtility.DisplayProgressBar(isLocking ? "Locking Materials" : "Unlocking Materials", m.name, i / length); + } + } + //create the assets + try + { + if (isLocking) + { + string hash = MaterialToShaderPropertyHash(m); + // Check that shader has already been created for this hash and still exists + // Or that the shader is being created for this has during this session + Material reference = null; + if(s_shaderPropertyCombinations.ContainsKey(hash)) + { + + reference = s_shaderPropertyCombinations[hash].FirstOrDefault(m2 => m2 != m && (materialsToChangeLock.Contains(m2) || Shader.Find(applyStructsLater[m2].newShaderName) != null)); + } + if (reference != null) + { + // Reuse existing shader and struct + ApplyStruct applyStruct = applyStructsLater[reference]; + applyStruct.material = m; + applyStructsLater[m] = applyStruct; + //Disable shader keywords + foreach (string keyword in m.shaderKeywords) + if (m.IsKeywordEnabled(keyword)) m.DisableKeyword(keyword); + + } + // Create new locked shader + else + { + ShaderOptimizer.Lock(m, + MaterialEditor.GetMaterialProperties(new UnityEngine.Object[] { m }), + applyShaderLater: true); + s_shaderPropertyCombinations[hash] = new List(); + } + // Add material to list of materials with same shader property hash + s_shaderPropertyCombinations[hash].Add(m); + // Update TAG_ALL_MATERIALS_GUIDS_USING_THIS_LOCKED_SHADER of all materials with same shader property hash + string tag = string.Join(",", s_shaderPropertyCombinations[hash].Select(m2 => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m2)))); + foreach (Material m2 in s_shaderPropertyCombinations[hash]) + m2.SetOverrideTag(TAG_ALL_MATERIALS_GUIDS_USING_THIS_LOCKED_SHADER, tag); + } + else if (!isLocking) + { + ShaderOptimizer.Unlock(m, shaderOptimizer); + } + } + catch (Exception e) + { + Debug.Log(e); + string position = e.StackTrace.Split('\n').FirstOrDefault(l => l.Contains("ThryEditor")); + if(position != null) + { + position = position.Split(new string[]{ "ThryEditor" }, StringSplitOptions.None).LastOrDefault(); + Debug.LogError("Could not un-/lock material " + m.name + " | Error thrown at " + position+ "\n"+e.StackTrace); + }else + { + Debug.LogError("Could not un-/lock material " + m.name + "\n"+e.StackTrace); + } + EditorUtility.ClearProgressBar(); + AssetDatabase.StopAssetEditing(); + return false; + } + i++; + } + + EditorUtility.ClearProgressBar(); + + // In case any keywords were messed up in the material following unlock, fix them now + if (!isLocking && Config.Singleton.fixKeywordsWhenLocking) + ShaderEditor.FixKeywords(materialsToChangeLock); + + AssetDatabase.StopAssetEditing(); + //unity now compiles all the shaders + + //now all new shaders are applied. this has to happen after unity compiled the shaders + if (isLocking) + { + AssetDatabase.Refresh(); + //Apply new shaders + foreach (Material m in materialsToChangeLock) + { + if (ShaderOptimizer.LockApplyShader(m)) + { + m.SetFloat(GetOptimizerPropertyName(m.shader), 1); + } + } + } + AssetDatabase.Refresh(); + + // Make sure things get saved after a cycle. This prevents thumbnails from getting stuck + if(Config.Singleton.saveAfterLockUnlock) + EditorApplication.update += QueueSaveAfterLockUnlock; + + if (ShaderEditor.Active != null && ShaderEditor.Active.IsDrawing) + { + GUIUtility.ExitGUI(); + } + return true; + } + + // This is just a wrapper so that it waits a cycle before saving + static void QueueSaveAfterLockUnlock() + { + EditorApplication.update -= QueueSaveAfterLockUnlock; + EditorApplication.update += SaveAfterLockUnlock; + } + + static void SaveAfterLockUnlock() + { + if (ShaderUtil.anythingCompiling) + return; + + EditorApplication.update -= SaveAfterLockUnlock; + AssetDatabase.SaveAssets(); + } + + public static string GetOptimizerPropertyName(Shader shader) + { + if (isShaderUsingThryOptimizer.ContainsKey(shader)) + { + if (isShaderUsingThryOptimizer[shader] == false) return null; + return shaderThryOptimizerPropertyName[shader]; + } + else + { + if (IsShaderUsingThryOptimizer(shader) == false) return null; + return shaderThryOptimizerPropertyName[shader]; + } + } + + private static Dictionary shaderThryOptimizerPropertyName = new Dictionary(); + private static Dictionary isShaderUsingThryOptimizer = new Dictionary(); + public static bool IsShaderUsingThryOptimizer(Shader shader) + { + if (isShaderUsingThryOptimizer.ContainsKey(shader)) + { + return isShaderUsingThryOptimizer[shader]; + } + SerializedObject shaderObject = new SerializedObject(shader); + SerializedProperty props = shaderObject.FindProperty("m_ParsedForm.m_PropInfo.m_Props"); + if (props != null) + { + foreach (SerializedProperty p in props) + { + SerializedProperty at = p.FindPropertyRelative("m_Attributes"); + if (at.arraySize > 0) + { + if (at.GetArrayElementAtIndex(0).stringValue == "ThryShaderOptimizerLockButton") + { + //Debug.Log(shader.name + " found to use optimizer "); + isShaderUsingThryOptimizer[shader] = true; + shaderThryOptimizerPropertyName[shader] = p.displayName; + return true; + } + } + } + } + isShaderUsingThryOptimizer[shader] = false; + return false; + } + + public static bool IsMaterialLocked(Material material) + { + return material.shader.name.StartsWith("Hidden/") && material.GetTag(TAG_ORIGINAL_SHADER, false, "") != ""; + } + + private static Dictionary shaderUsedTextureReferencesCount = new Dictionary(); + public static int GetUsedTextureReferencesCount(Shader s) + { + //Shader.m_ParsedForm.m_SubShaders[i].m_Passes[j].m_Programs[k].m_SubPrograms[l].m_Parameters[m].m_TextureParams[n] + //m_Programs not avaiable in unity 2019 + return 0; + /*if (shaderUsedTextureReferencesCount.ContainsKey(s)) return shaderUsedTextureReferencesCount[s]; + SerializedObject shaderObject = new SerializedObject(s); + SerializedProperty m_SubShaders = shaderObject.FindProperty("m_ParsedForm.m_SubShaders"); + for (int i_subShader = 0; i_subShader < m_SubShaders.arraySize; i_subShader++) + { + SerializedProperty m_Passes = m_SubShaders.GetArrayElementAtIndex(i_subShader).FindPropertyRelative("m_Passes"); + for (int i_passes = 0; i_passes < m_Passes.arraySize; i_passes++) + { + SerializedProperty m_Programs = m_Passes.GetArrayElementAtIndex(i_passes); + foreach (SerializedProperty p in m_Programs) Debug.Log(p.displayName); + } + } + return 0;*/ + } + } + + public class UnlockedMaterialsList : EditorWindow + { + private Vector2 scrollPosition = Vector2.zero; + + static Dictionary> unlockedMaterialsByShader = new Dictionary>(); + static Dictionary> lockedMaterialsByShader = new Dictionary>(); + static Dictionary lockedMaterialsByOriginalShader = new Dictionary(); + static Dictionary unlockedFoldouts = new Dictionary(); + static Dictionary lockedFoldouts = new Dictionary(); + string searchTerm = ""; + + private void OnEnable() + { + UpdateList(); + scrollPosition = Vector2.zero; + } + + bool LockAllWarning(List materialsToLock) + { + return EditorUtility.DisplayDialog("Lock All Materials", $"You're about to lock {materialsToLock.Count} materials. This might take a while. Are you sure you want to proceed?", "Lock All", "Cancel"); + } + + bool UnlockAllWarning(List materialsToUnlock) + { + return EditorUtility.DisplayDialog("Unlock All Materials", $"You're about to unlock {materialsToUnlock.Count} materials. This might cause crashes if over 64 textures are used in all materials on a single shader.\n\nAre you sure you want to proceed?", "Unlock All", "Cancel"); + } + + void UpdateList() + { + lockedMaterialsByOriginalShader.Clear(); + unlockedMaterialsByShader.Clear(); + lockedMaterialsByShader.Clear(); + string[] guids = AssetDatabase.FindAssets($"t:material {searchTerm}"); + float step = 1.0f / guids.Length; + float f = 0; + EditorUtility.DisplayProgressBar("Searching materials...", "", f); + + List unlockedMaterials = new List(); + List lockedMaterials = new List(); + + foreach (string g in guids) + { + Material m = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g)); + if (m != null && m.shader != null && ShaderOptimizer.IsShaderUsingThryOptimizer(m.shader)) + { + if(ShaderOptimizer.IsMaterialLocked(m)) + lockedMaterials.Add(m); + else + unlockedMaterials.Add(m); + } + f = f + step; + EditorUtility.DisplayProgressBar("Searching materials...", m.name, f); + } + foreach (IGrouping materials in unlockedMaterials.GroupBy(m => m.shader)) + { + unlockedMaterialsByShader.Add(materials.Key, materials.ToList()); + if(!unlockedFoldouts.ContainsKey(materials.Key.name)) + unlockedFoldouts.Add(materials.Key.name, false); + } + + foreach (Material material in lockedMaterials) + { + string originalShaderName = material.GetTag(ShaderOptimizer.TAG_ORIGINAL_SHADER, false, ""); + Shader originalShader = null; + if (originalShaderName == "" && originalShader == null) ShaderOptimizer.GuessShader(material.shader, out originalShader); + if (originalShader == null) originalShader = Shader.Find(originalShaderName); + + if (originalShader != null) + { + if (!lockedMaterialsByShader.ContainsKey(originalShader)) + lockedMaterialsByShader[originalShader] = new List(); + lockedMaterialsByShader[originalShader].Add(material); + + if (!lockedFoldouts.ContainsKey(originalShader.name)) + lockedFoldouts.Add(originalShader.name, false); + } + } + EditorUtility.ClearProgressBar(); + } + + private void OnGUI() + { + List materialsToLock = new List(); + + EditorGUI.BeginChangeCheck(); + EditorGUILayout.BeginHorizontal(); + searchTerm = EditorGUILayout.DelayedTextField(searchTerm); + if (GUILayout.Button("Update/Search") || EditorGUI.EndChangeCheck()) + UpdateList(); + EditorGUILayout.EndHorizontal(); + int unlockedMaterials = unlockedMaterialsByShader.Values.SelectMany(col => col).ToList().Count; + int lockedMaterials = lockedMaterialsByShader.Values.SelectMany(col => col).ToList().Count; + + EditorGUILayout.Space(10, true); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + EditorGUILayout.LabelField($"Unlocked Materials ({unlockedMaterials})", Styles.EDITOR_LABEL_HEADER); + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Lock All")) + { + List allUnlockedMaterials = unlockedMaterialsByShader.Values.SelectMany(col => col).ToList(); + if(LockAllWarning(allUnlockedMaterials)) + materialsToLock = allUnlockedMaterials; + } + if (GUILayout.Button("Expand All")) unlockedFoldouts.Keys.ToList().ForEach(f => unlockedFoldouts[f] = true); + if (GUILayout.Button("Collapse All")) unlockedFoldouts.Keys.ToList().ForEach(f => unlockedFoldouts[f] = false); + + EditorGUILayout.EndHorizontal(); + + if (unlockedMaterialsByShader.Count == 0) + GUILayout.Label("No Locked materials found for search term.", Styles.greenStyle); + + foreach (KeyValuePair> shaderMaterials in unlockedMaterialsByShader) + { + EditorGUILayout.Space(); + + unlockedFoldouts[shaderMaterials.Key.name] = EditorGUILayout.BeginFoldoutHeaderGroup(unlockedFoldouts[shaderMaterials.Key.name], $"{shaderMaterials.Key.name} ({shaderMaterials.Value.Count.ToString()})"); + if (unlockedFoldouts[shaderMaterials.Key.name]) + { + if (GUILayout.Button("Lock All")) + { + if(LockAllWarning(shaderMaterials.Value)) + materialsToLock = shaderMaterials.Value; + } + + foreach (Material m in shaderMaterials.Value) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.ObjectField(m, typeof(Material), false); + //EditorGUILayout.IntField(ShaderOptimizer.GetUsedTextureReferencesCount(m.shader)); + if (GUILayout.Button("Lock")) + materialsToLock.Add(m); + + EditorGUILayout.EndHorizontal(); + } + } + EditorGUILayout.EndFoldoutHeaderGroup(); + } + + if (materialsToLock.Count > 0) + { + ShaderOptimizer.SetLockedForAllMaterials(materialsToLock, 1, true, false, true); + materialsToLock.Clear(); + UpdateList(); + } + + EditorGUILayout.Space(10, true); + + EditorGUILayout.LabelField($"Locked Materials ({lockedMaterials})", Styles.EDITOR_LABEL_HEADER); + List materialsToUnlock = new List(); + + EditorGUILayout.BeginHorizontal(); + if (GUILayout.Button("Unlock All")) + { + List allLockedMaterials = lockedMaterialsByShader.Values.SelectMany(col => col).ToList(); + if (UnlockAllWarning(allLockedMaterials)) + { + materialsToUnlock = allLockedMaterials; + } + } + if (GUILayout.Button("Expand All")) lockedFoldouts.Keys.ToList().ForEach(f => lockedFoldouts[f] = true); + if (GUILayout.Button("Collapse All")) lockedFoldouts.Keys.ToList().ForEach(f => lockedFoldouts[f] = false); + + EditorGUILayout.EndHorizontal(); + + if (lockedMaterialsByShader.Count == 0) + GUILayout.Label("No Unlocked materials found for search term.", Styles.greenStyle); + + foreach (KeyValuePair> shaderMaterials in lockedMaterialsByShader) + { + EditorGUILayout.Space(); + + lockedFoldouts[shaderMaterials.Key.name] = EditorGUILayout.BeginFoldoutHeaderGroup(lockedFoldouts[shaderMaterials.Key.name], $"{shaderMaterials.Key.name} ({shaderMaterials.Value.Count.ToString()})"); + if (lockedFoldouts[shaderMaterials.Key.name]) + { + if (GUILayout.Button("Unlock All")) + { + if(UnlockAllWarning(shaderMaterials.Value)) + materialsToUnlock = shaderMaterials.Value; + } + + foreach (Material m in shaderMaterials.Value) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.ObjectField(m, typeof(Material), false); + //EditorGUILayout.IntField(ShaderOptimizer.GetUsedTextureReferencesCount(m.shader)); + if (GUILayout.Button("Unlock")) + materialsToUnlock.Add(m); + + EditorGUILayout.EndHorizontal(); + } + } + EditorGUILayout.EndFoldoutHeaderGroup(); + } + + if (materialsToUnlock.Count > 0) + { + ShaderOptimizer.SetLockedForAllMaterials(materialsToUnlock, 0, true, false, true); + materialsToUnlock.Clear(); + UpdateList(); + } + EditorGUILayout.EndScrollView(); + } + } +} diff --git a/Scripts/ThryEditor/Editor/ShaderTranslator.cs b/Scripts/ThryEditor/Editor/ShaderTranslator.cs new file mode 100644 index 0000000..3e8e399 --- /dev/null +++ b/Scripts/ThryEditor/Editor/ShaderTranslator.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; +using static Thry.ThryEditor.ShaderTranslator; + +namespace Thry.ThryEditor +{ + public class ShaderTranslator : ScriptableObject + { + public string Name; + public string OriginShader; + public string TargetShader; + public bool MatchOriginShaderBasedOnRegex; + public bool MatchTargetShaderBasedOnRegex; + public string OriginShaderRegex; + public string TargetShaderRegex; + public List PropertyTranslations; + + [Serializable] + public class PropertyTranslation + { + public string Origin; + public string Target; + public string Math; + } + + public List GetPropertyTranslations() + { + if (PropertyTranslations == null) + { + PropertyTranslations = new List(); + } + + return PropertyTranslations; + } + + public void Apply(ShaderEditor editor) + { + Shader originShader = Shader.Find(OriginShader); + Shader targetShader = Shader.Find(TargetShader); + SerializedObject serializedMaterial = new SerializedObject(editor.Materials[0]); + + foreach(PropertyTranslation trans in GetPropertyTranslations()) + { + if (editor.PropertyDictionary.ContainsKey(trans.Target)) + { + SerializedProperty p; + switch (editor.PropertyDictionary[trans.Target].MaterialProperty.type) + { + case MaterialProperty.PropType.Float: + case MaterialProperty.PropType.Range: + p = GetProperty(serializedMaterial, "m_SavedProperties.m_Floats", trans.Origin); + if (p != null) + { + float f = p.FindPropertyRelative("second").floatValue; + if (trans.Math.Length > 0) f = Helper.SolveMath(trans.Math, f); + editor.PropertyDictionary[trans.Target].MaterialProperty.floatValue = f; + } + break; + case MaterialProperty.PropType.Vector: + p = GetProperty(serializedMaterial, "m_SavedProperties.m_Colors", trans.Origin); + if (p != null) editor.PropertyDictionary[trans.Target].MaterialProperty.vectorValue = p.FindPropertyRelative("second").vector4Value; + break; + case MaterialProperty.PropType.Color: + p = GetProperty(serializedMaterial, "m_SavedProperties.m_Colors", trans.Origin); + if (p != null) editor.PropertyDictionary[trans.Target].MaterialProperty.colorValue = p.FindPropertyRelative("second").colorValue; + break; + case MaterialProperty.PropType.Texture: + p = GetProperty(serializedMaterial, "m_SavedProperties.m_TexEnvs", trans.Origin); + if (p != null) + { + SerializedProperty values = p.FindPropertyRelative("second"); + editor.PropertyDictionary[trans.Target].MaterialProperty.textureValue = + values.FindPropertyRelative("m_Texture").objectReferenceValue as Texture; + Vector2 scale = values.FindPropertyRelative("m_Scale").vector2Value; + Vector2 offset = values.FindPropertyRelative("m_Offset").vector2Value; + editor.PropertyDictionary[trans.Target].MaterialProperty.textureScaleAndOffset = + new Vector4(scale.x, scale.y , offset.x, offset.y); + } + break; + } + } + } + } + + SerializedProperty GetProperty(SerializedObject o, string arrayPath, string propertyName) + { + SerializedProperty array = o.FindProperty(arrayPath); + for(int i = 0; i < array.arraySize; i++) + { + if (array.GetArrayElementAtIndex(i).displayName == propertyName) + { + return array.GetArrayElementAtIndex(i); + } + } + return null; + } + + static List s_translationDefinitions; + static List TranslationDefinitions + { + get + { + if (s_translationDefinitions == null) + s_translationDefinitions = AssetDatabase.FindAssets("t:" + nameof(ShaderTranslator)).Select( + g => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g))).ToList(); + return s_translationDefinitions; + } + } + + public static ShaderTranslator CheckForExistingTranslationFile(Shader origin, Shader target) + { + return TranslationDefinitions.FirstOrDefault(t => + t.MatchOriginShaderBasedOnRegex ? (Regex.IsMatch(origin.name, t.OriginShaderRegex)) : (t.OriginShader == origin.name) && + t.MatchTargetShaderBasedOnRegex ? (Regex.IsMatch(target.name, t.TargetShaderRegex)) : (t.TargetShader == target.name) ); + } + + public static void SuggestedTranslationButtonGUI(ShaderEditor editor) + { + if(editor.SuggestedTranslationDefinition != null) + { + if(GUILayout.Button("Apply " + editor.SuggestedTranslationDefinition.Name + " shader translation.")) + { + editor.SuggestedTranslationDefinition.Apply(editor); + } + } + } + + public static void TranslationSelectionGUI(ShaderEditor editor) + { + Rect r; + if (GuiHelper.ButtonWithCursor(Styles.icon_style_shaders, "Shader Translation", 25, 25, out r)) + { + EditorUtility.DisplayCustomMenu(r, TranslationDefinitions.Select(t => new GUIContent(t.Name)).ToArray(), -1, ConfirmTranslationSelection, editor); + } + } + + static void ConfirmTranslationSelection(object userData, string[] options, int selected) + { + TranslationDefinitions[selected].Apply(userData as ShaderEditor); + } + + [MenuItem("Assets/Thry/ShaderTranslator/New Definition", false, 380)] + public static void CreateNewTranslationDefinition() + { + ShaderTranslator shaderTranslator = CreateInstance(); + string path = UnityHelper.GetCurrentAssetExplorerFolder() + "/shaderTranslationDefinition.asset"; + AssetDatabase.CreateAsset(shaderTranslator, path); + EditorGUIUtility.PingObject(shaderTranslator); + TranslationDefinitions.Add(shaderTranslator); + } + } + + public class ShaderTranslatorSelecUI : EditorWindow + { + + } + + [CustomEditor(typeof(ShaderTranslator))] + public class ShaderTranslatorEditorUI : Editor + { + public override void OnInspectorGUI() + { + serializedObject.Update(); + ShaderTranslator translator = serializedObject.targetObject as ShaderTranslator; + + translator.Name = EditorGUILayout.TextField("Translation File Name: " , translator.Name); + + GUILayout.Space(10); + + string[] shaders = AssetDatabase.FindAssets("t:shader").Select(g => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(g)).name). + Where(s => s.StartsWith("Hidden") == false).ToArray(); + + EditorGUI.BeginChangeCheck(); + int originIndex = EditorGUILayout.Popup("From Shader", Array.IndexOf(shaders, translator.OriginShader), shaders); + if (EditorGUI.EndChangeCheck()) translator.OriginShader = shaders[originIndex]; + + EditorGUI.BeginChangeCheck(); + int targetIndex = EditorGUILayout.Popup("To Shader", Array.IndexOf(shaders, translator.TargetShader), shaders); + if (EditorGUI.EndChangeCheck()) translator.TargetShader = shaders[targetIndex]; + + translator.MatchOriginShaderBasedOnRegex = EditorGUILayout.ToggleLeft(new GUIContent("Match Origin Shader Using Regex", + "Match the origin shader for suggestions based on a regex definition."), translator.MatchOriginShaderBasedOnRegex); + if (translator.MatchOriginShaderBasedOnRegex) + translator.OriginShaderRegex = EditorGUILayout.TextField("Origin Shader Regex", translator.OriginShaderRegex); + translator.MatchTargetShaderBasedOnRegex = EditorGUILayout.ToggleLeft(new GUIContent("Match Target Shader Using Regex", + "Match the target shader for suggestions based on a regex definition."), translator.MatchTargetShaderBasedOnRegex); + if (translator.MatchTargetShaderBasedOnRegex) + translator.TargetShaderRegex = EditorGUILayout.TextField("Target Shader Regex", translator.TargetShaderRegex); + + if (originIndex < 0 || targetIndex < 0) + { + EditorGUILayout.HelpBox("Could not find origin or target shader.", MessageType.Error); + return; + } + + Shader origin = Shader.Find(shaders[originIndex]); + Shader target = Shader.Find(shaders[targetIndex]); + + GUILayout.Space(10); + + using (new GUILayout.VerticalScope("box")) + { + GUILayout.Label("Property Translation", EditorStyles.boldLabel); + GUILayout.BeginHorizontal(); + GUILayout.Label("From"); + GUILayout.Label("To"); + GUILayout.Label("Math"); + GUILayout.EndHorizontal(); + List remove = new List(); + foreach (PropertyTranslation trans in translator.GetPropertyTranslations()) + { + Rect fullWidth = EditorGUILayout.GetControlRect(); + Rect r = fullWidth; + r.width = (r.width - 20) / 3; + if (GUI.Button(r, trans.Origin)) GuiHelper.SearchableEnumPopup.CreateSearchableEnumPopup( + MaterialEditor.GetMaterialProperties(new UnityEngine.Object[] { new Material(origin) }).Select(p => p.name).ToArray(), trans.Origin, + (newValue) => trans.Origin = newValue); + r.x += r.width; + if (GUI.Button(r, trans.Target)) GuiHelper.SearchableEnumPopup.CreateSearchableEnumPopup( + MaterialEditor.GetMaterialProperties(new UnityEngine.Object[] { new Material(target) }).Select(p => p.name).ToArray(), trans.Target, + (newValue) => trans.Target = newValue); + r.x += r.width; + trans.Math = EditorGUI.TextField(r, trans.Math); + r.x += r.width; + r.width = 20; + if (GUI.Button(r, GUIContent.none, Styles.icon_style_remove)) remove.Add(trans); + } + + foreach (PropertyTranslation r in remove) + translator.GetPropertyTranslations().Remove(r); + + Rect buttonRect = EditorGUILayout.GetControlRect(); + buttonRect.x = buttonRect.width - 20; + buttonRect.width = 20; + if (GUI.Button(buttonRect, GUIContent.none, Styles.icon_style_add)) translator.GetPropertyTranslations().Add(new PropertyTranslation()); + } + + serializedObject.Update(); + EditorUtility.SetDirty(serializedObject.targetObject); + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Styles.cs b/Scripts/ThryEditor/Editor/Styles.cs new file mode 100644 index 0000000..79d6df9 --- /dev/null +++ b/Scripts/ThryEditor/Editor/Styles.cs @@ -0,0 +1,96 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class Styles + { + public static GUIStyle masterLabel { get; private set; } = new GUIStyle(GUI.skin.label) { richText = true, alignment = TextAnchor.MiddleCenter }; + public static GUIStyle EDITOR_LABEL_HEADER { get; private set; } = new GUIStyle(GUI.skin.label) { fontSize = 16, alignment = TextAnchor.MiddleCenter }; + public static GUIStyle dropDownHeader { get; private set; } = new GUIStyle(new GUIStyle("ShurikenModuleTitle")) + { + font = new GUIStyle(EditorStyles.label).font, + fontSize = GUI.skin.font.fontSize, + border = new RectOffset(15, 7, 4, 4), + fixedHeight = 22, + contentOffset = new Vector2(20f, -2f) + }; + + public static Color COLOR_BG { get; private set; } = (EditorGUIUtility.isProSkin) ? new Color(0.4f, 0.4f, 0.4f) : new Color(0.8f, 0.8f, 0.8f); + public static Color COLOR_FG { get; private set; } = (EditorGUIUtility.isProSkin) ? new Color(0.8f, 0.8f, 0.8f) : Color.black; + + private static Color COLOR_ICON_FONT = GUI.skin.label.normal.textColor; + private static Color COLOR_ICON_GRAY = EditorGUIUtility.isProSkin ? COLOR_ICON_FONT : new Color(0.4f, 0.4f, 0.4f); + public static Color COLOR_ICON_ACTIVE_CYAN = Color.cyan; + private static Color COLOR_ICON_ACTIVE_RED = Color.red; + public static Color COLOR_BACKGROUND_1 = EditorGUIUtility.isProSkin ? new Color(0.27f, 0.27f, 0.27f) : new Color(0.65f, 0.65f, 0.65f); + public static Color COLOR_BACKGROUND_2 = EditorGUIUtility.isProSkin ? new Color(0.5f, 0.5f, 0.5f) : new Color(0.85f, 0.85f, 0.85f); + + public static GUIStyle dropDownHeaderLabel { get; private set; } = new GUIStyle(EditorStyles.boldLabel) { alignment = TextAnchor.MiddleCenter }; + public static GUIStyle label_align_right { get; private set; } = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; + public static GUIStyle dropDownHeaderButton { get; private set; } = new GUIStyle(EditorStyles.toolbarButton); + public static GUIStyle vectorPropertyStyle { get; private set; } = new GUIStyle() { padding = new RectOffset(0, 0, 2, 2) }; + public static GUIStyle greenStyle { get; private set; } = new GUIStyle() { normal = new GUIStyleState() { textColor = new Color(0, 0.5f, 0) } }; + public static GUIStyle animatedIndicatorStyle { get; private set; } = new GUIStyle() { normal = new GUIStyleState() { textColor = new Color(0.3f, 1, 0.3f) }, alignment = TextAnchor.MiddleRight }; + public static GUIStyle presetIndicatorStyle { get; private set; } = new GUIStyle() { normal = new GUIStyleState() { textColor = Color.cyan }, alignment = TextAnchor.MiddleRight }; + public static GUIStyle orangeStyle { get; private set; } = new GUIStyle() { normal = new GUIStyleState() { textColor = new Color(0.9f, 0.5f, 0) } }; + public static GUIStyle cyanStyle { get; private set; } = new GUIStyle() { normal = new GUIStyleState() { textColor = COLOR_ICON_ACTIVE_CYAN } }; + public static GUIStyle redStyle { get; private set; } = new GUIStyle() { normal = new GUIStyleState() { textColor = Color.red } }; + public static GUIStyle made_by_style { get; private set; } = new GUIStyle(EditorStyles.label) { fontSize = 10 }; + public static GUIStyle notification_style { get; private set; } = new GUIStyle(GUI.skin.box) { fontSize = 12, wordWrap = true, normal = new GUIStyleState() { textColor = Color.red } }; + + public static GUIStyle style_toggle_left_richtext { get; private set; } = new GUIStyle(EditorStyles.label) { richText = true }; + public static GUIStyle richtext { get; private set; } = new GUIStyle(EditorStyles.label) { richText = true, wordWrap = true }; + public static GUIStyle richtext_center { get; private set; } = new GUIStyle(EditorStyles.label) { richText = true, wordWrap = true, alignment = TextAnchor.MiddleCenter }; + + public static GUIStyle ButtonGreenText { get; private set; } = new GUIStyle(GUI.skin.button) { normal = new GUIStyleState() { textColor = new Color(0, 0.5f, 0) } }; + + public static GUIStyle icon_style_help = CreateIconStyle(EditorGUIUtility.IconContent("_Help")); + public static GUIStyle icon_style_menu = CreateIconStyle(EditorGUIUtility.IconContent("_Menu")); + public static GUIStyle icon_style_settings = CreateIconStyle(EditorGUIUtility.IconContent("_Popup")); + public static GUIStyle icon_style_search = CreateIconStyle(EditorGUIUtility.IconContent("Search Icon")); + public static GUIStyle icon_style_presets = CreateIconStyle(EditorGUIUtility.IconContent("Preset.Context")); + public static GUIStyle icon_style_add = CreateIconStyle(EditorGUIUtility.IconContent("PrefabOverlayAdded Icon")); + public static GUIStyle icon_style_remove = CreateIconStyle(EditorGUIUtility.IconContent("PrefabOverlayRemoved Icon")); + public static GUIStyle icon_style_refresh = CreateIconStyle(EditorGUIUtility.IconContent("d_Refresh")); + public static GUIStyle icon_style_shaders = CreateIconStyle(EditorGUIUtility.IconContent("d_ShaderVariantCollection Icon")); + public static GUIStyle icon_style_tools = CreateIconStyle(EditorGUIUtility.IconContent("d_SceneViewTools")); + public static GUIStyle icon_style_linked = CreateIconStyle(LoadTextureByGUID(RESOURCE_GUID.ICON_LINK)); + public static GUIStyle icon_style_thryIcon = CreateIconStyle(LoadTextureByGUID(RESOURCE_GUID.ICON_THRY)); + + public static Texture texture_icon_shaders = EditorGUIUtility.IconContent("d_ShaderVariantCollection Icon").image; + + static GUIStyle CreateIconStyle(GUIContent content) + { + return CreateIconStyle(content.image as Texture2D); + } + static GUIStyle CreateIconStyle(Texture2D texture) + { + return new GUIStyle() + { + stretchWidth = true, + stretchHeight = true, + fixedHeight = 0, + fixedWidth = 0, + normal = new GUIStyleState() + { + background = texture + } + }; + } + + + private static Texture2D LoadTextureByGUID(string guid) + { + string path = AssetDatabase.GUIDToAssetPath(guid); + if(path == null) return Texture2D.whiteTexture; + return AssetDatabase.LoadAssetAtPath(path); + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/TexturePacker.cs b/Scripts/ThryEditor/Editor/TexturePacker.cs new file mode 100644 index 0000000..0f8d5ce --- /dev/null +++ b/Scripts/ThryEditor/Editor/TexturePacker.cs @@ -0,0 +1,1485 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class TexturePacker : EditorWindow + { + const int MIN_WIDTH = 850; + const int MIN_HEIGHT = 790; + + [MenuItem("Thry/Texture Packer", priority = 100)] + public static TexturePacker ShowWindow() + { + TexturePacker packer = (TexturePacker)GetWindow(typeof(TexturePacker)); + packer.minSize = new Vector2(MIN_WIDTH, MIN_HEIGHT); + packer.titleContent = new GUIContent("Thry Texture Packer"); + packer.OnSave = null; // clear save callback + packer.OnChange = null; // clear save callback + return packer; + } + + [MenuItem("Assets/Thry/Open in Texture Packer")] + public static void OpenInTexturePacker() + { + TexturePacker packer = ShowWindow(); + packer.InitilizeWithOneTexture(Selection.activeObject as Texture2D); + } + + [MenuItem("Assets/Thry/Open in Texture Packer", true)] + public static bool OpenInTexturePackerValidate() + { + return Selection.activeObject is Texture2D; + } + +#region DataStructures + public enum TextureChannelIn { R, G, B, A, Max, None } + public enum TextureChannelOut { R, G, B, A, None } + public enum BlendMode { Add, Multiply, Max, Min } + public enum InvertMode { None, Invert} + public enum SaveType { PNG, JPG } + public enum InputType { Texture, Color, Gradient } + public enum GradientDirection { Horizontal, Vertical } + public enum KernelPreset { None, Custom, EdgeDetection, Sharpen, GaussianBlur3x3, GaussianBlur5x5 } + [Serializable] + public class ImageAdjust + { + public float Brightness = 1; + public float Hue = 0; + public float Saturation = 1; + public float Rotation = 0; + public Vector2 Scale = Vector2.one; + public Vector2 Offset = Vector2Int.zero; + public bool ChangeCheck = false; + public Vector2Int Resolution = new Vector2Int(16, 16); + } + static string GetTypeEnding(SaveType t) + { + switch (t) + { + case SaveType.PNG: return ".png"; + case SaveType.JPG: return ".jpg"; + default: return ".png"; + } + } + [Serializable] + public class OutputConfig + { + public BlendMode BlendMode; + public InvertMode Invert; + public float Fallback; + + public OutputConfig(float fallback = 0) + { + Fallback = fallback; + } + } + + [Serializable] + public class TextureSource + { + public Texture2D Texture; + public long LastHandledTextureEditTime; + public FilterMode FilterMode; + public Color Color; + public Gradient Gradient; + public GradientDirection GradientDirection; + public Texture2D GradientTexture; + public Texture2D TextureTexture; + + InputType _inputType = InputType.Texture; + public InputType InputType + { + get + { + return _inputType; + } + set + { + if(_inputType != value) + { + _inputType = value; + if(_inputType == InputType.Texture) Texture = TextureTexture; + if(_inputType == InputType.Gradient) Texture = GradientTexture; + } + } + } + + public TextureSource() + { + } + + public TextureSource(Texture2D tex) + { + SetTexture(tex); + } + + public void SetTexture(Texture2D tex) + { + Texture = tex; + FilterMode = tex != null ? tex.filterMode : FilterMode.Bilinear; + } + + public static void SetUncompressedTextureDirty(Texture2D tex) + { + if (_cachedUncompressedTextures.ContainsKey(tex)) + { + _cachedUncompressedTexturesNeedsReload[tex] = true; + } + } + + static Dictionary _cachedUncompressedTextures = new Dictionary(); + static Dictionary _cachedUncompressedTexturesNeedsReload = new Dictionary(); + public Texture2D UncompressedTexture + { + get + { + if(_cachedUncompressedTextures.ContainsKey(Texture) == false || _cachedUncompressedTexturesNeedsReload[Texture]) + { + string path = AssetDatabase.GetAssetPath(Texture); + if(path.EndsWith(".png") || path.EndsWith(".jpg")) + { + EditorUtility.DisplayProgressBar("Loading Raw PNG", "Loading " + path, 0.5f); + Texture2D tex = new Texture2D(2,2, TextureFormat.RGBA32, false, true); + tex.LoadImage(System.IO.File.ReadAllBytes(path)); + tex.filterMode = Texture.filterMode; + _cachedUncompressedTextures[Texture] = tex; + EditorUtility.ClearProgressBar(); + }else if (path.EndsWith(".tga")) + { + Texture2D tex = TextureHelper.LoadTGA(path, true); + tex.filterMode = Texture.filterMode; + _cachedUncompressedTextures[Texture] = tex; + } + else + { + _cachedUncompressedTextures[Texture] = Texture; + } + _cachedUncompressedTexturesNeedsReload[Texture] = false; + } + return _cachedUncompressedTextures[Texture]; + } + } + + public void FindMaxSize(ref int width, ref int height) + { + if (Texture == null) return; + width = Mathf.Max(width, UncompressedTexture.width); + height = Mathf.Max(height, UncompressedTexture.height); + } + } + + [Serializable] + public class Connection + { + public int FromTextureIndex = -1; + public TextureChannelIn FromChannel = TextureChannelIn.None; + public TextureChannelOut ToChannel = TextureChannelOut.None; + + public static Connection CreateFull(int index, TextureChannelIn channel, TextureChannelOut toChannel) + { + Connection connection = new Connection(); + connection.FromTextureIndex = index; + connection.FromChannel = channel; + connection.ToChannel = toChannel; + return connection; + } + + public static Connection Create(int index, TextureChannelIn channel) + { + Connection connection = new Connection(); + connection.FromTextureIndex = index; + connection.FromChannel = channel; + return connection; + } + + public static Connection Create(TextureChannelOut channel) + { + Connection connection = new Connection(); + connection.ToChannel = channel; + return connection; + } + + public void SetFrom(int index, TextureChannelIn channel, TexturePacker packer) + { + // cancle if selecting same channel + if(FromTextureIndex == index && FromChannel == channel) + { + packer._creatingConnection = null; + return; + } + // set + FromTextureIndex = index; + FromChannel = channel; + // check if done + if(ToChannel == TextureChannelOut.None) return; + // check if already exists + if(packer._connections.Exists(c => c.ToChannel == ToChannel && c.FromTextureIndex == FromTextureIndex && c.FromChannel == FromChannel)) + { + packer._creatingConnection = null; + return; + } + packer._connections.Add(this); + packer._creatingConnection = null; + packer._changeCheckForPacking = true; + } + + public void SetTo(TextureChannelOut channel, TexturePacker packer) + { + // cancle if selecting same channel + if (ToChannel == channel) + { + return; + } + // set + ToChannel = channel; + // check if done + if(FromTextureIndex == -1 || FromChannel == TextureChannelIn.None) return; + // check if already exists + if(packer._connections.Exists(c => c.ToChannel == ToChannel && c.FromTextureIndex == FromTextureIndex && c.FromChannel == FromChannel)) + { + packer._creatingConnection = null; + return; + } + packer._connections.Add(this); + packer._creatingConnection = null; + packer._changeCheckForPacking = true; + } + + Vector3 _bezierStart, _bezierEnd, _bezierStartTangent, _bezierEndTangent; + + public void CalculateBezierPoints(Vector2[] positionsIn, Vector2[] positionsOut) + { + _bezierStart = positionsIn[FromTextureIndex * 5 + (int)FromChannel]; + _bezierEnd = positionsOut[(int)ToChannel]; + _bezierStartTangent = _bezierStart + Vector3.right * 50; + _bezierEndTangent = _bezierEnd + Vector3.left * 50; + } + + public Vector3 BezierStart { get { return _bezierStart; } } + public Vector3 BezierEnd { get { return _bezierEnd; } } + public Vector3 BezierStartTangent { get { return _bezierStartTangent; } } + public Vector3 BezierEndTangent { get { return _bezierEndTangent; } } + } + struct InteractionWithConnection + { + public Connection connection; + public float distanceX; + } +#endregion + + const string CHANNEL_PREVIEW_SHADER = "Hidden/Thry/ChannelPreview"; + + TextureSource[] _textureSources = new TextureSource[] + { + new TextureSource(), + new TextureSource(), + new TextureSource(), + new TextureSource(), + }; + OutputConfig[] _outputConfigs = new OutputConfig[] + { + new OutputConfig(0), + new OutputConfig(0), + new OutputConfig(0), + new OutputConfig(1), + }; + + Vector2 _scrollPosition; + + + TexturePackerConfig _config; + List _connections = new List(); + Connection _creatingConnection; + Texture2D _outputTexture; + ColorSpace _colorSpace = ColorSpace.Uninitialized; + FilterMode _filterMode = FilterMode.Bilinear; + + string _saveFolder; + string _saveName; + SaveType _saveType = SaveType.PNG; + float _saveQuality = 1; + + KernelPreset _kernelPreset = KernelPreset.None; + bool _kernelEditHorizontal = true; + float[] _kernel_x = GetKernelPreset(KernelPreset.None, true); + float[] _kernel_y = GetKernelPreset(KernelPreset.None, false); + int _kernel_loops = 1; + float _kernel_strength = 1; + bool _kernel_twoPass; + bool _kernel_grayScale; + bool[] _kernel_channels = new bool[4] { true, true, true, true }; + bool[] _channel_export = new bool[4] { true, true, true, true }; + + ImageAdjust _imageAdjust = new ImageAdjust(); + + public Action OnSave; + public Action OnChange; + + static Material s_channelPreviewMaterial; + static Material ChannelPreviewMaterial + { + get + { + if (s_channelPreviewMaterial == null) + { + s_channelPreviewMaterial = new Material(Shader.Find(CHANNEL_PREVIEW_SHADER)); + } + return s_channelPreviewMaterial; + } + } + + static ComputeShader s_computeShader; + static ComputeShader ComputeShader + { + get + { + if (s_computeShader == null) + { + s_computeShader = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath("56f54c5664777a747b2552701571174d")); + } + return s_computeShader; + } + } + + Vector2[] _positionsChannelIn = new Vector2[20]; + Vector2[] _positionsChannelOut = new Vector2[4]; + Rect[] _rectsChannelIn = new Rect[20]; + Rect[] _rectsChannelOut = new Rect[4]; + + public void InitilizeWithData(TextureSource[] sources, OutputConfig[] configs, IEnumerable connections, FilterMode filterMode, ColorSpace colorSpace) + { + _textureSources = new TextureSource[] + { + new TextureSource(), + new TextureSource(), + new TextureSource(), + new TextureSource(), + }; + Array.Copy(sources, _textureSources, sources.Length); + _outputConfigs = configs; + _connections = connections.ToList(); + _filterMode = filterMode; + _colorSpace = colorSpace; + // Reset Color Adjust + _imageAdjust = new ImageAdjust(); + DeterminePathAndFileNameIfEmpty(true); + DetermineImportSettings(); + Pack(); + } + + void InitilizeWithOneTexture(Texture2D texture) + { + _connections.Clear(); + _textureSources[0].SetTexture(texture); + _textureSources[1].SetTexture(texture); + _textureSources[2].SetTexture(texture); + _textureSources[3].SetTexture(texture); + // Add connections + _connections.Add(Connection.CreateFull(0, TextureChannelIn.R, TextureChannelOut.R)); + _connections.Add(Connection.CreateFull(1, TextureChannelIn.G, TextureChannelOut.G)); + _connections.Add(Connection.CreateFull(2, TextureChannelIn.B, TextureChannelOut.B)); + _connections.Add(Connection.CreateFull(3, TextureChannelIn.A, TextureChannelOut.A)); + // Reset Color Adjust + _imageAdjust = new ImageAdjust(); + DeterminePathAndFileNameIfEmpty(true); + DetermineImportSettings(); + DetermineOutputResolution(_textureSources, _imageAdjust); + Pack(); + } + + const int TOP_OFFSET = 50; + const int INPUT_PADDING = 20; + const int OUTPUT_HEIGHT = 300; + + bool _changeCheckForPacking; + private void OnGUI() + { + _scrollPosition = GUILayout.BeginScrollView(_scrollPosition); + + DrawConfigGUI(); + // Draw three texture slots on the left, a space in the middle, and one texutre slot on the right + GUILayout.BeginVertical(); + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + _changeCheckForPacking = false; + + GUILayout.BeginVertical(); + GUILayout.Space(TOP_OFFSET); + bool didInputTexturesChange = false; + didInputTexturesChange |= DrawInput( _textureSources[0], 0); + GUILayout.Space(INPUT_PADDING); + didInputTexturesChange |= DrawInput( _textureSources[1], 1); + GUILayout.Space(INPUT_PADDING); + didInputTexturesChange |= DrawInput( _textureSources[2], 2); + GUILayout.Space(INPUT_PADDING); + didInputTexturesChange |= DrawInput( _textureSources[3], 3); + GUILayout.EndVertical(); + float inputHeight = 120 * 4 + INPUT_PADDING * 3 + TOP_OFFSET; + + GUILayout.Space(400); + Rect rect_outputAndSettings = EditorGUILayout.BeginVertical(); + float output_y_offset = TOP_OFFSET + (inputHeight - TOP_OFFSET - OUTPUT_HEIGHT) / 2; + GUILayout.Space(output_y_offset); + DrawOutput(_outputTexture, OUTPUT_HEIGHT); + + EditorGUILayout.Space(15); + Rect backgroundImageSettings = EditorGUILayout.BeginVertical(); + backgroundImageSettings = new RectOffset(5, 5, 5, 5).Add(backgroundImageSettings); + GUI.DrawTexture(backgroundImageSettings, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 1, Styles.COLOR_BACKGROUND_1, 0, 10); + + EditorGUI.BeginChangeCheck(); + _colorSpace = (ColorSpace)EditorGUILayout.EnumPopup(_colorSpace); + _filterMode = (FilterMode)EditorGUILayout.EnumPopup(_filterMode); + _changeCheckForPacking |= EditorGUI.EndChangeCheck(); + + // Make the sliders delayed, else the UX feels terrible + EditorGUI.BeginChangeCheck(); + EventType eventTypeBeforerSliders = Event.current.type; + bool wasWide = EditorGUIUtility.wideMode; + EditorGUIUtility.wideMode = true; + _imageAdjust.Resolution = EditorGUILayout.Vector2IntField("Resolution", _imageAdjust.Resolution); + _imageAdjust.Scale = EditorGUILayout.Vector2Field("Scale", _imageAdjust.Scale); + _imageAdjust.Offset = EditorGUILayout.Vector2Field("Offset", _imageAdjust.Offset); + _imageAdjust.Rotation = EditorGUILayout.Slider("Rotation", _imageAdjust.Rotation, -180, 180); + _imageAdjust.Hue = EditorGUILayout.Slider("Hue", _imageAdjust.Hue, 0, 1); + _imageAdjust.Saturation = EditorGUILayout.Slider("Saturation", _imageAdjust.Saturation, 0, 3); + _imageAdjust.Brightness = EditorGUILayout.Slider("Brightness", _imageAdjust.Brightness, 0, 3); + _imageAdjust.ChangeCheck |= EditorGUI.EndChangeCheck(); + EditorGUIUtility.wideMode = wasWide; + if(_imageAdjust.ChangeCheck && (eventTypeBeforerSliders == EventType.MouseUp || (eventTypeBeforerSliders == EventType.KeyUp && Event.current.keyCode == KeyCode.Return))) + { + _changeCheckForPacking = true; + _imageAdjust.ChangeCheck = false; + } + + DrawKernelGUI(); + + EditorGUILayout.EndVertical(); + + EditorGUILayout.EndVertical(); + + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + + DrawConnections(); + + if(didInputTexturesChange) + { + DetermineImportSettings(); + } + if(_changeCheckForPacking) + { + Pack(); + Repaint(); + } + + GUILayout.Space(20); + DrawSaveGUI(); + GUILayout.EndVertical(); + + HandleConnectionEditing(); + HandleConnectionCreation(); + + GUILayout.EndScrollView(); + } + + void HandleConnectionEditing() + { + if(Event.current.type == EventType.MouseDown && Event.current.button == 0) + { + InteractionWithConnection toEdit = CheckIfConnectionClicked(); + if(toEdit.connection != null) + { + _connections.Remove(toEdit.connection); + // Remove the connection on one side + if(toEdit.distanceX > 0.5) + toEdit.connection.ToChannel = TextureChannelOut.None; + else + toEdit.connection.FromChannel = TextureChannelIn.None; + _creatingConnection = toEdit.connection; + Pack(); + Repaint(); + } + } + } + + void HandleConnectionCreation() + { + // Connections are not nullable anymore, since they are serialized + if(_creatingConnection != null && (_creatingConnection.FromChannel != TextureChannelIn.None || _creatingConnection.ToChannel != TextureChannelOut.None)) + { + // if user clicked anywhere on the screen, stop creating the connection + if(Event.current.type == EventType.MouseUp) + { + // Check if mouse position is over any input / output slot + Vector2 mousePosition = Event.current.mousePosition; + for(int t = 0; t < 4; t++) + { + for(int c = 0; c < 5; c++) + { + if(_rectsChannelIn[t * 5 + c].Contains(mousePosition)) + { + _creatingConnection.SetFrom(t, (TextureChannelIn)c, this); + Pack(); + Repaint(); + return; + } + } + } + for(int c = 0; c < 4; c++) + { + if(_rectsChannelOut[c].Contains(mousePosition)) + { + _creatingConnection.SetTo((TextureChannelOut)c, this); + Pack(); + Repaint(); + return; + } + } + _creatingConnection = null; + return; + } + + Vector2 bezierStart, bezierEnd, bezierStartTangent, bezierEndTangent; + Color color = Color.white; + + bezierEnd = Event.current.mousePosition; + + if(_creatingConnection.FromChannel != TextureChannelIn.None) + { + bezierStart = _positionsChannelIn[_creatingConnection.FromTextureIndex * 5 + (int)_creatingConnection.FromChannel]; + bezierStartTangent = bezierStart + Vector2.right * 50; + bezierEndTangent = bezierEnd + Vector2.left * 50; + color = GetColor(_creatingConnection.FromChannel); + } + else + { + bezierStart = _positionsChannelOut[(int)_creatingConnection.ToChannel]; + bezierStartTangent = bezierStart + Vector2.left * 50; + bezierEndTangent = bezierEnd + Vector2.right * 50; + color = GetColor(_creatingConnection.ToChannel); + } + + Handles.DrawBezier(bezierStart, bezierEnd, bezierStartTangent, bezierEndTangent, color, null, 2); + Repaint(); + } + } + + InteractionWithConnection CheckIfConnectionClicked() + { + Vector2 mousePos = Event.current.mousePosition; + float minDistance = 50; + InteractionWithConnection clickedConnection = new InteractionWithConnection(); + foreach(Connection c in _connections) + { + Vector3 from = c.BezierStart; + Vector3 to = c.BezierEnd; + float topY = Mathf.Max(from.y, to.y); + float bottomY = Mathf.Min(from.y, to.y); + float leftX = Mathf.Min(from.x, to.x); + float rightX = Mathf.Max(from.x, to.x); + // check if mouse is in the area of the bezier curve + if(mousePos.x > leftX && mousePos.x < rightX) + { + if(mousePos.y > bottomY && mousePos.y < topY) + { + // check if mouse is close to the bezier curve + float distance = HandleUtility.DistancePointBezier(mousePos, c.BezierStart, c.BezierEnd, c.BezierStartTangent, c.BezierEndTangent); + if(distance < 50) + { + if(distance < minDistance) + { + minDistance = distance; + clickedConnection.connection = c; + clickedConnection.distanceX = (mousePos.x - leftX) / (rightX - leftX); + } + } + } + } + } + return clickedConnection; + } + + void DrawConfigGUI() + { + Rect bg = new Rect(position.width / 2 - 150, 10, 300, 30); + Rect rObjField = new RectOffset(5, 5, 5, 5).Remove(bg); + GUI.DrawTexture(bg, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, Styles.COLOR_BACKGROUND_1, 0, 10); + + if(_config == null) + { + rObjField = new RectOffset(0, 100, 0, 0).Remove(rObjField); + Rect rButton = new Rect(rObjField.x + rObjField.width + 5, rObjField.y, 95, rObjField.height); + if(GUI.Button(rButton, "New config")) + { + CreateConfig(); + } + } + + EditorGUI.BeginChangeCheck(); + _config = (TexturePackerConfig)EditorGUI.ObjectField(rObjField, _config, typeof(TexturePackerConfig), false); + if(EditorGUI.EndChangeCheck()) + { + LoadConfig(); + Pack(); + Repaint(); + } + } + + void DrawKernelGUI() + { + Rect r_enum = EditorGUILayout.GetControlRect(false, 20); + + EditorGUI.BeginChangeCheck(); + _kernelPreset = (KernelPreset)EditorGUI.EnumPopup(r_enum, "Kernel Filter", _kernelPreset); + if(EditorGUI.EndChangeCheck()) + { + _kernel_x = _kernelPreset == KernelPreset.Custom ? _kernel_x : GetKernelPreset(_kernelPreset, true); + _kernel_y = _kernelPreset == KernelPreset.Custom ? _kernel_y : GetKernelPreset(_kernelPreset, false); + KernelPresetSetValues(_kernelPreset); + Pack(); + Repaint(); + } + + this.minSize = new Vector2(MIN_WIDTH, _kernelPreset == KernelPreset.None ? MIN_HEIGHT : MIN_HEIGHT + 250); + + if(_kernelPreset != KernelPreset.None) + { + EventType eventTypeBeforerSliders = Event.current.type; + _kernel_loops = EditorGUILayout.IntSlider("Loops", _kernel_loops, 1, 25); + _kernel_strength = EditorGUILayout.Slider("Strength", _kernel_strength, 0, 1); + _kernel_twoPass = EditorGUILayout.Toggle("Two Pass", _kernel_twoPass); + _kernel_grayScale = EditorGUILayout.Toggle("Gray Scale", _kernel_grayScale); + Rect r_channels = EditorGUILayout.GetControlRect(false, 20); + r_channels.width /= 4; + float prevLabelWidth = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 10; + _kernel_channels[0] = EditorGUI.Toggle(r_channels, "R", _kernel_channels[0]); + r_channels.x += r_channels.width; + _kernel_channels[1] = EditorGUI.Toggle(r_channels, "G", _kernel_channels[1]); + r_channels.x += r_channels.width; + _kernel_channels[2] = EditorGUI.Toggle(r_channels, "B", _kernel_channels[2]); + r_channels.x += r_channels.width; + _kernel_channels[3] = EditorGUI.Toggle(r_channels, "A", _kernel_channels[3]); + EditorGUIUtility.labelWidth = prevLabelWidth; + if(Event.current.type == EventType.Used && (eventTypeBeforerSliders == EventType.MouseUp || (eventTypeBeforerSliders == EventType.KeyUp && Event.current.keyCode == KeyCode.Return))) + { + Pack(); + Repaint(); + } + + if(_kernel_twoPass) + { + Rect r_buttons = EditorGUILayout.GetControlRect(false, 20); + if(GUI.Button( new Rect(r_buttons.x, r_buttons.y, r_buttons.width / 2, r_buttons.height), "X")) _kernelEditHorizontal = true; + if(GUI.Button( new Rect(r_buttons.x + r_buttons.width / 2, r_buttons.y, r_buttons.width / 2, r_buttons.height), "Y")) _kernelEditHorizontal = false; + } + + Rect r = EditorGUILayout.GetControlRect(false, 130); + EditorGUI.BeginChangeCheck(); + // draw 5x5 matrix inside the r_kernelX rect + for(int x = 0; x < 5; x++) + { + for(int y = 0; y < 5; y++) + { + Rect r_cell = new Rect(r.x + x * r.width / 5, r.y + y * r.height / 5, r.width / 5, r.height / 5); + if(_kernelEditHorizontal || !_kernel_twoPass) _kernel_x[x + y * 5] = EditorGUI.DelayedFloatField(r_cell, _kernel_x[x + y * 5]); + else _kernel_y[x + y * 5] = EditorGUI.DelayedFloatField(r_cell, _kernel_y[x + y * 5]); + } + } + if(EditorGUI.EndChangeCheck()) + { + _kernelPreset = KernelPreset.Custom; + Pack(); + Repaint(); + } + } + } + + void DrawSaveGUI() + { + // Saving information + // folder selection + // determine folder & filename from asset name if not set + if(string.IsNullOrEmpty(_saveFolder)) + { + DeterminePathAndFileNameIfEmpty(); + } + + Rect r = EditorGUILayout.BeginHorizontal(); + + Rect background = new Rect(r.x + r.width / 2 - 400, r.y - 5, 800, 77); + GUI.DrawTexture(background, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 0, Styles.COLOR_BACKGROUND_1, 0, 10); + + GUILayout.FlexibleSpace(); + // show current path + GUILayout.Label("Save to: "); + GUILayout.Label(_saveFolder + "\\"); + _saveName = GUILayout.TextField(_saveName, GUILayout.MinWidth(50)); + _saveType = (SaveType)EditorGUILayout.EnumPopup(_saveType, GUILayout.Width(70)); + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + if(GUILayout.Button("Change Folder", GUILayout.Width(100))) + { + string path = EditorUtility.OpenFolderPanel("Select folder", _saveFolder, ""); + if (!string.IsNullOrEmpty(path)) + { + // Make path relative to Assets folder + path = path.Replace(Application.dataPath, "Assets"); + _saveFolder = path; + } + } + if(GUILayout.Button("Save", GUILayout.Width(100))) + { + Save(); + } + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + + GUILayout.Space(10); + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + _channel_export[0] = GUILayout.Toggle(_channel_export[0], "R", GUILayout.Width(26)); + _channel_export[1] = GUILayout.Toggle(_channel_export[1], "G", GUILayout.Width(26)); + _channel_export[2] = GUILayout.Toggle(_channel_export[2], "B", GUILayout.Width(26)); + _channel_export[3] = GUILayout.Toggle(_channel_export[3], "A", GUILayout.Width(26)); + if(GUILayout.Button("Export Channels", GUILayout.Width(130))) + { + ExportChannels(false); + } + if(GUILayout.Button("Export Channels (B&W)", GUILayout.Width(150))) + { + ExportChannels(true); + } + GUILayout.FlexibleSpace(); + EditorGUILayout.EndHorizontal(); + } + + void DeterminePathAndFileNameIfEmpty(bool forceOverwrite = false) + { + foreach(TextureSource s in _textureSources) + { + if(s.Texture != null) + { + string path = AssetDatabase.GetAssetPath(s.Texture); + if(string.IsNullOrWhiteSpace(path)) + continue; + if(string.IsNullOrWhiteSpace(_saveFolder) || forceOverwrite) + _saveFolder = Path.GetDirectoryName(path); + if(string.IsNullOrWhiteSpace(_saveName) || forceOverwrite) + _saveName = Path.GetFileNameWithoutExtension(path) + "_packed"; + break; + } + } + } + + void DetermineImportSettings() + { + _colorSpace = ColorSpace.Gamma; + _filterMode = FilterMode.Bilinear; + foreach(TextureSource s in _textureSources) + { + if(DetermineImportSettings(s)) + break; + } + } + + bool DetermineImportSettings(TextureSource s) + { + if(s.Texture != null) + { + string path = AssetDatabase.GetAssetPath(s.Texture); + if(path == null) + return false; + TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; + if(importer != null) + { + _colorSpace = importer.sRGBTexture ? ColorSpace.Gamma : ColorSpace.Linear; + _filterMode = importer.filterMode; + return true; + } + } + return false; + } + + void DrawConnections() + { + // Draw connections as lines + foreach (Connection c in _connections) + { + c.CalculateBezierPoints(_positionsChannelIn, _positionsChannelOut); + Handles.DrawBezier(c.BezierStart, c.BezierEnd, c.BezierStartTangent, c.BezierEndTangent, GetColor(c.FromChannel), null, 2); + } + } + + void DrawOutput(Texture2D texture, int height = 200) + { + int channelWidth = height / 4; + + Rect rect = GUILayoutUtility.GetRect(height, height); + + // draw 4 channl boxes on the left side + Rect rectR = new Rect(rect.x - channelWidth, rect.y, channelWidth, channelWidth); + Rect rectG = new Rect(rect.x - channelWidth, rect.y + channelWidth, channelWidth, channelWidth); + Rect rectB = new Rect(rect.x - channelWidth, rect.y + channelWidth * 2, channelWidth, channelWidth); + Rect rectA = new Rect(rect.x - channelWidth, rect.y + channelWidth * 3, channelWidth, channelWidth); + + // Draw circle button bext to each channel box + int buttonWidth = 80; + int buttonHeight = 40; + Rect buttonR = new Rect(rectR.x - buttonWidth - 5, rectR.y + rectR.height / 2 - buttonHeight / 2, buttonWidth, buttonHeight); + Rect buttonG = new Rect(rectG.x - buttonWidth - 5, rectG.y + rectG.height / 2 - buttonHeight / 2, buttonWidth, buttonHeight); + Rect buttonB = new Rect(rectB.x - buttonWidth - 5, rectB.y + rectB.height / 2 - buttonHeight / 2, buttonWidth, buttonHeight); + Rect buttonA = new Rect(rectA.x - buttonWidth - 5, rectA.y + rectA.height / 2 - buttonHeight / 2, buttonWidth, buttonHeight); + + // Draw background + Rect background = new Rect(buttonR.x + 10, rect.y - 5, (rect.x + rect.width + 5) - (buttonR.x + 10), rect.height + 10); + GUI.DrawTexture(background, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 1, Styles.COLOR_BACKGROUND_1, 0, 10); + + EditorGUI.DrawTextureTransparent(rect, texture != null ? texture : Texture2D.blackTexture, ScaleMode.ScaleToFit, 1); + // draw 4 channl boxes on the left side + if (texture != null) + { + ChannelPreviewMaterial.SetTexture("_MainTex", texture); + ChannelPreviewMaterial.SetFloat("_Channel", 0); + EditorGUI.DrawPreviewTexture(rectR, texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + ChannelPreviewMaterial.SetFloat("_Channel", 1); + EditorGUI.DrawPreviewTexture(rectG, texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + ChannelPreviewMaterial.SetFloat("_Channel", 2); + EditorGUI.DrawPreviewTexture(rectB, texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + ChannelPreviewMaterial.SetFloat("_Channel", 3); + EditorGUI.DrawPreviewTexture(rectA, texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + } + else + { + EditorGUI.DrawRect(rectR, Color.black); + EditorGUI.DrawRect(rectG, Color.black); + EditorGUI.DrawRect(rectB, Color.black); + EditorGUI.DrawRect(rectA, Color.black); + } + // Draw circle button bext to each channel box + _positionsChannelOut[0] = new Vector2(buttonR.x, buttonR.y + buttonR.height / 2); + _positionsChannelOut[1] = new Vector2(buttonG.x, buttonG.y + buttonG.height / 2); + _positionsChannelOut[2] = new Vector2(buttonB.x, buttonB.y + buttonB.height / 2); + _positionsChannelOut[3] = new Vector2(buttonA.x, buttonA.y + buttonA.height / 2); + _rectsChannelOut[0] = buttonR; + _rectsChannelOut[1] = buttonG; + _rectsChannelOut[2] = buttonB; + _rectsChannelOut[3] = buttonA; + DrawOutputChannel(buttonR, TextureChannelOut.R, _outputConfigs[0]); + DrawOutputChannel(buttonG, TextureChannelOut.G, _outputConfigs[1]); + DrawOutputChannel(buttonB, TextureChannelOut.B, _outputConfigs[2]); + DrawOutputChannel(buttonA, TextureChannelOut.A, _outputConfigs[3]); + } + + void DrawOutputChannel(Rect position, TextureChannelOut channel, OutputConfig config) + { + // RGBA on the left side + // fallback or (blendmode & invert) on the right side + Rect channelRect = new Rect(position.x, position.y, 20, position.height); + Rect fallbackRect = new Rect(position.x + 20, position.y, position.width - 20, position.height); + Rect blendmodeRect = new Rect(fallbackRect.x, fallbackRect.y, fallbackRect.width, fallbackRect.height / 2); + Rect invertRect = new Rect(fallbackRect.x, fallbackRect.y + fallbackRect.height / 2, fallbackRect.width, fallbackRect.height / 2); + + if(Event.current.type == EventType.MouseDown && channelRect.Contains(Event.current.mousePosition)) + { + Event.current.Use(); + if (_creatingConnection != null) _creatingConnection.SetTo(channel, this); + else _creatingConnection = Connection.Create(channel); + } + GUI.Button(channelRect, channel.ToString()); + + EditorGUI.BeginChangeCheck(); + if(DoFallback(channel)) + { + config.Fallback = EditorGUI.FloatField(fallbackRect, config.Fallback); + }else + { + config.BlendMode = (BlendMode)EditorGUI.EnumPopup(blendmodeRect, config.BlendMode); + config.Invert = (InvertMode)EditorGUI.EnumPopup(invertRect, config.Invert); + } + _changeCheckForPacking |= EditorGUI.EndChangeCheck(); + } + + bool DrawInput(TextureSource texture, int index, int textureHeight = 100) + { + int channelWidth = textureHeight / 5; + Rect rect = GUILayoutUtility.GetRect(textureHeight, textureHeight + 40); + Rect typeRect = new Rect(rect.x, rect.y, textureHeight, 20); + Rect textureRect = new Rect(rect.x, rect.y + 20, textureHeight, textureHeight); + Rect filterRect = new Rect(textureRect.x, textureRect.y + textureHeight, textureRect.width, 20); + + Rect background = new Rect(rect.x - 5, rect.y - 5, rect.width + channelWidth + 40, rect.height + 10); + GUI.DrawTexture(background, Texture2D.whiteTexture, ScaleMode.StretchToFill, true, 1, Styles.COLOR_BACKGROUND_1, 0, 10); + + // Draw textrue & filtermode. Change filtermode if texture is changed + EditorGUI.BeginChangeCheck(); + texture.InputType = (InputType)EditorGUI.EnumPopup(typeRect, texture.InputType); + bool didTextureChange = false; + switch(texture.InputType) + { + case InputType.Texture: + EditorGUI.BeginChangeCheck(); + texture.Texture = (Texture2D)EditorGUI.ObjectField(textureRect, texture.Texture, typeof(Texture2D), false); + didTextureChange = EditorGUI.EndChangeCheck(); + if(didTextureChange && texture.Texture != null) texture.FilterMode = texture.Texture.filterMode; + if(didTextureChange) DetermineOutputResolution(_textureSources, _imageAdjust); + texture.FilterMode = (FilterMode)EditorGUI.EnumPopup(filterRect, texture.FilterMode); + break; + case InputType.Gradient: + if(texture.GradientTexture == null) EditorGUI.DrawRect(textureRect, Color.black); + else EditorGUI.DrawPreviewTexture(textureRect, texture.GradientTexture); + if(Event.current.type == EventType.MouseDown && textureRect.Contains(Event.current.mousePosition)) + { + if(texture.Gradient == null) texture.Gradient = new Gradient(); + GradientEditor2.Open(texture.Gradient, (Gradient gradient, Texture2D tex) => { + texture.Gradient = gradient; + texture.GradientTexture = tex; + texture.Texture = tex; + // Needs to call these itself because it's in a callback not the OnGUI method + Pack(); + Repaint(); + }, texture.GradientDirection == GradientDirection.Vertical, false, _imageAdjust.Resolution, new Vector2Int(4096, 4096)); + + } + EditorGUI.BeginChangeCheck(); + texture.GradientDirection = (GradientDirection)EditorGUI.EnumPopup(filterRect, texture.GradientDirection); + if(EditorGUI.EndChangeCheck() && texture.Gradient != null) + { + texture.GradientTexture = Converter.GradientToTexture(texture.Gradient, _imageAdjust.Resolution.x, _imageAdjust.Resolution.y, texture.GradientDirection == GradientDirection.Vertical); + texture.Texture = texture.GradientTexture; + } + break; + case InputType.Color: + EditorGUI.BeginChangeCheck(); + texture.Color = EditorGUI.ColorField(textureRect, texture.Color); + if(EditorGUI.EndChangeCheck()) + { + texture.Texture = Converter.ColorToTexture(texture.Color, 16, 16); + } + break; + } + + _changeCheckForPacking |= EditorGUI.EndChangeCheck(); + + // draw 4 channl boxes on the right side + Rect rectR = new Rect(textureRect.x + textureRect.width, textureRect.y, channelWidth, channelWidth); + Rect rectG = new Rect(textureRect.x + textureRect.width, textureRect.y + channelWidth, channelWidth, channelWidth); + Rect rectB = new Rect(textureRect.x + textureRect.width, textureRect.y + channelWidth * 2, channelWidth, channelWidth); + Rect rectA = new Rect(textureRect.x + textureRect.width, textureRect.y + channelWidth * 3, channelWidth, channelWidth); + Rect rectMax = new Rect(textureRect.x + textureRect.width, textureRect.y + channelWidth * 4, channelWidth, channelWidth); + if (texture.Texture != null) + { + ChannelPreviewMaterial.SetTexture("_MainTex", texture.Texture); + ChannelPreviewMaterial.SetFloat("_Channel", 0); + EditorGUI.DrawPreviewTexture(rectR, texture.Texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + ChannelPreviewMaterial.SetFloat("_Channel", 1); + EditorGUI.DrawPreviewTexture(rectG, texture.Texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + ChannelPreviewMaterial.SetFloat("_Channel", 2); + EditorGUI.DrawPreviewTexture(rectB, texture.Texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + ChannelPreviewMaterial.SetFloat("_Channel", 3); + EditorGUI.DrawPreviewTexture(rectA, texture.Texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + ChannelPreviewMaterial.SetFloat("_Channel", 4); + EditorGUI.DrawPreviewTexture(rectMax, texture.Texture, ChannelPreviewMaterial, ScaleMode.ScaleToFit); + }else + { + EditorGUI.DrawRect(rectR, Color.black); + EditorGUI.DrawRect(rectG, Color.black); + EditorGUI.DrawRect(rectB, Color.black); + EditorGUI.DrawRect(rectA, Color.black); + EditorGUI.DrawRect(rectMax, Color.black); + } + // Draw circle button bext to each channel box + Rect circleR = new Rect(rectR.x + rectR.width + 5, rectR.y + rectR.height / 2 - 10 + 1, 40, 18); + Rect circleG = new Rect(rectG.x + rectG.width + 5, rectG.y + rectG.height / 2 - 10 + 1, 40, 18); + Rect circleB = new Rect(rectB.x + rectB.width + 5, rectB.y + rectB.height / 2 - 10 + 1, 40, 18); + Rect circleA = new Rect(rectA.x + rectA.width + 5, rectA.y + rectA.height / 2 - 10 + 1, 40, 18); + Rect circleMax = new Rect(rectMax.x + rectMax.width + 5, rectMax.y + rectMax.height / 2 - 10, 40, 18); + _positionsChannelIn[index * 5 + 0] = new Vector2(circleR.x + circleR.width, circleR.y + circleR.height / 2); + _positionsChannelIn[index * 5 + 1] = new Vector2(circleG.x + circleG.width, circleG.y + circleG.height / 2); + _positionsChannelIn[index * 5 + 2] = new Vector2(circleB.x + circleB.width, circleB.y + circleB.height / 2); + _positionsChannelIn[index * 5 + 3] = new Vector2(circleA.x + circleA.width, circleA.y + circleA.height / 2); + _positionsChannelIn[index * 5 + 4] = new Vector2(circleMax.x + circleMax.width, circleMax.y + circleMax.height / 2); + _rectsChannelIn[index * 5 + 0] = circleR; + _rectsChannelIn[index * 5 + 1] = circleG; + _rectsChannelIn[index * 5 + 2] = circleB; + _rectsChannelIn[index * 5 + 3] = circleA; + _rectsChannelIn[index * 5 + 4] = circleMax; + DrawInputChannel(circleR, index, TextureChannelIn.R); + DrawInputChannel(circleG, index, TextureChannelIn.G); + DrawInputChannel(circleB, index, TextureChannelIn.B); + DrawInputChannel(circleA, index, TextureChannelIn.A); + DrawInputChannel(circleMax, index, TextureChannelIn.Max); + + return didTextureChange; + } + + void DrawInputChannel(Rect position, int index, TextureChannelIn channel) + { + if(Event.current.type == EventType.MouseDown && position.Contains(Event.current.mousePosition)) + { + Event.current.Use(); + if (_creatingConnection == null) _creatingConnection = Connection.Create(index, channel); + else _creatingConnection.SetFrom(index, channel, this); + } + GUI.Button(position, channel.ToString()); + } + + + + bool DoFallback(TextureChannelOut channel) + { + return _connections.Any(c => c.ToChannel == channel && c.FromTextureIndex != -1 + && _textureSources[c.FromTextureIndex].Texture != null) == false; + } + + Color GetColor(TextureChannelIn c) + { + switch (c) + { + case TextureChannelIn.R: return Color.red; + case TextureChannelIn.G: return Color.green; + case TextureChannelIn.B: return Color.blue; + case TextureChannelIn.A: return Color.white; + case TextureChannelIn.Max: return Color.yellow; + default: return Color.black; + } + } + + Color GetColor(TextureChannelOut c) + { + switch (c) + { + case TextureChannelOut.R: return Color.red; + case TextureChannelOut.G: return Color.green; + case TextureChannelOut.B: return Color.blue; + case TextureChannelOut.A: return Color.white; + default: return Color.black; + } + } + + // Packing Logic + + void Pack() + { + SaveConfig(); + // Update all gradient textures (incase max size changed) + Vector2Int gradientSize = _imageAdjust.Resolution; + foreach (TextureSource source in _textureSources) + { + if (source.InputType == InputType.Gradient && source.GradientTexture != null && source.GradientTexture.width != gradientSize.x && source.GradientTexture.height != gradientSize.y) + { + source.GradientTexture = Converter.GradientToTexture(source.Gradient, gradientSize.x, gradientSize.y, source.GradientDirection == GradientDirection.Vertical); + source.Texture = source.GradientTexture; + } + } + + _outputTexture = Pack(_textureSources, _outputConfigs, _connections, _filterMode, _colorSpace, _imageAdjust, _kernel_x, _kernel_y, _kernel_loops, _kernel_strength, _kernel_twoPass, _kernel_grayScale, _kernel_channels); + if(OnChange != null) OnChange(_outputTexture, _textureSources, _outputConfigs, _connections.ToArray()); + } + + static void DetermineOutputResolution(TextureSource[] sources, ImageAdjust colorAdjust) + { + int width = 16; + int height = 16; + foreach (TextureSource source in sources) + { + source.FindMaxSize(ref width, ref height); + } + // round up to nearest power of 2 + width = Mathf.NextPowerOfTwo(width); + height = Mathf.NextPowerOfTwo(height); + // clamp to max size of 4096 + width = Mathf.Clamp(width, 16, 4096); + height = Mathf.Clamp(height, 16, 4096); + colorAdjust.Resolution = new Vector2Int(width, height); + } + + public static Texture2D Pack(TextureSource[] sources, OutputConfig[] outputConfigs, IEnumerable connections, FilterMode targetFilterMode, ColorSpace targetColorSpace, ImageAdjust colorAdjust = null, + float[] kernelX = null, float[] kernelY = null, int kernelLoops = 1, float kernelStrength = 1, bool kernelTwoPass = false, bool kernelGrayscale = false, bool[] kernelChannels = null) + { + if(colorAdjust == null) + { + colorAdjust = new ImageAdjust(); + DetermineOutputResolution(sources, colorAdjust); + } + int width = colorAdjust.Resolution.x; + int height = colorAdjust.Resolution.y; + + + RenderTexture target = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); + target.enableRandomWrite = true; + target.filterMode = targetFilterMode; + target.Create(); + + ComputeShader.SetTexture(0, "Result", target); + ComputeShader.SetFloat("Width", width); + ComputeShader.SetFloat("Height", height); + + ComputeShader.SetFloat("Rotation", colorAdjust.Rotation / 360f * 2f * Mathf.PI); + ComputeShader.SetVector("Scale", colorAdjust.Scale); + ComputeShader.SetVector("Offset", colorAdjust.Offset); + ComputeShader.SetFloat("Hue", colorAdjust.Hue); + ComputeShader.SetFloat("Saturation", colorAdjust.Saturation); + ComputeShader.SetFloat("Brightness", colorAdjust.Brightness); + + bool repeatTextures = Math.Abs(colorAdjust.Scale.x) > 1 || Math.Abs(colorAdjust.Scale.y) > 1; + + // Set Compute Shader Properties + int rCons = SetComputeValues(sources, connections, outputConfigs[0], TextureChannelOut.R, repeatTextures); + int gCons = SetComputeValues(sources, connections, outputConfigs[1], TextureChannelOut.G, repeatTextures); + int bCons = SetComputeValues(sources, connections, outputConfigs[2], TextureChannelOut.B, repeatTextures); + int aCons = SetComputeValues(sources, connections, outputConfigs[3], TextureChannelOut.A, repeatTextures); + + bool hasTransparency = aCons > 0 || outputConfigs[3].Fallback < 1; + + ComputeShader.Dispatch(0, width / 8 + 1, height / 8 + 1, 1); + + if(kernelX != null && kernelY != null) + { + // Settings Vector4s instead of floats because the SetFloats function is broken + float[] kernelNone = GetKernelPreset(KernelPreset.None, false); + ComputeShader.SetVectorArray("Kernel_X", kernelX.Select((f,i) => new Vector4(Mathf.Lerp(kernelNone[i], f, kernelStrength), 0, 0, 0)).ToArray()); + ComputeShader.SetVectorArray("Kernel_Y", kernelY.Select((f,i) => new Vector4(Mathf.Lerp(kernelNone[i], f, kernelStrength), 0, 0, 0)).ToArray()); + ComputeShader.SetBool("Kernel_Grayscale", kernelGrayscale); + ComputeShader.SetBool("Kernel_TwoPass", kernelTwoPass); + ComputeShader.SetVector("Kernel_Channels", new Vector4(kernelChannels[0] ? 1 : 0, kernelChannels[1] ? 1 : 0, kernelChannels[2] ? 1 : 0, kernelChannels[3] ? 1 : 0)); + + // define the opposite way, because each loop flips it + RenderTexture filterTarget = target; + + RenderTexture filterInput = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); + filterInput.enableRandomWrite = true; + filterInput.filterMode = targetFilterMode; + filterInput.Create(); + + for(int i = 0; i < kernelLoops; i++) + { + RenderTexture temp = filterInput; + filterInput = filterTarget; + filterTarget = temp; + + ComputeShader.SetTexture(1, "Kernel_Input", filterInput); + ComputeShader.SetTexture(1, "Result", filterTarget); + ComputeShader.Dispatch(1, width / 8 + 1, height / 8 + 1, 1); + } + + target = filterTarget; + } + + Texture2D atlas = new Texture2D(width, height, TextureFormat.RGBA32, true, targetColorSpace == ColorSpace.Linear); + RenderTexture.active = target; + atlas.ReadPixels(new Rect(0, 0, width, height), 0, 0); + atlas.filterMode = targetFilterMode; + atlas.wrapMode = TextureWrapMode.Clamp; + atlas.alphaIsTransparency = hasTransparency; + atlas.Apply(); + + return atlas; + } + + static int SetComputeValues(TextureSource[] sources, IEnumerable allConnections, OutputConfig config, TextureChannelOut outChannel, bool repeatMode) + { + // Find all incoming connections + Connection[] chnlConnections = allConnections.Where(c => c.ToChannel == outChannel && sources[c.FromTextureIndex].Texture != null).ToArray(); + + // Set textures + for(int i = 0; i < chnlConnections.Length; i++) + { + TextureSource s = sources[chnlConnections[i].FromTextureIndex]; + // set the sampler states correctly + s.UncompressedTexture.wrapMode = repeatMode ? TextureWrapMode.Repeat : TextureWrapMode.Clamp; + s.UncompressedTexture.filterMode = s.InputType == InputType.Gradient ? FilterMode.Bilinear : s.FilterMode; + ComputeShader.SetTexture(0, outChannel.ToString() + "_Input_" + i, s.UncompressedTexture); + ComputeShader.SetInt(outChannel.ToString() + "_Channel_" + i, (int)chnlConnections[i].FromChannel); + } + for(int i = chnlConnections.Length; i < 4; i++) + { + ComputeShader.SetTexture(0, outChannel.ToString() + "_Input_" + i, Texture2D.whiteTexture); + } + + // Set other data + ComputeShader.SetInt(outChannel.ToString() + "_Count", chnlConnections.Length); + ComputeShader.SetInt(outChannel.ToString() + "_BlendMode", (int)config.BlendMode); + ComputeShader.SetBool(outChannel.ToString() + "_Invert", config.Invert == InvertMode.Invert); + ComputeShader.SetFloat(outChannel.ToString() + "_Fallback", config.Fallback); + + return chnlConnections.Length; + } + +#region Kernel + + + static float[] GetKernelPreset(KernelPreset preset, bool isXKernel) + { + // return a 5x5 kernel. always 25 values + switch(preset) + { + case KernelPreset.Sharpen: return new float[]{ 0,0,0,0,0, 0,0,-0.5f, 0,0, 0,-0.5f, 3, -0.5f, 0, 0,0, -0.5f, 0,0, 0,0,0,0,0 }; + case KernelPreset.EdgeDetection: + if (isXKernel) return new float[]{ 0,0,0,0,0, 0,-1,0,1,0, 0,-2,0,2,0, 0,-1,0,1,0, 0,0,0,0,0 }; + else return new float[]{ 0,0,0,0,0, 0,-1,-2,-1,0, 0,0,0,0,0, 0,1,2,1,0, 0,0,0,0,0 }; + case KernelPreset.GaussianBlur3x3: return new float[]{ 0,0,0,0,0, 0,0.0625f,0.125f,0.0625f,0, 0,0.125f,0.25f,0.125f,0, 0,0.0625f,0.125f,0.0625f,0, 0,0,0,0,0 }; + case KernelPreset.GaussianBlur5x5: return new float[]{ 0.003f, 0.0133f, 0.0219f, 0.0133f, 0.003f, 0.0133f, 0.0596f, 0.0983f, 0.0596f, 0.0133f, 0.0219f, 0.0983f, 0.1621f, 0.0983f, 0.0219f, 0.0133f, 0.0596f, 0.0983f, 0.0596f, 0.0133f, 0.003f, 0.0133f, 0.0219f, 0.0133f, 0.003f }; + } + return new float[]{ 0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0 }; + } + + void KernelPresetSetValues(KernelPreset preset) + { + _kernel_loops = 1; + _kernel_strength = 1; + _kernel_twoPass = false; + _kernel_grayScale = false; + _kernel_channels = new bool[]{ true, true, true, true }; + if(preset == KernelPreset.GaussianBlur3x3 || preset == KernelPreset.GaussianBlur5x5) _kernel_loops = 10; + if(preset == KernelPreset.EdgeDetection) _kernel_twoPass = true; + if(preset == KernelPreset.EdgeDetection) _kernel_channels = new bool[]{ true, true, true, false }; + } + +# endregion + +# region Channel Unpacker + + + + void ExportChannel(RenderTexture renderTex, Vector4 lerpR, Vector4 lerpG, Vector4 lerpB, Vector4 lerpA , string namePostfix) + { + ComputeShader.SetVector("Channels_Strength_R", lerpR); + ComputeShader.SetVector("Channels_Strength_G", lerpG); + ComputeShader.SetVector("Channels_Strength_B", lerpB); + ComputeShader.SetVector("Channels_Strength_A", lerpA); + ComputeShader.Dispatch(2, _outputTexture.width / 8, _outputTexture.height / 8, 1); + + Texture2D tex = new Texture2D(renderTex.width, renderTex.height, TextureFormat.RGBA32, true, _colorSpace == ColorSpace.Linear); + RenderTexture.active = renderTex; + tex.ReadPixels(new Rect(0, 0, renderTex.width, renderTex.height), 0, 0); + tex.filterMode = renderTex.filterMode; + tex.wrapMode = TextureWrapMode.Clamp; + tex.alphaIsTransparency = false; + tex.Apply(); + + Save(tex, _saveFolder, _saveName + namePostfix); + } + + void ExportChannels(bool exportAsBlackAndWhite) + { + Pack(); + DeterminePathAndFileNameIfEmpty(); + + RenderTexture target = new RenderTexture(_outputTexture.width, _outputTexture.height, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); + target.enableRandomWrite = true; + target.filterMode = _outputTexture.filterMode; + target.Create(); + ComputeShader.SetTexture(2, "Unpacker_Input", _outputTexture); + ComputeShader.SetTexture(2, "Result", target); + + Vector4 r = new Vector4(1, 0, 0, 0); + Vector4 g = new Vector4(0, 1, 0, 0); + Vector4 b = new Vector4(0, 0, 1, 0); + Vector4 a = new Vector4(0, 0, 0, 1); + Vector4 none = new Vector4(0, 0, 0, 0); + if(exportAsBlackAndWhite) + { + if(_channel_export[0]) + ExportChannel(target, r, r, r, none, "_R"); + if(_channel_export[1]) + ExportChannel(target, g, g, g, none, "_G"); + if(_channel_export[2]) + ExportChannel(target, b, b, b, none, "_B"); + if(_channel_export[3]) + ExportChannel(target, a, a, a, none, "_A"); + } + else + { + if(_channel_export[0]) + ExportChannel(target, r, none, none, none, "_R"); + if(_channel_export[1]) + ExportChannel(target, none, g, none, none, "_G"); + if(_channel_export[2]) + ExportChannel(target, none, none, b, none, "_B"); + if(_channel_export[3]) + ExportChannel(target, none, none, none, a, "_A"); + } + } + +# endregion + +#region Save + void Save() + { + if (_outputTexture == null) return; + DeterminePathAndFileNameIfEmpty(); + string path = _saveFolder + "/" + _saveName + GetTypeEnding(_saveType); + byte[] bytes = null; + if(File.Exists(path)) + { + // open dialog + if (!EditorUtility.DisplayDialog("File already exists", "Do you want to overwrite the file?", "Yes", "No")) + { + return; + } + } + switch (_saveType) + { + case SaveType.PNG: bytes = _outputTexture.EncodeToPNG(); break; + case SaveType.JPG: bytes = _outputTexture.EncodeToJPG((int)_saveQuality); break; + } + System.IO.File.WriteAllBytes(path, bytes); + AssetDatabase.Refresh(); + + TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; + importer.streamingMipmaps = true; + importer.sRGBTexture = _colorSpace == ColorSpace.Gamma; + importer.filterMode = _filterMode; + importer.alphaIsTransparency = _outputTexture.alphaIsTransparency; + importer.textureCompression = TextureImporterCompression.Compressed; + TextureImporterFormat overwriteFormat = importer.DoesSourceTextureHaveAlpha() ? + Config.Singleton.texturePackerCompressionWithAlphaOverwrite : Config.Singleton.texturePackerCompressionNoAlphaOverwrite; + if(overwriteFormat != TextureImporterFormat.Automatic) + { + importer.SetPlatformTextureSettings(new TextureImporterPlatformSettings() + { + name = "PC", + overridden = true, + maxTextureSize = 2048, + format = overwriteFormat + }); + }else + { + importer.SetPlatformTextureSettings(new TextureImporterPlatformSettings() + { + name = "PC", + overridden = false, + }); + } + importer.SaveAndReimport(); + + Texture2D tex = AssetDatabase.LoadAssetAtPath(path); + if(OnSave != null) OnSave(tex); + } + + void Save(Texture2D texture, string folder, string name) + { + string path = folder + "/" + name + GetTypeEnding(_saveType); + byte[] bytes = null; + if(File.Exists(path)) + { + // open dialog + if (!EditorUtility.DisplayDialog("File already exists", "Do you want to overwrite the file?", "Yes", "No")) + { + return; + } + } + switch (_saveType) + { + case SaveType.PNG: bytes = texture.EncodeToPNG(); break; + case SaveType.JPG: bytes = texture.EncodeToJPG((int)_saveQuality); break; + } + System.IO.File.WriteAllBytes(path, bytes); + AssetDatabase.Refresh(); + + TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter; + importer.streamingMipmaps = true; + importer.sRGBTexture = _colorSpace == ColorSpace.Gamma; + importer.filterMode = _filterMode; + importer.alphaIsTransparency = texture.alphaIsTransparency; + importer.textureCompression = TextureImporterCompression.Compressed; + TextureImporterFormat overwriteFormat = importer.DoesSourceTextureHaveAlpha() ? + Config.Singleton.texturePackerCompressionWithAlphaOverwrite : Config.Singleton.texturePackerCompressionNoAlphaOverwrite; + if(overwriteFormat != TextureImporterFormat.Automatic) + { + importer.SetPlatformTextureSettings(new TextureImporterPlatformSettings() + { + name = "PC", + overridden = true, + maxTextureSize = 2048, + format = overwriteFormat + }); + }else + { + importer.SetPlatformTextureSettings(new TextureImporterPlatformSettings() + { + name = "PC", + overridden = false, + }); + } + importer.SaveAndReimport(); + } + + void CreateConfig() + { + string path = EditorUtility.SaveFilePanelInProject("Save Texture Packer Config", "TexturePackerConfig", "asset", "Save Texture Packer Config", _saveFolder); + if (path.Length != 0) + { + _config = ScriptableObject.CreateInstance(); + AssetDatabase.CreateAsset(_config, path); + SaveConfig(); + } + } + + void SaveConfig() + { + if (_config == null) return; + _config.Sources = _textureSources; + _config.Connections = _connections.ToArray(); + _config.OutputConfigs = _outputConfigs; + _config.SaveFolder = _saveFolder; + _config.SaveName = _saveName; + _config.SaveType = _saveType; + _config.SaveQuality = _saveQuality; + _config.ColorSpace = _colorSpace; + _config.FilterMode = _filterMode; + _config.ImageAdjust = _imageAdjust; + + _config.KernelPreset = _kernelPreset; + _config.KernelX = _kernel_x; + _config.KernelY = _kernel_y; + _config.KernelLoops = _kernel_loops; + _config.KernelStrength = _kernel_strength; + _config.KernelChannels = _kernel_channels; + EditorUtility.SetDirty(_config); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + void LoadConfig() + { + if(_config == null) return; + _textureSources = _config.Sources; + _connections = _config.Connections.ToList(); + _outputConfigs = _config.OutputConfigs; + _saveFolder = _config.SaveFolder; + _saveName = _config.SaveName; + _saveType = _config.SaveType; + _saveQuality = _config.SaveQuality; + _colorSpace = _config.ColorSpace; + _filterMode = _config.FilterMode; + _imageAdjust = _config.ImageAdjust; + + _kernelPreset = _config.KernelPreset; + _kernel_x = _config.KernelX; + _kernel_y = _config.KernelY; + _kernel_loops = _config.KernelLoops; + _kernel_strength = _config.KernelStrength; + _kernel_channels = _config.KernelChannels; + // Expand sources to 4 + _textureSources = _textureSources.Concat(Enumerable.Repeat(new TextureSource(), 4 - _textureSources.Length)).ToArray(); + // Expand output configs to 4 + _outputConfigs = _outputConfigs.Concat(Enumerable.Repeat(new OutputConfig(), 4 - _outputConfigs.Length)).ToArray(); + + } + +#endregion + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/TexturePackerConfig.cs b/Scripts/ThryEditor/Editor/TexturePackerConfig.cs new file mode 100644 index 0000000..c9bff9c --- /dev/null +++ b/Scripts/ThryEditor/Editor/TexturePackerConfig.cs @@ -0,0 +1,28 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using static Thry.TexturePacker; + +namespace Thry +{ + public class TexturePackerConfig : ScriptableObject + { + public TextureSource[] Sources; + public OutputConfig[] OutputConfigs; + public Connection[] Connections; + public ColorSpace ColorSpace; + public FilterMode FilterMode; + public SaveType SaveType; + public float SaveQuality; + public ImageAdjust ImageAdjust; + public string SaveFolder; + public string SaveName; + + public KernelPreset KernelPreset; + public float[] KernelX; + public float[] KernelY; + public float KernelStrength; + public int KernelLoops; + public bool[] KernelChannels; + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/ThryAssemblyDefinition.asmdef b/Scripts/ThryEditor/Editor/ThryAssemblyDefinition.asmdef new file mode 100644 index 0000000..4193e7c --- /dev/null +++ b/Scripts/ThryEditor/Editor/ThryAssemblyDefinition.asmdef @@ -0,0 +1,15 @@ +{ + "name": "ThryAssemblyDefinitionMeli", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/ThryEditor.cs b/Scripts/ThryEditor/Editor/ThryEditor.cs new file mode 100644 index 0000000..1c91c78 --- /dev/null +++ b/Scripts/ThryEditor/Editor/ThryEditor.cs @@ -0,0 +1,944 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; +using Thry; +using System; +using System.Reflection; +using System.Linq; +using Thry.ThryEditor; + +namespace Thry +{ + public class ShaderEditor : ShaderGUI + { + public const string EXTRA_OPTIONS_PREFIX = "--"; + public const float MATERIAL_NOT_RESET = 69.12f; + + public const string PROPERTY_NAME_MASTER_LABEL = "shader_master_label"; + public const string PROPERTY_NAME_LABEL_FILE = "shader_properties_label_file"; + public const string PROPERTY_NAME_LOCALE = "shader_locale"; + public const string PROPERTY_NAME_ON_SWAP_TO_ACTIONS = "shader_on_swap_to"; + public const string PROPERTY_NAME_SHADER_VERSION = "shader_version"; + public const string PROPERTY_NAME_EDITOR_DETECT = "shader_is_using_thry_editor"; + + //Static + private static string s_edtiorDirectoryPath; + + public static InputEvent Input = new InputEvent(); + public static ShaderEditor Active; + + // Stores the different shader properties + public ShaderGroup MainGroup; + private RenderQueueProperty _renderQueueProperty; + private VRCFallbackProperty _vRCFallbackProperty; + + // UI Instance Variables + + public bool DoShowSearchBar; + private string _enteredSearchTerm = ""; + private string _appliedSearchTerm = ""; + + // shader specified values + private ShaderHeaderProperty _shaderHeader = null; + private List _footers; + + // sates + private bool _isFirstOnGUICall = true; + private bool _wasUsed = false; + private bool _doReloadNextDraw = false; + private bool _didSwapToShader = false; + + //EditorData + public MaterialEditor Editor; + public MaterialProperty[] Properties; + public Material[] Materials; + public Shader Shader; + public ShaderPart CurrentProperty; + public Dictionary PropertyDictionary; + public List ShaderParts; + public List TextureArrayProperties; + public bool IsFirstCall; + public bool DoUseShaderOptimizer; + public bool IsLockedMaterial; + public bool IsInAnimationMode; + public Renderer ActiveRenderer; + public string RenamedPropertySuffix; + public bool HasCustomRenameSuffix; + public Localization Locale; + public ShaderTranslator SuggestedTranslationDefinition; + private string _duplicatePropertyNamesString = null; + + //Shader Versioning + private Version _shaderVersionLocal; + private Version _shaderVersionRemote; + private bool _hasShaderUpdateUrl = false; + private bool _isShaderUpToDate = true; + private string _shaderUpdateUrl = null; + + //other + ShaderProperty ShaderOptimizerProperty { get; set; } + + private DefineableAction[] _onSwapToActions = null; + + public bool IsDrawing { get; private set; } = false; + public bool IsPresetEditor { get; private set; } = false; + + public bool HasMixedCustomPropertySuffix + { + get + { + if (Materials.Length == 1) return false; + string suffix = ShaderOptimizer.GetRenamedPropertySuffix(Materials[0]); + for (int i = 1; i < Materials.Length; i++) + { + if (suffix != ShaderOptimizer.GetRenamedPropertySuffix(Materials[i])) return true; + } + return false; + } + } + + //-------------Init functions-------------------- + + private Dictionary LoadDisplayNamesFromFile() + { + //load display names from file if it exists + MaterialProperty label_file_property = GetMaterialProperty(PROPERTY_NAME_LABEL_FILE); + Dictionary labels = new Dictionary(); + if (label_file_property != null) + { + string[] guids = AssetDatabase.FindAssets(label_file_property.displayName); + if (guids.Length == 0) + { + Debug.LogWarning("Label File could not be found"); + return labels; + } + string path = AssetDatabase.GUIDToAssetPath(guids[0]); + string[] data = Regex.Split(Thry.FileHelper.ReadFileIntoString(path), @"\r?\n"); + foreach (string d in data) + { + string[] set = Regex.Split(d, ":="); + if (set.Length > 1) labels[set[0]] = set[1]; + } + } + return labels; + } + + public static string SplitOptionsFromDisplayName(ref string displayName) + { + if (displayName.Contains(EXTRA_OPTIONS_PREFIX)) + { + string[] parts = displayName.Split(new string[] { EXTRA_OPTIONS_PREFIX }, 2, System.StringSplitOptions.None); + displayName = parts[0]; + return parts[1]; + } + return null; + } + + private enum ThryPropertyType + { + none, property, master_label, footer, legacy_header, legacy_header_end, legacy_header_start, group_start, group_end, instancing, dsgi, lightmap_flags, locale, on_swap_to, space, shader_version + } + + private ThryPropertyType GetPropertyType(MaterialProperty p) + { + string name = p.name; + MaterialProperty.PropFlags flags = p.flags; + + if (flags == MaterialProperty.PropFlags.HideInInspector) + { + if (name == PROPERTY_NAME_MASTER_LABEL) + return ThryPropertyType.master_label; + if (name == PROPERTY_NAME_ON_SWAP_TO_ACTIONS) + return ThryPropertyType.on_swap_to; + if (name == PROPERTY_NAME_SHADER_VERSION) + return ThryPropertyType.shader_version; + + if (name.StartsWith("m_start", StringComparison.Ordinal)) + return ThryPropertyType.legacy_header_start; + if (name.StartsWith("m_end", StringComparison.Ordinal)) + return ThryPropertyType.legacy_header_end; + if (name.StartsWith("m_", StringComparison.Ordinal)) + return ThryPropertyType.legacy_header; + if (name.StartsWith("g_start", StringComparison.Ordinal)) + return ThryPropertyType.group_start; + if (name.StartsWith("g_end", StringComparison.Ordinal)) + return ThryPropertyType.group_end; + if (name.StartsWith("footer_", StringComparison.Ordinal)) + return ThryPropertyType.footer; + if (name == "Instancing") + return ThryPropertyType.instancing; + if (name == "DSGI") + return ThryPropertyType.dsgi; + if (name == "LightmapFlags") + return ThryPropertyType.lightmap_flags; + if (name == PROPERTY_NAME_LOCALE) + return ThryPropertyType.locale; + if (name.StartsWith("space")) + return ThryPropertyType.space; + } + else if(flags.HasFlag(MaterialProperty.PropFlags.HideInInspector) == false) + { + return ThryPropertyType.property; + } + return ThryPropertyType.none; + } + + private void LoadLocales() + { + MaterialProperty locales_property = GetMaterialProperty(PROPERTY_NAME_LOCALE); + Locale = null; + if (locales_property != null) + { + string guid = locales_property.displayName; + Locale = Localization.Load(guid); + }else + { + Locale = Localization.Create(); + } + } + + public void FakePartialInitilizationForLocaleGathering(Shader s) + { + Material material = new Material(s); + Materials = new Material[] { material }; + Editor = MaterialEditor.CreateEditor(new UnityEngine.Object[] { material }) as MaterialEditor; + Properties = MaterialEditor.GetMaterialProperties(Materials); + RenamedPropertySuffix = ShaderOptimizer.GetRenamedPropertySuffix(Materials[0]); + HasCustomRenameSuffix = ShaderOptimizer.HasCustomRenameSuffix(Materials[0]); + ShaderEditor.Active = this; + CollectAllProperties(); + UnityEngine.Object.DestroyImmediate(Editor); + UnityEngine.Object.DestroyImmediate(material); + } + + //finds all properties and headers and stores them in correct order + private void CollectAllProperties() + { + //load display names from file if it exists + MaterialProperty[] props = Properties; + Dictionary labels = LoadDisplayNamesFromFile(); + LoadLocales(); + + PropertyDictionary = new Dictionary(); + ShaderParts = new List(); + MainGroup = new ShaderGroup(this); //init top object that all Shader Objects are childs of + Stack headerStack = new Stack(); //header stack. used to keep track if editorData header to parent new objects to + headerStack.Push(MainGroup); //add top object as top object to stack + headerStack.Push(MainGroup); //add top object a second time, because it get's popped with first actual header item + _footers = new List(); //init footer list + int headerCount = 0; + DrawingData.IsCollectingProperties = true; + + List duplicateProperties = new List(); // for debugging + + for (int i = 0; i < props.Length; i++) + { + string displayName = props[i].displayName; + + //Load from label file + if (labels.ContainsKey(props[i].name)) displayName = labels[props[i].name]; + + //extract json data from display name + string optionsRaw = SplitOptionsFromDisplayName(ref displayName); + + displayName = Locale.Get(props[i], displayName); + + int offset = headerCount; + + DrawingData.ResetLastDrawerData(); + + ThryPropertyType type = GetPropertyType(props[i]); + switch (type) + { + case ThryPropertyType.legacy_header: + headerStack.Pop(); + break; + case ThryPropertyType.legacy_header_start: + offset = ++headerCount; + break; + case ThryPropertyType.legacy_header_end: + headerStack.Pop(); + headerCount--; + break; + case ThryPropertyType.on_swap_to: + _onSwapToActions = PropertyOptions.Deserialize(optionsRaw).actions; + break; + } + ShaderProperty NewProperty = null; + ShaderPart newPart = null; + switch (type) + { + case ThryPropertyType.master_label: + _shaderHeader = new ShaderHeaderProperty(this, props[i], displayName, 0, optionsRaw, false); + break; + case ThryPropertyType.footer: + _footers.Add(new FooterButton(Parser.Deserialize(displayName))); + break; + case ThryPropertyType.legacy_header: + case ThryPropertyType.legacy_header_start: + ShaderHeader newHeader = new ShaderHeader(this, props[i], Editor, displayName, offset, optionsRaw); + headerStack.Peek().addPart(newHeader); + headerStack.Push(newHeader); + newPart = newHeader; + break; + case ThryPropertyType.group_start: + ShaderGroup new_group = new ShaderGroup(this, props[i], Editor, displayName, offset, optionsRaw); + headerStack.Peek().addPart(new_group); + headerStack.Push(new_group); + newPart = new_group; + break; + case ThryPropertyType.group_end: + headerStack.Pop(); + break; + case ThryPropertyType.none: + case ThryPropertyType.property: + if (props[i].type == MaterialProperty.PropType.Texture) + NewProperty = new TextureProperty(this, props[i], displayName, offset, optionsRaw, props[i].flags.HasFlag(MaterialProperty.PropFlags.NoScaleOffset) == false, false, i); + else + NewProperty = new ShaderProperty(this, props[i], displayName, offset, optionsRaw, false, i); + break; + case ThryPropertyType.lightmap_flags: + NewProperty = new GIProperty(this, props[i], displayName, offset, optionsRaw, false); + break; + case ThryPropertyType.dsgi: + NewProperty = new DSGIProperty(this, props[i], displayName, offset, optionsRaw, false); + break; + case ThryPropertyType.instancing: + NewProperty = new InstancingProperty(this, props[i], displayName, offset, optionsRaw, false); + break; + case ThryPropertyType.locale: + NewProperty = new LocaleProperty(this, props[i], displayName, offset, optionsRaw, false); + break; + case ThryPropertyType.shader_version: + PropertyOptions options = PropertyOptions.Deserialize(optionsRaw); + _shaderVersionRemote = new Version(WebHelper.GetCachedString(options.remote_version_url)); + _shaderVersionLocal = new Version(displayName); + _isShaderUpToDate = _shaderVersionLocal >= _shaderVersionRemote; + _shaderUpdateUrl = options.generic_string; + _hasShaderUpdateUrl = _shaderUpdateUrl != null; + break; + } + if (NewProperty != null) + { + newPart = NewProperty; + if (PropertyDictionary.ContainsKey(props[i].name)) + { + duplicateProperties.Add(props[i].name); + continue; + } + PropertyDictionary.Add(props[i].name, NewProperty); + if (type != ThryPropertyType.none) + headerStack.Peek().addPart(NewProperty); + } + //if new header is at end property + if (headerStack.Peek() is ShaderHeader && (headerStack.Peek() as ShaderHeader).GetEndProperty() == props[i].name) + { + headerStack.Pop(); + headerCount--; + } + if (newPart != null) + { + ShaderParts.Add(newPart); + } + } + + if(duplicateProperties.Count > 0 && Config.Singleton.enableDeveloperMode) + _duplicatePropertyNamesString = string.Join(", ", duplicateProperties.ToArray()); + + DrawingData.IsCollectingProperties = false; + } + + //-------------Draw Functions---------------- + + public void InitlizeThryUI() + { + Config config = Config.Singleton; + Active = this; + Helper.RegisterEditorUse(); + + //get material targets + Materials = Editor.targets.Select(o => o as Material).ToArray(); + + Shader = Materials[0].shader; + + RenamedPropertySuffix = ShaderOptimizer.GetRenamedPropertySuffix(Materials[0]); + HasCustomRenameSuffix = ShaderOptimizer.HasCustomRenameSuffix(Materials[0]); + + IsPresetEditor = Materials.Length == 1 && Presets.ArePreset(Materials); + + //collect shader properties + CollectAllProperties(); + + if (ShaderOptimizer.IsShaderUsingThryOptimizer(Shader)) + { + ShaderOptimizerProperty = PropertyDictionary[ShaderOptimizer.GetOptimizerPropertyName(Shader)]; + if(ShaderOptimizerProperty != null) ShaderOptimizerProperty.ExemptFromLockedDisabling = true; + } + + _renderQueueProperty = new RenderQueueProperty(this); + _vRCFallbackProperty = new VRCFallbackProperty(this); + ShaderParts.Add(_renderQueueProperty); + ShaderParts.Add(_vRCFallbackProperty); + + AddResetProperty(); + + if(Config.Singleton.forceAsyncCompilationPreview) + { + ShaderUtil.allowAsyncCompilation = true; + } + + _isFirstOnGUICall = false; + } + + private Dictionary materialPropertyDictionary; + public MaterialProperty GetMaterialProperty(string name) + { + if (materialPropertyDictionary == null) + { + materialPropertyDictionary = new Dictionary(); + foreach (MaterialProperty p in Properties) + if (materialPropertyDictionary.ContainsKey(p.name) == false) materialPropertyDictionary.Add(p.name, p); + } + if (materialPropertyDictionary.ContainsKey(name)) + return materialPropertyDictionary[name]; + return null; + } + + private void AddResetProperty() + { + if (Materials[0].HasProperty(PROPERTY_NAME_EDITOR_DETECT) == false) + { + string path = AssetDatabase.GetAssetPath(Materials[0].shader); + UnityHelper.AddShaderPropertyToSourceCode(path, "[HideInInspector] shader_is_using_thry_editor(\"\", Float)", "0"); + } + Materials[0].SetFloat(PROPERTY_NAME_EDITOR_DETECT, 69); + } + + + + public override void OnClosed(Material material) + { + base.OnClosed(material); + _isFirstOnGUICall = true; + } + + public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader) + { + //Unity sets the render queue to the shader defult when changing shader + //This seems to be some deeper process that cant be disabled so i just set it again after the swap + //Even material.shader = newShader resets the queue. (this is actually the only thing the base function does) + int previousQueue = material.renderQueue; + base.AssignNewShaderToMaterial(material, oldShader, newShader); + material.renderQueue = previousQueue; + SuggestedTranslationDefinition = ShaderTranslator.CheckForExistingTranslationFile(oldShader, newShader); + FixKeywords(new Material[] { material }); + _doReloadNextDraw = true; + _didSwapToShader = true; + } + + void InitEditorData(MaterialEditor materialEditor) + { + Editor = materialEditor; + TextureArrayProperties = new List(); + IsFirstCall = true; + } + + public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] props) + { + IsDrawing = true; + //Init + bool reloadUI = _isFirstOnGUICall || (_doReloadNextDraw && Event.current.type == EventType.Layout) || (materialEditor.target as Material).shader != Shader; + if (reloadUI) + { + InitEditorData(materialEditor); + Properties = props; + InitlizeThryUI(); + } + + //Update Data + Properties = props; + Shader = Materials[0].shader; + Input.Update(IsLockedMaterial); + ActiveRenderer = Selection.activeTransform?.GetComponent(); + IsInAnimationMode = AnimationMode.InAnimationMode(); + + Active = this; + + GUIManualReloadButton(); + GUIDevloperMode(); + GUIShaderVersioning(); + + // Add a logo to the top of the inspector + Texture2D logo = Resources.Load("primosmall"); + if (logo != null) + { + GUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + Rect logoRect = GUILayoutUtility.GetRect(logo.width, logo.height); + GUI.DrawTexture(logoRect, logo, ScaleMode.ScaleToFit); + GUILayout.FlexibleSpace(); + GUILayout.EndHorizontal(); + GUILayout.Space(10); // Add some space after the logo + } + + GUITopBar(); + GUISearchBar(); + Presets.PresetEditorGUI(this); + ShaderTranslator.SuggestedTranslationButtonGUI(this); + + //PROPERTIES + foreach (ShaderPart part in MainGroup.parts) + { + part.Draw(); + } + + //Render Queue selection + if(VRCInterface.IsVRCSDKInstalled()) _vRCFallbackProperty.Draw(); + if (Config.Singleton.showRenderQueue) _renderQueueProperty.Draw(); + + BetterTooltips.DrawActive(); + + GUIFooters(); + + HandleEvents(); + + IsDrawing = false; + } + + private void GUIManualReloadButton() + { + if (Config.Singleton.showManualReloadButton) + { + if(GUILayout.Button("Manual Reload")) + { + this.Reload(); + } + } + } + + private void GUIDevloperMode() + { + if (Config.Singleton.enableDeveloperMode) + { + // Show duplicate property names + if(_duplicatePropertyNamesString != null) + { + EditorGUILayout.HelpBox("Duplicate Property Names:" + _duplicatePropertyNamesString, MessageType.Warning); + } + } + } + + private void GUIShaderVersioning() + { + if (!_isShaderUpToDate) + { + Rect r = EditorGUILayout.GetControlRect(false, _hasShaderUpdateUrl ? 30 : 15); + EditorGUI.LabelField(r, $"[New Shader Version available] {_shaderVersionLocal} -> {_shaderVersionRemote}" + (_hasShaderUpdateUrl ? "\n Click here to download." : ""), Styles.redStyle); + if(Input.HadMouseDownRepaint && _hasShaderUpdateUrl && GUILayoutUtility.GetLastRect().Contains(Input.mouse_position)) Application.OpenURL(_shaderUpdateUrl); + } + } + + private void GUITopBar() + { + //if header is texture, draw it first so other ui elements can be positions below + if (_shaderHeader != null && _shaderHeader.Options.texture != null) _shaderHeader.Draw(); + + bool drawAboveToolbar = EditorGUIUtility.wideMode == false; + if(drawAboveToolbar) _shaderHeader.Draw(new CRect(EditorGUILayout.GetControlRect())); + + Rect mainHeaderRect = EditorGUILayout.BeginHorizontal(); + //draw editor settings button + if (GuiHelper.ButtonWithCursor(Styles.icon_style_settings, "Settings", 25, 25)) + { + EditorWindow.GetWindow(false, "Thry Settings", true); + } + if (GuiHelper.ButtonWithCursor(Styles.icon_style_search, "Search", 25, 25)) + { + DoShowSearchBar = !DoShowSearchBar; + if(!DoShowSearchBar) ClearSearch(); + } + if (GuiHelper.ButtonWithCursor(Styles.icon_style_presets, "Presets" , 25, 25)) + { + Presets.OpenPresetsMenu(GUILayoutUtility.GetLastRect(), this); + } + + //draw master label text after ui elements, so it can be positioned between + if (_shaderHeader != null && !drawAboveToolbar) _shaderHeader.Draw(new CRect(mainHeaderRect)); + + GUILayout.FlexibleSpace(); + Rect popupPosition; + if (GuiHelper.ButtonWithCursor(Styles.icon_style_tools, "Tools", 25, 25, out popupPosition)) + { + PopupTools(popupPosition); + } + ShaderTranslator.TranslationSelectionGUI(this); + if (GuiHelper.ButtonWithCursor(Styles.icon_style_thryIcon, "Thryrallo", 25, 25)) + Application.OpenURL("https://www.twitter.com/thryrallo"); + EditorGUILayout.EndHorizontal(); + } + + private void GUISearchBar() + { + if (DoShowSearchBar) + { + EditorGUI.BeginChangeCheck(); + _enteredSearchTerm = EditorGUILayout.TextField(_enteredSearchTerm); + if (EditorGUI.EndChangeCheck()) + { + _appliedSearchTerm = _enteredSearchTerm.ToLower(); + UpdateSearch(MainGroup); + } + } + } + + private void GUIFooters() + { + try + { + FooterButton.DrawList(_footers); + } + catch (Exception ex) + { + Debug.LogWarning(ex); + } + if (GUILayout.Button("✧UI Made by Meliodas✧", Styles.made_by_style)) + Application.OpenURL("https://twitter.com/Meliodas7DL"); + EditorGUIUtility.AddCursorRect(GUILayoutUtility.GetLastRect(), MouseCursor.Link); + } + + private void PopupTools(Rect position) + { + var menu = new GenericMenu(); + + menu.AddItem(new GUIContent("Fix Keywords"), false, delegate () + { + FixKeywords(Materials); + }); + menu.AddSeparator(""); + + int unboundTextures = MaterialCleaner.CountUnusedProperties(MaterialCleaner.CleanPropertyType.Texture, Materials); + int unboundProperties = MaterialCleaner.CountAllUnusedProperties(Materials); + List unusedTextures = new List(); + MainGroup.FindUnusedTextures(unusedTextures, true); + if (unboundTextures > 0 && !IsLockedMaterial) + { + menu.AddItem(new GUIContent($"Unbound Textures: {unboundTextures}/List in console"), false, delegate () + { + MaterialCleaner.ListUnusedProperties(MaterialCleaner.CleanPropertyType.Texture, Materials); + }); + menu.AddItem(new GUIContent($"Unbound Textures: {unboundTextures}/Remove"), false, delegate () + { + MaterialCleaner.RemoveUnusedProperties(MaterialCleaner.CleanPropertyType.Texture, Materials); + }); + } + else + { + menu.AddDisabledItem(new GUIContent($"Unbound textures: 0")); + } + if (unusedTextures.Count > 0 && !IsLockedMaterial) + { + menu.AddItem(new GUIContent($"Unused Textures: {unusedTextures.Count}/List in console"), false, delegate () + { + Out("Unused textures", unusedTextures.Select(s => $"↳{s}")); + }); + menu.AddItem(new GUIContent($"Unused Textures: {unusedTextures.Count}/Remove"), false, delegate () + { + foreach (string t in unusedTextures) if (PropertyDictionary.ContainsKey(t)) PropertyDictionary[t].MaterialProperty.textureValue = null; + }); + } + else + { + menu.AddDisabledItem(new GUIContent($"Unused textures: 0")); + } + if (unboundProperties > 0 && !IsLockedMaterial) + { + menu.AddItem(new GUIContent($"Unbound properties: {unboundProperties}/List in console"), false, delegate () + { + MaterialCleaner.ListUnusedProperties(MaterialCleaner.CleanPropertyType.Texture, Materials); + MaterialCleaner.ListUnusedProperties(MaterialCleaner.CleanPropertyType.Float, Materials); + MaterialCleaner.ListUnusedProperties(MaterialCleaner.CleanPropertyType.Color, Materials); + }); + menu.AddItem(new GUIContent($"Unbound properties: {unboundProperties}/Remove"), false, delegate () + { + MaterialCleaner.RemoveAllUnusedProperties(MaterialCleaner.CleanPropertyType.Texture, Materials); + }); + } + else + { + menu.AddDisabledItem(new GUIContent($"Unbound properties: 0")); + } + menu.DropDown(position); + } + + public static void Out(string s) + { + Debug.Log($"[Thry] {s}"); + } + public static void Out(string header, params string[] lines) + { + Debug.Log($"[Thry] {header}\n{lines.Aggregate((s1, s2) => s1 + "\n" + s2)}"); + } + public static void Out(string header, IEnumerable lines) + { + if (lines.Count() == 0) Out(header); + else Debug.Log($"[Thry] {header}\n{lines.Aggregate((s1, s2) => s1 + "\n" + s2)}"); + } + public static void Out(string header, Color c, IEnumerable lines) + { + if (lines.Count() == 0) Out(header); + else Debug.Log($"[Thry] {header} \n{lines.Aggregate((s1, s2) => s1 + "\n" + s2)}"); + } + + private void HandleEvents() + { + Event e = Event.current; + //if reloaded, set reload to false + if (_doReloadNextDraw && Event.current.type == EventType.Layout) _doReloadNextDraw = false; + + //if was undo, reload + bool isUndo = (e.type == EventType.ExecuteCommand || e.type == EventType.ValidateCommand) && e.commandName == "UndoRedoPerformed"; + if (isUndo) _doReloadNextDraw = true; + + + //on swap + if (_onSwapToActions != null && _didSwapToShader) + { + foreach (DefineableAction a in _onSwapToActions) + a.Perform(Materials); + _onSwapToActions = null; + _didSwapToShader = false; + } + + //test if material has been reset + if (_wasUsed && e.type == EventType.Repaint) + { + if (Materials[0].HasProperty("shader_is_using_thry_editor") && Materials[0].GetFloat("shader_is_using_thry_editor") != 69) + { + _doReloadNextDraw = true; + HandleReset(); + _wasUsed = true; + } + } + + if (e.type == EventType.Used) _wasUsed = true; + if (Input.HadMouseDownRepaint) Input.HadMouseDown = false; + Input.HadMouseDownRepaint = false; + IsFirstCall = false; + materialPropertyDictionary = null; + } + + //iterate the same way drawing would iterate + //if display part, display all parents parts + private void UpdateSearch(ShaderPart part) + { + part.has_not_searchedFor = part.Content.text.ToLower().Contains(_appliedSearchTerm) == false; + if (part is ShaderGroup) + { + foreach (ShaderPart p in (part as ShaderGroup).parts) + { + UpdateSearch(p); + part.has_not_searchedFor &= p.has_not_searchedFor; + } + } + } + + private void ClearSearch() + { + _appliedSearchTerm = ""; + UpdateSearch(MainGroup); + } + + private void HandleReset() + { + MaterialLinker.UnlinkAll(Materials[0]); + ShaderOptimizer.DeleteTags(Materials); + } + + public void Repaint() + { + if (Materials.Length > 0) + EditorUtility.SetDirty(Materials[0]); + } + + public static void RepaintActive() + { + if (ShaderEditor.Active != null) + Active.Repaint(); + } + + public void Reload() + { + this._isFirstOnGUICall = true; + this._didSwapToShader = true; + this._doReloadNextDraw = true; + this.Repaint(); + ThryWideEnumDrawer.Reload(); + ThryRGBAPackerDrawer.Reload(); + } + + public static void ReloadActive() + { + if (ShaderEditor.Active != null) + Active.Reload(); + } + + public void ApplyDrawers() + { + foreach (Material target in Materials) + MaterialEditor.ApplyMaterialPropertyDrawers(target); + } + + public static string GetShaderEditorDirectoryPath() + { + if (s_edtiorDirectoryPath == null) + { + IEnumerable paths = AssetDatabase.FindAssets("ThryEditor").Select(g => AssetDatabase.GUIDToAssetPath(g)); + foreach (string p in paths) + { + if (p.EndsWith("/ThryEditor.cs")) + s_edtiorDirectoryPath = Directory.GetParent(Path.GetDirectoryName(p)).FullName; + } + } + return s_edtiorDirectoryPath; + } + + // Cache property->keyword lookup for performance + static Dictionary keywords)>> PropertyKeywordsByShader = new Dictionary keywords)>>(); + + /// Iterate through all materials to ensure keywords list matches properties. + public static void FixKeywords(IEnumerable materialsToFix) + { + // Process Shaders + IEnumerable uniqueShadersMaterials = materialsToFix.GroupBy(m => m.shader).Select(g => g.First()); + IEnumerable shadersWithThryEditor = uniqueShadersMaterials.Where(m => ShaderHelper.IsShaderUsingThryEditor(m)).Select(m => m.shader); + + // Clear cache every time if in developer mode, so that changes aren't missed + if(Config.Singleton.enableDeveloperMode) + PropertyKeywordsByShader.Clear(); + + float f = 0; + int count = shadersWithThryEditor.Count(); + + if(count > 1) EditorUtility.DisplayProgressBar("Validating Keywords", "Processing Shaders", 0); + + foreach (Shader s in shadersWithThryEditor) + { + if(count > 1) EditorUtility.DisplayProgressBar("Validating Keywords", $"Processing Shader: {s.name}", f++ / count); + if(!PropertyKeywordsByShader.ContainsKey(s)) + PropertyKeywordsByShader[s] = ShaderHelper.GetPropertyKeywordsForShader(s); + } + // Find Materials + IEnumerable materials = materialsToFix.Where(m => PropertyKeywordsByShader.ContainsKey(m.shader)); + f = 0; + count = materials.Count(); + + // Set Keywords + foreach(Material m in materials) + { + if(count > 1) EditorUtility.DisplayProgressBar("Validating Keywords", $"Validating Material: {m.name}", f++ / count); + + List keywordsInMaterial = m.shaderKeywords.ToList(); + + foreach((string prop, List keywords) in PropertyKeywordsByShader[m.shader]) + { + switch(keywords.Count) + { + case 0: + break; + case 1: + string keyword = keywords[0]; + keywordsInMaterial.Remove(keyword); + + if(m.GetFloat(prop) == 1) + m.EnableKeyword(keyword); + else + m.DisableKeyword(keyword); + break; + default: // KeywordEnum + for (int i = 0; i < keywords.Count; i++) + { + keywordsInMaterial.Remove(keywords[i]); + if (m.GetFloat(prop) == i) + m.EnableKeyword(keywords[i]); + else + m.DisableKeyword(keywords[i]); + } + break; + } + } + + // Disable any remaining keywords + foreach(string keyword in keywordsInMaterial) + m.DisableKeyword(keyword); + } + if(count > 1) EditorUtility.ClearProgressBar(); + } + + /// Iterate through all materials with FixKeywords. + [MenuItem("Thry/Shader Tools/Fix Keywords for All Materials (Slow)", priority = -20)] + static void FixAllKeywords() + { + IEnumerable materialsToFix = AssetDatabase.FindAssets("t:material") + .Select(g => AssetDatabase.GUIDToAssetPath(g)) + .Where(p => string.IsNullOrEmpty(p) == false) + .Select(p => AssetDatabase.LoadAssetAtPath(p)) + .Where(m => m != null && m.shader != null) + .Where(m => ShaderOptimizer.IsMaterialLocked(m) == false); + + FixKeywords(materialsToFix); + } + + [MenuItem("Thry/Twitter", priority = -100)] + static void MenuThryTwitter() + { + Application.OpenURL("https://www.twitter.com/thryrallo"); + } + + [MenuItem("Thry/ShaderUI/Settings",priority = -20)] + static void MenuShaderUISettings() + { + EditorWindow.GetWindow(false, "Thry Settings", true); + } + + [MenuItem("Thry/Shader Optimizer/Upgraded Animated Properties", priority = -20)] + static void MenuUpgradeAnimatedPropertiesToTagsOnAllMaterials() + { + ShaderOptimizer.UpgradeAnimatedPropertiesToTagsOnAllMaterials(); + } + + [MenuItem("Thry/Shader Optimizer/Materials List", priority = 0)] + static void MenuShaderOptUnlockedMaterials() + { + EditorWindow.GetWindow(false, "Materials", true); + } + + [MenuItem("Assets/Thry/Materials/Cleaner/List Unbound Properties", priority = 303)] + static void AssetsCleanMaterials_ListUnboundProperties() + { + IEnumerable materials = Selection.objects.Where(o => o is Material).Select(o => o as Material); + foreach (Material m in materials) + { + Debug.Log("_______Unbound Properties for " + m.name + "_______"); + MaterialCleaner.ListUnusedProperties(MaterialCleaner.CleanPropertyType.Texture, m); + MaterialCleaner.ListUnusedProperties(MaterialCleaner.CleanPropertyType.Color, m); + MaterialCleaner.ListUnusedProperties(MaterialCleaner.CleanPropertyType.Float, m); + } + } + + [MenuItem("Assets/Thry/Materials/Cleaner/Remove Unbound Textures", priority = 303)] + static void AssetsCleanMaterials_CleanUnboundTextures() + { + IEnumerable materials = Selection.objects.Where(o => o is Material).Select(o => o as Material); + foreach (Material m in materials) + { + Debug.Log("_______Removing Unbound Textures for " + m.name + "_______"); + MaterialCleaner.RemoveAllUnusedProperties(MaterialCleaner.CleanPropertyType.Texture, m); + } + } + } +} diff --git a/Scripts/ThryEditor/Editor/ThryFileBuilder.cs b/Scripts/ThryEditor/Editor/ThryFileBuilder.cs new file mode 100644 index 0000000..62cdad8 --- /dev/null +++ b/Scripts/ThryEditor/Editor/ThryFileBuilder.cs @@ -0,0 +1,65 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Thry { + public class ThryFileCreator { + + [MenuItem("Thry/ShaderUI/UI Creator Helper/Create Label Boiler", false, priority = 40)] + public static void CreateLabel() + { + string[] names = GetProperties(); + string data = names.Aggregate("", (n1, n2) => n1 + n2 + ":=" + n2 + "--{tooltip:}\n"); + Save(data, "_label.txt"); + } + [MenuItem("Thry/ShaderUI/UI Creator Helper/Create Label Boiler", true, priority = 40)] + static bool CreateLabelVaildate() + { + return ValidateSelection(); + } + + [MenuItem("Thry/ShaderUI/UI Creator Helper/Create Label Boiler + Locale Boiler", false, priority = 40)] + public static void CreateLabelLocale() + { + string[] names = GetProperties(); + string label_data = names.Aggregate("", (n1, n2) => n1 + + n2 + ":=locale::" + n2 + "_text--{tooltip:locale::" + n2 + "_tooltip}\n"); + string locale_data = names.Aggregate(",English\n", (n1, n2) => n1 + + n2 + "_text," + n2 + "\n"+ + n2 + "_tooltip,\n"); + Save(label_data, "_label.txt"); + Save(locale_data, "_locale.txt"); + } + [MenuItem("Thry/ShaderUI Creator Helper/Create Label Boiler + Locale Boiler", true, priority = 40)] + static bool CreateLabelLocaleValidate() + { + return ValidateSelection(); + } + + private static bool ValidateSelection() + { + if (Selection.activeObject == null) + return false; + string path = AssetDatabase.GetAssetPath(Selection.activeObject).ToLower(); + return path.EndsWith(".shader"); + } + + private static string[] GetProperties() + { + Shader shader = (Shader)Selection.activeObject; + return MaterialEditor.GetMaterialProperties(new Material[] { new Material(shader) }).Select(p => p.name).ToArray(); + } + + private static void Save(string data, string add_string) + { + string path = AssetDatabase.GetAssetPath(Selection.activeObject); + path = Path.GetDirectoryName(path)+ "/"+ Path.GetFileNameWithoutExtension(path) + add_string; + FileHelper.WriteStringToFile(data, path); + AssetDatabase.Refresh(); + EditorGUIUtility.PingObject(AssetDatabase.LoadMainAssetAtPath(path)); + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/ThryTexturePacker.compute b/Scripts/ThryEditor/Editor/ThryTexturePacker.compute new file mode 100644 index 0000000..fd27d51 --- /dev/null +++ b/Scripts/ThryEditor/Editor/ThryTexturePacker.compute @@ -0,0 +1,286 @@ +#pragma kernel CSMain + +RWTexture2D Result; + +float Width; +float Height; + +float Rotation; + +float Hue; +float Saturation; +float Brightness; +float2 Scale; +float2 Offset; + +Texture2D R_Input_0; +Texture2D R_Input_1; +Texture2D R_Input_2; +Texture2D R_Input_3; +SamplerState sampler_R_Input_0; +SamplerState sampler_R_Input_1; +SamplerState sampler_R_Input_2; +SamplerState sampler_R_Input_3; +int R_Channel_0; +int R_Channel_1; +int R_Channel_2; +int R_Channel_3; +int R_Count; +int R_BlendMode; +float R_Fallback; +bool R_Invert; + +Texture2D G_Input_0; +Texture2D G_Input_1; +Texture2D G_Input_2; +Texture2D G_Input_3; +SamplerState sampler_G_Input_0; +SamplerState sampler_G_Input_1; +SamplerState sampler_G_Input_2; +SamplerState sampler_G_Input_3; +int G_Channel_0; +int G_Channel_1; +int G_Channel_2; +int G_Channel_3; +int G_Count; +int G_BlendMode; +float G_Fallback; +bool G_Invert; + +Texture2D B_Input_0; +Texture2D B_Input_1; +Texture2D B_Input_2; +Texture2D B_Input_3; +SamplerState sampler_B_Input_0; +SamplerState sampler_B_Input_1; +SamplerState sampler_B_Input_2; +SamplerState sampler_B_Input_3; +int B_Channel_0; +int B_Channel_1; +int B_Channel_2; +int B_Channel_3; +int B_Count; +int B_BlendMode; +float B_Fallback; +bool B_Invert; + +Texture2D A_Input_0; +Texture2D A_Input_1; +Texture2D A_Input_2; +Texture2D A_Input_3; +SamplerState sampler_A_Input_0; +SamplerState sampler_A_Input_1; +SamplerState sampler_A_Input_2; +SamplerState sampler_A_Input_3; +int A_Channel_0; +int A_Channel_1; +int A_Channel_2; +int A_Channel_3; +int A_Count; +int A_BlendMode; +float A_Fallback; +bool A_Invert; + +// blendmodes +// 0 = add +// 1 = multiply +// 3 = max +// 4 = min + +float SampleTexture(Texture2D tex, SamplerState texSampler, int channel, float2 uv) +{ + float4 pixelColor = tex.SampleLevel(texSampler,uv,0); + if (channel == 0) return pixelColor.r; + else if (channel == 1) return pixelColor.g; + else if (channel == 2) return pixelColor.b; + else if (channel == 3) return pixelColor.a; + else return max(pixelColor.r, max(pixelColor.g, pixelColor.b)); +} + +float SampleAndBlendTexture(float value, Texture2D tex, SamplerState texSampler, int blendMode, int channel, float2 uv) +{ + float newValue = SampleTexture(tex, texSampler, channel, uv); + if (blendMode == 0) return value + newValue; + else if (blendMode == 1) return value * newValue; + else if (blendMode == 2) return max(value, newValue); + else if (blendMode == 3) return min(value, newValue); + else return newValue; +} + +float SampleAndBlendTextures(Texture2D tex1, Texture2D tex2, Texture2D tex3, Texture2D tex4, + int channel1, int channel2, int channel3, int channel4, + SamplerState texSampler1, SamplerState texSampler2, SamplerState texSampler3, SamplerState texSampler4, + int count, int blendMode, float2 uv) +{ + float value = SampleTexture(tex1, texSampler1, channel1, uv); + if(count > 1) value = SampleAndBlendTexture(value, tex2, texSampler2, blendMode, channel2, uv); + if(count > 2) value = SampleAndBlendTexture(value, tex3, texSampler3, blendMode, channel3, uv); + if(count > 3) value = SampleAndBlendTexture(value, tex4, texSampler4, blendMode, channel4, uv); + return value; +} + +float SampleInput(Texture2D tex1, Texture2D tex2, Texture2D tex3, Texture2D tex4, + int channel1, int channel2, int channel3, int channel4, + SamplerState texSampler1, SamplerState texSampler2, SamplerState texSampler3, SamplerState texSampler4, + int count, int blendMode, float fallback, bool doInvert, float2 uv) { + float value = fallback; + if(count > 0) + { + value = SampleAndBlendTextures(tex1, tex2, tex3, tex4, channel1, channel2, channel3, channel4, texSampler1, texSampler2, texSampler3, texSampler4, count, blendMode, uv); + if(doInvert) value = 1 - value; + } + return value; +} + +float3 rgb2hsv(float3 rgb){ + float4 p = (rgb.g < rgb.b) ? float4(rgb.bg, -1.0, 2.0/3.0) : float4(rgb.gb, 0.0, -1.0/3.0); + float4 q = (rgb.r < p.x) ? float4(p.xyw, rgb.r) : float4(rgb.r, p.yzx); + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return float3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +float3 hsv2rgb(float3 hsv){ + float4 K = float4(1.0, 2.0/3.0, 1.0/3.0, 3.0); + float3 p = abs(frac(hsv.xxx + K.xyz) * 6.0 - K.www); + return hsv.z * lerp(K.xxx, saturate(p - K.xxx), hsv.y); +} + +float4 ApplyHSV(float4 pixel, float hue, float saturation, float value){ + float3 hsv = rgb2hsv(pixel.rgb); + hsv.r += hue; + hsv.g *= saturation; + hsv.b *= value; + return float4(hsv2rgb(hsv.rgb), pixel.a); +} + +[numthreads(8, 8, 1)] +void CSMain(uint3 id : SV_DispatchThreadID) +{ + float2 uv = float2(id.x / Width, id.y / Height) + Offset; + + // Rotate uv by Rotation around 0.5 0.5 and scale by Scale + float2 center = float2(0.5, 0.5); + float2 rotated = float2(0, 0); + rotated.x = (uv.x - center.x) * cos(Rotation) - (uv.y - center.y) * sin(Rotation); + rotated.y = (uv.x - center.x) * sin(Rotation) + (uv.y - center.y) * cos(Rotation); + uv = rotated * Scale + center; + + float4 pixel = float4(1, 1, 1, 1); + pixel.r = SampleInput(R_Input_0, R_Input_1, R_Input_2, R_Input_3, R_Channel_0, R_Channel_1, R_Channel_2, R_Channel_3, sampler_R_Input_0, sampler_R_Input_1, sampler_R_Input_2, sampler_R_Input_3, R_Count, R_BlendMode, R_Fallback, R_Invert, uv); + pixel.g = SampleInput(G_Input_0, G_Input_1, G_Input_2, G_Input_3, G_Channel_0, G_Channel_1, G_Channel_2, G_Channel_3, sampler_G_Input_0, sampler_G_Input_1, sampler_G_Input_2, sampler_G_Input_3, G_Count, G_BlendMode, G_Fallback, G_Invert, uv); + pixel.b = SampleInput(B_Input_0, B_Input_1, B_Input_2, B_Input_3, B_Channel_0, B_Channel_1, B_Channel_2, B_Channel_3, sampler_B_Input_0, sampler_B_Input_1, sampler_B_Input_2, sampler_B_Input_3, B_Count, B_BlendMode, B_Fallback, B_Invert, uv); + pixel.a = SampleInput(A_Input_0, A_Input_1, A_Input_2, A_Input_3, A_Channel_0, A_Channel_1, A_Channel_2, A_Channel_3, sampler_A_Input_0, sampler_A_Input_1, sampler_A_Input_2, sampler_A_Input_3, A_Count, A_BlendMode, A_Fallback, A_Invert, uv); + pixel = ApplyHSV(pixel, Hue, Saturation, Brightness); + Result[id.xy] = pixel; +} + + +#pragma kernel CS_Kernel + +Texture2D Kernel_Input; +float4 Kernel_X[25]; +float4 Kernel_Y[25]; +bool Kernel_Grayscale; +bool Kernel_TwoPass; +float4 Kernel_Channels; + +[numthreads(8, 8, 1)] +void CS_Kernel(uint3 id : SV_DispatchThreadID) +{ + float4 pixel_x = float4(0, 0, 0, 0); + float4 pixel_y = float4(0, 0, 0, 0); + + for (int x = -2; x <= 2; x++) + { + for (int y = -2; y <= 2; y++) + { + float4 p = Kernel_Input.Load(int3(id.xy + int2(x,y), 0)); + [branch] + if(Kernel_Grayscale) + { + float gray = dot(p.rgb, float3(0.2126, 0.7152, 0.0722)); + pixel_x += float4(gray * Kernel_X[x + 2 + (y + 2) * 5].x, 0, 0, 0); + [branch] + if(Kernel_TwoPass) + { + pixel_y += float4(gray * Kernel_Y[x + 2 + (y + 2) * 5].x, 0, 0, 0); + } + }else + { + pixel_x += p * Kernel_X[x + 2 + (y + 2) * 5].x; + [branch] + if(Kernel_TwoPass) + { + pixel_y += p * Kernel_Y[x + 2 + (y + 2) * 5].x; + } + } + } + } + + float4 color = Kernel_Input.Load(int3(id.xy, 0)); + float4 res = color; + [branch] + if(Kernel_Grayscale) + { + color = float4(dot(color.rgb, float3(0.2126, 0.7152, 0.0722)), 0, 0, color.a); + [branch] + if(Kernel_TwoPass) + { + res = sqrt((pixel_x * pixel_x + pixel_y * pixel_y)).x; + } + else + { + res = pixel_x.x; + } + }else + { + [branch] + if(Kernel_TwoPass) + { + res = sqrt((pixel_x * pixel_x + pixel_y * pixel_y)); + } + else + { + res = pixel_x; + } + } + color.r = lerp(color.r, res.r, Kernel_Channels.x); + color.g = lerp(color.g, res.g, Kernel_Channels.y); + color.b = lerp(color.b, res.b, Kernel_Channels.z); + color.a = lerp(color.a, res.a, Kernel_Channels.w); + Result[id.xy] = color; + +} + +#pragma kernel CS_ChannelLerper + +Texture2D Unpacker_Input; +float4 Channels_Strength_R; +float4 Channels_Strength_G; +float4 Channels_Strength_B; +float4 Channels_Strength_A; + +[numthreads(8, 8, 1)] +void CS_ChannelLerper(uint3 id : SV_DispatchThreadID) +{ + float4 color = Unpacker_Input.Load(int3(id.xy, 0)); + float4 res = color; + res.r = lerp(0, color.r, Channels_Strength_R.x) + + lerp(0, color.g, Channels_Strength_R.y) + + lerp(0, color.b, Channels_Strength_R.z) + + lerp(0, color.a, Channels_Strength_R.w); + res.g = lerp(0, color.r, Channels_Strength_G.x) + + lerp(0, color.g, Channels_Strength_G.y) + + lerp(0, color.b, Channels_Strength_G.z) + + lerp(0, color.a, Channels_Strength_G.w); + res.b = lerp(0, color.r, Channels_Strength_B.x) + + lerp(0, color.g, Channels_Strength_B.y) + + lerp(0, color.b, Channels_Strength_B.z) + + lerp(0, color.a, Channels_Strength_B.w); + res.a = lerp(0, color.r, Channels_Strength_A.x) + + lerp(0, color.g, Channels_Strength_A.y) + + lerp(0, color.b, Channels_Strength_A.z) + + lerp(0, color.a, Channels_Strength_A.w); + Result[id.xy] = res; +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/UnitTests.cs b/Scripts/ThryEditor/Editor/UnitTests.cs new file mode 100644 index 0000000..05cc0d1 --- /dev/null +++ b/Scripts/ThryEditor/Editor/UnitTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class UnitTests + { + [MenuItem("Thry/ShaderUI/Test/Custom Test")] + public static void CustomTest() + { + List<(Type, string)> tests = GetParserTests(); + (Type, string) problem = tests[2]; + Parser.Deserialize(problem.Item2, problem.Item1); + } + + [MenuItem("Thry/ShaderUI/Test/Run Unit Tests")] + public static void RunUnitTests() + { + int testCount = 0; + int passedTests = 0; + // Parser Tests + List<(Type, string)> tests = GetParserTests(); + foreach((Type t, string data) test in tests) + { + Debug.Log($"Running test {test.t.Name}"); + object obj = null; + object obj2 = null; + string serialized1 = null; + string serialized2 = null; + try + { + obj = Parser.Deserialize(test.data, test.t); + serialized1 = Parser.Serialize(obj); + obj2 = Parser.Deserialize(serialized1, test.t); + serialized2 = Parser.Serialize(obj2); + }catch(Exception e) + { + Debug.LogError($"Failed to deserialize {test.t.Name} with error {e.Message}"); + continue; + } + bool passed = serialized1 == serialized2 && serialized1 != null; + Debug.Assert(passed, $"Serialization of {test.t.Name} failed. Serialized1: {serialized1} Serialized2: {serialized2}"); + passedTests += passed ? 1 : 0; + testCount++; + } + if(testCount == passedTests) + { + Debug.Log($"Passed all tests"); + }else + { + Debug.Log($"Passed {passedTests}/{testCount} tests"); + } + } + + static List<(Type, string)> GetParserTests() + { + TextAsset txt = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath("aaf371d691a1f4d428144aae9cec4b5f")); + // Document is formated as follows: + // ##ClassName + // + List<(Type, string)> tests = new List<(Type, string)>(); + foreach(string line in txt.text.Replace("\r", "").Split('\n')) + { + if (line.StartsWith("##")) + { + string className = line.Substring(2); + Type type = Type.GetType(className); + if (type == null) + { + Debug.LogError($"Could not find type {className}"); + continue; + } + tests.Add((type, "")); + }else + { + (Type, string) last = tests[tests.Count - 1]; + last.Item2 += line + "\n"; + tests[tests.Count - 1] = last; + } + } + return tests; + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Editor/Unity.cs b/Scripts/ThryEditor/Editor/Unity.cs new file mode 100644 index 0000000..f31c3b4 --- /dev/null +++ b/Scripts/ThryEditor/Editor/Unity.cs @@ -0,0 +1,245 @@ +// Material/Shader Inspector for Unity 2017/2018 +// Copyright (C) 2019 Thryrallo + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEngine; + +namespace Thry +{ + public class UnityHelper + { + [MenuItem("Assets/Thry/Copy GUID")] + public static void CopyGUID() + { + string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(Selection.activeObject)); + EditorGUIUtility.systemCopyBuffer = guid; + } + + public static List FindAssetsWithFilename(string filename) + { + string[] guids = AssetDatabase.FindAssets(Path.GetFileNameWithoutExtension(filename)); + return guids.Select(g => AssetDatabase.GUIDToAssetPath(g)).Where(p => p.EndsWith(filename)).ToList(); + } + + public static void SetDefineSymbol(string symbol, bool active) + { + SetDefineSymbol(symbol, active, true); + } + + public static void SetDefineSymbol(string symbol, bool active, bool refresh_if_changed) + { + try + { + string symbols = PlayerSettings.GetScriptingDefineSymbolsForGroup( + BuildTargetGroup.Standalone); + if (!symbols.Contains(symbol) && active) + { + PlayerSettings.SetScriptingDefineSymbolsForGroup( + BuildTargetGroup.Standalone, symbols + ";" + symbol); + if(refresh_if_changed) + AssetDatabase.Refresh(); + } + else if (symbols.Contains(symbol) && !active) + { + PlayerSettings.SetScriptingDefineSymbolsForGroup( + BuildTargetGroup.Standalone, Regex.Replace(symbols, @";?" + @symbol, "")); + if(refresh_if_changed) + AssetDatabase.Refresh(); + } + } + catch (Exception e) + { + e.ToString(); + } + } + + public static void RemoveDefineSymbols() + { + UnityHelper.SetDefineSymbol(DEFINE_SYMBOLS.IMAGING_EXISTS, false); + } + + public static void RepaintEditorWindow() where T : EditorWindow + { + EditorWindow window = (EditorWindow)Resources.FindObjectsOfTypeAll().FirstOrDefault(); + if (window != null) window.Repaint(); + } + + public static string GetGUID(UnityEngine.Object o) + { + return AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o)); + } + + public static WindowType FindOpenEditorWindow() where WindowType : EditorWindow + { + WindowType[] windows = Resources.FindObjectsOfTypeAll(); + if (windows != null && windows.Length > 0) + { + return windows[0]; + } + return null; + } + + public static EditorWindow FindOpenEditorWindow(Type type) + { + UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(type); + if (windows != null && windows.Length > 0) + { + return windows[0] as EditorWindow; + } + return null; + } + + public static string GetCurrentAssetExplorerFolder() + { + if (Selection.activeObject) return "Assets"; + string path = AssetDatabase.GetAssetPath(Selection.activeObject); + if (Directory.Exists(path)) return path; + else return Path.GetDirectoryName(path); + } + + public static void AddShaderPropertyToSourceCode(string path, string property, string value) + { + string shaderCode = FileHelper.ReadFileIntoString(path); + string pattern = @"Properties.*\n?\s*{"; + RegexOptions options = RegexOptions.Multiline; + shaderCode = Regex.Replace(shaderCode, pattern, "Properties \r\n {" + " \r\n " + property + "=" + value, options); + + FileHelper.WriteStringToFile(shaderCode, path); + } + } + + public class UnityFixer + { + public const string RSP_DRAWING_DLL_CODE = "\n-r:System.Drawing.dll"; + public const string RSP_DRAWING_DLL_DEFINE_CODE = "\n-define:SYSTEM_DRAWING"; + public const string RSP_DRAWING_DLL_REGEX = @"-r:\s*System\.Drawing\.dll"; + public const string RSP_DRAWING_DLL_DEFINE_REGEX = @"-define:\s*SYSTEM_DRAWING"; + + +#if UNITY_2019_1_OR_NEWER + public const string RSP_FILENAME = "csc"; +#else + public const string RSP_FILENAME = "mcs"; +#endif + + public static void OnAssetDeleteCheckDrawingDLL(string[] deleted_assets) + { + foreach (string path in deleted_assets) + { + if (path == PATH.RSP_NEEDED_PATH + RSP_FILENAME + ".rsp" || path.EndsWith("/System.Drawing.dll")) + UnityHelper.SetDefineSymbol(DEFINE_SYMBOLS.IMAGING_EXISTS, false, true); + } + } + + public static void CheckAPICompatibility() + { + ApiCompatibilityLevel level = PlayerSettings.GetApiCompatibilityLevel(BuildTargetGroup.Standalone); + if (level == ApiCompatibilityLevel.NET_2_0_Subset) + PlayerSettings.SetApiCompatibilityLevel(BuildTargetGroup.Standalone, ApiCompatibilityLevel.NET_2_0); + } + + public static void CheckDrawingDll() + { + string path = PATH.RSP_NEEDED_PATH + RSP_FILENAME + ".rsp"; + bool refresh = true; + bool containsDLL = DoesRSPContainDrawingDLL(path); + bool containsDefine = DoesRSPContainDrawingDLLDefine(path); + if (!containsDefine && !containsDLL) + { + AddDrawingDLLToRSP(path); + AddDrawingDLLDefineToRSP(path); + } + else if (!containsDLL) + AddDrawingDLLToRSP(path); + else if (!containsDefine) + AddDrawingDLLDefineToRSP(path); + else + refresh = false; + if (refresh) + AssetDatabase.ImportAsset(path); + } + + + + private static bool DoesRSPContainDrawingDLL(string rsp_path) + { + if (!File.Exists(rsp_path)) return false; + string rsp_data = FileHelper.ReadFileIntoString(rsp_path); + return (Regex.Match(rsp_data, RSP_DRAWING_DLL_REGEX).Success); + } + + private static bool DoesRSPContainDrawingDLLDefine(string rsp_path) + { + if (!File.Exists(rsp_path)) return false; + string rsp_data = FileHelper.ReadFileIntoString(rsp_path); + return (Regex.Match(rsp_data, RSP_DRAWING_DLL_DEFINE_REGEX).Success); + } + + private static void AddDrawingDLLToRSP(string rsp_path) + { + string rsp_data = FileHelper.ReadFileIntoString(rsp_path); + rsp_data += RSP_DRAWING_DLL_CODE; + FileHelper.WriteStringToFile(rsp_data, rsp_path); + } + + private static void AddDrawingDLLDefineToRSP(string rsp_path) + { + string rsp_data = FileHelper.ReadFileIntoString(rsp_path); + rsp_data += RSP_DRAWING_DLL_DEFINE_CODE; + FileHelper.WriteStringToFile(rsp_data, rsp_path); + } + } + + [InitializeOnLoad] + public class OnCompileHandler + { + static OnCompileHandler() + { + //Init Editor Variables with paths + ShaderEditor.GetShaderEditorDirectoryPath(); + + Config.OnCompile(); + TrashHandler.EmptyThryTrash(); + + UnityFixer.CheckAPICompatibility(); //check that Net_2.0 is ApiLevel + UnityFixer.CheckDrawingDll(); //check that drawing.dll is imported + } + } + + public class AssetChangeHandler : AssetPostprocessor + { + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) + { + if (deletedAssets.Length > 0) + AssetsDeleted(deletedAssets); + } + + private static void AssetsDeleted(string[] assets) + { + UnityFixer.OnAssetDeleteCheckDrawingDLL(assets); + if (CheckForEditorRemove(assets)) + { + Debug.Log("[Thry] ShaderEditor is being deleted."); + Config.Singleton.verion = "0"; + Config.Singleton.Save(); + } + } + + private static bool CheckForEditorRemove(string[] assets) + { + string test_for = ShaderEditor.GetShaderEditorDirectoryPath() + "/Editor/ShaderEditor.cs"; + foreach (string p in assets) + { + if (p == test_for) + return true; + } + return false; + } + } +} \ No newline at end of file diff --git a/Scripts/ThryEditor/Examples/Example1.shader b/Scripts/ThryEditor/Examples/Example1.shader new file mode 100644 index 0000000..0ea4b74 --- /dev/null +++ b/Scripts/ThryEditor/Examples/Example1.shader @@ -0,0 +1,440 @@ +Shader "Thry/Example 1" +{ + Properties + { + [HideInInspector] shader_is_using_thry_editor("", Float)=0 + [HideInInspector] shader_master_label("Example 1", Float) = 0 + [HideInInspector] shader_presets("ThryPresetsExample", Float) = 0 + [HideInInspector] shader_properties_label_file("ThryLabelExample", Float) = 0 + + [HideInInspector] footer_website("", Float) = 0 + [HideInInspector] footer_github("", Float) = 0 + + + shader_properties_locale("locale::locale--{file_name:thry_locale_example}", Float) = 0 + [Enum(Cutout,0,Transparent,1)]variant_selector("Variant--{on_value_actions:[{value:0,actions:[{type:SET_PROPERTY,data:_ZWrite=1},{type:SET_SHADER,data:Thry/Example 1}]},{value:1,actions:[{type:SET_PROPERTY,data:_ZWrite=0},{type:SET_SHADER,data:Thry/Example 2}]}]}",Float) = 0 + + [ThryWideEnum(Opaque, 0, Cutout, 1, Fade, 2, Transparent, 3, Additive, 4, Soft Additive, 5, Multiplicative, 6, 2x Multiplicative, 7, Multiplicative Grab Pass, 8)]_Mode("Rendering Preset--{on_value:'' + 0,render_queue = 2000,render_type = Opaque,_BlendOp = 0,_BlendOpAlpha = 0,_Cutoff = 0,_SrcBlend = 1,_DstBlend = 0,_AlphaToMask = 0,_ZWrite = 1,_ZTest = 4,_AlphaPremultiply = 0; + 1,render_queue = 2460,render_type = TransparentCutout,_BlendOp = 0,_BlendOpAlpha = 0,_Cutoff = 0.5,_SrcBlend = 1,_DstBlend = 0,_AlphaToMask = 1,_ZWrite = 1,_ZTest = 4,_AlphaPremultiply = 0 + '' }", Int) = 0 + + [HideInInspector] m_mainOptions("Main", Float) = 0 + _Color("Color & Alpha", Color) = (1, 1, 1, 1) + [Helpbox]_HelpboxForSomething("Alpha is controlled in the color", Float) = 1 + _Saturation("Saturation", Range(-1, 1)) = 0 + _MainVertexColoring("Use Vertex Color", Range(0,1)) = 0 + _MainEmissionStrength("Basic Emission", Range(0, 20)) = 0 + [Curve]_MainTex("Texture", 2D) = "white" { } + [PanningTexture][Normal]_BumpMap("Normal Map", 2D) = "bump" { } + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _BumpMapUV("Normal UV#", Int) = 0 + [HideInInspector][Vector2]_MainNormalPan("Panning", Vector) = (0, 0, 0, 0) + _BumpScale("Normal Intensity", Range(0, 10)) = 1 + _AlphaMask("Alpha Mask", 2D) = "white" { } + [Vector2]_GlobalPanSpeed("Global Pan Speed", Vector) = (0, 0, 0, 0) + + [HideInInspector] m_start_Alpha("Alpha Options--{altClick:{type:URL,data:https://thryrallo.de}}", Float) = 0 + _Clip("Alpha Cuttoff", Range(0, 1.001)) = 0.5 + [Toggle(_)]_ForceOpaque("Force Opaque", Float) = 0 + [Toggle(_)]_MainAlphaToCoverage("Alpha To Coverage", Float) = 1 + _MainMipScale("Mip Level Alpha Scale", Range(0, 1)) = 0.25 + [HideInInspector] m_end_Alpha("Alpha Options", Float) = 0 + + [HideInInspector] m_start_DetailOptions("Details", Float) = 0 + _DetailMask("Detail Mask (R:Texture, G:Normal)", 2D) = "white" { } + [PanningTexture]_DetailTex("Detail Texture", 2D) = "gray" { } + [HideInInspector][Vector2]_DetailTexturePan("Panning", Vector) = (0, 0, 0, 0) + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _DetailTexUV("Detail Tex UV#", Int) = 0 + _DetailTexIntensity("Detail Tex Intensity", Range(0, 10)) = 1 + _DetailBrightness("Detail Brightness:", Range(0, 2)) = 1 + _DetailTint("Detail Tint", Color) = (1, 1, 1) + [Normal]_DetailNormalMap("Detail Normal", 2D) = "bump" { } + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _DetailNormalUV("Detail Normal UV#", Int) = 0 + _DetailNormalMapScale("Detail Normal Intensity", Range(0, 10)) = 1 + [HideInInspector][Vector2]_MainDetailNormalPan("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector] m_end_DetailOptions("Details", Float) = 0 + + [HideInInspector] m_lightingOptions("Lighting Options", Float) = 0 + [HideInInspector] m_start_Lighting("Light and Shadow", Float) = 0 + [Toggle(_NORMALMAP)]_EnableLighting("Enable Lighting", Float) = 1 + [HideInInspector] g_start_l("", Int) = 0 + [Enum(Natural, 0, Controlled, 1, Standardish, 2)] _LightingType("Lighting Type", Int) = 1 + [Gradient]_ToonRamp("Lighting Ramp", 2D) = "white" { } + _LightingShadowMask("Shadow Mask (R)", 2D) = "white" { } + _ShadowStrength("Shadow Strength", Range(0, 1)) = .2 + _ShadowOffset("Shadow Offset", Range(-1, 1)) = 0 + _AOMap("AO Map", 2D) = "white" { } + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _LightingAOUV("AO Map UV#", Int) = 0 + _AOStrength("AO Strength", Range(0, 1)) = 1 + _LightingMinLightBrightness("Min Brightness", Range(0,1)) = 0 + [HideInInspector] m_start_lightingStandard("Standardish Settings", Float) = 0 + _LightingStandardSmoothness("Smoothness", Range(0, 1)) = 0 + [HideInInspector] m_end_lightingStandard("Standardish Settings", Float) = 0 + [HideInInspector] m_start_lightingAdvanced("Advanced", Float) = 0 + _LightingIndirectContribution("Indirect Contribution", Range(0, 1)) = .25 + _AdditiveSoftness("Additive Softness", Range(0, 0.5)) = 0.005 + _AdditiveOffset("Additive Offset", Range(-0.5, 0.5)) = 0 + _LightingAdditiveIntensity("Additive Intensity", Range(0,1)) = 1 + _AttenuationMultiplier("Attenuation", Range(0, 1)) = 0 + [HideInInspector] m_end_lightingAdvanced("Advanced", Float) = 0 + [HideInInspector] m_start_lightingBeta("Beta", Float) = 0 + [Toggle(_)]_LightingStandardControlsToon("Standard Lighting Controls Toon Ramp", Float) = 0 + [HideInInspector] m_end_lightingBeta("Beta", Float) = 0 + [HideInInspector] g_end_l("", Int) = 0 + [HideInInspector] m_end_Lighting("Light and Shadow", Float) = 0 + + [HideInInspector] m_start_subsurface("Subsurface Scattering", Float) = 0 + [Toggle(_TERRAIN_NORMAL_MAP)]_EnableSSS("Enable Subsurface Scattering", Float) = 0 + _SSSColor("Subsurface Color", Color) = (1, 1, 1, 1) + _SSSThicknessMap("Thickness Map", 2D) = "black" { } + _SSSThicknessMod("Thickness mod", Range(-1, 1)) = 0 + _SSSSCale("Light Strength", Range(0, 1)) = 0 + _SSSPower("Light Spread", Range(1, 100)) = 1 + _SSSDistortion("Light Distortion", Range(0, 1)) = 0 + [HideInInspector] m_end_subsurface("Subsurface Scattering", Float) = 0 + + [HideInInspector] m_start_rimLightOptions("Rim Lighting", Float) = 0 + [Toggle(_GLOSSYREFLECTIONS_OFF)]_EnableRimLighting("Enable Rim Lighting", Float) = 0 + [Toggle(_)]_RimLightingInvert("Invert Rim Lighting", Float) = 0 + _RimLightColor("Rim Color", Color) = (1, 1, 1, 1) + _RimWidth("Rim Width", Range(0, 1)) = 0.8 + _RimSharpness("Rim Sharpness", Range(0, 1)) = .25 + _RimStrength("Rim Emission", Range(0, 20)) = 0 + _RimBrighten("Rim Color Brighten", Range(0, 3)) = 0 + _RimLightColorBias("Rim Color Bias", Range(0, 1)) = 0 + [PanningTexture]_RimTex("Rim Texture", 2D) = "white" { } + _RimMask("Rim Mask", 2D) = "white" { } + [HideInInspector][Vector2]_RimTexPanSpeed("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector] m_start_reflectionRim("Environmental Rim", Float) = 0 + [Toggle(_)]_EnableEnvironmentalRim("Enable Environmental Rim", Float) = 0 + _RimEnviroMask("Mask", 2D) = "white" { } + _RimEnviroBlur("Blur", Range(0, 1)) = 0.7 + _RimEnviroWidth("Rim Width", Range(0, 1)) = 0.45 + _RimEnviroSharpness("Rim Sharpness", Range(0, 1)) = 0 + _RimEnviroMinBrightness("Min Brightness Threshold", Range(0, 2)) = 0 + [HideInInspector] m_end_reflectionRim("Environmental Rim", Float) = 0 + [HideInInspector] m_start_rimWidthNoise("Width Noise", Float) = 0 + [PanningTexture]_RimWidthNoiseTexture("Rim Width Noise", 2D) = "black" { } + _RimWidthNoiseStrength("Intensity", Range(0, 1)) = 0.1 + [HideInInspector][Vector2]_RimWidthNoisePan("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector] m_end_rimWidthNoise("Width Noise", Float) = 0 + [HideInInspector] m_start_ShadowMix("Shadow Mix", Float) = 0 + _ShadowMix("Shadow Mix In", Range(0, 1)) = 0 + _ShadowMixThreshold("Shadow Mix Threshold", Range(0, 1)) = .5 + _ShadowMixWidthMod("Shadow Mix Width Mod", Range(0, 10)) = .5 + [HideInInspector] m_end_ShadowMix("Shadow Mix", Float) = 0 + [HideInInspector] m_end_rimLightOptions("Rim Lighting", Float) = 0 + + [HideInInspector] m_start_bakedLighting("Baked Lighting", Float) = 0 + _GIEmissionMultiplier("GI Emission Multiplier", Float) = 1 + [HideInInspector] DSGI("DSGI", Float) = 0 //add this property for double sided illumination settings to be shown + [HideInInspector] LightmapFlags("Lightmap Flags", Float) = 0 //add this property for lightmap flags settings to be shown + [HideInInspector] m_end_bakedLighting("Baked Lighting", Float) = 0 + + [HideInInspector] m_reflectionOptions("Reflections", Float) = 0 + [HideInInspector] m_start_Metallic("Metallics", Float) = 0 + [Toggle(_METALLICGLOSSMAP)]_EnableMetallic("Enable Metallics", Float) = 0 + _CubeMap("Baked CubeMap", Cube) = "" { } + [Toggle(_)]_SampleWorld("Force Baked Cubemap", Range(0, 1)) = 0 + _MetalReflectionTint("Reflection Tint", Color) = (1, 1, 1) + _MetallicMask("Metallic Mask", 2D) = "white" { } + _Metallic("Metallic", Range(0, 1)) = 0 + _SmoothnessMask("Smoothness Map", 2D) = "white" { } + [Toggle(_)]_InvertSmoothness("Invert Smoothness Map", Range(0, 1)) = 0 + _Smoothness("Smoothness", Range(0, 1)) = 0 + [HideInInspector] m_end_Metallic("Metallics", Float) = 0 + + [HideInInspector] m_start_clearCoat("Clear Coat", Float) = 0 + [Toggle(_COLORCOLOR_ON)]_EnableClearCoat("Enable Clear Coat", Float) = 0 + [Enum(Vertex, 0, Pixel, 1)] _ClearCoatNormalToUse("What Normal?", Int) = 0 + _ClearCoatCubeMap("Baked CubeMap", Cube) = "" { } + [Toggle(_)]_ClearCoatSampleWorld("Force Baked Cubemap", Range(0, 1)) = 0 + _ClearCoatTint("Reflection Tint", Color) = (1, 1, 1) + _ClearCoatMask("Mask", 2D) = "white" { } + _ClearCoat("Clear Coat", Range(0, 1)) = 1 + _ClearCoatSmoothnessMask("Smoothness Map", 2D) = "white" { } + [Toggle(_)]_ClearCoatInvertSmoothness("Invert Smoothness Map", Range(0, 1)) = 0 + _ClearCoatSmoothness("Smoothness", Range(0, 1)) = 0 + [Toggle(_)]_ClearCoatForceLighting("Force Lighting", Float) = 0 + [HideInInspector] m_end_clearCoat("Clear Coat", Float) = 0 + + [HideInInspector] m_start_matcap("Matcap / Sphere Textures", Float) = 0 + [Toggle(_COLORADDSUBDIFF_ON)]_MatcapEnable("Enable Matcap", Float) = 0 + _MatcapColor("Color", Color) = (1, 1, 1, 1) + [TextureNoSO]_Matcap("Matcap", 2D) = "white" { } + _MatcapBorder("Border", Range(0, .5)) = 0.43 + _MatcapMask("Mask", 2D) = "white" { } + _MatcapIntensity("Intensity", Range(0, 5)) = 1 + _MatcapLightMask("Hide in Shadow", Range(0, 1)) = 0 + _MatcapReplace("Replace With Matcap", Range(0, 1)) = 1 + _MatcapMultiply("Multiply Matcap", Range(0, 1)) = 0 + _MatcapAdd("Add Matcap", Range(0, 1)) = 0 + [Enum(Vertex, 0, Pixel, 1)] _MatcapNormal("Normal to use", Int) = 1 + [HideInInspector] m_end_matcap("Matcap", Float) = 0 + [HideInInspector] m_start_Matcap2("Matcap 2", Float) = 0 + [Toggle(_)]_Matcap2Enable("Enable Matcap 2", Float) = 0 + _Matcap2Color("Color", Color) = (1, 1, 1, 1) + [TextureNoSO]_Matcap2("Matcap", 2D) = "white" { } + _Matcap2Border("Border", Range(0, .5)) = 0.43 + _Matcap2Mask("Mask", 2D) = "white" { } + _Matcap2Intensity("Intensity", Range(0, 5)) = 1 + _Matcap2LightMask("Hide in Shadow", Range(0, 1)) = 0 + _Matcap2Replace("Replace With Matcap", Range(0, 1)) = 0 + _Matcap2Multiply("Multiply Matcap", Range(0, 1)) = 0 + _Matcap2Add("Add Matcap", Range(0, 1)) = 0 + [Enum(Vertex, 0, Pixel, 1)] _Matcap2Normal("Normal to use", Int) = 1 + [HideInInspector] m_end_Matcap2("Matcap 2", Float) = 0 + + [HideInInspector] m_start_specular("Specular Reflections", Float) = 0 + [Toggle(_SPECGLOSSMAP)]_EnableSpecular("Enable Specular", Float) = 0 + [Enum(Realistic, 1, Toon, 2, Anisotropic, 3)] _SpecularType("Specular Type", Int) = 1 + _SpecularMinLightBrightness("Min Light Brightness", Range(0, 1)) = 0 + _SpecularTint("Specular Tint", Color) = (.2, .2, .2, 1) + _SpecularMixAlbedoIntoTint("Mix Material Color Into Tint", Range(0, 1)) = 0 + _SpecularSmoothness("Smoothness", Range(-2, 1)) = .75 + _SpecularMap("Specular Map", 2D) = "white" { } + [Toggle(_)]_SpecularInvertSmoothness("Invert Smoothness", Float) = 0 + _SpecularMask("Specular Mask", 2D) = "white" { } + [Enum(Alpha, 0, Grayscale, 1)] _SmoothnessFrom("Smoothness From", Int) = 1 + [HideInInspector] m_start_SpecularToon("Toon", Float) = 0 + [MultiSlider]_SpecularToonInnerOuter("Inner/Outer Edge", Vector) = (0.25, 0.3, 0, 1) + [HideInInspector] m_end_SpecularToon("Toon", Float) = 0 + [HideInInspector] m_start_Anisotropic("Anisotropic", Float) = 0 + [Enum(Tangent, 0, Bitangent, 1)] _SpecWhatTangent("(Bi)Tangent?", Int) = 0 + _AnisoSpec1Alpha("Spec1 Alpha", Range(0, 1)) = 1 + _AnisoSpec2Alpha("Spec2 Alpha", Range(0, 1)) = 1 + //_Spec1Offset ("Spec1 Offset", Float) = 0 + //_Spec1JitterStrength ("Spec1 Jitter Strength", Float) = 1.0 + _Spec2Smoothness("Spec2 Smoothness", Range(0, 1)) = 0 + //_Spec2Offset ("Spec2 Offset", Float) = 0 + //_Spec2JitterStrength ("Spec2 Jitter Strength", Float) = 1.0 + [Toggle(_)]_AnisoUseTangentMap("Use Directional Map?", Float) = 0 + _AnisoTangentMap("Anisotropic Directional Map", 2D) = "bump" { } + //_ShiftTexture ("Shift Texture", 2D) = "black" { } + [HideInInspector] m_end_Anisotropic("Anisotropic", Float) = 0 + [HideInInspector] m_end_specular("Specular Reflections", Float) = 0 + + [HideInInspector] m_Special_Effects("Special Effects", Float) = 0 + [HideInInspector] m_start_emissionOptions("Emission / Glow", Float) = 0 + [Toggle(_EMISSION)]_EnableEmission("Enable Emission", Float) = 0 + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _EmissionUV("Emission UV#", Int) = 0 + [HDR]_EmissionColor("Emission Color", Color) = (1, 1, 1, 1) + [PanningTexture]_EmissionMap("Emission Map", 2D) = "white" { } + [PanningTexture]_EmissionMask("Emission Mask", 2D) = "white" { } + [HideInInspector][Vector2]_EmissionMapPan("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector][Vector2]_EmissionMaskPan("Panning", Vector) = (0, 0, 0, 0) + _EmissionStrength("Emission Strength", Range(0, 20)) = 0 + // Inward out emission + [HideInInspector] m_start_CenterOutEmission("Center Out Emission", Float) = 0 + [Toggle(_)]_EmissionCenterOutEnabled("Enable Center Out", Float) = 0 + _EmissionCenterOutSpeed("Flow Speed", Float) = 5 + [HideInInspector] m_end_CenterOutEmission("inward out emission", Float) = 0 + //Glow in the dark Emission + [HideInInspector] m_start_glowInDarkEmissionOptions("Glow In The Dark Emission (Requires Lighting Enabled)", Float) = 0 + [Toggle(_)]_EnableGITDEmission("Enable Glow In The Dark", Float) = 0 + [Enum(World, 0, Mesh, 1)] _GITDEWorldOrMesh("Lighting Type", Int) = 0 + _GITDEMinEmissionMultiplier("Min Emission Multiplier", Range(0, 1)) = 1 + _GITDEMaxEmissionMultiplier("Max Emission Multiplier", Range(0, 1)) = 0 + _GITDEMinLight("Min Lighting", Range(0, 1)) = 0 + _GITDEMaxLight("Max Lighting", Range(0, 1)) = 1 + [HideInInspector] m_end_glowInDarkEmissionOptions("Glow In The Dark Emission (Requires Lighting Enabled)", Float) = 0 + + [HideInInspector] m_start_blinkingEmissionOptions("Blinking Emission", Float) = 0 + _EmissiveBlink_Min("Emissive Blink Min", Float) = 1 + _EmissiveBlink_Max("Emissive Blink Max", Float) = 1 + _EmissiveBlink_Velocity("Emissive Blink Velocity", Float) = 4 + [HideInInspector] m_end_blinkingEmissionOptions("Blinking Emission", Float) = 0 + + [HideInInspector] m_start_scrollingEmissionOptions("Scrolling Emission", Float) = 0 + [Toggle(_)] _ScrollingEmission("Enable Scrolling Emission", Float) = 0 + _EmissiveScroll_Direction("Emissive Scroll Direction", Vector) = (0, -10, 0, 0) + _EmissiveScroll_Width("Emissive Scroll Width", Float) = 10 + _EmissiveScroll_Velocity("Emissive Scroll Velocity", Float) = 10 + _EmissiveScroll_Interval("Emissive Scroll Interval", Float) = 20 + [HideInInspector] m_end_scrollingEmissionOptions("Scrolling Emission", Float) = 0 + [HideInInspector] m_end_emissionOptions("Emission / Glow", Float) = 0 + + [HideInInspector] m_start_flipBook("Flipbook", Float) = 0 + [Toggle(_FLIPBOOK_BLENDING)]_EnableFlipbook("Enable Flipbook", Float) = 0 + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _FlipbookUV("Flipbook UV#", Int) = 0 + [TextureArray]_FlipbookTexArray("Texture Array--{reference_property:_FlipbookTotalFrames}", 2DArray) = "" { } + _FlipbookColor("Color & alpha", Color) = (1, 1, 1, 1) + _FlipbookTotalFrames("Total Frames", Int) = 1 + _FlipbookFPS("FPS", Float) = 30.0 + _FlipbookScaleOffset("Scale | Offset", Vector) = (1, 1, 0, 0) + [Toggle(_)]_FlipbookTiled("Tiled?", Float) = 0 + _FlipbookEmissionStrength("Emission Strength", Range(0, 20)) = 0 + _FlipbookRotation("Rotation", Range(0, 360)) = 0 + _FlipbookReplace("Replace", Range(0, 1)) = 1 + _FlipbookMultiply("Multiply", Range(0, 1)) = 0 + _FlipbookAdd("Add", Range(0, 1)) = 0 + [HideInInspector] m_start_manualFlipbookControl("Manual Control", Float) = 0 + _FlipbookCurrentFrame("Current Frame", Float) = -1 + [HideInInspector] m_end_manualFlipbookControl("Manual Control", Float) = 0 + [HideInInspector] m_end_flipBook("Flipbook", Float) = 0 + + [HideInInspector] m_start_dissolve("Dissolve", Float) = 0 + [Toggle(_ALPHABLEND_ON)]_EnableDissolve("Enable Dissolve", Float) = 0 + [Enum(Basic, 1, Point2Point, 2)] _DissolveType("Dissolve Type", Int) = 1 + _DissolveEdgeWidth("Edge Width", Range(0, .5)) = 0.025 + _DissolveEdgeHardness("Edge Hardness", Range(0, 1)) = 0.5 + _DissolveEdgeColor("Edge Color", Color) = (1, 1, 1, 1) + [Gradient]_DissolveEdgeGradient("Edge Gradient", 2D) = "white" { } + _DissolveEdgeEmission("Edge Emission", Range(0, 20)) = 0 + _DissolveTextureColor("Dissolve to Color", Color) = (1, 1, 1, 1) + [PanningTexture]_DissolveToTexture("Dissolve to Texture", 2D) = "white" { } + _DissolveToEmissionStrength("Dissolve to Emission Strength", Range(0, 20)) = 0 + [HideInInspector][Vector2]_DissolveToPanning("Panning", Vector) = (0, 0, 0, 0) + [PanningTexture]_DissolveNoiseTexture("Dissolve Noise", 2D) = "white" { } + [Toggle(_)]_DissolveInvertNoise("Invert Noise", Float) = 0 + [PanningTexture]_DissolveDetailNoise("Dissolve Detail Noise", 2D) = "black" { } + [Toggle(_)]_DissolveInvertDetailNoise("Invert Detail Noise", Float) = 0 + _DissolveDetailStrength("Dissolve Detail Strength", Range(0, 1)) = 0.1 + [HideInInspector][Vector2]_DissolveNoisePan("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector][Vector2]_DissolveDetailPan("Panning", Vector) = (0, 0, 0, 0) + _DissolveAlpha("Dissolve Alpha", Range(0, 1)) = 0 + _DissolveMask("Dissolve Mask", 2D) = "white" { } + [Toggle(_)]_ContinuousDissolve("Continuous Dissolve Speed", Float) = 0 + [HideInInspector] m_start_pointToPoint("point to point", Float) = 0 + [Enum(Local, 0, World, 1)] _DissolveP2PWorldLocal("World/Local", Int) = 0 + _DissolveP2PEdgeLength("Edge Length", Float) = 0.1 + [Vector3]_DissolveStartPoint("Start Point", Vector) = (0, -1, 0, 0) + [Vector3]_DissolveEndPoint("End Point", Vector) = (0, 1, 0, 0) + [HideInInspector] m_end_pointToPoint("Point To Point", Float) = 0 + [HideInInspector] m_end_dissolve("Dissolve", Float) = 0 + + [HideInInspector] m_start_panosphereOptions("Panosphere / Cubemaps", Float) = 0 + [Toggle(_DETAIL_MULX2)]_PanoToggle("Enable Panosphere", Float) = 0 + _PanosphereColor("Color", Color) = (1, 1, 1, 1) + _PanosphereTexture("Texture", 2D) = "white" { } + _PanoMapTexture("Mask", 2D) = "white" { } + _PanoEmission("Emission Strength", Range(0, 10)) = 0 + _PanoBlend("Alpha", Range(0, 1)) = 0 + [Vector3]_PanospherePan("Pan Speed", Vector) = (0, 0, 0, 0) + [Toggle(_)]_PanoCubeMapToggle("Use Cubemap", Float) = 0 + [TextureNoSO]_PanoCubeMap("CubeMap", Cube) = "" { } + [HideInInspector] m_end_panosphereOptions("Panosphere / Cubemaps", Float) = 0 + + [HideInInspector] m_start_mirrorOptions("Mirror", Float) = 0 + [Toggle(_REQUIRE_UV2)]_EnableMirrorOptions("Enable Mirror Options", Float) = 0 + [Enum(ShowInBoth, 0, ShowOnlyInMirror, 1, DontShowInMirror, 2)] _Mirror("Show in mirror", Int) = 0 + [Toggle(_)]_EnableMirrorTexture("Enable Mirror Texture", Float) = 0 + _MirrorTexture("Mirror Tex", 2D) = "white" { } + [HideInInspector] m_end_mirrorOptions("Mirror", Float) = 0 + + [HideInInspector] m_start_distanceFade("Distance Fade", Float) = 0 + _MainMinAlpha("Minimum Alpha", Range(0, 1)) = 0 + _MainFadeTexture("Fade Map", 2D) = "white" { } + [Vector2]_MainDistanceFade("Distance Fade X to Y", Vector) = (0, 0, 0, 0) + [HideInInspector] m_end_distanceFade("Distance Fade", Float) = 0 + + [HideInInspector] m_start_angularFade("Angular Fade", Float) = 0 + [Toggle(_SUNDISK_NONE)]_EnableRandom("Enable Angular Fade", Float) = 0 + [Enum(Camera Face Model, 0, Model Face Camera, 1, Face Each Other, 2)] _AngleType("Angle Type", Int) = 0 + [Enum(Model, 0, Vertex, 1)] _AngleCompareTo("Model or Vert Positon", Int) = 0 + [Vector3]_AngleForwardDirection("Forward Direction", Vector) = (0, 0, 1, 0) + _CameraAngleMin("Camera Angle Min", Range(0, 180)) = 45 + _CameraAngleMax("Camera Angle Max", Range(0, 180)) = 90 + _ModelAngleMin("Model Angle Min", Range(0, 180)) = 45 + _ModelAngleMax("Model Angle Max", Range(0, 180)) = 90 + _AngleMinAlpha("Min Alpha", Range(0, 1)) = 0 + [HideInInspector] m_end_angularFade("Angular Fade", Float) = 0 + // End Special Effects + + [HideInInspector] m_parallaxMap("Parallax", Float) = 0 + [Toggle(_PARALLAXMAP)]_ParallaxMap("Enable Parallax FX", Float) = 0 + [Toggle(_)]_ParallaxHeightMapEnabled("Enable Parallax Height", Float) = 0 + [Toggle(_)]_ParallaxInternalMapEnabled("Enable Parallax Internal", Float) = 0 + [HideInInspector] m_start_parallaxHeightmap("Heightmap", Float) = 0 + _ParallaxHeightMap("Height Map", 2D) = "black" { } + _ParallaxStrength("Parallax Strength", Range(0, 1)) = 0 + [HideInInspector] m_end_parallaxHeightmap("Heightmap", Float) = 0 + [HideInInspector] m_start_parallaxInternal("Internal", Float) = 0 + [Enum(Basic, 0, HeightMap, 1)] _ParallaxInternalHeightmapMode("Parallax Mode", Int) = 0 + [Toggle(_)]_ParallaxInternalHeightFromAlpha("HeightFromAlpha", Float) = 0 + _ParallaxInternalMap("Internal Map", 2D) = "black" { } + _ParallaxInternalIterations("Parallax Internal Iterations", Range(1, 50)) = 1 + _ParallaxInternalMinDepth("Min Depth", Float) = 0 + _ParallaxInternalMaxDepth("Max Depth", Float) = 1 + _ParallaxInternalMinFade("Min Depth Brightness", Range(0, 5)) = 0 + _ParallaxInternalMaxFade("Max Depth Brightness", Range(0, 5)) = 1 + _ParallaxInternalMinColor("Min Depth Color", Color) = (1, 1, 1, 1) + _ParallaxInternalMaxColor("Max Depth Color", Color) = (1, 1, 1, 1) + [Vector2]_ParallaxInternalPanSpeed("Pan Speed", Vector) = (0, 0, 0, 0) + [Vector2]_ParallaxInternalPanDepthSpeed("Per Level Speed Multiplier", Vector) = (0, 0, 0, 0) + [HideInInspector] m_end_parallaxInternal("Internal", Float) = 0 + [HideInInspector] m_start_parallaxAdvanced("Advanced", Float) = 0 + _ParallaxBias("Parallax Bias (0.42)", Float) = 0.42 + [HideInInspector] m_end_parallaxAdvanced("Advanced", Float) = 0 + + [HideInInspector] m_renderingOptions("Rendering Options", Float) = 0 + [Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", Float) = 2 + [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("ZTest", Float) = 4 + [Enum(UnityEngine.Rendering.BlendMode)] _SourceBlend("Source Blend", Float) = 5 + [Enum(UnityEngine.Rendering.BlendMode)] _DestinationBlend("Destination Blend", Float) = 10 + [Enum(Off, 0, On, 1)] _ZWrite("ZWrite", Int) = 1 + _ZBias("ZBias", Float) = 0.0 + [Toggle(_)]_IgnoreFog("Ignore Fog", Float) = 0 + //[HideInInspector] Instancing ("Instancing", Float) = 0 //add this property for instancing variants settings to be shown + + [HideInInspector] m_start_StencilPassOptions("Stencil", Float) = 0 + [IntRange] _StencilRef("Stencil Reference Value", Range(0, 255)) = 0 + //[IntRange] _StencilReadMaskRef ("Stencil ReadMask Value", Range(0, 255)) = 0 + //[IntRange] _StencilWriteMaskRef ("Stencil WriteMask Value", Range(0, 255)) = 0 + [Enum(UnityEngine.Rendering.StencilOp)] _StencilPassOp("Stencil Pass Op", Float) = 0 + [Enum(UnityEngine.Rendering.StencilOp)] _StencilFailOp("Stencil Fail Op", Float) = 0 + [Enum(UnityEngine.Rendering.StencilOp)] _StencilZFailOp("Stencil ZFail Op", Float) = 0 + [Enum(UnityEngine.Rendering.CompareFunction)] _StencilCompareFunction("Stencil Compare Function", Float) = 8 + [HideInInspector] m_end_StencilPassOptions("Stencil", Float) = 0 + + [HideInInspector] m_start_debugOptions("Debug", Float) = 0 + [Toggle(_COLOROVERLAY_ON)]_DebugDisplayDebug("Display Debug Info", Float) = 0 + [Enum(Off, 0, Vertex Normal, 1, Pixel Normal, 2, Tangent, 3, Binormal, 4)] _DebugMeshData("Mesh Data", Int) = 0 + [Enum(Off, 0, Attenuation, 1, Direct Lighting, 2, Indirect Lighting, 3, light Map, 4, Ramped Light Map, 5, Final Lighting, 6)] _DebugLightingData("Lighting Data", Int) = 0 + [Enum(Off, 0, finalSpecular, 1, tangentDirectionMap, 2, shiftTexture, 3)] _DebugSpecularData("Specular Data", Int) = 0 + [Enum(Off, 0, View Dir, 1, Tangent View Dir, 2, Forward Dir, 3, WorldPos, 4, View Dot Normal, 5)] _DebugCameraData("Camera Data", Int) = 0 + [HideInInspector] m_end_debugOptions("Debug", Float) = 0 + } + + CustomEditor "Thry.ShaderEditor" + SubShader{ + Tags { "RenderType" = "Opaque" } + LOD 200 + + CGPROGRAM + // Physically based Standard lighting model, and enable shadows on all light types + #pragma surface surf Standard fullforwardshadows + + // Use shader model 3.0 target, to get nicer looking lighting + #pragma target 3.0 + + sampler2D _MainTex; + + struct Input { + float2 uv_MainTex; + }; + + half _Glossiness; + half _Metallic; + fixed4 _Color; + + // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. + // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. + // #pragma instancing_options assumeuniformscaling + UNITY_INSTANCING_BUFFER_START(Props) + // put more per-instance properties here + UNITY_INSTANCING_BUFFER_END(Props) + + void surf(Input IN, inout SurfaceOutputStandard o) { + // Albedo comes from a texture tinted by color + fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; + o.Albedo = c.rgb; + // Metallic and smoothness come from slider variables + o.Metallic = _Metallic; + o.Smoothness = _Glossiness; + o.Alpha = c.a; + } + ENDCG + } + FallBack "Diffuse" +} diff --git a/Scripts/ThryEditor/Examples/Example2.shader b/Scripts/ThryEditor/Examples/Example2.shader new file mode 100644 index 0000000..6cdcb28 --- /dev/null +++ b/Scripts/ThryEditor/Examples/Example2.shader @@ -0,0 +1,435 @@ +Shader "Thry/Example 2" +{ + Properties + { + [HideInInspector] shader_is_using_thry_editor("", Float)=0 + [HideInInspector] shader_master_label("Example 2", Float) = 0 + [HideInInspector] shader_presets("ThryPresetsExample", Float) = 0 + [HideInInspector] shader_properties_label_file("ThryLabelExample", Float) = 0 + + [HideInInspector] footer_website("", Float) = 0 + [HideInInspector] footer_github("", Float) = 0 + + + shader_properties_locale("locale::locale--{file_name:thry_locale_example}", Float) = 0 + [Enum(Cutout,0,Transparent,1)]variant_selector("Variant--{on_value_actions:[{value:0,actions:[{type:SET_PROPERTY,data:_ZWrite=1},{type:SET_SHADER,data:Thry/Example 1}]},{value:1,actions:[{type:SET_PROPERTY,data:_ZWrite=0},{type:SET_SHADER,data:Thry/Example 2}]}]}",Float) = 0 + + [HideInInspector] m_mainOptions("Main", Float) = 0 + _Color("Color & Alpha", Color) = (1, 1, 1, 1) + [Helpbox]_HelpboxForSomething("Alpha is controlled in the color", Float) = 1 + _Saturation("Saturation", Range(-1, 1)) = 0 + _MainVertexColoring("Use Vertex Color", Range(0,1)) = 0 + _MainEmissionStrength("Basic Emission", Range(0, 20)) = 0 + [Curve]_MainTex("Texture", 2D) = "white" { } + [PanningTexture][Normal]_BumpMap("Normal Map", 2D) = "bump" { } + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _BumpMapUV("Normal UV#", Int) = 0 + [HideInInspector][Vector2]_MainNormalPan("Panning", Vector) = (0, 0, 0, 0) + _BumpScale("Normal Intensity", Range(0, 10)) = 1 + _AlphaMask("Alpha Mask", 2D) = "white" { } + [Vector2]_GlobalPanSpeed("Global Pan Speed", Vector) = (0, 0, 0, 0) + + [HideInInspector] m_start_Alpha("Alpha Options--{altClick:{type:URL,data:https://thryrallo.de}}", Float) = 0 + _Clip("Alpha Cuttoff", Range(0, 1.001)) = 0.5 + [Toggle(_)]_ForceOpaque("Force Opaque", Float) = 0 + [Toggle(_)]_MainAlphaToCoverage("Alpha To Coverage", Float) = 1 + _MainMipScale("Mip Level Alpha Scale", Range(0, 1)) = 0.25 + [HideInInspector] m_end_Alpha("Alpha Options", Float) = 0 + + [HideInInspector] m_start_DetailOptions("Details", Float) = 0 + _DetailMask("Detail Mask (R:Texture, G:Normal)", 2D) = "white" { } + [PanningTexture]_DetailTex("Detail Texture", 2D) = "gray" { } + [HideInInspector][Vector2]_DetailTexturePan("Panning", Vector) = (0, 0, 0, 0) + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _DetailTexUV("Detail Tex UV#", Int) = 0 + _DetailTexIntensity("Detail Tex Intensity", Range(0, 10)) = 1 + _DetailBrightness("Detail Brightness:", Range(0, 2)) = 1 + _DetailTint("Detail Tint", Color) = (1, 1, 1) + [Normal]_DetailNormalMap("Detail Normal", 2D) = "bump" { } + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _DetailNormalUV("Detail Normal UV#", Int) = 0 + _DetailNormalMapScale("Detail Normal Intensity", Range(0, 10)) = 1 + [HideInInspector][Vector2]_MainDetailNormalPan("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector] m_end_DetailOptions("Details", Float) = 0 + + [HideInInspector] m_lightingOptions("Lighting Options", Float) = 0 + [HideInInspector] m_start_Lighting("Light and Shadow", Float) = 0 + [Toggle(_NORMALMAP)]_EnableLighting("Enable Lighting", Float) = 1 + [HideInInspector] g_start_l("", Int) = 0 + [Enum(Natural, 0, Controlled, 1, Standardish, 2)] _LightingType("Lighting Type", Int) = 1 + [Gradient]_ToonRamp("Lighting Ramp", 2D) = "white" { } + _LightingShadowMask("Shadow Mask (R)", 2D) = "white" { } + _ShadowStrength("Shadow Strength", Range(0, 1)) = .2 + _ShadowOffset("Shadow Offset", Range(-1, 1)) = 0 + _AOMap("AO Map", 2D) = "white" { } + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _LightingAOUV("AO Map UV#", Int) = 0 + _AOStrength("AO Strength", Range(0, 1)) = 1 + _LightingMinLightBrightness("Min Brightness", Range(0,1)) = 0 + [HideInInspector] m_start_lightingStandard("Standardish Settings", Float) = 0 + _LightingStandardSmoothness("Smoothness", Range(0, 1)) = 0 + [HideInInspector] m_end_lightingStandard("Standardish Settings", Float) = 0 + [HideInInspector] m_start_lightingAdvanced("Advanced", Float) = 0 + _LightingIndirectContribution("Indirect Contribution", Range(0, 1)) = .25 + _AdditiveSoftness("Additive Softness", Range(0, 0.5)) = 0.005 + _AdditiveOffset("Additive Offset", Range(-0.5, 0.5)) = 0 + _LightingAdditiveIntensity("Additive Intensity", Range(0,1)) = 1 + _AttenuationMultiplier("Attenuation", Range(0, 1)) = 0 + [HideInInspector] m_end_lightingAdvanced("Advanced", Float) = 0 + [HideInInspector] m_start_lightingBeta("Beta", Float) = 0 + [Toggle(_)]_LightingStandardControlsToon("Standard Lighting Controls Toon Ramp", Float) = 0 + [HideInInspector] m_end_lightingBeta("Beta", Float) = 0 + [HideInInspector] g_end_l("", Int) = 0 + [HideInInspector] m_end_Lighting("Light and Shadow", Float) = 0 + + [HideInInspector] m_start_subsurface("Subsurface Scattering", Float) = 0 + [Toggle(_TERRAIN_NORMAL_MAP)]_EnableSSS("Enable Subsurface Scattering", Float) = 0 + _SSSColor("Subsurface Color", Color) = (1, 1, 1, 1) + _SSSThicknessMap("Thickness Map", 2D) = "black" { } + _SSSThicknessMod("Thickness mod", Range(-1, 1)) = 0 + _SSSSCale("Light Strength", Range(0, 1)) = 0 + _SSSPower("Light Spread", Range(1, 100)) = 1 + _SSSDistortion("Light Distortion", Range(0, 1)) = 0 + [HideInInspector] m_end_subsurface("Subsurface Scattering", Float) = 0 + + [HideInInspector] m_start_rimLightOptions("Rim Lighting", Float) = 0 + [Toggle(_GLOSSYREFLECTIONS_OFF)]_EnableRimLighting("Enable Rim Lighting", Float) = 0 + [Toggle(_)]_RimLightingInvert("Invert Rim Lighting", Float) = 0 + _RimLightColor("Rim Color", Color) = (1, 1, 1, 1) + _RimWidth("Rim Width", Range(0, 1)) = 0.8 + _RimSharpness("Rim Sharpness", Range(0, 1)) = .25 + _RimStrength("Rim Emission", Range(0, 20)) = 0 + _RimBrighten("Rim Color Brighten", Range(0, 3)) = 0 + _RimLightColorBias("Rim Color Bias", Range(0, 1)) = 0 + [PanningTexture]_RimTex("Rim Texture", 2D) = "white" { } + _RimMask("Rim Mask", 2D) = "white" { } + [HideInInspector][Vector2]_RimTexPanSpeed("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector] m_start_reflectionRim("Environmental Rim", Float) = 0 + [Toggle(_)]_EnableEnvironmentalRim("Enable Environmental Rim", Float) = 0 + _RimEnviroMask("Mask", 2D) = "white" { } + _RimEnviroBlur("Blur", Range(0, 1)) = 0.7 + _RimEnviroWidth("Rim Width", Range(0, 1)) = 0.45 + _RimEnviroSharpness("Rim Sharpness", Range(0, 1)) = 0 + _RimEnviroMinBrightness("Min Brightness Threshold", Range(0, 2)) = 0 + [HideInInspector] m_end_reflectionRim("Environmental Rim", Float) = 0 + [HideInInspector] m_start_rimWidthNoise("Width Noise", Float) = 0 + [PanningTexture]_RimWidthNoiseTexture("Rim Width Noise", 2D) = "black" { } + _RimWidthNoiseStrength("Intensity", Range(0, 1)) = 0.1 + [HideInInspector][Vector2]_RimWidthNoisePan("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector] m_end_rimWidthNoise("Width Noise", Float) = 0 + [HideInInspector] m_start_ShadowMix("Shadow Mix", Float) = 0 + _ShadowMix("Shadow Mix In", Range(0, 1)) = 0 + _ShadowMixThreshold("Shadow Mix Threshold", Range(0, 1)) = .5 + _ShadowMixWidthMod("Shadow Mix Width Mod", Range(0, 10)) = .5 + [HideInInspector] m_end_ShadowMix("Shadow Mix", Float) = 0 + [HideInInspector] m_end_rimLightOptions("Rim Lighting", Float) = 0 + + [HideInInspector] m_start_bakedLighting("Baked Lighting", Float) = 0 + _GIEmissionMultiplier("GI Emission Multiplier", Float) = 1 + [HideInInspector] DSGI("DSGI", Float) = 0 //add this property for double sided illumination settings to be shown + [HideInInspector] LightmapFlags("Lightmap Flags", Float) = 0 //add this property for lightmap flags settings to be shown + [HideInInspector] m_end_bakedLighting("Baked Lighting", Float) = 0 + + [HideInInspector] m_reflectionOptions("Reflections", Float) = 0 + [HideInInspector] m_start_Metallic("Metallics", Float) = 0 + [Toggle(_METALLICGLOSSMAP)]_EnableMetallic("Enable Metallics", Float) = 0 + _CubeMap("Baked CubeMap", Cube) = "" { } + [Toggle(_)]_SampleWorld("Force Baked Cubemap", Range(0, 1)) = 0 + _MetalReflectionTint("Reflection Tint", Color) = (1, 1, 1) + _MetallicMask("Metallic Mask", 2D) = "white" { } + _Metallic("Metallic", Range(0, 1)) = 0 + _SmoothnessMask("Smoothness Map", 2D) = "white" { } + [Toggle(_)]_InvertSmoothness("Invert Smoothness Map", Range(0, 1)) = 0 + _Smoothness("Smoothness", Range(0, 1)) = 0 + [HideInInspector] m_end_Metallic("Metallics", Float) = 0 + + [HideInInspector] m_start_clearCoat("Clear Coat", Float) = 0 + [Toggle(_COLORCOLOR_ON)]_EnableClearCoat("Enable Clear Coat", Float) = 0 + [Enum(Vertex, 0, Pixel, 1)] _ClearCoatNormalToUse("What Normal?", Int) = 0 + _ClearCoatCubeMap("Baked CubeMap", Cube) = "" { } + [Toggle(_)]_ClearCoatSampleWorld("Force Baked Cubemap", Range(0, 1)) = 0 + _ClearCoatTint("Reflection Tint", Color) = (1, 1, 1) + _ClearCoatMask("Mask", 2D) = "white" { } + _ClearCoat("Clear Coat", Range(0, 1)) = 1 + _ClearCoatSmoothnessMask("Smoothness Map", 2D) = "white" { } + [Toggle(_)]_ClearCoatInvertSmoothness("Invert Smoothness Map", Range(0, 1)) = 0 + _ClearCoatSmoothness("Smoothness", Range(0, 1)) = 0 + [Toggle(_)]_ClearCoatForceLighting("Force Lighting", Float) = 0 + [HideInInspector] m_end_clearCoat("Clear Coat", Float) = 0 + + [HideInInspector] m_start_matcap("Matcap / Sphere Textures", Float) = 0 + [Toggle(_COLORADDSUBDIFF_ON)]_MatcapEnable("Enable Matcap", Float) = 0 + _MatcapColor("Color", Color) = (1, 1, 1, 1) + [TextureNoSO]_Matcap("Matcap", 2D) = "white" { } + _MatcapBorder("Border", Range(0, .5)) = 0.43 + _MatcapMask("Mask", 2D) = "white" { } + _MatcapIntensity("Intensity", Range(0, 5)) = 1 + _MatcapLightMask("Hide in Shadow", Range(0, 1)) = 0 + _MatcapReplace("Replace With Matcap", Range(0, 1)) = 1 + _MatcapMultiply("Multiply Matcap", Range(0, 1)) = 0 + _MatcapAdd("Add Matcap", Range(0, 1)) = 0 + [Enum(Vertex, 0, Pixel, 1)] _MatcapNormal("Normal to use", Int) = 1 + [HideInInspector] m_end_matcap("Matcap", Float) = 0 + [HideInInspector] m_start_Matcap2("Matcap 2", Float) = 0 + [Toggle(_)]_Matcap2Enable("Enable Matcap 2", Float) = 0 + _Matcap2Color("Color", Color) = (1, 1, 1, 1) + [TextureNoSO]_Matcap2("Matcap", 2D) = "white" { } + _Matcap2Border("Border", Range(0, .5)) = 0.43 + _Matcap2Mask("Mask", 2D) = "white" { } + _Matcap2Intensity("Intensity", Range(0, 5)) = 1 + _Matcap2LightMask("Hide in Shadow", Range(0, 1)) = 0 + _Matcap2Replace("Replace With Matcap", Range(0, 1)) = 0 + _Matcap2Multiply("Multiply Matcap", Range(0, 1)) = 0 + _Matcap2Add("Add Matcap", Range(0, 1)) = 0 + [Enum(Vertex, 0, Pixel, 1)] _Matcap2Normal("Normal to use", Int) = 1 + [HideInInspector] m_end_Matcap2("Matcap 2", Float) = 0 + + [HideInInspector] m_start_specular("Specular Reflections", Float) = 0 + [Toggle(_SPECGLOSSMAP)]_EnableSpecular("Enable Specular", Float) = 0 + [Enum(Realistic, 1, Toon, 2, Anisotropic, 3)] _SpecularType("Specular Type", Int) = 1 + _SpecularMinLightBrightness("Min Light Brightness", Range(0, 1)) = 0 + _SpecularTint("Specular Tint", Color) = (.2, .2, .2, 1) + _SpecularMixAlbedoIntoTint("Mix Material Color Into Tint", Range(0, 1)) = 0 + _SpecularSmoothness("Smoothness", Range(-2, 1)) = .75 + _SpecularMap("Specular Map", 2D) = "white" { } + [Toggle(_)]_SpecularInvertSmoothness("Invert Smoothness", Float) = 0 + _SpecularMask("Specular Mask", 2D) = "white" { } + [Enum(Alpha, 0, Grayscale, 1)] _SmoothnessFrom("Smoothness From", Int) = 1 + [HideInInspector] m_start_SpecularToon("Toon", Float) = 0 + [MultiSlider]_SpecularToonInnerOuter("Inner/Outer Edge", Vector) = (0.25, 0.3, 0, 1) + [HideInInspector] m_end_SpecularToon("Toon", Float) = 0 + [HideInInspector] m_start_Anisotropic("Anisotropic", Float) = 0 + [Enum(Tangent, 0, Bitangent, 1)] _SpecWhatTangent("(Bi)Tangent?", Int) = 0 + _AnisoSpec1Alpha("Spec1 Alpha", Range(0, 1)) = 1 + _AnisoSpec2Alpha("Spec2 Alpha", Range(0, 1)) = 1 + //_Spec1Offset ("Spec1 Offset", Float) = 0 + //_Spec1JitterStrength ("Spec1 Jitter Strength", Float) = 1.0 + _Spec2Smoothness("Spec2 Smoothness", Range(0, 1)) = 0 + //_Spec2Offset ("Spec2 Offset", Float) = 0 + //_Spec2JitterStrength ("Spec2 Jitter Strength", Float) = 1.0 + [Toggle(_)]_AnisoUseTangentMap("Use Directional Map?", Float) = 0 + _AnisoTangentMap("Anisotropic Directional Map", 2D) = "bump" { } + //_ShiftTexture ("Shift Texture", 2D) = "black" { } + [HideInInspector] m_end_Anisotropic("Anisotropic", Float) = 0 + [HideInInspector] m_end_specular("Specular Reflections", Float) = 0 + + [HideInInspector] m_Special_Effects("Special Effects", Float) = 0 + [HideInInspector] m_start_emissionOptions("Emission / Glow", Float) = 0 + [Toggle(_EMISSION)]_EnableEmission("Enable Emission", Float) = 0 + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _EmissionUV("Emission UV#", Int) = 0 + [HDR]_EmissionColor("Emission Color", Color) = (1, 1, 1, 1) + [PanningTexture]_EmissionMap("Emission Map", 2D) = "white" { } + [PanningTexture]_EmissionMask("Emission Mask", 2D) = "white" { } + [HideInInspector][Vector2]_EmissionMapPan("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector][Vector2]_EmissionMaskPan("Panning", Vector) = (0, 0, 0, 0) + _EmissionStrength("Emission Strength", Range(0, 20)) = 0 + // Inward out emission + [HideInInspector] m_start_CenterOutEmission("Center Out Emission", Float) = 0 + [Toggle(_)]_EmissionCenterOutEnabled("Enable Center Out", Float) = 0 + _EmissionCenterOutSpeed("Flow Speed", Float) = 5 + [HideInInspector] m_end_CenterOutEmission("inward out emission", Float) = 0 + //Glow in the dark Emission + [HideInInspector] m_start_glowInDarkEmissionOptions("Glow In The Dark Emission (Requires Lighting Enabled)", Float) = 0 + [Toggle(_)]_EnableGITDEmission("Enable Glow In The Dark", Float) = 0 + [Enum(World, 0, Mesh, 1)] _GITDEWorldOrMesh("Lighting Type", Int) = 0 + _GITDEMinEmissionMultiplier("Min Emission Multiplier", Range(0, 1)) = 1 + _GITDEMaxEmissionMultiplier("Max Emission Multiplier", Range(0, 1)) = 0 + _GITDEMinLight("Min Lighting", Range(0, 1)) = 0 + _GITDEMaxLight("Max Lighting", Range(0, 1)) = 1 + [HideInInspector] m_end_glowInDarkEmissionOptions("Glow In The Dark Emission (Requires Lighting Enabled)", Float) = 0 + + [HideInInspector] m_start_blinkingEmissionOptions("Blinking Emission", Float) = 0 + _EmissiveBlink_Min("Emissive Blink Min", Float) = 1 + _EmissiveBlink_Max("Emissive Blink Max", Float) = 1 + _EmissiveBlink_Velocity("Emissive Blink Velocity", Float) = 4 + [HideInInspector] m_end_blinkingEmissionOptions("Blinking Emission", Float) = 0 + + [HideInInspector] m_start_scrollingEmissionOptions("Scrolling Emission", Float) = 0 + [Toggle(_)] _ScrollingEmission("Enable Scrolling Emission", Float) = 0 + _EmissiveScroll_Direction("Emissive Scroll Direction", Vector) = (0, -10, 0, 0) + _EmissiveScroll_Width("Emissive Scroll Width", Float) = 10 + _EmissiveScroll_Velocity("Emissive Scroll Velocity", Float) = 10 + _EmissiveScroll_Interval("Emissive Scroll Interval", Float) = 20 + [HideInInspector] m_end_scrollingEmissionOptions("Scrolling Emission", Float) = 0 + [HideInInspector] m_end_emissionOptions("Emission / Glow", Float) = 0 + + [HideInInspector] m_start_flipBook("Flipbook", Float) = 0 + [Toggle(_FLIPBOOK_BLENDING)]_EnableFlipbook("Enable Flipbook", Float) = 0 + [Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)] _FlipbookUV("Flipbook UV#", Int) = 0 + [TextureArray]_FlipbookTexArray("Texture Array--{reference_property:_FlipbookTotalFrames}", 2DArray) = "" { } + _FlipbookColor("Color & alpha", Color) = (1, 1, 1, 1) + _FlipbookTotalFrames("Total Frames", Int) = 1 + _FlipbookFPS("FPS", Float) = 30.0 + _FlipbookScaleOffset("Scale | Offset", Vector) = (1, 1, 0, 0) + [Toggle(_)]_FlipbookTiled("Tiled?", Float) = 0 + _FlipbookEmissionStrength("Emission Strength", Range(0, 20)) = 0 + _FlipbookRotation("Rotation", Range(0, 360)) = 0 + _FlipbookReplace("Replace", Range(0, 1)) = 1 + _FlipbookMultiply("Multiply", Range(0, 1)) = 0 + _FlipbookAdd("Add", Range(0, 1)) = 0 + [HideInInspector] m_start_manualFlipbookControl("Manual Control", Float) = 0 + _FlipbookCurrentFrame("Current Frame", Float) = -1 + [HideInInspector] m_end_manualFlipbookControl("Manual Control", Float) = 0 + [HideInInspector] m_end_flipBook("Flipbook", Float) = 0 + + [HideInInspector] m_start_dissolve("Dissolve", Float) = 0 + [Toggle(_ALPHABLEND_ON)]_EnableDissolve("Enable Dissolve", Float) = 0 + [Enum(Basic, 1, Point2Point, 2)] _DissolveType("Dissolve Type", Int) = 1 + _DissolveEdgeWidth("Edge Width", Range(0, .5)) = 0.025 + _DissolveEdgeHardness("Edge Hardness", Range(0, 1)) = 0.5 + _DissolveEdgeColor("Edge Color", Color) = (1, 1, 1, 1) + [Gradient]_DissolveEdgeGradient("Edge Gradient", 2D) = "white" { } + _DissolveEdgeEmission("Edge Emission", Range(0, 20)) = 0 + _DissolveTextureColor("Dissolve to Color", Color) = (1, 1, 1, 1) + [PanningTexture]_DissolveToTexture("Dissolve to Texture", 2D) = "white" { } + _DissolveToEmissionStrength("Dissolve to Emission Strength", Range(0, 20)) = 0 + [HideInInspector][Vector2]_DissolveToPanning("Panning", Vector) = (0, 0, 0, 0) + [PanningTexture]_DissolveNoiseTexture("Dissolve Noise", 2D) = "white" { } + [Toggle(_)]_DissolveInvertNoise("Invert Noise", Float) = 0 + [PanningTexture]_DissolveDetailNoise("Dissolve Detail Noise", 2D) = "black" { } + [Toggle(_)]_DissolveInvertDetailNoise("Invert Detail Noise", Float) = 0 + _DissolveDetailStrength("Dissolve Detail Strength", Range(0, 1)) = 0.1 + [HideInInspector][Vector2]_DissolveNoisePan("Panning", Vector) = (0, 0, 0, 0) + [HideInInspector][Vector2]_DissolveDetailPan("Panning", Vector) = (0, 0, 0, 0) + _DissolveAlpha("Dissolve Alpha", Range(0, 1)) = 0 + _DissolveMask("Dissolve Mask", 2D) = "white" { } + [Toggle(_)]_ContinuousDissolve("Continuous Dissolve Speed", Float) = 0 + [HideInInspector] m_start_pointToPoint("point to point", Float) = 0 + [Enum(Local, 0, World, 1)] _DissolveP2PWorldLocal("World/Local", Int) = 0 + _DissolveP2PEdgeLength("Edge Length", Float) = 0.1 + [Vector3]_DissolveStartPoint("Start Point", Vector) = (0, -1, 0, 0) + [Vector3]_DissolveEndPoint("End Point", Vector) = (0, 1, 0, 0) + [HideInInspector] m_end_pointToPoint("Point To Point", Float) = 0 + [HideInInspector] m_end_dissolve("Dissolve", Float) = 0 + + [HideInInspector] m_start_panosphereOptions("Panosphere / Cubemaps", Float) = 0 + [Toggle(_DETAIL_MULX2)]_PanoToggle("Enable Panosphere", Float) = 0 + _PanosphereColor("Color", Color) = (1, 1, 1, 1) + _PanosphereTexture("Texture", 2D) = "white" { } + _PanoMapTexture("Mask", 2D) = "white" { } + _PanoEmission("Emission Strength", Range(0, 10)) = 0 + _PanoBlend("Alpha", Range(0, 1)) = 0 + [Vector3]_PanospherePan("Pan Speed", Vector) = (0, 0, 0, 0) + [Toggle(_)]_PanoCubeMapToggle("Use Cubemap", Float) = 0 + [TextureNoSO]_PanoCubeMap("CubeMap", Cube) = "" { } + [HideInInspector] m_end_panosphereOptions("Panosphere / Cubemaps", Float) = 0 + + [HideInInspector] m_start_mirrorOptions("Mirror", Float) = 0 + [Toggle(_REQUIRE_UV2)]_EnableMirrorOptions("Enable Mirror Options", Float) = 0 + [Enum(ShowInBoth, 0, ShowOnlyInMirror, 1, DontShowInMirror, 2)] _Mirror("Show in mirror", Int) = 0 + [Toggle(_)]_EnableMirrorTexture("Enable Mirror Texture", Float) = 0 + _MirrorTexture("Mirror Tex", 2D) = "white" { } + [HideInInspector] m_end_mirrorOptions("Mirror", Float) = 0 + + [HideInInspector] m_start_distanceFade("Distance Fade", Float) = 0 + _MainMinAlpha("Minimum Alpha", Range(0, 1)) = 0 + _MainFadeTexture("Fade Map", 2D) = "white" { } + [Vector2]_MainDistanceFade("Distance Fade X to Y", Vector) = (0, 0, 0, 0) + [HideInInspector] m_end_distanceFade("Distance Fade", Float) = 0 + + [HideInInspector] m_start_angularFade("Angular Fade", Float) = 0 + [Toggle(_SUNDISK_NONE)]_EnableRandom("Enable Angular Fade", Float) = 0 + [Enum(Camera Face Model, 0, Model Face Camera, 1, Face Each Other, 2)] _AngleType("Angle Type", Int) = 0 + [Enum(Model, 0, Vertex, 1)] _AngleCompareTo("Model or Vert Positon", Int) = 0 + [Vector3]_AngleForwardDirection("Forward Direction", Vector) = (0, 0, 1, 0) + _CameraAngleMin("Camera Angle Min", Range(0, 180)) = 45 + _CameraAngleMax("Camera Angle Max", Range(0, 180)) = 90 + _ModelAngleMin("Model Angle Min", Range(0, 180)) = 45 + _ModelAngleMax("Model Angle Max", Range(0, 180)) = 90 + _AngleMinAlpha("Min Alpha", Range(0, 1)) = 0 + [HideInInspector] m_end_angularFade("Angular Fade", Float) = 0 + // End Special Effects + + [HideInInspector] m_parallaxMap("Parallax", Float) = 0 + [Toggle(_PARALLAXMAP)]_ParallaxMap("Enable Parallax FX", Float) = 0 + [Toggle(_)]_ParallaxHeightMapEnabled("Enable Parallax Height", Float) = 0 + [Toggle(_)]_ParallaxInternalMapEnabled("Enable Parallax Internal", Float) = 0 + [HideInInspector] m_start_parallaxHeightmap("Heightmap", Float) = 0 + _ParallaxHeightMap("Height Map", 2D) = "black" { } + _ParallaxStrength("Parallax Strength", Range(0, 1)) = 0 + [HideInInspector] m_end_parallaxHeightmap("Heightmap", Float) = 0 + [HideInInspector] m_start_parallaxInternal("Internal", Float) = 0 + [Enum(Basic, 0, HeightMap, 1)] _ParallaxInternalHeightmapMode("Parallax Mode", Int) = 0 + [Toggle(_)]_ParallaxInternalHeightFromAlpha("HeightFromAlpha", Float) = 0 + _ParallaxInternalMap("Internal Map", 2D) = "black" { } + _ParallaxInternalIterations("Parallax Internal Iterations", Range(1, 50)) = 1 + _ParallaxInternalMinDepth("Min Depth", Float) = 0 + _ParallaxInternalMaxDepth("Max Depth", Float) = 1 + _ParallaxInternalMinFade("Min Depth Brightness", Range(0, 5)) = 0 + _ParallaxInternalMaxFade("Max Depth Brightness", Range(0, 5)) = 1 + _ParallaxInternalMinColor("Min Depth Color", Color) = (1, 1, 1, 1) + _ParallaxInternalMaxColor("Max Depth Color", Color) = (1, 1, 1, 1) + [Vector2]_ParallaxInternalPanSpeed("Pan Speed", Vector) = (0, 0, 0, 0) + [Vector2]_ParallaxInternalPanDepthSpeed("Per Level Speed Multiplier", Vector) = (0, 0, 0, 0) + [HideInInspector] m_end_parallaxInternal("Internal", Float) = 0 + [HideInInspector] m_start_parallaxAdvanced("Advanced", Float) = 0 + _ParallaxBias("Parallax Bias (0.42)", Float) = 0.42 + [HideInInspector] m_end_parallaxAdvanced("Advanced", Float) = 0 + + [HideInInspector] m_renderingOptions("Rendering Options", Float) = 0 + [Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", Float) = 2 + [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest("ZTest", Float) = 4 + [Enum(UnityEngine.Rendering.BlendMode)] _SourceBlend("Source Blend", Float) = 5 + [Enum(UnityEngine.Rendering.BlendMode)] _DestinationBlend("Destination Blend", Float) = 10 + [Enum(Off, 0, On, 1)] _ZWrite("ZWrite", Int) = 1 + _ZBias("ZBias", Float) = 0.0 + [Toggle(_)]_IgnoreFog("Ignore Fog", Float) = 0 + //[HideInInspector] Instancing ("Instancing", Float) = 0 //add this property for instancing variants settings to be shown + + [HideInInspector] m_start_StencilPassOptions("Stencil", Float) = 0 + [IntRange] _StencilRef("Stencil Reference Value", Range(0, 255)) = 0 + //[IntRange] _StencilReadMaskRef ("Stencil ReadMask Value", Range(0, 255)) = 0 + //[IntRange] _StencilWriteMaskRef ("Stencil WriteMask Value", Range(0, 255)) = 0 + [Enum(UnityEngine.Rendering.StencilOp)] _StencilPassOp("Stencil Pass Op", Float) = 0 + [Enum(UnityEngine.Rendering.StencilOp)] _StencilFailOp("Stencil Fail Op", Float) = 0 + [Enum(UnityEngine.Rendering.StencilOp)] _StencilZFailOp("Stencil ZFail Op", Float) = 0 + [Enum(UnityEngine.Rendering.CompareFunction)] _StencilCompareFunction("Stencil Compare Function", Float) = 8 + [HideInInspector] m_end_StencilPassOptions("Stencil", Float) = 0 + + [HideInInspector] m_start_debugOptions("Debug", Float) = 0 + [Toggle(_COLOROVERLAY_ON)]_DebugDisplayDebug("Display Debug Info", Float) = 0 + [Enum(Off, 0, Vertex Normal, 1, Pixel Normal, 2, Tangent, 3, Binormal, 4)] _DebugMeshData("Mesh Data", Int) = 0 + [Enum(Off, 0, Attenuation, 1, Direct Lighting, 2, Indirect Lighting, 3, light Map, 4, Ramped Light Map, 5, Final Lighting, 6)] _DebugLightingData("Lighting Data", Int) = 0 + [Enum(Off, 0, finalSpecular, 1, tangentDirectionMap, 2, shiftTexture, 3)] _DebugSpecularData("Specular Data", Int) = 0 + [Enum(Off, 0, View Dir, 1, Tangent View Dir, 2, Forward Dir, 3, WorldPos, 4, View Dot Normal, 5)] _DebugCameraData("Camera Data", Int) = 0 + [HideInInspector] m_end_debugOptions("Debug", Float) = 0 + } + + CustomEditor "Thry.ShaderEditor" + SubShader{ + Tags { "RenderType" = "Transparent" "Queue"="Transparent" } + LOD 200 + + CGPROGRAM + // Physically based Standard lighting model, and enable shadows on all light types + #pragma surface surf Standard fullforwardshadows + + // Use shader model 3.0 target, to get nicer looking lighting + #pragma target 3.0 + + sampler2D _MainTex; + + struct Input { + float2 uv_MainTex; + }; + + half _Glossiness; + half _Metallic; + fixed4 _Color; + + // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. + // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. + // #pragma instancing_options assumeuniformscaling + UNITY_INSTANCING_BUFFER_START(Props) + // put more per-instance properties here + UNITY_INSTANCING_BUFFER_END(Props) + + void surf(Input IN, inout SurfaceOutputStandard o) { + // Albedo comes from a texture tinted by color + fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color; + o.Albedo = c.rgb; + // Metallic and smoothness come from slider variables + o.Metallic = _Metallic; + o.Smoothness = _Glossiness; + o.Alpha = c.a; + } + ENDCG + } + FallBack "Diffuse" +} diff --git a/Scripts/ThryEditor/Examples/ThryLabelExample.txt b/Scripts/ThryEditor/Examples/ThryLabelExample.txt new file mode 100644 index 0000000..5793b63 --- /dev/null +++ b/Scripts/ThryEditor/Examples/ThryLabelExample.txt @@ -0,0 +1,6 @@ +m_start_Lighting:=Light and Shadow--{reference_property:_EnableLighting,button_right:{text:Tutorial,action:{type:URL,data:https://youtu.be/2B-EJutVjs8},hover:YouTube}} +_EnableLighting:=Enable Lighting--{hide_in_inspector:true} +g_start_l:=--{condition_enable:{type:PROPERTY_BOOL,data:_EnableLighting}} + +footer_website:={texture:{name:thry_settings_icon,height:32},action:{type:URL,data:https://www.ShaderEditor.thryrallo.de},hover:Website} +footer_github:={text:Github,action:{type:URL,data:https://github.com/thryrallo/ShaderEditor}} diff --git a/Scripts/ThryEditor/Examples/ThryPresetsExample.txt b/Scripts/ThryEditor/Examples/ThryPresetsExample.txt new file mode 100644 index 0000000..e69de29 diff --git a/Scripts/ThryEditor/Examples/thry_locale_example.csv b/Scripts/ThryEditor/Examples/thry_locale_example.csv new file mode 100644 index 0000000..77c5cb2 --- /dev/null +++ b/Scripts/ThryEditor/Examples/thry_locale_example.csv @@ -0,0 +1,3 @@ +,English,German +MainTex,Main Texture,Haupt Texture +locale,Language,Sprache \ No newline at end of file diff --git a/Scripts/ThryEditor/LICENSE b/Scripts/ThryEditor/LICENSE new file mode 100644 index 0000000..7131d6e --- /dev/null +++ b/Scripts/ThryEditor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Thryrallo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Scripts/ThryEditor/README.md b/Scripts/ThryEditor/README.md new file mode 100644 index 0000000..ee0c10a --- /dev/null +++ b/Scripts/ThryEditor/README.md @@ -0,0 +1,4 @@ +# ThryEditor +General Unity Shader Inspector/Editor with focus on vrchat shaders + +# [Discord Server for all my Assets](https://discord.thryrallo.de/) diff --git a/Scripts/ThryEditor/Resources/decal_gizmo.png b/Scripts/ThryEditor/Resources/decal_gizmo.png new file mode 100644 index 0000000..1215d03 Binary files /dev/null and b/Scripts/ThryEditor/Resources/decal_gizmo.png differ diff --git a/Scripts/ThryEditor/Resources/thryEditor_iconThry.png b/Scripts/ThryEditor/Resources/thryEditor_iconThry.png new file mode 100644 index 0000000..10b3071 Binary files /dev/null and b/Scripts/ThryEditor/Resources/thryEditor_iconThry.png differ diff --git a/Scripts/ThryEditor/Resources/thryEditor_link.png b/Scripts/ThryEditor/Resources/thryEditor_link.png new file mode 100644 index 0000000..286d510 Binary files /dev/null and b/Scripts/ThryEditor/Resources/thryEditor_link.png differ diff --git a/Scripts/ThryEditor/docs.html b/Scripts/ThryEditor/docs.html new file mode 100644 index 0000000..38ec893 --- /dev/null +++ b/Scripts/ThryEditor/docs.html @@ -0,0 +1,639 @@ + + + Thry Editor Documentation + + + +

Editor Properties

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameEffectRequiredExample
shader_master_labelDisplay name customizes the gui headerNo[HideInInspector] shader_master_label ("<color =#ff0000ff>❤</color> <color =#000000ff>Poiyomi Toon Shader V4.0</color> <color =#ff0000ff>❤</color>", Float) = 0
shader_properties_label_fileIf specified the editor tries to load property's display names out of this fileNo[HideInInspector] shader_properties_label_file ("poiToonLabels", Float) = 0
shader_properties_localeIs used to specify a locale file and locale selector. Specify "locale::<string>" anywhere to load a string from the locale file.No[HideInInspector] shader_properties_locale ("locale::locale--{file_name:locale_file_name}", Float) = 0
shader_on_swap_toIs used to specify actions to be executed when the material is switched to this shader.No[HideInInspector] shader_on_swap_to ("--{actions:[{type:SET_PROPERTY,data:_ZWrite=1},{type:SET_PROPERTY,data:_CullBack=0}]}", Float) = 0
footer_<string>Adds a footer button to the bottom of the ui. Multiple footers can be added to one shader. The displayname has to be a ButtonData objectNo + [HideInInspector] footer_github ("{text:Github,action:{type:URL,data:https://github.com/thryrallo/thryeditor}}", Float) = 0
+ [HideInInspector] footer_discord ("{texture:{name:discord-icon,height:40},action:{type:URL,data:https://discord.gg}}", Float) = 0 +
DSGIadd this property for double sided illumination settings to be shownNo[HideInInspector] DSGI ("", Float) = 0
Instancingadd this property for instancing variants settings to be shownNo[HideInInspector] Instancing ("", Float) = 0
LightmapFlagsadd this property for lightmap flags settings to be shownNo[HideInInspector] LightmapFlags ("", Float) = 0
m_<string>starts a dropdown menu. all properties underneath, till the next menu is specified, are in this menuNo[HideInInspector] m_mainOptions ("Main", Float) = 0
m_start_<string>starts a dropdown menu. all properties underneath, till this menu's end is specified, are in this menu. use if you want to layer menus.No[HideInInspector] m_start_Alpha ("Alpha Options", Float) = 0
m_end_<string>ends a dropdown menu that has been started with m_start_<string>No[HideInInspector] m_end_Alpha ("", Float) = 0
g_start_<string>starts a group. properties are grouped together. not visible to the user. can be used to hide multiple properties with one condition specification.No[HideInInspector] g_start_blending ("--{condition_show:{type:PROPERTY_BOOL,data:_DisplayBlending}}", Float) = 0
g_end_<string>ends a group that has been started with g_start_<string>No[HideInInspector] g_start_blending ("", Float) = 0
+

Suggestions:

+ + + + + + + + + + + +
NameDescriptionExample
Variant SelectorUse a commbination of Enum and on_value_actions to create a varian selector + [Enum(Cutout,0,Transparent,1)]variant_selector("Variant--{on_value_actions:[{value:0,actions:[{type:SET_PROPERTY,data:_ZWrite=1},{type:SET_SHADER,data:Thry/Example 1}]},{value:1,actions:[{type:SET_PROPERTY,data:_ZWrite=0},{type:SET_SHADER,data:Thry/Example 2}]}]}",Float) = 0 +
+

Drawers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Drawer NameEffectExtra Property OptionsExample
[SmallTexture]Creates a texture field that will always be small[SmallTexture]_MainTex("Main Texture",2D)= "white" { }
[BigTexture]Creates a texture field that will always be big[BigTexture]_MainTex("Main Texture",2D)= "white" { }
[StylizedBigTexture]Creates a texture field that will always be big but stylized differently[StylizedBigTexture]_MainTex("Main Texture",2D)= "white" { }
[Gradient] + Creates a texture field with a gradient field next to it. +
Gradient is automatically converted to texture. +
[Gradient]_ColorRamp("Color Ramp",2D)= "white" { }
[MultiSlider] + Creates a slider for a range. +
Is used with a vector property. +
x and y are the slider values. z is slider minimum. w is slider maximum. +
[MultiSlider]_Slider("Multi Slider",Vector)= (0.1,0.9,0,1)
[TextureArray]Creates field that accepts Texture Arrays[TextureArray]_FlipbookTexArray ("Texture Array", 2DArray) = "" {}
[Vector2]Creates a Vector 2 field[Vector2]_Vector("Vector with 2 values",Vector)= (0,0,0,0)
[Vector3]Creates Vector 3 field[Vector3]_Vector("Vector with 3 values",Vector)= (0,0,0,0)
[Curve]Creates a curve field[Curve]_ColorCurve("Curve",2D)= "white" { }
[Helpbox]Creates an info box[Helpbox]_MainHelpbox("This is the text inside the info box",Float)= 0
+ +

Supported Default Unity Flags

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameEffect
[NoScaleOffset]
[Normal]
[Space]
[Space(Int)]
[Toggle()]
+ +

Property Options

+ + None of the poperty options are required.
+ Options are defined in the display name of a property inside curly brackets and after "--":
+ In practice: +

_Tex("Texture--{Put all your options in here}",2D) = "white" { }
+ _Tex("Texture--{offet:2,hover:read this on hover,altClick{type:URL,data:http://thryrallo.de}}",2D) = "white" { }
+ + Use json syntax instead to future proof your properties!
+ You can use '' instead of " inside property display names +

+ Example:
+ {''text'':''Youtube'',''action'':{''type'':''URL'',''data'':''https://www.youtube.com/''} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameValue TypeEffectDrawer SpecificExample
offsetintadds an extra x-offset to the propertyNo_Tex("Texture--{offset:1}",2D) = "white" { }
tooltipstringtext that is shown when hovering above propertyNo_Tex("Texture--{tooltip:use this texture for albedo}",2D) = "white" { }
altClickActionperforms an action when holding alt down and clicking on propertyNo_Tex("Texture--{altClick:{type:URL,data:http://thryrallo.de}}",2D) = "white" { }
condition_showConditionlet's you define a condition that has to be true for this property to be shown in the uiNo_Tex("Texture--{condition_show:{type:PROPERTY_BOOL,data:_ForceOpaque==1}}",2D) = "white" { }
condition_enableConditionlet's you define a condition that has to be true for this property to be enabledNo_Tex("Texture--{condition_enable:{type:PROPERTY_BOOL,data:_ForceOpaque==1}}",2D) = "white" { }
on_value_actionsPropertyValueAction[]let's you define a actions that happen if this property is set to a specfiic value.No + [Enum(Cutout,0,Transparent,1)]variant_selector("Variant--{on_value_actions:[{value:0,actions:[{type:SET_PROPERTY,data:_ZWrite=1},{type:SET_SHADER,data:Thry/Example 1}]},{value:1,actions:[{type:SET_PROPERTY,data:_ZWrite=0},{type:SET_SHADER,data:Thry/Example 2}]}]}",Float) = 0 +
button_rightButtonlet's you define a button that is shown on the side of a dropdown headerYes, only headers_Tex("Texture--{button_right:{text:Test Button,action:{type:URL,data:https://github.com/Thryrallo/thryeditor},hover:hover text,condition_show:{type:PROPERTY_BOOL,data:_ShowButtonOnMenus}}}",2D) = "white" { }
textureTextureDataDefines the texture settings for created textures.[Gradient],[Curve][Gradient]_ColorRamp ("Gradient --{texture:{width:256,height:16,filterMode:Point,wrapMode:Clamp}}", 2D) = "white" { }
[Curve]_MainTex ("Texture --{image:{width:256,height:16,channel:b}}", 2D) = "white" { }
force_texture_optionsboolDefault: false. Set this to true to hide the texture options and force your defined texture settings.[Gradient][Gradient]_ColorRamp ("Gradient --{texture:{width:256,height:16,filterMode:Point,wrapMode:Clamp},force_texture_options:true}", 2D) = "white" { }
hide_in_inspectorboolDefault: false. Set this to true to hide the property in ThryEditor, but not the unity default inspector. Usefull if you already display the options in a texture dropdown or Foldout Header.No_Toogle ("Gradient Lighting--{texture:{hide_in_inspector:true}}", Int) = 0
reference_propertiesstring[]Default: null. specified properties will be drawn in texture foldout menuTexture_Texture("Panning Texture --{reference_properties:[_PanSpeed,_PanUV]}", 2D) = "white" { }
reference_propertystringSpecifies a property by it's name.
+ If defined on a menu header, it will create a toggle linked with the referenced property.
+ If defined on a TextureArray it will fill this float property with the texture array depth (frame count) after creating an array from a gif or multiple images.
+ If defined on a texture property it will draw this property next to the texture property (for example for a color field)
[TextureArray],Texture,MenuHeaderLight and Shadow--{reference_property:_EnableLighting}
[TextureArray]_Texture("Animated Texture --{reference_property:_FrameCount}", 2DArray) = { }
is_hideableboolIf set to true, property will be able to be hidden using the little eye icon in the top right of the inspector.MenuHeaders[HideInInspector] m_vertex("Vertex Options--{button_right:{text:Tutorial,action:{type:URL,data:https://www.youtube.com/watch?v=FO-bxI5znI0},hover:YouTube},is_hideable:true}", Float) = 0
is_hidden_defaultboolIf set to true and property is hideable, the property will be hidden by default.MenuHeaders[HideInInspector] m_vertex("Vertex Options--{button_right:{text:Tutorial,action:{type:URL,data:https://www.youtube.com/watch?v=FO-bxI5znI0},hover:YouTube},is_hideable:true,is_hidden_default:true}", Float) = 0
+ +

Data Structures

+
+

Button

+ Variables: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameValue Typerequireddefault
textstringtext or texture
textureTextureDatatext or texture
hoverstringno
actionActionkinda
condition_showConditionno
+
+
+

PropertyValueAction

+ Variables: + + + + + + + + + + + + + + + + +
NameValue Typerequired
valuestringYes
actionsAction[]Yes
+

Action

+ Variables: + + + + + + + + + + + + + + + + +
NameValue Typerequired
typeActionTypeYes
datastringYes
+

Enum: ActionType

+ States: + + + + + + + + + + + + + + + + + + + + + +
ValueEffectExample
URLOpens the url in browser{type:URL,data:https://github.com/Thryrallo/thryeditor}
SET_PROPERTYSets the value of a specified property.{type:SET_PROPERTY,data:_ZWrite=1}
SET_SHADERChanges the shader of the material.{type:SET_SHADER,data:Thry/Example 1}
+
+
+

TextureData

+ Variables: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameValue TyperequireddefaultExplanation
namestringnoFile name of image
widthintno128
heightintno128
channelcharnor
ansioLevelintno1
filterModeEnum(FilterMode)noBilinearEnumValues: Bilinear,Point,Trilinear
wrapModeEnum(TextureWrapMode)noRepeatEnumValues: Clamp,Mirror,MirrorOnce,Repeat
+
+
+

Condition

+ Variables: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameValue Typerequireddefault
typeConditionTypeYesNone
datastringIf type!=AND && type!=OR yes
condition1Conditionif type==AND || type==OR yesnull
condition2Conditionif type==AND || type==OR yesnull
+ Data will have different meanings depending on type
+
+ Data
+ Useable Comparators: ==,!=,>,< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
typedata valueexample
PROPERTY_BOOL<PropertyName>(<Comparator><value>)?{type:PROPERTY_BOOL,data:_ForceOpaque}
or {type:PROPERTY_BOOL,data:_ForceOpaque==0}
EDITOR_VERSION<Comparator><value>{type:EDITOR_VERSION,data:>0.17} #checks if installed editor version > 0.17
VRC_SDK_VERSION<Comparator><value>{type:VRC_SDK_VERSION,data:>0.17} #checks if installed vrc sdk version > 0.17
TEXTURE_SET<PropertyName>{type:TEXTURE_SET,data:_ToonRamp} #checks if texture _ToonRamp is set
DROPDOWN<PropertyName><Comparator><value>{type:DROPDOWN,data:_LightingType==2} #checks if property _LightingType has enum 2 selected
+

Enum: ConditionType

+ States: + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueEffect
ANDCombines two conditions with &&
ORCombines two conditions with ||
PROPERTY_BOOLchecks agains the float value of a property
EDITOR_VERSIONchecks agains the version number of ThryEditor
VRC_SDK_VERSIONchecks agains the version number of the VRC SDK
+
+ + diff --git a/Scripts/ThryEditor/thry_editor_locale.csv b/Scripts/ThryEditor/thry_editor_locale.csv new file mode 100644 index 0000000..bec4532 --- /dev/null +++ b/Scripts/ThryEditor/thry_editor_locale.csv @@ -0,0 +1,91 @@ +,English,German,Japanese,Spanish,Pirate,UwU,Binary,Chinese, +translator,,Thryrallo,Ponponsan,Spanish Hub,,,,INYO, +translation,Translation,Übersetzung,翻訳,Traducción,,,,翻译, +,,,,,,,,, +locale,Language,Sprache,言語,Language,Language,Wanguage,01001100 01100001 01101110 01100111 01110101 01100001 01100111 01100101,语言, +locale_tooltip,Select Language for ThryEditor,Wähle die Sprache für ThryEditor,ThryEditorの言語を選択してください,Select Language for ThryEditor,Select Language for ThryEditor,Sewect wanguage fow thwyeditow,01010011 01100101 01101100 01100101 01100011 01110100 00100000 01001100 01100001 01101110 01100111 01110101 01100001 01100111 01100101 00100000 01100110 01101111 01110010 00100000 01010100 01101000 01110010 01111001 01000101 01100100 01101001 01110100 01101111 01110010,选择Thry编辑器窗口的语言, +,,,,,,,,, +first_install_message,Hello. You just installed a shader using the Thry Shader Editor for it's UI. This is the editor's settings window. It offers you some customizability. You can close this window and open it back up over your menu if you want to change something.,Thry Shader Editor wurde installiert. Dies ist das Fenster für die Editor Einstellungen.,正常にThry Shader Editorはインストールされました。こちらがエディター設定画面になります。,Se ha actualizado exitosamente Thry Shader Editor. Esta es la ventana de configuración del editor.,Thry shader editor successfully installed. This here be the editor settings window.,Thwy shadew editow successfuwwy instawwed. Thiws iws the editow settings window.,01010100 01101000 01110010 01111001 00100000 01010011 01101000 01100001 01100100 01100101 01110010 00100000 01000101 01100100 01101001 01110100 01101111 01110010 00100000 01110011 01110101 01100011 01100011 01100101 01110011 01110011 01100110 01110101 01101100 01101100 01111001 00100000 01101001 01101110 01110011 01110100 01100001 01101100 01101100 01100101 01100100 00101110 00100000 01010100 01101000 01101001 01110011 00100000 01101001 01110011 00100000 01110100 01101000 01100101 00100000 01100101 01100100 01101001 01110100 01101111 01110010 00100000 01110011 01100101 01110100 01110100 01101001 01101110 01100111 01110011 00100000 01110111 01101001 01101110 01100100 01101111 01110111 00101110,你好。你剛剛完成安装了一个使用Thry Shader编辑器UI的着色器。這是編輯器的設置窗口。它為您提供了一些自定义能力。如果您要更改某些內容,可以關閉此窗口並在菜單上重新打開此窗口。, +update_message,Thry editor has been updated,Thry editor wurde aktualisiert,Thry editorは新しいバージョンに更新されました,Thry editor ha sido actualizado.,Thry editor has been updated,Thwy editow has bewn updated,01010100 01101000 01110010 01111001 00100000 01100101 01100100 01101001 01110100 01101111 01110010 00100000 01101000 01100001 01110011 00100000 01100010 01100101 01100101 01101110 00100000 01110101 01110000 01100100 01100001 01110100 01100101 01100100,Thry编辑器已更新完成, +downgrade_message,Warning: The Version of Thry Editor has declined,Warning: Die Version von Thry Editor hat sich verringert,注意!: Thry Editorが下位のバージョンに変更されました,Advertencia: la versión de Thry Editor ha esta desactualizada.,Warning: the version o' thry editor 'as declined,Wawning: the vewsion of thwy editow has decwined,01010111 01100001 01110010 01101110 01101001 01101110 01100111 00111010 00100000 01010100 01101000 01100101 00100000 01010110 01100101 01110010 01110011 01101001 01101111 01101110 00100000 01101111 01100110 00100000 01010100 01101000 01110010 01111001 00100000 01000101 01100100 01101001 01110100 01101111 01110010 00100000 01101000 01100001 01110011 00100000 01100100 01100101 01100011 01101100 01101001 01101110 01100101 01100100,警告:Thry编辑器的版本已下降, +,,,,,,,,, +header_editor,Editor,Editor,エディター,Editor,,Editor,01000101 01100100 01101001 01110100 01101111 01110010,编辑器, +,,,,,,,,, +header_modules,Install Packages,Installiere Packages,追加モジュール,Modulos extra,Install Packages,Install Packages,01000101 01111000 01110100 01110010 01100001 00100000 01001101 01101111 01100100 01110101 01101100 01100101 01110011,安装额外功能模组, +header_packages_curated,Curated Tools,,,,,,,, +header_packages_vrcprefabs,VRCPrefabs List,,,,,,,, +,,,,,,,,, +newest,newest,neuste,最新,el más nuevo,,newest,01101110 01100101 01110111 01100101 01110011 01110100,最新, +version,version,version,バージョン,version,,vewsion,01110110 01100101 01110010 01110011 01101001 01101111 01101110,版本, +user,user,Benutzer,ユーザー,Usuario,,usew,01110101 01110011 01100101 01110010,用户, +requirements,Requirements,Voraussetzungen,必要バージョン,Requisitos,,Wequiwements,01010010 01100101 01110001 01110101 01101001 01110010 01100101 01101101 01100101 01101110 01110100 01110011,要求, +add,Add,Hinzufügen,追加,Añadir,,Add,01000001 01100100 01100100,添加, +delete,Delete,Löschen,削除,Borrar,,Dewete,01000100 01100101 01101100 01100101 01110100 01100101,删除, +save,Save,Speichern,保存,Guardar,,Save,01010011 01100001 01110110 01100101,保存, +colorSpaceWarningSRGB,"This texture is marked not as sRGB, but should be" +colorSpaceWarningLinear,"This texture is marked as sRGB, but should be linear" +,,,,,,,,, +default_texture_type,Default Texture Display,Texturen Darstellung,デフォルト表示テクスチャー,Visualización de textura predeterminada,Default Texture Display,Defauwt textuwe dispway,01000100 01100101 01100110 01100001 01110101 01101100 01110100 00100000 01010100 01100101 01111000 01110100 01110101 01110010 01100101 00100000 01000100 01101001 01110011 01110000 01101100 01100001 01111001,默认贴图显示, +default_texture_type_tooltip,Select how your textures should be displayed if the property doesn't force the type,Wähle wie deine Texture Menüs dargestellt werden.,プロパティーに指定されなかった場合にどうテクスチャーが表示されるか指定してください,Seleccione cómo se deben mostrar sus texturas si la propiedad no fuerza el tipo,Select 'ow yer textures should be displayed if the property doesn't force the type,Sewect how youw textuwes shouwd be dispwayed if the pwopewty doesn't fowce the type,01010011 01100101 01101100 01100101 01100011 01110100 00100000 01101000 01101111 01110111 00100000 01111001 01101111 01110101 01110010 00100000 01110100 01100101 01111000 01110100 01110101 01110010 01100101 01110011 00100000 01110011 01101000 01101111 01110101 01101100 01100100 00100000 01100010 01100101 00100000 01100100 01101001 01110011 01110000 01101100 01100001 01111001 01100101 01100100 00100000 01101001 01100110 00100000 01110100 01101000 01100101 00100000 01110000 01110010 01101111 01110000 01100101 01110010 01110100 01111001 00100000 01100100 01101111 01100101 01110011 01101110 00100111 01110100 00100000 01100110 01101111 01110010 01100011 01100101 00100000 01110100 01101000 01100101 00100000 01110100 01111001 01110000 01100101,选择当属性未强制指定类型时的帖图显示类型, +showRenderQueue,Show Render Queue,Zeige Render Queue,Render Queue(描画順)の表示,Mostrar Render Queue,Show Render Queue,Show wendew queue,01010011 01101000 01101111 01110111 00100000 01010010 01100101 01101110 01100100 01100101 01110010 00100000 01010001 01110101 01100101 01110101 01100101,显示渲染队列(Render Queue), +showRenderQueue_tooltip,enable a render queue selector,"erlaubt dem render queue wähler auch mit vrchat zu funktionieren, indem er kopien des orginal shaders mit der neuen render queue erzeugt.",Render Queue(描画順)セレクターの有効化,Habilitar un selector de render queue.,enable a render queue selector,enabwe a wendew queue sewectow,01100101 01101110 01100001 01100010 01101100 01100101 00100000 01100001 00100000 01110010 01100101 01101110 01100100 01100101 01110010 00100000 01110001 01110101 01100101 01110101 01100101 00100000 01110011 01100101 01101100 01100101 01100011 01110100 01101111 01110010,启用渲染队列选择器, +allowCustomLockingRenaming,Allow custom renaming for locking,,,,,,,, +allowCustomLockingRenaming_tooltip,,,,,,,,, +showManualReloadButton,Show Manual Reload Button,,,,,,,, +showManualReloadButton_tooltip,Show button to force the ui to reload,,,,,,,, +gradient_name,Gradient Save File Names,Gradient Datei Namen,グラデーションの保存名,Nombres de archivo de guardado de gradiente,Gradient Save File Names,Gwadient save fiwe names,01000111 01110010 01100001 01100100 01101001 01100101 01101110 01110100 00100000 01010011 01100001 01110110 01100101 00100000 01000110 01101001 01101100 01100101 00100000 01001110 01100001 01101101 01100101 01110011,渐层梯度贴图保存名称命名, +gradient_name_tooltip,"configures the way gradient texture files are named. use , and to identify the texture","Definiert wie gradient Datein benannt werden. benutze , und um die texture zu indentifizieren",シェーダーインポート時にポップアップを表示する,"Configura la forma en que se nombran los archivos de textura de gradiente. use , y para identificar la textura","configures the way gradient texture files are named. use , and to identify the texture","configuwes the way gwadient textuwe fiwes awe named. use , awnd tuwu identify the textuwe",01100011 01101111 01101110 01100110 01101001 01100111 01110101 01110010 01100101 01110011 00100000 01110100 01101000 01100101 00100000 01110111 01100001 01111001 00100000 01100111 01110010 01100001 01100100 01101001 01100101 01101110 01110100 00100000 01110100 01100101 01111000 01110100 01110101 01110010 01100101 00100000 01100110 01101001 01101100 01100101 01110011 00100000 01100001 01110010 01100101 00100000 01101110 01100001 01101101 01100101 01100100 00101110 00100000 01110101 01110011 01100101 00100000 00111100 01101101 01100001 01110100 01100101 01110010 01101001 01100001 01101100 00111110 00101100 00100000 00111100 01101000 01100001 01110011 01101000 00111110 00100000 01100001 01101110 01100100 00100000 00111100 01110000 01110010 01101111 01110000 00111110 00100000 01110100 01101111 00100000 01101001 01100100 01100101 01101110 01110100 01101001 01100110 01111001 00100000 01110100 01101000 01100101 00100000 01110100 01100101 01111000 01110100 01110101 01110010 01100101,设置渐层梯度贴图的命名规则。使用 , 来定义贴图 +gradient_add_hash_or_prop,Consider adding or .,Consider adding or .,名前にまたはを追加することを推奨します。,Considere agregar o .,Consider adding or .,Considew adding ow .,01000011 01101111 01101110 01110011 01101001 01100100 01100101 01110010 00100000 01100001 01100100 01100100 01101001 01101110 01100111 00100000 00111100 01101000 01100001 01110011 01101000 00111110 00100000 01101111 01110010 00100000 00111100 01110000 01110010 01101111 01110000 00111110 00101110,推荐添加 , +gradient_add_material,Consider adding .,Consider adding .,名前にを追加することを推奨します。,Considere agregar .,Consider adding .,Considew adding .,01000011 01101111 01101110 01110011 01101001 01100100 01100101 01110010 00100000 01100001 01100100 01100100 01101001 01101110 01100111 00100000 00111100 01101101 01100001 01110100 01100101 01110010 01101001 01100001 01101100 00111110 00101110,推荐添加 , +gradient_add_material_or_prop,Add or to destingish between gradients.,Füge oder hinzu um zwischen gradients unterscheiden zu können.,名前にまたはを追加して識別可能にしてください。,Agregue o para distinguir entre gradientes.,Add or to destingish between gradients.,Add ow tuwu destingish between gwadients.,01000001 01100100 01100100 00100000 00111100 01101101 01100001 01110100 01100101 01110010 01101001 01100001 01101100 00111110 00100000 00111100 01101000 01100001 01110011 01101000 00111110 00100000 01101111 01110010 00100000 00111100 01110000 01110010 01101111 01110000 00111110 00100000 01110100 01101111 00100000 01100100 01100101 01110011 01110100 01101001 01101110 01100111 01101001 01110011 01101000 00100000 01100010 01100101 01110100 01110111 01100101 01100101 01101110 00100000 01100111 01110010 01100001 01100100 01101001 01100101 01101110 01110100 01110011 00101110,添加 以识别不同的渐层梯度贴图, +gradient_good_naming,Good naming.,Gute Benennung.,正常な名前です、いいセンスだ。,Buen nombre.,Good naming.,Good naming.,01000111 01101111 01101111 01100100 00100000 01101110 01100001 01101101 01101001 01101110 01100111 00101110,良好的名称, +,,,,,,,,, +autoMarkPropertiesAnimated,Automatically mark animated properties,,,,,,,, +autoMarkPropertiesAnimated_tooltip,Automatically mark properties as animated when changed their value while in animation mode.,,,,,,,, +,,,,,,,,, +auto_lock_dialog,"{0} material(s) have not been locked and will now be locked automatically. Locking in can dramatically improve runtime performance.\n\nDuring this time unity will remain unresponsive, please be patient.",,,,,,,, +lockin_button_single,Lock In Optimized Shader,Material optimieren und sperren,,,,,,, +lockin_button_multi,Lock in Optimized Shaders ({0} materials),{0} Materialien optimieren und sperren,,,,,,, +unlock_button_single,Unlock Shader,Material entsperren,,,,,,, +unlock_button_multi,Unlock Shaders ({0} materials),{0} Materialien entsperren,,,,,,, +,,,,,,,,, +preset_material_notify,This material is a preset.,Dieses Material definiert eine Voreinstellung.,,,,,,, +preset_name,Preset Name,Voreinstellung Benennung,,,,,,, +preset_revert,Revert Preset: ,Voreinstellung rückgänig machen: ,,,,,,, +,,,,,,,,, +yes,Yes,,,,,,,, +no,No,,,,,,,, +recommended,Recommended,,,,,,,, +autoSetAnchorOverride,Auto set Anchor Overrides (Bad Lighting Fix),,,,,,,, +autoSetAnchorOverride_tooltip,Automatically sets anchor overrides for renderers that don't have them set already. This happens during avatar upload and fixes some common lighting issues ingame.,,,,,,,, +humanBoneAnchor,Human Bone Anchor,,,,,,,, +humanBoneAnchor_tooltip,Humanoid bone to use as the anchor override if GameObject not found.,,,,,,,, +anchorOverrideObjectName,Object Anchor Name,,,,,,,, +anchorOverrideObjectName_tooltip,Name of a GameObject somewhere in your avatar's hierarchy to use as an anchor.\nIf not found the humanoid bone will be used.,,,,,,,, +autoAnchorDialog_Title,Thry: Bad Lighting Fix,,,,,,,, +autoAnchorDialog_Text,Your avatar's renderers don't all have an anchor override set.\nThis can cause flickering and uneven lighting on your avatar in some worlds.\n\nWould you like this to always be fixed automatically?\n\nYou can change this in Thry settings at any time.,,,,,,,, +autoAnchorError_NotHumanoid,Auto Anchor Setter: Avatar {0} is not humanoid or is missing an Animator.,,,,,,,, +forceAsyncCompilationPreview,Force Async Compilation In Preview (restart if disabling),,,,,,,, +technical_header,Technical Settings,,,,,,,, +forceAsyncCompilationPreview_tooltip,Speeds up compilation time and doesn't block UI during compilation. (restart editor when disabling),,,,,,,, +saveAfterLockUnlock,Save after lock/unlock,,,,,,,, +saveAfterLockUnlock_tooltip,This Should prevent cyan thumbnails when using Force Async Compilation.,,,,,,,, +fixKeywordsWhenLocking,Fix Keywords When Locking,,,,,,,, +fixKeywordsWhenLocking_tooltip,Fixes certain locking errors relating to keywords being missing.,,,,,,,, +enableDeveloperMode,Developer Mode,,,,,,,, +enableDeveloperMode_tooltip,Enables developer mode. This will show additional options and information in the Shader UI.,,,,,,,, +disableUnlockedShaderStrippingOnBuild,Disable Unlocked Shader Stripping from Build (Requires Developer Mode enabled),,,,,,,, +disableUnlockedShaderStrippingOnBuild_tooltip,Disables functionality that strips unlocked shaders from the build. Debug only, unlocked shaders are slower.,,,,,,,, +,,,,,,,,, +textures_header,Textures Compression,,,,,,,, +texturePackerCompressionWithAlphaOverwrite,Texture Packer Compression | Textures With Alpha,,,,,,,, +texturePackerCompressionWithAlphaOverwrite_tooltip,,,,,,,, +texturePackerCompressionNoAlphaOverwrite,Texture Packer Compression | Textures Without Alpha,,,,,,,, +texturePackerCompressionNoAlphaOverwrite_tooltip,,,,,,,, +gradientEditorCompressionOverwrite,Gradient Editor Compression,,,,,,,, +gradientEditorCompressionOverwrite_tooltip,,,,,,,, +,,,,,,,,, +developer_header,Developer Settings,,,,,,,, +avatar_fixes_header,Avatar Fixes,,,,,,,, +shader_ui_design_header,Shader UI Design,,,,,,,, +shader_ui_features_header,Shader UI Features,,,,,,,, \ No newline at end of file