"use client"; import { emitter, ItemNode, sceneRegistry, useScene, WallNode, } from "@pascal-app/core"; import { useGridEvents, useViewer, Viewer } from "@pascal-app/viewer"; import { Stats } from "@react-three/drei"; import { useFrame, useThree } from "@react-three/fiber"; import { useEffect, useMemo, useRef } from "react"; import { Color, Object3D } from "three"; import { outline } from "three/addons/tsl/display/OutlineNode.js"; import { color, float, fract, fwidth, mix, oscSine, pass, positionLocal, time, uniform, } from "three/tsl"; import { MeshBasicNodeMaterial, PostProcessing } from "three/webgpu"; import { ActionMenu } from "./ui/action-menu"; import { WallTool } from "./tools/wall/wall-tool"; import { ToolManager } from "./tools/tool-manager"; const selectedObjects: Object3D[] = []; export default function Editor() { return (
{/* */}
); } export const Passes = ({}) => { const { gl: renderer, scene, camera } = useThree(); const postProcessingRef = useRef(null); useEffect(() => { if (!renderer || !scene || !camera) { return; } const scenePass = pass(scene, camera); // Get texture nodes const outputPass = scenePass.getTextureNode("output"); const edgeStrength = uniform(3.0); const edgeGlow = uniform(0.0); const edgeThickness = uniform(1.0); const pulsePeriod = uniform(0); const visibleEdgeColor = uniform(new Color(0xffffff)); const hiddenEdgeColor = uniform(new Color(0x4e3636)); const outlinePass = outline(scene, camera, { selectedObjects, edgeGlow, edgeThickness, }); const { visibleEdge, hiddenEdge } = outlinePass; const period = time.div(pulsePeriod).mul(2); const osc = oscSine(period).mul(0.5).add(0.5); // osc [ 0.5, 1.0 ] const outlineColor = visibleEdge .mul(visibleEdgeColor) .add(hiddenEdge.mul(hiddenEdgeColor)) .mul(edgeStrength); const outlinePulse = pulsePeriod .greaterThan(0) .select(outlineColor.mul(osc), outlineColor); // Setup post-processing const postProcessing = new PostProcessing(renderer); postProcessing.outputNode = outlinePulse.add(scenePass); postProcessingRef.current = postProcessing; return () => { postProcessingRef.current = null; }; }, [renderer, scene, camera]); useFrame(() => { if (postProcessingRef.current) { postProcessingRef.current.render(); } }, 1); return null; }; 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, ]); const handlers = useGridEvents(); return ( ); }; const DraftSelector = () => { const selectedItemId = useRef(null); const itemSelectedAt = useRef(0); useEffect(() => { emitter.on('building:enter', (event) => { console.log('Entered building:', event.node.id); const itemMesh = sceneRegistry.nodes.get(event.node.id); selectedObjects.length = 0; selectedObjects.push(itemMesh); }); emitter.on('building:leave', (event) => { console.log('Leaving building:', event.node.id); selectedObjects.length = 0; }); emitter.on("item:click", (event) => { event.stopPropagation(); if (Date.now() - itemSelectedAt.current < 50) { return; } itemSelectedAt.current = Date.now(); if (selectedItemId.current === event.node.id) { selectedItemId.current = null; console.log("Deselected item:", event.node.id); selectedObjects.length = 0; return; } selectedItemId.current = event.node.id; const itemMesh = sceneRegistry.nodes.get(event.node.id); if (!itemMesh) return; selectedObjects.push(itemMesh); console.log("Selected item:", event.node.id); }); emitter.on("wall:click", (event) => { if (Date.now() - itemSelectedAt.current < 50) { return; } itemSelectedAt.current = Date.now(); if (selectedItemId.current === event.node.id) { selectedItemId.current = null; console.log("Deselected item:", event.node.id); selectedObjects.length = 0; return; } selectedItemId.current = event.node.id; const itemMesh = sceneRegistry.nodes.get(event.node.id); if (!itemMesh) return; selectedObjects.push(itemMesh); console.log("Selected item:", event.node.id); }); emitter.on("wall:move", (event) => { const wallNode = event.node as WallNode; if (wallNode.children.length === 0) return; const itemId = wallNode.children[0]; if (!itemId) return; if (selectedItemId.current !== itemId) return; const itemNode = useScene.getState().nodes[itemId]; const itemMesh = sceneRegistry.nodes.get(itemId); if (!itemNode || !itemMesh) return; itemMesh.position.set( event.position[0], event.position[1], event.position[2] ); useScene.getState().dirtyNodes.add(wallNode.id); console.log( "Wall move event:", wallNode.id, "Point position:", event.position ); }); }, []); return null; }; const LevelModeSwitcher = () => { const setLevelMode = useViewer((state) => state.setLevelMode); const levelMode = useViewer((state) => state.levelMode); return (
); };