Merge branch 'feature-text' into develop

feature-better-tweakables
Andrew Cassidy 4 years ago
commit 9f32a42e51

8
.gitignore vendored

@ -2,15 +2,19 @@
Assets/*
!Assets/Shaders/
!Assets/Textures/
!Assets/Scripts/
!Assets/UI/
!Assets/ConformalDecals/
KSP/
Library/
Logs/
Packages/
ProjectSettings/
Temp/
# Unity Assetbundle Manifest Files
GameData/ConformalDecals/Resources/Resources
GameData/ConformalDecals/Resources/Resources.manifest
GameData/ConformalDecals/Resources/conformaldecals.shab.manifest
GameData/ConformalDecals/Resources/*.manifest
# Unity Project Files
PartTools.cfg

@ -0,0 +1,36 @@
using System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace ConformalDecals.UI {
[AddComponentMenu("UI/BoxSlider", 35)]
[RequireComponent(typeof(RectTransform))]
public class BoxSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement {
[Serializable]
public class BoxSliderEvent : UnityEvent<Vector2> { }
[SerializeField] private RectTransform _handleRect;
[SerializeField] private Vector2 _value = Vector2.zero;
// Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers.
[SerializeField] private BoxSliderEvent _onValueChanged = new BoxSliderEvent();
public BoxSliderEvent OnValueChanged {
get => _onValueChanged;
set => _onValueChanged = value;
}
// Private fields
public void OnDrag(PointerEventData eventData) { }
public void OnInitializePotentialDrag(PointerEventData eventData) { }
public void Rebuild(CanvasUpdate executing) { }
public void LayoutComplete() { }
public void GraphicUpdateComplete() { }
}
}

@ -0,0 +1,13 @@
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorBoxSlider : MonoBehaviour{
[SerializeField] private Vector2 _value;
[SerializeField] private BoxSlider _slider;
[SerializeField] private Image _image;
public void OnSliderUpdate(Vector2 value) { }
}
}

@ -0,0 +1,18 @@
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorChannelSlider : MonoBehaviour {
[SerializeField] private float _value;
[SerializeField] private int _channel;
[SerializeField] private bool _hsv;
[SerializeField] private Selectable _textBox;
[SerializeField] private Slider _slider;
[SerializeField] private Image _image;
public void OnTextBoxUpdate(string text) { }
public void OnSliderUpdate(float value) { }
}
}

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorPickerController : MonoBehaviour {
[SerializeField] private Color _value;
[SerializeField] private Image _previewImage;
[SerializeField] private Selectable _hexTextBox;
public void Close() { }
public void OnHexColorUpdate(string text) { }
}
}

@ -0,0 +1,3 @@
{
"name": "ConformalDecals"
}

@ -0,0 +1,10 @@
using UnityEngine;
namespace ConformalDecals.UI {
public class FontMenuController : MonoBehaviour {
[SerializeField] private GameObject _menuItem;
[SerializeField] private GameObject _menuList;
public void Close() { }
}
}

@ -0,0 +1,37 @@
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class TextEntryController : MonoBehaviour {
[SerializeField] private Selectable _textBox;
[SerializeField] private Button _fontButton;
[SerializeField] private Slider _lineSpacingSlider;
[SerializeField] private Selectable _lineSpacingTextBox;
[SerializeField] private Slider _charSpacingSlider;
[SerializeField] private Selectable _charSpacingTextBox;
[SerializeField] private Toggle _boldButton;
[SerializeField] private Toggle _italicButton;
[SerializeField] private Toggle _underlineButton;
[SerializeField] private Toggle _smallCapsButton;
[SerializeField] private Toggle _verticalButton;
public void Close() { }
public void OnTextUpdate(string text) { }
public void OnFontMenu() { }
public void OnLineSpacingUpdate(float value) { }
public void OnLineSpacingUpdate(string text) { }
public void OnCharSpacingUpdate(float value) { }
public void OnCharSpacingUpdate(string text) { }
public void OnBoldUpdate(bool state) { }
public void OnItalicUpdate(bool state) { }
public void OnUnderlineUpdate(bool state) { }
public void OnSmallCapsUpdate(bool state) { }
public void OnVerticalUpdate(bool state) { }
}
}

@ -0,0 +1 @@
/Users/drewcassidy/Projects/KSP-Conformal-Decals/Source/ConformalDecals/UI/UITag.cs

@ -0,0 +1,117 @@
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.Rendering;
public class TextRenderTest : MonoBehaviour {
//[InspectorButton("go")] public bool button;
public Camera _camera;
public GameObject _cameraObject;
public TextMeshPro _tmp;
public Material _blitMaterial;
public Material _targetMaterial;
public RenderTexture renderTex;
private float pixelDensity = 8;
private int MaxTextureSize = 4096;
private static readonly int Decal = Shader.PropertyToID("_Decal");
public const TextureFormat TextTextureFormat = TextureFormat.RG16;
public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8;
// Start is called before the first frame update
void Start() {
Debug.Log("starting...");
StartCoroutine(OnRender());
}
// Update is called once per frame
void Update() { }
private IEnumerator OnRender() {
Debug.Log("starting...2");
// calculate camera and texture size
_tmp.ForceMeshUpdate();
var mesh = _tmp.mesh;
mesh.RecalculateBounds();
var bounds = mesh.bounds;
Debug.Log(bounds.size);
var width = bounds.size.x * pixelDensity;
var height = bounds.size.y * pixelDensity;
var widthPoT = Mathf.NextPowerOfTwo((int) width);
var heightPoT = Mathf.NextPowerOfTwo((int) height);
if (widthPoT > MaxTextureSize) {
widthPoT /= widthPoT / MaxTextureSize;
heightPoT /= widthPoT / MaxTextureSize;
}
if (heightPoT > MaxTextureSize) {
widthPoT /= heightPoT / MaxTextureSize;
heightPoT /= heightPoT / MaxTextureSize;
}
widthPoT = Mathf.Min(widthPoT, MaxTextureSize);
heightPoT = Mathf.Min(heightPoT, MaxTextureSize);
var widthRatio = widthPoT / width;
var heightRatio = heightPoT / height;
var sizeRatio = Mathf.Min(widthRatio, heightRatio);
Debug.Log(sizeRatio);
int scaledHeight = (int) (sizeRatio * height);
int scaledWidth = (int) (sizeRatio * width);
Debug.Log($"width = {scaledWidth}");
Debug.Log($"height = {scaledHeight}");
_camera.orthographicSize = scaledHeight / pixelDensity / 2;
_camera.aspect = (float) widthPoT / heightPoT;
_cameraObject.transform.localPosition = new Vector3(bounds.center.x, bounds.center.y, -1);
var halfHeight = heightPoT / pixelDensity / 2 / sizeRatio;
var halfWidth = widthPoT / pixelDensity / 2 / sizeRatio;
var matrix = Matrix4x4.Ortho(bounds.center.x - halfWidth, bounds.center.x + halfWidth,
bounds.center.y - halfHeight, bounds.center.y + halfHeight, -1, 1);
// setup texture
var texture = new Texture2D(widthPoT, heightPoT, TextTextureFormat, true);
_targetMaterial.SetTexture(Decal, texture);
// setup render texture
renderTex = RenderTexture.GetTemporary(widthPoT, heightPoT, 0, TextRenderTextureFormat, RenderTextureReadWrite.Linear, 1);
renderTex.autoGenerateMips = false;
RenderTexture.active = renderTex;
GL.PushMatrix();
GL.LoadProjectionMatrix(matrix);
_blitMaterial.SetPass(0);
Graphics.DrawMeshNow(mesh, Matrix4x4.identity);
GL.PopMatrix();
// setup material
_blitMaterial.mainTexture = _tmp.font.atlas;
yield return null;
RenderTexture.active = renderTex;
texture.ReadPixels(new Rect(0, 0, widthPoT, heightPoT), 0, 0, true);
texture.Apply(false, true);
RenderTexture.ReleaseTemporary(renderTex);
}
}

@ -0,0 +1,143 @@
Shader "ConformalDecals/UI/Color Slider"
{
Properties
{
_Color("Color", Color) = (0,0,0,0)
_Radius("Radius", Float) = 4
_OutlineGradient("Outline Gradient Step", Range (0, 1)) = 0.6
_OutlineOpacity("Outline Opacity", Range (0, 0.5)) = 0.1
_OutlineWidth("Outline Width", Float) = 3
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
[Toggle(HUE)] _Hue ("Hue", int) = 0
[Toggle(RED)] _Red ("Red", int) = 0
[Toggle(GREEN)] _Green ("Green", int) = 0
[Toggle(BLUE)] _Blue ("Blue", int) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma require integers
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#include "HSL.cginc"
#include "SDF.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
#pragma multi_compile_local HUE RED GREEN BLUE
float4 _ClipRect;
float _Radius;
float4 _Color;
float _OutlineGradient;
float _OutlineOpacity;
float _OutlineWidth;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPosition = v.vertex;
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float4 color = 1;
#ifdef HUE
color.rgb = HSV2RGB(float3(i.uv.y, _Color.y, _Color.z));
#endif //HUE
#ifdef RED
color.rgb = float3(i.uv.x, _Color.g, _Color.b);
#endif //RED
#ifdef GREEN
color.rgb = float3(_Color.r, i.uv.x, _Color.b);
#endif //GREEN
#ifdef BLUE
color.rgb = float3(_Color.r, _Color.g, i.uv.x);
#endif //BLUE
float rrect = sdRoundedUVBox(i.uv, _Radius);
float gradient = smoothstep(_OutlineGradient, 1 - _OutlineGradient, i.uv.y);
float outlineOpacity = _OutlineOpacity * smoothstep(-1*_OutlineWidth, 0, rrect);
color.rgb = lerp(color.rgb, gradient, outlineOpacity);
color.a = saturate(0.5 - rrect);
#ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
return color;
}
ENDCG
}
}
}

@ -0,0 +1,122 @@
Shader "ConformalDecals/UI/HSV Square"
{
Properties
{
_Color("Color", Color) = (0,0,0,0)
_Radius("Radius", Float) = 4
_OutlineGradient("Outline Gradient Step", Range (0, 1)) = 0.6
_OutlineOpacity("Outline Opacity", Range (0, 0.5)) = 0.1
_OutlineWidth("Outline Width", Float) = 3
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma require integers
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#include "HSL.cginc"
#include "SDF.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
float4 _ClipRect;
float _Radius;
float4 _Color;
float _OutlineGradient;
float _OutlineOpacity;
float _OutlineWidth;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPosition = v.vertex;
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 color = 1;
color.a = saturate(0.5 - sdRoundedUVBox(i.uv, _Radius));
color.rgb = HSV2RGB(float3(_Color.x, i.uv.x, i.uv.y));
float rrect = sdRoundedUVBox(i.uv, _Radius);
float gradient = smoothstep(_OutlineGradient, 1 - _OutlineGradient, i.uv.y);
float outlineOpacity = _OutlineOpacity * smoothstep(-1*_OutlineWidth, 0, rrect);
color.rgb = lerp(color.rgb, gradient, outlineOpacity);
#ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
return color;
}
ENDCG
}
}
}

@ -0,0 +1,120 @@
Shader "ConformalDecals/UI/Color Swatch"
{
Properties
{
_Color("Color", Color) = (0,0,0,0)
_Radius("Radius", Float) = 4
_OutlineGradient("Outline Gradient Step", Range (0, 1)) = 0.6
_OutlineOpacity("Outline Opacity", Range (0, 0.5)) = 0.1
_OutlineWidth("Outline Width", Float) = 3
_StencilComp ("Stencil Comparison", Float) = 8
_Stencil ("Stencil ID", Float) = 0
_StencilOp ("Stencil Operation", Float) = 0
_StencilWriteMask ("Stencil Write Mask", Float) = 255
_StencilReadMask ("Stencil Read Mask", Float) = 255
_ColorMask ("Color Mask", Float) = 15
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Stencil
{
Ref [_Stencil]
Comp [_StencilComp]
Pass [_StencilOp]
ReadMask [_StencilReadMask]
WriteMask [_StencilWriteMask]
}
Cull Off
Lighting Off
ZWrite Off
ZTest [unity_GUIZTestMode]
Blend SrcAlpha OneMinusSrcAlpha
ColorMask [_ColorMask]
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma require integers
#include "UnityCG.cginc"
#include "UnityUI.cginc"
#include "HSL.cginc"
#include "SDF.cginc"
#pragma multi_compile_local _ UNITY_UI_CLIP_RECT
#pragma multi_compile_local _ UNITY_UI_ALPHACLIP
#pragma multi_compile_local HUE RED GREEN BLUE
float4 _ClipRect;
float _Radius;
float4 _Color;
float _OutlineGradient;
float _OutlineOpacity;
float _OutlineWidth;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float4 worldPosition : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldPosition = v.vertex;
o.uv = v.uv;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 color = _Color;
color.a = saturate(0.5 - sdRoundedUVBox(i.uv, _Radius));
float rrect = sdRoundedUVBox(i.uv, _Radius);
float gradient = smoothstep(_OutlineGradient, 1 - _OutlineGradient, i.uv.y);
float outlineOpacity = _OutlineOpacity * smoothstep(-1*_OutlineWidth, 0, rrect);
color.rgb = lerp(color.rgb, gradient, outlineOpacity);
#ifdef UNITY_UI_CLIP_RECT
color.a *= UnityGet2DClipping(i.worldPosition.xy, _ClipRect);
#endif
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
return color;
}
ENDCG
}
}
}

@ -4,7 +4,7 @@
#include "AutoLight.cginc"
#include "Lighting.cginc"
#define CLIP_MARGIN 0.1
#define CLIP_MARGIN 0.05
#define EDGE_MARGIN 0.01
// UNIFORM VARIABLES
@ -85,7 +85,7 @@ struct DecalSurfaceInput
#ifdef DECAL_BASE_NORMAL
float3 normal;
#endif
#endif //DECAL_BASE_NORMAL
float3 vertex_normal;
float3 viewDir;
@ -150,14 +150,7 @@ inline float BoundsDist(float3 p, float3 normal, float3 projNormal) {
float dist = max(max(q.x, q.y), q.z); // pseudo SDF
float ndist = EDGE_MARGIN - dot(normal, projNormal); // SDF to normal
return 10 * max(dist, ndist); // return intersection
#endif
}
inline float SDFAA(float dist) {
float ddist = length(float2(ddx(dist), ddy(dist)));
float pixelDist = dist / ddist;
return saturate(0.5-pixelDist);
return saturate(0.5 - dist);
#endif //DECAL_PREVIEW
}
#endif

@ -0,0 +1,17 @@
#ifndef HSL_INCLUDED
#define HSL_INCLUDED
inline float3 HSL2RGB(float3 hsl) {
int3 n = int3(0, 8, 4);
float3 k = (n + hsl.x * 12) % 12;
float a = hsl.y * min(hsl.z, 1 - hsl.z);
return hsl.z - a * max(-1, min(k - 3, min(9 - k, 1)));
}
inline float3 HSV2RGB(float3 hsv) {
int3 n = int3(5, 3, 1);
float3 k = (n + hsv.x * 6) % 6;
return hsv.z - hsv.z * hsv.y * max(0, min(1, min(k, 4 - k)));
}
#endif

@ -0,0 +1,45 @@
#ifndef SDF_INCLUDED
#define SDF_INCLUDED
// based on functions by Inigo Quilez
// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm
// SDF of a box
float sdBox( in float2 p, in float2 b ) {
float2 d = abs(p)-b;
return length(max(d,0.0)) + min(max(d.x,d.y),0.0);
}
// SDF of a box with corner radius r
float sdRoundedBox( in float2 p, in float2 b, in float r ) {
float2 d = abs(p)-b+r;
return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - r;
}
// SDF of a box with corner radius r, based on the current UV position
// UV must be ∈ (0,1), with (0,0) on one corner
float sdRoundedUVBox( float2 uv, float r ) {
float dx = ddx(uv.x);
float dy = ddy(uv.y);
float2 dim = abs(float2(1/dx, 1/dy));
float2 halfDim = dim / 2;
float2 pos = (dim * uv) - halfDim;
return sdRoundedBox(pos, halfDim, r);
}
inline float SDFdDist(float dist) {
return length(float2(ddx(dist), ddy(dist)));
}
inline float SDFAA(float dist, float ddist) {
float pixelDist = dist / ddist;
return saturate(0.5-pixelDist);
}
inline float SDFAA(float dist) {
return SDFAA(dist, SDFdDist(dist));
}
#endif

@ -69,6 +69,7 @@
#include "UnityCG.cginc"
#include "DecalsCommon.cginc"
#include "DecalsSurface.cginc"
#include "SDF.cginc"
#include "StandardDecal.cginc"
ENDCG
@ -98,6 +99,7 @@
#include "UnityCG.cginc"
#include "DecalsCommon.cginc"
#include "DecalsSurface.cginc"
#include "SDF.cginc"
#include "StandardDecal.cginc"
ENDCG

@ -0,0 +1,73 @@
Shader "ConformalDecals/Text Blit"
{
Properties
{
_MainTex("_MainTex (RGB spec(A))", 2D) = "white" {}
_WeightNormal("Weight Normal", float) = 0
_WeightBold("Weight Bold", float) = 0.5
_ScaleRatioA("Scale RatioA", float) = 1
_ScaleRatioB("Scale RatioB", float) = 1
_ScaleRatioC("Scale RatioC", float) = 1
}
SubShader
{
Tags { "Queue" = "Transparent" }
Cull Off
ZWrite Off
Pass
{
BlendOp Max
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex;
// font weights to fake bold
float _WeightNormal;
float _WeightBold;
// no idea what these do
float _ScaleRatioA;
float _ScaleRatioB;
float _ScaleRatioC;
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0; // u, v, bias, 0
};
v2f vert(float4 vertex : POSITION, float2 uv0 : TEXCOORD0, float2 uv1 : TEXCOORD1) {
float bold = step(uv1.y, 0);
float weight = lerp(_WeightNormal, _WeightBold, bold) * _ScaleRatioA / 8.0;
float bias = 1 - weight;
v2f o;
o.pos = UnityObjectToClipPos(vertex);
o.uv = float4(uv0.x, uv0.y, bias, weight);
return o;
}
fixed4 frag (v2f i) : SV_Target {
float2 uv = i.uv.xy;
float bias = i.uv.z;
float weight = i.uv.w;
fixed4 c = 0;
c.r = saturate(tex2D(_MainTex,(uv)).a + weight);
return c;
}
ENDCG
}
}
}

@ -0,0 +1,49 @@
float4 _DecalColor;
float4 _OutlineColor;
float _OutlineWidth;
void surf(DecalSurfaceInput IN, inout SurfaceOutput o) {
float4 color = _DecalColor;
float dist = _Cutoff - tex2D(_Decal, IN.uv_decal).r; // text distance
#ifdef DECAL_OUTLINE
// Outline
float outlineOffset = _OutlineWidth * 0.25;
float outlineRadius = _OutlineWidth * 0.5;
#ifdef DECAL_FILL
// Outline and Fill
float outlineDist = -dist - outlineOffset;
float outlineFactor = SDFAA(outlineDist);
dist -= outlineOffset;
color = lerp(_DecalColor, _OutlineColor, outlineFactor);
#else
// Outline Only
float outlineDist = abs(dist) - outlineOffset;
dist = outlineDist;
color = _OutlineColor;
#endif
#endif
dist = max(dist, BoundsDist(IN.uv, IN.vertex_normal, _DecalNormal));
float ddist = SDFdDist(dist); // distance gradient magnitude
o.Alpha = _DecalOpacity * SDFAA(dist, ddist);
o.Albedo = UnderwaterFog(IN.worldPosition, color).rgb;
#ifdef DECAL_BASE_NORMAL
float3 normal = IN.normal;
float wearFactor = 1 - normal.z;
float wearFactorAlpha = saturate(_EdgeWearStrength * wearFactor);
o.Alpha *= saturate(1 + _EdgeWearOffset - saturate(_EdgeWearStrength * wearFactor));
#endif
#ifdef DECAL_SPECMAP
float4 specular = tex2D(_SpecMap, IN.uv_specmap);
o.Gloss = specular.r;
o.Specular = _Shininess;
#endif
half rim = 1.0 - saturate(dot(normalize(IN.viewDir), o.Normal));
o.Emission = (_RimColor.rgb * pow(rim, _RimFalloff)) * _RimColor.a;
}

@ -0,0 +1,112 @@
Shader "ConformalDecals/Decal/Text"
{
Properties
{
[Header(Decal)]
[Toggle(DECAL_FILL)] _Fill ("Fill", int) = 0
_Decal("Decal Texture", 2D) = "gray" {}
_DecalColor("Decal Color", Color) = (1,1,1,1)
_Weight("Text Weight", Range(0,1)) = 0
[Header(Outline)]
[Toggle(DECAL_OUTLINE)] _Outline ("Outline", int) = 0
_OutlineColor("Outline Color", Color) = (0,0,0,1)
_OutlineWidth("Outline Width", Range(0,1)) = 0.1
[Header(Normal)]
[Toggle(DECAL_BASE_NORMAL)] _BaseNormal ("Use Base Normal", int) = 0
_BumpMap("Bump Map", 2D) = "bump" {}
_EdgeWearStrength("Edge Wear Strength", Range(0,500)) = 100
_EdgeWearOffset("Edge Wear Offset", Range(0,1)) = 0.1
[Header(Specularity)]
[Toggle(DECAL_SPECMAP)] _Decal_SpecMap ("Has SpecMap", int) = 0
_SpecMap ("Specular Map)", 2D) = "black" {}
_SpecColor ("_SpecColor", Color) = (0.25, 0.25, 0.25, 1)
_Shininess ("Shininess", Range (0.03, 10)) = 0.3
_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
_DecalOpacity("Opacity", Range(0,1) ) = 1
_Background("Background Color", Color) = (0.9,0.9,0.9,0.7)
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", int) = 2
[Toggle] _ZWrite ("ZWrite", Float) = 1.0
[Toggle(DECAL_PREVIEW)] _Preview ("Preview", int) = 0
[Header(Effects)]
[PerRendererData]_Opacity("_Opacity", Range(0,1) ) = 1
_Color("_Color", Color) = (1,1,1,1)
[PerRendererData]_RimFalloff("_RimFalloff", Range(0.01,5) ) = 0.1
[PerRendererData]_RimColor("_RimColor", Color) = (0,0,0,0)
[PerRendererData]_UnderwaterFogFactor ("Underwater Fog Factor", Range(0,1)) = 0
}
SubShader
{
Tags { "Queue" = "Geometry+100" "IgnoreProjector" = "true" "DisableBatching" = "true"}
Cull [_Cull]
Pass
{
Name "FORWARD"
Tags { "LightMode" = "ForwardBase" }
ZWrite [_ZWrite]
ZTest LEqual
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert_forward
#pragma fragment frag_forward
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap
#pragma skip_variants SHADOWS_DEPTH SHADOWS_CUBE SHADOWS_SHADOWMASK LIGHTMAP_SHADOW_MIXING POINT_COOKIE
#pragma multi_compile_local __ DECAL_PREVIEW
#pragma multi_compile_local __ DECAL_BASE_NORMAL
#pragma multi_compile_local __ DECAL_SPECMAP
#pragma multi_compile_local __ DECAL_OUTLINE
#pragma multi_compile_local __ DECAL_FILL
#include "UnityCG.cginc"
#include "DecalsCommon.cginc"
#include "DecalsSurface.cginc"
#include "SDF.cginc"
#include "TextDecal.cginc"
ENDCG
}
Pass
{
Name "FORWARD"
Tags { "LightMode" = "ForwardAdd" }
ZWrite Off
ZTest LEqual
Blend One One
Offset -1, -1
CGPROGRAM
#pragma vertex vert_forward
#pragma fragment frag_forward
#pragma multi_compile_fwdadd nolightmap nodirlightmap nodynlightmap
#pragma skip_variants SHADOWS_DEPTH SHADOWS_CUBE SHADOWS_SHADOWMASK LIGHTMAP_SHADOW_MIXING POINT_COOKIE
#pragma multi_compile_local __ DECAL_PREVIEW
#pragma multi_compile_local __ DECAL_BASE_NORMAL
#pragma multi_compile_local __ DECAL_SPECMAP
#pragma multi_compile_local __ DECAL_OUTLINE
#pragma multi_compile_local __ DECAL_FILL
#include "UnityCG.cginc"
#include "DecalsCommon.cginc"
#include "DecalsSurface.cginc"
#include "SDF.cginc"
#include "TextDecal.cginc"
ENDCG
}
// shadow casting support
UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

@ -0,0 +1,93 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: BSlider
m_Shader: {fileID: 4800000, guid: a6b2542ba8ea844e7b0526fab69d88ed, type: 3}
m_ShaderKeywords: BLUE
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _Blue: 1
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _GradientStep: 0.331
- _Green: 0
- _Hue: 0
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _OutlineGradient: 0.7
- _OutlineOpacity: 0.15
- _OutlineWidth: 2.5
- _Parallax: 0.02
- _Radius: 4
- _Red: 0
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 0, g: 0, b: 0, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,93 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: GSlider
m_Shader: {fileID: 4800000, guid: a6b2542ba8ea844e7b0526fab69d88ed, type: 3}
m_ShaderKeywords: GREEN
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _Blue: 0
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _GradientStep: 0.2
- _Green: 1
- _Hue: 0
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _OutlineGradient: 0.7
- _OutlineOpacity: 0.15
- _OutlineWidth: 2.5
- _Parallax: 0.02
- _Radius: 4
- _Red: 0
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 0, g: 0, b: 0, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}

@ -0,0 +1,93 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: HSLSlider
m_Shader: {fileID: 4800000, guid: a6b2542ba8ea844e7b0526fab69d88ed, type: 3}
m_ShaderKeywords: HUE
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _Blue: 0
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _GradientStep: 0.2
- _Green: 0
- _Hue: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _OutlineGradient: 0.55
- _OutlineOpacity: 0.15
- _OutlineWidth: 2.5
- _Parallax: 0.02
- _Radius: 4
- _Red: 0
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 0, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}

@ -0,0 +1,89 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: HSLSquare
m_Shader: {fileID: 4800000, guid: 41b82117f67243a4851d2ce6bbed0d6a, type: 3}
m_ShaderKeywords:
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Hue: 0.566
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _OutlineGradient: 0.55
- _OutlineOpacity: 0.15
- _OutlineWidth: 2.5
- _Parallax: 0.02
- _Radius: 4
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}

@ -0,0 +1,93 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: RSlider
m_Shader: {fileID: 4800000, guid: a6b2542ba8ea844e7b0526fab69d88ed, type: 3}
m_ShaderKeywords: RED
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _Blue: 0
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _GradientStep: 0.2
- _Green: 0
- _Hue: 0
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _OutlineGradient: 0.7
- _OutlineOpacity: 0.15
- _OutlineWidth: 2.5
- _Parallax: 0.02
- _Radius: 4
- _Red: 1
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 0, g: 0, b: 0, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}

@ -0,0 +1,92 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Swatch
m_Shader: {fileID: 4800000, guid: a6e04e87fe864ed6a3f6a3ce52f57024, type: 3}
m_ShaderKeywords: RED
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Floats:
- _Blue: 0
- _BumpScale: 1
- _ColorMask: 15
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 1
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Green: 0
- _Hue: 0
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _OutlineGradient: 0.3
- _OutlineOpacity: 0.15
- _OutlineWidth: 2.5
- _Parallax: 0.02
- _Radius: 4
- _Red: 1
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _Stencil: 0
- _StencilComp: 8
- _StencilOp: 0
- _StencilReadMask: 255
- _StencilWriteMask: 255
- _UVSec: 0
- _UseUIAlphaClip: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 0.5176471, g: 0.5019608, b: 0.5019608, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}

File diff suppressed because it is too large Load Diff

@ -18,6 +18,14 @@ Localization
#LOC_ConformalDecals_gui-aspectratio = Aspect Ratio
#LOC_ConformalDecals_gui-select-flag = Select Flag
#LOC_ConformalDecals_gui-reset-flag = Reset Flag
#LOC_ConformalDecals_gui-set-text = Set Text
#LOC_ConformalDecals_gui-group-fill = Fill
#LOC_ConformalDecals_gui-group-outline = Outline
#LOC_ConformalDecals_gui-fill = Fill
#LOC_ConformalDecals_gui-set-fill-color = Set Fill Color
#LOC_ConformalDecals_gui-outline = Outline
#LOC_ConformalDecals_gui-outline-width = Outline Width
#LOC_ConformalDecals_gui-set-outline-color = Set Outline Color
// PARTS
@ -26,6 +34,11 @@ Localization
#LOC_ConformalDecals_flag-description = A simple switchable flag. Can either use the mission flag or select a specific flag to use.
#LOC_ConformalDecals_flag-tags = conformal decal sticker flag
// Text
#LOC_ConformalDecals_text-title = CDL-T Text Decal
#LOC_ConformalDecals_text-description = A decal that can display custom text in a variety of fonts
#LOC_ConformalDecals_text-tags = conformal decal sticker text font
// Generic Decals
#LOC_ConformalDecals_generic-title = CDL-1 Generic Decal
#LOC_ConformalDecals_generic-description = A set of generic warning decals and signs to add to your vehicles.

@ -0,0 +1,63 @@
PART
{
name = conformaldecals-text
module = Part
author = Andrew Cassidy
MODEL
{
model = ConformalDecals/Assets/decal-blank
scale = 1.0, 1.0, 1.0
}
rescaleFactor = 1
// Attachment
attachRules = 1,1,0,0,1
node_attach = 0.0, 0.0, 0.1, 0.0, 0.0, -1.0
// Tech
TechRequired = start
// Info
cost = 75
category = Structural
// CDL-T Text Decal
title = #LOC_ConformalDecals_text-title
// Peel-N-Stik Adhesive Decals
manufacturer = #LOC_ConformalDecals_agent-peel-n-stick_title
// A decal that can display custom text in a variety of fonts
description = #LOC_ConformalDecals_text-description
// conformal decal sticker text font
tags = #LOC_ConformalDecals_text-tags
bulkheadProfiles = srf
// Parameters
mass = 0.0005
dragModel = NONE
angularDrag = 0.0
crashTolerance = 10
maxTemp = 2000
breakingForce = 350
breakingTorque = 150
physicalSignificance = NONE
MODULE
{
name = ModuleConformalText
text = Text
shader = ConformalDecals/Decal/Text
useBaseNormal = true
scaleMode = MINIMUM
defaultDepth = 0.2
defaultCutoff = 0.5
}
}

@ -1,6 +1,7 @@
// Custom category, requires WildBlueTools to work
MODCAT
{
name = conformaldecals
title = #LOC_ConformalDecals_category-decals_title // Decals
folderName = ConformalDecals

@ -20,4 +20,41 @@ CONFORMALDECALS {
shader = KSP/Specular (Transparent)
shader = Solid Color (Alpha)
}
FONT {
name = LiberationSans SDF
title = Liberation Sans
}
FONT {
name = Calibri SDF
title = Calibri
}
FONT {
name = NotoSans-Regular SDF
title = Noto Sans
}
FONT {
name = Waukegan LDO Extended SDF
title = Waukegan Extended
}
FONT {
name = Nasalization SDF
title = Nasalization
}
FONT {
name = Helvetica SDF
title = Helvetica
}
FONT {
name = amarurgt SDF
title = Amarillo USAF
style = 32
styleMask = 4
}
}

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8580f3c17793051fdc08447401fb7920665ad681ff352588508044b662a3240b
size 364900

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@ -17,7 +17,7 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<OutputPath>bin/Debug/</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
@ -25,12 +25,12 @@
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<OutputPath>bin/Release/</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\ConformalDecals.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<DocumentationFile>bin/Release/ConformalDecals.xml</DocumentationFile>
<NoWarn>CS1591,CS0649</NoWarn>
</PropertyGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
@ -41,35 +41,73 @@
</Reference>
<Reference Include="System" />
<Reference Include="UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls\UnityEngine.dll</HintPath>
<HintPath>dlls/UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.AssetBundleModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.AssetBundleModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls\UnityEngine.CoreModule.dll</HintPath>
<HintPath>dlls/UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.PhysicsModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls\UnityEngine.PhysicsModule.dll</HintPath>
<HintPath>dlls/UnityEngine.PhysicsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextCoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.TextCoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.TextRenderingModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.UI.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIElementsModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.UIElementsModule.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.UIModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>dlls/UnityEngine.UIModule.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DecalConfig.cs" />
<Compile Include="DecalIconFixer.cs" />
<Compile Include="DecalPropertyIDs.cs" />
<Compile Include="MaterialProperties\MaterialColorProperty.cs" />
<Compile Include="MaterialProperties\MaterialFloatProperty.cs" />
<Compile Include="MaterialProperties\MaterialKeywordProperty.cs" />
<Compile Include="MaterialProperties\MaterialProperty.cs" />
<Compile Include="MaterialProperties\MaterialPropertyCollection.cs" />
<Compile Include="MaterialProperties\MaterialTextureProperty.cs" />
<Compile Include="MaterialProperties/MaterialColorProperty.cs" />
<Compile Include="MaterialProperties/MaterialFloatProperty.cs" />
<Compile Include="MaterialProperties/MaterialKeywordProperty.cs" />
<Compile Include="MaterialProperties/MaterialProperty.cs" />
<Compile Include="MaterialProperties/MaterialPropertyCollection.cs" />
<Compile Include="MaterialProperties/MaterialTextureProperty.cs" />
<Compile Include="ModuleConformalFlag.cs" />
<Compile Include="ModuleConformalText.cs" />
<Compile Include="ProjectionTarget.cs" />
<Compile Include="ModuleConformalDecal.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Test\TestLayers.cs" />
<Compile Include="Util\Logging.cs" />
<Compile Include="Util\OrientedBounds.cs" />
<Compile Include="Util\ParseUtil.cs" />
<Compile Include="Properties/AssemblyInfo.cs" />
<Compile Include="Text/DecalFont.cs" />
<Compile Include="Text/FontLoader.cs" />
<Compile Include="Text/TextRenderer.cs" />
<Compile Include="Text/DecalText.cs" />
<Compile Include="Test/TestLayers.cs" />
<Compile Include="Text\DecalTextStyle.cs" />
<Compile Include="Text\TextRenderOutput.cs" />
<Compile Include="Text\TextRenderJob.cs" />
<Compile Include="UI/ColorPickerController.cs" />
<Compile Include="UI/FontMenuController.cs" />
<Compile Include="UI/FontMenuItem.cs" />
<Compile Include="UI/TextEntryController.cs" />
<Compile Include="UI/UILoader.cs" />
<Compile Include="UI/UITag.cs" />
<Compile Include="UI\ColorBoxSlider.cs" />
<Compile Include="UI\ColorChannelSlider.cs" />
<Compile Include="Util/Logging.cs" />
<Compile Include="Util/OrientedBounds.cs" />
<Compile Include="Util/TextureUtils.cs" />
<Compile Include="Util/ParseUtil.cs" />
<Compile Include="UI/BoxSlider.cs" />
<Compile Include="Util\ColorHSL.cs" />
<Compile Include="Util\ColorHSV.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildToolsPath)/Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>sh -e -c "cp -v '$(TargetPath)' '$(SolutionDir)/../GameData/ConformalDecals/Plugins'"</PostBuildEvent>
</PropertyGroup>

@ -1,13 +1,18 @@
using System;
using System.Collections.Generic;
using ConformalDecals.Text;
using ConformalDecals.Util;
using TMPro;
using UniLinq;
using UnityEngine;
namespace ConformalDecals {
public static class DecalConfig {
private static Texture2D _blankNormal;
private static List<string> _shaderBlacklist;
private static int _decalLayer = 31;
private static bool _selectableInFlight;
private static Texture2D _blankNormal;
private static List<string> _shaderBlacklist;
private static Dictionary<string, DecalFont> _fontList;
private static int _decalLayer = 31;
private static bool _selectableInFlight;
private struct LegacyShaderEntry {
public string name;
@ -37,12 +42,17 @@ namespace ConformalDecals {
},
};
public static Texture2D BlankNormal => _blankNormal;
public static int DecalLayer => _decalLayer;
public static bool SelectableInFlight => _selectableInFlight;
public static IEnumerable<DecalFont> Fonts => _fontList.Values;
public static DecalFont FallbackFont { get; private set; }
public static bool IsBlacklisted(Shader shader) {
return IsBlacklisted(shader.name);
}
@ -63,14 +73,36 @@ namespace ConformalDecals {
return false;
}
public static DecalFont GetFont(string name) {
if (_fontList.TryGetValue(name, out var font)) {
return font;
}
else {
throw new KeyNotFoundException($"Font {name} not found");
}
}
private static void ParseConfig(ConfigNode node) {
ParseUtil.ParseIntIndirect(ref _decalLayer, node, "decalLayer");
ParseUtil.ParseBoolIndirect(ref _selectableInFlight, node, "selectableInFlight");
foreach (var blacklist in node.GetNodes("SHADERBLACKLIST")) {
foreach (var shaderName in blacklist.GetValuesList("shader")) {
_shaderBlacklist.Add(shaderName);
}
ParseUtil.ParseIntIndirect(ref _decalLayer, node, "decalLayer");
ParseUtil.ParseBoolIndirect(ref _selectableInFlight, node, "selectableInFlight");
}
var allFonts = Resources.FindObjectsOfTypeAll<TMP_FontAsset>();
foreach (var fontNode in node.GetNodes("FONT")) {
try {
var font = new DecalFont(fontNode, allFonts);
_fontList.Add(font.Name, font);
}
catch (Exception e) {
Debug.LogException(e);
}
}
}
@ -96,6 +128,7 @@ namespace ConformalDecals {
// ReSharper disable once UnusedMember.Global
public static void ModuleManagerPostLoad() {
_shaderBlacklist = new List<string>();
_fontList = new Dictionary<string, DecalFont>();
var configs = GameDatabase.Instance.GetConfigs("CONFORMALDECALS");

@ -19,7 +19,10 @@ namespace ConformalDecals.MaterialProperties {
[SerializeField] private Vector2 _textureOffset;
[SerializeField] private Vector2 _textureScale = Vector2.one;
public Texture2D Texture => _texture;
public Texture2D Texture {
get => _texture;
set => _texture = value;
}
public string TextureUrl {
get => _textureUrl;

@ -246,8 +246,6 @@ namespace ConformalDecals {
_boundsRenderer = decalProjectorTransform.GetComponent<MeshRenderer>();
UpdateMaterials();
// handle tweakables
if (HighLogic.LoadedSceneIsEditor) {
GameEvents.onEditorPartEvent.Add(OnEditorEvent);
@ -360,7 +358,7 @@ namespace ConformalDecals {
}
}
protected void OnAttach() {
protected virtual void OnAttach() {
if (part.parent == null) {
this.LogError("Attach function called but part has no parent!");
_isAttached = false;
@ -383,7 +381,7 @@ namespace ConformalDecals {
UpdateScale();
}
protected void OnDetach() {
protected virtual void OnDetach() {
_isAttached = false;
// unhide model
@ -456,7 +454,7 @@ namespace ConformalDecals {
}
}
protected void UpdateMaterials() {
protected virtual void UpdateMaterials() {
materialProperties.UpdateMaterials();
materialProperties.SetOpacity(opacity);
materialProperties.SetCutoff(cutoff);
@ -500,7 +498,7 @@ namespace ConformalDecals {
}
}
protected void UpdateTweakables() {
protected virtual void UpdateTweakables() {
// setup tweakable fields
var scaleField = Fields[nameof(scale)];
var depthField = Fields[nameof(depth)];
@ -514,7 +512,7 @@ namespace ConformalDecals {
cutoffField.guiActiveEditor = cutoffAdjustable;
wearField.guiActiveEditor = useBaseNormal;
var steps = 20;
var steps = 40;
if (scaleAdjustable) {
var minValue = Mathf.Max(Mathf.Epsilon, scaleRange.x);

@ -0,0 +1,292 @@
using ConformalDecals.MaterialProperties;
using ConformalDecals.Text;
using ConformalDecals.UI;
using TMPro;
using UnityEngine;
namespace ConformalDecals {
public class ModuleConformalText : ModuleConformalDecal, ISerializationCallbackReceiver {
[KSPField(isPersistant = true)] public string text = "Text";
[KSPField(isPersistant = true)] public Color fillColor = Color.black;
[KSPField(isPersistant = true)] public Color outlineColor = Color.white;
[KSPField] public Vector2 lineSpacingRange = new Vector2(-50, 50);
[KSPField] public Vector2 charSpacingRange = new Vector2(-50, 50);
// serialization-only fields. do not use except in serialization functions
[KSPField(isPersistant = true)] public string fontName = "Calibri SDF";
[KSPField(isPersistant = true)] public int style;
[KSPField(isPersistant = true)] public bool vertical;
[KSPField(isPersistant = true)] public float lineSpacing;
[KSPField(isPersistant = true)] public float charSpacing;
// KSP TWEAKABLES
[KSPEvent(guiName = "#LOC_ConformalDecals_gui-set-text", guiActive = false, guiActiveEditor = true)]
public void SetText() {
if (_textEntryController == null) {
_textEntryController = TextEntryController.Create(text, _font, _style, lineSpacingRange, charSpacingRange, OnTextUpdate);
}
else {
_textEntryController.Close();
}
}
// FILL
[KSPField(guiName = "#LOC_ConformalDecals_gui-fill", groupName = "decal-fill", groupDisplayName = "#LOC_ConformalDecals_gui-group-fill",
guiActive = false, guiActiveEditor = true, isPersistant = true),
UI_Toggle()]
public bool fillEnabled = true;
[KSPEvent(guiName = "#LOC_ConformalDecals_gui-set-fill-color", groupName = "decal-fill", groupDisplayName = "#LOC_ConformalDecals_gui-group-fill",
guiActive = false, guiActiveEditor = true)]
public void SetFillColor() {
if (_fillColorPickerController == null) {
_fillColorPickerController = ColorPickerController.Create(fillColor, OnFillColorUpdate);
}
else {
_fillColorPickerController.Close();
}
}
// OUTLINE
[KSPField(guiName = "#LOC_ConformalDecals_gui-outline", groupName = "decal-outline", groupDisplayName = "#LOC_ConformalDecals_gui-group-outline",
guiActive = false, guiActiveEditor = true, isPersistant = true),
UI_Toggle()]
public bool outlineEnabled;
[KSPField(guiName = "#LOC_ConformalDecals_gui-outline-width", groupName = "decal-outline", groupDisplayName = "#LOC_ConformalDecals_gui-group-outline",
guiActive = false, guiActiveEditor = true, isPersistant = true, guiFormat = "F2"),
UI_FloatRange(stepIncrement = 0.05f)]
public float outlineWidth = 0.1f;
[KSPEvent(guiName = "#LOC_ConformalDecals_gui-set-outline-color", groupName = "decal-outline", groupDisplayName = "#LOC_ConformalDecals_gui-group-outline",
guiActive = false, guiActiveEditor = true)]
public void SetOutlineColor() {
if (_outlineColorPickerController == null) {
_outlineColorPickerController = ColorPickerController.Create(outlineColor, OnOutlineColorUpdate);
}
else {
_outlineColorPickerController.Close();
}
}
private DecalTextStyle _style;
private DecalFont _font;
private TextEntryController _textEntryController;
private ColorPickerController _fillColorPickerController;
private ColorPickerController _outlineColorPickerController;
private MaterialTextureProperty _decalTextureProperty;
private MaterialKeywordProperty _fillEnabledProperty;
private MaterialColorProperty _fillColorProperty;
private MaterialKeywordProperty _outlineEnabledProperty;
private MaterialColorProperty _outlineColorProperty;
private MaterialFloatProperty _outlineWidthProperty;
private TextRenderJob _currentJob;
private DecalText _currentText;
public override void OnLoad(ConfigNode node) {
base.OnLoad(node);
OnAfterDeserialize();
UpdateTextRecursive();
}
public override void OnSave(ConfigNode node) {
OnBeforeSerialize();
base.OnSave(node);
}
public override void OnStart(StartState state) {
base.OnStart(state);
UpdateTextRecursive();
}
public override void OnAwake() {
base.OnAwake();
_decalTextureProperty = materialProperties.AddOrGetTextureProperty("_Decal", true);
_fillEnabledProperty = materialProperties.AddOrGetProperty<MaterialKeywordProperty>("DECAL_FILL");
_fillColorProperty = materialProperties.AddOrGetProperty<MaterialColorProperty>("_DecalColor");
_outlineEnabledProperty = materialProperties.AddOrGetProperty<MaterialKeywordProperty>("DECAL_OUTLINE");
_outlineColorProperty = materialProperties.AddOrGetProperty<MaterialColorProperty>("_OutlineColor");
_outlineWidthProperty = materialProperties.AddOrGetProperty<MaterialFloatProperty>("_OutlineWidth");
}
public void OnTextUpdate(string newText, DecalFont newFont, DecalTextStyle newStyle) {
text = newText;
_font = newFont;
_style = newStyle;
UpdateTextRecursive();
}
public void OnFillColorUpdate(Color rgb, Util.ColorHSV hsv) {
fillColor = rgb;
UpdateMaterials();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalText>();
decal.fillColor = fillColor;
decal.UpdateMaterials();
}
}
public void OnOutlineColorUpdate(Color rgb, Util.ColorHSV hsv) {
outlineColor = rgb;
UpdateMaterials();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalText>();
decal.outlineColor = outlineColor;
decal.UpdateMaterials();
}
}
public void OnFillToggle(BaseField field, object obj) {
// fill and outline cant both be disabled
outlineEnabled = outlineEnabled || (!outlineEnabled && !fillEnabled);
UpdateTweakables();
UpdateMaterials();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalText>();
decal.fillEnabled = fillEnabled;
decal.outlineEnabled = outlineEnabled;
decal.UpdateTweakables();
decal.UpdateMaterials();
}
}
public void OnOutlineToggle(BaseField field, object obj) {
// fill and outline cant both be disabled
fillEnabled = fillEnabled || (!fillEnabled && !outlineEnabled);
UpdateTweakables();
UpdateMaterials();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalText>();
decal.fillEnabled = fillEnabled;
decal.outlineEnabled = outlineEnabled;
decal.UpdateTweakables();
decal.UpdateMaterials();
}
}
public void OnOutlineWidthUpdate(BaseField field, object obj) {
UpdateMaterials();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalText>();
decal.outlineWidth = outlineWidth;
decal.UpdateMaterials();
}
}
public void OnBeforeSerialize() {
fontName = _font.Name;
style = (int) _style.FontStyle;
vertical = _style.Vertical;
lineSpacing = _style.LineSpacing;
charSpacing = _style.CharSpacing;
}
public void OnAfterDeserialize() {
_font = DecalConfig.GetFont(fontName);
_style = new DecalTextStyle((FontStyles) style, vertical, lineSpacing, charSpacing);
}
public override void OnDestroy() {
if (HighLogic.LoadedSceneIsGame && _currentText != null) TextRenderer.UnregisterText(_currentText);
base.OnDestroy();
}
protected override void OnDetach() {
// close all UIs
if (_textEntryController != null) _textEntryController.Close();
if (_fillColorPickerController != null) _fillColorPickerController.Close();
if (_outlineColorPickerController != null) _outlineColorPickerController.Close();
base.OnDetach();
}
private void UpdateTextRecursive() {
UpdateText();
foreach (var counterpart in part.symmetryCounterparts) {
var decal = counterpart.GetComponent<ModuleConformalText>();
decal.text = text;
decal._font = _font;
decal._style = _style;
decal._currentJob = _currentJob;
decal._currentText = _currentText;
decal.UpdateText();
}
}
private void UpdateText() {
// Render text
var newText = new DecalText(text, _font, _style);
var output = TextRenderer.UpdateTextNow(_currentText, newText);
_currentText = newText;
UpdateTexture(output);
// TODO: ASYNC RENDERING
// var newText = new DecalText(text, _font, _style);
// _currentJob = TextRenderer.UpdateText(_currentText, newText, UpdateTexture);
// _currentText = newText;
}
public void UpdateTexture(TextRenderOutput output) {
_decalTextureProperty.Texture = output.Texture;
_decalTextureProperty.SetTile(output.Window);
UpdateMaterials();
UpdateScale();
}
protected override void UpdateMaterials() {
_fillEnabledProperty.value = fillEnabled;
_fillColorProperty.color = fillColor;
_outlineEnabledProperty.value = outlineEnabled;
_outlineColorProperty.color = outlineColor;
_outlineWidthProperty.value = outlineWidth;
base.UpdateMaterials();
}
protected override void UpdateTweakables() {
var fillEnabledField = Fields[nameof(fillEnabled)];
var fillColorEvent = Events["SetFillColor"];
var outlineEnabledField = Fields[nameof(outlineEnabled)];
var outlineWidthField = Fields[nameof(outlineWidth)];
var outlineColorEvent = Events["SetOutlineColor"];
fillColorEvent.guiActiveEditor = fillEnabled;
outlineWidthField.guiActiveEditor = outlineEnabled;
outlineColorEvent.guiActiveEditor = outlineEnabled;
((UI_Toggle) fillEnabledField.uiControlEditor).onFieldChanged = OnFillToggle;
((UI_Toggle) outlineEnabledField.uiControlEditor).onFieldChanged = OnOutlineToggle;
((UI_FloatRange) outlineWidthField.uiControlEditor).onFieldChanged = OnOutlineWidthUpdate;
base.UpdateTweakables();
}
}
}

@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Conformal Decals")]
[assembly: AssemblyTitle("ConformalDecals")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Cineboxandrew")]

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using ConformalDecals.Util;
using TMPro;
using UniLinq;
namespace ConformalDecals.Text {
public class DecalFont : IEquatable<DecalFont> {
public string Title { get; }
public TMP_FontAsset FontAsset { get; }
public string Name => FontAsset.name;
public FontStyles FontStyle { get; }
public bool Bold => (FontStyle & FontStyles.Bold) != 0;
public bool Italic => (FontStyle & FontStyles.Italic) != 0;
public bool Underline => (FontStyle & FontStyles.Underline) != 0;
public bool SmallCaps => (FontStyle & FontStyles.SmallCaps) != 0;
public FontStyles FontStyleMask { get; }
public bool BoldMask => (FontStyleMask & FontStyles.Bold) != 0;
public bool ItalicMask => (FontStyleMask & FontStyles.Italic) != 0;
public bool UnderlineMask => (FontStyleMask & FontStyles.Underline) != 0;
public bool SmallCapsMask => (FontStyleMask & FontStyles.SmallCaps) != 0;
public DecalFont(ConfigNode node, IEnumerable<TMP_FontAsset> fontAssets) {
if (node == null) throw new ArgumentNullException(nameof(node));
if (fontAssets == null) throw new ArgumentNullException(nameof(fontAssets));
var name = ParseUtil.ParseString(node, "name");
FontAsset = fontAssets.First(o => o.name == name);
if (FontAsset == null) {
throw new FormatException($"Could not find font asset named {name}");
}
Title = ParseUtil.ParseString(node, "title", true, name);
FontStyle = (FontStyles) ParseUtil.ParseInt(node, "style", true);
FontStyleMask = (FontStyles) ParseUtil.ParseInt(node, "styleMask", true);
}
public void SetupSample(TMP_Text tmp) {
if (tmp == null) throw new ArgumentNullException(nameof(tmp));
if (FontAsset == null) throw new InvalidOperationException("DecalFont has not been initialized and Font is null.");
tmp.text = Title;
tmp.font = FontAsset;
tmp.fontStyle = FontStyle;
}
public bool Equals(DecalFont other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Title == other.Title && Equals(FontAsset, other.FontAsset) && FontStyle == other.FontStyle && FontStyleMask == other.FontStyleMask;
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((DecalFont) obj);
}
public override int GetHashCode() {
unchecked {
var hashCode = (Title != null ? Title.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (FontAsset != null ? FontAsset.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int) FontStyle;
hashCode = (hashCode * 397) ^ (int) FontStyleMask;
return hashCode;
}
}
public static bool operator ==(DecalFont left, DecalFont right) {
return Equals(left, right);
}
public static bool operator !=(DecalFont left, DecalFont right) {
return !Equals(left, right);
}
}
}

@ -0,0 +1,60 @@
using System;
using System.Text.RegularExpressions;
namespace ConformalDecals.Text {
public class DecalText : IEquatable<DecalText> {
public string Text { get; }
public DecalFont Font { get; }
public DecalTextStyle Style { get; }
public string FormattedText {
get {
if (Style.Vertical) {
return Regex.Replace(Text, @"(.)", "$1\n");
}
else {
return Text;
}
}
}
public DecalText(string text, DecalFont font, DecalTextStyle style) {
if (font == null) throw new ArgumentNullException(nameof(font));
Text = text;
Font = font;
Style = style;
}
public bool Equals(DecalText other) {
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Text == other.Text && Equals(Font, other.Font) && Style.Equals(other.Style);
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((DecalText) obj);
}
public override int GetHashCode() {
unchecked {
var hashCode = (Text != null ? Text.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (Font != null ? Font.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ Style.GetHashCode();
return hashCode;
}
}
public static bool operator ==(DecalText left, DecalText right) {
return Equals(left, right);
}
public static bool operator !=(DecalText left, DecalText right) {
return !Equals(left, right);
}
}
}

@ -0,0 +1,100 @@
using System;
using TMPro;
using UnityEngine;
// ReSharper disable NonReadonlyMemberInGetHashCode
namespace ConformalDecals.Text {
public struct DecalTextStyle : IEquatable<DecalTextStyle> {
private FontStyles _fontStyle;
private bool _vertical;
private float _lineSpacing;
private float _charSpacing;
public FontStyles FontStyle {
get => _fontStyle;
set => _fontStyle = value;
}
public bool Bold {
get => (FontStyle & FontStyles.Bold) != 0;
set {
if (value) FontStyle |= FontStyles.Bold;
else FontStyle &= ~FontStyles.Bold;
}
}
public bool Italic {
get => (FontStyle & FontStyles.Italic) != 0;
set {
if (value) FontStyle |= FontStyles.Italic;
else FontStyle &= ~FontStyles.Italic;
}
}
public bool Underline {
get => (FontStyle & FontStyles.Underline) != 0;
set {
if (value) FontStyle |= FontStyles.Underline;
else FontStyle &= ~FontStyles.Underline;
}
}
public bool SmallCaps {
get => (FontStyle & FontStyles.SmallCaps) != 0;
set {
if (value) FontStyle |= FontStyles.SmallCaps;
else FontStyle &= ~FontStyles.SmallCaps;
}
}
public bool Vertical {
get => _vertical;
set => _vertical = value;
}
public float LineSpacing {
get => _lineSpacing;
set => _lineSpacing = value;
}
public float CharSpacing {
get => _charSpacing;
set => _charSpacing = value;
}
public DecalTextStyle(FontStyles fontStyle, bool vertical, float lineSpacing, float charSpacing) {
_fontStyle = fontStyle;
_vertical = vertical;
_lineSpacing = lineSpacing;
_charSpacing = charSpacing;
}
public bool Equals(DecalTextStyle other) {
return FontStyle == other.FontStyle && Vertical == other.Vertical &&
Mathf.Approximately(LineSpacing, other.LineSpacing) &&
Mathf.Approximately(CharSpacing, other.CharSpacing);
}
public override bool Equals(object obj) {
return obj is DecalTextStyle other && Equals(other);
}
public override int GetHashCode() {
unchecked {
var hashCode = (int) FontStyle;
hashCode = (hashCode * 397) ^ Vertical.GetHashCode();
hashCode = (hashCode * 397) ^ LineSpacing.GetHashCode();
hashCode = (hashCode * 397) ^ CharSpacing.GetHashCode();
return hashCode;
}
}
public static bool operator ==(DecalTextStyle left, DecalTextStyle right) {
return left.Equals(right);
}
public static bool operator !=(DecalTextStyle left, DecalTextStyle right) {
return !left.Equals(right);
}
}
}

@ -0,0 +1,36 @@
using System.IO;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UniLinq;
using UnityEngine;
namespace ConformalDecals.Text {
[DatabaseLoaderAttrib(new[] {"kspfont"})]
public class FontLoader : DatabaseLoader<GameDatabase.TextureInfo> {
private const string FallbackName = "NotoSans-Regular SDF";
private static TMP_FontAsset _fallbackFont;
public override IEnumerator Load(UrlDir.UrlFile urlFile, FileInfo fileInfo) {
if (_fallbackFont == null) {
_fallbackFont = Resources.FindObjectsOfTypeAll<TMP_FontAsset>().First(o => o.name == FallbackName);
if (_fallbackFont == null) Debug.LogError($"Could not find fallback font '{FallbackName}'");
}
Debug.Log($"[ConformalDecals] '{urlFile.fullPath}'");
var bundle = AssetBundle.LoadFromFile(urlFile.fullPath);
if (!bundle) {
Debug.Log($"[ConformalDecals] could not load font asset {urlFile.fullPath}");
}
else {
var loadedFonts = bundle.LoadAllAssets<TMP_FontAsset>();
foreach (var font in loadedFonts) {
Debug.Log($"[ConformalDecals] adding font {font.name}");
font.fallbackFontAssets.Add(_fallbackFont);
}
}
yield break;
}
}
}

@ -0,0 +1,35 @@
using System;
using UnityEngine.Events;
namespace ConformalDecals.Text {
public class TextRenderJob {
public DecalText OldText { get; }
public DecalText NewText { get; }
public bool Needed { get; private set; }
public bool IsStarted { get; private set; }
public bool IsDone { get; private set; }
public readonly TextRenderer.TextRenderEvent onRenderFinished = new TextRenderer.TextRenderEvent();
public TextRenderJob(DecalText oldText, DecalText newText, UnityAction<TextRenderOutput> renderFinishedCallback) {
OldText = oldText;
NewText = newText ?? throw new ArgumentNullException(nameof(newText));
Needed = true;
if (renderFinishedCallback != null) onRenderFinished.AddListener(renderFinishedCallback);
}
public void Cancel() {
Needed = false;
}
public void Start() {
IsStarted = true;
}
public void Finish(TextRenderOutput output) {
IsDone = true;
onRenderFinished.Invoke(output);
}
}
}

@ -0,0 +1,16 @@
using UnityEngine;
namespace ConformalDecals.Text {
public class TextRenderOutput {
public Texture2D Texture { get; private set; }
public Rect Window { get; private set; }
public int UserCount { get; set; }
public TextRenderOutput(Texture2D texture, Rect window) {
Texture = texture;
Window = window;
}
}
}

@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
namespace ConformalDecals.Text {
[KSPAddon(KSPAddon.Startup.Instantly, true)]
public class TextRenderer : MonoBehaviour {
public const TextureFormat TextTextureFormat = TextureFormat.RG16;
public const RenderTextureFormat TextRenderTextureFormat = RenderTextureFormat.R8;
public static TextRenderer Instance {
get {
if (!_instance._isSetup) {
_instance.Setup();
}
return _instance;
}
}
[Serializable]
public class TextRenderEvent : UnityEvent<TextRenderOutput> { }
private const string ShaderName = "ConformalDecals/Text Blit";
private const int MaxTextureSize = 4096;
private const float FontSize = 100;
private const float PixelDensity = 5;
private static TextRenderer _instance;
private bool _isSetup;
private TextMeshPro _tmp;
private Shader _blitShader;
private static readonly Dictionary<DecalText, TextRenderOutput> RenderCache = new Dictionary<DecalText, TextRenderOutput>();
private static readonly Queue<TextRenderJob> RenderJobs = new Queue<TextRenderJob>();
// Update text using the job queue
public static TextRenderJob UpdateText(DecalText oldText, DecalText newText, UnityAction<TextRenderOutput> renderFinishedCallback) {
if (newText == null) throw new ArgumentNullException(nameof(newText));
var job = new TextRenderJob(oldText, newText, renderFinishedCallback);
RenderJobs.Enqueue(job);
return job;
}
// Update text immediately without using job queue
public static TextRenderOutput UpdateTextNow(DecalText oldText, DecalText newText) {
if (newText == null) throw new ArgumentNullException(nameof(newText));
return Instance.RunJob(new TextRenderJob(oldText, newText, null), out _);
}
// Unregister a user of a piece of text
public static void UnregisterText(DecalText text) {
Debug.Log($"[ConformalDecals] Unregistering text '{text.Text}'");
if (RenderCache.TryGetValue(text, out var renderedText)) {
renderedText.UserCount--;
if (renderedText.UserCount <= 0) {
RenderCache.Remove(text);
Destroy(renderedText.Texture);
}
}
}
private void Start() {
if (_instance != null) {
Debug.Log("[ConformalDecals] Duplicate TextRenderer created???");
}
Debug.Log("[ConformalDecals] Creating TextRenderer Object");
_instance = this;
DontDestroyOnLoad(gameObject);
}
private void Setup() {
if (_isSetup) return;
Debug.Log("[ConformalDecals] Setting Up TextRenderer Object");
_tmp = gameObject.AddComponent<TextMeshPro>();
_tmp.renderer.enabled = false; // dont automatically render
_blitShader = Shabby.Shabby.FindShader(ShaderName);
if (_blitShader == null) Debug.LogError($"[ConformalDecals] could not find text blit shader named '{ShaderName}'");
_isSetup = true;
}
// Run a text render job
private TextRenderOutput RunJob(TextRenderJob job, out bool renderNeeded) {
if (!job.Needed) {
renderNeeded = false;
return null;
}
Debug.Log($"[ConformalDecals] Starting Text Rendering Job. queue depth = {RenderJobs.Count}, cache size = {RenderCache.Count}");
foreach (var cacheitem in RenderCache) {
Debug.Log($"[ConformalDecals] Cache item: '{cacheitem.Key.Text}' with {cacheitem.Value.UserCount} users");
}
job.Start();
Texture2D texture = null;
if (job.OldText != null && RenderCache.TryGetValue(job.OldText, out var oldRender)) {
// old output still exists
oldRender.UserCount--;
if (oldRender.UserCount <= 0) {
// this is the only usage of this output, so we are free to re-render into the texture
Debug.Log("Render output is not shared with other users, so reusing texture and removing cache slot");
texture = oldRender.Texture;
RenderCache.Remove(job.OldText);
}
else {
// other things are using this render output, so decriment usercount, and we'll make a new entry instead
Debug.Log("Render output is shared with other users, so making new output");
}
}
// now that all old references are handled, begin rendering the new output
if (RenderCache.TryGetValue(job.NewText, out var renderOutput)) {
renderNeeded = false;
}
else {
renderNeeded = true;
renderOutput = RenderText(job.NewText, texture);
RenderCache.Add(job.NewText, renderOutput);
}
renderOutput.UserCount++;
job.Finish(renderOutput);
return renderOutput;
}
// Render a piece of text to a given texture
public TextRenderOutput RenderText(DecalText text, Texture2D texture) {
if (text == null) throw new ArgumentNullException(nameof(text));
if (_tmp == null) throw new InvalidOperationException("TextMeshPro object not yet created.");
// SETUP TMP OBJECT FOR RENDERING
_tmp.text = text.FormattedText;
_tmp.font = text.Font.FontAsset;
_tmp.fontStyle = text.Style.FontStyle | text.Font.FontStyle;
_tmp.lineSpacing = text.Style.LineSpacing;
_tmp.characterSpacing = text.Style.CharSpacing;
_tmp.extraPadding = true;
_tmp.enableKerning = true;
_tmp.enableWordWrapping = false;
_tmp.overflowMode = TextOverflowModes.Overflow;
_tmp.alignment = TextAlignmentOptions.Center;
_tmp.fontSize = FontSize;
// GENERATE MESH
_tmp.ClearMesh(false);
_tmp.ForceMeshUpdate();
var meshFilters = gameObject.GetComponentsInChildren<MeshFilter>();
var meshes = new Mesh[meshFilters.Length];
var materials = new Material[meshFilters.Length];
var bounds = new Bounds();
// SETUP MATERIALS AND BOUNDS
for (int i = 0; i < meshFilters.Length; i++) {
var renderer = meshFilters[i].gameObject.GetComponent<MeshRenderer>();
meshes[i] = meshFilters[i].mesh;
if (i == 0) meshes[i] = _tmp.mesh;
materials[i] = Instantiate(renderer.material);
materials[i].shader = _blitShader;
if (renderer == null) throw new FormatException($"Object {meshFilters[i].gameObject.name} has filter but no renderer");
if (meshes[i] == null) throw new FormatException($"Object {meshFilters[i].gameObject.name} has a null mesh");
if (i == 0) {
bounds = meshes[i].bounds;
}
else {
bounds.Encapsulate(meshes[i].bounds);
}
}
// CALCULATE SIZES
var size = bounds.size * PixelDensity;
var textureSize = new Vector2Int {
x = Mathf.NextPowerOfTwo((int) size.x),
y = Mathf.NextPowerOfTwo((int) size.y)
};
if (textureSize.x == 0 || textureSize.y == 0) {
Debug.LogWarning("[ConformalDecals] No text present or error in texture size calculation. Aborting.");
return new TextRenderOutput(Texture2D.blackTexture, Rect.zero);
}
// make sure texture isnt too big, scale it down if it is
// this is just so you dont crash the game by pasting in the entire script of The Bee Movie
if (textureSize.x > MaxTextureSize) {
textureSize.y /= textureSize.x / MaxTextureSize;
textureSize.x = MaxTextureSize;
}
if (textureSize.y > MaxTextureSize) {
textureSize.x /= textureSize.y / MaxTextureSize;
textureSize.y = MaxTextureSize;
}
// scale up everything to fit the texture for maximum usage
float sizeRatio = Mathf.Min(textureSize.x / size.x, textureSize.y / size.y);
// calculate where in the texture the used area actually is
var window = new Rect {
size = size * sizeRatio,
center = (Vector2) textureSize / 2
};
// SETUP TEXTURE
if (texture == null) {
texture = new Texture2D(textureSize.x, textureSize.y, TextTextureFormat, true);
}
else if (texture.width != textureSize.x || texture.height != textureSize.y || texture.format != TextTextureFormat) {
texture.Resize(textureSize.x, textureSize.y, TextTextureFormat, true);
}
// GENERATE PROJECTION MATRIX
var halfSize = (Vector2) textureSize / PixelDensity / 2 / sizeRatio;
var matrix = Matrix4x4.Ortho(bounds.center.x - halfSize.x, bounds.center.x + halfSize.x,
bounds.center.y - halfSize.y, bounds.center.y + halfSize.y, -1, 1);
// GET RENDERTEX
var renderTex = RenderTexture.GetTemporary(textureSize.x, textureSize.y, 0, TextRenderTextureFormat, RenderTextureReadWrite.Linear, 1);
renderTex.autoGenerateMips = false;
// RENDER
Graphics.SetRenderTarget(renderTex);
GL.PushMatrix();
GL.LoadProjectionMatrix(matrix);
GL.Clear(false, true, Color.black);
for (var i = 0; i < meshes.Length; i++) {
if (meshes[i].vertexCount >= 3) {
materials[i].SetPass(0);
Graphics.DrawMeshNow(meshes[i], Matrix4x4.identity);
}
}
GL.PopMatrix();
// COPY TEXTURE BACK INTO RAM
RenderTexture.active = renderTex;
texture.ReadPixels(new Rect(0, 0, textureSize.x, textureSize.y), 0, 0, true);
texture.Apply();
// RELEASE RENDERTEX
RenderTexture.ReleaseTemporary(renderTex);
// CLEAR SUBMESHES
for (int i = 0; i < transform.childCount; i++) {
Destroy(transform.GetChild(i).gameObject);
}
return new TextRenderOutput(texture, window);
}
}
}

@ -0,0 +1,176 @@
using System;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace ConformalDecals.UI {
[AddComponentMenu("UI/BoxSlider", 35)]
[RequireComponent(typeof(RectTransform))]
public class BoxSlider : Selectable, IDragHandler, IInitializePotentialDragHandler, ICanvasElement {
[Serializable]
public class BoxSliderEvent : UnityEvent<Vector2> { }
[SerializeField] private RectTransform _handleRect;
[SerializeField] private Vector2 _value = Vector2.zero;
public RectTransform HandleRect {
get => _handleRect;
set {
if (value == null) throw new ArgumentNullException(nameof(value));
if (value != _handleRect) {
_handleRect = value;
UpdateCachedReferences();
UpdateVisuals();
}
}
}
public Vector2 Value {
get => _value;
set {
_value = value;
_onValueChanged.Invoke(value);
UpdateVisuals();
}
}
[Space(6)]
// Allow for delegate-based subscriptions for faster events than 'eventReceiver', and allowing for multiple receivers.
[SerializeField]
private BoxSliderEvent _onValueChanged = new BoxSliderEvent();
public BoxSliderEvent OnValueChanged {
get => _onValueChanged;
set => _onValueChanged = value;
}
// Private fields
private Transform _handleTransform;
private RectTransform _handleContainerRect;
// The offset from handle position to mouse down position
private Vector2 _offset = Vector2.zero;
#if UNITY_EDITOR
protected override void OnValidate() {
base.OnValidate();
//Onvalidate is called before OnEnabled. We need to make sure not to touch any other objects before OnEnable is run.
if (IsActive()) {
UpdateCachedReferences();
// Update rects since other things might affect them even if value didn't change.
UpdateVisuals();
}
#if UNITY_2018_3_OR_NEWER
if (!UnityEditor.PrefabUtility.IsPartOfPrefabAsset(this) && !Application.isPlaying)
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
#else
var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this);
if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying)
CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this);
#endif
}
#endif // if UNITY_EDITOR
public virtual void Rebuild(CanvasUpdate executing) {
#if UNITY_EDITOR
if (executing == CanvasUpdate.Prelayout)
OnValueChanged.Invoke(Value);
#endif
}
public void LayoutComplete() { }
public void GraphicUpdateComplete() { }
protected override void OnEnable() {
base.OnEnable();
UpdateCachedReferences();
// Update rects since they need to be initialized correctly.
UpdateVisuals();
}
private void UpdateCachedReferences() {
if (_handleRect) {
_handleTransform = _handleRect.transform;
if (_handleTransform.parent != null)
_handleContainerRect = _handleTransform.parent.GetComponent<RectTransform>();
}
else {
_handleContainerRect = null;
}
}
protected override void OnRectTransformDimensionsChange() {
base.OnRectTransformDimensionsChange();
UpdateVisuals();
}
// Force-update the slider. Useful if you've changed the properties and want it to update visually.
private void UpdateVisuals() {
if (_handleContainerRect != null) {
_handleRect.anchorMin = _value;
_handleRect.anchorMax = _value;
}
}
// Update the slider's position based on the mouse.
private void UpdateDrag(PointerEventData eventData, Camera cam) {
var clickRect = _handleContainerRect;
if (clickRect != null && clickRect.rect.size[0] > 0) {
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(clickRect, eventData.position, cam, out var localCursor))
return;
var rect = clickRect.rect;
localCursor -= rect.position;
Vector2 newVal;
newVal.x = Mathf.Clamp01((localCursor - _offset).x / rect.size.x);
newVal.y = Mathf.Clamp01((localCursor - _offset).y / rect.size.y);
Value = newVal;
}
}
private bool MayDrag(PointerEventData eventData) {
return IsActive() && IsInteractable() && eventData.button == PointerEventData.InputButton.Left;
}
public override void OnPointerDown(PointerEventData eventData) {
if (!MayDrag(eventData))
return;
base.OnPointerDown(eventData);
_offset = Vector2.zero;
if (_handleContainerRect != null && RectTransformUtility.RectangleContainsScreenPoint(_handleRect, eventData.position, eventData.enterEventCamera)) {
Vector2 localMousePos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(_handleRect, eventData.position, eventData.pressEventCamera, out localMousePos))
_offset = localMousePos;
_offset.y = -_offset.y;
}
else {
// Outside the slider handle - jump to this point instead
UpdateDrag(eventData, eventData.pressEventCamera);
}
}
public virtual void OnDrag(PointerEventData eventData) {
if (!MayDrag(eventData))
return;
UpdateDrag(eventData, eventData.pressEventCamera);
}
public virtual void OnInitializePotentialDrag(PointerEventData eventData) {
eventData.useDragThreshold = false;
}
}
}

@ -0,0 +1,63 @@
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorBoxSlider : MonoBehaviour {
[SerializeField] public ColorPickerController.SVUpdateEvent onValueChanged = new ColorPickerController.SVUpdateEvent();
[SerializeField] private Vector2 _value;
[SerializeField] private BoxSlider _slider;
[SerializeField] private Image _image;
private bool _ignoreUpdates;
public Vector2 Value {
get => _value;
set {
_value.x = Mathf.Clamp01(value.x);
_value.y = Mathf.Clamp01(value.y);
UpdateSlider();
UpdateChannels();
}
}
public void OnColorUpdate(Color rgb, Util.ColorHSV hsv) {
if (_ignoreUpdates) return;
_image.material.SetColor(PropertyIDs._Color, (Vector4) hsv);
_value.x = hsv.s;
_value.y = hsv.v;
UpdateSlider();
}
public void OnSliderUpdate(Vector2 value) {
if (_ignoreUpdates) return;
_value = value;
UpdateChannels();
}
private void Awake() {
var boxSlider = gameObject.GetComponentInChildren<BoxSlider>();
boxSlider.OnValueChanged.AddListener(OnSliderUpdate);
}
private void UpdateChannels() {
_ignoreUpdates = true;
onValueChanged.Invoke(_value);
_ignoreUpdates = false;
}
private void UpdateSlider() {
_ignoreUpdates = true;
_slider.Value = _value;
_ignoreUpdates = false;
}
}
}

@ -0,0 +1,79 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorChannelSlider : MonoBehaviour {
[SerializeField] public ColorPickerController.ChannelUpdateEvent onChannelChanged = new ColorPickerController.ChannelUpdateEvent();
[SerializeField] private float _value;
[SerializeField] private int _channel;
[SerializeField] private bool _hsv;
[SerializeField] private Selectable _textBox;
[SerializeField] private Slider _slider;
[SerializeField] private Image _image;
private bool _ignoreUpdates;
public float Value {
get => _value;
set {
_value = Mathf.Clamp01(value);
UpdateSlider();
UpdateTextbox();
UpdateChannel();
}
}
public void OnColorUpdate(Color rgb, Util.ColorHSV hsv) {
if (_ignoreUpdates) return;
_image.material.SetColor(PropertyIDs._Color, _hsv ? (Color) (Vector4) hsv : rgb);
_value = _hsv ? hsv[_channel] : rgb[_channel];
UpdateSlider();
UpdateTextbox();
}
public void OnTextBoxUpdate(string text) {
if (_ignoreUpdates) return;
if (byte.TryParse(text, out byte byteValue)) {
_value = (float) byteValue / 255;
UpdateSlider();
UpdateChannel();
}
else {
// value is invalid, reset value
UpdateTextbox();
}
}
public void OnSliderUpdate(float value) {
if (_ignoreUpdates) return;
_value = value;
UpdateTextbox();
UpdateChannel();
}
private void UpdateChannel() {
onChannelChanged.Invoke(_value, _channel, _hsv);
}
private void UpdateSlider() {
_ignoreUpdates = true;
_slider.value = _value;
_ignoreUpdates = false;
}
private void UpdateTextbox() {
if (_textBox == null) return;
_ignoreUpdates = true;
((TMP_InputField) _textBox).text = ((byte) (255 * _value)).ToString();
_ignoreUpdates = false;
}
}
}

@ -0,0 +1,117 @@
using System;
using ConformalDecals.Util;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class ColorPickerController : MonoBehaviour {
[Serializable]
public class ColorUpdateEvent : UnityEvent<Color, Util.ColorHSV> { }
[Serializable]
public class ChannelUpdateEvent : UnityEvent<float, int, bool> { }
[Serializable]
public class SVUpdateEvent : UnityEvent<Vector2> { }
[SerializeField] public ColorUpdateEvent onColorChanged = new ColorUpdateEvent();
[SerializeField] private Color _value;
[SerializeField] private Image _previewImage;
[SerializeField] private Selectable _hexTextBox;
private bool _ignoreUpdate;
public Color RGB {
get => _value;
set {
_value = value;
OnColorUpdate();
}
}
public Util.ColorHSV HSV {
get => Util.ColorHSV.RGB2HSV(_value);
set {
_value = Util.ColorHSV.HSV2RGB(value);
OnColorUpdate();
}
}
public static ColorPickerController Create(Color rgb, UnityAction<Color, Util.ColorHSV> colorUpdateCallback) {
var menu = Instantiate(UILoader.ColorPickerPrefab, MainCanvasUtil.MainCanvas.transform, true);
menu.AddComponent<DragPanel>();
MenuNavigation.SpawnMenuNavigation(menu, Navigation.Mode.Automatic, true);
var controller = menu.GetComponent<ColorPickerController>();
controller.RGB = rgb;
controller.onColorChanged.AddListener(colorUpdateCallback);
return controller;
}
public void Close() {
Destroy(gameObject);
}
public void OnChannelUpdate(float value, int channel, bool hsv) {
if (hsv) {
var newHSV = HSV;
newHSV[channel] = value;
HSV = newHSV;
}
else {
var newRGB = RGB;
newRGB[channel] = value;
RGB = newRGB;
}
}
public void OnSVUpdate(Vector2 sv) {
var newHSV = HSV;
newHSV.s = sv.x;
newHSV.v = sv.y;
HSV = newHSV;
}
public void OnColorUpdate() {
onColorChanged.Invoke(RGB, HSV);
_previewImage.material.SetColor(PropertyIDs._Color, RGB);
UpdateHexColor();
}
public void OnHexColorUpdate(string text) {
if (_ignoreUpdate) return;
if (ParseUtil.TryParseHexColor(text, out var newRGB)) {
RGB = newRGB;
OnColorUpdate();
}
else {
UpdateHexColor();
}
}
private void Awake() {
foreach (var slider in gameObject.GetComponentsInChildren<ColorChannelSlider>()) {
slider.onChannelChanged.AddListener(OnChannelUpdate);
onColorChanged.AddListener(slider.OnColorUpdate);
}
foreach (var box in gameObject.GetComponentsInChildren<ColorBoxSlider>()) {
box.onValueChanged.AddListener(OnSVUpdate);
onColorChanged.AddListener(box.OnColorUpdate);
}
}
private void UpdateHexColor() {
_ignoreUpdate = true;
var byteColor = (Color32) RGB;
var hexColor = $"{byteColor.r:x2}{byteColor.g:x2}{byteColor.b:x2}";
((TMP_InputField) _hexTextBox).text = hexColor;
_ignoreUpdate = false;
}
}
}

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using ConformalDecals.Text;
using UniLinq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class FontMenuController : MonoBehaviour {
[Serializable]
public class FontUpdateEvent : UnityEvent<DecalFont> { }
[SerializeField] public FontUpdateEvent onFontChanged = new FontUpdateEvent();
[SerializeField] private GameObject _menuItem;
[SerializeField] private GameObject _menuList;
private DecalFont _currentFont;
public static FontMenuController Create(IEnumerable<DecalFont> fonts, DecalFont currentFont, UnityAction<DecalFont> fontUpdateCallback) {
var menu = Instantiate(UILoader.FontMenuPrefab, MainCanvasUtil.MainCanvas.transform, true);
menu.AddComponent<DragPanel>();
MenuNavigation.SpawnMenuNavigation(menu, Navigation.Mode.Automatic, true);
var controller = menu.GetComponent<FontMenuController>();
controller._currentFont = currentFont;
controller.onFontChanged.AddListener(fontUpdateCallback);
controller.Populate(fonts);
return controller;
}
public void Close() {
Destroy(gameObject);
}
public void OnFontSelected(DecalFont font) {
_currentFont = font ?? throw new ArgumentNullException(nameof(font));
onFontChanged.Invoke(_currentFont);
}
public void Populate(IEnumerable<DecalFont> fonts) {
if (fonts == null) throw new ArgumentNullException(nameof(fonts));
Toggle active = null;
foreach (var font in fonts.OrderBy(x => x.Title)) {
Debug.Log(font.Title);
var listItem = GameObject.Instantiate(_menuItem, _menuList.transform);
listItem.name = font.Title;
listItem.SetActive(true);
var fontItem = listItem.AddComponent<FontMenuItem>();
fontItem.Font = font;
fontItem.fontSelectionCallback = OnFontSelected;
if (font == _currentFont) active = fontItem.toggle;
}
if (active != null) active.isOn = true;
}
}
}

@ -0,0 +1,36 @@
using System;
using ConformalDecals.Text;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class FontMenuItem : MonoBehaviour {
public DecalFont Font {
get => _font;
set {
_font = value;
_font.SetupSample(_label);
}
}
public delegate void FontSelectionReceiver(DecalFont font);
public FontSelectionReceiver fontSelectionCallback;
public Toggle toggle;
private DecalFont _font;
private TMP_Text _label;
private void Awake() {
_label = gameObject.GetComponentInChildren<TextMeshProUGUI>();
toggle = gameObject.GetComponent<Toggle>();
toggle.isOn = false;
toggle.onValueChanged.AddListener(delegate { OnToggle(toggle); });
}
public void OnToggle(Toggle change) {
if (change.isOn) fontSelectionCallback?.Invoke(_font);
}
}
}

@ -0,0 +1,263 @@
using System;
using ConformalDecals.Text;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace ConformalDecals.UI {
public class TextEntryController : MonoBehaviour {
[Serializable]
public class TextUpdateEvent : UnityEvent<string, DecalFont, DecalTextStyle> { }
[SerializeField] public TextUpdateEvent onValueChanged = new TextUpdateEvent();
[SerializeField] private Selectable _textBox;
[SerializeField] private Button _fontButton;
[SerializeField] private Slider _lineSpacingSlider;
[SerializeField] private Selectable _lineSpacingTextBox;
[SerializeField] private Slider _charSpacingSlider;
[SerializeField] private Selectable _charSpacingTextBox;
[SerializeField] private Toggle _boldButton;
[SerializeField] private Toggle _italicButton;
[SerializeField] private Toggle _underlineButton;
[SerializeField] private Toggle _smallCapsButton;
[SerializeField] private Toggle _verticalButton;
private string _text;
private DecalFont _font;
private DecalTextStyle _style;
private Vector2 _lineSpacingRange;
private Vector2 _charSpacingRange;
private FontMenuController _fontMenu;
private bool _ignoreUpdates;
public static TextEntryController Create(
string text, DecalFont font, DecalTextStyle style,
Vector2 lineSpacingRange, Vector2 charSpacingRange,
UnityAction<string, DecalFont, DecalTextStyle> textUpdateCallback) {
var window = Instantiate(UILoader.TextEntryPrefab, MainCanvasUtil.MainCanvas.transform, true);
window.AddComponent<DragPanel>();
MenuNavigation.SpawnMenuNavigation(window, Navigation.Mode.Automatic, true);
var controller = window.GetComponent<TextEntryController>();
controller._text = text;
controller._font = font;
controller._style = style;
controller._lineSpacingRange = lineSpacingRange;
controller._charSpacingRange = charSpacingRange;
controller.onValueChanged.AddListener(textUpdateCallback);
return controller;
}
public void Close() {
if (_fontMenu != null) _fontMenu.Close();
Destroy(gameObject);
}
public void OnTextUpdate(string newText) {
this._text = newText;
OnValueChanged();
}
public void OnFontMenu() {
if (_fontMenu == null) _fontMenu = FontMenuController.Create(DecalConfig.Fonts, _font, OnFontUpdate);
}
public void OnFontUpdate(DecalFont font) {
if (_ignoreUpdates) return;
_font = font;
font.SetupSample(_fontButton.GetComponentInChildren<TextMeshProUGUI>());
var textBox = ((TMP_InputField) _textBox);
textBox.textComponent.fontStyle = _style.FontStyle | _font.FontStyle;
textBox.fontAsset = _font.FontAsset;
UpdateStyleButtons();
OnValueChanged();
}
public void OnLineSpacingUpdate(float value) {
if (_ignoreUpdates) return;
_style.LineSpacing = Mathf.Lerp(_lineSpacingRange.x, _lineSpacingRange.y, value);
UpdateLineSpacing();
OnValueChanged();
}
public void OnLineSpacingUpdate(string text) {
if (_ignoreUpdates) return;
if (float.TryParse(text, out var value)) {
_style.LineSpacing = Mathf.Clamp(value, _lineSpacingRange.x, _lineSpacingRange.y);
}
else {
Debug.LogWarning("[ConformalDecals] line spacing value '{text}' could not be parsed.");
}
UpdateLineSpacing();
OnValueChanged();
}
public void OnCharSpacingUpdate(float value) {
if (_ignoreUpdates) return;
_style.CharSpacing = Mathf.Lerp(_charSpacingRange.x, _charSpacingRange.y, value);
UpdateCharSpacing();
OnValueChanged();
}
public void OnCharSpacingUpdate(string text) {
if (_ignoreUpdates) return;
if (float.TryParse(text, out var value)) {
_style.CharSpacing = Mathf.Clamp(value, _charSpacingRange.x, _charSpacingRange.y);
}
else {
Debug.LogWarning("[ConformalDecals] char spacing value '{text}' could not be parsed.");
}
UpdateCharSpacing();
OnValueChanged();
}
public void OnBoldUpdate(bool state) {
if (_ignoreUpdates) return;
_style.Bold = state;
OnValueChanged();
}
public void OnItalicUpdate(bool state) {
if (_ignoreUpdates) return;
_style.Italic = state;
OnValueChanged();
}
public void OnUnderlineUpdate(bool state) {
if (_ignoreUpdates) return;
_style.Underline = state;
OnValueChanged();
}
public void OnSmallCapsUpdate(bool state) {
if (_ignoreUpdates) return;
_style.SmallCaps = state;
OnValueChanged();
}
public void OnVerticalUpdate(bool state) {
if (_ignoreUpdates) return;
_style.Vertical = state;
OnValueChanged();
}
private void Start() {
((TMP_InputField) _textBox).text = _text;
_font.SetupSample(_fontButton.GetComponentInChildren<TextMeshProUGUI>());
UpdateStyleButtons();
UpdateLineSpacing();
UpdateCharSpacing();
}
private void OnValueChanged() {
onValueChanged.Invoke(_text, _font, _style);
}
private void UpdateStyleButtons() {
_ignoreUpdates = true;
if (_font.Bold) {
_boldButton.interactable = false;
_boldButton.isOn = true;
}
else if (_font.BoldMask) {
_boldButton.interactable = false;
_boldButton.isOn = false;
}
else {
_boldButton.interactable = true;
_boldButton.isOn = _style.Bold;
}
if (_font.Italic) {
_italicButton.interactable = false;
_italicButton.isOn = true;
}
else if (_font.ItalicMask) {
_italicButton.interactable = false;
_italicButton.isOn = false;
}
else {
_italicButton.interactable = true;
_italicButton.isOn = _style.Italic;
}
if (_font.Underline) {
_underlineButton.interactable = false;
_underlineButton.isOn = true;
}
else if (_font.UnderlineMask) {
_underlineButton.interactable = false;
_underlineButton.isOn = false;
}
else {
_underlineButton.interactable = true;
_underlineButton.isOn = _style.Underline;
}
if (_font.SmallCaps) {
_smallCapsButton.interactable = false;
_smallCapsButton.isOn = true;
}
else if (_font.SmallCapsMask) {
_smallCapsButton.interactable = false;
_smallCapsButton.isOn = false;
}
else {
_smallCapsButton.interactable = true;
_smallCapsButton.isOn = _style.SmallCaps;
}
_verticalButton.isOn = _style.Vertical;
_ignoreUpdates = false;
}
private void UpdateLineSpacing() {
_ignoreUpdates = true;
_lineSpacingSlider.value = Mathf.InverseLerp(_lineSpacingRange.x, _lineSpacingRange.y, _style.LineSpacing);
((TMP_InputField) _lineSpacingTextBox).text = $"{_style.LineSpacing:F1}";
_ignoreUpdates = false;
}
private void UpdateCharSpacing() {
_ignoreUpdates = true;
_charSpacingSlider.value = Mathf.InverseLerp(_charSpacingRange.x, _charSpacingRange.y, _style.CharSpacing);
((TMP_InputField) _charSpacingTextBox).text = $"{_style.CharSpacing:F1}";
_ignoreUpdates = false;
}
}
}

@ -0,0 +1,140 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace ConformalDecals.UI {
[KSPAddon(KSPAddon.Startup.Instantly, true)]
public class UILoader : MonoBehaviour {
private static readonly string Path = KSPUtil.ApplicationRootPath + "GameData/ConformalDecals/Resources/";
private static GameObject _textEntryPrefab;
private static GameObject _fontMenuPrefab;
private static GameObject _colorPickerPrefab;
public static GameObject FontMenuPrefab => _fontMenuPrefab;
public static GameObject TextEntryPrefab => _textEntryPrefab;
public static GameObject ColorPickerPrefab => _colorPickerPrefab;
private void Awake() {
var prefabs = AssetBundle.LoadFromFile(Path + "ui.conformaldecals");
_textEntryPrefab = prefabs.LoadAsset("TextEntryPanel") as GameObject;
_fontMenuPrefab = prefabs.LoadAsset("FontMenuPanel") as GameObject;
_colorPickerPrefab = prefabs.LoadAsset("ColorPickerPanel") as GameObject;
ProcessWindow(_textEntryPrefab);
ProcessWindow(_fontMenuPrefab);
ProcessWindow(_colorPickerPrefab);
}
private static void ProcessWindow(GameObject window) {
var skin = UISkinManager.defaultSkin;
var font = UISkinManager.TMPFont;
var background = window.GetComponent<Image>();
background.sprite = skin.window.normal.background;
background.type = Image.Type.Sliced;
var texts = window.GetComponentsInChildren<TextMeshProUGUI>(true);
foreach (var text in texts) {
ProcessText(text, font, Color.white);
}
var tags = window.GetComponentsInChildren<UITag>(true);
foreach (var tag in tags) {
Debug.Log($"Handling object {tag.gameObject.name}");
switch (tag.type) {
case UITag.UIType.Window:
ProcessImage(tag.gameObject, skin.window);
break;
case UITag.UIType.Button:
ProcessSelectable(tag.gameObject, skin.button);
break;
case UITag.UIType.ButtonToggle:
ProcessToggleButton(tag.gameObject, skin.button);
break;
case UITag.UIType.RadioToggle:
ProcessSelectable(tag.gameObject, skin.toggle);
break;
case UITag.UIType.BoxSlider:
case UITag.UIType.Slider:
ProcessSlider(tag.gameObject, skin.horizontalSlider, skin.horizontalSliderThumb);
break;
case UITag.UIType.Box:
ProcessSelectable(tag.gameObject, skin.box);
break;
case UITag.UIType.Dropdown:
ProcessDropdown(tag.gameObject, skin.button, skin.window);
break;
case UITag.UIType.Label:
ProcessText(tag.GetComponent<TextMeshProUGUI>(), font, new Color(0.718f, 0.996f, 0.000f, 1.000f), 14);
break;
case UITag.UIType.Header:
ProcessText(tag.GetComponent<TextMeshProUGUI>(), font, new Color(0.718f, 0.996f, 0.000f, 1.000f), 16);
break;
}
}
}
private static void ProcessImage(GameObject gameObject, UIStyle style) {
var image = gameObject.GetComponent<Image>();
if (image != null) {
ProcessImage(image, style.normal);
}
}
private static void ProcessImage(Image image, UIStyleState state) {
image.sprite = state.background;
image.color = Color.white;
image.type = Image.Type.Sliced;
}
private static void ProcessSelectable(GameObject gameObject, UIStyle style) {
var selectable = gameObject.GetComponent<Selectable>();
if (selectable == null) {
ProcessImage(gameObject, style);
}
else {
ProcessImage(selectable.image, style.normal);
selectable.transition = Selectable.Transition.SpriteSwap;
var state = selectable.spriteState;
state.highlightedSprite = style.highlight.background;
state.selectedSprite = style.highlight.background;
state.pressedSprite = style.active.background;
state.disabledSprite = style.disabled.background;
selectable.spriteState = state;
}
}
private static void ProcessToggleButton(GameObject gameObject, UIStyle style) {
ProcessSelectable(gameObject, style);
var toggle = gameObject.GetComponent<Toggle>();
if (toggle != null) ProcessImage(toggle.graphic as Image, style.active);
}
private static void ProcessSlider(GameObject gameObject, UIStyle backgroundStyle, UIStyle thumbStyle) {
ProcessSelectable(gameObject, thumbStyle);
var background = gameObject.transform.Find("Background");
if (background != null) ProcessImage(background.gameObject, backgroundStyle);
}
private static void ProcessDropdown(GameObject gameObject, UIStyle buttonStyle, UIStyle windowStyle) {
ProcessSelectable(gameObject, buttonStyle);
var template = gameObject.transform.Find("Template").gameObject;
if (template != null) ProcessImage(template, windowStyle);
}
private static void ProcessText(TextMeshProUGUI text, TMP_FontAsset font, Color color, int size = -1) {
text.font = font;
text.color = color;
if (size > 0) text.fontSize = size;
}
}
}

@ -0,0 +1,21 @@
using UnityEngine;
namespace ConformalDecals.UI {
public class UITag : MonoBehaviour {
public enum UIType {
None,
Window,
Box,
Button,
ButtonToggle,
RadioToggle,
Slider,
Dropdown,
Label,
Header,
BoxSlider
}
[SerializeField] public UIType type = UIType.None;
}
}

@ -0,0 +1,161 @@
using System;
using System.Globalization;
using UnityEngine;
namespace ConformalDecals.Util {
public struct ColorHSL : IEquatable<Color> {
public float h;
public float s;
public float l;
public float a;
public ColorHSL(float h, float s = 1, float l = 0.5f, float a = 1) {
this.h = h;
this.s = s;
this.l = l;
this.a = a;
}
public override string ToString() {
return $"HSLA({this.h:F3}, {this.s:F3}, {this.l:F3}, {this.a:F3})";
}
public string ToString(string format) {
return
"HSLA(" +
$"{this.h.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.s.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.l.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.a.ToString(format, CultureInfo.InvariantCulture.NumberFormat)})";
}
public bool Equals(ColorHSL other) {
return (this.h.Equals(other.h) && this.s.Equals(other.s) && this.l.Equals(other.l) && this.a.Equals(other.a));
}
public bool Equals(Color other) {
var rgb = HSL2RGB(this);
return rgb.Equals(other);
}
public override bool Equals(object obj) {
if (obj is ColorHSL otherHSL) return Equals(otherHSL);
if (obj is Color otherRGB) return Equals(otherRGB);
return false;
}
public override int GetHashCode() {
return ((Vector4) this).GetHashCode();
}
public float this[int index] {
get {
switch (index) {
case 0:
return this.h;
case 1:
return this.s;
case 2:
return this.l;
case 3:
return this.a;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
set {
switch (index) {
case 0:
this.h = value;
break;
case 1:
this.s = value;
break;
case 2:
this.l = value;
break;
case 3:
this.a = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
}
public static bool operator ==(ColorHSL lhs, ColorHSL rhs) {
return lhs.Equals(rhs);
}
public static bool operator !=(ColorHSL lhs, ColorHSL rhs) {
return !(lhs == rhs);
}
public static implicit operator Vector4(ColorHSL c) {
return new Vector4(c.h, c.s, c.l, c.a);
}
public static implicit operator ColorHSL(Vector4 v) {
return new ColorHSL(v.x, v.y, v.z, v.w);
}
public static implicit operator ColorHSL(Color rgb) {
return RGB2HSL(rgb);
}
public static implicit operator Color(ColorHSL hsl) {
return HSL2RGB(hsl);
}
public static Color HSL2RGB(ColorHSL hsl) {
float a = hsl.s * Mathf.Min(hsl.l, 1 - hsl.l);
float Component(int n) {
float k = (n + hsl.h * 12) % 12;
return hsl.l - a * Mathf.Max(-1, Mathf.Min(k - 3, Mathf.Min(9 - k, 1)));
}
return new Color(Component(0), Component(8), Component(4), hsl.a);
}
public static ColorHSL RGB2HSL(Color rgb) {
float h = 0;
float s = 0;
float l = 0;
if (rgb.r >= rgb.g && rgb.r >= rgb.b) {
float xMin = Mathf.Min(rgb.g, rgb.b);
l = (rgb.r + xMin) / 2;
s = (rgb.r - l) / Mathf.Min(l, 1 - l);
float c = rgb.r - xMin;
if (c > Mathf.Epsilon) h = (rgb.g - rgb.b) / (6 * c);
}
else if (rgb.g >= rgb.r && rgb.g >= rgb.b) {
float xMin = Mathf.Min(rgb.r, rgb.b);
l = (rgb.g + xMin) / 2;
s = (rgb.g - l) / Mathf.Min(l, 1 - l);
float c = rgb.g - xMin;
if (c > Mathf.Epsilon) h = (2 + ((rgb.b - rgb.r) / c)) / 6;
}
else if (rgb.b >= rgb.r && rgb.b >= rgb.g) {
float xMin = Mathf.Min(rgb.r, rgb.g);
l = (rgb.b + xMin) / 2;
s = (rgb.b - l) / Mathf.Min(l, 1 - l);
float c = rgb.g - xMin;
if (c > Mathf.Epsilon) h = (4 + ((rgb.r - rgb.g) / c)) / 6;
}
return new ColorHSL(h, s, l, rgb.a);
}
}
}

@ -0,0 +1,123 @@
using System;
using System.Globalization;
using UnityEngine;
namespace ConformalDecals.Util {
public struct ColorHSV : IEquatable<Color> {
public float h;
public float s;
public float v;
public float a;
public ColorHSV(float h, float s = 1, float v = 1, float a = 1) {
this.h = h;
this.s = s;
this.v = v;
this.a = a;
}
public override string ToString() {
return $"HSVA({this.h:F3}, {this.s:F3}, {this.v:F3}, {this.a:F3})";
}
public string ToString(string format) {
return
"HSVA(" +
$"{this.h.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.s.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.v.ToString(format, CultureInfo.InvariantCulture.NumberFormat)}, " +
$"{this.a.ToString(format, CultureInfo.InvariantCulture.NumberFormat)})";
}
public bool Equals(ColorHSL other) {
return (this.h.Equals(other.h) && this.s.Equals(other.s) && this.v.Equals(other.l) && this.a.Equals(other.a));
}
public bool Equals(Color other) {
var rgb = HSV2RGB(this);
return rgb.Equals(other);
}
public override bool Equals(object obj) {
if (obj is ColorHSL otherHSL) return Equals(otherHSL);
if (obj is Color otherRGB) return Equals(otherRGB);
return false;
}
public override int GetHashCode() {
return ((Vector4) this).GetHashCode();
}
public float this[int index] {
get {
switch (index) {
case 0:
return this.h;
case 1:
return this.s;
case 2:
return this.v;
case 3:
return this.a;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
set {
switch (index) {
case 0:
this.h = value;
break;
case 1:
this.s = value;
break;
case 2:
this.v = value;
break;
case 3:
this.a = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
}
public static bool operator ==(ColorHSV lhs, ColorHSV rhs) {
return lhs.Equals(rhs);
}
public static bool operator !=(ColorHSV lhs, ColorHSV rhs) {
return !(lhs == rhs);
}
public static implicit operator Vector4(ColorHSV c) {
return new Vector4(c.h, c.s, c.v, c.a);
}
public static implicit operator ColorHSV(Vector4 v) {
return new ColorHSV(v.x, v.y, v.z, v.w);
}
public static implicit operator ColorHSV(Color rgb) {
return RGB2HSV(rgb);
}
public static implicit operator Color(ColorHSV hsv) {
return HSV2RGB(hsv);
}
public static Color HSV2RGB(ColorHSV hsv) {
var rgb = Color.HSVToRGB(hsv.h, hsv.s, hsv.v, false);
rgb.a = hsv.a;
return rgb;
}
public static ColorHSV RGB2HSV(Color rgb) {
var hsv = new ColorHSV {a = rgb.a};
Color.RGBToHSV(rgb, out hsv.h, out hsv.s, out hsv.v);
return hsv;
}
}
}

@ -139,44 +139,53 @@ namespace ConformalDecals.Util {
throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} : '{valueString}");
}
public static bool TryParseHexColor(string valueString, out Color32 value) {
value = new Color32(0, 0, 0, byte.MaxValue);
if (!int.TryParse(valueString, System.Globalization.NumberStyles.HexNumber, null, out var hexColor)) return false;
switch (valueString.Length) {
case 8: // RRGGBBAA
value.a = (byte) (hexColor & 0xFF);
hexColor >>= 8;
goto case 6;
case 6: // RRGGBB
value.b = (byte) (hexColor & 0xFF);
hexColor >>= 8;
value.g = (byte) (hexColor & 0xFF);
hexColor >>= 8;
value.r = (byte) (hexColor & 0xFF);
return true;
case 4: // RGBA
value.a = (byte) ((hexColor & 0xF) << 4);
hexColor >>= 4;
goto case 3;
case 3: // RGB
value.b = (byte) (hexColor & 0xF << 4);
hexColor >>= 4;
value.g = (byte) (hexColor & 0xF << 4);
hexColor >>= 4;
value.r = (byte) (hexColor & 0xF << 4);
return true;
default:
return false;
}
}
public static bool TryParseColor32(string valueString, out Color32 value) {
value = new Color32(0, 0, 0, byte.MaxValue);
// HTML-style hex color
// hex color
if (valueString[0] == '#') {
var hexColorString = valueString.Substring(1);
if (!int.TryParse(hexColorString, System.Globalization.NumberStyles.HexNumber, null, out var hexColor)) return false;
switch (hexColorString.Length) {
case 8: // RRGGBBAA
value.a = (byte) (hexColor & 0xFF);
hexColor >>= 8;
goto case 6;
case 6: // RRGGBB
value.b = (byte) (hexColor & 0xFF);
hexColor >>= 8;
value.g = (byte) (hexColor & 0xFF);
hexColor >>= 8;
value.r = (byte) (hexColor & 0xFF);
return true;
case 4: // RGBA
value.a = (byte) ((hexColor & 0xF) << 4);
hexColor >>= 4;
goto case 3;
case 3: // RGB
value.b = (byte) (hexColor & 0xF << 4);
hexColor >>= 4;
value.g = (byte) (hexColor & 0xF << 4);
hexColor >>= 4;
value.r = (byte) (hexColor & 0xF << 4);
return true;
default:
return false;
if (TryParseHexColor(hexColorString, out var hexColor)) {
value = hexColor;
return true;
}
}

@ -0,0 +1,263 @@
using UnityEngine;
namespace ConformalDecals.Util {
public static class TextureUtils {
public enum BlitMode {
Set,
Add,
}
public static Color32 AddColor32(Color32 color1, Color32 color2) {
return new Color32((byte) (color1.r + color2.r), (byte) (color1.g + color2.g), (byte) (color1.b + color2.b), (byte) (color1.a + color2.a));
}
public static Color32 AddColor32Clamped(Color32 color1, Color32 color2) {
var r = color1.r + color2.r;
var g = color1.g + color2.g;
var b = color1.b + color2.b;
var a = color1.a + color2.a;
if (r > byte.MaxValue) r = byte.MaxValue;
if (g > byte.MaxValue) g = byte.MaxValue;
if (b > byte.MaxValue) b = byte.MaxValue;
if (a > byte.MaxValue) a = byte.MaxValue;
return new Color32((byte) r, (byte) g, (byte) b, (byte) a);
}
public static void ClearTexture(Color32[] colors, Color32 clearColor = default) {
for (var i = 0; i < colors.Length; i++) {
colors[i] = clearColor;
}
}
public static void BlitRectAlpha(
Texture2D src, Color32[] srcColors, Vector2Int srcPos,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos,
Vector2Int size, BlitMode mode) {
ClipRect(src, ref srcPos, dst, ref dstPos, ref size);
if (size.x <= 0 || size.y <= 0) return;
int srcIndex = srcPos.x + srcPos.y * src.width;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = size.y - 1; dy >= 0; dy--) {
for (int dx = size.x - 1; dx >= 0; dx--) {
switch (mode) {
case BlitMode.Set:
dstColors[dstIndex + dx].a = srcColors[srcIndex + dx].a;
break;
case BlitMode.Add:
var s = srcColors[srcIndex + dx].a;
var d = dstColors[dstIndex + dx].a;
var sum = s + d;
if (sum > byte.MaxValue) sum = byte.MaxValue;
dstColors[dstIndex + dx].a = (byte) sum;
break;
}
}
srcIndex += src.width;
dstIndex += dst.width;
}
}
public static void BlitRect(
Texture2D src, Color32[] srcColors, Vector2Int srcPos,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos,
Vector2Int size, BlitMode mode) {
ClipRect(src, ref srcPos, dst, ref dstPos, ref size);
if (size.x <= 0 || size.y <= 0) return;
int srcIndex = srcPos.x + srcPos.y * src.width;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = 0; dy < size.y; dy++) {
for (int dx = 0; dx < size.x; dx++) {
switch (mode) {
case BlitMode.Set:
dstColors[dstIndex + dx] = srcColors[srcIndex + dx];
break;
case BlitMode.Add:
dstColors[dstIndex + dx] = AddColor32Clamped(srcColors[srcIndex + dx], dstColors[dstIndex + dx]);
break;
}
}
srcIndex += src.width;
dstIndex += dst.width;
}
}
public static void BlitRectBilinearAlpha(
Texture2D src, Vector2Int srcPos, Vector2 srcSize,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos, Vector2Int dstSize,
BlitMode mode) {
var sizeRatio = dstSize / srcSize;
ClipRect(src, ref srcPos, dst, ref dstPos, ref srcSize, ref dstSize);
if (dstSize.x <= 0 || dstSize.y <= 0) return;
var srcPixel = new Vector2(1.0f / src.width, 1.0f / src.height);
var srcStart = (srcPos * srcPixel) + (srcPixel / 2);
var srcStep = sizeRatio * srcPixel;
var srcY = srcStart.y;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = 0;
dy < dstSize.y;
dy++) {
var srcX = srcStart.x;
for (int dx = 0; dx < dstSize.x; dx++) {
switch (mode) {
case BlitMode.Set:
dstColors[dstIndex + dx].a = (byte) (src.GetPixelBilinear(srcX, srcY).a * byte.MaxValue);
break;
case BlitMode.Add:
var s = (byte) (src.GetPixelBilinear(srcX, srcY).a * byte.MaxValue);
var d = dstColors[dstIndex + dx].a;
var sum = s + d;
if (sum > byte.MaxValue) sum = byte.MaxValue;
dstColors[dstIndex + dx].a = (byte) sum;
break;
}
srcX += srcStep.x;
}
srcY += srcStep.y;
dstIndex += dst.width;
}
}
public static void BlitRectBilinear(
Texture2D src, Vector2Int srcPos, Vector2 srcSize,
Texture2D dst, Color32[] dstColors, Vector2Int dstPos, Vector2Int dstSize,
BlitMode mode) {
var sizeRatio = dstSize / srcSize;
ClipRect(src, ref srcPos, dst, ref dstPos, ref srcSize, ref dstSize);
if (dstSize.x <= 0 || dstSize.y <= 0) return;
var srcPixel = new Vector2(1.0f / src.width, 1.0f / src.height);
var srcStart = (srcPos * srcPixel) + (srcPixel / 2);
var srcStep = sizeRatio * srcPixel;
var srcY = srcStart.y;
int dstIndex = dstPos.x + dstPos.y * dst.width;
for (int dy = 0;
dy < dstSize.y;
dy++) {
var srcX = srcStart.x;
for (int dx = 0; dx < dstSize.x; dx++) {
switch (mode) {
case BlitMode.Set:
dstColors[dstIndex + dx] = src.GetPixelBilinear(srcX, srcY);
break;
case BlitMode.Add:
dstColors[dstIndex + dx] = AddColor32Clamped(src.GetPixelBilinear(srcX, srcY), dstColors[dstIndex + dx]);
break;
}
srcX += srcStep.x;
}
srcY += srcStep.y;
dstIndex += dst.width;
}
}
private static void ClipRect(Texture2D src, ref Vector2Int srcPos, Texture2D dst, ref Vector2Int dstPos, ref Vector2Int size) {
if (srcPos.x < 0) {
size.x += srcPos.x;
dstPos.x -= srcPos.x;
srcPos.x = 0;
}
if (srcPos.y < 0) {
size.y += srcPos.y;
dstPos.y -= srcPos.y;
srcPos.y = 0;
}
if (dstPos.x < 0) {
size.x += dstPos.x;
srcPos.x -= dstPos.x;
dstPos.x = 0;
}
if (dstPos.y < 0) {
size.y += dstPos.y;
srcPos.y -= dstPos.y;
dstPos.y = 0;
}
if (srcPos.x + size.x > src.width) size.x = src.width - srcPos.x;
if (srcPos.y + size.y > src.height) size.y = src.height - srcPos.y;
if (dstPos.x + size.x > dst.width) size.x = dst.width - srcPos.x;
if (dstPos.y + size.y > dst.height) size.y = dst.height - srcPos.y;
}
private static void ClipRect(Texture2D src, ref Vector2Int srcPos, Texture2D dst, ref Vector2Int dstPos, ref Vector2 srcSize, ref Vector2Int dstSize) {
var sizeRatio = dstSize / srcSize;
if (srcPos.x < 0) {
dstSize.x += (int) (srcPos.x * sizeRatio.x);
dstPos.x -= (int) (srcPos.x * sizeRatio.x);
srcSize.x += srcPos.x;
srcPos.x = 0;
}
if (srcPos.y < 0) {
dstSize.y += (int) (srcPos.y * sizeRatio.y);
dstPos.y -= (int) (srcPos.y * sizeRatio.y);
srcSize.y += srcPos.y;
srcPos.y = 0;
}
if (dstPos.x < 0) {
srcSize.x += dstPos.x / sizeRatio.x;
srcPos.x -= (int) (dstPos.x / sizeRatio.x);
dstSize.x += dstPos.x;
dstPos.x = 0;
}
if (dstPos.y < 0) {
srcSize.y += dstPos.y / sizeRatio.y;
srcPos.y -= (int) (dstPos.y / sizeRatio.y);
dstSize.y += dstPos.y;
dstPos.y = 0;
}
if (srcPos.x + srcSize.x > src.width) {
srcSize.x = src.width - srcPos.x;
dstSize.x = (int) (srcSize.x * sizeRatio.x);
}
if (srcPos.y + srcSize.y > src.height) {
srcSize.y = src.height - srcPos.y;
dstSize.y = (int) (srcSize.y * sizeRatio.y);
}
if (dstPos.x + dstSize.x > dst.width) {
dstSize.x = dst.width - srcPos.x;
srcSize.x = (int) (dstSize.x / sizeRatio.x);
}
if (dstPos.y + dstSize.y > dst.height) {
dstSize.y = dst.height - srcPos.y;
srcSize.y = (int) (dstSize.y / sizeRatio.y);
}
}
}
}

@ -2,15 +2,16 @@ v0.2.0
------
- New Parts:
- CDL-3 Surface Base Decal: A set of conformal decals based on the symbols from the movie Moon designed by Gavin Rothery
- CDL-T Custom Text Decal: A customizable text decal with a variety of fonts
- Changes:
- New "KEYWORD" material modifier, allowing for shader features to be enabled and disabled.
- material modifiers can now be removed in variants by setting `remove = true` inside them.
- Unified all shaders into a single "Standard" shader with variants supporting any combination of bump, specular and emissive maps, plus SDF alphas.
- Unified all decal shaders into a single "Standard" shader with variants supporting any combination of bump, specular and emissive maps, plus SDF alphas.
- Old shaders are remapped to Standard shader plus keywords automatically.
- New SDF-based antialiasing for when decals extend to their borders, i.e. on opaque flags.
- Fixes:
- Fixed WIDTH and HEIGHT scale modes being flipped
- Removed debug log statements
- Removed some debug log statements
v0.1.4
------

Loading…
Cancel
Save