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>
This commit is contained in:
1 parent
fe8f12b8bb
commit
057c99fa1d
7 files changed
+313
-128
No files matched your search
@@ -16,9 +16,21 @@ public partial class MeshPainter
|
||||
// map's canonical Color32 bytes. Raw (not PNG) avoids any sRGB/alpha-premultiply ambiguity and
|
||||
// round-trips the exact canonical bytes ReadCanonical produces.
|
||||
private const uint PaintFileMagic = 0x504D4843; // 'CHMP' little-endian
|
||||
private const int PaintFileVersion = 2; // v2: added the display-name header
|
||||
private const int PaintFileVersion = 3; // v3: added the per-player material scalars (emission + camo)
|
||||
private const string PaintFileExt = ".paint";
|
||||
|
||||
/// <summary>
|
||||
/// Per-player material scalars stored alongside the maps from v3 on. These are NOT baked into the
|
||||
/// painted pixels (a global emission multiplier + the PlayerCamoLit knobs), so they ride the slot
|
||||
/// header and are re-applied onto the painter when the slot loads.
|
||||
/// </summary>
|
||||
private struct PaintScalars
|
||||
{
|
||||
public bool has; // false = legacy v2 slot with no scalars -> leave the painter's values untouched
|
||||
public float emissionIntensity;
|
||||
public float camoHalfLambert, camoHalfLambertBrightness, camoSpecularDampen;
|
||||
}
|
||||
|
||||
private static string PaintLibraryDir =>
|
||||
Path.Combine(Application.persistentDataPath, "MeshPaint", "Saved");
|
||||
|
||||
@@ -83,6 +95,14 @@ public partial class MeshPainter
|
||||
w.Write(textureSize);
|
||||
w.Write(MAP_COUNT);
|
||||
|
||||
// v3: per-player material scalars that are NOT baked into the maps (a global emission
|
||||
// multiplier + the PlayerCamoLit knobs). The painter is ready here (_setupValid), so the
|
||||
// material-backed camo getters return the live values rather than the not-ready 0.
|
||||
w.Write(emissionIntensity);
|
||||
w.Write(CamoHalfLambert);
|
||||
w.Write(CamoHalfLambertBrightness);
|
||||
w.Write(CamoSpecularDampen);
|
||||
|
||||
var bytes = new byte[n * 4];
|
||||
for (int mi = 0; mi < MAP_COUNT; mi++)
|
||||
{
|
||||
@@ -116,7 +136,7 @@ public partial class MeshPainter
|
||||
{
|
||||
if (!_setupValid) return false;
|
||||
|
||||
Color32[][] canonical = ReadMaps(PathForName(name));
|
||||
Color32[][] canonical = ReadMaps(PathForName(name), out PaintScalars scalars);
|
||||
if (canonical == null) return false;
|
||||
|
||||
// Re-seed exactly like SwitchBackend / ApplyPaintSnapshot, so the island/dilation seeding path
|
||||
@@ -126,6 +146,19 @@ public partial class MeshPainter
|
||||
// A load replaces the whole painting and is NOT undoable: the loaded maps become the new history
|
||||
// baseline, clearing any prior undo/redo. Peers reset to the same baseline via the load broadcast.
|
||||
ResetHistoryToCurrent(canonical);
|
||||
|
||||
// v3 slots also carry the per-player material scalars; apply them so the loaded look is complete.
|
||||
// Legacy v2 slots have none -> leave the current emission/camo untouched. The painter is ready here
|
||||
// (_setupValid), so the material-backed camo setters take effect; PlayerPaintSync replicates the
|
||||
// changed values to peers via their owner-authoritative NetworkVariables on its next LateUpdate.
|
||||
if (scalars.has)
|
||||
{
|
||||
emissionIntensity = scalars.emissionIntensity;
|
||||
RefreshEmissionColor();
|
||||
CamoHalfLambert = scalars.camoHalfLambert;
|
||||
CamoHalfLambertBrightness = scalars.camoHalfLambertBrightness;
|
||||
CamoSpecularDampen = scalars.camoSpecularDampen;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -155,7 +188,8 @@ public partial class MeshPainter
|
||||
using (var r = new BinaryReader(fs, Encoding.UTF8))
|
||||
{
|
||||
if (r.ReadUInt32() != PaintFileMagic) return null;
|
||||
if (r.ReadInt32() != PaintFileVersion) return null;
|
||||
int ver = r.ReadInt32();
|
||||
if (ver != 2 && ver != PaintFileVersion) return null; // list legacy v2 slots + current
|
||||
return r.ReadString();
|
||||
}
|
||||
}
|
||||
@@ -169,8 +203,9 @@ public partial class MeshPainter
|
||||
/// Reads all maps from a slot file in canonical layout, or null if missing / incompatible. Per-map
|
||||
/// null entries are tolerated by both backends (they fall back to the material seed).
|
||||
/// </summary>
|
||||
private Color32[][] ReadMaps(string path)
|
||||
private Color32[][] ReadMaps(string path, out PaintScalars scalars)
|
||||
{
|
||||
scalars = default;
|
||||
if (!File.Exists(path)) return null;
|
||||
|
||||
try
|
||||
@@ -180,12 +215,22 @@ public partial class MeshPainter
|
||||
{
|
||||
if (r.ReadUInt32() != PaintFileMagic) return null;
|
||||
int ver = r.ReadInt32();
|
||||
if (ver != PaintFileVersion) return null;
|
||||
if (ver != 2 && ver != PaintFileVersion) return null; // read legacy v2 + current
|
||||
r.ReadString(); // display name (header only; not needed for the pixel load)
|
||||
int w = r.ReadInt32();
|
||||
int h = r.ReadInt32();
|
||||
int maps = r.ReadInt32();
|
||||
|
||||
// v3+: the per-player material scalars sit between the header and the pixel maps.
|
||||
if (ver >= 3)
|
||||
{
|
||||
scalars.has = true;
|
||||
scalars.emissionIntensity = r.ReadSingle();
|
||||
scalars.camoHalfLambert = r.ReadSingle();
|
||||
scalars.camoHalfLambertBrightness = r.ReadSingle();
|
||||
scalars.camoSpecularDampen = r.ReadSingle();
|
||||
}
|
||||
|
||||
// Resolution change -> ignore rather than smear mismatched data; the brush re-seeds
|
||||
// from the material instead.
|
||||
if (w != textureSize || h != textureSize)
|
||||
|
||||
Reference in new issue
Block a user