After a successful pipette pick the painter now enables all four paint channels (albedo, metallic/smoothness, occlusion, emission) and raises a Picked event. The HUD subscribes and re-syncs every widget (toggles, sliders, both colour pickers) to the adopted brush values. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88 lines
4.0 KiB
C#
88 lines
4.0 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
public partial class MeshPainter
|
|
{
|
|
// --- Pipette (eyedropper) ---
|
|
//
|
|
// A sampling MODE rather than a paint channel: while active, a left-click raycasts onto
|
|
// the mesh, reads ALL painted channel values at the hit texel (albedo, metallic,
|
|
// smoothness, occlusion, emission) and adopts them into the brush properties - with every
|
|
// opacity/alpha forced to 1 so re-painting reproduces the picked look at full strength.
|
|
// The values are read through ReadCanonical(), which already converts both backends (GPU
|
|
// RenderTextures / CPU Texture2D mirrors) into the same canonical material layout, so the
|
|
// pick matches what is stored without any new colour-space handling.
|
|
|
|
[Header("Brush - pipette (eyedropper)")]
|
|
[Tooltip("Pipette mode: a click samples ALL channel values at the hit point (albedo, " +
|
|
"metallic, smoothness, occlusion, emission) and adopts them into the brush " +
|
|
"(all alphas set to 1) instead of painting. Held button picks only once.")]
|
|
public bool pipette = false;
|
|
|
|
/// <summary>
|
|
/// Raised after a successful pick, once the brush has adopted the sampled values and all four
|
|
/// paint-channel flags have been enabled. The in-game HUD listens to re-sync its widgets.
|
|
/// </summary>
|
|
public event Action Picked;
|
|
|
|
private bool _pickHeld; // edge tracking so a held button picks once, not every frame
|
|
|
|
/// <summary>
|
|
/// Raycasts at a screen position, finds the hit texel UV and copies every painted channel
|
|
/// value there into the brush (albedo color, metallic, smoothness, occlusion, emission
|
|
/// color), forcing all opacities/alphas to 1. Reads regardless of which channels are
|
|
/// currently toggled for painting.
|
|
/// </summary>
|
|
private void PickAtScreen(Vector2 screenPos)
|
|
{
|
|
if (!TryRaycastPaintMesh(screenPos, out RaycastHit hit)) return;
|
|
if (_verts == null || _uvs == null || _tris == null || hit.triangleIndex < 0) return;
|
|
|
|
int t = hit.triangleIndex;
|
|
if (t * 3 + 2 >= _tris.Length) return;
|
|
int i0 = _tris[t * 3], i1 = _tris[t * 3 + 1], i2 = _tris[t * 3 + 2];
|
|
|
|
// UV at the hit via barycentric interpolation of the cached UVs - same UV space the
|
|
// brush paints into, so the sampled texel is exactly the one that would be painted.
|
|
Vector3 bc = hit.barycentricCoordinate;
|
|
Vector2 uv = _uvs[i0] * bc.x + _uvs[i1] * bc.y + _uvs[i2] * bc.z;
|
|
|
|
int px = Mathf.Clamp(Mathf.FloorToInt(uv.x * textureSize), 0, textureSize - 1);
|
|
int py = Mathf.Clamp(Mathf.FloorToInt(uv.y * textureSize), 0, textureSize - 1);
|
|
int idx = py * textureSize + px;
|
|
|
|
// ReadCanonical gives material layout for either backend:
|
|
// albedo -> sRGB RGB
|
|
// MSO -> R = metallic, G = occlusion, A = smoothness (linear data)
|
|
// emission -> sRGB RGB
|
|
Color32[] alb = ReadCanonical(MAP_ALBEDO);
|
|
Color32[] mso = ReadCanonical(MAP_MSO);
|
|
Color32[] em = ReadCanonical(MAP_EM);
|
|
if (idx < 0 || idx >= alb.Length || idx >= mso.Length || idx >= em.Length) return;
|
|
|
|
Color32 a = alb[idx];
|
|
Color32 m = mso[idx];
|
|
Color32 e = em[idx];
|
|
|
|
// sRGB bytes map straight back into the gamma-space brush colours (round-trips exactly).
|
|
brushColor = new Color(a.r / 255f, a.g / 255f, a.b / 255f, 1f);
|
|
metallic = m.r / 255f;
|
|
occlusion = m.g / 255f;
|
|
smoothness = m.a / 255f;
|
|
emissionColor = new Color(e.r / 255f, e.g / 255f, e.b / 255f, 1f);
|
|
|
|
// "Set all alpha values to 1": full-strength adoption across every opacity control.
|
|
metallicSmoothnessOpacity = 1f;
|
|
occlusionOpacity = 1f;
|
|
|
|
// Picking a whole material implies repainting all of it, so enable every channel.
|
|
paintAlbedo = true;
|
|
paintMetallicSmoothness = true;
|
|
paintOcclusion = true;
|
|
paintEmission = true;
|
|
|
|
// Let listeners (the in-game HUD) refresh their widgets to the adopted values + flags.
|
|
Picked?.Invoke();
|
|
}
|
|
}
|