/** * LANE-RENDER — remove-mode hover. * * Two reads at once: a hazard-striped decal on the machine's footprint (this is the * ground you are about to clear) and a red wash over its volume (this is the thing that * is going away). Stripes because the codex dresses everything horrifying as routine * industrial equipment — demolition should look like a permit, not an explosion. * * Driven by the `__remove` ghost sentinel; see ghost.ts. */ import * as THREE from 'three'; const DEMOLITION = 0xff3b30; const DECAL_VERT = /* glsl */ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `; const DECAL_FRAG = /* glsl */ ` precision highp float; uniform float uTime; uniform vec2 uScale; // footprint in tiles, so stripes don't stretch on big machines uniform vec3 uColor; varying vec2 vUv; void main() { // Border: constant thickness regardless of footprint. vec2 d = min(vUv, 1.0 - vUv) * uScale; float edge = min(d.x, d.y); float border = 1.0 - smoothstep(0.04, 0.10, edge); // Hazard stripes, crawling. vec2 p = vUv * uScale; float s = fract((p.x + p.y) * 1.6 - uTime * 0.5); float stripe = step(0.5, s); float a = max(border, stripe * 0.28); if (a < 0.01) discard; gl_FragColor = vec4(uColor, a * 0.85); } `; export class RemoveHover { readonly group = new THREE.Group(); private decal: THREE.Mesh; private wash: THREE.Mesh; private uniforms = { uTime: { value: 0 }, uScale: { value: new THREE.Vector2(1, 1) }, uColor: { value: new THREE.Color(DEMOLITION) }, }; constructor() { this.group.name = 'removeHover'; this.group.visible = false; this.group.renderOrder = 11; this.decal = new THREE.Mesh( new THREE.PlaneGeometry(1, 1), new THREE.ShaderMaterial({ uniforms: this.uniforms, vertexShader: DECAL_VERT, fragmentShader: DECAL_FRAG, transparent: true, depthWrite: false, }), ); this.decal.rotation.x = -Math.PI / 2; this.decal.position.y = 0.02; // just clear of the ground plane this.group.add(this.decal); this.wash = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: DEMOLITION, transparent: true, opacity: 0.3, depthWrite: false, }), ); this.group.add(this.wash); } hide(): void { this.group.visible = false; } /** * Frame the machine we're about to demolish. `w`/`d` are its ROTATED footprint, so a * 2x3 turned east reads as 3x2 — the decal has to match what's actually occupied. */ show(cx: number, cz: number, w: number, d: number, height: number, timeSec: number): void { this.group.visible = true; this.uniforms.uTime.value = timeSec; this.uniforms.uScale.value.set(w, d); this.decal.scale.set(w, d, 1); this.decal.position.set(cx, 0.02, cz); this.wash.scale.set(w * 0.94, height, d * 0.94); this.wash.position.set(cx, height / 2, cz); } }