From 7d7412a395bbcffcee54fb6bad0a414329c4282f Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:26:42 +0800 Subject: [PATCH 01/43] Fix package install --- package.json | 3 ++- yarn.lock | 45 +++++++++++++++++++++++++++++---------------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 7b8f73f31..d61a7fada 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ }, "resolutions": { "@types/react": "^18.2.0", - "esbuild": "^0.18.20" + "esbuild": "^0.18.20", + "**/gl": "^6.0.2" } } diff --git a/yarn.lock b/yarn.lock index 1f93b5868..32ef966ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3017,6 +3017,11 @@ expect@^29.4.3: jest-message-util "^29.4.3" jest-util "^29.4.3" +exponential-backoff@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" + integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3290,17 +3295,17 @@ gl-wiretap@^0.6.2: resolved "https://registry.yarnpkg.com/gl-wiretap/-/gl-wiretap-0.6.2.tgz#e4aa19622831088fbaa7e5a18d01768f7a3fb07c" integrity sha512-fxy1XGiPkfzK+T3XKDbY7yaqMBmozCGvAFyTwaZA3imeZH83w7Hr3r3bYlMRWIyzMI/lDUvUMM/92LE2OwqFyQ== -gl@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/gl/-/gl-5.0.3.tgz#a10f37c50e48954348cc3e790b83313049bdbe1c" - integrity sha512-toWmb3Rgli5Wl9ygjZeglFBVLDYMOomy+rXlVZVDCoIRV+6mQE5nY4NgQgokYIc5oQzc1pvWY9lQJ0hGn61ZUg== +gl@^5.0.3, gl@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/gl/-/gl-6.0.2.tgz#685579732a19075e3acf4684edb1270278e551c7" + integrity sha512-yBbfpChOtFvg5D+KtMaBFvj6yt3vUnheNAH+UrQH2TfDB8kr0tERdL0Tjhe0W7xJ6jR6ftQBluTZR9jXUnKe8g== dependencies: bindings "^1.5.0" bit-twiddle "^1.0.2" glsl-tokenizer "^2.1.5" - nan "^2.16.0" - node-abi "^3.22.0" - node-gyp "^9.0.0" + nan "^2.17.0" + node-abi "^3.26.0" + node-gyp "^9.2.0" prebuild-install "^7.1.1" glob-parent@^5.1.2, glob-parent@~5.1.2: @@ -4822,10 +4827,10 @@ ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nan@^2.16.0: - version "2.17.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" - integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== +nan@^2.17.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" + integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== nanoid@^3.3.6: version "3.3.6" @@ -4860,7 +4865,14 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-abi@^3.22.0, node-abi@^3.3.0: +node-abi@^3.26.0: + version "3.54.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.54.0.tgz#f6386f7548817acac6434c6cba02999c9aebcc69" + integrity sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA== + dependencies: + semver "^7.3.5" + +node-abi@^3.3.0: version "3.33.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.33.0.tgz#8b23a0cec84e1c5f5411836de6a9b84bccf26e7f" integrity sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog== @@ -4872,12 +4884,13 @@ node-getopt@^0.3.2: resolved "https://registry.yarnpkg.com/node-getopt/-/node-getopt-0.3.2.tgz#57507cd22f6f69650aa99252304a842f1224e44c" integrity sha512-yqkmYrMbK1wPrfz7mgeYvA4tBperLg9FQ4S3Sau3nSAkpOA0x0zC8nQ1siBwozy1f4SE8vq2n1WKv99r+PCa1Q== -node-gyp@^9.0.0: - version "9.3.1" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.3.1.tgz#1e19f5f290afcc9c46973d68700cbd21a96192e4" - integrity sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg== +node-gyp@^9.2.0: + version "9.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-9.4.1.tgz#8a1023e0d6766ecb52764cc3a734b36ff275e185" + integrity sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ== dependencies: env-paths "^2.2.0" + exponential-backoff "^3.1.1" glob "^7.1.4" graceful-fs "^4.2.6" make-fetch-happen "^10.0.3" From 63be093deeb5c5b24431ac2061ca658e8c21793c Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 5 Feb 2024 14:54:19 +0800 Subject: [PATCH 02/43] Transferred libraries --- modules.json | 5 + package.json | 9 +- src/bundles/ar/AR.ts | 76 ++++ src/bundles/ar/ObjectsHelper.ts | 123 ++++++ src/bundles/ar/OverlayHelper.ts | 14 + src/bundles/ar/index.ts | 42 ++ .../ar/libraries/calibration_library/Misc.tsx | 22 + .../calibration_library/PlayAreaContext.tsx | 161 +++++++ .../controls_library/ControlsContext.tsx | 65 +++ .../ar/libraries/controls_library/RayCast.tsx | 68 +++ .../object_state_library/ARObject.tsx | 219 ++++++++++ .../ARObjectComponent.tsx | 220 ++++++++++ .../object_state_library/Behaviour.tsx | 413 ++++++++++++++++++ .../object_state_library/ErrorBoundary.tsx | 28 ++ .../model_components/GltfComponent.tsx | 67 +++ .../model_components/ImageComponent.tsx | 22 + .../model_components/InterfaceComponent.tsx | 176 ++++++++ .../model_components/LightComponent.tsx | 21 + .../model_components/ShapeComponent.tsx | 35 ++ .../model_components/TextComponent.tsx | 52 +++ .../ui_component/UIColumnComponent.tsx | 111 +++++ .../ui_component/UIComponent.tsx | 107 +++++ .../ui_component/UIImageComponent.tsx | 41 ++ .../ui_component/UIRowComponent.tsx | 112 +++++ .../ui_component/UITextComponent.tsx | 128 ++++++ .../ScreenStateContext.tsx | 129 ++++++ src/tabs/AugmentedReality/index.tsx | 71 +++ yarn.lock | 403 ++++++++++++++++- 28 files changed, 2936 insertions(+), 4 deletions(-) create mode 100644 src/bundles/ar/AR.ts create mode 100644 src/bundles/ar/ObjectsHelper.ts create mode 100644 src/bundles/ar/OverlayHelper.ts create mode 100644 src/bundles/ar/index.ts create mode 100644 src/bundles/ar/libraries/calibration_library/Misc.tsx create mode 100644 src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx create mode 100644 src/bundles/ar/libraries/controls_library/ControlsContext.tsx create mode 100644 src/bundles/ar/libraries/controls_library/RayCast.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/ARObject.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/Behaviour.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/ErrorBoundary.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx create mode 100644 src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx create mode 100644 src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx create mode 100644 src/tabs/AugmentedReality/index.tsx diff --git a/modules.json b/modules.json index 7e3cc6942..d7ed7b7be 100644 --- a/modules.json +++ b/modules.json @@ -98,5 +98,10 @@ "tabs": [ "physics_2d" ] + }, + "ar": { + "tabs": [ + "AugmentedReality" + ] } } \ No newline at end of file diff --git a/package.json b/package.json index d61a7fada..d546e4904 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,10 @@ "@jscad/modeling": "2.9.6", "@jscad/regl-renderer": "^2.6.1", "@jscad/stl-serializer": "^2.1.13", + "@react-three/drei": "^9.97.0", + "@react-three/fiber": "^8.15.16", + "@react-three/xr": "^5.7.1", + "@types/three": "^0.161.2", "ace-builds": "^1.25.1", "classnames": "^2.3.1", "dayjs": "^1.10.4", @@ -121,7 +125,10 @@ "save-file": "^2.3.1", "source-academy-utils": "^1.0.0", "source-academy-wabt": "^1.0.4", - "tslib": "^2.3.1" + "spring": "^0.0.0", + "three": "^0.161.0", + "tslib": "^2.3.1", + "uniqid": "^5.4.0" }, "jest": { "projects": [ diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts new file mode 100644 index 000000000..582d1fcb1 --- /dev/null +++ b/src/bundles/ar/AR.ts @@ -0,0 +1,76 @@ +import { Vector3 } from "three"; +import { ARObject } from "./libraries/object_state_library/ARObject"; +import { OverlayHelper, Toggle } from "./OverlayHelper"; + +import context from "js-slang/context"; + +export class ARState { + arObjects: ARObject[] = []; + overlay = new OverlayHelper(); + + constructor() { + (window as any).arController = this; + } +} + +let moduleState = new ARState(); + +export function getModuleState(): ARState { + return (window as any).arController as ARState; +} + +// Overlay + +export function setLeftToggle(text: string, callback: () => void) { + moduleState.overlay.toggleLeft = new Toggle(text, callback); +} + +export function setCentreToggle(text: string, callback: () => void) { + moduleState.overlay.toggleCentre = new Toggle(text, callback); +} + +export function setRightToggle(text: string, callback: () => void) { + moduleState.overlay.toggleRight = new Toggle(text, callback); +} + +export function removeLeftToggle() { + moduleState.overlay.toggleLeft = undefined; +} + +export function removeCentreToggle() { + moduleState.overlay.toggleCentre = undefined; +} + +export function removeRightToggle() { + moduleState.overlay.toggleRight = undefined; +} + +// Objects + +export function createVector3(x: number, y: number, z: number): Vector3 { + return new Vector3(x, y, z); +} + +export function addARObject(object: ARObject) { + if ( + moduleState.arObjects.find((item) => { + item.id === object.id; + }) + ) { + return; // Already in array + } + let newArray = Object.assign([], moduleState.arObjects); + newArray.push(object); + moduleState.arObjects = newArray; +} + +export function removeARObject(object: ARObject) { + moduleState.arObjects = moduleState.arObjects.filter((item) => { + item.id !== object.id; + }); +} + +export function clearARObjects() { + moduleState.arObjects = []; +} + diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts new file mode 100644 index 000000000..a607a02d2 --- /dev/null +++ b/src/bundles/ar/ObjectsHelper.ts @@ -0,0 +1,123 @@ +import { Vector3 } from "three"; +import { + ARObject, + CubeObject, + LightObject, + UIObject, +} from "./libraries/object_state_library/ARObject"; +import { + AlwaysRender, + FixRotation, + MovementStyle, + OrbitMovement, + type PathItem, + PathMovement, + RenderWithinDistance, + RotateAroundY, + RotateToUser, + SpringMovement, +} from "./libraries/object_state_library/Behaviour"; +import uniqid from "uniqid"; + +// Objects + +export function createCubeObject( + position: Vector3, + width: number, + height: number, + depth: number, + color: number, + onSelect?: (object: ARObject) => {} +): CubeObject { + return new CubeObject( + uniqid(), + position, + width, + height, + depth, + color, + undefined, + undefined, + undefined, + onSelect + ); +} + +export function createInterfaceObject( + position: Vector3, + uiJson: any, + onSelect?: (object: ARObject) => {} +): UIObject { + return new UIObject( + uniqid(), + position, + uiJson, + undefined, + undefined, + undefined, + onSelect + ); +} + +export function createLightObject( + position: Vector3, + intensity: number +): LightObject { + return new LightObject(uniqid(), position, intensity); +} + +// Rotation + +export function setFixedRotation(object: ARObject, radians: number) { + object.behaviours.rotation = new FixRotation(radians); +} + +export function setRotateToUser(object: ARObject) { + object.behaviours.rotation = new RotateToUser(); +} + +export function setRotateAroundY(object: ARObject) { + object.behaviours.rotation = new RotateAroundY(); +} + +// Render + +export function setAlwaysRender(object: ARObject) { + object.behaviours.render = new AlwaysRender(); +} + +export function setRenderDistance(object: ARObject, distance: number) { + object.behaviours.render = new RenderWithinDistance(distance); +} + +// Movement + +export function createPathItem( + start: Vector3, + end: Vector3, + duration: number +): PathItem { + return { + start: start, + end: end, + duration: duration, + style: MovementStyle.Linear, + }; +} + +export function setPathMovement(object: ARObject, path: PathItem[]) { + object.behaviours.movement = new PathMovement(path); +} + +export function setOrbitMovement( + object: ARObject, + radius: number, + duration: number +) { + object.behaviours.movement = new OrbitMovement(radius, duration); +} + +export function setSpringMovement(object: ARObject) { + object.behaviours.model = new SpringMovement(); +} + diff --git a/src/bundles/ar/OverlayHelper.ts b/src/bundles/ar/OverlayHelper.ts new file mode 100644 index 000000000..286b07bab --- /dev/null +++ b/src/bundles/ar/OverlayHelper.ts @@ -0,0 +1,14 @@ +export class Toggle { + text: string; + callback: () => void; + constructor(text: string, callback: () => void) { + this.text = text; + this.callback = callback; + } +} + +export class OverlayHelper { + toggleLeft: Toggle | undefined; + toggleCentre: Toggle | undefined; + toggleRight: Toggle | undefined; +} diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/index.ts new file mode 100644 index 000000000..834fbfaee --- /dev/null +++ b/src/bundles/ar/index.ts @@ -0,0 +1,42 @@ +/** + * Module for creating and interacting with augmented reality content. + * + * Allows starting of an XR context within Source Academy. + * Currently only works on Android. + * Desktop version of Chrome can be used to run an emulator via WebXR API Emulator plugin. + * @module ar + * @author Chong Wen Hao + */ + +/* + To access things like the context or module state you can just import the context + using the import below + */ + +export { + setLeftToggle, + setCentreToggle, + setRightToggle, + removeLeftToggle, + removeCentreToggle, + removeRightToggle, + createVector3, + addARObject, + removeARObject, + clearARObjects, +} from "./AR"; + +export { + createCubeObject, + createInterfaceObject, + createLightObject, + setFixedRotation, + setRotateToUser, + setRotateAroundY, + setAlwaysRender, + setRenderDistance, + createPathItem, + setPathMovement, + setOrbitMovement, + setSpringMovement, +} from "./ObjectsHelper"; diff --git a/src/bundles/ar/libraries/calibration_library/Misc.tsx b/src/bundles/ar/libraries/calibration_library/Misc.tsx new file mode 100644 index 000000000..014a47624 --- /dev/null +++ b/src/bundles/ar/libraries/calibration_library/Misc.tsx @@ -0,0 +1,22 @@ +import { Vector3 } from "three"; + +/** + * Converts object to a Vector3. + * + * @param object Object to parse + * @returns Vector3 if successful, undefined if failed + */ +export function parseVector3(object: any) { + if (!object) return undefined; + let x = object.x; + let y = object.y; + let z = object.z; + if ( + typeof x === "number" && + typeof y === "number" && + typeof z === "number" + ) { + return new Vector3(x, y, z); + } + return undefined; +} diff --git a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx new file mode 100644 index 000000000..3c9fc6d8b --- /dev/null +++ b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx @@ -0,0 +1,161 @@ +import { useThree } from "@react-three/fiber"; +import { type ReactNode, createContext, useContext, useState } from "react"; +import { Vector3, Euler } from "three"; + +type ContextType = { + setCameraAsOrigin: () => void; + getCameraRelativePosition: () => Vector3; + getCameraRelativeRotation: () => Euler; + setPlayArea: (origin: Vector3, cameraRotation: Euler) => void; +}; + +const Context = createContext({ + setCameraAsOrigin: () => {}, + getCameraRelativePosition: () => new Vector3(), + getCameraRelativeRotation: () => new Euler(), + setPlayArea: () => {}, +}); + +type Props = { + children: ReactNode; +}; + +/** + * Parent component with play area context. + * Allows for translation between world position and relative position. + * + * Steps to use: + * 1. Call 'setPlayArea' to callibrate origin position and angle. + * 2. To convert from relative position to world position, call 'getPosition'. + * 3. To convert back to relative position, call 'getRelativePosition'. + * + * Components within it can call 'usePlayArea' to obtain this context. + */ +export function PlayAreaContext(props: Props) { + const [origin, setOrigin] = useState(new Vector3(0, 0, 0)); + const [angle, setAngle] = useState(0); + const three = useThree(); + const DEFAULT_HEIGHT = 1; + + // Three + + function setCameraAsOrigin() { + let cameraPosition = three.camera.position; + setPlayArea( + new Vector3( + cameraPosition.x, + cameraPosition.y - DEFAULT_HEIGHT, + cameraPosition.z + ), + three.camera.rotation + ); + } + + function getCameraRelativePosition() { + return getRelativePosition(three.camera.position); + } + + function getCameraRelativeRotation() { + return getRelativeRotation(three.camera.rotation); + } + + // Logic + + /** + * Sets the origin position and angle for calibration. + * Relative to actual world origin. + * Converts camera rotation to angle around vertical axis. + * Angle measured counterclockwise, starting from 12 o'clock. + * + * @param origin Position of user in world coordinates + * @param cameraRotation Camera rotation of user + */ + function setPlayArea(origin: Vector3, cameraRotation: Euler) { + setOrigin(origin); + setAngle(eulerToAngle(cameraRotation)); + } + + /** + * Converts euler to y-axis rotation angle. + * @param euler Euler to convert + * @returns Angle in radians + */ + function eulerToAngle(euler: Euler) { + let x = Math.sin(euler.y); + let z = Math.cos(euler.y) * Math.cos(euler.x); + let selectedAngle = 0; + if (z !== 0) { + selectedAngle = Math.atan(x / z); + if (z < 0) { + selectedAngle += Math.PI; + } else if (x < 0) { + selectedAngle += Math.PI * 2; + } + } else { + if (x > 0) { + selectedAngle = Math.PI / 2; + } else if (x < 0) { + selectedAngle = (Math.PI * 3) / 2; + } + } + return selectedAngle; + } + + // /** + // * Converts relative position to world position. + // * Used for placing object into world. + // * + // * @param relativePosition Relative position of the object + // * @returns Actual position of the object + // */ + // function getPosition(relativePosition: Vector3) { + // var clonedPosition = relativePosition.clone(); + // clonedPosition.applyAxisAngle(new Vector3(0, 1, 0), rotation); + // let x = clonedPosition.x + origin.x; + // let y = clonedPosition.y + origin.y; + // let z = clonedPosition.z + origin.z; + // return new Vector3(x, y, z); + // } + + /** + * Converts world position to relative position. + * Used for saving object's current position in world. + * + * @param position Actual position of the object + * @returns Relative position of the object + */ + function getRelativePosition(position: Vector3) { + let x = position.x - origin.x; + let y = position.y - origin.y; + let z = position.z - origin.z; + let relativePosition = new Vector3(x, y, z); + relativePosition.applyAxisAngle(new Vector3(0, 1, 0), -angle); + return relativePosition; + } + + function getRelativeRotation(rotation: Euler) { + let vector3 = new Vector3().setFromEuler(rotation); + vector3.applyAxisAngle(new Vector3(0, 1, 0), -angle); + return new Euler().setFromVector3(vector3); + } + + return ( + + + {props.children} + + + ); +} + +export function usePlayArea() { + return useContext(Context); +} + diff --git a/src/bundles/ar/libraries/controls_library/ControlsContext.tsx b/src/bundles/ar/libraries/controls_library/ControlsContext.tsx new file mode 100644 index 000000000..ee6d629d6 --- /dev/null +++ b/src/bundles/ar/libraries/controls_library/ControlsContext.tsx @@ -0,0 +1,65 @@ +import { useFrame, useThree } from "@react-three/fiber"; +import { + type ReactNode, + createContext, + createRef, + useContext, + useRef, +} from "react"; +import { Mesh } from "three"; +import { getIntersection } from "./RayCast"; + +type ContextType = { + object: React.MutableRefObject; + setCallback: ( + callback: (prev: Mesh | undefined, current: Mesh | undefined) => void + ) => void; +}; + +const Context = createContext({ + object: createRef(), + setCallback: () => {}, +}); + +type Props = { + children: ReactNode; +}; + +export function ControlsContext(props: Props) { + const facingObject = useRef(undefined); + const callback = + useRef<(prev: Mesh | undefined, current: Mesh | undefined) => void>(); + + const three = useThree(); + + useFrame((_state, _delta) => { + let item = getIntersection(three.camera, three.scene); + if (item === undefined && facingObject.current !== undefined) { + let prev = facingObject.current; + facingObject.current = undefined; + callback.current?.(prev, undefined); + } else if (item !== undefined && facingObject.current?.uuid !== item.uuid) { + let prev = facingObject.current; + facingObject.current = item; + callback.current?.(prev, item); + } + }); + + return ( + { + callback.current = newCallback; + }, + }} + > + {props.children} + + ); +} + +export function useControls() { + return useContext(Context); +} + diff --git a/src/bundles/ar/libraries/controls_library/RayCast.tsx b/src/bundles/ar/libraries/controls_library/RayCast.tsx new file mode 100644 index 000000000..e5ee615c1 --- /dev/null +++ b/src/bundles/ar/libraries/controls_library/RayCast.tsx @@ -0,0 +1,68 @@ +import { + Camera, + Group, + Mesh, + Object3D, + type Object3DEventMap, + Raycaster, + Vector2, +} from "three"; + +const raycaster = new Raycaster(); + +export function getIntersection(camera: Camera, coreObject: Object3D) { + let pointer = new Vector2(0, 0); + raycaster.setFromCamera(pointer, camera); + let cascadeChildren = getCascadeMeshs(coreObject.children); + let items = raycaster.intersectObjects(cascadeChildren, true); + let nearestItem = items + .filter((item) => { + return item.distance != 0; + }) + .sort((item) => { + return item.distance; + }); + if (nearestItem.length > 0) { + return getTopParent(nearestItem[0].object, coreObject); + } else { + return undefined; + } +} + +function getCascadeMeshs(children: Object3D[]) { + let cascadeChildren: Object3D[] = []; + let queue = Array.from(children); + while (queue.length > 0) { + let item = queue.pop(); + if (item) { + if (item instanceof Mesh) { + cascadeChildren.push(item); + } else if (item instanceof Group) { + queue = queue.concat(item.children); + } + } + } + return cascadeChildren; +} + +function getTopParent( + child: Object3D, + coreObject: Object3D +): Mesh | undefined { + let parent = child; + let lastMesh = child; + while ( + parent.parent instanceof Object3D && + parent.parent.uuid !== coreObject.uuid + ) { + parent = parent.parent; + if (parent instanceof Mesh) { + lastMesh = parent; + } + } + if (lastMesh instanceof Mesh) { + return lastMesh; + } + return undefined; +} + diff --git a/src/bundles/ar/libraries/object_state_library/ARObject.tsx b/src/bundles/ar/libraries/object_state_library/ARObject.tsx new file mode 100644 index 000000000..81e78f58e --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/ARObject.tsx @@ -0,0 +1,219 @@ +import { BoxGeometry, MeshStandardMaterial, Vector3 } from "three"; +import { + type Behaviours, + LightModel, + RenderWithinDistance, + ShapeModel, + InterfaceModel, + RotationClass, + parseRotation, + RenderClass, + parseRender, + parseMovement, + MovementClass, +} from "./Behaviour"; +import ARObjectComponent from "./ARObjectComponent"; +import { parseVector3 } from "../calibration_library/Misc"; + +/** + * Abstract class for an AR object. + * Extend this class to create your AR object. + * + * When converting to JSON, the object's class name will be stored in the 'class' key. + * This can be used for identifying and restoring the object's class after parsing the JSON. + */ +export class ARObject { + type: string = ""; // Unique identifier for class + id: string; + position: Vector3; + behaviours: Behaviours; + uuid: string | undefined = undefined; + isHighlighted = false; + onSelect?: (object: ARObject) => void; + constructor( + id: string, + position: Vector3, + behaviours: Behaviours, + onSelect?: (object: ARObject) => void + ) { + this.id = id; + this.position = position; + this.behaviours = behaviours; + this.onSelect = onSelect; + } + toJSON = () => { + let object = Object.assign({}, this) as any; + let behavioursClone = Object.assign({}, this.behaviours) as any; + delete behavioursClone.model; + object.behaviours = behavioursClone; + return object; + }; + getComponent(getUserPosition: () => Vector3) { + return ( + { + this.uuid = uuid; + }} + onSelect={this.onSelect} + /> + ); + } +} + +const CUBE_OBJECT_TYPE = "CubeObject"; +export class CubeObject extends ARObject { + type = CUBE_OBJECT_TYPE; + width: number; + height: number; + depth: number; + color: number; + constructor( + id: string, + position: Vector3, + width: number, + height: number, + depth: number, + color: number, + render?: RenderClass, + rotation?: RotationClass, + movement?: MovementClass, + onSelect?: (object: ARObject) => void + ) { + super( + id, + position, + { + model: new ShapeModel( + new BoxGeometry(width, height, depth), + new MeshStandardMaterial({ color: color }) + ), + render: render, + rotation: rotation, + movement: movement, + }, + onSelect + ); + this.width = width; + this.height = height; + this.depth = depth; + this.color = color; + } + static parseObject(object: any, onSelect?: () => void): ARObject | undefined { + if (!object || object.type !== CUBE_OBJECT_TYPE) return undefined; + let id = object.id; + let position = parseVector3(object.position); + let render = parseRender(object.behaviours?.render); + let rotation = parseRotation(object.behaviours?.rotation); + let movement = parseMovement(object.behaviours?.movement); + let width = object.width; + let height = object.height; + let depth = object.depth; + let color = object.color; + if ( + typeof id === "string" && + position instanceof Vector3 && + typeof width === "number" && + typeof height === "number" && + typeof depth === "number" && + typeof color === "number" + ) { + return new CubeObject( + id, + position, + width, + height, + depth, + color, + render, + rotation, + movement, + onSelect + ); + } + return undefined; + } +} + +const UI_OBJECT_TYPE = "UIObject"; +export class UIObject extends ARObject { + type = UI_OBJECT_TYPE; + uiJson: any; + constructor( + id: string, + position: Vector3, + uiJson: any, + render?: RenderClass, + rotation?: RotationClass, + movement?: MovementClass, + onSelect?: (object: ARObject) => void + ) { + super( + id, + position, + { + model: new InterfaceModel(uiJson), + render: render, + rotation: rotation, + movement: movement, + }, + onSelect + ); + this.uiJson = uiJson; + } + static parseObject(object: any, onSelect?: () => void): ARObject | undefined { + if (!object || object.type !== UI_OBJECT_TYPE) return undefined; + let id = object.id; + let position = parseVector3(object.position); + let render = parseRender(object.behaviours?.render); + let rotation = parseRotation(object.behaviours?.rotation); + let movement = parseMovement(object.behaviours?.movement); + let uiJson = object.uiJson; + if ( + typeof id === "string" && + position instanceof Vector3 && + uiJson !== undefined + ) { + return new UIObject( + id, + position, + uiJson, + render, + rotation, + movement, + onSelect + ); + } + return undefined; + } +} + +const LIGHT_OBJECT_TYPE = "LightObject"; +export class LightObject extends ARObject { + type = LIGHT_OBJECT_TYPE; + intensity: number; + constructor(id: string, position: Vector3, intensity: number) { + super(id, position, { + model: new LightModel(intensity), + render: new RenderWithinDistance(20), + }); + this.intensity = intensity; + } + static parseObject(object: any): ARObject | undefined { + if (!object || object.type !== LIGHT_OBJECT_TYPE) return undefined; + let id = object.id; + let position = parseVector3(object.position); + let intensity = object.intensity; + if ( + typeof id === "string" && + position instanceof Vector3 && + typeof intensity === "number" + ) { + return new LightObject(id, position, intensity); + } + return undefined; + } +} + diff --git a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx new file mode 100644 index 000000000..20f03e690 --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx @@ -0,0 +1,220 @@ +import { Interactive } from "@react-three/xr"; +import { ARObject } from "./ARObject"; +import { type MutableRefObject, type ReactNode, useRef, useState } from "react"; +import { + AlwaysRender, + FixRotation, + GltfModel, + ImageModel, + InterfaceModel, + LightModel, + OrbitMovement, + PathMovement, + RenderWithinDistance, + RotateAroundY, + RotateToUser, + ShapeModel, + SpringMovement, + TextModel, +} from "./Behaviour"; +import { Mesh, Vector3 } from "three"; +import { useFrame, extend } from "@react-three/fiber"; +import ErrorBoundary from "./ErrorBoundary"; +import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry"; +import GltfComponent from "./model_components/GltfComponent"; +import TextComponent from "./model_components/TextComponent"; +import ImageComponent from "./model_components/ImageComponent"; +import ShapeComponent from "./model_components/ShapeComponent"; +import { useSpring, SpringValue } from "@react-spring/three"; +import LightComponent from "./model_components/LightComponent"; +import InterfaceComponent from "./model_components/InterfaceComponent"; + +extend({ TextGeometry }); + +type Props = { + arObject: ARObject; + getUserPosition: () => Vector3; + setUUID: (uuid: string) => void; + onSelect?: (object: ARObject) => void; + children?: ReactNode; +}; + +type SpringProps = { + position: SpringValue<[number, number, number]>; +}; + +/** + * Component for showing a single AR object. + * Use translatePosition and translateRotation if callibration is required. + */ +export default function ARObjectComponent(props: Props) { + const ref = useRef(null); + const [showComponent, setShowComponent] = useState(false); + const [targetPosition, setTargetPosition] = useState(props.arObject.position); + const spring: SpringProps = useSpring({ + position: vector3ToArray(targetPosition), + }); + const [isHighlighted, setHighlighted] = useState(false); + + useFrame((state, delta) => { + let uuid = ref.current?.uuid; + if (uuid) { + props.setUUID(uuid); + } + if (isHighlighted !== props.arObject.isHighlighted) { + setHighlighted(props.arObject.isHighlighted); + } + let currentPosition = updatePosition( + props.arObject, + ref, + targetPosition, + setTargetPosition + ); + let userPosition = props.getUserPosition(); + handleVisibility( + props.arObject, + currentPosition, + userPosition, + setShowComponent + ); + handleRotation(props.arObject, currentPosition, userPosition, ref, delta); + }); + + function vector3ToArray(vector: Vector3) { + return [vector.x, vector.y, vector.z] as [number, number, number]; + } + + if (!showComponent) return null; + + return ( + }> + { + let onSelect = props.onSelect; + if (onSelect) { + onSelect(props.arObject); + } + }} + > + + + + ); +} + +// Movement + +/** + * Translates relative position to world position. + * @returns World position. + */ +function updatePosition( + arObject: ARObject, + ref: MutableRefObject, + targetPosition: Vector3, + setTargetPosition: React.Dispatch> +) { + let position = arObject.position.clone(); + let movement = arObject.behaviours.movement; + if (movement instanceof PathMovement) { + position = movement.getOffsetPosition(position); + } else if (movement instanceof OrbitMovement) { + position = movement.getOffsetPosition(position); + } else if (movement instanceof SpringMovement) { + if (targetPosition !== arObject.position) { + setTargetPosition(arObject.position); + } + return ref?.current?.position ?? arObject.position; + } + let mesh = ref.current; + if (mesh) { + mesh.position.x = position.x; + mesh.position.y = position.y; + mesh.position.z = position.z; + } + return position; +} + +// Model + +type ModelProps = { + arObject: ARObject; + meshRef: MutableRefObject; + children: ReactNode | undefined; + springPosition: SpringValue<[number, number, number]>; + isHighlighted: boolean; +}; + +function ModelComponent(props: ModelProps) { + let modelClass = props.arObject.behaviours.model; + if (modelClass instanceof GltfModel) { + return ; + } + if (modelClass instanceof ShapeModel) { + return ; + } + if (modelClass instanceof TextModel) { + return ; + } + if (modelClass instanceof ImageModel) { + return ; + } + if (modelClass instanceof InterfaceModel) { + return ; + } + if (modelClass instanceof LightModel) { + return ; + } + return <>; +} + +// Render + +function handleVisibility( + arObject: ARObject, + position: Vector3, + userPosition: Vector3, + setShowComponent: React.Dispatch> +) { + let behaviour = arObject.behaviours.render ?? new RenderWithinDistance(5); + if (behaviour instanceof RenderWithinDistance) { + let distanceVector = new Vector3(0, 0, 0); + let distance = distanceVector.subVectors(position, userPosition).length(); + setShowComponent(distance <= behaviour.distance); + } else if (behaviour instanceof AlwaysRender) { + setShowComponent(true); + } +} + +// Rotation + +function handleRotation( + arObject: ARObject, + position: Vector3, + userPosition: Vector3, + ref: MutableRefObject, + delta: number +) { + let rotation = arObject.behaviours.rotation; + let mesh = ref.current; + if (!mesh) return; + if (rotation instanceof RotateToUser) { + mesh.rotation.y = Math.atan2( + userPosition.x - position.x, + userPosition.z - position.z + ); + } else if (rotation instanceof RotateAroundY) { + mesh.rotation.y += delta; + } else if (rotation instanceof FixRotation) { + mesh.rotation.y = rotation.rotation; + } else { + mesh.rotation.y = 0; + } +} + diff --git a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx new file mode 100644 index 000000000..ff813510f --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx @@ -0,0 +1,413 @@ +import { + BufferGeometry, + Material, + type NormalBufferAttributes, + Vector3, +} from "three"; +import { UIBasicComponent } from "./ui_component/UIComponent"; +import { parseVector3 } from "../calibration_library/Misc"; + +export type Behaviours = { + model: ModelClass; + render?: RenderClass; + rotation?: RotationClass; + movement?: MovementClass; +}; + +export interface Behaviour {} + +// Model + +/** + * Base class for a model behaviour. + */ +export abstract class ModelClass implements Behaviour {} + +/** + * Behaviour for a glTF model. + * + * @param resource Path to glTF file + * @param scale Scale to render model + */ +export class GltfModel implements ModelClass { + resource: string; + scale: Vector3; + callAnimation?: (actionName: string) => void; + constructor(resource: string, scale: Vector3) { + this.resource = resource; + this.scale = scale; + } +} + +/** + * Behaviour for ThreeJS shapes model. + * + * @param geometry ThreeJS geometry + * @param material ThreeJS material + */ +export class ShapeModel implements ModelClass { + geometry: BufferGeometry | undefined; + material: Material | Material[] | undefined; + constructor( + geometry: BufferGeometry | undefined, + material: Material | Material[] | undefined + ) { + this.geometry = geometry; + this.material = material; + } +} + +export class TextModel implements ModelClass { + text: string; + width: number; + constructor(text: string, width: number) { + this.text = text; + this.width = width; + } +} + +export class ImageModel implements ModelClass { + src: string; + width: number; + height: number; + constructor(src: string, width: number, height: number) { + this.src = src; + this.width = width; + this.height = height; + } +} + +export class InterfaceModel implements ModelClass { + uiJson: UIBasicComponent | undefined; + constructor(uiJson: any) { + this.uiJson = uiJson; + } +} + +export class LightModel implements ModelClass { + intensity: number; + constructor(intensity: number) { + this.intensity = intensity; + } +} + +// Render + +const RENDER_DISTANCE = "RenderWithinDistance"; +const RENDER_ALWAYS = "AlwaysRender"; + +export function parseRender(render: any): RenderClass | undefined { + if (!render) return undefined; + switch (render.type) { + case RENDER_ALWAYS: { + return new AlwaysRender(); + } + case RENDER_DISTANCE: { + let distance = 5; + if (typeof render.distance === "number") { + distance = render.distance as number; + } + return new RenderWithinDistance(distance); + } + } + return undefined; +} + +export abstract class RenderClass implements Behaviour { + type: string = ""; +} + +export class RenderWithinDistance implements RenderClass { + type = RENDER_DISTANCE; + distance: number; + constructor(distance: number) { + this.distance = distance; + } +} + +export class AlwaysRender implements RenderClass { + type = RENDER_ALWAYS; +} + +// Rotation + +const ROTATION_USER = "RotateToUser"; +const ROTATION_Y = "RotateAroundY"; +const ROTATION_FIX = "FixRotation"; + +export function parseRotation(rotation: any): RotationClass | undefined { + if (!rotation) return undefined; + switch (rotation?.type) { + case ROTATION_USER: { + return new RotateToUser(); + } + case ROTATION_Y: { + return new RotateAroundY(); + } + case ROTATION_FIX: { + let angle = 0; + if (typeof rotation.rotation === "number") { + angle = rotation.rotation as number; + } + return new FixRotation(angle); + } + } + return undefined; +} + +/** + * Base class for a rotation behaviour. + */ +export abstract class RotationClass implements Behaviour { + type: string = ""; +} + +/** + * Behaviour where object will always rotate to the user. + */ +export class RotateToUser implements RotationClass { + type = ROTATION_USER; +} + +/** + * Behaviour where object will keep spinning around the y-axis. + */ +export class RotateAroundY implements RotationClass { + type = ROTATION_Y; +} + +/** + * Behaviour where object will stay in a fixed rotation. + */ +export class FixRotation implements RotationClass { + type = ROTATION_FIX; + rotation: number; + constructor(radians: number) { + this.rotation = radians; + } +} + +// Movement + +const MOVEMENT_PATH = "PathMovement"; +const MOVEMENT_ORBIT = "OrbitMovement"; +const MOVEMENT_SPRING = "SpringMovement"; + +export function parseMovement(movement: any, getCurrentTime?: () => number) { + if (!movement) return undefined; + switch (movement.type) { + case MOVEMENT_PATH: { + let startTime = movement.startTime; + let pathItems = movement.path; + if ( + (startTime === undefined || typeof startTime === "number") && + Array.isArray(pathItems) + ) { + let parsedPathItems = parsePathItems(pathItems); + return new PathMovement(parsedPathItems, startTime, getCurrentTime); + } + break; + } + case MOVEMENT_ORBIT: { + let radius = movement.radius; + let duration = movement.duration; + let startTime = movement.startTime; + if ( + typeof radius === "number" && + typeof duration === "number" && + (startTime === undefined || typeof startTime === "number") + ) { + return new OrbitMovement(radius, duration, startTime, getCurrentTime); + } + break; + } + case MOVEMENT_SPRING: + return new SpringMovement(); + } + return undefined; +} + +/** + * Base class for a movement behaviour. + * + * @param startTime Reference time for the start of movement, for syncing + */ +export abstract class MovementClass implements Behaviour { + type: string = ""; +} + +/** + * Defines movement in a straight line for a path. + */ +export type PathItem = { + start: Vector3; + end: Vector3; + duration: number; + style: MovementStyle; +}; + +function parsePathItems(path: any[]) { + let result: PathItem[] = []; + for (let i = 0; i < path.length; i++) { + let item = path[i]; + let start = parseVector3(item.start); + let end = parseVector3(item.end); + let duration = item.duration; + if ( + start instanceof Vector3 && + end instanceof Vector3 && + (duration === undefined || typeof duration === "number") + ) { + let movementStyle = MovementStyle.Linear; + if (item.style === MovementStyle.FastToSlow) { + movementStyle = MovementStyle.FastToSlow; + } else if (item.style === MovementStyle.SlowToFast) { + movementStyle = MovementStyle.SlowToFast; + } + result.push({ + start: start, + end: end, + duration: duration, + style: movementStyle, + }); + } + } + return result; +} + +export enum MovementStyle { + Linear, + FastToSlow, + SlowToFast, +} + +/** + * Behaviour where the object moves in the defined path. + * Cycles through the path array repeatedly. + * + * @param path Array of path items defining movement of object + * @param startTime Reference time for the start of movement, for syncing + */ +export class PathMovement extends MovementClass { + type = MOVEMENT_PATH; + path: PathItem[]; + totalDuration: number; + startTime: number; + getCurrentTime: () => number; + constructor( + path: PathItem[], + startTime?: number, + getCurrentTime?: () => number + ) { + super(); + this.path = path; + this.totalDuration = 0; + if (startTime) { + this.startTime = startTime; + } else { + this.startTime = new Date().getTime(); + } + if (getCurrentTime) { + this.getCurrentTime = getCurrentTime; + } else { + this.getCurrentTime = () => new Date().getTime(); + } + path.forEach((item) => { + this.totalDuration += item.duration; + }); + } + + public getOffsetPosition(position: Vector3) { + let currentFrame = + (this.getCurrentTime() - this.startTime) % (this.totalDuration * 1000); + let currentMovementIndex = 0; + while (currentFrame > 0 && currentMovementIndex < this.path.length) { + let currentItem = this.path[currentMovementIndex]; + if (currentFrame >= currentItem.duration * 1000) { + currentFrame -= currentItem.duration * 1000; + currentMovementIndex++; + continue; + } + let ratio = Math.min( + Math.max(0, currentFrame / (currentItem.duration * 1000)), + 1 + ); + switch (currentItem.style) { + case MovementStyle.SlowToFast: { + ratio = Math.pow(ratio, 5); + break; + } + case MovementStyle.FastToSlow: { + let negative = 1 - ratio; + negative = Math.pow(negative, 5); + ratio = 1 - negative; + break; + } + } + let x = + position.x + + currentItem.start.x + + ratio * (currentItem.end.x - currentItem.start.x); + let y = + position.y + + currentItem.start.y + + ratio * (currentItem.end.y - currentItem.start.y); + let z = + position.z + + currentItem.start.z + + ratio * (currentItem.end.z - currentItem.start.z); + return new Vector3(x, y, z); + } + return position; + } +} + +/** + * Behaviour where the object orbits around its position at a specified radius. + * + * @param radius Radius of orbit + * @param duration Duration of a single orbit + * @param startTime Reference time for the start of movement, for syncing + */ +export class OrbitMovement extends MovementClass { + type = MOVEMENT_ORBIT; + radius: number; + duration: number; + startTime: number; + getCurrentTime: () => number; + constructor( + radius: number, + duration: number, + startTime?: number, + getCurrentTime?: () => number + ) { + super(); + this.radius = radius; + this.duration = duration; + if (startTime) { + this.startTime = startTime; + } else { + this.startTime = new Date().getTime(); + } + if (getCurrentTime) { + this.getCurrentTime = getCurrentTime; + } else { + this.getCurrentTime = () => new Date().getTime(); + } + } + public getOffsetPosition(position: Vector3) { + let currentFrame = + (this.getCurrentTime() - this.startTime) % (this.duration * 1000); + let ratio = Math.min(Math.max(0, currentFrame / (this.duration * 1000)), 1); + let angle = ratio * Math.PI * 2; + let x = position.x + this.radius * Math.sin(angle); + let y = position.y; + let z = position.z + this.radius * Math.cos(angle); + return new Vector3(x, y, z); + } +} + +export class SpringMovement extends MovementClass { + type = MOVEMENT_SPRING; +} + diff --git a/src/bundles/ar/libraries/object_state_library/ErrorBoundary.tsx b/src/bundles/ar/libraries/object_state_library/ErrorBoundary.tsx new file mode 100644 index 000000000..bd034af66 --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/ErrorBoundary.tsx @@ -0,0 +1,28 @@ +import { Component, type ReactNode } from "react"; + +interface Props { + fallback: ReactNode; + children?: ReactNode; +} + +interface State { + hasError: boolean; +} + +export default class ErrorBoundary extends Component { + public state: State = { + hasError: false, + }; + + public static getDerivedStateFromError(_: Error): State { + return { hasError: true }; + } + + public render() { + if (this.state.hasError) { + return this.props.fallback; + } + return this.props.children; + } +} + diff --git a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx new file mode 100644 index 000000000..aa15fba91 --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx @@ -0,0 +1,67 @@ +import { useFrame } from "@react-three/fiber"; +import { + type MutableRefObject, + type ReactNode, + Suspense, + useEffect, + useRef, + useState, +} from "react"; +import * as SkeletonUtils from "three/examples/jsm/utils/SkeletonUtils"; +import { Object3D, type Object3DEventMap, AnimationMixer } from "three"; +import { GltfModel } from "../Behaviour"; +import { ARObject } from "../ARObject"; +import { SpringValue, animated } from "@react-spring/three"; +import { useGLTF } from "@react-three/drei"; + +type GltfProps = { + gltfModel: GltfModel; + arObject: ARObject; + meshRef: MutableRefObject; + springPosition: SpringValue<[number, number, number]>; + children: ReactNode | undefined; +}; + +export default function GltfComponent(props: GltfProps) { + const model = useGLTF(props.gltfModel.resource); + const [scene, setScene] = useState>(); + const mixer = useRef(); + + useEffect(() => { + let clonedScene = SkeletonUtils.clone(model.scene); + setScene(clonedScene); + mixer.current = new AnimationMixer(clonedScene); + }, [model.scene]); + + useEffect(() => { + if (model.animations.length > 0) { + props.gltfModel.callAnimation = (actionName: string) => { + let selectedAction = model.animations.find((item) => { + return item.name === actionName; + }); + if (!selectedAction) return; + mixer.current?.stopAllAction(); + let action = mixer.current?.clipAction(selectedAction); + action?.play(); + }; + } + }, [props.gltfModel, model.animations, model.animations.length]); + + useFrame((state, delta) => { + mixer.current?.update(delta); + }); + + return ( + + + {scene ? : null} + + {props.children} + + ); +} + diff --git a/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx new file mode 100644 index 000000000..01d92a291 --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx @@ -0,0 +1,22 @@ +import { type MutableRefObject } from "react"; +import { ImageModel } from "../Behaviour"; +import { Image } from "@react-three/drei"; +import { SpringValue, animated } from "@react-spring/three"; + +type ImageProps = { + imageModel: ImageModel; + meshRef: MutableRefObject; + springPosition: SpringValue<[number, number, number]>; +}; + +export default function ImageComponent(props: ImageProps) { + return ( + + + + ); +} + diff --git a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx new file mode 100644 index 000000000..d343a9b31 --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx @@ -0,0 +1,176 @@ +import { SpringValue, animated } from "@react-spring/three"; +import { InterfaceModel as InterfaceModel } from "../Behaviour"; +import { type MutableRefObject } from "react"; +import { UIBasicComponent } from "../ui_component/UIComponent"; +import UIRowComponent, { + VerticalAlignment, +} from "../ui_component/UIRowComponent"; +import UIColumnComponent, { + HorizontalAlignment, +} from "../ui_component/UIColumnComponent"; +import UITextComponent from "../ui_component/UITextComponent"; +import { Color, Vector3 } from "three"; +import UIImageComponent from "../ui_component/UIImageComponent"; + +type InterfaceProps = { + interfaceModel: InterfaceModel; + meshRef: MutableRefObject; + springPosition: SpringValue<[number, number, number]>; +}; + +export default function InterfaceComponent(props: InterfaceProps) { + return ( + + {parseJsonInterface(props.interfaceModel.uiJson)?.getComponent( + new Vector3(0), + () => {} + )} + + ); +} + +export function parseJsonInterface(uiJson: any) { + if (!uiJson) { + return undefined; + } + let componentType = uiJson.type as string; + let id = uiJson.id; + let paddingLeft = uiJson.paddingLeft; + let paddingRight = uiJson.paddingRight; + let paddingTop = uiJson.paddingTop; + let paddingBottom = uiJson.paddingBottom; + if ( + typeof id !== "string" || + typeof paddingLeft !== "number" || + typeof paddingRight !== "number" || + typeof paddingTop !== "number" || + typeof paddingBottom !== "number" + ) { + return; + } + switch (componentType) { + case "UIColumnComponent": { + let horizontalAlignmentIndex = uiJson.horizontalAlignment; + let horizontalAlignment = HorizontalAlignment.Left; + if (typeof horizontalAlignmentIndex === "number") { + let parsedIndex = Math.min(Math.max(0, horizontalAlignmentIndex), 2); + horizontalAlignment = parsedIndex; + } + let children: UIBasicComponent[] = []; + let jsonChildren = uiJson.children; + if (Array.isArray(jsonChildren)) { + jsonChildren.forEach((jsonChild) => { + let child = parseJsonInterface(jsonChild); + if (child) { + children.push(child); + } + }); + } + let background = uiJson.background; + if (typeof background != "number") { + background = undefined; + } + return new UIColumnComponent({ + horizontalAlignment: horizontalAlignment, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + children: children, + background: background, + id: id, + }); + } + case "UIRowComponent": { + let verticalAlignmentIndex = uiJson.verticalAlignment; + let verticalAlignment = VerticalAlignment.Top; + if (typeof verticalAlignmentIndex === "number") { + let parsedIndex = Math.min(Math.max(0, verticalAlignmentIndex), 2); + verticalAlignment = parsedIndex; + } + let children: UIBasicComponent[] = []; + let jsonChildren = uiJson.children; + if (Array.isArray(jsonChildren)) { + jsonChildren.forEach((jsonChild) => { + let child = parseJsonInterface(jsonChild); + if (child) { + children.push(child); + } + }); + } + let background = uiJson.background; + if (typeof background != "number") { + background = undefined; + } + return new UIRowComponent({ + verticalAlignment: verticalAlignment, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + children: children, + background: background, + id: id, + }); + } + case "UITextComponent": { + let text = uiJson.text; + let textSize = uiJson.textSize; + let textWidth = uiJson.textWidth; + let textAlign = uiJson.textAlign; + let color = uiJson.color; + if ( + typeof text === "string" && + typeof textSize === "number" && + typeof textWidth === "number" && + typeof color === "number" + ) { + return new UITextComponent({ + text: text, + textSize: textSize, + textWidth: textWidth, + textAlign: textAlign, + color: new Color(color), + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + id: id, + }); + } + break; + } + case "UIImageComponent": { + let src = uiJson.src; + let imageWidth = uiJson.imageWidth; + let imageHeight = uiJson.imageHeight; + if ( + typeof src === "string" && + typeof imageWidth === "number" && + typeof imageHeight === "number" + ) { + return new UIImageComponent({ + src: src, + imageWidth: imageWidth, + imageHeight: imageHeight, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + id: id, + }); + } + break; + } + } + return undefined; +} + diff --git a/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx new file mode 100644 index 000000000..f93c041a7 --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx @@ -0,0 +1,21 @@ +import { type MutableRefObject, type ReactNode } from "react"; +import { ARObject } from "../ARObject"; +import { LightModel } from "../Behaviour"; +import { SpringValue, animated } from "@react-spring/three"; + +type LightProps = { + lightModel: LightModel; + arObject: ARObject; + meshRef: MutableRefObject; + springPosition: SpringValue<[number, number, number]>; + children: ReactNode | undefined; +}; + +export default function LightComponent(props: LightProps) { + return ( + + + + ); +} + diff --git a/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx new file mode 100644 index 000000000..d7286941d --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx @@ -0,0 +1,35 @@ +import { type MutableRefObject, type ReactNode } from "react"; +import { ShapeModel } from "../Behaviour"; +import { SpringValue, animated } from "@react-spring/three"; +import { Outlines } from "@react-three/drei"; +import { Color } from "three"; + +type ShapeProps = { + shapeModel: ShapeModel; + meshRef: MutableRefObject; + springPosition: SpringValue<[number, number, number]>; + isHighlighted: boolean; + children: ReactNode | undefined; +}; + +export default function ShapeComponent(props: ShapeProps) { + return ( + + + + + + ); +} + diff --git a/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx new file mode 100644 index 000000000..5fdf5d989 --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx @@ -0,0 +1,52 @@ +import { type MutableRefObject, type ReactNode, useRef, useState } from "react"; +import { TextModel } from "../Behaviour"; +import { Mesh } from "three"; +import { Text } from "@react-three/drei"; +import { SpringValue, animated } from "@react-spring/three"; + +type TextProps = { + textModel: TextModel; + meshRef: MutableRefObject; + springPosition: SpringValue<[number, number, number]>; + children: ReactNode | undefined; +}; + +export default function TextComponent(props: TextProps) { + let textRef = useRef(null); + let [height, setHeight] = useState(0); + + function getBoxHeight() { + if (height > 0) return; + if (textRef.current) { + let geometry = textRef.current.geometry; + geometry.computeBoundingBox(); + if (geometry.boundingBox) { + let minY = geometry.boundingBox.min.y; + let maxY = geometry.boundingBox.max.y; + let newHeight = maxY - minY + 0.05; + if (newHeight === Infinity) return; + setHeight(newHeight); + } + } + } + + return ( + + + + + {props.textModel.text} + + + ); +} + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx new file mode 100644 index 000000000..3c62aab3a --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -0,0 +1,111 @@ +import { type ReactNode, useState } from "react"; +import { + UIBasicComponent, + LayoutComponent, + type PaddingType, +} from "./UIComponent"; +import { Color, Vector3 } from "three"; + +type UIColumnProps = { + children?: UIBasicComponent[]; + horizontalAlignment?: HorizontalAlignment; + padding?: number | PaddingType; + background?: Color; + id?: string; +}; + +export default class UIColumnComponent extends LayoutComponent { + horizontalAlignment: HorizontalAlignment; + background: Color; + constructor(props: UIColumnProps) { + super(props.padding, props.id); + if (props.background) { + this.background = props.background; + } else { + this.background = new Color(0xffffff); + } + if (props.children) { + this.children = props.children; + } + if (props.horizontalAlignment !== undefined) { + this.horizontalAlignment = props.horizontalAlignment; + } else { + this.horizontalAlignment = HorizontalAlignment.Center; + } + this.calculateDimensions(); + } + getWidth = () => { + let width = this.paddingLeft + this.paddingRight; + let maxChildWidth = 0; + this.children.forEach((item) => { + item.calculateDimensions(); + maxChildWidth = Math.max(maxChildWidth, item.width); + }); + return width + maxChildWidth; + }; + getHeight = () => { + let height = this.paddingTop + this.paddingBottom; + this.children.forEach((item) => { + item.calculateDimensions(); + height += item.height; + }); + return height; + }; + getComponent = (position: Vector3, updateParent: () => void) => { + return ColumnUIComponent(this, position, updateParent); + }; +} + +function ColumnUIComponent( + component: UIColumnComponent, + position: Vector3, + updateParent: () => void +) { + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + + function updateSize() { + setHeight(component.height); + setWidth(component.width); + updateParent(); + } + + function ChildrenComponents() { + let children: ReactNode[] = []; + let currentYPosition = -height / 2 + component.paddingTop; + for (let i = 0; i < component.children.length; i++) { + let child = component.children[i]; + let relativeYPosition = currentYPosition + child.height / 2; + currentYPosition += child.height; + let relativeXPosition = 0; + if (component.horizontalAlignment === HorizontalAlignment.Left) { + relativeXPosition = -(width - child.width) / 2 + component.paddingLeft; + } else if (component.horizontalAlignment === HorizontalAlignment.Right) { + relativeXPosition = (width - child.width) / 2 - component.paddingRight; + } + let childPosition = new Vector3(relativeXPosition, -relativeYPosition, 0); + children.push( + + {child.getComponent(childPosition, updateSize)} + + ); + } + return {children}; + } + return ( + + + + + + + + ); +} + +export enum HorizontalAlignment { + Left, + Center, + Right, +} + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx new file mode 100644 index 000000000..713d0ee91 --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx @@ -0,0 +1,107 @@ +import { useEffect } from "react"; +import { Vector3 } from "three"; +import uniqid from "uniqid"; + +type UIComponentProps = { + position: Vector3; + children: LayoutComponent; +}; + +export default function UIComponent(props: UIComponentProps) { + useEffect(() => { + props.children.calculateDimensions(); + }, [props.children]); + return {props.children.getComponent(props.position, () => {})}; +} + +export type PaddingType = { + paddingLeft?: number; + paddingRight?: number; + paddingTop?: number; + paddingBottom?: number; +}; + +export class UIBasicComponent { + type: string; + paddingLeft = 0; + paddingRight = 0; + paddingTop = 0; + paddingBottom = 0; + height = 0; + width = 0; + id = ""; + layer = 1; + parent?: UIBasicComponent = undefined; + constructor(padding?: number | PaddingType, id?: string) { + this.type = this.constructor.name; + if (padding) { + if (typeof padding === "number") { + this.paddingLeft = padding; + this.paddingRight = padding; + this.paddingTop = padding; + this.paddingBottom = padding; + } else { + if (padding.paddingLeft) { + this.paddingLeft = padding.paddingLeft; + } + if (padding.paddingRight) { + this.paddingRight = padding.paddingRight; + } + if (padding.paddingTop) { + this.paddingTop = padding.paddingTop; + } + if (padding.paddingBottom) { + this.paddingBottom = padding.paddingBottom; + } + } + } + if (id) { + this.id = id; + } else { + this.id = uniqid(); + } + } + toJSON = () => { + let object = Object.assign({}, this) as any; + delete object.height; + delete object.width; + delete object.layer; + delete object.parent; + return object; + }; + calculateDimensions = () => { + this.calculateLayer(); + let newHeight = this.getHeight(); + let newWidth = this.getWidth(); + if (this.height !== newHeight || this.width !== newWidth) { + this.height = newHeight; + this.width = newWidth; + this.parent?.calculateDimensions(); + } + }; + getWidth = () => { + return this.paddingLeft + this.paddingRight; + }; + getHeight = () => { + return this.paddingTop + this.paddingBottom; + }; + calculateLayer = () => {}; + getComponent = (position: Vector3, updateParent: () => void) => { + return ; + }; +} + +export class LayoutComponent extends UIBasicComponent { + children: UIBasicComponent[] = []; + calculateLayer = () => { + this.layer = 1; + this.children.forEach((child) => { + if (child instanceof LayoutComponent && this.layer <= child.layer) { + this.layer = child.layer + 1; + } + }); + this.children.forEach((child) => { + child.parent = this; + }); + }; +} diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx new file mode 100644 index 000000000..c4488861b --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx @@ -0,0 +1,41 @@ +import { Vector3 } from "three"; +import { UIBasicComponent, type PaddingType } from "./UIComponent"; +import { Image } from "@react-three/drei"; + +type UIImageProps = { + src: string; + imageWidth: number; + imageHeight: number; + padding?: number | PaddingType; + id?: string; +}; + +export default class UIImageComponent extends UIBasicComponent { + src: string; + imageWidth: number; + imageHeight: number; + constructor(props: UIImageProps) { + super(props.padding, props.id); + this.src = props.src; + this.imageWidth = props.imageWidth; + this.imageHeight = props.imageHeight; + } + getWidth = () => { + return this.imageWidth + this.paddingTop + this.paddingBottom; + }; + getHeight = () => { + return this.imageHeight + this.paddingTop + this.paddingBottom; + }; + getComponent = (position: Vector3, updateParent: () => void) => { + return ImageUIComponent(this, position); + }; +} + +function ImageUIComponent(component: UIImageComponent, position: Vector3) { + return ( + + + + ); +} + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx new file mode 100644 index 000000000..fe821031b --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx @@ -0,0 +1,112 @@ +import { type ReactNode, useState } from "react"; +import { + UIBasicComponent, + LayoutComponent, + type PaddingType, +} from "./UIComponent"; +import { Color, Vector3 } from "three"; + +type UIRowProps = { + children?: UIBasicComponent[]; + verticalAlignment?: VerticalAlignment; + padding?: number | PaddingType; + background?: Color; + id?: string; +}; + +export default class UIRowComponent extends LayoutComponent { + verticalAlignment: VerticalAlignment; + background: Color; + constructor(props: UIRowProps) { + super(props.padding, props.id); + if (props.background) { + this.background = props.background; + } else { + this.background = new Color(0xffffff); + } + if (props.children) { + this.children = props.children; + } + if (props.verticalAlignment !== undefined) { + this.verticalAlignment = props.verticalAlignment; + } else { + this.verticalAlignment = VerticalAlignment.Middle; + } + this.calculateDimensions(); + } + getWidth = () => { + let width = this.paddingLeft + this.paddingRight; + this.children.forEach((item) => { + item.calculateDimensions(); + width += item.width; + }); + return width; + }; + getHeight = () => { + let height = this.paddingTop + this.paddingBottom; + let maxChildHeight = 0; + this.children.forEach((item) => { + item.calculateDimensions(); + maxChildHeight = Math.max(maxChildHeight, item.height); + }); + return height + maxChildHeight; + }; + getComponent = (position: Vector3, updateParent: () => void) => { + return RowUIComponent(this, position, updateParent); + }; +} + +function RowUIComponent( + component: UIRowComponent, + position: Vector3, + updateParent: () => void +) { + const [width, setWidth] = useState(component.width); + const [height, setHeight] = useState(component.height); + + function updateSize() { + setHeight(component.height); + setWidth(component.width); + updateParent(); + } + + function ChildrenComponents() { + let children: ReactNode[] = []; + let currentXPosition = -width / 2 + component.paddingLeft; + for (let i = 0; i < component.children.length; i++) { + let child = component.children[i]; + let relativeXPosition = currentXPosition + child.width / 2; + currentXPosition += child.width; + let relativeYPosition = 0; + if (component.verticalAlignment === VerticalAlignment.Top) { + relativeYPosition = (height - child.height) / 2 - component.paddingTop; + } else if (component.verticalAlignment === VerticalAlignment.Bottom) { + relativeYPosition = + -(height - child.height) / 2 + component.paddingBottom; + } + let childPosition = new Vector3(relativeXPosition, relativeYPosition, 0); + children.push( + + {child.getComponent(childPosition, updateSize)} + + ); + } + return {children}; + } + return ( + + + + + + + + ); +} + +export enum VerticalAlignment { + Top, + Middle, + Bottom, +} + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx new file mode 100644 index 000000000..4861695bf --- /dev/null +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -0,0 +1,128 @@ +import { Color, Mesh, Vector3 } from "three"; +import { UIBasicComponent, type PaddingType } from "./UIComponent"; +import { Text } from "@react-three/drei"; +import { useEffect, useRef, useState } from "react"; + +type UITextProps = { + text: string; + textSize: number; + textWidth: number; + textAlign?: number; + color?: Color; + padding?: number | PaddingType; + id?: string; +}; + +export default class UITextComponent extends UIBasicComponent { + text: string; + textSize: number; + textWidth: number; + textHeight = 0; + textAlign: number; + color: Color; + constructor(props: UITextProps) { + super(props.padding, props.id); + this.text = props.text; + this.textSize = props.textSize; + this.textWidth = props.textWidth; + this.textAlign = props.textAlign ?? 0; + this.color = props.color ?? new Color(0x000000); + } + getWidth = () => { + return this.textWidth + this.paddingTop + this.paddingBottom; + }; + getHeight = () => { + return this.textHeight + this.paddingTop + this.paddingBottom; + }; + updateHeight = (newHeight: number) => { + if (newHeight === 0) { + return; + } + this.textHeight = newHeight; + this.height = this.textHeight + this.paddingTop + this.paddingBottom; + let parent = this.parent; + while (parent) { + parent.calculateDimensions(); + parent = parent.parent; + } + }; + getComponent = (position: Vector3, updateParent: () => void) => { + return TextUIComponent(this, position, updateParent); + }; +} + +function TextUIComponent( + component: UITextComponent, + position: Vector3, + updateParent: () => void +) { + const [offsetX, setOffsetX] = useState(0); + let ref = useRef(null); + + useEffect(() => { + if (ref) { + getSize(); + } + }, [ref]); + + async function getSize() { + if (ref.current) { + let geometry = ref.current.geometry; + geometry.computeBoundingBox(); + if (geometry.boundingBox) { + let minY = geometry.boundingBox.min.y; + let maxY = geometry.boundingBox.max.y; + let textHeight = maxY - minY; + let minX = geometry.boundingBox.min.x; + let maxX = geometry.boundingBox.max.x; + let textWidth = maxX - minX; + if (Number.isFinite(textHeight) && Number.isFinite(textWidth)) { + component.updateHeight(textHeight); + updateParent(); + let offsetMagnitude = (component.textWidth - textWidth) / 2; + if (offsetMagnitude <= 0) return; + if (component.textAlign == 0) { + setOffsetX(-offsetMagnitude); + } else if (component.textAlign == 2) { + setOffsetX(offsetMagnitude); + } + } else { + setTimeout(() => { + getSize(); + }, 100); + } + } else { + setTimeout(() => { + getSize(); + }, 100); + } + } + } + + function getTextAlign(index: number) { + switch (index) { + case 1: + return "left"; + case 2: + return "right"; + } + return "center"; + } + + return ( + + + {component.text} + + + ); +} + diff --git a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx new file mode 100644 index 000000000..5a9654a7c --- /dev/null +++ b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx @@ -0,0 +1,129 @@ +import { + type ReactNode, + createContext, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { XR } from "@react-three/xr"; +import { Canvas } from "@react-three/fiber"; + +type ContextType = { + overlayRef: React.RefObject | null; + domOverlay: XRDOMOverlayInit | undefined; + setStates: (arState: ReactNode, overlayState: ReactNode) => void; + component: ReactNode; +}; + +const Context = createContext({ + overlayRef: null, + domOverlay: undefined, + setStates: () => {}, + component: <>, +}); + +type Props = { + children: ReactNode; +}; + +/** + * Parent component with screen state context. + * Used for managing ar content and its corresponding overlay. + * + * Steps to use: + * 1. Add 'domOverlay' to sessionInit of AR toggle. + * 2. Add 'component' as a child of this ScreenStateContext. + * 3. Call 'setStates' to set all possible screen states. + * 4. The first screen state in the map is used by default. + * 5. To switch screen states, call 'setSelectedState' with the new state's key. + * 6. Use 'overlayRef' to interact with the existing overlay. + * + * Components within it can call 'useScreenState' to obtain this context. + */ +export function ScreenStateContext(props: Props) { + const [arState, setArState] = useState(); + const [overlayState, setOverlayState] = useState(<>); + + const overlayRef = useRef(null); + const [domOverlay, setDomOverlay] = useState(); + const defaultComponent = ( + <> + + + <> + + +
+ + ); + const [component, setComponent] = useState(defaultComponent); + + useEffect(() => { + if (!overlayRef) return; + let overlay = overlayRef.current; + if (overlay) { + setDomOverlay(overlay); + } else { + setDomOverlay(undefined); + } + }, [overlayRef, component]); + + useEffect(() => { + setComponent( + <> + + {arState} + +
+ {overlayState} +
+ + ); + }, [arState, overlayState]); + + function setStates( + newArState: ReactNode | undefined, + newOverlayState: ReactNode | undefined + ) { + if (newArState) { + setArState(newArState); + } else { + setArState(); + } + if (newOverlayState) { + setOverlayState(newOverlayState); + } else { + setOverlayState(<>); + } + } + + return ( + + {props.children} + + ); +} + +export function useScreenState() { + return useContext(Context); +} + diff --git a/src/tabs/AugmentedReality/index.tsx b/src/tabs/AugmentedReality/index.tsx new file mode 100644 index 000000000..34eb95b09 --- /dev/null +++ b/src/tabs/AugmentedReality/index.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +/** + * + * @author + * @author + */ + +/** + * React Component props for the Tab. + */ +type Props = { + children?: never; + className?: never; + context?: any; +}; + +/** + * React Component state for the Tab. + */ +type State = { + counter: number; +}; + +/** + * The main React Component of the Tab. + */ +class Repeat extends React.Component { + constructor(props) { + super(props); + this.state = { + counter: 0, + }; + } + + public render() { + const { counter } = this.state; + return ( +
This is spawned from the repeat package. Counter is {counter}
+ ); + } +} + +export default { + /** + * This function will be called to determine if the component will be + * rendered. Currently spawns when the result in the REPL is "test". + * @param {DebuggerContext} context + * @returns {boolean} + */ + toSpawn: (context: any) => context.result.value === 'test', + + /** + * This function will be called to render the module tab in the side contents + * on Source Academy frontend. + * @param {DebuggerContext} context + */ + body: (context: any) => , + + /** + * The Tab's icon tooltip in the side contents on Source Academy frontend. + */ + label: 'Sample Tab', + + /** + * BlueprintJS IconName element's name, used to render the icon which will be + * displayed in the side contents panel. + * @see https://blueprintjs.com/docs/#icons + */ + iconName: 'build', +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 32ef966ac..b8883edc7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -428,6 +428,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.11.2", "@babel/runtime@^7.17.8": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.20.7", "@babel/template@^7.3.3": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" @@ -1062,6 +1069,11 @@ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60" integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA== +"@mediapipe/tasks-vision@0.10.8": + version "0.10.8" + resolved "https://registry.yarnpkg.com/@mediapipe/tasks-vision/-/tasks-vision-0.10.8.tgz#a78e137018a19933b7a1d0e887d553d4ab833d10" + integrity sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1104,6 +1116,106 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== +"@react-spring/animated@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.6.1.tgz#ccc626d847cbe346f5f8815d0928183c647eb425" + integrity sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ== + dependencies: + "@react-spring/shared" "~9.6.1" + "@react-spring/types" "~9.6.1" + +"@react-spring/core@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.6.1.tgz#ebe07c20682b360b06af116ea24e2b609e778c10" + integrity sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ== + dependencies: + "@react-spring/animated" "~9.6.1" + "@react-spring/rafz" "~9.6.1" + "@react-spring/shared" "~9.6.1" + "@react-spring/types" "~9.6.1" + +"@react-spring/rafz@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.6.1.tgz#d71aafb92b78b24e4ff84639f52745afc285c38d" + integrity sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ== + +"@react-spring/shared@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.6.1.tgz#4e2e4296910656c02bd9fd54c559702bc836ac4e" + integrity sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw== + dependencies: + "@react-spring/rafz" "~9.6.1" + "@react-spring/types" "~9.6.1" + +"@react-spring/three@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/three/-/three-9.6.1.tgz#095fcd1dc6509127c33c14486d88289b89baeb9d" + integrity sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA== + dependencies: + "@react-spring/animated" "~9.6.1" + "@react-spring/core" "~9.6.1" + "@react-spring/shared" "~9.6.1" + "@react-spring/types" "~9.6.1" + +"@react-spring/types@~9.6.1": + version "9.6.1" + resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.6.1.tgz#913d3a68c5cbc1124fdb18eff919432f7b6abdde" + integrity sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q== + +"@react-three/drei@^9.97.0": + version "9.97.0" + resolved "https://registry.yarnpkg.com/@react-three/drei/-/drei-9.97.0.tgz#7a52643143181a69665801cb29d236b9a9612f06" + integrity sha512-D7qNQDbEoYHMlJU0tr/f/rCmw1TQmI+INEGGrfoAEA+sIvqLw9wLCra+1+t22lEPCpdpmVSMmdv6PynMcv5tCQ== + dependencies: + "@babel/runtime" "^7.11.2" + "@mediapipe/tasks-vision" "0.10.8" + "@react-spring/three" "~9.6.1" + "@use-gesture/react" "^10.2.24" + camera-controls "^2.4.2" + cross-env "^7.0.3" + detect-gpu "^5.0.28" + glsl-noise "^0.0.0" + maath "^0.10.7" + meshline "^3.1.6" + react-composer "^5.0.3" + react-merge-refs "^1.1.0" + stats-gl "^2.0.0" + stats.js "^0.17.0" + suspend-react "^0.1.3" + three-mesh-bvh "^0.7.0" + three-stdlib "^2.29.4" + troika-three-text "^0.47.2" + tunnel-rat "^0.1.2" + utility-types "^3.10.0" + uuid "^9.0.1" + zustand "^3.7.1" + +"@react-three/fiber@^8.15.16": + version "8.15.16" + resolved "https://registry.yarnpkg.com/@react-three/fiber/-/fiber-8.15.16.tgz#eadaa9432dd55a7d7334c42880470c7ac3c30e83" + integrity sha512-4f47K9e2mP8W/guNtu3e2J/Nt6GwKTsX/YP2dktPZRcpHYEsqfXCO8kSfvVMb+lQ8wR0HoFzggqdnGuhZaui0g== + dependencies: + "@babel/runtime" "^7.17.8" + "@types/react-reconciler" "^0.26.7" + "@types/webxr" "*" + base64-js "^1.5.1" + buffer "^6.0.3" + its-fine "^1.0.6" + react-reconciler "^0.27.0" + react-use-measure "^2.1.1" + scheduler "^0.21.0" + suspend-react "^0.1.3" + zustand "^3.7.1" + +"@react-three/xr@^5.7.1": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@react-three/xr/-/xr-5.7.1.tgz#ebba10f08047691f1ee18aa4bfbc1106cfebf811" + integrity sha512-GaRUSA+lE8VJF/NrXq7QQByZ4UGHbQQ4rs3QCphZs9fVidK86hGrMOQ0kL79gZc5pa3V5uFGlOhNcUdsTYE3Bg== + dependencies: + "@types/webxr" "*" + three-stdlib "^2.21.1" + zustand "^3.7.1" + "@sinclair/typebox@^0.25.16": version "0.25.23" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.23.tgz#1c15b0d2b872d89cc0f47c7243eacb447df8b8bd" @@ -1188,6 +1300,11 @@ resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.2.tgz#6495303f049689ce936ed328a3e5ede9c51408ee" integrity sha512-Rt4IC1T7xkCWa0OG1oSsPa0iqnxlDeQqKXZAHrQGLb7wFGncWm85MaxKUjAGejOrUynOgWlFi4c6S6IyJwoK4g== +"@types/draco3d@^1.4.0": + version "1.4.9" + resolved "https://registry.yarnpkg.com/@types/draco3d/-/draco3d-1.4.9.tgz#eb3eb7c5fd6f3490ab86ed7ebf36e595a9dc179b" + integrity sha512-4MMUjMQb4yA5fJ4osXx+QxGHt0/ZSy4spT6jL1HM7Tn8OJEC35siqdnpOo+HxPhYjqEFumKfGVF9hJfdyKBIBA== + "@types/eslint@^8.4.10": version "8.21.1" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.21.1.tgz#110b441a210d53ab47795124dbc3e9bb993d1e7c" @@ -1281,6 +1398,11 @@ dependencies: undici-types "~5.26.4" +"@types/offscreencanvas@^2019.6.4": + version "2019.7.3" + resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz#90267db13f64d6e9ccb5ae3eac92786a7c77a516" + integrity sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A== + "@types/plotly.js-dist@npm:@types/plotly.js": version "2.12.16" resolved "https://registry.yarnpkg.com/@types/plotly.js/-/plotly.js-2.12.16.tgz#99a7ef35052ba5ef985a036ba0ec2d549a604a3c" @@ -1303,6 +1425,20 @@ dependencies: "@types/react" "*" +"@types/react-reconciler@^0.26.7": + version "0.26.7" + resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.26.7.tgz#0c4643f30821ae057e401b0d9037e03e8e9b2a36" + integrity sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ== + dependencies: + "@types/react" "*" + +"@types/react-reconciler@^0.28.0": + version "0.28.8" + resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.28.8.tgz#e51710572bcccf214306833c2438575d310b3e98" + integrity sha512-SN9c4kxXZonFhbX4hJrZy37yw9e7EIxcpHCxQv5JUS18wDE5ovkQKlqQEkufdJCCMfuI9BnjUJvhYeJ9x5Ra7g== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^18.2.0": version "18.2.33" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.33.tgz#055356243dc4350a9ee6c6a2c07c5cae12e38877" @@ -1332,11 +1468,31 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stats.js@*": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@types/stats.js/-/stats.js-0.17.3.tgz#705446e12ce0fad618557dd88236f51148b7a935" + integrity sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ== + +"@types/three@^0.161.2": + version "0.161.2" + resolved "https://registry.yarnpkg.com/@types/three/-/three-0.161.2.tgz#3c7e3f40869ad52970f517583cc200472e8918bf" + integrity sha512-DazpZ+cIfBzbW/p0zm6G8CS03HBMd748A3R1ZOXHpqaXZLv2I5zNgQUrRG//UfJ6zYFp2cUoCQaOLaz8ubH07w== + dependencies: + "@types/stats.js" "*" + "@types/webxr" "*" + fflate "~0.6.10" + meshoptimizer "~0.18.1" + "@types/tough-cookie@*": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== +"@types/webxr@*", "@types/webxr@^0.5.2": + version "0.5.13" + resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.13.tgz#5fd07863819c30869d66b765926d0b5a53a7e9e0" + integrity sha512-Hi4K3aTEoaa31Cep75AA9wK5q2iZgC1L70serPbI11L4YieoZpu5LvLr6FZXyIdqkkGPh1WMuDf6oSPHJXBkoA== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -1482,6 +1638,18 @@ "@typescript-eslint/types" "6.6.0" eslint-visitor-keys "^3.4.1" +"@use-gesture/core@10.3.0": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.3.0.tgz#9afd3777a45b2a08990a5dcfcf8d9ddd55b00db9" + integrity sha512-rh+6MND31zfHcy9VU3dOZCqGY511lvGcfyJenN4cWZe0u1BH6brBpBddLVXhF2r4BMqWbvxfsbL7D287thJU2A== + +"@use-gesture/react@^10.2.24": + version "10.3.0" + resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.3.0.tgz#180534c821fd635c2853cbcfa813f92c94f27e3f" + integrity sha512-3zc+Ve99z4usVP6l9knYVbVnZgfqhKah7sIG+PS2w+vpig2v2OLct05vs+ZXMzwxdNCMka8B+8WlOo0z6Pn6DA== + dependencies: + "@use-gesture/core" "10.3.0" + "@vitejs/plugin-react@^4.0.4": version "4.0.4" resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.0.4.tgz#31c3f779dc534e045c4b134e7cf7b150af0a7646" @@ -1835,7 +2003,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.3.1: +base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -1845,6 +2013,13 @@ basic-auth@^1.0.3: resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884" integrity sha512-CtGuTyWf3ig+sgRyC7uP6DM3N+5ur/p8L+FPfsd+BbIfIs74TFfCajZTHnCw6K5dqM0bZEbRIqRy1fAdiUJhTA== +bidi-js@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.3.tgz#6f8bcf3c877c4d9220ddf49b9bb6930c88f877d2" + integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw== + dependencies: + require-from-string "^2.0.2" + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -1940,6 +2115,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + cacache@^16.1.0: version "16.1.3" resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" @@ -1995,6 +2178,11 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +camera-controls@^2.4.2: + version "2.7.4" + resolved "https://registry.yarnpkg.com/camera-controls/-/camera-controls-2.7.4.tgz#45d39b58f5d0647269ee8522322c87acfbac9646" + integrity sha512-cW2T+eZDD0IdHTqZMRcbEZUHIqVJGnRTA1W7RRbr6Pi/LS6SL15s7w4JEe/JdPaVkbgcqPju1dUCsmFfKdop+A== + camera-unproject@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/camera-unproject/-/camera-unproject-1.0.1.tgz#86927a9d6d5340a8c9e36da840f7ccb6d6da12cf" @@ -2308,6 +2496,11 @@ dayjs@^1.10.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -2412,6 +2605,13 @@ depd@^1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +detect-gpu@^5.0.28: + version "5.0.38" + resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-5.0.38.tgz#1c05ce728ea1229d16db15b865631609bf0d6952" + integrity sha512-36QeGHSXYcJ/RfrnPEScR8GDprbXFG4ZhXsfVNVHztZr38+fRxgHnJl3CjYXXjbeRUhu3ZZBJh6Lg0A9v0Qd8A== + dependencies: + webgl-constants "^1.1.1" + detect-libc@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" @@ -2486,6 +2686,11 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" +draco3d@^1.4.1: + version "1.5.7" + resolved "https://registry.yarnpkg.com/draco3d/-/draco3d-1.5.7.tgz#94f9bce293eb8920c159dc91a4ce9124a9e899e0" + integrity sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ== + dtype@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dtype/-/dtype-2.0.0.tgz#cd052323ce061444ecd2e8f5748f69a29be28434" @@ -3062,6 +3267,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@^0.6.9, fflate@~0.6.10: + version "0.6.10" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43" + integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg== + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3376,6 +3586,11 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +glsl-noise@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/glsl-noise/-/glsl-noise-0.0.0.tgz#367745f3a33382c0eeec4cb54b7e99cfc1d7670b" + integrity sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w== + glsl-tokenizer@^2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/glsl-tokenizer/-/glsl-tokenizer-2.1.5.tgz#1c2e78c16589933c274ba278d0a63b370c5fee1a" @@ -3588,7 +3803,7 @@ iconv-lite@0.6.3, iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -3959,6 +4174,13 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +its-fine@^1.0.6: + version "1.1.1" + resolved "https://registry.yarnpkg.com/its-fine/-/its-fine-1.1.1.tgz#e74b93fddd487441f978a50f64f0f5af4d2fc38e" + integrity sha512-v1Ia1xl20KbuSGlwoaGsW0oxsw8Be+TrXweidxD9oT/1lAh6O3K3/GIM95Tt6WCiv6W+h2M7RB1TwdoAjQyyKw== + dependencies: + "@types/react-reconciler" "^0.28.0" + jest-changed-files@^29.4.3: version "29.4.3" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.4.3.tgz#7961fe32536b9b6d5c28dfa0abcfab31abcf50a7" @@ -4617,6 +4839,11 @@ lunr@^2.3.9: resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== +maath@^0.10.7: + version "0.10.7" + resolved "https://registry.yarnpkg.com/maath/-/maath-0.10.7.tgz#9289b42a5db8ac5b26407b3bfca4e3bebefe50b4" + integrity sha512-zQ2xd7dNOIVTjAS+hj22fyj1EFYmOJX6tzKjZ92r6WDoq8hyFxjuGA2q950tmR4iC/EKXoMQdSipkaJVuUHDTg== + make-dir@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -4680,6 +4907,16 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +meshline@^3.1.6: + version "3.2.0" + resolved "https://registry.yarnpkg.com/meshline/-/meshline-3.2.0.tgz#0fcffd1fcae780b0e6bf0db991c8d7384154b075" + integrity sha512-ZaJkC967GTuef7UBdO0rGPX544oIWaNo7tYedVHSoR2lje6RR16fX8IsgMxgxoYYERtjqsRWIYBSPBxG4QR84Q== + +meshoptimizer@~0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz#cdb90907f30a7b5b1190facd3b7ee6b7087797d8" + integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw== + micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -5293,6 +5530,11 @@ postinstall-postinstall@^2.1.0: resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== +potpack@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14" + integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ== + prebuild-install@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" @@ -5365,7 +5607,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5435,6 +5677,13 @@ react-ace@^10.1.0: lodash.isequal "^4.5.0" prop-types "^15.7.2" +react-composer@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-composer/-/react-composer-5.0.3.tgz#7beb9513da5e8687f4f434ea1333ef36a4f3091b" + integrity sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA== + dependencies: + prop-types "^15.6.0" + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -5470,6 +5719,11 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-merge-refs@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" + integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== + react-popper@^1.3.11: version "1.3.11" resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.11.tgz#a2cc3f0a67b75b66cfa62d2c409f9dd1fcc71ffd" @@ -5491,6 +5745,14 @@ react-popper@^2.3.0: react-fast-compare "^3.0.1" warning "^4.0.2" +react-reconciler@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.27.0.tgz#360124fdf2d76447c7491ee5f0e04503ed9acf5b" + integrity sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.21.0" + react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" @@ -5516,6 +5778,13 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" +react-use-measure@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.1.1.tgz#5824537f4ee01c9469c45d5f7a8446177c6cc4ba" + integrity sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig== + dependencies: + debounce "^1.2.1" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -5559,6 +5828,11 @@ regenerator-runtime@^0.13.11: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -5583,6 +5857,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + requires-port@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -5713,6 +5992,13 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" +scheduler@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0.tgz#6fd2532ff5a6d877b6edb12f00d8ab7e8f308820" + integrity sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ== + dependencies: + loose-envify "^1.1.0" + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" @@ -5923,6 +6209,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +spring@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/spring/-/spring-0.0.0.tgz#27a9f571d49f3f29e90c6b9625364073a8353815" + integrity sha512-hQKa8vrkjMLCR5HUMQNRS5oUA0FKSgSvpLSyu6dcNUBc1uCfZ+TssIQXcKQtV4JP8QYGwKtz+6d630ovL4lzeg== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -5942,6 +6233,16 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stats-gl@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/stats-gl/-/stats-gl-2.0.1.tgz#4626a1575af00f0c5daba41ebc8f8e29a0a1998a" + integrity sha512-EhFm1AxoSBK3MflkFawZ4jmOX1dWu0nBAtCpvGxGsondEvCpsohbpRpM8pi8UAcxG5eRsDsCiRcxdH20j3Rp9A== + +stats.js@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/stats.js/-/stats.js-0.17.0.tgz#b1c3dc46d94498b578b7fd3985b81ace7131cc7d" + integrity sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw== + stop-iteration-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" @@ -6076,6 +6377,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +suspend-react@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/suspend-react/-/suspend-react-0.1.3.tgz#a52f49d21cfae9a2fb70bd0c68413d3f9d90768e" + integrity sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -6128,6 +6434,28 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +three-mesh-bvh@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/three-mesh-bvh/-/three-mesh-bvh-0.7.1.tgz#c11461b73e71c143717f95e0a8e6c9721ad0de77" + integrity sha512-63xvjnmK3FpA41klHfVvTMi2JRFdKeu3b4STBcLvGyDMYRpkBIyJ2d77CnNvKBZl9bed4sdEyrqE3AOsGorjUA== + +three-stdlib@^2.21.1, three-stdlib@^2.29.4: + version "2.29.4" + resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.29.4.tgz#6e8741f6a2d435d15ed73f3a14dd149660d4ce51" + integrity sha512-XNzGCrz/uAk9XoLwd35eN7dQyI4ggXZTeqjcN034YdYBpBlNO9kmLHehl/0Nw9jCelblB7jla+unHAOIyLyV6Q== + dependencies: + "@types/draco3d" "^1.4.0" + "@types/offscreencanvas" "^2019.6.4" + "@types/webxr" "^0.5.2" + draco3d "^1.4.1" + fflate "^0.6.9" + potpack "^1.0.1" + +three@^0.161.0: + version "0.161.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.161.0.tgz#38aafaa82fe5467fde2e33933515d1b6beb17d91" + integrity sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw== + through2@^0.6.3: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" @@ -6186,6 +6514,26 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +troika-three-text@^0.47.2: + version "0.47.2" + resolved "https://registry.yarnpkg.com/troika-three-text/-/troika-three-text-0.47.2.tgz#fdf89059c010563bb829262b20c41f69ca79b712" + integrity sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng== + dependencies: + bidi-js "^1.0.2" + troika-three-utils "^0.47.2" + troika-worker-utils "^0.47.2" + webgl-sdf-generator "1.1.1" + +troika-three-utils@^0.47.2: + version "0.47.2" + resolved "https://registry.yarnpkg.com/troika-three-utils/-/troika-three-utils-0.47.2.tgz#af49ca694245dce631963d5fefe4e8e1b8af9044" + integrity sha512-/28plhCxfKtH7MSxEGx8e3b/OXU5A0xlwl+Sbdp0H8FXUHKZDoksduEKmjQayXYtxAyuUiCRunYIv/8Vi7aiyg== + +troika-worker-utils@^0.47.2: + version "0.47.2" + resolved "https://registry.yarnpkg.com/troika-worker-utils/-/troika-worker-utils-0.47.2.tgz#e7c5de5f37d56c072b13fa8112bb844e048ff46c" + integrity sha512-mzss4MeyzUkYBppn4x5cdAqrhBHFEuVmMMgLMTyFV23x6GvQMyo+/R5E5Lsbrt7WSt5RfvewjcwD1DChRTA9lA== + ts-api-utils@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" @@ -6249,6 +6597,13 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel-rat@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tunnel-rat/-/tunnel-rat-0.1.2.tgz#1717efbc474ea2d8aa05a91622457a6e201c0aeb" + integrity sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ== + dependencies: + zustand "^4.3.2" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -6334,6 +6689,11 @@ union@~0.5.0: dependencies: qs "^6.4.0" +uniqid@^5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-5.4.0.tgz#4e17bfcab66dfe33563411ae0c801f46ef964e66" + integrity sha512-38JRbJ4Fj94VmnC7G/J/5n5SC7Ab46OM5iNtSstB/ko3l1b5g7ALt4qzHFgGciFkyiRNtDXtLNb+VsxtMSE77A== + unique-filename@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" @@ -6408,6 +6768,11 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +use-sync-external-store@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -6420,6 +6785,16 @@ util@^0.10.3: dependencies: inherits "2.0.3" +utility-types@^3.10.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" + integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== + +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-to-istanbul@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" @@ -6471,6 +6846,16 @@ warning@^4.0.2, warning@^4.0.3: dependencies: loose-envify "^1.0.0" +webgl-constants@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/webgl-constants/-/webgl-constants-1.1.1.tgz#f9633ee87fea56647a60b9ce735cbdfb891c6855" + integrity sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg== + +webgl-sdf-generator@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz#3e1b422b3d87cd3cc77f2602c9db63bc0f6accbd" + integrity sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA== + webgpu@^0.1.16: version "0.1.16" resolved "https://registry.yarnpkg.com/webgpu/-/webgpu-0.1.16.tgz#dec416373e308181b28864b58c8a914461d7ceee" @@ -6666,3 +7051,15 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zustand@^3.7.1: + version "3.7.2" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.2.tgz#7b44c4f4a5bfd7a8296a3957b13e1c346f42514d" + integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA== + +zustand@^4.3.2: + version "4.5.0" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.0.tgz#141354af56f91de378aa6c4b930032ab338f3ef0" + integrity sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A== + dependencies: + use-sync-external-store "1.2.0" From 656838583605aded169436a6edec267bbc87269b Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 5 Feb 2024 17:16:49 +0800 Subject: [PATCH 03/43] Fix build issues --- package.json | 1 + .../ar/libraries/object_state_library/ARObjectComponent.tsx | 6 +----- .../object_state_library/model_components/GltfComponent.tsx | 3 +-- yarn.lock | 5 +++++ 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index d546e4904..b17902842 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "gl-matrix": "^3.3.0", "js-slang": "^1.0.20", "lodash": "^4.17.21", + "os": "^0.1.2", "patch-package": "^6.5.1", "phaser": "^3.54.0", "plotly.js-dist": "^2.17.1", diff --git a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx index 20f03e690..f641c4ffd 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx @@ -18,9 +18,8 @@ import { TextModel, } from "./Behaviour"; import { Mesh, Vector3 } from "three"; -import { useFrame, extend } from "@react-three/fiber"; +import { useFrame } from "@react-three/fiber"; import ErrorBoundary from "./ErrorBoundary"; -import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry"; import GltfComponent from "./model_components/GltfComponent"; import TextComponent from "./model_components/TextComponent"; import ImageComponent from "./model_components/ImageComponent"; @@ -29,8 +28,6 @@ import { useSpring, SpringValue } from "@react-spring/three"; import LightComponent from "./model_components/LightComponent"; import InterfaceComponent from "./model_components/InterfaceComponent"; -extend({ TextGeometry }); - type Props = { arObject: ARObject; getUserPosition: () => Vector3; @@ -217,4 +214,3 @@ function handleRotation( mesh.rotation.y = 0; } } - diff --git a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx index aa15fba91..313efff1a 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx @@ -7,7 +7,7 @@ import { useRef, useState, } from "react"; -import * as SkeletonUtils from "three/examples/jsm/utils/SkeletonUtils"; +import * as SkeletonUtils from "three/examples/jsm/utils/SkeletonUtils.js"; import { Object3D, type Object3DEventMap, AnimationMixer } from "three"; import { GltfModel } from "../Behaviour"; import { ARObject } from "../ARObject"; @@ -64,4 +64,3 @@ export default function GltfComponent(props: GltfProps) { ); } - diff --git a/yarn.lock b/yarn.lock index b8883edc7..41dc4c9a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5316,6 +5316,11 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +os@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/os/-/os-0.1.2.tgz#f29a50c62908516ba42652de42f7038600cadbc2" + integrity sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" From 5dc4b62554dd9df5880212ac2b485a5a162ccb68 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 5 Feb 2024 21:58:31 +0800 Subject: [PATCH 04/43] Setup AR toggle and link module state --- src/bundles/ar/AR.ts | 30 +++-- src/bundles/ar/ARComponent.tsx | 192 ++++++++++++++++++++++++++++ src/bundles/ar/index.ts | 1 + src/tabs/AugmentedReality/index.tsx | 45 +++---- 4 files changed, 233 insertions(+), 35 deletions(-) create mode 100644 src/bundles/ar/ARComponent.tsx diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 582d1fcb1..217744029 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -2,18 +2,14 @@ import { Vector3 } from "three"; import { ARObject } from "./libraries/object_state_library/ARObject"; import { OverlayHelper, Toggle } from "./OverlayHelper"; -import context from "js-slang/context"; - export class ARState { arObjects: ARObject[] = []; overlay = new OverlayHelper(); - - constructor() { - (window as any).arController = this; - } } -let moduleState = new ARState(); +export function initAR() { + (window as any).arController = new ARState(); +} export function getModuleState(): ARState { return (window as any).arController as ARState; @@ -22,26 +18,38 @@ export function getModuleState(): ARState { // Overlay export function setLeftToggle(text: string, callback: () => void) { + let moduleState = getModuleState(); + if (!moduleState) return; moduleState.overlay.toggleLeft = new Toggle(text, callback); } export function setCentreToggle(text: string, callback: () => void) { + let moduleState = getModuleState(); + if (!moduleState) return; moduleState.overlay.toggleCentre = new Toggle(text, callback); } export function setRightToggle(text: string, callback: () => void) { + let moduleState = getModuleState(); + if (!moduleState) return; moduleState.overlay.toggleRight = new Toggle(text, callback); } export function removeLeftToggle() { + let moduleState = getModuleState(); + if (!moduleState) return; moduleState.overlay.toggleLeft = undefined; } export function removeCentreToggle() { + let moduleState = getModuleState(); + if (!moduleState) return; moduleState.overlay.toggleCentre = undefined; } export function removeRightToggle() { + let moduleState = getModuleState(); + if (!moduleState) return; moduleState.overlay.toggleRight = undefined; } @@ -52,6 +60,8 @@ export function createVector3(x: number, y: number, z: number): Vector3 { } export function addARObject(object: ARObject) { + let moduleState = getModuleState(); + if (!moduleState) return; if ( moduleState.arObjects.find((item) => { item.id === object.id; @@ -62,15 +72,19 @@ export function addARObject(object: ARObject) { let newArray = Object.assign([], moduleState.arObjects); newArray.push(object); moduleState.arObjects = newArray; + console.log("Adding Objects", newArray); } export function removeARObject(object: ARObject) { + let moduleState = getModuleState(); + if (!moduleState) return; moduleState.arObjects = moduleState.arObjects.filter((item) => { item.id !== object.id; }); } export function clearARObjects() { + let moduleState = getModuleState(); + if (!moduleState) return; moduleState.arObjects = []; } - diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx new file mode 100644 index 000000000..58e23ccd2 --- /dev/null +++ b/src/bundles/ar/ARComponent.tsx @@ -0,0 +1,192 @@ +import { ARButton } from "@react-three/xr"; +import { + ScreenStateContext, + useScreenState, +} from "./libraries/screen_state_library/ScreenStateContext"; +import { type RefObject, useEffect, useMemo } from "react"; +import { + PlayAreaContext, + usePlayArea, +} from "./libraries/calibration_library/PlayAreaContext"; +import { + ControlsContext, + useControls, +} from "./libraries/controls_library/ControlsContext"; +import { ARState } from "./AR"; +import { OverlayHelper } from "./OverlayHelper"; + +export default function ARComponent(props: ARState) { + return ( + + + + ); +} + +function ButtonComponent(props: ARState) { + const screenState = useScreenState(); + + useEffect(() => { + screenState.setStates(, ); + }, []); + + return ( +
+ + {screenState.component} +
+ ); +} + +function Overlay() { + return ( +
+
+ ); +} + +function AugmentedLayer(props: ARState) { + return ( + + + + + + ); +} + +function AugmentedContent(props: ARState) { + const screenState = useScreenState(); + const playArea = usePlayArea(); + const controls = useControls(); + + const objects = useMemo(() => props.arObjects, [props.arObjects]); + + console.log("Objects", objects); + + useEffect(() => { + controls.setCallback((prev, current) => { + if (prev) { + let object = objects.find((item) => { + return item.uuid === prev.uuid; + }); + if (object) { + object.isHighlighted = false; + } + } + if (current) { + let object = objects.find((item) => { + return item.uuid === current.uuid; + }); + if (object) { + object.isHighlighted = true; + } + } + }); + }, []); + + useEffect(() => { + setupToggles(props.overlay, screenState.overlayRef); + }, [ + props.overlay.toggleCentre, + props.overlay.toggleLeft, + props.overlay.toggleRight, + ]); + + return ( + + {/* {objects.map((item) => { + return item.getComponent(playArea.getCameraRelativePosition); + })} */} + + ); +} + +function setupToggles( + overlayHelper: OverlayHelper, + overlayRef: RefObject | null +) { + if (!overlayRef || !overlayRef.current) return; + let overlay = overlayRef.current; + // Left + let leftToggle = overlay?.querySelector("#left-toggle") as HTMLElement; + if (leftToggle) { + if (overlayHelper.toggleLeft) { + leftToggle.style.display = "block"; + leftToggle.textContent = overlayHelper.toggleLeft.text; + leftToggle.onclick = overlayHelper.toggleLeft.callback; + } else { + leftToggle.style.display = "none"; + leftToggle.textContent = ""; + leftToggle.onclick = () => {}; + } + } + // Centre + let centreToggle = overlay?.querySelector("#centre-toggle") as HTMLElement; + if (centreToggle) { + if (overlayHelper.toggleCentre) { + centreToggle.style.display = "block"; + centreToggle.textContent = overlayHelper.toggleCentre.text; + centreToggle.onclick = overlayHelper.toggleCentre.callback; + } else { + centreToggle.style.display = "none"; + centreToggle.textContent = ""; + centreToggle.onclick = () => {}; + } + } + // Right + let rightToggle = overlay?.querySelector("#right-toggle") as HTMLElement; + if (rightToggle) { + if (overlayHelper.toggleRight) { + rightToggle.style.display = "block"; + rightToggle.textContent = overlayHelper.toggleRight.text; + rightToggle.onclick = overlayHelper.toggleRight.callback; + } else { + rightToggle.style.display = "none"; + rightToggle.textContent = ""; + rightToggle.onclick = () => {}; + } + } +} + diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/index.ts index 834fbfaee..89ad5b900 100644 --- a/src/bundles/ar/index.ts +++ b/src/bundles/ar/index.ts @@ -14,6 +14,7 @@ */ export { + initAR, setLeftToggle, setCentreToggle, setRightToggle, diff --git a/src/tabs/AugmentedReality/index.tsx b/src/tabs/AugmentedReality/index.tsx index 34eb95b09..4fe2d0887 100644 --- a/src/tabs/AugmentedReality/index.tsx +++ b/src/tabs/AugmentedReality/index.tsx @@ -1,42 +1,33 @@ -import React from 'react'; +import React from "react"; +import { getModuleState } from "../../bundles/ar/AR"; +import ARComponent from "../../bundles/ar/ARComponent"; /** - * - * @author - * @author + * Tab for viewing augmented reality content + * @module ar + * @author Chong Wen Hao */ /** * React Component props for the Tab. */ -type Props = { - children?: never; - className?: never; - context?: any; -}; - -/** - * React Component state for the Tab. - */ -type State = { - counter: number; -}; +type Props = {}; /** * The main React Component of the Tab. */ -class Repeat extends React.Component { +class ARMainComponent extends React.Component { constructor(props) { super(props); - this.state = { - counter: 0, - }; } public render() { - const { counter } = this.state; return ( -
This is spawned from the repeat package. Counter is {counter}
+
+

Instructions:

+

Click on the toggle below to enter AR mode.

+ +
); } } @@ -48,24 +39,24 @@ export default { * @param {DebuggerContext} context * @returns {boolean} */ - toSpawn: (context: any) => context.result.value === 'test', + toSpawn: (context: any) => getModuleState() !== undefined, /** * This function will be called to render the module tab in the side contents * on Source Academy frontend. * @param {DebuggerContext} context */ - body: (context: any) => , + body: (_context: any) => , /** * The Tab's icon tooltip in the side contents on Source Academy frontend. */ - label: 'Sample Tab', + label: "AR Tab", /** * BlueprintJS IconName element's name, used to render the icon which will be * displayed in the side contents panel. * @see https://blueprintjs.com/docs/#icons */ - iconName: 'build', -}; \ No newline at end of file + iconName: "intelligence", +}; From a583421f39d4dc3535e20fc2f1a58a0c22cc88ca Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 6 Feb 2024 15:05:57 +0800 Subject: [PATCH 05/43] Add parsing for objects Objects had their class removed when passing via state, need to parse again --- src/bundles/ar/ARComponent.tsx | 45 ++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index 58e23ccd2..16b27b029 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -3,7 +3,7 @@ import { ScreenStateContext, useScreenState, } from "./libraries/screen_state_library/ScreenStateContext"; -import { type RefObject, useEffect, useMemo } from "react"; +import { type RefObject, useEffect, useState } from "react"; import { PlayAreaContext, usePlayArea, @@ -14,6 +14,12 @@ import { } from "./libraries/controls_library/ControlsContext"; import { ARState } from "./AR"; import { OverlayHelper } from "./OverlayHelper"; +import { + CubeObject, + type ARObject, + UIObject, + LightObject, +} from "./libraries/object_state_library/ARObject"; export default function ARComponent(props: ARState) { return ( @@ -55,6 +61,7 @@ function Overlay() { bottom: 30, left: 20, background: "#fafafa", + color: "#212121", borderRadius: 30, padding: "15px 30px", }} @@ -66,6 +73,7 @@ function Overlay() { position: "absolute", bottom: 30, background: "#fafafa", + color: "#212121", borderRadius: 30, padding: "15px 30px", }} @@ -78,6 +86,7 @@ function Overlay() { bottom: 30, right: 20, background: "#fafafa", + color: "#212121", borderRadius: 30, padding: "15px 30px", }} @@ -100,10 +109,7 @@ function AugmentedContent(props: ARState) { const screenState = useScreenState(); const playArea = usePlayArea(); const controls = useControls(); - - const objects = useMemo(() => props.arObjects, [props.arObjects]); - - console.log("Objects", objects); + const [objects, setObjects] = useState([]); useEffect(() => { controls.setCallback((prev, current) => { @@ -134,11 +140,35 @@ function AugmentedContent(props: ARState) { props.overlay.toggleRight, ]); + useEffect(() => { + console.log("Updated"); + let newObjects: ARObject[] = []; + props.arObjects.forEach((object) => { + if (!object) return; + let newObject = CubeObject.parseObject(object); + if (newObject) { + newObjects.push(newObject); + return; + } + newObject = UIObject.parseObject(object); + if (newObject) { + newObjects.push(newObject); + return; + } + newObject = LightObject.parseObject(object); + if (newObject) { + newObjects.push(newObject); + return; + } + }); + setObjects(newObjects); + }, [props, props.arObjects]); + return ( - {/* {objects.map((item) => { + {objects.map((item) => { return item.getComponent(playArea.getCameraRelativePosition); - })} */} + })} ); } @@ -189,4 +219,3 @@ function setupToggles( } } } - From 97028eeb2199e59c782bd2541b99106c4f827ca8 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:00:24 +0800 Subject: [PATCH 06/43] Fix toggle click --- src/bundles/ar/AR.ts | 1 - src/bundles/ar/ARComponent.tsx | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 217744029..3078830bb 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -72,7 +72,6 @@ export function addARObject(object: ARObject) { let newArray = Object.assign([], moduleState.arObjects); newArray.push(object); moduleState.arObjects = newArray; - console.log("Adding Objects", newArray); } export function removeARObject(object: ARObject) { diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index 16b27b029..71c416c7b 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -185,7 +185,9 @@ function setupToggles( if (overlayHelper.toggleLeft) { leftToggle.style.display = "block"; leftToggle.textContent = overlayHelper.toggleLeft.text; - leftToggle.onclick = overlayHelper.toggleLeft.callback; + leftToggle.onclick = () => { + overlayHelper.toggleLeft?.callback(); + }; } else { leftToggle.style.display = "none"; leftToggle.textContent = ""; @@ -198,7 +200,9 @@ function setupToggles( if (overlayHelper.toggleCentre) { centreToggle.style.display = "block"; centreToggle.textContent = overlayHelper.toggleCentre.text; - centreToggle.onclick = overlayHelper.toggleCentre.callback; + centreToggle.onclick = () => { + overlayHelper.toggleCentre?.callback(); + }; } else { centreToggle.style.display = "none"; centreToggle.textContent = ""; @@ -211,7 +215,9 @@ function setupToggles( if (overlayHelper.toggleRight) { rightToggle.style.display = "block"; rightToggle.textContent = overlayHelper.toggleRight.text; - rightToggle.onclick = overlayHelper.toggleRight.callback; + rightToggle.onclick = () => { + overlayHelper.toggleRight?.callback(); + }; } else { rightToggle.style.display = "none"; rightToggle.textContent = ""; From d46461c3cd8a4048c9e0aa86b992a0fb6b93abf6 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:11:01 +0800 Subject: [PATCH 07/43] Fix issue with add/remove AR object --- src/bundles/ar/AR.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 3078830bb..184025eb8 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -64,7 +64,7 @@ export function addARObject(object: ARObject) { if (!moduleState) return; if ( moduleState.arObjects.find((item) => { - item.id === object.id; + return item.id === object.id; }) ) { return; // Already in array @@ -78,7 +78,7 @@ export function removeARObject(object: ARObject) { let moduleState = getModuleState(); if (!moduleState) return; moduleState.arObjects = moduleState.arObjects.filter((item) => { - item.id !== object.id; + return item.id !== object.id; }); } From 3cd7249565b0aa6de7b5fd6c9ae4495bee4aa5a4 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:30:53 +0800 Subject: [PATCH 08/43] Added callback for AR updates --- src/bundles/ar/AR.ts | 11 +++++++++++ src/bundles/ar/ARComponent.tsx | 14 +++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 184025eb8..5331ad863 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -15,6 +15,13 @@ export function getModuleState(): ARState { return (window as any).arController as ARState; } +export function callARCallback() { + let f = (window as any).arControllerCallback as Function; + if (f) { + f(); + } +} + // Overlay export function setLeftToggle(text: string, callback: () => void) { @@ -72,6 +79,7 @@ export function addARObject(object: ARObject) { let newArray = Object.assign([], moduleState.arObjects); newArray.push(object); moduleState.arObjects = newArray; + callARCallback(); } export function removeARObject(object: ARObject) { @@ -80,10 +88,13 @@ export function removeARObject(object: ARObject) { moduleState.arObjects = moduleState.arObjects.filter((item) => { return item.id !== object.id; }); + callARCallback(); } export function clearARObjects() { let moduleState = getModuleState(); if (!moduleState) return; moduleState.arObjects = []; + callARCallback(); } + diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index 71c416c7b..7f73270ab 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -12,7 +12,7 @@ import { ControlsContext, useControls, } from "./libraries/controls_library/ControlsContext"; -import { ARState } from "./AR"; +import { ARState, getModuleState } from "./AR"; import { OverlayHelper } from "./OverlayHelper"; import { CubeObject, @@ -130,6 +130,11 @@ function AugmentedContent(props: ARState) { } } }); + (window as any).arControllerCallback = () => { + let newState = getModuleState(); + updateObjects(newState); + }; + updateObjects(props); }, []); useEffect(() => { @@ -140,10 +145,9 @@ function AugmentedContent(props: ARState) { props.overlay.toggleRight, ]); - useEffect(() => { - console.log("Updated"); + function updateObjects(state: ARState) { let newObjects: ARObject[] = []; - props.arObjects.forEach((object) => { + state.arObjects.forEach((object) => { if (!object) return; let newObject = CubeObject.parseObject(object); if (newObject) { @@ -162,7 +166,7 @@ function AugmentedContent(props: ARState) { } }); setObjects(newObjects); - }, [props, props.arObjects]); + } return ( From 6a82ae9aba29b4ca1ad738959d6671c99b92c6ea Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:15:26 +0800 Subject: [PATCH 09/43] Added row, col and text UI components - Bug with color --- src/bundles/ar/ObjectsHelper.ts | 88 ++++++++++++++++++- src/bundles/ar/index.ts | 14 +++ .../ui_component/UIColumnComponent.tsx | 13 +-- .../ui_component/UIRowComponent.tsx | 12 +-- .../ui_component/UITextComponent.tsx | 8 +- 5 files changed, 110 insertions(+), 25 deletions(-) diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index a607a02d2..19fd26c5c 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -1,4 +1,4 @@ -import { Vector3 } from "three"; +import { Color, Vector3 } from "three"; import { ARObject, CubeObject, @@ -18,6 +18,14 @@ import { SpringMovement, } from "./libraries/object_state_library/Behaviour"; import uniqid from "uniqid"; +import type { UIBasicComponent } from "./libraries/object_state_library/ui_component/UIComponent"; +import UIRowComponent, { + VerticalAlignment, +} from "./libraries/object_state_library/ui_component/UIRowComponent"; +import UIColumnComponent, { + HorizontalAlignment, +} from "./libraries/object_state_library/ui_component/UIColumnComponent"; +import UITextComponent from "./libraries/object_state_library/ui_component/UITextComponent"; // Objects @@ -45,13 +53,13 @@ export function createCubeObject( export function createInterfaceObject( position: Vector3, - uiJson: any, + rootComponent: UIBasicComponent, onSelect?: (object: ARObject) => {} ): UIObject { return new UIObject( uniqid(), position, - uiJson, + rootComponent.toJSON(), undefined, undefined, undefined, @@ -66,6 +74,79 @@ export function createLightObject( return new LightObject(uniqid(), position, intensity); } +// Interface + +export function createInterfaceRow( + children: UIBasicComponent[], + verticalAlignment: VerticalAlignment, + paddingLeft: number, + paddingRight: number, + paddingTop: number, + paddingBottom: number, + backgroundColor: number +): UIRowComponent { + return new UIRowComponent({ + children: children, + verticalAlignment: verticalAlignment, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + background: backgroundColor, + id: uniqid(), + }); +} + +export function createInterfaceColumn( + children: UIBasicComponent[], + horizontalAlignment: HorizontalAlignment, + paddingLeft: number, + paddingRight: number, + paddingTop: number, + paddingBottom: number, + backgroundColor: number +): UIColumnComponent { + return new UIColumnComponent({ + children: children, + horizontalAlignment: horizontalAlignment, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + background: backgroundColor, + id: uniqid(), + }); +} + +export function createInterfaceText( + text: string, + textSize: number, + textWidth: number, + paddingLeft: number, + paddingRight: number, + paddingTop: number, + paddingBottom: number, + color: number +): UITextComponent { + return new UITextComponent({ + text: text, + textSize: textSize, + textWidth: textWidth, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + color: color, + id: uniqid(), + }); +} + // Rotation export function setFixedRotation(object: ARObject, radians: number) { @@ -120,4 +201,3 @@ export function setOrbitMovement( export function setSpringMovement(object: ARObject) { object.behaviours.model = new SpringMovement(); } - diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/index.ts index 89ad5b900..7e03997a4 100644 --- a/src/bundles/ar/index.ts +++ b/src/bundles/ar/index.ts @@ -8,6 +8,9 @@ * @author Chong Wen Hao */ +import { HorizontalAlignment } from "./libraries/object_state_library/ui_component/UIColumnComponent"; +import { VerticalAlignment } from "./libraries/object_state_library/ui_component/UIRowComponent"; + /* To access things like the context or module state you can just import the context using the import below @@ -31,6 +34,9 @@ export { createCubeObject, createInterfaceObject, createLightObject, + createInterfaceRow, + createInterfaceColumn, + createInterfaceText, setFixedRotation, setRotateToUser, setRotateAroundY, @@ -41,3 +47,11 @@ export { setOrbitMovement, setSpringMovement, } from "./ObjectsHelper"; + +export const alignmentTop = VerticalAlignment.Top; +export const alignmentMiddle = VerticalAlignment.Middle; +export const alignmentBottom = VerticalAlignment.Bottom; + +export const alignmentLeft = HorizontalAlignment.Left; +export const alignmentCenter = HorizontalAlignment.Center; +export const alignmentRight = HorizontalAlignment.Right; diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index 3c62aab3a..65841f658 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -10,20 +10,16 @@ type UIColumnProps = { children?: UIBasicComponent[]; horizontalAlignment?: HorizontalAlignment; padding?: number | PaddingType; - background?: Color; + background?: number; id?: string; }; export default class UIColumnComponent extends LayoutComponent { horizontalAlignment: HorizontalAlignment; - background: Color; + background: number; constructor(props: UIColumnProps) { super(props.padding, props.id); - if (props.background) { - this.background = props.background; - } else { - this.background = new Color(0xffffff); - } + this.background = props.background ?? 0xffffff; if (props.children) { this.children = props.children; } @@ -96,7 +92,7 @@ function ColumnUIComponent( - + @@ -108,4 +104,3 @@ export enum HorizontalAlignment { Center, Right, } - diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx index fe821031b..e9aebb57e 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx @@ -10,20 +10,16 @@ type UIRowProps = { children?: UIBasicComponent[]; verticalAlignment?: VerticalAlignment; padding?: number | PaddingType; - background?: Color; + background?: number; id?: string; }; export default class UIRowComponent extends LayoutComponent { verticalAlignment: VerticalAlignment; - background: Color; + background: number; constructor(props: UIRowProps) { super(props.padding, props.id); - if (props.background) { - this.background = props.background; - } else { - this.background = new Color(0xffffff); - } + this.background = props.background ?? 0xffffff; if (props.children) { this.children = props.children; } @@ -97,7 +93,7 @@ function RowUIComponent( - + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx index 4861695bf..47202ac56 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -8,7 +8,7 @@ type UITextProps = { textSize: number; textWidth: number; textAlign?: number; - color?: Color; + color?: number; padding?: number | PaddingType; id?: string; }; @@ -19,14 +19,14 @@ export default class UITextComponent extends UIBasicComponent { textWidth: number; textHeight = 0; textAlign: number; - color: Color; + color: number; constructor(props: UITextProps) { super(props.padding, props.id); this.text = props.text; this.textSize = props.textSize; this.textWidth = props.textWidth; this.textAlign = props.textAlign ?? 0; - this.color = props.color ?? new Color(0x000000); + this.color = props.color ?? 0; } getWidth = () => { return this.textWidth + this.paddingTop + this.paddingBottom; @@ -117,7 +117,7 @@ function TextUIComponent( fontSize={component.textSize} maxWidth={component.textWidth} textAlign={getTextAlign(component.textAlign)} - color={component.color} + color={new Color(component.color)} overflowWrap="normal" > {component.text} From 20a7144110e976417586fb4c86a97037e90fe31f Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:59:25 +0800 Subject: [PATCH 10/43] Added image --- src/bundles/ar/ObjectsHelper.ts | 26 +++++++++++++++++++++++++- src/bundles/ar/index.ts | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index 19fd26c5c..cb87fc5ff 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -1,4 +1,4 @@ -import { Color, Vector3 } from "three"; +import { Vector3 } from "three"; import { ARObject, CubeObject, @@ -26,6 +26,7 @@ import UIColumnComponent, { HorizontalAlignment, } from "./libraries/object_state_library/ui_component/UIColumnComponent"; import UITextComponent from "./libraries/object_state_library/ui_component/UITextComponent"; +import UIImageComponent from "./libraries/object_state_library/ui_component/UIImageComponent"; // Objects @@ -147,6 +148,29 @@ export function createInterfaceText( }); } +export function createInterfaceImage( + src: string, + imageWidth: number, + imageHeight: number, + paddingLeft: number, + paddingRight: number, + paddingTop: number, + paddingBottom: number +): UIImageComponent { + return new UIImageComponent({ + src: src, + imageWidth: imageWidth, + imageHeight: imageHeight, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + id: uniqid(), + }); +} + // Rotation export function setFixedRotation(object: ARObject, radians: number) { diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/index.ts index 7e03997a4..10f0d91ff 100644 --- a/src/bundles/ar/index.ts +++ b/src/bundles/ar/index.ts @@ -37,6 +37,7 @@ export { createInterfaceRow, createInterfaceColumn, createInterfaceText, + createInterfaceImage, setFixedRotation, setRotateToUser, setRotateAroundY, From e4d74cea90c0c27260ff87169eb5a1ac811126ed Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:06:31 +0800 Subject: [PATCH 11/43] Fix row/column color issue and image size --- .../model_components/InterfaceComponent.tsx | 4 ++-- .../ui_component/UIColumnComponent.tsx | 2 ++ .../object_state_library/ui_component/UIImageComponent.tsx | 7 +++++-- .../object_state_library/ui_component/UIRowComponent.tsx | 2 ++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx index d343a9b31..ba15ef658 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx @@ -9,7 +9,7 @@ import UIColumnComponent, { HorizontalAlignment, } from "../ui_component/UIColumnComponent"; import UITextComponent from "../ui_component/UITextComponent"; -import { Color, Vector3 } from "three"; +import { Vector3 } from "three"; import UIImageComponent from "../ui_component/UIImageComponent"; type InterfaceProps = { @@ -134,7 +134,7 @@ export function parseJsonInterface(uiJson: any) { textSize: textSize, textWidth: textWidth, textAlign: textAlign, - color: new Color(color), + color: color, padding: { paddingLeft: paddingLeft, paddingRight: paddingRight, diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index 65841f658..ae68475c4 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -88,8 +88,10 @@ function ColumnUIComponent( } return {children}; } + return ( + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx index c4488861b..f55b980f0 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx @@ -1,6 +1,7 @@ import { Vector3 } from "three"; import { UIBasicComponent, type PaddingType } from "./UIComponent"; import { Image } from "@react-three/drei"; +import { useEffect } from "react"; type UIImageProps = { src: string; @@ -34,8 +35,10 @@ export default class UIImageComponent extends UIBasicComponent { function ImageUIComponent(component: UIImageComponent, position: Vector3) { return ( - + ); } - diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx index e9aebb57e..732461ed4 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx @@ -89,8 +89,10 @@ function RowUIComponent( } return {children}; } + return ( + From 93bd4acb4f45335cebf9536cbd01f32861819e87 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 13 Feb 2024 07:53:41 +0800 Subject: [PATCH 12/43] Hide AR content when outside --- .../ScreenStateContext.tsx | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx index 5a9654a7c..77faee660 100644 --- a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx +++ b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx @@ -44,6 +44,7 @@ type Props = { export function ScreenStateContext(props: Props) { const [arState, setArState] = useState(); const [overlayState, setOverlayState] = useState(<>); + const isXRSession = useRef(false); const overlayRef = useRef(null); const [domOverlay, setDomOverlay] = useState(); @@ -70,10 +71,25 @@ export function ScreenStateContext(props: Props) { }, [overlayRef, component]); useEffect(() => { + updateComponent(); + }, [arState, overlayState, isXRSession.current]); + + function updateComponent() { setComponent( <> - {arState} + { + isXRSession.current = true; + updateComponent(); + }} + onSessionEnd={() => { + isXRSession.current = false; + updateComponent(); + }} + > + {isXRSession.current ? arState : <>} +
- {overlayState} + {isXRSession.current ? overlayState : <>}
); - }, [arState, overlayState]); + } function setStates( newArState: ReactNode | undefined, @@ -126,4 +142,3 @@ export function ScreenStateContext(props: Props) { export function useScreenState() { return useContext(Context); } - From ec19deed799e0881cef6369f19a2723d20fc1226 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:13:19 +0800 Subject: [PATCH 13/43] Standardize center --- src/bundles/ar/AR.ts | 8 +++---- src/bundles/ar/ARComponent.tsx | 38 +++++++++++++++++++++------------ src/bundles/ar/OverlayHelper.ts | 19 +++++++++-------- src/bundles/ar/index.ts | 5 +++-- 4 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 5331ad863..07f99ec20 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -30,10 +30,10 @@ export function setLeftToggle(text: string, callback: () => void) { moduleState.overlay.toggleLeft = new Toggle(text, callback); } -export function setCentreToggle(text: string, callback: () => void) { +export function setCenterToggle(text: string, callback: () => void) { let moduleState = getModuleState(); if (!moduleState) return; - moduleState.overlay.toggleCentre = new Toggle(text, callback); + moduleState.overlay.toggleCenter = new Toggle(text, callback); } export function setRightToggle(text: string, callback: () => void) { @@ -48,10 +48,10 @@ export function removeLeftToggle() { moduleState.overlay.toggleLeft = undefined; } -export function removeCentreToggle() { +export function removeCenterToggle() { let moduleState = getModuleState(); if (!moduleState) return; - moduleState.overlay.toggleCentre = undefined; + moduleState.overlay.toggleCenter = undefined; } export function removeRightToggle() { diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index 7f73270ab..752f89d3a 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -52,12 +52,19 @@ function ButtonComponent(props: ARState) { function Overlay() { return ( -
+
@@ -235,4 +235,3 @@ function setupToggles( } } } - From ebbb4986ecc3789e4b9fca3fb721c3ffb8ca966b Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:06:14 +0800 Subject: [PATCH 16/43] Added sphere --- src/bundles/ar/ARComponent.tsx | 6 ++ src/bundles/ar/ObjectsHelper.ts | 19 +++++ src/bundles/ar/index.ts | 2 +- .../object_state_library/ARObject.tsx | 70 ++++++++++++++++++- 4 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index aa4d4b90a..0d76b369c 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -19,6 +19,7 @@ import { type ARObject, UIObject, LightObject, + SphereObject, } from "./libraries/object_state_library/ARObject"; export default function ARComponent(props: ARState) { @@ -160,6 +161,11 @@ function AugmentedContent(props: ARState) { newObjects.push(newObject); return; } + newObject = SphereObject.parseObject(object); + if (newObject) { + newObjects.push(newObject); + return; + } newObject = UIObject.parseObject(object); if (newObject) { newObjects.push(newObject); diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index cb87fc5ff..77ab39dad 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -3,6 +3,7 @@ import { ARObject, CubeObject, LightObject, + SphereObject, UIObject, } from "./libraries/object_state_library/ARObject"; import { @@ -52,6 +53,24 @@ export function createCubeObject( ); } +export function createSphereObject( + position: Vector3, + radius: number, + color: number, + onSelect?: (object: ARObject) => {} +): SphereObject { + return new SphereObject( + uniqid(), + position, + radius, + color, + undefined, + undefined, + undefined, + onSelect + ); +} + export function createInterfaceObject( position: Vector3, rootComponent: UIBasicComponent, diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/index.ts index ac2adc85f..951a8272c 100644 --- a/src/bundles/ar/index.ts +++ b/src/bundles/ar/index.ts @@ -32,6 +32,7 @@ export { export { createCubeObject, + createSphereObject, createInterfaceObject, createLightObject, createInterfaceRow, @@ -56,4 +57,3 @@ export const alignmentBottom = VerticalAlignment.Bottom; export const alignmentLeft = HorizontalAlignment.Left; export const alignmentCenter = HorizontalAlignment.Center; export const alignmentRight = HorizontalAlignment.Right; - diff --git a/src/bundles/ar/libraries/object_state_library/ARObject.tsx b/src/bundles/ar/libraries/object_state_library/ARObject.tsx index 81e78f58e..134d6c0e3 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObject.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObject.tsx @@ -1,4 +1,9 @@ -import { BoxGeometry, MeshStandardMaterial, Vector3 } from "three"; +import { + BoxGeometry, + MeshStandardMaterial, + SphereGeometry, + Vector3, +} from "three"; import { type Behaviours, LightModel, @@ -137,6 +142,68 @@ export class CubeObject extends ARObject { } } +const SPHERE_OBJECT_TYPE = "SphereObject"; +export class SphereObject extends ARObject { + type = SPHERE_OBJECT_TYPE; + radius: number; + color: number; + constructor( + id: string, + position: Vector3, + radius: number, + color: number, + render?: RenderClass, + rotation?: RotationClass, + movement?: MovementClass, + onSelect?: (object: ARObject) => void + ) { + super( + id, + position, + { + model: new ShapeModel( + new SphereGeometry(radius, 20, 20), + new MeshStandardMaterial({ color: color }) + ), + render: render, + rotation: rotation, + movement: movement, + }, + onSelect + ); + this.radius = radius; + this.color = color; + } + static parseObject(object: any, onSelect?: () => void): ARObject | undefined { + if (!object || object.type !== SPHERE_OBJECT_TYPE) return undefined; + let id = object.id; + let position = parseVector3(object.position); + let render = parseRender(object.behaviours?.render); + let rotation = parseRotation(object.behaviours?.rotation); + let movement = parseMovement(object.behaviours?.movement); + let radius = object.radius; + let color = object.color; + if ( + typeof id === "string" && + position instanceof Vector3 && + typeof radius === "number" && + typeof color === "number" + ) { + return new SphereObject( + id, + position, + radius, + color, + render, + rotation, + movement, + onSelect + ); + } + return undefined; + } +} + const UI_OBJECT_TYPE = "UIObject"; export class UIObject extends ARObject { type = UI_OBJECT_TYPE; @@ -216,4 +283,3 @@ export class LightObject extends ARObject { return undefined; } } - From f220c2c1b445880be3aa6b65d4b6a59b5d98d961 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:46:51 +0800 Subject: [PATCH 17/43] Add move --- src/bundles/ar/AR.ts | 18 ++++++++++++++++++ src/bundles/ar/ObjectsHelper.ts | 5 ++++- src/bundles/ar/index.ts | 4 ++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 07f99ec20..701d5b917 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -98,3 +98,21 @@ export function clearARObjects() { callARCallback(); } +export function getXPosition(object: ARObject): number { + return object.position.x; +} + +export function getYPosition(object: ARObject): number { + return object.position.y; +} + +export function getZPosition(object: ARObject): number { + return object.position.z; +} + +export function moveARObject(object: ARObject, position: Vector3) { + let moduleState = getModuleState(); + if (!moduleState) return; + object.position = position; + callARCallback(); +} diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index 77ab39dad..0ed6f5b7e 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -67,7 +67,10 @@ export function createSphereObject( undefined, undefined, undefined, - onSelect + (object: ARObject) => { + console.log("Selected", onSelect); + onSelect?.(object); + } ); } diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/index.ts index 951a8272c..804386dcc 100644 --- a/src/bundles/ar/index.ts +++ b/src/bundles/ar/index.ts @@ -28,6 +28,10 @@ export { addARObject, removeARObject, clearARObjects, + getXPosition, + getYPosition, + getZPosition, + moveARObject, } from "./AR"; export { From 0f98dd69a85c156126e638ca825b368db472963e Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:00:14 +0800 Subject: [PATCH 18/43] Fix issue with spring movement --- src/bundles/ar/ObjectsHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index 0ed6f5b7e..1cf4bf4ae 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -245,5 +245,5 @@ export function setOrbitMovement( } export function setSpringMovement(object: ARObject) { - object.behaviours.model = new SpringMovement(); + object.behaviours.movement = new SpringMovement(); } From c14c2a12a6f18865f1f37e5d2032e0897fe37ad5 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:46:43 +0800 Subject: [PATCH 19/43] Fix interface refresh --- .../model_components/InterfaceComponent.tsx | 23 +++++++++++++++---- .../ui_component/UIColumnComponent.tsx | 22 +++++++++++++----- .../ui_component/UIImageComponent.tsx | 11 +++++++-- .../ui_component/UIRowComponent.tsx | 21 ++++++++++++----- .../ui_component/UITextComponent.tsx | 21 ++++++++++++----- 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx index ba15ef658..b0ac84591 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx @@ -1,6 +1,11 @@ import { SpringValue, animated } from "@react-spring/three"; import { InterfaceModel as InterfaceModel } from "../Behaviour"; -import { type MutableRefObject } from "react"; +import { + useEffect, + type MutableRefObject, + useState, + type ReactNode, +} from "react"; import { UIBasicComponent } from "../ui_component/UIComponent"; import UIRowComponent, { VerticalAlignment, @@ -19,12 +24,20 @@ type InterfaceProps = { }; export default function InterfaceComponent(props: InterfaceProps) { - return ( - - {parseJsonInterface(props.interfaceModel.uiJson)?.getComponent( + const [components, setComponents] = useState(); + + useEffect(() => { + setComponents( + parseJsonInterface(props.interfaceModel.uiJson)?.getComponent( new Vector3(0), () => {} - )} + ) + ); + }, [props.interfaceModel.uiJson]); + + return ( + + {components} ); } diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index ae68475c4..78a1166ed 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -48,15 +48,24 @@ export default class UIColumnComponent extends LayoutComponent { return height; }; getComponent = (position: Vector3, updateParent: () => void) => { - return ColumnUIComponent(this, position, updateParent); + return ( + + ); }; } -function ColumnUIComponent( - component: UIColumnComponent, - position: Vector3, - updateParent: () => void -) { +function ColumnUIComponent(props: { + component: UIColumnComponent; + position: Vector3; + updateParent: () => void; +}) { + let { component, position, updateParent } = props; + const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); @@ -106,3 +115,4 @@ export enum HorizontalAlignment { Center, Right, } + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx index f55b980f0..b9ab66020 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx @@ -28,11 +28,17 @@ export default class UIImageComponent extends UIBasicComponent { return this.imageHeight + this.paddingTop + this.paddingBottom; }; getComponent = (position: Vector3, updateParent: () => void) => { - return ImageUIComponent(this, position); + return ( + + ); }; } -function ImageUIComponent(component: UIImageComponent, position: Vector3) { +function ImageUIComponent(props: { + component: UIImageComponent; + position: Vector3; +}) { + let { component, position } = props; return ( ); } + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx index 732461ed4..645148871 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx @@ -48,15 +48,24 @@ export default class UIRowComponent extends LayoutComponent { return height + maxChildHeight; }; getComponent = (position: Vector3, updateParent: () => void) => { - return RowUIComponent(this, position, updateParent); + return ( + + ); }; } -function RowUIComponent( - component: UIRowComponent, - position: Vector3, - updateParent: () => void -) { +function RowUIComponent(props: { + component: UIRowComponent; + position: Vector3; + updateParent: () => void; +}) { + let { component, position, updateParent } = props; + const [width, setWidth] = useState(component.width); const [height, setHeight] = useState(component.height); diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx index 47202ac56..0e48bcfb1 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -47,15 +47,24 @@ export default class UITextComponent extends UIBasicComponent { } }; getComponent = (position: Vector3, updateParent: () => void) => { - return TextUIComponent(this, position, updateParent); + return ( + + ); }; } -function TextUIComponent( - component: UITextComponent, - position: Vector3, - updateParent: () => void -) { +function TextUIComponent(props: { + component: UITextComponent; + position: Vector3; + updateParent: () => void; +}) { + let { component, position, updateParent } = props; + const [offsetX, setOffsetX] = useState(0); let ref = useRef(null); From a203d3863d839835eaab6c2fc81a6e7918e9a5f2 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:58:10 +0800 Subject: [PATCH 20/43] Added click to call function --- src/bundles/ar/AR.ts | 16 ++++++++++++++-- src/bundles/ar/ARComponent.tsx | 8 ++++++++ src/bundles/ar/ObjectsHelper.ts | 19 +++++++++++-------- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 701d5b917..e43b1bd80 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -5,17 +5,23 @@ import { OverlayHelper, Toggle } from "./OverlayHelper"; export class ARState { arObjects: ARObject[] = []; overlay = new OverlayHelper(); + clickCallbacks = new Map void>(); } export function initAR() { - (window as any).arController = new ARState(); + let controller = new ARState(); + (window as any).arController = controller; + (window as any).arOnClickCallback = (id: string) => { + let callback = controller.clickCallbacks.get(id); + callback?.(); + }; } export function getModuleState(): ARState { return (window as any).arController as ARState; } -export function callARCallback() { +function callARCallback() { let f = (window as any).arControllerCallback as Function; if (f) { f(); @@ -76,6 +82,12 @@ export function addARObject(object: ARObject) { ) { return; // Already in array } + if (object.onSelect) { + moduleState.clickCallbacks.set(object.id, () => { + console.log("Called", object); + object.onSelect?.(object); + }); + } let newArray = Object.assign([], moduleState.arObjects); newArray.push(object); moduleState.arObjects = newArray; diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index 0d76b369c..fc0a3f865 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -177,6 +177,14 @@ function AugmentedContent(props: ARState) { return; } }); + newObjects.forEach((object) => { + object.onSelect = () => { + let callback = (window as any).arOnClickCallback as Function; + if (callback) { + callback(object.id); + } + }; + }); setObjects(newObjects); } diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index 1cf4bf4ae..334af4d50 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -37,7 +37,7 @@ export function createCubeObject( height: number, depth: number, color: number, - onSelect?: (object: ARObject) => {} + onSelect?: () => {} ): CubeObject { return new CubeObject( uniqid(), @@ -49,7 +49,9 @@ export function createCubeObject( undefined, undefined, undefined, - onSelect + (_: ARObject) => { + onSelect?.(); + } ); } @@ -57,7 +59,7 @@ export function createSphereObject( position: Vector3, radius: number, color: number, - onSelect?: (object: ARObject) => {} + onSelect?: () => {} ): SphereObject { return new SphereObject( uniqid(), @@ -67,9 +69,8 @@ export function createSphereObject( undefined, undefined, undefined, - (object: ARObject) => { - console.log("Selected", onSelect); - onSelect?.(object); + (_: ARObject) => { + onSelect?.(); } ); } @@ -77,7 +78,7 @@ export function createSphereObject( export function createInterfaceObject( position: Vector3, rootComponent: UIBasicComponent, - onSelect?: (object: ARObject) => {} + onSelect?: () => {} ): UIObject { return new UIObject( uniqid(), @@ -86,7 +87,9 @@ export function createInterfaceObject( undefined, undefined, undefined, - onSelect + (_: ARObject) => { + onSelect?.(); + } ); } From 3ef09c3219bddce41ae46344abaf29f8e5722ca3 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:51:52 +0800 Subject: [PATCH 21/43] Added recallibration --- src/bundles/ar/AR.ts | 2 +- src/bundles/ar/ARComponent.tsx | 28 ++++++++++++++++++++++++++-- src/bundles/ar/ObjectsHelper.ts | 14 ++++++++++++++ src/bundles/ar/index.ts | 1 + 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index e43b1bd80..c484c8d5c 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -21,7 +21,7 @@ export function getModuleState(): ARState { return (window as any).arController as ARState; } -function callARCallback() { +export function callARCallback() { let f = (window as any).arControllerCallback as Function; if (f) { f(); diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index fc0a3f865..74433ab15 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -21,6 +21,7 @@ import { LightObject, SphereObject, } from "./libraries/object_state_library/ARObject"; +import { useThree } from "@react-three/fiber"; export default function ARComponent(props: ARState) { return ( @@ -65,6 +66,18 @@ function Overlay() { padding: "0px 20px 60px 20px", }} > +
@@ -232,53 +232,53 @@ function setupToggles( let overlay = overlayRef.current; // Recalibrate let recalibrateToggle = overlay?.querySelector( - "#recalibrate-toggle" + '#recalibrate-toggle' ) as HTMLElement; if (recalibrateToggle) { recalibrateToggle.onclick = recalibrate; } // Left - let leftToggle = overlay?.querySelector("#left-toggle") as HTMLElement; + let leftToggle = overlay?.querySelector('#left-toggle') as HTMLElement; if (leftToggle) { if (overlayHelper.toggleLeft) { - leftToggle.style.display = "block"; + leftToggle.style.display = 'block'; leftToggle.textContent = overlayHelper.toggleLeft.text; leftToggle.onclick = () => { overlayHelper.toggleLeft?.callback(); }; } else { - leftToggle.style.display = "none"; - leftToggle.textContent = ""; + leftToggle.style.display = 'none'; + leftToggle.textContent = ''; leftToggle.onclick = () => {}; } } // Center - let centerToggle = overlay?.querySelector("#center-toggle") as HTMLElement; + let centerToggle = overlay?.querySelector('#center-toggle') as HTMLElement; if (centerToggle) { if (overlayHelper.toggleCenter) { - centerToggle.style.display = "block"; + centerToggle.style.display = 'block'; centerToggle.textContent = overlayHelper.toggleCenter.text; centerToggle.onclick = () => { overlayHelper.toggleCenter?.callback(); }; } else { - centerToggle.style.display = "none"; - centerToggle.textContent = ""; + centerToggle.style.display = 'none'; + centerToggle.textContent = ''; centerToggle.onclick = () => {}; } } // Right - let rightToggle = overlay?.querySelector("#right-toggle") as HTMLElement; + let rightToggle = overlay?.querySelector('#right-toggle') as HTMLElement; if (rightToggle) { if (overlayHelper.toggleRight) { - rightToggle.style.display = "block"; + rightToggle.style.display = 'block'; rightToggle.textContent = overlayHelper.toggleRight.text; rightToggle.onclick = () => { overlayHelper.toggleRight?.callback(); }; } else { - rightToggle.style.display = "none"; - rightToggle.textContent = ""; + rightToggle.style.display = 'none'; + rightToggle.textContent = ''; rightToggle.onclick = () => {}; } } diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index c7ea7f951..e4c869514 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -1,11 +1,11 @@ -import { Vector3 } from "three"; +import { Vector3 } from 'three'; import { ARObject, CubeObject, LightObject, SphereObject, UIObject, -} from "./libraries/object_state_library/ARObject"; +} from './libraries/object_state_library/ARObject'; import { AlwaysRender, FixRotation, @@ -17,18 +17,18 @@ import { RotateAroundY, RotateToUser, SpringMovement, -} from "./libraries/object_state_library/Behaviour"; -import uniqid from "uniqid"; -import type { UIBasicComponent } from "./libraries/object_state_library/ui_component/UIComponent"; +} from './libraries/object_state_library/Behaviour'; +import uniqid from 'uniqid'; +import type { UIBasicComponent } from './libraries/object_state_library/ui_component/UIComponent'; import UIRowComponent, { VerticalAlignment, -} from "./libraries/object_state_library/ui_component/UIRowComponent"; +} from './libraries/object_state_library/ui_component/UIRowComponent'; import UIColumnComponent, { HorizontalAlignment, -} from "./libraries/object_state_library/ui_component/UIColumnComponent"; -import UITextComponent from "./libraries/object_state_library/ui_component/UITextComponent"; -import UIImageComponent from "./libraries/object_state_library/ui_component/UIImageComponent"; -import { callARCallback } from "./AR"; +} from './libraries/object_state_library/ui_component/UIColumnComponent'; +import UITextComponent from './libraries/object_state_library/ui_component/UITextComponent'; +import UIImageComponent from './libraries/object_state_library/ui_component/UIImageComponent'; +import { callARCallback } from './AR'; // Objects diff --git a/src/bundles/ar/OverlayHelper.ts b/src/bundles/ar/OverlayHelper.ts index 2b25b7d03..8a7d576bb 100644 --- a/src/bundles/ar/OverlayHelper.ts +++ b/src/bundles/ar/OverlayHelper.ts @@ -12,4 +12,3 @@ export class OverlayHelper { toggleCenter: Toggle | undefined; toggleRight: Toggle | undefined; } - diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/index.ts index db382932b..21dff999a 100644 --- a/src/bundles/ar/index.ts +++ b/src/bundles/ar/index.ts @@ -8,8 +8,8 @@ * @author Chong Wen Hao */ -import { HorizontalAlignment } from "./libraries/object_state_library/ui_component/UIColumnComponent"; -import { VerticalAlignment } from "./libraries/object_state_library/ui_component/UIRowComponent"; +import { HorizontalAlignment } from './libraries/object_state_library/ui_component/UIColumnComponent'; +import { VerticalAlignment } from './libraries/object_state_library/ui_component/UIRowComponent'; /* To access things like the context or module state you can just import the context @@ -32,7 +32,7 @@ export { getYPosition, getZPosition, moveARObject, -} from "./AR"; +} from './AR'; export { createCubeObject, @@ -53,7 +53,7 @@ export { setOrbitMovement, setSpringMovement, clearMovement, -} from "./ObjectsHelper"; +} from './ObjectsHelper'; export const alignmentTop = VerticalAlignment.Top; export const alignmentMiddle = VerticalAlignment.Middle; diff --git a/src/bundles/ar/libraries/calibration_library/Misc.tsx b/src/bundles/ar/libraries/calibration_library/Misc.tsx index 014a47624..1b52e8ecf 100644 --- a/src/bundles/ar/libraries/calibration_library/Misc.tsx +++ b/src/bundles/ar/libraries/calibration_library/Misc.tsx @@ -1,4 +1,4 @@ -import { Vector3 } from "three"; +import { Vector3 } from 'three'; /** * Converts object to a Vector3. @@ -7,16 +7,12 @@ import { Vector3 } from "three"; * @returns Vector3 if successful, undefined if failed */ export function parseVector3(object: any) { - if (!object) return undefined; - let x = object.x; - let y = object.y; - let z = object.z; - if ( - typeof x === "number" && - typeof y === "number" && - typeof z === "number" - ) { - return new Vector3(x, y, z); - } - return undefined; + if (!object) return undefined; + let x = object.x; + let y = object.y; + let z = object.z; + if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number') { + return new Vector3(x, y, z); + } + return undefined; } diff --git a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx index 3c9fc6d8b..374b2a212 100644 --- a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx +++ b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx @@ -1,6 +1,6 @@ -import { useThree } from "@react-three/fiber"; -import { type ReactNode, createContext, useContext, useState } from "react"; -import { Vector3, Euler } from "three"; +import { useThree } from '@react-three/fiber'; +import { type ReactNode, createContext, useContext, useState } from 'react'; +import { Vector3, Euler } from 'three'; type ContextType = { setCameraAsOrigin: () => void; @@ -158,4 +158,3 @@ export function PlayAreaContext(props: Props) { export function usePlayArea() { return useContext(Context); } - diff --git a/src/bundles/ar/libraries/controls_library/ControlsContext.tsx b/src/bundles/ar/libraries/controls_library/ControlsContext.tsx index ee6d629d6..6804cb76b 100644 --- a/src/bundles/ar/libraries/controls_library/ControlsContext.tsx +++ b/src/bundles/ar/libraries/controls_library/ControlsContext.tsx @@ -1,13 +1,13 @@ -import { useFrame, useThree } from "@react-three/fiber"; +import { useFrame, useThree } from '@react-three/fiber'; import { type ReactNode, createContext, createRef, useContext, useRef, -} from "react"; -import { Mesh } from "three"; -import { getIntersection } from "./RayCast"; +} from 'react'; +import { Mesh } from 'three'; +import { getIntersection } from './RayCast'; type ContextType = { object: React.MutableRefObject; @@ -62,4 +62,3 @@ export function ControlsContext(props: Props) { export function useControls() { return useContext(Context); } - diff --git a/src/bundles/ar/libraries/controls_library/RayCast.tsx b/src/bundles/ar/libraries/controls_library/RayCast.tsx index e5ee615c1..53bb46fa9 100644 --- a/src/bundles/ar/libraries/controls_library/RayCast.tsx +++ b/src/bundles/ar/libraries/controls_library/RayCast.tsx @@ -6,7 +6,7 @@ import { type Object3DEventMap, Raycaster, Vector2, -} from "three"; +} from 'three'; const raycaster = new Raycaster(); @@ -65,4 +65,3 @@ function getTopParent( } return undefined; } - diff --git a/src/bundles/ar/libraries/object_state_library/ARObject.tsx b/src/bundles/ar/libraries/object_state_library/ARObject.tsx index 134d6c0e3..d8c5469cd 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObject.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObject.tsx @@ -3,7 +3,7 @@ import { MeshStandardMaterial, SphereGeometry, Vector3, -} from "three"; +} from 'three'; import { type Behaviours, LightModel, @@ -16,9 +16,9 @@ import { parseRender, parseMovement, MovementClass, -} from "./Behaviour"; -import ARObjectComponent from "./ARObjectComponent"; -import { parseVector3 } from "../calibration_library/Misc"; +} from './Behaviour'; +import ARObjectComponent from './ARObjectComponent'; +import { parseVector3 } from '../calibration_library/Misc'; /** * Abstract class for an AR object. @@ -28,7 +28,7 @@ import { parseVector3 } from "../calibration_library/Misc"; * This can be used for identifying and restoring the object's class after parsing the JSON. */ export class ARObject { - type: string = ""; // Unique identifier for class + type: string = ''; // Unique identifier for class id: string; position: Vector3; behaviours: Behaviours; @@ -68,7 +68,7 @@ export class ARObject { } } -const CUBE_OBJECT_TYPE = "CubeObject"; +const CUBE_OBJECT_TYPE = 'CubeObject'; export class CubeObject extends ARObject { type = CUBE_OBJECT_TYPE; width: number; @@ -118,12 +118,12 @@ export class CubeObject extends ARObject { let depth = object.depth; let color = object.color; if ( - typeof id === "string" && + typeof id === 'string' && position instanceof Vector3 && - typeof width === "number" && - typeof height === "number" && - typeof depth === "number" && - typeof color === "number" + typeof width === 'number' && + typeof height === 'number' && + typeof depth === 'number' && + typeof color === 'number' ) { return new CubeObject( id, @@ -142,7 +142,7 @@ export class CubeObject extends ARObject { } } -const SPHERE_OBJECT_TYPE = "SphereObject"; +const SPHERE_OBJECT_TYPE = 'SphereObject'; export class SphereObject extends ARObject { type = SPHERE_OBJECT_TYPE; radius: number; @@ -184,10 +184,10 @@ export class SphereObject extends ARObject { let radius = object.radius; let color = object.color; if ( - typeof id === "string" && + typeof id === 'string' && position instanceof Vector3 && - typeof radius === "number" && - typeof color === "number" + typeof radius === 'number' && + typeof color === 'number' ) { return new SphereObject( id, @@ -204,7 +204,7 @@ export class SphereObject extends ARObject { } } -const UI_OBJECT_TYPE = "UIObject"; +const UI_OBJECT_TYPE = 'UIObject'; export class UIObject extends ARObject { type = UI_OBJECT_TYPE; uiJson: any; @@ -239,7 +239,7 @@ export class UIObject extends ARObject { let movement = parseMovement(object.behaviours?.movement); let uiJson = object.uiJson; if ( - typeof id === "string" && + typeof id === 'string' && position instanceof Vector3 && uiJson !== undefined ) { @@ -257,7 +257,7 @@ export class UIObject extends ARObject { } } -const LIGHT_OBJECT_TYPE = "LightObject"; +const LIGHT_OBJECT_TYPE = 'LightObject'; export class LightObject extends ARObject { type = LIGHT_OBJECT_TYPE; intensity: number; @@ -274,9 +274,9 @@ export class LightObject extends ARObject { let position = parseVector3(object.position); let intensity = object.intensity; if ( - typeof id === "string" && + typeof id === 'string' && position instanceof Vector3 && - typeof intensity === "number" + typeof intensity === 'number' ) { return new LightObject(id, position, intensity); } diff --git a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx index f641c4ffd..f16772997 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx @@ -1,6 +1,6 @@ -import { Interactive } from "@react-three/xr"; -import { ARObject } from "./ARObject"; -import { type MutableRefObject, type ReactNode, useRef, useState } from "react"; +import { Interactive } from '@react-three/xr'; +import { ARObject } from './ARObject'; +import { type MutableRefObject, type ReactNode, useRef, useState } from 'react'; import { AlwaysRender, FixRotation, @@ -16,17 +16,17 @@ import { ShapeModel, SpringMovement, TextModel, -} from "./Behaviour"; -import { Mesh, Vector3 } from "three"; -import { useFrame } from "@react-three/fiber"; -import ErrorBoundary from "./ErrorBoundary"; -import GltfComponent from "./model_components/GltfComponent"; -import TextComponent from "./model_components/TextComponent"; -import ImageComponent from "./model_components/ImageComponent"; -import ShapeComponent from "./model_components/ShapeComponent"; -import { useSpring, SpringValue } from "@react-spring/three"; -import LightComponent from "./model_components/LightComponent"; -import InterfaceComponent from "./model_components/InterfaceComponent"; +} from './Behaviour'; +import { Mesh, Vector3 } from 'three'; +import { useFrame } from '@react-three/fiber'; +import ErrorBoundary from './ErrorBoundary'; +import GltfComponent from './model_components/GltfComponent'; +import TextComponent from './model_components/TextComponent'; +import ImageComponent from './model_components/ImageComponent'; +import ShapeComponent from './model_components/ShapeComponent'; +import { useSpring, SpringValue } from '@react-spring/three'; +import LightComponent from './model_components/LightComponent'; +import InterfaceComponent from './model_components/InterfaceComponent'; type Props = { arObject: ARObject; diff --git a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx index ff813510f..873f2e185 100644 --- a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx +++ b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx @@ -3,9 +3,9 @@ import { Material, type NormalBufferAttributes, Vector3, -} from "three"; -import { UIBasicComponent } from "./ui_component/UIComponent"; -import { parseVector3 } from "../calibration_library/Misc"; +} from 'three'; +import { UIBasicComponent } from './ui_component/UIComponent'; +import { parseVector3 } from '../calibration_library/Misc'; export type Behaviours = { model: ModelClass; @@ -93,8 +93,8 @@ export class LightModel implements ModelClass { // Render -const RENDER_DISTANCE = "RenderWithinDistance"; -const RENDER_ALWAYS = "AlwaysRender"; +const RENDER_DISTANCE = 'RenderWithinDistance'; +const RENDER_ALWAYS = 'AlwaysRender'; export function parseRender(render: any): RenderClass | undefined { if (!render) return undefined; @@ -104,7 +104,7 @@ export function parseRender(render: any): RenderClass | undefined { } case RENDER_DISTANCE: { let distance = 5; - if (typeof render.distance === "number") { + if (typeof render.distance === 'number') { distance = render.distance as number; } return new RenderWithinDistance(distance); @@ -114,7 +114,7 @@ export function parseRender(render: any): RenderClass | undefined { } export abstract class RenderClass implements Behaviour { - type: string = ""; + type: string = ''; } export class RenderWithinDistance implements RenderClass { @@ -131,9 +131,9 @@ export class AlwaysRender implements RenderClass { // Rotation -const ROTATION_USER = "RotateToUser"; -const ROTATION_Y = "RotateAroundY"; -const ROTATION_FIX = "FixRotation"; +const ROTATION_USER = 'RotateToUser'; +const ROTATION_Y = 'RotateAroundY'; +const ROTATION_FIX = 'FixRotation'; export function parseRotation(rotation: any): RotationClass | undefined { if (!rotation) return undefined; @@ -146,7 +146,7 @@ export function parseRotation(rotation: any): RotationClass | undefined { } case ROTATION_FIX: { let angle = 0; - if (typeof rotation.rotation === "number") { + if (typeof rotation.rotation === 'number') { angle = rotation.rotation as number; } return new FixRotation(angle); @@ -159,7 +159,7 @@ export function parseRotation(rotation: any): RotationClass | undefined { * Base class for a rotation behaviour. */ export abstract class RotationClass implements Behaviour { - type: string = ""; + type: string = ''; } /** @@ -189,9 +189,9 @@ export class FixRotation implements RotationClass { // Movement -const MOVEMENT_PATH = "PathMovement"; -const MOVEMENT_ORBIT = "OrbitMovement"; -const MOVEMENT_SPRING = "SpringMovement"; +const MOVEMENT_PATH = 'PathMovement'; +const MOVEMENT_ORBIT = 'OrbitMovement'; +const MOVEMENT_SPRING = 'SpringMovement'; export function parseMovement(movement: any, getCurrentTime?: () => number) { if (!movement) return undefined; @@ -200,7 +200,7 @@ export function parseMovement(movement: any, getCurrentTime?: () => number) { let startTime = movement.startTime; let pathItems = movement.path; if ( - (startTime === undefined || typeof startTime === "number") && + (startTime === undefined || typeof startTime === 'number') && Array.isArray(pathItems) ) { let parsedPathItems = parsePathItems(pathItems); @@ -213,9 +213,9 @@ export function parseMovement(movement: any, getCurrentTime?: () => number) { let duration = movement.duration; let startTime = movement.startTime; if ( - typeof radius === "number" && - typeof duration === "number" && - (startTime === undefined || typeof startTime === "number") + typeof radius === 'number' && + typeof duration === 'number' && + (startTime === undefined || typeof startTime === 'number') ) { return new OrbitMovement(radius, duration, startTime, getCurrentTime); } @@ -233,7 +233,7 @@ export function parseMovement(movement: any, getCurrentTime?: () => number) { * @param startTime Reference time for the start of movement, for syncing */ export abstract class MovementClass implements Behaviour { - type: string = ""; + type: string = ''; } /** @@ -256,7 +256,7 @@ function parsePathItems(path: any[]) { if ( start instanceof Vector3 && end instanceof Vector3 && - (duration === undefined || typeof duration === "number") + (duration === undefined || typeof duration === 'number') ) { let movementStyle = MovementStyle.Linear; if (item.style === MovementStyle.FastToSlow) { @@ -410,4 +410,3 @@ export class OrbitMovement extends MovementClass { export class SpringMovement extends MovementClass { type = MOVEMENT_SPRING; } - diff --git a/src/bundles/ar/libraries/object_state_library/ErrorBoundary.tsx b/src/bundles/ar/libraries/object_state_library/ErrorBoundary.tsx index bd034af66..58efd5440 100644 --- a/src/bundles/ar/libraries/object_state_library/ErrorBoundary.tsx +++ b/src/bundles/ar/libraries/object_state_library/ErrorBoundary.tsx @@ -1,4 +1,4 @@ -import { Component, type ReactNode } from "react"; +import { Component, type ReactNode } from 'react'; interface Props { fallback: ReactNode; @@ -25,4 +25,3 @@ export default class ErrorBoundary extends Component { return this.props.children; } } - diff --git a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx index 313efff1a..29539e38b 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx @@ -1,4 +1,4 @@ -import { useFrame } from "@react-three/fiber"; +import { useFrame } from '@react-three/fiber'; import { type MutableRefObject, type ReactNode, @@ -6,13 +6,13 @@ import { useEffect, useRef, useState, -} from "react"; -import * as SkeletonUtils from "three/examples/jsm/utils/SkeletonUtils.js"; -import { Object3D, type Object3DEventMap, AnimationMixer } from "three"; -import { GltfModel } from "../Behaviour"; -import { ARObject } from "../ARObject"; -import { SpringValue, animated } from "@react-spring/three"; -import { useGLTF } from "@react-three/drei"; +} from 'react'; +import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js'; +import { Object3D, type Object3DEventMap, AnimationMixer } from 'three'; +import { GltfModel } from '../Behaviour'; +import { ARObject } from '../ARObject'; +import { SpringValue, animated } from '@react-spring/three'; +import { useGLTF } from '@react-three/drei'; type GltfProps = { gltfModel: GltfModel; diff --git a/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx index 01d92a291..b85088228 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx @@ -1,7 +1,7 @@ -import { type MutableRefObject } from "react"; -import { ImageModel } from "../Behaviour"; -import { Image } from "@react-three/drei"; -import { SpringValue, animated } from "@react-spring/three"; +import { type MutableRefObject } from 'react'; +import { ImageModel } from '../Behaviour'; +import { Image } from '@react-three/drei'; +import { SpringValue, animated } from '@react-spring/three'; type ImageProps = { imageModel: ImageModel; @@ -19,4 +19,3 @@ export default function ImageComponent(props: ImageProps) { ); } - diff --git a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx index b0ac84591..2f7593848 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx @@ -1,21 +1,21 @@ -import { SpringValue, animated } from "@react-spring/three"; -import { InterfaceModel as InterfaceModel } from "../Behaviour"; +import { SpringValue, animated } from '@react-spring/three'; +import { InterfaceModel as InterfaceModel } from '../Behaviour'; import { useEffect, type MutableRefObject, useState, type ReactNode, -} from "react"; -import { UIBasicComponent } from "../ui_component/UIComponent"; +} from 'react'; +import { UIBasicComponent } from '../ui_component/UIComponent'; import UIRowComponent, { VerticalAlignment, -} from "../ui_component/UIRowComponent"; +} from '../ui_component/UIRowComponent'; import UIColumnComponent, { HorizontalAlignment, -} from "../ui_component/UIColumnComponent"; -import UITextComponent from "../ui_component/UITextComponent"; -import { Vector3 } from "three"; -import UIImageComponent from "../ui_component/UIImageComponent"; +} from '../ui_component/UIColumnComponent'; +import UITextComponent from '../ui_component/UITextComponent'; +import { Vector3 } from 'three'; +import UIImageComponent from '../ui_component/UIImageComponent'; type InterfaceProps = { interfaceModel: InterfaceModel; @@ -53,19 +53,19 @@ export function parseJsonInterface(uiJson: any) { let paddingTop = uiJson.paddingTop; let paddingBottom = uiJson.paddingBottom; if ( - typeof id !== "string" || - typeof paddingLeft !== "number" || - typeof paddingRight !== "number" || - typeof paddingTop !== "number" || - typeof paddingBottom !== "number" + typeof id !== 'string' || + typeof paddingLeft !== 'number' || + typeof paddingRight !== 'number' || + typeof paddingTop !== 'number' || + typeof paddingBottom !== 'number' ) { return; } switch (componentType) { - case "UIColumnComponent": { + case 'UIColumnComponent': { let horizontalAlignmentIndex = uiJson.horizontalAlignment; let horizontalAlignment = HorizontalAlignment.Left; - if (typeof horizontalAlignmentIndex === "number") { + if (typeof horizontalAlignmentIndex === 'number') { let parsedIndex = Math.min(Math.max(0, horizontalAlignmentIndex), 2); horizontalAlignment = parsedIndex; } @@ -80,7 +80,7 @@ export function parseJsonInterface(uiJson: any) { }); } let background = uiJson.background; - if (typeof background != "number") { + if (typeof background != 'number') { background = undefined; } return new UIColumnComponent({ @@ -96,10 +96,10 @@ export function parseJsonInterface(uiJson: any) { id: id, }); } - case "UIRowComponent": { + case 'UIRowComponent': { let verticalAlignmentIndex = uiJson.verticalAlignment; let verticalAlignment = VerticalAlignment.Top; - if (typeof verticalAlignmentIndex === "number") { + if (typeof verticalAlignmentIndex === 'number') { let parsedIndex = Math.min(Math.max(0, verticalAlignmentIndex), 2); verticalAlignment = parsedIndex; } @@ -114,7 +114,7 @@ export function parseJsonInterface(uiJson: any) { }); } let background = uiJson.background; - if (typeof background != "number") { + if (typeof background != 'number') { background = undefined; } return new UIRowComponent({ @@ -130,17 +130,17 @@ export function parseJsonInterface(uiJson: any) { id: id, }); } - case "UITextComponent": { + case 'UITextComponent': { let text = uiJson.text; let textSize = uiJson.textSize; let textWidth = uiJson.textWidth; let textAlign = uiJson.textAlign; let color = uiJson.color; if ( - typeof text === "string" && - typeof textSize === "number" && - typeof textWidth === "number" && - typeof color === "number" + typeof text === 'string' && + typeof textSize === 'number' && + typeof textWidth === 'number' && + typeof color === 'number' ) { return new UITextComponent({ text: text, @@ -159,14 +159,14 @@ export function parseJsonInterface(uiJson: any) { } break; } - case "UIImageComponent": { + case 'UIImageComponent': { let src = uiJson.src; let imageWidth = uiJson.imageWidth; let imageHeight = uiJson.imageHeight; if ( - typeof src === "string" && - typeof imageWidth === "number" && - typeof imageHeight === "number" + typeof src === 'string' && + typeof imageWidth === 'number' && + typeof imageHeight === 'number' ) { return new UIImageComponent({ src: src, @@ -186,4 +186,3 @@ export function parseJsonInterface(uiJson: any) { } return undefined; } - diff --git a/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx index f93c041a7..6434e40f1 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx @@ -1,7 +1,7 @@ -import { type MutableRefObject, type ReactNode } from "react"; -import { ARObject } from "../ARObject"; -import { LightModel } from "../Behaviour"; -import { SpringValue, animated } from "@react-spring/three"; +import { type MutableRefObject, type ReactNode } from 'react'; +import { ARObject } from '../ARObject'; +import { LightModel } from '../Behaviour'; +import { SpringValue, animated } from '@react-spring/three'; type LightProps = { lightModel: LightModel; @@ -18,4 +18,3 @@ export default function LightComponent(props: LightProps) { ); } - diff --git a/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx index d7286941d..32250367c 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx @@ -1,8 +1,8 @@ -import { type MutableRefObject, type ReactNode } from "react"; -import { ShapeModel } from "../Behaviour"; -import { SpringValue, animated } from "@react-spring/three"; -import { Outlines } from "@react-three/drei"; -import { Color } from "three"; +import { type MutableRefObject, type ReactNode } from 'react'; +import { ShapeModel } from '../Behaviour'; +import { SpringValue, animated } from '@react-spring/three'; +import { Outlines } from '@react-three/drei'; +import { Color } from 'three'; type ShapeProps = { shapeModel: ShapeModel; @@ -32,4 +32,3 @@ export default function ShapeComponent(props: ShapeProps) { ); } - diff --git a/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx index 5fdf5d989..5dc7710f6 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx @@ -1,8 +1,8 @@ -import { type MutableRefObject, type ReactNode, useRef, useState } from "react"; -import { TextModel } from "../Behaviour"; -import { Mesh } from "three"; -import { Text } from "@react-three/drei"; -import { SpringValue, animated } from "@react-spring/three"; +import { type MutableRefObject, type ReactNode, useRef, useState } from 'react'; +import { TextModel } from '../Behaviour'; +import { Mesh } from 'three'; +import { Text } from '@react-three/drei'; +import { SpringValue, animated } from '@react-spring/three'; type TextProps = { textModel: TextModel; @@ -49,4 +49,3 @@ export default function TextComponent(props: TextProps) { ); } - diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index bd285fa43..d89ae3275 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -1,10 +1,10 @@ -import { type ReactNode, useState } from "react"; +import { type ReactNode, useState } from 'react'; import { UIBasicComponent, LayoutComponent, type PaddingType, -} from "./UIComponent"; -import { Color, Vector3 } from "three"; +} from './UIComponent'; +import { Color, Vector3 } from 'three'; type UIColumnProps = { children?: UIBasicComponent[]; @@ -115,16 +115,16 @@ function ColumnUIComponent(props: { let child = component.children[i]; let childPosition = props.componentPositions[i]; children.push( - + {child.getComponent(childPosition, updateSize)} ); } - return {children}; + return {children}; } return ( - + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx index 713d0ee91..a2a08342e 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx @@ -1,107 +1,107 @@ -import { useEffect } from "react"; -import { Vector3 } from "three"; -import uniqid from "uniqid"; +import { useEffect } from 'react'; +import { Vector3 } from 'three'; +import uniqid from 'uniqid'; type UIComponentProps = { - position: Vector3; - children: LayoutComponent; + position: Vector3; + children: LayoutComponent; }; export default function UIComponent(props: UIComponentProps) { - useEffect(() => { - props.children.calculateDimensions(); - }, [props.children]); - return {props.children.getComponent(props.position, () => {})}; + useEffect(() => { + props.children.calculateDimensions(); + }, [props.children]); + return {props.children.getComponent(props.position, () => {})}; } export type PaddingType = { - paddingLeft?: number; - paddingRight?: number; - paddingTop?: number; - paddingBottom?: number; + paddingLeft?: number; + paddingRight?: number; + paddingTop?: number; + paddingBottom?: number; }; export class UIBasicComponent { - type: string; - paddingLeft = 0; - paddingRight = 0; - paddingTop = 0; - paddingBottom = 0; - height = 0; - width = 0; - id = ""; - layer = 1; - parent?: UIBasicComponent = undefined; - constructor(padding?: number | PaddingType, id?: string) { - this.type = this.constructor.name; - if (padding) { - if (typeof padding === "number") { - this.paddingLeft = padding; - this.paddingRight = padding; - this.paddingTop = padding; - this.paddingBottom = padding; - } else { - if (padding.paddingLeft) { - this.paddingLeft = padding.paddingLeft; - } - if (padding.paddingRight) { - this.paddingRight = padding.paddingRight; - } - if (padding.paddingTop) { - this.paddingTop = padding.paddingTop; - } - if (padding.paddingBottom) { - this.paddingBottom = padding.paddingBottom; - } - } + type: string; + paddingLeft = 0; + paddingRight = 0; + paddingTop = 0; + paddingBottom = 0; + height = 0; + width = 0; + id = ''; + layer = 1; + parent?: UIBasicComponent = undefined; + constructor(padding?: number | PaddingType, id?: string) { + this.type = this.constructor.name; + if (padding) { + if (typeof padding === 'number') { + this.paddingLeft = padding; + this.paddingRight = padding; + this.paddingTop = padding; + this.paddingBottom = padding; + } else { + if (padding.paddingLeft) { + this.paddingLeft = padding.paddingLeft; } - if (id) { - this.id = id; - } else { - this.id = uniqid(); + if (padding.paddingRight) { + this.paddingRight = padding.paddingRight; } - } - toJSON = () => { - let object = Object.assign({}, this) as any; - delete object.height; - delete object.width; - delete object.layer; - delete object.parent; - return object; - }; - calculateDimensions = () => { - this.calculateLayer(); - let newHeight = this.getHeight(); - let newWidth = this.getWidth(); - if (this.height !== newHeight || this.width !== newWidth) { - this.height = newHeight; - this.width = newWidth; - this.parent?.calculateDimensions(); + if (padding.paddingTop) { + this.paddingTop = padding.paddingTop; + } + if (padding.paddingBottom) { + this.paddingBottom = padding.paddingBottom; } - }; - getWidth = () => { - return this.paddingLeft + this.paddingRight; - }; - getHeight = () => { - return this.paddingTop + this.paddingBottom; - }; - calculateLayer = () => {}; - getComponent = (position: Vector3, updateParent: () => void) => { - return ; - }; + } + } + if (id) { + this.id = id; + } else { + this.id = uniqid(); + } + } + toJSON = () => { + let object = Object.assign({}, this) as any; + delete object.height; + delete object.width; + delete object.layer; + delete object.parent; + return object; + }; + calculateDimensions = () => { + this.calculateLayer(); + let newHeight = this.getHeight(); + let newWidth = this.getWidth(); + if (this.height !== newHeight || this.width !== newWidth) { + this.height = newHeight; + this.width = newWidth; + this.parent?.calculateDimensions(); + } + }; + getWidth = () => { + return this.paddingLeft + this.paddingRight; + }; + getHeight = () => { + return this.paddingTop + this.paddingBottom; + }; + calculateLayer = () => {}; + getComponent = (position: Vector3, updateParent: () => void) => { + return ; + }; } export class LayoutComponent extends UIBasicComponent { - children: UIBasicComponent[] = []; - calculateLayer = () => { - this.layer = 1; - this.children.forEach((child) => { - if (child instanceof LayoutComponent && this.layer <= child.layer) { - this.layer = child.layer + 1; - } - }); - this.children.forEach((child) => { - child.parent = this; - }); - }; + children: UIBasicComponent[] = []; + calculateLayer = () => { + this.layer = 1; + this.children.forEach((child) => { + if (child instanceof LayoutComponent && this.layer <= child.layer) { + this.layer = child.layer + 1; + } + }); + this.children.forEach((child) => { + child.parent = this; + }); + }; } diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx index 9e0582b0c..c7af141b8 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx @@ -1,6 +1,6 @@ -import { Vector3 } from "three"; -import { UIBasicComponent, type PaddingType } from "./UIComponent"; -import { Image } from "@react-three/drei"; +import { Vector3 } from 'three'; +import { UIBasicComponent, type PaddingType } from './UIComponent'; +import { Image } from '@react-three/drei'; type UIImageProps = { src: string; @@ -26,7 +26,7 @@ export default class UIImageComponent extends UIBasicComponent { getHeight = () => { return this.imageHeight + this.paddingTop + this.paddingBottom; }; - getComponent = (position: Vector3, updateParent: () => void) => { + getComponent = (position: Vector3, _: () => void) => { return ( ); @@ -39,7 +39,7 @@ function ImageUIComponent(props: { }) { let { component, position } = props; return ( - + + {child.getComponent(childPosition, updateSize)} ); } - return {children}; + return {children}; } return ( - + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx index b0df82d3a..21e9a5f5d 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -1,8 +1,8 @@ -import { Color, Mesh, Vector3 } from "three"; -import { UIBasicComponent, type PaddingType } from "./UIComponent"; -import { Text } from "@react-three/drei"; -import { useEffect, useRef, useState } from "react"; -import { HorizontalAlignment } from "./UIColumnComponent"; +import { Color, Mesh, Vector3 } from 'three'; +import { UIBasicComponent, type PaddingType } from './UIComponent'; +import { Text } from '@react-three/drei'; +import { useEffect, useRef, useState } from 'react'; +import { HorizontalAlignment } from './UIColumnComponent'; type UITextProps = { text: string; @@ -118,15 +118,15 @@ function TextUIComponent(props: { function getTextAlign(alignment: HorizontalAlignment) { switch (alignment) { case HorizontalAlignment.Left: - return "left"; + return 'left'; case HorizontalAlignment.Right: - return "right"; + return 'right'; } - return "center"; + return 'center'; } return ( - + | null; @@ -55,7 +55,7 @@ export function ScreenStateContext(props: Props) { <> -
+
); const [component, setComponent] = useState(defaultComponent); @@ -94,13 +94,13 @@ export function ScreenStateContext(props: Props) {
{isXRSession.current ? overlayState : <>} diff --git a/src/bundles/communication/Communications.ts b/src/bundles/communication/Communications.ts index d7a1d2a82..bde585f51 100644 --- a/src/bundles/communication/Communications.ts +++ b/src/bundles/communication/Communications.ts @@ -1,7 +1,7 @@ -import context from "js-slang/context"; -import { MultiUserController } from "./MultiUserController"; -import { GlobalStateController } from "./GlobalStateController"; -import { RpcController } from "./RpcController"; +import context from 'js-slang/context'; +import { MultiUserController } from './MultiUserController'; +import { GlobalStateController } from './GlobalStateController'; +import { RpcController } from './RpcController'; class CommunicationModuleState { multiUser: MultiUserController; @@ -15,7 +15,7 @@ class CommunicationModuleState { } } -let moduleState = new CommunicationModuleState("broker.hivemq.com", 8884); +let moduleState = new CommunicationModuleState('broker.hivemq.com', 8884); context.moduleContexts.communication.state = moduleState; // Loop diff --git a/src/bundles/communication/GlobalStateController.ts b/src/bundles/communication/GlobalStateController.ts index d850ba7c1..6f9856478 100644 --- a/src/bundles/communication/GlobalStateController.ts +++ b/src/bundles/communication/GlobalStateController.ts @@ -1,4 +1,4 @@ -import { MultiUserController } from "./MultiUserController"; +import { MultiUserController } from './MultiUserController'; /** * Controller for maintaining a global state across all devices. @@ -50,15 +50,15 @@ export class GlobalStateController { } return; } - if (!preSplitTopic.startsWith("/")) { - preSplitTopic = "/" + preSplitTopic; + if (!preSplitTopic.startsWith('/')) { + preSplitTopic = '/' + preSplitTopic; } - let splitTopic = preSplitTopic.split("/"); + let splitTopic = preSplitTopic.split('/'); try { let newGlobalState = Object.assign({}, this.globalState); if ( this.globalState instanceof Array || - typeof this.globalState === "string" + typeof this.globalState === 'string' ) { newGlobalState = {}; } @@ -68,7 +68,7 @@ export class GlobalStateController { if ( !(currentJson[subTopic] instanceof Object) || currentJson[subTopic] instanceof Array || - typeof currentJson[subTopic] === "string" + typeof currentJson[subTopic] === 'string' ) { currentJson[subTopic] = {}; } @@ -104,8 +104,8 @@ export class GlobalStateController { public updateGlobalState(path: string, updatedState: any) { if (this.topicHeader.length === 0) return; let topic = this.topicHeader; - if (path.length !== 0 && !path.startsWith("/")) { - topic += "/"; + if (path.length !== 0 && !path.startsWith('/')) { + topic += '/'; } topic += path; this.multiUser.controller?.publish( diff --git a/src/bundles/communication/MqttController.ts b/src/bundles/communication/MqttController.ts index 476bca5ad..339ab620c 100644 --- a/src/bundles/communication/MqttController.ts +++ b/src/bundles/communication/MqttController.ts @@ -1,9 +1,9 @@ -import { connect, MqttClient } from "mqtt/dist/mqtt"; +import { connect, MqttClient } from 'mqtt/dist/mqtt'; -export const STATE_CONNECTED = "Connected"; -export const STATE_DISCONNECTED = "Disconnected"; -export const STATE_RECONNECTED = "Reconnected"; -export const STATE_OFFLINE = "Offline"; +export const STATE_CONNECTED = 'Connected'; +export const STATE_DISCONNECTED = 'Disconnected'; +export const STATE_RECONNECTED = 'Reconnected'; +export const STATE_OFFLINE = 'Offline'; /** * Abstraction of MQTT. @@ -17,7 +17,7 @@ export class MqttController { private connectionCallback: (status: string) => void; private messageCallback: (topic: string, message: string) => void; - address: string = ""; + address: string = ''; port: number = 8080; constructor( @@ -34,23 +34,23 @@ export class MqttController { */ public connectClient() { if (this.connected || this.address.length === 0) return; - let link = "wss://" + this.address + ":" + this.port + "/mqtt"; + let link = 'wss://' + this.address + ':' + this.port + '/mqtt'; this.client = connect(link); this.connected = true; - this.client.on("connect", () => { + this.client.on('connect', () => { this.connectionCallback(STATE_CONNECTED); }); - this.client.on("disconnect", () => { + this.client.on('disconnect', () => { this.connectionCallback(STATE_DISCONNECTED); }); - this.client.on("reconnect", () => { + this.client.on('reconnect', () => { this.connectionCallback(STATE_RECONNECTED); }); - this.client.on("offline", () => { + this.client.on('offline', () => { this.connectionCallback(STATE_OFFLINE); }); - this.client.on("message", (topic, message) => { - this.messageCallback(topic, new TextDecoder("utf-8").decode(message)); + this.client.on('message', (topic, message) => { + this.messageCallback(topic, new TextDecoder('utf-8').decode(message)); }); } diff --git a/src/bundles/communication/MultiUserController.ts b/src/bundles/communication/MultiUserController.ts index 48b777cfc..72711fad4 100644 --- a/src/bundles/communication/MultiUserController.ts +++ b/src/bundles/communication/MultiUserController.ts @@ -1,4 +1,4 @@ -import { MqttController, STATE_DISCONNECTED } from "./MqttController"; +import { MqttController, STATE_DISCONNECTED } from './MqttController'; /** * Controller with implementation of MQTT. @@ -28,9 +28,9 @@ export class MultiUserController { this.connectionState = status; }, (topic: string, message: string) => { - let splitTopic = topic.split("/"); + let splitTopic = topic.split('/'); this.messageCallbacks.forEach((callback, identifier) => { - let splitIdentifier = identifier.split("/"); + let splitIdentifier = identifier.split('/'); if (splitTopic.length < splitIdentifier.length) return; for (let i = 0; i < splitIdentifier.length; i++) { if (splitIdentifier[i] !== splitTopic[i]) return; @@ -56,7 +56,7 @@ export class MultiUserController { identifier: string, callback: (topic: string, message: string) => void ) { - this.controller?.subscribe(identifier + "/#"); + this.controller?.subscribe(identifier + '/#'); this.messageCallbacks.set(identifier, callback); } } diff --git a/src/bundles/communication/RpcController.ts b/src/bundles/communication/RpcController.ts index a876ea455..21ff9a97d 100644 --- a/src/bundles/communication/RpcController.ts +++ b/src/bundles/communication/RpcController.ts @@ -1,9 +1,9 @@ -import { MultiUserController } from "./MultiUserController"; -import uniqid from "uniqid"; +import { MultiUserController } from './MultiUserController'; +import uniqid from 'uniqid'; type DeclaredFunction = { - name: string; - func: (...args: any[]) => any; + name: string; + func: (...args: any[]) => any; }; /** @@ -14,117 +14,110 @@ type DeclaredFunction = { * @param userId ID of the user, used for identifying caller/callee. */ export class RpcController { - private topicHeader: string; - private multiUser: MultiUserController; - private userId: string; - private functions = new Map(); - private pendingReturns = new Map void>(); - private returnTopic: string; + private topicHeader: string; + private multiUser: MultiUserController; + private userId: string; + private functions = new Map(); + private pendingReturns = new Map void>(); + private returnTopic: string; - constructor( - topicHeader: string, - multiUser: MultiUserController, - userId?: string - ) { - this.topicHeader = topicHeader; - this.multiUser = multiUser; - this.userId = userId ?? uniqid(); - this.returnTopic = this.topicHeader + "_return/" + this.userId; - this.multiUser.addMessageCallback( - this.returnTopic, - (topic, message) => { - let messageJson = JSON.parse(message); - let callId = messageJson.callId; - let callback = this.pendingReturns.get(callId); - if (callback) { - this.pendingReturns.delete(callId); - callback(messageJson.result); - } - } - ); - } + constructor( + topicHeader: string, + multiUser: MultiUserController, + userId?: string + ) { + this.topicHeader = topicHeader; + this.multiUser = multiUser; + this.userId = userId ?? uniqid(); + this.returnTopic = this.topicHeader + '_return/' + this.userId; + this.multiUser.addMessageCallback(this.returnTopic, (topic, message) => { + let messageJson = JSON.parse(message); + let callId = messageJson.callId; + let callback = this.pendingReturns.get(callId); + if (callback) { + this.pendingReturns.delete(callId); + callback(messageJson.result); + } + }); + } - /** - * Sends return value back to caller. - * - * @param sender ID of caller. - * @param callId ID of function call. - * @param value Return value for function call. - */ - private returnResponse(sender: string, callId: string, value: any) { - let message = { - callId: callId, - result: value, - }; - let topic = this.topicHeader + "_return/" + sender; - this.multiUser.controller?.publish( - topic, - JSON.stringify(message), - false - ); - } + /** + * Sends return value back to caller. + * + * @param sender ID of caller. + * @param callId ID of function call. + * @param value Return value for function call. + */ + private returnResponse(sender: string, callId: string, value: any) { + let message = { + callId: callId, + result: value, + }; + let topic = this.topicHeader + '_return/' + sender; + this.multiUser.controller?.publish(topic, JSON.stringify(message), false); + } - /** - * Exposes a function to other callers. - * - * @param name Name for the function, cannot include '/'. - * @param func Function to run. - */ - public expose(name: string, func: (...args: any[]) => any) { - let item = { - name: name, - func: func, - }; - this.functions.set(name, item); - let functionTopic = this.topicHeader + "/" + this.userId + "/" + name; - this.multiUser.addMessageCallback(functionTopic, (topic, message) => { - let splitTopic = topic.split("/"); - if (splitTopic.length !== 3) { - return; - } - let parsedMessage = JSON.parse(message); - let callId = parsedMessage.callId; - let sender = parsedMessage.sender; - if (!callId || !sender) return; - let name = splitTopic[2]; - let func = this.functions.get(name); - if (!func) { - this.returnResponse(sender, callId, null); - return; - } - try { - let args = parsedMessage.args; - let result = func?.func(...args); - this.returnResponse(sender, callId, result); - } catch { - this.returnResponse(sender, callId, null); - } - }); - } + /** + * Exposes a function to other callers. + * + * @param name Name for the function, cannot include '/'. + * @param func Function to run. + */ + public expose(name: string, func: (...args: any[]) => any) { + let item = { + name: name, + func: func, + }; + this.functions.set(name, item); + let functionTopic = this.topicHeader + '/' + this.userId + '/' + name; + this.multiUser.addMessageCallback(functionTopic, (topic, message) => { + let splitTopic = topic.split('/'); + if (splitTopic.length !== 3) { + return; + } + let parsedMessage = JSON.parse(message); + let callId = parsedMessage.callId; + let sender = parsedMessage.sender; + if (!callId || !sender) return; + let name = splitTopic[2]; + let func = this.functions.get(name); + if (!func) { + this.returnResponse(sender, callId, null); + return; + } + try { + let args = parsedMessage.args; + let result = func?.func(...args); + this.returnResponse(sender, callId, result); + } catch { + this.returnResponse(sender, callId, null); + } + }); + } - /** - * Calls a function on another device. - * - * @param receiver ID of the callee. - * @param name Name of the function to call. - * @param args Argument values of the function. - * @param callback Callback for return value received. - */ - public callFunction( - receiver: string, - name: string, - args: any[], - callback: (args: any[]) => void - ) { - let topic = this.topicHeader + "/" + receiver + "/" + name; - let callId = uniqid(); - this.pendingReturns.set(callId, callback); - let messageJson = { - sender: this.userId, - callId: callId, - args: args, - }; - let messageString = JSON.stringify(messageJson); - this.multiUser.controller?.publish(topic, messageString, false); - } + /** + * Calls a function on another device. + * + * @param receiver ID of the callee. + * @param name Name of the function to call. + * @param args Argument values of the function. + * @param callback Callback for return value received. + */ + public callFunction( + receiver: string, + name: string, + args: any[], + callback: (args: any[]) => void + ) { + let topic = this.topicHeader + '/' + receiver + '/' + name; + let callId = uniqid(); + this.pendingReturns.set(callId, callback); + let messageJson = { + sender: this.userId, + callId: callId, + args: args, + }; + let messageString = JSON.stringify(messageJson); + this.multiUser.controller?.publish(topic, messageString, false); + } } diff --git a/src/bundles/communication/__tests__/index.ts b/src/bundles/communication/__tests__/index.ts index dfdb11551..dce4ad1a2 100644 --- a/src/bundles/communication/__tests__/index.ts +++ b/src/bundles/communication/__tests__/index.ts @@ -1,30 +1,30 @@ -import { MultiUserController } from "../MultiUserController"; -import { GlobalStateController } from "../GlobalStateController"; +import { MultiUserController } from '../MultiUserController'; +import { GlobalStateController } from '../GlobalStateController'; let multiUser = new MultiUserController(); -multiUser.setupController("broker.hivemq.com", 8884); +multiUser.setupController('broker.hivemq.com', 8884); let globalStateController = new GlobalStateController( - "test", + 'test', multiUser, (_) => {} ); // Empty Root - Replace root. -test("Empty Root Set Null", () => { +test('Empty Root Set Null', () => { globalStateController.globalState = undefined; - globalStateController.parseGlobalStateMessage("", JSON.stringify(null)); + globalStateController.parseGlobalStateMessage('', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( JSON.stringify(null) ); }); -test("Empty Root Set Object", () => { +test('Empty Root Set Object', () => { globalStateController.globalState = undefined; let object = { - a: "b", + a: 'b', }; - globalStateController.parseGlobalStateMessage("", JSON.stringify(object)); + globalStateController.parseGlobalStateMessage('', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( JSON.stringify(object) ); @@ -32,36 +32,36 @@ test("Empty Root Set Object", () => { // Non-Empty Root - Replace root. -test("Non-Empty Root Set Empty", () => { +test('Non-Empty Root Set Empty', () => { let object = { - a: "b", + a: 'b', }; globalStateController.globalState = object; - globalStateController.parseGlobalStateMessage("", JSON.stringify(undefined)); + globalStateController.parseGlobalStateMessage('', JSON.stringify(undefined)); expect(JSON.stringify(globalStateController.globalState)).toBe( JSON.stringify(undefined) ); }); -test("Non-Empty Root Set Null", () => { +test('Non-Empty Root Set Null', () => { let object = { - a: "b", + a: 'b', }; globalStateController.globalState = object; - globalStateController.parseGlobalStateMessage("", JSON.stringify(null)); + globalStateController.parseGlobalStateMessage('', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( JSON.stringify(null) ); }); -test("Non-Empty Root Set Object", () => { +test('Non-Empty Root Set Object', () => { globalStateController.globalState = { - a: "b", + a: 'b', }; let object = { - c: "d", + c: 'd', }; - globalStateController.parseGlobalStateMessage("", JSON.stringify(object)); + globalStateController.parseGlobalStateMessage('', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( JSON.stringify(object) ); @@ -69,25 +69,25 @@ test("Non-Empty Root Set Object", () => { // Branch Value - Replace value if non-empty, remove path if empty. -test("Branch Value Set Empty", () => { +test('Branch Value Set Empty', () => { globalStateController.globalState = { - a: "b", - c: "d", + a: 'b', + c: 'd', }; - globalStateController.parseGlobalStateMessage("a", JSON.stringify(undefined)); + globalStateController.parseGlobalStateMessage('a', JSON.stringify(undefined)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ c: "d" }) + JSON.stringify({ c: 'd' }) ); }); -test("Nested Branch Value Set Empty", () => { +test('Nested Branch Value Set Empty', () => { globalStateController.globalState = { a: { - b: "c", + b: 'c', }, }; globalStateController.parseGlobalStateMessage( - "a/b", + 'a/b', JSON.stringify(undefined) ); expect(JSON.stringify(globalStateController.globalState)).toBe( @@ -95,53 +95,53 @@ test("Nested Branch Value Set Empty", () => { ); }); -test("Branch Value Set Null", () => { +test('Branch Value Set Null', () => { globalStateController.globalState = { - a: "b", - c: "d", + a: 'b', + c: 'd', }; - globalStateController.parseGlobalStateMessage("a", JSON.stringify(null)); + globalStateController.parseGlobalStateMessage('a', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: null, c: "d" }) + JSON.stringify({ a: null, c: 'd' }) ); }); -test("Nested Branch Value Set Null", () => { +test('Nested Branch Value Set Null', () => { globalStateController.globalState = { a: { - b: "c", + b: 'c', }, }; - globalStateController.parseGlobalStateMessage("a/b", JSON.stringify(null)); + globalStateController.parseGlobalStateMessage('a/b', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( JSON.stringify({ a: { b: null } }) ); }); -test("Branch Value Set Object", () => { +test('Branch Value Set Object', () => { globalStateController.globalState = { - a: "b", - c: "d", + a: 'b', + c: 'd', }; let object = { - b: "e", + b: 'e', }; - globalStateController.parseGlobalStateMessage("a", JSON.stringify(object)); + globalStateController.parseGlobalStateMessage('a', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: object, c: "d" }) + JSON.stringify({ a: object, c: 'd' }) ); }); -test("Nested Branch Value Set Object", () => { +test('Nested Branch Value Set Object', () => { globalStateController.globalState = { a: { - b: "c", + b: 'c', }, }; let object = { - c: "d", + c: 'd', }; - globalStateController.parseGlobalStateMessage("a/b", JSON.stringify(object)); + globalStateController.parseGlobalStateMessage('a/b', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( JSON.stringify({ a: { b: object } }) ); @@ -149,76 +149,76 @@ test("Nested Branch Value Set Object", () => { // Branch Object - Replace object if non-empty, remove path if empty. -test("Branch Object Set Empty", () => { +test('Branch Object Set Empty', () => { globalStateController.globalState = { - a: { b: "c" }, - d: "e", + a: { b: 'c' }, + d: 'e', }; - globalStateController.parseGlobalStateMessage("a", JSON.stringify(undefined)); + globalStateController.parseGlobalStateMessage('a', JSON.stringify(undefined)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ d: "e" }) + JSON.stringify({ d: 'e' }) ); }); -test("Nested Branch Object Set Empty", () => { +test('Nested Branch Object Set Empty', () => { globalStateController.globalState = { - a: { b: { c: "d" }, e: "f" }, + a: { b: { c: 'd' }, e: 'f' }, }; globalStateController.parseGlobalStateMessage( - "a/b", + 'a/b', JSON.stringify(undefined) ); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: { e: "f" } }) + JSON.stringify({ a: { e: 'f' } }) ); }); -test("Branch Object Set Null", () => { +test('Branch Object Set Null', () => { globalStateController.globalState = { - a: { b: "c" }, - d: "e", + a: { b: 'c' }, + d: 'e', }; - globalStateController.parseGlobalStateMessage("a", JSON.stringify(null)); + globalStateController.parseGlobalStateMessage('a', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: null, d: "e" }) + JSON.stringify({ a: null, d: 'e' }) ); }); -test("Nested Branch Object Set Null", () => { +test('Nested Branch Object Set Null', () => { globalStateController.globalState = { - a: { b: { c: "d" }, e: "f" }, + a: { b: { c: 'd' }, e: 'f' }, }; - globalStateController.parseGlobalStateMessage("a/b", JSON.stringify(null)); + globalStateController.parseGlobalStateMessage('a/b', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: { b: null, e: "f" } }) + JSON.stringify({ a: { b: null, e: 'f' } }) ); }); -test("Branch Object Set Object", () => { +test('Branch Object Set Object', () => { globalStateController.globalState = { - a: { b: "c", d: "e" }, - f: "g", + a: { b: 'c', d: 'e' }, + f: 'g', }; let object = { - d: "f", - g: "h", + d: 'f', + g: 'h', }; - globalStateController.parseGlobalStateMessage("a", JSON.stringify(object)); + globalStateController.parseGlobalStateMessage('a', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: object, f: "g" }) + JSON.stringify({ a: object, f: 'g' }) ); }); -test("Nested Branch Object Set Null", () => { +test('Nested Branch Object Set Null', () => { globalStateController.globalState = { - a: { b: { c: "d" }, e: "f" }, + a: { b: { c: 'd' }, e: 'f' }, }; let object = { - c: "g", - h: "i", + c: 'g', + h: 'i', }; - globalStateController.parseGlobalStateMessage("a/b", JSON.stringify(object)); + globalStateController.parseGlobalStateMessage('a/b', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: { b: object, e: "f" } }) + JSON.stringify({ a: { b: object, e: 'f' } }) ); }); diff --git a/src/bundles/communication/index.ts b/src/bundles/communication/index.ts index 08eadd16e..65cdab6e4 100644 --- a/src/bundles/communication/index.ts +++ b/src/bundles/communication/index.ts @@ -10,19 +10,19 @@ */ export { - STATE_CONNECTED, - STATE_DISCONNECTED, - STATE_OFFLINE, - STATE_RECONNECTED, -} from "./MqttController"; + STATE_CONNECTED, + STATE_DISCONNECTED, + STATE_OFFLINE, + STATE_RECONNECTED, +} from './MqttController'; export { - initGlobalState, - getGlobalState, - updateGlobalState, - initRpc, - expose, - callFunction, - keepRunning, - stopRunning, -} from "./Communications"; + initGlobalState, + getGlobalState, + updateGlobalState, + initRpc, + expose, + callFunction, + keepRunning, + stopRunning, +} from './Communications'; From 6b30001e5f7fbf2211c0a77f46374cfd2b54f2e9 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Wed, 21 Feb 2024 07:16:24 +0800 Subject: [PATCH 36/43] Extra lint cleanup --- package.json | 1 + src/bundles/ar/AR.ts | 19 ++- src/bundles/ar/ARComponent.tsx | 23 +-- src/bundles/ar/ObjectsHelper.ts | 34 ++-- .../calibration_library/PlayAreaContext.tsx | 11 +- .../controls_library/ControlsContext.tsx | 4 +- .../ar/libraries/controls_library/RayCast.tsx | 15 +- .../object_state_library/ARObject.tsx | 34 ++-- .../ARObjectComponent.tsx | 19 ++- .../object_state_library/Behaviour.tsx | 160 +++++++++--------- .../model_components/GltfComponent.tsx | 14 +- .../model_components/ImageComponent.tsx | 4 +- .../model_components/InterfaceComponent.tsx | 10 +- .../model_components/LightComponent.tsx | 6 +- .../model_components/ShapeComponent.tsx | 4 +- .../model_components/TextComponent.tsx | 6 +- .../ui_component/UIColumnComponent.tsx | 4 +- .../ui_component/UIComponent.tsx | 4 +- .../ui_component/UIRowComponent.tsx | 4 +- .../ScreenStateContext.tsx | 4 +- src/bundles/communication/Communications.ts | 8 +- .../communication/GlobalStateController.ts | 6 +- src/bundles/communication/MqttController.ts | 2 +- .../communication/MultiUserController.ts | 4 +- src/bundles/communication/RpcController.ts | 4 +- src/bundles/communication/__tests__/index.ts | 40 ++--- yarn.lock | 39 +++++ 27 files changed, 261 insertions(+), 222 deletions(-) diff --git a/package.json b/package.json index 779cfe5b9..d6de67209 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "@jscad/modeling": "2.9.6", "@jscad/regl-renderer": "^2.6.1", "@jscad/stl-serializer": "^2.1.13", + "@react-spring/three": "^9.7.3", "@react-three/drei": "^9.97.0", "@react-three/fiber": "^8.15.16", "@react-three/xr": "^5.7.1", diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 44323b6a2..b29665837 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -1,6 +1,7 @@ import { Vector3 } from 'three'; import { ARObject } from './libraries/object_state_library/ARObject'; import { OverlayHelper, Toggle } from './OverlayHelper'; +import { Globals } from '@react-spring/three'; export class ARState { arObjects: ARObject[] = []; @@ -8,6 +9,11 @@ export class ARState { clickCallbacks = new Map void>(); } +// Fix issue with React Spring +Globals.assign({ + frameLoop: 'always', +}); + /** * Initialize AR. */ @@ -126,16 +132,11 @@ export function createVector3(x: number, y: number, z: number): Vector3 { export function addARObject(object: ARObject) { let moduleState = getModuleState(); if (!moduleState) return; - if ( - moduleState.arObjects.find((item) => { - return item.id === object.id; - }) - ) { + if (moduleState.arObjects.find((item) => item.id === object.id)) { return; // Already in array } if (object.onSelect) { moduleState.clickCallbacks.set(object.id, () => { - console.log('Called', object); object.onSelect?.(object); }); } @@ -153,9 +154,9 @@ export function addARObject(object: ARObject) { export function removeARObject(object: ARObject) { let moduleState = getModuleState(); if (!moduleState) return; - moduleState.arObjects = moduleState.arObjects.filter((item) => { - return item.id !== object.id; - }); + moduleState.arObjects = moduleState.arObjects.filter( + (item) => item.id !== object.id, + ); callARCallback(); } diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index 172d25344..78cc2dec9 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -12,8 +12,8 @@ import { ControlsContext, useControls, } from './libraries/controls_library/ControlsContext'; -import { ARState, getModuleState } from './AR'; -import { OverlayHelper } from './OverlayHelper'; +import { type ARState, getModuleState } from './AR'; +import { type OverlayHelper } from './OverlayHelper'; import { CubeObject, type ARObject, @@ -145,17 +145,13 @@ function AugmentedContent(props: ARState) { useEffect(() => { controls.setCallback((prev, current) => { if (prev) { - let object = objects.find((item) => { - return item.uuid === prev.uuid; - }); + let object = objects.find((item) => item.uuid === prev.uuid); if (object) { object.isHighlighted = false; } } if (current) { - let object = objects.find((item) => { - return item.uuid === current.uuid; - }); + let object = objects.find((item) => item.uuid === current.uuid); if (object) { object.isHighlighted = true; } @@ -200,7 +196,6 @@ function AugmentedContent(props: ARState) { newObject = LightObject.parseObject(object); if (newObject) { newObjects.push(newObject); - return; } }); newObjects.forEach((object) => { @@ -216,9 +211,9 @@ function AugmentedContent(props: ARState) { return ( - {objects.map((item) => { - return item.getComponent(playArea.getCameraRelativePosition); - })} + {objects.map((item) => + item.getComponent(playArea.getCameraRelativePosition), + )} ); } @@ -226,13 +221,13 @@ function AugmentedContent(props: ARState) { function setupToggles( overlayHelper: OverlayHelper, overlayRef: RefObject | null, - recalibrate: () => void + recalibrate: () => void, ) { if (!overlayRef || !overlayRef.current) return; let overlay = overlayRef.current; // Recalibrate let recalibrateToggle = overlay?.querySelector( - '#recalibrate-toggle' + '#recalibrate-toggle', ) as HTMLElement; if (recalibrateToggle) { recalibrateToggle.onclick = recalibrate; diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index e4c869514..a54866093 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -1,6 +1,6 @@ -import { Vector3 } from 'three'; +import { type Vector3 } from 'three'; import { - ARObject, + type ARObject, CubeObject, LightObject, SphereObject, @@ -21,10 +21,10 @@ import { import uniqid from 'uniqid'; import type { UIBasicComponent } from './libraries/object_state_library/ui_component/UIComponent'; import UIRowComponent, { - VerticalAlignment, + type VerticalAlignment, } from './libraries/object_state_library/ui_component/UIRowComponent'; import UIColumnComponent, { - HorizontalAlignment, + type HorizontalAlignment, } from './libraries/object_state_library/ui_component/UIColumnComponent'; import UITextComponent from './libraries/object_state_library/ui_component/UITextComponent'; import UIImageComponent from './libraries/object_state_library/ui_component/UIImageComponent'; @@ -49,7 +49,7 @@ export function createCubeObject( height: number, depth: number, color: number, - onSelect?: () => {} + onSelect?: () => {}, ): CubeObject { return new CubeObject( uniqid(), @@ -63,7 +63,7 @@ export function createCubeObject( undefined, (_: ARObject) => { onSelect?.(); - } + }, ); } @@ -80,7 +80,7 @@ export function createSphereObject( position: Vector3, radius: number, color: number, - onSelect?: () => {} + onSelect?: () => {}, ): SphereObject { return new SphereObject( uniqid(), @@ -92,7 +92,7 @@ export function createSphereObject( undefined, (_: ARObject) => { onSelect?.(); - } + }, ); } @@ -108,7 +108,7 @@ export function createSphereObject( export function createInterfaceObject( position: Vector3, rootComponent: UIBasicComponent, - onSelect?: () => {} + onSelect?: () => {}, ): UIObject { return new UIObject( uniqid(), @@ -119,7 +119,7 @@ export function createInterfaceObject( undefined, (_: ARObject) => { onSelect?.(); - } + }, ); } @@ -133,7 +133,7 @@ export function createInterfaceObject( */ export function createLightObject( position: Vector3, - intensity: number + intensity: number, ): LightObject { return new LightObject(uniqid(), position, intensity); } @@ -159,7 +159,7 @@ export function createInterfaceRow( paddingRight: number, paddingTop: number, paddingBottom: number, - backgroundColor: number + backgroundColor: number, ): UIRowComponent { return new UIRowComponent({ children: children, @@ -194,7 +194,7 @@ export function createInterfaceColumn( paddingRight: number, paddingTop: number, paddingBottom: number, - backgroundColor: number + backgroundColor: number, ): UIColumnComponent { return new UIColumnComponent({ children: children, @@ -233,7 +233,7 @@ export function createInterfaceText( paddingRight: number, paddingTop: number, paddingBottom: number, - color: number + color: number, ): UITextComponent { return new UITextComponent({ text: text, @@ -271,7 +271,7 @@ export function createInterfaceImage( paddingLeft: number, paddingRight: number, paddingTop: number, - paddingBottom: number + paddingBottom: number, ): UIImageComponent { return new UIImageComponent({ src: src, @@ -356,7 +356,7 @@ export function setRenderDistance(object: ARObject, distance: number) { export function createPathItem( start: Vector3, end: Vector3, - duration: number + duration: number, ): PathItem { return { start: start, @@ -387,7 +387,7 @@ export function setPathMovement(object: ARObject, path: PathItem[]) { export function setOrbitMovement( object: ARObject, radius: number, - duration: number + duration: number, ) { object.behaviours.movement = new OrbitMovement(radius, duration); callARCallback(); diff --git a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx index 374b2a212..1c9a9bdce 100644 --- a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx +++ b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx @@ -45,9 +45,9 @@ export function PlayAreaContext(props: Props) { new Vector3( cameraPosition.x, cameraPosition.y - DEFAULT_HEIGHT, - cameraPosition.z + cameraPosition.z, ), - three.camera.rotation + three.camera.rotation, ); } @@ -134,9 +134,12 @@ export function PlayAreaContext(props: Props) { } function getRelativeRotation(rotation: Euler) { - let vector3 = new Vector3().setFromEuler(rotation); + let vector3 = new Vector3(); + vector3.setFromEuler(rotation); vector3.applyAxisAngle(new Vector3(0, 1, 0), -angle); - return new Euler().setFromVector3(vector3); + let euler = new Euler(); + euler.setFromVector3(vector3); + return euler; } return ( diff --git a/src/bundles/ar/libraries/controls_library/ControlsContext.tsx b/src/bundles/ar/libraries/controls_library/ControlsContext.tsx index 6804cb76b..fccc2aebf 100644 --- a/src/bundles/ar/libraries/controls_library/ControlsContext.tsx +++ b/src/bundles/ar/libraries/controls_library/ControlsContext.tsx @@ -6,13 +6,13 @@ import { useContext, useRef, } from 'react'; -import { Mesh } from 'three'; +import { type Mesh } from 'three'; import { getIntersection } from './RayCast'; type ContextType = { object: React.MutableRefObject; setCallback: ( - callback: (prev: Mesh | undefined, current: Mesh | undefined) => void + callback: (prev: Mesh | undefined, current: Mesh | undefined) => void, ) => void; }; diff --git a/src/bundles/ar/libraries/controls_library/RayCast.tsx b/src/bundles/ar/libraries/controls_library/RayCast.tsx index 53bb46fa9..52d30abc5 100644 --- a/src/bundles/ar/libraries/controls_library/RayCast.tsx +++ b/src/bundles/ar/libraries/controls_library/RayCast.tsx @@ -1,5 +1,5 @@ import { - Camera, + type Camera, Group, Mesh, Object3D, @@ -16,17 +16,12 @@ export function getIntersection(camera: Camera, coreObject: Object3D) { let cascadeChildren = getCascadeMeshs(coreObject.children); let items = raycaster.intersectObjects(cascadeChildren, true); let nearestItem = items - .filter((item) => { - return item.distance != 0; - }) - .sort((item) => { - return item.distance; - }); + .filter((item) => item.distance !== 0) + .sort((item) => item.distance); if (nearestItem.length > 0) { return getTopParent(nearestItem[0].object, coreObject); - } else { - return undefined; } + return undefined; } function getCascadeMeshs(children: Object3D[]) { @@ -47,7 +42,7 @@ function getCascadeMeshs(children: Object3D[]) { function getTopParent( child: Object3D, - coreObject: Object3D + coreObject: Object3D, ): Mesh | undefined { let parent = child; let lastMesh = child; diff --git a/src/bundles/ar/libraries/object_state_library/ARObject.tsx b/src/bundles/ar/libraries/object_state_library/ARObject.tsx index d8c5469cd..5d58f88d3 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObject.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObject.tsx @@ -10,12 +10,12 @@ import { RenderWithinDistance, ShapeModel, InterfaceModel, - RotationClass, + type RotationClass, parseRotation, - RenderClass, + type RenderClass, parseRender, parseMovement, - MovementClass, + type MovementClass, } from './Behaviour'; import ARObjectComponent from './ARObjectComponent'; import { parseVector3 } from '../calibration_library/Misc'; @@ -39,7 +39,7 @@ export class ARObject { id: string, position: Vector3, behaviours: Behaviours, - onSelect?: (object: ARObject) => void + onSelect?: (object: ARObject) => void, ) { this.id = id; this.position = position; @@ -47,8 +47,8 @@ export class ARObject { this.onSelect = onSelect; } toJSON = () => { - let object = Object.assign({}, this) as any; - let behavioursClone = Object.assign({}, this.behaviours) as any; + let object = { ...this }; + let behavioursClone = { ...this.behaviours } as any; delete behavioursClone.model; object.behaviours = behavioursClone; return object; @@ -85,7 +85,7 @@ export class CubeObject extends ARObject { render?: RenderClass, rotation?: RotationClass, movement?: MovementClass, - onSelect?: (object: ARObject) => void + onSelect?: (object: ARObject) => void, ) { super( id, @@ -93,13 +93,13 @@ export class CubeObject extends ARObject { { model: new ShapeModel( new BoxGeometry(width, height, depth), - new MeshStandardMaterial({ color: color }) + new MeshStandardMaterial({ color: color }), ), render: render, rotation: rotation, movement: movement, }, - onSelect + onSelect, ); this.width = width; this.height = height; @@ -135,7 +135,7 @@ export class CubeObject extends ARObject { render, rotation, movement, - onSelect + onSelect, ); } return undefined; @@ -155,7 +155,7 @@ export class SphereObject extends ARObject { render?: RenderClass, rotation?: RotationClass, movement?: MovementClass, - onSelect?: (object: ARObject) => void + onSelect?: (object: ARObject) => void, ) { super( id, @@ -163,13 +163,13 @@ export class SphereObject extends ARObject { { model: new ShapeModel( new SphereGeometry(radius, 20, 20), - new MeshStandardMaterial({ color: color }) + new MeshStandardMaterial({ color: color }), ), render: render, rotation: rotation, movement: movement, }, - onSelect + onSelect, ); this.radius = radius; this.color = color; @@ -197,7 +197,7 @@ export class SphereObject extends ARObject { render, rotation, movement, - onSelect + onSelect, ); } return undefined; @@ -215,7 +215,7 @@ export class UIObject extends ARObject { render?: RenderClass, rotation?: RotationClass, movement?: MovementClass, - onSelect?: (object: ARObject) => void + onSelect?: (object: ARObject) => void, ) { super( id, @@ -226,7 +226,7 @@ export class UIObject extends ARObject { rotation: rotation, movement: movement, }, - onSelect + onSelect, ); this.uiJson = uiJson; } @@ -250,7 +250,7 @@ export class UIObject extends ARObject { render, rotation, movement, - onSelect + onSelect, ); } return undefined; diff --git a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx index f16772997..043046d7f 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx @@ -1,5 +1,5 @@ import { Interactive } from '@react-three/xr'; -import { ARObject } from './ARObject'; +import { type ARObject } from './ARObject'; import { type MutableRefObject, type ReactNode, useRef, useState } from 'react'; import { AlwaysRender, @@ -24,7 +24,7 @@ import GltfComponent from './model_components/GltfComponent'; import TextComponent from './model_components/TextComponent'; import ImageComponent from './model_components/ImageComponent'; import ShapeComponent from './model_components/ShapeComponent'; -import { useSpring, SpringValue } from '@react-spring/three'; +import { useSpring, type SpringValue } from '@react-spring/three'; import LightComponent from './model_components/LightComponent'; import InterfaceComponent from './model_components/InterfaceComponent'; @@ -65,14 +65,14 @@ export default function ARObjectComponent(props: Props) { props.arObject, ref, targetPosition, - setTargetPosition + setTargetPosition, ); let userPosition = props.getUserPosition(); handleVisibility( props.arObject, currentPosition, userPosition, - setShowComponent + setShowComponent, ); handleRotation(props.arObject, currentPosition, userPosition, ref, delta); }); @@ -115,7 +115,7 @@ function updatePosition( arObject: ARObject, ref: MutableRefObject, targetPosition: Vector3, - setTargetPosition: React.Dispatch> + setTargetPosition: React.Dispatch>, ) { let position = arObject.position.clone(); let movement = arObject.behaviours.movement; @@ -177,12 +177,13 @@ function handleVisibility( arObject: ARObject, position: Vector3, userPosition: Vector3, - setShowComponent: React.Dispatch> + setShowComponent: React.Dispatch>, ) { let behaviour = arObject.behaviours.render ?? new RenderWithinDistance(5); if (behaviour instanceof RenderWithinDistance) { let distanceVector = new Vector3(0, 0, 0); - let distance = distanceVector.subVectors(position, userPosition).length(); + distanceVector.subVectors(position, userPosition); + let distance = distanceVector.length(); setShowComponent(distance <= behaviour.distance); } else if (behaviour instanceof AlwaysRender) { setShowComponent(true); @@ -196,7 +197,7 @@ function handleRotation( position: Vector3, userPosition: Vector3, ref: MutableRefObject, - delta: number + delta: number, ) { let rotation = arObject.behaviours.rotation; let mesh = ref.current; @@ -204,7 +205,7 @@ function handleRotation( if (rotation instanceof RotateToUser) { mesh.rotation.y = Math.atan2( userPosition.x - position.x, - userPosition.z - position.z + userPosition.z - position.z, ); } else if (rotation instanceof RotateAroundY) { mesh.rotation.y += delta; diff --git a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx index 873f2e185..e792860e6 100644 --- a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx +++ b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx @@ -1,10 +1,10 @@ import { - BufferGeometry, - Material, + type BufferGeometry, + type Material, type NormalBufferAttributes, Vector3, } from 'three'; -import { UIBasicComponent } from './ui_component/UIComponent'; +import { type UIBasicComponent } from './ui_component/UIComponent'; import { parseVector3 } from '../calibration_library/Misc'; export type Behaviours = { @@ -50,7 +50,7 @@ export class ShapeModel implements ModelClass { material: Material | Material[] | undefined; constructor( geometry: BufferGeometry | undefined, - material: Material | Material[] | undefined + material: Material | Material[] | undefined, ) { this.geometry = geometry; this.material = material; @@ -96,23 +96,6 @@ export class LightModel implements ModelClass { const RENDER_DISTANCE = 'RenderWithinDistance'; const RENDER_ALWAYS = 'AlwaysRender'; -export function parseRender(render: any): RenderClass | undefined { - if (!render) return undefined; - switch (render.type) { - case RENDER_ALWAYS: { - return new AlwaysRender(); - } - case RENDER_DISTANCE: { - let distance = 5; - if (typeof render.distance === 'number') { - distance = render.distance as number; - } - return new RenderWithinDistance(distance); - } - } - return undefined; -} - export abstract class RenderClass implements Behaviour { type: string = ''; } @@ -129,32 +112,29 @@ export class AlwaysRender implements RenderClass { type = RENDER_ALWAYS; } -// Rotation - -const ROTATION_USER = 'RotateToUser'; -const ROTATION_Y = 'RotateAroundY'; -const ROTATION_FIX = 'FixRotation'; - -export function parseRotation(rotation: any): RotationClass | undefined { - if (!rotation) return undefined; - switch (rotation?.type) { - case ROTATION_USER: { - return new RotateToUser(); - } - case ROTATION_Y: { - return new RotateAroundY(); +export function parseRender(render: any): RenderClass | undefined { + if (!render) return undefined; + switch (render.type) { + case RENDER_ALWAYS: { + return new AlwaysRender(); } - case ROTATION_FIX: { - let angle = 0; - if (typeof rotation.rotation === 'number') { - angle = rotation.rotation as number; + case RENDER_DISTANCE: { + let distance = 5; + if (typeof render.distance === 'number') { + distance = render.distance as number; } - return new FixRotation(angle); + return new RenderWithinDistance(distance); } } return undefined; } +// Rotation + +const ROTATION_USER = 'RotateToUser'; +const ROTATION_Y = 'RotateAroundY'; +const ROTATION_FIX = 'FixRotation'; + /** * Base class for a rotation behaviour. */ @@ -187,46 +167,32 @@ export class FixRotation implements RotationClass { } } -// Movement - -const MOVEMENT_PATH = 'PathMovement'; -const MOVEMENT_ORBIT = 'OrbitMovement'; -const MOVEMENT_SPRING = 'SpringMovement'; - -export function parseMovement(movement: any, getCurrentTime?: () => number) { - if (!movement) return undefined; - switch (movement.type) { - case MOVEMENT_PATH: { - let startTime = movement.startTime; - let pathItems = movement.path; - if ( - (startTime === undefined || typeof startTime === 'number') && - Array.isArray(pathItems) - ) { - let parsedPathItems = parsePathItems(pathItems); - return new PathMovement(parsedPathItems, startTime, getCurrentTime); - } - break; +export function parseRotation(rotation: any): RotationClass | undefined { + if (!rotation) return undefined; + switch (rotation?.type) { + case ROTATION_USER: { + return new RotateToUser(); } - case MOVEMENT_ORBIT: { - let radius = movement.radius; - let duration = movement.duration; - let startTime = movement.startTime; - if ( - typeof radius === 'number' && - typeof duration === 'number' && - (startTime === undefined || typeof startTime === 'number') - ) { - return new OrbitMovement(radius, duration, startTime, getCurrentTime); + case ROTATION_Y: { + return new RotateAroundY(); + } + case ROTATION_FIX: { + let angle = 0; + if (typeof rotation.rotation === 'number') { + angle = rotation.rotation as number; } - break; + return new FixRotation(angle); } - case MOVEMENT_SPRING: - return new SpringMovement(); } return undefined; } +// Movement + +const MOVEMENT_PATH = 'PathMovement'; +const MOVEMENT_ORBIT = 'OrbitMovement'; +const MOVEMENT_SPRING = 'SpringMovement'; + /** * Base class for a movement behaviour. * @@ -297,7 +263,7 @@ export class PathMovement extends MovementClass { constructor( path: PathItem[], startTime?: number, - getCurrentTime?: () => number + getCurrentTime?: () => number, ) { super(); this.path = path; @@ -305,12 +271,16 @@ export class PathMovement extends MovementClass { if (startTime) { this.startTime = startTime; } else { - this.startTime = new Date().getTime(); + let currentDate = new Date(); + this.startTime = currentDate.getTime(); } if (getCurrentTime) { this.getCurrentTime = getCurrentTime; } else { - this.getCurrentTime = () => new Date().getTime(); + this.getCurrentTime = () => { + let currentDate = new Date(); + return currentDate.getTime(); + }; } path.forEach((item) => { this.totalDuration += item.duration; @@ -330,7 +300,7 @@ export class PathMovement extends MovementClass { } let ratio = Math.min( Math.max(0, currentFrame / (currentItem.duration * 1000)), - 1 + 1, ); switch (currentItem.style) { case MovementStyle.SlowToFast: { @@ -379,7 +349,7 @@ export class OrbitMovement extends MovementClass { radius: number, duration: number, startTime?: number, - getCurrentTime?: () => number + getCurrentTime?: () => number, ) { super(); this.radius = radius; @@ -410,3 +380,37 @@ export class OrbitMovement extends MovementClass { export class SpringMovement extends MovementClass { type = MOVEMENT_SPRING; } + +export function parseMovement(movement: any, getCurrentTime?: () => number) { + if (!movement) return undefined; + switch (movement.type) { + case MOVEMENT_PATH: { + let startTime = movement.startTime; + let pathItems = movement.path; + if ( + (startTime === undefined || typeof startTime === 'number') && + Array.isArray(pathItems) + ) { + let parsedPathItems = parsePathItems(pathItems); + return new PathMovement(parsedPathItems, startTime, getCurrentTime); + } + break; + } + case MOVEMENT_ORBIT: { + let radius = movement.radius; + let duration = movement.duration; + let startTime = movement.startTime; + if ( + typeof radius === 'number' && + typeof duration === 'number' && + (startTime === undefined || typeof startTime === 'number') + ) { + return new OrbitMovement(radius, duration, startTime, getCurrentTime); + } + break; + } + case MOVEMENT_SPRING: + return new SpringMovement(); + } + return undefined; +} diff --git a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx index 29539e38b..82b7ab62c 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx @@ -8,10 +8,10 @@ import { useState, } from 'react'; import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js'; -import { Object3D, type Object3DEventMap, AnimationMixer } from 'three'; -import { GltfModel } from '../Behaviour'; -import { ARObject } from '../ARObject'; -import { SpringValue, animated } from '@react-spring/three'; +import { type Object3D, type Object3DEventMap, AnimationMixer } from 'three'; +import { type GltfModel } from '../Behaviour'; +import { type ARObject } from '../ARObject'; +import { type SpringValue, animated } from '@react-spring/three'; import { useGLTF } from '@react-three/drei'; type GltfProps = { @@ -36,9 +36,9 @@ export default function GltfComponent(props: GltfProps) { useEffect(() => { if (model.animations.length > 0) { props.gltfModel.callAnimation = (actionName: string) => { - let selectedAction = model.animations.find((item) => { - return item.name === actionName; - }); + let selectedAction = model.animations.find( + (item) => item.name === actionName, + ); if (!selectedAction) return; mixer.current?.stopAllAction(); let action = mixer.current?.clipAction(selectedAction); diff --git a/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx index b85088228..2eb38612e 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/ImageComponent.tsx @@ -1,7 +1,7 @@ import { type MutableRefObject } from 'react'; -import { ImageModel } from '../Behaviour'; +import { type ImageModel } from '../Behaviour'; import { Image } from '@react-three/drei'; -import { SpringValue, animated } from '@react-spring/three'; +import { type SpringValue, animated } from '@react-spring/three'; type ImageProps = { imageModel: ImageModel; diff --git a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx index 2f7593848..da89b09f2 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx @@ -1,12 +1,12 @@ -import { SpringValue, animated } from '@react-spring/three'; -import { InterfaceModel as InterfaceModel } from '../Behaviour'; +import { type SpringValue, animated } from '@react-spring/three'; +import { type InterfaceModel } from '../Behaviour'; import { useEffect, type MutableRefObject, useState, type ReactNode, } from 'react'; -import { UIBasicComponent } from '../ui_component/UIComponent'; +import { type UIBasicComponent } from '../ui_component/UIComponent'; import UIRowComponent, { VerticalAlignment, } from '../ui_component/UIRowComponent'; @@ -30,8 +30,8 @@ export default function InterfaceComponent(props: InterfaceProps) { setComponents( parseJsonInterface(props.interfaceModel.uiJson)?.getComponent( new Vector3(0), - () => {} - ) + () => {}, + ), ); }, [props.interfaceModel.uiJson]); diff --git a/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx index 6434e40f1..bbc7fe258 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/LightComponent.tsx @@ -1,7 +1,7 @@ import { type MutableRefObject, type ReactNode } from 'react'; -import { ARObject } from '../ARObject'; -import { LightModel } from '../Behaviour'; -import { SpringValue, animated } from '@react-spring/three'; +import { type ARObject } from '../ARObject'; +import { type LightModel } from '../Behaviour'; +import { type SpringValue, animated } from '@react-spring/three'; type LightProps = { lightModel: LightModel; diff --git a/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx index 32250367c..c26b7d3e0 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/ShapeComponent.tsx @@ -1,6 +1,6 @@ import { type MutableRefObject, type ReactNode } from 'react'; -import { ShapeModel } from '../Behaviour'; -import { SpringValue, animated } from '@react-spring/three'; +import { type ShapeModel } from '../Behaviour'; +import { type SpringValue, animated } from '@react-spring/three'; import { Outlines } from '@react-three/drei'; import { Color } from 'three'; diff --git a/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx index 5dc7710f6..7d036d166 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/TextComponent.tsx @@ -1,8 +1,8 @@ import { type MutableRefObject, type ReactNode, useRef, useState } from 'react'; -import { TextModel } from '../Behaviour'; -import { Mesh } from 'three'; +import { type TextModel } from '../Behaviour'; +import { type Mesh } from 'three'; import { Text } from '@react-three/drei'; -import { SpringValue, animated } from '@react-spring/three'; +import { type SpringValue, animated } from '@react-spring/three'; type TextProps = { textModel: TextModel; diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index d89ae3275..b93c9556e 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -1,6 +1,6 @@ import { type ReactNode, useState } from 'react'; import { - UIBasicComponent, + type UIBasicComponent, LayoutComponent, type PaddingType, } from './UIComponent'; @@ -117,7 +117,7 @@ function ColumnUIComponent(props: { children.push( {child.getComponent(childPosition, updateSize)} - + , ); } return {children}; diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx index a2a08342e..0318628f6 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx @@ -62,7 +62,7 @@ export class UIBasicComponent { } } toJSON = () => { - let object = Object.assign({}, this) as any; + let object = { ...this } as any; delete object.height; delete object.width; delete object.layer; @@ -86,7 +86,7 @@ export class UIBasicComponent { return this.paddingTop + this.paddingBottom; }; calculateLayer = () => {}; - getComponent = (position: Vector3, updateParent: () => void) => { + getComponent = (position: Vector3, _: () => void) => { return ; }; } diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx index 772ad1e9f..85afe5ed4 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx @@ -1,6 +1,6 @@ import { type ReactNode, useState } from 'react'; import { - UIBasicComponent, + type UIBasicComponent, LayoutComponent, type PaddingType, } from './UIComponent'; @@ -117,7 +117,7 @@ function RowUIComponent(props: { children.push( {child.getComponent(childPosition, updateSize)} - + , ); } return {children}; diff --git a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx index 33e2f376f..56c3ca232 100644 --- a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx +++ b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx @@ -105,13 +105,13 @@ export function ScreenStateContext(props: Props) { > {isXRSession.current ? overlayState : <>}
- + , ); } function setStates( newArState: ReactNode | undefined, - newOverlayState: ReactNode | undefined + newOverlayState: ReactNode | undefined, ) { if (newArState) { setArState(newArState); diff --git a/src/bundles/communication/Communications.ts b/src/bundles/communication/Communications.ts index bde585f51..f73d736ca 100644 --- a/src/bundles/communication/Communications.ts +++ b/src/bundles/communication/Communications.ts @@ -49,12 +49,12 @@ export function stopRunning() { */ export function initGlobalState( topicHeader: string, - callback: (state: any) => void + callback: (state: any) => void, ) { moduleState.globalState = new GlobalStateController( topicHeader, moduleState.multiUser, - callback + callback, ); } @@ -90,7 +90,7 @@ export function initRpc(topicHeader: string, userId?: string) { moduleState.rpc = new RpcController( topicHeader, moduleState.multiUser, - userId + userId, ); } @@ -117,7 +117,7 @@ export function callFunction( receiver: string, name: string, args: any[], - callback: (args: any[]) => void + callback: (args: any[]) => void, ) { moduleState.rpc?.callFunction(receiver, name, args, callback); } diff --git a/src/bundles/communication/GlobalStateController.ts b/src/bundles/communication/GlobalStateController.ts index 6f9856478..9da72abdf 100644 --- a/src/bundles/communication/GlobalStateController.ts +++ b/src/bundles/communication/GlobalStateController.ts @@ -17,7 +17,7 @@ export class GlobalStateController { constructor( topicHeader: string, multiUser: MultiUserController, - callback: (state: any) => void + callback: (state: any) => void, ) { this.topicHeader = topicHeader; this.multiUser = multiUser; @@ -34,7 +34,7 @@ export class GlobalStateController { this.multiUser.addMessageCallback(this.topicHeader, (topic, message) => { let shortenedTopic = topic.substring( this.topicHeader.length, - topic.length + topic.length, ); this.parseGlobalStateMessage(shortenedTopic, message); }); @@ -111,7 +111,7 @@ export class GlobalStateController { this.multiUser.controller?.publish( topic, JSON.stringify(updatedState), - false + false, ); } } diff --git a/src/bundles/communication/MqttController.ts b/src/bundles/communication/MqttController.ts index 339ab620c..be96a6a17 100644 --- a/src/bundles/communication/MqttController.ts +++ b/src/bundles/communication/MqttController.ts @@ -22,7 +22,7 @@ export class MqttController { constructor( connectionCallback: (status: string) => void, - messageCallback: (topic: string, message: string) => void + messageCallback: (topic: string, message: string) => void, ) { this.connectionCallback = connectionCallback; this.messageCallback = messageCallback; diff --git a/src/bundles/communication/MultiUserController.ts b/src/bundles/communication/MultiUserController.ts index 72711fad4..4f5969fa0 100644 --- a/src/bundles/communication/MultiUserController.ts +++ b/src/bundles/communication/MultiUserController.ts @@ -37,7 +37,7 @@ export class MultiUserController { } callback(topic, message); }); - } + }, ); this.controller = currentController; } @@ -54,7 +54,7 @@ export class MultiUserController { */ public addMessageCallback( identifier: string, - callback: (topic: string, message: string) => void + callback: (topic: string, message: string) => void, ) { this.controller?.subscribe(identifier + '/#'); this.messageCallbacks.set(identifier, callback); diff --git a/src/bundles/communication/RpcController.ts b/src/bundles/communication/RpcController.ts index 21ff9a97d..6c7865fcd 100644 --- a/src/bundles/communication/RpcController.ts +++ b/src/bundles/communication/RpcController.ts @@ -24,7 +24,7 @@ export class RpcController { constructor( topicHeader: string, multiUser: MultiUserController, - userId?: string + userId?: string, ) { this.topicHeader = topicHeader; this.multiUser = multiUser; @@ -107,7 +107,7 @@ export class RpcController { receiver: string, name: string, args: any[], - callback: (args: any[]) => void + callback: (args: any[]) => void, ) { let topic = this.topicHeader + '/' + receiver + '/' + name; let callId = uniqid(); diff --git a/src/bundles/communication/__tests__/index.ts b/src/bundles/communication/__tests__/index.ts index dce4ad1a2..73c402bec 100644 --- a/src/bundles/communication/__tests__/index.ts +++ b/src/bundles/communication/__tests__/index.ts @@ -6,7 +6,7 @@ multiUser.setupController('broker.hivemq.com', 8884); let globalStateController = new GlobalStateController( 'test', multiUser, - (_) => {} + (_) => {}, ); // Empty Root - Replace root. @@ -15,7 +15,7 @@ test('Empty Root Set Null', () => { globalStateController.globalState = undefined; globalStateController.parseGlobalStateMessage('', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify(null) + JSON.stringify(null), ); }); @@ -26,7 +26,7 @@ test('Empty Root Set Object', () => { }; globalStateController.parseGlobalStateMessage('', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify(object) + JSON.stringify(object), ); }); @@ -39,7 +39,7 @@ test('Non-Empty Root Set Empty', () => { globalStateController.globalState = object; globalStateController.parseGlobalStateMessage('', JSON.stringify(undefined)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify(undefined) + JSON.stringify(undefined), ); }); @@ -50,7 +50,7 @@ test('Non-Empty Root Set Null', () => { globalStateController.globalState = object; globalStateController.parseGlobalStateMessage('', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify(null) + JSON.stringify(null), ); }); @@ -63,7 +63,7 @@ test('Non-Empty Root Set Object', () => { }; globalStateController.parseGlobalStateMessage('', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify(object) + JSON.stringify(object), ); }); @@ -76,7 +76,7 @@ test('Branch Value Set Empty', () => { }; globalStateController.parseGlobalStateMessage('a', JSON.stringify(undefined)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ c: 'd' }) + JSON.stringify({ c: 'd' }), ); }); @@ -88,10 +88,10 @@ test('Nested Branch Value Set Empty', () => { }; globalStateController.parseGlobalStateMessage( 'a/b', - JSON.stringify(undefined) + JSON.stringify(undefined), ); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: {} }) + JSON.stringify({ a: {} }), ); }); @@ -102,7 +102,7 @@ test('Branch Value Set Null', () => { }; globalStateController.parseGlobalStateMessage('a', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: null, c: 'd' }) + JSON.stringify({ a: null, c: 'd' }), ); }); @@ -114,7 +114,7 @@ test('Nested Branch Value Set Null', () => { }; globalStateController.parseGlobalStateMessage('a/b', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: { b: null } }) + JSON.stringify({ a: { b: null } }), ); }); @@ -128,7 +128,7 @@ test('Branch Value Set Object', () => { }; globalStateController.parseGlobalStateMessage('a', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: object, c: 'd' }) + JSON.stringify({ a: object, c: 'd' }), ); }); @@ -143,7 +143,7 @@ test('Nested Branch Value Set Object', () => { }; globalStateController.parseGlobalStateMessage('a/b', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: { b: object } }) + JSON.stringify({ a: { b: object } }), ); }); @@ -156,7 +156,7 @@ test('Branch Object Set Empty', () => { }; globalStateController.parseGlobalStateMessage('a', JSON.stringify(undefined)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ d: 'e' }) + JSON.stringify({ d: 'e' }), ); }); @@ -166,10 +166,10 @@ test('Nested Branch Object Set Empty', () => { }; globalStateController.parseGlobalStateMessage( 'a/b', - JSON.stringify(undefined) + JSON.stringify(undefined), ); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: { e: 'f' } }) + JSON.stringify({ a: { e: 'f' } }), ); }); @@ -180,7 +180,7 @@ test('Branch Object Set Null', () => { }; globalStateController.parseGlobalStateMessage('a', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: null, d: 'e' }) + JSON.stringify({ a: null, d: 'e' }), ); }); @@ -190,7 +190,7 @@ test('Nested Branch Object Set Null', () => { }; globalStateController.parseGlobalStateMessage('a/b', JSON.stringify(null)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: { b: null, e: 'f' } }) + JSON.stringify({ a: { b: null, e: 'f' } }), ); }); @@ -205,7 +205,7 @@ test('Branch Object Set Object', () => { }; globalStateController.parseGlobalStateMessage('a', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: object, f: 'g' }) + JSON.stringify({ a: object, f: 'g' }), ); }); @@ -219,6 +219,6 @@ test('Nested Branch Object Set Null', () => { }; globalStateController.parseGlobalStateMessage('a/b', JSON.stringify(object)); expect(JSON.stringify(globalStateController.globalState)).toBe( - JSON.stringify({ a: { b: object, e: 'f' } }) + JSON.stringify({ a: { b: object, e: 'f' } }), ); }); diff --git a/yarn.lock b/yarn.lock index 029426b1c..2b8d5beca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1124,6 +1124,14 @@ "@react-spring/shared" "~9.6.1" "@react-spring/types" "~9.6.1" +"@react-spring/animated@~9.7.3": + version "9.7.3" + resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.7.3.tgz#4211b1a6d48da0ff474a125e93c0f460ff816e0f" + integrity sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw== + dependencies: + "@react-spring/shared" "~9.7.3" + "@react-spring/types" "~9.7.3" + "@react-spring/core@~9.6.1": version "9.6.1" resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.6.1.tgz#ebe07c20682b360b06af116ea24e2b609e778c10" @@ -1134,6 +1142,15 @@ "@react-spring/shared" "~9.6.1" "@react-spring/types" "~9.6.1" +"@react-spring/core@~9.7.3": + version "9.7.3" + resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.7.3.tgz#60056bcb397f2c4f371c6c9a5f882db77ae90095" + integrity sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ== + dependencies: + "@react-spring/animated" "~9.7.3" + "@react-spring/shared" "~9.7.3" + "@react-spring/types" "~9.7.3" + "@react-spring/rafz@~9.6.1": version "9.6.1" resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.6.1.tgz#d71aafb92b78b24e4ff84639f52745afc285c38d" @@ -1147,6 +1164,23 @@ "@react-spring/rafz" "~9.6.1" "@react-spring/types" "~9.6.1" +"@react-spring/shared@~9.7.3": + version "9.7.3" + resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.7.3.tgz#4cf29797847c689912aec4e62e34c99a4d5d9e53" + integrity sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA== + dependencies: + "@react-spring/types" "~9.7.3" + +"@react-spring/three@^9.7.3": + version "9.7.3" + resolved "https://registry.yarnpkg.com/@react-spring/three/-/three-9.7.3.tgz#4358a0c4640efe2972c4f7d0f7cd4efe927471c1" + integrity sha512-Q1p512CqUlmMK8UMBF/Rj79qndhOWq4XUTayxMP9S892jiXzWQuj+xC3Xvm59DP/D4JXusXpxxqfgoH+hmOktA== + dependencies: + "@react-spring/animated" "~9.7.3" + "@react-spring/core" "~9.7.3" + "@react-spring/shared" "~9.7.3" + "@react-spring/types" "~9.7.3" + "@react-spring/three@~9.6.1": version "9.6.1" resolved "https://registry.yarnpkg.com/@react-spring/three/-/three-9.6.1.tgz#095fcd1dc6509127c33c14486d88289b89baeb9d" @@ -1162,6 +1196,11 @@ resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.6.1.tgz#913d3a68c5cbc1124fdb18eff919432f7b6abdde" integrity sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q== +"@react-spring/types@~9.7.3": + version "9.7.3" + resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.7.3.tgz#ea78fd447cbc2612c1f5d55852e3c331e8172a0b" + integrity sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw== + "@react-three/drei@^9.97.0": version "9.97.0" resolved "https://registry.yarnpkg.com/@react-three/drei/-/drei-9.97.0.tgz#7a52643143181a69665801cb29d236b9a9612f06" From f85deab6eb310becd8ec9ed02fe585bf62117b3d Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Wed, 21 Feb 2024 07:59:39 +0800 Subject: [PATCH 37/43] Clean up code --- src/bundles/ar/AR.ts | 2 +- .../calibration_library/PlayAreaContext.tsx | 16 +- .../ARObjectComponent.tsx | 2 +- .../object_state_library/Behaviour.tsx | 24 +- .../model_components/InterfaceComponent.tsx | 306 +++++++++++------- .../ui_component/UIColumnComponent.tsx | 34 +- .../ui_component/UIComponent.tsx | 16 +- .../ui_component/UIImageComponent.tsx | 16 +- .../ui_component/UIRowComponent.tsx | 22 +- .../ui_component/UITextComponent.tsx | 8 +- .../communication/GlobalStateController.ts | 4 +- src/bundles/communication/MqttController.ts | 15 +- src/bundles/communication/RpcController.ts | 10 +- src/tabs/AugmentedReality/index.tsx | 16 +- 14 files changed, 274 insertions(+), 217 deletions(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index b29665837..6f187d19a 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -1,5 +1,5 @@ import { Vector3 } from 'three'; -import { ARObject } from './libraries/object_state_library/ARObject'; +import { type ARObject } from './libraries/object_state_library/ARObject'; import { OverlayHelper, Toggle } from './OverlayHelper'; import { Globals } from '@react-spring/three'; diff --git a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx index 1c9a9bdce..8e3b1f797 100644 --- a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx +++ b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx @@ -67,11 +67,11 @@ export function PlayAreaContext(props: Props) { * Converts camera rotation to angle around vertical axis. * Angle measured counterclockwise, starting from 12 o'clock. * - * @param origin Position of user in world coordinates + * @param cameraPosition Position of user in world coordinates * @param cameraRotation Camera rotation of user */ - function setPlayArea(origin: Vector3, cameraRotation: Euler) { - setOrigin(origin); + function setPlayArea(cameraPosition: Vector3, cameraRotation: Euler) { + setOrigin(cameraPosition); setAngle(eulerToAngle(cameraRotation)); } @@ -91,12 +91,10 @@ export function PlayAreaContext(props: Props) { } else if (x < 0) { selectedAngle += Math.PI * 2; } - } else { - if (x > 0) { - selectedAngle = Math.PI / 2; - } else if (x < 0) { - selectedAngle = (Math.PI * 3) / 2; - } + } else if (x > 0) { + selectedAngle = Math.PI / 2; + } else if (x < 0) { + selectedAngle = (Math.PI * 3) / 2; } return selectedAngle; } diff --git a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx index 043046d7f..e5725912e 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObjectComponent.tsx @@ -17,7 +17,7 @@ import { SpringMovement, TextModel, } from './Behaviour'; -import { Mesh, Vector3 } from 'three'; +import { type Mesh, Vector3 } from 'three'; import { useFrame } from '@react-three/fiber'; import ErrorBoundary from './ErrorBoundary'; import GltfComponent from './model_components/GltfComponent'; diff --git a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx index e792860e6..0e4fc5368 100644 --- a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx +++ b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx @@ -202,6 +202,12 @@ export abstract class MovementClass implements Behaviour { type: string = ''; } +export enum MovementStyle { + Linear, + FastToSlow, + SlowToFast, +} + /** * Defines movement in a straight line for a path. */ @@ -241,12 +247,6 @@ function parsePathItems(path: any[]) { return result; } -export enum MovementStyle { - Linear, - FastToSlow, - SlowToFast, -} - /** * Behaviour where the object moves in the defined path. * Cycles through the path array repeatedly. @@ -304,12 +304,12 @@ export class PathMovement extends MovementClass { ); switch (currentItem.style) { case MovementStyle.SlowToFast: { - ratio = Math.pow(ratio, 5); + ratio = ratio ** 5; break; } case MovementStyle.FastToSlow: { let negative = 1 - ratio; - negative = Math.pow(negative, 5); + negative = negative ** 5; ratio = 1 - negative; break; } @@ -357,12 +357,16 @@ export class OrbitMovement extends MovementClass { if (startTime) { this.startTime = startTime; } else { - this.startTime = new Date().getTime(); + let currentDate = new Date(); + this.startTime = currentDate.getTime(); } if (getCurrentTime) { this.getCurrentTime = getCurrentTime; } else { - this.getCurrentTime = () => new Date().getTime(); + this.getCurrentTime = () => { + let currentDate = new Date(); + return currentDate.getTime(); + }; } } public getOffsetPosition(position: Vector3) { diff --git a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx index da89b09f2..523eceebc 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx @@ -27,12 +27,8 @@ export default function InterfaceComponent(props: InterfaceProps) { const [components, setComponents] = useState(); useEffect(() => { - setComponents( - parseJsonInterface(props.interfaceModel.uiJson)?.getComponent( - new Vector3(0), - () => {}, - ), - ); + let parsedJson = parseJsonInterface(props.interfaceModel.uiJson); + setComponents(parsedJson?.getComponent(new Vector3(0), () => {})); }, [props.interfaceModel.uiJson]); return ( @@ -63,126 +59,198 @@ export function parseJsonInterface(uiJson: any) { } switch (componentType) { case 'UIColumnComponent': { - let horizontalAlignmentIndex = uiJson.horizontalAlignment; - let horizontalAlignment = HorizontalAlignment.Left; - if (typeof horizontalAlignmentIndex === 'number') { - let parsedIndex = Math.min(Math.max(0, horizontalAlignmentIndex), 2); - horizontalAlignment = parsedIndex; - } - let children: UIBasicComponent[] = []; - let jsonChildren = uiJson.children; - if (Array.isArray(jsonChildren)) { - jsonChildren.forEach((jsonChild) => { - let child = parseJsonInterface(jsonChild); - if (child) { - children.push(child); - } - }); - } - let background = uiJson.background; - if (typeof background != 'number') { - background = undefined; - } - return new UIColumnComponent({ - horizontalAlignment: horizontalAlignment, - padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, - }, - children: children, - background: background, - id: id, - }); + return parseColumn( + uiJson, + id, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, + ); } case 'UIRowComponent': { - let verticalAlignmentIndex = uiJson.verticalAlignment; - let verticalAlignment = VerticalAlignment.Top; - if (typeof verticalAlignmentIndex === 'number') { - let parsedIndex = Math.min(Math.max(0, verticalAlignmentIndex), 2); - verticalAlignment = parsedIndex; - } - let children: UIBasicComponent[] = []; - let jsonChildren = uiJson.children; - if (Array.isArray(jsonChildren)) { - jsonChildren.forEach((jsonChild) => { - let child = parseJsonInterface(jsonChild); - if (child) { - children.push(child); - } - }); - } - let background = uiJson.background; - if (typeof background != 'number') { - background = undefined; - } - return new UIRowComponent({ - verticalAlignment: verticalAlignment, - padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, - }, - children: children, - background: background, - id: id, - }); + return parseRow( + uiJson, + id, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, + ); } case 'UITextComponent': { - let text = uiJson.text; - let textSize = uiJson.textSize; - let textWidth = uiJson.textWidth; - let textAlign = uiJson.textAlign; - let color = uiJson.color; - if ( - typeof text === 'string' && - typeof textSize === 'number' && - typeof textWidth === 'number' && - typeof color === 'number' - ) { - return new UITextComponent({ - text: text, - textSize: textSize, - textWidth: textWidth, - textAlign: textAlign, - color: color, - padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, - }, - id: id, - }); - } - break; + return parseText( + uiJson, + id, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, + ); } case 'UIImageComponent': { - let src = uiJson.src; - let imageWidth = uiJson.imageWidth; - let imageHeight = uiJson.imageHeight; - if ( - typeof src === 'string' && - typeof imageWidth === 'number' && - typeof imageHeight === 'number' - ) { - return new UIImageComponent({ - src: src, - imageWidth: imageWidth, - imageHeight: imageHeight, - padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, - }, - id: id, - }); - } - break; + return parseImage( + uiJson, + id, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, + ); } } return undefined; } + +function parseColumn( + uiJson: any, + id: string, + paddingLeft: number | undefined, + paddingRight: number | undefined, + paddingTop: number | undefined, + paddingBottom: number | undefined, +) { + let horizontalAlignmentIndex = uiJson.horizontalAlignment; + let horizontalAlignment = HorizontalAlignment.Left; + if (typeof horizontalAlignmentIndex === 'number') { + let parsedIndex = Math.min(Math.max(0, horizontalAlignmentIndex), 2); + horizontalAlignment = parsedIndex; + } + let children: UIBasicComponent[] = []; + let jsonChildren = uiJson.children; + if (Array.isArray(jsonChildren)) { + jsonChildren.forEach((jsonChild) => { + let child = parseJsonInterface(jsonChild); + if (child) { + children.push(child); + } + }); + } + let background = uiJson.background; + if (typeof background !== 'number') { + background = undefined; + } + return new UIColumnComponent({ + horizontalAlignment: horizontalAlignment, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + children: children, + background: background, + id: id, + }); +} + +function parseRow( + uiJson: any, + id: string, + paddingLeft: number | undefined, + paddingRight: number | undefined, + paddingTop: number | undefined, + paddingBottom: number | undefined, +) { + let verticalAlignmentIndex = uiJson.verticalAlignment; + let verticalAlignment = VerticalAlignment.Top; + if (typeof verticalAlignmentIndex === 'number') { + let parsedIndex = Math.min(Math.max(0, verticalAlignmentIndex), 2); + verticalAlignment = parsedIndex; + } + let children: UIBasicComponent[] = []; + let jsonChildren = uiJson.children; + if (Array.isArray(jsonChildren)) { + jsonChildren.forEach((jsonChild) => { + let child = parseJsonInterface(jsonChild); + if (child) { + children.push(child); + } + }); + } + let background = uiJson.background; + if (typeof background !== 'number') { + background = undefined; + } + return new UIRowComponent({ + verticalAlignment: verticalAlignment, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + children: children, + background: background, + id: id, + }); +} + +function parseText( + uiJson: any, + id: string, + paddingLeft: number | undefined, + paddingRight: number | undefined, + paddingTop: number | undefined, + paddingBottom: number | undefined, +) { + let text = uiJson.text; + let textSize = uiJson.textSize; + let textWidth = uiJson.textWidth; + let textAlign = uiJson.textAlign; + let color = uiJson.color; + if ( + typeof text === 'string' && + typeof textSize === 'number' && + typeof textWidth === 'number' && + typeof color === 'number' + ) { + return new UITextComponent({ + text: text, + textSize: textSize, + textWidth: textWidth, + textAlign: textAlign, + color: color, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + id: id, + }); + } + return undefined; +} + +function parseImage( + uiJson: any, + id: string, + paddingLeft: number | undefined, + paddingRight: number | undefined, + paddingTop: number | undefined, + paddingBottom: number | undefined, +) { + let src = uiJson.src; + let imageWidth = uiJson.imageWidth; + let imageHeight = uiJson.imageHeight; + if ( + typeof src === 'string' && + typeof imageWidth === 'number' && + typeof imageHeight === 'number' + ) { + return new UIImageComponent({ + src: src, + imageWidth: imageWidth, + imageHeight: imageHeight, + padding: { + paddingLeft: paddingLeft, + paddingRight: paddingRight, + paddingTop: paddingTop, + paddingBottom: paddingBottom, + }, + id: id, + }); + } + return undefined; +} diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index b93c9556e..776e7d0e0 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -6,6 +6,12 @@ import { } from './UIComponent'; import { Color, Vector3 } from 'three'; +export enum HorizontalAlignment { + Left, + Center, + Right, +} + type UIColumnProps = { children?: UIBasicComponent[]; horizontalAlignment?: HorizontalAlignment; @@ -47,16 +53,14 @@ export default class UIColumnComponent extends LayoutComponent { }); return height; }; - getComponent = (position: Vector3, updateParent: () => void) => { - return ( - - ); - }; + getComponent = (position: Vector3, updateParent: () => void) => ( + + ); } function ColumnUIComponent(props: { @@ -77,8 +81,8 @@ function ColumnUIComponent(props: { setWidth(component.width); updateChildrenAlignment(); if ( - previousHeight != component.height || - previousWidth != component.width + previousHeight !== component.height || + previousWidth !== component.width ) { updateParent(); } @@ -134,9 +138,3 @@ function ColumnUIComponent(props: { ); } - -export enum HorizontalAlignment { - Left, - Center, - Right, -} diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx index 0318628f6..bdf2ab134 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react'; -import { Vector3 } from 'three'; +import { type Vector3 } from 'three'; import uniqid from 'uniqid'; type UIComponentProps = { @@ -79,16 +79,12 @@ export class UIBasicComponent { this.parent?.calculateDimensions(); } }; - getWidth = () => { - return this.paddingLeft + this.paddingRight; - }; - getHeight = () => { - return this.paddingTop + this.paddingBottom; - }; + getWidth = () => this.paddingLeft + this.paddingRight; + getHeight = () => this.paddingTop + this.paddingBottom; calculateLayer = () => {}; - getComponent = (position: Vector3, _: () => void) => { - return ; - }; + getComponent = (position: Vector3, _: () => void) => ( + + ); } export class LayoutComponent extends UIBasicComponent { diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx index c7af141b8..279a12d64 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx @@ -20,17 +20,11 @@ export default class UIImageComponent extends UIBasicComponent { this.imageWidth = props.imageWidth; this.imageHeight = props.imageHeight; } - getWidth = () => { - return this.imageWidth + this.paddingTop + this.paddingBottom; - }; - getHeight = () => { - return this.imageHeight + this.paddingTop + this.paddingBottom; - }; - getComponent = (position: Vector3, _: () => void) => { - return ( - - ); - }; + getWidth = () => this.imageWidth + this.paddingTop + this.paddingBottom; + getHeight = () => this.imageHeight + this.paddingTop + this.paddingBottom; + getComponent = (position: Vector3, _: () => void) => ( + + ); } function ImageUIComponent(props: { diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx index 85afe5ed4..af1c2ee71 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx @@ -6,6 +6,12 @@ import { } from './UIComponent'; import { Color, Vector3 } from 'three'; +export enum VerticalAlignment { + Top, + Middle, + Bottom, +} + type UIRowProps = { children?: UIBasicComponent[]; verticalAlignment?: VerticalAlignment; @@ -77,8 +83,8 @@ function RowUIComponent(props: { setWidth(component.width); updateChildrenAlignment(); if ( - previousHeight != component.height || - previousWidth != component.width + previousHeight !== component.height || + previousWidth !== component.width ) { updateParent(); } @@ -105,15 +111,15 @@ function RowUIComponent(props: { setComponentPositions(positions); } - function ChildrenComponents(props: { componentPositions: Vector3[] }) { - if (props.componentPositions.length !== component.children.length) { + function ChildrenComponents(childProps: { componentPositions: Vector3[] }) { + if (childProps.componentPositions.length !== component.children.length) { updateChildrenAlignment(); return null; } let children: ReactNode[] = []; for (let i = 0; i < component.children.length; i++) { let child = component.children[i]; - let childPosition = props.componentPositions[i]; + let childPosition = childProps.componentPositions[i]; children.push( {child.getComponent(childPosition, updateSize)} @@ -134,9 +140,3 @@ function RowUIComponent(props: { ); } - -export enum VerticalAlignment { - Top, - Middle, - Bottom, -} diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx index 21e9a5f5d..7d64b1cd7 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -1,4 +1,4 @@ -import { Color, Mesh, Vector3 } from 'three'; +import { Color, type Mesh, Vector3 } from 'three'; import { UIBasicComponent, type PaddingType } from './UIComponent'; import { Text } from '@react-three/drei'; import { useEffect, useRef, useState } from 'react'; @@ -41,7 +41,7 @@ export default class UITextComponent extends UIBasicComponent { } this.textHeight = newTextHeight; let newHeight = this.getHeight(); - if (this.height != newHeight) { + if (this.height !== newHeight) { this.height = newHeight; let parent = this.parent; while (parent) { @@ -97,9 +97,9 @@ function TextUIComponent(props: { } let offsetMagnitude = (component.textWidth - textWidth) / 2; if (offsetMagnitude <= 0) return; - if (component.textAlign == HorizontalAlignment.Left) { + if (component.textAlign === HorizontalAlignment.Left) { setOffsetX(-offsetMagnitude); - } else if (component.textAlign == HorizontalAlignment.Right) { + } else if (component.textAlign === HorizontalAlignment.Right) { setOffsetX(offsetMagnitude); } } else { diff --git a/src/bundles/communication/GlobalStateController.ts b/src/bundles/communication/GlobalStateController.ts index 9da72abdf..346bc5eff 100644 --- a/src/bundles/communication/GlobalStateController.ts +++ b/src/bundles/communication/GlobalStateController.ts @@ -1,4 +1,4 @@ -import { MultiUserController } from './MultiUserController'; +import { type MultiUserController } from './MultiUserController'; /** * Controller for maintaining a global state across all devices. @@ -55,7 +55,7 @@ export class GlobalStateController { } let splitTopic = preSplitTopic.split('/'); try { - let newGlobalState = Object.assign({}, this.globalState); + let newGlobalState = { ...this.globalState }; if ( this.globalState instanceof Array || typeof this.globalState === 'string' diff --git a/src/bundles/communication/MqttController.ts b/src/bundles/communication/MqttController.ts index be96a6a17..301567c08 100644 --- a/src/bundles/communication/MqttController.ts +++ b/src/bundles/communication/MqttController.ts @@ -1,4 +1,4 @@ -import { connect, MqttClient } from 'mqtt/dist/mqtt'; +import { connect, type MqttClient } from 'mqtt/dist/mqtt'; export const STATE_CONNECTED = 'Connected'; export const STATE_DISCONNECTED = 'Disconnected'; @@ -50,7 +50,8 @@ export class MqttController { this.connectionCallback(STATE_OFFLINE); }); this.client.on('message', (topic, message) => { - this.messageCallback(topic, new TextDecoder('utf-8').decode(message)); + let decoder = new TextDecoder('utf-8'); + this.messageCallback(topic, decoder.decode(message)); }); } @@ -58,8 +59,9 @@ export class MqttController { * Disconnects the MQTT client. */ public disconnect() { - if (this.client == null) return; - this.client.end(true); + if (this.client) { + this.client.end(true); + } this.connectionCallback = () => {}; this.messageCallback = () => {}; } @@ -85,8 +87,9 @@ export class MqttController { * @param topic Identifier for group of devices receiving the broadcast. */ public subscribe(topic: string) { - if (this.client == null) return; - this.client.subscribe(topic); + if (this.client) { + this.client.subscribe(topic); + } } /** diff --git a/src/bundles/communication/RpcController.ts b/src/bundles/communication/RpcController.ts index 6c7865fcd..2b5f23615 100644 --- a/src/bundles/communication/RpcController.ts +++ b/src/bundles/communication/RpcController.ts @@ -1,4 +1,4 @@ -import { MultiUserController } from './MultiUserController'; +import { type MultiUserController } from './MultiUserController'; import uniqid from 'uniqid'; type DeclaredFunction = { @@ -79,15 +79,15 @@ export class RpcController { let callId = parsedMessage.callId; let sender = parsedMessage.sender; if (!callId || !sender) return; - let name = splitTopic[2]; - let func = this.functions.get(name); - if (!func) { + let calledName = splitTopic[2]; + let calledFunc = this.functions.get(calledName); + if (!calledFunc) { this.returnResponse(sender, callId, null); return; } try { let args = parsedMessage.args; - let result = func?.func(...args); + let result = calledFunc?.func(...args); this.returnResponse(sender, callId, result); } catch { this.returnResponse(sender, callId, null); diff --git a/src/tabs/AugmentedReality/index.tsx b/src/tabs/AugmentedReality/index.tsx index 4fe2d0887..89dd420ef 100644 --- a/src/tabs/AugmentedReality/index.tsx +++ b/src/tabs/AugmentedReality/index.tsx @@ -1,6 +1,6 @@ -import React from "react"; -import { getModuleState } from "../../bundles/ar/AR"; -import ARComponent from "../../bundles/ar/ARComponent"; +import React from 'react'; +import { getModuleState } from '../../bundles/ar/AR'; +import ARComponent from '../../bundles/ar/ARComponent'; /** * Tab for viewing augmented reality content @@ -17,10 +17,6 @@ type Props = {}; * The main React Component of the Tab. */ class ARMainComponent extends React.Component { - constructor(props) { - super(props); - } - public render() { return (
@@ -39,7 +35,7 @@ export default { * @param {DebuggerContext} context * @returns {boolean} */ - toSpawn: (context: any) => getModuleState() !== undefined, + toSpawn: (_: any) => getModuleState() !== undefined, /** * This function will be called to render the module tab in the side contents @@ -51,12 +47,12 @@ export default { /** * The Tab's icon tooltip in the side contents on Source Academy frontend. */ - label: "AR Tab", + label: 'AR Tab', /** * BlueprintJS IconName element's name, used to render the icon which will be * displayed in the side contents panel. * @see https://blueprintjs.com/docs/#icons */ - iconName: "intelligence", + iconName: 'intelligence', }; From 75462dea47250f899635cb146072f011e016bb52 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Sat, 24 Feb 2024 16:24:58 +0800 Subject: [PATCH 38/43] Added gltf object --- src/bundles/ar/ARComponent.tsx | 6 ++ src/bundles/ar/ObjectsHelper.ts | 31 ++++++++++ src/bundles/ar/index.ts | 1 + .../object_state_library/ARObject.tsx | 60 +++++++++++++++++++ .../model_components/GltfComponent.tsx | 1 + 5 files changed, 99 insertions(+) diff --git a/src/bundles/ar/ARComponent.tsx b/src/bundles/ar/ARComponent.tsx index 78cc2dec9..70da338bc 100644 --- a/src/bundles/ar/ARComponent.tsx +++ b/src/bundles/ar/ARComponent.tsx @@ -20,6 +20,7 @@ import { UIObject, LightObject, SphereObject, + GltfObject, } from './libraries/object_state_library/ARObject'; import { useThree } from '@react-three/fiber'; @@ -188,6 +189,11 @@ function AugmentedContent(props: ARState) { newObjects.push(newObject); return; } + newObject = GltfObject.parseObject(object); + if (newObject) { + newObjects.push(newObject); + return; + } newObject = UIObject.parseObject(object); if (newObject) { newObjects.push(newObject); diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index a54866093..3aa9921ee 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -5,6 +5,7 @@ import { LightObject, SphereObject, UIObject, + GltfObject, } from './libraries/object_state_library/ARObject'; import { AlwaysRender, @@ -96,6 +97,36 @@ export function createSphereObject( ); } +/** + * Creates an instance of 3D object with GLTF model. + * Build it with `createInterfaceRow`, `createInterfaceColumn`, `createInterfaceText` and `createInterfaceImage`. + * + * @param position Position of object in augmented world. + * @param src URL of GLTF resources. + * @param scale Scale of loaded object. + * @param onSelect Function to call when object is tapped. + * @returns Created AR interface object. + */ +export function createGltfObject( + position: Vector3, + src: string, + scale: number, + onSelect?: () => {}, +): GltfObject { + return new GltfObject( + uniqid(), + position, + src, + scale, + undefined, + undefined, + undefined, + (_: ARObject) => { + onSelect?.(); + }, + ); +} + /** * Creates an instance of AR user interface. * Build it with `createInterfaceRow`, `createInterfaceColumn`, `createInterfaceText` and `createInterfaceImage`. diff --git a/src/bundles/ar/index.ts b/src/bundles/ar/index.ts index 21dff999a..ff24870f5 100644 --- a/src/bundles/ar/index.ts +++ b/src/bundles/ar/index.ts @@ -37,6 +37,7 @@ export { export { createCubeObject, createSphereObject, + createGltfObject, createInterfaceObject, createLightObject, createInterfaceRow, diff --git a/src/bundles/ar/libraries/object_state_library/ARObject.tsx b/src/bundles/ar/libraries/object_state_library/ARObject.tsx index 5d58f88d3..94768e8a6 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObject.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObject.tsx @@ -16,6 +16,7 @@ import { parseRender, parseMovement, type MovementClass, + GltfModel, } from './Behaviour'; import ARObjectComponent from './ARObjectComponent'; import { parseVector3 } from '../calibration_library/Misc'; @@ -204,6 +205,65 @@ export class SphereObject extends ARObject { } } +const GLTF_OBJECT_TYPE = 'GltfObject'; +export class GltfObject extends ARObject { + type = GLTF_OBJECT_TYPE; + src: string; + scale: number; + constructor( + id: string, + position: Vector3, + src: string, + scale: number, + render?: RenderClass, + rotation?: RotationClass, + movement?: MovementClass, + onSelect?: (object: ARObject) => void, + ) { + super( + id, + position, + { + model: new GltfModel(src, scale), + render: render, + rotation: rotation, + movement: movement, + }, + onSelect, + ); + this.src = src; + this.scale = scale; + } + static parseObject(object: any, onSelect?: () => void): ARObject | undefined { + if (!object || object.type !== GLTF_OBJECT_TYPE) return undefined; + let id = object.id; + let position = parseVector3(object.position); + let render = parseRender(object.behaviours?.render); + let rotation = parseRotation(object.behaviours?.rotation); + let movement = parseMovement(object.behaviours?.movement); + let src = object.src; + let scale = object.scale; + if ( + typeof id === 'string' && + position instanceof Vector3 && + typeof src === 'string' && + typeof scale === 'number' + ) { + return new GltfObject( + id, + position, + src, + scale, + render, + rotation, + movement, + onSelect, + ); + } + return undefined; + } +} + const UI_OBJECT_TYPE = 'UIObject'; export class UIObject extends ARObject { type = UI_OBJECT_TYPE; diff --git a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx index 82b7ab62c..fd2ee8a2f 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/GltfComponent.tsx @@ -51,6 +51,7 @@ export default function GltfComponent(props: GltfProps) { mixer.current?.update(delta); }); + // Issue with excessively deep animated mesh https://github.com/pmndrs/react-spring/issues/1515 return ( Date: Sat, 24 Feb 2024 17:15:26 +0800 Subject: [PATCH 39/43] More code cleanup --- .../object_state_library/Behaviour.tsx | 4 ++-- .../ui_component/UIColumnComponent.tsx | 6 ++--- .../ui_component/UIComponent.tsx | 2 +- .../ui_component/UIImageComponent.tsx | 4 ++-- .../ui_component/UIRowComponent.tsx | 24 +++++++++---------- .../ui_component/UITextComponent.tsx | 2 +- .../communication/GlobalStateController.ts | 2 +- src/bundles/communication/MqttController.ts | 2 +- .../communication/MultiUserController.ts | 2 +- src/bundles/communication/RpcController.ts | 8 +++---- 10 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx index 0e4fc5368..6fbba7847 100644 --- a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx +++ b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx @@ -304,12 +304,12 @@ export class PathMovement extends MovementClass { ); switch (currentItem.style) { case MovementStyle.SlowToFast: { - ratio = ratio ** 5; + ratio **= 5; break; } case MovementStyle.FastToSlow: { let negative = 1 - ratio; - negative = negative ** 5; + negative **= 5; ratio = 1 - negative; break; } diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index 776e7d0e0..ecbaf5b80 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -119,16 +119,16 @@ function ColumnUIComponent(props: { let child = component.children[i]; let childPosition = props.componentPositions[i]; children.push( - + {child.getComponent(childPosition, updateSize)} , ); } - return {children}; + return {children}; } return ( - + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx index bdf2ab134..1f267d0be 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIComponent.tsx @@ -83,7 +83,7 @@ export class UIBasicComponent { getHeight = () => this.paddingTop + this.paddingBottom; calculateLayer = () => {}; getComponent = (position: Vector3, _: () => void) => ( - + ); } diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx index 279a12d64..cc4810fdf 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx @@ -1,4 +1,4 @@ -import { Vector3 } from 'three'; +import { type Vector3 } from 'three'; import { UIBasicComponent, type PaddingType } from './UIComponent'; import { Image } from '@react-three/drei'; @@ -33,7 +33,7 @@ function ImageUIComponent(props: { }) { let { component, position } = props; return ( - + void) => { - return ( - - ); - }; + getComponent = (position: Vector3, updateParent: () => void) => ( + + ); } function RowUIComponent(props: { @@ -121,16 +119,16 @@ function RowUIComponent(props: { let child = component.children[i]; let childPosition = childProps.componentPositions[i]; children.push( - + {child.getComponent(childPosition, updateSize)} , ); } - return {children}; + return {children}; } return ( - + diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx index 7d64b1cd7..1ed8e87b4 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -126,7 +126,7 @@ function TextUIComponent(props: { } return ( - + { diff --git a/src/bundles/communication/MultiUserController.ts b/src/bundles/communication/MultiUserController.ts index 4f5969fa0..6d1a13b2b 100644 --- a/src/bundles/communication/MultiUserController.ts +++ b/src/bundles/communication/MultiUserController.ts @@ -56,7 +56,7 @@ export class MultiUserController { identifier: string, callback: (topic: string, message: string) => void, ) { - this.controller?.subscribe(identifier + '/#'); + this.controller?.subscribe(`${identifier}/#`); this.messageCallbacks.set(identifier, callback); } } diff --git a/src/bundles/communication/RpcController.ts b/src/bundles/communication/RpcController.ts index 2b5f23615..8ed8a70cd 100644 --- a/src/bundles/communication/RpcController.ts +++ b/src/bundles/communication/RpcController.ts @@ -29,7 +29,7 @@ export class RpcController { this.topicHeader = topicHeader; this.multiUser = multiUser; this.userId = userId ?? uniqid(); - this.returnTopic = this.topicHeader + '_return/' + this.userId; + this.returnTopic = `${this.topicHeader}_return/${this.userId}`; this.multiUser.addMessageCallback(this.returnTopic, (topic, message) => { let messageJson = JSON.parse(message); let callId = messageJson.callId; @@ -53,7 +53,7 @@ export class RpcController { callId: callId, result: value, }; - let topic = this.topicHeader + '_return/' + sender; + let topic = `${this.topicHeader}_return/${sender}`; this.multiUser.controller?.publish(topic, JSON.stringify(message), false); } @@ -69,7 +69,7 @@ export class RpcController { func: func, }; this.functions.set(name, item); - let functionTopic = this.topicHeader + '/' + this.userId + '/' + name; + let functionTopic = `${this.topicHeader}/${this.userId}/${name}`; this.multiUser.addMessageCallback(functionTopic, (topic, message) => { let splitTopic = topic.split('/'); if (splitTopic.length !== 3) { @@ -109,7 +109,7 @@ export class RpcController { args: any[], callback: (args: any[]) => void, ) { - let topic = this.topicHeader + '/' + receiver + '/' + name; + let topic = `${this.topicHeader}/${receiver}/${name}`; let callId = uniqid(); this.pendingReturns.set(callId, callback); let messageJson = { From 81886db7bf940d4999144c7f1a3349cc2bad742b Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Sat, 24 Feb 2024 17:47:05 +0800 Subject: [PATCH 40/43] Fix property shorthand lint --- src/bundles/ar/ObjectsHelper.ts | 66 +++++++-------- .../object_state_library/ARObject.tsx | 28 +++---- .../object_state_library/Behaviour.tsx | 6 +- .../model_components/InterfaceComponent.tsx | 80 +++++++++---------- .../ui_component/UIColumnComponent.tsx | 4 +- .../ui_component/UIRowComponent.tsx | 4 +- .../ui_component/UITextComponent.tsx | 26 +++--- .../ScreenStateContext.tsx | 6 +- src/bundles/communication/RpcController.ts | 16 ++-- 9 files changed, 115 insertions(+), 121 deletions(-) diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index 3aa9921ee..bf0f88ec3 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -193,15 +193,15 @@ export function createInterfaceRow( backgroundColor: number, ): UIRowComponent { return new UIRowComponent({ - children: children, - verticalAlignment: verticalAlignment, + children, + verticalAlignment, padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, }, - background: backgroundColor, + backgroundColor: backgroundColor, id: uniqid(), }); } @@ -228,15 +228,15 @@ export function createInterfaceColumn( backgroundColor: number, ): UIColumnComponent { return new UIColumnComponent({ - children: children, - horizontalAlignment: horizontalAlignment, + children, + horizontalAlignment, padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, }, - background: backgroundColor, + backgroundColor, id: uniqid(), }); } @@ -267,17 +267,17 @@ export function createInterfaceText( color: number, ): UITextComponent { return new UITextComponent({ - text: text, - textSize: textSize, - textWidth: textWidth, - textAlign: textAlign, + text, + textSize, + textWidth, + textAlign, padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, }, - color: color, + color, id: uniqid(), }); } @@ -305,14 +305,14 @@ export function createInterfaceImage( paddingBottom: number, ): UIImageComponent { return new UIImageComponent({ - src: src, - imageWidth: imageWidth, - imageHeight: imageHeight, + src, + imageWidth, + imageHeight, padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, }, id: uniqid(), }); @@ -390,9 +390,9 @@ export function createPathItem( duration: number, ): PathItem { return { - start: start, - end: end, - duration: duration, + start, + end, + duration, style: MovementStyle.Linear, }; } diff --git a/src/bundles/ar/libraries/object_state_library/ARObject.tsx b/src/bundles/ar/libraries/object_state_library/ARObject.tsx index 94768e8a6..d69356be2 100644 --- a/src/bundles/ar/libraries/object_state_library/ARObject.tsx +++ b/src/bundles/ar/libraries/object_state_library/ARObject.tsx @@ -94,11 +94,11 @@ export class CubeObject extends ARObject { { model: new ShapeModel( new BoxGeometry(width, height, depth), - new MeshStandardMaterial({ color: color }), + new MeshStandardMaterial({ color }), ), - render: render, - rotation: rotation, - movement: movement, + render, + rotation, + movement, }, onSelect, ); @@ -164,11 +164,11 @@ export class SphereObject extends ARObject { { model: new ShapeModel( new SphereGeometry(radius, 20, 20), - new MeshStandardMaterial({ color: color }), + new MeshStandardMaterial({ color }), ), - render: render, - rotation: rotation, - movement: movement, + render, + rotation, + movement, }, onSelect, ); @@ -225,9 +225,9 @@ export class GltfObject extends ARObject { position, { model: new GltfModel(src, scale), - render: render, - rotation: rotation, - movement: movement, + render, + rotation, + movement, }, onSelect, ); @@ -282,9 +282,9 @@ export class UIObject extends ARObject { position, { model: new InterfaceModel(uiJson), - render: render, - rotation: rotation, - movement: movement, + render, + rotation, + movement, }, onSelect, ); diff --git a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx index 6fbba7847..21947ced3 100644 --- a/src/bundles/ar/libraries/object_state_library/Behaviour.tsx +++ b/src/bundles/ar/libraries/object_state_library/Behaviour.tsx @@ -237,9 +237,9 @@ function parsePathItems(path: any[]) { movementStyle = MovementStyle.SlowToFast; } result.push({ - start: start, - end: end, - duration: duration, + start, + end, + duration, style: movementStyle, }); } diff --git a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx index 523eceebc..21d466c6f 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx @@ -126,21 +126,21 @@ function parseColumn( } }); } - let background = uiJson.background; - if (typeof background !== 'number') { - background = undefined; + let backgroundColor = uiJson.background; + if (typeof backgroundColor !== 'number') { + backgroundColor = undefined; } return new UIColumnComponent({ - horizontalAlignment: horizontalAlignment, + children, + horizontalAlignment, padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, }, - children: children, - background: background, - id: id, + backgroundColor, + id, }); } @@ -168,21 +168,21 @@ function parseRow( } }); } - let background = uiJson.background; - if (typeof background !== 'number') { - background = undefined; + let backgroundColor = uiJson.background; + if (typeof backgroundColor !== 'number') { + backgroundColor = undefined; } return new UIRowComponent({ - verticalAlignment: verticalAlignment, + children, + verticalAlignment, padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, }, - children: children, - background: background, - id: id, + backgroundColor, + id, }); } @@ -206,18 +206,18 @@ function parseText( typeof color === 'number' ) { return new UITextComponent({ - text: text, - textSize: textSize, - textWidth: textWidth, - textAlign: textAlign, - color: color, + text, + textSize, + textWidth, + textAlign, + color, padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, }, - id: id, + id, }); } return undefined; @@ -240,16 +240,16 @@ function parseImage( typeof imageHeight === 'number' ) { return new UIImageComponent({ - src: src, - imageWidth: imageWidth, - imageHeight: imageHeight, + src, + imageWidth, + imageHeight, padding: { - paddingLeft: paddingLeft, - paddingRight: paddingRight, - paddingTop: paddingTop, - paddingBottom: paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingBottom, }, - id: id, + id, }); } return undefined; diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index ecbaf5b80..72eb5fa9e 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -16,7 +16,7 @@ type UIColumnProps = { children?: UIBasicComponent[]; horizontalAlignment?: HorizontalAlignment; padding?: number | PaddingType; - background?: number; + backgroundColor?: number; id?: string; }; @@ -25,7 +25,7 @@ export default class UIColumnComponent extends LayoutComponent { background: number; constructor(props: UIColumnProps) { super(props.padding, props.id); - this.background = props.background ?? 0xffffff; + this.background = props.backgroundColor ?? 0xffffff; if (props.children) { this.children = props.children; } diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx index 127c84805..111c3ee0d 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx @@ -16,7 +16,7 @@ type UIRowProps = { children?: UIBasicComponent[]; verticalAlignment?: VerticalAlignment; padding?: number | PaddingType; - background?: number; + backgroundColor?: number; id?: string; }; @@ -25,7 +25,7 @@ export default class UIRowComponent extends LayoutComponent { background: number; constructor(props: UIRowProps) { super(props.padding, props.id); - this.background = props.background ?? 0xffffff; + this.background = props.backgroundColor ?? 0xffffff; if (props.children) { this.children = props.children; } diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx index 1ed8e87b4..d98b7ccf7 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -29,12 +29,8 @@ export default class UITextComponent extends UIBasicComponent { this.textAlign = props.textAlign ?? HorizontalAlignment.Center; this.color = props.color ?? 0; } - getWidth = () => { - return this.textWidth + this.paddingTop + this.paddingBottom; - }; - getHeight = () => { - return this.textHeight + this.paddingTop + this.paddingBottom; - }; + getWidth = () => this.textWidth + this.paddingTop + this.paddingBottom; + getHeight = () => this.textHeight + this.paddingTop + this.paddingBottom; updateHeight = (newTextHeight: number): boolean => { if (newTextHeight === 0) { return false; @@ -52,16 +48,14 @@ export default class UITextComponent extends UIBasicComponent { } return false; }; - getComponent = (position: Vector3, updateParent: () => void) => { - return ( - - ); - }; + getComponent = (position: Vector3, updateParent: () => void) => ( + + ); } function TextUIComponent(props: { diff --git a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx index 56c3ca232..fabcdface 100644 --- a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx +++ b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx @@ -128,10 +128,10 @@ export function ScreenStateContext(props: Props) { return ( {props.children} diff --git a/src/bundles/communication/RpcController.ts b/src/bundles/communication/RpcController.ts index 8ed8a70cd..f649c0868 100644 --- a/src/bundles/communication/RpcController.ts +++ b/src/bundles/communication/RpcController.ts @@ -46,12 +46,12 @@ export class RpcController { * * @param sender ID of caller. * @param callId ID of function call. - * @param value Return value for function call. + * @param result Return value for function call. */ - private returnResponse(sender: string, callId: string, value: any) { + private returnResponse(sender: string, callId: string, result: any) { let message = { - callId: callId, - result: value, + callId, + result, }; let topic = `${this.topicHeader}_return/${sender}`; this.multiUser.controller?.publish(topic, JSON.stringify(message), false); @@ -65,8 +65,8 @@ export class RpcController { */ public expose(name: string, func: (...args: any[]) => any) { let item = { - name: name, - func: func, + name, + func, }; this.functions.set(name, item); let functionTopic = `${this.topicHeader}/${this.userId}/${name}`; @@ -114,8 +114,8 @@ export class RpcController { this.pendingReturns.set(callId, callback); let messageJson = { sender: this.userId, - callId: callId, - args: args, + callId, + args, }; let messageString = JSON.stringify(messageJson); this.multiUser.controller?.publish(topic, messageString, false); From c697d1979721de0749618e48e894b7b5227d7224 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Sat, 24 Feb 2024 18:05:26 +0800 Subject: [PATCH 41/43] Address warnings for lint --- src/bundles/ar/ObjectsHelper.ts | 3 ++- .../ar/libraries/calibration_library/PlayAreaContext.tsx | 4 ++-- .../ar/libraries/controls_library/ControlsContext.tsx | 6 ++---- .../model_components/InterfaceComponent.tsx | 2 +- .../object_state_library/ui_component/UIColumnComponent.tsx | 6 +++--- .../libraries/screen_state_library/ScreenStateContext.tsx | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index bf0f88ec3..3fe05aa0d 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -227,6 +227,7 @@ export function createInterfaceColumn( paddingBottom: number, backgroundColor: number, ): UIColumnComponent { + let id = uniqid(); return new UIColumnComponent({ children, horizontalAlignment, @@ -237,7 +238,7 @@ export function createInterfaceColumn( paddingBottom, }, backgroundColor, - id: uniqid(), + id, }); } diff --git a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx index 8e3b1f797..7117f32b1 100644 --- a/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx +++ b/src/bundles/ar/libraries/calibration_library/PlayAreaContext.tsx @@ -10,10 +10,10 @@ type ContextType = { }; const Context = createContext({ - setCameraAsOrigin: () => {}, + setCameraAsOrigin: () => undefined, getCameraRelativePosition: () => new Vector3(), getCameraRelativeRotation: () => new Euler(), - setPlayArea: () => {}, + setPlayArea: () => undefined, }); type Props = { diff --git a/src/bundles/ar/libraries/controls_library/ControlsContext.tsx b/src/bundles/ar/libraries/controls_library/ControlsContext.tsx index fccc2aebf..98ebc41d6 100644 --- a/src/bundles/ar/libraries/controls_library/ControlsContext.tsx +++ b/src/bundles/ar/libraries/controls_library/ControlsContext.tsx @@ -18,7 +18,7 @@ type ContextType = { const Context = createContext({ object: createRef(), - setCallback: () => {}, + setCallback: () => undefined, }); type Props = { @@ -49,9 +49,7 @@ export function ControlsContext(props: Props) { { - callback.current = newCallback; - }, + setCallback: (newCallback) => (callback.current = newCallback), }} > {props.children} diff --git a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx index 21d466c6f..362f4925d 100644 --- a/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/model_components/InterfaceComponent.tsx @@ -55,7 +55,7 @@ export function parseJsonInterface(uiJson: any) { typeof paddingTop !== 'number' || typeof paddingBottom !== 'number' ) { - return; + return undefined; } switch (componentType) { case 'UIColumnComponent': { diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index 72eb5fa9e..a1c4fe3e4 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -109,15 +109,15 @@ function ColumnUIComponent(props: { setComponentPositions(positions); } - function ChildrenComponents(props: { componentPositions: Vector3[] }) { - if (props.componentPositions.length !== component.children.length) { + function ChildrenComponents(childProps: { componentPositions: Vector3[] }) { + if (childProps.componentPositions.length !== component.children.length) { updateChildrenAlignment(); return null; } let children: ReactNode[] = []; for (let i = 0; i < component.children.length; i++) { let child = component.children[i]; - let childPosition = props.componentPositions[i]; + let childPosition = childProps.componentPositions[i]; children.push( {child.getComponent(childPosition, updateSize)} diff --git a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx index fabcdface..6ab845e82 100644 --- a/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx +++ b/src/bundles/ar/libraries/screen_state_library/ScreenStateContext.tsx @@ -19,7 +19,7 @@ type ContextType = { const Context = createContext({ overlayRef: null, domOverlay: undefined, - setStates: () => {}, + setStates: () => undefined, component: <>, }); From 781021f4798e16daa0bc6f267860581d7d36f339 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Sat, 24 Feb 2024 18:31:08 +0800 Subject: [PATCH 42/43] Fix more lint issues --- src/bundles/ar/ObjectsHelper.ts | 5 ++--- .../object_state_library/ui_component/UITextComponent.tsx | 6 +++--- src/bundles/communication/Communications.ts | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/bundles/ar/ObjectsHelper.ts b/src/bundles/ar/ObjectsHelper.ts index 3fe05aa0d..29472bb4f 100644 --- a/src/bundles/ar/ObjectsHelper.ts +++ b/src/bundles/ar/ObjectsHelper.ts @@ -201,7 +201,7 @@ export function createInterfaceRow( paddingTop, paddingBottom, }, - backgroundColor: backgroundColor, + backgroundColor, id: uniqid(), }); } @@ -227,7 +227,6 @@ export function createInterfaceColumn( paddingBottom: number, backgroundColor: number, ): UIColumnComponent { - let id = uniqid(); return new UIColumnComponent({ children, horizontalAlignment, @@ -238,7 +237,7 @@ export function createInterfaceColumn( paddingBottom, }, backgroundColor, - id, + id: uniqid(), }); } diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx index d98b7ccf7..411083fa0 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -1,6 +1,6 @@ import { Color, type Mesh, Vector3 } from 'three'; import { UIBasicComponent, type PaddingType } from './UIComponent'; -import { Text } from '@react-three/drei'; +import { Text as ThreeText } from '@react-three/drei'; import { useEffect, useRef, useState } from 'react'; import { HorizontalAlignment } from './UIColumnComponent'; @@ -121,7 +121,7 @@ function TextUIComponent(props: { return ( - {component.text} - + ); } diff --git a/src/bundles/communication/Communications.ts b/src/bundles/communication/Communications.ts index f73d736ca..8b1781fb3 100644 --- a/src/bundles/communication/Communications.ts +++ b/src/bundles/communication/Communications.ts @@ -20,7 +20,7 @@ context.moduleContexts.communication.state = moduleState; // Loop -let interval: number | undefined = undefined; +let interval: number | undefined; /** * Keeps the program running so that messages can come in. From 4e902133cd6a09b0089ec445730d5b93e7dd8cc6 Mon Sep 17 00:00:00 2001 From: Chong Wen Hao <58220142+8kdesign@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:49:30 +0800 Subject: [PATCH 43/43] Fix padding --- src/bundles/ar/AR.ts | 3 ++- .../ui_component/UIColumnComponent.tsx | 11 +++++++---- .../ui_component/UIImageComponent.tsx | 2 +- .../ui_component/UIRowComponent.tsx | 13 ++++++++----- .../ui_component/UITextComponent.tsx | 2 +- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/bundles/ar/AR.ts b/src/bundles/ar/AR.ts index 6f187d19a..c0cfff2c1 100644 --- a/src/bundles/ar/AR.ts +++ b/src/bundles/ar/AR.ts @@ -9,7 +9,8 @@ export class ARState { clickCallbacks = new Map void>(); } -// Fix issue with React Spring +// Fix issue with React Spring, but spams console log. +// https://github.com/pmndrs/react-spring/issues/1586#issuecomment-870778191 Globals.assign({ frameLoop: 'always', }); diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx index a1c4fe3e4..73628f517 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIColumnComponent.tsx @@ -93,14 +93,17 @@ function ColumnUIComponent(props: { let currentYPosition = -component.height / 2 + component.paddingTop; for (let i = 0; i < component.children.length; i++) { let child = component.children[i]; - let relativeYPosition = currentYPosition + child.height / 2; + let relativeYPosition = + currentYPosition + + child.height / 2 + + (child.paddingTop - child.paddingBottom) / 2; currentYPosition += child.height; - let relativeXPosition = 0; + let relativeXPosition = (child.paddingLeft - child.paddingRight) / 2; if (component.horizontalAlignment === HorizontalAlignment.Left) { - relativeXPosition = + relativeXPosition += -(component.width - child.width) / 2 + component.paddingLeft; } else if (component.horizontalAlignment === HorizontalAlignment.Right) { - relativeXPosition = + relativeXPosition += (component.width - child.width) / 2 - component.paddingRight; } let childPosition = new Vector3(relativeXPosition, -relativeYPosition, 0); diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx index cc4810fdf..29d9e4d68 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIImageComponent.tsx @@ -20,7 +20,7 @@ export default class UIImageComponent extends UIBasicComponent { this.imageWidth = props.imageWidth; this.imageHeight = props.imageHeight; } - getWidth = () => this.imageWidth + this.paddingTop + this.paddingBottom; + getWidth = () => this.imageWidth + this.paddingLeft + this.paddingRight; getHeight = () => this.imageHeight + this.paddingTop + this.paddingBottom; getComponent = (position: Vector3, _: () => void) => ( diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx index 111c3ee0d..014327bba 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UIRowComponent.tsx @@ -93,15 +93,18 @@ function RowUIComponent(props: { let currentXPosition = -component.width / 2 + component.paddingLeft; for (let i = 0; i < component.children.length; i++) { let child = component.children[i]; - let relativeXPosition = currentXPosition + child.width / 2; + let relativeXPosition = + currentXPosition + + child.width / 2 + + (child.paddingLeft - child.paddingRight) / 2; currentXPosition += child.width; - let relativeYPosition = 0; + let relativeYPosition = -(child.paddingTop - child.paddingBottom) / 2; if (component.verticalAlignment === VerticalAlignment.Top) { - relativeYPosition = + relativeYPosition += (component.height - child.height) / 2 - component.paddingTop; } else if (component.verticalAlignment === VerticalAlignment.Bottom) { - relativeYPosition = - -(component.height - child.height) / 2 + component.paddingBottom; + relativeYPosition += + -(component.height - child.height) / 2 - component.paddingBottom; } let childPosition = new Vector3(relativeXPosition, relativeYPosition, 0); positions.push(childPosition); diff --git a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx index 411083fa0..ff99ae2b4 100644 --- a/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx +++ b/src/bundles/ar/libraries/object_state_library/ui_component/UITextComponent.tsx @@ -29,7 +29,7 @@ export default class UITextComponent extends UIBasicComponent { this.textAlign = props.textAlign ?? HorizontalAlignment.Center; this.color = props.color ?? 0; } - getWidth = () => this.textWidth + this.paddingTop + this.paddingBottom; + getWidth = () => this.textWidth + this.paddingLeft + this.paddingRight; getHeight = () => this.textHeight + this.paddingTop + this.paddingBottom; updateHeight = (newTextHeight: number): boolean => { if (newTextHeight === 0) {