events handlers
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { BuildingNode, LevelNode, useRegistry } from "@pascal-app/core";
|
||||
import { useRef } from "react";
|
||||
import { Group } from "three";
|
||||
import { NodeRenderer } from "../node-renderer";
|
||||
|
||||
export const BuildingRenderer = ({ node }: { node: BuildingNode }) => {
|
||||
const ref = useRef<Group>(null!);
|
||||
|
||||
useRegistry(node.id, node.type, ref);
|
||||
return (
|
||||
<group ref={ref}>
|
||||
{/* <mesh receiveShadow>
|
||||
<boxGeometry args={[10, 0.1, 10]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
</mesh> */}
|
||||
{node.children.map((childId) => (
|
||||
<NodeRenderer key={childId} nodeId={childId} />
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { useGLTF } from "@react-three/drei/core/Gltf";
|
||||
import { ThreeEvent } from "@react-three/fiber/dist/declarations/src/core/events";
|
||||
import { useCallback, useRef } from "react";
|
||||
import { Group } from "three";
|
||||
import { useNodeEvents } from "../../../hooks/use-node-events";
|
||||
|
||||
export const ItemRenderer = ({ node }: { node: ItemNode }) => {
|
||||
const ref = useRef<Group>(null!);
|
||||
@@ -15,114 +16,7 @@ export const ItemRenderer = ({ node }: { node: ItemNode }) => {
|
||||
nodes.cutout.visible = false;
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
const onPointerDown = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
// Only emit events for left-click (button 0)
|
||||
if (e.button !== 0) return;
|
||||
|
||||
const eventData = {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z] as [number, number, number],
|
||||
normal: e.face
|
||||
? ([e.face.normal.x, e.face.normal.y, e.face.normal.z] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
])
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
};
|
||||
emitter.emit("item:pointerdown", eventData);
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onClick = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
// Only emit events for left-click (button 0)
|
||||
if (e.button !== 0) return;
|
||||
|
||||
const eventData = {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z] as [number, number, number],
|
||||
normal: e.face
|
||||
? ([e.face.normal.x, e.face.normal.y, e.face.normal.z] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
])
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
};
|
||||
emitter.emit("item:click", eventData);
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onPointerUp = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
// Only emit events for left-click (button 0)
|
||||
if (e.button !== 0) return;
|
||||
|
||||
emitter.emit("item:pointerup", {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? ([e.face.normal.x, e.face.normal.y, e.face.normal.z] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
])
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
});
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onPointerEnter = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
emitter.emit("item:enter", {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
});
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onPointerLeave = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
emitter.emit("item:leave", {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
});
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onPointerMove = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
emitter.emit("item:move", {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
});
|
||||
},
|
||||
[node]
|
||||
);
|
||||
const handlers = useNodeEvents(node, "item");
|
||||
|
||||
return (
|
||||
<Clone
|
||||
@@ -130,12 +24,7 @@ export const ItemRenderer = ({ node }: { node: ItemNode }) => {
|
||||
object={scene}
|
||||
position={node.position}
|
||||
rotation={node.rotation}
|
||||
onPointerDown={onPointerDown}
|
||||
onClick={onClick}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerEnter={onPointerEnter}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
{...handlers}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,10 +9,10 @@ export const LevelRenderer = ({ node }: { node: LevelNode }) => {
|
||||
useRegistry(node.id, node.type, ref);
|
||||
return (
|
||||
<group ref={ref}>
|
||||
<mesh receiveShadow>
|
||||
{/* <mesh receiveShadow>
|
||||
<boxGeometry args={[10, 0.1, 10]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
</mesh>
|
||||
</mesh> */}
|
||||
{node.children.map((childId) => (
|
||||
<NodeRenderer key={childId} nodeId={childId} />
|
||||
))}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { AnyNode, useScene } from "@pascal-app/core";
|
||||
import { ItemRenderer } from "./item/item-renderer";
|
||||
import { LevelRenderer } from "./level/level-renderer";
|
||||
import { WallRenderer } from "./wall/wall-renderer";
|
||||
import { BuildingRenderer } from "./building/building-renderer";
|
||||
|
||||
export const NodeRenderer = ({ nodeId }: { nodeId: AnyNode["id"] }) => {
|
||||
const node = useScene((state) => state.nodes[nodeId]);
|
||||
@@ -12,6 +13,7 @@ export const NodeRenderer = ({ nodeId }: { nodeId: AnyNode["id"] }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{node.type === "building" && <BuildingRenderer node={node} />}
|
||||
{node.type === "level" && <LevelRenderer node={node} />}
|
||||
{node.type === "item" && <ItemRenderer node={node} />}
|
||||
{node.type === "wall" && <WallRenderer node={node} />}
|
||||
|
||||
@@ -3,132 +3,21 @@ import { ThreeEvent } from "@react-three/fiber";
|
||||
import { useCallback, useRef } from "react";
|
||||
import { Mesh } from "three";
|
||||
import { NodeRenderer } from "../node-renderer";
|
||||
import { useNodeEvents } from "../../../hooks/use-node-events";
|
||||
|
||||
export const WallRenderer = ({ node }: { node: WallNode }) => {
|
||||
const ref = useRef<Mesh>(null!);
|
||||
|
||||
useRegistry(node.id, "wall", ref);
|
||||
|
||||
// Event handlers
|
||||
|
||||
const onPointerDown = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
// Only emit events for left-click (button 0)
|
||||
if (e.button !== 0) return;
|
||||
|
||||
const eventData = {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z] as [number, number, number],
|
||||
normal: e.face
|
||||
? ([e.face.normal.x, e.face.normal.y, e.face.normal.z] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
])
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
};
|
||||
emitter.emit("wall:pointerdown", eventData);
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onClick = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
// Only emit events for left-click (button 0)
|
||||
if (e.button !== 0) return;
|
||||
|
||||
const eventData = {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z] as [number, number, number],
|
||||
normal: e.face
|
||||
? ([e.face.normal.x, e.face.normal.y, e.face.normal.z] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
])
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
};
|
||||
emitter.emit("wall:click", eventData);
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onPointerUp = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
// Only emit events for left-click (button 0)
|
||||
if (e.button !== 0) return;
|
||||
|
||||
emitter.emit("wall:pointerup", {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? ([e.face.normal.x, e.face.normal.y, e.face.normal.z] as [
|
||||
number,
|
||||
number,
|
||||
number
|
||||
])
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
});
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onPointerEnter = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
emitter.emit("wall:enter", {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
});
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onPointerLeave = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
emitter.emit("wall:leave", {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
});
|
||||
},
|
||||
[node]
|
||||
);
|
||||
|
||||
const onPointerMove = useCallback(
|
||||
(e: ThreeEvent<PointerEvent>) => {
|
||||
emitter.emit("wall:move", {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
});
|
||||
},
|
||||
[node]
|
||||
);
|
||||
const handlers = useNodeEvents(node, "wall");
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={ref}
|
||||
castShadow
|
||||
receiveShadow
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerEnter={onPointerEnter}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
onClick={onClick}
|
||||
{...handlers}
|
||||
>
|
||||
{/* WallSystem will replace this geometry in the next frame */}
|
||||
<boxGeometry args={[0, 0, 0]} />
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import {
|
||||
emitter,
|
||||
EventSuffix,
|
||||
ItemEvent,
|
||||
ItemNode,
|
||||
WallEvent,
|
||||
WallNode,
|
||||
} from "@pascal-app/core";
|
||||
import { ThreeEvent } from "@react-three/fiber";
|
||||
|
||||
type NodeConfig = {
|
||||
item: { node: ItemNode; event: ItemEvent };
|
||||
wall: { node: WallNode; event: WallEvent };
|
||||
};
|
||||
|
||||
type NodeType = keyof NodeConfig;
|
||||
|
||||
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 payload = {
|
||||
node,
|
||||
position: [e.point.x, e.point.y, e.point.z],
|
||||
normal: e.face
|
||||
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
|
||||
: undefined,
|
||||
stopPropagation: () => e.stopPropagation(),
|
||||
} as NodeConfig[T]["event"];
|
||||
|
||||
emitter.emit(eventKey, payload);
|
||||
};
|
||||
|
||||
return {
|
||||
onPointerDown: (e: ThreeEvent<PointerEvent>) => {
|
||||
if (e.button !== 0) return;
|
||||
emit("pointerdown", e);
|
||||
},
|
||||
onPointerUp: (e: ThreeEvent<PointerEvent>) => {
|
||||
if (e.button !== 0) return;
|
||||
emit("pointerup", e);
|
||||
},
|
||||
onClick: (e: ThreeEvent<PointerEvent>) => {
|
||||
if (e.button !== 0) return;
|
||||
emit("click", e);
|
||||
},
|
||||
onPointerEnter: (e: ThreeEvent<PointerEvent>) => emit("enter", e),
|
||||
onPointerLeave: (e: ThreeEvent<PointerEvent>) => emit("leave", e),
|
||||
onPointerMove: (e: ThreeEvent<PointerEvent>) => emit("move", e),
|
||||
onDoubleClick: (e: ThreeEvent<PointerEvent>) => emit("double-click", e),
|
||||
onContextMenu: (e: ThreeEvent<PointerEvent>) => emit("context-menu", e),
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { LevelNode } from "@pascal-app/core";
|
||||
import { BuildingNode, LevelNode } from "@pascal-app/core";
|
||||
import { create } from "zustand";
|
||||
|
||||
type ViewerState = {
|
||||
levelMode: "stacked" | "exploded" | "solo" | "manual";
|
||||
setLevelMode: (mode: "stacked" | "exploded" | "solo" | "manual") => void;
|
||||
|
||||
currentBuildingId: BuildingNode["id"] | null;
|
||||
setCurrentBuildingId: (id: BuildingNode["id"] | null) => void;
|
||||
currentLevelId: LevelNode["id"] | null;
|
||||
setCurrentLevelId: (id: LevelNode["id"] | null) => void;
|
||||
};
|
||||
@@ -16,6 +18,8 @@ const useViewer = create<ViewerState>()((set, get) => ({
|
||||
setLevelMode: (mode) => set({ levelMode: mode }),
|
||||
currentLevelId: null,
|
||||
setCurrentLevelId: (id) => set({ currentLevelId: id }),
|
||||
currentBuildingId: null,
|
||||
setCurrentBuildingId: (id) => set({ currentBuildingId: id }),
|
||||
}));
|
||||
|
||||
export default useViewer;
|
||||
|
||||
Reference in New Issue
Block a user