using System;
using System.Collections.Generic;
using UnityEngine;
namespace ProceduralToolkit
{
///
/// Representation of color in HSV model
///
[Serializable]
public struct ColorHSV
{
///
/// Hue component of the color
///
public float h;
///
/// Saturation component of the color
///
public float s;
///
/// Value component of the color
///
public float v;
///
/// Alpha component of the color
///
public float a;
///
/// Returns the opposite color on the color wheel
///
public ColorHSV complementary => WithOffsetH(180);
///
/// Access the h, s, v, a components using [0], [1], [2], [3] respectively
///
public float this[int index]
{
get
{
switch (index)
{
case 0:
return h;
case 1:
return s;
case 2:
return v;
case 3:
return a;
default:
throw new IndexOutOfRangeException("Invalid ColorHSV index!");
}
}
set
{
switch (index)
{
case 0:
h = value;
break;
case 1:
s = value;
break;
case 2:
v = value;
break;
case 3:
a = value;
break;
default:
throw new IndexOutOfRangeException("Invalid ColorHSV index!");
}
}
}
///
/// Constructs a new ColorHSV with the given h, s, v, a components
///
/// Hue component
/// Saturation component
/// Value component
/// Alpha component
public ColorHSV(float h, float s, float v, float a)
{
this.h = h;
this.s = s;
this.v = v;
this.a = a;
}
///
/// Constructs a new ColorHSV with the given h, s, v components and sets the alpha to 1
///
/// Hue component
/// Saturation component
/// Value component
public ColorHSV(float h, float s, float v)
{
this.h = h;
this.s = s;
this.v = v;
a = 1;
}
///
/// Constructs a new ColorHSV from a Color
///
public ColorHSV(Color color)
{
Color.RGBToHSV(color, out h, out s, out v);
a = color.a;
}
public static explicit operator Vector3(ColorHSV c)
{
return new Vector3(c.h, c.s, c.v);
}
public static explicit operator Vector4(ColorHSV c)
{
return new Vector4(c.h, c.s, c.v, c.a);
}
public static explicit operator ColorHSV(Vector3 v)
{
return new ColorHSV(v.x, v.y, v.z);
}
public static explicit operator ColorHSV(Vector4 v)
{
return new ColorHSV(v.x, v.y, v.z, v.w);
}
public static ColorHSV operator +(ColorHSV a, ColorHSV b)
{
return new ColorHSV(a.h + b.h, a.s + b.s, a.v + b.v, a.a + b.a);
}
public static ColorHSV operator -(ColorHSV a, ColorHSV b)
{
return new ColorHSV(a.h - b.h, a.s - b.s, a.v - b.v, a.a - b.a);
}
public static ColorHSV operator *(ColorHSV a, ColorHSV b)
{
return new ColorHSV(a.h*b.h, a.s*b.s, a.v*b.v, a.a*b.a);
}
public static ColorHSV operator *(ColorHSV a, float b)
{
return new ColorHSV(a.h*b, a.s*b, a.v*b, a.a*b);
}
public static ColorHSV operator *(float b, ColorHSV a)
{
return new ColorHSV(a.h*b, a.s*b, a.v*b, a.a*b);
}
public static ColorHSV operator /(ColorHSV a, float b)
{
return new ColorHSV(a.h/b, a.s/b, a.v/b, a.a/b);
}
public static bool operator ==(ColorHSV lhs, ColorHSV rhs)
{
return (Vector4) lhs == (Vector4) rhs;
}
public static bool operator !=(ColorHSV lhs, ColorHSV rhs)
{
return !(lhs == rhs);
}
///
/// Returns a nicely formatted string for this color
///
public override string ToString()
{
return string.Format("HSVA({0:F3}, {1:F3}, {2:F3}, {3:F3})", h, s, v, a);
}
///
/// Returns the color as a hexadecimal string in the format "RRGGBB"
///
public string ToHtmlStringRGB()
{
return ColorUtility.ToHtmlStringRGB(ToColor());
}
///
/// Returns the color as a hexadecimal string in the format "RRGGBBAA"
///
public string ToHtmlStringRGBA()
{
return ColorUtility.ToHtmlStringRGBA(ToColor());
}
public override int GetHashCode()
{
return ((Vector4) this).GetHashCode();
}
public override bool Equals(object other)
{
if (!(other is ColorHSV))
{
return false;
}
ColorHSV color = (ColorHSV) other;
if (h.Equals(color.h) && s.Equals(color.s) && v.Equals(color.v))
{
return a.Equals(color.a);
}
return false;
}
///
/// Converts ColorHSV to a RGB representation
///
public Color ToColor()
{
var color = Color.HSVToRGB(h, s, v);
color.a = a;
return color;
}
///
/// Returns a new color with the hue offset by degrees
///
public ColorHSV WithOffsetH(float angle)
{
return WithH(Mathf.Repeat(h + angle/360, 1));
}
///
/// Returns a new color with the modified hue component
///
public ColorHSV WithH(float h)
{
return new ColorHSV(h, s, v, a);
}
///
/// Returns a new color with the modified saturation component
///
public ColorHSV WithS(float s)
{
return new ColorHSV(h, s, v, a);
}
///
/// Returns a new color with the modified value component
///
public ColorHSV WithV(float v)
{
return new ColorHSV(h, s, v, a);
}
///
/// Returns a new color with the modified saturation and value components
///
public ColorHSV WithSV(float s, float v)
{
return new ColorHSV(h, s, v, a);
}
///
/// Returns a new color with the modified alpha component
///
public ColorHSV WithA(float a)
{
return new ColorHSV(h, s, v, a);
}
///
/// Returns a list containing this color, a of analogous colors and an optional complementary color
///
public List GetAnalogousPalette(int count = 2, bool withComplementary = false)
{
const float analogousAngle = 30;
var palette = new List {this};
int rightCount = count/2;
int leftCount = count - rightCount;
for (int i = 0; i < leftCount; i++)
{
palette.Add(WithOffsetH(-(i + 1)*analogousAngle));
}
for (int i = 0; i < rightCount; i++)
{
palette.Add(WithOffsetH((i + 1)*analogousAngle));
}
if (withComplementary)
{
palette.Add(complementary);
}
return palette;
}
///
/// Returns a list containing this color, two triadic colors and an optional complementary color
///
public List GetTriadicPalette(bool withComplementary = false)
{
const float triadicAngle = 120;
var palette = new List
{
this,
WithOffsetH(-triadicAngle),
WithOffsetH(triadicAngle)
};
if (withComplementary)
{
palette.Add(complementary);
}
return palette;
}
///
/// Returns a list containing this color and three tetradic colors
///
public List GetTetradicPalette()
{
const float tetradicAngle = 60;
var palette = new List
{
this,
WithOffsetH(tetradicAngle),
complementary,
complementary.WithOffsetH(tetradicAngle)
};
return palette;
}
///
/// Linearly interpolates between two colors
///
public static ColorHSV Lerp(ColorHSV a, ColorHSV b, float t)
{
t = Mathf.Clamp01(t);
return LerpUnclamped(a, b, t);
}
///
/// Linearly interpolates between two colors without clamping the interpolant
///
public static ColorHSV LerpUnclamped(ColorHSV a, ColorHSV b, float t)
{
float deltaH = Mathf.Repeat(b.h - a.h, 1);
if (deltaH > 0.5f)
{
deltaH -= 1;
}
return new ColorHSV(
Mathf.Repeat(a.h + deltaH*t, 1),
a.s + (b.s - a.s)*t,
a.v + (b.v - a.v)*t,
a.a + (b.a - a.a)*t);
}
}
}