Files
chameleon/Assets/Scripts/SliderCurve.cs
T
adminandClaude Opus 4.8 057c99fa1d Persist material scalars, reset the HUD, and split alpha from the picker
Save and restore the emission intensity and the three camo knobs
(half-Lambert, half-Lambert brightness, specular dampen) with a named
painting. The .paint format moves to v3 with a small scalar block
between the header and the pixel maps; v2 paintings still load with
their painter values left untouched. Loading re-syncs the emission and
camo sliders, and peers pick the values up for free through the
existing owner-authoritative paint NetworkVariables.

Add a HUD reset (Lib_ResetHud button plus context-menu fallback) that
behaves as if the saved hud-layout file were deleted: it removes the
file, restores panels to their authored prefab positions, and resets
the brush values to the painter/material defaults captured before any
carry/restore runs.

Remove the alpha bar from HsvColorPicker so alpha is driven by
standalone Slider_AlbedoAlpha / Slider_EmissionAlpha bindings; the
picker now writes RGB only and the sliders write alpha only, with no
feedback between them. Add a generic SliderCurve component that reshapes
a slider's handle position into a non-linear value (with an inverse for
setting values back), and make the slider wiring curve-aware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:20:13 +02:00

85 lines
3.7 KiB
C#

using System;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// Reshapes a UI <see cref="Slider"/>'s response with an <see cref="AnimationCurve"/> so the value runs
/// non-linearly between the slider's authored <c>minValue</c>/<c>maxValue</c>. The curve maps the handle
/// position (x, 0..1 - linear in screen space) to a value fraction (y, 0..1) that is then scaled to the
/// slider's range. The slider's own min/max stays the single source of truth for the range; this only
/// bends the mapping inside it - so dropping the component onto an existing slider needs no range re-entry.
///
/// <see cref="Value"/> is the curved output, <see cref="SetOutput"/> positions the handle to show a given
/// output (inverting the curve), and <see cref="ValueChanged"/> raises the curved value whenever the
/// handle moves - so the component is usable both standalone (wire <see cref="ValueChanged"/>) and from
/// code that drives the raw slider (read <see cref="Value"/>).
///
/// The curve should be monotonic increasing (the default linear is); the inverse used by
/// <see cref="SetOutput"/> assumes that.
/// </summary>
[RequireComponent(typeof(Slider))]
[DisallowMultipleComponent]
public class SliderCurve : MonoBehaviour
{
[Tooltip("Maps the handle position (x, 0..1) to a value fraction (y, 0..1), scaled to the slider's " +
"minValue/maxValue. Default linear = no change. Should be monotonic increasing.")]
public AnimationCurve response = AnimationCurve.Linear(0f, 0f, 1f, 1f);
/// <summary>Raised with the curved output whenever the handle moves.</summary>
public event Action<float> ValueChanged;
private Slider _slider;
private Slider Slider => _slider ? _slider : (_slider = GetComponent<Slider>());
/// <summary>The curved output value for the current handle position.</summary>
public float Value => Slider ? Curve(Slider.normalizedValue) : 0f;
private void OnEnable()
{
if (Slider) Slider.onValueChanged.AddListener(OnSliderChanged);
}
private void OnDisable()
{
if (_slider) _slider.onValueChanged.RemoveListener(OnSliderChanged);
}
private void OnSliderChanged(float _) => ValueChanged?.Invoke(Value);
/// <summary>Positions the handle so <see cref="Value"/> reads <paramref name="output"/>, raising the
/// slider's <c>onValueChanged</c> (and thus <see cref="ValueChanged"/>).</summary>
public void SetOutput(float output)
{
if (Slider) Slider.normalizedValue = Position(output); // normalizedValue setter notifies
}
/// <summary>As <see cref="SetOutput"/> but without raising any change event (for silent seeding).</summary>
public void SetOutputWithoutNotify(float output)
{
if (Slider) Slider.SetValueWithoutNotify(Mathf.Lerp(Slider.minValue, Slider.maxValue, Position(output)));
}
// handle position (0..1) -> curved output value in [minValue, maxValue]
private float Curve(float t)
{
float y = Mathf.Clamp01(response.Evaluate(Mathf.Clamp01(t)));
float v = Mathf.Lerp(Slider.minValue, Slider.maxValue, y);
return Slider && Slider.wholeNumbers ? Mathf.Round(v) : v;
}
// curved output value -> handle position (0..1), inverting the (monotonic) curve via bisection
private float Position(float output)
{
var s = Slider;
if (!s) return 0f;
float target = Mathf.Clamp01(Mathf.InverseLerp(s.minValue, s.maxValue, output)); // desired y
float lo = 0f, hi = 1f;
for (int i = 0; i < 24; i++)
{
float mid = 0.5f * (lo + hi);
if (Mathf.Clamp01(response.Evaluate(mid)) < target) lo = mid; else hi = mid;
}
return 0.5f * (lo + hi);
}
}