@toolproof-core/visualization 1.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.
@@ -0,0 +1,163 @@
1
+ 'use client';
2
+
3
+ import RuntimeView, { type RuntimeCameraConfig } from './RuntimeView.js';
4
+ import Portal, { type PortalInteractionPolicy } from '../primitives/Portal.js';
5
+ import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
6
+ import * as THREE from 'three';
7
+ import { OrbitControls } from '@react-three/drei';
8
+
9
+ export type SpaceId = 'genesis' | 'strategy'; // ATTENTION: TYPE-SpaceIdentity
10
+
11
+ export type PortalDescriptor = {
12
+ id: string;
13
+ from: SpaceId;
14
+ to: SpaceId;
15
+ position: [number, number, number];
16
+ rotation: [number, number, number];
17
+ scale: number;
18
+ visible: boolean;
19
+ };
20
+
21
+ export type RuntimeControllerStatus = {
22
+ activeSpace: SpaceId;
23
+ portal?: PortalDescriptor;
24
+ isTransitioning: boolean;
25
+ };
26
+
27
+ export type RuntimeControllerHandle = {
28
+ setActiveSpace: (space: SpaceId) => void;
29
+ transitionTo: (space: SpaceId, opts?: { via?: 'portal'; portalId?: string }) => void;
30
+ getStatus: () => RuntimeControllerStatus;
31
+ subscribeStatus: (listener: (status: RuntimeControllerStatus) => void) => () => void;
32
+ };
33
+
34
+ type OrbitControlsImpl = React.ElementRef<typeof OrbitControls>;
35
+
36
+ export type RuntimeControllerRenderSpaceArgs = {
37
+ activeSpace: SpaceId;
38
+ orbitControlsRef: React.RefObject<OrbitControlsImpl | null>;
39
+ setPortal: (portal?: PortalDescriptor) => void;
40
+ };
41
+
42
+ export type RuntimeControllerProps = {
43
+ initialSpace: SpaceId;
44
+ cameraConfig: RuntimeCameraConfig;
45
+ renderSpace: (args: RuntimeControllerRenderSpaceArgs) => React.ReactNode;
46
+ background?: string;
47
+ /**
48
+ * Controls whether the user can activate/deactivate the runtime portal via gesture.
49
+ * Defaults to "toggle" to preserve existing behavior.
50
+ */
51
+ portalInteractionPolicy?: PortalInteractionPolicy;
52
+ };
53
+
54
+ export default React.forwardRef<RuntimeControllerHandle, RuntimeControllerProps>(function RuntimeController(props, ref) {
55
+ const [activeSpace, setActiveSpaceState] = useState<SpaceId>(props.initialSpace);
56
+ const [portalDescriptor, setPortalDescriptor] = useState<PortalDescriptor | undefined>(undefined);
57
+ const [isTransitioning, setIsTransitioning] = useState(false);
58
+ const [isPortalActive, setIsPortalActive] = useState(false);
59
+
60
+ const orbitControlsRef = useRef<OrbitControlsImpl | null>(null);
61
+ const cameraRef = useRef<THREE.Camera | null>(null);
62
+
63
+ const listenersRef = useRef(new Set<(status: RuntimeControllerStatus) => void>());
64
+
65
+ const setPortal = useCallback((portal?: PortalDescriptor) => {
66
+ setPortalDescriptor(portal);
67
+ }, []);
68
+
69
+ const status = useMemo<RuntimeControllerStatus>(() => {
70
+ return {
71
+ activeSpace,
72
+ // portal: portalDescriptor,
73
+ isTransitioning,
74
+ };
75
+ }, [activeSpace, isTransitioning, portalDescriptor]);
76
+
77
+ useEffect(() => {
78
+ for (const listener of listenersRef.current) {
79
+ listener(status);
80
+ }
81
+ }, [status]);
82
+
83
+ const setActiveSpace = useCallback((space: SpaceId) => {
84
+ setIsTransitioning(false);
85
+ setIsPortalActive(false);
86
+ setActiveSpaceState(space);
87
+ }, []);
88
+
89
+ const transitionTo = useCallback(
90
+ (space: SpaceId, opts?: { via?: 'portal'; portalId?: string }) => {
91
+ if (space === activeSpace) return;
92
+
93
+ if (opts?.via === 'portal') {
94
+ const canUsePortal =
95
+ !!portalDescriptor?.visible &&
96
+ portalDescriptor.from === activeSpace &&
97
+ portalDescriptor.to === space &&
98
+ (!opts.portalId || portalDescriptor.id === opts.portalId);
99
+
100
+ if (canUsePortal) {
101
+ setIsTransitioning(true);
102
+ setIsPortalActive(true);
103
+ return;
104
+ }
105
+ }
106
+
107
+ setActiveSpace(space);
108
+ },
109
+ [activeSpace, portalDescriptor, setActiveSpace]
110
+ );
111
+
112
+ React.useImperativeHandle(
113
+ ref,
114
+ (): RuntimeControllerHandle => ({
115
+ setActiveSpace,
116
+ transitionTo,
117
+ getStatus: () => status,
118
+ subscribeStatus: (listener) => {
119
+ listenersRef.current.add(listener);
120
+ listener(status);
121
+ return () => {
122
+ listenersRef.current.delete(listener);
123
+ };
124
+ },
125
+ }),
126
+ [setActiveSpace, status, transitionTo]
127
+ );
128
+
129
+ const showPortal = !!portalDescriptor?.visible && portalDescriptor.from === activeSpace;
130
+
131
+ return (
132
+ <RuntimeView cameraConfig={props.cameraConfig} orbitControlsRef={orbitControlsRef} cameraRef={cameraRef} background={props.background ?? 'skyblue'}>
133
+ {props.renderSpace({ activeSpace, orbitControlsRef, setPortal })}
134
+
135
+ {showPortal && portalDescriptor ? (
136
+ <group position={portalDescriptor.position} rotation={portalDescriptor.rotation} scale={portalDescriptor.scale}>
137
+ <Portal
138
+ isActive={isPortalActive}
139
+ setIsActive={(active) => {
140
+ setIsPortalActive(active);
141
+ setIsTransitioning(active);
142
+ }}
143
+ interactionPolicy={props.portalInteractionPolicy ?? 'toggle'}
144
+ orbitControlsRef={orbitControlsRef as unknown as React.RefObject<{ target: THREE.Vector3 } | null>}
145
+ onTransitionComplete={(active) => {
146
+ if (!active) {
147
+ setIsTransitioning(false);
148
+ setIsPortalActive(false);
149
+ return;
150
+ }
151
+
152
+ // Transition finished: commit to the destination space.
153
+ const destination = portalDescriptor.to;
154
+ setIsTransitioning(false);
155
+ setIsPortalActive(false);
156
+ setActiveSpaceState(destination);
157
+ }}
158
+ />
159
+ </group>
160
+ ) : null}
161
+ </RuntimeView>
162
+ );
163
+ });
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+
3
+ import type React from 'react';
4
+ import { Canvas } from '@react-three/fiber';
5
+ import { OrbitControls } from '@react-three/drei';
6
+ import { XR, createXRStore, useXR, type XRStore } from '@react-three/xr';
7
+ import * as THREE from 'three';
8
+ import { useEffect, useMemo, useRef } from 'react';
9
+
10
+ export type RuntimeCameraConfig = {
11
+ position: [number, number, number];
12
+ target: [number, number, number];
13
+ fov: number;
14
+ };
15
+
16
+ type OrbitControlsImpl = React.ElementRef<typeof OrbitControls>;
17
+
18
+ export const runtimeXrStore: XRStore = createXRStore();
19
+
20
+ function RuntimeControls({
21
+ orbitControlsRef,
22
+ target,
23
+ }: {
24
+ orbitControlsRef: React.RefObject<OrbitControlsImpl | null>;
25
+ target: [number, number, number];
26
+ }) {
27
+ const session = useXR((state) => state.session);
28
+
29
+ return (
30
+ <OrbitControls
31
+ ref={orbitControlsRef}
32
+ target={target}
33
+ makeDefault
34
+ enableDamping
35
+ dampingFactor={0.05}
36
+ enabled={!session}
37
+ />
38
+ );
39
+ }
40
+
41
+ export default function RuntimeView(props: {
42
+ children: React.ReactNode;
43
+ cameraConfig: RuntimeCameraConfig;
44
+ orbitControlsRef?: React.RefObject<OrbitControlsImpl | null>;
45
+ cameraRef?: React.RefObject<THREE.Camera | null>;
46
+ background?: string;
47
+ }) {
48
+ const { children, cameraConfig, background = 'skyblue' } = props;
49
+
50
+ const fallbackOrbitControlsRef = useRef<OrbitControlsImpl | null>(null);
51
+ const fallbackCameraRef = useRef<THREE.Camera | null>(null);
52
+
53
+ const orbitControlsRef = props.orbitControlsRef ?? fallbackOrbitControlsRef;
54
+ const cameraRef = props.cameraRef ?? fallbackCameraRef;
55
+
56
+ const initialCamera = useMemo(
57
+ () => ({ position: cameraConfig.position, fov: cameraConfig.fov }),
58
+ // Only used for initial mount; follow-up updates happen via effect.
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ []
61
+ );
62
+
63
+ useEffect(() => {
64
+ const camera = cameraRef.current;
65
+ const controls = orbitControlsRef.current;
66
+ if (!camera || !controls) return;
67
+
68
+ camera.position.set(...cameraConfig.position);
69
+ if ('fov' in camera && typeof (camera as THREE.PerspectiveCamera).fov === 'number') {
70
+ (camera as THREE.PerspectiveCamera).fov = cameraConfig.fov;
71
+ (camera as THREE.PerspectiveCamera).updateProjectionMatrix();
72
+ }
73
+
74
+ controls.target.set(...cameraConfig.target);
75
+ controls.update();
76
+ }, [cameraConfig, cameraRef, orbitControlsRef]);
77
+
78
+ return (
79
+ <Canvas
80
+ style={{ width: '100%', height: '100%' }}
81
+ camera={initialCamera}
82
+ onCreated={({ camera, scene }) => {
83
+ cameraRef.current = camera;
84
+ scene.background = new THREE.Color(background);
85
+ }}
86
+ >
87
+ <XR store={runtimeXrStore}>
88
+ <RuntimeControls orbitControlsRef={orbitControlsRef} target={cameraConfig.target} />
89
+ {children}
90
+ </XR>
91
+ </Canvas>
92
+ );
93
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist",
6
+ "jsx": "react-jsx",
7
+ },
8
+ "include": [
9
+ "src"
10
+ ],
11
+ "exclude": [
12
+ "dist",
13
+ "node_modules"
14
+ ]
15
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/_lib/types.ts","./src/primitives/portal.tsx","./src/primitives/ringofmeshes.tsx","./src/primitives/spokeofmeshes.tsx","./src/runtime/runtimecontroller.tsx","./src/runtime/runtimeview.tsx"],"version":"5.9.3"}