mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
Merge pull request #109 from All-Of-Us-Mods/more-colors
Add variable colors
This commit is contained in:
18 files changed
+945
-10
No files matched your search
@@ -98,3 +98,6 @@ dotnet_diagnostic.IDE0045.severity = none
|
||||
|
||||
# IDE0046: Convert to conditional expression
|
||||
dotnet_diagnostic.IDE0046.severity = none
|
||||
|
||||
# S1244: Floating point numbers should not be tested for equality
|
||||
dotnet_diagnostic.S1244.severity = none
|
||||
@@ -1,4 +1,6 @@
|
||||
using MiraAPI.Colors;
|
||||
using MiraAPI.Colors.ColorSpaces;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example;
|
||||
@@ -14,4 +16,9 @@ public static class ExampleColors
|
||||
public static CustomColor Rose { get; } = new("color.rose", new Color(0.98f, 0.26f, 0.62f));
|
||||
|
||||
public static CustomColor Gold { get; } = new("color.gold", new Color(1.0f, 0.84f, 0.0f));
|
||||
|
||||
public static VariableColor Rainbow { get; } = new("color.rainbow", t => new HsbColor(MathUtilities.PingPong(t, 1f, 0.3f), 1f, 1f))
|
||||
{
|
||||
ColorBrightness = CustomColorBrightness.Lighter,
|
||||
};
|
||||
}
|
||||
@@ -80,4 +80,5 @@ Eliminate every other faction.</string>
|
||||
<string name="color.cerulean">Cerulean</string>
|
||||
<string name="color.rose">Rose</string>
|
||||
<string name="color.gold">Gold</string>
|
||||
<string name="color.rainbow">Rainbow</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Colors.ColorSpaces;
|
||||
|
||||
/// <summary>
|
||||
/// A struct that denotes the CMYK (Cyan, Magenta, Yellow, Key) color space.
|
||||
/// </summary>
|
||||
/// <param name="c">The cyan component.</param>
|
||||
/// <param name="m">The magenta component.</param>
|
||||
/// <param name="y">The yellow component.</param>
|
||||
/// <param name="k">The key component.</param>
|
||||
/// <param name="a">The alpha component.</param>
|
||||
[Serializable]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")]
|
||||
public record struct CmykColor(float c, float m, float y, float k, float a = 1f)
|
||||
{
|
||||
/// <summary>
|
||||
/// Implicitly converts a <see cref="CmykColor"/> to a Unity <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="c">The color being converted.</param>
|
||||
public static implicit operator Color(CmykColor c)
|
||||
{
|
||||
return ToColor(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a Unity <see cref="Color"/> to a <see cref="CmykColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="c">The color being converted.</param>
|
||||
public static implicit operator CmykColor(Color c)
|
||||
{
|
||||
return FromColor(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="CmykColor"/> to a standard Unity RGB <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="cmyk">The CMYK color to convert.</param>
|
||||
/// <returns>A Unity <see cref="Color"/> representing the same visual color.</returns>
|
||||
public static Color ToColor(CmykColor cmyk)
|
||||
{
|
||||
var key = 1f - cmyk.k;
|
||||
|
||||
var r = (1f - cmyk.c) * key;
|
||||
var g = (1f - cmyk.m) * key;
|
||||
var b = (1f - cmyk.y) * key;
|
||||
|
||||
return new Color(MathUtilities.Clamp01(r), MathUtilities.Clamp01(g), MathUtilities.Clamp01(b), cmyk.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a standard Unity RGB <see cref="Color"/> to a <see cref="CmykColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="color">The Unity Color to convert.</param>
|
||||
/// <returns>A <see cref="CmykColor"/> representing the same visual color.</returns>
|
||||
public static CmykColor FromColor(Color color)
|
||||
{
|
||||
var r = color.r;
|
||||
var g = color.g;
|
||||
var b = color.b;
|
||||
|
||||
var k = 1f - MathF.Max(r, MathF.Max(g, b));
|
||||
|
||||
if (k >= 1f)
|
||||
return new CmykColor(0f, 0f, 0f, 1f, color.a);
|
||||
|
||||
var invK = 1f / (1f - k);
|
||||
|
||||
var c = (1f - r - k) * invK;
|
||||
var m = (1f - g - k) * invK;
|
||||
var y = (1f - b - k) * invK;
|
||||
|
||||
return new CmykColor(MathUtilities.Clamp01(c), MathUtilities.Clamp01(m), MathUtilities.Clamp01(y), MathUtilities.Clamp01(k), color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two <see cref="CmykColor"/> values.
|
||||
/// </summary>
|
||||
/// <param name="a">The starting color.</param>
|
||||
/// <param name="b">The ending color.</param>
|
||||
/// <param name="t">The interpolation value between the two colors, clamped to a 0 to 1 range.</param>
|
||||
/// <returns>The interpolated <see cref="CmykColor"/>.</returns>
|
||||
public static CmykColor Lerp(CmykColor a, CmykColor b, float t)
|
||||
{
|
||||
return LerpUnclamped(a, b, MathUtilities.Clamp01(t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two <see cref="CmykColor"/> values without clamping the interpolant.
|
||||
/// </summary>
|
||||
/// <param name="a">The starting color.</param>
|
||||
/// <param name="b">The ending color.</param>
|
||||
/// <param name="t">The interpolation value between the two colors.</param>
|
||||
/// <returns>The interpolated <see cref="CmykColor"/>.</returns>
|
||||
public static CmykColor LerpUnclamped(CmykColor a, CmykColor b, float t)
|
||||
{
|
||||
return new CmykColor(
|
||||
MathUtilities.LerpUnclamped(a.c, b.c, t),
|
||||
MathUtilities.LerpUnclamped(a.m, b.m, t),
|
||||
MathUtilities.LerpUnclamped(a.y, b.y, t),
|
||||
MathUtilities.LerpUnclamped(a.k, b.k, t),
|
||||
MathUtilities.LerpUnclamped(a.a, b.a, t));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Colors.ColorSpaces;
|
||||
|
||||
/// <summary>
|
||||
/// A struct that denotes the HSB (Hue, Saturation, Brightness) color space.
|
||||
/// </summary>
|
||||
/// <param name="h">The hue component.</param>
|
||||
/// <param name="s">The saturation component.</param>
|
||||
/// <param name="b">The brightness component.</param>
|
||||
/// <param name="a">The alpha component.</param>
|
||||
[Serializable]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")]
|
||||
public record struct HsbColor(float h, float s, float b, float a = 1f)
|
||||
{
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="HsbColor"/> to a Unity <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="c">The color being converted.</param>
|
||||
public static implicit operator Color(HsbColor c)
|
||||
{
|
||||
return ToColor(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a Unity <see cref="Color"/> to an <see cref="HsbColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="c">The color being converted.</param>
|
||||
public static implicit operator HsbColor(Color c)
|
||||
{
|
||||
return FromColor(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an <see cref="HsbColor"/> to a standard Unity RGB <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="hsb">The HSB color to convert.</param>
|
||||
/// <returns>A Unity <see cref="Color"/> representing the same visual color.</returns>
|
||||
public static Color ToColor(HsbColor hsb)
|
||||
{
|
||||
if (hsb.s <= float.Epsilon)
|
||||
return new Color(MathUtilities.Clamp01(hsb.b), MathUtilities.Clamp01(hsb.b), MathUtilities.Clamp01(hsb.b), hsb.a);
|
||||
|
||||
var c = hsb.b * hsb.s;
|
||||
var hPrime = hsb.h * 6f;
|
||||
var x = c * (1f - MathF.Abs((hPrime % 2f) - 1f));
|
||||
var m = hsb.b - c;
|
||||
|
||||
var (r, g, b) = hPrime switch
|
||||
{
|
||||
< 1f => (c, x, 0f),
|
||||
< 2f => (x, c, 0f),
|
||||
< 3f => (0f, c, x),
|
||||
< 4f => (0f, x, c),
|
||||
< 5f => (x, 0f, c),
|
||||
<= 6f => (c, 0f, x),
|
||||
_ => (0f, 0f, 0f),
|
||||
};
|
||||
|
||||
return new Color(
|
||||
MathUtilities.Clamp01(r + m),
|
||||
MathUtilities.Clamp01(g + m),
|
||||
MathUtilities.Clamp01(b + m),
|
||||
hsb.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a standard Unity RGB <see cref="Color"/> to an <see cref="HsbColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="color">The Unity Color to convert.</param>
|
||||
/// <returns>An <see cref="HsbColor"/> representing the same visual color.</returns>
|
||||
public static HsbColor FromColor(Color color)
|
||||
{
|
||||
var max = MathF.Max(color.r, MathF.Max(color.g, color.b));
|
||||
var min = MathF.Min(color.r, MathF.Min(color.g, color.b));
|
||||
var delta = max - min;
|
||||
|
||||
var h = 0f;
|
||||
|
||||
if (delta > 0f)
|
||||
{
|
||||
if (max == color.r)
|
||||
h = (color.g - color.b) / delta % 6f;
|
||||
else if (max == color.g)
|
||||
h = (color.b - color.r) / delta + 2f;
|
||||
else
|
||||
h = (color.r - color.g) / delta + 4f;
|
||||
|
||||
h /= 6f;
|
||||
|
||||
if (h < 0f)
|
||||
h += 1f;
|
||||
}
|
||||
|
||||
var s = max == 0f ? 0f : delta / max;
|
||||
var b = max;
|
||||
|
||||
return new HsbColor(h, s, b, color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two <see cref="HsbColor"/> values, using the shortest path for the hue.
|
||||
/// </summary>
|
||||
/// <param name="a">The starting color.</param>
|
||||
/// <param name="b">The ending color.</param>
|
||||
/// <param name="t">The interpolation value between the two colors, clamped to a 0 to 1 range.</param>
|
||||
/// <returns>The interpolated <see cref="HsbColor"/>.</returns>
|
||||
public static HsbColor Lerp(HsbColor a, HsbColor b, float t)
|
||||
{
|
||||
return LerpUnclamped(a, b, MathUtilities.Clamp01(t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two <see cref="HsbColor"/> values without clamping the interpolant.
|
||||
/// </summary>
|
||||
/// <param name="a">The starting color.</param>
|
||||
/// <param name="b">The ending color.</param>
|
||||
/// <param name="t">The interpolation value between the two colors.</param>
|
||||
/// <returns>The interpolated <see cref="HsbColor"/>.</returns>
|
||||
public static HsbColor LerpUnclamped(HsbColor a, HsbColor b, float t)
|
||||
{
|
||||
var hueDifference = MathUtilities.Repeat(b.h - a.h, 1f);
|
||||
|
||||
if (hueDifference > 0.5f)
|
||||
hueDifference -= 1f;
|
||||
|
||||
return new HsbColor(
|
||||
MathUtilities.Repeat(a.h + (hueDifference * t), 1f),
|
||||
MathUtilities.LerpUnclamped(a.s, b.s, t),
|
||||
MathUtilities.LerpUnclamped(a.b, b.b, t),
|
||||
MathUtilities.LerpUnclamped(a.a, b.a, t));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Colors.ColorSpaces;
|
||||
|
||||
/// <summary>
|
||||
/// A struct that denotes the HSL (Hue, Saturation, Lightness) color space.
|
||||
/// </summary>
|
||||
/// <param name="h">The hue component.</param>
|
||||
/// <param name="s">The saturation component.</param>
|
||||
/// <param name="l">The lightness component.</param>
|
||||
/// <param name="a">The alpha component.</param>
|
||||
[Serializable]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")]
|
||||
public record struct HslColor(float h, float s, float l, float a = 1f)
|
||||
{
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="HslColor"/> to a Unity <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="c">The color being converted.</param>
|
||||
public static implicit operator Color(HslColor c)
|
||||
{
|
||||
return ToColor(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts a Unity <see cref="Color"/> to an <see cref="HslColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="c">The color being converted.</param>
|
||||
public static implicit operator HslColor(Color c)
|
||||
{
|
||||
return FromColor(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an <see cref="HslColor"/> to a standard Unity RGB <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="hsl">The HSL color to convert.</param>
|
||||
/// <returns>A Unity <see cref="Color"/> representing the same visual color.</returns>
|
||||
public static Color ToColor(HslColor hsl)
|
||||
{
|
||||
if (hsl.s <= float.Epsilon)
|
||||
return new Color(MathUtilities.Clamp01(hsl.l), MathUtilities.Clamp01(hsl.l), MathUtilities.Clamp01(hsl.l), hsl.a);
|
||||
|
||||
var c = (1f - MathF.Abs(2f * hsl.l - 1f)) * hsl.s;
|
||||
var hPrime = hsl.h * 6f;
|
||||
var x = c * (1f - MathF.Abs((hPrime % 2f) - 1f));
|
||||
var m = hsl.l - c / 2f;
|
||||
|
||||
var (r, g, b) = hPrime switch
|
||||
{
|
||||
< 1f => (c, x, 0f),
|
||||
< 2f => (x, c, 0f),
|
||||
< 3f => (0f, c, x),
|
||||
< 4f => (0f, x, c),
|
||||
< 5f => (x, 0f, c),
|
||||
<= 6f => (c, 0f, x),
|
||||
_ => (0f, 0f, 0f),
|
||||
};
|
||||
|
||||
return new Color(MathUtilities.Clamp01(r + m), MathUtilities.Clamp01(g + m), MathUtilities.Clamp01(b + m), hsl.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a standard Unity RGB <see cref="Color"/> to an <see cref="HslColor"/>.
|
||||
/// </summary>
|
||||
/// <param name="color">The Unity Color to convert.</param>
|
||||
/// <returns>An <see cref="HslColor"/> representing the same visual color.</returns>
|
||||
public static HslColor FromColor(Color color)
|
||||
{
|
||||
var max = MathF.Max(color.r, MathF.Max(color.g, color.b));
|
||||
var min = MathF.Min(color.r, MathF.Min(color.g, color.b));
|
||||
var l = (max + min) / 2f;
|
||||
var delta = max - min;
|
||||
|
||||
var s = delta == 0f ? 0f : delta / (1f - MathF.Abs((2f * l) - 1f));
|
||||
|
||||
var h = 0f;
|
||||
|
||||
if (delta > 0f)
|
||||
{
|
||||
if (max == color.r)
|
||||
h = (color.g - color.b) / delta % 6f;
|
||||
else if (max == color.g)
|
||||
h = (color.b - color.r) / delta + 2f;
|
||||
else
|
||||
h = (color.r - color.g) / delta + 4f;
|
||||
|
||||
h /= 6f;
|
||||
|
||||
if (h < 0f)
|
||||
h += 1f;
|
||||
}
|
||||
|
||||
return new HslColor(h, s, l, color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two <see cref="HslColor"/> values, using the shortest path for the hue.
|
||||
/// </summary>
|
||||
/// <param name="a">The starting color.</param>
|
||||
/// <param name="b">The ending color.</param>
|
||||
/// <param name="t">The interpolation value between the two colors, clamped to a 0 to 1 range.</param>
|
||||
/// <returns>The interpolated <see cref="HslColor"/>.</returns>
|
||||
public static HslColor Lerp(HslColor a, HslColor b, float t)
|
||||
{
|
||||
return LerpUnclamped(a, b, MathUtilities.Clamp01(t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two <see cref="HslColor"/> values without clamping the interpolant.
|
||||
/// </summary>
|
||||
/// <param name="a">The starting color.</param>
|
||||
/// <param name="b">The ending color.</param>
|
||||
/// <param name="t">The interpolation value between the two colors.</param>
|
||||
/// <returns>The interpolated <see cref="HslColor"/>.</returns>
|
||||
public static HslColor LerpUnclamped(HslColor a, HslColor b, float t)
|
||||
{
|
||||
var hueDifference = MathUtilities.Repeat(b.h - a.h, 1f);
|
||||
|
||||
if (hueDifference > 0.5f)
|
||||
hueDifference -= 1f;
|
||||
|
||||
return new HslColor(
|
||||
MathUtilities.Repeat(a.h + (hueDifference * t), 1f),
|
||||
MathUtilities.LerpUnclamped(a.s, b.s, t),
|
||||
MathUtilities.LerpUnclamped(a.l, b.l, t),
|
||||
MathUtilities.LerpUnclamped(a.a, b.a, t));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MiraAPI.Translation;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -10,17 +11,37 @@ namespace MiraAPI.Colors;
|
||||
/// <param name="name">The name of the option.</param>
|
||||
/// <param name="mainColor">The main color.</param>
|
||||
/// <param name="shadowColor">The shadow color.</param>
|
||||
public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Internal code.")]
|
||||
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Looks weird.")]
|
||||
public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the main color.
|
||||
/// </summary>
|
||||
public Color32 MainColor { get; set; } = mainColor;
|
||||
public Color32 MainColor
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
InternalMainColor = value;
|
||||
}
|
||||
}
|
||||
= mainColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the shadow color.
|
||||
/// </summary>
|
||||
public Color32 ShadowColor { get; set; } = shadowColor;
|
||||
public Color32 ShadowColor
|
||||
{
|
||||
get;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
InternalShadowColor = value;
|
||||
}
|
||||
}
|
||||
= shadowColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the color.
|
||||
@@ -32,6 +53,9 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha
|
||||
/// </summary>
|
||||
public CustomColorBrightness ColorBrightness { get; set; } = CustomColorBrightness.Darker;
|
||||
|
||||
internal Color InternalMainColor = mainColor;
|
||||
internal Color InternalShadowColor = shadowColor;
|
||||
|
||||
/// <inheritdoc cref="CustomColor"/>
|
||||
public CustomColor(StringNames name, Color32 mainColor)
|
||||
: this(name, mainColor, mainColor.GetShadowColor(60))
|
||||
@@ -56,6 +80,11 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha
|
||||
/// </summary>
|
||||
public enum CustomColorBrightness
|
||||
{
|
||||
/// <summary>
|
||||
/// An invalid color.
|
||||
/// </summary>
|
||||
Invalid = -1,
|
||||
|
||||
/// <summary>
|
||||
/// A lighter color.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Colors;
|
||||
|
||||
@@ -15,9 +17,36 @@ public static class PaletteManager
|
||||
public static CustomColor[] RegisteredColors => [.. CustomColors];
|
||||
|
||||
internal static readonly List<CustomColor> CustomColors = [];
|
||||
internal static readonly HashSet<StringNames> VariableColorNames = [];
|
||||
|
||||
private static readonly CustomColorBrightness[] ColorBrightnessMap =
|
||||
[
|
||||
CustomColorBrightness.Darker, // Red
|
||||
CustomColorBrightness.Darker, // Blue
|
||||
CustomColorBrightness.Darker, // Green
|
||||
CustomColorBrightness.Lighter, // Pink
|
||||
CustomColorBrightness.Lighter, // Orange
|
||||
CustomColorBrightness.Lighter, // Yellow
|
||||
CustomColorBrightness.Darker, // Black
|
||||
CustomColorBrightness.Lighter, // White
|
||||
CustomColorBrightness.Darker, // Purple
|
||||
CustomColorBrightness.Darker, // Brown
|
||||
CustomColorBrightness.Lighter, // Cyan
|
||||
CustomColorBrightness.Lighter, // Lime
|
||||
CustomColorBrightness.Darker, // Maroon
|
||||
CustomColorBrightness.Lighter, // Rose
|
||||
CustomColorBrightness.Lighter, // Banana
|
||||
CustomColorBrightness.Darker, // Gray
|
||||
CustomColorBrightness.Darker, // Tan
|
||||
CustomColorBrightness.Lighter, // Coral
|
||||
];
|
||||
|
||||
private static int originalLength;
|
||||
|
||||
internal static void RegisterAllColors()
|
||||
{
|
||||
originalLength = Palette.PlayerColors.Length;
|
||||
|
||||
var colors = CustomColors.Select(x => x.MainColor).ToArray();
|
||||
var shadowColors = CustomColors.Select(x => x.ShadowColor).ToArray();
|
||||
var stringNames = CustomColors.Select(x => x.Name).ToArray();
|
||||
@@ -29,4 +58,90 @@ public static class PaletteManager
|
||||
Palette.TextColors = Palette.TextColors.ToArray().AddRangeToArray(colors);
|
||||
Palette.TextOutlineColors = Palette.TextOutlineColors.ToArray().AddRangeToArray(shadowColors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the provided color ID belongs to a variable color.
|
||||
/// </summary>
|
||||
/// <param name="colorId">The id of the color.</param>
|
||||
/// <returns><c>true</c> if the color is a variable color; <c>false</c> otherwise.</returns>
|
||||
public static bool IsVariable(int colorId)
|
||||
{
|
||||
if ((uint)colorId >= (uint)Palette.ColorNames.Length)
|
||||
return false;
|
||||
|
||||
return VariableColorNames.Contains(Palette.ColorNames[colorId]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the provided color ID belongs to a custom color.
|
||||
/// </summary>
|
||||
/// <param name="colorId">The ID of the color.</param>
|
||||
/// <returns><c>true</c> if the color is a custom color; <c>false</c> otherwise.</returns>
|
||||
public static bool IsCustomColor(int colorId)
|
||||
{
|
||||
return (uint)(colorId - originalLength) < (uint)CustomColors.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main color of the provided color ID.
|
||||
/// </summary>
|
||||
/// <param name="colorId">The id of the color.</param>
|
||||
/// <returns>The main color associated with the provided ID.</returns>
|
||||
public static Color GetMainColor(int colorId)
|
||||
{
|
||||
if ((uint)colorId >= (uint)Palette.ColorNames.Length)
|
||||
return Color.white;
|
||||
|
||||
return !TryGetCustomColor(colorId, out var custom)
|
||||
? Palette.PlayerColors[colorId]
|
||||
: (custom is VariableColor variable
|
||||
? variable.EvaluateMainColor(Time.time)
|
||||
: custom.InternalMainColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shadow color of the provided color ID.
|
||||
/// </summary>
|
||||
/// <param name="colorId">The ID of the color.</param>
|
||||
/// <returns>The shadow color associated with the provided ID.</returns>
|
||||
public static Color GetShadowColor(int colorId)
|
||||
{
|
||||
if ((uint)colorId >= (uint)Palette.ColorNames.Length)
|
||||
return Color.white;
|
||||
|
||||
return !TryGetCustomColor(colorId, out var custom)
|
||||
? Palette.ShadowColors[colorId]
|
||||
: (custom is VariableColor variable
|
||||
? variable.EvaluateShadowColor(Time.time)
|
||||
: custom.InternalShadowColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the brightness type of the color associated with the provided ID.
|
||||
/// </summary>
|
||||
/// <param name="colorId">The ID of the color.</param>
|
||||
/// <returns>The brightness type of the provided ID.</returns>
|
||||
public static CustomColorBrightness GetBrightness(int colorId)
|
||||
{
|
||||
if ((uint)colorId >= (uint)Palette.ColorNames.Length)
|
||||
return CustomColorBrightness.Invalid;
|
||||
|
||||
return TryGetCustomColor(colorId, out var custom)
|
||||
? custom.ColorBrightness
|
||||
: ColorBrightnessMap[colorId];
|
||||
}
|
||||
|
||||
internal static bool TryGetCustomColor(int colorId, [NotNullWhen(true)] out CustomColor? custom)
|
||||
{
|
||||
var index = colorId - originalLength;
|
||||
|
||||
if ((uint)index >= (uint)CustomColors.Count)
|
||||
{
|
||||
custom = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
custom = CustomColors[index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Colors;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a dynamic custom color that changes over time.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the color.</param>
|
||||
/// <param name="mainEvaluator">The function to evaluate the dynamic main color.</param>
|
||||
/// <param name="shadowEvaluator">The function to evaluate the dynamic shadow color.</param>
|
||||
/// <param name="mainColor">The static main color used when dynamic colors are disabled.</param>
|
||||
/// <param name="shadowColor">The static shadow color used when dynamic colors are disabled.</param>
|
||||
/// <param name="defaultTime">The default time that is used to automatically fill in the static colors when the latter is not provided.</param>
|
||||
public sealed class VariableColor(StringNames name, Func<float, Color> mainEvaluator, Func<float, Color>? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
|
||||
: CustomColor(name, mainColor ?? mainEvaluator(defaultTime), shadowColor ?? shadowEvaluator?.Invoke(defaultTime) ?? (mainColor ?? mainEvaluator(defaultTime)).GetShadowColor(60))
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that takes in a time value and returns the dynamic main color.
|
||||
/// </summary>
|
||||
public Func<float, Color> MainEvaluator { get; set; } = mainEvaluator;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a delegate that takes in a time value and returns the dynamic shadow color.
|
||||
/// </summary>
|
||||
public Func<float, Color> ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(0.24f));
|
||||
|
||||
/// <inheritdoc cref="VariableColor"/>
|
||||
public VariableColor(StringNames name, Func<float, Color> mainEvaluator, Color32? mainColor = null, float defaultTime = 0f)
|
||||
: this(name, mainEvaluator, null, mainColor, null, defaultTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="VariableColor"/>
|
||||
public VariableColor(string name, Func<float, Color> mainEvaluator, Color32? mainColor = null, float defaultTime = 0f)
|
||||
: this(name, mainEvaluator, null, mainColor, null, defaultTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="VariableColor"/>
|
||||
public VariableColor(string name, Func<float, Color> mainEvaluator, Func<float, Color>? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
|
||||
: this(MiraLocaleManager.GetOrCreateLocaleString(name), mainEvaluator, shadowEvaluator, mainColor, shadowColor, defaultTime)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the dynamic main color based on the provided time value.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to evaluate the color against.</param>
|
||||
/// <returns>The dynamically evaluated color, or the static <see cref="CustomColor.MainColor"/> if static variable colors are enabled in the user's settings.</returns>
|
||||
public Color EvaluateMainColor(float time)
|
||||
{
|
||||
return LocalSettingsTabSingleton<MiraApiSettings>.Instance.EnableVariableColors.Value
|
||||
? MainEvaluator(time)
|
||||
: InternalMainColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the dynamic shadow color based on the provided time value.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to evaluate the color against.</param>
|
||||
/// <returns>The dynamically evaluated color, or the static <see cref="CustomColor.ShadowColor"/> if static variable colors are enabled in the user's settings.</returns>
|
||||
public Color EvaluateShadowColor(float time)
|
||||
{
|
||||
return LocalSettingsTabSingleton<MiraApiSettings>.Instance.EnableVariableColors.Value
|
||||
? ShadowEvaluator(time)
|
||||
: InternalShadowColor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Colors;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI;
|
||||
|
||||
/// <summary>
|
||||
/// The base class for variable color behaviour on <see cref="SpriteRenderer"/>s.
|
||||
/// </summary>
|
||||
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity methods.")]
|
||||
[SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "Unity fields.")]
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Read above.")]
|
||||
public abstract class BaseVariableColorBehaviour : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="VariableColor"/> to apply.
|
||||
/// </summary>
|
||||
protected VariableColor? color;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current variable color.
|
||||
/// </summary>
|
||||
/// <param name="newColor">The new <see cref="VariableColor"/>.</param>
|
||||
[HideFromIl2Cpp]
|
||||
public void SetColor(VariableColor? newColor)
|
||||
{
|
||||
color = newColor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A mono script that adjusts the material colors of a <see cref="Renderer"/> component that it's attached to based on the provided variable color.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity methods.")]
|
||||
public sealed class VariableColorBehaviour : BaseVariableColorBehaviour
|
||||
{
|
||||
private Renderer? rend;
|
||||
|
||||
private static readonly Color VisorColor = Palette.VisorColor;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the new <see cref="Renderer"/> target.
|
||||
/// </summary>
|
||||
/// <param name="newRend">The new <see cref="Renderer"/> that the script must target.</param>
|
||||
public void SetRend(Renderer? newRend)
|
||||
{
|
||||
rend = newRend;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rend = GetComponent<Renderer>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (color == null || rend == null)
|
||||
return;
|
||||
|
||||
var time = Time.time;
|
||||
var material = rend.material;
|
||||
|
||||
material.SetColor(ShaderID.VisorColor, VisorColor);
|
||||
|
||||
material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor(time));
|
||||
material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor(time));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A mono script that adjusts the graphic colors of a <see cref="SpriteRenderer"/> component that it's attached to based on the provided variable color.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity methods.")]
|
||||
public sealed class BasicVariableColorBehaviour : BaseVariableColorBehaviour
|
||||
{
|
||||
private SpriteRenderer? rend;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the new <see cref="SpriteRenderer"/> target.
|
||||
/// </summary>
|
||||
/// <param name="newRend">The new <see cref="SpriteRenderer"/> that the script must target.</param>
|
||||
public void SetRend(SpriteRenderer? newRend)
|
||||
{
|
||||
rend = newRend;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
rend = GetComponent<SpriteRenderer>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (color != null && rend != null)
|
||||
rend.color = color.EvaluateMainColor(Time.time);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using MiraAPI.Patches.Stubs;
|
||||
using MiraAPI.Translation;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
@@ -247,8 +248,7 @@ public abstract class CustomPaginableMenu : CustomPhoneMenu<CustomPaginableMenu.
|
||||
|
||||
var wikiClickSound = HudManager.Instance?.MapButton?.ClickSound;
|
||||
|
||||
var searchFocusButton = searchTextbox.gameObject.GetComponent<PassiveButton>()
|
||||
?? searchTextbox.gameObject.AddComponent<PassiveButton>();
|
||||
var searchFocusButton = searchTextbox.gameObject.GetOrAddComponent<PassiveButton>();
|
||||
if (wikiClickSound != null)
|
||||
{
|
||||
searchFocusButton.ClickSound = wikiClickSound;
|
||||
|
||||
@@ -233,4 +233,10 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> EnableSabotageBlares { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableSabotageBlare", true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether variable colors will remain static or not.
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> EnableVariableColors { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableVariableColors", true);
|
||||
}
|
||||
@@ -13,4 +13,4 @@ dotnet_diagnostic.SA1611.severity = none
|
||||
dotnet_diagnostic.SA1615.severity = none
|
||||
|
||||
# SA1602: Enumeration items should be documented.
|
||||
dotnet_diagnostic.SA1602.severity = none
|
||||
dotnet_diagnostic.SA1602.severity = none
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
using AmongUs.Data;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Colors;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Colors;
|
||||
|
||||
[HarmonyPatch(typeof(PlayerMaterial))]
|
||||
internal static class SetPlayerMaterialPatch
|
||||
{
|
||||
[HarmonyPatch(nameof(PlayerMaterial.SetColors), typeof(int), typeof(Renderer))]
|
||||
public static bool Prefix(int colorId, Renderer rend)
|
||||
{
|
||||
rend.gameObject.GetOrAddComponent<VariableColorBehaviour>().SetColor(
|
||||
PaletteManager.TryGetCustomColor(colorId, out var color) && color is VariableColor variable
|
||||
? variable
|
||||
: null);
|
||||
return !PaletteManager.IsVariable(colorId);
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(PlayerMaterial.SetColors), typeof(Color), typeof(Renderer))]
|
||||
public static void Prefix(Renderer rend)
|
||||
{
|
||||
rend.gameObject.GetOrAddComponent<VariableColorBehaviour>().SetColor(null);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.Update))]
|
||||
internal static class PlayerTabPatch
|
||||
{
|
||||
public static void Postfix(PlayerTab __instance)
|
||||
{
|
||||
for (var i = 0; i < __instance.ColorChips.Count; i++)
|
||||
{
|
||||
__instance.ColorChips[i].Inner.SpriteColor = PaletteManager.GetMainColor(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ChatNotification), nameof(ChatNotification.Update))]
|
||||
internal static class ChatNotifRainbowPatch
|
||||
{
|
||||
private static readonly Regex RichTags = new(@"<[^>]*>", default, Regex.InfiniteMatchTimeout);
|
||||
|
||||
public static void Postfix(ChatNotification __instance)
|
||||
{
|
||||
if (!__instance.gameObject.active)
|
||||
return;
|
||||
|
||||
var colorId = __instance.player.cosmetics.ColorId;
|
||||
|
||||
if (!PaletteManager.IsVariable(colorId))
|
||||
return;
|
||||
|
||||
var color = PaletteManager.GetMainColor(colorId);
|
||||
var str = color.ToHtmlStringRGBA();
|
||||
var nameText = __instance.playerNameText;
|
||||
nameText.text = "<color=#" + str + ">" + RichTags.Replace(nameText.text, string.Empty);
|
||||
nameText.outlineColor = color.GetShadowColor(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(HostInfoPanel), nameof(HostInfoPanel.Update))]
|
||||
internal static class RainbowLobbyInfoPanePatch
|
||||
{
|
||||
[SuppressMessage("Style", "IDE0045:Convert to conditional expression", Justification = "Operator becomes too large.")]
|
||||
public static void Postfix(HostInfoPanel __instance)
|
||||
{
|
||||
if (!__instance.gameObject.activeInHierarchy)
|
||||
return;
|
||||
|
||||
var colorId = __instance.player.cosmetics.ColorId;
|
||||
|
||||
if (!PaletteManager.IsVariable(colorId))
|
||||
return;
|
||||
|
||||
var host = GameData.Instance.GetHost();
|
||||
|
||||
if (!host)
|
||||
return;
|
||||
|
||||
var text = PaletteManager.GetMainColor(colorId).ToHtmlStringRGBA();
|
||||
var label = __instance.hostLabel;
|
||||
label.text = TranslationController.Instance.GetString(StringNames.HostNounLabel);
|
||||
|
||||
if (__instance.ShouldBoldenHostLabel(DataManager.Settings.Language.CurrentLanguage))
|
||||
{
|
||||
label.text = "<b>" + label.text + "</b>";
|
||||
}
|
||||
|
||||
if (AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
__instance.playerName.text = (string.IsNullOrEmpty(host.PlayerName)
|
||||
? "..."
|
||||
: $"<color=#{text}>{host.PlayerName}</color>")
|
||||
+ " <size=90%><b><font=\"Barlow-BoldItalic SDF\" material=\"Barlow-BoldItalic SDF Outline\">" +
|
||||
TranslationController.Instance.GetString(StringNames.HostYouLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.playerName.text =
|
||||
(string.IsNullOrEmpty(host.PlayerName) ? "..." : $"<color=#{text}>{host.PlayerName}</color>") +
|
||||
" (" + __instance.player.ColorBlindName + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,7 +404,7 @@ public sealed class MiraPluginManager
|
||||
|
||||
foreach (var property in type.GetProperties())
|
||||
{
|
||||
if (property.PropertyType != typeof(CustomColor))
|
||||
if (!property.PropertyType.IsAssignableTo(typeof(CustomColor)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -416,6 +416,9 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
|
||||
PaletteManager.CustomColors.Add(color);
|
||||
|
||||
if (color is VariableColor)
|
||||
PaletteManager.VariableColorNames.Add(color.Name);
|
||||
}
|
||||
|
||||
foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(CustomColor))))
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<string name="MiraApi.WikiOnBottomRow">Display Wiki on Bottom Row</string>
|
||||
<string name="MiraApi.EnableSabotageFlashes">Enable Sabotage Flashes</string>
|
||||
<string name="MiraApi.EnableSabotageBlare">Enable Sabotage Blare</string>
|
||||
<string name="MiraApi.EnableVariableColors">Enable Variable Color Changes</string>
|
||||
<string name="MiraApi.ReturnToRoleSettings">RETURN TO ROLE SETTINGS</string>
|
||||
<string name="MiraApi.CustomCategory1">Custom Category 1</string>
|
||||
<string name="MiraApi.CustomCategory2">Custom Category 2</string>
|
||||
|
||||
@@ -620,7 +620,23 @@ public static class Extensions
|
||||
(byte)Mathf.Clamp(color.r - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.g - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.b - darknessAmount, 0, 255),
|
||||
byte.MaxValue);
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a darkened version of a <see cref="Color"/>.
|
||||
/// </summary>
|
||||
/// <param name="color">The original <see cref="Color"/>.</param>
|
||||
/// <param name="darknessAmount">A darkness amount between 0 and 1.</param>
|
||||
/// <returns>The darkened <see cref="Color"/>.</returns>
|
||||
public static Color GetShadowColor(this Color color, float darknessAmount)
|
||||
{
|
||||
return
|
||||
new Color(
|
||||
MathUtilities.Clamp01(color.r - darknessAmount),
|
||||
MathUtilities.Clamp01(color.g - darknessAmount),
|
||||
MathUtilities.Clamp01(color.b - darknessAmount),
|
||||
color.a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -796,7 +812,7 @@ public static class Extensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fixed version of <see cref="Reactor.Utilities.Extensions.UnityExtensions.SetOutline(Renderer, Color?)"/>.
|
||||
/// Fixed version of <see cref="UnityExtensions.SetOutline(Renderer, Color?)"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The <see cref="Renderer"/> you want to update the outline for.</param>
|
||||
/// <param name="color">The outline <see cref="Color"/>.</param>
|
||||
@@ -855,4 +871,17 @@ public static class Extensions
|
||||
userData.joystickMaps[0].actionElementMaps.Add(map);
|
||||
return action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a component that is attached to the provided <see cref="GameObject"/>, attaching and returning the component if it wasn't found.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The component type.</typeparam>
|
||||
/// <param name="go">The <see cref="GameObject"/> to get the component from.</param>
|
||||
/// <returns>The component.</returns>
|
||||
public static T GetOrAddComponent<T>(this GameObject go)
|
||||
where T : Component
|
||||
{
|
||||
var comp = go.GetComponent<T>();
|
||||
return comp ? comp : go.AddComponent<T>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Provides mathematical utility methods.
|
||||
/// </summary>
|
||||
public static class MathUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two values, clamping the interpolant between 0 and 1.
|
||||
/// </summary>
|
||||
/// <param name="a">The starting value.</param>
|
||||
/// <param name="b">The ending value.</param>
|
||||
/// <param name="t">The interpolation value, automatically clamped to a 0 to 1 range.</param>
|
||||
/// <returns>The interpolated float.</returns>
|
||||
public static float Lerp(float a, float b, float t)
|
||||
{
|
||||
return a + ((b - a) * Clamp01(t));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly interpolates between two values without clamping the interpolant.
|
||||
/// </summary>
|
||||
/// <param name="a">The starting value.</param>
|
||||
/// <param name="b">The ending value.</param>
|
||||
/// <param name="t">The interpolation value.</param>
|
||||
/// <returns>The interpolated float.</returns>
|
||||
public static float LerpUnclamped(float a, float b, float t)
|
||||
{
|
||||
return a + ((b - a) * t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps a floating-point value strictly between 0 and 1.
|
||||
/// </summary>
|
||||
/// <param name="value">The floating-point value to clamp.</param>
|
||||
/// <returns>The clamped value.</returns>
|
||||
public static float Clamp01(float value)
|
||||
{
|
||||
return Math.Clamp(value, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ping-pongs a value back and forth between a minimum and maximum value.
|
||||
/// </summary>
|
||||
/// <param name="t">The input time or interpolant.</param>
|
||||
/// <param name="min">The minimum boundary of the ping-pong wave.</param>
|
||||
/// <param name="max">The maximum boundary of the ping-pong wave.</param>
|
||||
/// <param name="speed">The multiplier for the speed of the ping-pong effect.</param>
|
||||
/// <returns>A value oscillating continuously between <paramref name="min"/> and <paramref name="max"/>.</returns>
|
||||
public static float PingPong(float t, float min, float max, float speed = 1f)
|
||||
{
|
||||
return min + PingPong(t, max - min, speed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ping-pongs a value back and forth between 0 and a specified length.
|
||||
/// </summary>
|
||||
/// <param name="t">The input time or interpolant.</param>
|
||||
/// <param name="length">The maximum value to bounce against, starting from 0.</param>
|
||||
/// <param name="speed">The multiplier for the speed of the ping-pong effect.</param>
|
||||
/// <returns>A value oscillating continuously between 0 and <paramref name="length"/>.</returns>
|
||||
public static float PingPong(float t, float length, float speed = 1f)
|
||||
{
|
||||
var dx = speed * t;
|
||||
var floor = (int)MathF.Floor(dx);
|
||||
var phase = floor % 2; // 0 = forward, 1 = backward
|
||||
return Clamp01(((dx - floor) * ((2 * phase) - 1)) + 1 - phase) * length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops a value strictly between 0 and a maximum length, behaving like a standard float modulo.
|
||||
/// </summary>
|
||||
/// <param name="t">The input value to loop.</param>
|
||||
/// <param name="length">The maximum length of the repeating sequence.</param>
|
||||
/// <returns>The wrapped value smoothly looping back to 0 upon reaching <paramref name="length"/>.</returns>
|
||||
public static float Repeat(float t, float length)
|
||||
{
|
||||
return Math.Clamp(t - (MathF.Floor(t / length) * length), 0f, length);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user