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; /// /// 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. /// public event Action Picked; private bool _pickHeld; // edge tracking so a held button picks once, not every frame /// /// 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. /// 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(); } }