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