diff --git a/apps/editor/components/editor.tsx b/apps/editor/components/editor.tsx index ed72a2db..c14c0d7d 100644 --- a/apps/editor/components/editor.tsx +++ b/apps/editor/components/editor.tsx @@ -9,8 +9,9 @@ import { } from "@pascal-app/core"; import { Viewer } from "@pascal-app/viewer"; import { Stats } from "@react-three/drei"; -import { useEffect, useRef } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { Mesh } from "three"; +import { color, float, fract, fwidth, mix, positionLocal } from "three/tsl"; import { MeshBasicNodeMaterial } from "three/webgpu"; export default function Editor() { @@ -20,11 +21,101 @@ export default function Editor() { + ); } +const Grid = ({ + cellSize = 0.5, + cellThickness = 0.5, + cellColor = "#888888", + sectionSize = 1, + sectionThickness = 1, + sectionColor = "#000000", + fadeDistance = 100, + fadeStrength = 1, +}: { + cellSize?: number; + cellThickness?: number; + cellColor?: string; + sectionSize?: number; + sectionThickness?: number; + sectionColor?: string; + fadeDistance?: number; + fadeStrength?: number; +}) => { + const material = useMemo(() => { + // Use xy since plane geometry is in XY space (before rotation) + const pos = positionLocal.xy; + + // Grid line function using fwidth for anti-aliasing + // Returns 1 on grid lines, 0 elsewhere + const getGrid = (size: number, thickness: number) => { + const r = pos.div(size); + const fw = fwidth(r); + // Distance to nearest grid line for each axis + const grid = fract(r.sub(0.5)).sub(0.5).abs(); + // Anti-aliased step: divide by fwidth and clamp + const lineX = float(1).sub( + grid.x + .div(fw.x) + .add(1 - thickness) + .min(1) + ); + const lineY = float(1).sub( + grid.y + .div(fw.y) + .add(1 - thickness) + .min(1) + ); + // Combine both axes - max gives us lines in both directions + return lineX.max(lineY); + }; + + const g1 = getGrid(cellSize, cellThickness); + const g2 = getGrid(sectionSize, sectionThickness); + + // Distance fade from center + const dist = pos.length(); + const fade = float(1).sub(dist.div(fadeDistance).min(1)).pow(fadeStrength); + + // Mix colors based on section grid + const gridColor = mix( + color(cellColor), + color(sectionColor), + float(sectionThickness).mul(g2).min(1) + ); + + // Combined alpha + const alpha = g1.add(g2).mul(fade); + const finalAlpha = mix(alpha.mul(0.75), alpha, g2); + + return new MeshBasicNodeMaterial({ + transparent: true, + colorNode: gridColor, + opacityNode: finalAlpha, + depthWrite: false, + }); + }, [ + cellSize, + cellThickness, + cellColor, + sectionSize, + sectionThickness, + sectionColor, + fadeDistance, + fadeStrength, + ]); + + return ( + + + + ); +}; + const Selector = () => { const selectedItemId = useRef(null); const itemSelectedAt = useRef(0);