hover / selection manager
This commit is contained in:
@@ -1,28 +1,123 @@
|
|||||||
import {
|
import {
|
||||||
|
type BuildingNode,
|
||||||
emitter,
|
emitter,
|
||||||
|
type GridEvent,
|
||||||
|
type ItemEvent,
|
||||||
type ItemNode,
|
type ItemNode,
|
||||||
sceneRegistry,
|
sceneRegistry,
|
||||||
useScene,
|
useScene,
|
||||||
|
type WallEvent,
|
||||||
type WallNode,
|
type WallNode,
|
||||||
} from "@pascal-app/core";
|
} from "@pascal-app/core";
|
||||||
import { useViewer } from "@pascal-app/viewer";
|
import { useViewer } from "@pascal-app/viewer";
|
||||||
|
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
import useEditor from "@/store/use-editor";
|
||||||
|
|
||||||
export const SelectionManager = () => {
|
export const SelectionManager = () => {
|
||||||
const selectedItemId = useRef<ItemNode["id"] | WallNode["id"]>(null);
|
const selectedItemId = useRef<ItemNode["id"] | WallNode["id"]>(null);
|
||||||
const itemSelectedAt = useRef<number>(0);
|
const itemSelectedAt = useRef<number>(0);
|
||||||
|
const phase = useEditor((state) => state.phase);
|
||||||
|
const mode = useEditor((state) => state.mode);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
emitter.on("building:enter", (event) => {
|
if (mode !== "select") {
|
||||||
// console.log("Entered building:", event.node.id);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phase === "site") {
|
||||||
|
const onBuildingEnter = (event: { node: BuildingNode }) => {
|
||||||
const itemMesh = sceneRegistry.nodes.get(event.node.id);
|
const itemMesh = sceneRegistry.nodes.get(event.node.id);
|
||||||
useViewer.getState().outliner.hoveredObjects.length = 0;
|
useViewer.getState().outliner.hoveredObjects.length = 0;
|
||||||
useViewer.getState().outliner.hoveredObjects.push(itemMesh);
|
useViewer.getState().outliner.hoveredObjects.push(itemMesh);
|
||||||
});
|
};
|
||||||
emitter.on("building:leave", (event) => {
|
const onBuildingLeave = (event: { node: BuildingNode }) => {
|
||||||
// console.log("Leaving building:", event.node.id);
|
|
||||||
useViewer.getState().outliner.hoveredObjects.length = 0;
|
useViewer.getState().outliner.hoveredObjects.length = 0;
|
||||||
});
|
};
|
||||||
|
emitter.on("building:enter", onBuildingEnter);
|
||||||
|
emitter.on("building:leave", onBuildingLeave);
|
||||||
|
return () => {
|
||||||
|
emitter.off("building:enter", onBuildingEnter);
|
||||||
|
emitter.off("building:leave", onBuildingLeave);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phase === "furnish") {
|
||||||
|
const onItemEnter = (event: ItemEvent) => {
|
||||||
|
const itemMesh = sceneRegistry.nodes.get(event.node.id);
|
||||||
|
useViewer.getState().outliner.hoveredObjects.length = 0;
|
||||||
|
useViewer.getState().outliner.hoveredObjects.push(itemMesh);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onItemLeave = (event: ItemEvent) => {
|
||||||
|
useViewer.getState().outliner.hoveredObjects.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onItemClick = (event: ItemEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
useViewer
|
||||||
|
.getState()
|
||||||
|
.outliner.selectedObjects.push(
|
||||||
|
sceneRegistry.nodes.get(event.node.id),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
emitter.on("item:enter", onItemEnter);
|
||||||
|
emitter.on("item:leave", onItemLeave);
|
||||||
|
emitter.on("item:click", onItemClick);
|
||||||
|
return () => {
|
||||||
|
emitter.off("item:enter", onItemEnter);
|
||||||
|
emitter.off("item:leave", onItemLeave);
|
||||||
|
emitter.off("item:click", onItemClick);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phase === "structure") {
|
||||||
|
const onGridClick = (event: GridEvent) => {
|
||||||
|
useViewer.getState().outliner.selectedObjects.length = 0;
|
||||||
|
};
|
||||||
|
const onWallEnter = (event: WallEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
const itemMesh = sceneRegistry.nodes.get(event.node.id);
|
||||||
|
useViewer.getState().outliner.hoveredObjects.length = 0;
|
||||||
|
useViewer.getState().outliner.hoveredObjects.push(itemMesh);
|
||||||
|
};
|
||||||
|
const onWallLeave = (event: WallEvent) => {
|
||||||
|
useViewer.getState().outliner.hoveredObjects.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onWallClick = (event: WallEvent) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (!event.nativeEvent.shiftKey) {
|
||||||
|
useViewer.getState().outliner.selectedObjects.length = 0;
|
||||||
|
useViewer
|
||||||
|
.getState()
|
||||||
|
.outliner.selectedObjects.push(
|
||||||
|
sceneRegistry.nodes.get(event.node.id),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// check if already selected
|
||||||
|
const selectedObjects = useViewer.getState().outliner.selectedObjects;
|
||||||
|
const wallMesh = sceneRegistry.nodes.get(event.node.id);
|
||||||
|
const index = selectedObjects.indexOf(wallMesh);
|
||||||
|
if (index > -1) {
|
||||||
|
// already selected, deselect
|
||||||
|
selectedObjects.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
// not selected, add to selection
|
||||||
|
selectedObjects.push(wallMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
emitter.on("grid:click", onGridClick);
|
||||||
|
emitter.on("wall:enter", onWallEnter);
|
||||||
|
emitter.on("wall:leave", onWallLeave);
|
||||||
|
emitter.on("wall:click", onWallClick);
|
||||||
|
return () => {
|
||||||
|
emitter.off("wall:enter", onWallEnter);
|
||||||
|
emitter.off("wall:leave", onWallLeave);
|
||||||
|
emitter.off("wall:click", onWallClick);
|
||||||
|
emitter.off("grid:click", onGridClick);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// emitter.on("item:click", (event) => {
|
// emitter.on("item:click", (event) => {
|
||||||
// event.stopPropagation();
|
// event.stopPropagation();
|
||||||
@@ -87,7 +182,7 @@ export const SelectionManager = () => {
|
|||||||
event.position,
|
event.position,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, []);
|
}, [mode, phase]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,49 +1,52 @@
|
|||||||
import mitt from 'mitt'
|
import type { ThreeEvent } from "@react-three/fiber";
|
||||||
import type { BuildingNode, ItemNode, WallNode } from '../schema'
|
import mitt from "mitt";
|
||||||
import type { AnyNode } from '../schema/types'
|
import type { BuildingNode, ItemNode, WallNode } from "../schema";
|
||||||
|
import type { AnyNode } from "../schema/types";
|
||||||
|
|
||||||
// Base event interfaces
|
// Base event interfaces
|
||||||
export interface GridEvent {
|
export interface GridEvent {
|
||||||
position: [number, number, number]
|
position: [number, number, number];
|
||||||
|
nativeEvent: ThreeEvent<PointerEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeEvent<T extends AnyNode = AnyNode> {
|
export interface NodeEvent<T extends AnyNode = AnyNode> {
|
||||||
node: T
|
node: T;
|
||||||
position: [number, number, number]
|
position: [number, number, number];
|
||||||
localPosition: [number, number, number]
|
localPosition: [number, number, number];
|
||||||
normal?: [number, number, number]
|
normal?: [number, number, number];
|
||||||
stopPropagation: () => void
|
stopPropagation: () => void;
|
||||||
|
nativeEvent: ThreeEvent<PointerEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WallEvent = NodeEvent<WallNode>
|
export type WallEvent = NodeEvent<WallNode>;
|
||||||
export type ItemEvent = NodeEvent<ItemNode>
|
export type ItemEvent = NodeEvent<ItemNode>;
|
||||||
export type BuildingEvent = NodeEvent<BuildingNode>
|
export type BuildingEvent = NodeEvent<BuildingNode>;
|
||||||
|
|
||||||
// Event suffixes - exported for use in hooks
|
// Event suffixes - exported for use in hooks
|
||||||
export const eventSuffixes = [
|
export const eventSuffixes = [
|
||||||
'click',
|
"click",
|
||||||
'move',
|
"move",
|
||||||
'enter',
|
"enter",
|
||||||
'leave',
|
"leave",
|
||||||
'pointerdown',
|
"pointerdown",
|
||||||
'pointerup',
|
"pointerup",
|
||||||
'context-menu',
|
"context-menu",
|
||||||
'double-click',
|
"double-click",
|
||||||
] as const
|
] as const;
|
||||||
|
|
||||||
export type EventSuffix = (typeof eventSuffixes)[number]
|
export type EventSuffix = (typeof eventSuffixes)[number];
|
||||||
|
|
||||||
type NodeEvents<T extends string, E> = {
|
type NodeEvents<T extends string, E> = {
|
||||||
[K in `${T}:${EventSuffix}`]: E
|
[K in `${T}:${EventSuffix}`]: E;
|
||||||
}
|
};
|
||||||
|
|
||||||
type GridEvents = {
|
type GridEvents = {
|
||||||
[K in `grid:${EventSuffix}`]: GridEvent
|
[K in `grid:${EventSuffix}`]: GridEvent;
|
||||||
}
|
};
|
||||||
|
|
||||||
type EditorEvents = GridEvents &
|
type EditorEvents = GridEvents &
|
||||||
NodeEvents<'wall', WallEvent> &
|
NodeEvents<"wall", WallEvent> &
|
||||||
NodeEvents<'item', ItemEvent> &
|
NodeEvents<"item", ItemEvent> &
|
||||||
NodeEvents<'building', BuildingEvent>
|
NodeEvents<"building", BuildingEvent>;
|
||||||
|
|
||||||
export const emitter = mitt<EditorEvents>()
|
export const emitter = mitt<EditorEvents>();
|
||||||
|
|||||||
@@ -1,93 +1,99 @@
|
|||||||
import { useFrame, useThree } from '@react-three/fiber'
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from "react";
|
||||||
import { Color } from 'three'
|
import { Color } from "three";
|
||||||
import { outline } from 'three/addons/tsl/display/OutlineNode.js'
|
import { outline } from "three/addons/tsl/display/OutlineNode.js";
|
||||||
import { oscSine, pass, time, uniform } from 'three/tsl'
|
import { oscSine, pass, time, uniform } from "three/tsl";
|
||||||
import { PostProcessing, type WebGPURenderer } from 'three/webgpu'
|
import { PostProcessing, type WebGPURenderer } from "three/webgpu";
|
||||||
import useViewer from '../../store/use-viewer'
|
import useViewer from "../../store/use-viewer";
|
||||||
|
|
||||||
const PostProcessingPasses = ({}) => {
|
const PostProcessingPasses = () => {
|
||||||
const { gl: renderer, scene, camera } = useThree()
|
const { gl: renderer, scene, camera } = useThree();
|
||||||
const postProcessingRef = useRef<PostProcessing | null>(null)
|
const postProcessingRef = useRef<PostProcessing | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!renderer || !scene || !camera) {
|
if (!renderer || !scene || !camera) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const scenePass = pass(scene, camera)
|
const scenePass = pass(scene, camera);
|
||||||
|
|
||||||
function generateSelectedOutlinePass() {
|
function generateSelectedOutlinePass() {
|
||||||
const edgeStrength = uniform(3)
|
const edgeStrength = uniform(3);
|
||||||
const edgeGlow = uniform(0)
|
const edgeGlow = uniform(0);
|
||||||
const edgeThickness = uniform(1)
|
const edgeThickness = uniform(1);
|
||||||
const visibleEdgeColor = uniform(new Color(0xffffff))
|
const visibleEdgeColor = uniform(new Color(0xffffff));
|
||||||
const hiddenEdgeColor = uniform(new Color(0xf3ff47))
|
const hiddenEdgeColor = uniform(new Color(0xf3ff47));
|
||||||
|
|
||||||
const outlinePass = outline(scene, camera, {
|
const outlinePass = outline(scene, camera, {
|
||||||
selectedObjects: useViewer.getState().outliner.selectedObjects,
|
selectedObjects: useViewer.getState().outliner.selectedObjects,
|
||||||
edgeGlow,
|
edgeGlow,
|
||||||
edgeThickness,
|
edgeThickness,
|
||||||
})
|
});
|
||||||
const { visibleEdge, hiddenEdge } = outlinePass
|
const { visibleEdge, hiddenEdge } = outlinePass;
|
||||||
|
|
||||||
const outlineColor = visibleEdge
|
const outlineColor = visibleEdge
|
||||||
.mul(visibleEdgeColor)
|
.mul(visibleEdgeColor)
|
||||||
.add(hiddenEdge.mul(hiddenEdgeColor))
|
.add(hiddenEdge.mul(hiddenEdgeColor))
|
||||||
.mul(edgeStrength)
|
.mul(edgeStrength);
|
||||||
|
|
||||||
return outlineColor
|
return outlineColor;
|
||||||
}
|
}
|
||||||
function generateHoverOutlinePass() {
|
function generateHoverOutlinePass() {
|
||||||
const edgeStrength = uniform(5)
|
const edgeStrength = uniform(5);
|
||||||
const edgeGlow = uniform(0.5)
|
const edgeGlow = uniform(0.5);
|
||||||
const edgeThickness = uniform(1.5)
|
const edgeThickness = uniform(1.5);
|
||||||
const pulsePeriod = uniform(3)
|
const pulsePeriod = uniform(3);
|
||||||
const visibleEdgeColor = uniform(new Color(0x00aaff))
|
const visibleEdgeColor = uniform(new Color(0x00aaff));
|
||||||
const hiddenEdgeColor = uniform(new Color(0xf3ff47))
|
const hiddenEdgeColor = uniform(new Color(0xf3ff47));
|
||||||
|
|
||||||
const outlinePass = outline(scene, camera, {
|
const outlinePass = outline(scene, camera, {
|
||||||
selectedObjects: useViewer.getState().outliner.hoveredObjects,
|
selectedObjects: useViewer.getState().outliner.hoveredObjects,
|
||||||
edgeGlow,
|
edgeGlow,
|
||||||
edgeThickness,
|
edgeThickness,
|
||||||
})
|
});
|
||||||
const { visibleEdge, hiddenEdge } = outlinePass
|
const { visibleEdge, hiddenEdge } = outlinePass;
|
||||||
|
|
||||||
const period = time.div(pulsePeriod).mul(2)
|
const period = time.div(pulsePeriod).mul(2);
|
||||||
const osc = oscSine(period).mul(0.5).add(0.5) // osc [ 0.5, 1.0 ]
|
const osc = oscSine(period).mul(0.5).add(0.5); // osc [ 0.5, 1.0 ]
|
||||||
|
|
||||||
const outlineColor = visibleEdge
|
const outlineColor = visibleEdge
|
||||||
.mul(visibleEdgeColor)
|
.mul(visibleEdgeColor)
|
||||||
.add(hiddenEdge.mul(hiddenEdgeColor))
|
.add(hiddenEdge.mul(hiddenEdgeColor))
|
||||||
.mul(edgeStrength)
|
.mul(edgeStrength);
|
||||||
const outlinePulse = pulsePeriod.greaterThan(0).select(outlineColor.mul(osc), outlineColor)
|
const outlinePulse = pulsePeriod
|
||||||
return outlinePulse
|
.greaterThan(0)
|
||||||
|
.select(outlineColor.mul(osc), outlineColor);
|
||||||
|
return outlinePulse;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup post-processing
|
// Setup post-processing
|
||||||
const postProcessing = new PostProcessing(renderer as unknown as WebGPURenderer)
|
const postProcessing = new PostProcessing(
|
||||||
|
renderer as unknown as WebGPURenderer,
|
||||||
|
);
|
||||||
|
|
||||||
const selectedOutlinePass = generateSelectedOutlinePass()
|
const selectedOutlinePass = generateSelectedOutlinePass();
|
||||||
const hoverOutlinePass = generateHoverOutlinePass()
|
const hoverOutlinePass = generateHoverOutlinePass();
|
||||||
|
|
||||||
postProcessing.outputNode = selectedOutlinePass.add(hoverOutlinePass).add(scenePass)
|
postProcessing.outputNode = selectedOutlinePass
|
||||||
postProcessingRef.current = postProcessing
|
.add(hoverOutlinePass)
|
||||||
|
.add(scenePass);
|
||||||
|
postProcessingRef.current = postProcessing;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (postProcessingRef.current) {
|
if (postProcessingRef.current) {
|
||||||
postProcessingRef.current.dispose()
|
postProcessingRef.current.dispose();
|
||||||
}
|
}
|
||||||
postProcessingRef.current = null
|
postProcessingRef.current = null;
|
||||||
}
|
};
|
||||||
}, [renderer, scene, camera])
|
}, [renderer, scene, camera]);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (postProcessingRef.current) {
|
if (postProcessingRef.current) {
|
||||||
postProcessingRef.current.render()
|
postProcessingRef.current.render();
|
||||||
}
|
}
|
||||||
}, 1)
|
}, 1);
|
||||||
|
|
||||||
return null
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default PostProcessingPasses
|
export default PostProcessingPasses;
|
||||||
|
|||||||
@@ -1,33 +1,34 @@
|
|||||||
import { type EventSuffix, emitter, type GridEvent } from '@pascal-app/core'
|
import { type EventSuffix, emitter, type GridEvent } from "@pascal-app/core";
|
||||||
import type { ThreeEvent } from '@react-three/fiber'
|
import type { ThreeEvent } from "@react-three/fiber";
|
||||||
|
|
||||||
export function useGridEvents() {
|
export function useGridEvents() {
|
||||||
const emit = (suffix: EventSuffix, e: ThreeEvent<PointerEvent>) => {
|
const emit = (suffix: EventSuffix, e: ThreeEvent<PointerEvent>) => {
|
||||||
const eventKey = `grid:${suffix}` as `grid:${EventSuffix}`
|
const eventKey = `grid:${suffix}` as `grid:${EventSuffix}`;
|
||||||
const payload: GridEvent = {
|
const payload: GridEvent = {
|
||||||
position: [e.point.x, e.point.y, e.point.z],
|
position: [e.point.x, e.point.y, e.point.z],
|
||||||
}
|
nativeEvent: e,
|
||||||
|
};
|
||||||
|
|
||||||
emitter.emit(eventKey, payload)
|
emitter.emit(eventKey, payload);
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onPointerDown: (e: ThreeEvent<PointerEvent>) => {
|
onPointerDown: (e: ThreeEvent<PointerEvent>) => {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return;
|
||||||
emit('pointerdown', e)
|
emit("pointerdown", e);
|
||||||
},
|
},
|
||||||
onPointerUp: (e: ThreeEvent<PointerEvent>) => {
|
onPointerUp: (e: ThreeEvent<PointerEvent>) => {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return;
|
||||||
emit('pointerup', e)
|
emit("pointerup", e);
|
||||||
},
|
},
|
||||||
onClick: (e: ThreeEvent<PointerEvent>) => {
|
onClick: (e: ThreeEvent<PointerEvent>) => {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return;
|
||||||
emit('click', e)
|
emit("click", e);
|
||||||
},
|
},
|
||||||
onPointerEnter: (e: ThreeEvent<PointerEvent>) => emit('enter', e),
|
onPointerEnter: (e: ThreeEvent<PointerEvent>) => emit("enter", e),
|
||||||
onPointerLeave: (e: ThreeEvent<PointerEvent>) => emit('leave', e),
|
onPointerLeave: (e: ThreeEvent<PointerEvent>) => emit("leave", e),
|
||||||
onPointerMove: (e: ThreeEvent<PointerEvent>) => emit('move', e),
|
onPointerMove: (e: ThreeEvent<PointerEvent>) => emit("move", e),
|
||||||
onDoubleClick: (e: ThreeEvent<PointerEvent>) => emit('double-click', e),
|
onDoubleClick: (e: ThreeEvent<PointerEvent>) => emit("double-click", e),
|
||||||
onContextMenu: (e: ThreeEvent<PointerEvent>) => emit('context-menu', e),
|
onContextMenu: (e: ThreeEvent<PointerEvent>) => emit("context-menu", e),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,50 +6,56 @@ import {
|
|||||||
type ItemNode,
|
type ItemNode,
|
||||||
type WallEvent,
|
type WallEvent,
|
||||||
type WallNode,
|
type WallNode,
|
||||||
} from '@pascal-app/core'
|
} from "@pascal-app/core";
|
||||||
import type { ThreeEvent } from '@react-three/fiber'
|
import type { ThreeEvent } from "@react-three/fiber";
|
||||||
import type { BuildingEvent } from '../../../core/src/events/bus'
|
import type { BuildingEvent } from "../../../core/src/events/bus";
|
||||||
|
|
||||||
type NodeConfig = {
|
type NodeConfig = {
|
||||||
item: { node: ItemNode; event: ItemEvent }
|
item: { node: ItemNode; event: ItemEvent };
|
||||||
wall: { node: WallNode; event: WallEvent }
|
wall: { node: WallNode; event: WallEvent };
|
||||||
building: { node: BuildingNode; event: BuildingEvent }
|
building: { node: BuildingNode; event: BuildingEvent };
|
||||||
}
|
};
|
||||||
|
|
||||||
type NodeType = keyof 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 emit = (suffix: EventSuffix, e: ThreeEvent<PointerEvent>) => {
|
||||||
const eventKey = `${type}:${suffix}` as `${T}:${EventSuffix}`
|
const eventKey = `${type}:${suffix}` as `${T}:${EventSuffix}`;
|
||||||
const localPoint = e.object.worldToLocal(e.point.clone())
|
const localPoint = e.object.worldToLocal(e.point.clone());
|
||||||
const payload = {
|
const payload = {
|
||||||
node,
|
node,
|
||||||
position: [e.point.x, e.point.y, e.point.z],
|
position: [e.point.x, e.point.y, e.point.z],
|
||||||
localPosition: [localPoint.x, localPoint.y, localPoint.z],
|
localPosition: [localPoint.x, localPoint.y, localPoint.z],
|
||||||
normal: e.face ? [e.face.normal.x, e.face.normal.y, e.face.normal.z] : undefined,
|
normal: e.face
|
||||||
|
? [e.face.normal.x, e.face.normal.y, e.face.normal.z]
|
||||||
|
: undefined,
|
||||||
stopPropagation: () => e.stopPropagation(),
|
stopPropagation: () => e.stopPropagation(),
|
||||||
} as NodeConfig[T]['event']
|
nativeEvent: e,
|
||||||
|
} as NodeConfig[T]["event"];
|
||||||
|
|
||||||
emitter.emit(eventKey, payload)
|
emitter.emit(eventKey, payload);
|
||||||
}
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onPointerDown: (e: ThreeEvent<PointerEvent>) => {
|
onPointerDown: (e: ThreeEvent<PointerEvent>) => {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return;
|
||||||
emit('pointerdown', e)
|
emit("pointerdown", e);
|
||||||
},
|
},
|
||||||
onPointerUp: (e: ThreeEvent<PointerEvent>) => {
|
onPointerUp: (e: ThreeEvent<PointerEvent>) => {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return;
|
||||||
emit('pointerup', e)
|
emit("pointerup", e);
|
||||||
},
|
},
|
||||||
onClick: (e: ThreeEvent<PointerEvent>) => {
|
onClick: (e: ThreeEvent<PointerEvent>) => {
|
||||||
if (e.button !== 0) return
|
if (e.button !== 0) return;
|
||||||
emit('click', e)
|
emit("click", e);
|
||||||
},
|
},
|
||||||
onPointerEnter: (e: ThreeEvent<PointerEvent>) => emit('enter', e),
|
onPointerEnter: (e: ThreeEvent<PointerEvent>) => emit("enter", e),
|
||||||
onPointerLeave: (e: ThreeEvent<PointerEvent>) => emit('leave', e),
|
onPointerLeave: (e: ThreeEvent<PointerEvent>) => emit("leave", e),
|
||||||
onPointerMove: (e: ThreeEvent<PointerEvent>) => emit('move', e),
|
onPointerMove: (e: ThreeEvent<PointerEvent>) => emit("move", e),
|
||||||
onDoubleClick: (e: ThreeEvent<PointerEvent>) => emit('double-click', e),
|
onDoubleClick: (e: ThreeEvent<PointerEvent>) => emit("double-click", e),
|
||||||
onContextMenu: (e: ThreeEvent<PointerEvent>) => emit('context-menu', e),
|
onContextMenu: (e: ThreeEvent<PointerEvent>) => emit("context-menu", e),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user