From dabaefc57a5a2dac41af0558e07055036980a6c1 Mon Sep 17 00:00:00 2001 From: wass08 Date: Tue, 20 Jan 2026 08:26:47 +0900 Subject: [PATCH] different level edition + fix --- apps/editor/components/editor.tsx | 26 +++++- .../components/tools/item/item-tool.tsx | 7 +- .../components/tools/wall/wall-tool.tsx | 87 +++++++++++-------- .../panels/site-panel/wall-tree-node.tsx | 4 +- packages/core/src/events/bus.ts | 2 +- packages/core/src/store/use-scene.ts | 13 +-- .../core/src/systems/wall/wall-system.tsx | 13 ++- packages/viewer/src/hooks/use-grid-events.ts | 4 +- 8 files changed, 99 insertions(+), 57 deletions(-) diff --git a/apps/editor/components/editor.tsx b/apps/editor/components/editor.tsx index 67035aad..9bf63d68 100644 --- a/apps/editor/components/editor.tsx +++ b/apps/editor/components/editor.tsx @@ -13,7 +13,7 @@ import { useGridEvents, useViewer, Viewer } from "@pascal-app/viewer"; import { CameraControls, CameraControlsImpl, Stats } from "@react-three/drei"; import { useFrame, useThree } from "@react-three/fiber"; import { useEffect, useMemo, useRef } from "react"; -import { Color, Object3D } from "three"; +import { Color, MathUtils, Mesh, Object3D } from "three"; import { outline } from "three/addons/tsl/display/OutlineNode.js"; import { color, @@ -254,9 +254,31 @@ const Grid = ({ ]); const handlers = useGridEvents(); + const gridRef = useRef(null!); + + useFrame((_, delta) => { + const currentLevelId = useViewer.getState().currentLevelId; + let targetY = 0; + if (currentLevelId) { + const levelMesh = sceneRegistry.nodes.get(currentLevelId); + if (levelMesh) { + targetY = levelMesh.position.y; + } + } + gridRef.current.position.y = MathUtils.lerp( + gridRef.current.position.y, + targetY, + 12 * delta, + ); + }); return ( - + ); diff --git a/apps/editor/components/tools/item/item-tool.tsx b/apps/editor/components/tools/item/item-tool.tsx index ccac63b2..242b63b5 100644 --- a/apps/editor/components/tools/item/item-tool.tsx +++ b/apps/editor/components/tools/item/item-tool.tsx @@ -82,7 +82,7 @@ export const ItemTool: React.FC = () => { gridPosition.current.y, gridPosition.current.z, ], - name: "Draft Item", + name: selectedItem.name, asset: selectedItem, }); useScene.getState().createNode(draftItem.current, currentLevelId); @@ -98,11 +98,11 @@ export const ItemTool: React.FC = () => { gridPosition.current.set( Math.round(event.position[0] * 2) / 2, 0, - Math.round(event.position[1] * 2) / 2, + Math.round(event.position[2] * 2) / 2, ); cursorRef.current.position.set( gridPosition.current.x, - 0, + event.position[1], gridPosition.current.z, ); checkCanPlace(); @@ -212,6 +212,7 @@ export const ItemTool: React.FC = () => { Math.round(event.position[1] * 2) / 2, Math.round(event.position[2] * 2) / 2, ); + const { node: { start, end }, } = event; diff --git a/apps/editor/components/tools/wall/wall-tool.tsx b/apps/editor/components/tools/wall/wall-tool.tsx index 608edbd6..f318ac95 100644 --- a/apps/editor/components/tools/wall/wall-tool.tsx +++ b/apps/editor/components/tools/wall/wall-tool.tsx @@ -1,45 +1,45 @@ import { emitter, GridEvent, useScene, WallNode } from "@pascal-app/core"; import { useViewer } from "@pascal-app/viewer"; -import { useEffect, useRef } from "react" +import { useEffect, useRef } from "react"; import { Line, Mesh, Vector3 } from "three"; - const commitWallDrawing = (start: [number, number], end: [number, number]) => { const { currentLevelId } = useViewer.getState(); const { createNode } = useScene.getState(); - + if (!currentLevelId) return; const wall = WallNode.parse({ start, end }); - + createNode(wall, currentLevelId); }; export const WallTool: React.FC = () => { - const cursorRef = useRef(null); const drawingLineRef = useRef(null!); useEffect(() => { - let buildingState = 0; const startingPoint = new Vector3(0, 0, 0); const endingPoint = new Vector3(0, 0, 0); let gridPosition: [number, number] = [0, 0]; - - drawingLineRef.current.geometry.setFromPoints([ - startingPoint, - endingPoint, - ]); + drawingLineRef.current.geometry.setFromPoints([startingPoint, endingPoint]); const onGridMove = (event: GridEvent) => { if (!cursorRef.current) return; - gridPosition = [Math.round(event.position[0] * 2) / 2, Math.round(event.position[1] * 2) / 2]; - cursorRef.current.position.set(gridPosition[0], 0.1, gridPosition[1]); + gridPosition = [ + Math.round(event.position[0] * 2) / 2, + Math.round(event.position[2] * 2) / 2, + ]; + cursorRef.current.position.set( + gridPosition[0], + event.position[1], + gridPosition[1], + ); if (buildingState === 1) { - endingPoint.set(gridPosition[0], 0.1, gridPosition[1]); + endingPoint.set(gridPosition[0], event.position[1], gridPosition[1]); } drawingLineRef.current.geometry.setFromPoints([ startingPoint, @@ -48,42 +48,55 @@ export const WallTool: React.FC = () => { }; const onGridClick = (event: GridEvent) => { if (buildingState === 0) { - startingPoint.set(gridPosition[0], 0.1, gridPosition[1]); + startingPoint.set(gridPosition[0], event.position[1], gridPosition[1]); buildingState = 1; - console.log('starting building at:', startingPoint); + console.log("starting building at:", startingPoint); drawingLineRef.current.visible = true; - } else if (buildingState === 1) { - commitWallDrawing([startingPoint.x, startingPoint.z], [gridPosition[0], gridPosition[1]]); + commitWallDrawing( + [startingPoint.x, startingPoint.z], + [endingPoint.x, endingPoint.z], + ); drawingLineRef.current.visible = false; buildingState = 0; } - }; - - emitter.on('grid:move', onGridMove); - emitter.on('grid:click', onGridClick); + emitter.on("grid:move", onGridMove); + emitter.on("grid:click", onGridClick); return () => { - emitter.off('grid:move', onGridMove); - emitter.off('grid:click', onGridClick); + emitter.off("grid:move", onGridMove); + emitter.off("grid:click", onGridClick); }; }, []); return ( - - - - - - {/* @ts-ignore */} - - - - + + + + + + {/* @ts-ignore */} + + + + + - - ) -} \ No newline at end of file + ); +}; diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx index 3421dc4e..72d67938 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx @@ -18,13 +18,13 @@ export function WallTreeNode({ node, depth }: WallTreeNodeProps) { const wallLength = Math.sqrt( Math.pow(node.end[0] - node.start[0], 2) + - Math.pow(node.end[1] - node.start[1], 2) + Math.pow(node.end[1] - node.start[1], 2), ).toFixed(1); return ( } - label={node.name || `Wall (${wallLength}m)`} + label={node.name || `Wall (${wallLength}m/${node.height || 2.5}m)`} depth={depth} hasChildren={node.children.length > 0} expanded={expanded} diff --git a/packages/core/src/events/bus.ts b/packages/core/src/events/bus.ts index 574cf061..ce76f82e 100644 --- a/packages/core/src/events/bus.ts +++ b/packages/core/src/events/bus.ts @@ -4,7 +4,7 @@ import { AnyNode } from "../schema/types"; // Base event interfaces export interface GridEvent { - position: [number, number]; + position: [number, number, number]; } export interface NodeEvent { diff --git a/packages/core/src/store/use-scene.ts b/packages/core/src/store/use-scene.ts index ce7e1a44..dddfbcef 100644 --- a/packages/core/src/store/use-scene.ts +++ b/packages/core/src/store/use-scene.ts @@ -90,26 +90,27 @@ const useScene = create()( start: [5, 5], end: [5, 0], children: [], - parentId: level0.id, + parentId: level1.id, }); const window1 = ItemNode.parse({ type: "item", name: "Window", position: [2.5, 0.5, 0], - parentId: wall0.id, + parentId: wall3.id, asset: { name: "Round Window", - thumbnail: "/items/window-round/thumbnail.png", + thumbnail: "/items/window-small/thumbnail.png", category: "windows", attachTo: "wall", - src: "/items/window-round/model.glb", + src: "/items/window-small/model.glb", }, }); - wall0.children.push(window1.id); + wall3.children.push(window1.id); - level0.children.push(wall0.id, wall1.id, wall2.id, wall3.id); + level0.children.push(wall0.id, wall1.id, wall2.id); + level1.children.push(wall3.id); building.children.push(level0.id, level1.id, level2.id); diff --git a/packages/core/src/systems/wall/wall-system.tsx b/packages/core/src/systems/wall/wall-system.tsx index 965e5e03..bf3a12f4 100644 --- a/packages/core/src/systems/wall/wall-system.tsx +++ b/packages/core/src/systems/wall/wall-system.tsx @@ -99,6 +99,10 @@ export function generateExtrudedWall( wallNode.end[0] - wallNode.start[0], ); + // Get the wall mesh's world Y position (from level offset) + const wallMesh = sceneRegistry.nodes.get(wallNode.id) as THREE.Mesh; + const wallWorldY = wallMesh?.getWorldPosition(new THREE.Vector3()).y ?? 0; + childrenNodes.forEach((child) => { // Only process items that are intended to be wall cutouts if (child.type !== "item") return; @@ -112,7 +116,7 @@ export function generateExtrudedWall( const cutoutMesh = childMesh.getObjectByName("cutout") as THREE.Mesh; if (!cutoutMesh) return; - const holePath = createPathFromCutout(cutoutMesh, wallStart, wallAngle); + const holePath = createPathFromCutout(cutoutMesh, wallStart, wallAngle, wallWorldY); if (holePath) { shape.holes.push(holePath); } @@ -138,12 +142,13 @@ export function generateExtrudedWall( * Wall-local space: * - Origin at wall start point * - X axis runs along the wall (toward end point) - * - Y axis is height (world Y) + * - Y axis is height (relative to wall's world Y position) */ function createPathFromCutout( cutoutMesh: THREE.Mesh, wallStart: [number, number], wallAngle: number, + wallWorldY: number, ): THREE.Path | null { const geometry = cutoutMesh.geometry; if (!geometry) return null; @@ -177,9 +182,9 @@ function createPathFromCutout( // 2. Rotate around Y axis to align wall with local X axis // The wall shape is drawn on XY plane, so we need: // - localX = distance along wall - // - localY = height (world Y) + // - localY = height relative to wall's Y position const localX = worldX * cosAngle - worldZ * sinAngle; - const localY = v3.y; // Height stays the same + const localY = v3.y - wallWorldY; // Subtract wall's world Y to get local height // Create a key for deduplication (with small tolerance) const key = `${localX.toFixed(4)},${localY.toFixed(4)}`; diff --git a/packages/viewer/src/hooks/use-grid-events.ts b/packages/viewer/src/hooks/use-grid-events.ts index fd4a2914..069ea22d 100644 --- a/packages/viewer/src/hooks/use-grid-events.ts +++ b/packages/viewer/src/hooks/use-grid-events.ts @@ -5,7 +5,7 @@ export function useGridEvents() { const emit = (suffix: EventSuffix, e: ThreeEvent) => { const eventKey = `grid:${suffix}` as `grid:${EventSuffix}`; const payload: GridEvent = { - position: [e.point.x, e.point.z], + position: [e.point.x, e.point.y, e.point.z], }; emitter.emit(eventKey, payload); @@ -30,4 +30,4 @@ export function useGridEvents() { onDoubleClick: (e: ThreeEvent) => emit("double-click", e), onContextMenu: (e: ThreeEvent) => emit("context-menu", e), }; -} \ No newline at end of file +}