diff --git a/Assets/Scripts/Components/MeshPainter/MeshPainter.cs b/Assets/Scripts/Components/MeshPainter/MeshPainter.cs
index eeed0b1..b27dbad 100644
--- a/Assets/Scripts/Components/MeshPainter/MeshPainter.cs
+++ b/Assets/Scripts/Components/MeshPainter/MeshPainter.cs
@@ -424,6 +424,8 @@ public partial class MeshPainter : MonoBehaviour
// --- preview overlay ---
/// True while the brush preview decal is actually drawn this frame (hover hits the paintable surface).
public bool BrushPreviewVisible { get; private set; }
+ /// True while the paint button is held on the paint path (stroke running). The cursor hides entirely.
+ public bool StrokeInProgress { get; private set; }
public Shader previewShader; // optional; else Shader.Find("Hidden/MeshPaintPreview")
private Material _previewMat;
private Mesh _previewQuad; // unit quad in the tangent plane (flat decal mode)
@@ -855,14 +857,20 @@ public partial class MeshPainter : MonoBehaviour
ResolveActiveMask();
if (!TryGetMouse(out bool pressed, out Vector2 screenPos))
+ {
+ StrokeInProgress = false;
return;
+ }
// Don't paint through the in-game UI (a HUD click must not also stamp the mesh behind it).
if (pressed && InputUtil.PointerOverUi())
pressed = false;
- // Brush preview at the hover point - independent of painting AND of the pipette, so the
- // footprint stays visible before pressing, while the pipette holds, and during wheel resize.
+ // While the button is held on the paint path the hardware cursor is hidden entirely
+ // (CursorController); the preview decal itself stays visible during the stroke.
+ StrokeInProgress = pressed && !pipette;
+
+ // Brush preview at the hover point.
if (previewMode != BrushPreviewMode.Off && _previewMat && paintCamera)
UpdatePreview(screenPos);
diff --git a/Assets/Scripts/Components/UI/CursorController.cs b/Assets/Scripts/Components/UI/CursorController.cs
index ff72572..ce23e58 100644
--- a/Assets/Scripts/Components/UI/CursorController.cs
+++ b/Assets/Scripts/Components/UI/CursorController.cs
@@ -13,14 +13,17 @@ public class CursorController : MonoBehaviour
[Header("Cursors (import type: Cursor)")]
[SerializeField] private Texture2D normalCursor;
- [Tooltip("Click point inside the texture, in pixels from the top-left.")]
+ [Tooltip("Click point inside the texture, normalized 0-1 from the top-left. Independent of the importer's Max Size.")]
[SerializeField] private Vector2 normalHotspot = Vector2.zero;
[SerializeField] private Texture2D paintCursor;
- [SerializeField] private Vector2 paintHotspot = new(16f, 16f);
+ [SerializeField] private Vector2 paintHotspot = new(0.5f, 0.5f);
[SerializeField] private Texture2D pipetteCursor;
- [SerializeField] private Vector2 pipetteHotspot = new(1f, 30f);
+ [SerializeField] private Vector2 pipetteHotspot = new(0f, 1f);
+ [Tooltip("Fully transparent texture shown while a stroke is running - swapping the cursor " +
+ "image avoids fighting the camera rigs over Cursor.visible (which caused flicker).")]
+ [SerializeField] private Texture2D blankCursor;
- private enum State { None, Normal, Paint, Pipette }
+ private enum State { None, Normal, Paint, Pipette, Blank }
private State _applied = State.None;
private CursorMode _appliedMode = CursorMode.Auto;
@@ -68,8 +71,11 @@ public class CursorController : MonoBehaviour
{
if (painter.pipette) want = State.Pipette;
// Paint cursor only while painting is actually possible, i.e. the brush preview
- // decal is drawn at the hover point this frame; hovering off the mesh -> normal.
- else if (painter.BrushPreviewVisible) want = State.Paint;
+ // decal is drawn at the hover point this frame (hovering off the mesh -> normal,
+ // also mid-stroke). While the stroke touches the mesh the decal alone marks the
+ // brush position -> blank cursor.
+ else if (painter.BrushPreviewVisible)
+ want = painter.StrokeInProgress ? State.Blank : State.Paint;
}
var mode = CursorMode.Auto;
@@ -83,12 +89,21 @@ public class CursorController : MonoBehaviour
switch (want)
{
- case State.Paint: Cursor.SetCursor(paintCursor, paintHotspot, mode); break;
- case State.Pipette: Cursor.SetCursor(pipetteCursor, pipetteHotspot, mode); break;
- default: Cursor.SetCursor(normalCursor, normalHotspot, mode); break;
+ case State.Blank: Apply(blankCursor, Vector2.zero, mode); break;
+ case State.Paint: Apply(paintCursor, paintHotspot, mode); break;
+ case State.Pipette: Apply(pipetteCursor, pipetteHotspot, mode); break;
+ default: Apply(normalCursor, normalHotspot, mode); break;
}
}
+ private static void Apply(Texture2D cursor, Vector2 normalizedHotspot, CursorMode mode)
+ {
+ var hotspot = cursor
+ ? Vector2.Scale(normalizedHotspot, new Vector2(cursor.width, cursor.height))
+ : Vector2.zero;
+ Cursor.SetCursor(cursor, hotspot, mode);
+ }
+
private void OnDisable()
{
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto); // back to the OS cursor
diff --git a/Assets/Textures/Cursors/Cursor_Blank.png b/Assets/Textures/Cursors/Cursor_Blank.png
new file mode 100644
index 0000000..6e3cb09
Binary files /dev/null and b/Assets/Textures/Cursors/Cursor_Blank.png differ
diff --git a/Assets/Textures/Cursors/Cursor_Blank.png.meta b/Assets/Textures/Cursors/Cursor_Blank.png.meta
new file mode 100644
index 0000000..ebd3e3a
--- /dev/null
+++ b/Assets/Textures/Cursors/Cursor_Blank.png.meta
@@ -0,0 +1,117 @@
+fileFormatVersion: 2
+guid: f924c6157db044a4bd2feac9d02cf38f
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 1
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 1
+ wrapV: 1
+ wrapW: 1
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 7
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 64
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 0
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Textures/Cursors/Cursor_Normal.png b/Assets/Textures/Cursors/Cursor_Normal.png
index 9ccc10e..06a4b91 100644
Binary files a/Assets/Textures/Cursors/Cursor_Normal.png and b/Assets/Textures/Cursors/Cursor_Normal.png differ
diff --git a/Assets/Textures/Cursors/Cursor_Normal.png.meta b/Assets/Textures/Cursors/Cursor_Normal.png.meta
index bd0d528..aaeb1ab 100644
--- a/Assets/Textures/Cursors/Cursor_Normal.png.meta
+++ b/Assets/Textures/Cursors/Cursor_Normal.png.meta
@@ -34,13 +34,13 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
- filterMode: 0
+ filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
- nPOTScale: 0
+ nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
@@ -69,7 +69,7 @@ TextureImporter:
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
- maxTextureSize: 2048
+ maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
diff --git a/Assets/Textures/Cursors/Cursor_Paint.png b/Assets/Textures/Cursors/Cursor_Paint.png
index 68758f0..5669d10 100644
Binary files a/Assets/Textures/Cursors/Cursor_Paint.png and b/Assets/Textures/Cursors/Cursor_Paint.png differ
diff --git a/Assets/Textures/Cursors/Cursor_Paint.png.meta b/Assets/Textures/Cursors/Cursor_Paint.png.meta
index f3c4922..12cf99f 100644
--- a/Assets/Textures/Cursors/Cursor_Paint.png.meta
+++ b/Assets/Textures/Cursors/Cursor_Paint.png.meta
@@ -34,13 +34,13 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
- filterMode: 0
+ filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
- nPOTScale: 0
+ nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
@@ -69,7 +69,7 @@ TextureImporter:
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
- maxTextureSize: 2048
+ maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
diff --git a/Assets/Textures/Cursors/Cursor_Pipette.png b/Assets/Textures/Cursors/Cursor_Pipette.png
index e0a0c73..a061199 100644
Binary files a/Assets/Textures/Cursors/Cursor_Pipette.png and b/Assets/Textures/Cursors/Cursor_Pipette.png differ
diff --git a/Assets/Textures/Cursors/Cursor_Pipette.png.meta b/Assets/Textures/Cursors/Cursor_Pipette.png.meta
index 76f182b..9414a1c 100644
--- a/Assets/Textures/Cursors/Cursor_Pipette.png.meta
+++ b/Assets/Textures/Cursors/Cursor_Pipette.png.meta
@@ -34,13 +34,13 @@ TextureImporter:
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
- filterMode: 0
+ filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
- nPOTScale: 0
+ nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
@@ -69,7 +69,7 @@ TextureImporter:
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
- maxTextureSize: 2048
+ maxTextureSize: 64
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0