@kiberon-labs/behave-graph-scene 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +18 -0
- package/.storybook/preview.ts +16 -0
- package/.storybook/vscode.css +822 -0
- package/.turbo/turbo-build.log +8 -0
- package/CHANGELOG.md +84 -0
- package/LICENSE +6 -0
- package/README.md +13 -0
- package/dist/Abstractions/Drivers/DummyScene.d.ts +64 -0
- package/dist/Abstractions/Drivers/DummyScene.d.ts.map +1 -0
- package/dist/Abstractions/Drivers/DummyScene.js +115 -0
- package/dist/Abstractions/Drivers/DummyScene.js.map +1 -0
- package/dist/Abstractions/IScene.d.ts +75 -0
- package/dist/Abstractions/IScene.d.ts.map +1 -0
- package/dist/Abstractions/IScene.js +19 -0
- package/dist/Abstractions/IScene.js.map +1 -0
- package/dist/GLTFJson.d.ts +33 -0
- package/dist/GLTFJson.d.ts.map +1 -0
- package/dist/Nodes/Actions/AddLight.d.ts +21 -0
- package/dist/Nodes/Actions/AddLight.d.ts.map +1 -0
- package/dist/Nodes/Actions/AddLight.js +38 -0
- package/dist/Nodes/Actions/AddLight.js.map +1 -0
- package/dist/Nodes/Actions/CloneMesh.d.ts +16 -0
- package/dist/Nodes/Actions/CloneMesh.d.ts.map +1 -0
- package/dist/Nodes/Actions/CloneMesh.js +28 -0
- package/dist/Nodes/Actions/CloneMesh.js.map +1 -0
- package/dist/Nodes/Actions/CreateMesh.d.ts +22 -0
- package/dist/Nodes/Actions/CreateMesh.d.ts.map +1 -0
- package/dist/Nodes/Actions/CreateMesh.js +44 -0
- package/dist/Nodes/Actions/CreateMesh.js.map +1 -0
- package/dist/Nodes/Actions/DeleteMesh.d.ts +15 -0
- package/dist/Nodes/Actions/DeleteMesh.d.ts.map +1 -0
- package/dist/Nodes/Actions/DeleteMesh.js +27 -0
- package/dist/Nodes/Actions/DeleteMesh.js.map +1 -0
- package/dist/Nodes/Actions/EaseSceneProperty.d.ts +23 -0
- package/dist/Nodes/Actions/EaseSceneProperty.d.ts.map +1 -0
- package/dist/Nodes/Actions/EaseSceneProperty.js +73 -0
- package/dist/Nodes/Actions/EaseSceneProperty.js.map +1 -0
- package/dist/Nodes/Actions/LookAt.d.ts +16 -0
- package/dist/Nodes/Actions/LookAt.d.ts.map +1 -0
- package/dist/Nodes/Actions/LookAt.js +35 -0
- package/dist/Nodes/Actions/LookAt.js.map +1 -0
- package/dist/Nodes/Actions/MoveTowards.d.ts +21 -0
- package/dist/Nodes/Actions/MoveTowards.d.ts.map +1 -0
- package/dist/Nodes/Actions/MoveTowards.js +46 -0
- package/dist/Nodes/Actions/MoveTowards.js.map +1 -0
- package/dist/Nodes/Actions/RemoveLight.d.ts +15 -0
- package/dist/Nodes/Actions/RemoveLight.d.ts.map +1 -0
- package/dist/Nodes/Actions/RemoveLight.js +27 -0
- package/dist/Nodes/Actions/RemoveLight.js.map +1 -0
- package/dist/Nodes/Actions/SetLightProperty.d.ts +25 -0
- package/dist/Nodes/Actions/SetLightProperty.d.ts.map +1 -0
- package/dist/Nodes/Actions/SetLightProperty.js +64 -0
- package/dist/Nodes/Actions/SetLightProperty.js.map +1 -0
- package/dist/Nodes/Actions/SetMaterialProperty.d.ts +25 -0
- package/dist/Nodes/Actions/SetMaterialProperty.d.ts.map +1 -0
- package/dist/Nodes/Actions/SetMaterialProperty.js +69 -0
- package/dist/Nodes/Actions/SetMaterialProperty.js.map +1 -0
- package/dist/Nodes/Actions/SetMeshPosition.d.ts +18 -0
- package/dist/Nodes/Actions/SetMeshPosition.d.ts.map +1 -0
- package/dist/Nodes/Actions/SetMeshPosition.js +34 -0
- package/dist/Nodes/Actions/SetMeshPosition.js.map +1 -0
- package/dist/Nodes/Actions/SetMeshVisible.d.ts +16 -0
- package/dist/Nodes/Actions/SetMeshVisible.d.ts.map +1 -0
- package/dist/Nodes/Actions/SetMeshVisible.js +28 -0
- package/dist/Nodes/Actions/SetMeshVisible.js.map +1 -0
- package/dist/Nodes/Actions/SetSceneProperty.d.ts +16 -0
- package/dist/Nodes/Actions/SetSceneProperty.d.ts.map +1 -0
- package/dist/Nodes/Actions/SetSceneProperty.js +28 -0
- package/dist/Nodes/Actions/SetSceneProperty.js.map +1 -0
- package/dist/Nodes/Events/OnAnyMeshClicked.d.ts +13 -0
- package/dist/Nodes/Events/OnAnyMeshClicked.d.ts.map +1 -0
- package/dist/Nodes/Events/OnAnyMeshClicked.js +34 -0
- package/dist/Nodes/Events/OnAnyMeshClicked.js.map +1 -0
- package/dist/Nodes/Events/OnSceneChanged.d.ts +12 -0
- package/dist/Nodes/Events/OnSceneChanged.d.ts.map +1 -0
- package/dist/Nodes/Events/OnSceneChanged.js +28 -0
- package/dist/Nodes/Events/OnSceneChanged.js.map +1 -0
- package/dist/Nodes/Events/OnSceneNodeClick.d.ts +18 -0
- package/dist/Nodes/Events/OnSceneNodeClick.d.ts.map +1 -0
- package/dist/Nodes/Events/OnSceneNodeClick.js +39 -0
- package/dist/Nodes/Events/OnSceneNodeClick.js.map +1 -0
- package/dist/Nodes/Logic/ColorNodes.d.ts +23 -0
- package/dist/Nodes/Logic/ColorNodes.d.ts.map +1 -0
- package/dist/Nodes/Logic/ColorNodes.js +137 -0
- package/dist/Nodes/Logic/ColorNodes.js.map +1 -0
- package/dist/Nodes/Logic/EulerNodes.d.ts +22 -0
- package/dist/Nodes/Logic/EulerNodes.d.ts.map +1 -0
- package/dist/Nodes/Logic/EulerNodes.js +132 -0
- package/dist/Nodes/Logic/EulerNodes.js.map +1 -0
- package/dist/Nodes/Logic/Mat3Nodes.d.ts +31 -0
- package/dist/Nodes/Logic/Mat3Nodes.d.ts.map +1 -0
- package/dist/Nodes/Logic/Mat3Nodes.js +199 -0
- package/dist/Nodes/Logic/Mat3Nodes.js.map +1 -0
- package/dist/Nodes/Logic/Mat4Nodes.d.ts +38 -0
- package/dist/Nodes/Logic/Mat4Nodes.d.ts.map +1 -0
- package/dist/Nodes/Logic/Mat4Nodes.js +267 -0
- package/dist/Nodes/Logic/Mat4Nodes.js.map +1 -0
- package/dist/Nodes/Logic/QuatNodes.d.ts +28 -0
- package/dist/Nodes/Logic/QuatNodes.d.ts.map +1 -0
- package/dist/Nodes/Logic/QuatNodes.js +173 -0
- package/dist/Nodes/Logic/QuatNodes.js.map +1 -0
- package/dist/Nodes/Logic/Vec2Nodes.d.ts +22 -0
- package/dist/Nodes/Logic/Vec2Nodes.d.ts.map +1 -0
- package/dist/Nodes/Logic/Vec2Nodes.js +115 -0
- package/dist/Nodes/Logic/Vec2Nodes.js.map +1 -0
- package/dist/Nodes/Logic/Vec3Nodes.d.ts +23 -0
- package/dist/Nodes/Logic/Vec3Nodes.d.ts.map +1 -0
- package/dist/Nodes/Logic/Vec3Nodes.js +137 -0
- package/dist/Nodes/Logic/Vec3Nodes.js.map +1 -0
- package/dist/Nodes/Logic/Vec4Nodes.d.ts +22 -0
- package/dist/Nodes/Logic/Vec4Nodes.d.ts.map +1 -0
- package/dist/Nodes/Logic/Vec4Nodes.js +132 -0
- package/dist/Nodes/Logic/Vec4Nodes.js.map +1 -0
- package/dist/Nodes/Logic/VecElements.d.ts +9 -0
- package/dist/Nodes/Logic/VecElements.d.ts.map +1 -0
- package/dist/Nodes/Logic/VecElements.js +22 -0
- package/dist/Nodes/Logic/VecElements.js.map +1 -0
- package/dist/Nodes/Queries/GetDistanceBetween.d.ts +18 -0
- package/dist/Nodes/Queries/GetDistanceBetween.d.ts.map +1 -0
- package/dist/Nodes/Queries/GetDistanceBetween.js +30 -0
- package/dist/Nodes/Queries/GetDistanceBetween.js.map +1 -0
- package/dist/Nodes/Queries/GetLightProperty.d.ts +23 -0
- package/dist/Nodes/Queries/GetLightProperty.d.ts.map +1 -0
- package/dist/Nodes/Queries/GetLightProperty.js +68 -0
- package/dist/Nodes/Queries/GetLightProperty.js.map +1 -0
- package/dist/Nodes/Queries/GetMaterialProperty.d.ts +23 -0
- package/dist/Nodes/Queries/GetMaterialProperty.d.ts.map +1 -0
- package/dist/Nodes/Queries/GetMaterialProperty.js +69 -0
- package/dist/Nodes/Queries/GetMaterialProperty.js.map +1 -0
- package/dist/Nodes/Queries/GetMeshPosition.d.ts +16 -0
- package/dist/Nodes/Queries/GetMeshPosition.d.ts.map +1 -0
- package/dist/Nodes/Queries/GetMeshPosition.js +29 -0
- package/dist/Nodes/Queries/GetMeshPosition.js.map +1 -0
- package/dist/Nodes/Queries/GetSceneProperty.d.ts +14 -0
- package/dist/Nodes/Queries/GetSceneProperty.d.ts.map +1 -0
- package/dist/Nodes/Queries/GetSceneProperty.js +23 -0
- package/dist/Nodes/Queries/GetSceneProperty.js.map +1 -0
- package/dist/Values/ColorValue.d.ts +7 -0
- package/dist/Values/ColorValue.d.ts.map +1 -0
- package/dist/Values/ColorValue.js +20 -0
- package/dist/Values/ColorValue.js.map +1 -0
- package/dist/Values/EulerValue.d.ts +7 -0
- package/dist/Values/EulerValue.d.ts.map +1 -0
- package/dist/Values/EulerValue.js +20 -0
- package/dist/Values/EulerValue.js.map +1 -0
- package/dist/Values/Internal/Mat3.d.ts +43 -0
- package/dist/Values/Internal/Mat3.d.ts.map +1 -0
- package/dist/Values/Internal/Mat3.js +276 -0
- package/dist/Values/Internal/Mat3.js.map +1 -0
- package/dist/Values/Internal/Mat4.d.ts +53 -0
- package/dist/Values/Internal/Mat4.d.ts.map +1 -0
- package/dist/Values/Internal/Mat4.js +443 -0
- package/dist/Values/Internal/Mat4.js.map +1 -0
- package/dist/Values/Internal/Vec2.d.ts +25 -0
- package/dist/Values/Internal/Vec2.d.ts.map +1 -0
- package/dist/Values/Internal/Vec2.js +64 -0
- package/dist/Values/Internal/Vec2.js.map +1 -0
- package/dist/Values/Internal/Vec3.d.ts +38 -0
- package/dist/Values/Internal/Vec3.d.ts.map +1 -0
- package/dist/Values/Internal/Vec3.js +157 -0
- package/dist/Values/Internal/Vec3.js.map +1 -0
- package/dist/Values/Internal/Vec4.d.ts +49 -0
- package/dist/Values/Internal/Vec4.d.ts.map +1 -0
- package/dist/Values/Internal/Vec4.js +190 -0
- package/dist/Values/Internal/Vec4.js.map +1 -0
- package/dist/Values/Mat3Value.d.ts +7 -0
- package/dist/Values/Mat3Value.d.ts.map +1 -0
- package/dist/Values/Mat3Value.js +16 -0
- package/dist/Values/Mat3Value.js.map +1 -0
- package/dist/Values/Mat4Value.d.ts +7 -0
- package/dist/Values/Mat4Value.d.ts.map +1 -0
- package/dist/Values/Mat4Value.js +16 -0
- package/dist/Values/Mat4Value.js.map +1 -0
- package/dist/Values/QuatValue.d.ts +7 -0
- package/dist/Values/QuatValue.d.ts.map +1 -0
- package/dist/Values/QuatValue.js +21 -0
- package/dist/Values/QuatValue.js.map +1 -0
- package/dist/Values/Vec2Value.d.ts +7 -0
- package/dist/Values/Vec2Value.d.ts.map +1 -0
- package/dist/Values/Vec2Value.js +16 -0
- package/dist/Values/Vec2Value.js.map +1 -0
- package/dist/Values/Vec3Value.d.ts +7 -0
- package/dist/Values/Vec3Value.d.ts.map +1 -0
- package/dist/Values/Vec3Value.js +20 -0
- package/dist/Values/Vec3Value.js.map +1 -0
- package/dist/Values/Vec4Value.d.ts +7 -0
- package/dist/Values/Vec4Value.d.ts.map +1 -0
- package/dist/Values/Vec4Value.js +21 -0
- package/dist/Values/Vec4Value.js.map +1 -0
- package/dist/_virtual/rolldown_runtime.js +35 -0
- package/dist/behave-graph.manifest.json +6082 -0
- package/dist/buildScene.d.ts +74 -0
- package/dist/buildScene.d.ts.map +1 -0
- package/dist/buildScene.js +304 -0
- package/dist/buildScene.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/manifest.source.d.ts +7 -0
- package/dist/manifest.source.d.ts.map +1 -0
- package/dist/manifest.source.js +54 -0
- package/dist/manifest.source.js.map +1 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/cjs/react-jsx-runtime.development.js +207 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/cjs/react-jsx-runtime.production.js +40 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/cjs/react-jsx-runtime.production.js.map +1 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/cjs/react.development.js +766 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/cjs/react.development.js.map +1 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/cjs/react.production.js +367 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/cjs/react.production.js.map +1 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/index.js +15 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/index.js.map +1 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/jsx-runtime.js +15 -0
- package/dist/node_modules/.pnpm/react@19.2.3/node_modules/react/jsx-runtime.js.map +1 -0
- package/dist/packages/nodes/scene/package.js +7 -0
- package/dist/packages/nodes/scene/package.js.map +1 -0
- package/dist/registerSceneProfile.d.ts +10 -0
- package/dist/registerSceneProfile.d.ts.map +1 -0
- package/dist/registerSceneProfile.js +112 -0
- package/dist/registerSceneProfile.js.map +1 -0
- package/dist/ui/controls/vec3.d.ts +9 -0
- package/dist/ui/controls/vec3.d.ts.map +1 -0
- package/dist/ui/controls/vec3.js +99 -0
- package/dist/ui/controls/vec3.js.map +1 -0
- package/package.json +42 -13
- package/src/Abstractions/Drivers/DummyScene.ts +110 -2
- package/src/Abstractions/IScene.ts +74 -3
- package/src/Nodes/Actions/AddLight.ts +46 -0
- package/src/Nodes/Actions/CloneMesh.ts +31 -0
- package/src/Nodes/Actions/CreateMesh.ts +47 -0
- package/src/Nodes/Actions/DeleteMesh.ts +29 -0
- package/src/Nodes/Actions/EaseSceneProperty.ts +6 -2
- package/src/Nodes/Actions/LookAt.ts +34 -0
- package/src/Nodes/Actions/MoveTowards.ts +55 -0
- package/src/Nodes/Actions/RemoveLight.ts +29 -0
- package/src/Nodes/Actions/SetLightProperty.ts +60 -0
- package/src/Nodes/Actions/SetMaterialProperty.ts +62 -0
- package/src/Nodes/Actions/SetMeshPosition.ts +37 -0
- package/src/Nodes/Actions/SetMeshVisible.ts +31 -0
- package/src/Nodes/Actions/SetSceneProperty.ts +3 -5
- package/src/Nodes/Events/OnAnyMeshClicked.ts +48 -0
- package/src/Nodes/Events/OnSceneChanged.ts +43 -0
- package/src/Nodes/Events/OnSceneNodeClick.ts +3 -3
- package/src/Nodes/Logic/Mat3Nodes.ts +0 -10
- package/src/Nodes/Logic/QuatNodes.ts +11 -11
- package/src/Nodes/Queries/GetDistanceBetween.ts +37 -0
- package/src/Nodes/Queries/GetLightProperty.ts +53 -0
- package/src/Nodes/Queries/GetMaterialProperty.ts +55 -0
- package/src/Nodes/Queries/GetMeshPosition.ts +32 -0
- package/src/Nodes/Queries/GetSceneProperty.ts +4 -5
- package/src/Values/Internal/Mat3.ts +3 -3
- package/src/Values/Internal/Mat4.ts +5 -4
- package/src/Values/Internal/Vec3.ts +5 -4
- package/src/Values/Internal/Vec4.ts +3 -2
- package/src/buildScene.ts +36 -2
- package/src/index.ts +26 -2
- package/src/manifest.source.ts +61 -0
- package/src/registerSceneProfile.ts +41 -4
- package/src/ui/controls/vec3.tsx +69 -0
- package/stories/click.stories.tsx +112 -0
- package/stories/components/DemoScene.ts +610 -0
- package/stories/components/SceneViewer.tsx +204 -0
- package/stories/components/SceneViewerPanel.tsx +41 -0
- package/stories/data/clickDemo.json +94 -0
- package/stories/data/rotate.json +402 -0
- package/stories/index.stories.tsx +90 -0
- package/stories/plugin/sceneViewerPlugin.tsx +88 -0
- package/tests/graphs/logic/Color.json +53 -53
- package/tests/graphs/logic/Euler.json +53 -53
- package/tests/graphs/logic/Quaternion.json +56 -56
- package/tests/graphs/logic/Vector2.json +50 -50
- package/tests/graphs/logic/Vector3.json +53 -53
- package/tests/graphs/logic/Vector4.json +56 -56
- package/tests/manifest.test.ts +65 -0
- package/tests/readSceneGraphs.test.ts +8 -1
- package/tests/registerSceneProfile.test.ts +6 -5
- package/tests/tsconfig.json +10 -10
- package/tsconfig.json +61 -54
- package/tsdown.config.ts +5 -1
- package/vite.config.js +7 -0
- package/src/Values/Internal/Mat2.ts +0 -214
- package/src/loadScene.ts +0 -81
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import {
|
|
3
|
+
kitchenSinkPlugin,
|
|
4
|
+
GraphProvider,
|
|
5
|
+
LayoutController,
|
|
6
|
+
localGraphRunnerPlugin,
|
|
7
|
+
System,
|
|
8
|
+
SystemProvider,
|
|
9
|
+
type UIGraphJSON
|
|
10
|
+
} from '@kiberon-labs/behave-graph-flow';
|
|
11
|
+
import {
|
|
12
|
+
DefaultLogger,
|
|
13
|
+
ManualLifecycleEventEmitter,
|
|
14
|
+
registerCoreProfile
|
|
15
|
+
} from '@kiberon-labs/behave-graph';
|
|
16
|
+
import { registerSceneProfile } from '@/registerSceneProfile';
|
|
17
|
+
import { DemoScene } from './components/DemoScene';
|
|
18
|
+
import { sceneViewerPlugin } from './plugin/sceneViewerPlugin';
|
|
19
|
+
import { Vec3Control } from '@/ui/controls/vec3';
|
|
20
|
+
import clickDemoGraph from './data/clickDemo.json';
|
|
21
|
+
|
|
22
|
+
const meta: Meta<typeof LayoutController> = {
|
|
23
|
+
component: LayoutController,
|
|
24
|
+
title: 'Apex/default',
|
|
25
|
+
decorators: [(Story) => <Story />],
|
|
26
|
+
parameters: {
|
|
27
|
+
layout: 'fullscreen'
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default meta;
|
|
32
|
+
|
|
33
|
+
type Story = StoryObj<typeof meta>;
|
|
34
|
+
|
|
35
|
+
// Create a demo scene instance
|
|
36
|
+
const demoScene = new DemoScene();
|
|
37
|
+
|
|
38
|
+
// Create the old-style registry for node definitions
|
|
39
|
+
const coreRegistry = registerSceneProfile(
|
|
40
|
+
registerCoreProfile({
|
|
41
|
+
nodes: {},
|
|
42
|
+
values: {},
|
|
43
|
+
dependencies: {
|
|
44
|
+
IScene: demoScene,
|
|
45
|
+
ILifecycleEventEmitter: new ManualLifecycleEventEmitter(),
|
|
46
|
+
ILogger: new DefaultLogger()
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// --- Click Demo story: preloads a graph with OnAnyMeshClicked → DebugLog ---
|
|
52
|
+
|
|
53
|
+
const clickDemoScene = new DemoScene();
|
|
54
|
+
|
|
55
|
+
const clickRegistry = registerSceneProfile(
|
|
56
|
+
registerCoreProfile({
|
|
57
|
+
nodes: {},
|
|
58
|
+
values: {},
|
|
59
|
+
dependencies: {
|
|
60
|
+
IScene: clickDemoScene,
|
|
61
|
+
ILifecycleEventEmitter: new ManualLifecycleEventEmitter(),
|
|
62
|
+
ILogger: new DefaultLogger()
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const clickNodeRegistry = {
|
|
68
|
+
values: clickRegistry.values,
|
|
69
|
+
specs: []
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const clickSys = new System(clickNodeRegistry);
|
|
73
|
+
const clickSession = clickSys.createSession('graph');
|
|
74
|
+
|
|
75
|
+
clickSys.controlStore.getState().registerControl('color', Vec3Control);
|
|
76
|
+
clickSys.controlStore.getState().registerControl('vec3', Vec3Control);
|
|
77
|
+
clickSys.controlStore.getState().registerControl('euler', Vec3Control);
|
|
78
|
+
|
|
79
|
+
clickSys.registerPlugin(kitchenSinkPlugin);
|
|
80
|
+
clickSys.registerPlugin(localGraphRunnerPlugin, {
|
|
81
|
+
registry: clickRegistry,
|
|
82
|
+
tickStrategy: async () => {
|
|
83
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
clickSys.registerPlugin(sceneViewerPlugin, {
|
|
87
|
+
scene: clickDemoScene,
|
|
88
|
+
addMenuItem: true
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Preload the click demo graph so the OnAnyMeshClicked event node is present.
|
|
92
|
+
// clickDemo.json is a full UIGraphJSON (UI state + inner flow), so deserialize
|
|
93
|
+
// the UI state and load the inner behave graph , the same path the menubar's
|
|
94
|
+
// "Load Graph" uses , rather than passing it straight to setGraph.
|
|
95
|
+
const clickDemoUiGraph = clickDemoGraph as unknown as UIGraphJSON;
|
|
96
|
+
clickSession.graph.deseralize(clickDemoUiGraph);
|
|
97
|
+
clickSession.flowStore
|
|
98
|
+
.getState()
|
|
99
|
+
.setGraph(clickDemoUiGraph.flow, { skipLayout: true });
|
|
100
|
+
|
|
101
|
+
export const ClickDemo: Story = {
|
|
102
|
+
render: () => {
|
|
103
|
+
return (
|
|
104
|
+
<SystemProvider value={clickSys}>
|
|
105
|
+
<GraphProvider value={clickSession}>
|
|
106
|
+
<LayoutController />
|
|
107
|
+
</GraphProvider>
|
|
108
|
+
</SystemProvider>
|
|
109
|
+
);
|
|
110
|
+
},
|
|
111
|
+
args: {}
|
|
112
|
+
};
|
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Scene,
|
|
3
|
+
Mesh,
|
|
4
|
+
BoxGeometry,
|
|
5
|
+
MeshPhongMaterial,
|
|
6
|
+
TorusKnotGeometry,
|
|
7
|
+
SphereGeometry,
|
|
8
|
+
CylinderGeometry,
|
|
9
|
+
PlaneGeometry,
|
|
10
|
+
ConeGeometry,
|
|
11
|
+
TorusGeometry,
|
|
12
|
+
PointLight,
|
|
13
|
+
DirectionalLight,
|
|
14
|
+
SpotLight,
|
|
15
|
+
AmbientLight,
|
|
16
|
+
type Light,
|
|
17
|
+
Vector3,
|
|
18
|
+
Color
|
|
19
|
+
} from 'three';
|
|
20
|
+
import { EventEmitter } from '@kiberon-labs/behave-graph';
|
|
21
|
+
import type { IScene } from '@/Abstractions/IScene';
|
|
22
|
+
import type { ChoiceJSON } from '@kiberon-labs/behave-graph';
|
|
23
|
+
|
|
24
|
+
// Copies whichever of x/y/z are present on the incoming value onto a target
|
|
25
|
+
// Vector3/Euler-like object, leaving unspecified axes untouched.
|
|
26
|
+
function assignVectorComponents(
|
|
27
|
+
target: { x: number; y: number; z: number },
|
|
28
|
+
value: any
|
|
29
|
+
): void {
|
|
30
|
+
for (const axis of ['x', 'y', 'z'] as const) {
|
|
31
|
+
if (value[axis] !== undefined) target[axis] = value[axis];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class DemoScene implements IScene {
|
|
36
|
+
public readonly scene: Scene;
|
|
37
|
+
public readonly onSceneChanged = new EventEmitter<void>();
|
|
38
|
+
|
|
39
|
+
private objects: Map<string, Mesh>;
|
|
40
|
+
private lights: Map<string, Light>;
|
|
41
|
+
private clickListeners: Map<string, Set<(jsonPath: string) => void>>;
|
|
42
|
+
private anyMeshClickListeners: Set<(meshName: string) => void>;
|
|
43
|
+
private sceneChangedListeners: Set<() => void>;
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
this.scene = new Scene();
|
|
47
|
+
this.objects = new Map();
|
|
48
|
+
this.lights = new Map();
|
|
49
|
+
this.clickListeners = new Map();
|
|
50
|
+
this.anyMeshClickListeners = new Set();
|
|
51
|
+
this.sceneChangedListeners = new Set();
|
|
52
|
+
|
|
53
|
+
this.initializeScene();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private initializeScene() {
|
|
57
|
+
// Create cube
|
|
58
|
+
const cubeGeometry = new BoxGeometry(2, 2, 2);
|
|
59
|
+
const cubeMaterial = new MeshPhongMaterial({
|
|
60
|
+
color: 0x2194ce,
|
|
61
|
+
shininess: 100
|
|
62
|
+
});
|
|
63
|
+
const cube = new Mesh(cubeGeometry, cubeMaterial);
|
|
64
|
+
cube.position.set(0, 1, 0);
|
|
65
|
+
cube.name = 'cube';
|
|
66
|
+
this.scene.add(cube);
|
|
67
|
+
this.objects.set('cube', cube);
|
|
68
|
+
|
|
69
|
+
// Create torus knot
|
|
70
|
+
const torusGeometry = new TorusKnotGeometry(0.8, 0.3, 100, 16);
|
|
71
|
+
const torusMaterial = new MeshPhongMaterial({
|
|
72
|
+
color: 0xce2194,
|
|
73
|
+
shininess: 100
|
|
74
|
+
});
|
|
75
|
+
const torus = new Mesh(torusGeometry, torusMaterial);
|
|
76
|
+
torus.position.set(4, 2, 2);
|
|
77
|
+
torus.name = 'torus';
|
|
78
|
+
this.scene.add(torus);
|
|
79
|
+
this.objects.set('torus', torus);
|
|
80
|
+
|
|
81
|
+
// Create sphere
|
|
82
|
+
const sphereGeometry = new SphereGeometry(1, 32, 32);
|
|
83
|
+
const sphereMaterial = new MeshPhongMaterial({
|
|
84
|
+
color: 0x21ce94,
|
|
85
|
+
shininess: 100
|
|
86
|
+
});
|
|
87
|
+
const sphere = new Mesh(sphereGeometry, sphereMaterial);
|
|
88
|
+
sphere.position.set(-4, 1, -2);
|
|
89
|
+
sphere.name = 'sphere';
|
|
90
|
+
this.scene.add(sphere);
|
|
91
|
+
this.objects.set('sphere', sphere);
|
|
92
|
+
|
|
93
|
+
// Create cylinder
|
|
94
|
+
const cylinderGeometry = new CylinderGeometry(0.7, 0.7, 3, 32);
|
|
95
|
+
const cylinderMaterial = new MeshPhongMaterial({
|
|
96
|
+
color: 0xce9421,
|
|
97
|
+
shininess: 100
|
|
98
|
+
});
|
|
99
|
+
const cylinder = new Mesh(cylinderGeometry, cylinderMaterial);
|
|
100
|
+
cylinder.position.set(-2, 1.5, 3);
|
|
101
|
+
cylinder.name = 'cylinder';
|
|
102
|
+
this.scene.add(cylinder);
|
|
103
|
+
this.objects.set('cylinder', cylinder);
|
|
104
|
+
|
|
105
|
+
// Create a default directional light
|
|
106
|
+
const defaultLight = new DirectionalLight(0xffffff, 1);
|
|
107
|
+
defaultLight.position.set(5, 10, 7);
|
|
108
|
+
defaultLight.name = 'defaultLight';
|
|
109
|
+
this.scene.add(defaultLight);
|
|
110
|
+
this.lights.set('defaultLight', defaultLight);
|
|
111
|
+
|
|
112
|
+
// Create a default ambient light
|
|
113
|
+
const ambientLight = new AmbientLight(0x404040, 0.5);
|
|
114
|
+
ambientLight.name = 'ambientLight';
|
|
115
|
+
this.scene.add(ambientLight);
|
|
116
|
+
this.lights.set('ambientLight', ambientLight);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getProperty(jsonPath: string): any {
|
|
120
|
+
const parts = jsonPath.split('/');
|
|
121
|
+
const objectName = parts[0]!;
|
|
122
|
+
const propertyName = parts[1];
|
|
123
|
+
|
|
124
|
+
const obj = this.objects.get(objectName);
|
|
125
|
+
if (!obj) return undefined;
|
|
126
|
+
|
|
127
|
+
switch (propertyName) {
|
|
128
|
+
case 'position':
|
|
129
|
+
return { x: obj.position.x, y: obj.position.y, z: obj.position.z };
|
|
130
|
+
case 'rotation':
|
|
131
|
+
return { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z };
|
|
132
|
+
case 'scale':
|
|
133
|
+
return { x: obj.scale.x, y: obj.scale.y, z: obj.scale.z };
|
|
134
|
+
case 'visible':
|
|
135
|
+
return obj.visible;
|
|
136
|
+
case 'color':
|
|
137
|
+
if (obj.material instanceof MeshPhongMaterial) {
|
|
138
|
+
const color = obj.material.color;
|
|
139
|
+
return { r: color.r, g: color.g, b: color.b };
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
default:
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setProperty(jsonPath: string, valueTypeName: string, value: any): void {
|
|
148
|
+
const parts = jsonPath.split('/');
|
|
149
|
+
const objectName = parts[0]!;
|
|
150
|
+
const propertyName = parts[1];
|
|
151
|
+
|
|
152
|
+
const obj = this.objects.get(objectName);
|
|
153
|
+
if (!obj) return;
|
|
154
|
+
|
|
155
|
+
switch (propertyName) {
|
|
156
|
+
case 'position':
|
|
157
|
+
assignVectorComponents(obj.position, value);
|
|
158
|
+
break;
|
|
159
|
+
case 'rotation':
|
|
160
|
+
assignVectorComponents(obj.rotation, value);
|
|
161
|
+
break;
|
|
162
|
+
case 'scale':
|
|
163
|
+
assignVectorComponents(obj.scale, value);
|
|
164
|
+
break;
|
|
165
|
+
case 'visible':
|
|
166
|
+
obj.visible = Boolean(value);
|
|
167
|
+
break;
|
|
168
|
+
case 'color':
|
|
169
|
+
if (obj.material instanceof MeshPhongMaterial) {
|
|
170
|
+
obj.material.color.setRGB(value.x || 0, value.y || 0, value.z || 0);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.onSceneChanged.emit();
|
|
176
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
addOnClickedListener(
|
|
180
|
+
jsonPath: string,
|
|
181
|
+
callback: (jsonPath: string) => void
|
|
182
|
+
): void {
|
|
183
|
+
if (!this.clickListeners.has(jsonPath)) {
|
|
184
|
+
this.clickListeners.set(jsonPath, new Set());
|
|
185
|
+
}
|
|
186
|
+
this.clickListeners.get(jsonPath)!.add(callback);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
removeOnClickedListener(
|
|
190
|
+
jsonPath: string,
|
|
191
|
+
callback: (jsonPath: string) => void
|
|
192
|
+
): void {
|
|
193
|
+
const listeners = this.clickListeners.get(jsonPath);
|
|
194
|
+
if (listeners) {
|
|
195
|
+
listeners.delete(callback);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
getRaycastableProperties(): ChoiceJSON {
|
|
200
|
+
const choices: ChoiceJSON = [];
|
|
201
|
+
this.objects.forEach((obj, name) => {
|
|
202
|
+
choices.push({
|
|
203
|
+
text: name,
|
|
204
|
+
value: name
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
return choices;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getProperties(valueFilter?: string): ChoiceJSON {
|
|
211
|
+
const choices: ChoiceJSON = [];
|
|
212
|
+
this.objects.forEach((obj, name) => {
|
|
213
|
+
if (valueFilter == 'euler' || valueFilter == 'vec3') {
|
|
214
|
+
choices.push({ text: `${name}/position`, value: `${name}/position` });
|
|
215
|
+
choices.push({ text: `${name}/rotation`, value: `${name}/rotation` });
|
|
216
|
+
choices.push({ text: `${name}/scale`, value: `${name}/scale` });
|
|
217
|
+
}
|
|
218
|
+
if (valueFilter === 'boolean') {
|
|
219
|
+
choices.push({ text: `${name}/visible`, value: `${name}/visible` });
|
|
220
|
+
}
|
|
221
|
+
if (valueFilter === 'color') {
|
|
222
|
+
choices.push({ text: `${name}/color`, value: `${name}/color` });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return choices;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
addOnSceneChangedListener(listener: () => void): void {
|
|
229
|
+
this.sceneChangedListeners.add(listener);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
removeOnSceneChangedListener(listener: () => void): void {
|
|
233
|
+
this.sceneChangedListeners.delete(listener);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Trigger a click event (for testing)
|
|
237
|
+
triggerClick(jsonPath: string): void {
|
|
238
|
+
const listeners = this.clickListeners.get(jsonPath);
|
|
239
|
+
if (listeners) {
|
|
240
|
+
listeners.forEach((callback) => callback(jsonPath));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// --- mesh lifecycle ---
|
|
245
|
+
|
|
246
|
+
private createGeometry(
|
|
247
|
+
geometryType: string,
|
|
248
|
+
size: { x: number; y: number; z: number }
|
|
249
|
+
) {
|
|
250
|
+
switch (geometryType) {
|
|
251
|
+
case 'box':
|
|
252
|
+
return new BoxGeometry(size.x, size.y, size.z);
|
|
253
|
+
case 'sphere':
|
|
254
|
+
return new SphereGeometry(size.x / 2, 32, 32);
|
|
255
|
+
case 'cylinder':
|
|
256
|
+
return new CylinderGeometry(size.x / 2, size.x / 2, size.y, 32);
|
|
257
|
+
case 'torus':
|
|
258
|
+
return new TorusGeometry(size.x / 2, size.y / 4, 16, 48);
|
|
259
|
+
case 'plane':
|
|
260
|
+
return new PlaneGeometry(size.x, size.y);
|
|
261
|
+
case 'cone':
|
|
262
|
+
return new ConeGeometry(size.x / 2, size.y, 32);
|
|
263
|
+
default:
|
|
264
|
+
return new BoxGeometry(size.x, size.y, size.z);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
createMesh(
|
|
269
|
+
name: string,
|
|
270
|
+
geometryType: string,
|
|
271
|
+
size: { x: number; y: number; z: number }
|
|
272
|
+
): void {
|
|
273
|
+
// Remove existing mesh with the same name
|
|
274
|
+
if (this.objects.has(name)) {
|
|
275
|
+
this.deleteMesh(name);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const geometry = this.createGeometry(geometryType, size);
|
|
279
|
+
const material = new MeshPhongMaterial({
|
|
280
|
+
color: 0xcccccc,
|
|
281
|
+
shininess: 100
|
|
282
|
+
});
|
|
283
|
+
const mesh = new Mesh(geometry, material);
|
|
284
|
+
mesh.name = name;
|
|
285
|
+
this.scene.add(mesh);
|
|
286
|
+
this.objects.set(name, mesh);
|
|
287
|
+
|
|
288
|
+
this.onSceneChanged.emit();
|
|
289
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
deleteMesh(name: string): void {
|
|
293
|
+
const obj = this.objects.get(name);
|
|
294
|
+
if (!obj) return;
|
|
295
|
+
|
|
296
|
+
this.scene.remove(obj);
|
|
297
|
+
obj.geometry.dispose();
|
|
298
|
+
if (obj.material instanceof MeshPhongMaterial) {
|
|
299
|
+
obj.material.dispose();
|
|
300
|
+
}
|
|
301
|
+
this.objects.delete(name);
|
|
302
|
+
|
|
303
|
+
this.onSceneChanged.emit();
|
|
304
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
getMeshNames(): ChoiceJSON {
|
|
308
|
+
const choices: ChoiceJSON = [];
|
|
309
|
+
this.objects.forEach((_obj, name) => {
|
|
310
|
+
choices.push({ text: name, value: name });
|
|
311
|
+
});
|
|
312
|
+
return choices;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// --- lighting ---
|
|
316
|
+
|
|
317
|
+
private createLight(
|
|
318
|
+
lightType: string,
|
|
319
|
+
color: { r: number; g: number; b: number },
|
|
320
|
+
intensity: number
|
|
321
|
+
): Light {
|
|
322
|
+
const threeColor = new Color(color.r, color.g, color.b);
|
|
323
|
+
|
|
324
|
+
switch (lightType) {
|
|
325
|
+
case 'point': {
|
|
326
|
+
const light = new PointLight(threeColor, intensity);
|
|
327
|
+
light.position.set(0, 5, 0);
|
|
328
|
+
return light;
|
|
329
|
+
}
|
|
330
|
+
case 'directional': {
|
|
331
|
+
const light = new DirectionalLight(threeColor, intensity);
|
|
332
|
+
light.position.set(5, 10, 7);
|
|
333
|
+
return light;
|
|
334
|
+
}
|
|
335
|
+
case 'spot': {
|
|
336
|
+
const light = new SpotLight(threeColor, intensity);
|
|
337
|
+
light.position.set(0, 10, 0);
|
|
338
|
+
return light;
|
|
339
|
+
}
|
|
340
|
+
case 'ambient':
|
|
341
|
+
return new AmbientLight(threeColor, intensity);
|
|
342
|
+
default:
|
|
343
|
+
return new PointLight(threeColor, intensity);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
addLight(
|
|
348
|
+
name: string,
|
|
349
|
+
lightType: string,
|
|
350
|
+
color: { r: number; g: number; b: number },
|
|
351
|
+
intensity: number
|
|
352
|
+
): void {
|
|
353
|
+
// Remove existing light with the same name
|
|
354
|
+
if (this.lights.has(name)) {
|
|
355
|
+
this.removeLight(name);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const light = this.createLight(lightType, color, intensity);
|
|
359
|
+
light.name = name;
|
|
360
|
+
this.scene.add(light);
|
|
361
|
+
this.lights.set(name, light);
|
|
362
|
+
|
|
363
|
+
this.onSceneChanged.emit();
|
|
364
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
removeLight(name: string): void {
|
|
368
|
+
const light = this.lights.get(name);
|
|
369
|
+
if (!light) return;
|
|
370
|
+
|
|
371
|
+
this.scene.remove(light);
|
|
372
|
+
light.dispose();
|
|
373
|
+
this.lights.delete(name);
|
|
374
|
+
|
|
375
|
+
this.onSceneChanged.emit();
|
|
376
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
setLightProperty(name: string, property: string, value: unknown): void {
|
|
380
|
+
const light = this.lights.get(name);
|
|
381
|
+
if (!light) return;
|
|
382
|
+
|
|
383
|
+
switch (property) {
|
|
384
|
+
case 'color': {
|
|
385
|
+
const c = value as { r: number; g: number; b: number };
|
|
386
|
+
light.color.setRGB(c.r, c.g, c.b);
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
case 'intensity':
|
|
390
|
+
light.intensity = value as number;
|
|
391
|
+
break;
|
|
392
|
+
case 'position': {
|
|
393
|
+
const p = value as { x: number; y: number; z: number };
|
|
394
|
+
light.position.set(p.x, p.y, p.z);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
this.onSceneChanged.emit();
|
|
400
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
getLightProperty(name: string, property: string): unknown {
|
|
404
|
+
const light = this.lights.get(name);
|
|
405
|
+
if (!light) return undefined;
|
|
406
|
+
|
|
407
|
+
switch (property) {
|
|
408
|
+
case 'color':
|
|
409
|
+
return {
|
|
410
|
+
r: light.color.r,
|
|
411
|
+
g: light.color.g,
|
|
412
|
+
b: light.color.b
|
|
413
|
+
};
|
|
414
|
+
case 'intensity':
|
|
415
|
+
return light.intensity;
|
|
416
|
+
case 'position':
|
|
417
|
+
return {
|
|
418
|
+
x: light.position.x,
|
|
419
|
+
y: light.position.y,
|
|
420
|
+
z: light.position.z
|
|
421
|
+
};
|
|
422
|
+
default:
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
getLightNames(): ChoiceJSON {
|
|
428
|
+
const choices: ChoiceJSON = [];
|
|
429
|
+
this.lights.forEach((_light, name) => {
|
|
430
|
+
choices.push({ text: name, value: name });
|
|
431
|
+
});
|
|
432
|
+
return choices;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// --- material ---
|
|
436
|
+
|
|
437
|
+
setMaterialProperty(
|
|
438
|
+
meshName: string,
|
|
439
|
+
property: string,
|
|
440
|
+
value: unknown
|
|
441
|
+
): void {
|
|
442
|
+
const obj = this.objects.get(meshName);
|
|
443
|
+
if (!obj) return;
|
|
444
|
+
if (!(obj.material instanceof MeshPhongMaterial)) return;
|
|
445
|
+
|
|
446
|
+
const mat = obj.material;
|
|
447
|
+
|
|
448
|
+
switch (property) {
|
|
449
|
+
case 'color': {
|
|
450
|
+
const c = value as { r: number; g: number; b: number };
|
|
451
|
+
mat.color.setRGB(c.r, c.g, c.b);
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
case 'opacity':
|
|
455
|
+
mat.opacity = value as number;
|
|
456
|
+
mat.transparent = mat.opacity < 1;
|
|
457
|
+
break;
|
|
458
|
+
case 'visible':
|
|
459
|
+
mat.visible = value as boolean;
|
|
460
|
+
break;
|
|
461
|
+
case 'wireframe':
|
|
462
|
+
mat.wireframe = value as boolean;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
mat.needsUpdate = true;
|
|
467
|
+
this.onSceneChanged.emit();
|
|
468
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
getMaterialProperty(meshName: string, property: string): unknown {
|
|
472
|
+
const obj = this.objects.get(meshName);
|
|
473
|
+
if (!obj) return undefined;
|
|
474
|
+
if (!(obj.material instanceof MeshPhongMaterial)) return undefined;
|
|
475
|
+
|
|
476
|
+
const mat = obj.material;
|
|
477
|
+
|
|
478
|
+
switch (property) {
|
|
479
|
+
case 'color':
|
|
480
|
+
return { r: mat.color.r, g: mat.color.g, b: mat.color.b };
|
|
481
|
+
case 'opacity':
|
|
482
|
+
return mat.opacity;
|
|
483
|
+
case 'visible':
|
|
484
|
+
return mat.visible;
|
|
485
|
+
case 'wireframe':
|
|
486
|
+
return mat.wireframe;
|
|
487
|
+
default:
|
|
488
|
+
return undefined;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// --- global click ---
|
|
493
|
+
|
|
494
|
+
addOnAnyMeshClickedListener(callback: (meshName: string) => void): void {
|
|
495
|
+
console.log(
|
|
496
|
+
'[DemoScene] addOnAnyMeshClickedListener , registering listener, total:',
|
|
497
|
+
this.anyMeshClickListeners.size + 1
|
|
498
|
+
);
|
|
499
|
+
this.anyMeshClickListeners.add(callback);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
removeOnAnyMeshClickedListener(callback: (meshName: string) => void): void {
|
|
503
|
+
this.anyMeshClickListeners.delete(callback);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Trigger the "any mesh clicked" event (for testing / raycaster hookup)
|
|
507
|
+
triggerAnyMeshClick(meshName: string): void {
|
|
508
|
+
console.log(
|
|
509
|
+
'[DemoScene] triggerAnyMeshClick:',
|
|
510
|
+
meshName,
|
|
511
|
+
', listeners:',
|
|
512
|
+
this.anyMeshClickListeners.size
|
|
513
|
+
);
|
|
514
|
+
this.anyMeshClickListeners.forEach((cb) => cb(meshName));
|
|
515
|
+
// Also fire per-mesh listeners for backwards compat
|
|
516
|
+
this.triggerClick(meshName);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// --- spatial helpers ---
|
|
520
|
+
|
|
521
|
+
getMeshPosition(
|
|
522
|
+
meshName: string
|
|
523
|
+
): { x: number; y: number; z: number } | undefined {
|
|
524
|
+
const obj = this.objects.get(meshName);
|
|
525
|
+
if (!obj) return undefined;
|
|
526
|
+
return {
|
|
527
|
+
x: obj.position.x,
|
|
528
|
+
y: obj.position.y,
|
|
529
|
+
z: obj.position.z
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
setMeshPosition(
|
|
534
|
+
meshName: string,
|
|
535
|
+
position: { x: number; y: number; z: number }
|
|
536
|
+
): void {
|
|
537
|
+
const obj = this.objects.get(meshName);
|
|
538
|
+
if (!obj) return;
|
|
539
|
+
obj.position.set(position.x, position.y, position.z);
|
|
540
|
+
this.onSceneChanged.emit();
|
|
541
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
getDistanceBetween(meshA: string, meshB: string): number {
|
|
545
|
+
const a = this.objects.get(meshA);
|
|
546
|
+
const b = this.objects.get(meshB);
|
|
547
|
+
if (!a || !b) return 0;
|
|
548
|
+
return a.position.distanceTo(b.position);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
lookAt(meshName: string, target: { x: number; y: number; z: number }): void {
|
|
552
|
+
const obj = this.objects.get(meshName);
|
|
553
|
+
if (!obj) return;
|
|
554
|
+
obj.lookAt(target.x, target.y, target.z);
|
|
555
|
+
this.onSceneChanged.emit();
|
|
556
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
moveTowards(
|
|
560
|
+
meshName: string,
|
|
561
|
+
target: { x: number; y: number; z: number },
|
|
562
|
+
speed: number,
|
|
563
|
+
deltaSeconds: number
|
|
564
|
+
): boolean {
|
|
565
|
+
const obj = this.objects.get(meshName);
|
|
566
|
+
if (!obj) return true;
|
|
567
|
+
|
|
568
|
+
const targetVec = new Vector3(target.x, target.y, target.z);
|
|
569
|
+
const direction = targetVec.clone().sub(obj.position);
|
|
570
|
+
const distance = direction.length();
|
|
571
|
+
const step = speed * deltaSeconds;
|
|
572
|
+
|
|
573
|
+
if (distance <= step) {
|
|
574
|
+
// Arrived
|
|
575
|
+
obj.position.copy(targetVec);
|
|
576
|
+
this.onSceneChanged.emit();
|
|
577
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
direction.normalize().multiplyScalar(step);
|
|
582
|
+
obj.position.add(direction);
|
|
583
|
+
this.onSceneChanged.emit();
|
|
584
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// --- mesh utilities ---
|
|
589
|
+
|
|
590
|
+
cloneMesh(sourceName: string, newName: string): void {
|
|
591
|
+
const source = this.objects.get(sourceName);
|
|
592
|
+
if (!source) return;
|
|
593
|
+
|
|
594
|
+
const cloned = source.clone();
|
|
595
|
+
cloned.name = newName;
|
|
596
|
+
this.scene.add(cloned);
|
|
597
|
+
this.objects.set(newName, cloned);
|
|
598
|
+
|
|
599
|
+
this.onSceneChanged.emit();
|
|
600
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
setMeshVisible(meshName: string, visible: boolean): void {
|
|
604
|
+
const obj = this.objects.get(meshName);
|
|
605
|
+
if (!obj) return;
|
|
606
|
+
obj.visible = visible;
|
|
607
|
+
this.onSceneChanged.emit();
|
|
608
|
+
this.sceneChangedListeners.forEach((listener) => listener());
|
|
609
|
+
}
|
|
610
|
+
}
|