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);