drafting wall attach system

This commit is contained in:
wass08
2026-01-19 07:59:06 +09:00
parent 159b61d22a
commit a217d0d3a6
8 changed files with 163 additions and 37 deletions
+1
View File
@@ -10,6 +10,7 @@ export interface GridEvent {
export interface NodeEvent<T extends AnyNode = AnyNode> {
node: T;
position: [number, number, number];
localPosition: [number, number, number];
normal?: [number, number, number];
stopPropagation: () => void;
}
+1
View File
@@ -93,6 +93,7 @@ const useScene = create<SceneState>()(
type: "item",
name: "Window",
position: [2.5, 0.5, 0],
parentId: wall0.id,
asset: {
name: "Round Window",
thumbnail: "/items/window-round/thumbnail.png",
@@ -47,12 +47,20 @@ function updateWallGeometry(wallId: string) {
.map((childId) => useScene.getState().nodes[childId])
.filter((n): n is AnyNode => n !== undefined);
// Perform the Extrusion with Holes logic we discussed
// Generate visual geometry with holes
const newGeo = generateExtrudedWall(node, childrenNodes);
mesh.geometry.dispose();
mesh.geometry = newGeo;
// Update collision mesh with solid geometry (no holes)
const collisionMesh = mesh.getObjectByName("collision-mesh") as THREE.Mesh;
if (collisionMesh) {
const collisionGeo = generateExtrudedWall(node, []); // No children = no holes
collisionMesh.geometry.dispose();
collisionMesh.geometry = collisionGeo;
}
mesh.position.set(node.start[0], 0, node.start[1]);
// Rotate mesh to look at 'end' point
@@ -1,8 +1,14 @@
import { emitter, ItemNode, useRegistry } from "@pascal-app/core";
import {
AnyNodeId,
emitter,
ItemNode,
useRegistry,
useScene,
} from "@pascal-app/core";
import { Clone } from "@react-three/drei/core/Clone";
import { useGLTF } from "@react-three/drei/core/Gltf";
import { ThreeEvent } from "@react-three/fiber/dist/declarations/src/core/events";
import { Suspense, useCallback, useRef } from "react";
import { Suspense, useCallback, useEffect, useRef } from "react";
import { Group } from "three";
import { useNodeEvents } from "../../../hooks/use-node-events";
@@ -29,6 +35,11 @@ const ModelRenderer = ({ node }: { node: ItemNode }) => {
const handlers = useNodeEvents(node, "item");
useEffect(() => {
if (!node.parentId) return;
useScene.getState().dirtyNodes.add(node.parentId as AnyNodeId);
}, []);
return (
<Clone
object={scene}
@@ -12,15 +12,13 @@ export const WallRenderer = ({ node }: { node: WallNode }) => {
const handlers = useNodeEvents(node, "wall");
return (
<mesh
ref={ref}
castShadow
receiveShadow
{...handlers}
>
<mesh ref={ref} castShadow receiveShadow>
{/* WallSystem will replace this geometry in the next frame */}
<boxGeometry args={[0, 0, 0]} />
<meshStandardMaterial color="lightgray" />
<mesh name="collision-mesh" {...handlers} visible={false}>
<boxGeometry args={[0, 0, 0]} />
</mesh>
{node.children.map((childId) => (
<NodeRenderer key={childId} nodeId={childId} />
+7 -2
View File
@@ -18,12 +18,17 @@ type NodeConfig = {
type NodeType = keyof NodeConfig;
export function useNodeEvents<T extends NodeType>(node: NodeConfig[T]["node"], type: T) {
export function useNodeEvents<T extends NodeType>(
node: NodeConfig[T]["node"],
type: T,
) {
const emit = (suffix: EventSuffix, e: ThreeEvent<PointerEvent>) => {
const eventKey = `${type}:${suffix}` as `${T}:${EventSuffix}`;
const localPoint = e.object.worldToLocal(e.point.clone());
const payload = {
node,
position: [e.point.x, e.point.y, e.point.z],
localPosition: [localPoint.x, localPoint.y, localPoint.z],
normal: e.face
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
: undefined,
@@ -52,4 +57,4 @@ export function useNodeEvents<T extends NodeType>(node: NodeConfig[T]["node"], t
onDoubleClick: (e: ThreeEvent<PointerEvent>) => emit("double-click", e),
onContextMenu: (e: ThreeEvent<PointerEvent>) => emit("context-menu", e),
};
}
}