Add pipette (eyedropper) brush mode and wire it into the HUD
Pipette samples all painted channel values (albedo, metallic, smoothness, occlusion, emission) at the clicked texel and adopts them into the brush with every opacity forced to 1, instead of painting. The screen raycast is extracted into a shared TryRaycastPaintMesh helper used by painting, preview and the pipette. A pipette toggle is added to the HUD panel prefab and wired in PaintHud. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
cdeebf9243
commit
24a36c0e61
5 files changed
+509
-12
No files matched your search
@@ -606,6 +606,22 @@ public partial class MeshPainter : MonoBehaviour
|
||||
if (pressed && IsPointerOverUi())
|
||||
pressed = false;
|
||||
|
||||
// Pipette (eyedropper): a fresh click samples ALL channel values at the hit point
|
||||
// and adopts them into the brush instead of painting. It takes over the click
|
||||
// entirely (no painting, no brush preview ring while active).
|
||||
if (pipette)
|
||||
{
|
||||
if (pressed)
|
||||
{
|
||||
if (!_pickHeld) PickAtScreen(screenPos);
|
||||
_pickHeld = true;
|
||||
}
|
||||
else _pickHeld = false;
|
||||
_hasLastScreen = false; // keep the paint stroke state clean for when pipette turns off
|
||||
return;
|
||||
}
|
||||
_pickHeld = false;
|
||||
|
||||
// Brush preview (size + mask) at the hover point - independent of painting,
|
||||
// so you see the brush before pressing the button.
|
||||
if (previewMode != BrushPreviewMode.Off && _previewMat && paintCamera)
|
||||
@@ -691,22 +707,19 @@ public partial class MeshPainter : MonoBehaviour
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raycasts at a screen position against the paint mesh and computes the brush
|
||||
/// placement: hit point (local space), local 3D radius (constant visual size via UV
|
||||
/// density) and the orthonormal tangent frame (u/v/n). Shared by painting and preview.
|
||||
/// Raycasts at a screen position and returns the nearest hit that belongs to THIS paint
|
||||
/// mesh. A plain raycast would return only the closest collider, so anything in front of
|
||||
/// the mesh (the player capsule, props, the camera-collision proxy) could block it; we
|
||||
/// scan all hits and keep the nearest on our own collider. Shared by painting, preview
|
||||
/// and the pipette.
|
||||
/// </summary>
|
||||
private bool TryComputeBrushPlacement(Vector2 screenPos, out Vector3 pLocal, out float radiusLocal,
|
||||
out Vector3 u, out Vector3 v, out Vector3 nLocal)
|
||||
private bool TryRaycastPaintMesh(Vector2 screenPos, out RaycastHit hit)
|
||||
{
|
||||
pLocal = default; radiusLocal = 0f; u = default; v = default; nLocal = default;
|
||||
hit = default;
|
||||
if (!paintCamera) return false;
|
||||
|
||||
Ray ray = paintCamera.ScreenPointToRay(screenPos);
|
||||
|
||||
// Pick the nearest hit that belongs to the paint mesh. A plain raycast would
|
||||
// return only the closest collider, so anything in front of the mesh (the
|
||||
// player capsule, props, the camera-collision proxy) could block painting.
|
||||
int hitCount = Physics.RaycastNonAlloc(ray, _rayHits, 1000f, ~0, QueryTriggerInteraction.Ignore);
|
||||
RaycastHit hit = default;
|
||||
float nearest = float.MaxValue;
|
||||
bool found = false;
|
||||
for (int i = 0; i < hitCount; i++)
|
||||
@@ -719,7 +732,20 @@ public partial class MeshPainter : MonoBehaviour
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) return false;
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raycasts at a screen position against the paint mesh and computes the brush
|
||||
/// placement: hit point (local space), local 3D radius (constant visual size via UV
|
||||
/// density) and the orthonormal tangent frame (u/v/n). Shared by painting and preview.
|
||||
/// </summary>
|
||||
private bool TryComputeBrushPlacement(Vector2 screenPos, out Vector3 pLocal, out float radiusLocal,
|
||||
out Vector3 u, out Vector3 v, out Vector3 nLocal)
|
||||
{
|
||||
pLocal = default; radiusLocal = 0f; u = default; v = default; nLocal = default;
|
||||
|
||||
if (!TryRaycastPaintMesh(screenPos, out RaycastHit hit)) return false;
|
||||
if (_verts == null || hit.triangleIndex < 0) return false;
|
||||
|
||||
pLocal = transform.InverseTransformPoint(hit.point);
|
||||
|
||||
Reference in new issue
Block a user