From cf92c9bd27a28b65ad78125cd03816512fcf13eb Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 27 Oct 2026 08:08:38 -0400 Subject: [PATCH] The glass board's marks are drawn over the glass and the figures: a dot on an empty square a piece may go to, a ring around a figure it may take or that is chosen, a halo for the last move, each on a dark rim so it reads on the pale air as well as the underworld Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx --- src/lib/components/Glass.svelte | 38 ++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/lib/components/Glass.svelte b/src/lib/components/Glass.svelte index 52df6e8..e82edb4 100644 --- a/src/lib/components/Glass.svelte +++ b/src/lib/components/Glass.svelte @@ -121,15 +121,27 @@ } // Marks on squares: what is selected, where it may go, the last move, a King in check. + // They are drawn over everything, so that neither the glass above nor a figure on the + // square can hide them, and each sits on a dark rim so it reads on the pale air as well as + // the underworld. A dot is an empty square a piece may go to; a ring hugs a figure's base. const marks = new THREE.Group(); scene.add(marks); - const markGeometry = new THREE.PlaneGeometry(CELL * 0.92, CELL * 0.92); - function mark(i: number, colour: string, opacity: number) { - const m = new THREE.Mesh(markGeometry, new THREE.MeshBasicMaterial({ color: colour, transparent: true, opacity, depthWrite: false })); - m.userData.square = i; - m.rotation.x = -Math.PI / 2; - m.position.copy(at(i)).add(new THREE.Vector3(0, 0.015, 0)); - marks.add(m); + const SHAPE = { + dot: [new THREE.CircleGeometry(CELL * 0.18, 24), new THREE.CircleGeometry(CELL * 0.23, 24)], + ring: [new THREE.RingGeometry(CELL * 0.5, CELL * 0.6, 48), new THREE.RingGeometry(CELL * 0.46, CELL * 0.64, 48)], + halo: [new THREE.RingGeometry(CELL * 0.66, CELL * 0.74, 48), new THREE.RingGeometry(CELL * 0.63, CELL * 0.77, 48)] + }; + function mark(i: number, shape: keyof typeof SHAPE, colour: string, opacity = 1) { + const [face, rim] = SHAPE[shape]; + const layers: [THREE.BufferGeometry, string, number][] = [[rim, '#1b1410', opacity * 0.75], [face, colour, opacity]]; + for (const [geometry, color, alpha] of layers) { + const m = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ color, transparent: true, opacity: alpha, depthWrite: false, depthTest: false, toneMapped: false })); + m.userData.square = i; + m.renderOrder = 10 + marks.children.length; + m.rotation.x = -Math.PI / 2; + m.position.copy(at(i)).add(new THREE.Vector3(0, 0.015, 0)); + marks.add(m); + } } // Figures: one scene per kind, shared geometry, a livery per side and a dimmed one for a piece held by a Basilisk. @@ -356,12 +368,12 @@ }); $effect(() => { marks.clear(); - for (const i of reach) mark(i, MARK.reach, 0.22); - for (const i of shownSquares) mark(i, MARK.last, 0.4); - for (const i of shownCaptured) mark(i, MARK.fallen, 0.45); - for (const i of targets) mark(i, MARK.target, 0.5); - if (selected !== null) mark(selected, MARK.selected, 0.6); - if (checkSquare >= 0) mark(checkSquare, MARK.check, 0.6); + for (const i of reach) mark(i, g.cells[i] === '' ? 'dot' : 'ring', MARK.reach, 0.85); + for (const i of shownSquares) mark(i, 'halo', MARK.last); + for (const i of shownCaptured) mark(i, 'halo', MARK.fallen); + for (const i of targets) mark(i, g.cells[i] === '' ? 'dot' : 'ring', MARK.target); + if (selected !== null) mark(selected, 'ring', MARK.selected); + if (checkSquare >= 0 && checkSquare !== selected) mark(checkSquare, 'ring', MARK.check); wake(); }); $effect(() => look(view));