From 1fb2d079bc27383c9ad0c1fd0a815a80334219b4 Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 17 Aug 2023 09:01:35 +0000 Subject: [PATCH 01/93] Initial commit --- modules.json | 5 ++ src/bundles/robot_simulation/functions.ts | 3 + src/bundles/robot_simulation/index.ts | 1 + src/tabs/RobotSimulation/index.tsx | 70 +++++++++++++++++++++++ 4 files changed, 79 insertions(+) create mode 100644 src/bundles/robot_simulation/functions.ts create mode 100644 src/bundles/robot_simulation/index.ts create mode 100644 src/tabs/RobotSimulation/index.tsx diff --git a/modules.json b/modules.json index 7e3cc6942..ec4763136 100644 --- a/modules.json +++ b/modules.json @@ -98,5 +98,10 @@ "tabs": [ "physics_2d" ] + }, + "robot_simulation": { + "tabs": [ + "RobotSimulation" + ] } } \ No newline at end of file diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts new file mode 100644 index 000000000..1e8592cca --- /dev/null +++ b/src/bundles/robot_simulation/functions.ts @@ -0,0 +1,3 @@ +export function show() { + console.log('hi'); +} diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts new file mode 100644 index 000000000..9208a7b2f --- /dev/null +++ b/src/bundles/robot_simulation/index.ts @@ -0,0 +1 @@ +export { show } from './functions'; diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/index.tsx new file mode 100644 index 000000000..9e3a6ad1a --- /dev/null +++ b/src/tabs/RobotSimulation/index.tsx @@ -0,0 +1,70 @@ +import React from 'react'; + +/** + * Robot Simulation + * @author Joel Chan + */ + +/** + * 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', +}; From 5e462fb28effcfe801f4e71a3e8e78b415c7e7c0 Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 17 Aug 2023 09:02:44 +0000 Subject: [PATCH 02/93] Add some permissions thing --- .husky/pre-commit | 8 ++++---- .husky/pre-push | 0 2 files changed, 4 insertions(+), 4 deletions(-) mode change 100644 => 100755 .husky/pre-commit mode change 100644 => 100755 .husky/pre-push diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 index b08f47166..dc811ba70 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn build --tsc --lint +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn build --tsc --lint diff --git a/.husky/pre-push b/.husky/pre-push old mode 100644 new mode 100755 From fc7669f56a0e60a7f622b9c0b0d9ab3ea4a24443 Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 17 Aug 2023 10:25:08 +0000 Subject: [PATCH 03/93] Get the templating done for the modal up and down --- src/bundles/robot_simulation/functions.ts | 2 +- src/tabs/RobotSimulation/components/Main.tsx | 26 ++++++++ src/tabs/RobotSimulation/components/Modal.tsx | 62 +++++++++++++++++++ .../components/Simulation/index.tsx | 11 ++++ src/tabs/RobotSimulation/components/TabUi.tsx | 17 +++++ src/tabs/RobotSimulation/index.tsx | 32 ++-------- 6 files changed, 123 insertions(+), 27 deletions(-) create mode 100644 src/tabs/RobotSimulation/components/Main.tsx create mode 100644 src/tabs/RobotSimulation/components/Modal.tsx create mode 100644 src/tabs/RobotSimulation/components/Simulation/index.tsx create mode 100644 src/tabs/RobotSimulation/components/TabUi.tsx diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 1e8592cca..41427d51e 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -1,3 +1,3 @@ export function show() { - console.log('hi'); + console.log('bro'); } diff --git a/src/tabs/RobotSimulation/components/Main.tsx b/src/tabs/RobotSimulation/components/Main.tsx new file mode 100644 index 000000000..8b8e39a22 --- /dev/null +++ b/src/tabs/RobotSimulation/components/Main.tsx @@ -0,0 +1,26 @@ +import TabUi from './TabUi'; +import { Modal } from './Modal'; +import { useState } from 'react'; +import SimulationCanvas from './Simulation'; + +export default function Main(): JSX.Element { + const [isCanvasShowing, setIsCanvasShowing] = useState(false); + + return ( +
+ { + setIsCanvasShowing(true); + }} + /> + { + setIsCanvasShowing(false); + }} + > + + +
+ ); +} diff --git a/src/tabs/RobotSimulation/components/Modal.tsx b/src/tabs/RobotSimulation/components/Modal.tsx new file mode 100644 index 000000000..f4d12556a --- /dev/null +++ b/src/tabs/RobotSimulation/components/Modal.tsx @@ -0,0 +1,62 @@ +import { type CSSProperties, type ReactNode } from 'react'; + +type ModalProps = { + isOpen: boolean; + onClose: () => void; + children: ReactNode; +}; + +export const containerStyle: CSSProperties = { + width: '100vw', + height: '100vh', + position: 'fixed', + bottom: 0, + top: 0, + left: 0, + right: 0, + zIndex: 20, + isolation: 'isolate', +}; + +export const closeButtonStyle: CSSProperties = { + position: 'fixed', + top: '10px', + right: '10px', + cursor: 'pointer', + fontSize: 24, + color: 'white', +}; + +export const greyedOutBackground: CSSProperties = { + background: 'black', + opacity: '70%', + width: '100%', + height: '100%', + position: 'absolute', + zIndex: -1, +}; + +export const childWrapperStyle: CSSProperties = { + display: 'flex', + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', +}; + +export function Modal({ children, isOpen, onClose }: ModalProps) { + return ( +
+
+ + x + +
{children}
+
+ ); +} diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx new file mode 100644 index 000000000..457a84e11 --- /dev/null +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -0,0 +1,11 @@ +import { type CSSProperties } from 'react'; + +const CanvasWrapperStyle: CSSProperties = { + width: 800, + height: 600, + backgroundColor: 'white', +}; + +export default function SimulationCanvas() { + return
hi
; +} diff --git a/src/tabs/RobotSimulation/components/TabUi.tsx b/src/tabs/RobotSimulation/components/TabUi.tsx new file mode 100644 index 000000000..577dee9c1 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabUi.tsx @@ -0,0 +1,17 @@ +type TabUiProps = { + onOpenCanvas: () => void; +}; + +export default function TabUi({ onOpenCanvas }: TabUiProps) { + return ( +
+ +
+ ); +} diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/index.tsx index 9e3a6ad1a..654190d9d 100644 --- a/src/tabs/RobotSimulation/index.tsx +++ b/src/tabs/RobotSimulation/index.tsx @@ -1,4 +1,6 @@ import React from 'react'; +import { type DebuggerContext } from '../../typings/type_helpers'; +import Main from './components/Main'; /** * Robot Simulation @@ -14,31 +16,7 @@ type Props = { 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 { /** @@ -47,14 +25,16 @@ export default { * @param {DebuggerContext} context * @returns {boolean} */ - toSpawn: (context: any) => context.result.value === 'test', + toSpawn(context: any) { + return true; + }, /** * 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. From b2f6fb99e5a128d460e3545aee4808dc05593b49 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 18 Aug 2023 07:57:56 +0000 Subject: [PATCH 04/93] Add permisisons to all --- .husky/.gitignore | 0 .husky/post-checkout | 0 .husky/post-merge | 0 .husky/post-rewrite | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .husky/.gitignore mode change 100644 => 100755 .husky/post-checkout mode change 100644 => 100755 .husky/post-merge mode change 100644 => 100755 .husky/post-rewrite diff --git a/.husky/.gitignore b/.husky/.gitignore old mode 100644 new mode 100755 diff --git a/.husky/post-checkout b/.husky/post-checkout old mode 100644 new mode 100755 diff --git a/.husky/post-merge b/.husky/post-merge old mode 100644 new mode 100755 diff --git a/.husky/post-rewrite b/.husky/post-rewrite old mode 100644 new mode 100755 From 717b244eb4002c9aba8a5152bb841df45bf3da59 Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 19 Sep 2023 07:39:58 +0000 Subject: [PATCH 05/93] Add the first edition of rapier-three controller --- package.json | 3 + src/bundles/robot_simulation/functions.ts | 2 +- .../three-rapier-controller/init.ts | 139 ++++++++++++++++++ .../three-rapier-controller/options.ts | 10 ++ .../render/controllers/tickManager.ts | 110 ++++++++++++++ .../render/physics/RAPIER.ts | 7 + .../render/physics/physics.ts | 80 ++++++++++ .../components/Simulation/index.tsx | 42 +++++- yarn.lock | 40 +++++ 9 files changed, 429 insertions(+), 4 deletions(-) create mode 100644 src/bundles/robot_simulation/three-rapier-controller/init.ts create mode 100644 src/bundles/robot_simulation/three-rapier-controller/options.ts create mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts create mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/physics/RAPIER.ts create mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts diff --git a/package.json b/package.json index 7801c19a4..6da625c19 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@types/node": "^17.0.23", "@types/plotly.js-dist": "npm:@types/plotly.js", "@types/react": "^17.0.43", + "@types/three": "^0.156.0", "@typescript-eslint/eslint-plugin": "^5.47.1", "@typescript-eslint/parser": "^5.47.1", "acorn": "^8.8.1", @@ -87,6 +88,7 @@ "@blueprintjs/popover2": "^1.4.3", "@box2d/core": "^0.10.0", "@box2d/debug-draw": "^0.10.0", + "@dimforge/rapier3d-compat": "^0.11.2", "@jscad/modeling": "^2.9.5", "@jscad/regl-renderer": "^2.6.1", "@jscad/stl-serializer": "^2.1.13", @@ -107,6 +109,7 @@ "save-file": "^2.3.1", "source-academy-utils": "^1.0.0", "source-academy-wabt": "^1.0.4", + "three": "^0.156.1", "tslib": "^2.3.1" }, "jest": { diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 41427d51e..4d58594a0 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -1,3 +1,3 @@ export function show() { - console.log('bro'); + console.log('show function called'); } diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts new file mode 100644 index 000000000..b66bdf968 --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/init.ts @@ -0,0 +1,139 @@ +import * as THREE from 'three'; + +import type Rapier from '@dimforge/rapier3d-compat'; + +import initRapier from './render/physics/RAPIER'; +import { physicsOptions, sceneOptions } from './options'; +import { addPhysics, type PhysicsObject } from './render/physics/physics'; +import TickManager from './render/controllers/tickManager'; + + + +const getGroundMesh = () => { + // * Settings + const planeWidth = 100; + const planeHeight = 100; + + // * Mesh + const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight); + const material = new THREE.MeshPhysicalMaterial({ + color: '#333', + side: THREE.DoubleSide, + }); + const plane = new THREE.Mesh(geometry, material); + + // * Physics + const collider = addPhysics( + plane, + 'fixed', + true, + () => { + plane.rotation.x -= Math.PI / 2; + }, + 'cuboid', + { + width: planeWidth / 2, + height: 0.001, + depth: planeHeight / 2, + }, + ).collider; + + return plane; +}; + +const getCubeMesh = (pos: THREE.Vector3) => { + // * Settings + const size = 1; + + // * Mesh + const geometry = new THREE.BoxGeometry(size, size, size); + const material = new THREE.MeshPhysicalMaterial({ + color: new THREE.Color() + .setHex(Math.min(Math.random() + 0.15, 1) * 0xffffff), + side: THREE.DoubleSide, + }); + const cube = new THREE.Mesh(geometry, material); + + cube.position.copy(pos); + cube.position.y += 2; + + // * Physics + const cubePhysics = addPhysics(cube, 'dynamic', true, () => { + cube.position.add(new THREE.Vector3(0, -10, 0)); + }, 'cuboid', { + width: size / 2, + height: size / 2, + depth: size / 2, + }); + + const rigidBody = cubePhysics.rigidBody; + + rigidBody.addForce(new RAPIER.Vector3(0, 12, 0), true); + + return cube; +}; + +let scene: THREE.Scene; +let camera: THREE.PerspectiveCamera; +let renderer: THREE.WebGLRenderer; +let renderWidth: number; +let renderHeight: number; +let renderAspectRatio: number; +let RAPIER: typeof Rapier; +let physicsWorld: Rapier.World; +let physicsObjects: Array; + +const renderTickManager = new TickManager(); + +export const initEngines = async () => { + RAPIER = await initRapier(); + physicsWorld = new RAPIER.World(physicsOptions.GRAVITY); + + physicsObjects = []; + + scene = new THREE.Scene(); + scene.background = new THREE.Color(0xffffff); + + renderWidth = sceneOptions.width; + renderHeight = sceneOptions.height; + + renderAspectRatio = renderWidth / renderHeight; + + camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); + camera.position.z = 7; + + renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setSize(renderWidth, renderHeight); + renderer.setPixelRatio(window.devicePixelRatio * 1.5); + + // Testing + const light = new THREE.PointLight(0xff0000, 1, 100); + light.position.set(0, 0, 0); + scene.add(light); + + const cube = getCubeMesh(new THREE.Vector3(0, 0, 0)); + scene.add(cube); + + // const ground = getGroundMesh(); + // scene.add(ground); + + + renderTickManager.startLoop(); +}; + +export const getRenderer = () => renderer; + +export const getRenderSize = () => ({ + width: renderWidth, + height: renderHeight, +}); + +export const getCanvasDom = () => renderer.domElement; + +export const getScene = () => scene; +export const getCamera = () => camera; +export const getPhysicsWorld = () => physicsWorld; +export const getPhysicsObjects = () => physicsObjects; + + +export { RAPIER }; diff --git a/src/bundles/robot_simulation/three-rapier-controller/options.ts b/src/bundles/robot_simulation/three-rapier-controller/options.ts new file mode 100644 index 000000000..07242b664 --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/options.ts @@ -0,0 +1,10 @@ +import { Vector3 } from 'three'; + +export const physicsOptions = { + GRAVITY: new Vector3(0.0, -9.81, 0.0), +} as const; + +export const sceneOptions = { + height: 600, + width: 800, +} as const; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts new file mode 100644 index 000000000..602f48450 --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts @@ -0,0 +1,110 @@ +import { BufferAttribute, LineSegments } from 'three'; +import { + getPhysicsWorld, + getPhysicsObjects, + getRenderer, + getScene, + getCamera, +} from '../../init'; + +// animation params +type Frame = XRFrame | null; + +export type TickData = { + timestamp: number + timeDiff: number + fps: number + frame: Frame +}; + +const localTickData: TickData = { + timestamp: 0, + timeDiff: 0, + fps: 0, + frame: null, +}; + +const localFrameOpts = { + data: localTickData, +}; + +const frameEvent = new MessageEvent('tick', localFrameOpts); + +class TickManager extends EventTarget { + timestamp: number; + timeDiff: number; + frame: Frame; + lastTimestamp: number; + fps: number; + + constructor({ timestamp, timeDiff, frame } = localTickData) { + super(); + + this.timestamp = timestamp; + this.timeDiff = timeDiff; + this.frame = frame; + this.lastTimestamp = 0; + this.fps = 0; + } + + startLoop() { + const renderer = getRenderer(); + const physics = getPhysicsWorld(); + const physicsObjects = getPhysicsObjects(); + const scene = getScene(); + const camera = getCamera(); + + + + if (!renderer) { + throw new Error('Updating Frame Failed : Uninitialized Renderer'); + } + + const animate = (timestamp: number, frame: Frame) => { + const now = performance.now(); + this.timestamp = timestamp ?? now; + this.timeDiff = timestamp - this.lastTimestamp; + + const timeDiffCapped = Math.min(Math.max(this.timeDiff, 0), 100); + + // physics + physics.step(); + + for (let i = 0; i < physicsObjects.length; i++) { + const po = physicsObjects[i]; + const autoAnimate = po.autoAnimate; + + if (autoAnimate) { + const mesh = po.mesh; + const collider = po.collider; + mesh.position.copy(collider.translation() as THREE.Vector3); + mesh.quaternion.copy(collider.rotation() as THREE.Quaternion); + console.log(collider.translation()); + } + + const fn = po.fn; + if (fn) { + fn(); + } + } + + // performance tracker start + this.fps = 1000 / this.timeDiff; + this.lastTimestamp = this.timestamp; + renderer.render(scene, camera); + this.tick(timestamp, timeDiffCapped, this.fps, frame); + }; + + renderer.setAnimationLoop(animate); + } + + tick(timestamp: number, timeDiff: number, fps: number, frame: Frame) { + localTickData.timestamp = timestamp; + localTickData.frame = frame; + localTickData.timeDiff = timeDiff; + localTickData.fps = fps; + this.dispatchEvent(frameEvent); + } +} + +export default TickManager; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/RAPIER.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/RAPIER.ts new file mode 100644 index 000000000..bb3b56c64 --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/render/physics/RAPIER.ts @@ -0,0 +1,7 @@ +const initRapier = async () => { + let r = await import('@dimforge/rapier3d-compat'); + await r.init(); + return r; +}; + +export default initRapier; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts new file mode 100644 index 000000000..e5dc33bb5 --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts @@ -0,0 +1,80 @@ +import type Rapier from '@dimforge/rapier3d-compat'; + +import { RAPIER, getPhysicsWorld, getPhysicsObjects } from '../../init'; + +export type PhysicsObject = { + mesh: THREE.Mesh + collider: Rapier.Collider + rigidBody: Rapier.RigidBody + fn?: Function + autoAnimate: boolean +}; + +export const addPhysics = ( + mesh: THREE.Mesh, + rigidBodyType: string, + autoAnimate: boolean = true, // update the mesh's position and quaternion based on the physics world every frame + postPhysicsFn?: Function, + colliderType?: string, + colliderSettings?: any, +) => { + const physics = getPhysicsWorld(); + const physicsObjects = getPhysicsObjects(); + + const rigidBodyDesc = (RAPIER.RigidBodyDesc as any)[rigidBodyType](); + rigidBodyDesc.setTranslation(mesh.position.x, mesh.position.y, mesh.position.z); + + // * Responsible for collision response + const rigidBody = physics.createRigidBody(rigidBodyDesc); + + let colliderDesc; + + switch (colliderType) { + case 'cuboid': + { + const { width, height, depth } = colliderSettings; + colliderDesc = RAPIER.ColliderDesc.cuboid(width, height, depth); + } + break; + + case 'ball': + { + const { radius } = colliderSettings; + colliderDesc = RAPIER.ColliderDesc.ball(radius); + } + break; + + case 'capsule': + { + const { halfHeight, radius } = colliderSettings; + colliderDesc = RAPIER.ColliderDesc.capsule(halfHeight, radius); + } + break; + + default: + colliderDesc = RAPIER.ColliderDesc.trimesh( + mesh.geometry.attributes.position.array as Float32Array, + mesh.geometry.index?.array as Uint32Array, + ); + break; + } + + if (!colliderDesc) { + console.error('Collider Mesh Error: convex mesh creation failed.'); + } + + // * Responsible for collision detection + const collider = physics.createCollider(colliderDesc, rigidBody); + + const physicsObject: PhysicsObject = { + mesh, + collider, + rigidBody, + fn: postPhysicsFn, + autoAnimate, + }; + + physicsObjects.push(physicsObject); + + return physicsObject; +}; diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 457a84e11..c873c5553 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -1,11 +1,47 @@ -import { type CSSProperties } from 'react'; +import { useRef, type CSSProperties, useEffect, useState } from 'react'; +import { getRenderer, initEngines } from '../../../../bundles/robot_simulation/three-rapier-controller/init'; const CanvasWrapperStyle: CSSProperties = { width: 800, height: 600, - backgroundColor: 'white', + backgroundColor: 'black', }; +const simulationCanvasStates = ['idle', 'loading', 'ready', 'error'] as const; + +type SimulationCanvasStates = typeof simulationCanvasStates[number]; + export default function SimulationCanvas() { - return
hi
; + const ref = useRef(null); + const [currentState, setCurrentState] = useState('idle'); + + useEffect(() => { + const startThreeAndRapierEngines = async () => { + setCurrentState('loading'); + initEngines() + .then(() => { + setCurrentState('ready'); + }) + .catch(() => { + setCurrentState('error'); + }); + }; + + const attachRenderDom = () => { + const renderer = getRenderer(); + if (ref.current && renderer) { + ref.current.replaceChildren(renderer.domElement); + } + }; + + if (currentState === 'idle') { + startThreeAndRapierEngines(); + } + + if (currentState === 'ready') { + attachRenderDom(); + } + }, [currentState]); + + return
{currentState}
; } diff --git a/yarn.lock b/yarn.lock index f502cd8fb..406b6731f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -366,6 +366,11 @@ dependencies: "@box2d/core" "^0.10.0" +"@dimforge/rapier3d-compat@^0.11.2": + version "0.11.2" + resolved "https://registry.yarnpkg.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.11.2.tgz#ae2b335f545decf1e82ff45bb10368e143de0fcb" + integrity sha512-vdWmlkpS3G8nGAzLuK7GYTpNdrkn/0NKCe0l1Jqxc7ZZOB3N0q9uG/Ap9l9bothWuAvxscIt0U97GVLr0lXWLg== + "@esbuild/android-arm64@0.17.19": version "0.17.19" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd" @@ -1066,11 +1071,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.0" + resolved "https://registry.yarnpkg.com/@types/stats.js/-/stats.js-0.17.0.tgz#0ed81d48e03b590c24da85540c1d952077a9fe20" + integrity sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w== + +"@types/three@^0.156.0": + version "0.156.0" + resolved "https://registry.yarnpkg.com/@types/three/-/three-0.156.0.tgz#cd49f2a12e858400962ea818d1e1c45e638141a8" + integrity sha512-733bXDSRdlrxqOmQuOmfC1UBRuJ2pREPk8sWnx9MtIJEVDQMx8U0NQO5MVVaOrjzDPyLI+cFPim2X/ss9v0+LQ== + 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@*": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.4.tgz#3d55a6427f9281d456843d754c99bf7804657fe3" + integrity sha512-41gfGLTtqXZhcmoDlLDHqMJDuwAMwhHwXf9Q2job3TUBsvkNfPNI/3IWVEtLH4tyY1ElWtfwIaoNeqeEX238/Q== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -2699,6 +2724,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +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" @@ -4288,6 +4318,11 @@ 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== +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" @@ -5693,6 +5728,11 @@ 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@^0.156.1: + version "0.156.1" + resolved "https://registry.yarnpkg.com/three/-/three-0.156.1.tgz#bab4fec121a5b3975eb4f4d227d9c912171eb399" + integrity sha512-kP7H0FK9d/k6t/XvQ9FO6i+QrePoDcNhwl0I02+wmUJRNSLCUIDMcfObnzQvxb37/0Uc9TDT0T1HgsRRrO6SYQ== + through2@^0.6.3: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" From dc92ae51db5d3c9b525f36c89477d2d5caa797d6 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 29 Sep 2023 12:22:16 +0000 Subject: [PATCH 06/93] Initialization of my module --- src/bundles/robot_simulation/functions.ts | 25 +++ src/bundles/robot_simulation/index.ts | 2 +- .../constants/states.ts | 3 + .../three-rapier-controller/init.ts | 154 +++++------------- .../render/controllers/tickManager.ts | 25 ++- .../render/physics/physics.ts | 12 +- src/tabs/RobotSimulation/components/Main.tsx | 5 +- .../components/Simulation/index.tsx | 35 ++-- src/tabs/RobotSimulation/index.tsx | 6 +- 9 files changed, 115 insertions(+), 152 deletions(-) create mode 100644 src/bundles/robot_simulation/three-rapier-controller/constants/states.ts diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 4d58594a0..3c4890114 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -1,3 +1,28 @@ +/** + * Robot simulation + * @module robot_simulation + */ + +import context from 'js-slang/context'; +import { type RobotSimulation, initEngines } from './three-rapier-controller/init'; + +const initial_simulation: RobotSimulation = { state: 'idle' }; +const contextState = context.moduleContexts.robot_simulation.state; +if (contextState === null) { + context.moduleContexts.robot_simulation.state = { simulation: initial_simulation }; +} + +export const getSimulation = ():RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; +export const setSimulation = (newSimulation:RobotSimulation):void => { + context.moduleContexts.robot_simulation.state.simulation = newSimulation; + console.log('Setting new value into simulation', newSimulation); +}; + + export function show() { console.log('show function called'); } + +export function init_simulation() { + initEngines(); +} diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 9208a7b2f..13eb1d9bb 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1 +1 @@ -export { show } from './functions'; +export { show, init_simulation } from './functions'; diff --git a/src/bundles/robot_simulation/three-rapier-controller/constants/states.ts b/src/bundles/robot_simulation/three-rapier-controller/constants/states.ts new file mode 100644 index 000000000..1cbde3c0b --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/constants/states.ts @@ -0,0 +1,3 @@ +export const simulationStates = ['idle', 'loading', 'ready', 'error'] as const; + +export type SimulationStates = typeof simulationStates[number]; diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts index b66bdf968..aea714e26 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/init.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/init.ts @@ -1,139 +1,69 @@ import * as THREE from 'three'; import type Rapier from '@dimforge/rapier3d-compat'; - import initRapier from './render/physics/RAPIER'; import { physicsOptions, sceneOptions } from './options'; -import { addPhysics, type PhysicsObject } from './render/physics/physics'; +import { type PhysicsObject } from './render/physics/physics'; +import { type SimulationStates } from './constants/states'; import TickManager from './render/controllers/tickManager'; +import { setSimulation } from '../functions'; - - -const getGroundMesh = () => { - // * Settings - const planeWidth = 100; - const planeHeight = 100; - - // * Mesh - const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight); - const material = new THREE.MeshPhysicalMaterial({ - color: '#333', - side: THREE.DoubleSide, - }); - const plane = new THREE.Mesh(geometry, material); - - // * Physics - const collider = addPhysics( - plane, - 'fixed', - true, - () => { - plane.rotation.x -= Math.PI / 2; - }, - 'cuboid', - { - width: planeWidth / 2, - height: 0.001, - depth: planeHeight / 2, - }, - ).collider; - - return plane; -}; - -const getCubeMesh = (pos: THREE.Vector3) => { - // * Settings - const size = 1; - - // * Mesh - const geometry = new THREE.BoxGeometry(size, size, size); - const material = new THREE.MeshPhysicalMaterial({ - color: new THREE.Color() - .setHex(Math.min(Math.random() + 0.15, 1) * 0xffffff), - side: THREE.DoubleSide, - }); - const cube = new THREE.Mesh(geometry, material); - - cube.position.copy(pos); - cube.position.y += 2; - - // * Physics - const cubePhysics = addPhysics(cube, 'dynamic', true, () => { - cube.position.add(new THREE.Vector3(0, -10, 0)); - }, 'cuboid', { - width: size / 2, - height: size / 2, - depth: size / 2, - }); - - const rigidBody = cubePhysics.rigidBody; - - rigidBody.addForce(new RAPIER.Vector3(0, 12, 0), true); - - return cube; -}; - -let scene: THREE.Scene; -let camera: THREE.PerspectiveCamera; -let renderer: THREE.WebGLRenderer; -let renderWidth: number; -let renderHeight: number; -let renderAspectRatio: number; let RAPIER: typeof Rapier; -let physicsWorld: Rapier.World; -let physicsObjects: Array; -const renderTickManager = new TickManager(); +export type RobotSimulation = { + state: Extract; +} | { + state: 'ready' + scene: THREE.Scene, + camera: THREE.PerspectiveCamera, + renderer: THREE.WebGLRenderer, + renderWidth: number, + renderHeight: number, + renderAspectRatio: number, + RAPIER: typeof Rapier, + physicsWorld: Rapier.World, + physicsObjects: Array, +}; export const initEngines = async () => { + setSimulation({ state: 'loading' }); RAPIER = await initRapier(); - physicsWorld = new RAPIER.World(physicsOptions.GRAVITY); - physicsObjects = []; + const renderTickManager = new TickManager(); + const physicsWorld = new RAPIER.World(physicsOptions.GRAVITY); - scene = new THREE.Scene(); - scene.background = new THREE.Color(0xffffff); + const physicsObjects:Array = []; - renderWidth = sceneOptions.width; - renderHeight = sceneOptions.height; + const scene = new THREE.Scene(); + scene.background = new THREE.Color(0xffffff); - renderAspectRatio = renderWidth / renderHeight; + const renderWidth = sceneOptions.width; + const renderHeight = sceneOptions.height; + const renderAspectRatio = renderWidth / renderHeight; - camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); + const camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); camera.position.z = 7; - renderer = new THREE.WebGLRenderer({ antialias: true }); + const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(renderWidth, renderHeight); renderer.setPixelRatio(window.devicePixelRatio * 1.5); - // Testing - const light = new THREE.PointLight(0xff0000, 1, 100); - light.position.set(0, 0, 0); - scene.add(light); - - const cube = getCubeMesh(new THREE.Vector3(0, 0, 0)); - scene.add(cube); - - // const ground = getGroundMesh(); - // scene.add(ground); - + const robotSimulation :RobotSimulation = { + state: 'ready', + scene, + camera, + renderer, + renderWidth, + renderHeight, + renderAspectRatio, + RAPIER, + physicsWorld, + physicsObjects, + }; + + setSimulation(robotSimulation); renderTickManager.startLoop(); }; -export const getRenderer = () => renderer; - -export const getRenderSize = () => ({ - width: renderWidth, - height: renderHeight, -}); - -export const getCanvasDom = () => renderer.domElement; - -export const getScene = () => scene; -export const getCamera = () => camera; -export const getPhysicsWorld = () => physicsWorld; -export const getPhysicsObjects = () => physicsObjects; - - export { RAPIER }; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts index 602f48450..03069d683 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts @@ -1,11 +1,4 @@ -import { BufferAttribute, LineSegments } from 'three'; -import { - getPhysicsWorld, - getPhysicsObjects, - getRenderer, - getScene, - getCamera, -} from '../../init'; +import { getSimulation } from '../../../functions'; // animation params type Frame = XRFrame | null; @@ -48,12 +41,15 @@ class TickManager extends EventTarget { } startLoop() { - const renderer = getRenderer(); - const physics = getPhysicsWorld(); - const physicsObjects = getPhysicsObjects(); - const scene = getScene(); - const camera = getCamera(); - + const simulation = getSimulation(); + if (simulation.state !== 'ready') { + throw new Error('Tried to start a loop before starting simulation'); + } + const renderer = simulation.renderer; + const physics = simulation.physicsWorld; + const physicsObjects = simulation.physicsObjects; + const scene = simulation.scene; + const camera = simulation.camera; if (!renderer) { @@ -79,7 +75,6 @@ class TickManager extends EventTarget { const collider = po.collider; mesh.position.copy(collider.translation() as THREE.Vector3); mesh.quaternion.copy(collider.rotation() as THREE.Quaternion); - console.log(collider.translation()); } const fn = po.fn; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts index e5dc33bb5..c3cbe4370 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts @@ -1,6 +1,7 @@ import type Rapier from '@dimforge/rapier3d-compat'; -import { RAPIER, getPhysicsWorld, getPhysicsObjects } from '../../init'; +import { RAPIER } from '../../init'; +import { getSimulation } from '../../../functions'; export type PhysicsObject = { mesh: THREE.Mesh @@ -18,8 +19,13 @@ export const addPhysics = ( colliderType?: string, colliderSettings?: any, ) => { - const physics = getPhysicsWorld(); - const physicsObjects = getPhysicsObjects(); + const simulation = getSimulation(); + if (simulation.state !== 'ready') { + throw new Error('Tried to add a physic object before initializing the simulation.'); + } + + const physics = simulation.physicsWorld; + const physicsObjects = simulation.physicsObjects; const rigidBodyDesc = (RAPIER.RigidBodyDesc as any)[rigidBodyType](); rigidBodyDesc.setTranslation(mesh.position.x, mesh.position.y, mesh.position.z); diff --git a/src/tabs/RobotSimulation/components/Main.tsx b/src/tabs/RobotSimulation/components/Main.tsx index 8b8e39a22..584b0f622 100644 --- a/src/tabs/RobotSimulation/components/Main.tsx +++ b/src/tabs/RobotSimulation/components/Main.tsx @@ -2,8 +2,9 @@ import TabUi from './TabUi'; import { Modal } from './Modal'; import { useState } from 'react'; import SimulationCanvas from './Simulation'; +import { type DebuggerContext } from '../../../typings/type_helpers'; -export default function Main(): JSX.Element { +export default function Main({ context }: { context:DebuggerContext }): JSX.Element { const [isCanvasShowing, setIsCanvasShowing] = useState(false); return ( @@ -19,7 +20,7 @@ export default function Main(): JSX.Element { setIsCanvasShowing(false); }} > - + ); diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index c873c5553..4006e29e7 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -1,5 +1,6 @@ import { useRef, type CSSProperties, useEffect, useState } from 'react'; -import { getRenderer, initEngines } from '../../../../bundles/robot_simulation/three-rapier-controller/init'; +import { type SimulationStates } from '../../../../bundles/robot_simulation/three-rapier-controller/constants/states'; +import type { DebuggerContext } from '../../../../typings/type_helpers'; const CanvasWrapperStyle: CSSProperties = { width: 800, @@ -7,28 +8,23 @@ const CanvasWrapperStyle: CSSProperties = { backgroundColor: 'black', }; -const simulationCanvasStates = ['idle', 'loading', 'ready', 'error'] as const; -type SimulationCanvasStates = typeof simulationCanvasStates[number]; - -export default function SimulationCanvas() { +export default function SimulationCanvas({ context }: { context:DebuggerContext }) { const ref = useRef(null); - const [currentState, setCurrentState] = useState('idle'); + const [currentState, setCurrentState] = useState('idle'); + const simulation = context.context.moduleContexts.robot_simulation.state.simulation; useEffect(() => { const startThreeAndRapierEngines = async () => { - setCurrentState('loading'); - initEngines() - .then(() => { - setCurrentState('ready'); - }) - .catch(() => { - setCurrentState('error'); - }); + console.log(simulation.state, currentState); + setCurrentState(simulation.state); }; const attachRenderDom = () => { - const renderer = getRenderer(); + if (simulation.state !== 'ready') { + throw new Error('Tried to attach dom to an unavailable simulation'); + } + const renderer = simulation.renderer; if (ref.current && renderer) { ref.current.replaceChildren(renderer.domElement); } @@ -41,7 +37,14 @@ export default function SimulationCanvas() { if (currentState === 'ready') { attachRenderDom(); } + if (currentState === 'loading') { + setTimeout(() => { + setCurrentState('idle'); + }, 500); + } }, [currentState]); - return
{currentState}
; + return <> +
{currentState}
+ ; } diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/index.tsx index 654190d9d..9bca5856d 100644 --- a/src/tabs/RobotSimulation/index.tsx +++ b/src/tabs/RobotSimulation/index.tsx @@ -25,8 +25,8 @@ export default { * @param {DebuggerContext} context * @returns {boolean} */ - toSpawn(context: any) { - return true; + toSpawn(context: DebuggerContext) { + return context.context.moduleContexts.robot_simulation.state.simulation?.state !== 'idle'; }, /** @@ -34,7 +34,7 @@ export default { * on Source Academy frontend. * @param {DebuggerContext} context */ - body: (context: any) =>
, + body: (context: DebuggerContext) =>
, /** * The Tab's icon tooltip in the side contents on Source Academy frontend. From a650095e2c5a664d955dde285487a565f0d70642 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 29 Sep 2023 13:16:41 +0000 Subject: [PATCH 07/93] Make a cube --- src/bundles/robot_simulation/functions.ts | 16 +---- .../three-rapier-controller/init.ts | 21 +++++- .../mesh/init_meshes.ts | 69 +++++++++++++++++++ .../render/controllers/tickManager.ts | 3 +- .../render/physics/physics.ts | 3 +- 5 files changed, 92 insertions(+), 20 deletions(-) create mode 100644 src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 3c4890114..44b917857 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -3,21 +3,7 @@ * @module robot_simulation */ -import context from 'js-slang/context'; -import { type RobotSimulation, initEngines } from './three-rapier-controller/init'; - -const initial_simulation: RobotSimulation = { state: 'idle' }; -const contextState = context.moduleContexts.robot_simulation.state; -if (contextState === null) { - context.moduleContexts.robot_simulation.state = { simulation: initial_simulation }; -} - -export const getSimulation = ():RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; -export const setSimulation = (newSimulation:RobotSimulation):void => { - context.moduleContexts.robot_simulation.state.simulation = newSimulation; - console.log('Setting new value into simulation', newSimulation); -}; - +import { initEngines } from './three-rapier-controller/init'; export function show() { console.log('show function called'); diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts index aea714e26..9587446fb 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/init.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/init.ts @@ -1,12 +1,16 @@ import * as THREE from 'three'; +// @ts-ignore-next-line import type Rapier from '@dimforge/rapier3d-compat'; +import context from 'js-slang/context'; + import initRapier from './render/physics/RAPIER'; import { physicsOptions, sceneOptions } from './options'; import { type PhysicsObject } from './render/physics/physics'; import { type SimulationStates } from './constants/states'; import TickManager from './render/controllers/tickManager'; -import { setSimulation } from '../functions'; +import { init_meshes } from './mesh/init_meshes'; + let RAPIER: typeof Rapier; @@ -25,6 +29,18 @@ export type RobotSimulation = { physicsObjects: Array, }; +const initial_simulation: RobotSimulation = { state: 'idle' }; +const contextState = context.moduleContexts.robot_simulation.state; +if (contextState === null) { + context.moduleContexts.robot_simulation.state = { simulation: initial_simulation }; +} + +export const getSimulation = ():RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; +export const setSimulation = (newSimulation:RobotSimulation):void => { + context.moduleContexts.robot_simulation.state.simulation = newSimulation; + console.log('Setting new value into simulation', newSimulation); +}; + export const initEngines = async () => { setSimulation({ state: 'loading' }); RAPIER = await initRapier(); @@ -42,7 +58,6 @@ export const initEngines = async () => { const renderAspectRatio = renderWidth / renderHeight; const camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); - camera.position.z = 7; const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(renderWidth, renderHeight); @@ -64,6 +79,8 @@ export const initEngines = async () => { setSimulation(robotSimulation); renderTickManager.startLoop(); + + init_meshes(); }; export { RAPIER }; diff --git a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts new file mode 100644 index 000000000..69cd52b8e --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts @@ -0,0 +1,69 @@ +import * as THREE from 'three'; +import { addPhysics } from '../render/physics/physics'; +import { getSimulation } from '../init'; + +const getCubeMesh = (pos: THREE.Vector3) => { + // * Settings + const size = 1; + + // * Mesh + const geometry = new THREE.BoxGeometry(size, size, size); + const material = new THREE.MeshPhysicalMaterial({ + color: new THREE.Color(0x000000), + side: THREE.DoubleSide, + }); + const cube = new THREE.Mesh(geometry, material); + + cube.position.copy(pos); + cube.position.z -= 7; + + // * Physics + addPhysics(cube, 'dynamic', true, undefined, 'cuboid', { + width: size / 2, + height: size / 2, + depth: size / 2, + }); + + + return cube; +}; + + +const getFloor = () => { + const size = 100; + const height = 1; + + const geometry = new THREE.BoxGeometry(size, height, size); + const material = new THREE.MeshPhysicalMaterial({ + color: new THREE.Color(0xFFC0CB), + side: THREE.DoubleSide, + }); + const cube = new THREE.Mesh(geometry, material); + cube.position.set(0, -2, 0); + + addPhysics(cube, 'fixed', true, undefined, 'cuboid', { + width: size, + height, + depth: size, + }); + + return cube; +}; + +export const init_meshes = () => { + const simulation = getSimulation(); + if (simulation.state !== 'ready') { + throw new Error('Cannot initialized mesh before simulation is ready'); + } + + const scene = simulation.scene; + + const light = new THREE.AmbientLight(0xffffff); + scene.add(light); + + const cube = getCubeMesh(new THREE.Vector3(0, 0, 0)); + scene.add(cube); + + const floor = getFloor(); + scene.add(floor); +}; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts index 03069d683..64d228ea4 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts @@ -1,4 +1,4 @@ -import { getSimulation } from '../../../functions'; +import { getSimulation } from '../../init'; // animation params type Frame = XRFrame | null; @@ -75,6 +75,7 @@ class TickManager extends EventTarget { const collider = po.collider; mesh.position.copy(collider.translation() as THREE.Vector3); mesh.quaternion.copy(collider.rotation() as THREE.Quaternion); + console.log(mesh.position); } const fn = po.fn; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts index c3cbe4370..0ed2a0e8d 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts @@ -1,7 +1,6 @@ import type Rapier from '@dimforge/rapier3d-compat'; -import { RAPIER } from '../../init'; -import { getSimulation } from '../../../functions'; +import { RAPIER, getSimulation } from '../../init'; export type PhysicsObject = { mesh: THREE.Mesh From e22aa255623aaab147b9e1d92af77ec32fbe0b7e Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 29 Sep 2023 14:58:15 +0000 Subject: [PATCH 08/93] Add ambient light --- .../robot_simulation/three-rapier-controller/init.ts | 6 ++++-- .../three-rapier-controller/mesh/init_meshes.ts | 3 --- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts index 9587446fb..c44b933ed 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/init.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/init.ts @@ -15,9 +15,9 @@ import { init_meshes } from './mesh/init_meshes'; let RAPIER: typeof Rapier; export type RobotSimulation = { - state: Extract; + state: Extract; } | { - state: 'ready' + state: Exclude, scene: THREE.Scene, camera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer, @@ -52,6 +52,8 @@ export const initEngines = async () => { const scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff); + const light = new THREE.AmbientLight(0xffffff); + scene.add(light); const renderWidth = sceneOptions.width; const renderHeight = sceneOptions.height; diff --git a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts index 69cd52b8e..1513e510b 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts @@ -58,9 +58,6 @@ export const init_meshes = () => { const scene = simulation.scene; - const light = new THREE.AmbientLight(0xffffff); - scene.add(light); - const cube = getCubeMesh(new THREE.Vector3(0, 0, 0)); scene.add(cube); From 03aeaff0557fe27ccc41fc53eb0880146e31671c Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 30 Sep 2023 04:18:22 +0000 Subject: [PATCH 09/93] Added the suspension forced --- .../mesh/init_meshes.ts | 39 +--- .../RayCastedVehicleController/carTuning.ts | 36 ++++ .../RayCastedVehicleController/index.ts | 191 ++++++++++++++++++ .../render/controllers/tickManager.ts | 4 +- .../render/physics/helpers.ts | 5 + .../render/physics/physics.ts | 34 +++- 6 files changed, 265 insertions(+), 44 deletions(-) create mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts create mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts create mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/physics/helpers.ts diff --git a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts index 1513e510b..6e7c1793a 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts @@ -1,35 +1,10 @@ import * as THREE from 'three'; import { addPhysics } from '../render/physics/physics'; import { getSimulation } from '../init'; +import { RayCastedVehicleController } from '../render/controllers/RayCastedVehicleController'; +import { carSettings } from '../render/controllers/RayCastedVehicleController/carTuning'; -const getCubeMesh = (pos: THREE.Vector3) => { - // * Settings - const size = 1; - - // * Mesh - const geometry = new THREE.BoxGeometry(size, size, size); - const material = new THREE.MeshPhysicalMaterial({ - color: new THREE.Color(0x000000), - side: THREE.DoubleSide, - }); - const cube = new THREE.Mesh(geometry, material); - - cube.position.copy(pos); - cube.position.z -= 7; - - // * Physics - addPhysics(cube, 'dynamic', true, undefined, 'cuboid', { - width: size / 2, - height: size / 2, - depth: size / 2, - }); - - - return cube; -}; - - -const getFloor = () => { +const addFloor = () => { const size = 100; const height = 1; @@ -56,11 +31,7 @@ export const init_meshes = () => { throw new Error('Cannot initialized mesh before simulation is ready'); } - const scene = simulation.scene; - - const cube = getCubeMesh(new THREE.Vector3(0, 0, 0)); - scene.add(cube); + const vehicleController = new RayCastedVehicleController(carSettings); - const floor = getFloor(); - scene.add(floor); + addFloor(); }; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts new file mode 100644 index 000000000..1fb0fa124 --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts @@ -0,0 +1,36 @@ +export type CarSettings = { + chassis: { + length: number; + height: number; + width: number; + }; + wheel: { + diameter: number; + maxSuspensionLength: number; + suspension: { stiffness: number; damping: number }; + buffer: number; + sideForceMultiplier: number; + }; + turning: { sensitivity: number }; +}; + +export const carSettings: CarSettings = { + chassis: { + length: 1, + height: 0.5, + width: 0.7, + }, + wheel: { + diameter: 1.5, + maxSuspensionLength: 2.5, + suspension: { + stiffness: 20, + damping: 2, + }, + buffer: 0.02, + sideForceMultiplier: -20, + }, + turning: { + sensitivity: 0.5, + }, +} as const; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts new file mode 100644 index 000000000..992be2764 --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts @@ -0,0 +1,191 @@ +import * as THREE from 'three'; + +import { + addPhysics, + type PhysicsObject, + type UpdateFunction, +} from '../../physics/physics'; +import { carSettings, type CarSettings } from './carTuning'; +import { quat, vec3 } from '../../physics/helpers'; +import { getSimulation, RAPIER } from '../../../init'; + +const getWheelDisplacements = (settings: CarSettings) => { + const rayDisplacements = [ + { + x: settings.chassis.width / 2 + settings.wheel.buffer, + y: 0, + z: settings.chassis.length / 2, + }, + { + x: -(settings.chassis.width / 2 + settings.wheel.buffer), + y: 0, + z: settings.chassis.length / 2, + }, + { + x: settings.chassis.width / 2 + settings.wheel.buffer, + y: 0, + z: -settings.chassis.length / 2, + }, + { + x: -(settings.chassis.width / 2 + settings.wheel.buffer), + y: 0, + z: -settings.chassis.length / 2, + }, + ]; + return rayDisplacements; +}; + +const createUpdateFunction = (settings: CarSettings): UpdateFunction => { + const simulation = getSimulation(); + if (simulation.state !== 'ready') { + throw new Error('Simulation not ready'); + } + + const world = simulation.physicsWorld; + + + const wheelRelativePositionFromOrigin = getWheelDisplacements(settings); + + const globalOrigin = { + x: 0, + y: 0, + z: 0, + }; + const downDirection = { + x: 0, + y: -1, + z: 0, + }; + + const ray1 = new RAPIER.Ray(globalOrigin, downDirection); + const ray2 = new RAPIER.Ray(globalOrigin, downDirection); + const ray3 = new RAPIER.Ray(globalOrigin, downDirection); + const ray4 = new RAPIER.Ray(globalOrigin, downDirection); + + const suspensionRays = [ray1, ray2, ray3, ray4]; + + + const updateFunction: UpdateFunction = ({ + mesh, + collider, + rigidBody: chassis, + }) => { + chassis.resetForces(true); + chassis.resetTorques(true); + + const chassisRotationQuat = quat(chassis.rotation()); + const velocity = chassis.linvel(); + + const wheelOrigins = [0, 1, 2, 3].map((wheelIndex) => { + const origin = chassis.translation(); + const rayDisplacement = vec3( + wheelRelativePositionFromOrigin[wheelIndex], + ) + .applyQuaternion(chassisRotationQuat); + origin.x += rayDisplacement.x; + origin.y += rayDisplacement.y; + origin.z += rayDisplacement.z; + return origin; + }); + + // Setting the suspension rays + for (let wheelIndex = 0; wheelIndex < 4; wheelIndex++) { + const ray = suspensionRays[wheelIndex]; + const rayDirection = vec3(downDirection) + .applyQuaternion(chassisRotationQuat); + ray.origin = wheelOrigins[wheelIndex]; + ray.dir = rayDirection; + } + + // Suspension forces + for (let wheelIndex = 0; wheelIndex < 4; wheelIndex++) { + const ray = suspensionRays[wheelIndex]; + const toiResult = world.castRay( + ray, + carSettings.wheel.maxSuspensionLength, + true, + 1, + ); + + if (toiResult === null) { + continue; + } + + const force + = carSettings.wheel.suspension.stiffness + * (carSettings.wheel.diameter - toiResult.toi) + - carSettings.wheel.suspension.damping * velocity.y; + + const direction = vec3({ + x: 0, + y: force, + z: 0, + }) + .applyQuaternion( + chassisRotationQuat, + ); + + chassis.addForceAtPoint(direction, ray.origin, true); + } + + + // Siding force + const horizonalVelocity = { + x: velocity.x, + y: 0, + z: velocity.z, + }; + const sideForceMagnitude = vec3(horizonalVelocity) + .projectOnVector( + vec3({ + x: 1, + y: 0, + z: 0, + }) + .applyQuaternion(chassisRotationQuat), + ) + .multiplyScalar(carSettings.wheel.sideForceMultiplier); + + chassis.addForce(sideForceMagnitude, true); + }; + + return updateFunction; +}; + +const createChassis = (setting: CarSettings) => { + const { + chassis: { height, width, length }, + } = setting; + const geometry = new THREE.BoxGeometry(width, height, length); + const material = new THREE.MeshPhysicalMaterial({ + color: new THREE.Color(0x000000), + side: THREE.DoubleSide, + }); + const chassis = new THREE.Mesh(geometry, material); + chassis.position.z -= 7; + + const chassisPhysicsObject = addPhysics( + chassis, + 'dynamic', + true, + createUpdateFunction(carSettings), + 'cuboid', + { + width: width / 2, + height: height / 2, + depth: length / 2, + }, + ); + + return chassisPhysicsObject; +}; + + +export class RayCastedVehicleController { + carSettings: CarSettings; + chassisPhysicsObject: PhysicsObject | null; + constructor(settings: CarSettings) { + this.carSettings = settings; + this.chassisPhysicsObject = createChassis(settings); + } +} diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts index 64d228ea4..58676c741 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts @@ -75,18 +75,18 @@ class TickManager extends EventTarget { const collider = po.collider; mesh.position.copy(collider.translation() as THREE.Vector3); mesh.quaternion.copy(collider.rotation() as THREE.Quaternion); - console.log(mesh.position); } const fn = po.fn; if (fn) { - fn(); + fn(po); } } // performance tracker start this.fps = 1000 / this.timeDiff; this.lastTimestamp = this.timestamp; + renderer.render(scene, camera); this.tick(timestamp, timeDiffCapped, this.fps, frame); }; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/helpers.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/helpers.ts new file mode 100644 index 000000000..aca09ae6a --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/render/physics/helpers.ts @@ -0,0 +1,5 @@ +import { Euler, Quaternion, Vector3 } from 'three'; + +export const quat = ({ x, y, z, w }) => new Quaternion(x, y, z, w); +export const vec3 = ({ x, y, z }) => new Vector3(x, y, z); +export const euler = ({ x, y, z }) => new Euler(x, y, z); diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts index 0ed2a0e8d..d190da205 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts @@ -2,32 +2,49 @@ import type Rapier from '@dimforge/rapier3d-compat'; import { RAPIER, getSimulation } from '../../init'; +export type UpdateFunction = ({ + mesh, + collider, + rigidBody, +}: { + mesh: THREE.Mesh; + collider: Rapier.Collider; + rigidBody: Rapier.RigidBody; +}) => void; + export type PhysicsObject = { - mesh: THREE.Mesh - collider: Rapier.Collider - rigidBody: Rapier.RigidBody - fn?: Function - autoAnimate: boolean + mesh: THREE.Mesh; + collider: Rapier.Collider; + rigidBody: Rapier.RigidBody; + fn?: UpdateFunction; + autoAnimate: boolean; }; export const addPhysics = ( mesh: THREE.Mesh, rigidBodyType: string, autoAnimate: boolean = true, // update the mesh's position and quaternion based on the physics world every frame - postPhysicsFn?: Function, + postPhysicsFn?: UpdateFunction, colliderType?: string, colliderSettings?: any, ) => { const simulation = getSimulation(); if (simulation.state !== 'ready') { - throw new Error('Tried to add a physic object before initializing the simulation.'); + throw new Error( + 'Tried to add a physic object before initializing the simulation.', + ); } const physics = simulation.physicsWorld; const physicsObjects = simulation.physicsObjects; + const scene = simulation.scene; const rigidBodyDesc = (RAPIER.RigidBodyDesc as any)[rigidBodyType](); - rigidBodyDesc.setTranslation(mesh.position.x, mesh.position.y, mesh.position.z); + rigidBodyDesc.setTranslation( + mesh.position.x, + mesh.position.y, + mesh.position.z, + ); // * Responsible for collision response const rigidBody = physics.createRigidBody(rigidBodyDesc); @@ -80,6 +97,7 @@ export const addPhysics = ( }; physicsObjects.push(physicsObject); + scene.add(mesh); return physicsObject; }; From b44aa922b204e21b682aa5c3b0f05bf303e054fd Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 30 Sep 2023 08:51:03 +0000 Subject: [PATCH 10/93] Remove car from init_meshes --- .../three-rapier-controller/mesh/init_meshes.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts index 6e7c1793a..ee96ebd7e 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts @@ -1,8 +1,6 @@ import * as THREE from 'three'; import { addPhysics } from '../render/physics/physics'; import { getSimulation } from '../init'; -import { RayCastedVehicleController } from '../render/controllers/RayCastedVehicleController'; -import { carSettings } from '../render/controllers/RayCastedVehicleController/carTuning'; const addFloor = () => { const size = 100; @@ -31,7 +29,5 @@ export const init_meshes = () => { throw new Error('Cannot initialized mesh before simulation is ready'); } - const vehicleController = new RayCastedVehicleController(carSettings); - addFloor(); }; From 08b90b503f57d64716a3b8d95b2d08fa2b6487d6 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 30 Sep 2023 08:53:07 +0000 Subject: [PATCH 11/93] Try to init --- src/bundles/robot_simulation/functions.ts | 8 +++- src/bundles/robot_simulation/index.ts | 2 +- .../three-rapier-controller/init.ts | 45 ++++++++++++------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 44b917857..0a45ea28d 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -3,7 +3,7 @@ * @module robot_simulation */ -import { initEngines } from './three-rapier-controller/init'; +import { getSimulation, initEngines } from './three-rapier-controller/init'; export function show() { console.log('show function called'); @@ -12,3 +12,9 @@ export function show() { export function init_simulation() { initEngines(); } + + +export function getRobot() { + const simulation = getSimulation(); + return simulation.state; +} diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 13eb1d9bb..67121358f 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1 +1 @@ -export { show, init_simulation } from './functions'; +export { show, init_simulation, getRobot } from './functions'; diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts index c44b933ed..da5d43bbe 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/init.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/init.ts @@ -10,33 +10,38 @@ import { type PhysicsObject } from './render/physics/physics'; import { type SimulationStates } from './constants/states'; import TickManager from './render/controllers/tickManager'; import { init_meshes } from './mesh/init_meshes'; - +import { RayCastedVehicleController } from './render/controllers/RayCastedVehicleController'; +import { carSettings } from './render/controllers/RayCastedVehicleController/carTuning'; let RAPIER: typeof Rapier; export type RobotSimulation = { state: Extract; -} | { - state: Exclude, - scene: THREE.Scene, - camera: THREE.PerspectiveCamera, - renderer: THREE.WebGLRenderer, - renderWidth: number, - renderHeight: number, - renderAspectRatio: number, - RAPIER: typeof Rapier, - physicsWorld: Rapier.World, - physicsObjects: Array, +} +| { + state: Exclude; + scene: THREE.Scene; + camera: THREE.PerspectiveCamera; + renderer: THREE.WebGLRenderer; + renderWidth: number; + renderHeight: number; + renderAspectRatio: number; + RAPIER: typeof Rapier; + physicsWorld: Rapier.World; + physicsObjects: Array; + robot?: RayCastedVehicleController; }; const initial_simulation: RobotSimulation = { state: 'idle' }; const contextState = context.moduleContexts.robot_simulation.state; if (contextState === null) { - context.moduleContexts.robot_simulation.state = { simulation: initial_simulation }; + context.moduleContexts.robot_simulation.state = { + simulation: initial_simulation, + }; } -export const getSimulation = ():RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; -export const setSimulation = (newSimulation:RobotSimulation):void => { +export const getSimulation = (): RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; +export const setSimulation = (newSimulation: RobotSimulation): void => { context.moduleContexts.robot_simulation.state.simulation = newSimulation; console.log('Setting new value into simulation', newSimulation); }; @@ -48,7 +53,7 @@ export const initEngines = async () => { const renderTickManager = new TickManager(); const physicsWorld = new RAPIER.World(physicsOptions.GRAVITY); - const physicsObjects:Array = []; + const physicsObjects: Array = []; const scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff); @@ -65,7 +70,7 @@ export const initEngines = async () => { renderer.setSize(renderWidth, renderHeight); renderer.setPixelRatio(window.devicePixelRatio * 1.5); - const robotSimulation :RobotSimulation = { + const robotSimulation: RobotSimulation = { state: 'ready', scene, camera, @@ -83,6 +88,12 @@ export const initEngines = async () => { renderTickManager.startLoop(); init_meshes(); + + const robot = new RayCastedVehicleController(carSettings); + setSimulation({ + ...robotSimulation, + robot, + }); }; export { RAPIER }; From e8c0801cab2c058b1f1b2bba095cd90a5c83a575 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 30 Sep 2023 09:04:50 +0000 Subject: [PATCH 12/93] Change init --- .../three-rapier-controller/init.ts | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts index da5d43bbe..70d54a2ab 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/init.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/init.ts @@ -17,31 +17,28 @@ let RAPIER: typeof Rapier; export type RobotSimulation = { state: Extract; -} -| { - state: Exclude; - scene: THREE.Scene; - camera: THREE.PerspectiveCamera; - renderer: THREE.WebGLRenderer; - renderWidth: number; - renderHeight: number; - renderAspectRatio: number; - RAPIER: typeof Rapier; - physicsWorld: Rapier.World; - physicsObjects: Array; - robot?: RayCastedVehicleController; +} | { + state: Exclude, + scene: THREE.Scene, + camera: THREE.PerspectiveCamera, + renderer: THREE.WebGLRenderer, + renderWidth: number, + renderHeight: number, + renderAspectRatio: number, + RAPIER: typeof Rapier, + physicsWorld: Rapier.World, + physicsObjects: Array, + robot?: RayCastedVehicleController }; const initial_simulation: RobotSimulation = { state: 'idle' }; const contextState = context.moduleContexts.robot_simulation.state; if (contextState === null) { - context.moduleContexts.robot_simulation.state = { - simulation: initial_simulation, - }; + context.moduleContexts.robot_simulation.state = { simulation: initial_simulation }; } -export const getSimulation = (): RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; -export const setSimulation = (newSimulation: RobotSimulation): void => { +export const getSimulation = ():RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; +export const setSimulation = (newSimulation:RobotSimulation):void => { context.moduleContexts.robot_simulation.state.simulation = newSimulation; console.log('Setting new value into simulation', newSimulation); }; @@ -53,7 +50,7 @@ export const initEngines = async () => { const renderTickManager = new TickManager(); const physicsWorld = new RAPIER.World(physicsOptions.GRAVITY); - const physicsObjects: Array = []; + const physicsObjects:Array = []; const scene = new THREE.Scene(); scene.background = new THREE.Color(0xffffff); @@ -70,7 +67,7 @@ export const initEngines = async () => { renderer.setSize(renderWidth, renderHeight); renderer.setPixelRatio(window.devicePixelRatio * 1.5); - const robotSimulation: RobotSimulation = { + const robotSimulation :RobotSimulation = { state: 'ready', scene, camera, From 8e1cd5b15f9c993f8b7c9d4d3839ffca54b5c0c5 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 7 Oct 2023 03:50:57 +0000 Subject: [PATCH 13/93] Added the iterator --- src/bundles/robot_simulation/functions.ts | 23 ++++++++++--- src/bundles/robot_simulation/index.ts | 2 +- .../three-rapier-controller/init.ts | 33 ++++++++++++------- .../mesh/init_meshes.ts | 2 +- .../RayCastedVehicleController/index.ts | 3 +- .../render/controllers/tickManager.ts | 5 +-- .../render/physics/physics.ts | 3 +- .../render/simulation.ts | 18 ++++++++++ 8 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/simulation.ts diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 0a45ea28d..fe439e490 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -3,18 +3,31 @@ * @module robot_simulation */ -import { getSimulation, initEngines } from './three-rapier-controller/init'; +import { initEngines } from './three-rapier-controller/init'; +import context from 'js-slang/context'; +import { getSimulation } from './three-rapier-controller/render/simulation'; + export function show() { - console.log('show function called'); + console.log('hi'); } export function init_simulation() { - initEngines(); + const code = context.unTypecheckedCode[0]; + initEngines(code); } +export function is_ready() { + const simulation = getSimulation(); + return simulation.state === 'ready'; +} -export function getRobot() { +export function forward() { const simulation = getSimulation(); - return simulation.state; + if (simulation.state !== 'ready') { + console.log('Not ready'); + return 'Not ready'; + } + console.log('Ready'); + return 'Ready'; } diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 67121358f..29136838b 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1 +1 @@ -export { show, init_simulation, getRobot } from './functions'; +export { show, init_simulation, is_ready, forward } from './functions'; diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts index 70d54a2ab..23fe11207 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/init.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/init.ts @@ -12,9 +12,12 @@ import TickManager from './render/controllers/tickManager'; import { init_meshes } from './mesh/init_meshes'; import { RayCastedVehicleController } from './render/controllers/RayCastedVehicleController'; import { carSettings } from './render/controllers/RayCastedVehicleController/carTuning'; +import { runECEvaluatorByJoel, type IOptions } from 'js-slang'; +import { getSimulation, setSimulation } from './render/simulation'; let RAPIER: typeof Rapier; + export type RobotSimulation = { state: Extract; } | { @@ -28,22 +31,27 @@ export type RobotSimulation = { RAPIER: typeof Rapier, physicsWorld: Rapier.World, physicsObjects: Array, - robot?: RayCastedVehicleController + robot?: RayCastedVehicleController, + EceIterator: Generator }; -const initial_simulation: RobotSimulation = { state: 'idle' }; -const contextState = context.moduleContexts.robot_simulation.state; -if (contextState === null) { - context.moduleContexts.robot_simulation.state = { simulation: initial_simulation }; -} +export const initEngines = async (code:string) => { + const currentSimulation = getSimulation(); -export const getSimulation = ():RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; -export const setSimulation = (newSimulation:RobotSimulation):void => { - context.moduleContexts.robot_simulation.state.simulation = newSimulation; - console.log('Setting new value into simulation', newSimulation); -}; + if (currentSimulation.state !== 'idle') { + console.log('Engine already initialized. Skipping initialization'); + return; + } + + const options : Partial = { + originalMaxExecTime: 1000, + scheduler: 'preemptive', + stepLimit: 1000, + throwInfiniteLoops: false, + useSubst: false, + }; + const it = runECEvaluatorByJoel(code, context, options); -export const initEngines = async () => { setSimulation({ state: 'loading' }); RAPIER = await initRapier(); @@ -78,6 +86,7 @@ export const initEngines = async () => { RAPIER, physicsWorld, physicsObjects, + EceIterator: it, }; setSimulation(robotSimulation); diff --git a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts index ee96ebd7e..49f874843 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts @@ -1,6 +1,6 @@ import * as THREE from 'three'; import { addPhysics } from '../render/physics/physics'; -import { getSimulation } from '../init'; +import { getSimulation } from '../render/simulation'; const addFloor = () => { const size = 100; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts index 992be2764..3ae89fbd9 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts @@ -7,7 +7,8 @@ import { } from '../../physics/physics'; import { carSettings, type CarSettings } from './carTuning'; import { quat, vec3 } from '../../physics/helpers'; -import { getSimulation, RAPIER } from '../../../init'; +import { RAPIER } from '../../../init'; +import { getSimulation } from '../../simulation'; const getWheelDisplacements = (settings: CarSettings) => { const rayDisplacements = [ diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts index 58676c741..a60257b55 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts @@ -1,6 +1,5 @@ -import { getSimulation } from '../../init'; +import { getSimulation } from '../simulation'; -// animation params type Frame = XRFrame | null; export type TickData = { @@ -50,6 +49,7 @@ class TickManager extends EventTarget { const physicsObjects = simulation.physicsObjects; const scene = simulation.scene; const camera = simulation.camera; + const ece = simulation.EceIterator; if (!renderer) { @@ -89,6 +89,7 @@ class TickManager extends EventTarget { renderer.render(scene, camera); this.tick(timestamp, timeDiffCapped, this.fps, frame); + console.log(ece.next()); }; renderer.setAnimationLoop(animate); diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts index d190da205..23f9c03be 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts @@ -1,6 +1,7 @@ import type Rapier from '@dimforge/rapier3d-compat'; -import { RAPIER, getSimulation } from '../../init'; +import { RAPIER } from '../../init'; +import { getSimulation } from '../simulation'; export type UpdateFunction = ({ mesh, diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/simulation.ts b/src/bundles/robot_simulation/three-rapier-controller/render/simulation.ts new file mode 100644 index 000000000..4915461c0 --- /dev/null +++ b/src/bundles/robot_simulation/three-rapier-controller/render/simulation.ts @@ -0,0 +1,18 @@ +import context from 'js-slang/context'; +import { type RobotSimulation } from '../init'; + +console.log('Simulation is being initialized'); + +const initial_simulation: RobotSimulation = { state: 'idle' }; +const contextState = context.moduleContexts.robot_simulation.state; + +if (contextState === null) { + context.moduleContexts.robot_simulation.state = { simulation: initial_simulation }; + console.log('simulation is being set to initial state'); +} + +export const getSimulation = ():RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; +export const setSimulation = (newSimulation:RobotSimulation):void => { + context.moduleContexts.robot_simulation.state.simulation = newSimulation; + console.log('Setting new value into simulation', newSimulation); +}; From 81b1640025cbd6eb75a1c77a9c88504db3a59d7b Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 9 Oct 2023 02:10:37 +0000 Subject: [PATCH 14/93] Add realistic dimensions and weight to the car --- src/bundles/robot_simulation/functions.ts | 73 ++++++++++++++++++- src/bundles/robot_simulation/index.ts | 2 +- .../three-rapier-controller/init.ts | 8 +- .../mesh/init_meshes.ts | 6 +- .../RayCastedVehicleController/carTuning.ts | 18 +++-- .../RayCastedVehicleController/index.ts | 6 +- 6 files changed, 94 insertions(+), 19 deletions(-) diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index fe439e490..aaeb112ff 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -15,6 +15,12 @@ export function show() { export function init_simulation() { const code = context.unTypecheckedCode[0]; initEngines(code); + context.runtime.break = true; +} + +export function in_simulation() { + const simulation = getSimulation(); + return simulation.state === 'ready'; } export function is_ready() { @@ -22,12 +28,73 @@ export function is_ready() { return simulation.state === 'ready'; } +const mag = 0.2; + export function forward() { const simulation = getSimulation(); if (simulation.state !== 'ready') { console.log('Not ready'); - return 'Not ready'; + return; + } + const robot = simulation.robot; + if (!robot) { + throw new Error('AHHH'); + } + + robot.chassisPhysicsObject.rigidBody.applyImpulse({ + x: 0, + y: 0, + z: mag, + }, true); +} +export function backward() { + const simulation = getSimulation(); + if (simulation.state !== 'ready') { + console.log('Not ready'); + return; + } + + const robot = simulation.robot; + if (!robot) { + throw new Error('AHHH'); + } + + robot.chassisPhysicsObject.rigidBody.applyImpulse({ + x: 0, + y: 0, + z: -mag, + }, true); +} + +// eslint-disable-next-line @typescript-eslint/naming-convention +export function ev3_runToRelativePosition(motor: number, position:number, speed:number) { + const simulation = getSimulation(); + if (simulation.state !== 'ready') { + console.log('Not ready'); + return; + } + + const robot = simulation.robot; + if (!robot) { + console.log('NO robot'); + throw new Error('AHHH'); } - console.log('Ready'); - return 'Ready'; + + const rb = robot.chassisPhysicsObject.rigidBody; + console.log(speed, rb.mass()); + + robot.chassisPhysicsObject.rigidBody.applyImpulse({ + x: 0, + y: 0, + z: speed * rb.mass(), + }, true); + + setTimeout(() => { + console.log('AHH'); + robot.chassisPhysicsObject.rigidBody.applyImpulse({ + x: 0, + y: 0, + z: -speed * rb.mass(), + }, true); + }, position / speed * 1000); } diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 29136838b..e6839d2cc 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1 +1 @@ -export { show, init_simulation, is_ready, forward } from './functions'; +export { show, init_simulation, is_ready, forward, in_simulation, backward, ev3_runToRelativePosition } from './functions'; diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts index 23fe11207..55ccd8254 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/init.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/init.ts @@ -44,9 +44,9 @@ export const initEngines = async (code:string) => { } const options : Partial = { - originalMaxExecTime: 1000, + originalMaxExecTime: Infinity, scheduler: 'preemptive', - stepLimit: 1000, + stepLimit: Infinity, throwInfiniteLoops: false, useSubst: false, }; @@ -70,6 +70,10 @@ export const initEngines = async (code:string) => { const renderAspectRatio = renderWidth / renderHeight; const camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); + // camera.translateY(5); + camera.translateX(2); + camera.translateY(-1.5); + camera.lookAt(new THREE.Vector3(0, -1.5, 0)); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(renderWidth, renderHeight); diff --git a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts index 49f874843..870df6686 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts @@ -15,9 +15,9 @@ const addFloor = () => { cube.position.set(0, -2, 0); addPhysics(cube, 'fixed', true, undefined, 'cuboid', { - width: size, - height, - depth: size, + width: size / 2, + height: height / 2, + depth: size / 2, }); return cube; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts index 1fb0fa124..185896f79 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts @@ -3,8 +3,10 @@ export type CarSettings = { length: number; height: number; width: number; + weight: number; }; wheel: { + restHeight:number; diameter: number; maxSuspensionLength: number; suspension: { stiffness: number; damping: number }; @@ -16,16 +18,18 @@ export type CarSettings = { export const carSettings: CarSettings = { chassis: { - length: 1, - height: 0.5, - width: 0.7, + length: 0.18, // in meters + height: 0.095, // in meters + width: 0.145, // in meters + weight: 0.6, // in kg }, wheel: { - diameter: 1.5, - maxSuspensionLength: 2.5, + restHeight: 0.03, + diameter: 0.055, + maxSuspensionLength: 0.1, suspension: { - stiffness: 20, - damping: 2, + stiffness: 70, + damping: 10, }, buffer: 0.02, sideForceMultiplier: -20, diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts index 3ae89fbd9..9160f33cc 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts +++ b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts @@ -114,7 +114,7 @@ const createUpdateFunction = (settings: CarSettings): UpdateFunction => { const force = carSettings.wheel.suspension.stiffness - * (carSettings.wheel.diameter - toiResult.toi) + * (carSettings.wheel.restHeight - toiResult.toi + (carSettings.chassis.height / 2)) - carSettings.wheel.suspension.damping * velocity.y; const direction = vec3({ @@ -163,7 +163,6 @@ const createChassis = (setting: CarSettings) => { side: THREE.DoubleSide, }); const chassis = new THREE.Mesh(geometry, material); - chassis.position.z -= 7; const chassisPhysicsObject = addPhysics( chassis, @@ -178,13 +177,14 @@ const createChassis = (setting: CarSettings) => { }, ); + chassisPhysicsObject.collider.setMass(carSettings.chassis.weight); return chassisPhysicsObject; }; export class RayCastedVehicleController { carSettings: CarSettings; - chassisPhysicsObject: PhysicsObject | null; + chassisPhysicsObject: PhysicsObject; constructor(settings: CarSettings) { this.carSettings = settings; this.chassisPhysicsObject = createChassis(settings); From 573be084f4ca01715f17a491f8c3b04b2dab612d Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 27 Oct 2023 05:05:02 +0000 Subject: [PATCH 15/93] Massive changes --- src/bundles/robot_simulation/functions.ts | 93 +------ src/bundles/robot_simulation/index.ts | 5 +- .../simulation/controllers/car_controller.ts | 229 ++++++++++++++++++ .../controllers}/physics/helpers.ts | 14 ++ .../controllers/physics/physics_controller.ts | 52 ++++ .../controllers/physics/speed_controller.ts | 64 +++++ .../simulation/controllers/physics/types.ts | 1 + .../controllers/program_controller.ts | 29 +++ .../controllers/render_controller.ts | 51 ++++ .../robot_simulation/simulation/index.ts | 16 ++ .../simulation/primitives/physics_object.ts | 183 ++++++++++++++ .../robot_simulation/simulation/world.ts | 157 ++++++++++++ .../constants/states.ts | 3 - .../three-rapier-controller/init.ts | 109 --------- .../mesh/init_meshes.ts | 33 --- .../three-rapier-controller/options.ts | 10 - .../RayCastedVehicleController/carTuning.ts | 40 --- .../RayCastedVehicleController/index.ts | 192 --------------- .../render/controllers/tickManager.ts | 107 -------- .../render/physics/RAPIER.ts | 7 - .../render/physics/physics.ts | 104 -------- .../render/simulation.ts | 18 -- src/tabs/RobotSimulation/components/Main.tsx | 2 +- src/tabs/RobotSimulation/components/Modal.tsx | 2 +- .../components/Simulation/index.tsx | 56 +++-- src/tabs/RobotSimulation/index.tsx | 3 +- 26 files changed, 852 insertions(+), 728 deletions(-) create mode 100644 src/bundles/robot_simulation/simulation/controllers/car_controller.ts rename src/bundles/robot_simulation/{three-rapier-controller/render => simulation/controllers}/physics/helpers.ts (51%) create mode 100644 src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts create mode 100644 src/bundles/robot_simulation/simulation/controllers/physics/speed_controller.ts create mode 100644 src/bundles/robot_simulation/simulation/controllers/physics/types.ts create mode 100644 src/bundles/robot_simulation/simulation/controllers/program_controller.ts create mode 100644 src/bundles/robot_simulation/simulation/controllers/render_controller.ts create mode 100644 src/bundles/robot_simulation/simulation/index.ts create mode 100644 src/bundles/robot_simulation/simulation/primitives/physics_object.ts create mode 100644 src/bundles/robot_simulation/simulation/world.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/constants/states.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/init.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/options.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/physics/RAPIER.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts delete mode 100644 src/bundles/robot_simulation/three-rapier-controller/render/simulation.ts diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index aaeb112ff..79bb25ce6 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -3,98 +3,27 @@ * @module robot_simulation */ -import { initEngines } from './three-rapier-controller/init'; import context from 'js-slang/context'; -import { getSimulation } from './three-rapier-controller/render/simulation'; +import { getWorld } from './simulation'; -export function show() { - console.log('hi'); -} - -export function init_simulation() { - const code = context.unTypecheckedCode[0]; - initEngines(code); - context.runtime.break = true; -} - -export function in_simulation() { - const simulation = getSimulation(); - return simulation.state === 'ready'; -} - -export function is_ready() { - const simulation = getSimulation(); - return simulation.state === 'ready'; -} -const mag = 0.2; - -export function forward() { - const simulation = getSimulation(); - if (simulation.state !== 'ready') { - console.log('Not ready'); - return; - } - const robot = simulation.robot; - if (!robot) { - throw new Error('AHHH'); - } - - robot.chassisPhysicsObject.rigidBody.applyImpulse({ - x: 0, - y: 0, - z: mag, - }, true); +export function show() { + console.log('This is the show function'); } -export function backward() { - const simulation = getSimulation(); - if (simulation.state !== 'ready') { - console.log('Not ready'); - return; - } - const robot = simulation.robot; - if (!robot) { - throw new Error('AHHH'); - } - robot.chassisPhysicsObject.rigidBody.applyImpulse({ - x: 0, - y: 0, - z: -mag, - }, true); +export function init_new_simulation() { + const code = context.unTypecheckedCode[0]; + const world = getWorld(); + world.init(code); } -// eslint-disable-next-line @typescript-eslint/naming-convention -export function ev3_runToRelativePosition(motor: number, position:number, speed:number) { - const simulation = getSimulation(); - if (simulation.state !== 'ready') { - console.log('Not ready'); +export function ev3_runToRelativePosition(motor:number, position: number, speed:number) { + const world = getWorld(); + if (world.state === 'loading') { return; } - const robot = simulation.robot; - if (!robot) { - console.log('NO robot'); - throw new Error('AHHH'); - } - - const rb = robot.chassisPhysicsObject.rigidBody; - console.log(speed, rb.mass()); - - robot.chassisPhysicsObject.rigidBody.applyImpulse({ - x: 0, - y: 0, - z: speed * rb.mass(), - }, true); - - setTimeout(() => { - console.log('AHH'); - robot.chassisPhysicsObject.rigidBody.applyImpulse({ - x: 0, - y: 0, - z: -speed * rb.mass(), - }, true); - }, position / speed * 1000); + world.carController.runToRelativePosition(motor, position, speed); } diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index e6839d2cc..9862d1e85 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1 +1,4 @@ -export { show, init_simulation, is_ready, forward, in_simulation, backward, ev3_runToRelativePosition } from './functions'; +export { show, init_simulation, is_ready, forward, in_simulation, backward, ev3_move_forward, ev3_runToRelativePosition } from './functions'; + + +export { init_new_simulation, new_ev3_runToRelativePosition } from './functions'; diff --git a/src/bundles/robot_simulation/simulation/controllers/car_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car_controller.ts new file mode 100644 index 000000000..746d82c8c --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/car_controller.ts @@ -0,0 +1,229 @@ +import * as THREE from 'three'; +import { + addCuboidPhysicsObject, + type PhysicsObject, +} from '../primitives/physics_object'; +import { type Vector } from './physics/types'; +import { type Ray } from '@dimforge/rapier3d-compat'; +import { RAPIER } from './physics/physics_controller'; +import { nullVector } from './physics/helpers'; +import { vec3 } from '../controllers/physics/helpers'; +import { instance } from '../world'; +import { SpeedController } from './physics/speed_controller'; + +export type CarSettings = { + chassis: { + length: number; + height: number; + width: number; + mass: number; + }; + wheel: { + restHeight: number; + diameter: number; + maxSuspensionLength: number; + suspension: { stiffness: number; damping: number }; + buffer: number; + sideForceMultiplier: number; + }; + turning: { sensitivity: number }; +}; + +export const settings: CarSettings = { + chassis: { + length: 0.18, // in meters + height: 0.095, // in meters + width: 0.145, // in meters + mass: 0.6, // in kg + }, + wheel: { + restHeight: 0.03, + diameter: 0.055, + maxSuspensionLength: 0.1, + suspension: { + stiffness: 70, + damping: 10, + }, + buffer: 0.02, + sideForceMultiplier: -20, + }, + turning: { + sensitivity: 0.5, + }, +} as const; + +class Wheel { + static downDirection = { + x: 0, + y: -1, + z: 0, + }; + + carSettings: CarSettings; + displacement: Vector; + ray: Ray; + chassis: PhysicsObject; + + displacementVector: THREE.Vector3; + downVector: THREE.Vector3; + forceVector: THREE.Vector3; + + constructor( + displacement: Vector, + chassis: PhysicsObject, + carSettings: CarSettings, + ) { + this.carSettings = carSettings; + this.displacement = displacement; + this.ray = new RAPIER.Ray(nullVector.RAPIER, nullVector.RAPIER); + this.chassis = chassis; + + this.displacementVector = vec3(this.displacement); + this.downVector = vec3(Wheel.downDirection); + this.forceVector = vec3(nullVector.RAPIER); + } + + step() { + const velocityY = this.chassis.velocity().y; + + // Reset vectors for memory efficiency + this.displacementVector.copy(this.displacement as THREE.Vector3); + this.downVector.copy(Wheel.downDirection as THREE.Vector3); + this.forceVector.set(0, 0, 0); + + // Convert local vectors to global/world space + const globalDisplacement = this.chassis.worldTranslation( + this.displacementVector, + ); + const globalDownDirection = this.chassis.worldDirection(this.downVector); + + this.ray.origin = globalDisplacement; + this.ray.dir = globalDownDirection; + + const result = instance.castRay( + this.ray, + settings.wheel.maxSuspensionLength, + ); + + if (result === null) { + return; + } + + const wheelDistance = result; + const wheelSettings = this.carSettings.wheel; + + // Calculate suspension force + const force + = wheelSettings.suspension.stiffness + * (wheelSettings.restHeight + - wheelDistance + + this.carSettings.chassis.height / 2) + - wheelSettings.suspension.damping * velocityY; + + this.forceVector.y = force; + + // Apply force at the wheel's global displacement + this.chassis.addForce( + this.chassis.worldDirection(this.forceVector), + globalDisplacement, + ); + } +} + +export class CarController { + carSettings: CarSettings; + chassis: PhysicsObject | null; + wheelDisplacements: Vector[]; + + wheels: Wheel[]; + leftMotor: SpeedController | null; + rightMotor: SpeedController | null; + + constructor(carSettings: CarSettings) { + this.carSettings = carSettings; + this.chassis = null; + this.rightMotor = null; + this.leftMotor = null; + this.wheels = []; + this.wheelDisplacements = this.#getWheelDisplacements(); + } + + init() { + this.chassis = this.#createChassis(); + this.#createWheels(this.chassis); + this.rightMotor = new SpeedController( + this.chassis, + this.wheelDisplacements[0], + ); + this.leftMotor = new SpeedController( + this.chassis, + this.wheelDisplacements[1], + ); + } + + runToRelativePosition(motor:number, position:number, speed:number) { + console.log('RUN TO RELATIVE POSITION', motor, position, speed); + const selectedMotor = motor === 0 ? this.leftMotor : this.rightMotor; + const time = Math.abs(position / speed) * 1000; + selectedMotor!.setSpeed(speed, time); + } + + #createChassis() { + const { width, height, length, mass } = this.carSettings.chassis; + + const chassis = addCuboidPhysicsObject({ + width, + height, + length, + position: new THREE.Vector3(0, 0.5564785599708557, 0), + }); + + chassis.setMass(mass); + + return chassis; + } + + #createWheels(chassis: PhysicsObject) { + const wheelDisplacements = this.wheelDisplacements; + this.wheels = wheelDisplacements.map( + (d) => new Wheel(d, chassis, this.carSettings), + ); + } + + #getWheelDisplacements() { + const { width, length } = this.carSettings.chassis; + const buffer = this.carSettings.wheel.buffer; + + return [ + { + x: width / 2 + buffer, + y: 0, + z: length / 2, + }, + { + x: -(width / 2 + buffer), + y: 0, + z: length / 2, + }, + { + x: width / 2 + buffer, + y: 0, + z: -length / 2, + }, + { + x: -(width / 2 + buffer), + y: 0, + z: -length / 2, + }, + ]; + } + + step(timestamp: number) { + this.chassis!.removeForcesAndTorques(); + this.wheels.forEach((wheel) => { + wheel.step(); + }); + this.leftMotor!.step(timestamp); + this.rightMotor!.step(timestamp); + } +} diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/helpers.ts b/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts similarity index 51% rename from src/bundles/robot_simulation/three-rapier-controller/render/physics/helpers.ts rename to src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts index aca09ae6a..3b305223b 100644 --- a/src/bundles/robot_simulation/three-rapier-controller/render/physics/helpers.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts @@ -1,5 +1,19 @@ +import { type Vector } from '@dimforge/rapier3d-compat'; import { Euler, Quaternion, Vector3 } from 'three'; export const quat = ({ x, y, z, w }) => new Quaternion(x, y, z, w); export const vec3 = ({ x, y, z }) => new Vector3(x, y, z); export const euler = ({ x, y, z }) => new Euler(x, y, z); + +export const nullVector = { + THREE: { + x: 0, + y: 0, + z: 0, + } as Vector3, + RAPIER: { + x: 0, + y: 0, + z: 0, + } as Vector, +}; diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts new file mode 100644 index 000000000..e485de6bd --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts @@ -0,0 +1,52 @@ +import Rapier, { type Ray, type RigidBody } from '@dimforge/rapier3d-compat'; + +let RAPIER: typeof Rapier; + + +export const physicsOptions = { + GRAVITY: new Rapier.Vector3(0.0, -9.81, 0.0), +} as const; + +export class PhysicsController { + isInitialized = false; + + RAPIER: typeof Rapier | null; + #world: Rapier.World | null; + + constructor() { + this.RAPIER = null; + this.#world = null; + } + + async init() { + let r = await import('@dimforge/rapier3d-compat'); + await r.init(); + + this.RAPIER = r; + RAPIER = r; + this.isInitialized = true; + this.#world = new r.World(physicsOptions.GRAVITY); + } + + createCollider(colliderDesc: Rapier.ColliderDesc, rigidBody: RigidBody) { + return this.#world!.createCollider(colliderDesc, rigidBody); + } + + createRigidBody(rigidBodyDesc: Rapier.RigidBodyDesc) { + return this.#world!.createRigidBody(rigidBodyDesc); + } + + castRay(ray:Ray, maxDistance:number):number | null { + const result = this.#world!.castRay(ray, maxDistance, true); + if (result === null) { + return null; + } + return result.toi; + } + + step(_: number) { + this.#world!.step(); + } +} + +export { RAPIER }; diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/speed_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/speed_controller.ts new file mode 100644 index 000000000..f17945aec --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/physics/speed_controller.ts @@ -0,0 +1,64 @@ +import { type PhysicsObject } from '../../primitives/physics_object'; +import { vec3 } from './helpers'; +import { type Vector } from './types'; + +export class SpeedController { + physicsObject: PhysicsObject; + displacement: Vector; + time:number; + + speed: number; + endTime:number; + + displacementVector:THREE.Vector3; + targetVelocity: THREE.Vector3; + + + constructor(physicsObject: PhysicsObject, displacement: Vector) { + this.physicsObject = physicsObject; + this.displacement = displacement; + this.speed = 0; + this.endTime = Infinity; + this.time = 0; + + this.displacementVector = vec3(this.displacement); + this.targetVelocity = vec3({ + x: 0, + y: 0, + z: this.speed, + }); + } + + + setSpeed(speed: number, duration: number) { + this.speed = speed; + this.endTime = this.time + duration; + } + + step(timestamp: number) { + console.log(this.speed, this.endTime, this.time); + this.time = timestamp; + if (this.time > this.endTime) { + console.log('UNSET SPEED'); + this.speed = 0; + this.endTime = Infinity; + } + + this.displacementVector.copy(this.displacement as THREE.Vector3); + this.targetVelocity.copy({ + x: 0, + y: 0, + z: this.speed, + } as THREE.Vector3); + + const worldVelocity = this.physicsObject.worldVelocity(this.displacementVector.clone()); + const velocityDelta = this.physicsObject.worldDirection(this.targetVelocity.clone()) + .sub(worldVelocity); + + + const impulse = velocityDelta.multiplyScalar(this.physicsObject.getMass() / 50); + + this.physicsObject.getRigidBody() + .applyImpulseAtPoint(impulse, this.physicsObject.worldTranslation(this.displacementVector.clone()), true); + } +} diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/types.ts b/src/bundles/robot_simulation/simulation/controllers/physics/types.ts new file mode 100644 index 000000000..246ff6372 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/physics/types.ts @@ -0,0 +1 @@ +export type Vector = { x:number, y:number, z:number }; diff --git a/src/bundles/robot_simulation/simulation/controllers/program_controller.ts b/src/bundles/robot_simulation/simulation/controllers/program_controller.ts new file mode 100644 index 000000000..45f0b8108 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/program_controller.ts @@ -0,0 +1,29 @@ +import { type IOptions, runECEvaluatorByJoel } from 'js-slang'; +import context from 'js-slang/context'; + +export class ProgramController { + #code: string; + #iterator: Generator | null; + + constructor() { + this.#code = ''; + this.#iterator = null; + } + + init(code: string) { + this.#code = code; + + const options: Partial = { + originalMaxExecTime: Infinity, + scheduler: 'preemptive', + stepLimit: Infinity, + throwInfiniteLoops: false, + useSubst: false, + }; + this.#iterator = runECEvaluatorByJoel(code, context, options); + } + + step(_: number) { + return this.#iterator!.next(); + } +} diff --git a/src/bundles/robot_simulation/simulation/controllers/render_controller.ts b/src/bundles/robot_simulation/simulation/controllers/render_controller.ts new file mode 100644 index 000000000..0b277549f --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/render_controller.ts @@ -0,0 +1,51 @@ +import * as THREE from 'three'; + + +export const sceneOptions = { + height: 600, + width: 800, +} as const; + + +export class RenderController { + #scene: THREE.Scene; + #camera: THREE.Camera; + #renderer: THREE.WebGLRenderer; + + constructor() { + this.#scene = new THREE.Scene(); + + const renderAspectRatio = sceneOptions.width / sceneOptions.height; + this.#camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); + + this.#renderer = new THREE.WebGLRenderer({ antialias: true }); + } + + init() { + this.#scene.background = new THREE.Color(0xffffff); + const light = new THREE.AmbientLight(0xffffff); + this.#scene.add(light); + + this.#camera.translateY(3); + this.#camera.lookAt(new THREE.Vector3(0, -1.5, 0)); + + // this.#camera.translateX(0.8); + // this.#camera.translateY(0.5); + // this.#camera.lookAt(new THREE.Vector3(0, 0.5, 0)); + + this.#renderer.setSize(sceneOptions.width, sceneOptions.height); + this.#renderer.setPixelRatio(window.devicePixelRatio * 1.5); + } + + setRendererOutput(domElement: HTMLDivElement) { + domElement.replaceChildren(this.#renderer.domElement); + } + + addMesh(mesh: THREE.Mesh) { + this.#scene.add(mesh); + } + + step(_: number) { + this.#renderer.render(this.#scene, this.#camera); + } +} diff --git a/src/bundles/robot_simulation/simulation/index.ts b/src/bundles/robot_simulation/simulation/index.ts new file mode 100644 index 000000000..47e0c7312 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/index.ts @@ -0,0 +1,16 @@ +import context from 'js-slang/context'; +import { World } from './world'; + +console.log('World is being initialized'); + +const contextState = context.moduleContexts.robot_simulation.state?.world; + +if (contextState === undefined) { + context.moduleContexts.robot_simulation.state = { + ...context.moduleContexts.robot_simulation.state, + world: new World(), + }; + console.log('world is being set to initial state'); +} + +export const getWorld = (): World => context.moduleContexts.robot_simulation.state.world; diff --git a/src/bundles/robot_simulation/simulation/primitives/physics_object.ts b/src/bundles/robot_simulation/simulation/primitives/physics_object.ts new file mode 100644 index 000000000..6d9ea3080 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/primitives/physics_object.ts @@ -0,0 +1,183 @@ +import Rapier, { type Vector3 } from '@dimforge/rapier3d-compat'; +import * as THREE from 'three'; +import { RAPIER } from '../controllers/physics/physics_controller'; + +import { instance } from '../world'; +import { nullVector, quat, vec3 } from '../controllers/physics/helpers'; + +type PhysicsObjectCache = { + rotation?: THREE.Quaternion; + linearVelocity?: THREE.Vector3; + translation?: THREE.Vector3; + worldVelocity?: THREE.Vector3; + angularVelocity?: THREE.Vector3; +}; + + +export class PhysicsObject { + #rigidBody: Rapier.RigidBody; + #mesh: THREE.Mesh; + #collider: Rapier.Collider; + #cache: PhysicsObjectCache = {}; + + constructor( + mesh: THREE.Mesh, + rigidBody: Rapier.RigidBody, + collider: Rapier.Collider, + ) { + this.#rigidBody = rigidBody; + this.#mesh = mesh; + this.#collider = collider; + } + + getRigidBody() { + return this.#rigidBody; + } + + setMass(mass: number) { + this.#collider.setMass(mass); + } + + getMass():number { + return this.#collider.mass(); + } + + removeForcesAndTorques() { + this.#rigidBody.resetForces(true); + this.#rigidBody.resetTorques(true); + } + + rotation(): THREE.Quaternion { + if (this.#cache.rotation) { + return this.#cache.rotation; + } + this.#cache.rotation = quat(this.#rigidBody.rotation()); + return this.#cache.rotation; + } + + velocity(): THREE.Vector3 { + if (this.#cache.linearVelocity) { + return this.#cache.linearVelocity; + } + this.#cache.linearVelocity = vec3(this.#rigidBody.linvel()); + return this.#cache.linearVelocity; + } + + angularVelocity(): THREE.Vector3 { + if (this.#cache.angularVelocity) { + return this.#cache.angularVelocity; + } + this.#cache.angularVelocity = vec3(this.#rigidBody.angvel()); + return this.#cache.angularVelocity; + } + + translation(): THREE.Vector3 { + if (this.#cache.translation) { + return this.#cache.translation; + } + this.#cache.translation = vec3(this.#rigidBody.translation()); + return this.#cache.translation; + } + + invalidateCache() { + this.#cache = {}; + } + + worldTranslation(localTranslation: THREE.Vector3 = nullVector.THREE): THREE.Vector3 { + const rotation = this.rotation(); + const translation = this.translation(); + + return localTranslation.applyQuaternion(rotation) + .add(translation); + } + + worldDirection(localDirection: THREE.Vector3): THREE.Vector3 { + const rotation = this.rotation(); + + return localDirection.applyQuaternion(rotation); + } + + distanceVectorOfPointToRotationalAxis(localPoint: THREE.Vector3 = nullVector.THREE) { + // TODO: Rewrite this such that it doesn't use clone()! + + return localPoint.clone() + .projectOnVector(this.angularVelocity()) + .negate() + .add(localPoint); + } + + tangentialVelocityOfPoint(localPoint: THREE.Vector3 = nullVector.THREE): THREE.Vector3 { + const distanceVector = this.distanceVectorOfPointToRotationalAxis(localPoint); + const angularVelocity = this.angularVelocity(); + const velocityMagnitude = distanceVector.length() * angularVelocity.length(); + + const tangentialVelocity = this.worldDirection(localPoint) + .cross(angularVelocity) + .negate() + .normalize() + .multiplyScalar(velocityMagnitude); + + return tangentialVelocity; + } + + + worldVelocity(localPoint: THREE.Vector3 = nullVector.THREE): THREE.Vector3 { + return this.tangentialVelocityOfPoint(localPoint) + .add(this.velocity()); + } + + addForce(force: Vector3, point: Vector3 = nullVector.RAPIER) { + return this.#rigidBody.addForceAtPoint(force, point, true); + } + + /** + * Syncs the mesh's position and quaternion with the physics world. + * Usually called after a physics step + */ + step(_: number) { + this.#mesh.position.copy(this.#collider.translation() as THREE.Vector3); + this.#mesh.quaternion.copy(this.#collider.rotation() as THREE.Quaternion); + this.invalidateCache(); + } +} + +type Cuboid = { + width: number; + height: number; + length: number; + position? : THREE.Vector3; + color?: THREE.Color; + dynamic?: boolean; +}; + +export const addCuboidPhysicsObject = ({ + width, + height, + length, + position = new THREE.Vector3(0, 0, 0), + color = new THREE.Color('blue'), + dynamic = true, +}: Cuboid): PhysicsObject => { + const geometry = new THREE.BoxGeometry(width, height, length); + const material = new THREE.MeshPhysicalMaterial({ + color, + side: THREE.DoubleSide, + }); + + const mesh = new THREE.Mesh(geometry, material); + mesh.position.copy(position); + + const rigidBodyDesc = dynamic ? RAPIER.RigidBodyDesc.dynamic() : Rapier.RigidBodyDesc.fixed(); + + rigidBodyDesc.translation.x = mesh.position.x; + rigidBodyDesc.translation.y = mesh.position.y; + rigidBodyDesc.translation.z = mesh.position.z; + + const colliderDesc = RAPIER.ColliderDesc.cuboid( + width / 2, + height / 2, + length / 2, + ); + + return instance.addRigidBody(mesh, rigidBodyDesc, colliderDesc); +}; diff --git a/src/bundles/robot_simulation/simulation/world.ts b/src/bundles/robot_simulation/simulation/world.ts new file mode 100644 index 000000000..8c05cdf62 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/world.ts @@ -0,0 +1,157 @@ +import { + type Ray, + type ColliderDesc, + type RigidBodyDesc, +} from '@dimforge/rapier3d-compat'; +import { PhysicsController } from './controllers/physics/physics_controller'; +import { ProgramController } from './controllers/program_controller'; +import { RenderController } from './controllers/render_controller'; +import { + PhysicsObject, + addCuboidPhysicsObject, +} from './primitives/physics_object'; +import * as THREE from 'three'; +import { settings, CarController } from './controllers/car_controller'; + +export const simulationStates = [ + 'unintialized', + 'loading', + 'ready', + 'running', +] as const; +export type SimulationStates = (typeof simulationStates)[number]; + +export type Internals = { + physicsController: PhysicsController; + renderController: RenderController; + programController: ProgramController; + physicsObjects: Array; +}; + +let instance: World; + +export class World { + state: SimulationStates; + #internals: Internals; + carController: CarController; + + constructor() { + if (instance) { + throw new Error('Only one instance of world is allowed'); + } + // eslint-disable-next-line consistent-this + instance = this; + + // initialization + this.state = 'unintialized'; + this.#internals = { + physicsController: new PhysicsController(), + renderController: new RenderController(), + programController: new ProgramController(), + physicsObjects: [], + }; + this.carController = new CarController(settings); + } + + async init(code: string) { + if (this.state === 'running') { + return; + } + + + this.state = 'loading'; + + const { + physicsController, + renderController, + programController, + } = this.#internals; + + await physicsController.init(); + renderController.init(); + programController.init(code); + this.carController.init(); + this.addFloor(); + this.state = 'ready'; + } + + addFloor() { + addCuboidPhysicsObject({ + width: 20, + height: 1, + length: 20, + color: new THREE.Color('white'), + dynamic: false, + position: new THREE.Vector3(0, 0, 0), + }); + } + + setRendererOutput(domElement: HTMLDivElement) { + console.log('Setting renderer output'); + this.#internals.renderController.setRendererOutput(domElement); + } + + startSimulation() { + if (this.state === 'ready') { + this.state = 'running'; + } + window.requestAnimationFrame(this.#step.bind(this)); + } + + stopSimulation() { + if (this.state === 'running') { + this.state = 'ready'; + } + } + + castRay(ray: Ray, maxDistance: number) { + const { physicsController } = this.#internals; + return physicsController.castRay(ray, maxDistance); + } + + addRigidBody( + mesh: THREE.Mesh, + rigidBodyDesc: RigidBodyDesc, + colliderDesc: ColliderDesc, + ) { + const { physicsController, renderController, physicsObjects } + = this.#internals; + + renderController.addMesh(mesh); + const rigidBody = physicsController.createRigidBody(rigidBodyDesc); + const collider = physicsController.createCollider(colliderDesc, rigidBody); + const physicsObject = new PhysicsObject(mesh, rigidBody, collider); + physicsObjects.push(physicsObject); + return physicsObject; + } + + #step(timestamp:number): void { + const { + programController, + physicsController, + renderController, + physicsObjects, + } = this.#internals; + + programController.step(timestamp); + + // Apply the forces of the car + this.carController.step(timestamp); + + // Calculate the new location of each physics object (including the car) + physicsController.step(timestamp); + + // Update the location of the mesh + for (const physicsObject of physicsObjects) { + physicsObject.step(timestamp); + } + + // Render the scene + renderController.step(timestamp); + + if (this.state === 'running') { + window.requestAnimationFrame(this.#step.bind(this)); + } + } +} +export { instance }; diff --git a/src/bundles/robot_simulation/three-rapier-controller/constants/states.ts b/src/bundles/robot_simulation/three-rapier-controller/constants/states.ts deleted file mode 100644 index 1cbde3c0b..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/constants/states.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const simulationStates = ['idle', 'loading', 'ready', 'error'] as const; - -export type SimulationStates = typeof simulationStates[number]; diff --git a/src/bundles/robot_simulation/three-rapier-controller/init.ts b/src/bundles/robot_simulation/three-rapier-controller/init.ts deleted file mode 100644 index 55ccd8254..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/init.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as THREE from 'three'; -// @ts-ignore-next-line - -import type Rapier from '@dimforge/rapier3d-compat'; -import context from 'js-slang/context'; - -import initRapier from './render/physics/RAPIER'; -import { physicsOptions, sceneOptions } from './options'; -import { type PhysicsObject } from './render/physics/physics'; -import { type SimulationStates } from './constants/states'; -import TickManager from './render/controllers/tickManager'; -import { init_meshes } from './mesh/init_meshes'; -import { RayCastedVehicleController } from './render/controllers/RayCastedVehicleController'; -import { carSettings } from './render/controllers/RayCastedVehicleController/carTuning'; -import { runECEvaluatorByJoel, type IOptions } from 'js-slang'; -import { getSimulation, setSimulation } from './render/simulation'; - -let RAPIER: typeof Rapier; - - -export type RobotSimulation = { - state: Extract; -} | { - state: Exclude, - scene: THREE.Scene, - camera: THREE.PerspectiveCamera, - renderer: THREE.WebGLRenderer, - renderWidth: number, - renderHeight: number, - renderAspectRatio: number, - RAPIER: typeof Rapier, - physicsWorld: Rapier.World, - physicsObjects: Array, - robot?: RayCastedVehicleController, - EceIterator: Generator -}; - -export const initEngines = async (code:string) => { - const currentSimulation = getSimulation(); - - if (currentSimulation.state !== 'idle') { - console.log('Engine already initialized. Skipping initialization'); - return; - } - - const options : Partial = { - originalMaxExecTime: Infinity, - scheduler: 'preemptive', - stepLimit: Infinity, - throwInfiniteLoops: false, - useSubst: false, - }; - const it = runECEvaluatorByJoel(code, context, options); - - setSimulation({ state: 'loading' }); - RAPIER = await initRapier(); - - const renderTickManager = new TickManager(); - const physicsWorld = new RAPIER.World(physicsOptions.GRAVITY); - - const physicsObjects:Array = []; - - const scene = new THREE.Scene(); - scene.background = new THREE.Color(0xffffff); - const light = new THREE.AmbientLight(0xffffff); - scene.add(light); - - const renderWidth = sceneOptions.width; - const renderHeight = sceneOptions.height; - const renderAspectRatio = renderWidth / renderHeight; - - const camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); - // camera.translateY(5); - camera.translateX(2); - camera.translateY(-1.5); - camera.lookAt(new THREE.Vector3(0, -1.5, 0)); - - const renderer = new THREE.WebGLRenderer({ antialias: true }); - renderer.setSize(renderWidth, renderHeight); - renderer.setPixelRatio(window.devicePixelRatio * 1.5); - - const robotSimulation :RobotSimulation = { - state: 'ready', - scene, - camera, - renderer, - renderWidth, - renderHeight, - renderAspectRatio, - RAPIER, - physicsWorld, - physicsObjects, - EceIterator: it, - }; - - setSimulation(robotSimulation); - - renderTickManager.startLoop(); - - init_meshes(); - - const robot = new RayCastedVehicleController(carSettings); - setSimulation({ - ...robotSimulation, - robot, - }); -}; - -export { RAPIER }; diff --git a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts b/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts deleted file mode 100644 index 870df6686..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/mesh/init_meshes.ts +++ /dev/null @@ -1,33 +0,0 @@ -import * as THREE from 'three'; -import { addPhysics } from '../render/physics/physics'; -import { getSimulation } from '../render/simulation'; - -const addFloor = () => { - const size = 100; - const height = 1; - - const geometry = new THREE.BoxGeometry(size, height, size); - const material = new THREE.MeshPhysicalMaterial({ - color: new THREE.Color(0xFFC0CB), - side: THREE.DoubleSide, - }); - const cube = new THREE.Mesh(geometry, material); - cube.position.set(0, -2, 0); - - addPhysics(cube, 'fixed', true, undefined, 'cuboid', { - width: size / 2, - height: height / 2, - depth: size / 2, - }); - - return cube; -}; - -export const init_meshes = () => { - const simulation = getSimulation(); - if (simulation.state !== 'ready') { - throw new Error('Cannot initialized mesh before simulation is ready'); - } - - addFloor(); -}; diff --git a/src/bundles/robot_simulation/three-rapier-controller/options.ts b/src/bundles/robot_simulation/three-rapier-controller/options.ts deleted file mode 100644 index 07242b664..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/options.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Vector3 } from 'three'; - -export const physicsOptions = { - GRAVITY: new Vector3(0.0, -9.81, 0.0), -} as const; - -export const sceneOptions = { - height: 600, - width: 800, -} as const; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts deleted file mode 100644 index 185896f79..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/carTuning.ts +++ /dev/null @@ -1,40 +0,0 @@ -export type CarSettings = { - chassis: { - length: number; - height: number; - width: number; - weight: number; - }; - wheel: { - restHeight:number; - diameter: number; - maxSuspensionLength: number; - suspension: { stiffness: number; damping: number }; - buffer: number; - sideForceMultiplier: number; - }; - turning: { sensitivity: number }; -}; - -export const carSettings: CarSettings = { - chassis: { - length: 0.18, // in meters - height: 0.095, // in meters - width: 0.145, // in meters - weight: 0.6, // in kg - }, - wheel: { - restHeight: 0.03, - diameter: 0.055, - maxSuspensionLength: 0.1, - suspension: { - stiffness: 70, - damping: 10, - }, - buffer: 0.02, - sideForceMultiplier: -20, - }, - turning: { - sensitivity: 0.5, - }, -} as const; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts deleted file mode 100644 index 9160f33cc..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/RayCastedVehicleController/index.ts +++ /dev/null @@ -1,192 +0,0 @@ -import * as THREE from 'three'; - -import { - addPhysics, - type PhysicsObject, - type UpdateFunction, -} from '../../physics/physics'; -import { carSettings, type CarSettings } from './carTuning'; -import { quat, vec3 } from '../../physics/helpers'; -import { RAPIER } from '../../../init'; -import { getSimulation } from '../../simulation'; - -const getWheelDisplacements = (settings: CarSettings) => { - const rayDisplacements = [ - { - x: settings.chassis.width / 2 + settings.wheel.buffer, - y: 0, - z: settings.chassis.length / 2, - }, - { - x: -(settings.chassis.width / 2 + settings.wheel.buffer), - y: 0, - z: settings.chassis.length / 2, - }, - { - x: settings.chassis.width / 2 + settings.wheel.buffer, - y: 0, - z: -settings.chassis.length / 2, - }, - { - x: -(settings.chassis.width / 2 + settings.wheel.buffer), - y: 0, - z: -settings.chassis.length / 2, - }, - ]; - return rayDisplacements; -}; - -const createUpdateFunction = (settings: CarSettings): UpdateFunction => { - const simulation = getSimulation(); - if (simulation.state !== 'ready') { - throw new Error('Simulation not ready'); - } - - const world = simulation.physicsWorld; - - - const wheelRelativePositionFromOrigin = getWheelDisplacements(settings); - - const globalOrigin = { - x: 0, - y: 0, - z: 0, - }; - const downDirection = { - x: 0, - y: -1, - z: 0, - }; - - const ray1 = new RAPIER.Ray(globalOrigin, downDirection); - const ray2 = new RAPIER.Ray(globalOrigin, downDirection); - const ray3 = new RAPIER.Ray(globalOrigin, downDirection); - const ray4 = new RAPIER.Ray(globalOrigin, downDirection); - - const suspensionRays = [ray1, ray2, ray3, ray4]; - - - const updateFunction: UpdateFunction = ({ - mesh, - collider, - rigidBody: chassis, - }) => { - chassis.resetForces(true); - chassis.resetTorques(true); - - const chassisRotationQuat = quat(chassis.rotation()); - const velocity = chassis.linvel(); - - const wheelOrigins = [0, 1, 2, 3].map((wheelIndex) => { - const origin = chassis.translation(); - const rayDisplacement = vec3( - wheelRelativePositionFromOrigin[wheelIndex], - ) - .applyQuaternion(chassisRotationQuat); - origin.x += rayDisplacement.x; - origin.y += rayDisplacement.y; - origin.z += rayDisplacement.z; - return origin; - }); - - // Setting the suspension rays - for (let wheelIndex = 0; wheelIndex < 4; wheelIndex++) { - const ray = suspensionRays[wheelIndex]; - const rayDirection = vec3(downDirection) - .applyQuaternion(chassisRotationQuat); - ray.origin = wheelOrigins[wheelIndex]; - ray.dir = rayDirection; - } - - // Suspension forces - for (let wheelIndex = 0; wheelIndex < 4; wheelIndex++) { - const ray = suspensionRays[wheelIndex]; - const toiResult = world.castRay( - ray, - carSettings.wheel.maxSuspensionLength, - true, - 1, - ); - - if (toiResult === null) { - continue; - } - - const force - = carSettings.wheel.suspension.stiffness - * (carSettings.wheel.restHeight - toiResult.toi + (carSettings.chassis.height / 2)) - - carSettings.wheel.suspension.damping * velocity.y; - - const direction = vec3({ - x: 0, - y: force, - z: 0, - }) - .applyQuaternion( - chassisRotationQuat, - ); - - chassis.addForceAtPoint(direction, ray.origin, true); - } - - - // Siding force - const horizonalVelocity = { - x: velocity.x, - y: 0, - z: velocity.z, - }; - const sideForceMagnitude = vec3(horizonalVelocity) - .projectOnVector( - vec3({ - x: 1, - y: 0, - z: 0, - }) - .applyQuaternion(chassisRotationQuat), - ) - .multiplyScalar(carSettings.wheel.sideForceMultiplier); - - chassis.addForce(sideForceMagnitude, true); - }; - - return updateFunction; -}; - -const createChassis = (setting: CarSettings) => { - const { - chassis: { height, width, length }, - } = setting; - const geometry = new THREE.BoxGeometry(width, height, length); - const material = new THREE.MeshPhysicalMaterial({ - color: new THREE.Color(0x000000), - side: THREE.DoubleSide, - }); - const chassis = new THREE.Mesh(geometry, material); - - const chassisPhysicsObject = addPhysics( - chassis, - 'dynamic', - true, - createUpdateFunction(carSettings), - 'cuboid', - { - width: width / 2, - height: height / 2, - depth: length / 2, - }, - ); - - chassisPhysicsObject.collider.setMass(carSettings.chassis.weight); - return chassisPhysicsObject; -}; - - -export class RayCastedVehicleController { - carSettings: CarSettings; - chassisPhysicsObject: PhysicsObject; - constructor(settings: CarSettings) { - this.carSettings = settings; - this.chassisPhysicsObject = createChassis(settings); - } -} diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts b/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts deleted file mode 100644 index a60257b55..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/render/controllers/tickManager.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { getSimulation } from '../simulation'; - -type Frame = XRFrame | null; - -export type TickData = { - timestamp: number - timeDiff: number - fps: number - frame: Frame -}; - -const localTickData: TickData = { - timestamp: 0, - timeDiff: 0, - fps: 0, - frame: null, -}; - -const localFrameOpts = { - data: localTickData, -}; - -const frameEvent = new MessageEvent('tick', localFrameOpts); - -class TickManager extends EventTarget { - timestamp: number; - timeDiff: number; - frame: Frame; - lastTimestamp: number; - fps: number; - - constructor({ timestamp, timeDiff, frame } = localTickData) { - super(); - - this.timestamp = timestamp; - this.timeDiff = timeDiff; - this.frame = frame; - this.lastTimestamp = 0; - this.fps = 0; - } - - startLoop() { - const simulation = getSimulation(); - if (simulation.state !== 'ready') { - throw new Error('Tried to start a loop before starting simulation'); - } - const renderer = simulation.renderer; - const physics = simulation.physicsWorld; - const physicsObjects = simulation.physicsObjects; - const scene = simulation.scene; - const camera = simulation.camera; - const ece = simulation.EceIterator; - - - if (!renderer) { - throw new Error('Updating Frame Failed : Uninitialized Renderer'); - } - - const animate = (timestamp: number, frame: Frame) => { - const now = performance.now(); - this.timestamp = timestamp ?? now; - this.timeDiff = timestamp - this.lastTimestamp; - - const timeDiffCapped = Math.min(Math.max(this.timeDiff, 0), 100); - - // physics - physics.step(); - - for (let i = 0; i < physicsObjects.length; i++) { - const po = physicsObjects[i]; - const autoAnimate = po.autoAnimate; - - if (autoAnimate) { - const mesh = po.mesh; - const collider = po.collider; - mesh.position.copy(collider.translation() as THREE.Vector3); - mesh.quaternion.copy(collider.rotation() as THREE.Quaternion); - } - - const fn = po.fn; - if (fn) { - fn(po); - } - } - - // performance tracker start - this.fps = 1000 / this.timeDiff; - this.lastTimestamp = this.timestamp; - - renderer.render(scene, camera); - this.tick(timestamp, timeDiffCapped, this.fps, frame); - console.log(ece.next()); - }; - - renderer.setAnimationLoop(animate); - } - - tick(timestamp: number, timeDiff: number, fps: number, frame: Frame) { - localTickData.timestamp = timestamp; - localTickData.frame = frame; - localTickData.timeDiff = timeDiff; - localTickData.fps = fps; - this.dispatchEvent(frameEvent); - } -} - -export default TickManager; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/RAPIER.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/RAPIER.ts deleted file mode 100644 index bb3b56c64..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/render/physics/RAPIER.ts +++ /dev/null @@ -1,7 +0,0 @@ -const initRapier = async () => { - let r = await import('@dimforge/rapier3d-compat'); - await r.init(); - return r; -}; - -export default initRapier; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts b/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts deleted file mode 100644 index 23f9c03be..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/render/physics/physics.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type Rapier from '@dimforge/rapier3d-compat'; - -import { RAPIER } from '../../init'; -import { getSimulation } from '../simulation'; - -export type UpdateFunction = ({ - mesh, - collider, - rigidBody, -}: { - mesh: THREE.Mesh; - collider: Rapier.Collider; - rigidBody: Rapier.RigidBody; -}) => void; - -export type PhysicsObject = { - mesh: THREE.Mesh; - collider: Rapier.Collider; - rigidBody: Rapier.RigidBody; - fn?: UpdateFunction; - autoAnimate: boolean; -}; - -export const addPhysics = ( - mesh: THREE.Mesh, - rigidBodyType: string, - autoAnimate: boolean = true, // update the mesh's position and quaternion based on the physics world every frame - postPhysicsFn?: UpdateFunction, - colliderType?: string, - colliderSettings?: any, -) => { - const simulation = getSimulation(); - if (simulation.state !== 'ready') { - throw new Error( - 'Tried to add a physic object before initializing the simulation.', - ); - } - - const physics = simulation.physicsWorld; - const physicsObjects = simulation.physicsObjects; - const scene = simulation.scene; - - const rigidBodyDesc = (RAPIER.RigidBodyDesc as any)[rigidBodyType](); - rigidBodyDesc.setTranslation( - mesh.position.x, - mesh.position.y, - mesh.position.z, - ); - - // * Responsible for collision response - const rigidBody = physics.createRigidBody(rigidBodyDesc); - - let colliderDesc; - - switch (colliderType) { - case 'cuboid': - { - const { width, height, depth } = colliderSettings; - colliderDesc = RAPIER.ColliderDesc.cuboid(width, height, depth); - } - break; - - case 'ball': - { - const { radius } = colliderSettings; - colliderDesc = RAPIER.ColliderDesc.ball(radius); - } - break; - - case 'capsule': - { - const { halfHeight, radius } = colliderSettings; - colliderDesc = RAPIER.ColliderDesc.capsule(halfHeight, radius); - } - break; - - default: - colliderDesc = RAPIER.ColliderDesc.trimesh( - mesh.geometry.attributes.position.array as Float32Array, - mesh.geometry.index?.array as Uint32Array, - ); - break; - } - - if (!colliderDesc) { - console.error('Collider Mesh Error: convex mesh creation failed.'); - } - - // * Responsible for collision detection - const collider = physics.createCollider(colliderDesc, rigidBody); - - const physicsObject: PhysicsObject = { - mesh, - collider, - rigidBody, - fn: postPhysicsFn, - autoAnimate, - }; - - physicsObjects.push(physicsObject); - scene.add(mesh); - - return physicsObject; -}; diff --git a/src/bundles/robot_simulation/three-rapier-controller/render/simulation.ts b/src/bundles/robot_simulation/three-rapier-controller/render/simulation.ts deleted file mode 100644 index 4915461c0..000000000 --- a/src/bundles/robot_simulation/three-rapier-controller/render/simulation.ts +++ /dev/null @@ -1,18 +0,0 @@ -import context from 'js-slang/context'; -import { type RobotSimulation } from '../init'; - -console.log('Simulation is being initialized'); - -const initial_simulation: RobotSimulation = { state: 'idle' }; -const contextState = context.moduleContexts.robot_simulation.state; - -if (contextState === null) { - context.moduleContexts.robot_simulation.state = { simulation: initial_simulation }; - console.log('simulation is being set to initial state'); -} - -export const getSimulation = ():RobotSimulation => context.moduleContexts.robot_simulation.state.simulation; -export const setSimulation = (newSimulation:RobotSimulation):void => { - context.moduleContexts.robot_simulation.state.simulation = newSimulation; - console.log('Setting new value into simulation', newSimulation); -}; diff --git a/src/tabs/RobotSimulation/components/Main.tsx b/src/tabs/RobotSimulation/components/Main.tsx index 584b0f622..5c3b621ff 100644 --- a/src/tabs/RobotSimulation/components/Main.tsx +++ b/src/tabs/RobotSimulation/components/Main.tsx @@ -20,7 +20,7 @@ export default function Main({ context }: { context:DebuggerContext }): JSX.Elem setIsCanvasShowing(false); }} > - + ); diff --git a/src/tabs/RobotSimulation/components/Modal.tsx b/src/tabs/RobotSimulation/components/Modal.tsx index f4d12556a..683b9e401 100644 --- a/src/tabs/RobotSimulation/components/Modal.tsx +++ b/src/tabs/RobotSimulation/components/Modal.tsx @@ -1,4 +1,4 @@ -import { type CSSProperties, type ReactNode } from 'react'; +import { useEffect, type CSSProperties, type ReactNode } from 'react'; type ModalProps = { isOpen: boolean; diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 4006e29e7..88ee9ac16 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -1,50 +1,68 @@ import { useRef, type CSSProperties, useEffect, useState } from 'react'; -import { type SimulationStates } from '../../../../bundles/robot_simulation/three-rapier-controller/constants/states'; import type { DebuggerContext } from '../../../../typings/type_helpers'; +import { + type SimulationStates, + type World, +} from '../../../../bundles/robot_simulation/simulation/world'; + const CanvasWrapperStyle: CSSProperties = { width: 800, height: 600, backgroundColor: 'black', }; - -export default function SimulationCanvas({ context }: { context:DebuggerContext }) { +export default function SimulationCanvas({ + context, + isOpen, +}: { + context: DebuggerContext; + isOpen: boolean; +}) { const ref = useRef(null); - const [currentState, setCurrentState] = useState('idle'); - const simulation = context.context.moduleContexts.robot_simulation.state.simulation; + const [currentState, setCurrentState] + = useState('unintialized'); + const world = context.context.moduleContexts.robot_simulation.state + .world as World; useEffect(() => { const startThreeAndRapierEngines = async () => { - console.log(simulation.state, currentState); - setCurrentState(simulation.state); + setCurrentState(world.state); }; const attachRenderDom = () => { - if (simulation.state !== 'ready') { - throw new Error('Tried to attach dom to an unavailable simulation'); - } - const renderer = simulation.renderer; - if (ref.current && renderer) { - ref.current.replaceChildren(renderer.domElement); + if (ref.current) { + world.setRendererOutput(ref.current); } }; - if (currentState === 'idle') { + if (currentState === 'unintialized') { startThreeAndRapierEngines(); } - if (currentState === 'ready') { + if (currentState === 'ready' || currentState === 'running') { attachRenderDom(); } if (currentState === 'loading') { setTimeout(() => { - setCurrentState('idle'); + setCurrentState('unintialized'); }, 500); } }, [currentState]); - return <> -
{currentState}
- ; + useEffect(() => { + if (isOpen) { + world.startSimulation(); + } else { + world.stopSimulation(); + } + }, [isOpen]); + + return ( + <> +
+ {currentState} +
+ + ); } diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/index.tsx index 9bca5856d..488ecaca6 100644 --- a/src/tabs/RobotSimulation/index.tsx +++ b/src/tabs/RobotSimulation/index.tsx @@ -26,7 +26,8 @@ export default { * @returns {boolean} */ toSpawn(context: DebuggerContext) { - return context.context.moduleContexts.robot_simulation.state.simulation?.state !== 'idle'; + console.log(context.context.moduleContexts.robot_simulation.state.world); + return context.context.moduleContexts.robot_simulation.state.world?.state !== 'idle'; }, /** From ff86553e5301ccf27606e44317193e5c0618d12f Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 7 Nov 2023 09:33:40 +0000 Subject: [PATCH 16/93] Massive changes again --- src/bundles/robot_simulation/functions.ts | 58 +++++++++- src/bundles/robot_simulation/index.ts | 14 ++- .../controllers/{ => car}/car_controller.ts | 101 +++++++++++++++--- .../controllers/car/speed_controller.ts | 72 +++++++++++++ .../controllers/physics/physics_controller.ts | 6 +- .../controllers/physics/speed_controller.ts | 64 ----------- .../controllers/program_controller.ts | 23 +++- .../controllers/render_controller.ts | 13 ++- .../simulation/controllers/time_controller.ts | 89 +++++++++++++++ .../simulation/primitives/physics_object.ts | 71 +++++------- .../simulation/primitives/step_cache.ts | 29 +++++ .../robot_simulation/simulation/types.ts | 3 + .../robot_simulation/simulation/world.ts | 80 +++++++++----- src/tabs/RobotSimulation/components/TabUi.tsx | 3 +- 14 files changed, 454 insertions(+), 172 deletions(-) rename src/bundles/robot_simulation/simulation/controllers/{ => car}/car_controller.ts (69%) create mode 100644 src/bundles/robot_simulation/simulation/controllers/car/speed_controller.ts delete mode 100644 src/bundles/robot_simulation/simulation/controllers/physics/speed_controller.ts create mode 100644 src/bundles/robot_simulation/simulation/controllers/time_controller.ts create mode 100644 src/bundles/robot_simulation/simulation/primitives/step_cache.ts create mode 100644 src/bundles/robot_simulation/simulation/types.ts diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 79bb25ce6..4762a0b15 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + /** * Robot simulation * @module robot_simulation @@ -6,20 +8,59 @@ import context from 'js-slang/context'; import { getWorld } from './simulation'; - +import { type MotorsOptions } from './simulation/controllers/car/car_controller'; export function show() { console.log('This is the show function'); } - -export function init_new_simulation() { +export function init_simulation() { const code = context.unTypecheckedCode[0]; const world = getWorld(); world.init(code); } -export function ev3_runToRelativePosition(motor:number, position: number, speed:number) { +export function ev3_motorA() { + const world = getWorld(); + if (world.state === 'loading') { + return -1; + } + + return world.carController.motorA(); +} + +export function ev3_motorB() { + const world = getWorld(); + if (world.state === 'loading') { + return -1; + } + + return world.carController.motorB(); +} + +export function ev3_motorC() { + const world = getWorld(); + if (world.state === 'loading') { + return -1; + } + + return world.carController.motorC(); +} + +export function ev3_motorD() { + const world = getWorld(); + if (world.state === 'loading') { + return -1; + } + + return world.carController.motorD(); +} + +export function ev3_runToRelativePosition( + motor: MotorsOptions, + position: number, + speed: number, +) { const world = getWorld(); if (world.state === 'loading') { return; @@ -27,3 +68,12 @@ export function ev3_runToRelativePosition(motor:number, position: number, speed: world.carController.runToRelativePosition(motor, position, speed); } + +export function ev3_pause(time: number) { + const world = getWorld(); + if (world.state === 'loading') { + return; + } + + world.carController.pause(time); +} diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 9862d1e85..a4bed35a4 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1,4 +1,10 @@ -export { show, init_simulation, is_ready, forward, in_simulation, backward, ev3_move_forward, ev3_runToRelativePosition } from './functions'; - - -export { init_new_simulation, new_ev3_runToRelativePosition } from './functions'; +export { + show, + ev3_motorA, + ev3_motorB, + ev3_motorC, + ev3_motorD, + init_simulation, + ev3_runToRelativePosition, + ev3_pause, +} from './functions'; diff --git a/src/bundles/robot_simulation/simulation/controllers/car_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts similarity index 69% rename from src/bundles/robot_simulation/simulation/controllers/car_controller.ts rename to src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts index 746d82c8c..fff3fbee5 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts @@ -1,15 +1,20 @@ import * as THREE from 'three'; +// import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; + import { addCuboidPhysicsObject, type PhysicsObject, -} from '../primitives/physics_object'; -import { type Vector } from './physics/types'; +} from '../../primitives/physics_object'; +import { type Vector } from '../physics/types'; import { type Ray } from '@dimforge/rapier3d-compat'; -import { RAPIER } from './physics/physics_controller'; -import { nullVector } from './physics/helpers'; -import { vec3 } from '../controllers/physics/helpers'; -import { instance } from '../world'; -import { SpeedController } from './physics/speed_controller'; +import { RAPIER } from '../physics/physics_controller'; +import { nullVector, vec3 } from '../physics/helpers'; +import { SpeedController } from './speed_controller'; + +import { instance } from '../../world'; +import { type Steppable } from '../../types'; + + export type CarSettings = { chassis: { @@ -52,7 +57,16 @@ export const settings: CarSettings = { }, } as const; -class Wheel { +const motors = { + A: 0, + B: 1, + C: 2, + D: 3, +} as const; + +export type MotorsOptions = (typeof motors)[keyof typeof motors]; + +class Wheel implements Steppable { static downDirection = { x: 0, y: -1, @@ -130,7 +144,7 @@ class Wheel { } } -export class CarController { +export class CarController implements Steppable { carSettings: CarSettings; chassis: PhysicsObject | null; wheelDisplacements: Vector[]; @@ -161,13 +175,6 @@ export class CarController { ); } - runToRelativePosition(motor:number, position:number, speed:number) { - console.log('RUN TO RELATIVE POSITION', motor, position, speed); - const selectedMotor = motor === 0 ? this.leftMotor : this.rightMotor; - const time = Math.abs(position / speed) * 1000; - selectedMotor!.setSpeed(speed, time); - } - #createChassis() { const { width, height, length, mass } = this.carSettings.chassis; @@ -218,6 +225,68 @@ export class CarController { ]; } + #getMotor(motor: MotorsOptions): SpeedController | null { + const motorMapping: Record = { + 0: null, + 1: this.leftMotor, + 2: this.rightMotor, + 3: null, + }; + + return motorMapping[motor]; + } + + // EV3 Functions + motorA(): MotorsOptions { + return motors.A; + } + + motorB(): MotorsOptions { + return motors.B; + } + + motorC(): MotorsOptions { + return motors.C; + } + + motorD(): MotorsOptions { + return motors.D; + } + + /** + * + * @param motor + * @param position in degrees + * @param speed in degrees per second + */ + runToRelativePosition(motor: MotorsOptions, position: number, speed: number) { + const selectedMotor = this.#getMotor(motor); + if (!selectedMotor) { + return; + } + + const tuningAmount = 3; + + const time = Math.abs(position / speed) * 1000; + + const speedInMetersPerSecond + = (speed / 360) * Math.PI * this.carSettings.wheel.diameter * tuningAmount; + + selectedMotor!.setSpeed(speedInMetersPerSecond, time); + } + + motorGetSpeed(motor: MotorsOptions) { + const selectedMotor = this.#getMotor(motor); + if (!selectedMotor) { + return 0; + } + return selectedMotor!.speed; + } + + pause(time: number) { + instance.pauseProgramController(time); + } + step(timestamp: number) { this.chassis!.removeForcesAndTorques(); this.wheels.forEach((wheel) => { diff --git a/src/bundles/robot_simulation/simulation/controllers/car/speed_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/speed_controller.ts new file mode 100644 index 000000000..afbd9e59d --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/car/speed_controller.ts @@ -0,0 +1,72 @@ +import { type PhysicsObject } from '../../primitives/physics_object'; +import { vec3 } from '../physics/helpers'; +import { type Vector } from '../physics/types'; + +import { instance } from '../../world'; +import { type Steppable } from '../../types'; + +export class SpeedController implements Steppable { + physicsObject: PhysicsObject; + displacement: Vector; + + speed: number; + + displacementVector: THREE.Vector3; + targetVelocity: THREE.Vector3; + + constructor(physicsObject: PhysicsObject, displacement: Vector) { + this.physicsObject = physicsObject; + this.displacement = displacement; + this.speed = 0; + + this.displacementVector = vec3(this.displacement); + this.targetVelocity = vec3({ + x: 0, + y: 0, + z: this.speed, + }); + } + + setSpeed(speed: number, duration: number) { + this.speed = speed; + + instance.setTimeout(() => { + this.speed = 0; + }, duration); + } + + step(_: number) { + this.displacementVector.copy(this.displacement as THREE.Vector3); + this.targetVelocity.copy({ + x: 0, + y: 0, + z: this.speed, + } as THREE.Vector3); + + const worldVelocity = this.physicsObject.worldVelocity( + this.displacementVector.clone(), + ); + + const velocityDelta = this.physicsObject + .worldDirection(this.targetVelocity.clone()) + .sub(worldVelocity); + + const impulse = velocityDelta + .multiplyScalar(this.physicsObject.getMass() / 5) + .projectOnPlane( + vec3({ + x: 0, + y: 1, + z: 0, + }), + ); + + this.physicsObject + .getRigidBody() + .applyImpulseAtPoint( + impulse, + this.physicsObject.worldTranslation(this.displacementVector.clone()), + true, + ); + } +} diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts index e485de6bd..03d139cd3 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts @@ -1,13 +1,13 @@ import Rapier, { type Ray, type RigidBody } from '@dimforge/rapier3d-compat'; +import { type Steppable } from '../../types'; let RAPIER: typeof Rapier; - export const physicsOptions = { GRAVITY: new Rapier.Vector3(0.0, -9.81, 0.0), } as const; -export class PhysicsController { +export class PhysicsController implements Steppable { isInitialized = false; RAPIER: typeof Rapier | null; @@ -36,7 +36,7 @@ export class PhysicsController { return this.#world!.createRigidBody(rigidBodyDesc); } - castRay(ray:Ray, maxDistance:number):number | null { + castRay(ray: Ray, maxDistance: number): number | null { const result = this.#world!.castRay(ray, maxDistance, true); if (result === null) { return null; diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/speed_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/speed_controller.ts deleted file mode 100644 index f17945aec..000000000 --- a/src/bundles/robot_simulation/simulation/controllers/physics/speed_controller.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { type PhysicsObject } from '../../primitives/physics_object'; -import { vec3 } from './helpers'; -import { type Vector } from './types'; - -export class SpeedController { - physicsObject: PhysicsObject; - displacement: Vector; - time:number; - - speed: number; - endTime:number; - - displacementVector:THREE.Vector3; - targetVelocity: THREE.Vector3; - - - constructor(physicsObject: PhysicsObject, displacement: Vector) { - this.physicsObject = physicsObject; - this.displacement = displacement; - this.speed = 0; - this.endTime = Infinity; - this.time = 0; - - this.displacementVector = vec3(this.displacement); - this.targetVelocity = vec3({ - x: 0, - y: 0, - z: this.speed, - }); - } - - - setSpeed(speed: number, duration: number) { - this.speed = speed; - this.endTime = this.time + duration; - } - - step(timestamp: number) { - console.log(this.speed, this.endTime, this.time); - this.time = timestamp; - if (this.time > this.endTime) { - console.log('UNSET SPEED'); - this.speed = 0; - this.endTime = Infinity; - } - - this.displacementVector.copy(this.displacement as THREE.Vector3); - this.targetVelocity.copy({ - x: 0, - y: 0, - z: this.speed, - } as THREE.Vector3); - - const worldVelocity = this.physicsObject.worldVelocity(this.displacementVector.clone()); - const velocityDelta = this.physicsObject.worldDirection(this.targetVelocity.clone()) - .sub(worldVelocity); - - - const impulse = velocityDelta.multiplyScalar(this.physicsObject.getMass() / 50); - - this.physicsObject.getRigidBody() - .applyImpulseAtPoint(impulse, this.physicsObject.worldTranslation(this.displacementVector.clone()), true); - } -} diff --git a/src/bundles/robot_simulation/simulation/controllers/program_controller.ts b/src/bundles/robot_simulation/simulation/controllers/program_controller.ts index 45f0b8108..df9407233 100644 --- a/src/bundles/robot_simulation/simulation/controllers/program_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/program_controller.ts @@ -1,13 +1,26 @@ import { type IOptions, runECEvaluatorByJoel } from 'js-slang'; import context from 'js-slang/context'; +import { type Steppable } from '../types'; -export class ProgramController { +import { instance } from '../world'; + +export class ProgramController implements Steppable { #code: string; #iterator: Generator | null; + #isPaused:boolean; constructor() { this.#code = ''; this.#iterator = null; + this.#isPaused = false; + } + + pause(time: number) { + this.#isPaused = true; + + instance.setTimeout(() => { + this.#isPaused = false; + }, time); } init(code: string) { @@ -20,10 +33,14 @@ export class ProgramController { throwInfiniteLoops: false, useSubst: false, }; - this.#iterator = runECEvaluatorByJoel(code, context, options); + + this.#iterator = runECEvaluatorByJoel(this.#code, context, options); } step(_: number) { - return this.#iterator!.next(); + if (this.#isPaused) { + return; + } + this.#iterator!.next(); } } diff --git a/src/bundles/robot_simulation/simulation/controllers/render_controller.ts b/src/bundles/robot_simulation/simulation/controllers/render_controller.ts index 0b277549f..b49359d53 100644 --- a/src/bundles/robot_simulation/simulation/controllers/render_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/render_controller.ts @@ -1,4 +1,8 @@ import * as THREE from 'three'; +// eslint-disable-next-line import/extensions +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; + +import { type Steppable } from '../types'; export const sceneOptions = { @@ -7,10 +11,11 @@ export const sceneOptions = { } as const; -export class RenderController { +export class RenderController implements Steppable { #scene: THREE.Scene; #camera: THREE.Camera; #renderer: THREE.WebGLRenderer; + #controls: OrbitControls; constructor() { this.#scene = new THREE.Scene(); @@ -19,6 +24,7 @@ export class RenderController { this.#camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); this.#renderer = new THREE.WebGLRenderer({ antialias: true }); + this.#controls = new OrbitControls(this.#camera, this.#renderer.domElement); } init() { @@ -26,7 +32,7 @@ export class RenderController { const light = new THREE.AmbientLight(0xffffff); this.#scene.add(light); - this.#camera.translateY(3); + this.#camera.translateY(2); this.#camera.lookAt(new THREE.Vector3(0, -1.5, 0)); // this.#camera.translateX(0.8); @@ -47,5 +53,8 @@ export class RenderController { step(_: number) { this.#renderer.render(this.#scene, this.#camera); + this.#controls.update(); } } + +export { THREE }; diff --git a/src/bundles/robot_simulation/simulation/controllers/time_controller.ts b/src/bundles/robot_simulation/simulation/controllers/time_controller.ts new file mode 100644 index 000000000..08f4308e8 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/time_controller.ts @@ -0,0 +1,89 @@ +import { type Steppable } from '../types'; + +export type TimeoutFunction = () => void; + +export class TimeController implements Steppable { + startTime:number | null; + pauseTime:number | null; // Time of the last pause + pausedTime: number; // Total time spent paused + isPaused:boolean; + elapsedTime:number; + timeoutCallbacks:Array<{ callback:TimeoutFunction, simulatedTime: number }>; + + constructor() { + this.startTime = null; + this.pauseTime = null; + this.isPaused = false; + this.pausedTime = 0; + this.elapsedTime = 0; + this.timeoutCallbacks = []; + } + + hasStarted() { + return this.startTime !== null; + } + + getElapsedTime() { + return this.elapsedTime; + } + + pause() { + if (!this.isPaused && this.hasStarted()) { + this.pauseTime = performance.now(); + this.isPaused = true; + } + } + + setTimeout(callback:TimeoutFunction, delay: number) { + if (!this.hasStarted()) { + throw new Error('Cannot set timeout before starting simulation'); + } + + const simulatedTime = this.elapsedTime + delay; + this.timeoutCallbacks.push({ + callback, + simulatedTime, + }); + } + + checkTimeouts() { + if (!this.hasStarted()) { + return; + } + + if (this.isPaused) { + return; + } + + // Filter out expired timeouts + // Could be done with a priority queue but this is simpler + const expiredTimeouts = this.timeoutCallbacks.filter((timeout) => timeout.simulatedTime <= this.elapsedTime); + for (const expiredTimeout of expiredTimeouts) { + const index = this.timeoutCallbacks.indexOf(expiredTimeout); + if (index !== -1) { + this.timeoutCallbacks.splice(index, 1); + expiredTimeout.callback(); + } + } + } + + step(timestamp:number):void { + // First step called + if (this.startTime === null) { + this.startTime = timestamp; + } + + // If paused, + if (this.isPaused && this.pauseTime !== null) { + // Update paused time + this.pausedTime += timestamp - this.pauseTime; + // unpause + this.pauseTime = null; + this.isPaused = false; + } + + // Not paused, so update elapsed time + this.elapsedTime = timestamp - this.startTime - this.pausedTime; + this.checkTimeouts(); + } +} diff --git a/src/bundles/robot_simulation/simulation/primitives/physics_object.ts b/src/bundles/robot_simulation/simulation/primitives/physics_object.ts index 6d9ea3080..724ed8fb4 100644 --- a/src/bundles/robot_simulation/simulation/primitives/physics_object.ts +++ b/src/bundles/robot_simulation/simulation/primitives/physics_object.ts @@ -5,20 +5,10 @@ import { RAPIER } from '../controllers/physics/physics_controller'; import { instance } from '../world'; import { nullVector, quat, vec3 } from '../controllers/physics/helpers'; -type PhysicsObjectCache = { - rotation?: THREE.Quaternion; - linearVelocity?: THREE.Vector3; - translation?: THREE.Vector3; - worldVelocity?: THREE.Vector3; - angularVelocity?: THREE.Vector3; -}; - - export class PhysicsObject { #rigidBody: Rapier.RigidBody; #mesh: THREE.Mesh; #collider: Rapier.Collider; - #cache: PhysicsObjectCache = {}; constructor( mesh: THREE.Mesh, @@ -38,7 +28,7 @@ export class PhysicsObject { this.#collider.setMass(mass); } - getMass():number { + getMass(): number { return this.#collider.mass(); } @@ -48,42 +38,24 @@ export class PhysicsObject { } rotation(): THREE.Quaternion { - if (this.#cache.rotation) { - return this.#cache.rotation; - } - this.#cache.rotation = quat(this.#rigidBody.rotation()); - return this.#cache.rotation; + return quat(this.#rigidBody.rotation()); } velocity(): THREE.Vector3 { - if (this.#cache.linearVelocity) { - return this.#cache.linearVelocity; - } - this.#cache.linearVelocity = vec3(this.#rigidBody.linvel()); - return this.#cache.linearVelocity; + return vec3(this.#rigidBody.linvel()); } angularVelocity(): THREE.Vector3 { - if (this.#cache.angularVelocity) { - return this.#cache.angularVelocity; - } - this.#cache.angularVelocity = vec3(this.#rigidBody.angvel()); - return this.#cache.angularVelocity; + return vec3(this.#rigidBody.angvel()); } translation(): THREE.Vector3 { - if (this.#cache.translation) { - return this.#cache.translation; - } - this.#cache.translation = vec3(this.#rigidBody.translation()); - return this.#cache.translation; - } - - invalidateCache() { - this.#cache = {}; + return vec3(this.#rigidBody.translation()); } - worldTranslation(localTranslation: THREE.Vector3 = nullVector.THREE): THREE.Vector3 { + worldTranslation( + localTranslation: THREE.Vector3 = nullVector.THREE, + ): THREE.Vector3 { const rotation = this.rotation(); const translation = this.translation(); @@ -97,19 +69,24 @@ export class PhysicsObject { return localDirection.applyQuaternion(rotation); } - distanceVectorOfPointToRotationalAxis(localPoint: THREE.Vector3 = nullVector.THREE) { - // TODO: Rewrite this such that it doesn't use clone()! - - return localPoint.clone() + distanceVectorOfPointToRotationalAxis( + localPoint: THREE.Vector3 = nullVector.THREE, + ) { + return localPoint + .clone() .projectOnVector(this.angularVelocity()) .negate() .add(localPoint); } - tangentialVelocityOfPoint(localPoint: THREE.Vector3 = nullVector.THREE): THREE.Vector3 { - const distanceVector = this.distanceVectorOfPointToRotationalAxis(localPoint); + tangentialVelocityOfPoint( + localPoint: THREE.Vector3 = nullVector.THREE, + ): THREE.Vector3 { + const distanceVector + = this.distanceVectorOfPointToRotationalAxis(localPoint); const angularVelocity = this.angularVelocity(); - const velocityMagnitude = distanceVector.length() * angularVelocity.length(); + const velocityMagnitude + = distanceVector.length() * angularVelocity.length(); const tangentialVelocity = this.worldDirection(localPoint) .cross(angularVelocity) @@ -120,7 +97,6 @@ export class PhysicsObject { return tangentialVelocity; } - worldVelocity(localPoint: THREE.Vector3 = nullVector.THREE): THREE.Vector3 { return this.tangentialVelocityOfPoint(localPoint) .add(this.velocity()); @@ -137,7 +113,6 @@ export class PhysicsObject { step(_: number) { this.#mesh.position.copy(this.#collider.translation() as THREE.Vector3); this.#mesh.quaternion.copy(this.#collider.rotation() as THREE.Quaternion); - this.invalidateCache(); } } @@ -145,7 +120,7 @@ type Cuboid = { width: number; height: number; length: number; - position? : THREE.Vector3; + position?: THREE.Vector3; color?: THREE.Color; dynamic?: boolean; }; @@ -167,7 +142,9 @@ export const addCuboidPhysicsObject = ({ const mesh = new THREE.Mesh(geometry, material); mesh.position.copy(position); - const rigidBodyDesc = dynamic ? RAPIER.RigidBodyDesc.dynamic() : Rapier.RigidBodyDesc.fixed(); + const rigidBodyDesc = dynamic + ? RAPIER.RigidBodyDesc.dynamic() + : Rapier.RigidBodyDesc.fixed(); rigidBodyDesc.translation.x = mesh.position.x; rigidBodyDesc.translation.y = mesh.position.y; diff --git a/src/bundles/robot_simulation/simulation/primitives/step_cache.ts b/src/bundles/robot_simulation/simulation/primitives/step_cache.ts new file mode 100644 index 000000000..7b4fa3721 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/primitives/step_cache.ts @@ -0,0 +1,29 @@ +// TODO: INCOMPLETE + +type CacheEntry = { + value:T; + valid:boolean; +}; + +export type CacheSchema = Record; + +export class StepCache { + private cache: Record> = {}; + + // Get a value from the cache. If it's invalidated, it will mark it as valid again + get(key: K): Schema[K] { + const entry = this.cache[key as string]; + if (!entry.valid) { + entry.valid = true; + } + return entry.value; + } + + + // Invalidate all cache entries + invalidateAll(): void { + for (const key in this.cache) { + this.cache[key].valid = false; + } + } +} diff --git a/src/bundles/robot_simulation/simulation/types.ts b/src/bundles/robot_simulation/simulation/types.ts new file mode 100644 index 000000000..c47102939 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/types.ts @@ -0,0 +1,3 @@ +export interface Steppable { + step(timstamp: number):void +} diff --git a/src/bundles/robot_simulation/simulation/world.ts b/src/bundles/robot_simulation/simulation/world.ts index 8c05cdf62..74c49ce4e 100644 --- a/src/bundles/robot_simulation/simulation/world.ts +++ b/src/bundles/robot_simulation/simulation/world.ts @@ -1,17 +1,24 @@ +import * as THREE from 'three'; + import { type Ray, type ColliderDesc, type RigidBodyDesc, } from '@dimforge/rapier3d-compat'; -import { PhysicsController } from './controllers/physics/physics_controller'; -import { ProgramController } from './controllers/program_controller'; -import { RenderController } from './controllers/render_controller'; + import { PhysicsObject, addCuboidPhysicsObject, } from './primitives/physics_object'; -import * as THREE from 'three'; -import { settings, CarController } from './controllers/car_controller'; + +import { PhysicsController } from './controllers/physics/physics_controller'; +import { ProgramController } from './controllers/program_controller'; +import { RenderController } from './controllers/render_controller'; +import { + TimeController, + type TimeoutFunction, +} from './controllers/time_controller'; +import { settings, CarController } from './controllers/car/car_controller'; export const simulationStates = [ 'unintialized', @@ -26,6 +33,7 @@ export type Internals = { renderController: RenderController; programController: ProgramController; physicsObjects: Array; + timeController: TimeController; }; let instance: World; @@ -49,6 +57,7 @@ export class World { renderController: new RenderController(), programController: new ProgramController(), physicsObjects: [], + timeController: new TimeController(), }; this.carController = new CarController(settings); } @@ -58,14 +67,10 @@ export class World { return; } - this.state = 'loading'; - const { - physicsController, - renderController, - programController, - } = this.#internals; + const { physicsController, renderController, programController } + = this.#internals; await physicsController.init(); renderController.init(); @@ -75,22 +80,6 @@ export class World { this.state = 'ready'; } - addFloor() { - addCuboidPhysicsObject({ - width: 20, - height: 1, - length: 20, - color: new THREE.Color('white'), - dynamic: false, - position: new THREE.Vector3(0, 0, 0), - }); - } - - setRendererOutput(domElement: HTMLDivElement) { - console.log('Setting renderer output'); - this.#internals.renderController.setRendererOutput(domElement); - } - startSimulation() { if (this.state === 'ready') { this.state = 'running'; @@ -101,9 +90,22 @@ export class World { stopSimulation() { if (this.state === 'running') { this.state = 'ready'; + this.#internals.timeController.pause(); } } + // Physics Controller + addFloor() { + addCuboidPhysicsObject({ + width: 20, + height: 1, + length: 20, + color: new THREE.Color('white'), + dynamic: false, + position: new THREE.Vector3(0, 0, 0), + }); + } + castRay(ray: Ray, maxDistance: number) { const { physicsController } = this.#internals; return physicsController.castRay(ray, maxDistance); @@ -125,12 +127,33 @@ export class World { return physicsObject; } - #step(timestamp:number): void { + // Render Controller + setRendererOutput(domElement: HTMLDivElement) { + console.log('Setting renderer output'); + this.#internals.renderController.setRendererOutput(domElement); + } + + // Time controller + getElapsedTime() { + return this.#internals.timeController.getElapsedTime(); + } + + setTimeout(callback: TimeoutFunction, delay: number) { + return this.#internals.timeController.setTimeout(callback, delay); + } + + // Program Controller + pauseProgramController(time: number) { + this.#internals.programController.pause(time); + } + + #step(timestamp: number): void { const { programController, physicsController, renderController, physicsObjects, + timeController, } = this.#internals; programController.step(timestamp); @@ -150,6 +173,7 @@ export class World { renderController.step(timestamp); if (this.state === 'running') { + timeController.step(timestamp); window.requestAnimationFrame(this.#step.bind(this)); } } diff --git a/src/tabs/RobotSimulation/components/TabUi.tsx b/src/tabs/RobotSimulation/components/TabUi.tsx index 577dee9c1..31d25f2ad 100644 --- a/src/tabs/RobotSimulation/components/TabUi.tsx +++ b/src/tabs/RobotSimulation/components/TabUi.tsx @@ -5,12 +5,13 @@ type TabUiProps = { export default function TabUi({ onOpenCanvas }: TabUiProps) { return (
+

Welcome to robot simulator.

); From e7789c1ab57dc862e663c504e3d730d229924c44 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 11 Nov 2023 08:30:23 +0000 Subject: [PATCH 17/93] motor done --- .../controllers/car/car_controller.ts | 140 +++++------------- ...peed_controller.ts => motor_controller.ts} | 45 +++++- .../controllers/car/ultrasonic_sensor.ts | 21 +++ .../controllers/car/wheel_controller.ts | 87 +++++++++++ .../controllers/physics/physics_controller.ts | 14 +- .../simulation/controllers/physics/types.ts | 1 - .../simulation/controllers/time_controller.ts | 7 +- .../simulation/primitives/cachedVector.ts | 36 +++++ .../simulation/primitives/physics_object.ts | 5 +- .../simulation/primitives/step_cache.ts | 29 ---- .../robot_simulation/simulation/world.ts | 4 + 11 files changed, 241 insertions(+), 148 deletions(-) rename src/bundles/robot_simulation/simulation/controllers/car/{speed_controller.ts => motor_controller.ts} (53%) create mode 100644 src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts create mode 100644 src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts create mode 100644 src/bundles/robot_simulation/simulation/primitives/cachedVector.ts delete mode 100644 src/bundles/robot_simulation/simulation/primitives/step_cache.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts index fff3fbee5..739247f5a 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts @@ -5,16 +5,12 @@ import { addCuboidPhysicsObject, type PhysicsObject, } from '../../primitives/physics_object'; -import { type Vector } from '../physics/types'; -import { type Ray } from '@dimforge/rapier3d-compat'; -import { RAPIER } from '../physics/physics_controller'; -import { nullVector, vec3 } from '../physics/helpers'; -import { SpeedController } from './speed_controller'; import { instance } from '../../world'; import { type Steppable } from '../../types'; - - +import { type Vector } from '../../primitives/cachedVector'; +import { MotorController } from './motor_controller'; +import { WheelController } from './wheel_controller'; export type CarSettings = { chassis: { @@ -29,7 +25,6 @@ export type CarSettings = { maxSuspensionLength: number; suspension: { stiffness: number; damping: number }; buffer: number; - sideForceMultiplier: number; }; turning: { sensitivity: number }; }; @@ -43,14 +38,13 @@ export const settings: CarSettings = { }, wheel: { restHeight: 0.03, - diameter: 0.055, + diameter: 0.056, maxSuspensionLength: 0.1, suspension: { stiffness: 70, - damping: 10, + damping: 3, }, buffer: 0.02, - sideForceMultiplier: -20, }, turning: { sensitivity: 0.5, @@ -64,94 +58,29 @@ const motors = { D: 3, } as const; -export type MotorsOptions = (typeof motors)[keyof typeof motors]; - -class Wheel implements Steppable { - static downDirection = { - x: 0, - y: -1, - z: 0, - }; - - carSettings: CarSettings; - displacement: Vector; - ray: Ray; - chassis: PhysicsObject; - - displacementVector: THREE.Vector3; - downVector: THREE.Vector3; - forceVector: THREE.Vector3; - - constructor( - displacement: Vector, - chassis: PhysicsObject, - carSettings: CarSettings, - ) { - this.carSettings = carSettings; - this.displacement = displacement; - this.ray = new RAPIER.Ray(nullVector.RAPIER, nullVector.RAPIER); - this.chassis = chassis; - - this.displacementVector = vec3(this.displacement); - this.downVector = vec3(Wheel.downDirection); - this.forceVector = vec3(nullVector.RAPIER); - } - - step() { - const velocityY = this.chassis.velocity().y; - - // Reset vectors for memory efficiency - this.displacementVector.copy(this.displacement as THREE.Vector3); - this.downVector.copy(Wheel.downDirection as THREE.Vector3); - this.forceVector.set(0, 0, 0); - - // Convert local vectors to global/world space - const globalDisplacement = this.chassis.worldTranslation( - this.displacementVector, - ); - const globalDownDirection = this.chassis.worldDirection(this.downVector); - - this.ray.origin = globalDisplacement; - this.ray.dir = globalDownDirection; - - const result = instance.castRay( - this.ray, - settings.wheel.maxSuspensionLength, - ); - - if (result === null) { - return; - } - - const wheelDistance = result; - const wheelSettings = this.carSettings.wheel; - - // Calculate suspension force - const force - = wheelSettings.suspension.stiffness - * (wheelSettings.restHeight - - wheelDistance - + this.carSettings.chassis.height / 2) - - wheelSettings.suspension.damping * velocityY; - - this.forceVector.y = force; +const motorDisplacements = [ + { + x: 0.058, + y: 0, + z: 0.055, + }, + { + x: -0.058, + y: 0, + z: 0.055, + }, +]; - // Apply force at the wheel's global displacement - this.chassis.addForce( - this.chassis.worldDirection(this.forceVector), - globalDisplacement, - ); - } -} +export type MotorsOptions = (typeof motors)[keyof typeof motors]; export class CarController implements Steppable { carSettings: CarSettings; chassis: PhysicsObject | null; wheelDisplacements: Vector[]; - wheels: Wheel[]; - leftMotor: SpeedController | null; - rightMotor: SpeedController | null; + wheels: WheelController[]; + leftMotor: MotorController | null; + rightMotor: MotorController | null; constructor(carSettings: CarSettings) { this.carSettings = carSettings; @@ -165,13 +94,13 @@ export class CarController implements Steppable { init() { this.chassis = this.#createChassis(); this.#createWheels(this.chassis); - this.rightMotor = new SpeedController( + this.rightMotor = new MotorController( this.chassis, - this.wheelDisplacements[0], + motorDisplacements[0], ); - this.leftMotor = new SpeedController( + this.leftMotor = new MotorController( this.chassis, - this.wheelDisplacements[1], + motorDisplacements[1], ); } @@ -193,7 +122,7 @@ export class CarController implements Steppable { #createWheels(chassis: PhysicsObject) { const wheelDisplacements = this.wheelDisplacements; this.wheels = wheelDisplacements.map( - (d) => new Wheel(d, chassis, this.carSettings), + (d) => new WheelController(d, chassis, this.carSettings), ); } @@ -225,8 +154,8 @@ export class CarController implements Steppable { ]; } - #getMotor(motor: MotorsOptions): SpeedController | null { - const motorMapping: Record = { + #getMotor(motor: MotorsOptions): MotorController | null { + const motorMapping: Record = { 0: null, 1: this.leftMotor, 2: this.rightMotor, @@ -265,14 +194,15 @@ export class CarController implements Steppable { return; } - const tuningAmount = 3; - - const time = Math.abs(position / speed) * 1000; - const speedInMetersPerSecond - = (speed / 360) * Math.PI * this.carSettings.wheel.diameter * tuningAmount; + = (speed / 360) * Math.PI * this.carSettings.wheel.diameter; + const distanceInMetersPerSecond + = (position / 360) * Math.PI * this.carSettings.wheel.diameter; - selectedMotor!.setSpeed(speedInMetersPerSecond, time); + selectedMotor!.setSpeedDistance( + speedInMetersPerSecond, + distanceInMetersPerSecond, + ); } motorGetSpeed(motor: MotorsOptions) { diff --git a/src/bundles/robot_simulation/simulation/controllers/car/speed_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts similarity index 53% rename from src/bundles/robot_simulation/simulation/controllers/car/speed_controller.ts rename to src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts index afbd9e59d..1cc53a9b8 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/speed_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts @@ -1,15 +1,20 @@ import { type PhysicsObject } from '../../primitives/physics_object'; import { vec3 } from '../physics/helpers'; -import { type Vector } from '../physics/types'; -import { instance } from '../../world'; import { type Steppable } from '../../types'; +import { type Vector } from '../../primitives/cachedVector'; +import { instance } from '../../world'; + -export class SpeedController implements Steppable { +export class MotorController implements Steppable { physicsObject: PhysicsObject; displacement: Vector; speed: number; + distance: number; + + distanceTraveled: number; + previousLocation: THREE.Vector3; displacementVector: THREE.Vector3; targetVelocity: THREE.Vector3; @@ -18,6 +23,7 @@ export class SpeedController implements Steppable { this.physicsObject = physicsObject; this.displacement = displacement; this.speed = 0; + this.distance = 0; this.displacementVector = vec3(this.displacement); this.targetVelocity = vec3({ @@ -25,14 +31,35 @@ export class SpeedController implements Steppable { y: 0, z: this.speed, }); + + this.distanceTraveled = 0; + this.previousLocation = this.physicsObject + .worldTranslation(vec3(this.displacementVector.clone())) + .clone(); } - setSpeed(speed: number, duration: number) { + setSpeedDistance(speed: number, distance: number) { + this.distance = distance; this.speed = speed; - + console.log(distance); + const beforeDistance = this.distanceTraveled; instance.setTimeout(() => { this.speed = 0; - }, duration); + console.log(this.distanceTraveled - beforeDistance); + }, distance / speed * 1000); + } + + #updateDistance() { + const worldTranslation = this.physicsObject.worldTranslation( + this.displacementVector.clone(), + ); + + const translationDelta = worldTranslation + .clone() + .sub(this.previousLocation); + this.previousLocation.copy(worldTranslation); + translationDelta.y = 0; + this.distanceTraveled += translationDelta.length(); } step(_: number) { @@ -43,16 +70,18 @@ export class SpeedController implements Steppable { z: this.speed, } as THREE.Vector3); + this.#updateDistance(); + const worldVelocity = this.physicsObject.worldVelocity( this.displacementVector.clone(), ); const velocityDelta = this.physicsObject - .worldDirection(this.targetVelocity.clone()) + .transformDirection(this.targetVelocity.clone()) .sub(worldVelocity); const impulse = velocityDelta - .multiplyScalar(this.physicsObject.getMass() / 5) + .multiplyScalar(this.physicsObject.getMass() / 4) .projectOnPlane( vec3({ x: 0, diff --git a/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts b/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts new file mode 100644 index 000000000..5b34c6432 --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts @@ -0,0 +1,21 @@ +import { type PhysicsObject } from '../../primitives/physics_object'; +import { type Vector } from '../../primitives/cachedVector'; +import { type Steppable } from '../../types'; + + +export class UltrasonicSensor implements Steppable { + physicsObject: PhysicsObject; + displacement: Vector; + direction:Vector; + + constructor(physicsObject: PhysicsObject, displacement: Vector, direction: Vector) { + this.physicsObject = physicsObject; + this.displacement = displacement; + this.direction = direction; + } + + + step(timstamp: number):void { + + } +} diff --git a/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts new file mode 100644 index 000000000..0033b6a8d --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts @@ -0,0 +1,87 @@ +import { type Ray } from '@dimforge/rapier3d-compat'; +import { type Vector } from '../../primitives/cachedVector'; +import { type Steppable } from '../../types'; +import { type CarSettings } from './car_controller'; +import { type PhysicsObject } from '../../primitives/physics_object'; +import { RAPIER } from '../physics/physics_controller'; +import { nullVector, vec3 } from '../physics/helpers'; +import { instance } from '../../world'; + +export class WheelController implements Steppable { + static downDirection = { + x: 0, + y: -1, + z: 0, + }; + + carSettings: CarSettings; + displacement: Vector; + ray: Ray; + chassis: PhysicsObject; + + displacementVector: THREE.Vector3; + downVector: THREE.Vector3; + forceVector: THREE.Vector3; + + constructor( + displacement: Vector, + chassis: PhysicsObject, + carSettings: CarSettings, + ) { + this.carSettings = carSettings; + this.displacement = displacement; + this.ray = new RAPIER.Ray(nullVector.RAPIER, nullVector.RAPIER); + this.chassis = chassis; + + this.displacementVector = vec3(this.displacement); + this.downVector = vec3(WheelController.downDirection); + this.forceVector = vec3(nullVector.RAPIER); + } + + step() { + // Reset vectors for memory efficiency + this.displacementVector.copy(this.displacement as THREE.Vector3); + this.downVector.copy(WheelController.downDirection as THREE.Vector3); + this.forceVector.set(0, 0, 0); + + const velocityY = this.chassis.worldVelocity(this.displacementVector.clone()).y; + + // Convert local vectors to global/world space + const globalDisplacement = this.chassis.worldTranslation( + this.displacementVector, + ); + const globalDownDirection = this.chassis.transformDirection(this.downVector); + + this.ray.origin = globalDisplacement; + this.ray.dir = globalDownDirection; + + const result = instance.castRay( + this.ray, + this.carSettings.wheel.maxSuspensionLength, + ); + + // Wheels are not touching the ground + if (result === null) { + return; + } + + const wheelDistance = result; + const wheelSettings = this.carSettings.wheel; + + // Calculate suspension force + const force + = wheelSettings.suspension.stiffness + * (wheelSettings.restHeight + - wheelDistance + + this.carSettings.chassis.height / 2) + - wheelSettings.suspension.damping * velocityY; + + this.forceVector.y = force; + + // Apply force at the wheel's global displacement + this.chassis.addForce( + this.chassis.transformDirection(this.forceVector), + globalDisplacement, + ); + } +} diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts index 03d139cd3..6bd690b20 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts @@ -1,14 +1,19 @@ import Rapier, { type Ray, type RigidBody } from '@dimforge/rapier3d-compat'; import { type Steppable } from '../../types'; +import { instance } from '../../world'; let RAPIER: typeof Rapier; export const physicsOptions = { GRAVITY: new Rapier.Vector3(0.0, -9.81, 0.0), + timestep: 1 / 60, } as const; + +// Heavily inspired by https://gafferongames.com/post/fix_your_timestep/ export class PhysicsController implements Steppable { isInitialized = false; + accumulator: number; RAPIER: typeof Rapier | null; #world: Rapier.World | null; @@ -16,6 +21,7 @@ export class PhysicsController implements Steppable { constructor() { this.RAPIER = null; this.#world = null; + this.accumulator = 0; } async init() { @@ -26,6 +32,7 @@ export class PhysicsController implements Steppable { RAPIER = r; this.isInitialized = true; this.#world = new r.World(physicsOptions.GRAVITY); + this.#world.timestep = physicsOptions.timestep; } createCollider(colliderDesc: Rapier.ColliderDesc, rigidBody: RigidBody) { @@ -45,7 +52,12 @@ export class PhysicsController implements Steppable { } step(_: number) { - this.#world!.step(); + this.accumulator += Math.min(instance.getFrameTime(), 0.05); + + while (this.accumulator >= physicsOptions.timestep) { + this.#world!.step(); + this.accumulator -= physicsOptions.timestep; + } } } diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/types.ts b/src/bundles/robot_simulation/simulation/controllers/physics/types.ts index 246ff6372..e69de29bb 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/types.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/types.ts @@ -1 +0,0 @@ -export type Vector = { x:number, y:number, z:number }; diff --git a/src/bundles/robot_simulation/simulation/controllers/time_controller.ts b/src/bundles/robot_simulation/simulation/controllers/time_controller.ts index 08f4308e8..820981167 100644 --- a/src/bundles/robot_simulation/simulation/controllers/time_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/time_controller.ts @@ -9,6 +9,7 @@ export class TimeController implements Steppable { isPaused:boolean; elapsedTime:number; timeoutCallbacks:Array<{ callback:TimeoutFunction, simulatedTime: number }>; + dt: number; constructor() { this.startTime = null; @@ -17,6 +18,7 @@ export class TimeController implements Steppable { this.pausedTime = 0; this.elapsedTime = 0; this.timeoutCallbacks = []; + this.dt = 0; } hasStarted() { @@ -83,7 +85,10 @@ export class TimeController implements Steppable { } // Not paused, so update elapsed time - this.elapsedTime = timestamp - this.startTime - this.pausedTime; + const new_elapsed_time = timestamp - this.startTime - this.pausedTime; + this.dt = new_elapsed_time - this.elapsedTime; + this.elapsedTime = new_elapsed_time; + this.checkTimeouts(); } } diff --git a/src/bundles/robot_simulation/simulation/primitives/cachedVector.ts b/src/bundles/robot_simulation/simulation/primitives/cachedVector.ts new file mode 100644 index 000000000..fa8badadd --- /dev/null +++ b/src/bundles/robot_simulation/simulation/primitives/cachedVector.ts @@ -0,0 +1,36 @@ +import * as THREE from 'three'; + +export type Vector = { x: number; y: number; z: number }; + +export class CachedVector implements Vector { + initial_vector: Vector; + cached_three_vector: THREE.Vector3; + x: number; + y:number; + z: number; + + constructor(vector: Vector) { + this.initial_vector = vector; + this.cached_three_vector = new THREE.Vector3(); + this.x = vector.x; + this.y = vector.y; + this.z = vector.z; + } +} + + +export class ImmutableVector implements Vector { + initial_vector: Vector; + cached_three_vector: THREE.Vector3; + x: number; + y:number; + z: number; + + constructor(vector: Vector) { + this.initial_vector = vector; + this.cached_three_vector = new THREE.Vector3(); + this.x = vector.x; + this.y = vector.y; + this.z = vector.z; + } +} diff --git a/src/bundles/robot_simulation/simulation/primitives/physics_object.ts b/src/bundles/robot_simulation/simulation/primitives/physics_object.ts index 724ed8fb4..e8e6acd5d 100644 --- a/src/bundles/robot_simulation/simulation/primitives/physics_object.ts +++ b/src/bundles/robot_simulation/simulation/primitives/physics_object.ts @@ -63,9 +63,8 @@ export class PhysicsObject { .add(translation); } - worldDirection(localDirection: THREE.Vector3): THREE.Vector3 { + transformDirection(localDirection: THREE.Vector3): THREE.Vector3 { const rotation = this.rotation(); - return localDirection.applyQuaternion(rotation); } @@ -88,7 +87,7 @@ export class PhysicsObject { const velocityMagnitude = distanceVector.length() * angularVelocity.length(); - const tangentialVelocity = this.worldDirection(localPoint) + const tangentialVelocity = this.transformDirection(localPoint) .cross(angularVelocity) .negate() .normalize() diff --git a/src/bundles/robot_simulation/simulation/primitives/step_cache.ts b/src/bundles/robot_simulation/simulation/primitives/step_cache.ts deleted file mode 100644 index 7b4fa3721..000000000 --- a/src/bundles/robot_simulation/simulation/primitives/step_cache.ts +++ /dev/null @@ -1,29 +0,0 @@ -// TODO: INCOMPLETE - -type CacheEntry = { - value:T; - valid:boolean; -}; - -export type CacheSchema = Record; - -export class StepCache { - private cache: Record> = {}; - - // Get a value from the cache. If it's invalidated, it will mark it as valid again - get(key: K): Schema[K] { - const entry = this.cache[key as string]; - if (!entry.valid) { - entry.valid = true; - } - return entry.value; - } - - - // Invalidate all cache entries - invalidateAll(): void { - for (const key in this.cache) { - this.cache[key].valid = false; - } - } -} diff --git a/src/bundles/robot_simulation/simulation/world.ts b/src/bundles/robot_simulation/simulation/world.ts index 74c49ce4e..c1c7baf54 100644 --- a/src/bundles/robot_simulation/simulation/world.ts +++ b/src/bundles/robot_simulation/simulation/world.ts @@ -142,6 +142,10 @@ export class World { return this.#internals.timeController.setTimeout(callback, delay); } + getFrameTime() { + return this.#internals.timeController.dt / 1000; + } + // Program Controller pauseProgramController(time: number) { this.#internals.programController.pause(time); From a28a949e90c15831ee76717b60e4410f6110d480 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sun, 12 Nov 2023 04:49:19 +0000 Subject: [PATCH 18/93] changed --- .../controllers/car/car_controller.ts | 5 +- .../controllers/car/motor_controller.ts | 9 +- .../controllers/car/ultrasonic_sensor.ts | 8 +- .../controllers/car/wheel_controller.ts | 12 +- .../simulation/controllers/physics/helpers.ts | 14 +- .../controllers/physics/physics_controller.ts | 39 ++++-- .../physics/physics_object_controller.ts} | 121 +++++++++++++++--- .../simulation/controllers/physics/types.ts | 0 .../controllers/render_controller.ts | 35 +++-- .../simulation/controllers/time_controller.ts | 28 ++-- .../simulation/primitives/cachedVector.ts | 36 ------ .../robot_simulation/simulation/world.ts | 45 ++++--- 12 files changed, 213 insertions(+), 139 deletions(-) rename src/bundles/robot_simulation/simulation/{primitives/physics_object.ts => controllers/physics/physics_object_controller.ts} (50%) delete mode 100644 src/bundles/robot_simulation/simulation/controllers/physics/types.ts delete mode 100644 src/bundles/robot_simulation/simulation/primitives/cachedVector.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts index 739247f5a..e5250c799 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts @@ -4,11 +4,12 @@ import * as THREE from 'three'; import { addCuboidPhysicsObject, type PhysicsObject, -} from '../../primitives/physics_object'; +} from '../physics/physics_object_controller'; +import { type Vector } from '../physics/helpers'; + import { instance } from '../../world'; import { type Steppable } from '../../types'; -import { type Vector } from '../../primitives/cachedVector'; import { MotorController } from './motor_controller'; import { WheelController } from './wheel_controller'; diff --git a/src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts index 1cc53a9b8..1c886e84f 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts @@ -1,8 +1,7 @@ -import { type PhysicsObject } from '../../primitives/physics_object'; -import { vec3 } from '../physics/helpers'; +import { type PhysicsObject } from '../physics/physics_object_controller'; +import { vec3, type Vector } from '../physics/helpers'; import { type Steppable } from '../../types'; -import { type Vector } from '../../primitives/cachedVector'; import { instance } from '../../world'; @@ -91,11 +90,9 @@ export class MotorController implements Steppable { ); this.physicsObject - .getRigidBody() - .applyImpulseAtPoint( + .applyImpulse( impulse, this.physicsObject.worldTranslation(this.displacementVector.clone()), - true, ); } } diff --git a/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts b/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts index 5b34c6432..5a025d2e2 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts @@ -1,9 +1,8 @@ -import { type PhysicsObject } from '../../primitives/physics_object'; +import { type PhysicsObject } from '../physics/physics_object_controller'; import { type Vector } from '../../primitives/cachedVector'; -import { type Steppable } from '../../types'; -export class UltrasonicSensor implements Steppable { +export class UltrasonicSensor { physicsObject: PhysicsObject; displacement: Vector; direction:Vector; @@ -14,8 +13,7 @@ export class UltrasonicSensor implements Steppable { this.direction = direction; } - - step(timstamp: number):void { + sense() { } } diff --git a/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts index 0033b6a8d..8a66aab29 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts @@ -1,11 +1,11 @@ -import { type Ray } from '@dimforge/rapier3d-compat'; -import { type Vector } from '../../primitives/cachedVector'; +import { Vector3, type Ray } from '@dimforge/rapier3d-compat'; import { type Steppable } from '../../types'; import { type CarSettings } from './car_controller'; -import { type PhysicsObject } from '../../primitives/physics_object'; +import { type PhysicsObject } from '../physics/physics_object_controller'; import { RAPIER } from '../physics/physics_controller'; -import { nullVector, vec3 } from '../physics/helpers'; +import { vec3, type Vector } from '../physics/helpers'; import { instance } from '../../world'; +import * as THREE from 'three'; export class WheelController implements Steppable { static downDirection = { @@ -30,12 +30,12 @@ export class WheelController implements Steppable { ) { this.carSettings = carSettings; this.displacement = displacement; - this.ray = new RAPIER.Ray(nullVector.RAPIER, nullVector.RAPIER); + this.ray = new RAPIER.Ray(new Vector3(0, 0, 0), new Vector3(0, 0, 0)); this.chassis = chassis; this.displacementVector = vec3(this.displacement); this.downVector = vec3(WheelController.downDirection); - this.forceVector = vec3(nullVector.RAPIER); + this.forceVector = new THREE.Vector3(); } step() { diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts b/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts index 3b305223b..837adc858 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts @@ -1,19 +1,7 @@ -import { type Vector } from '@dimforge/rapier3d-compat'; import { Euler, Quaternion, Vector3 } from 'three'; export const quat = ({ x, y, z, w }) => new Quaternion(x, y, z, w); export const vec3 = ({ x, y, z }) => new Vector3(x, y, z); export const euler = ({ x, y, z }) => new Euler(x, y, z); -export const nullVector = { - THREE: { - x: 0, - y: 0, - z: 0, - } as Vector3, - RAPIER: { - x: 0, - y: 0, - z: 0, - } as Vector, -}; +export type Vector = { x: number, y:number, z:number }; diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts index 6bd690b20..003b23ce3 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts @@ -9,19 +9,18 @@ export const physicsOptions = { timestep: 1 / 60, } as const; - -// Heavily inspired by https://gafferongames.com/post/fix_your_timestep/ export class PhysicsController implements Steppable { - isInitialized = false; - accumulator: number; - RAPIER: typeof Rapier | null; + #world: Rapier.World | null; + #accumulator: number; + #residual: number; constructor() { this.RAPIER = null; this.#world = null; - this.accumulator = 0; + this.#accumulator = 0; + this.#residual = 0; } async init() { @@ -30,7 +29,7 @@ export class PhysicsController implements Steppable { this.RAPIER = r; RAPIER = r; - this.isInitialized = true; + this.#world = new r.World(physicsOptions.GRAVITY); this.#world.timestep = physicsOptions.timestep; } @@ -51,13 +50,29 @@ export class PhysicsController implements Steppable { return result.toi; } - step(_: number) { - this.accumulator += Math.min(instance.getFrameTime(), 0.05); + getResidualTime() { + return this.#residual; + } + + /** + * Advances the physics simulation in discrete steps based on the frame time. + * Inspired by: https://gafferongames.com/post/fix_your_timestep/ + * @param {number} _ - Unused parameter, can be removed if not required elsewhere. + */ + step(_) { + // Limit the frame time to a maximum value to avoid spiral of death. + const maxFrameTime = 0.05; + this.#accumulator += Math.min(instance.getFrameTime(), maxFrameTime); - while (this.accumulator >= physicsOptions.timestep) { - this.#world!.step(); - this.accumulator -= physicsOptions.timestep; + // Update the physics world in fixed time steps + while (this.#accumulator >= physicsOptions.timestep) { + instance.savePhysicsObjectState(); + this.#world!.step(); // Advance the physics simulation + this.#accumulator -= physicsOptions.timestep; } + + // Calculate the residual for interpolations + this.#residual = this.#accumulator / physicsOptions.timestep; } } diff --git a/src/bundles/robot_simulation/simulation/primitives/physics_object.ts b/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts similarity index 50% rename from src/bundles/robot_simulation/simulation/primitives/physics_object.ts rename to src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts index e8e6acd5d..deb4d5eac 100644 --- a/src/bundles/robot_simulation/simulation/primitives/physics_object.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts @@ -1,14 +1,21 @@ -import Rapier, { type Vector3 } from '@dimforge/rapier3d-compat'; +import type Rapier from '@dimforge/rapier3d-compat'; import * as THREE from 'three'; -import { RAPIER } from '../controllers/physics/physics_controller'; +import { RAPIER } from './physics_controller'; -import { instance } from '../world'; -import { nullVector, quat, vec3 } from '../controllers/physics/helpers'; +import { instance } from '../../world'; +import { quat, vec3 } from './helpers'; +import { type Steppable } from '../../types'; + +type Orientation = { + translation: THREE.Vector3; + rotation: THREE.Quaternion; +}; export class PhysicsObject { #rigidBody: Rapier.RigidBody; #mesh: THREE.Mesh; #collider: Rapier.Collider; + #previousState?: Orientation; constructor( mesh: THREE.Mesh, @@ -20,6 +27,7 @@ export class PhysicsObject { this.#collider = collider; } + // This is a bypass for development. getRigidBody() { return this.#rigidBody; } @@ -32,6 +40,20 @@ export class PhysicsObject { return this.#collider.mass(); } + addForce( + force: THREE.Vector3, + point: THREE.Vector3 = new THREE.Vector3(), + ): void { + return this.#rigidBody.addForceAtPoint(force, point, true); + } + + applyImpulse( + impulse: THREE.Vector3, + point: THREE.Vector3 = new THREE.Vector3(), + ) { + return this.#rigidBody.applyImpulseAtPoint(impulse, point, true); + } + removeForcesAndTorques() { this.#rigidBody.resetForces(true); this.#rigidBody.resetTorques(true); @@ -54,7 +76,7 @@ export class PhysicsObject { } worldTranslation( - localTranslation: THREE.Vector3 = nullVector.THREE, + localTranslation: THREE.Vector3 = new THREE.Vector3(), ): THREE.Vector3 { const rotation = this.rotation(); const translation = this.translation(); @@ -69,7 +91,7 @@ export class PhysicsObject { } distanceVectorOfPointToRotationalAxis( - localPoint: THREE.Vector3 = nullVector.THREE, + localPoint: THREE.Vector3 = new THREE.Vector3(), ) { return localPoint .clone() @@ -78,31 +100,75 @@ export class PhysicsObject { .add(localPoint); } - tangentialVelocityOfPoint( - localPoint: THREE.Vector3 = nullVector.THREE, - ): THREE.Vector3 { + /** + * Calculates the tangential velocity of a point in a rotating system. + * @param {THREE.Vector3} localPoint - The point for which to calculate the tangential velocity. + * @returns {THREE.Vector3} The tangential velocity vector of the point. + */ + tangentialVelocityOfPoint(localPoint = new THREE.Vector3()): THREE.Vector3 { + // Calculate the distance vector from the point to the rotational axis const distanceVector = this.distanceVectorOfPointToRotationalAxis(localPoint); + + // Retrieve the angular velocity of the system const angularVelocity = this.angularVelocity(); + + // Calculate the magnitude of the tangential velocity const velocityMagnitude = distanceVector.length() * angularVelocity.length(); + // Calculate the tangential velocity vector const tangentialVelocity = this.transformDirection(localPoint) .cross(angularVelocity) .negate() .normalize() .multiplyScalar(velocityMagnitude); + // Return the tangential velocity vector return tangentialVelocity; } - worldVelocity(localPoint: THREE.Vector3 = nullVector.THREE): THREE.Vector3 { + worldVelocity( + localPoint: THREE.Vector3 = new THREE.Vector3(), + ): THREE.Vector3 { return this.tangentialVelocityOfPoint(localPoint) .add(this.velocity()); } - addForce(force: Vector3, point: Vector3 = nullVector.RAPIER) { - return this.#rigidBody.addForceAtPoint(force, point, true); + setMeshPosition(newPosition: THREE.Vector3) { + this.#mesh.position.copy(newPosition); + } + + setMeshRotation(newRotation: THREE.Quaternion) { + this.#mesh.quaternion.copy(newRotation); + } + + + savePreviousState() { + this.#previousState = { + translation: this.translation() + .clone(), + rotation: this.rotation() + .clone(), + }; + } + + getLerpedState(): Orientation { + if (!this.#previousState) { + return { + translation: this.translation(), + rotation: this.rotation(), + }; + } + + const alpha = instance.getResidualTime(); + + return { + translation: this.#previousState.translation.clone() + .lerp(this.translation(), alpha), + rotation: this.#previousState.rotation.clone() + .slerp(this.rotation(), alpha), + }; } /** @@ -110,8 +176,33 @@ export class PhysicsObject { * Usually called after a physics step */ step(_: number) { - this.#mesh.position.copy(this.#collider.translation() as THREE.Vector3); - this.#mesh.quaternion.copy(this.#collider.rotation() as THREE.Quaternion); + const lerpedState = this.getLerpedState(); + this.#mesh.position.copy(lerpedState.translation); + this.#mesh.quaternion.copy(lerpedState.rotation); + } +} + +export class PhysicsObjectController implements Steppable { + physicsObjects: Array; + + constructor() { + this.physicsObjects = []; + } + + add(physicsObject: PhysicsObject) { + this.physicsObjects.push(physicsObject); + } + + saveLocation() { + for (const physicsObject of this.physicsObjects) { + physicsObject.savePreviousState(); + } + } + + step(_) { + for (const physicsObject of this.physicsObjects) { + physicsObject.step(_); + } } } @@ -143,7 +234,7 @@ export const addCuboidPhysicsObject = ({ const rigidBodyDesc = dynamic ? RAPIER.RigidBodyDesc.dynamic() - : Rapier.RigidBodyDesc.fixed(); + : RAPIER.RigidBodyDesc.fixed(); rigidBodyDesc.translation.x = mesh.position.x; rigidBodyDesc.translation.y = mesh.position.y; diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/types.ts b/src/bundles/robot_simulation/simulation/controllers/physics/types.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/bundles/robot_simulation/simulation/controllers/render_controller.ts b/src/bundles/robot_simulation/simulation/controllers/render_controller.ts index b49359d53..49f695a62 100644 --- a/src/bundles/robot_simulation/simulation/controllers/render_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/render_controller.ts @@ -1,16 +1,19 @@ +/* eslint-disable import/extensions */ import * as THREE from 'three'; -// eslint-disable-next-line import/extensions + import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; +import { + type GLTF, + GLTFLoader, +} from 'three/examples/jsm/loaders/GLTFLoader.js'; import { type Steppable } from '../types'; - export const sceneOptions = { height: 600, width: 800, } as const; - export class RenderController implements Steppable { #scene: THREE.Scene; #camera: THREE.Camera; @@ -21,13 +24,18 @@ export class RenderController implements Steppable { this.#scene = new THREE.Scene(); const renderAspectRatio = sceneOptions.width / sceneOptions.height; - this.#camera = new THREE.PerspectiveCamera(75, renderAspectRatio, 0.01, 1000); + this.#camera = new THREE.PerspectiveCamera( + 75, + renderAspectRatio, + 0.01, + 1000, + ); this.#renderer = new THREE.WebGLRenderer({ antialias: true }); this.#controls = new OrbitControls(this.#camera, this.#renderer.domElement); } - init() { + init(): void { this.#scene.background = new THREE.Color(0xffffff); const light = new THREE.AmbientLight(0xffffff); this.#scene.add(light); @@ -35,23 +43,26 @@ export class RenderController implements Steppable { this.#camera.translateY(2); this.#camera.lookAt(new THREE.Vector3(0, -1.5, 0)); - // this.#camera.translateX(0.8); - // this.#camera.translateY(0.5); - // this.#camera.lookAt(new THREE.Vector3(0, 0.5, 0)); - this.#renderer.setSize(sceneOptions.width, sceneOptions.height); this.#renderer.setPixelRatio(window.devicePixelRatio * 1.5); } - setRendererOutput(domElement: HTMLDivElement) { + loadGTLF(url: string): Promise { + const loader = new GLTFLoader(); + return new Promise((resolve, reject) => { + loader.load(url, resolve, () => {}, reject); + }); + } + + setRendererOutput(domElement: HTMLDivElement): void { domElement.replaceChildren(this.#renderer.domElement); } - addMesh(mesh: THREE.Mesh) { + addMesh(mesh: THREE.Mesh): void { this.#scene.add(mesh); } - step(_: number) { + step(_: number): void { this.#renderer.render(this.#scene, this.#camera); this.#controls.update(); } diff --git a/src/bundles/robot_simulation/simulation/controllers/time_controller.ts b/src/bundles/robot_simulation/simulation/controllers/time_controller.ts index 820981167..b3a740cce 100644 --- a/src/bundles/robot_simulation/simulation/controllers/time_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/time_controller.ts @@ -3,12 +3,12 @@ import { type Steppable } from '../types'; export type TimeoutFunction = () => void; export class TimeController implements Steppable { - startTime:number | null; - pauseTime:number | null; // Time of the last pause + startTime: number | null; + pauseTime: number | null; // Time of the last pause pausedTime: number; // Total time spent paused - isPaused:boolean; - elapsedTime:number; - timeoutCallbacks:Array<{ callback:TimeoutFunction, simulatedTime: number }>; + isPaused: boolean; + elapsedTime: number; + timeoutCallbacks: Array<{ callback: TimeoutFunction; simulatedTime: number }>; dt: number; constructor() { @@ -21,22 +21,22 @@ export class TimeController implements Steppable { this.dt = 0; } - hasStarted() { + hasStarted(): boolean { return this.startTime !== null; } - getElapsedTime() { + getElapsedTime(): number { return this.elapsedTime; } - pause() { + pause(): void { if (!this.isPaused && this.hasStarted()) { this.pauseTime = performance.now(); this.isPaused = true; } } - setTimeout(callback:TimeoutFunction, delay: number) { + setTimeout(callback: TimeoutFunction, delay: number): void { if (!this.hasStarted()) { throw new Error('Cannot set timeout before starting simulation'); } @@ -48,7 +48,7 @@ export class TimeController implements Steppable { }); } - checkTimeouts() { + #checkTimeouts(): void { if (!this.hasStarted()) { return; } @@ -59,7 +59,9 @@ export class TimeController implements Steppable { // Filter out expired timeouts // Could be done with a priority queue but this is simpler - const expiredTimeouts = this.timeoutCallbacks.filter((timeout) => timeout.simulatedTime <= this.elapsedTime); + const expiredTimeouts = this.timeoutCallbacks.filter( + (timeout) => timeout.simulatedTime <= this.elapsedTime, + ); for (const expiredTimeout of expiredTimeouts) { const index = this.timeoutCallbacks.indexOf(expiredTimeout); if (index !== -1) { @@ -69,7 +71,7 @@ export class TimeController implements Steppable { } } - step(timestamp:number):void { + step(timestamp: number): void { // First step called if (this.startTime === null) { this.startTime = timestamp; @@ -89,6 +91,6 @@ export class TimeController implements Steppable { this.dt = new_elapsed_time - this.elapsedTime; this.elapsedTime = new_elapsed_time; - this.checkTimeouts(); + this.#checkTimeouts(); } } diff --git a/src/bundles/robot_simulation/simulation/primitives/cachedVector.ts b/src/bundles/robot_simulation/simulation/primitives/cachedVector.ts deleted file mode 100644 index fa8badadd..000000000 --- a/src/bundles/robot_simulation/simulation/primitives/cachedVector.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as THREE from 'three'; - -export type Vector = { x: number; y: number; z: number }; - -export class CachedVector implements Vector { - initial_vector: Vector; - cached_three_vector: THREE.Vector3; - x: number; - y:number; - z: number; - - constructor(vector: Vector) { - this.initial_vector = vector; - this.cached_three_vector = new THREE.Vector3(); - this.x = vector.x; - this.y = vector.y; - this.z = vector.z; - } -} - - -export class ImmutableVector implements Vector { - initial_vector: Vector; - cached_three_vector: THREE.Vector3; - x: number; - y:number; - z: number; - - constructor(vector: Vector) { - this.initial_vector = vector; - this.cached_three_vector = new THREE.Vector3(); - this.x = vector.x; - this.y = vector.y; - this.z = vector.z; - } -} diff --git a/src/bundles/robot_simulation/simulation/world.ts b/src/bundles/robot_simulation/simulation/world.ts index c1c7baf54..4e5b1085e 100644 --- a/src/bundles/robot_simulation/simulation/world.ts +++ b/src/bundles/robot_simulation/simulation/world.ts @@ -7,9 +7,9 @@ import { } from '@dimforge/rapier3d-compat'; import { - PhysicsObject, + PhysicsObject, PhysicsObjectController, addCuboidPhysicsObject, -} from './primitives/physics_object'; +} from './controllers/physics/physics_object_controller'; import { PhysicsController } from './controllers/physics/physics_controller'; import { ProgramController } from './controllers/program_controller'; @@ -32,7 +32,7 @@ export type Internals = { physicsController: PhysicsController; renderController: RenderController; programController: ProgramController; - physicsObjects: Array; + physicsObjects: PhysicsObjectController; timeController: TimeController; }; @@ -56,13 +56,13 @@ export class World { physicsController: new PhysicsController(), renderController: new RenderController(), programController: new ProgramController(), - physicsObjects: [], + physicsObjects: new PhysicsObjectController(), timeController: new TimeController(), }; this.carController = new CarController(settings); } - async init(code: string) { + async init(code: string): Promise { if (this.state === 'running') { return; } @@ -80,14 +80,14 @@ export class World { this.state = 'ready'; } - startSimulation() { + startSimulation(): void { if (this.state === 'ready') { this.state = 'running'; } window.requestAnimationFrame(this.#step.bind(this)); } - stopSimulation() { + stopSimulation(): void { if (this.state === 'running') { this.state = 'ready'; this.#internals.timeController.pause(); @@ -95,7 +95,7 @@ export class World { } // Physics Controller - addFloor() { + addFloor(): void { addCuboidPhysicsObject({ width: 20, height: 1, @@ -106,7 +106,7 @@ export class World { }); } - castRay(ray: Ray, maxDistance: number) { + castRay(ray: Ray, maxDistance: number): number | null { const { physicsController } = this.#internals; return physicsController.castRay(ray, maxDistance); } @@ -115,7 +115,7 @@ export class World { mesh: THREE.Mesh, rigidBodyDesc: RigidBodyDesc, colliderDesc: ColliderDesc, - ) { + ): PhysicsObject { const { physicsController, renderController, physicsObjects } = this.#internals; @@ -123,10 +123,14 @@ export class World { const rigidBody = physicsController.createRigidBody(rigidBodyDesc); const collider = physicsController.createCollider(colliderDesc, rigidBody); const physicsObject = new PhysicsObject(mesh, rigidBody, collider); - physicsObjects.push(physicsObject); + physicsObjects.add(physicsObject); return physicsObject; } + getResidualTime(): number { + return this.#internals.physicsController.getResidualTime(); + } + // Render Controller setRendererOutput(domElement: HTMLDivElement) { console.log('Setting renderer output'); @@ -134,23 +138,28 @@ export class World { } // Time controller - getElapsedTime() { + getElapsedTime(): number { return this.#internals.timeController.getElapsedTime(); } - setTimeout(callback: TimeoutFunction, delay: number) { - return this.#internals.timeController.setTimeout(callback, delay); + setTimeout(callback: TimeoutFunction, delay: number): void { + this.#internals.timeController.setTimeout(callback, delay); } - getFrameTime() { + getFrameTime(): number { return this.#internals.timeController.dt / 1000; } // Program Controller - pauseProgramController(time: number) { + pauseProgramController(time: number): void { this.#internals.programController.pause(time); } + // Physic Object Controller + savePhysicsObjectState() { + this.#internals.physicsObjects.saveLocation(); + } + #step(timestamp: number): void { const { programController, @@ -169,9 +178,7 @@ export class World { physicsController.step(timestamp); // Update the location of the mesh - for (const physicsObject of physicsObjects) { - physicsObject.step(timestamp); - } + physicsObjects.step(timestamp); // Render the scene renderController.step(timestamp); From f3ae8fadc6a95fd0ac26b1054172bd2dce5ef639 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sun, 12 Nov 2023 04:50:04 +0000 Subject: [PATCH 19/93] fix type --- .../simulation/controllers/car/ultrasonic_sensor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts b/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts index 5a025d2e2..4bc262e1e 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts @@ -1,5 +1,5 @@ +import { type Vector } from '../physics/helpers'; import { type PhysicsObject } from '../physics/physics_object_controller'; -import { type Vector } from '../../primitives/cachedVector'; export class UltrasonicSensor { From 5fb40056de139ace38bde8bc8cc20216b7ad747a Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 13 Nov 2023 06:13:33 +0000 Subject: [PATCH 20/93] Clean up code --- .../controllers/car/car_controller.ts | 3 +- .../physics/physics_object_controller.ts | 26 ++---- .../simulation/controllers/time_controller.ts | 86 +++++++++---------- .../robot_simulation/simulation/world.ts | 12 ++- 4 files changed, 62 insertions(+), 65 deletions(-) diff --git a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts index e5250c799..0fb487571 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts @@ -1,5 +1,4 @@ import * as THREE from 'three'; -// import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { addCuboidPhysicsObject, @@ -76,12 +75,12 @@ export type MotorsOptions = (typeof motors)[keyof typeof motors]; export class CarController implements Steppable { carSettings: CarSettings; - chassis: PhysicsObject | null; wheelDisplacements: Vector[]; wheels: WheelController[]; leftMotor: MotorController | null; rightMotor: MotorController | null; + chassis: PhysicsObject | null; constructor(carSettings: CarSettings) { this.carSettings = carSettings; diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts index deb4d5eac..348ef274f 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts @@ -11,10 +11,11 @@ type Orientation = { rotation: THREE.Quaternion; }; -export class PhysicsObject { +export class PhysicsObject implements Steppable { #rigidBody: Rapier.RigidBody; #mesh: THREE.Mesh; #collider: Rapier.Collider; + #previousState?: Orientation; constructor( @@ -135,15 +136,6 @@ export class PhysicsObject { .add(this.velocity()); } - setMeshPosition(newPosition: THREE.Vector3) { - this.#mesh.position.copy(newPosition); - } - - setMeshRotation(newRotation: THREE.Quaternion) { - this.#mesh.quaternion.copy(newRotation); - } - - savePreviousState() { this.#previousState = { translation: this.translation() @@ -153,7 +145,7 @@ export class PhysicsObject { }; } - getLerpedState(): Orientation { + getInterpolatedState(): Orientation { if (!this.#previousState) { return { translation: this.translation(), @@ -161,13 +153,13 @@ export class PhysicsObject { }; } - const alpha = instance.getResidualTime(); + const interpolationFactor = instance.getResidualTime(); return { translation: this.#previousState.translation.clone() - .lerp(this.translation(), alpha), + .lerp(this.translation(), interpolationFactor), rotation: this.#previousState.rotation.clone() - .slerp(this.rotation(), alpha), + .slerp(this.rotation(), interpolationFactor), }; } @@ -176,9 +168,9 @@ export class PhysicsObject { * Usually called after a physics step */ step(_: number) { - const lerpedState = this.getLerpedState(); - this.#mesh.position.copy(lerpedState.translation); - this.#mesh.quaternion.copy(lerpedState.rotation); + const interpolatedState = this.getInterpolatedState(); + this.#mesh.position.copy(interpolatedState.translation); + this.#mesh.quaternion.copy(interpolatedState.rotation); } } diff --git a/src/bundles/robot_simulation/simulation/controllers/time_controller.ts b/src/bundles/robot_simulation/simulation/controllers/time_controller.ts index b3a740cce..015eb5187 100644 --- a/src/bundles/robot_simulation/simulation/controllers/time_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/time_controller.ts @@ -1,34 +1,41 @@ import { type Steppable } from '../types'; -export type TimeoutFunction = () => void; +export type TimeoutCallback = () => void; export class TimeController implements Steppable { - startTime: number | null; - pauseTime: number | null; // Time of the last pause - pausedTime: number; // Total time spent paused - isPaused: boolean; - elapsedTime: number; - timeoutCallbacks: Array<{ callback: TimeoutFunction; simulatedTime: number }>; - dt: number; + private startTime: number | null; + private pauseTime: number | null; // Time when the last pause occurred + private totalPausedTime: number; // Total duration of pauses + private isPaused: boolean; + private elapsedTime: number; // Time since start excluding paused durations + private timeoutEvents: Array<{ callback: TimeoutCallback; triggerTime: number }>; + private deltaTime: number; // Time difference between the current and last step constructor() { this.startTime = null; this.pauseTime = null; this.isPaused = false; - this.pausedTime = 0; + this.totalPausedTime = 0; this.elapsedTime = 0; - this.timeoutCallbacks = []; - this.dt = 0; + this.timeoutEvents = []; + this.deltaTime = 0; } + // Check if the simulation has started hasStarted(): boolean { return this.startTime !== null; } + // Get the total elapsed time excluding paused durations getElapsedTime(): number { return this.elapsedTime; } + getDeltaTime(): number { + return this.deltaTime; + } + + // Pause the simulation pause(): void { if (!this.isPaused && this.hasStarted()) { this.pauseTime = performance.now(); @@ -36,61 +43,54 @@ export class TimeController implements Steppable { } } - setTimeout(callback: TimeoutFunction, delay: number): void { + // Schedule a callback function to execute after a delay + setTimeout(callback: TimeoutCallback, delay: number): void { if (!this.hasStarted()) { throw new Error('Cannot set timeout before starting simulation'); } - const simulatedTime = this.elapsedTime + delay; - this.timeoutCallbacks.push({ + const triggerTime = this.elapsedTime + delay; + this.timeoutEvents.push({ callback, - simulatedTime, + triggerTime, }); } - #checkTimeouts(): void { - if (!this.hasStarted()) { - return; - } + // Check and execute expired timeout events + private checkTimeouts(): void { + if (!this.hasStarted() || this.isPaused) return; - if (this.isPaused) { - return; - } - - // Filter out expired timeouts - // Could be done with a priority queue but this is simpler - const expiredTimeouts = this.timeoutCallbacks.filter( - (timeout) => timeout.simulatedTime <= this.elapsedTime, - ); - for (const expiredTimeout of expiredTimeouts) { - const index = this.timeoutCallbacks.indexOf(expiredTimeout); - if (index !== -1) { - this.timeoutCallbacks.splice(index, 1); - expiredTimeout.callback(); + const currentTime = this.elapsedTime; + this.timeoutEvents = this.timeoutEvents.filter((timeoutEvent) => { + if (timeoutEvent.triggerTime <= currentTime) { + timeoutEvent.callback(); + return false; // Remove from the array } - } + return true; // Keep in the array + }); } + // Update the state for each step in the simulation step(timestamp: number): void { - // First step called + // Initialize start time on first step if (this.startTime === null) { this.startTime = timestamp; } - // If paused, + // Resume from pause if (this.isPaused && this.pauseTime !== null) { - // Update paused time - this.pausedTime += timestamp - this.pauseTime; - // unpause + this.totalPausedTime += timestamp - this.pauseTime; this.pauseTime = null; this.isPaused = false; } - // Not paused, so update elapsed time - const new_elapsed_time = timestamp - this.startTime - this.pausedTime; - this.dt = new_elapsed_time - this.elapsedTime; + + // Update elapsed and delta time + const new_elapsed_time = timestamp - this.startTime - this.totalPausedTime; + this.deltaTime = new_elapsed_time - this.elapsedTime; this.elapsedTime = new_elapsed_time; - this.#checkTimeouts(); + // Process timeout events + this.checkTimeouts(); } } diff --git a/src/bundles/robot_simulation/simulation/world.ts b/src/bundles/robot_simulation/simulation/world.ts index 4e5b1085e..ceb5cd292 100644 --- a/src/bundles/robot_simulation/simulation/world.ts +++ b/src/bundles/robot_simulation/simulation/world.ts @@ -16,9 +16,10 @@ import { ProgramController } from './controllers/program_controller'; import { RenderController } from './controllers/render_controller'; import { TimeController, - type TimeoutFunction, + type TimeoutCallback, } from './controllers/time_controller'; import { settings, CarController } from './controllers/car/car_controller'; +import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; export const simulationStates = [ 'unintialized', @@ -137,17 +138,21 @@ export class World { this.#internals.renderController.setRendererOutput(domElement); } + loadGTLF(url: string): Promise { + return this.#internals.renderController.loadGTLF(url); + } + // Time controller getElapsedTime(): number { return this.#internals.timeController.getElapsedTime(); } - setTimeout(callback: TimeoutFunction, delay: number): void { + setTimeout(callback: TimeoutCallback, delay: number): void { this.#internals.timeController.setTimeout(callback, delay); } getFrameTime(): number { - return this.#internals.timeController.dt / 1000; + return this.#internals.timeController.getDeltaTime() / 1000; } // Program Controller @@ -189,4 +194,5 @@ export class World { } } } + export { instance }; From cd792e5ef948b0ff6a98193eda68c37fe2d4ccd1 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 15 Nov 2023 08:46:34 +0000 Subject: [PATCH 21/93] Formatting --- .../robot_simulation/simulation/controllers/physics/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts b/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts index 837adc858..f22a29658 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts @@ -4,4 +4,4 @@ export const quat = ({ x, y, z, w }) => new Quaternion(x, y, z, w); export const vec3 = ({ x, y, z }) => new Vector3(x, y, z); export const euler = ({ x, y, z }) => new Euler(x, y, z); -export type Vector = { x: number, y:number, z:number }; +export type Vector = { x: number; y: number; z: number }; From d9a85d5e597b9b768b8584993681c8202b801d30 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 18 Nov 2023 07:00:09 +0000 Subject: [PATCH 22/93] Move buffer --- .../simulation/controllers/car/car_controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts index 0fb487571..41a01300b 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts @@ -134,22 +134,22 @@ export class CarController implements Steppable { { x: width / 2 + buffer, y: 0, - z: length / 2, + z: length / 2 - buffer, }, { x: -(width / 2 + buffer), y: 0, - z: length / 2, + z: length / 2 - buffer, }, { x: width / 2 + buffer, y: 0, - z: -length / 2, + z: -(length / 2 - buffer), }, { x: -(width / 2 + buffer), y: 0, - z: -length / 2, + z: -(length / 2 - buffer), }, ]; } From 771dfd819b702796cdaa66554738c91956270a17 Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 21 Nov 2023 00:48:33 +0000 Subject: [PATCH 23/93] Change the wheel --- .../controllers/car/car_controller.ts | 1 - .../controllers/car/pid_controller.ts | 115 ++++++++++++++++++ .../controllers/car/wheel_controller.ts | 60 ++++----- .../controllers/physics/physics_controller.ts | 13 +- .../physics/physics_object_controller.ts | 12 -- .../robot_simulation/simulation/world.ts | 2 +- 6 files changed, 149 insertions(+), 54 deletions(-) create mode 100644 src/bundles/robot_simulation/simulation/controllers/car/pid_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts index 41a01300b..1e175c480 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts @@ -218,7 +218,6 @@ export class CarController implements Steppable { } step(timestamp: number) { - this.chassis!.removeForcesAndTorques(); this.wheels.forEach((wheel) => { wheel.step(); }); diff --git a/src/bundles/robot_simulation/simulation/controllers/car/pid_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/pid_controller.ts new file mode 100644 index 000000000..f9e52a09f --- /dev/null +++ b/src/bundles/robot_simulation/simulation/controllers/car/pid_controller.ts @@ -0,0 +1,115 @@ +import * as THREE from 'three'; + +type NullaryFunction = () => T; +type BinaryFunction = (a: T, b: T) => T; +type ScaleFunction = (value: T, scale: number) => T; + +type PIDControllerOptions = { + zero: NullaryFunction; + add: BinaryFunction; + subtract: BinaryFunction; + scale: ScaleFunction; + + proportionalGain: number; + integralGain: number; + derivativeGain: number; +}; + +class PIDController { + zero: NullaryFunction; + add: BinaryFunction; + subtract: BinaryFunction; + scale: ScaleFunction; + + proportionalGain: number; + integralGain: number; + derivativeGain: number; + + errorsSum: T; + previousError: T; + + constructor({ + zero, + add, + subtract, + scale, + proportionalGain, + integralGain, + derivativeGain, + }: PIDControllerOptions) { + this.zero = zero; + this.add = add; + this.subtract = subtract; + this.scale = scale; + + this.proportionalGain = proportionalGain; + this.integralGain = integralGain; + this.derivativeGain = derivativeGain; + + this.errorsSum = this.zero(); + this.previousError = this.zero(); + } + + calculate(currentValue: T, setpoint: T): T { + const error = this.subtract(setpoint, currentValue); + console.log(error, 'error'); + this.errorsSum = this.add(this.errorsSum, error); + + const proportional = this.scale(error, this.proportionalGain); + const integral = this.scale(this.errorsSum, this.integralGain); + const derivative = this.scale(this.subtract(error, this.previousError), this.derivativeGain); + console.log(this.derivativeGain, this.integralGain, this.proportionalGain); + console.log(derivative, integral, proportional); + this.previousError = error; + + return this.add(this.add(proportional, integral), derivative); + } +} + + +export class NumberPidController extends PIDController { + constructor({ + proportionalGain, + integralGain, + derivativeGain, + }: { + proportionalGain: number; + integralGain: number; + derivativeGain: number; + }) { + super({ + zero: () => 0, + add: (a, b) => a + b, + subtract: (a, b) => a - b, + scale: (value, scale) => value * scale, + proportionalGain, + integralGain, + derivativeGain, + }); + } +} + +export class VectorPIDController extends PIDController { + constructor({ + proportionalGain, + integralGain, + derivativeGain, + }: { + proportionalGain: number; + integralGain: number; + derivativeGain: number; + }) { + super({ + zero: () => new THREE.Vector3(0, 0, 0), + add: (a, b) => a.clone() + .add(b), + subtract: (a, b) => a.clone() + .sub(b), + scale: (value, scale) => value.clone() + .multiplyScalar(scale), + proportionalGain, + integralGain, + derivativeGain, + }); + } +} diff --git a/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts b/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts index 8a66aab29..9c62d14f5 100644 --- a/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts @@ -5,14 +5,11 @@ import { type PhysicsObject } from '../physics/physics_object_controller'; import { RAPIER } from '../physics/physics_controller'; import { vec3, type Vector } from '../physics/helpers'; import { instance } from '../../world'; -import * as THREE from 'three'; +import type * as THREE from 'three'; +import { NumberPidController } from './pid_controller'; export class WheelController implements Steppable { - static downDirection = { - x: 0, - y: -1, - z: 0, - }; + pidController: NumberPidController; carSettings: CarSettings; displacement: Vector; @@ -21,7 +18,6 @@ export class WheelController implements Steppable { displacementVector: THREE.Vector3; downVector: THREE.Vector3; - forceVector: THREE.Vector3; constructor( displacement: Vector, @@ -34,53 +30,45 @@ export class WheelController implements Steppable { this.chassis = chassis; this.displacementVector = vec3(this.displacement); - this.downVector = vec3(WheelController.downDirection); - this.forceVector = new THREE.Vector3(); + this.downVector = vec3({ + x: 0, + y: -1, + z: 0, + }); + this.pidController = new NumberPidController({ + proportionalGain: 0.7, + derivativeGain: 3, + integralGain: 0.002, + }); } step() { - // Reset vectors for memory efficiency - this.displacementVector.copy(this.displacement as THREE.Vector3); - this.downVector.copy(WheelController.downDirection as THREE.Vector3); - this.forceVector.set(0, 0, 0); - - const velocityY = this.chassis.worldVelocity(this.displacementVector.clone()).y; + const wheelSettings = this.carSettings.wheel; - // Convert local vectors to global/world space const globalDisplacement = this.chassis.worldTranslation( - this.displacementVector, + this.displacementVector.clone(), ); - const globalDownDirection = this.chassis.transformDirection(this.downVector); + const globalDownDirection = this.chassis.transformDirection(this.downVector.clone()); this.ray.origin = globalDisplacement; this.ray.dir = globalDownDirection; - const result = instance.castRay( - this.ray, - this.carSettings.wheel.maxSuspensionLength, - ); + const result = instance.castRay(this.ray, wheelSettings.maxSuspensionLength); // Wheels are not touching the ground if (result === null) { return; } - const wheelDistance = result; - const wheelSettings = this.carSettings.wheel; - - // Calculate suspension force - const force - = wheelSettings.suspension.stiffness - * (wheelSettings.restHeight - - wheelDistance - + this.carSettings.chassis.height / 2) - - wheelSettings.suspension.damping * velocityY; + const { distance: wheelDistance, normal } = result; + const error = this.pidController.calculate(wheelDistance, wheelSettings.restHeight + this.carSettings.chassis.height / 2); - this.forceVector.y = force; + const force = vec3(normal) + .normalize() + .multiplyScalar(error * this.chassis.getMass()); - // Apply force at the wheel's global displacement - this.chassis.addForce( - this.chassis.transformDirection(this.forceVector), + this.chassis.applyImpulse( + force, globalDisplacement, ); } diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts index 003b23ce3..2a3798cf2 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts @@ -1,4 +1,4 @@ -import Rapier, { type Ray, type RigidBody } from '@dimforge/rapier3d-compat'; +import Rapier, { type Vector, type Ray, type RigidBody } from '@dimforge/rapier3d-compat'; import { type Steppable } from '../../types'; import { instance } from '../../world'; @@ -42,12 +42,17 @@ export class PhysicsController implements Steppable { return this.#world!.createRigidBody(rigidBodyDesc); } - castRay(ray: Ray, maxDistance: number): number | null { - const result = this.#world!.castRay(ray, maxDistance, true); + castRay(ray: Ray, maxDistance: number): { distance:number, normal: Vector } | null { + const result = this.#world!.castRayAndGetNormal(ray, maxDistance, true); + if (result === null) { return null; } - return result.toi; + + return { + distance: result.toi, + normal: result.normal, + }; } getResidualTime() { diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts b/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts index 348ef274f..64990ff7f 100644 --- a/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts @@ -41,13 +41,6 @@ export class PhysicsObject implements Steppable { return this.#collider.mass(); } - addForce( - force: THREE.Vector3, - point: THREE.Vector3 = new THREE.Vector3(), - ): void { - return this.#rigidBody.addForceAtPoint(force, point, true); - } - applyImpulse( impulse: THREE.Vector3, point: THREE.Vector3 = new THREE.Vector3(), @@ -55,11 +48,6 @@ export class PhysicsObject implements Steppable { return this.#rigidBody.applyImpulseAtPoint(impulse, point, true); } - removeForcesAndTorques() { - this.#rigidBody.resetForces(true); - this.#rigidBody.resetTorques(true); - } - rotation(): THREE.Quaternion { return quat(this.#rigidBody.rotation()); } diff --git a/src/bundles/robot_simulation/simulation/world.ts b/src/bundles/robot_simulation/simulation/world.ts index ceb5cd292..235ae6f7b 100644 --- a/src/bundles/robot_simulation/simulation/world.ts +++ b/src/bundles/robot_simulation/simulation/world.ts @@ -107,7 +107,7 @@ export class World { }); } - castRay(ray: Ray, maxDistance: number): number | null { + castRay(ray: Ray, maxDistance: number): ReturnType { const { physicsController } = this.#internals; return physicsController.castRay(ray, maxDistance); } From f12cbec7aeeeff714dc4f03ddee65b66f2af474e Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 21 Nov 2023 01:44:29 +0000 Subject: [PATCH 24/93] Add the interception --- src/bundles/robot_simulation/functions.ts | 3 +++ .../simulation/controllers/program_controller.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 4762a0b15..42db69e42 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -18,6 +18,9 @@ export function init_simulation() { const code = context.unTypecheckedCode[0]; const world = getWorld(); world.init(code); + if (world.state === 'loading') { + throw new Error('Interrupt execution by module'); + } } export function ev3_motorA() { diff --git a/src/bundles/robot_simulation/simulation/controllers/program_controller.ts b/src/bundles/robot_simulation/simulation/controllers/program_controller.ts index df9407233..66be82ae3 100644 --- a/src/bundles/robot_simulation/simulation/controllers/program_controller.ts +++ b/src/bundles/robot_simulation/simulation/controllers/program_controller.ts @@ -34,6 +34,8 @@ export class ProgramController implements Steppable { useSubst: false, }; + context.errors = []; + this.#iterator = runECEvaluatorByJoel(this.#code, context, options); } From aad793d1d85b0001dd4f95cddff0c1bc312cd9b9 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sun, 21 Jan 2024 01:31:48 +0000 Subject: [PATCH 25/93] MAssive push --- src/bundles/robot_simulation/config.ts | 122 +++++++++++++++ .../controllers/environment/Environment.ts | 58 +++++++ .../controllers/ev3/components/Chassis.ts | 25 ++++ .../controllers/ev3/components/Mesh.ts | 66 ++++++++ .../controllers/ev3/components/Motor.ts | 65 ++++++++ .../controllers/ev3/components/Wheel.ts | 68 +++++++++ .../controllers/ev3/ev3/default.ts | 131 ++++++++++++++++ .../ev3/feedback_control/PidController.ts | 109 ++++++++++++++ .../controllers/ev3/sensor/ColorSensor.ts | 109 ++++++++++++++ .../controllers/ev3/sensor/types.ts | 3 + .../robot_simulation/controllers/index.ts | 4 + .../controllers/program/Program.ts | 44 ++++++ .../engine/Core/Controller.ts | 88 +++++++++++ .../robot_simulation/engine/Core/Events.ts | 88 +++++++++++ .../robot_simulation/engine/Core/Timer.ts | 64 ++++++++ .../robot_simulation/engine/Entity/Entity.ts | 129 ++++++++++++++++ .../engine/Entity/EntityFactory.ts | 41 +++++ .../robot_simulation/engine/Math/Convert.ts | 5 + .../robot_simulation/engine/Math/Vector.ts | 2 + .../robot_simulation/engine/Physics.ts | 110 ++++++++++++++ .../robot_simulation/engine/Render/Camera.ts | 36 +++++ .../engine/Render/MeshFactory.ts | 33 ++++ .../engine/Render/Renderer.ts | 90 +++++++++++ src/bundles/robot_simulation/engine/World.ts | 122 +++++++++++++++ .../engine/__tests__/Physics.ts | 120 +++++++++++++++ .../engine/__tests__/Timer.ts | 93 ++++++++++++ src/bundles/robot_simulation/engine/index.ts | 8 + src/bundles/robot_simulation/functions.ts | 4 +- src/bundles/robot_simulation/index.ts | 12 +- src/bundles/robot_simulation/main.ts | 141 ++++++++++++++++++ .../robot_simulation/simulation/index.ts | 16 +- src/tabs/RobotSimulation/components/Modal.tsx | 2 +- .../components/Simulation/index.tsx | 68 +++++++-- .../components/TabPanels/ColorSensorPanel.tsx | 30 ++++ .../components/TabPanels/MonitoringPanel.tsx | 3 + .../components/TabPanels/MotorPidPanel.tsx | 57 +++++++ .../components/TabPanels/WheelPidPanel.tsx | 63 ++++++++ src/tabs/RobotSimulation/index.tsx | 10 +- 38 files changed, 2205 insertions(+), 34 deletions(-) create mode 100644 src/bundles/robot_simulation/config.ts create mode 100644 src/bundles/robot_simulation/controllers/environment/Environment.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Motor.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/types.ts create mode 100644 src/bundles/robot_simulation/controllers/index.ts create mode 100644 src/bundles/robot_simulation/controllers/program/Program.ts create mode 100644 src/bundles/robot_simulation/engine/Core/Controller.ts create mode 100644 src/bundles/robot_simulation/engine/Core/Events.ts create mode 100644 src/bundles/robot_simulation/engine/Core/Timer.ts create mode 100644 src/bundles/robot_simulation/engine/Entity/Entity.ts create mode 100644 src/bundles/robot_simulation/engine/Entity/EntityFactory.ts create mode 100644 src/bundles/robot_simulation/engine/Math/Convert.ts create mode 100644 src/bundles/robot_simulation/engine/Math/Vector.ts create mode 100644 src/bundles/robot_simulation/engine/Physics.ts create mode 100644 src/bundles/robot_simulation/engine/Render/Camera.ts create mode 100644 src/bundles/robot_simulation/engine/Render/MeshFactory.ts create mode 100644 src/bundles/robot_simulation/engine/Render/Renderer.ts create mode 100644 src/bundles/robot_simulation/engine/World.ts create mode 100644 src/bundles/robot_simulation/engine/__tests__/Physics.ts create mode 100644 src/bundles/robot_simulation/engine/__tests__/Timer.ts create mode 100644 src/bundles/robot_simulation/engine/index.ts create mode 100644 src/bundles/robot_simulation/main.ts create mode 100644 src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx diff --git a/src/bundles/robot_simulation/config.ts b/src/bundles/robot_simulation/config.ts new file mode 100644 index 000000000..0a93e161c --- /dev/null +++ b/src/bundles/robot_simulation/config.ts @@ -0,0 +1,122 @@ +import * as THREE from 'three'; + +import { type PhysicsConfig } from './engine/Physics'; +import { type CameraOptions } from './engine/Render/Camera'; +import { type RenderConfig } from './engine/Render/Renderer'; +import { type EntityCuboidOptions } from './engine/Entity/EntityFactory'; +import { type SimpleVector } from './engine/Math/Vector'; +import { type MeshConfig } from './controllers/ev3/components/Mesh'; + +const tinyBuffer = 0.02; + +export const physicsConfig: PhysicsConfig = { + gravity: { + x: 0, + y: -9.81, + z: 0, + }, + timestep: 1 / 60, +}; + +export const renderConfig: RenderConfig = { + width: 900, + height: 500, + control: 'orbit', +}; + +export const sceneCamera: CameraOptions = { + type: 'perspective', + aspect: renderConfig.width / renderConfig.height, + fov: 75, + near: 0.1, + far: 1000, +}; + +export const chassisConfig: EntityCuboidOptions = { + orientation: { + position: { + x: 0, + y: 2, + z: 0, + }, + rotation: { + x: 0, + y: 0, + z: 0, + w: 0, + }, + }, + mass: 0.6, + height: 0.095, + width: 0.145, + length: 0.18, + type: 'dynamic', +}; + +export const meshConfig: MeshConfig = { + orientation: chassisConfig.orientation, + width: chassisConfig.width, + height: chassisConfig.height, + length: chassisConfig.length, + color: new THREE.Color('blue'), + debug: true, +}; + + +export const wheelDisplacements: Record = { + frontLeftWheel: { + x: -(chassisConfig.width / 2 + tinyBuffer), + y: 0, + z: chassisConfig.length / 2 - tinyBuffer, + }, + + frontRightWheel: { + x: chassisConfig.width / 2 + tinyBuffer, + y: 0, + z: chassisConfig.length / 2 - tinyBuffer, + }, + backLeftWheel: { + x: -(chassisConfig.width / 2 + tinyBuffer), + y: 0, + z: -(chassisConfig.length / 2 - tinyBuffer), + }, + backRightWheel: { + x: chassisConfig.width / 2 + tinyBuffer, + y: 0, + z: -(chassisConfig.length / 2 - tinyBuffer), + }, +}; + +export const wheelPidConfig = { + proportionalGain: 3, + integralGain: 0.01, + derivativeGain: 2.5, +}; + +export const motorDisplacements = { + leftMotor: { + x: 0.058, + y: 0, + z: 0.055, + }, + rightMotor: { + x: -0.058, + y: 0, + z: 0.055, + }, +}; + +export const motorPidConfig = { + proportionalGain: 0.25, + derivativeGain: 0, + integralGain: 0, +}; + + +export const colorSensorConfig = { + displacement: { + x: 0.04, + y: -(chassisConfig.height / 2), + z: 0.01, + }, +}; diff --git a/src/bundles/robot_simulation/controllers/environment/Environment.ts b/src/bundles/robot_simulation/controllers/environment/Environment.ts new file mode 100644 index 000000000..b56911dc8 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/environment/Environment.ts @@ -0,0 +1,58 @@ +import { type Physics, type Controller, type Renderer, EntityFactory, MeshFactory } from '../../engine'; +import * as THREE from 'three'; + +import { type EntityCuboidOptions } from '../../engine/Entity/EntityFactory'; +import { type RenderCuboidOptions } from '../../engine/Render/MeshFactory'; + +export const floorConfig: EntityCuboidOptions & RenderCuboidOptions = { + debug: false, + orientation: { + position: { + x: 0, + y: -0.5, + z: 0, + }, + rotation: { + x: 0, + y: 0, + z: 0, + w: 0, + }, + }, + mass: 1, + height: 1, + width: 20, + length: 20, + color: new THREE.Color('white'), + type: 'fixed', +}; + + +export class Environment implements Controller { + private physics: Physics; + private renderer: Renderer; + private mesh: THREE.Mesh; + + constructor(physics:Physics, renderer: Renderer) { + this.physics = physics; + this.renderer = renderer; + + this.mesh = MeshFactory.addCuboid(floorConfig); + } + + async start(): Promise { + EntityFactory.addCuboid(this.physics, floorConfig); + + // Load the texture + const texture = new THREE.TextureLoader() + .load('https://www.shutterstock.com/image-vector/black-white-stripes-260nw-785326606.jpg'); + + // Create a material with the texture + const material = new THREE.MeshStandardMaterial({ map: texture }); + + // Apply the material to the mesh + this.mesh.material = material; + + this.renderer.add(this.mesh); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts new file mode 100644 index 000000000..ae4db79ae --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -0,0 +1,25 @@ +import { type Physics, type Controller, EntityFactory, type Entity } from '../../../engine'; +import { type EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; + +export class ChassisWrapper implements Controller { + private physics: Physics; + private config: EntityCuboidOptions; + + private chassis: Entity | null = null; + + constructor(physics: Physics, config: EntityCuboidOptions) { + this.physics = physics; + this.config = config; + } + + async start() { + this.chassis = EntityFactory.addCuboid(this.physics, this.config); + } + + getEntity() { + if (this.chassis === null) { + throw new Error('Chassis not initialized'); + } + return this.chassis; + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts new file mode 100644 index 000000000..7fdd3ca1c --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -0,0 +1,66 @@ +import { type Entity, type Controller, MeshFactory, Renderer } from '../../../engine'; +import * as THREE from 'three'; +import { type Orientation } from '../../../engine/Entity/Entity'; +import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; +import { type ChassisWrapper } from './Chassis'; + + +export type MeshConfig = { + orientation: Orientation + width: number; + height: number; + length: number; + color: THREE.Color; + debug: boolean; +}; + +export class Mesh implements Controller { + chassis: Entity | null = null; + render: Renderer; + meshConfig: MeshConfig; + debugMesh: THREE.Mesh; + chassisWrapper: ChassisWrapper; + mesh: GLTF | null = null; + + constructor(chassisWrapper: ChassisWrapper, render:Renderer, meshConfig: MeshConfig) { + this.chassisWrapper = chassisWrapper; + this.debugMesh = MeshFactory.addCuboid(meshConfig); + this.meshConfig = meshConfig; + this.render = render; + render.add(this.debugMesh); + } + + async start(): Promise { + this.mesh = await Renderer.loadGTLF('https://keen-longma-3c1be1.netlify.app/try_one.gltf'); + + const box = new THREE.Box3() + .setFromObject(this.mesh.scene); + + const size = new THREE.Vector3(); + box.getSize(size); + + const scaleX = this.meshConfig.width / size.x; + const scaleY = this.meshConfig.height / size.y; + const scaleZ = this.meshConfig.length / size.z; + + this.mesh.scene.scale.set(scaleX, scaleY, scaleZ); + + this.render.add(this.mesh.scene); + } + + update() { + const chassisEntity = this.chassisWrapper.getEntity(); + if (this.meshConfig.debug || this.mesh === null) { + this.debugMesh.visible = true; + } else { + this.debugMesh.visible = false; + } + + this.debugMesh.position.copy(chassisEntity + .getPosition() as THREE.Vector3); + this.debugMesh.quaternion.copy(chassisEntity.getRotation() as THREE.Quaternion); + + this.mesh?.scene.position.copy(chassisEntity.getPosition() as THREE.Vector3); + this.mesh?.scene.quaternion.copy(chassisEntity.getRotation() as THREE.Quaternion); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts new file mode 100644 index 000000000..aa01465e8 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -0,0 +1,65 @@ +import { type Controller, type Physics } from '../../../engine'; +import { type SimpleVector } from '../../../engine/Math/Vector'; +import { vec3 } from '../../../engine/Math/Convert'; +import { VectorPidController } from '../feedback_control/PidController'; +import type * as THREE from 'three'; +import { type ChassisWrapper } from './Chassis'; + + +type MotorConfig = { + pid: { + proportionalGain: number; + derivativeGain: number; + integralGain: number; + } +}; + +export class Motor implements Controller { + pid: VectorPidController; + displacementVector: THREE.Vector3; + motorVelocity: number; + + physics: Physics; + chassisWrapper: ChassisWrapper; + + constructor(chassisWrapper: ChassisWrapper, physics: Physics, displacement: SimpleVector, config: MotorConfig) { + this.chassisWrapper = chassisWrapper; + this.physics = physics; + this.pid = new VectorPidController(config.pid); + this.displacementVector = vec3(displacement); + this.motorVelocity = 0; + } + + setVelocity(velocity: number) { + this.motorVelocity = velocity; + } + + fixedUpdate(_: number): void { + const chassis = this.chassisWrapper.getEntity(); + const targetMotorVelocity = vec3({ + x: 0, + y: 0, + z: this.motorVelocity, + }); + + const targetMotorGlobalVelocity = chassis.transformDirection(targetMotorVelocity); + const actualMotorGlobalVelocity = chassis.worldVelocity(this.displacementVector.clone()); + + const error = this.pid.calculate(actualMotorGlobalVelocity, targetMotorGlobalVelocity); + + const motorGlobalPosition = chassis.worldTranslation(this.displacementVector.clone()); + + const impulse = error.projectOnPlane(vec3({ + x: 0, + y: 1, + z: 0, + })) + .multiplyScalar(chassis.getMass()); + + chassis.applyImpulse(impulse, motorGlobalPosition); + } + + toString() { + return 'Motor'; + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts new file mode 100644 index 000000000..35af73b02 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -0,0 +1,68 @@ +import { type Controller, type Physics } from '../../../engine'; +import { type SimpleVector } from '../../../engine/Math/Vector'; +import { vec3 } from '../../../engine/Math/Convert'; +import { NumberPidController } from '../feedback_control/PidController'; +import * as THREE from 'three'; +import { type ChassisWrapper } from './Chassis'; + +type WheelConfig = { + pid: { + proportionalGain: number; + derivativeGain: number; + integralGain: number; + }, + debug: boolean, +}; + +export class Wheel implements Controller { + chassisWrapper: ChassisWrapper; + pid: NumberPidController; + displacement: SimpleVector; + displacementVector: THREE.Vector3; + downVector: THREE.Vector3; + physics: Physics; + arrowHelper: THREE.ArrowHelper; + + constructor(chassisWrapper: ChassisWrapper, physics: Physics, displacement: SimpleVector, config: WheelConfig) { + this.chassisWrapper = chassisWrapper; + this.physics = physics; + this.pid = new NumberPidController(config.pid); + this.displacement = displacement; + this.displacementVector = vec3(this.displacement); + this.downVector = vec3({ + x: 0, + y: -1, + z: 0, + }); + this.arrowHelper = new THREE.ArrowHelper(); + this.arrowHelper.visible = config.debug; + } + + fixedUpdate(_: number): void { + const chassis = this.chassisWrapper.getEntity(); + + const globalDisplacement = chassis.worldTranslation( + this.displacementVector.clone(), + ); + + const globalDownDirection = chassis.transformDirection(this.downVector.clone()); + const result = this.physics.castRay(globalDisplacement, globalDownDirection, 0.2); + + // Wheels are not touching the ground + if (result === null) { + return; + } + + const { distance: wheelDistance, normal } = result; + const error = this.pid.calculate(wheelDistance, 0.03 + 0.095 / 2); + + const force = vec3(normal) + .normalize() + .multiplyScalar(error * chassis.getMass()); + + chassis.applyImpulse( + force, + globalDisplacement, + ); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts new file mode 100644 index 000000000..99bc5901e --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts @@ -0,0 +1,131 @@ +import { type Physics, type Renderer, ControllerMap } from '../../../engine'; +import { type EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; +import { type SimpleVector } from '../../../engine/Math/Vector'; +import { type PIDConfig } from '../feedback_control/PidController'; + +import { Wheel } from '../components/Wheel'; +import { Motor } from '../components/Motor'; +import { ColorSensor } from '../sensor/ColorSensor'; +import { Mesh, type MeshConfig } from '../components/Mesh'; +import { ChassisWrapper } from '../components/Chassis'; + +type WheelControllers = { + frontLeftWheel: Wheel; + frontRightWheel: Wheel; + backLeftWheel: Wheel; + backRightWheel: Wheel; +}; + +type MotorControllers = { + leftMotor: Motor; + rightMotor: Motor; +}; + +type SensorControllers = { + colorSensor: ColorSensor; +}; + +type ChassisControllers = { + mesh: Mesh; + chassis: ChassisWrapper; +}; + +type DefaultEv3Controller = WheelControllers & +MotorControllers & +SensorControllers & +ChassisControllers; + +type Ev3Config = { + chassis: EntityCuboidOptions; + wheel: { + displacements: Record; + pid: PIDConfig; + }; + motor: { + displacements: Record; + pid: PIDConfig; + }; + colorSensor: { + displacement: SimpleVector; + } + mesh: MeshConfig; +}; + +export type DefaultEv3 = ControllerMap; + +export const createDefaultEv3 = ( + physics: Physics, + render: Renderer, + config: Ev3Config, +): DefaultEv3 => { + const chassis = new ChassisWrapper(physics, config.chassis); + const mesh = new Mesh(chassis, render, config.mesh); + + const wheelPidConfig = { + pid: config.wheel.pid, + debug: true, + }; + + const frontLeftWheel = new Wheel( + chassis, + physics, + config.wheel.displacements.frontLeftWheel, + wheelPidConfig, + ); + + const frontRightWheel = new Wheel( + chassis, + physics, + config.wheel.displacements.frontRightWheel, + wheelPidConfig, + ); + + const backLeftWheel = new Wheel( + chassis, + physics, + config.wheel.displacements.backLeftWheel, + wheelPidConfig, + ); + const backRightWheel = new Wheel( + chassis, + physics, + config.wheel.displacements.backRightWheel, + wheelPidConfig, + ); + + // Motors + const motorPidConfig = { + pid: config.motor.pid, + }; + + const leftMotor = new Motor( + chassis, + physics, + config.motor.displacements.leftMotor, + motorPidConfig, + ); + + const rightMotor = new Motor( + chassis, + physics, + config.motor.displacements.rightMotor, + motorPidConfig, + ); + + // Sensors + const colorSensor = new ColorSensor(chassis, render.scene(), config.colorSensor.displacement, { debug: true }); + + const ev3: DefaultEv3 = new ControllerMap({ + frontLeftWheel, + frontRightWheel, + backLeftWheel, + backRightWheel, + leftMotor, + rightMotor, + colorSensor, + mesh, + chassis, + }); + + return ev3; +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts b/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts new file mode 100644 index 000000000..701ff0795 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts @@ -0,0 +1,109 @@ +import * as THREE from 'three'; + +export type PIDConfig = { + proportionalGain: number; + integralGain: number; + derivativeGain: number; +}; + +type NullaryFunction = () => T; +type BinaryFunction = (a: T, b: T) => T; +type ScaleFunction = (value: T, scale: number) => T; + +type PIDControllerOptions = { + zero: NullaryFunction; + add: BinaryFunction; + subtract: BinaryFunction; + scale: ScaleFunction; + + proportionalGain: number; + integralGain: number; + derivativeGain: number; +}; + +class PIDController { + zero: NullaryFunction; + add: BinaryFunction; + subtract: BinaryFunction; + scale: ScaleFunction; + + proportionalGain: number; + integralGain: number; + derivativeGain: number; + + errorsSum: T; + previousError: T; + + constructor({ + zero, + add, + subtract, + scale, + proportionalGain, + integralGain, + derivativeGain, + }: PIDControllerOptions) { + this.zero = zero; + this.add = add; + this.subtract = subtract; + this.scale = scale; + + this.proportionalGain = proportionalGain; + this.integralGain = integralGain; + this.derivativeGain = derivativeGain; + + this.errorsSum = this.zero(); + this.previousError = this.zero(); + } + + calculate(currentValue: T, setpoint: T): T { + const error = this.subtract(setpoint, currentValue); + this.errorsSum = this.add(this.errorsSum, error); + + const proportional = this.scale(error, this.proportionalGain); + const integral = this.scale(this.errorsSum, this.integralGain); + const derivative = this.scale(this.subtract(error, this.previousError), this.derivativeGain); + this.previousError = error; + + return this.add(this.add(proportional, integral), derivative); + } +} + +export class NumberPidController extends PIDController { + constructor({ + proportionalGain, + integralGain, + derivativeGain, + }: PIDConfig) { + super({ + zero: () => 0, + add: (a, b) => a + b, + subtract: (a, b) => a - b, + scale: (value, scale) => value * scale, + proportionalGain, + integralGain, + derivativeGain, + }); + } +} + +export class VectorPidController extends PIDController { + constructor({ + proportionalGain, + integralGain, + derivativeGain, + }: PIDConfig) { + super({ + zero: () => new THREE.Vector3(0, 0, 0), + add: (a, b) => a.clone() + .add(b), + subtract: (a, b) => a.clone() + .sub(b), + scale: (value, scale) => value.clone() + .multiplyScalar(scale), + proportionalGain, + integralGain, + derivativeGain, + }); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts new file mode 100644 index 000000000..c27c3071a --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -0,0 +1,109 @@ +import * as THREE from 'three'; +import { Renderer } from '../../../engine'; +import { type Sensor } from './types'; +import { type ChassisWrapper } from '../components/Chassis'; +import { type SimpleVector } from '../../../engine/Math/Vector'; +import { vec3 } from '../../../engine/Math/Convert'; + +type Color = { r:number, g:number, b:number }; + +type ColorSensorConfig = { + debug: boolean +}; + +export class ColorSensor implements Sensor { + renderer: Renderer; + camera: THREE.Camera; + displacement: THREE.Vector3; + chassisWrapper: ChassisWrapper; + + accumulator = 0; + + colorSensed:Color; + + constructor(chassisWrapper: ChassisWrapper, scene: THREE.Scene, displacement: SimpleVector, config: ColorSensorConfig) { + this.chassisWrapper = chassisWrapper; + this.camera = Renderer.sensorCamera(); + this.displacement = vec3(displacement); + this.colorSensed = { + r: 0, + g: 0, + b: 0, + }; + + this.renderer = new Renderer(scene, this.camera, { + width: 16, + height: 16, + control: 'none', + }); + + + if (config.debug) { + const helper = new THREE.CameraHelper(this.camera); + scene.add(helper); + } + } + + getColorSensorPosition() { + const chassis = this.chassisWrapper.getEntity(); + const colorSensorPosition = chassis.worldTranslation(this.displacement.clone()); + return colorSensorPosition; + } + + getElement(): HTMLCanvasElement { + return this.renderer.getElement(); + } + + sense(): Color { + return this.colorSensed; + } + + fixedUpdate(fixedDeltaTime: number) { + this.accumulator += fixedDeltaTime; + if (this.accumulator < 1) { + return; + } + this.accumulator -= 1; + + this.renderer.render(); + + this.camera.position.copy(this.getColorSensorPosition()); + + const lookAt = this.getColorSensorPosition(); + lookAt.y -= 1; + this.camera.lookAt(lookAt); + + const rendererCanvas = this.getElement(); + const tempCanvas = document.createElement('canvas'); + + tempCanvas.width = rendererCanvas.width; + tempCanvas.height = rendererCanvas.height; + const tempCtx = tempCanvas.getContext('2d')!; + + tempCtx.drawImage(rendererCanvas, 0, 0); + + const imageData = tempCtx.getImageData(0, 0, 16, 16, {}); + + const averageColor = { + r: 0, + g: 0, + b: 0, + }; + + for (let i = 0; i < imageData.data.length; i += 4) { + const r = imageData.data[i]; + const g = imageData.data[i + 1]; + const b = imageData.data[i + 2]; + // const a = imageData.data[i + 3]; + averageColor.r += r; + averageColor.g += g; + averageColor.b += b; + } + + averageColor.r /= imageData.data.length; + averageColor.g /= imageData.data.length; + averageColor.b /= imageData.data.length; + + this.colorSensed = averageColor; + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts new file mode 100644 index 000000000..1c1aab00e --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts @@ -0,0 +1,3 @@ +import { type Controller } from '../../../engine'; + +export type Sensor = Controller & { sense: () => T }; diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts new file mode 100644 index 000000000..79728db89 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/index.ts @@ -0,0 +1,4 @@ +export { Environment } from './environment/Environment'; +export { Program } from './program/Program'; + +export { DefaultEv3 } from './ev3/ev3/default'; diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts new file mode 100644 index 000000000..2c1cd2cbb --- /dev/null +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -0,0 +1,44 @@ +import { type IOptions, runECEvaluatorByJoel } from 'js-slang'; +import context from 'js-slang/context'; +import { type Controller } from '../../engine'; + + +export class Program implements Controller { + #code: string; + #iterator: Generator | null; + #isPaused:boolean; + + constructor(code: string) { + this.#code = code; + this.#iterator = null; + this.#isPaused = false; + console.log(code); + } + + pause(time: number) { + this.#isPaused = true; + } + + start() { + console.log('start'); + + const options: Partial = { + originalMaxExecTime: Infinity, + scheduler: 'preemptive', + stepLimit: Infinity, + throwInfiniteLoops: false, + useSubst: false, + }; + + context.errors = []; + + this.#iterator = runECEvaluatorByJoel(this.#code, context, options); + } + + fixedUpdate(_: number) { + if (this.#isPaused) { + return; + } + const __ = this.#iterator!.next(); + } +} diff --git a/src/bundles/robot_simulation/engine/Core/Controller.ts b/src/bundles/robot_simulation/engine/Core/Controller.ts new file mode 100644 index 000000000..6d18364e5 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Core/Controller.ts @@ -0,0 +1,88 @@ +export interface Controller { + start?(): Promise | void; + update?(deltaTime: number): void; + fixedUpdate?(fixedDeltaTime: number): void; + onDestroy?(): void; +} + + + +export class ControllerMap> implements Controller { + map: M; + callbacks?: Partial; + + constructor(map: M, callbacks?: Partial) { + this.map = map; + this.callbacks = callbacks; + } + + get(name: K): M[K] { + return this.map[name]; + } + + async start(): Promise { + await this.callbacks?.start?.(); + await Promise.all(Object.values(this.map) + .map(async (controller) => { + await controller.start?.(); + })); + } + + update(deltaTime: number): void { + this.callbacks?.update?.(deltaTime); + Object.values(this.map) + .forEach((controller) => { + controller.update?.(deltaTime); + }); + } + + fixedUpdate(fixedDeltaTime: number): void { + this.callbacks?.fixedUpdate?.(fixedDeltaTime); + Object.values(this.map) + .forEach((controller) => { + controller.fixedUpdate?.(fixedDeltaTime); + }); + } + + onDestroy(): void { + this.callbacks?.onDestroy?.(); + Object.values(this.map) + .forEach((controller) => { + controller.onDestroy?.(); + }); + } +} + + +export class ControllerGroup implements Controller { + private controllers: Controller[] = []; + + public addController(...controllers: Controller[]): void { + this.controllers.push(...controllers); + } + + start(): void { + this.controllers.forEach((controller) => { + controller.start?.(); + }); + } + + update(deltaTime: number): void { + this.controllers.forEach((controller) => { + controller.update?.(deltaTime); + }); + } + + fixedUpdate(fixedDeltaTime: number): void { + this.controllers.forEach((controller) => { + controller.fixedUpdate?.(fixedDeltaTime); + }); + } + + onDestroy(): void { + this.controllers.forEach((controller) => { + controller.onDestroy?.(); + }); + this.controllers = []; + } +} diff --git a/src/bundles/robot_simulation/engine/Core/Events.ts b/src/bundles/robot_simulation/engine/Core/Events.ts new file mode 100644 index 000000000..bc79c8f81 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Core/Events.ts @@ -0,0 +1,88 @@ +import { type FrameTimingInfo } from './Timer'; + +export class TimeStampedEvent extends Event { + frameTimingInfo: FrameTimingInfo; + + constructor(type: string, frameTimingInfo: FrameTimingInfo) { + super(type); + this.frameTimingInfo = frameTimingInfo; + } +} + +// Copied from https://github.com/DerZade/typescript-event-target/blob/master/src/TypedEventTarget.ts + +export type TypedEventListener = ( + evt: M[T] +) => void | Promise; + +export interface TypedEventListenerObject { + handleEvent: (evt: M[T]) => void | Promise; +} + +export type TypedEventListenerOrEventListenerObject = + | TypedEventListener + | TypedEventListenerObject; + +type ValueIsEvent = { + [key in keyof T]: Event; +}; + +export interface TypedEventTarget> { + /** Appends an event listener for events whose type attribute value is type. + * The callback argument sets the callback that will be invoked when the event + * is dispatched. + * + * The options argument sets listener-specific options. For compatibility this + * can be a boolean, in which case the method behaves exactly as if the value + * was specified as options's capture. + * + * When set to true, options's capture prevents callback from being invoked + * when the event's eventPhase attribute value is BUBBLING_PHASE. When false + * (or not present), callback will not be invoked when event's eventPhase + * attribute value is CAPTURING_PHASE. Either way, callback will be invoked if + * event's eventPhase attribute value is AT_TARGET. + * + * When set to true, options's passive indicates that the callback will not + * cancel the event by invoking preventDefault(). This is used to enable + * performance optimizations described in § 2.8 Observing event listeners. + * + * When set to true, options's once indicates that the callback will only be + * invoked once after which the event listener will be removed. + * + * The event listener is appended to target's event listener list and is not + * appended if it has the same type, callback, and capture. */ + addEventListener: ( + type: T, + listener: TypedEventListenerOrEventListenerObject | null, + options?: boolean | AddEventListenerOptions + ) => void; + + /** Removes the event listener in target's event listener list with the same + * type, callback, and options. */ + removeEventListener: ( + type: T, + callback: TypedEventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean + ) => void; + + /** + * Dispatches a synthetic event event to target and returns true if either + * event's cancelable attribute value is false or its preventDefault() method + * was not invoked, and false otherwise. + * @deprecated To ensure type safety use `dispatchTypedEvent` instead. + */ + dispatchEvent: (event: Event) => boolean; +} +export class TypedEventTarget> extends EventTarget { + /** + * Dispatches a synthetic event event to target and returns true if either + * event's cancelable attribute value is false or its preventDefault() method + * was not invoked, and false otherwise. + */ + public dispatchTypedEvent( + _type: T, + event: M[T], + ): boolean { + return super.dispatchEvent(event); + } +} diff --git a/src/bundles/robot_simulation/engine/Core/Timer.ts b/src/bundles/robot_simulation/engine/Core/Timer.ts new file mode 100644 index 000000000..1d7ed5546 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Core/Timer.ts @@ -0,0 +1,64 @@ +export type FrameTimingInfo = { + elapsedTimeReal: number; + elapsedTimeSimulated: number; + frameDuration: number; + framesPerSecond: number; +}; + +export class Timer { + private _isRunning = false; + private _elapsedSimulatedTime = 0; + private _timeSpentPaused = 0; + private _frameDuration = 0; + + + private _startTime: number | null = null; + private _pausedAt: number | null = null; + private _currentTime: number | null = null; + + /** + * Pauses the timer and marks the pause time. + */ + pause(): void { + if (this._currentTime === null) { + return; + } + + if (this._isRunning) { + this._isRunning = false; + this._pausedAt = this._currentTime; + } + } + + /** + * Steps the timer forward, calculates frame timing info. + * @param timestamp - The current timestamp. + * @returns The frame timing information. + */ + step(timestamp: number): FrameTimingInfo { + if (this._startTime === null) { + this._startTime = timestamp; + } + + if (!this._isRunning) { + this._isRunning = true; + } + + if (this._pausedAt !== null) { + this._timeSpentPaused += timestamp - this._pausedAt; + this._pausedAt = null; + } + + this._frameDuration = this._currentTime ? timestamp - this._currentTime : 0; + this._currentTime = timestamp; + + this._elapsedSimulatedTime = timestamp - this._startTime - this._timeSpentPaused; + + return { + elapsedTimeReal: this._currentTime - this._startTime, + elapsedTimeSimulated: this._elapsedSimulatedTime, + frameDuration: this._frameDuration, + framesPerSecond: 1000 / this._frameDuration, + }; + } +} diff --git a/src/bundles/robot_simulation/engine/Entity/Entity.ts b/src/bundles/robot_simulation/engine/Entity/Entity.ts new file mode 100644 index 000000000..c26c8c9b1 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Entity/Entity.ts @@ -0,0 +1,129 @@ +import * as THREE from 'three'; +import type Rapier from '@dimforge/rapier3d-compat'; +import { type SimpleQuaternion, type SimpleVector } from '../Math/Vector'; +import { vec3, quat } from '../Math/Convert'; + +type BodyConfiguration = { + rapierRigidBody: Rapier.RigidBody; + rapierCollider: Rapier.Collider; +}; + +export type Orientation = { + position: SimpleVector; + rotation: SimpleQuaternion; +}; + +export class Entity { + #rapierRigidBody: Rapier.RigidBody; + #rapierCollider: Rapier.Collider; + + constructor(configuration: BodyConfiguration) { + this.#rapierRigidBody = configuration.rapierRigidBody; + this.#rapierCollider = configuration.rapierCollider; + } + + getPosition(): SimpleVector { + return this.#rapierRigidBody.translation(); + } + + getRotation(): SimpleQuaternion { + return this.#rapierRigidBody.rotation(); + } + + setOrientation(orientation: Orientation) { + this.#rapierRigidBody.setTranslation(orientation.position, true); + this.#rapierRigidBody.setRotation(orientation.rotation, true); + } + + setMass(mass: number) { + this.#rapierCollider.setMass(mass); + } + + getMass(): number { + return this.#rapierCollider.mass(); + } + + applyImpulse( + impulse: THREE.Vector3, + point: THREE.Vector3 = new THREE.Vector3(), + ) { + return this.#rapierRigidBody.applyImpulseAtPoint(impulse, point, true); + } + + rotation(): THREE.Quaternion { + return quat(this.#rapierRigidBody.rotation()); + } + + velocity(): THREE.Vector3 { + return vec3(this.#rapierRigidBody.linvel()); + } + + angularVelocity(): THREE.Vector3 { + return vec3(this.#rapierRigidBody.angvel()); + } + + translation(): THREE.Vector3 { + return vec3(this.#rapierRigidBody.translation()); + } + + worldTranslation( + localTranslation: THREE.Vector3 = new THREE.Vector3(), + ): THREE.Vector3 { + const rotation = this.rotation(); + const translation = this.translation(); + + return localTranslation.applyQuaternion(rotation) + .add(translation); + } + + transformDirection(localDirection: THREE.Vector3): THREE.Vector3 { + const rotation = this.rotation(); + return localDirection.clone() + .applyQuaternion(rotation); + } + + distanceVectorOfPointToRotationalAxis( + localPoint: THREE.Vector3 = new THREE.Vector3(), + ) { + return localPoint + .clone() + .projectOnVector(this.angularVelocity()) + .negate() + .add(localPoint); + } + + /** + * Calculates the tangential velocity of a point in a rotating system. + * @param {THREE.Vector3} localPoint - The point for which to calculate the tangential velocity. + * @returns {THREE.Vector3} The tangential velocity vector of the point. + */ + tangentialVelocityOfPoint(localPoint = new THREE.Vector3()): THREE.Vector3 { + // Calculate the distance vector from the point to the rotational axis + const distanceVector + = this.distanceVectorOfPointToRotationalAxis(localPoint); + + // Retrieve the angular velocity of the system + const angularVelocity = this.angularVelocity(); + + // Calculate the magnitude of the tangential velocity + const velocityMagnitude + = distanceVector.length() * angularVelocity.length(); + + // Calculate the tangential velocity vector + const tangentialVelocity = this.transformDirection(localPoint) + .cross(angularVelocity) + .negate() + .normalize() + .multiplyScalar(velocityMagnitude); + + // Return the tangential velocity vector + return tangentialVelocity; + } + + worldVelocity( + localPoint: THREE.Vector3 = new THREE.Vector3(), + ): THREE.Vector3 { + return this.tangentialVelocityOfPoint(localPoint) + .add(this.velocity()); + } +} diff --git a/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts b/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts new file mode 100644 index 000000000..f9d9235bc --- /dev/null +++ b/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts @@ -0,0 +1,41 @@ +import { type Physics } from '../Physics'; +import { Entity, type Orientation } from './Entity'; + +type RigidBodyTypes = 'fixed' | 'dynamic'; + +export type EntityCuboidOptions = { + orientation: Orientation; + width: number; + height: number; + length: number; + mass: number; + type: RigidBodyTypes; +}; + +export function addCuboid( + physics: Physics, + options: EntityCuboidOptions, +): Entity { + const { orientation, width, height, length, type, mass } = options; + + const rigidBodyDesc = physics.RAPIER.RigidBodyDesc[type](); + const colliderDesc = physics.RAPIER.ColliderDesc.cuboid( + width / 2, + height / 2, + length / 2, + ); + + colliderDesc.mass = mass; + + const rigidBody = physics.createRigidBody(rigidBodyDesc); + const collider = physics.createCollider(colliderDesc, rigidBody); + + const entity = new Entity({ + rapierRigidBody: rigidBody, + rapierCollider: collider, + }); + + entity.setOrientation(orientation); + + return entity; +} diff --git a/src/bundles/robot_simulation/engine/Math/Convert.ts b/src/bundles/robot_simulation/engine/Math/Convert.ts new file mode 100644 index 000000000..aca09ae6a --- /dev/null +++ b/src/bundles/robot_simulation/engine/Math/Convert.ts @@ -0,0 +1,5 @@ +import { Euler, Quaternion, Vector3 } from 'three'; + +export const quat = ({ x, y, z, w }) => new Quaternion(x, y, z, w); +export const vec3 = ({ x, y, z }) => new Vector3(x, y, z); +export const euler = ({ x, y, z }) => new Euler(x, y, z); diff --git a/src/bundles/robot_simulation/engine/Math/Vector.ts b/src/bundles/robot_simulation/engine/Math/Vector.ts new file mode 100644 index 000000000..5ca43320c --- /dev/null +++ b/src/bundles/robot_simulation/engine/Math/Vector.ts @@ -0,0 +1,2 @@ +export type SimpleVector = { x: number, y: number, z: number }; +export type SimpleQuaternion = { x: number, y: number, z: number, w: number }; diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts new file mode 100644 index 000000000..04e899c26 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -0,0 +1,110 @@ +import rapier from '@dimforge/rapier3d-compat'; + +import { type SimpleVector } from './Math/Vector'; +import { type FrameTimingInfo } from './Core/Timer'; + +export type PhysicsConfig = { + gravity: SimpleVector; + timestep: number; +}; + +type NotInitializedInternals = { + initialized: false; +}; + +type InitializedInternals = { + initialized: true; + world: rapier.World; + accumulator: number; +}; + +type PhysicsInternals = NotInitializedInternals | InitializedInternals; + +export class Physics { + RAPIER: typeof rapier; + configuration: PhysicsConfig; + internals: PhysicsInternals; + + constructor(configuration: PhysicsConfig) { + this.configuration = configuration; + this.RAPIER = rapier; + + this.internals = { initialized: false }; + } + + async start() { + await rapier.init(); + console.log('Physics started'); + + this.RAPIER = rapier; + + const world = new rapier.World(this.configuration.gravity); + world.timestep = this.configuration.timestep; + + this.internals = { + initialized: true, + world, + accumulator: 0, + }; + } + + createRigidBody(rigidBodyDesc: rapier.RigidBodyDesc): rapier.RigidBody { + if (this.internals.initialized === false) { + throw Error("Physics engine hasn't been initialized yet"); + } + + return this.internals.world.createRigidBody(rigidBodyDesc); + } + + createCollider( + colliderDesc: rapier.ColliderDesc, + rigidBody: rapier.RigidBody, + ): rapier.Collider { + if (this.internals.initialized === false) { + throw Error("Physics engine hasn't been initialized yet"); + } + return this.internals.world.createCollider(colliderDesc, rigidBody); + } + + castRay( + globalPosition: THREE.Vector3, + globalDirection: THREE.Vector3, + maxDistance: number, + ) : { + distance: number; + normal: SimpleVector; + } | null { + if (this.internals.initialized === false) { + throw Error("Physics engine hasn't been initialized yet"); + } + + const ray = new this.RAPIER.Ray(globalPosition, globalDirection); + const result = this.internals.world.castRayAndGetNormal(ray, maxDistance, false); + + this.internals.world.castRay(ray, maxDistance, false); + + if (result === null) { + return null; + } + + return { + distance: result.toi, + normal: result.normal, + }; + } + + step(timing: FrameTimingInfo) : void { + if (this.internals.initialized === false) { + throw Error("Physics engine hasn't been initialized yet"); + } + + const maxFrameTime = 0.05; + const frameDuration = timing.frameDuration / 1000; + this.internals.accumulator += Math.min(frameDuration, maxFrameTime); + + while (this.internals.accumulator >= this.configuration.timestep) { + this.internals.world.step(); + this.internals.accumulator -= this.configuration.timestep; + } + } +} diff --git a/src/bundles/robot_simulation/engine/Render/Camera.ts b/src/bundles/robot_simulation/engine/Render/Camera.ts new file mode 100644 index 000000000..1756bd1e0 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Render/Camera.ts @@ -0,0 +1,36 @@ +import * as THREE from 'three'; + +type OrthographicCameraOptions = { + type: 'orthographic'; +}; + +type PerspectiveCameraOptions = { + type: 'perspective'; + fov: number; + aspect: number; + near: number; + far: number; +}; + +export type CameraOptions = + | OrthographicCameraOptions + | PerspectiveCameraOptions; + +export function getCamera(cameraOptions: CameraOptions): THREE.Camera { + switch (cameraOptions.type) { + case 'perspective': + return new THREE.PerspectiveCamera( + cameraOptions.fov, + cameraOptions.aspect, + cameraOptions.near, + cameraOptions.far, + ); + case 'orthographic': + return new THREE.OrthographicCamera(); + default: { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars + const _: never = cameraOptions; + throw new Error(`Unknown camera type: ${cameraOptions}`); + } + } +} diff --git a/src/bundles/robot_simulation/engine/Render/MeshFactory.ts b/src/bundles/robot_simulation/engine/Render/MeshFactory.ts new file mode 100644 index 000000000..9c51c346f --- /dev/null +++ b/src/bundles/robot_simulation/engine/Render/MeshFactory.ts @@ -0,0 +1,33 @@ +import * as THREE from 'three'; +import { type Orientation } from '../Entity/Entity'; + +export type RenderCuboidOptions = { + orientation: Orientation; + width: number; + height: number; + length: number; + color: THREE.Color; + debug: boolean; +}; + +export function addCuboid(options: RenderCuboidOptions): THREE.Mesh { + const { orientation, width, height, length, color } = options; + const geometry = new THREE.BoxGeometry(width, height, length); + const material = options.debug + ? new THREE.MeshPhysicalMaterial({ + color, + wireframe: true, + }) + : new THREE.MeshPhysicalMaterial({ + color, + side: THREE.DoubleSide, + }); + + + const mesh = new THREE.Mesh(geometry, material); + + mesh.position.copy(orientation.position as THREE.Vector3); + mesh.quaternion.copy(orientation.rotation as THREE.Quaternion); + + return mesh; +} diff --git a/src/bundles/robot_simulation/engine/Render/Renderer.ts b/src/bundles/robot_simulation/engine/Render/Renderer.ts new file mode 100644 index 000000000..538aaf5a2 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Render/Renderer.ts @@ -0,0 +1,90 @@ +/* eslint-disable import/extensions */ +import * as THREE from 'three'; +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; + +import { type FrameTimingInfo } from '../Core/Timer'; +import { + type GLTF, + GLTFLoader, +} from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { getCamera, type CameraOptions } from './Camera'; + +type ControlType = 'none' | 'orbit'; + +export type RenderConfig = { + width: number; + height: number; + control: ControlType +}; + +export class Renderer { + element?: HTMLElement; + + #scene: THREE.Scene; + #camera: THREE.Camera; + #renderer: THREE.WebGLRenderer; + #controls: OrbitControls; + + constructor( + scene: THREE.Scene, + camera: THREE.Camera, + configuration: RenderConfig, + ) { + this.#camera = camera; + this.#scene = scene; + this.#renderer = new THREE.WebGLRenderer({ antialias: true }); + + this.#controls = new OrbitControls(this.#camera, this.#renderer.domElement); + + this.#renderer.setSize(configuration.width, configuration.height); + this.#renderer.setPixelRatio(window.devicePixelRatio * 1.5); + + const light = new THREE.AmbientLight(0xffffff); + this.#scene.add(light); + this.#scene.background = new THREE.Color(0xffffff); + } + + static scene(): THREE.Scene { + return new THREE.Scene(); + } + + static camera(cameraOptions: CameraOptions): THREE.Camera { + const camera = getCamera(cameraOptions); + return camera; + } + + static sensorCamera(): THREE.Camera { + const renderAspectRatio = 1; + return new THREE.PerspectiveCamera(10, renderAspectRatio, 0.01, 1000); + } + + static loadGTLF(url: string): Promise { + const loader = new GLTFLoader(); + return new Promise((resolve, reject) => { + loader.load(url, resolve, () => {}, reject); + }); + } + + scene(): THREE.Scene { + return this.#scene; + } + + render() { + return this.#renderer.render(this.#scene, this.#camera); + } + + getElement(): HTMLCanvasElement { + return this.#renderer.domElement; + } + + add( + ...input: Parameters + ): ReturnType { + return this.#scene.add(...input); + } + + step(_: FrameTimingInfo) { + this.render(); + this.#controls.update(); + } +} diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/engine/World.ts new file mode 100644 index 000000000..c78fe63f1 --- /dev/null +++ b/src/bundles/robot_simulation/engine/World.ts @@ -0,0 +1,122 @@ +import { type Controller, ControllerGroup } from './Core/Controller'; +import { TimeStampedEvent, TypedEventTarget } from './Core/Events'; +import { type Physics } from './Physics'; +import { type Renderer } from './Render/Renderer'; +import { type Timer } from './Core/Timer'; + +export const worldStates = [ + 'unintialized', + 'loading', + 'ready', + 'running', +] as const; +export type WorldState = (typeof worldStates)[number]; + +type WorldEventMap = { + worldStart: Event; + worldStateChange: Event; + beforePhysicsUpdate: TimeStampedEvent; + afterPhysicsUpdate: TimeStampedEvent; + beforeRender: TimeStampedEvent; + afterRender: TimeStampedEvent; +}; + +export class World extends TypedEventTarget { + state: WorldState; + physics: Physics; + render: Renderer; + timer: Timer; + controllers: ControllerGroup; + + constructor(physics: Physics, render: Renderer, timer: Timer) { + super(); + this.state = 'unintialized'; + this.physics = physics; + this.render = render; + this.timer = timer; + this.controllers = new ControllerGroup(); + } + + addController(...controllers: Controller[]) { + this.controllers.addController(...controllers); + + this.addEventListener('worldStart', () => { + controllers.forEach((controller) => { + controller.start?.(); + }); + }); + + this.addEventListener('beforeRender', (e) => { + controllers.forEach((controller) => { + controller.update?.(e.frameTimingInfo.frameDuration); + }); + }); + + this.addEventListener('beforePhysicsUpdate', () => { + controllers.forEach((controller) => { + controller.fixedUpdate?.(this.physics.configuration.timestep); + }); + }); + } + + async init() { + this.setState('loading'); + await this.physics.start(); + this.dispatchTypedEvent('worldStart', new Event('worldStart')); + this.setState('ready'); + } + + private setState(newState: WorldState) { + if (this.state !== newState) { + this.dispatchTypedEvent( + 'worldStateChange', + new Event('worldStateChange'), + ); + this.state = newState; + } + } + + pause() { + this.setState('ready'); + this.timer.pause(); + } + + start() { + if (this.state === 'ready') { + this.setState('running'); + window.requestAnimationFrame(this.step.bind(this)); + } + } + + step(timestamp: number) { + const frameTimingInfo = this.timer.step(timestamp); + + // Update physics + this.dispatchTypedEvent( + 'beforePhysicsUpdate', + new TimeStampedEvent('beforePhysicsUpdate', frameTimingInfo), + ); + this.physics.step(frameTimingInfo); + this.dispatchTypedEvent( + 'afterPhysicsUpdate', + new TimeStampedEvent('afterPhysicsUpdate', frameTimingInfo), + ); + + + // Update render + this.dispatchTypedEvent( + 'beforeRender', + new TimeStampedEvent('beforeRender', frameTimingInfo), + ); + this.render.step(frameTimingInfo); + this.dispatchTypedEvent( + 'afterRender', + new TimeStampedEvent('afterRender', frameTimingInfo), + ); + + + if (this.state === 'running') { + window.requestAnimationFrame(this.step.bind(this)); + } + } +} diff --git a/src/bundles/robot_simulation/engine/__tests__/Physics.ts b/src/bundles/robot_simulation/engine/__tests__/Physics.ts new file mode 100644 index 000000000..c3bad127f --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Physics.ts @@ -0,0 +1,120 @@ +// physics.test.js +import { Physics } from '../Physics'; +import rapier from '@dimforge/rapier3d-compat'; + +// Mock rapier +jest.mock('@dimforge/rapier3d-compat', () => { + return { + init: jest.fn(), + World: jest.fn().mockImplementation(() => ({ + timestep: jest.fn(), + createRigidBody: jest.fn(), + createCollider: jest.fn(), + castRayAndGetNormal: jest.fn(), + step: jest.fn(), + castRay: jest.fn(), + })), + Ray: jest.fn(), + RigidBodyDesc: jest.fn(), + ColliderDesc: jest.fn(), + }; +}); + +describe('Physics', () => { + let physics; + const config = { gravity: { x: 0, y: -9.81, z: 0 }, timestep: 1 / 60 }; + + beforeEach(() => { + physics = new Physics(config); + }); + + test('constructor initializes configuration', () => { + expect(physics.configuration).toEqual(config); + }); + + test('start initializes the physics world', async () => { + await physics.start(); + expect(rapier.init).toHaveBeenCalled(); + expect(physics.internals).toHaveProperty('initialized', true); + expect(physics.internals).toHaveProperty('world'); + expect(physics.internals).toHaveProperty('accumulator', 0); + }); + + + test('createRigidBody throws if not initialized', () => { + expect(() => physics.createRigidBody({})).toThrow("Physics engine hasn't been initialized yet"); + }); + + test('createRigidBody creates a rigid body when initialized', async () => { + await physics.start(); // Initialize + const rigidBodyDesc = {}; // Mocked rigid body descriptor + physics.createRigidBody(rigidBodyDesc); + expect(physics.internals.world.createRigidBody).toHaveBeenCalledWith(rigidBodyDesc); + }); + + test('createCollider creates a collider when initialized', async () => { + await physics.start(); // Initialize + const colliderDesc = {}; // Mocked collider descriptor + const rigidBody = {}; // Mocked rigid body + physics.createCollider(colliderDesc, rigidBody); + expect(physics.internals.world.createCollider).toHaveBeenCalledWith(colliderDesc, rigidBody); + }); + + test('castRay returns correct result when initialized', async () => { + await physics.start(); // Initialize + const globalPosition = {}; // Mocked global position + const globalDirection = {}; // Mocked global direction + const maxDistance = 100; + + // Mock the return value of castRayAndGetNormal + const expectedResult = { toi: 10, normal: { x: 0, y: 1, z: 0 } }; + physics.internals.world.castRayAndGetNormal.mockReturnValue(expectedResult); + + const result = physics.castRay(globalPosition, globalDirection, maxDistance); + expect(result).toEqual({ + distance: expectedResult.toi, + normal: expectedResult.normal, + }); + expect(physics.internals.world.castRayAndGetNormal).toHaveBeenCalledWith(expect.anything(), maxDistance, false); + }); + + test('castRay returns null result castRayAndGetNormal returns null', async () => { + await physics.start(); // Initialize + const globalPosition = {}; // Mocked global position + const globalDirection = {}; // Mocked global direction + const maxDistance = 100; + + // Mock the return value of castRayAndGetNormal + const expectedResult = null; + physics.internals.world.castRayAndGetNormal.mockReturnValue(expectedResult); + + const result = physics.castRay(globalPosition, globalDirection, maxDistance); + expect(result).toEqual(null); + }); + + test('step advances physics world by correct timestep', async () => { + await physics.start(); // Initialize + const frameTimingInfo = { frameDuration: 1000 / 60 }; // 60 FPS + physics.step(frameTimingInfo); + expect(physics.internals.world.step).toHaveBeenCalledTimes(1); + }); + + test('castRay throws if not initialized', () => { + expect(() => { + physics.castRay({}, {}, 100); + }).toThrow("Physics engine hasn't been initialized yet"); + }); + + test('createCollider throws if not initialized', () => { + expect(() => { + physics.createCollider({}, {}, 100); + }).toThrow("Physics engine hasn't been initialized yet"); + }); + + test('step throws if not initialized', () => { + expect(() => { + physics.step({ frameDuration: 1000 / 60 }); + }).toThrow("Physics engine hasn't been initialized yet"); + }); +}); + diff --git a/src/bundles/robot_simulation/engine/__tests__/Timer.ts b/src/bundles/robot_simulation/engine/__tests__/Timer.ts new file mode 100644 index 000000000..e8630fb50 --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Timer.ts @@ -0,0 +1,93 @@ +import { Timer } from '../Core/Timer'; // Adjust the import path as per your project structure + +describe('Timer', () => { + let timer; + let mockTimestamp; + + beforeEach(() => { + timer = new Timer(); + mockTimestamp = 1000; // Initial mock timestamp in milliseconds + }); + + describe('pause method', () => { + it('should not pause if the timer has not started', () => { + timer.pause(); + expect(timer).toHaveProperty('_isRunning', false); + expect(timer).toHaveProperty('_pausedAt', null); + }); + + it('should pause the running timer', () => { + timer.step(mockTimestamp); // Start the timer + timer.pause(); + expect(timer).toHaveProperty('_isRunning', false); + expect(timer).toHaveProperty('_pausedAt', mockTimestamp); + }); + }); + + describe('step method', () => { + it('should start the timer if not already started', () => { + timer.step(mockTimestamp); + expect(timer).toHaveProperty('_startTime', mockTimestamp); + }); + + it('should update the current time', () => { + timer.step(mockTimestamp); + expect(timer).toHaveProperty('_currentTime', mockTimestamp); + }); + + it('should accumulate paused time', () => { + timer.step(mockTimestamp); // Start the timer + timer.pause(); + const resumeTimestamp = 2000; + timer.step(resumeTimestamp); + expect(timer).toHaveProperty('_timeSpentPaused', resumeTimestamp - mockTimestamp); + }); + + it('should calculate the correct frame duration', () => { + timer.step(mockTimestamp); + const newTimestamp = 1500; + timer.step(newTimestamp); + expect(timer).toHaveProperty('_frameDuration', 500); + }); + + it('should resume the timer if it was paused', () => { + timer.step(mockTimestamp); + timer.pause(); + const resumeTimestamp = 2000; + timer.step(resumeTimestamp); + expect(timer).toHaveProperty('_isRunning', true); + }); + + it('should return correct FrameTimingInfo', () => { + const frameTimingInfo = timer.step(mockTimestamp); + expect(frameTimingInfo).toMatchObject({ + elapsedTimeReal: expect.any(Number), + elapsedTimeSimulated: expect.any(Number), + frameDuration: expect.any(Number), + framesPerSecond: expect.any(Number), + }); + }); + + it('should correctly handle time after pause and resume', () => { + const startTimestamp = 1000; + const pauseTimestamp = 2000; + const resumeTimestamp = 3000; + const finalTimestamp = 4000; + + timer.step(startTimestamp); // Start the timer + timer.pause(); + + timer.step(pauseTimestamp); // Paused at 2000ms + timer.step(resumeTimestamp); // Resumed at 3000ms + const frameTimingInfo = timer.step(finalTimestamp); // Stepped at 4000ms + + // Total elapsed time should be 3000ms (4000 - 1000) + // Time spent paused is 1000ms (3000 - 2000) + // Therefore, elapsed simulated time should be 2000ms (3000 - 1000) + expect(frameTimingInfo.elapsedTimeSimulated).toBe(2000); + expect(frameTimingInfo.elapsedTimeReal).toBe(3000); + expect(frameTimingInfo.frameDuration).toBe(1000); // Time since last step + expect(frameTimingInfo.framesPerSecond).toBeCloseTo(1); // 1000ms frame duration + }); + }); +}); diff --git a/src/bundles/robot_simulation/engine/index.ts b/src/bundles/robot_simulation/engine/index.ts new file mode 100644 index 000000000..69b522946 --- /dev/null +++ b/src/bundles/robot_simulation/engine/index.ts @@ -0,0 +1,8 @@ +export { World } from './World'; +export { Physics } from './Physics'; +export { Renderer } from './Render/Renderer'; +export { Timer } from './Core/Timer'; +export { ControllerGroup, Controller, ControllerMap } from './Core/Controller'; +export { Entity } from './Entity/Entity'; +export * as EntityFactory from './Entity/EntityFactory'; +export * as MeshFactory from './Render/MeshFactory'; diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts index 42db69e42..7d6de98a7 100644 --- a/src/bundles/robot_simulation/functions.ts +++ b/src/bundles/robot_simulation/functions.ts @@ -10,8 +10,8 @@ import context from 'js-slang/context'; import { getWorld } from './simulation'; import { type MotorsOptions } from './simulation/controllers/car/car_controller'; -export function show() { - console.log('This is the show function'); +export async function show() { + throw new Error('Interrupt execution by module'); } export function init_simulation() { diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index a4bed35a4..4d3c02afd 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1,10 +1,16 @@ export { show, + ev3_pause, +} from './functions'; + +export { + init_simulation, ev3_motorA, ev3_motorB, ev3_motorC, ev3_motorD, - init_simulation, ev3_runToRelativePosition, - ev3_pause, -} from './functions'; + ev3_colorSensorRed, + ev3_colorSensorGreen, + ev3_colorSensorBlue, +} from './main'; diff --git a/src/bundles/robot_simulation/main.ts b/src/bundles/robot_simulation/main.ts new file mode 100644 index 000000000..4be8efb98 --- /dev/null +++ b/src/bundles/robot_simulation/main.ts @@ -0,0 +1,141 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { + chassisConfig, + colorSensorConfig, + meshConfig, + motorDisplacements, + motorPidConfig, + physicsConfig, + renderConfig, + sceneCamera, + wheelDisplacements, + wheelPidConfig, +} from './config'; +import { Environment, type DefaultEv3, Program } from './controllers'; +import { type Motor } from './controllers/ev3/components/Motor'; +import { createDefaultEv3 } from './controllers/ev3/ev3/default'; +import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; +import { Physics, Renderer, Timer, World } from './engine'; + +import context from 'js-slang/context'; + +type MotorFunctionReturnType = Motor | null; + +const storedWorld = context.moduleContexts.robot_simulation.state?.world; + +export function getWorld(): World { + const world = context.moduleContexts.robot_simulation.state?.world; + if (world === undefined) { + throw new Error('World not initialized'); + } + return world as World; +} + +export function getEv3(): DefaultEv3 { + const ev3 = context.moduleContexts.robot_simulation.state?.ev3; + if (ev3 === undefined) { + throw new Error('ev3 not initialized'); + } + return ev3 as DefaultEv3; +} + +export function init_simulation() { + console.log(storedWorld, 'storedWorld'); + if (storedWorld !== undefined) { + return; + } + + const code = context.unTypecheckedCode[0]; + + const scene = Renderer.scene(); + const camera = Renderer.camera(sceneCamera); + camera.translateY(2); + camera.translateZ(-2); + const renderer = new Renderer(scene, camera, renderConfig); + + const physics = new Physics(physicsConfig); + + const timer = new Timer(); + const world = new World(physics, renderer, timer); + + const environmentController = new Environment(physics, renderer); + world.addController(environmentController); + + const program = new Program(code); + world.addController(program); + + const config = { + chassis: chassisConfig, + motor: { + displacements: motorDisplacements, + pid: motorPidConfig, + }, + wheel: { + displacements: wheelDisplacements, + pid: wheelPidConfig, + }, + colorSensor: { + displacement: colorSensorConfig.displacement, + }, + mesh: meshConfig, + }; + + const ev3 = createDefaultEv3(physics, renderer, config); + + world.addController(ev3); + world.init(); + + context.moduleContexts.robot_simulation.state = { + world, + ev3, + }; + + throw new Error('Interrupt'); +} + +export function ev3_motorA(): MotorFunctionReturnType { + const ev3 = getEv3(); + return ev3.get('leftMotor'); +} + +export function ev3_motorB(): MotorFunctionReturnType { + const ev3 = getEv3(); + return ev3.get('rightMotor'); +} + +export function ev3_motorC(): MotorFunctionReturnType { + return null; +} + +export function ev3_motorD(): MotorFunctionReturnType { + return null; +} + +export function ev3_runToRelativePosition( + motor: MotorFunctionReturnType, + position: number, + speed: number, +): void { + if (motor === null) { + return; + } + + motor.setVelocity(speed); +} + +export function ev3_colorSensor() { + const ev3 = getEv3(); + return ev3.get('colorSensor'); +} + +export function ev3_colorSensorRed(colorSensor: ColorSensor) { + return colorSensor.sense().r; +} + +export function ev3_colorSensorGreen(colorSensor: ColorSensor) { + return colorSensor.sense().g; +} + +export function ev3_colorSensorBlue(colorSensor: ColorSensor) { + return colorSensor.sense().b; +} diff --git a/src/bundles/robot_simulation/simulation/index.ts b/src/bundles/robot_simulation/simulation/index.ts index 47e0c7312..eefbfa4e6 100644 --- a/src/bundles/robot_simulation/simulation/index.ts +++ b/src/bundles/robot_simulation/simulation/index.ts @@ -1,16 +1,16 @@ import context from 'js-slang/context'; -import { World } from './world'; +import { type World } from './world'; console.log('World is being initialized'); const contextState = context.moduleContexts.robot_simulation.state?.world; -if (contextState === undefined) { - context.moduleContexts.robot_simulation.state = { - ...context.moduleContexts.robot_simulation.state, - world: new World(), - }; - console.log('world is being set to initial state'); -} +// if (contextState === undefined) { +// context.moduleContexts.robot_simulation.state = { +// ...context.moduleContexts.robot_simulation.state, +// world: new World(), +// }; +// console.log('world is being set to initial state'); +// } export const getWorld = (): World => context.moduleContexts.robot_simulation.state.world; diff --git a/src/tabs/RobotSimulation/components/Modal.tsx b/src/tabs/RobotSimulation/components/Modal.tsx index 683b9e401..f4d12556a 100644 --- a/src/tabs/RobotSimulation/components/Modal.tsx +++ b/src/tabs/RobotSimulation/components/Modal.tsx @@ -1,4 +1,4 @@ -import { useEffect, type CSSProperties, type ReactNode } from 'react'; +import { type CSSProperties, type ReactNode } from 'react'; type ModalProps = { isOpen: boolean; diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 88ee9ac16..00bf7fae7 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -1,15 +1,36 @@ import { useRef, type CSSProperties, useEffect, useState } from 'react'; import type { DebuggerContext } from '../../../../typings/type_helpers'; -import { - type SimulationStates, - type World, -} from '../../../../bundles/robot_simulation/simulation/world'; +import { type SimulationStates } from '../../../../bundles/robot_simulation/simulation/world'; +import { type World } from '../../../../bundles/robot_simulation/engine'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; +import { Tab, Tabs } from '@blueprintjs/core'; +import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; +import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; +import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; +import { MonitoringPanel } from '../TabPanels/MonitoringPanel'; -const CanvasWrapperStyle: CSSProperties = { - width: 800, - height: 600, - backgroundColor: 'black', +const WrapperStyle: CSSProperties = { + display: 'flex', + flexDirection: 'column', + gap: '0.6rem', +}; + +const CanvasStyle: CSSProperties = { + width: 900, + height: 500, + borderRadius: 3, + overflow: 'hidden', + boxShadow: 'inset 0 0 0 1px rgba(255, 255, 255, 0.2)', +}; + +const bottomPanelStyle: CSSProperties = { + width: 900, + height: 200, + backgroundColor: '#1a2530', + borderRadius: 3, + overflow: 'hidden', + boxShadow: 'inset 0 0 0 1px rgba(255, 255, 255, 0.2)', }; export default function SimulationCanvas({ @@ -20,11 +41,15 @@ export default function SimulationCanvas({ isOpen: boolean; }) { const ref = useRef(null); + const sensorRef = useRef(null); const [currentState, setCurrentState] = useState('unintialized'); const world = context.context.moduleContexts.robot_simulation.state .world as World; + const ev3 = context.context.moduleContexts.robot_simulation.state + .ev3 as DefaultEv3; + useEffect(() => { const startThreeAndRapierEngines = async () => { setCurrentState(world.state); @@ -32,7 +57,12 @@ export default function SimulationCanvas({ const attachRenderDom = () => { if (ref.current) { - world.setRendererOutput(ref.current); + ref.current.replaceChildren(world.render.getElement()); + } + + if (sensorRef.current) { + sensorRef.current.replaceChildren(ev3.get('colorSensor') + .getElement()); } }; @@ -52,17 +82,25 @@ export default function SimulationCanvas({ useEffect(() => { if (isOpen) { - world.startSimulation(); + world.start(); } else { - world.stopSimulation(); + world.pause(); } }, [isOpen]); return ( - <> -
- {currentState} +
+
+
{currentState}
+
+
+ + } /> + } /> + } /> + }/> +
- +
); } diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx new file mode 100644 index 000000000..831e3abfc --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx @@ -0,0 +1,30 @@ +import { useEffect, useRef, useState } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default'; +import { Switch } from '@blueprintjs/core'; + +export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { + const colorSensor = ev3.get('colorSensor'); + const sensorVisionRef = useRef(null); + const [_, update] = useState(0); + const colorSensed = colorSensor.sense(); + + useEffect(() => { + if (sensorVisionRef.current) { + sensorVisionRef.current.replaceChildren(colorSensor.getElement()); + } + + // Hacky + setInterval(() => { + update((i) => i + 1); + }, 1000); + }, []); + + return <> +
+
+

Red: {colorSensed.r}

+

Green: {colorSensed.g}

+

Blue: {colorSensed.b}

+
+ ; +}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx new file mode 100644 index 000000000..0fef2f299 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx @@ -0,0 +1,3 @@ +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; + +export const MonitoringPanel = ({ ev3 }: { ev3: DefaultEv3 }) => <>hi; diff --git a/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx new file mode 100644 index 000000000..9568e95cc --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx @@ -0,0 +1,57 @@ +import { type CSSProperties } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; +import { NumericInput } from '@blueprintjs/core'; + +const RowStyle: CSSProperties = { + display: 'flex', + flexDirection: 'row', + gap: '0.6rem', +}; + +export const MotorPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { + const onChangeProportional = (value: number) => { + ev3.get('leftMotor').pid.proportionalGain = value; + ev3.get('rightMotor').pid.proportionalGain = value; + }; + const onChangeIntegral = (value: number) => { + ev3.get('leftMotor').pid.integralGain = value; + ev3.get('rightMotor').pid.integralGain = value; + }; + const onChangeDerivative = (value: number) => { + ev3.get('leftMotor').pid.derivativeGain = value; + ev3.get('rightMotor').pid.derivativeGain = value; + }; + + + return ( +
+
+ Proportional Gain: + +
+
+ Integral Gain: + +
+
+ Derivative Gain: + +
+
+ ); +}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx new file mode 100644 index 000000000..2e312eff2 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx @@ -0,0 +1,63 @@ +import { type CSSProperties } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; +import { NumericInput } from '@blueprintjs/core'; + +const RowStyle: CSSProperties = { + display: 'flex', + flexDirection: 'row', + gap: '0.6rem', +}; + +export const WheelPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { + const onChangeProportional = (value: number) => { + ev3.get('backLeftWheel').pid.proportionalGain = value; + ev3.get('backRightWheel').pid.proportionalGain = value; + ev3.get('frontLeftWheel').pid.proportionalGain = value; + ev3.get('frontRightWheel').pid.proportionalGain = value; + }; + const onChangeIntegral = (value: number) => { + ev3.get('backLeftWheel').pid.integralGain = value; + ev3.get('backRightWheel').pid.integralGain = value; + ev3.get('frontLeftWheel').pid.integralGain = value; + ev3.get('frontRightWheel').pid.integralGain = value; + }; + const onChangeDerivative = (value: number) => { + ev3.get('backLeftWheel').pid.derivativeGain = value; + ev3.get('backRightWheel').pid.derivativeGain = value; + ev3.get('frontLeftWheel').pid.derivativeGain = value; + ev3.get('frontRightWheel').pid.derivativeGain = value; + }; + + + return ( +
+
+ Proportional Gain: + +
+
+ Integral Gain: + +
+
+ Derivative Gain: + +
+
+ ); +}; diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/index.tsx index 488ecaca6..4211d2135 100644 --- a/src/tabs/RobotSimulation/index.tsx +++ b/src/tabs/RobotSimulation/index.tsx @@ -16,8 +16,6 @@ type Props = { context?: any; }; - - export default { /** * This function will be called to determine if the component will be @@ -26,8 +24,10 @@ export default { * @returns {boolean} */ toSpawn(context: DebuggerContext) { - console.log(context.context.moduleContexts.robot_simulation.state.world); - return context.context.moduleContexts.robot_simulation.state.world?.state !== 'idle'; + const worldState = context.context.moduleContexts.robot_simulation.state?.world?.state; + return ( + worldState !== undefined + ); }, /** @@ -35,7 +35,7 @@ export default { * on Source Academy frontend. * @param {DebuggerContext} context */ - body: (context: DebuggerContext) =>
, + body: (context: DebuggerContext) =>
, /** * The Tab's icon tooltip in the side contents on Source Academy frontend. From 2543145b734193bb5420e3a5d6141959ceeac378 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 22 Jan 2024 09:37:02 +0000 Subject: [PATCH 26/93] Added color sensor --- .../controllers/ev3/components/Wheel.ts | 36 +++++++++++++------ .../controllers/ev3/ev3/default.ts | 4 +++ .../controllers/ev3/sensor/ColorSensor.ts | 5 +-- .../controllers/program/Program.ts | 36 +++++++++++-------- .../engine/Core/CallbackHandler.ts | 33 +++++++++++++++++ .../engine/Core/Controller.ts | 26 +++++++------- .../engine/Render/Renderer.ts | 2 +- src/bundles/robot_simulation/engine/World.ts | 5 +-- src/bundles/robot_simulation/engine/index.ts | 2 +- src/bundles/robot_simulation/index.ts | 6 +--- src/bundles/robot_simulation/main.ts | 16 +++++++++ 11 files changed, 123 insertions(+), 48 deletions(-) create mode 100644 src/bundles/robot_simulation/engine/Core/CallbackHandler.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index 35af73b02..199520a5b 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -1,4 +1,4 @@ -import { type Controller, type Physics } from '../../../engine'; +import { type Controller, type Physics, type Renderer } from '../../../engine'; import { type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; import { NumberPidController } from '../feedback_control/PidController'; @@ -10,8 +10,8 @@ type WheelConfig = { proportionalGain: number; derivativeGain: number; integralGain: number; - }, - debug: boolean, + }; + debug: boolean; }; export class Wheel implements Controller { @@ -23,7 +23,13 @@ export class Wheel implements Controller { physics: Physics; arrowHelper: THREE.ArrowHelper; - constructor(chassisWrapper: ChassisWrapper, physics: Physics, displacement: SimpleVector, config: WheelConfig) { + constructor( + chassisWrapper: ChassisWrapper, + physics: Physics, + render: Renderer, + displacement: SimpleVector, + config: WheelConfig, + ) { this.chassisWrapper = chassisWrapper; this.physics = physics; this.pid = new NumberPidController(config.pid); @@ -36,6 +42,8 @@ export class Wheel implements Controller { }); this.arrowHelper = new THREE.ArrowHelper(); this.arrowHelper.visible = config.debug; + this.arrowHelper.setColor('red'); + render.add(this.arrowHelper); } fixedUpdate(_: number): void { @@ -45,8 +53,14 @@ export class Wheel implements Controller { this.displacementVector.clone(), ); - const globalDownDirection = chassis.transformDirection(this.downVector.clone()); - const result = this.physics.castRay(globalDisplacement, globalDownDirection, 0.2); + const globalDownDirection = chassis.transformDirection( + this.downVector.clone(), + ); + const result = this.physics.castRay( + globalDisplacement, + globalDownDirection, + 0.2, + ); // Wheels are not touching the ground if (result === null) { @@ -60,9 +74,11 @@ export class Wheel implements Controller { .normalize() .multiplyScalar(error * chassis.getMass()); - chassis.applyImpulse( - force, - globalDisplacement, - ); + + chassis.applyImpulse(force, globalDisplacement); + + this.arrowHelper.setLength(force.length() * 5000); + this.arrowHelper.setDirection(force.normalize()); + this.arrowHelper.position.copy(globalDisplacement); } } diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts index 99bc5901e..a5906c95b 100644 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts @@ -69,6 +69,7 @@ export const createDefaultEv3 = ( const frontLeftWheel = new Wheel( chassis, physics, + render, config.wheel.displacements.frontLeftWheel, wheelPidConfig, ); @@ -76,6 +77,7 @@ export const createDefaultEv3 = ( const frontRightWheel = new Wheel( chassis, physics, + render, config.wheel.displacements.frontRightWheel, wheelPidConfig, ); @@ -83,12 +85,14 @@ export const createDefaultEv3 = ( const backLeftWheel = new Wheel( chassis, physics, + render, config.wheel.displacements.backLeftWheel, wheelPidConfig, ); const backRightWheel = new Wheel( chassis, physics, + render, config.wheel.displacements.backRightWheel, wheelPidConfig, ); diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts index c27c3071a..7fe3cd51c 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -60,7 +60,7 @@ export class ColorSensor implements Sensor { fixedUpdate(fixedDeltaTime: number) { this.accumulator += fixedDeltaTime; - if (this.accumulator < 1) { + if (this.accumulator < 1 / 30) { return; } this.accumulator -= 1; @@ -78,7 +78,8 @@ export class ColorSensor implements Sensor { tempCanvas.width = rendererCanvas.width; tempCanvas.height = rendererCanvas.height; - const tempCtx = tempCanvas.getContext('2d')!; + + const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true })!; tempCtx.drawImage(rendererCanvas, 0, 0); diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index 2c1cd2cbb..5dc72b558 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -1,27 +1,29 @@ import { type IOptions, runECEvaluatorByJoel } from 'js-slang'; import context from 'js-slang/context'; -import { type Controller } from '../../engine'; +import { type FrameTimingInfo, type Controller } from '../../engine'; +import { CallbackHandler } from '../../engine/Core/CallbackHandler'; export class Program implements Controller { - #code: string; - #iterator: Generator | null; - #isPaused:boolean; + code: string; + iterator: Generator | null; + isPaused:boolean; + callbackHandler = new CallbackHandler(); constructor(code: string) { - this.#code = code; - this.#iterator = null; - this.#isPaused = false; - console.log(code); + this.code = code; + this.iterator = null; + this.isPaused = false; } - pause(time: number) { - this.#isPaused = true; + pause(pauseDuration: number) { + this.isPaused = true; + this.callbackHandler.addCallback(() => { + this.isPaused = false; + }, pauseDuration); } start() { - console.log('start'); - const options: Partial = { originalMaxExecTime: Infinity, scheduler: 'preemptive', @@ -32,13 +34,17 @@ export class Program implements Controller { context.errors = []; - this.#iterator = runECEvaluatorByJoel(this.#code, context, options); + this.iterator = runECEvaluatorByJoel(this.code, context, options); } fixedUpdate(_: number) { - if (this.#isPaused) { + if (this.isPaused) { return; } - const __ = this.#iterator!.next(); + this.iterator!.next(); + } + + update(frameTiming: FrameTimingInfo): void { + this.callbackHandler.checkCallbacks(frameTiming); } } diff --git a/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts b/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts new file mode 100644 index 000000000..5687d8918 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts @@ -0,0 +1,33 @@ +import { type FrameTimingInfo } from './Timer'; + +export type TimeoutCallback = () => void; + +export class CallbackHandler { + callbackStore: Array<{ callback: TimeoutCallback; triggerTime: number }>; + currentTime: number; + + constructor() { + this.callbackStore = []; + this.currentTime = 0; + } + + addCallback(callback: TimeoutCallback, delay: number): void { + const triggerTime = this.currentTime + delay; + this.callbackStore.push({ + callback, + triggerTime, + }); + } + + checkCallbacks(frameTimingInfo: FrameTimingInfo): void { + this.currentTime = frameTimingInfo.elapsedTimeSimulated; + + this.callbackStore = this.callbackStore.filter((timeoutEvent) => { + if (timeoutEvent.triggerTime <= this.currentTime) { + timeoutEvent.callback(); + return false; // Remove from the array + } + return true; // Keep in the array + }); + } +} diff --git a/src/bundles/robot_simulation/engine/Core/Controller.ts b/src/bundles/robot_simulation/engine/Core/Controller.ts index 6d18364e5..c05e6d99e 100644 --- a/src/bundles/robot_simulation/engine/Core/Controller.ts +++ b/src/bundles/robot_simulation/engine/Core/Controller.ts @@ -1,13 +1,14 @@ +import { type FrameTimingInfo } from './Timer'; + export interface Controller { start?(): Promise | void; - update?(deltaTime: number): void; + update?(deltaTime: FrameTimingInfo): void; fixedUpdate?(fixedDeltaTime: number): void; onDestroy?(): void; } - - -export class ControllerMap> implements Controller { +export class ControllerMap> +implements Controller { map: M; callbacks?: Partial; @@ -22,13 +23,15 @@ export class ControllerMap> implements Cont async start(): Promise { await this.callbacks?.start?.(); - await Promise.all(Object.values(this.map) - .map(async (controller) => { - await controller.start?.(); - })); + await Promise.all( + Object.values(this.map) + .map(async (controller) => { + await controller.start?.(); + }), + ); } - update(deltaTime: number): void { + update(deltaTime: FrameTimingInfo): void { this.callbacks?.update?.(deltaTime); Object.values(this.map) .forEach((controller) => { @@ -53,9 +56,8 @@ export class ControllerMap> implements Cont } } - export class ControllerGroup implements Controller { - private controllers: Controller[] = []; + controllers: Controller[] = []; public addController(...controllers: Controller[]): void { this.controllers.push(...controllers); @@ -67,7 +69,7 @@ export class ControllerGroup implements Controller { }); } - update(deltaTime: number): void { + update(deltaTime: FrameTimingInfo): void { this.controllers.forEach((controller) => { controller.update?.(deltaTime); }); diff --git a/src/bundles/robot_simulation/engine/Render/Renderer.ts b/src/bundles/robot_simulation/engine/Render/Renderer.ts index 538aaf5a2..d1e36c057 100644 --- a/src/bundles/robot_simulation/engine/Render/Renderer.ts +++ b/src/bundles/robot_simulation/engine/Render/Renderer.ts @@ -55,7 +55,7 @@ export class Renderer { static sensorCamera(): THREE.Camera { const renderAspectRatio = 1; - return new THREE.PerspectiveCamera(10, renderAspectRatio, 0.01, 1000); + return new THREE.PerspectiveCamera(10, renderAspectRatio, 0.01, 1); } static loadGTLF(url: string): Promise { diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/engine/World.ts index c78fe63f1..88c54558a 100644 --- a/src/bundles/robot_simulation/engine/World.ts +++ b/src/bundles/robot_simulation/engine/World.ts @@ -48,11 +48,11 @@ export class World extends TypedEventTarget { this.addEventListener('beforeRender', (e) => { controllers.forEach((controller) => { - controller.update?.(e.frameTimingInfo.frameDuration); + controller.update?.(e.frameTimingInfo); }); }); - this.addEventListener('beforePhysicsUpdate', () => { + this.addEventListener('beforePhysicsUpdate', (e) => { controllers.forEach((controller) => { controller.fixedUpdate?.(this.physics.configuration.timestep); }); @@ -96,6 +96,7 @@ export class World extends TypedEventTarget { 'beforePhysicsUpdate', new TimeStampedEvent('beforePhysicsUpdate', frameTimingInfo), ); + this.physics.step(frameTimingInfo); this.dispatchTypedEvent( 'afterPhysicsUpdate', diff --git a/src/bundles/robot_simulation/engine/index.ts b/src/bundles/robot_simulation/engine/index.ts index 69b522946..e112840cb 100644 --- a/src/bundles/robot_simulation/engine/index.ts +++ b/src/bundles/robot_simulation/engine/index.ts @@ -1,7 +1,7 @@ export { World } from './World'; export { Physics } from './Physics'; export { Renderer } from './Render/Renderer'; -export { Timer } from './Core/Timer'; +export { Timer, FrameTimingInfo } from './Core/Timer'; export { ControllerGroup, Controller, ControllerMap } from './Core/Controller'; export { Entity } from './Entity/Entity'; export * as EntityFactory from './Entity/EntityFactory'; diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 4d3c02afd..af4c35f26 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1,8 +1,3 @@ -export { - show, - ev3_pause, -} from './functions'; - export { init_simulation, ev3_motorA, @@ -12,5 +7,6 @@ export { ev3_runToRelativePosition, ev3_colorSensorRed, ev3_colorSensorGreen, + ev3_pause, ev3_colorSensorBlue, } from './main'; diff --git a/src/bundles/robot_simulation/main.ts b/src/bundles/robot_simulation/main.ts index 4be8efb98..e5b337d51 100644 --- a/src/bundles/robot_simulation/main.ts +++ b/src/bundles/robot_simulation/main.ts @@ -23,6 +23,8 @@ type MotorFunctionReturnType = Motor | null; const storedWorld = context.moduleContexts.robot_simulation.state?.world; +// Helpers + export function getWorld(): World { const world = context.moduleContexts.robot_simulation.state?.world; if (world === undefined) { @@ -39,6 +41,8 @@ export function getEv3(): DefaultEv3 { return ev3 as DefaultEv3; } +// Initialization + export function init_simulation() { console.log(storedWorld, 'storedWorld'); if (storedWorld !== undefined) { @@ -93,6 +97,16 @@ export function init_simulation() { throw new Error('Interrupt'); } +// Utility +export function ev3_pause(duration: number): void { + const world = getWorld(); + // TODO: Fix the way we get reference to the program, Maybe use ControllerMap + const program = world.controllers.controllers[1] as Program; + console.log(world.controllers, program, duration); + program.pause(duration); +} + +// Motor export function ev3_motorA(): MotorFunctionReturnType { const ev3 = getEv3(); return ev3.get('leftMotor'); @@ -123,6 +137,8 @@ export function ev3_runToRelativePosition( motor.setVelocity(speed); } +// Color Sensor + export function ev3_colorSensor() { const ev3 = getEv3(); return ev3.get('colorSensor'); From d221978eb8e050c3c135596e8a3bcec9394ba540 Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 25 Jan 2024 09:37:29 +0000 Subject: [PATCH 27/93] Change folder name --- src/bundles/robot_simulation/config.ts | 13 ++ .../controllers/ev3/components/Motor.ts | 3 + .../controllers/ev3/ev3/default.ts | 9 + .../ev3/sensor/UltrasonicSensor.ts | 51 ++++++ .../controllers/car/car_controller.ts | 0 .../controllers/car/motor_controller.ts | 0 .../controllers/car/pid_controller.ts | 0 .../controllers/car/ultrasonic_sensor.ts | 0 .../controllers/car/wheel_controller.ts | 0 .../controllers/physics/helpers.ts | 0 .../controllers/physics/physics_controller.ts | 0 .../physics/physics_object_controller.ts | 0 .../controllers/program_controller.ts | 0 .../controllers/render_controller.ts | 0 .../controllers/time_controller.ts | 0 .../{simulation => delete_this}/index.ts | 0 .../{simulation => delete_this}/types.ts | 0 .../{simulation => delete_this}/world.ts | 0 src/bundles/robot_simulation/engine/World.ts | 2 +- src/bundles/robot_simulation/ev3_functions.ts | 67 ++++++++ src/bundles/robot_simulation/functions.ts | 82 --------- .../robot_simulation/helper_functions.ts | 120 +++++++++++++ src/bundles/robot_simulation/index.ts | 16 +- .../robot_simulation/interrupt/index.ts | 5 + src/bundles/robot_simulation/main.ts | 157 ------------------ .../components/Simulation/index.tsx | 4 +- 26 files changed, 285 insertions(+), 244 deletions(-) create mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/car/car_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/car/motor_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/car/pid_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/car/ultrasonic_sensor.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/car/wheel_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/physics/helpers.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/physics/physics_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/physics/physics_object_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/program_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/render_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/controllers/time_controller.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/index.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/types.ts (100%) rename src/bundles/robot_simulation/{simulation => delete_this}/world.ts (100%) create mode 100644 src/bundles/robot_simulation/ev3_functions.ts delete mode 100644 src/bundles/robot_simulation/functions.ts create mode 100644 src/bundles/robot_simulation/helper_functions.ts create mode 100644 src/bundles/robot_simulation/interrupt/index.ts delete mode 100644 src/bundles/robot_simulation/main.ts diff --git a/src/bundles/robot_simulation/config.ts b/src/bundles/robot_simulation/config.ts index 0a93e161c..a520ba7d7 100644 --- a/src/bundles/robot_simulation/config.ts +++ b/src/bundles/robot_simulation/config.ts @@ -120,3 +120,16 @@ export const colorSensorConfig = { z: 0.01, }, }; + +export const ultrasonicSensorConfig = { + displacement: { + x: 0.04, + y: -(chassisConfig.height / 2), + z: 0.01, + }, + direction: { + x: 0, + y: 0, + z: 1, + }, +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index aa01465e8..dcbc01935 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -35,6 +35,7 @@ export class Motor implements Controller { } fixedUpdate(_: number): void { + console.log('THIS IS VELOCITY', this.motorVelocity); const chassis = this.chassisWrapper.getEntity(); const targetMotorVelocity = vec3({ x: 0, @@ -56,6 +57,8 @@ export class Motor implements Controller { })) .multiplyScalar(chassis.getMass()); + console.log(impulse); + chassis.applyImpulse(impulse, motorGlobalPosition); } diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts index a5906c95b..699b8c8fe 100644 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts @@ -8,6 +8,7 @@ import { Motor } from '../components/Motor'; import { ColorSensor } from '../sensor/ColorSensor'; import { Mesh, type MeshConfig } from '../components/Mesh'; import { ChassisWrapper } from '../components/Chassis'; +import { UltrasonicSensor } from '../sensor/UltrasonicSensor'; type WheelControllers = { frontLeftWheel: Wheel; @@ -23,6 +24,7 @@ type MotorControllers = { type SensorControllers = { colorSensor: ColorSensor; + ultrasonicSensor: UltrasonicSensor; }; type ChassisControllers = { @@ -47,6 +49,10 @@ type Ev3Config = { }; colorSensor: { displacement: SimpleVector; + }, + ultrasonicSensor: { + displacement: SimpleVector; + direction: SimpleVector; } mesh: MeshConfig; }; @@ -119,6 +125,8 @@ export const createDefaultEv3 = ( // Sensors const colorSensor = new ColorSensor(chassis, render.scene(), config.colorSensor.displacement, { debug: true }); + const ultrasonicSensor = new UltrasonicSensor(chassis, physics, config.ultrasonicSensor.displacement, config.ultrasonicSensor.direction); + const ev3: DefaultEv3 = new ControllerMap({ frontLeftWheel, frontRightWheel, @@ -127,6 +135,7 @@ export const createDefaultEv3 = ( leftMotor, rightMotor, colorSensor, + ultrasonicSensor, mesh, chassis, }); diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts new file mode 100644 index 000000000..72d601277 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -0,0 +1,51 @@ +import { type SimpleVector } from '../../../engine/Math/Vector'; +import { type ChassisWrapper } from '../components/Chassis'; +import { type Sensor } from './types'; +import { vec3 } from '../../../engine/Math/Convert'; +import { type Physics } from '../../../engine'; + +export class UltrasonicSensor implements Sensor { + chassisWrapper: ChassisWrapper; + physics: Physics; + displacement: THREE.Vector3; + direction: THREE.Vector3; + distanceSensed: number = 0; + + constructor( + chassis: ChassisWrapper, + physics: Physics, + displacement: SimpleVector, + direction: SimpleVector, + ) { + this.chassisWrapper = chassis; + this.physics = physics; + this.displacement = vec3(displacement); + this.direction = vec3(direction); + } + + sense(): number { + return this.distanceSensed; + } + + fixedUpdate(_: number): void { + const chassis = this.chassisWrapper.getEntity(); + const globalDisplacement = chassis.worldTranslation( + this.displacement.clone(), + ); + const globalDirection = chassis.transformDirection(this.direction.clone()); + + const result = this.physics.castRay( + globalDisplacement, + globalDirection, + 0.2, + ); + + if (result === null) { + return; + } + + const { distance: wheelDistance } = result; + + this.distanceSensed = wheelDistance; + } +} diff --git a/src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/car/car_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/car/car_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/car/car_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/car/motor_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/car/motor_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/car/motor_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/car/pid_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/car/pid_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/car/pid_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/car/pid_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts b/src/bundles/robot_simulation/delete_this/controllers/car/ultrasonic_sensor.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/car/ultrasonic_sensor.ts rename to src/bundles/robot_simulation/delete_this/controllers/car/ultrasonic_sensor.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/car/wheel_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/car/wheel_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/car/wheel_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts b/src/bundles/robot_simulation/delete_this/controllers/physics/helpers.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/physics/helpers.ts rename to src/bundles/robot_simulation/delete_this/controllers/physics/helpers.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/physics/physics_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/physics/physics_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/physics/physics_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/physics/physics_object_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/physics/physics_object_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/physics/physics_object_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/program_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/program_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/program_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/program_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/render_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/render_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/render_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/render_controller.ts diff --git a/src/bundles/robot_simulation/simulation/controllers/time_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/time_controller.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/controllers/time_controller.ts rename to src/bundles/robot_simulation/delete_this/controllers/time_controller.ts diff --git a/src/bundles/robot_simulation/simulation/index.ts b/src/bundles/robot_simulation/delete_this/index.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/index.ts rename to src/bundles/robot_simulation/delete_this/index.ts diff --git a/src/bundles/robot_simulation/simulation/types.ts b/src/bundles/robot_simulation/delete_this/types.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/types.ts rename to src/bundles/robot_simulation/delete_this/types.ts diff --git a/src/bundles/robot_simulation/simulation/world.ts b/src/bundles/robot_simulation/delete_this/world.ts similarity index 100% rename from src/bundles/robot_simulation/simulation/world.ts rename to src/bundles/robot_simulation/delete_this/world.ts diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/engine/World.ts index 88c54558a..81cafaa09 100644 --- a/src/bundles/robot_simulation/engine/World.ts +++ b/src/bundles/robot_simulation/engine/World.ts @@ -52,7 +52,7 @@ export class World extends TypedEventTarget { }); }); - this.addEventListener('beforePhysicsUpdate', (e) => { + this.addEventListener('beforePhysicsUpdate', (_) => { controllers.forEach((controller) => { controller.fixedUpdate?.(this.physics.configuration.timestep); }); diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts new file mode 100644 index 000000000..8178c658d --- /dev/null +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -0,0 +1,67 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import { type Program } from './controllers'; +import { type Motor } from './controllers/ev3/components/Motor'; +import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; + +import { getEv3FromContext, getWorldFromContext } from './helper_functions'; + +type MotorFunctionReturnType = Motor | null; + +// Utility +export function ev3_pause(duration: number): void { + const world = getWorldFromContext(); + const program = world.controllers.controllers[1] as Program; + console.log(world.controllers, program, duration); + program.pause(duration); +} + +// Motor +export function ev3_motorA(): MotorFunctionReturnType { + const ev3 = getEv3FromContext(); + return ev3.get('leftMotor'); +} + +export function ev3_motorB(): MotorFunctionReturnType { + const ev3 = getEv3FromContext(); + return ev3.get('rightMotor'); +} + +export function ev3_motorC(): MotorFunctionReturnType { + return null; +} + +export function ev3_motorD(): MotorFunctionReturnType { + return null; +} + +export function ev3_runToRelativePosition( + motor: MotorFunctionReturnType, + position: number, + speed: number, +): void { + if (motor === null) { + return; + } + + motor.setVelocity(speed); +} + +// Color Sensor + +export function ev3_colorSensor() { + const ev3 = getEv3FromContext(); + return ev3.get('colorSensor'); +} + +export function ev3_colorSensorRed(colorSensor: ColorSensor) { + return colorSensor.sense().r; +} + +export function ev3_colorSensorGreen(colorSensor: ColorSensor) { + return colorSensor.sense().g; +} + +export function ev3_colorSensorBlue(colorSensor: ColorSensor) { + return colorSensor.sense().b; +} diff --git a/src/bundles/robot_simulation/functions.ts b/src/bundles/robot_simulation/functions.ts deleted file mode 100644 index 7d6de98a7..000000000 --- a/src/bundles/robot_simulation/functions.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - -/** - * Robot simulation - * @module robot_simulation - */ - -import context from 'js-slang/context'; - -import { getWorld } from './simulation'; -import { type MotorsOptions } from './simulation/controllers/car/car_controller'; - -export async function show() { - throw new Error('Interrupt execution by module'); -} - -export function init_simulation() { - const code = context.unTypecheckedCode[0]; - const world = getWorld(); - world.init(code); - if (world.state === 'loading') { - throw new Error('Interrupt execution by module'); - } -} - -export function ev3_motorA() { - const world = getWorld(); - if (world.state === 'loading') { - return -1; - } - - return world.carController.motorA(); -} - -export function ev3_motorB() { - const world = getWorld(); - if (world.state === 'loading') { - return -1; - } - - return world.carController.motorB(); -} - -export function ev3_motorC() { - const world = getWorld(); - if (world.state === 'loading') { - return -1; - } - - return world.carController.motorC(); -} - -export function ev3_motorD() { - const world = getWorld(); - if (world.state === 'loading') { - return -1; - } - - return world.carController.motorD(); -} - -export function ev3_runToRelativePosition( - motor: MotorsOptions, - position: number, - speed: number, -) { - const world = getWorld(); - if (world.state === 'loading') { - return; - } - - world.carController.runToRelativePosition(motor, position, speed); -} - -export function ev3_pause(time: number) { - const world = getWorld(); - if (world.state === 'loading') { - return; - } - - world.carController.pause(time); -} diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts new file mode 100644 index 000000000..33e9ee8a9 --- /dev/null +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -0,0 +1,120 @@ +import { + chassisConfig, + colorSensorConfig, + meshConfig, + motorDisplacements, + motorPidConfig, + physicsConfig, + renderConfig, + sceneCamera, + wheelDisplacements, + wheelPidConfig, + ultrasonicSensorConfig, +} from './config'; + +import { Environment, type DefaultEv3, Program } from './controllers'; +import { createDefaultEv3 } from './controllers/ev3/ev3/default'; +import { type Controller, Physics, Renderer, Timer, World } from './engine'; + +import context from 'js-slang/context'; +import { interrupt } from './interrupt'; + + +const storedWorld = context.moduleContexts.robot_simulation.state?.world; + + +export function getWorldFromContext(): World { + const world = context.moduleContexts.robot_simulation.state?.world; + if (world === undefined) { + throw new Error('World not initialized'); + } + return world as World; +} + +export function getEv3FromContext(): DefaultEv3 { + const ev3 = context.moduleContexts.robot_simulation.state?.ev3; + if (ev3 === undefined) { + throw new Error('ev3 not initialized'); + } + return ev3 as DefaultEv3; +} + + +// Initialization functions +export function createRenderer(): Renderer { + const scene = Renderer.scene(); + const camera = Renderer.camera(sceneCamera); + camera.translateY(2); + camera.translateZ(-2); + const renderer = new Renderer(scene, camera, renderConfig); + return renderer; +} + +export function createPhysics(): Physics { + const physics = new Physics(physicsConfig); + return physics; +} + +export function createTimer(): Timer { + const timer = new Timer(); + return timer; +} + +export function createWorld(physics: Physics, renderer: Renderer, timer: Timer) { + const world = new World(physics, renderer, timer); + return world; +} + +export function createEv3(physics: Physics, renderer: Renderer): DefaultEv3 { + const config = { + chassis: chassisConfig, + motor: { + displacements: motorDisplacements, + pid: motorPidConfig, + }, + wheel: { + displacements: wheelDisplacements, + pid: wheelPidConfig, + }, + colorSensor: { + displacement: colorSensorConfig.displacement, + }, + ultrasonicSensor: ultrasonicSensorConfig, + mesh: meshConfig, + }; + + const ev3 = createDefaultEv3(physics, renderer, config); + return ev3; +} + +export function createFloor(physics: Physics, renderer: Renderer) { + const environment = new Environment(physics, renderer); + return environment; +} + +export function createCSE() { + const code = context.unTypecheckedCode[0]; + const program = new Program(code); + return program; +} + +export function addControllerToWorld(controller: Controller, world:World) { + world.addController(controller); +} + +export function saveToContext(world: World, ev3: DefaultEv3) { + context.moduleContexts.robot_simulation.state = { + world, + ev3, + }; +} + +// Initialization +export function init_simulation(worldFactory: () => World) { + if (storedWorld !== undefined) { + return; + } + const world = worldFactory(); + world.init(); + interrupt(); +} diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index af4c35f26..b099d4afc 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1,5 +1,4 @@ export { - init_simulation, ev3_motorA, ev3_motorB, ev3_motorC, @@ -9,4 +8,17 @@ export { ev3_colorSensorGreen, ev3_pause, ev3_colorSensorBlue, -} from './main'; +} from './ev3_functions'; + +export { + init_simulation, + createRenderer, + createPhysics, + createTimer, + createWorld, + createEv3, + createFloor, + createCSE, + addControllerToWorld, + saveToContext, +} from './helper_functions'; diff --git a/src/bundles/robot_simulation/interrupt/index.ts b/src/bundles/robot_simulation/interrupt/index.ts new file mode 100644 index 000000000..789ab2521 --- /dev/null +++ b/src/bundles/robot_simulation/interrupt/index.ts @@ -0,0 +1,5 @@ +/* eslint-disable @typescript-eslint/no-throw-literal */ + +export function interrupt() { + throw 'source_academy_interrupt'; +} diff --git a/src/bundles/robot_simulation/main.ts b/src/bundles/robot_simulation/main.ts deleted file mode 100644 index e5b337d51..000000000 --- a/src/bundles/robot_simulation/main.ts +++ /dev/null @@ -1,157 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { - chassisConfig, - colorSensorConfig, - meshConfig, - motorDisplacements, - motorPidConfig, - physicsConfig, - renderConfig, - sceneCamera, - wheelDisplacements, - wheelPidConfig, -} from './config'; -import { Environment, type DefaultEv3, Program } from './controllers'; -import { type Motor } from './controllers/ev3/components/Motor'; -import { createDefaultEv3 } from './controllers/ev3/ev3/default'; -import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; -import { Physics, Renderer, Timer, World } from './engine'; - -import context from 'js-slang/context'; - -type MotorFunctionReturnType = Motor | null; - -const storedWorld = context.moduleContexts.robot_simulation.state?.world; - -// Helpers - -export function getWorld(): World { - const world = context.moduleContexts.robot_simulation.state?.world; - if (world === undefined) { - throw new Error('World not initialized'); - } - return world as World; -} - -export function getEv3(): DefaultEv3 { - const ev3 = context.moduleContexts.robot_simulation.state?.ev3; - if (ev3 === undefined) { - throw new Error('ev3 not initialized'); - } - return ev3 as DefaultEv3; -} - -// Initialization - -export function init_simulation() { - console.log(storedWorld, 'storedWorld'); - if (storedWorld !== undefined) { - return; - } - - const code = context.unTypecheckedCode[0]; - - const scene = Renderer.scene(); - const camera = Renderer.camera(sceneCamera); - camera.translateY(2); - camera.translateZ(-2); - const renderer = new Renderer(scene, camera, renderConfig); - - const physics = new Physics(physicsConfig); - - const timer = new Timer(); - const world = new World(physics, renderer, timer); - - const environmentController = new Environment(physics, renderer); - world.addController(environmentController); - - const program = new Program(code); - world.addController(program); - - const config = { - chassis: chassisConfig, - motor: { - displacements: motorDisplacements, - pid: motorPidConfig, - }, - wheel: { - displacements: wheelDisplacements, - pid: wheelPidConfig, - }, - colorSensor: { - displacement: colorSensorConfig.displacement, - }, - mesh: meshConfig, - }; - - const ev3 = createDefaultEv3(physics, renderer, config); - - world.addController(ev3); - world.init(); - - context.moduleContexts.robot_simulation.state = { - world, - ev3, - }; - - throw new Error('Interrupt'); -} - -// Utility -export function ev3_pause(duration: number): void { - const world = getWorld(); - // TODO: Fix the way we get reference to the program, Maybe use ControllerMap - const program = world.controllers.controllers[1] as Program; - console.log(world.controllers, program, duration); - program.pause(duration); -} - -// Motor -export function ev3_motorA(): MotorFunctionReturnType { - const ev3 = getEv3(); - return ev3.get('leftMotor'); -} - -export function ev3_motorB(): MotorFunctionReturnType { - const ev3 = getEv3(); - return ev3.get('rightMotor'); -} - -export function ev3_motorC(): MotorFunctionReturnType { - return null; -} - -export function ev3_motorD(): MotorFunctionReturnType { - return null; -} - -export function ev3_runToRelativePosition( - motor: MotorFunctionReturnType, - position: number, - speed: number, -): void { - if (motor === null) { - return; - } - - motor.setVelocity(speed); -} - -// Color Sensor - -export function ev3_colorSensor() { - const ev3 = getEv3(); - return ev3.get('colorSensor'); -} - -export function ev3_colorSensorRed(colorSensor: ColorSensor) { - return colorSensor.sense().r; -} - -export function ev3_colorSensorGreen(colorSensor: ColorSensor) { - return colorSensor.sense().g; -} - -export function ev3_colorSensorBlue(colorSensor: ColorSensor) { - return colorSensor.sense().b; -} diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 00bf7fae7..3fe062aa8 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -1,7 +1,6 @@ import { useRef, type CSSProperties, useEffect, useState } from 'react'; import type { DebuggerContext } from '../../../../typings/type_helpers'; -import { type SimulationStates } from '../../../../bundles/robot_simulation/simulation/world'; import { type World } from '../../../../bundles/robot_simulation/engine'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; import { Tab, Tabs } from '@blueprintjs/core'; @@ -9,6 +8,7 @@ import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; import { MonitoringPanel } from '../TabPanels/MonitoringPanel'; +import { type WorldState } from '../../../../bundles/robot_simulation/engine/World'; const WrapperStyle: CSSProperties = { display: 'flex', @@ -43,7 +43,7 @@ export default function SimulationCanvas({ const ref = useRef(null); const sensorRef = useRef(null); const [currentState, setCurrentState] - = useState('unintialized'); + = useState('unintialized'); const world = context.context.moduleContexts.robot_simulation.state .world as World; From adc41163c3606d00cc1ad4fc3dd50c22ba7564d3 Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 25 Jan 2024 14:32:12 +0000 Subject: [PATCH 28/93] Remove console.log for impulse --- .../robot_simulation/controllers/ev3/components/Motor.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index dcbc01935..aa01465e8 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -35,7 +35,6 @@ export class Motor implements Controller { } fixedUpdate(_: number): void { - console.log('THIS IS VELOCITY', this.motorVelocity); const chassis = this.chassisWrapper.getEntity(); const targetMotorVelocity = vec3({ x: 0, @@ -57,8 +56,6 @@ export class Motor implements Controller { })) .multiplyScalar(chassis.getMass()); - console.log(impulse); - chassis.applyImpulse(impulse, motorGlobalPosition); } From 56d0dcc8047d91dd1b3ac70d60fa59cc559625f7 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 26 Jan 2024 08:42:42 +0000 Subject: [PATCH 29/93] Add ultrasonic sensor --- src/bundles/robot_simulation/config.ts | 3 +- .../environment/{Environment.ts => Floor.ts} | 2 +- .../controllers/environment/Wall.ts | 46 +++++++++++++++++++ .../controllers/ev3/ev3/default.ts | 21 +++++++-- .../ev3/sensor/UltrasonicSensor.ts | 28 ++++++++++- .../robot_simulation/controllers/index.ts | 3 +- .../robot_simulation/engine/Entity/Entity.ts | 8 ++++ .../robot_simulation/engine/Physics.ts | 18 ++++++-- src/bundles/robot_simulation/ev3_functions.ts | 3 +- .../robot_simulation/helper_functions.ts | 9 +++- src/bundles/robot_simulation/index.ts | 1 + 11 files changed, 127 insertions(+), 15 deletions(-) rename src/bundles/robot_simulation/controllers/environment/{Environment.ts => Floor.ts} (92%) create mode 100644 src/bundles/robot_simulation/controllers/environment/Wall.ts diff --git a/src/bundles/robot_simulation/config.ts b/src/bundles/robot_simulation/config.ts index a520ba7d7..3665a35f1 100644 --- a/src/bundles/robot_simulation/config.ts +++ b/src/bundles/robot_simulation/config.ts @@ -124,7 +124,7 @@ export const colorSensorConfig = { export const ultrasonicSensorConfig = { displacement: { x: 0.04, - y: -(chassisConfig.height / 2), + y: 0, z: 0.01, }, direction: { @@ -132,4 +132,5 @@ export const ultrasonicSensorConfig = { y: 0, z: 1, }, + debug: true, }; diff --git a/src/bundles/robot_simulation/controllers/environment/Environment.ts b/src/bundles/robot_simulation/controllers/environment/Floor.ts similarity index 92% rename from src/bundles/robot_simulation/controllers/environment/Environment.ts rename to src/bundles/robot_simulation/controllers/environment/Floor.ts index b56911dc8..3149ef8b8 100644 --- a/src/bundles/robot_simulation/controllers/environment/Environment.ts +++ b/src/bundles/robot_simulation/controllers/environment/Floor.ts @@ -28,7 +28,7 @@ export const floorConfig: EntityCuboidOptions & RenderCuboidOptions = { }; -export class Environment implements Controller { +export class Floor implements Controller { private physics: Physics; private renderer: Renderer; private mesh: THREE.Mesh; diff --git a/src/bundles/robot_simulation/controllers/environment/Wall.ts b/src/bundles/robot_simulation/controllers/environment/Wall.ts new file mode 100644 index 000000000..81a746614 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/environment/Wall.ts @@ -0,0 +1,46 @@ +import { type Physics, type Controller, type Renderer, EntityFactory, MeshFactory } from '../../engine'; +import * as THREE from 'three'; + +import { type EntityCuboidOptions } from '../../engine/Entity/EntityFactory'; +import { type RenderCuboidOptions } from '../../engine/Render/MeshFactory'; + +export const wallConfig: EntityCuboidOptions & RenderCuboidOptions = { + debug: false, + orientation: { + position: { + x: 0, + y: 1, + z: 1, + }, + rotation: { + x: 0, + y: 0, + z: 0, + w: 0, + }, + }, + mass: 1, + height: 2, + width: 1, + length: 0.1, + color: new THREE.Color('white'), + type: 'fixed', +}; + + +export class Wall implements Controller { + private physics: Physics; + private renderer: Renderer; + private mesh: THREE.Mesh; + + constructor(physics:Physics, renderer: Renderer) { + this.physics = physics; + this.renderer = renderer; + this.mesh = MeshFactory.addCuboid(wallConfig); + } + + async start(): Promise { + EntityFactory.addCuboid(this.physics, wallConfig); + this.renderer.add(this.mesh); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts index 699b8c8fe..66cce7f2c 100644 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts @@ -49,11 +49,12 @@ type Ev3Config = { }; colorSensor: { displacement: SimpleVector; - }, + }; ultrasonicSensor: { displacement: SimpleVector; direction: SimpleVector; - } + debug: boolean; + }; mesh: MeshConfig; }; @@ -123,9 +124,21 @@ export const createDefaultEv3 = ( ); // Sensors - const colorSensor = new ColorSensor(chassis, render.scene(), config.colorSensor.displacement, { debug: true }); + const colorSensor = new ColorSensor( + chassis, + render.scene(), + config.colorSensor.displacement, + { debug: true }, + ); - const ultrasonicSensor = new UltrasonicSensor(chassis, physics, config.ultrasonicSensor.displacement, config.ultrasonicSensor.direction); + const ultrasonicSensor = new UltrasonicSensor( + chassis, + physics, + render, + config.ultrasonicSensor.displacement, + config.ultrasonicSensor.direction, + { debug: config.ultrasonicSensor.debug }, + ); const ev3: DefaultEv3 = new ControllerMap({ frontLeftWheel, diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts index 72d601277..10d5ebb6b 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -2,7 +2,12 @@ import { type SimpleVector } from '../../../engine/Math/Vector'; import { type ChassisWrapper } from '../components/Chassis'; import { type Sensor } from './types'; import { vec3 } from '../../../engine/Math/Convert'; -import { type Physics } from '../../../engine'; +import { type Renderer, type Physics } from '../../../engine'; +import * as THREE from 'three'; + +type UltrasonicSensorConfig = { + debug: boolean; +}; export class UltrasonicSensor implements Sensor { chassisWrapper: ChassisWrapper; @@ -10,17 +15,27 @@ export class UltrasonicSensor implements Sensor { displacement: THREE.Vector3; direction: THREE.Vector3; distanceSensed: number = 0; + render: Renderer; + config: UltrasonicSensorConfig; + debugArrow: THREE.ArrowHelper; constructor( chassis: ChassisWrapper, physics: Physics, + render: Renderer, displacement: SimpleVector, direction: SimpleVector, + config: UltrasonicSensorConfig, ) { this.chassisWrapper = chassis; this.physics = physics; + this.render = render; this.displacement = vec3(displacement); this.direction = vec3(direction); + this.config = config; + this.debugArrow = new THREE.ArrowHelper(); + this.debugArrow.visible = false; + this.render.add(this.debugArrow); } sense(): number { @@ -38,8 +53,19 @@ export class UltrasonicSensor implements Sensor { globalDisplacement, globalDirection, 0.2, + this.chassisWrapper.getEntity() + .getCollider(), ); + // console.log(result); + + if (this.config.debug) { + this.debugArrow.visible = true; + this.debugArrow.position.copy(globalDisplacement); + this.debugArrow.setDirection(globalDirection.normalize()); + } + + // TODO: Figure out what the sensor should return if it doesn't sense anything if (result === null) { return; } diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts index 79728db89..0d1880bda 100644 --- a/src/bundles/robot_simulation/controllers/index.ts +++ b/src/bundles/robot_simulation/controllers/index.ts @@ -1,4 +1,5 @@ -export { Environment } from './environment/Environment'; +export { Floor } from './environment/Floor'; export { Program } from './program/Program'; +export { Wall } from './environment/Wall'; export { DefaultEv3 } from './ev3/ev3/default'; diff --git a/src/bundles/robot_simulation/engine/Entity/Entity.ts b/src/bundles/robot_simulation/engine/Entity/Entity.ts index c26c8c9b1..158a59c0a 100644 --- a/src/bundles/robot_simulation/engine/Entity/Entity.ts +++ b/src/bundles/robot_simulation/engine/Entity/Entity.ts @@ -22,6 +22,14 @@ export class Entity { this.#rapierCollider = configuration.rapierCollider; } + getCollider(): Rapier.Collider { + return this.#rapierCollider; + } + + getRigidBody(): Rapier.RigidBody { + return this.#rapierRigidBody; + } + getPosition(): SimpleVector { return this.#rapierRigidBody.translation(); } diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 04e899c26..7a1ce336f 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -1,4 +1,4 @@ -import rapier from '@dimforge/rapier3d-compat'; +import rapier, { QueryFilterFlags } from '@dimforge/rapier3d-compat'; import { type SimpleVector } from './Math/Vector'; import { type FrameTimingInfo } from './Core/Timer'; @@ -70,7 +70,8 @@ export class Physics { globalPosition: THREE.Vector3, globalDirection: THREE.Vector3, maxDistance: number, - ) : { + excludeCollider?: rapier.Collider, + ): { distance: number; normal: SimpleVector; } | null { @@ -79,7 +80,16 @@ export class Physics { } const ray = new this.RAPIER.Ray(globalPosition, globalDirection); - const result = this.internals.world.castRayAndGetNormal(ray, maxDistance, false); + + // https://rapier.rs/javascript3d/classes/World.html#castRayAndGetNormal + const result = this.internals.world.castRayAndGetNormal( + ray, + maxDistance, + false, + undefined, + undefined, + excludeCollider, + ); this.internals.world.castRay(ray, maxDistance, false); @@ -93,7 +103,7 @@ export class Physics { }; } - step(timing: FrameTimingInfo) : void { + step(timing: FrameTimingInfo): void { if (this.internals.initialized === false) { throw Error("Physics engine hasn't been initialized yet"); } diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 8178c658d..d4e804933 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -11,7 +11,8 @@ type MotorFunctionReturnType = Motor | null; // Utility export function ev3_pause(duration: number): void { const world = getWorldFromContext(); - const program = world.controllers.controllers[1] as Program; + // TODO: FIX THIS! CAUSES BUGS + const program = world.controllers.controllers[2] as Program; console.log(world.controllers, program, duration); program.pause(duration); } diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index 33e9ee8a9..6bbca8753 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -12,7 +12,7 @@ import { ultrasonicSensorConfig, } from './config'; -import { Environment, type DefaultEv3, Program } from './controllers'; +import { Floor, type DefaultEv3, Program, Wall } from './controllers'; import { createDefaultEv3 } from './controllers/ev3/ev3/default'; import { type Controller, Physics, Renderer, Timer, World } from './engine'; @@ -88,7 +88,12 @@ export function createEv3(physics: Physics, renderer: Renderer): DefaultEv3 { } export function createFloor(physics: Physics, renderer: Renderer) { - const environment = new Environment(physics, renderer); + const environment = new Floor(physics, renderer); + return environment; +} + +export function createWall(physics: Physics, renderer: Renderer) { + const environment = new Wall(physics, renderer); return environment; } diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index b099d4afc..b0b243756 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -16,6 +16,7 @@ export { createPhysics, createTimer, createWorld, + createWall, createEv3, createFloor, createCSE, From 5fbf82997883ee08d9471616aecfa23215d99014 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 26 Jan 2024 08:50:11 +0000 Subject: [PATCH 30/93] Minor changes to ultrasonic sensor --- .../controllers/ev3/sensor/UltrasonicSensor.ts | 4 +--- src/bundles/robot_simulation/engine/Physics.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts index 10d5ebb6b..b41ec19cc 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -52,13 +52,11 @@ export class UltrasonicSensor implements Sensor { const result = this.physics.castRay( globalDisplacement, globalDirection, - 0.2, + 1, this.chassisWrapper.getEntity() .getCollider(), ); - // console.log(result); - if (this.config.debug) { this.debugArrow.visible = true; this.debugArrow.position.copy(globalDisplacement); diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 7a1ce336f..98dbc371e 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -85,7 +85,7 @@ export class Physics { const result = this.internals.world.castRayAndGetNormal( ray, maxDistance, - false, + true, undefined, undefined, excludeCollider, From 96a552cfca7958036b623b039b5ad859ab3e82a9 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 29 Jan 2024 02:42:44 +0000 Subject: [PATCH 31/93] Achieved determinism --- src/bundles/robot_simulation/config.ts | 10 +++++----- .../controllers/ev3/components/Wheel.ts | 8 ++++---- .../controllers/ev3/sensor/ColorSensor.ts | 12 +++++------ .../controllers/program/Program.ts | 4 +++- .../robot_simulation/engine/Physics.ts | 20 +++++++++++++++++-- src/bundles/robot_simulation/engine/World.ts | 14 +------------ 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/bundles/robot_simulation/config.ts b/src/bundles/robot_simulation/config.ts index 3665a35f1..23a8537b9 100644 --- a/src/bundles/robot_simulation/config.ts +++ b/src/bundles/robot_simulation/config.ts @@ -15,7 +15,7 @@ export const physicsConfig: PhysicsConfig = { y: -9.81, z: 0, }, - timestep: 1 / 60, + timestep: 1 / 20, }; export const renderConfig: RenderConfig = { @@ -36,7 +36,7 @@ export const chassisConfig: EntityCuboidOptions = { orientation: { position: { x: 0, - y: 2, + y: 0.2, z: 0, }, rotation: { @@ -88,9 +88,9 @@ export const wheelDisplacements: Record = { }; export const wheelPidConfig = { - proportionalGain: 3, - integralGain: 0.01, - derivativeGain: 2.5, + proportionalGain: 90, + integralGain: 0.58, + derivativeGain: 12, }; export const motorDisplacements = { diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index 199520a5b..aa3ce97ea 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -46,7 +46,7 @@ export class Wheel implements Controller { render.add(this.arrowHelper); } - fixedUpdate(_: number): void { + fixedUpdate(timestep: number): void { const chassis = this.chassisWrapper.getEntity(); const globalDisplacement = chassis.worldTranslation( @@ -59,7 +59,7 @@ export class Wheel implements Controller { const result = this.physics.castRay( globalDisplacement, globalDownDirection, - 0.2, + 0.1, ); // Wheels are not touching the ground @@ -72,12 +72,12 @@ export class Wheel implements Controller { const force = vec3(normal) .normalize() - .multiplyScalar(error * chassis.getMass()); + .multiplyScalar(error * chassis.getMass() * timestep); chassis.applyImpulse(force, globalDisplacement); - this.arrowHelper.setLength(force.length() * 5000); + this.arrowHelper.setLength(force.length() * 1000); this.arrowHelper.setDirection(force.normalize()); this.arrowHelper.position.copy(globalDisplacement); } diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts index 7fe3cd51c..e0770cb28 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -18,8 +18,8 @@ export class ColorSensor implements Sensor { chassisWrapper: ChassisWrapper; accumulator = 0; - colorSensed:Color; + tempCanvas: HTMLCanvasElement; constructor(chassisWrapper: ChassisWrapper, scene: THREE.Scene, displacement: SimpleVector, config: ColorSensorConfig) { this.chassisWrapper = chassisWrapper; @@ -36,6 +36,7 @@ export class ColorSensor implements Sensor { height: 16, control: 'none', }); + this.tempCanvas = document.createElement('canvas'); if (config.debug) { @@ -60,7 +61,7 @@ export class ColorSensor implements Sensor { fixedUpdate(fixedDeltaTime: number) { this.accumulator += fixedDeltaTime; - if (this.accumulator < 1 / 30) { + if (this.accumulator < 1) { return; } this.accumulator -= 1; @@ -74,12 +75,11 @@ export class ColorSensor implements Sensor { this.camera.lookAt(lookAt); const rendererCanvas = this.getElement(); - const tempCanvas = document.createElement('canvas'); - tempCanvas.width = rendererCanvas.width; - tempCanvas.height = rendererCanvas.height; + this.tempCanvas.width = rendererCanvas.width; + this.tempCanvas.height = rendererCanvas.height; - const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true })!; + const tempCtx = this.tempCanvas.getContext('2d', { willReadFrequently: true })!; tempCtx.drawImage(rendererCanvas, 0, 0); diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index 5dc72b558..6776753e6 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -41,7 +41,9 @@ export class Program implements Controller { if (this.isPaused) { return; } - this.iterator!.next(); + for (let i = 0; i < 4; i++) { + this.iterator!.next(); + } } update(frameTiming: FrameTimingInfo): void { diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 98dbc371e..2a08c0f71 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -1,7 +1,13 @@ -import rapier, { QueryFilterFlags } from '@dimforge/rapier3d-compat'; +import rapier from '@dimforge/rapier3d-compat'; import { type SimpleVector } from './Math/Vector'; import { type FrameTimingInfo } from './Core/Timer'; +import { TimeStampedEvent, TypedEventTarget } from './Core/Events'; + +type PhysicsEventMap = { + beforePhysicsUpdate: TimeStampedEvent; + afterPhysicsUpdate: TimeStampedEvent; +}; export type PhysicsConfig = { gravity: SimpleVector; @@ -20,12 +26,13 @@ type InitializedInternals = { type PhysicsInternals = NotInitializedInternals | InitializedInternals; -export class Physics { +export class Physics extends TypedEventTarget { RAPIER: typeof rapier; configuration: PhysicsConfig; internals: PhysicsInternals; constructor(configuration: PhysicsConfig) { + super(); this.configuration = configuration; this.RAPIER = rapier; @@ -113,7 +120,16 @@ export class Physics { this.internals.accumulator += Math.min(frameDuration, maxFrameTime); while (this.internals.accumulator >= this.configuration.timestep) { + this.dispatchTypedEvent( + 'beforePhysicsUpdate', + new TimeStampedEvent('beforePhysicsUpdate', timing), + ); this.internals.world.step(); + this.dispatchTypedEvent( + 'afterPhysicsUpdate', + new TimeStampedEvent('afterPhysicsUpdate', timing), + ); + this.internals.accumulator -= this.configuration.timestep; } } diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/engine/World.ts index 81cafaa09..aac07c581 100644 --- a/src/bundles/robot_simulation/engine/World.ts +++ b/src/bundles/robot_simulation/engine/World.ts @@ -15,8 +15,6 @@ export type WorldState = (typeof worldStates)[number]; type WorldEventMap = { worldStart: Event; worldStateChange: Event; - beforePhysicsUpdate: TimeStampedEvent; - afterPhysicsUpdate: TimeStampedEvent; beforeRender: TimeStampedEvent; afterRender: TimeStampedEvent; }; @@ -52,7 +50,7 @@ export class World extends TypedEventTarget { }); }); - this.addEventListener('beforePhysicsUpdate', (_) => { + this.physics.addEventListener('beforePhysicsUpdate', (_) => { controllers.forEach((controller) => { controller.fixedUpdate?.(this.physics.configuration.timestep); }); @@ -92,17 +90,7 @@ export class World extends TypedEventTarget { const frameTimingInfo = this.timer.step(timestamp); // Update physics - this.dispatchTypedEvent( - 'beforePhysicsUpdate', - new TimeStampedEvent('beforePhysicsUpdate', frameTimingInfo), - ); - this.physics.step(frameTimingInfo); - this.dispatchTypedEvent( - 'afterPhysicsUpdate', - new TimeStampedEvent('afterPhysicsUpdate', frameTimingInfo), - ); - // Update render this.dispatchTypedEvent( From be35a0c2be4b02afd375aaa3e62b3dc8cf20c172 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sun, 4 Feb 2024 07:38:33 +0000 Subject: [PATCH 32/93] Add ultrasonic motor function --- .../controllers/environment/Floor.ts | 11 +- .../controllers/ev3/components/Motor.ts | 16 +- .../controllers/program/Program.ts | 3 +- .../controllers/program/evaluate.ts | 148 ++++++++++++++++++ .../controllers/program/interpreter.ts | 84 ++++++++++ src/bundles/robot_simulation/ev3_functions.ts | 29 +++- src/bundles/robot_simulation/index.ts | 2 + .../components/Simulation/index.tsx | 2 + .../TabPanels/UltrasonicSensorPanel.tsx | 24 +++ 9 files changed, 313 insertions(+), 6 deletions(-) create mode 100644 src/bundles/robot_simulation/controllers/program/evaluate.ts create mode 100644 src/bundles/robot_simulation/controllers/program/interpreter.ts create mode 100644 src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx diff --git a/src/bundles/robot_simulation/controllers/environment/Floor.ts b/src/bundles/robot_simulation/controllers/environment/Floor.ts index 3149ef8b8..3b0c75509 100644 --- a/src/bundles/robot_simulation/controllers/environment/Floor.ts +++ b/src/bundles/robot_simulation/controllers/environment/Floor.ts @@ -32,12 +32,16 @@ export class Floor implements Controller { private physics: Physics; private renderer: Renderer; private mesh: THREE.Mesh; + private paper: THREE.Mesh; constructor(physics:Physics, renderer: Renderer) { this.physics = physics; this.renderer = renderer; this.mesh = MeshFactory.addCuboid(floorConfig); + + const plane = new THREE.PlaneGeometry(1, 1); // Creating a 1x1 plane for the carpet + this.paper = new THREE.Mesh(plane); } async start(): Promise { @@ -49,10 +53,13 @@ export class Floor implements Controller { // Create a material with the texture const material = new THREE.MeshStandardMaterial({ map: texture }); + this.paper.scale.set(2, 2, 2); // Scale the plane to the size of the floor + this.paper.position.set(0, 0.001, 0); // Position the plane at the floor + this.paper.rotation.x = -Math.PI / 2; // Rotate the plane to be parallel to the floor + this.paper.material = material; // Apply the material to the mesh - this.mesh.material = material; - this.renderer.add(this.mesh); + this.renderer.add(this.paper); } } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index aa01465e8..0b87334fd 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -1,9 +1,10 @@ -import { type Controller, type Physics } from '../../../engine'; +import { type FrameTimingInfo, type Controller, type Physics } from '../../../engine'; import { type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; import { VectorPidController } from '../feedback_control/PidController'; import type * as THREE from 'three'; import { type ChassisWrapper } from './Chassis'; +import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; type MotorConfig = { @@ -18,6 +19,7 @@ export class Motor implements Controller { pid: VectorPidController; displacementVector: THREE.Vector3; motorVelocity: number; + callbackHandler = new CallbackHandler(); physics: Physics; chassisWrapper: ChassisWrapper; @@ -34,6 +36,14 @@ export class Motor implements Controller { this.motorVelocity = velocity; } + setSpeedDistance(speed: number, distance: number) { + // TODO: Tune this + this.motorVelocity = speed * 500; + this.callbackHandler.addCallback(() => { + this.motorVelocity = 0; + }, distance / speed * 1000); + } + fixedUpdate(_: number): void { const chassis = this.chassisWrapper.getEntity(); const targetMotorVelocity = vec3({ @@ -59,6 +69,10 @@ export class Motor implements Controller { chassis.applyImpulse(impulse, motorGlobalPosition); } + update(deltaTime: FrameTimingInfo): void { + this.callbackHandler.checkCallbacks(deltaTime); + } + toString() { return 'Motor'; } diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index 6776753e6..7c07a323f 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -1,4 +1,5 @@ -import { type IOptions, runECEvaluatorByJoel } from 'js-slang'; +import { type IOptions } from 'js-slang'; +import { runECEvaluatorByJoel } from './evaluate'; import context from 'js-slang/context'; import { type FrameTimingInfo, type Controller } from '../../engine'; import { CallbackHandler } from '../../engine/Core/CallbackHandler'; diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/controllers/program/evaluate.ts new file mode 100644 index 000000000..ef55205f4 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/program/evaluate.ts @@ -0,0 +1,148 @@ +import type * as es from 'estree'; +import * as _ from 'lodash'; + +import { type IOptions } from 'js-slang/dist'; +import { + is_null, +} from 'js-slang/dist/stdlib/list'; +import { Variant, type Context, type RecursivePartial } from 'js-slang/dist/types'; +import { Agenda, cmdEvaluators, Stash } from 'js-slang/dist/ec-evaluator/interpreter'; +import { ECError } from 'js-slang/dist/ec-evaluator/types'; +import { isNode } from 'js-slang/dist/ec-evaluator/utils'; +import { parse } from 'js-slang/dist/parser/parser'; + +const DEFAULT_SOURCE_OPTIONS = { + scheduler: 'async', + steps: 1000, + stepLimit: -1, + executionMethod: 'auto', + variant: Variant.DEFAULT, + originalMaxExecTime: 1000, + useSubst: false, + isPrelude: false, + throwInfiniteLoops: true, + envSteps: -1, + importOptions: { + wrapSourceModules: true, + checkImports: true, + loadTabs: true, + }, +}; + + +export function* runECEvaluatorByJoel( + code: string, + context: Context, + options: RecursivePartial, +): Generator { + const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); + const program = parse(code, context); + is_null(null); + + if (!program) { + return; + } + + yield* evaluateByJoel(program, context, theOptions); +} + +/** + * Function to be called when a program is to be interpreted using + * the explicit control evaluator. + * + * @param program The program to evaluate. + * @param context The context to evaluate the program in. + * @returns The result of running the ECE machine. + */ +export function* evaluateByJoel( + program: es.Program, + context: Context, + options: IOptions, +): Generator { + try { + context.runtime.isRunning = true; + context.runtime.agenda = new Agenda(program); + context.runtime.stash = new Stash(); + yield* runECEMachineByJoel( + context, + context.runtime.agenda, + context.runtime.stash, + options.envSteps, + options.stepLimit, + options.isPrelude, + ); + } catch (error) { + // console.error('ecerror:', error) + return new ECError(error); + } finally { + context.runtime.isRunning = false; + } +} + +function* runECEMachineByJoel( + context: Context, + agenda: Agenda, + stash: Stash, + envSteps: number, + stepLimit: number, + isPrelude: boolean = false, +) { + context.runtime.break = false; + context.runtime.nodes = []; + let steps = 1; + + let command = agenda.peek(); + + // First node will be a Program + context.runtime.nodes.unshift(command as es.Program); + + while (command) { + // Return to capture a snapshot of the agenda and stash after the target step count is reached + if (!isPrelude && steps === envSteps) { + return stash.peek(); + } + // Step limit reached, stop further evaluation + if (!isPrelude && steps === stepLimit) { + break; + } + + if (isNode(command) && command.type === 'DebuggerStatement') { + // steps += 1 + + // Record debugger step if running for the first time + if (envSteps === -1) { + context.runtime.breakpointSteps.push(steps); + } + } + + agenda.pop(); + if (isNode(command)) { + context.runtime.nodes.shift(); + context.runtime.nodes.unshift(command); + cmdEvaluators[command.type](command, context, agenda, stash, isPrelude); + if (context.runtime.break && context.runtime.debuggerOn) { + // We can put this under isNode since context.runtime.break + // will only be updated after a debugger statement and so we will + // run into a node immediately after. + // With the new evaluator, we don't return a break + // return new ECEBreak() + } + } else { + // Command is an instrucion + cmdEvaluators[command.instrType](command, context, agenda, stash, isPrelude); + } + + // Push undefined into the stack if both agenda and stash is empty + if (agenda.isEmpty() && stash.isEmpty()) { + stash.push(undefined); + } + command = agenda.peek(); + yield steps; + steps += 1; + } + + if (!isPrelude) { + context.runtime.envStepsTotal = steps; + } + return stash.peek(); +} diff --git a/src/bundles/robot_simulation/controllers/program/interpreter.ts b/src/bundles/robot_simulation/controllers/program/interpreter.ts new file mode 100644 index 000000000..c82ac1d4d --- /dev/null +++ b/src/bundles/robot_simulation/controllers/program/interpreter.ts @@ -0,0 +1,84 @@ +import type * as es from 'estree'; + +import { + cmdEvaluators, + type Agenda, + type Stash, +} from 'js-slang/dist/ec-evaluator/interpreter'; +import { type Context } from 'js-slang'; + +import { isNode } from 'js-slang/dist/ec-evaluator/utils'; + +export function* runECEMachineByJoel( + context: Context, + agenda: Agenda, + stash: Stash, + envSteps: number, + stepLimit: number, + isPrelude: boolean = false, +) { + context.runtime.break = false; + context.runtime.nodes = []; + let steps = 1; + + let command = agenda.peek(); + + // First node will be a Program + context.runtime.nodes.unshift(command as es.Program); + + while (command) { + // Return to capture a snapshot of the agenda and stash after the target step count is reached + if (!isPrelude && steps === envSteps) { + return stash.peek(); + } + // Step limit reached, stop further evaluation + if (!isPrelude && steps === stepLimit) { + break; + } + + if (isNode(command) && command.type === 'DebuggerStatement') { + // steps += 1 + + // Record debugger step if running for the first time + if (envSteps === -1) { + context.runtime.breakpointSteps.push(steps); + } + } + + agenda.pop(); + if (isNode(command)) { + context.runtime.nodes.shift(); + context.runtime.nodes.unshift(command); + cmdEvaluators[command.type](command, context, agenda, stash, isPrelude); + if (context.runtime.break && context.runtime.debuggerOn) { + // We can put this under isNode since context.runtime.break + // will only be updated after a debugger statement and so we will + // run into a node immediately after. + // With the new evaluator, we don't return a break + // return new ECEBreak() + } + } else { + // Command is an instrucion + cmdEvaluators[command.instrType]( + command, + context, + agenda, + stash, + isPrelude, + ); + } + + // Push undefined into the stack if both agenda and stash is empty + if (agenda.isEmpty() && stash.isEmpty()) { + stash.push(undefined); + } + command = agenda.peek(); + yield steps; + steps += 1; + } + + if (!isPrelude) { + context.runtime.envStepsTotal = steps; + } + return stash.peek(); +} diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index d4e804933..56cd612df 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -3,6 +3,7 @@ import { type Program } from './controllers'; import { type Motor } from './controllers/ev3/components/Motor'; import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; +import { type UltrasonicSensor } from './controllers/ev3/sensor/UltrasonicSensor'; import { getEv3FromContext, getWorldFromContext } from './helper_functions'; @@ -12,7 +13,7 @@ type MotorFunctionReturnType = Motor | null; export function ev3_pause(duration: number): void { const world = getWorldFromContext(); // TODO: FIX THIS! CAUSES BUGS - const program = world.controllers.controllers[2] as Program; + const program = world.controllers.controllers[0] as Program; console.log(world.controllers, program, duration); program.pause(duration); } @@ -45,7 +46,15 @@ export function ev3_runToRelativePosition( return; } - motor.setVelocity(speed); + const wheelDiameter = 0.2; + + const speedInMetersPerSecond + = (speed / 360) * Math.PI * wheelDiameter; + + const distanceInMetersPerSecond + = (position / 360) * Math.PI * wheelDiameter; + + motor.setSpeedDistance(speedInMetersPerSecond, distanceInMetersPerSecond); } // Color Sensor @@ -66,3 +75,19 @@ export function ev3_colorSensorGreen(colorSensor: ColorSensor) { export function ev3_colorSensorBlue(colorSensor: ColorSensor) { return colorSensor.sense().b; } + + +// Ultrasonic Sensor + +export function test() { + console.log('TEST'); +} + +export function ev3_ultrasonicSensor() { + const ev3 = getEv3FromContext(); + return ev3.get('ultrasonicSensor'); +} + +export function ev3_ultrasonicSensorDistance(ultraSonicSensor: UltrasonicSensor) { + return ultraSonicSensor.sense(); +} diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index b0b243756..915bdcd01 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -8,6 +8,8 @@ export { ev3_colorSensorGreen, ev3_pause, ev3_colorSensorBlue, + ev3_ultrasonicSensor, + ev3_ultrasonicSensorDistance, } from './ev3_functions'; export { diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 3fe062aa8..62502a811 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -9,6 +9,7 @@ import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; import { MonitoringPanel } from '../TabPanels/MonitoringPanel'; import { type WorldState } from '../../../../bundles/robot_simulation/engine/World'; +import { UltrasonicSensorPanel } from '../TabPanels/UltrasonicSensorPanel'; const WrapperStyle: CSSProperties = { display: 'flex', @@ -99,6 +100,7 @@ export default function SimulationCanvas({ } /> } /> }/> + }/>
diff --git a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx new file mode 100644 index 000000000..61ef3f19d --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default'; + +export const UltrasonicSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { + const ultrasonicSensor = ev3.get('ultrasonicSensor'); + const [_, update] = useState(0); + + const distanceSensed = ultrasonicSensor.sense(); + + useEffect(() => { + // Hacky + setInterval(() => { + update((i) => i + 1); + }, 1000); + }, []); + + return ( + <> +
+

Distance: {distanceSensed}

+
+ + ); +}; From 893ae079053eb8d1f090e5a96028454b961aa4cd Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 28 Feb 2024 06:43:43 +0000 Subject: [PATCH 33/93] Add new event handler and event console --- .../controllers/ev3/components/Mesh.ts | 35 ++++-- .../controllers/program/Program.ts | 20 ++- .../controllers/program/evaluate.ts | 114 ++---------------- .../controllers/program/interpreter.ts | 84 ------------- .../robot_simulation/engine/Core/Events.ts | 99 +++++---------- .../engine/Core/RobotConsole.ts | 30 +++++ .../robot_simulation/engine/Physics.ts | 4 +- src/bundles/robot_simulation/engine/World.ts | 49 +++++--- .../engine/__tests__/Physics.ts | 2 +- src/bundles/robot_simulation/ev3_functions.ts | 7 +- .../robot_simulation/helper_functions.ts | 20 +-- src/bundles/robot_simulation/index.ts | 1 + .../components/Simulation/index.tsx | 7 +- .../components/TabPanels/ColorSensorPanel.tsx | 1 - .../components/TabPanels/ConsolePanel.tsx | 14 +++ 15 files changed, 181 insertions(+), 306 deletions(-) delete mode 100644 src/bundles/robot_simulation/controllers/program/interpreter.ts create mode 100644 src/bundles/robot_simulation/engine/Core/RobotConsole.ts create mode 100644 src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index 7fdd3ca1c..2d08885da 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -1,12 +1,16 @@ -import { type Entity, type Controller, MeshFactory, Renderer } from '../../../engine'; +import { + type Entity, + type Controller, + MeshFactory, + Renderer, +} from '../../../engine'; import * as THREE from 'three'; import { type Orientation } from '../../../engine/Entity/Entity'; import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { type ChassisWrapper } from './Chassis'; - export type MeshConfig = { - orientation: Orientation + orientation: Orientation; width: number; height: number; length: number; @@ -22,7 +26,11 @@ export class Mesh implements Controller { chassisWrapper: ChassisWrapper; mesh: GLTF | null = null; - constructor(chassisWrapper: ChassisWrapper, render:Renderer, meshConfig: MeshConfig) { + constructor( + chassisWrapper: ChassisWrapper, + render: Renderer, + meshConfig: MeshConfig, + ) { this.chassisWrapper = chassisWrapper; this.debugMesh = MeshFactory.addCuboid(meshConfig); this.meshConfig = meshConfig; @@ -31,7 +39,9 @@ export class Mesh implements Controller { } async start(): Promise { - this.mesh = await Renderer.loadGTLF('https://keen-longma-3c1be1.netlify.app/try_one.gltf'); + this.mesh = await Renderer.loadGTLF( + 'https://keen-longma-3c1be1.netlify.app/2_colors_corrected.gltf', + ); const box = new THREE.Box3() .setFromObject(this.mesh.scene); @@ -56,11 +66,16 @@ export class Mesh implements Controller { this.debugMesh.visible = false; } - this.debugMesh.position.copy(chassisEntity - .getPosition() as THREE.Vector3); - this.debugMesh.quaternion.copy(chassisEntity.getRotation() as THREE.Quaternion); + this.debugMesh.position.copy(chassisEntity.getPosition() as THREE.Vector3); + this.debugMesh.quaternion.copy( + chassisEntity.getRotation() as THREE.Quaternion, + ); - this.mesh?.scene.position.copy(chassisEntity.getPosition() as THREE.Vector3); - this.mesh?.scene.quaternion.copy(chassisEntity.getRotation() as THREE.Quaternion); + this.mesh?.scene.position.copy( + chassisEntity.getPosition() as THREE.Vector3, + ); + this.mesh?.scene.quaternion.copy( + chassisEntity.getRotation() as THREE.Quaternion, + ); } } diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index 7c07a323f..60df1b340 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -1,13 +1,17 @@ import { type IOptions } from 'js-slang'; -import { runECEvaluatorByJoel } from './evaluate'; +import { runECEvaluator } from './evaluate'; import context from 'js-slang/context'; import { type FrameTimingInfo, type Controller } from '../../engine'; import { CallbackHandler } from '../../engine/Core/CallbackHandler'; +type ProgramConfig = { + stepsPerTick: number; +}; + export class Program implements Controller { code: string; - iterator: Generator | null; + iterator: ReturnType | null; isPaused:boolean; callbackHandler = new CallbackHandler(); @@ -35,15 +39,21 @@ export class Program implements Controller { context.errors = []; - this.iterator = runECEvaluatorByJoel(this.code, context, options); + this.iterator = runECEvaluator(this.code, context, options); } fixedUpdate(_: number) { + if (!this.iterator) { + throw Error('Program not started'); + } + if (this.isPaused) { return; } - for (let i = 0; i < 4; i++) { - this.iterator!.next(); + + // steps per tick + for (let i = 0; i < 10; i++) { + const result = this.iterator.next(); } } diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/controllers/program/evaluate.ts index ef55205f4..70ebd46fc 100644 --- a/src/bundles/robot_simulation/controllers/program/evaluate.ts +++ b/src/bundles/robot_simulation/controllers/program/evaluate.ts @@ -1,14 +1,9 @@ -import type * as es from 'estree'; import * as _ from 'lodash'; import { type IOptions } from 'js-slang/dist'; -import { - is_null, -} from 'js-slang/dist/stdlib/list'; + import { Variant, type Context, type RecursivePartial } from 'js-slang/dist/types'; -import { Agenda, cmdEvaluators, Stash } from 'js-slang/dist/ec-evaluator/interpreter'; -import { ECError } from 'js-slang/dist/ec-evaluator/types'; -import { isNode } from 'js-slang/dist/ec-evaluator/utils'; +import { Control, Stash, generateCSEMachineStateStream } from 'js-slang/dist/cse-machine/interpreter'; import { parse } from 'js-slang/dist/parser/parser'; const DEFAULT_SOURCE_OPTIONS = { @@ -30,119 +25,34 @@ const DEFAULT_SOURCE_OPTIONS = { }; -export function* runECEvaluatorByJoel( +export function* runECEvaluator( code: string, context: Context, options: RecursivePartial, -): Generator { +): Generator<{ steps: number }, void, undefined> { const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); const program = parse(code, context); - is_null(null); if (!program) { return; } - yield* evaluateByJoel(program, context, theOptions); -} - -/** - * Function to be called when a program is to be interpreted using - * the explicit control evaluator. - * - * @param program The program to evaluate. - * @param context The context to evaluate the program in. - * @returns The result of running the ECE machine. - */ -export function* evaluateByJoel( - program: es.Program, - context: Context, - options: IOptions, -): Generator { try { context.runtime.isRunning = true; - context.runtime.agenda = new Agenda(program); + context.runtime.control = new Control(program); context.runtime.stash = new Stash(); - yield* runECEMachineByJoel( + yield* generateCSEMachineStateStream( context, - context.runtime.agenda, + context.runtime.control, context.runtime.stash, - options.envSteps, - options.stepLimit, - options.isPrelude, + theOptions.envSteps, + theOptions.stepLimit, + theOptions.isPrelude, ); + // eslint-disable-next-line no-useless-catch } catch (error) { - // console.error('ecerror:', error) - return new ECError(error); + throw error; } finally { context.runtime.isRunning = false; } } - -function* runECEMachineByJoel( - context: Context, - agenda: Agenda, - stash: Stash, - envSteps: number, - stepLimit: number, - isPrelude: boolean = false, -) { - context.runtime.break = false; - context.runtime.nodes = []; - let steps = 1; - - let command = agenda.peek(); - - // First node will be a Program - context.runtime.nodes.unshift(command as es.Program); - - while (command) { - // Return to capture a snapshot of the agenda and stash after the target step count is reached - if (!isPrelude && steps === envSteps) { - return stash.peek(); - } - // Step limit reached, stop further evaluation - if (!isPrelude && steps === stepLimit) { - break; - } - - if (isNode(command) && command.type === 'DebuggerStatement') { - // steps += 1 - - // Record debugger step if running for the first time - if (envSteps === -1) { - context.runtime.breakpointSteps.push(steps); - } - } - - agenda.pop(); - if (isNode(command)) { - context.runtime.nodes.shift(); - context.runtime.nodes.unshift(command); - cmdEvaluators[command.type](command, context, agenda, stash, isPrelude); - if (context.runtime.break && context.runtime.debuggerOn) { - // We can put this under isNode since context.runtime.break - // will only be updated after a debugger statement and so we will - // run into a node immediately after. - // With the new evaluator, we don't return a break - // return new ECEBreak() - } - } else { - // Command is an instrucion - cmdEvaluators[command.instrType](command, context, agenda, stash, isPrelude); - } - - // Push undefined into the stack if both agenda and stash is empty - if (agenda.isEmpty() && stash.isEmpty()) { - stash.push(undefined); - } - command = agenda.peek(); - yield steps; - steps += 1; - } - - if (!isPrelude) { - context.runtime.envStepsTotal = steps; - } - return stash.peek(); -} diff --git a/src/bundles/robot_simulation/controllers/program/interpreter.ts b/src/bundles/robot_simulation/controllers/program/interpreter.ts deleted file mode 100644 index c82ac1d4d..000000000 --- a/src/bundles/robot_simulation/controllers/program/interpreter.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type * as es from 'estree'; - -import { - cmdEvaluators, - type Agenda, - type Stash, -} from 'js-slang/dist/ec-evaluator/interpreter'; -import { type Context } from 'js-slang'; - -import { isNode } from 'js-slang/dist/ec-evaluator/utils'; - -export function* runECEMachineByJoel( - context: Context, - agenda: Agenda, - stash: Stash, - envSteps: number, - stepLimit: number, - isPrelude: boolean = false, -) { - context.runtime.break = false; - context.runtime.nodes = []; - let steps = 1; - - let command = agenda.peek(); - - // First node will be a Program - context.runtime.nodes.unshift(command as es.Program); - - while (command) { - // Return to capture a snapshot of the agenda and stash after the target step count is reached - if (!isPrelude && steps === envSteps) { - return stash.peek(); - } - // Step limit reached, stop further evaluation - if (!isPrelude && steps === stepLimit) { - break; - } - - if (isNode(command) && command.type === 'DebuggerStatement') { - // steps += 1 - - // Record debugger step if running for the first time - if (envSteps === -1) { - context.runtime.breakpointSteps.push(steps); - } - } - - agenda.pop(); - if (isNode(command)) { - context.runtime.nodes.shift(); - context.runtime.nodes.unshift(command); - cmdEvaluators[command.type](command, context, agenda, stash, isPrelude); - if (context.runtime.break && context.runtime.debuggerOn) { - // We can put this under isNode since context.runtime.break - // will only be updated after a debugger statement and so we will - // run into a node immediately after. - // With the new evaluator, we don't return a break - // return new ECEBreak() - } - } else { - // Command is an instrucion - cmdEvaluators[command.instrType]( - command, - context, - agenda, - stash, - isPrelude, - ); - } - - // Push undefined into the stack if both agenda and stash is empty - if (agenda.isEmpty() && stash.isEmpty()) { - stash.push(undefined); - } - command = agenda.peek(); - yield steps; - steps += 1; - } - - if (!isPrelude) { - context.runtime.envStepsTotal = steps; - } - return stash.peek(); -} diff --git a/src/bundles/robot_simulation/engine/Core/Events.ts b/src/bundles/robot_simulation/engine/Core/Events.ts index bc79c8f81..c3b160f80 100644 --- a/src/bundles/robot_simulation/engine/Core/Events.ts +++ b/src/bundles/robot_simulation/engine/Core/Events.ts @@ -9,80 +9,43 @@ export class TimeStampedEvent extends Event { } } -// Copied from https://github.com/DerZade/typescript-event-target/blob/master/src/TypedEventTarget.ts - -export type TypedEventListener = ( - evt: M[T] +export type Listener = ( + event: EventMap[EventName] ) => void | Promise; -export interface TypedEventListenerObject { - handleEvent: (evt: M[T]) => void | Promise; -} - -export type TypedEventListenerOrEventListenerObject = - | TypedEventListener - | TypedEventListenerObject; - -type ValueIsEvent = { - [key in keyof T]: Event; +type ValueIsEvent = { + [EventName in keyof EventMap]: Event; }; -export interface TypedEventTarget> { - /** Appends an event listener for events whose type attribute value is type. - * The callback argument sets the callback that will be invoked when the event - * is dispatched. - * - * The options argument sets listener-specific options. For compatibility this - * can be a boolean, in which case the method behaves exactly as if the value - * was specified as options's capture. - * - * When set to true, options's capture prevents callback from being invoked - * when the event's eventPhase attribute value is BUBBLING_PHASE. When false - * (or not present), callback will not be invoked when event's eventPhase - * attribute value is CAPTURING_PHASE. Either way, callback will be invoked if - * event's eventPhase attribute value is AT_TARGET. - * - * When set to true, options's passive indicates that the callback will not - * cancel the event by invoking preventDefault(). This is used to enable - * performance optimizations described in § 2.8 Observing event listeners. - * - * When set to true, options's once indicates that the callback will only be - * invoked once after which the event listener will be removed. - * - * The event listener is appended to target's event listener list and is not - * appended if it has the same type, callback, and capture. */ - addEventListener: ( - type: T, - listener: TypedEventListenerOrEventListenerObject | null, - options?: boolean | AddEventListenerOptions - ) => void; +export class TypedEventTarget> { + private listeners: { + [EventName in keyof EventMap]?: Array>; + }; - /** Removes the event listener in target's event listener list with the same - * type, callback, and options. */ - removeEventListener: ( - type: T, - callback: TypedEventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean - ) => void; + constructor() { + this.listeners = {}; + } - /** - * Dispatches a synthetic event event to target and returns true if either - * event's cancelable attribute value is false or its preventDefault() method - * was not invoked, and false otherwise. - * @deprecated To ensure type safety use `dispatchTypedEvent` instead. - */ - dispatchEvent: (event: Event) => boolean; -} -export class TypedEventTarget> extends EventTarget { - /** - * Dispatches a synthetic event event to target and returns true if either - * event's cancelable attribute value is false or its preventDefault() method - * was not invoked, and false otherwise. - */ - public dispatchTypedEvent( - _type: T, - event: M[T], + addEventListener( + type: EventName, + listener: Listener, + ): void { + if (!this.listeners[type]) { + this.listeners[type] = []; + } + this.listeners[type]?.push(listener); + } + + public dispatchEvent( + _type: EventName, + event: EventMap[EventName], ): boolean { - return super.dispatchEvent(event); + const listeners = this.listeners[_type]; + if (listeners) { + listeners.forEach((listener) => { + listener(event); + }); + } + return true; } } diff --git a/src/bundles/robot_simulation/engine/Core/RobotConsole.ts b/src/bundles/robot_simulation/engine/Core/RobotConsole.ts new file mode 100644 index 000000000..af54d9621 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Core/RobotConsole.ts @@ -0,0 +1,30 @@ +import { type Controller } from '..'; + +const logLevels = ['debug', 'error'] as const; +type LogLevel = typeof logLevels[number]; + +export type LogEntry = { + message: string; + level: LogLevel; + timestamp: number; +}; + +export class RobotConsole { + logs: LogEntry[]; + + constructor() { + this.logs = []; + } + + log(message: string, level: LogLevel = 'debug') { + this.logs.push({ + message, + level, + timestamp: Date.now(), + }); + } + + getLogs() { + return this.logs; + } +} diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 2a08c0f71..8f686cf2e 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -120,12 +120,12 @@ export class Physics extends TypedEventTarget { this.internals.accumulator += Math.min(frameDuration, maxFrameTime); while (this.internals.accumulator >= this.configuration.timestep) { - this.dispatchTypedEvent( + this.dispatchEvent( 'beforePhysicsUpdate', new TimeStampedEvent('beforePhysicsUpdate', timing), ); this.internals.world.step(); - this.dispatchTypedEvent( + this.dispatchEvent( 'afterPhysicsUpdate', new TimeStampedEvent('afterPhysicsUpdate', timing), ); diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/engine/World.ts index aac07c581..de39e0540 100644 --- a/src/bundles/robot_simulation/engine/World.ts +++ b/src/bundles/robot_simulation/engine/World.ts @@ -4,11 +4,15 @@ import { type Physics } from './Physics'; import { type Renderer } from './Render/Renderer'; import { type Timer } from './Core/Timer'; +import { type RobotConsole } from './Core/RobotConsole'; + + export const worldStates = [ 'unintialized', 'loading', 'ready', 'running', + 'error', ] as const; export type WorldState = (typeof worldStates)[number]; @@ -24,15 +28,17 @@ export class World extends TypedEventTarget { physics: Physics; render: Renderer; timer: Timer; + robotConsole: RobotConsole; controllers: ControllerGroup; - constructor(physics: Physics, render: Renderer, timer: Timer) { + constructor(physics: Physics, render: Renderer, timer: Timer, robotConsole: RobotConsole) { super(); this.state = 'unintialized'; this.physics = physics; this.render = render; this.timer = timer; this.controllers = new ControllerGroup(); + this.robotConsole = robotConsole; } addController(...controllers: Controller[]) { @@ -60,13 +66,13 @@ export class World extends TypedEventTarget { async init() { this.setState('loading'); await this.physics.start(); - this.dispatchTypedEvent('worldStart', new Event('worldStart')); + this.dispatchEvent('worldStart', new Event('worldStart')); this.setState('ready'); } private setState(newState: WorldState) { if (this.state !== newState) { - this.dispatchTypedEvent( + this.dispatchEvent( 'worldStateChange', new Event('worldStateChange'), ); @@ -85,27 +91,32 @@ export class World extends TypedEventTarget { window.requestAnimationFrame(this.step.bind(this)); } } - step(timestamp: number) { - const frameTimingInfo = this.timer.step(timestamp); + try { + const frameTimingInfo = this.timer.step(timestamp); - // Update physics - this.physics.step(frameTimingInfo); + // Update physics + this.physics.step(frameTimingInfo); - // Update render - this.dispatchTypedEvent( - 'beforeRender', - new TimeStampedEvent('beforeRender', frameTimingInfo), - ); - this.render.step(frameTimingInfo); - this.dispatchTypedEvent( - 'afterRender', - new TimeStampedEvent('afterRender', frameTimingInfo), - ); + // Update render + this.dispatchEvent( + 'beforeRender', + new TimeStampedEvent('beforeRender', frameTimingInfo), + ); + this.render.step(frameTimingInfo); + this.dispatchEvent( + 'afterRender', + new TimeStampedEvent('afterRender', frameTimingInfo), + ); - if (this.state === 'running') { - window.requestAnimationFrame(this.step.bind(this)); + + if (this.state === 'running') { + window.requestAnimationFrame(this.step.bind(this)); + } + } catch (e) { + console.log('Error caught', e); + this.setState('error'); } } } diff --git a/src/bundles/robot_simulation/engine/__tests__/Physics.ts b/src/bundles/robot_simulation/engine/__tests__/Physics.ts index c3bad127f..0c44b8a92 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Physics.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Physics.ts @@ -75,7 +75,7 @@ describe('Physics', () => { distance: expectedResult.toi, normal: expectedResult.normal, }); - expect(physics.internals.world.castRayAndGetNormal).toHaveBeenCalledWith(expect.anything(), maxDistance, false); + expect(physics.internals.world.castRayAndGetNormal).toHaveBeenCalledWith(expect.anything(), maxDistance, true, undefined, undefined, undefined); }); test('castRay returns null result castRayAndGetNormal returns null', async () => { diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 56cd612df..42745af4a 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -14,7 +14,6 @@ export function ev3_pause(duration: number): void { const world = getWorldFromContext(); // TODO: FIX THIS! CAUSES BUGS const program = world.controllers.controllers[0] as Program; - console.log(world.controllers, program, duration); program.pause(duration); } @@ -79,15 +78,11 @@ export function ev3_colorSensorBlue(colorSensor: ColorSensor) { // Ultrasonic Sensor -export function test() { - console.log('TEST'); -} - export function ev3_ultrasonicSensor() { const ev3 = getEv3FromContext(); return ev3.get('ultrasonicSensor'); } -export function ev3_ultrasonicSensorDistance(ultraSonicSensor: UltrasonicSensor) { +export function ev3_ultrasonicSensorDistance(ultraSonicSensor: UltrasonicSensor): number { return ultraSonicSensor.sense(); } diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index 6bbca8753..f5c515530 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -18,6 +18,7 @@ import { type Controller, Physics, Renderer, Timer, World } from './engine'; import context from 'js-slang/context'; import { interrupt } from './interrupt'; +import { RobotConsole } from './engine/Core/RobotConsole'; const storedWorld = context.moduleContexts.robot_simulation.state?.world; @@ -60,8 +61,8 @@ export function createTimer(): Timer { return timer; } -export function createWorld(physics: Physics, renderer: Renderer, timer: Timer) { - const world = new World(physics, renderer, timer); +export function createWorld(physics: Physics, renderer: Renderer, timer: Timer, robotConsole: RobotConsole) { + const world = new World(physics, renderer, timer, robotConsole); return world; } @@ -103,15 +104,20 @@ export function createCSE() { return program; } +export function createRobotConsole() { + const robot_console = new RobotConsole(); + return robot_console; +} + export function addControllerToWorld(controller: Controller, world:World) { world.addController(controller); } -export function saveToContext(world: World, ev3: DefaultEv3) { - context.moduleContexts.robot_simulation.state = { - world, - ev3, - }; +export function saveToContext(key:string, value:any) { + if (!context.moduleContexts.robot_simulation.state) { + context.moduleContexts.robot_simulation.state = {}; + } + context.moduleContexts.robot_simulation.state[key] = value; } // Initialization diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 915bdcd01..dae98820b 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -23,5 +23,6 @@ export { createFloor, createCSE, addControllerToWorld, + createRobotConsole, saveToContext, } from './helper_functions'; diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 62502a811..7ae9b33b0 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -10,6 +10,8 @@ import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; import { MonitoringPanel } from '../TabPanels/MonitoringPanel'; import { type WorldState } from '../../../../bundles/robot_simulation/engine/World'; import { UltrasonicSensorPanel } from '../TabPanels/UltrasonicSensorPanel'; +import { ConsolePanel } from '../TabPanels/ConsolePanel'; +import { type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; const WrapperStyle: CSSProperties = { display: 'flex', @@ -51,6 +53,8 @@ export default function SimulationCanvas({ const ev3 = context.context.moduleContexts.robot_simulation.state .ev3 as DefaultEv3; + const console = context.context.moduleContexts.robot_simulation.state.console as RobotConsole; + useEffect(() => { const startThreeAndRapierEngines = async () => { setCurrentState(world.state); @@ -96,11 +100,12 @@ export default function SimulationCanvas({
- } /> + } /> } /> } /> }/> }/> + } />
diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx index 831e3abfc..4cbb542c1 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx @@ -1,6 +1,5 @@ import { useEffect, useRef, useState } from 'react'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default'; -import { Switch } from '@blueprintjs/core'; export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { const colorSensor = ev3.get('colorSensor'); diff --git a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx new file mode 100644 index 000000000..a331d38c6 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx @@ -0,0 +1,14 @@ +import { type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; + +export const ConsolePanel = ({ console: robot_console }: { console: RobotConsole }) => { + if (robot_console === undefined) { + return
+

No console found in the context

+

Instantiate a console and save it to context

+
const robot_console = createConsole();
+
savetoContext("console", robot_console);
+
; + } + + return <>hi; +}; From 97a1946db0b486799e0b17dd97c525fae5e35718 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 28 Feb 2024 08:29:51 +0000 Subject: [PATCH 34/93] Remove unneeded files --- .../controllers/ev3/components/Mesh.ts | 3 +- .../robot_simulation/controllers/index.ts | 4 +- .../controllers/car/car_controller.ts | 227 ----------------- .../controllers/car/motor_controller.ts | 98 -------- .../controllers/car/pid_controller.ts | 115 --------- .../controllers/car/ultrasonic_sensor.ts | 19 -- .../controllers/car/wheel_controller.ts | 75 ------ .../controllers/physics/helpers.ts | 7 - .../controllers/physics/physics_controller.ts | 84 ------- .../physics/physics_object_controller.ts | 230 ------------------ .../controllers/program_controller.ts | 48 ---- .../controllers/render_controller.ts | 71 ------ .../controllers/time_controller.ts | 96 -------- .../robot_simulation/delete_this/index.ts | 16 -- .../robot_simulation/delete_this/types.ts | 3 - .../robot_simulation/delete_this/world.ts | 198 --------------- .../robot_simulation/engine/Physics.ts | 2 + src/bundles/robot_simulation/engine/index.ts | 4 +- .../components/Simulation/index.tsx | 5 +- 19 files changed, 11 insertions(+), 1294 deletions(-) delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/car/car_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/car/motor_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/car/pid_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/car/ultrasonic_sensor.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/car/wheel_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/physics/helpers.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/physics/physics_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/physics/physics_object_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/program_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/render_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/controllers/time_controller.ts delete mode 100644 src/bundles/robot_simulation/delete_this/index.ts delete mode 100644 src/bundles/robot_simulation/delete_this/types.ts delete mode 100644 src/bundles/robot_simulation/delete_this/world.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index 2d08885da..d96fa686a 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -6,7 +6,8 @@ import { } from '../../../engine'; import * as THREE from 'three'; import { type Orientation } from '../../../engine/Entity/Entity'; -import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; +// eslint-disable-next-line import/extensions +import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { type ChassisWrapper } from './Chassis'; export type MeshConfig = { diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts index 0d1880bda..2596e2ece 100644 --- a/src/bundles/robot_simulation/controllers/index.ts +++ b/src/bundles/robot_simulation/controllers/index.ts @@ -1,5 +1,5 @@ export { Floor } from './environment/Floor'; +export { type DefaultEv3 } from './ev3/ev3/default'; + export { Program } from './program/Program'; export { Wall } from './environment/Wall'; - -export { DefaultEv3 } from './ev3/ev3/default'; diff --git a/src/bundles/robot_simulation/delete_this/controllers/car/car_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/car/car_controller.ts deleted file mode 100644 index 1e175c480..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/car/car_controller.ts +++ /dev/null @@ -1,227 +0,0 @@ -import * as THREE from 'three'; - -import { - addCuboidPhysicsObject, - type PhysicsObject, -} from '../physics/physics_object_controller'; -import { type Vector } from '../physics/helpers'; - - -import { instance } from '../../world'; -import { type Steppable } from '../../types'; -import { MotorController } from './motor_controller'; -import { WheelController } from './wheel_controller'; - -export type CarSettings = { - chassis: { - length: number; - height: number; - width: number; - mass: number; - }; - wheel: { - restHeight: number; - diameter: number; - maxSuspensionLength: number; - suspension: { stiffness: number; damping: number }; - buffer: number; - }; - turning: { sensitivity: number }; -}; - -export const settings: CarSettings = { - chassis: { - length: 0.18, // in meters - height: 0.095, // in meters - width: 0.145, // in meters - mass: 0.6, // in kg - }, - wheel: { - restHeight: 0.03, - diameter: 0.056, - maxSuspensionLength: 0.1, - suspension: { - stiffness: 70, - damping: 3, - }, - buffer: 0.02, - }, - turning: { - sensitivity: 0.5, - }, -} as const; - -const motors = { - A: 0, - B: 1, - C: 2, - D: 3, -} as const; - -const motorDisplacements = [ - { - x: 0.058, - y: 0, - z: 0.055, - }, - { - x: -0.058, - y: 0, - z: 0.055, - }, -]; - -export type MotorsOptions = (typeof motors)[keyof typeof motors]; - -export class CarController implements Steppable { - carSettings: CarSettings; - wheelDisplacements: Vector[]; - - wheels: WheelController[]; - leftMotor: MotorController | null; - rightMotor: MotorController | null; - chassis: PhysicsObject | null; - - constructor(carSettings: CarSettings) { - this.carSettings = carSettings; - this.chassis = null; - this.rightMotor = null; - this.leftMotor = null; - this.wheels = []; - this.wheelDisplacements = this.#getWheelDisplacements(); - } - - init() { - this.chassis = this.#createChassis(); - this.#createWheels(this.chassis); - this.rightMotor = new MotorController( - this.chassis, - motorDisplacements[0], - ); - this.leftMotor = new MotorController( - this.chassis, - motorDisplacements[1], - ); - } - - #createChassis() { - const { width, height, length, mass } = this.carSettings.chassis; - - const chassis = addCuboidPhysicsObject({ - width, - height, - length, - position: new THREE.Vector3(0, 0.5564785599708557, 0), - }); - - chassis.setMass(mass); - - return chassis; - } - - #createWheels(chassis: PhysicsObject) { - const wheelDisplacements = this.wheelDisplacements; - this.wheels = wheelDisplacements.map( - (d) => new WheelController(d, chassis, this.carSettings), - ); - } - - #getWheelDisplacements() { - const { width, length } = this.carSettings.chassis; - const buffer = this.carSettings.wheel.buffer; - - return [ - { - x: width / 2 + buffer, - y: 0, - z: length / 2 - buffer, - }, - { - x: -(width / 2 + buffer), - y: 0, - z: length / 2 - buffer, - }, - { - x: width / 2 + buffer, - y: 0, - z: -(length / 2 - buffer), - }, - { - x: -(width / 2 + buffer), - y: 0, - z: -(length / 2 - buffer), - }, - ]; - } - - #getMotor(motor: MotorsOptions): MotorController | null { - const motorMapping: Record = { - 0: null, - 1: this.leftMotor, - 2: this.rightMotor, - 3: null, - }; - - return motorMapping[motor]; - } - - // EV3 Functions - motorA(): MotorsOptions { - return motors.A; - } - - motorB(): MotorsOptions { - return motors.B; - } - - motorC(): MotorsOptions { - return motors.C; - } - - motorD(): MotorsOptions { - return motors.D; - } - - /** - * - * @param motor - * @param position in degrees - * @param speed in degrees per second - */ - runToRelativePosition(motor: MotorsOptions, position: number, speed: number) { - const selectedMotor = this.#getMotor(motor); - if (!selectedMotor) { - return; - } - - const speedInMetersPerSecond - = (speed / 360) * Math.PI * this.carSettings.wheel.diameter; - const distanceInMetersPerSecond - = (position / 360) * Math.PI * this.carSettings.wheel.diameter; - - selectedMotor!.setSpeedDistance( - speedInMetersPerSecond, - distanceInMetersPerSecond, - ); - } - - motorGetSpeed(motor: MotorsOptions) { - const selectedMotor = this.#getMotor(motor); - if (!selectedMotor) { - return 0; - } - return selectedMotor!.speed; - } - - pause(time: number) { - instance.pauseProgramController(time); - } - - step(timestamp: number) { - this.wheels.forEach((wheel) => { - wheel.step(); - }); - this.leftMotor!.step(timestamp); - this.rightMotor!.step(timestamp); - } -} diff --git a/src/bundles/robot_simulation/delete_this/controllers/car/motor_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/car/motor_controller.ts deleted file mode 100644 index 1c886e84f..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/car/motor_controller.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { type PhysicsObject } from '../physics/physics_object_controller'; -import { vec3, type Vector } from '../physics/helpers'; - -import { type Steppable } from '../../types'; -import { instance } from '../../world'; - - -export class MotorController implements Steppable { - physicsObject: PhysicsObject; - displacement: Vector; - - speed: number; - distance: number; - - distanceTraveled: number; - previousLocation: THREE.Vector3; - - displacementVector: THREE.Vector3; - targetVelocity: THREE.Vector3; - - constructor(physicsObject: PhysicsObject, displacement: Vector) { - this.physicsObject = physicsObject; - this.displacement = displacement; - this.speed = 0; - this.distance = 0; - - this.displacementVector = vec3(this.displacement); - this.targetVelocity = vec3({ - x: 0, - y: 0, - z: this.speed, - }); - - this.distanceTraveled = 0; - this.previousLocation = this.physicsObject - .worldTranslation(vec3(this.displacementVector.clone())) - .clone(); - } - - setSpeedDistance(speed: number, distance: number) { - this.distance = distance; - this.speed = speed; - console.log(distance); - const beforeDistance = this.distanceTraveled; - instance.setTimeout(() => { - this.speed = 0; - console.log(this.distanceTraveled - beforeDistance); - }, distance / speed * 1000); - } - - #updateDistance() { - const worldTranslation = this.physicsObject.worldTranslation( - this.displacementVector.clone(), - ); - - const translationDelta = worldTranslation - .clone() - .sub(this.previousLocation); - this.previousLocation.copy(worldTranslation); - translationDelta.y = 0; - this.distanceTraveled += translationDelta.length(); - } - - step(_: number) { - this.displacementVector.copy(this.displacement as THREE.Vector3); - this.targetVelocity.copy({ - x: 0, - y: 0, - z: this.speed, - } as THREE.Vector3); - - this.#updateDistance(); - - const worldVelocity = this.physicsObject.worldVelocity( - this.displacementVector.clone(), - ); - - const velocityDelta = this.physicsObject - .transformDirection(this.targetVelocity.clone()) - .sub(worldVelocity); - - const impulse = velocityDelta - .multiplyScalar(this.physicsObject.getMass() / 4) - .projectOnPlane( - vec3({ - x: 0, - y: 1, - z: 0, - }), - ); - - this.physicsObject - .applyImpulse( - impulse, - this.physicsObject.worldTranslation(this.displacementVector.clone()), - ); - } -} diff --git a/src/bundles/robot_simulation/delete_this/controllers/car/pid_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/car/pid_controller.ts deleted file mode 100644 index f9e52a09f..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/car/pid_controller.ts +++ /dev/null @@ -1,115 +0,0 @@ -import * as THREE from 'three'; - -type NullaryFunction = () => T; -type BinaryFunction = (a: T, b: T) => T; -type ScaleFunction = (value: T, scale: number) => T; - -type PIDControllerOptions = { - zero: NullaryFunction; - add: BinaryFunction; - subtract: BinaryFunction; - scale: ScaleFunction; - - proportionalGain: number; - integralGain: number; - derivativeGain: number; -}; - -class PIDController { - zero: NullaryFunction; - add: BinaryFunction; - subtract: BinaryFunction; - scale: ScaleFunction; - - proportionalGain: number; - integralGain: number; - derivativeGain: number; - - errorsSum: T; - previousError: T; - - constructor({ - zero, - add, - subtract, - scale, - proportionalGain, - integralGain, - derivativeGain, - }: PIDControllerOptions) { - this.zero = zero; - this.add = add; - this.subtract = subtract; - this.scale = scale; - - this.proportionalGain = proportionalGain; - this.integralGain = integralGain; - this.derivativeGain = derivativeGain; - - this.errorsSum = this.zero(); - this.previousError = this.zero(); - } - - calculate(currentValue: T, setpoint: T): T { - const error = this.subtract(setpoint, currentValue); - console.log(error, 'error'); - this.errorsSum = this.add(this.errorsSum, error); - - const proportional = this.scale(error, this.proportionalGain); - const integral = this.scale(this.errorsSum, this.integralGain); - const derivative = this.scale(this.subtract(error, this.previousError), this.derivativeGain); - console.log(this.derivativeGain, this.integralGain, this.proportionalGain); - console.log(derivative, integral, proportional); - this.previousError = error; - - return this.add(this.add(proportional, integral), derivative); - } -} - - -export class NumberPidController extends PIDController { - constructor({ - proportionalGain, - integralGain, - derivativeGain, - }: { - proportionalGain: number; - integralGain: number; - derivativeGain: number; - }) { - super({ - zero: () => 0, - add: (a, b) => a + b, - subtract: (a, b) => a - b, - scale: (value, scale) => value * scale, - proportionalGain, - integralGain, - derivativeGain, - }); - } -} - -export class VectorPIDController extends PIDController { - constructor({ - proportionalGain, - integralGain, - derivativeGain, - }: { - proportionalGain: number; - integralGain: number; - derivativeGain: number; - }) { - super({ - zero: () => new THREE.Vector3(0, 0, 0), - add: (a, b) => a.clone() - .add(b), - subtract: (a, b) => a.clone() - .sub(b), - scale: (value, scale) => value.clone() - .multiplyScalar(scale), - proportionalGain, - integralGain, - derivativeGain, - }); - } -} diff --git a/src/bundles/robot_simulation/delete_this/controllers/car/ultrasonic_sensor.ts b/src/bundles/robot_simulation/delete_this/controllers/car/ultrasonic_sensor.ts deleted file mode 100644 index 4bc262e1e..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/car/ultrasonic_sensor.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type Vector } from '../physics/helpers'; -import { type PhysicsObject } from '../physics/physics_object_controller'; - - -export class UltrasonicSensor { - physicsObject: PhysicsObject; - displacement: Vector; - direction:Vector; - - constructor(physicsObject: PhysicsObject, displacement: Vector, direction: Vector) { - this.physicsObject = physicsObject; - this.displacement = displacement; - this.direction = direction; - } - - sense() { - - } -} diff --git a/src/bundles/robot_simulation/delete_this/controllers/car/wheel_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/car/wheel_controller.ts deleted file mode 100644 index 9c62d14f5..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/car/wheel_controller.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Vector3, type Ray } from '@dimforge/rapier3d-compat'; -import { type Steppable } from '../../types'; -import { type CarSettings } from './car_controller'; -import { type PhysicsObject } from '../physics/physics_object_controller'; -import { RAPIER } from '../physics/physics_controller'; -import { vec3, type Vector } from '../physics/helpers'; -import { instance } from '../../world'; -import type * as THREE from 'three'; -import { NumberPidController } from './pid_controller'; - -export class WheelController implements Steppable { - pidController: NumberPidController; - - carSettings: CarSettings; - displacement: Vector; - ray: Ray; - chassis: PhysicsObject; - - displacementVector: THREE.Vector3; - downVector: THREE.Vector3; - - constructor( - displacement: Vector, - chassis: PhysicsObject, - carSettings: CarSettings, - ) { - this.carSettings = carSettings; - this.displacement = displacement; - this.ray = new RAPIER.Ray(new Vector3(0, 0, 0), new Vector3(0, 0, 0)); - this.chassis = chassis; - - this.displacementVector = vec3(this.displacement); - this.downVector = vec3({ - x: 0, - y: -1, - z: 0, - }); - this.pidController = new NumberPidController({ - proportionalGain: 0.7, - derivativeGain: 3, - integralGain: 0.002, - }); - } - - step() { - const wheelSettings = this.carSettings.wheel; - - const globalDisplacement = this.chassis.worldTranslation( - this.displacementVector.clone(), - ); - const globalDownDirection = this.chassis.transformDirection(this.downVector.clone()); - - this.ray.origin = globalDisplacement; - this.ray.dir = globalDownDirection; - - const result = instance.castRay(this.ray, wheelSettings.maxSuspensionLength); - - // Wheels are not touching the ground - if (result === null) { - return; - } - - const { distance: wheelDistance, normal } = result; - const error = this.pidController.calculate(wheelDistance, wheelSettings.restHeight + this.carSettings.chassis.height / 2); - - const force = vec3(normal) - .normalize() - .multiplyScalar(error * this.chassis.getMass()); - - this.chassis.applyImpulse( - force, - globalDisplacement, - ); - } -} diff --git a/src/bundles/robot_simulation/delete_this/controllers/physics/helpers.ts b/src/bundles/robot_simulation/delete_this/controllers/physics/helpers.ts deleted file mode 100644 index f22a29658..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/physics/helpers.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Euler, Quaternion, Vector3 } from 'three'; - -export const quat = ({ x, y, z, w }) => new Quaternion(x, y, z, w); -export const vec3 = ({ x, y, z }) => new Vector3(x, y, z); -export const euler = ({ x, y, z }) => new Euler(x, y, z); - -export type Vector = { x: number; y: number; z: number }; diff --git a/src/bundles/robot_simulation/delete_this/controllers/physics/physics_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/physics/physics_controller.ts deleted file mode 100644 index 2a3798cf2..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/physics/physics_controller.ts +++ /dev/null @@ -1,84 +0,0 @@ -import Rapier, { type Vector, type Ray, type RigidBody } from '@dimforge/rapier3d-compat'; -import { type Steppable } from '../../types'; -import { instance } from '../../world'; - -let RAPIER: typeof Rapier; - -export const physicsOptions = { - GRAVITY: new Rapier.Vector3(0.0, -9.81, 0.0), - timestep: 1 / 60, -} as const; - -export class PhysicsController implements Steppable { - RAPIER: typeof Rapier | null; - - #world: Rapier.World | null; - #accumulator: number; - #residual: number; - - constructor() { - this.RAPIER = null; - this.#world = null; - this.#accumulator = 0; - this.#residual = 0; - } - - async init() { - let r = await import('@dimforge/rapier3d-compat'); - await r.init(); - - this.RAPIER = r; - RAPIER = r; - - this.#world = new r.World(physicsOptions.GRAVITY); - this.#world.timestep = physicsOptions.timestep; - } - - createCollider(colliderDesc: Rapier.ColliderDesc, rigidBody: RigidBody) { - return this.#world!.createCollider(colliderDesc, rigidBody); - } - - createRigidBody(rigidBodyDesc: Rapier.RigidBodyDesc) { - return this.#world!.createRigidBody(rigidBodyDesc); - } - - castRay(ray: Ray, maxDistance: number): { distance:number, normal: Vector } | null { - const result = this.#world!.castRayAndGetNormal(ray, maxDistance, true); - - if (result === null) { - return null; - } - - return { - distance: result.toi, - normal: result.normal, - }; - } - - getResidualTime() { - return this.#residual; - } - - /** - * Advances the physics simulation in discrete steps based on the frame time. - * Inspired by: https://gafferongames.com/post/fix_your_timestep/ - * @param {number} _ - Unused parameter, can be removed if not required elsewhere. - */ - step(_) { - // Limit the frame time to a maximum value to avoid spiral of death. - const maxFrameTime = 0.05; - this.#accumulator += Math.min(instance.getFrameTime(), maxFrameTime); - - // Update the physics world in fixed time steps - while (this.#accumulator >= physicsOptions.timestep) { - instance.savePhysicsObjectState(); - this.#world!.step(); // Advance the physics simulation - this.#accumulator -= physicsOptions.timestep; - } - - // Calculate the residual for interpolations - this.#residual = this.#accumulator / physicsOptions.timestep; - } -} - -export { RAPIER }; diff --git a/src/bundles/robot_simulation/delete_this/controllers/physics/physics_object_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/physics/physics_object_controller.ts deleted file mode 100644 index 64990ff7f..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/physics/physics_object_controller.ts +++ /dev/null @@ -1,230 +0,0 @@ -import type Rapier from '@dimforge/rapier3d-compat'; -import * as THREE from 'three'; -import { RAPIER } from './physics_controller'; - -import { instance } from '../../world'; -import { quat, vec3 } from './helpers'; -import { type Steppable } from '../../types'; - -type Orientation = { - translation: THREE.Vector3; - rotation: THREE.Quaternion; -}; - -export class PhysicsObject implements Steppable { - #rigidBody: Rapier.RigidBody; - #mesh: THREE.Mesh; - #collider: Rapier.Collider; - - #previousState?: Orientation; - - constructor( - mesh: THREE.Mesh, - rigidBody: Rapier.RigidBody, - collider: Rapier.Collider, - ) { - this.#rigidBody = rigidBody; - this.#mesh = mesh; - this.#collider = collider; - } - - // This is a bypass for development. - getRigidBody() { - return this.#rigidBody; - } - - setMass(mass: number) { - this.#collider.setMass(mass); - } - - getMass(): number { - return this.#collider.mass(); - } - - applyImpulse( - impulse: THREE.Vector3, - point: THREE.Vector3 = new THREE.Vector3(), - ) { - return this.#rigidBody.applyImpulseAtPoint(impulse, point, true); - } - - rotation(): THREE.Quaternion { - return quat(this.#rigidBody.rotation()); - } - - velocity(): THREE.Vector3 { - return vec3(this.#rigidBody.linvel()); - } - - angularVelocity(): THREE.Vector3 { - return vec3(this.#rigidBody.angvel()); - } - - translation(): THREE.Vector3 { - return vec3(this.#rigidBody.translation()); - } - - worldTranslation( - localTranslation: THREE.Vector3 = new THREE.Vector3(), - ): THREE.Vector3 { - const rotation = this.rotation(); - const translation = this.translation(); - - return localTranslation.applyQuaternion(rotation) - .add(translation); - } - - transformDirection(localDirection: THREE.Vector3): THREE.Vector3 { - const rotation = this.rotation(); - return localDirection.applyQuaternion(rotation); - } - - distanceVectorOfPointToRotationalAxis( - localPoint: THREE.Vector3 = new THREE.Vector3(), - ) { - return localPoint - .clone() - .projectOnVector(this.angularVelocity()) - .negate() - .add(localPoint); - } - - /** - * Calculates the tangential velocity of a point in a rotating system. - * @param {THREE.Vector3} localPoint - The point for which to calculate the tangential velocity. - * @returns {THREE.Vector3} The tangential velocity vector of the point. - */ - tangentialVelocityOfPoint(localPoint = new THREE.Vector3()): THREE.Vector3 { - // Calculate the distance vector from the point to the rotational axis - const distanceVector - = this.distanceVectorOfPointToRotationalAxis(localPoint); - - // Retrieve the angular velocity of the system - const angularVelocity = this.angularVelocity(); - - // Calculate the magnitude of the tangential velocity - const velocityMagnitude - = distanceVector.length() * angularVelocity.length(); - - // Calculate the tangential velocity vector - const tangentialVelocity = this.transformDirection(localPoint) - .cross(angularVelocity) - .negate() - .normalize() - .multiplyScalar(velocityMagnitude); - - // Return the tangential velocity vector - return tangentialVelocity; - } - - worldVelocity( - localPoint: THREE.Vector3 = new THREE.Vector3(), - ): THREE.Vector3 { - return this.tangentialVelocityOfPoint(localPoint) - .add(this.velocity()); - } - - savePreviousState() { - this.#previousState = { - translation: this.translation() - .clone(), - rotation: this.rotation() - .clone(), - }; - } - - getInterpolatedState(): Orientation { - if (!this.#previousState) { - return { - translation: this.translation(), - rotation: this.rotation(), - }; - } - - const interpolationFactor = instance.getResidualTime(); - - return { - translation: this.#previousState.translation.clone() - .lerp(this.translation(), interpolationFactor), - rotation: this.#previousState.rotation.clone() - .slerp(this.rotation(), interpolationFactor), - }; - } - - /** - * Syncs the mesh's position and quaternion with the physics world. - * Usually called after a physics step - */ - step(_: number) { - const interpolatedState = this.getInterpolatedState(); - this.#mesh.position.copy(interpolatedState.translation); - this.#mesh.quaternion.copy(interpolatedState.rotation); - } -} - -export class PhysicsObjectController implements Steppable { - physicsObjects: Array; - - constructor() { - this.physicsObjects = []; - } - - add(physicsObject: PhysicsObject) { - this.physicsObjects.push(physicsObject); - } - - saveLocation() { - for (const physicsObject of this.physicsObjects) { - physicsObject.savePreviousState(); - } - } - - step(_) { - for (const physicsObject of this.physicsObjects) { - physicsObject.step(_); - } - } -} - -type Cuboid = { - width: number; - height: number; - length: number; - position?: THREE.Vector3; - color?: THREE.Color; - dynamic?: boolean; -}; - -export const addCuboidPhysicsObject = ({ - width, - height, - length, - position = new THREE.Vector3(0, 0, 0), - color = new THREE.Color('blue'), - dynamic = true, -}: Cuboid): PhysicsObject => { - const geometry = new THREE.BoxGeometry(width, height, length); - const material = new THREE.MeshPhysicalMaterial({ - color, - side: THREE.DoubleSide, - }); - - const mesh = new THREE.Mesh(geometry, material); - mesh.position.copy(position); - - const rigidBodyDesc = dynamic - ? RAPIER.RigidBodyDesc.dynamic() - : RAPIER.RigidBodyDesc.fixed(); - - rigidBodyDesc.translation.x = mesh.position.x; - rigidBodyDesc.translation.y = mesh.position.y; - rigidBodyDesc.translation.z = mesh.position.z; - - const colliderDesc = RAPIER.ColliderDesc.cuboid( - width / 2, - height / 2, - length / 2, - ); - - return instance.addRigidBody(mesh, rigidBodyDesc, colliderDesc); -}; diff --git a/src/bundles/robot_simulation/delete_this/controllers/program_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/program_controller.ts deleted file mode 100644 index 66be82ae3..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/program_controller.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { type IOptions, runECEvaluatorByJoel } from 'js-slang'; -import context from 'js-slang/context'; -import { type Steppable } from '../types'; - -import { instance } from '../world'; - -export class ProgramController implements Steppable { - #code: string; - #iterator: Generator | null; - #isPaused:boolean; - - constructor() { - this.#code = ''; - this.#iterator = null; - this.#isPaused = false; - } - - pause(time: number) { - this.#isPaused = true; - - instance.setTimeout(() => { - this.#isPaused = false; - }, time); - } - - init(code: string) { - this.#code = code; - - const options: Partial = { - originalMaxExecTime: Infinity, - scheduler: 'preemptive', - stepLimit: Infinity, - throwInfiniteLoops: false, - useSubst: false, - }; - - context.errors = []; - - this.#iterator = runECEvaluatorByJoel(this.#code, context, options); - } - - step(_: number) { - if (this.#isPaused) { - return; - } - this.#iterator!.next(); - } -} diff --git a/src/bundles/robot_simulation/delete_this/controllers/render_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/render_controller.ts deleted file mode 100644 index 49f695a62..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/render_controller.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-disable import/extensions */ -import * as THREE from 'three'; - -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; -import { - type GLTF, - GLTFLoader, -} from 'three/examples/jsm/loaders/GLTFLoader.js'; - -import { type Steppable } from '../types'; - -export const sceneOptions = { - height: 600, - width: 800, -} as const; - -export class RenderController implements Steppable { - #scene: THREE.Scene; - #camera: THREE.Camera; - #renderer: THREE.WebGLRenderer; - #controls: OrbitControls; - - constructor() { - this.#scene = new THREE.Scene(); - - const renderAspectRatio = sceneOptions.width / sceneOptions.height; - this.#camera = new THREE.PerspectiveCamera( - 75, - renderAspectRatio, - 0.01, - 1000, - ); - - this.#renderer = new THREE.WebGLRenderer({ antialias: true }); - this.#controls = new OrbitControls(this.#camera, this.#renderer.domElement); - } - - init(): void { - this.#scene.background = new THREE.Color(0xffffff); - const light = new THREE.AmbientLight(0xffffff); - this.#scene.add(light); - - this.#camera.translateY(2); - this.#camera.lookAt(new THREE.Vector3(0, -1.5, 0)); - - this.#renderer.setSize(sceneOptions.width, sceneOptions.height); - this.#renderer.setPixelRatio(window.devicePixelRatio * 1.5); - } - - loadGTLF(url: string): Promise { - const loader = new GLTFLoader(); - return new Promise((resolve, reject) => { - loader.load(url, resolve, () => {}, reject); - }); - } - - setRendererOutput(domElement: HTMLDivElement): void { - domElement.replaceChildren(this.#renderer.domElement); - } - - addMesh(mesh: THREE.Mesh): void { - this.#scene.add(mesh); - } - - step(_: number): void { - this.#renderer.render(this.#scene, this.#camera); - this.#controls.update(); - } -} - -export { THREE }; diff --git a/src/bundles/robot_simulation/delete_this/controllers/time_controller.ts b/src/bundles/robot_simulation/delete_this/controllers/time_controller.ts deleted file mode 100644 index 015eb5187..000000000 --- a/src/bundles/robot_simulation/delete_this/controllers/time_controller.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { type Steppable } from '../types'; - -export type TimeoutCallback = () => void; - -export class TimeController implements Steppable { - private startTime: number | null; - private pauseTime: number | null; // Time when the last pause occurred - private totalPausedTime: number; // Total duration of pauses - private isPaused: boolean; - private elapsedTime: number; // Time since start excluding paused durations - private timeoutEvents: Array<{ callback: TimeoutCallback; triggerTime: number }>; - private deltaTime: number; // Time difference between the current and last step - - constructor() { - this.startTime = null; - this.pauseTime = null; - this.isPaused = false; - this.totalPausedTime = 0; - this.elapsedTime = 0; - this.timeoutEvents = []; - this.deltaTime = 0; - } - - // Check if the simulation has started - hasStarted(): boolean { - return this.startTime !== null; - } - - // Get the total elapsed time excluding paused durations - getElapsedTime(): number { - return this.elapsedTime; - } - - getDeltaTime(): number { - return this.deltaTime; - } - - // Pause the simulation - pause(): void { - if (!this.isPaused && this.hasStarted()) { - this.pauseTime = performance.now(); - this.isPaused = true; - } - } - - // Schedule a callback function to execute after a delay - setTimeout(callback: TimeoutCallback, delay: number): void { - if (!this.hasStarted()) { - throw new Error('Cannot set timeout before starting simulation'); - } - - const triggerTime = this.elapsedTime + delay; - this.timeoutEvents.push({ - callback, - triggerTime, - }); - } - - // Check and execute expired timeout events - private checkTimeouts(): void { - if (!this.hasStarted() || this.isPaused) return; - - const currentTime = this.elapsedTime; - this.timeoutEvents = this.timeoutEvents.filter((timeoutEvent) => { - if (timeoutEvent.triggerTime <= currentTime) { - timeoutEvent.callback(); - return false; // Remove from the array - } - return true; // Keep in the array - }); - } - - // Update the state for each step in the simulation - step(timestamp: number): void { - // Initialize start time on first step - if (this.startTime === null) { - this.startTime = timestamp; - } - - // Resume from pause - if (this.isPaused && this.pauseTime !== null) { - this.totalPausedTime += timestamp - this.pauseTime; - this.pauseTime = null; - this.isPaused = false; - } - - - // Update elapsed and delta time - const new_elapsed_time = timestamp - this.startTime - this.totalPausedTime; - this.deltaTime = new_elapsed_time - this.elapsedTime; - this.elapsedTime = new_elapsed_time; - - // Process timeout events - this.checkTimeouts(); - } -} diff --git a/src/bundles/robot_simulation/delete_this/index.ts b/src/bundles/robot_simulation/delete_this/index.ts deleted file mode 100644 index eefbfa4e6..000000000 --- a/src/bundles/robot_simulation/delete_this/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import context from 'js-slang/context'; -import { type World } from './world'; - -console.log('World is being initialized'); - -const contextState = context.moduleContexts.robot_simulation.state?.world; - -// if (contextState === undefined) { -// context.moduleContexts.robot_simulation.state = { -// ...context.moduleContexts.robot_simulation.state, -// world: new World(), -// }; -// console.log('world is being set to initial state'); -// } - -export const getWorld = (): World => context.moduleContexts.robot_simulation.state.world; diff --git a/src/bundles/robot_simulation/delete_this/types.ts b/src/bundles/robot_simulation/delete_this/types.ts deleted file mode 100644 index c47102939..000000000 --- a/src/bundles/robot_simulation/delete_this/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface Steppable { - step(timstamp: number):void -} diff --git a/src/bundles/robot_simulation/delete_this/world.ts b/src/bundles/robot_simulation/delete_this/world.ts deleted file mode 100644 index 235ae6f7b..000000000 --- a/src/bundles/robot_simulation/delete_this/world.ts +++ /dev/null @@ -1,198 +0,0 @@ -import * as THREE from 'three'; - -import { - type Ray, - type ColliderDesc, - type RigidBodyDesc, -} from '@dimforge/rapier3d-compat'; - -import { - PhysicsObject, PhysicsObjectController, - addCuboidPhysicsObject, -} from './controllers/physics/physics_object_controller'; - -import { PhysicsController } from './controllers/physics/physics_controller'; -import { ProgramController } from './controllers/program_controller'; -import { RenderController } from './controllers/render_controller'; -import { - TimeController, - type TimeoutCallback, -} from './controllers/time_controller'; -import { settings, CarController } from './controllers/car/car_controller'; -import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; - -export const simulationStates = [ - 'unintialized', - 'loading', - 'ready', - 'running', -] as const; -export type SimulationStates = (typeof simulationStates)[number]; - -export type Internals = { - physicsController: PhysicsController; - renderController: RenderController; - programController: ProgramController; - physicsObjects: PhysicsObjectController; - timeController: TimeController; -}; - -let instance: World; - -export class World { - state: SimulationStates; - #internals: Internals; - carController: CarController; - - constructor() { - if (instance) { - throw new Error('Only one instance of world is allowed'); - } - // eslint-disable-next-line consistent-this - instance = this; - - // initialization - this.state = 'unintialized'; - this.#internals = { - physicsController: new PhysicsController(), - renderController: new RenderController(), - programController: new ProgramController(), - physicsObjects: new PhysicsObjectController(), - timeController: new TimeController(), - }; - this.carController = new CarController(settings); - } - - async init(code: string): Promise { - if (this.state === 'running') { - return; - } - - this.state = 'loading'; - - const { physicsController, renderController, programController } - = this.#internals; - - await physicsController.init(); - renderController.init(); - programController.init(code); - this.carController.init(); - this.addFloor(); - this.state = 'ready'; - } - - startSimulation(): void { - if (this.state === 'ready') { - this.state = 'running'; - } - window.requestAnimationFrame(this.#step.bind(this)); - } - - stopSimulation(): void { - if (this.state === 'running') { - this.state = 'ready'; - this.#internals.timeController.pause(); - } - } - - // Physics Controller - addFloor(): void { - addCuboidPhysicsObject({ - width: 20, - height: 1, - length: 20, - color: new THREE.Color('white'), - dynamic: false, - position: new THREE.Vector3(0, 0, 0), - }); - } - - castRay(ray: Ray, maxDistance: number): ReturnType { - const { physicsController } = this.#internals; - return physicsController.castRay(ray, maxDistance); - } - - addRigidBody( - mesh: THREE.Mesh, - rigidBodyDesc: RigidBodyDesc, - colliderDesc: ColliderDesc, - ): PhysicsObject { - const { physicsController, renderController, physicsObjects } - = this.#internals; - - renderController.addMesh(mesh); - const rigidBody = physicsController.createRigidBody(rigidBodyDesc); - const collider = physicsController.createCollider(colliderDesc, rigidBody); - const physicsObject = new PhysicsObject(mesh, rigidBody, collider); - physicsObjects.add(physicsObject); - return physicsObject; - } - - getResidualTime(): number { - return this.#internals.physicsController.getResidualTime(); - } - - // Render Controller - setRendererOutput(domElement: HTMLDivElement) { - console.log('Setting renderer output'); - this.#internals.renderController.setRendererOutput(domElement); - } - - loadGTLF(url: string): Promise { - return this.#internals.renderController.loadGTLF(url); - } - - // Time controller - getElapsedTime(): number { - return this.#internals.timeController.getElapsedTime(); - } - - setTimeout(callback: TimeoutCallback, delay: number): void { - this.#internals.timeController.setTimeout(callback, delay); - } - - getFrameTime(): number { - return this.#internals.timeController.getDeltaTime() / 1000; - } - - // Program Controller - pauseProgramController(time: number): void { - this.#internals.programController.pause(time); - } - - // Physic Object Controller - savePhysicsObjectState() { - this.#internals.physicsObjects.saveLocation(); - } - - #step(timestamp: number): void { - const { - programController, - physicsController, - renderController, - physicsObjects, - timeController, - } = this.#internals; - - programController.step(timestamp); - - // Apply the forces of the car - this.carController.step(timestamp); - - // Calculate the new location of each physics object (including the car) - physicsController.step(timestamp); - - // Update the location of the mesh - physicsObjects.step(timestamp); - - // Render the scene - renderController.step(timestamp); - - if (this.state === 'running') { - timeController.step(timestamp); - window.requestAnimationFrame(this.#step.bind(this)); - } - } -} - -export { instance }; diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 8f686cf2e..3b65cc508 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -1,5 +1,7 @@ import rapier from '@dimforge/rapier3d-compat'; +import type * as THREE from 'three'; + import { type SimpleVector } from './Math/Vector'; import { type FrameTimingInfo } from './Core/Timer'; import { TimeStampedEvent, TypedEventTarget } from './Core/Events'; diff --git a/src/bundles/robot_simulation/engine/index.ts b/src/bundles/robot_simulation/engine/index.ts index e112840cb..a33280630 100644 --- a/src/bundles/robot_simulation/engine/index.ts +++ b/src/bundles/robot_simulation/engine/index.ts @@ -1,8 +1,8 @@ export { World } from './World'; export { Physics } from './Physics'; export { Renderer } from './Render/Renderer'; -export { Timer, FrameTimingInfo } from './Core/Timer'; -export { ControllerGroup, Controller, ControllerMap } from './Core/Controller'; +export { Timer, type FrameTimingInfo } from './Core/Timer'; +export { ControllerGroup, type Controller, ControllerMap } from './Core/Controller'; export { Entity } from './Entity/Entity'; export * as EntityFactory from './Entity/EntityFactory'; export * as MeshFactory from './Render/MeshFactory'; diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 7ae9b33b0..031ef2f8b 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -3,15 +3,16 @@ import type { DebuggerContext } from '../../../../typings/type_helpers'; import { type World } from '../../../../bundles/robot_simulation/engine'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; +import { type WorldState } from '../../../../bundles/robot_simulation/engine/World'; +import { type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; + import { Tab, Tabs } from '@blueprintjs/core'; import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; import { MonitoringPanel } from '../TabPanels/MonitoringPanel'; -import { type WorldState } from '../../../../bundles/robot_simulation/engine/World'; import { UltrasonicSensorPanel } from '../TabPanels/UltrasonicSensorPanel'; import { ConsolePanel } from '../TabPanels/ConsolePanel'; -import { type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; const WrapperStyle: CSSProperties = { display: 'flex', From 25a52a55ef4f0111d243622c1bedd392c2f4e300 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 28 Feb 2024 11:31:51 +0000 Subject: [PATCH 35/93] Minor modifications so that js-slang/context is not imported --- src/bundles/robot_simulation/controllers/index.ts | 1 - src/bundles/robot_simulation/ev3_functions.ts | 3 ++- src/bundles/robot_simulation/helper_functions.ts | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts index 2596e2ece..8b8898751 100644 --- a/src/bundles/robot_simulation/controllers/index.ts +++ b/src/bundles/robot_simulation/controllers/index.ts @@ -1,5 +1,4 @@ export { Floor } from './environment/Floor'; export { type DefaultEv3 } from './ev3/ev3/default'; -export { Program } from './program/Program'; export { Wall } from './environment/Wall'; diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 42745af4a..480cd55f5 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { type Program } from './controllers'; +import { type Program } from './controllers/program/Program'; + import { type Motor } from './controllers/ev3/components/Motor'; import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; import { type UltrasonicSensor } from './controllers/ev3/sensor/UltrasonicSensor'; diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index f5c515530..5c257cf9b 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -12,7 +12,8 @@ import { ultrasonicSensorConfig, } from './config'; -import { Floor, type DefaultEv3, Program, Wall } from './controllers'; +import { Program } from './controllers/program/Program'; +import { Floor, type DefaultEv3, Wall } from './controllers'; import { createDefaultEv3 } from './controllers/ev3/ev3/default'; import { type Controller, Physics, Renderer, Timer, World } from './engine'; From 74f8837be820182e6c2c18adfeb21cb9bfede4db Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 29 Feb 2024 02:03:21 +0000 Subject: [PATCH 36/93] Add @types/three --- package.json | 1 + yarn.lock | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/package.json b/package.json index bf98f59f8..b34538e5f 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@types/plotly.js-dist": "npm:@types/plotly.js", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", + "@types/three": "^0.161.2", "@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/parser": "^6.6.0", "@vitejs/plugin-react": "^4.0.4", diff --git a/yarn.lock b/yarn.lock index 81037fd78..652f5f446 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1337,11 +1337,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@*": + version "0.5.14" + resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.14.tgz#9a03121a4912ea113b31e5c9c17f164d4fff8a1f" + integrity sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -3062,6 +3082,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +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" @@ -4680,6 +4705,11 @@ 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== +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" From 78759a0b6360d22fe9a27ffd9ff7411a0b8460b2 Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 29 Feb 2024 05:24:37 +0000 Subject: [PATCH 37/93] Update threejs --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b34538e5f..02841d20e 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "save-file": "^2.3.1", "source-academy-utils": "^1.0.0", "source-academy-wabt": "^1.0.4", - "three": "^0.156.1", + "three": "^0.161.2", "tslib": "^2.3.1" }, "jest": { diff --git a/yarn.lock b/yarn.lock index 652f5f446..a5995e3f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6150,10 +6150,10 @@ 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@^0.156.1: - version "0.156.1" - resolved "https://registry.yarnpkg.com/three/-/three-0.156.1.tgz#bab4fec121a5b3975eb4f4d227d9c912171eb399" - integrity sha512-kP7H0FK9d/k6t/XvQ9FO6i+QrePoDcNhwl0I02+wmUJRNSLCUIDMcfObnzQvxb37/0Uc9TDT0T1HgsRRrO6SYQ== +three@^0.161.2: + 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" From 9faf350444a05e2e66889d6d7621bf9a802ab8b6 Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 29 Feb 2024 05:25:10 +0000 Subject: [PATCH 38/93] Change mesh to without wheels --- src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index d96fa686a..d43da175b 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -41,7 +41,7 @@ export class Mesh implements Controller { async start(): Promise { this.mesh = await Renderer.loadGTLF( - 'https://keen-longma-3c1be1.netlify.app/2_colors_corrected.gltf', + 'https://keen-longma-3c1be1.netlify.app/4_no_wheels.gltf', ); const box = new THREE.Box3() From 2975a6e4ac31d79a374964f5756faa6dd604780a Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 29 Feb 2024 06:58:21 +0000 Subject: [PATCH 39/93] Add console --- .../controllers/program/Program.ts | 25 ++++--- .../controllers/program/error.ts | 6 ++ .../engine/Core/RobotConsole.ts | 6 +- src/bundles/robot_simulation/engine/World.ts | 9 +++ .../components/Simulation/index.tsx | 5 +- .../components/TabPanels/ConsolePanel.tsx | 65 +++++++++++++++---- .../TabPanels/tabComponents/LastUpdated.tsx | 15 +++++ .../hooks/fetchFromSimulation.ts | 19 ++++++ 8 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 src/bundles/robot_simulation/controllers/program/error.ts create mode 100644 src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx create mode 100644 src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index 60df1b340..768b36ab0 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -3,12 +3,12 @@ import { runECEvaluator } from './evaluate'; import context from 'js-slang/context'; import { type FrameTimingInfo, type Controller } from '../../engine'; import { CallbackHandler } from '../../engine/Core/CallbackHandler'; +import { ProgramError } from './error'; type ProgramConfig = { stepsPerTick: number; }; - export class Program implements Controller { code: string; iterator: ReturnType | null; @@ -43,17 +43,22 @@ export class Program implements Controller { } fixedUpdate(_: number) { - if (!this.iterator) { - throw Error('Program not started'); - } + try { + if (!this.iterator) { + throw Error('Program not started'); + } - if (this.isPaused) { - return; - } + if (this.isPaused) { + return; + } - // steps per tick - for (let i = 0; i < 10; i++) { - const result = this.iterator.next(); + // steps per tick + for (let i = 0; i < 10; i++) { + const result = this.iterator.next(); + } + } catch (e) { + console.error(e); + throw new ProgramError('Error in program execution. Please check your code and try again.'); } } diff --git a/src/bundles/robot_simulation/controllers/program/error.ts b/src/bundles/robot_simulation/controllers/program/error.ts new file mode 100644 index 000000000..6fe991fb5 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/program/error.ts @@ -0,0 +1,6 @@ +export class ProgramError extends Error { + constructor(message) { + super(message); + this.name = 'ProgramError'; + } +} diff --git a/src/bundles/robot_simulation/engine/Core/RobotConsole.ts b/src/bundles/robot_simulation/engine/Core/RobotConsole.ts index af54d9621..1d6c87722 100644 --- a/src/bundles/robot_simulation/engine/Core/RobotConsole.ts +++ b/src/bundles/robot_simulation/engine/Core/RobotConsole.ts @@ -1,6 +1,4 @@ -import { type Controller } from '..'; - -const logLevels = ['debug', 'error'] as const; +const logLevels = ['source', 'error'] as const; type LogLevel = typeof logLevels[number]; export type LogEntry = { @@ -16,7 +14,7 @@ export class RobotConsole { this.logs = []; } - log(message: string, level: LogLevel = 'debug') { + log(message: string, level) { this.logs.push({ message, level, diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/engine/World.ts index de39e0540..11e841090 100644 --- a/src/bundles/robot_simulation/engine/World.ts +++ b/src/bundles/robot_simulation/engine/World.ts @@ -5,6 +5,7 @@ import { type Renderer } from './Render/Renderer'; import { type Timer } from './Core/Timer'; import { type RobotConsole } from './Core/RobotConsole'; +import { ProgramError } from '../controllers/program/error'; export const worldStates = [ @@ -116,6 +117,14 @@ export class World extends TypedEventTarget { } } catch (e) { console.log('Error caught', e); + if (e instanceof Error) { + this.robotConsole.log(e.message, 'error'); + } else if (e instanceof ProgramError) { + this.robotConsole.log(e.message, 'source'); + } else { + // e is not an error. Just log a generic error message + this.robotConsole.log('An error occurred', 'error'); + } this.setState('error'); } } diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 031ef2f8b..73df77bf3 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -4,7 +4,6 @@ import type { DebuggerContext } from '../../../../typings/type_helpers'; import { type World } from '../../../../bundles/robot_simulation/engine'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; import { type WorldState } from '../../../../bundles/robot_simulation/engine/World'; -import { type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; import { Tab, Tabs } from '@blueprintjs/core'; import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; @@ -54,7 +53,7 @@ export default function SimulationCanvas({ const ev3 = context.context.moduleContexts.robot_simulation.state .ev3 as DefaultEv3; - const console = context.context.moduleContexts.robot_simulation.state.console as RobotConsole; + const robotConsole = world.robotConsole; useEffect(() => { const startThreeAndRapierEngines = async () => { @@ -106,7 +105,7 @@ export default function SimulationCanvas({ } /> }/> }/> - } /> + } /> diff --git a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx index a331d38c6..92793383c 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx @@ -1,14 +1,57 @@ -import { type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; - -export const ConsolePanel = ({ console: robot_console }: { console: RobotConsole }) => { - if (robot_console === undefined) { - return
-

No console found in the context

-

Instantiate a console and save it to context

-
const robot_console = createConsole();
-
savetoContext("console", robot_console);
-
; +import { type LogEntry, type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; +import { useFetchFromSimulation } from '../../hooks/fetchFromSimulation'; +import { LastUpdated, getTimeString } from './tabComponents/LastUpdated'; + + +const getLogString = (log: LogEntry) => { + const logLevelText :Record = { + source: 'Runtime Source Error', + error: 'Error', + }; + + const timeString = getTimeString(new Date(log.timestamp)); + return `[${timeString}] ${logLevelText[log.level]}: ${log.message}`; +}; + +export const ConsolePanel = ({ + robot_console, +}: { + robot_console?: RobotConsole; +}) => { + const [timing, logs] = useFetchFromSimulation(() => { + if (robot_console === undefined) { + return null; + } + return robot_console.getLogs(); + }, 1000); + + if (timing === null) { + return
Not fetched yet
; + } + + if (logs === null) { + return ( +
+ Console not found. Ensure that the world is initialized properly. +
+ ); + } + + if (logs.length === 0) { + return ( +
+ +

There is currently no logs

+
+ ); } - return <>hi; + return ( +
+ +
    + {logs.map((log, i) =>
  • {getLogString(log)}
  • )} +
+
+ ); }; diff --git a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx new file mode 100644 index 000000000..4310909da --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx @@ -0,0 +1,15 @@ +export const getTimeString = (date: Date) => { + const options: Intl.DateTimeFormatOptions = { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, // Use 24-hour format. Set to true for 12-hour format if preferred. + }; + return date.toLocaleTimeString([], options); +}; + +export const LastUpdated = ({ time }: { time: Date }) => { + const timeString = getTimeString(time); + + return Last updated: {timeString}; +}; diff --git a/src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts b/src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts new file mode 100644 index 000000000..463fb04a2 --- /dev/null +++ b/src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from 'react'; + + +export const useFetchFromSimulation = (fetchFn:() => T, fetchInterval: number) => { + const [fetchTime, setFetchTime] = useState(null); + const [fetchedData, setFetchedData] = useState(null); + + useEffect(() => { + const interval = setInterval(() => { + const data = fetchFn(); + setFetchedData(data); + setFetchTime(new Date()); + }, fetchInterval); + + return () => clearInterval(interval); + }); + + return [fetchTime, fetchedData] as const; +}; From df94dabe6935f4a041a62680ca0a531b72ee27d0 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 1 Mar 2024 07:22:06 +0000 Subject: [PATCH 40/93] Add wheel mesh logic --- src/bundles/robot_simulation/config.ts | 14 ++--- .../controllers/ev3/components/Chassis.ts | 30 ++++++++++- .../controllers/ev3/components/Mesh.ts | 21 ++------ .../controllers/ev3/components/Motor.ts | 53 +++++++++++++++++-- .../controllers/ev3/components/Wheel.ts | 8 ++- .../controllers/ev3/ev3/default.ts | 4 +- .../components/TabPanels/WheelPidPanel.tsx | 2 +- 7 files changed, 99 insertions(+), 33 deletions(-) diff --git a/src/bundles/robot_simulation/config.ts b/src/bundles/robot_simulation/config.ts index 23a8537b9..d07c0e984 100644 --- a/src/bundles/robot_simulation/config.ts +++ b/src/bundles/robot_simulation/config.ts @@ -36,7 +36,7 @@ export const chassisConfig: EntityCuboidOptions = { orientation: { position: { x: 0, - y: 0.2, + y: 0.0775, z: 0, }, rotation: { @@ -56,7 +56,7 @@ export const chassisConfig: EntityCuboidOptions = { export const meshConfig: MeshConfig = { orientation: chassisConfig.orientation, width: chassisConfig.width, - height: chassisConfig.height, + height: chassisConfig.height + 0.02, length: chassisConfig.length, color: new THREE.Color('blue'), debug: true, @@ -88,21 +88,21 @@ export const wheelDisplacements: Record = { }; export const wheelPidConfig = { - proportionalGain: 90, - integralGain: 0.58, - derivativeGain: 12, + proportionalGain: 27, + integralGain: 8, + derivativeGain: 40, }; export const motorDisplacements = { leftMotor: { x: 0.058, y: 0, - z: 0.055, + z: 0.03, }, rightMotor: { x: -0.058, y: 0, - z: 0.055, + z: 0.03, }, }; diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts index ae4db79ae..f84857724 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -1,15 +1,33 @@ -import { type Physics, type Controller, EntityFactory, type Entity } from '../../../engine'; +import * as THREE from 'three'; +import { type Physics, type Controller, EntityFactory, type Entity, MeshFactory, type Renderer } from '../../../engine'; import { type EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; +import type { MeshConfig } from './Mesh'; export class ChassisWrapper implements Controller { private physics: Physics; private config: EntityCuboidOptions; private chassis: Entity | null = null; + debugMesh: THREE.Mesh; - constructor(physics: Physics, config: EntityCuboidOptions) { + + constructor(physics: Physics, render: Renderer, config: EntityCuboidOptions) { this.physics = physics; this.config = config; + + + // Debug mesh + const meshConfig :MeshConfig = { + orientation: config.orientation, + width: config.width, + height: config.height, + length: config.length, + color: new THREE.Color(0x00ff00), + debug: true, + }; + + this.debugMesh = MeshFactory.addCuboid(meshConfig); + render.add(this.debugMesh); } async start() { @@ -22,4 +40,12 @@ export class ChassisWrapper implements Controller { } return this.chassis; } + + update() { + const chassisEntity = this.getEntity(); + this.debugMesh.position.copy(chassisEntity.getPosition() as THREE.Vector3); + this.debugMesh.quaternion.copy( + chassisEntity.getRotation() as THREE.Quaternion, + ); + } } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index d43da175b..0f064dd1c 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -23,7 +23,6 @@ export class Mesh implements Controller { chassis: Entity | null = null; render: Renderer; meshConfig: MeshConfig; - debugMesh: THREE.Mesh; chassisWrapper: ChassisWrapper; mesh: GLTF | null = null; @@ -33,15 +32,13 @@ export class Mesh implements Controller { meshConfig: MeshConfig, ) { this.chassisWrapper = chassisWrapper; - this.debugMesh = MeshFactory.addCuboid(meshConfig); this.meshConfig = meshConfig; this.render = render; - render.add(this.debugMesh); } async start(): Promise { this.mesh = await Renderer.loadGTLF( - 'https://keen-longma-3c1be1.netlify.app/4_no_wheels.gltf', + 'https://keen-longma-3c1be1.netlify.app/6_remove_wheels.gltf', ); const box = new THREE.Box3() @@ -61,20 +58,12 @@ export class Mesh implements Controller { update() { const chassisEntity = this.chassisWrapper.getEntity(); - if (this.meshConfig.debug || this.mesh === null) { - this.debugMesh.visible = true; - } else { - this.debugMesh.visible = false; - } - this.debugMesh.position.copy(chassisEntity.getPosition() as THREE.Vector3); - this.debugMesh.quaternion.copy( - chassisEntity.getRotation() as THREE.Quaternion, - ); + const chassisPosition = chassisEntity.getPosition() as THREE.Vector3; - this.mesh?.scene.position.copy( - chassisEntity.getPosition() as THREE.Vector3, - ); + chassisPosition.y -= 0.02 / 2; + + this.mesh?.scene.position.copy(chassisPosition); this.mesh?.scene.quaternion.copy( chassisEntity.getRotation() as THREE.Quaternion, ); diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index 0b87334fd..e278527ca 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -1,11 +1,15 @@ -import { type FrameTimingInfo, type Controller, type Physics } from '../../../engine'; +import { type Controller, type Physics, Renderer } from '../../../engine'; import { type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; import { VectorPidController } from '../feedback_control/PidController'; -import type * as THREE from 'three'; +import * as THREE from 'three'; import { type ChassisWrapper } from './Chassis'; import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; +// eslint-disable-next-line import/extensions +import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; + +type WheelSide = 'left' | 'right'; type MotorConfig = { pid: { @@ -20,16 +24,22 @@ export class Motor implements Controller { displacementVector: THREE.Vector3; motorVelocity: number; callbackHandler = new CallbackHandler(); + wheelSide: WheelSide; physics: Physics; + render: Renderer; chassisWrapper: ChassisWrapper; - constructor(chassisWrapper: ChassisWrapper, physics: Physics, displacement: SimpleVector, config: MotorConfig) { + mesh: GLTF | null = null; + + constructor(chassisWrapper: ChassisWrapper, physics: Physics, renderer: Renderer, displacement: SimpleVector, config: MotorConfig) { this.chassisWrapper = chassisWrapper; this.physics = physics; this.pid = new VectorPidController(config.pid); this.displacementVector = vec3(displacement); this.motorVelocity = 0; + this.render = renderer; + this.wheelSide = displacement.x > 0 ? 'right' : 'left'; } setVelocity(velocity: number) { @@ -44,6 +54,27 @@ export class Motor implements Controller { }, distance / speed * 1000); } + async start(): Promise { + this.mesh = await Renderer.loadGTLF( + 'https://keen-longma-3c1be1.netlify.app/6_wheel.gltf', + ); + + const box = new THREE.Box3() + .setFromObject(this.mesh.scene); + + const size = new THREE.Vector3(); + + box.getSize(size); + + const scaleX = 0.028 / size.x; + const scaleY = 0.055 / size.y; + const scaleZ = 0.055 / size.z; + + this.mesh.scene.scale.set(scaleX, scaleY, scaleZ); + + this.render.add(this.mesh.scene); + } + fixedUpdate(_: number): void { const chassis = this.chassisWrapper.getEntity(); const targetMotorVelocity = vec3({ @@ -69,8 +100,22 @@ export class Motor implements Controller { chassis.applyImpulse(impulse, motorGlobalPosition); } - update(deltaTime: FrameTimingInfo): void { + + update(deltaTime): void { this.callbackHandler.checkCallbacks(deltaTime); + const chassisEntity = this.chassisWrapper.getEntity(); + const wheelPosition = chassisEntity.worldTranslation(this.displacementVector.clone()) as THREE.Vector3; + wheelPosition.y = 0.055 / 2; + this.mesh?.scene.position.copy( + wheelPosition, + ); + this.mesh?.scene.quaternion.copy( + chassisEntity.getRotation() as THREE.Quaternion, + ); + this.mesh?.scene.rotateX(90 / 180 * Math.PI); + if (this.wheelSide === 'left') { + this.mesh?.scene.rotateZ(Math.PI); + } } toString() { diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index aa3ce97ea..1c290c119 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -1,4 +1,4 @@ -import { type Controller, type Physics, type Renderer } from '../../../engine'; +import { type Renderer, type Controller, type Physics } from '../../../engine'; import { type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; import { NumberPidController } from '../feedback_control/PidController'; @@ -21,6 +21,7 @@ export class Wheel implements Controller { displacementVector: THREE.Vector3; downVector: THREE.Vector3; physics: Physics; + render: Renderer; arrowHelper: THREE.ArrowHelper; constructor( @@ -44,6 +45,7 @@ export class Wheel implements Controller { this.arrowHelper.visible = config.debug; this.arrowHelper.setColor('red'); render.add(this.arrowHelper); + this.render = render; } fixedUpdate(timestep: number): void { @@ -59,7 +61,7 @@ export class Wheel implements Controller { const result = this.physics.castRay( globalDisplacement, globalDownDirection, - 0.1, + 0.5, ); // Wheels are not touching the ground @@ -68,6 +70,8 @@ export class Wheel implements Controller { } const { distance: wheelDistance, normal } = result; + + const error = this.pid.calculate(wheelDistance, 0.03 + 0.095 / 2); const force = vec3(normal) diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts index 66cce7f2c..be63ea3ab 100644 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts @@ -65,7 +65,7 @@ export const createDefaultEv3 = ( render: Renderer, config: Ev3Config, ): DefaultEv3 => { - const chassis = new ChassisWrapper(physics, config.chassis); + const chassis = new ChassisWrapper(physics, render, config.chassis); const mesh = new Mesh(chassis, render, config.mesh); const wheelPidConfig = { @@ -112,6 +112,7 @@ export const createDefaultEv3 = ( const leftMotor = new Motor( chassis, physics, + render, config.motor.displacements.leftMotor, motorPidConfig, ); @@ -119,6 +120,7 @@ export const createDefaultEv3 = ( const rightMotor = new Motor( chassis, physics, + render, config.motor.displacements.rightMotor, motorPidConfig, ); diff --git a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx index 2e312eff2..4090922ab 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx @@ -36,7 +36,7 @@ export const WheelPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { From aef9f245ad7700e8a604508df058787b58ac18a5 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 1 Mar 2024 08:48:34 +0000 Subject: [PATCH 41/93] Add a bunch of test --- .../controllers/ev3/components/Chassis.ts | 14 +- .../robot_simulation/engine/Core/Events.ts | 3 +- .../robot_simulation/engine/Physics.ts | 1 - .../robot_simulation/engine/Render/Camera.ts | 2 +- .../engine/__tests__/Core/Events.ts | 85 ++++++++ .../engine/__tests__/Core/RobotConsole.ts | 46 +++++ .../engine/__tests__/{ => Core}/Timer.ts | 186 +++++++++--------- .../engine/__tests__/Math/Convert.ts | 38 ++++ .../engine/__tests__/Render/Camera.ts | 41 ++++ 9 files changed, 316 insertions(+), 100 deletions(-) create mode 100644 src/bundles/robot_simulation/engine/__tests__/Core/Events.ts create mode 100644 src/bundles/robot_simulation/engine/__tests__/Core/RobotConsole.ts rename src/bundles/robot_simulation/engine/__tests__/{ => Core}/Timer.ts (94%) create mode 100644 src/bundles/robot_simulation/engine/__tests__/Math/Convert.ts create mode 100644 src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts index f84857724..529682c37 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -1,5 +1,13 @@ import * as THREE from 'three'; -import { type Physics, type Controller, EntityFactory, type Entity, MeshFactory, type Renderer } from '../../../engine'; + +import { + type Physics, + type Controller, + EntityFactory, + type Entity, + MeshFactory, + type Renderer, +} from '../../../engine'; import { type EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; import type { MeshConfig } from './Mesh'; @@ -10,14 +18,12 @@ export class ChassisWrapper implements Controller { private chassis: Entity | null = null; debugMesh: THREE.Mesh; - constructor(physics: Physics, render: Renderer, config: EntityCuboidOptions) { this.physics = physics; this.config = config; - // Debug mesh - const meshConfig :MeshConfig = { + const meshConfig: MeshConfig = { orientation: config.orientation, width: config.width, height: config.height, diff --git a/src/bundles/robot_simulation/engine/Core/Events.ts b/src/bundles/robot_simulation/engine/Core/Events.ts index c3b160f80..c1952ce4d 100644 --- a/src/bundles/robot_simulation/engine/Core/Events.ts +++ b/src/bundles/robot_simulation/engine/Core/Events.ts @@ -33,7 +33,8 @@ export class TypedEventTarget> { if (!this.listeners[type]) { this.listeners[type] = []; } - this.listeners[type]?.push(listener); + // Non-null assertion is safe because we just checked if it's undefined + this.listeners[type]!.push(listener); } public dispatchEvent( diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 3b65cc508..bc8b213c9 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -43,7 +43,6 @@ export class Physics extends TypedEventTarget { async start() { await rapier.init(); - console.log('Physics started'); this.RAPIER = rapier; diff --git a/src/bundles/robot_simulation/engine/Render/Camera.ts b/src/bundles/robot_simulation/engine/Render/Camera.ts index 1756bd1e0..04d8cb60c 100644 --- a/src/bundles/robot_simulation/engine/Render/Camera.ts +++ b/src/bundles/robot_simulation/engine/Render/Camera.ts @@ -30,7 +30,7 @@ export function getCamera(cameraOptions: CameraOptions): THREE.Camera { default: { // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars const _: never = cameraOptions; - throw new Error(`Unknown camera type: ${cameraOptions}`); + throw new Error('Unknown camera type'); } } } diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Events.ts b/src/bundles/robot_simulation/engine/__tests__/Core/Events.ts new file mode 100644 index 000000000..489f99e4a --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Core/Events.ts @@ -0,0 +1,85 @@ +import { TypedEventTarget } from "../../Core/Events"; + +class StringEvent extends Event { + public data: string; + + constructor(type: string, data: string) { + super(type); + this.data = data; + } +} + +class NumberEvent extends Event { + public data: number; + + constructor(type: string, data: number) { + super(type); + this.data = data; + } +} + +interface EventMap { + event1: StringEvent; + event2: NumberEvent; +} + +describe("TypedEventTarget", () => { + let eventTarget: TypedEventTarget; + + beforeEach(() => { + eventTarget = new TypedEventTarget(); + }); + + test("addEventListener adds a listener for the specified event type", () => { + const listener = jest.fn(); + eventTarget.addEventListener("event1", listener); + const event = new StringEvent("event1", "Hello"); + eventTarget.dispatchEvent("event1", event); + expect(listener).toHaveBeenCalledWith(event); + }); + + test("addEventListener adds multiple listeners for the same event type", () => { + const listener1 = jest.fn(); + const listener2 = jest.fn(); + eventTarget.addEventListener("event1", listener1); + eventTarget.addEventListener("event1", listener2); + const event = new StringEvent("event1", "Hello"); + eventTarget.dispatchEvent("event1", event); + expect(listener1).toHaveBeenCalledWith(event); + expect(listener2).toHaveBeenCalledWith(event); + }); + + test("addEventListener adds listeners for different event types", () => { + const listener1 = jest.fn(); + const listener2 = jest.fn(); + eventTarget.addEventListener("event1", listener1); + eventTarget.addEventListener("event2", listener2); + const event1 = new StringEvent("event1", "Hello"); + const event2 = new NumberEvent("event2", 42); + eventTarget.dispatchEvent("event1", event1); + eventTarget.dispatchEvent("event2", event2); + expect(listener1).toHaveBeenCalledWith(event1); + expect(listener2).toHaveBeenCalledWith(event2); + }); + + test("dispatchEvent dispatches the event to the registered listeners", () => { + const listener1 = jest.fn(); + const listener2 = jest.fn(); + eventTarget.addEventListener("event1", listener1); + eventTarget.addEventListener("event2", listener2); + const event1 = new StringEvent("event1", "Hello"); + const event2 = new NumberEvent("event2", 42); + eventTarget.dispatchEvent("event1", event1); + eventTarget.dispatchEvent("event2", event2); + expect(listener1).toHaveBeenCalledWith(event1); + expect(listener2).toHaveBeenCalledWith(event2); + }); + + test("dispatchEvent returns true if there are listeners for the event type", () => { + const listener = jest.fn(); + eventTarget.addEventListener("event1", listener); + const event = new StringEvent("event1", "Hello"); + const result = eventTarget.dispatchEvent("event1", event); + expect(result).toBe(true); + }); +}); diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/RobotConsole.ts b/src/bundles/robot_simulation/engine/__tests__/Core/RobotConsole.ts new file mode 100644 index 000000000..765e6a2de --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Core/RobotConsole.ts @@ -0,0 +1,46 @@ +import { RobotConsole } from '../../Core/RobotConsole'; // Adjust the import path as necessary + +describe('RobotConsole', () => { + let robotConsole: RobotConsole; + + beforeEach(() => { + robotConsole = new RobotConsole(); + }); + + test('initially has an empty log array', () => { + expect(robotConsole.getLogs()).toEqual([]); + }); + + test('adds a log entry with correct structure', () => { + const message = 'Test message'; + const level = 'error'; + robotConsole.log(message, level); + + const logs = robotConsole.getLogs(); + expect(logs.length).toBe(1); + expect(logs[0]).toHaveProperty('message', message); + expect(logs[0]).toHaveProperty('level', level); + expect(logs[0]).toHaveProperty('timestamp'); + expect(typeof logs[0].timestamp).toBe('number'); + }); + + test('adds multiple log entries', () => { + robotConsole.log('First message', 'source'); + robotConsole.log('Second message', 'error'); + + const logs = robotConsole.getLogs(); + expect(logs.length).toBe(2); + expect(logs[0].message).toBe('First message'); + expect(logs[1].message).toBe('Second message'); + }); + + test('log entries have a timestamp in increasing order', () => { + robotConsole.log('First message', 'source'); + const firstTimestamp = Date.now(); + robotConsole.log('Second message', 'error'); + + const logs = robotConsole.getLogs(); + expect(logs[0].timestamp).toBeLessThanOrEqual(firstTimestamp); + expect(logs[1].timestamp).toBeGreaterThanOrEqual(logs[0].timestamp); + }); +}); diff --git a/src/bundles/robot_simulation/engine/__tests__/Timer.ts b/src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts similarity index 94% rename from src/bundles/robot_simulation/engine/__tests__/Timer.ts rename to src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts index e8630fb50..6050420cc 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Timer.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts @@ -1,93 +1,93 @@ -import { Timer } from '../Core/Timer'; // Adjust the import path as per your project structure - -describe('Timer', () => { - let timer; - let mockTimestamp; - - beforeEach(() => { - timer = new Timer(); - mockTimestamp = 1000; // Initial mock timestamp in milliseconds - }); - - describe('pause method', () => { - it('should not pause if the timer has not started', () => { - timer.pause(); - expect(timer).toHaveProperty('_isRunning', false); - expect(timer).toHaveProperty('_pausedAt', null); - }); - - it('should pause the running timer', () => { - timer.step(mockTimestamp); // Start the timer - timer.pause(); - expect(timer).toHaveProperty('_isRunning', false); - expect(timer).toHaveProperty('_pausedAt', mockTimestamp); - }); - }); - - describe('step method', () => { - it('should start the timer if not already started', () => { - timer.step(mockTimestamp); - expect(timer).toHaveProperty('_startTime', mockTimestamp); - }); - - it('should update the current time', () => { - timer.step(mockTimestamp); - expect(timer).toHaveProperty('_currentTime', mockTimestamp); - }); - - it('should accumulate paused time', () => { - timer.step(mockTimestamp); // Start the timer - timer.pause(); - const resumeTimestamp = 2000; - timer.step(resumeTimestamp); - expect(timer).toHaveProperty('_timeSpentPaused', resumeTimestamp - mockTimestamp); - }); - - it('should calculate the correct frame duration', () => { - timer.step(mockTimestamp); - const newTimestamp = 1500; - timer.step(newTimestamp); - expect(timer).toHaveProperty('_frameDuration', 500); - }); - - it('should resume the timer if it was paused', () => { - timer.step(mockTimestamp); - timer.pause(); - const resumeTimestamp = 2000; - timer.step(resumeTimestamp); - expect(timer).toHaveProperty('_isRunning', true); - }); - - it('should return correct FrameTimingInfo', () => { - const frameTimingInfo = timer.step(mockTimestamp); - expect(frameTimingInfo).toMatchObject({ - elapsedTimeReal: expect.any(Number), - elapsedTimeSimulated: expect.any(Number), - frameDuration: expect.any(Number), - framesPerSecond: expect.any(Number), - }); - }); - - it('should correctly handle time after pause and resume', () => { - const startTimestamp = 1000; - const pauseTimestamp = 2000; - const resumeTimestamp = 3000; - const finalTimestamp = 4000; - - timer.step(startTimestamp); // Start the timer - timer.pause(); - - timer.step(pauseTimestamp); // Paused at 2000ms - timer.step(resumeTimestamp); // Resumed at 3000ms - const frameTimingInfo = timer.step(finalTimestamp); // Stepped at 4000ms - - // Total elapsed time should be 3000ms (4000 - 1000) - // Time spent paused is 1000ms (3000 - 2000) - // Therefore, elapsed simulated time should be 2000ms (3000 - 1000) - expect(frameTimingInfo.elapsedTimeSimulated).toBe(2000); - expect(frameTimingInfo.elapsedTimeReal).toBe(3000); - expect(frameTimingInfo.frameDuration).toBe(1000); // Time since last step - expect(frameTimingInfo.framesPerSecond).toBeCloseTo(1); // 1000ms frame duration - }); - }); -}); +import { Timer } from '../../Core/Timer'; // Adjust the import path as per your project structure + +describe('Timer', () => { + let timer; + let mockTimestamp; + + beforeEach(() => { + timer = new Timer(); + mockTimestamp = 1000; // Initial mock timestamp in milliseconds + }); + + describe('pause method', () => { + it('should not pause if the timer has not started', () => { + timer.pause(); + expect(timer).toHaveProperty('_isRunning', false); + expect(timer).toHaveProperty('_pausedAt', null); + }); + + it('should pause the running timer', () => { + timer.step(mockTimestamp); // Start the timer + timer.pause(); + expect(timer).toHaveProperty('_isRunning', false); + expect(timer).toHaveProperty('_pausedAt', mockTimestamp); + }); + }); + + describe('step method', () => { + it('should start the timer if not already started', () => { + timer.step(mockTimestamp); + expect(timer).toHaveProperty('_startTime', mockTimestamp); + }); + + it('should update the current time', () => { + timer.step(mockTimestamp); + expect(timer).toHaveProperty('_currentTime', mockTimestamp); + }); + + it('should accumulate paused time', () => { + timer.step(mockTimestamp); // Start the timer + timer.pause(); + const resumeTimestamp = 2000; + timer.step(resumeTimestamp); + expect(timer).toHaveProperty('_timeSpentPaused', resumeTimestamp - mockTimestamp); + }); + + it('should calculate the correct frame duration', () => { + timer.step(mockTimestamp); + const newTimestamp = 1500; + timer.step(newTimestamp); + expect(timer).toHaveProperty('_frameDuration', 500); + }); + + it('should resume the timer if it was paused', () => { + timer.step(mockTimestamp); + timer.pause(); + const resumeTimestamp = 2000; + timer.step(resumeTimestamp); + expect(timer).toHaveProperty('_isRunning', true); + }); + + it('should return correct FrameTimingInfo', () => { + const frameTimingInfo = timer.step(mockTimestamp); + expect(frameTimingInfo).toMatchObject({ + elapsedTimeReal: expect.any(Number), + elapsedTimeSimulated: expect.any(Number), + frameDuration: expect.any(Number), + framesPerSecond: expect.any(Number), + }); + }); + + it('should correctly handle time after pause and resume', () => { + const startTimestamp = 1000; + const pauseTimestamp = 2000; + const resumeTimestamp = 3000; + const finalTimestamp = 4000; + + timer.step(startTimestamp); // Start the timer + timer.pause(); + + timer.step(pauseTimestamp); // Paused at 2000ms + timer.step(resumeTimestamp); // Resumed at 3000ms + const frameTimingInfo = timer.step(finalTimestamp); // Stepped at 4000ms + + // Total elapsed time should be 3000ms (4000 - 1000) + // Time spent paused is 1000ms (3000 - 2000) + // Therefore, elapsed simulated time should be 2000ms (3000 - 1000) + expect(frameTimingInfo.elapsedTimeSimulated).toBe(2000); + expect(frameTimingInfo.elapsedTimeReal).toBe(3000); + expect(frameTimingInfo.frameDuration).toBe(1000); // Time since last step + expect(frameTimingInfo.framesPerSecond).toBeCloseTo(1); // 1000ms frame duration + }); + }); +}); diff --git a/src/bundles/robot_simulation/engine/__tests__/Math/Convert.ts b/src/bundles/robot_simulation/engine/__tests__/Math/Convert.ts new file mode 100644 index 000000000..566cedfc1 --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Math/Convert.ts @@ -0,0 +1,38 @@ +import { Quaternion, Vector3, Euler } from 'three'; +import { quat, vec3, euler } from '../../Math/Convert'; // Adjust the import path as necessary + +describe('Three.js utility functions', () => { + describe('quat function', () => { + it('creates a Quaternion with the specified x, y, z, w values', () => { + const x = 1, y = 2, z = 3, w = 4; + const quaternion = quat({ x, y, z, w }); + expect(quaternion).toBeInstanceOf(Quaternion); + expect(quaternion.x).toBe(x); + expect(quaternion.y).toBe(y); + expect(quaternion.z).toBe(z); + expect(quaternion.w).toBe(w); + }); + }); + + describe('vec3 function', () => { + it('creates a Vector3 with the specified x, y, z values', () => { + const x = 5, y = 6, z = 7; + const vector = vec3({ x, y, z }); + expect(vector).toBeInstanceOf(Vector3); + expect(vector.x).toBe(x); + expect(vector.y).toBe(y); + expect(vector.z).toBe(z); + }); + }); + + describe('euler function', () => { + it('creates an Euler with the specified x, y, z values', () => { + const x = 0.1, y = 0.2, z = 0.3; + const eulerInstance = euler({ x, y, z }); + expect(eulerInstance).toBeInstanceOf(Euler); + expect(eulerInstance.x).toBeCloseTo(x); + expect(eulerInstance.y).toBeCloseTo(y); + expect(eulerInstance.z).toBeCloseTo(z); + }); + }); +}); diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts b/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts new file mode 100644 index 000000000..9b346b89e --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts @@ -0,0 +1,41 @@ +import * as THREE from 'three'; + +import { CameraOptions, getCamera } from '../../Render/Camera'; + + +describe('getCamera', () => { + test('returns a PerspectiveCamera when type is "perspective"', () => { + const cameraOptions: CameraOptions = { + type: 'perspective', + fov: 45, + aspect: 16 / 9, + near: 0.1, + far: 1000, + }; + + + const camera = getCamera(cameraOptions); + expect(camera).toBeInstanceOf(THREE.PerspectiveCamera); + const perspectiveCamera = camera as THREE.PerspectiveCamera; + expect(perspectiveCamera.fov).toBe(cameraOptions.fov); + expect(perspectiveCamera.aspect).toBe(cameraOptions.aspect); + expect(perspectiveCamera.near).toBe(cameraOptions.near); + expect(perspectiveCamera.far).toBe(cameraOptions.far); + }); + + test('returns an OrthographicCamera when type is "orthographic"', () => { + const cameraOptions: CameraOptions = { + type: 'orthographic', + }; + const camera = getCamera(cameraOptions); + expect(camera).toBeInstanceOf(THREE.OrthographicCamera); + }); + + test('throws an error when type is unknown', () => { + const cameraOptions: CameraOptions = { + // @ts-expect-error + type: 'unknown', + }; + expect(() => getCamera(cameraOptions)).toThrowError('Unknown camera type'); + }); +}); \ No newline at end of file From 1483717c43433609af8b589cccc0c872fe7b4361 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 1 Mar 2024 10:10:56 +0000 Subject: [PATCH 42/93] Did partial tuning of the motor --- .../robot_simulation/controllers/ev3/components/Motor.ts | 4 ++-- src/bundles/robot_simulation/controllers/program/Program.ts | 2 +- src/bundles/robot_simulation/ev3_functions.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index e278527ca..d3e7e7bf2 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -47,8 +47,8 @@ export class Motor implements Controller { } setSpeedDistance(speed: number, distance: number) { - // TODO: Tune this - this.motorVelocity = speed * 500; + this.motorVelocity = speed; + this.callbackHandler.addCallback(() => { this.motorVelocity = 0; }, distance / speed * 1000); diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index 768b36ab0..bca7dd6b8 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -53,7 +53,7 @@ export class Program implements Controller { } // steps per tick - for (let i = 0; i < 10; i++) { + for (let i = 0; i < 11; i++) { const result = this.iterator.next(); } } catch (e) { diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 480cd55f5..0f4e24777 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -46,7 +46,7 @@ export function ev3_runToRelativePosition( return; } - const wheelDiameter = 0.2; + const wheelDiameter = 0.055; const speedInMetersPerSecond = (speed / 360) * Math.PI * wheelDiameter; From 972b4d4a5fe6434c8f580fb848baba30c7ec0f36 Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 1 Mar 2024 13:28:11 +0000 Subject: [PATCH 43/93] Change the way the timing is done --- .../controllers/ev3/components/Mesh.ts | 1 - .../controllers/ev3/components/Motor.ts | 2 +- .../controllers/ev3/components/Wheel.ts | 6 ++- .../controllers/ev3/sensor/ColorSensor.ts | 7 ++- .../ev3/sensor/UltrasonicSensor.ts | 2 +- .../engine/Core/Controller.ts | 26 +++++------ .../robot_simulation/engine/Core/Events.ts | 11 ----- .../robot_simulation/engine/Physics.ts | 46 ++++++++++++++++--- src/bundles/robot_simulation/engine/World.ts | 16 +++---- .../engine/__tests__/Physics.ts | 2 +- 10 files changed, 71 insertions(+), 48 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index 0f064dd1c..a5f96131d 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -1,7 +1,6 @@ import { type Entity, type Controller, - MeshFactory, Renderer, } from '../../../engine'; import * as THREE from 'three'; diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index d3e7e7bf2..cc908009d 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -75,7 +75,7 @@ export class Motor implements Controller { this.render.add(this.mesh.scene); } - fixedUpdate(_: number): void { + fixedUpdate(): void { const chassis = this.chassisWrapper.getEntity(); const targetMotorVelocity = vec3({ x: 0, diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index 1c290c119..fef6be33a 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -4,6 +4,7 @@ import { vec3 } from '../../../engine/Math/Convert'; import { NumberPidController } from '../feedback_control/PidController'; import * as THREE from 'three'; import { type ChassisWrapper } from './Chassis'; +import type { PhysicsTimingInfo } from '../../../engine/Physics'; type WheelConfig = { pid: { @@ -48,7 +49,8 @@ export class Wheel implements Controller { this.render = render; } - fixedUpdate(timestep: number): void { + fixedUpdate(timingInfo: PhysicsTimingInfo): void { + const { timeStep } = timingInfo; const chassis = this.chassisWrapper.getEntity(); const globalDisplacement = chassis.worldTranslation( @@ -76,7 +78,7 @@ export class Wheel implements Controller { const force = vec3(normal) .normalize() - .multiplyScalar(error * chassis.getMass() * timestep); + .multiplyScalar(error * chassis.getMass() * timeStep); chassis.applyImpulse(force, globalDisplacement); diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts index e0770cb28..98a6ebf0d 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -4,6 +4,7 @@ import { type Sensor } from './types'; import { type ChassisWrapper } from '../components/Chassis'; import { type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; +import type { PhysicsTimingInfo } from '../../../engine/Physics'; type Color = { r:number, g:number, b:number }; @@ -59,8 +60,10 @@ export class ColorSensor implements Sensor { return this.colorSensed; } - fixedUpdate(fixedDeltaTime: number) { - this.accumulator += fixedDeltaTime; + fixedUpdate(timingInfo: PhysicsTimingInfo) { + const { timeStep } = timingInfo; + + this.accumulator += timeStep; if (this.accumulator < 1) { return; } diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts index b41ec19cc..789039dcd 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -42,7 +42,7 @@ export class UltrasonicSensor implements Sensor { return this.distanceSensed; } - fixedUpdate(_: number): void { + fixedUpdate(): void { const chassis = this.chassisWrapper.getEntity(); const globalDisplacement = chassis.worldTranslation( this.displacement.clone(), diff --git a/src/bundles/robot_simulation/engine/Core/Controller.ts b/src/bundles/robot_simulation/engine/Core/Controller.ts index c05e6d99e..b7135127c 100644 --- a/src/bundles/robot_simulation/engine/Core/Controller.ts +++ b/src/bundles/robot_simulation/engine/Core/Controller.ts @@ -1,9 +1,9 @@ -import { type FrameTimingInfo } from './Timer'; +import { type PhysicsTimingInfo } from '../Physics'; export interface Controller { start?(): Promise | void; - update?(deltaTime: FrameTimingInfo): void; - fixedUpdate?(fixedDeltaTime: number): void; + update?(timingInfo: PhysicsTimingInfo): void; + fixedUpdate?(timingInfo: PhysicsTimingInfo): void; onDestroy?(): void; } @@ -31,19 +31,19 @@ implements Controller { ); } - update(deltaTime: FrameTimingInfo): void { - this.callbacks?.update?.(deltaTime); + update(timingInfo: PhysicsTimingInfo): void { + this.callbacks?.update?.(timingInfo); Object.values(this.map) .forEach((controller) => { - controller.update?.(deltaTime); + controller.update?.(timingInfo); }); } - fixedUpdate(fixedDeltaTime: number): void { - this.callbacks?.fixedUpdate?.(fixedDeltaTime); + fixedUpdate(timingInfo: PhysicsTimingInfo): void { + this.callbacks?.fixedUpdate?.(timingInfo); Object.values(this.map) .forEach((controller) => { - controller.fixedUpdate?.(fixedDeltaTime); + controller.fixedUpdate?.(timingInfo); }); } @@ -69,15 +69,15 @@ export class ControllerGroup implements Controller { }); } - update(deltaTime: FrameTimingInfo): void { + update(timingInfo: PhysicsTimingInfo): void { this.controllers.forEach((controller) => { - controller.update?.(deltaTime); + controller.update?.(timingInfo); }); } - fixedUpdate(fixedDeltaTime: number): void { + fixedUpdate(timingInfo: PhysicsTimingInfo): void { this.controllers.forEach((controller) => { - controller.fixedUpdate?.(fixedDeltaTime); + controller.fixedUpdate?.(timingInfo); }); } diff --git a/src/bundles/robot_simulation/engine/Core/Events.ts b/src/bundles/robot_simulation/engine/Core/Events.ts index c1952ce4d..f00c0c625 100644 --- a/src/bundles/robot_simulation/engine/Core/Events.ts +++ b/src/bundles/robot_simulation/engine/Core/Events.ts @@ -1,14 +1,3 @@ -import { type FrameTimingInfo } from './Timer'; - -export class TimeStampedEvent extends Event { - frameTimingInfo: FrameTimingInfo; - - constructor(type: string, frameTimingInfo: FrameTimingInfo) { - super(type); - this.frameTimingInfo = frameTimingInfo; - } -} - export type Listener = ( event: EventMap[EventName] ) => void | Promise; diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index bc8b213c9..3dbb536fe 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -4,7 +4,22 @@ import type * as THREE from 'three'; import { type SimpleVector } from './Math/Vector'; import { type FrameTimingInfo } from './Core/Timer'; -import { TimeStampedEvent, TypedEventTarget } from './Core/Events'; +import { TypedEventTarget } from './Core/Events'; + +export type PhysicsTimingInfo = FrameTimingInfo & { + stepCount: number; + timeStep: number; + physicsTimeStamp: number; +}; + +export class TimeStampedEvent extends Event { + frameTimingInfo: PhysicsTimingInfo; + + constructor(type: string, frameTimingInfo: PhysicsTimingInfo) { + super(type); + this.frameTimingInfo = frameTimingInfo; + } +} type PhysicsEventMap = { beforePhysicsUpdate: TimeStampedEvent; @@ -24,6 +39,7 @@ type InitializedInternals = { initialized: true; world: rapier.World; accumulator: number; + stepCount: number; }; type PhysicsInternals = NotInitializedInternals | InitializedInternals; @@ -53,6 +69,7 @@ export class Physics extends TypedEventTarget { initialized: true, world, accumulator: 0, + stepCount: 0, }; } @@ -111,27 +128,42 @@ export class Physics extends TypedEventTarget { }; } - step(timing: FrameTimingInfo): void { + step(timing: FrameTimingInfo): PhysicsTimingInfo { if (this.internals.initialized === false) { throw Error("Physics engine hasn't been initialized yet"); } const maxFrameTime = 0.05; const frameDuration = timing.frameDuration / 1000; - this.internals.accumulator += Math.min(frameDuration, maxFrameTime); + this.internals.accumulator -= Math.min(frameDuration, maxFrameTime); - while (this.internals.accumulator >= this.configuration.timestep) { + const currentPhysicsTimingInfo = { + ...timing, + stepCount: this.internals.stepCount, + timeStep: this.configuration.timestep, + physicsTimeStamp: this.internals.stepCount * this.configuration.timestep, + }; + + while (this.internals.accumulator <= 0) { this.dispatchEvent( 'beforePhysicsUpdate', - new TimeStampedEvent('beforePhysicsUpdate', timing), + new TimeStampedEvent('beforePhysicsUpdate', currentPhysicsTimingInfo), ); + this.internals.world.step(); + + this.internals.stepCount += 1; + currentPhysicsTimingInfo.stepCount = this.internals.stepCount; + currentPhysicsTimingInfo.physicsTimeStamp += this.configuration.timestep; + this.dispatchEvent( 'afterPhysicsUpdate', - new TimeStampedEvent('afterPhysicsUpdate', timing), + new TimeStampedEvent('afterPhysicsUpdate', currentPhysicsTimingInfo), ); - this.internals.accumulator -= this.configuration.timestep; + this.internals.accumulator += this.configuration.timestep; } + + return currentPhysicsTimingInfo; } } diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/engine/World.ts index 11e841090..93a5c7dc5 100644 --- a/src/bundles/robot_simulation/engine/World.ts +++ b/src/bundles/robot_simulation/engine/World.ts @@ -1,6 +1,6 @@ import { type Controller, ControllerGroup } from './Core/Controller'; -import { TimeStampedEvent, TypedEventTarget } from './Core/Events'; -import { type Physics } from './Physics'; +import { TypedEventTarget } from './Core/Events'; +import { TimeStampedEvent, type Physics } from './Physics'; import { type Renderer } from './Render/Renderer'; import { type Timer } from './Core/Timer'; @@ -57,9 +57,9 @@ export class World extends TypedEventTarget { }); }); - this.physics.addEventListener('beforePhysicsUpdate', (_) => { + this.physics.addEventListener('beforePhysicsUpdate', (e) => { controllers.forEach((controller) => { - controller.fixedUpdate?.(this.physics.configuration.timestep); + controller.fixedUpdate?.(e.frameTimingInfo); }); }); } @@ -96,19 +96,17 @@ export class World extends TypedEventTarget { try { const frameTimingInfo = this.timer.step(timestamp); - // Update physics - this.physics.step(frameTimingInfo); - + const physicsTimingInfo = this.physics.step(frameTimingInfo); // Update render this.dispatchEvent( 'beforeRender', - new TimeStampedEvent('beforeRender', frameTimingInfo), + new TimeStampedEvent('beforeRender', physicsTimingInfo), ); this.render.step(frameTimingInfo); this.dispatchEvent( 'afterRender', - new TimeStampedEvent('afterRender', frameTimingInfo), + new TimeStampedEvent('afterRender', physicsTimingInfo), ); diff --git a/src/bundles/robot_simulation/engine/__tests__/Physics.ts b/src/bundles/robot_simulation/engine/__tests__/Physics.ts index 0c44b8a92..dd68973d7 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Physics.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Physics.ts @@ -96,7 +96,7 @@ describe('Physics', () => { await physics.start(); // Initialize const frameTimingInfo = { frameDuration: 1000 / 60 }; // 60 FPS physics.step(frameTimingInfo); - expect(physics.internals.world.step).toHaveBeenCalledTimes(1); + expect(physics.internals.world.step).toHaveBeenCalledTimes(2); }); test('castRay throws if not initialized', () => { From 405313b7adc338198666da1c7f39e48f0a8f33e2 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 2 Mar 2024 03:33:40 +0000 Subject: [PATCH 44/93] Make CallbackHandler deterministic --- .../controllers/ev3/components/Wheel.ts | 4 +-- .../controllers/ev3/sensor/ColorSensor.ts | 8 ++--- .../controllers/program/Program.ts | 7 ++-- .../engine/Core/CallbackHandler.ts | 32 +++++++++++-------- .../robot_simulation/engine/Physics.ts | 7 ++-- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index fef6be33a..5b5eb6cf2 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -50,7 +50,7 @@ export class Wheel implements Controller { } fixedUpdate(timingInfo: PhysicsTimingInfo): void { - const { timeStep } = timingInfo; + const { timestep } = timingInfo; const chassis = this.chassisWrapper.getEntity(); const globalDisplacement = chassis.worldTranslation( @@ -78,7 +78,7 @@ export class Wheel implements Controller { const force = vec3(normal) .normalize() - .multiplyScalar(error * chassis.getMass() * timeStep); + .multiplyScalar(error * chassis.getMass() * timestep / 1000); chassis.applyImpulse(force, globalDisplacement); diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts index 98a6ebf0d..e954d8685 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -61,13 +61,13 @@ export class ColorSensor implements Sensor { } fixedUpdate(timingInfo: PhysicsTimingInfo) { - const { timeStep } = timingInfo; + const { timestep } = timingInfo; - this.accumulator += timeStep; - if (this.accumulator < 1) { + this.accumulator += timestep; + if (this.accumulator < 1000) { return; } - this.accumulator -= 1; + this.accumulator -= 1000; this.renderer.render(); diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index bca7dd6b8..37cf18ef1 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -1,9 +1,10 @@ import { type IOptions } from 'js-slang'; import { runECEvaluator } from './evaluate'; import context from 'js-slang/context'; -import { type FrameTimingInfo, type Controller } from '../../engine'; +import { type Controller } from '../../engine'; import { CallbackHandler } from '../../engine/Core/CallbackHandler'; import { ProgramError } from './error'; +import type { PhysicsTimingInfo } from '../../engine/Physics'; type ProgramConfig = { stepsPerTick: number; @@ -42,7 +43,7 @@ export class Program implements Controller { this.iterator = runECEvaluator(this.code, context, options); } - fixedUpdate(_: number) { + fixedUpdate() { try { if (!this.iterator) { throw Error('Program not started'); @@ -62,7 +63,7 @@ export class Program implements Controller { } } - update(frameTiming: FrameTimingInfo): void { + update(frameTiming: PhysicsTimingInfo): void { this.callbackHandler.checkCallbacks(frameTiming); } } diff --git a/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts b/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts index 5687d8918..76e1bae8d 100644 --- a/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts +++ b/src/bundles/robot_simulation/engine/Core/CallbackHandler.ts @@ -1,33 +1,39 @@ -import { type FrameTimingInfo } from './Timer'; +import type { PhysicsTimingInfo } from '../Physics'; export type TimeoutCallback = () => void; +export type CallbackEntry = { callback: TimeoutCallback; delay: number }; export class CallbackHandler { - callbackStore: Array<{ callback: TimeoutCallback; triggerTime: number }>; - currentTime: number; + callbackStore: Array; + currentStepCount: number; constructor() { this.callbackStore = []; - this.currentTime = 0; + this.currentStepCount = 0; } addCallback(callback: TimeoutCallback, delay: number): void { - const triggerTime = this.currentTime + delay; this.callbackStore.push({ callback, - triggerTime, + delay, }); } - checkCallbacks(frameTimingInfo: FrameTimingInfo): void { - this.currentTime = frameTimingInfo.elapsedTimeSimulated; + checkCallbacks(frameTimingInfo: PhysicsTimingInfo): void { + if (frameTimingInfo.stepCount === this.currentStepCount) { + return; + } - this.callbackStore = this.callbackStore.filter((timeoutEvent) => { - if (timeoutEvent.triggerTime <= this.currentTime) { - timeoutEvent.callback(); - return false; // Remove from the array + this.currentStepCount = frameTimingInfo.stepCount; + + this.callbackStore = this.callbackStore.filter((callbackEntry) => { + callbackEntry.delay -= frameTimingInfo.timestep; + + if (callbackEntry.delay <= 0) { + callbackEntry.callback(); + return false; } - return true; // Keep in the array + return true; }); } } diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 3dbb536fe..1c5bc2d51 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -8,8 +8,7 @@ import { TypedEventTarget } from './Core/Events'; export type PhysicsTimingInfo = FrameTimingInfo & { stepCount: number; - timeStep: number; - physicsTimeStamp: number; + timestep: number; }; export class TimeStampedEvent extends Event { @@ -140,8 +139,7 @@ export class Physics extends TypedEventTarget { const currentPhysicsTimingInfo = { ...timing, stepCount: this.internals.stepCount, - timeStep: this.configuration.timestep, - physicsTimeStamp: this.internals.stepCount * this.configuration.timestep, + timestep: this.configuration.timestep * 1000, }; while (this.internals.accumulator <= 0) { @@ -154,7 +152,6 @@ export class Physics extends TypedEventTarget { this.internals.stepCount += 1; currentPhysicsTimingInfo.stepCount = this.internals.stepCount; - currentPhysicsTimingInfo.physicsTimeStamp += this.configuration.timestep; this.dispatchEvent( 'afterPhysicsUpdate', From 6fe3315aadad5a86146e1d7fedf366f46d6cb858 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 2 Mar 2024 06:39:49 +0000 Subject: [PATCH 45/93] Make wall yellow --- src/bundles/robot_simulation/controllers/environment/Wall.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundles/robot_simulation/controllers/environment/Wall.ts b/src/bundles/robot_simulation/controllers/environment/Wall.ts index 81a746614..a20d6217e 100644 --- a/src/bundles/robot_simulation/controllers/environment/Wall.ts +++ b/src/bundles/robot_simulation/controllers/environment/Wall.ts @@ -23,7 +23,7 @@ export const wallConfig: EntityCuboidOptions & RenderCuboidOptions = { height: 2, width: 1, length: 0.1, - color: new THREE.Color('white'), + color: new THREE.Color('yellow'), type: 'fixed', }; From 5dbf490a53cd147680660336edf176fbb3f331e4 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 4 Mar 2024 05:13:13 +0000 Subject: [PATCH 46/93] Fix the program identifier bug and callback controller determinism --- .../controllers/ev3/components/Motor.ts | 21 +++-- .../controllers/program/Program.ts | 10 +- .../engine/Core/Controller.ts | 1 + .../engine/__tests__/Core/CallbackHandler.ts | 92 +++++++++++++++++++ src/bundles/robot_simulation/ev3_functions.ts | 23 +++-- src/jest.config.js | 3 + src/jest.polyfills.js | 6 ++ yarn.lock | 6 +- 8 files changed, 141 insertions(+), 21 deletions(-) create mode 100644 src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts create mode 100644 src/jest.polyfills.js diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index cc908009d..839e54a4b 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -8,6 +8,7 @@ import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; // eslint-disable-next-line import/extensions import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import type { PhysicsTimingInfo } from '../../../engine/Physics'; type WheelSide = 'left' | 'right'; @@ -29,6 +30,7 @@ export class Motor implements Controller { physics: Physics; render: Renderer; chassisWrapper: ChassisWrapper; + meshRotation: number; mesh: GLTF | null = null; @@ -40,6 +42,7 @@ export class Motor implements Controller { this.motorVelocity = 0; this.render = renderer; this.wheelSide = displacement.x > 0 ? 'right' : 'left'; + this.meshRotation = 0; } setVelocity(velocity: number) { @@ -67,8 +70,8 @@ export class Motor implements Controller { box.getSize(size); const scaleX = 0.028 / size.x; - const scaleY = 0.055 / size.y; - const scaleZ = 0.055 / size.z; + const scaleY = 0.0575 / size.y; + const scaleZ = 0.0575 / size.z; this.mesh.scene.scale.set(scaleX, scaleY, scaleZ); @@ -101,18 +104,24 @@ export class Motor implements Controller { } - update(deltaTime): void { - this.callbackHandler.checkCallbacks(deltaTime); + update(timingInfo: PhysicsTimingInfo): void { + this.callbackHandler.checkCallbacks(timingInfo); const chassisEntity = this.chassisWrapper.getEntity(); const wheelPosition = chassisEntity.worldTranslation(this.displacementVector.clone()) as THREE.Vector3; - wheelPosition.y = 0.055 / 2; + wheelPosition.y = 0.0575 / 2; this.mesh?.scene.position.copy( wheelPosition, ); this.mesh?.scene.quaternion.copy( chassisEntity.getRotation() as THREE.Quaternion, ); - this.mesh?.scene.rotateX(90 / 180 * Math.PI); + + const frameDuration = timingInfo.frameDuration; + const radiansPerSecond = this.motorVelocity / 0.0575 * 2 * frameDuration / 1000; + + this.meshRotation += radiansPerSecond; + + this.mesh?.scene.rotateX(this.meshRotation); if (this.wheelSide === 'left') { this.mesh?.scene.rotateZ(Math.PI); } diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index 37cf18ef1..7edb56da2 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -10,13 +10,17 @@ type ProgramConfig = { stepsPerTick: number; }; +export const program_controller_identifier = 'program_controller'; + export class Program implements Controller { code: string; iterator: ReturnType | null; - isPaused:boolean; + isPaused: boolean; callbackHandler = new CallbackHandler(); + name: string; constructor(code: string) { + this.name = program_controller_identifier; this.code = code; this.iterator = null; this.isPaused = false; @@ -59,7 +63,9 @@ export class Program implements Controller { } } catch (e) { console.error(e); - throw new ProgramError('Error in program execution. Please check your code and try again.'); + throw new ProgramError( + 'Error in program execution. Please check your code and try again.', + ); } } diff --git a/src/bundles/robot_simulation/engine/Core/Controller.ts b/src/bundles/robot_simulation/engine/Core/Controller.ts index b7135127c..f636b4535 100644 --- a/src/bundles/robot_simulation/engine/Core/Controller.ts +++ b/src/bundles/robot_simulation/engine/Core/Controller.ts @@ -1,6 +1,7 @@ import { type PhysicsTimingInfo } from '../Physics'; export interface Controller { + name?: string; start?(): Promise | void; update?(timingInfo: PhysicsTimingInfo): void; fixedUpdate?(timingInfo: PhysicsTimingInfo): void; diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts b/src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts new file mode 100644 index 000000000..5c858061f --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts @@ -0,0 +1,92 @@ +import { CallbackHandler } from "../../Core/CallbackHandler"; +import { PhysicsTimingInfo } from "../../Physics"; + +// Helper function to create a PhysicsTimingInfo object +// CallbackHandler only uses the stepCount and timestep properties +// so we only need to create those +const createTimingInfo = ({ + stepCount, + timestep, +}: { + stepCount: number; + timestep: number; +}) => { + return { stepCount, timestep } as PhysicsTimingInfo; +}; + +describe("CallbackHandler", () => { + test("adds callbacks correctly", () => { + const handler = new CallbackHandler(); + const mockCallback = jest.fn(); + + handler.addCallback(mockCallback, 100); + + expect(handler.callbackStore.length).toBe(1); + expect(handler.callbackStore[0].delay).toBe(100); + expect(handler.callbackStore[0].callback).toBe(mockCallback); + }); + + test("executes callback after correct delay", () => { + jest.useFakeTimers(); + const handler = new CallbackHandler(); + const mockCallback = jest.fn(); + + handler.addCallback(mockCallback, 100); + handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 100 })); + + expect(mockCallback).toHaveBeenCalled(); + }); + + test("removes callback after execution", () => { + const handler = new CallbackHandler(); + const mockCallback = jest.fn(); + + handler.addCallback(mockCallback, 100); + handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 100 })); + + expect(handler.callbackStore.length).toBe(0); + }); + + test("handles multiple callbacks correctly", () => { + const handler = new CallbackHandler(); + const mockCallback1 = jest.fn(); + const mockCallback2 = jest.fn(); + + handler.addCallback(mockCallback1, 50); + handler.addCallback(mockCallback2, 100); + handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 50 })); + + expect(mockCallback1).toHaveBeenCalled(); + expect(mockCallback2).not.toHaveBeenCalled(); + + handler.checkCallbacks(createTimingInfo({ stepCount: 2, timestep: 50 })); + expect(mockCallback2).toHaveBeenCalled(); + }); + + test("does not execute callback before its time", () => { + const handler = new CallbackHandler(); + const mockCallback = jest.fn(); + + handler.addCallback(mockCallback, 100); + handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 50 })); + + expect(mockCallback).not.toHaveBeenCalled(); + }); + + test("correctly handles step count changes", () => { + const handler = new CallbackHandler(); + const mockCallback = jest.fn(); + + handler.addCallback(mockCallback, 100); + // Simulate no step count change + handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 50 })); + handler.checkCallbacks(createTimingInfo({ stepCount: 1, timestep: 50 })); // No change in step count + + expect(mockCallback).not.toHaveBeenCalled(); + expect(handler.currentStepCount).toBe(1); + + // Now change the step count + handler.checkCallbacks(createTimingInfo({ stepCount: 2, timestep: 50 })); + expect(handler.currentStepCount).toBe(2); + }); +}); diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 0f4e24777..4666daa89 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { type Program } from './controllers/program/Program'; +import { + program_controller_identifier, + type Program, +} from './controllers/program/Program'; import { type Motor } from './controllers/ev3/components/Motor'; import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; @@ -13,8 +16,9 @@ type MotorFunctionReturnType = Motor | null; // Utility export function ev3_pause(duration: number): void { const world = getWorldFromContext(); - // TODO: FIX THIS! CAUSES BUGS - const program = world.controllers.controllers[0] as Program; + const program = world.controllers.controllers.find( + (controller) => controller.name === program_controller_identifier, + ) as Program; program.pause(duration); } @@ -46,13 +50,11 @@ export function ev3_runToRelativePosition( return; } - const wheelDiameter = 0.055; + const wheelDiameter = 0.0575; - const speedInMetersPerSecond - = (speed / 360) * Math.PI * wheelDiameter; + const speedInMetersPerSecond = (speed / 360) * Math.PI * wheelDiameter; - const distanceInMetersPerSecond - = (position / 360) * Math.PI * wheelDiameter; + const distanceInMetersPerSecond = (position / 360) * Math.PI * wheelDiameter; motor.setSpeedDistance(speedInMetersPerSecond, distanceInMetersPerSecond); } @@ -76,7 +78,6 @@ export function ev3_colorSensorBlue(colorSensor: ColorSensor) { return colorSensor.sense().b; } - // Ultrasonic Sensor export function ev3_ultrasonicSensor() { @@ -84,6 +85,8 @@ export function ev3_ultrasonicSensor() { return ev3.get('ultrasonicSensor'); } -export function ev3_ultrasonicSensorDistance(ultraSonicSensor: UltrasonicSensor): number { +export function ev3_ultrasonicSensorDistance( + ultraSonicSensor: UltrasonicSensor, +): number { return ultraSonicSensor.sense(); } diff --git a/src/jest.config.js b/src/jest.config.js index 88208aef2..38f5de478 100644 --- a/src/jest.config.js +++ b/src/jest.config.js @@ -28,4 +28,7 @@ export default { 'node_modules/(?!=chalk)/', '.+\\.js' ], + setupFiles: [ + "./jest.polyfills.js" + ] }; diff --git a/src/jest.polyfills.js b/src/jest.polyfills.js new file mode 100644 index 000000000..7658fe509 --- /dev/null +++ b/src/jest.polyfills.js @@ -0,0 +1,6 @@ +const { TextDecoder, TextEncoder } = require("node:util"); + +Object.defineProperties(globalThis, { + TextDecoder: { value: TextDecoder }, + TextEncoder: { value: TextEncoder }, +}); diff --git a/yarn.lock b/yarn.lock index a5995e3f3..ec8c4fcc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6151,9 +6151,9 @@ text-table@^0.2.0: integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== three@^0.161.2: - version "0.161.0" - resolved "https://registry.yarnpkg.com/three/-/three-0.161.0.tgz#38aafaa82fe5467fde2e33933515d1b6beb17d91" - integrity sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw== + version "0.162.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.162.0.tgz#b15a511f1498e0c42d4d00bbb411c7527b06097e" + integrity sha512-xfCYj4RnlozReCmUd+XQzj6/5OjDNHBy5nT6rVwrOKGENAvpXe2z1jL+DZYaMu4/9pNsjH/4Os/VvS9IrH7IOQ== through2@^0.6.3: version "0.6.5" From 39e85dc0ee8a3100111e0be8ea00025acbc5f9fd Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 4 Mar 2024 06:13:27 +0000 Subject: [PATCH 47/93] Fix controller bug and add test --- .../engine/Core/Controller.ts | 10 +- .../engine/__tests__/Core/Controller.ts | 160 ++++++++++++++++++ 2 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts diff --git a/src/bundles/robot_simulation/engine/Core/Controller.ts b/src/bundles/robot_simulation/engine/Core/Controller.ts index f636b4535..af3c50d2d 100644 --- a/src/bundles/robot_simulation/engine/Core/Controller.ts +++ b/src/bundles/robot_simulation/engine/Core/Controller.ts @@ -64,10 +64,12 @@ export class ControllerGroup implements Controller { this.controllers.push(...controllers); } - start(): void { - this.controllers.forEach((controller) => { - controller.start?.(); - }); + async start?(): Promise { + await Promise.all( + this.controllers.map(async (controller) => { + await controller.start?.(); + }), + ); } update(timingInfo: PhysicsTimingInfo): void { diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts b/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts new file mode 100644 index 000000000..9ea4801fd --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts @@ -0,0 +1,160 @@ +import { + Controller, + ControllerGroup, + ControllerMap, +} from "../../Core/Controller"; // Adjust the path as necessary +import { PhysicsTimingInfo } from "../../Physics"; + +// Helper function to create a PhysicsTimingInfo object +// CallbackHandler only uses the stepCount and timestep properties +// so we only need to create those +const createTimingInfo = () => { + return { stepCount: 1, timestep: 2 } as PhysicsTimingInfo; +}; + +describe("ControllerMap methods", () => { + // Define test cases in an array of arrays. Each inner array represents parameters for a single test case. + const methodsTestData: Array< + [string, jest.Mock, { async: boolean; args?: any[] }] + > = [ + ["start", jest.fn(), { async: true }], + ["update", jest.fn(), { async: false, args: [createTimingInfo()] }], + ["fixedUpdate", jest.fn(), { async: false, args: [createTimingInfo()] }], + ["onDestroy", jest.fn(), { async: false }], + ]; + + test.each(methodsTestData)( + "%s calls %s on all contained controllers", + async (methodName, mockMethod, { async, args = [] }) => { + const notCalledMethod = jest.fn(); + const controllerMap = new ControllerMap( + { + first: { [methodName]: mockMethod }, + second: { [methodName]: mockMethod }, + // @ts-expect-error + third: { notMethodName: notCalledMethod }, + }, + { [methodName]: mockMethod, notMethodName: notCalledMethod } + ); + + // If the method is async, await it. Otherwise, call it directly. + if (async) { + await controllerMap[methodName](...args); + } else { + controllerMap[methodName](...args); + } + + // Assert that each controller's method was called once + expect(mockMethod).toHaveBeenCalledTimes(3); + expect(notCalledMethod).not.toHaveBeenCalled(); + if (args.length) { + expect(mockMethod).toHaveBeenCalledWith(...args); + } + } + ); + + test.each(methodsTestData)( + "no calls if missing callbacks object", + async (methodName, mockMethod, { async, args = [] }) => { + mockMethod.mockClear(); + const controllerMap = new ControllerMap({}); + if (async) { + await controllerMap[methodName](...args); + } else { + controllerMap[methodName](...args); + } + expect(mockMethod).toHaveBeenCalledTimes(0); + } + ); + + test("get returns the correct controller for a given key", () => { + // Setup: Create a couple of mock controllers + const mockController1 = { + name: "Controller1", + start: jest.fn(), + update: jest.fn(), + }; + const mockController2 = { + name: "Controller2", + start: jest.fn(), + update: jest.fn(), + }; + + const controllerMap = new ControllerMap({ + controller1: mockController1, + controller2: mockController2, + }); + + expect(controllerMap.get("controller1")).toBe(mockController1); + expect(controllerMap.get("controller2")).toBe(mockController2); + + // @ts-expect-error + expect(controllerMap.get("controller3")).toBeUndefined(); + }); +}); + +describe("ControllerGroup", () => { + // Define test data for each method + const methodsTestData: Array<[string, { async: boolean; args: any[] }]> = [ + ["start", { async: true, args: [] }], + ["update", { async: false, args: [{ stepCount: 1, timestep: 20 }] }], // Assuming createTimingInfo() returns something similar + ["fixedUpdate", { async: false, args: [{ stepCount: 2, timestep: 15 }] }], + ["onDestroy", { async: false, args: [] }], + ]; + + test.each(methodsTestData)( + "%s method behavior", + async (methodName, { async, args }) => { + const mockMethod = jest.fn(); + const notCalledMethod = jest.fn(); + const controller: Controller = { + [methodName]: mockMethod, + // @ts-expect-error + "notMethodName": notCalledMethod, + }; + const controllerGroup = new ControllerGroup(); + controllerGroup.addController(controller); + + // Execute the method + if (async) { + await controllerGroup[methodName](...args); + } else { + controllerGroup[methodName](...args); + } + + // Assertions + if (methodName === "onDestroy") { + // Special case for onDestroy to check if controllers are cleared + expect(controllerGroup.controllers).toHaveLength(0); + } else { + // For start, update, fixedUpdate check if the method was called correctly + expect(mockMethod).toHaveBeenCalledTimes(1); + expect(notCalledMethod).not.toHaveBeenCalled(); + if (args.length) { + expect(mockMethod).toHaveBeenCalledWith(...args); + } + } + } + ); + + test.each(methodsTestData)( + "%s no method calls if controller does not have method name", + async (methodName, { async, args }) => { + const notCalledMethod = jest.fn(); + const controller: Controller = { + // @ts-expect-error + "notMethodName": notCalledMethod, + }; + const controllerGroup = new ControllerGroup(); + controllerGroup.addController(controller); + + if (async) { + await controllerGroup[methodName](...args); + } else { + controllerGroup[methodName](...args); + } + + expect(notCalledMethod).not.toHaveBeenCalled(); + } + ); +}); From 9fc7ac44f3afe768514017a9de4c55dbb2d53c76 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 4 Mar 2024 06:57:40 +0000 Subject: [PATCH 48/93] Make Three implement SimpleVector and MeshFactory tests --- .../controllers/ev3/components/Chassis.ts | 4 +- .../controllers/ev3/components/Mesh.ts | 6 +- .../controllers/ev3/components/Motor.ts | 4 +- .../robot_simulation/engine/Entity/Entity.ts | 7 +- .../engine/Entity/EntityFactory.ts | 3 +- .../robot_simulation/engine/Math/Vector.ts | 12 ++ .../engine/Render/MeshFactory.ts | 6 +- .../engine/__tests__/Core/Controller.ts | 2 +- .../engine/__tests__/Render/MeshFactory.ts | 117 ++++++++++++++++++ 9 files changed, 143 insertions(+), 18 deletions(-) create mode 100644 src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts index 529682c37..cb5eb437e 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -49,9 +49,9 @@ export class ChassisWrapper implements Controller { update() { const chassisEntity = this.getEntity(); - this.debugMesh.position.copy(chassisEntity.getPosition() as THREE.Vector3); + this.debugMesh.position.copy(chassisEntity.getPosition()); this.debugMesh.quaternion.copy( - chassisEntity.getRotation() as THREE.Quaternion, + chassisEntity.getRotation(), ); } } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index a5f96131d..2631c754a 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -4,10 +4,10 @@ import { Renderer, } from '../../../engine'; import * as THREE from 'three'; -import { type Orientation } from '../../../engine/Entity/Entity'; // eslint-disable-next-line import/extensions import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { type ChassisWrapper } from './Chassis'; +import type { Orientation } from '../../../engine/Math/Vector'; export type MeshConfig = { orientation: Orientation; @@ -58,13 +58,13 @@ export class Mesh implements Controller { update() { const chassisEntity = this.chassisWrapper.getEntity(); - const chassisPosition = chassisEntity.getPosition() as THREE.Vector3; + const chassisPosition = chassisEntity.getPosition(); chassisPosition.y -= 0.02 / 2; this.mesh?.scene.position.copy(chassisPosition); this.mesh?.scene.quaternion.copy( - chassisEntity.getRotation() as THREE.Quaternion, + chassisEntity.getRotation(), ); } } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index 839e54a4b..61163ed04 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -107,13 +107,13 @@ export class Motor implements Controller { update(timingInfo: PhysicsTimingInfo): void { this.callbackHandler.checkCallbacks(timingInfo); const chassisEntity = this.chassisWrapper.getEntity(); - const wheelPosition = chassisEntity.worldTranslation(this.displacementVector.clone()) as THREE.Vector3; + const wheelPosition = chassisEntity.worldTranslation(this.displacementVector.clone()); wheelPosition.y = 0.0575 / 2; this.mesh?.scene.position.copy( wheelPosition, ); this.mesh?.scene.quaternion.copy( - chassisEntity.getRotation() as THREE.Quaternion, + chassisEntity.getRotation(), ); const frameDuration = timingInfo.frameDuration; diff --git a/src/bundles/robot_simulation/engine/Entity/Entity.ts b/src/bundles/robot_simulation/engine/Entity/Entity.ts index 158a59c0a..371cd1c98 100644 --- a/src/bundles/robot_simulation/engine/Entity/Entity.ts +++ b/src/bundles/robot_simulation/engine/Entity/Entity.ts @@ -1,6 +1,6 @@ import * as THREE from 'three'; import type Rapier from '@dimforge/rapier3d-compat'; -import { type SimpleQuaternion, type SimpleVector } from '../Math/Vector'; +import { type Orientation, type SimpleQuaternion, type SimpleVector } from '../Math/Vector'; import { vec3, quat } from '../Math/Convert'; type BodyConfiguration = { @@ -8,11 +8,6 @@ type BodyConfiguration = { rapierCollider: Rapier.Collider; }; -export type Orientation = { - position: SimpleVector; - rotation: SimpleQuaternion; -}; - export class Entity { #rapierRigidBody: Rapier.RigidBody; #rapierCollider: Rapier.Collider; diff --git a/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts b/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts index f9d9235bc..65c1700d4 100644 --- a/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts +++ b/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts @@ -1,5 +1,6 @@ +import type { Orientation } from '../Math/Vector'; import { type Physics } from '../Physics'; -import { Entity, type Orientation } from './Entity'; +import { Entity } from './Entity'; type RigidBodyTypes = 'fixed' | 'dynamic'; diff --git a/src/bundles/robot_simulation/engine/Math/Vector.ts b/src/bundles/robot_simulation/engine/Math/Vector.ts index 5ca43320c..fb00bbd12 100644 --- a/src/bundles/robot_simulation/engine/Math/Vector.ts +++ b/src/bundles/robot_simulation/engine/Math/Vector.ts @@ -1,2 +1,14 @@ export type SimpleVector = { x: number, y: number, z: number }; export type SimpleQuaternion = { x: number, y: number, z: number, w: number }; + +export type Orientation = { + position: SimpleVector; + rotation: SimpleQuaternion; +}; + +// Vector3 and Quaternion already extends SimpleVector and SimpleQuaternion +// so we can just add the interface to the existing declaration +declare module 'three' { + interface Vector3 extends SimpleVector {} + interface Quaternion extends SimpleQuaternion {} +} diff --git a/src/bundles/robot_simulation/engine/Render/MeshFactory.ts b/src/bundles/robot_simulation/engine/Render/MeshFactory.ts index 9c51c346f..6fbb41e41 100644 --- a/src/bundles/robot_simulation/engine/Render/MeshFactory.ts +++ b/src/bundles/robot_simulation/engine/Render/MeshFactory.ts @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import { type Orientation } from '../Entity/Entity'; +import { type Orientation } from '../Math/Vector'; export type RenderCuboidOptions = { orientation: Orientation; @@ -26,8 +26,8 @@ export function addCuboid(options: RenderCuboidOptions): THREE.Mesh { const mesh = new THREE.Mesh(geometry, material); - mesh.position.copy(orientation.position as THREE.Vector3); - mesh.quaternion.copy(orientation.rotation as THREE.Quaternion); + mesh.position.copy(orientation.position); + mesh.quaternion.copy(orientation.rotation); return mesh; } diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts b/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts index 9ea4801fd..7815c4d6e 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts @@ -2,7 +2,7 @@ import { Controller, ControllerGroup, ControllerMap, -} from "../../Core/Controller"; // Adjust the path as necessary +} from "../../Core/Controller"; import { PhysicsTimingInfo } from "../../Physics"; // Helper function to create a PhysicsTimingInfo object diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts b/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts new file mode 100644 index 000000000..b413c50d4 --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts @@ -0,0 +1,117 @@ +import * as THREE from 'three'; +import {addCuboid} from '../../Render/MeshFactory'; + + +// Mock the necessary Three.js methods and classes +jest.mock('three', () => { + const originalModule = jest.requireActual('three'); + + class Vector3 { + x:number; y:number; z:number;; + constructor(x = 0, y = 0, z = 0) { + this.x = x; + this.y = y; + this.z = z; + } + + copy(vector) { + this.x = vector.x; + this.y = vector.y; + this.z = vector.z; + return this; // Return this for chaining + } + } + + + class Quaternion { + x:number; y:number; z:number; w: number; + constructor(x = 0, y = 0, z = 0, w = 1) { // Default w to 1 for a neutral quaternion + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + copy(quaternion) { + this.x = quaternion.x; + this.y = quaternion.y; + this.z = quaternion.z; + this.w = quaternion.w; + return this; // Return this for chaining + } + } + + return { + ...originalModule, + BoxGeometry: jest.fn(), + MeshPhysicalMaterial: jest.fn(), + Mesh: jest.fn().mockImplementation(function (geometry, material) { + this.geometry = geometry; + this.material = material; + this.position = new Vector3(); + this.quaternion = new Quaternion(); + }), + Vector3: Vector3, + Quaternion: Quaternion, + Color: jest.fn().mockImplementation(function (color) { + return { color }; + }), + }; + }); + + + describe('addCuboid', () => { + it('creates a cuboid with the correct dimensions and color', () => { + const orientation = { + position: new THREE.Vector3(1, 2, 3), + rotation: new THREE.Quaternion(0, 0, 0, 1), + }; + const width = 4; + const height = 5; + const length = 6; + const color = new THREE.Color('red'); + const debug = false; + + const mesh = addCuboid({ orientation, width, height, length, color, debug }); + + expect(THREE.BoxGeometry).toHaveBeenCalledWith(width, height, length); + expect(THREE.MeshPhysicalMaterial).toHaveBeenCalledWith({ + color, + side: THREE.DoubleSide, + }); + expect(mesh).toHaveProperty('geometry'); + expect(mesh).toHaveProperty('material'); + expect(mesh.position).toEqual({ x: 1, y: 2, z: 3 }); + expect(mesh.quaternion).toEqual({ x: 0, y: 0, z: 0, w: 1 }); + }); + + + it('creates a cuboid with wireframe material when debug is true', () => { + const orientation = { + position: new THREE.Vector3(1, 2, 3), + rotation: new THREE.Quaternion(0, 0, 0, 1), + }; + const width = 4; + const height = 5; + const length = 6; + const color = new THREE.Color('red'); + const debug = true; // Enable debug mode + + const mesh = addCuboid({ orientation, width, height, length, color, debug }); + + // Check that the BoxGeometry was called with the correct dimensions + expect(THREE.BoxGeometry).toHaveBeenCalledWith(width, height, length); + + // Verify that the material is created with wireframe enabled + expect(THREE.MeshPhysicalMaterial).toHaveBeenCalledWith({ + color, + wireframe: true, // Expect wireframe to be true when debug is true + }); + + // Verify the mesh properties + expect(mesh).toHaveProperty('geometry'); + expect(mesh).toHaveProperty('material'); + expect(mesh.position).toEqual({ x: 1, y: 2, z: 3 }); + expect(mesh.quaternion).toEqual({ x: 0, y: 0, z: 0, w: 1 }); + }); + }); \ No newline at end of file From b8d721252838f7308d37eb093dae60d55dbf7f95 Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 7 Mar 2024 02:03:08 +0000 Subject: [PATCH 49/93] Add a comment in jest polyfills --- src/jest.polyfills.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jest.polyfills.js b/src/jest.polyfills.js index 7658fe509..fc9ef790b 100644 --- a/src/jest.polyfills.js +++ b/src/jest.polyfills.js @@ -1,5 +1,6 @@ const { TextDecoder, TextEncoder } = require("node:util"); +// According to the Jest docs, https://mswjs.io/docs/migrations/1.x-to-2.x#environment Object.defineProperties(globalThis, { TextDecoder: { value: TextDecoder }, TextEncoder: { value: TextEncoder }, From e5d247d47d84a28faebc966684331c8a1bc7f7ad Mon Sep 17 00:00:00 2001 From: joel chan Date: Sun, 10 Mar 2024 11:46:57 +0000 Subject: [PATCH 50/93] Function clean ups --- src/bundles/robot_simulation/config.ts | 70 ++-- .../controllers/environment/Floor.ts | 2 +- .../controllers/environment/Wall.ts | 2 +- .../controllers/ev3/components/Chassis.ts | 51 +-- .../controllers/ev3/components/Mesh.ts | 64 ++-- .../controllers/ev3/components/Motor.ts | 142 ++++---- .../controllers/ev3/components/Wheel.ts | 40 ++- .../ev3/{default.ts => default/defaultEv3.ts} | 322 +++++++++--------- .../controllers/ev3/sensor/ColorSensor.ts | 117 ++++--- .../ev3/sensor/UltrasonicSensor.ts | 3 +- .../robot_simulation/controllers/index.ts | 2 +- .../controllers/utils/mergeConfig.ts | 12 + .../engine/Helpers/FpsMonitor.ts | 50 +++ .../engine/Render/Renderer.ts | 15 - .../engine/Render/{ => helpers}/Camera.ts | 84 +++-- .../engine/Render/helpers/GLTF.ts | 31 ++ .../Render/{ => helpers}/MeshFactory.ts | 66 ++-- .../engine/Render/helpers/Scene.ts | 3 + src/bundles/robot_simulation/engine/index.ts | 2 +- .../robot_simulation/helper_functions.ts | 26 +- .../components/TabPanels/ColorSensorPanel.tsx | 4 +- .../TabPanels/UltrasonicSensorPanel.tsx | 2 +- 22 files changed, 655 insertions(+), 455 deletions(-) rename src/bundles/robot_simulation/controllers/ev3/ev3/{default.ts => default/defaultEv3.ts} (75%) create mode 100644 src/bundles/robot_simulation/controllers/utils/mergeConfig.ts create mode 100644 src/bundles/robot_simulation/engine/Helpers/FpsMonitor.ts rename src/bundles/robot_simulation/engine/Render/{ => helpers}/Camera.ts (59%) create mode 100644 src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts rename src/bundles/robot_simulation/engine/Render/{ => helpers}/MeshFactory.ts (90%) create mode 100644 src/bundles/robot_simulation/engine/Render/helpers/Scene.ts diff --git a/src/bundles/robot_simulation/config.ts b/src/bundles/robot_simulation/config.ts index d07c0e984..689db4a7e 100644 --- a/src/bundles/robot_simulation/config.ts +++ b/src/bundles/robot_simulation/config.ts @@ -1,13 +1,13 @@ -import * as THREE from 'three'; - import { type PhysicsConfig } from './engine/Physics'; -import { type CameraOptions } from './engine/Render/Camera'; +import { type CameraOptions } from './engine/Render/helpers/Camera'; import { type RenderConfig } from './engine/Render/Renderer'; -import { type EntityCuboidOptions } from './engine/Entity/EntityFactory'; import { type SimpleVector } from './engine/Math/Vector'; import { type MeshConfig } from './controllers/ev3/components/Mesh'; +import type { ChassisWrapperConfig } from './controllers/ev3/components/Chassis'; +import type { MotorConfig } from './controllers/ev3/components/Motor'; +import type { ColorSensorConfig } from './controllers/ev3/sensor/ColorSensor'; -const tinyBuffer = 0.02; +const tinyConstant = 0.012; export const physicsConfig: PhysicsConfig = { gravity: { @@ -32,7 +32,7 @@ export const sceneCamera: CameraOptions = { far: 1000, }; -export const chassisConfig: EntityCuboidOptions = { +export const chassisConfig: ChassisWrapperConfig = { orientation: { position: { x: 0, @@ -51,39 +51,40 @@ export const chassisConfig: EntityCuboidOptions = { width: 0.145, length: 0.18, type: 'dynamic', + debug: true, }; -export const meshConfig: MeshConfig = { - orientation: chassisConfig.orientation, +export const meshConfig: Omit = { width: chassisConfig.width, - height: chassisConfig.height + 0.02, + height: chassisConfig.height, length: chassisConfig.length, - color: new THREE.Color('blue'), - debug: true, + offset: { + y: 0.02, + }, }; export const wheelDisplacements: Record = { frontLeftWheel: { - x: -(chassisConfig.width / 2 + tinyBuffer), - y: 0, - z: chassisConfig.length / 2 - tinyBuffer, + x: -(chassisConfig.width / 2), + y: -(chassisConfig.height / 2), + z: chassisConfig.length / 2 - tinyConstant, }, frontRightWheel: { - x: chassisConfig.width / 2 + tinyBuffer, - y: 0, - z: chassisConfig.length / 2 - tinyBuffer, + x: chassisConfig.width / 2, + y: -(chassisConfig.height / 2), + z: chassisConfig.length / 2 - tinyConstant, }, backLeftWheel: { - x: -(chassisConfig.width / 2 + tinyBuffer), - y: 0, - z: -(chassisConfig.length / 2 - tinyBuffer), + x: -(chassisConfig.width / 2), + y: -(chassisConfig.height / 2), + z: -(chassisConfig.length / 2 - tinyConstant), }, backRightWheel: { - x: chassisConfig.width / 2 + tinyBuffer, - y: 0, - z: -(chassisConfig.length / 2 - tinyBuffer), + x: chassisConfig.width / 2, + y: -(chassisConfig.height / 2), + z: -(chassisConfig.length / 2 - tinyConstant), }, }; @@ -93,6 +94,12 @@ export const wheelPidConfig = { derivativeGain: 40, }; +export const wheelMeshConfig: Omit = { + width: 0.028, + height: 0.0575, + length: 0.0575, +}; + export const motorDisplacements = { leftMotor: { x: 0.058, @@ -113,12 +120,27 @@ export const motorPidConfig = { }; -export const colorSensorConfig = { +export const colorSensorConfig: { displacement: SimpleVector, config: ColorSensorConfig } = { displacement: { x: 0.04, y: -(chassisConfig.height / 2), z: 0.01, }, + config: { + size: { + height: 16, + width: 16, + }, + camera: { + type: 'perspective', + aspect: 1, + fov: 10, + near: 0.01, + far: 1, + }, + tickRateInSeconds: 0.1, + debug: true, + }, }; export const ultrasonicSensorConfig = { diff --git a/src/bundles/robot_simulation/controllers/environment/Floor.ts b/src/bundles/robot_simulation/controllers/environment/Floor.ts index 3b0c75509..707da069a 100644 --- a/src/bundles/robot_simulation/controllers/environment/Floor.ts +++ b/src/bundles/robot_simulation/controllers/environment/Floor.ts @@ -2,7 +2,7 @@ import { type Physics, type Controller, type Renderer, EntityFactory, MeshFactor import * as THREE from 'three'; import { type EntityCuboidOptions } from '../../engine/Entity/EntityFactory'; -import { type RenderCuboidOptions } from '../../engine/Render/MeshFactory'; +import { type RenderCuboidOptions } from '../../engine/Render/helpers/MeshFactory'; export const floorConfig: EntityCuboidOptions & RenderCuboidOptions = { debug: false, diff --git a/src/bundles/robot_simulation/controllers/environment/Wall.ts b/src/bundles/robot_simulation/controllers/environment/Wall.ts index a20d6217e..755078172 100644 --- a/src/bundles/robot_simulation/controllers/environment/Wall.ts +++ b/src/bundles/robot_simulation/controllers/environment/Wall.ts @@ -2,7 +2,7 @@ import { type Physics, type Controller, type Renderer, EntityFactory, MeshFactor import * as THREE from 'three'; import { type EntityCuboidOptions } from '../../engine/Entity/EntityFactory'; -import { type RenderCuboidOptions } from '../../engine/Render/MeshFactory'; +import { type RenderCuboidOptions } from '../../engine/Render/helpers/MeshFactory'; export const wallConfig: EntityCuboidOptions & RenderCuboidOptions = { debug: false, diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts index cb5eb437e..5f21e2c0e 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -9,49 +9,62 @@ import { type Renderer, } from '../../../engine'; import { type EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; -import type { MeshConfig } from './Mesh'; +export type ChassisWrapperConfig = EntityCuboidOptions & { + debug: boolean; +}; + +/** + * Wrapper for the chassis entity. It is needed because the chassis entity can only be initialized + * after the physics engine has been started. Therefore, the chassis entity needs to be wrapped in + * a controller. + * + * We also use this class to add an optional debug mesh to the chassis. + */ export class ChassisWrapper implements Controller { - private physics: Physics; - private config: EntityCuboidOptions; + physics: Physics; + render: Renderer; + config: ChassisWrapperConfig; - private chassis: Entity | null = null; + chassis: Entity | null = null; debugMesh: THREE.Mesh; - constructor(physics: Physics, render: Renderer, config: EntityCuboidOptions) { + constructor( + physics: Physics, + render: Renderer, + config: ChassisWrapperConfig, + ) { this.physics = physics; + this.render = render; this.config = config; - // Debug mesh - const meshConfig: MeshConfig = { + // Debug mesh. + this.debugMesh = MeshFactory.addCuboid({ orientation: config.orientation, width: config.width, height: config.height, length: config.length, color: new THREE.Color(0x00ff00), debug: true, - }; - - this.debugMesh = MeshFactory.addCuboid(meshConfig); + }); + // Set visible based on config. + this.debugMesh.visible = config.debug; render.add(this.debugMesh); } - async start() { - this.chassis = EntityFactory.addCuboid(this.physics, this.config); - } - - getEntity() { + getEntity(): Entity { if (this.chassis === null) { throw new Error('Chassis not initialized'); } return this.chassis; } - update() { + async start(): Promise { + this.chassis = EntityFactory.addCuboid(this.physics, this.config); + } + update(): void { const chassisEntity = this.getEntity(); this.debugMesh.position.copy(chassisEntity.getPosition()); - this.debugMesh.quaternion.copy( - chassisEntity.getRotation(), - ); + this.debugMesh.quaternion.copy(chassisEntity.getRotation()); } } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index 2631c754a..3b9274b79 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -1,70 +1,64 @@ -import { - type Entity, - type Controller, - Renderer, -} from '../../../engine'; -import * as THREE from 'three'; +import { type Controller, type Renderer } from '../../../engine'; // eslint-disable-next-line import/extensions import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { type ChassisWrapper } from './Chassis'; -import type { Orientation } from '../../../engine/Math/Vector'; +import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; +import type { SimpleVector } from '../../../engine/Math/Vector'; export type MeshConfig = { - orientation: Orientation; + url: string; width: number; height: number; length: number; - color: THREE.Color; - debug: boolean; + offset?: Partial; }; +/** + * This represents the mesh of the robot. In reality, the mesh could be part of the chassis, + * but for the sake of clarity it is split into its own controller. + */ export class Mesh implements Controller { - chassis: Entity | null = null; - render: Renderer; - meshConfig: MeshConfig; chassisWrapper: ChassisWrapper; + render: Renderer; + config: MeshConfig; + offset: SimpleVector; + mesh: GLTF | null = null; constructor( chassisWrapper: ChassisWrapper, render: Renderer, - meshConfig: MeshConfig, + config: MeshConfig, ) { this.chassisWrapper = chassisWrapper; - this.meshConfig = meshConfig; this.render = render; + this.config = config; + this.offset = { + x: this.config?.offset?.x || 0, + y: this.config?.offset?.y || 0, + z: this.config?.offset?.z || 0, + }; } async start(): Promise { - this.mesh = await Renderer.loadGTLF( - 'https://keen-longma-3c1be1.netlify.app/6_remove_wheels.gltf', - ); - - const box = new THREE.Box3() - .setFromObject(this.mesh.scene); - - const size = new THREE.Vector3(); - box.getSize(size); - - const scaleX = this.meshConfig.width / size.x; - const scaleY = this.meshConfig.height / size.y; - const scaleZ = this.meshConfig.length / size.z; - - this.mesh.scene.scale.set(scaleX, scaleY, scaleZ); + this.mesh = await loadGLTF(this.config.url, { + x: this.config.width + this.offset.x, + y: this.config.height + this.offset.y, + z: this.config.length + this.offset.z, + }); this.render.add(this.mesh.scene); } update() { const chassisEntity = this.chassisWrapper.getEntity(); - const chassisPosition = chassisEntity.getPosition(); - chassisPosition.y -= 0.02 / 2; + chassisPosition.x -= this.offset.x / 2; + chassisPosition.y -= this.offset.y / 2; + chassisPosition.z -= this.offset.z / 2; this.mesh?.scene.position.copy(chassisPosition); - this.mesh?.scene.quaternion.copy( - chassisEntity.getRotation(), - ); + this.mesh?.scene.quaternion.copy(chassisEntity.getRotation()); } } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index 61163ed04..35814126f 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -1,52 +1,65 @@ -import { type Controller, type Physics, Renderer } from '../../../engine'; +import { type Controller, type Physics, type Renderer } from '../../../engine'; import { type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; import { VectorPidController } from '../feedback_control/PidController'; -import * as THREE from 'three'; +import type * as THREE from 'three'; import { type ChassisWrapper } from './Chassis'; import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; // eslint-disable-next-line import/extensions import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; import type { PhysicsTimingInfo } from '../../../engine/Physics'; +import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; type WheelSide = 'left' | 'right'; -type MotorConfig = { +export type MotorConfig = { pid: { proportionalGain: number; derivativeGain: number; integralGain: number; - } + }; + mesh: { + url: string; + width: number; + height: number; + length: number; + }; }; export class Motor implements Controller { - pid: VectorPidController; + chassisWrapper: ChassisWrapper; + physics: Physics; + render: Renderer; displacementVector: THREE.Vector3; + config: MotorConfig; + motorVelocity: number; + meshRotation: number; + pid: VectorPidController; + callbackHandler = new CallbackHandler(); wheelSide: WheelSide; - physics: Physics; - render: Renderer; - chassisWrapper: ChassisWrapper; - meshRotation: number; - mesh: GLTF | null = null; - constructor(chassisWrapper: ChassisWrapper, physics: Physics, renderer: Renderer, displacement: SimpleVector, config: MotorConfig) { + constructor( + chassisWrapper: ChassisWrapper, + physics: Physics, + render: Renderer, + displacement: SimpleVector, + config: MotorConfig, + ) { this.chassisWrapper = chassisWrapper; this.physics = physics; - this.pid = new VectorPidController(config.pid); + this.render = render; this.displacementVector = vec3(displacement); + this.config = config; + + this.pid = new VectorPidController(config.pid); this.motorVelocity = 0; - this.render = renderer; - this.wheelSide = displacement.x > 0 ? 'right' : 'left'; this.meshRotation = 0; - } - - setVelocity(velocity: number) { - this.motorVelocity = velocity; + this.wheelSide = displacement.x > 0 ? 'right' : 'left'; } setSpeedDistance(speed: number, distance: number) { @@ -54,31 +67,20 @@ export class Motor implements Controller { this.callbackHandler.addCallback(() => { this.motorVelocity = 0; - }, distance / speed * 1000); + }, (distance / speed) * 1000); } async start(): Promise { - this.mesh = await Renderer.loadGTLF( - 'https://keen-longma-3c1be1.netlify.app/6_wheel.gltf', - ); - - const box = new THREE.Box3() - .setFromObject(this.mesh.scene); - - const size = new THREE.Vector3(); - - box.getSize(size); - - const scaleX = 0.028 / size.x; - const scaleY = 0.0575 / size.y; - const scaleZ = 0.0575 / size.z; - - this.mesh.scene.scale.set(scaleX, scaleY, scaleZ); - + this.mesh = await loadGLTF(this.config.mesh.url, { + x: this.config.mesh.width, + y: this.config.mesh.height, + z: this.config.mesh.length, + }); this.render.add(this.mesh.scene); } - fixedUpdate(): void { + fixedUpdate(timingInfo: PhysicsTimingInfo): void { + this.callbackHandler.checkCallbacks(timingInfo); const chassis = this.chassisWrapper.getEntity(); const targetMotorVelocity = vec3({ x: 0, @@ -86,48 +88,58 @@ export class Motor implements Controller { z: this.motorVelocity, }); - const targetMotorGlobalVelocity = chassis.transformDirection(targetMotorVelocity); - const actualMotorGlobalVelocity = chassis.worldVelocity(this.displacementVector.clone()); + const targetMotorGlobalVelocity + = chassis.transformDirection(targetMotorVelocity); + const actualMotorGlobalVelocity = chassis.worldVelocity( + this.displacementVector.clone(), + ); - const error = this.pid.calculate(actualMotorGlobalVelocity, targetMotorGlobalVelocity); + const error = this.pid.calculate( + actualMotorGlobalVelocity, + targetMotorGlobalVelocity, + ); - const motorGlobalPosition = chassis.worldTranslation(this.displacementVector.clone()); + const motorGlobalPosition = chassis.worldTranslation( + this.displacementVector.clone(), + ); - const impulse = error.projectOnPlane(vec3({ - x: 0, - y: 1, - z: 0, - })) + const impulse = error + .projectOnPlane( + vec3({ + x: 0, + y: 1, + z: 0, + }), + ) .multiplyScalar(chassis.getMass()); chassis.applyImpulse(impulse, motorGlobalPosition); } - update(timingInfo: PhysicsTimingInfo): void { - this.callbackHandler.checkCallbacks(timingInfo); + // Retrieve the chassis entity once to avoid multiple calls const chassisEntity = this.chassisWrapper.getEntity(); + + // Calculate the new wheel position, adjusting the y-coordinate to half the mesh height const wheelPosition = chassisEntity.worldTranslation(this.displacementVector.clone()); - wheelPosition.y = 0.0575 / 2; - this.mesh?.scene.position.copy( - wheelPosition, - ); - this.mesh?.scene.quaternion.copy( - chassisEntity.getRotation(), - ); + wheelPosition.y = this.config.mesh.height / 2; // Ensure the wheel is placed correctly vertically - const frameDuration = timingInfo.frameDuration; - const radiansPerSecond = this.motorVelocity / 0.0575 * 2 * frameDuration / 1000; + // If mesh is loaded, update its position and orientation + if (this.mesh) { + this.mesh.scene.position.copy(wheelPosition); + this.mesh.scene.quaternion.copy(chassisEntity.getRotation()); - this.meshRotation += radiansPerSecond; + // Calculate rotation adjustment based on motor velocity and frame duration + const radiansPerFrame = 2 * (this.motorVelocity / this.config.mesh.height) * timingInfo.frameDuration / 1000; - this.mesh?.scene.rotateX(this.meshRotation); - if (this.wheelSide === 'left') { - this.mesh?.scene.rotateZ(Math.PI); - } - } + // Apply rotation changes to simulate wheel turning + this.meshRotation += radiansPerFrame; + this.mesh.scene.rotateX(this.meshRotation); - toString() { - return 'Motor'; + // If the wheel is on the left side, flip it to face the correct direction + if (this.wheelSide === 'left') { + this.mesh.scene.rotateZ(Math.PI); + } + } } } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index 5b5eb6cf2..03b100ec4 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -12,17 +12,20 @@ type WheelConfig = { derivativeGain: number; integralGain: number; }; + gapToFloor: number; + maxRayDistance:number; debug: boolean; }; export class Wheel implements Controller { chassisWrapper: ChassisWrapper; + physics: Physics; + render: Renderer; + config: WheelConfig; + pid: NumberPidController; - displacement: SimpleVector; displacementVector: THREE.Vector3; downVector: THREE.Vector3; - physics: Physics; - render: Renderer; arrowHelper: THREE.ArrowHelper; constructor( @@ -34,36 +37,39 @@ export class Wheel implements Controller { ) { this.chassisWrapper = chassisWrapper; this.physics = physics; + this.render = render; + this.displacementVector = vec3(displacement); + this.config = config; + this.pid = new NumberPidController(config.pid); - this.displacement = displacement; - this.displacementVector = vec3(this.displacement); this.downVector = vec3({ x: 0, y: -1, z: 0, }); + + // Debug arrow. this.arrowHelper = new THREE.ArrowHelper(); this.arrowHelper.visible = config.debug; this.arrowHelper.setColor('red'); render.add(this.arrowHelper); - this.render = render; } fixedUpdate(timingInfo: PhysicsTimingInfo): void { - const { timestep } = timingInfo; const chassis = this.chassisWrapper.getEntity(); const globalDisplacement = chassis.worldTranslation( this.displacementVector.clone(), ); - const globalDownDirection = chassis.transformDirection( this.downVector.clone(), ); + const result = this.physics.castRay( globalDisplacement, globalDownDirection, - 0.5, + this.config.maxRayDistance, + chassis.getCollider(), ); // Wheels are not touching the ground @@ -71,15 +77,23 @@ export class Wheel implements Controller { return; } - const { distance: wheelDistance, normal } = result; + let { distance: wheelDistance, normal } = result; + // If distance is zero, the ray originate from inside the floor/wall. + // If that is true, we assume the normal is pointing up. + if (wheelDistance === 0) { + normal = { + x: 0, + y: 1, + z: 0, + }; + } - const error = this.pid.calculate(wheelDistance, 0.03 + 0.095 / 2); + const error = this.pid.calculate(wheelDistance, this.config.gapToFloor); const force = vec3(normal) .normalize() - .multiplyScalar(error * chassis.getMass() * timestep / 1000); - + .multiplyScalar((error * chassis.getMass() * timingInfo.timestep) / 1000); chassis.applyImpulse(force, globalDisplacement); diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3.ts similarity index 75% rename from src/bundles/robot_simulation/controllers/ev3/ev3/default.ts rename to src/bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3.ts index be63ea3ab..22f4bef00 100644 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default.ts +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3.ts @@ -1,159 +1,163 @@ -import { type Physics, type Renderer, ControllerMap } from '../../../engine'; -import { type EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; -import { type SimpleVector } from '../../../engine/Math/Vector'; -import { type PIDConfig } from '../feedback_control/PidController'; - -import { Wheel } from '../components/Wheel'; -import { Motor } from '../components/Motor'; -import { ColorSensor } from '../sensor/ColorSensor'; -import { Mesh, type MeshConfig } from '../components/Mesh'; -import { ChassisWrapper } from '../components/Chassis'; -import { UltrasonicSensor } from '../sensor/UltrasonicSensor'; - -type WheelControllers = { - frontLeftWheel: Wheel; - frontRightWheel: Wheel; - backLeftWheel: Wheel; - backRightWheel: Wheel; -}; - -type MotorControllers = { - leftMotor: Motor; - rightMotor: Motor; -}; - -type SensorControllers = { - colorSensor: ColorSensor; - ultrasonicSensor: UltrasonicSensor; -}; - -type ChassisControllers = { - mesh: Mesh; - chassis: ChassisWrapper; -}; - -type DefaultEv3Controller = WheelControllers & -MotorControllers & -SensorControllers & -ChassisControllers; - -type Ev3Config = { - chassis: EntityCuboidOptions; - wheel: { - displacements: Record; - pid: PIDConfig; - }; - motor: { - displacements: Record; - pid: PIDConfig; - }; - colorSensor: { - displacement: SimpleVector; - }; - ultrasonicSensor: { - displacement: SimpleVector; - direction: SimpleVector; - debug: boolean; - }; - mesh: MeshConfig; -}; - -export type DefaultEv3 = ControllerMap; - -export const createDefaultEv3 = ( - physics: Physics, - render: Renderer, - config: Ev3Config, -): DefaultEv3 => { - const chassis = new ChassisWrapper(physics, render, config.chassis); - const mesh = new Mesh(chassis, render, config.mesh); - - const wheelPidConfig = { - pid: config.wheel.pid, - debug: true, - }; - - const frontLeftWheel = new Wheel( - chassis, - physics, - render, - config.wheel.displacements.frontLeftWheel, - wheelPidConfig, - ); - - const frontRightWheel = new Wheel( - chassis, - physics, - render, - config.wheel.displacements.frontRightWheel, - wheelPidConfig, - ); - - const backLeftWheel = new Wheel( - chassis, - physics, - render, - config.wheel.displacements.backLeftWheel, - wheelPidConfig, - ); - const backRightWheel = new Wheel( - chassis, - physics, - render, - config.wheel.displacements.backRightWheel, - wheelPidConfig, - ); - - // Motors - const motorPidConfig = { - pid: config.motor.pid, - }; - - const leftMotor = new Motor( - chassis, - physics, - render, - config.motor.displacements.leftMotor, - motorPidConfig, - ); - - const rightMotor = new Motor( - chassis, - physics, - render, - config.motor.displacements.rightMotor, - motorPidConfig, - ); - - // Sensors - const colorSensor = new ColorSensor( - chassis, - render.scene(), - config.colorSensor.displacement, - { debug: true }, - ); - - const ultrasonicSensor = new UltrasonicSensor( - chassis, - physics, - render, - config.ultrasonicSensor.displacement, - config.ultrasonicSensor.direction, - { debug: config.ultrasonicSensor.debug }, - ); - - const ev3: DefaultEv3 = new ControllerMap({ - frontLeftWheel, - frontRightWheel, - backLeftWheel, - backRightWheel, - leftMotor, - rightMotor, - colorSensor, - ultrasonicSensor, - mesh, - chassis, - }); - - return ev3; -}; +import { type Physics, type Renderer, ControllerMap } from '../../../../engine'; +import { type SimpleVector } from '../../../../engine/Math/Vector'; +import { type PIDConfig } from '../../feedback_control/PidController'; + +import { Wheel } from '../../components/Wheel'; +import { Motor, type MotorConfig } from '../../components/Motor'; +import { ColorSensor, type ColorSensorConfig } from '../../sensor/ColorSensor'; +import { Mesh, type MeshConfig } from '../../components/Mesh'; +import { ChassisWrapper, type ChassisWrapperConfig } from '../../components/Chassis'; +import { UltrasonicSensor } from '../../sensor/UltrasonicSensor'; + +type WheelControllers = { + frontLeftWheel: Wheel; + frontRightWheel: Wheel; + backLeftWheel: Wheel; + backRightWheel: Wheel; +}; + +type MotorControllers = { + leftMotor: Motor; + rightMotor: Motor; +}; + +type SensorControllers = { + colorSensor: ColorSensor; + ultrasonicSensor: UltrasonicSensor; +}; + +type ChassisControllers = { + mesh: Mesh; + chassis: ChassisWrapper; +}; + +type DefaultEv3Controller = WheelControllers & +MotorControllers & +SensorControllers & +ChassisControllers; + +export type Ev3Config = { + chassis: ChassisWrapperConfig; + wheel: { + displacements: Record; + pid: PIDConfig; + gapToFloor: number; + maxRayDistance: number; + }; + motor: MotorConfig & { + displacements: Record; + }; + colorSensor: { + displacement: SimpleVector; + config: ColorSensorConfig; + }; + ultrasonicSensor: { + displacement: SimpleVector; + direction: SimpleVector; + debug: boolean; + }; + mesh: MeshConfig; +}; + +export type DefaultEv3 = ControllerMap; + +export const createDefaultEv3 = ( + physics: Physics, + render: Renderer, + config: Ev3Config, +): DefaultEv3 => { + const chassis = new ChassisWrapper(physics, render, config.chassis); + const mesh = new Mesh(chassis, render, config.mesh); + + const wheelPidConfig = { + pid: config.wheel.pid, + gapToFloor: config.wheel.gapToFloor, + maxRayDistance: config.wheel.maxRayDistance, + debug: true, + }; + + const frontLeftWheel = new Wheel( + chassis, + physics, + render, + config.wheel.displacements.frontLeftWheel, + wheelPidConfig, + ); + + const frontRightWheel = new Wheel( + chassis, + physics, + render, + config.wheel.displacements.frontRightWheel, + wheelPidConfig, + ); + + const backLeftWheel = new Wheel( + chassis, + physics, + render, + config.wheel.displacements.backLeftWheel, + wheelPidConfig, + ); + const backRightWheel = new Wheel( + chassis, + physics, + render, + config.wheel.displacements.backRightWheel, + wheelPidConfig, + ); + + // Motors + const motorPidConfig = { + pid: config.motor.pid, + mesh: config.motor.mesh, + }; + + const leftMotor = new Motor( + chassis, + physics, + render, + config.motor.displacements.leftMotor, + motorPidConfig, + ); + + const rightMotor = new Motor( + chassis, + physics, + render, + config.motor.displacements.rightMotor, + motorPidConfig, + ); + + // Sensors + const colorSensor = new ColorSensor( + chassis, + render, + config.colorSensor.displacement, + config.colorSensor.config, + ); + + const ultrasonicSensor = new UltrasonicSensor( + chassis, + physics, + render, + config.ultrasonicSensor.displacement, + config.ultrasonicSensor.direction, + { debug: config.ultrasonicSensor.debug }, + ); + + const ev3: DefaultEv3 = new ControllerMap({ + frontLeftWheel, + frontRightWheel, + backLeftWheel, + backRightWheel, + leftMotor, + rightMotor, + colorSensor, + ultrasonicSensor, + mesh, + chassis, + }); + + return ev3; +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts index e954d8685..033af994c 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -5,89 +5,127 @@ import { type ChassisWrapper } from '../components/Chassis'; import { type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; import type { PhysicsTimingInfo } from '../../../engine/Physics'; - -type Color = { r:number, g:number, b:number }; - -type ColorSensorConfig = { - debug: boolean +import { + getCamera, + type CameraOptions, +} from '../../../engine/Render/helpers/Camera'; + +type Color = { r: number; g: number; b: number }; + +export type ColorSensorConfig = { + size: { + height: number; + width: number; + }; + camera: CameraOptions; + tickRateInSeconds: number; + debug: boolean; }; export class ColorSensor implements Sensor { - renderer: Renderer; - camera: THREE.Camera; - displacement: THREE.Vector3; chassisWrapper: ChassisWrapper; + displacement: THREE.Vector3; + config: ColorSensorConfig; + camera: THREE.Camera; + renderer: Renderer; accumulator = 0; - colorSensed:Color; + colorSensed: Color; tempCanvas: HTMLCanvasElement; - constructor(chassisWrapper: ChassisWrapper, scene: THREE.Scene, displacement: SimpleVector, config: ColorSensorConfig) { + constructor( + chassisWrapper: ChassisWrapper, + render: Renderer, + displacement: SimpleVector, + config: ColorSensorConfig, + ) { this.chassisWrapper = chassisWrapper; - this.camera = Renderer.sensorCamera(); this.displacement = vec3(displacement); + this.config = config; + + this.camera = getCamera(config.camera); + // We create a new renderer with the same scene. But we use a different camera. + this.renderer = new Renderer(render.scene(), this.camera, { + width: this.config.size.width, + height: this.config.size.height, + control: 'none', + }); + this.colorSensed = { r: 0, g: 0, b: 0, }; - this.renderer = new Renderer(scene, this.camera, { - width: 16, - height: 16, - control: 'none', - }); this.tempCanvas = document.createElement('canvas'); - + this.tempCanvas.width = this.config.size.width; + this.tempCanvas.height = this.config.size.height; if (config.debug) { const helper = new THREE.CameraHelper(this.camera); - scene.add(helper); + render.add(helper); } } getColorSensorPosition() { const chassis = this.chassisWrapper.getEntity(); - const colorSensorPosition = chassis.worldTranslation(this.displacement.clone()); + const colorSensorPosition = chassis.worldTranslation( + this.displacement.clone(), + ); return colorSensorPosition; } - getElement(): HTMLCanvasElement { - return this.renderer.getElement(); - } - sense(): Color { return this.colorSensed; } + // Even though we are rendering, we use fixedUpdate because the student's code can be affected + // by the values of sense() and could affect the determinism of the simulation. fixedUpdate(timingInfo: PhysicsTimingInfo) { - const { timestep } = timingInfo; + this.accumulator += timingInfo.timestep; + + const tickRateInMilliseconds = this.config.tickRateInSeconds * 1000; - this.accumulator += timestep; - if (this.accumulator < 1000) { + // We check the accumulator to see if it's time update the color sensor. + // If it's not time, we return early. + if (this.accumulator < tickRateInMilliseconds) { return; } - this.accumulator -= 1000; - - this.renderer.render(); + this.accumulator -= tickRateInMilliseconds; + // We move the camera to the right position this.camera.position.copy(this.getColorSensorPosition()); + // Point it downwards + this.camera.lookAt( + this.camera.position.x, + this.camera.position.y - 1, // 1 unit below its current position + this.camera.position.z, + ); + + // We render to load the color sensor data into the renderer. + this.renderer.render(); - const lookAt = this.getColorSensorPosition(); - lookAt.y -= 1; - this.camera.lookAt(lookAt); - - const rendererCanvas = this.getElement(); - - this.tempCanvas.width = rendererCanvas.width; - this.tempCanvas.height = rendererCanvas.height; + // We get the HTMLCanvasElement from the renderer + const rendererCanvas = this.renderer.getElement(); - const tempCtx = this.tempCanvas.getContext('2d', { willReadFrequently: true })!; + // Get the context from the temp canvas + const tempCtx = this.tempCanvas.getContext('2d', { + willReadFrequently: true, + })!; + // Draw the renderer canvas to the temp canvas tempCtx.drawImage(rendererCanvas, 0, 0); - const imageData = tempCtx.getImageData(0, 0, 16, 16, {}); + // Get the image data from the temp canvas + const imageData = tempCtx.getImageData( + 0, + 0, + this.config.size.width, + this.config.size.height, + {}, + ); + // Calculate the average color const averageColor = { r: 0, g: 0, @@ -98,7 +136,6 @@ export class ColorSensor implements Sensor { const r = imageData.data[i]; const g = imageData.data[i + 1]; const b = imageData.data[i + 2]; - // const a = imageData.data[i + 3]; averageColor.r += r; averageColor.g += g; averageColor.b += b; diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts index 789039dcd..814d2088d 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -33,6 +33,8 @@ export class UltrasonicSensor implements Sensor { this.displacement = vec3(displacement); this.direction = vec3(direction); this.config = config; + + // Debug arrow this.debugArrow = new THREE.ArrowHelper(); this.debugArrow.visible = false; this.render.add(this.debugArrow); @@ -63,7 +65,6 @@ export class UltrasonicSensor implements Sensor { this.debugArrow.setDirection(globalDirection.normalize()); } - // TODO: Figure out what the sensor should return if it doesn't sense anything if (result === null) { return; } diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts index 8b8898751..0a5ec5249 100644 --- a/src/bundles/robot_simulation/controllers/index.ts +++ b/src/bundles/robot_simulation/controllers/index.ts @@ -1,4 +1,4 @@ export { Floor } from './environment/Floor'; -export { type DefaultEv3 } from './ev3/ev3/default'; +export { type DefaultEv3 } from './ev3/ev3/default/defaultEv3'; export { Wall } from './environment/Wall'; diff --git a/src/bundles/robot_simulation/controllers/utils/mergeConfig.ts b/src/bundles/robot_simulation/controllers/utils/mergeConfig.ts new file mode 100644 index 000000000..bd480da85 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/utils/mergeConfig.ts @@ -0,0 +1,12 @@ +import * as _ from 'lodash'; + +export type RecursivePartial = T extends object ? { + [P in keyof T]?: RecursivePartial; +} : T; + +export const mergeConfig = (defaultConfig:T, userConfig?: RecursivePartial) :T => { + if (userConfig === undefined) { + return defaultConfig; + } + return _.merge({ ...defaultConfig }, userConfig); +}; diff --git a/src/bundles/robot_simulation/engine/Helpers/FpsMonitor.ts b/src/bundles/robot_simulation/engine/Helpers/FpsMonitor.ts new file mode 100644 index 000000000..5a8f3ce2e --- /dev/null +++ b/src/bundles/robot_simulation/engine/Helpers/FpsMonitor.ts @@ -0,0 +1,50 @@ +import type { DefaultEv3 } from '../../controllers'; +import type { Controller } from '../Core/Controller'; +import type { PhysicsTimingInfo } from '../Physics'; +import type { Renderer } from '../Render/Renderer'; + +export class FpsMonitor implements Controller { + render: Renderer; + overlayDiv: HTMLDivElement; + textContainer: HTMLDivElement; + ev3: DefaultEv3; + added = false; + + constructor(render: Renderer, ev3: DefaultEv3) { + this.ev3 = ev3; + this.render = render; + this.overlayDiv = document.createElement('div'); + this.overlayDiv.style.position = 'absolute'; + this.overlayDiv.style.color = 'black'; + this.overlayDiv.style.top = '0'; + this.overlayDiv.style.left = '0'; + this.overlayDiv.style.width = '100%'; // Cover the full area of the container + this.overlayDiv.style.height = '100%'; + this.overlayDiv.style.pointerEvents = 'none'; // Allows clicking through the div if needed + this.overlayDiv.id = 'fpsMonitor'; + this.textContainer = document.createElement('div'); + this.textContainer.style.position = 'absolute'; + this.textContainer.style.fontSize = '20px'; + this.textContainer.style.top = '300px'; + this.textContainer.style.left = '700px'; + this.textContainer.style.backgroundColor = 'white'; + + this.overlayDiv.appendChild(this.textContainer); + } + + fixedUpdate(timingInfo: PhysicsTimingInfo): void { + const fps = timingInfo.framesPerSecond.toFixed(2); + const distance = this.ev3.get('ultrasonicSensor') + .sense(); + const canvas = this.render.getElement(); + + if (timingInfo.stepCount % 2 === 0) { + this.textContainer.innerHTML = `FPS: ${fps}
Distance: ${distance}`; + } + + if (!this.added) { + canvas.parentNode!.appendChild(this.overlayDiv); + this.added = true; + } + } +} diff --git a/src/bundles/robot_simulation/engine/Render/Renderer.ts b/src/bundles/robot_simulation/engine/Render/Renderer.ts index d1e36c057..41c981769 100644 --- a/src/bundles/robot_simulation/engine/Render/Renderer.ts +++ b/src/bundles/robot_simulation/engine/Render/Renderer.ts @@ -7,7 +7,6 @@ import { type GLTF, GLTFLoader, } from 'three/examples/jsm/loaders/GLTFLoader.js'; -import { getCamera, type CameraOptions } from './Camera'; type ControlType = 'none' | 'orbit'; @@ -44,20 +43,6 @@ export class Renderer { this.#scene.background = new THREE.Color(0xffffff); } - static scene(): THREE.Scene { - return new THREE.Scene(); - } - - static camera(cameraOptions: CameraOptions): THREE.Camera { - const camera = getCamera(cameraOptions); - return camera; - } - - static sensorCamera(): THREE.Camera { - const renderAspectRatio = 1; - return new THREE.PerspectiveCamera(10, renderAspectRatio, 0.01, 1); - } - static loadGTLF(url: string): Promise { const loader = new GLTFLoader(); return new Promise((resolve, reject) => { diff --git a/src/bundles/robot_simulation/engine/Render/Camera.ts b/src/bundles/robot_simulation/engine/Render/helpers/Camera.ts similarity index 59% rename from src/bundles/robot_simulation/engine/Render/Camera.ts rename to src/bundles/robot_simulation/engine/Render/helpers/Camera.ts index 04d8cb60c..ac9a9c22e 100644 --- a/src/bundles/robot_simulation/engine/Render/Camera.ts +++ b/src/bundles/robot_simulation/engine/Render/helpers/Camera.ts @@ -1,36 +1,48 @@ -import * as THREE from 'three'; - -type OrthographicCameraOptions = { - type: 'orthographic'; -}; - -type PerspectiveCameraOptions = { - type: 'perspective'; - fov: number; - aspect: number; - near: number; - far: number; -}; - -export type CameraOptions = - | OrthographicCameraOptions - | PerspectiveCameraOptions; - -export function getCamera(cameraOptions: CameraOptions): THREE.Camera { - switch (cameraOptions.type) { - case 'perspective': - return new THREE.PerspectiveCamera( - cameraOptions.fov, - cameraOptions.aspect, - cameraOptions.near, - cameraOptions.far, - ); - case 'orthographic': - return new THREE.OrthographicCamera(); - default: { - // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars - const _: never = cameraOptions; - throw new Error('Unknown camera type'); - } - } -} +import * as THREE from 'three'; + +type OrthographicCameraOptions = { + type: 'orthographic'; +}; + +type PerspectiveCameraOptions = { + type: 'perspective'; + fov: number; + aspect: number; + near: number; + far: number; +}; + +export type CameraOptions = + | OrthographicCameraOptions + | PerspectiveCameraOptions; + +const setCameraPosition = (camera: THREE.Camera, position: THREE.Vector3) => { + camera.position.copy(position); + camera.lookAt(0, 0, 0); +}; + +export function getCamera(cameraOptions: CameraOptions): THREE.Camera { + const defaultPosition = new THREE.Vector3(0, 2, -2); + switch (cameraOptions.type) { + case 'perspective': { + const camera = new THREE.PerspectiveCamera( + cameraOptions.fov, + cameraOptions.aspect, + cameraOptions.near, + cameraOptions.far, + ); + setCameraPosition(camera, defaultPosition); + return camera; + } + case 'orthographic': { + const camera = new THREE.OrthographicCamera(); + setCameraPosition(camera, defaultPosition); + return camera; + } + default: { + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars + const _: never = cameraOptions; + throw new Error('Unknown camera type'); + } + } +} diff --git a/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts b/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts new file mode 100644 index 000000000..dc0ab8fd5 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts @@ -0,0 +1,31 @@ +import * as THREE from 'three'; +// eslint-disable-next-line import/extensions +import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import type { SimpleVector } from '../../Math/Vector'; + +type GLTFLoaderOptions = SimpleVector; + +export function loadGLTF(url: string, targetSize: GLTFLoaderOptions): Promise { + const loader = new GLTFLoader(); + return new Promise((resolve, reject) => { + loader.load( + url, + (data) => { + const box = new THREE.Box3() + .setFromObject(data.scene); + const meshSize = new THREE.Vector3(); + box.getSize(meshSize); + + const scaleX = targetSize.x / meshSize.x; + const scaleY = targetSize.y / meshSize.y; + const scaleZ = targetSize.z / meshSize.z; + + data.scene.scale.set(scaleX, scaleY, scaleZ); + + resolve(data); + }, + undefined, + (error) => reject(error), + ); + }); +} diff --git a/src/bundles/robot_simulation/engine/Render/MeshFactory.ts b/src/bundles/robot_simulation/engine/Render/helpers/MeshFactory.ts similarity index 90% rename from src/bundles/robot_simulation/engine/Render/MeshFactory.ts rename to src/bundles/robot_simulation/engine/Render/helpers/MeshFactory.ts index 6fbb41e41..b525a1e12 100644 --- a/src/bundles/robot_simulation/engine/Render/MeshFactory.ts +++ b/src/bundles/robot_simulation/engine/Render/helpers/MeshFactory.ts @@ -1,33 +1,33 @@ -import * as THREE from 'three'; -import { type Orientation } from '../Math/Vector'; - -export type RenderCuboidOptions = { - orientation: Orientation; - width: number; - height: number; - length: number; - color: THREE.Color; - debug: boolean; -}; - -export function addCuboid(options: RenderCuboidOptions): THREE.Mesh { - const { orientation, width, height, length, color } = options; - const geometry = new THREE.BoxGeometry(width, height, length); - const material = options.debug - ? new THREE.MeshPhysicalMaterial({ - color, - wireframe: true, - }) - : new THREE.MeshPhysicalMaterial({ - color, - side: THREE.DoubleSide, - }); - - - const mesh = new THREE.Mesh(geometry, material); - - mesh.position.copy(orientation.position); - mesh.quaternion.copy(orientation.rotation); - - return mesh; -} +import * as THREE from 'three'; +import { type Orientation } from '../../Math/Vector'; + +export type RenderCuboidOptions = { + orientation: Orientation; + width: number; + height: number; + length: number; + color: THREE.Color; + debug: boolean; +}; + +export function addCuboid(options: RenderCuboidOptions): THREE.Mesh { + const { orientation, width, height, length, color } = options; + const geometry = new THREE.BoxGeometry(width, height, length); + const material = options.debug + ? new THREE.MeshPhysicalMaterial({ + color, + wireframe: true, + }) + : new THREE.MeshPhysicalMaterial({ + color, + side: THREE.DoubleSide, + }); + + + const mesh = new THREE.Mesh(geometry, material); + + mesh.position.copy(orientation.position); + mesh.quaternion.copy(orientation.rotation); + + return mesh; +} diff --git a/src/bundles/robot_simulation/engine/Render/helpers/Scene.ts b/src/bundles/robot_simulation/engine/Render/helpers/Scene.ts new file mode 100644 index 000000000..55d9d1111 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Render/helpers/Scene.ts @@ -0,0 +1,3 @@ +import * as THREE from 'three'; + +export const createScene = () => new THREE.Scene(); diff --git a/src/bundles/robot_simulation/engine/index.ts b/src/bundles/robot_simulation/engine/index.ts index a33280630..5cdb152ad 100644 --- a/src/bundles/robot_simulation/engine/index.ts +++ b/src/bundles/robot_simulation/engine/index.ts @@ -5,4 +5,4 @@ export { Timer, type FrameTimingInfo } from './Core/Timer'; export { ControllerGroup, type Controller, ControllerMap } from './Core/Controller'; export { Entity } from './Entity/Entity'; export * as EntityFactory from './Entity/EntityFactory'; -export * as MeshFactory from './Render/MeshFactory'; +export * as MeshFactory from './Render/helpers/MeshFactory'; diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index 5c257cf9b..f58ae827c 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -10,16 +10,19 @@ import { wheelDisplacements, wheelPidConfig, ultrasonicSensorConfig, + wheelMeshConfig, } from './config'; import { Program } from './controllers/program/Program'; import { Floor, type DefaultEv3, Wall } from './controllers'; -import { createDefaultEv3 } from './controllers/ev3/ev3/default'; +import { createDefaultEv3, type Ev3Config } from './controllers/ev3/ev3/default/defaultEv3'; import { type Controller, Physics, Renderer, Timer, World } from './engine'; import context from 'js-slang/context'; import { interrupt } from './interrupt'; import { RobotConsole } from './engine/Core/RobotConsole'; +import { getCamera } from './engine/Render/helpers/Camera'; +import { createScene } from './engine/Render/helpers/Scene'; const storedWorld = context.moduleContexts.robot_simulation.state?.world; @@ -41,13 +44,10 @@ export function getEv3FromContext(): DefaultEv3 { return ev3 as DefaultEv3; } - // Initialization functions export function createRenderer(): Renderer { - const scene = Renderer.scene(); - const camera = Renderer.camera(sceneCamera); - camera.translateY(2); - camera.translateZ(-2); + const scene = createScene(); + const camera = getCamera(sceneCamera); const renderer = new Renderer(scene, camera, renderConfig); return renderer; } @@ -68,21 +68,31 @@ export function createWorld(physics: Physics, renderer: Renderer, timer: Timer, } export function createEv3(physics: Physics, renderer: Renderer): DefaultEv3 { - const config = { + const config:Ev3Config = { chassis: chassisConfig, motor: { displacements: motorDisplacements, pid: motorPidConfig, + mesh: { + ...wheelMeshConfig, + url: 'https://keen-longma-3c1be1.netlify.app/6_wheel.gltf', + }, }, wheel: { displacements: wheelDisplacements, pid: wheelPidConfig, + gapToFloor: 0.03, + maxRayDistance: 0.05, }, colorSensor: { displacement: colorSensorConfig.displacement, + config: colorSensorConfig.config, }, ultrasonicSensor: ultrasonicSensorConfig, - mesh: meshConfig, + mesh: { + ...meshConfig, + url: 'https://keen-longma-3c1be1.netlify.app/6_remove_wheels.gltf', + }, }; const ev3 = createDefaultEv3(physics, renderer, config); diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx index 4cbb542c1..1ddf139e6 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3'; export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { const colorSensor = ev3.get('colorSensor'); @@ -9,7 +9,7 @@ export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { useEffect(() => { if (sensorVisionRef.current) { - sensorVisionRef.current.replaceChildren(colorSensor.getElement()); + sensorVisionRef.current.replaceChildren(colorSensor.renderer.getElement()); } // Hacky diff --git a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx index 61ef3f19d..44e5104c1 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3'; export const UltrasonicSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { const ultrasonicSensor = ev3.get('ultrasonicSensor'); From ed0a5d2497069f6f3e418d8038652c0df7ffa9fe Mon Sep 17 00:00:00 2001 From: joel chan Date: Sun, 10 Mar 2024 11:47:22 +0000 Subject: [PATCH 51/93] Change the paths in the tests --- src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts | 2 +- .../robot_simulation/engine/__tests__/Render/MeshFactory.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts b/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts index 9b346b89e..52643ca09 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts @@ -1,6 +1,6 @@ import * as THREE from 'three'; -import { CameraOptions, getCamera } from '../../Render/Camera'; +import { CameraOptions, getCamera } from '../../Render/helpers/Camera'; describe('getCamera', () => { diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts b/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts index b413c50d4..0cf967675 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts @@ -1,5 +1,5 @@ import * as THREE from 'three'; -import {addCuboid} from '../../Render/MeshFactory'; +import {addCuboid} from '../../Render/helpers/MeshFactory'; // Mock the necessary Three.js methods and classes From 31f9adefe8f019529361c54f769fba4ac91e0bc5 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 11 Mar 2024 03:51:01 +0000 Subject: [PATCH 52/93] Make the lighting nicer --- src/bundles/robot_simulation/engine/Render/Renderer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bundles/robot_simulation/engine/Render/Renderer.ts b/src/bundles/robot_simulation/engine/Render/Renderer.ts index 41c981769..b7b521827 100644 --- a/src/bundles/robot_simulation/engine/Render/Renderer.ts +++ b/src/bundles/robot_simulation/engine/Render/Renderer.ts @@ -32,14 +32,18 @@ export class Renderer { this.#camera = camera; this.#scene = scene; this.#renderer = new THREE.WebGLRenderer({ antialias: true }); + this.#renderer.shadowMap.enabled = true; this.#controls = new OrbitControls(this.#camera, this.#renderer.domElement); this.#renderer.setSize(configuration.width, configuration.height); this.#renderer.setPixelRatio(window.devicePixelRatio * 1.5); - const light = new THREE.AmbientLight(0xffffff); + const light = new THREE.PointLight(0xffffff, 1); + const ambient = new THREE.AmbientLight(0xffffff, 0.20); + light.position.set(0, 1, 0); this.#scene.add(light); + this.#scene.add(ambient); this.#scene.background = new THREE.Color(0xffffff); } From 3ef42efe0ddba75ce6fe34156eacea1e7d760af7 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 11 Mar 2024 05:47:59 +0000 Subject: [PATCH 53/93] Add Paper and DebugArrow --- .../controllers/environment/Floor.ts | 19 -------- .../controllers/environment/Paper.ts | 35 +++++++++++++++ .../controllers/ev3/components/Chassis.ts | 1 + .../controllers/ev3/components/Motor.ts | 19 ++++++-- .../controllers/ev3/components/Wheel.ts | 16 +++---- .../engine/Render/debug/DebugArrow.ts | 45 +++++++++++++++++++ .../robot_simulation/helper_functions.ts | 13 ++++++ src/bundles/robot_simulation/index.ts | 1 + 8 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 src/bundles/robot_simulation/controllers/environment/Paper.ts create mode 100644 src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts diff --git a/src/bundles/robot_simulation/controllers/environment/Floor.ts b/src/bundles/robot_simulation/controllers/environment/Floor.ts index 707da069a..1d3c80843 100644 --- a/src/bundles/robot_simulation/controllers/environment/Floor.ts +++ b/src/bundles/robot_simulation/controllers/environment/Floor.ts @@ -32,34 +32,15 @@ export class Floor implements Controller { private physics: Physics; private renderer: Renderer; private mesh: THREE.Mesh; - private paper: THREE.Mesh; constructor(physics:Physics, renderer: Renderer) { this.physics = physics; this.renderer = renderer; - this.mesh = MeshFactory.addCuboid(floorConfig); - - const plane = new THREE.PlaneGeometry(1, 1); // Creating a 1x1 plane for the carpet - this.paper = new THREE.Mesh(plane); } async start(): Promise { EntityFactory.addCuboid(this.physics, floorConfig); - - // Load the texture - const texture = new THREE.TextureLoader() - .load('https://www.shutterstock.com/image-vector/black-white-stripes-260nw-785326606.jpg'); - - // Create a material with the texture - const material = new THREE.MeshStandardMaterial({ map: texture }); - this.paper.scale.set(2, 2, 2); // Scale the plane to the size of the floor - this.paper.position.set(0, 0.001, 0); // Position the plane at the floor - this.paper.rotation.x = -Math.PI / 2; // Rotate the plane to be parallel to the floor - this.paper.material = material; - - // Apply the material to the mesh this.renderer.add(this.mesh); - this.renderer.add(this.paper); } } diff --git a/src/bundles/robot_simulation/controllers/environment/Paper.ts b/src/bundles/robot_simulation/controllers/environment/Paper.ts new file mode 100644 index 000000000..2151ec13a --- /dev/null +++ b/src/bundles/robot_simulation/controllers/environment/Paper.ts @@ -0,0 +1,35 @@ +import * as THREE from 'three'; + +import type { Renderer } from '../../engine'; + +export type PaperConfig = { + url: string; + mesh: { + width: number; + height: number; + }; +}; + +export class Paper { + render: Renderer; + config: PaperConfig; + paper: THREE.Mesh; + + constructor(render: Renderer, config: PaperConfig) { + this.render = render; + this.config = config; + + const plane = new THREE.PlaneGeometry(this.config.mesh.width, this.config.mesh.height); // Creating a 1x1 plane for the carpet + this.paper = new THREE.Mesh(plane); + } + + async start() { + const texture = new THREE.TextureLoader() + .load(this.config.url); + const material = new THREE.MeshStandardMaterial({ map: texture }); + this.paper.position.set(0, 0.001, 0); // Position the plane at the floor + this.paper.rotation.x = -Math.PI / 2; + this.paper.material = material; + this.render.add(this.paper); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts index 5f21e2c0e..db63ae656 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -62,6 +62,7 @@ export class ChassisWrapper implements Controller { async start(): Promise { this.chassis = EntityFactory.addCuboid(this.physics, this.config); } + update(): void { const chassisEntity = this.getEntity(); this.debugMesh.position.copy(chassisEntity.getPosition()); diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index 35814126f..e03bca2b0 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -26,7 +26,10 @@ export type MotorConfig = { length: number; }; }; - +/** + * This represents the motor of the robot and is responsible for moving the robot. It is also + * responsible for the visual representation of the wheel and the friction. + */ export class Motor implements Controller { chassisWrapper: ChassisWrapper; physics: Physics; @@ -82,28 +85,36 @@ export class Motor implements Controller { fixedUpdate(timingInfo: PhysicsTimingInfo): void { this.callbackHandler.checkCallbacks(timingInfo); const chassis = this.chassisWrapper.getEntity(); + + // Calculate the target motor velocity from the chassis perspective const targetMotorVelocity = vec3({ x: 0, y: 0, z: this.motorVelocity, }); + // Transform it to the global perspective const targetMotorGlobalVelocity = chassis.transformDirection(targetMotorVelocity); + + // Calculate the actual motor velocity from the global perspective const actualMotorGlobalVelocity = chassis.worldVelocity( this.displacementVector.clone(), ); - const error = this.pid.calculate( + // Calculate the PID output with the PID controller + const pidOutput = this.pid.calculate( actualMotorGlobalVelocity, targetMotorGlobalVelocity, ); + // Find the global position of the motor const motorGlobalPosition = chassis.worldTranslation( this.displacementVector.clone(), ); - const impulse = error + // Calculate the impulse to apply to the chassis + const impulse = pidOutput .projectOnPlane( vec3({ x: 0, @@ -113,11 +124,11 @@ export class Motor implements Controller { ) .multiplyScalar(chassis.getMass()); + // Apply the impulse to the chassis chassis.applyImpulse(impulse, motorGlobalPosition); } update(timingInfo: PhysicsTimingInfo): void { - // Retrieve the chassis entity once to avoid multiple calls const chassisEntity = this.chassisWrapper.getEntity(); // Calculate the new wheel position, adjusting the y-coordinate to half the mesh height diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index 03b100ec4..b1333a14e 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -2,9 +2,10 @@ import { type Renderer, type Controller, type Physics } from '../../../engine'; import { type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; import { NumberPidController } from '../feedback_control/PidController'; -import * as THREE from 'three'; +import type * as THREE from 'three'; import { type ChassisWrapper } from './Chassis'; import type { PhysicsTimingInfo } from '../../../engine/Physics'; +import { DebugArrow } from '../../../engine/Render/debug/DebugArrow'; type WheelConfig = { pid: { @@ -26,7 +27,7 @@ export class Wheel implements Controller { pid: NumberPidController; displacementVector: THREE.Vector3; downVector: THREE.Vector3; - arrowHelper: THREE.ArrowHelper; + arrowHelper: DebugArrow; constructor( chassisWrapper: ChassisWrapper, @@ -49,10 +50,8 @@ export class Wheel implements Controller { }); // Debug arrow. - this.arrowHelper = new THREE.ArrowHelper(); - this.arrowHelper.visible = config.debug; - this.arrowHelper.setColor('red'); - render.add(this.arrowHelper); + this.arrowHelper = new DebugArrow({ debug: config.debug }); + render.add(this.arrowHelper.getMesh()); } fixedUpdate(timingInfo: PhysicsTimingInfo): void { @@ -97,8 +96,7 @@ export class Wheel implements Controller { chassis.applyImpulse(force, globalDisplacement); - this.arrowHelper.setLength(force.length() * 1000); - this.arrowHelper.setDirection(force.normalize()); - this.arrowHelper.position.copy(globalDisplacement); + // Debug arrow. + this.arrowHelper.update(globalDisplacement, force.clone(), force.length() * 1000); } } diff --git a/src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts b/src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts new file mode 100644 index 000000000..083cc0f71 --- /dev/null +++ b/src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts @@ -0,0 +1,45 @@ +import * as THREE from 'three'; + +import { + mergeConfig, + type RecursivePartial, +} from '../../../controllers/utils/mergeConfig'; + +type DebugArrowConfig = { + color: THREE.Color; + debug: boolean; +}; + +const defaultDebugArrowConfig = { + color: new THREE.Color(0xff0000), + debug: false, +}; + +export class DebugArrow { + config: DebugArrowConfig; + arrow: THREE.ArrowHelper; + + constructor(config?: RecursivePartial) { + this.config = mergeConfig(defaultDebugArrowConfig, config); + this.arrow = new THREE.ArrowHelper(); + this.arrow.setColor(this.config.color); + } + + getMesh(): THREE.ArrowHelper { + return this.arrow; + } + + update( + position: THREE.Vector3, + direction: THREE.Vector3, + length: number, + debug?: boolean, + ): void { + this.arrow.position.copy(position); + this.arrow.setDirection(direction.normalize()); + this.arrow.setLength(length); + if (debug) { + this.arrow.visible = debug; + } + } +} diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index f58ae827c..4d8d25fc7 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -23,6 +23,7 @@ import { interrupt } from './interrupt'; import { RobotConsole } from './engine/Core/RobotConsole'; import { getCamera } from './engine/Render/helpers/Camera'; import { createScene } from './engine/Render/helpers/Scene'; +import { Paper, type PaperConfig } from './controllers/environment/Paper'; const storedWorld = context.moduleContexts.robot_simulation.state?.world; @@ -109,6 +110,18 @@ export function createWall(physics: Physics, renderer: Renderer) { return environment; } +export function createPaper(render:Renderer, url:string, width: number, height: number) { + const paperConfig: PaperConfig = { + url, + mesh: { + width, + height, + }, + }; + const paper = new Paper(render, paperConfig); + return paper; +} + export function createCSE() { const code = context.unTypecheckedCode[0]; const program = new Program(code); diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index dae98820b..bf7883b88 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -20,6 +20,7 @@ export { createWorld, createWall, createEv3, + createPaper, createFloor, createCSE, addControllerToWorld, From a4a062404b3d72225f796531eee738d00239a0e8 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 11 Mar 2024 12:09:50 +0000 Subject: [PATCH 54/93] Massive refactor --- src/bundles/robot_simulation/config.ts | 156 +------------- .../controllers/environment/Cuboid.ts | 68 ++++++ .../controllers/environment/Floor.ts | 46 ---- .../controllers/environment/Paper.ts | 4 +- .../controllers/environment/Wall.ts | 46 ---- .../controllers/ev3/components/Chassis.ts | 4 +- .../controllers/ev3/components/Mesh.ts | 12 +- .../controllers/ev3/components/Motor.ts | 22 +- .../controllers/ev3/components/Wheel.ts | 6 +- .../controllers/ev3/ev3/default/config.ts | 155 +++++++++++++ .../controllers/ev3/ev3/default/defaultEv3.ts | 163 -------------- .../controllers/ev3/ev3/default/ev3.ts | 76 +++++++ .../controllers/ev3/ev3/default/types.ts | 87 ++++++++ .../controllers/ev3/sensor/ColorSensor.ts | 4 +- .../ev3/sensor/UltrasonicSensor.ts | 10 +- .../robot_simulation/controllers/index.ts | 5 +- .../engine/Entity/EntityFactory.ts | 18 +- .../robot_simulation/engine/Math/Vector.ts | 10 +- .../engine/Render/helpers/GLTF.ts | 12 +- .../engine/Render/helpers/MeshFactory.ts | 15 +- .../robot_simulation/helper_functions.ts | 203 ++++++++++++------ src/bundles/robot_simulation/index.ts | 8 +- .../components/TabPanels/ColorSensorPanel.tsx | 2 +- .../TabPanels/UltrasonicSensorPanel.tsx | 2 +- 24 files changed, 590 insertions(+), 544 deletions(-) create mode 100644 src/bundles/robot_simulation/controllers/environment/Cuboid.ts delete mode 100644 src/bundles/robot_simulation/controllers/environment/Floor.ts delete mode 100644 src/bundles/robot_simulation/controllers/environment/Wall.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts diff --git a/src/bundles/robot_simulation/config.ts b/src/bundles/robot_simulation/config.ts index 689db4a7e..b8c9bea5f 100644 --- a/src/bundles/robot_simulation/config.ts +++ b/src/bundles/robot_simulation/config.ts @@ -1,158 +1,4 @@ -import { type PhysicsConfig } from './engine/Physics'; -import { type CameraOptions } from './engine/Render/helpers/Camera'; -import { type RenderConfig } from './engine/Render/Renderer'; -import { type SimpleVector } from './engine/Math/Vector'; -import { type MeshConfig } from './controllers/ev3/components/Mesh'; -import type { ChassisWrapperConfig } from './controllers/ev3/components/Chassis'; -import type { MotorConfig } from './controllers/ev3/components/Motor'; -import type { ColorSensorConfig } from './controllers/ev3/sensor/ColorSensor'; - -const tinyConstant = 0.012; - -export const physicsConfig: PhysicsConfig = { - gravity: { - x: 0, - y: -9.81, - z: 0, - }, - timestep: 1 / 20, -}; - -export const renderConfig: RenderConfig = { +export const sceneConfig = { width: 900, height: 500, - control: 'orbit', -}; - -export const sceneCamera: CameraOptions = { - type: 'perspective', - aspect: renderConfig.width / renderConfig.height, - fov: 75, - near: 0.1, - far: 1000, -}; - -export const chassisConfig: ChassisWrapperConfig = { - orientation: { - position: { - x: 0, - y: 0.0775, - z: 0, - }, - rotation: { - x: 0, - y: 0, - z: 0, - w: 0, - }, - }, - mass: 0.6, - height: 0.095, - width: 0.145, - length: 0.18, - type: 'dynamic', - debug: true, -}; - -export const meshConfig: Omit = { - width: chassisConfig.width, - height: chassisConfig.height, - length: chassisConfig.length, - offset: { - y: 0.02, - }, -}; - - -export const wheelDisplacements: Record = { - frontLeftWheel: { - x: -(chassisConfig.width / 2), - y: -(chassisConfig.height / 2), - z: chassisConfig.length / 2 - tinyConstant, - }, - - frontRightWheel: { - x: chassisConfig.width / 2, - y: -(chassisConfig.height / 2), - z: chassisConfig.length / 2 - tinyConstant, - }, - backLeftWheel: { - x: -(chassisConfig.width / 2), - y: -(chassisConfig.height / 2), - z: -(chassisConfig.length / 2 - tinyConstant), - }, - backRightWheel: { - x: chassisConfig.width / 2, - y: -(chassisConfig.height / 2), - z: -(chassisConfig.length / 2 - tinyConstant), - }, -}; - -export const wheelPidConfig = { - proportionalGain: 27, - integralGain: 8, - derivativeGain: 40, -}; - -export const wheelMeshConfig: Omit = { - width: 0.028, - height: 0.0575, - length: 0.0575, -}; - -export const motorDisplacements = { - leftMotor: { - x: 0.058, - y: 0, - z: 0.03, - }, - rightMotor: { - x: -0.058, - y: 0, - z: 0.03, - }, -}; - -export const motorPidConfig = { - proportionalGain: 0.25, - derivativeGain: 0, - integralGain: 0, -}; - - -export const colorSensorConfig: { displacement: SimpleVector, config: ColorSensorConfig } = { - displacement: { - x: 0.04, - y: -(chassisConfig.height / 2), - z: 0.01, - }, - config: { - size: { - height: 16, - width: 16, - }, - camera: { - type: 'perspective', - aspect: 1, - fov: 10, - near: 0.01, - far: 1, - }, - tickRateInSeconds: 0.1, - debug: true, - }, -}; - -export const ultrasonicSensorConfig = { - displacement: { - x: 0.04, - y: 0, - z: 0.01, - }, - direction: { - x: 0, - y: 0, - z: 1, - }, - debug: true, }; diff --git a/src/bundles/robot_simulation/controllers/environment/Cuboid.ts b/src/bundles/robot_simulation/controllers/environment/Cuboid.ts new file mode 100644 index 000000000..0bc6f23ad --- /dev/null +++ b/src/bundles/robot_simulation/controllers/environment/Cuboid.ts @@ -0,0 +1,68 @@ +import * as THREE from 'three'; + +import { + EntityFactory, + MeshFactory, + type Physics, + type Renderer, +} from '../../engine'; +import type { Dimension, SimpleVector } from '../../engine/Math/Vector'; +import type { RenderCuboidOptions } from '../../engine/Render/helpers/MeshFactory'; +import type { + EntityCuboidOptions, + RigidBodyType, +} from '../../engine/Entity/EntityFactory'; + +export type CuboidConfig = { + position: SimpleVector; + dimension: Dimension; + mass: number; + color: string | number; + type: RigidBodyType; +}; + +const noRotation = { + x: 0, + y: 0, + z: 0, + w: 1, +}; + +export class Cuboid { + physics: Physics; + render: Renderer; + config: CuboidConfig; + + constructor(physics: Physics, renderer: Renderer, config: CuboidConfig) { + this.physics = physics; + this.render = renderer; + this.config = config; + + const renderCuboidOption: RenderCuboidOptions = { + orientation: { + position: config.position, + rotation: noRotation, + }, + dimension: config.dimension, + color: new THREE.Color(config.color), + debug: false, + }; + + const mesh = MeshFactory.addCuboid(renderCuboidOption); + this.render.add(mesh); + } + + start() { + const entityCuboidOption: EntityCuboidOptions = { + orientation: { + position: this.config.position, + rotation: noRotation, + }, + dimension: this.config.dimension, + mass: this.config.mass, + type: this.config.type, + }; + + EntityFactory.addCuboid(this.physics, entityCuboidOption); + } +} diff --git a/src/bundles/robot_simulation/controllers/environment/Floor.ts b/src/bundles/robot_simulation/controllers/environment/Floor.ts deleted file mode 100644 index 1d3c80843..000000000 --- a/src/bundles/robot_simulation/controllers/environment/Floor.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { type Physics, type Controller, type Renderer, EntityFactory, MeshFactory } from '../../engine'; -import * as THREE from 'three'; - -import { type EntityCuboidOptions } from '../../engine/Entity/EntityFactory'; -import { type RenderCuboidOptions } from '../../engine/Render/helpers/MeshFactory'; - -export const floorConfig: EntityCuboidOptions & RenderCuboidOptions = { - debug: false, - orientation: { - position: { - x: 0, - y: -0.5, - z: 0, - }, - rotation: { - x: 0, - y: 0, - z: 0, - w: 0, - }, - }, - mass: 1, - height: 1, - width: 20, - length: 20, - color: new THREE.Color('white'), - type: 'fixed', -}; - - -export class Floor implements Controller { - private physics: Physics; - private renderer: Renderer; - private mesh: THREE.Mesh; - - constructor(physics:Physics, renderer: Renderer) { - this.physics = physics; - this.renderer = renderer; - this.mesh = MeshFactory.addCuboid(floorConfig); - } - - async start(): Promise { - EntityFactory.addCuboid(this.physics, floorConfig); - this.renderer.add(this.mesh); - } -} diff --git a/src/bundles/robot_simulation/controllers/environment/Paper.ts b/src/bundles/robot_simulation/controllers/environment/Paper.ts index 2151ec13a..08ab66a13 100644 --- a/src/bundles/robot_simulation/controllers/environment/Paper.ts +++ b/src/bundles/robot_simulation/controllers/environment/Paper.ts @@ -4,7 +4,7 @@ import type { Renderer } from '../../engine'; export type PaperConfig = { url: string; - mesh: { + dimension: { width: number; height: number; }; @@ -19,7 +19,7 @@ export class Paper { this.render = render; this.config = config; - const plane = new THREE.PlaneGeometry(this.config.mesh.width, this.config.mesh.height); // Creating a 1x1 plane for the carpet + const plane = new THREE.PlaneGeometry(this.config.dimension.width, this.config.dimension.height); // Creating a 1x1 plane for the carpet this.paper = new THREE.Mesh(plane); } diff --git a/src/bundles/robot_simulation/controllers/environment/Wall.ts b/src/bundles/robot_simulation/controllers/environment/Wall.ts deleted file mode 100644 index 755078172..000000000 --- a/src/bundles/robot_simulation/controllers/environment/Wall.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { type Physics, type Controller, type Renderer, EntityFactory, MeshFactory } from '../../engine'; -import * as THREE from 'three'; - -import { type EntityCuboidOptions } from '../../engine/Entity/EntityFactory'; -import { type RenderCuboidOptions } from '../../engine/Render/helpers/MeshFactory'; - -export const wallConfig: EntityCuboidOptions & RenderCuboidOptions = { - debug: false, - orientation: { - position: { - x: 0, - y: 1, - z: 1, - }, - rotation: { - x: 0, - y: 0, - z: 0, - w: 0, - }, - }, - mass: 1, - height: 2, - width: 1, - length: 0.1, - color: new THREE.Color('yellow'), - type: 'fixed', -}; - - -export class Wall implements Controller { - private physics: Physics; - private renderer: Renderer; - private mesh: THREE.Mesh; - - constructor(physics:Physics, renderer: Renderer) { - this.physics = physics; - this.renderer = renderer; - this.mesh = MeshFactory.addCuboid(wallConfig); - } - - async start(): Promise { - EntityFactory.addCuboid(this.physics, wallConfig); - this.renderer.add(this.mesh); - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts index db63ae656..a721918ec 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -41,9 +41,7 @@ export class ChassisWrapper implements Controller { // Debug mesh. this.debugMesh = MeshFactory.addCuboid({ orientation: config.orientation, - width: config.width, - height: config.height, - length: config.length, + dimension: config.dimension, color: new THREE.Color(0x00ff00), debug: true, }); diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index 3b9274b79..bb9640964 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -3,13 +3,11 @@ import { type Controller, type Renderer } from '../../../engine'; import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { type ChassisWrapper } from './Chassis'; import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; -import type { SimpleVector } from '../../../engine/Math/Vector'; +import type { Dimension, SimpleVector } from '../../../engine/Math/Vector'; export type MeshConfig = { url: string; - width: number; - height: number; - length: number; + dimension: Dimension; offset?: Partial; }; @@ -41,11 +39,7 @@ export class Mesh implements Controller { } async start(): Promise { - this.mesh = await loadGLTF(this.config.url, { - x: this.config.width + this.offset.x, - y: this.config.height + this.offset.y, - z: this.config.length + this.offset.z, - }); + this.mesh = await loadGLTF(this.config.url, this.config.dimension); this.render.add(this.mesh.scene); } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts index e03bca2b0..b95f3712d 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -1,5 +1,5 @@ import { type Controller, type Physics, type Renderer } from '../../../engine'; -import { type SimpleVector } from '../../../engine/Math/Vector'; +import { type Dimension, type SimpleVector } from '../../../engine/Math/Vector'; import { vec3 } from '../../../engine/Math/Convert'; import { VectorPidController } from '../feedback_control/PidController'; import type * as THREE from 'three'; @@ -14,6 +14,7 @@ import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; type WheelSide = 'left' | 'right'; export type MotorConfig = { + displacement: SimpleVector; pid: { proportionalGain: number; derivativeGain: number; @@ -21,9 +22,7 @@ export type MotorConfig = { }; mesh: { url: string; - width: number; - height: number; - length: number; + dimension: Dimension; }; }; /** @@ -50,19 +49,18 @@ export class Motor implements Controller { chassisWrapper: ChassisWrapper, physics: Physics, render: Renderer, - displacement: SimpleVector, config: MotorConfig, ) { this.chassisWrapper = chassisWrapper; this.physics = physics; this.render = render; - this.displacementVector = vec3(displacement); + this.displacementVector = vec3(config.displacement); this.config = config; this.pid = new VectorPidController(config.pid); this.motorVelocity = 0; this.meshRotation = 0; - this.wheelSide = displacement.x > 0 ? 'right' : 'left'; + this.wheelSide = config.displacement.x > 0 ? 'right' : 'left'; } setSpeedDistance(speed: number, distance: number) { @@ -74,11 +72,7 @@ export class Motor implements Controller { } async start(): Promise { - this.mesh = await loadGLTF(this.config.mesh.url, { - x: this.config.mesh.width, - y: this.config.mesh.height, - z: this.config.mesh.length, - }); + this.mesh = await loadGLTF(this.config.mesh.url, this.config.mesh.dimension); this.render.add(this.mesh.scene); } @@ -133,7 +127,7 @@ export class Motor implements Controller { // Calculate the new wheel position, adjusting the y-coordinate to half the mesh height const wheelPosition = chassisEntity.worldTranslation(this.displacementVector.clone()); - wheelPosition.y = this.config.mesh.height / 2; // Ensure the wheel is placed correctly vertically + wheelPosition.y = this.config.mesh.dimension.height / 2; // Ensure the wheel is placed correctly vertically // If mesh is loaded, update its position and orientation if (this.mesh) { @@ -141,7 +135,7 @@ export class Motor implements Controller { this.mesh.scene.quaternion.copy(chassisEntity.getRotation()); // Calculate rotation adjustment based on motor velocity and frame duration - const radiansPerFrame = 2 * (this.motorVelocity / this.config.mesh.height) * timingInfo.frameDuration / 1000; + const radiansPerFrame = 2 * (this.motorVelocity / this.config.mesh.dimension.height) * timingInfo.frameDuration / 1000; // Apply rotation changes to simulate wheel turning this.meshRotation += radiansPerFrame; diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index b1333a14e..b7a0984e7 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -7,12 +7,13 @@ import { type ChassisWrapper } from './Chassis'; import type { PhysicsTimingInfo } from '../../../engine/Physics'; import { DebugArrow } from '../../../engine/Render/debug/DebugArrow'; -type WheelConfig = { +export type WheelConfig = { pid: { proportionalGain: number; derivativeGain: number; integralGain: number; }; + displacement: SimpleVector; gapToFloor: number; maxRayDistance:number; debug: boolean; @@ -33,13 +34,12 @@ export class Wheel implements Controller { chassisWrapper: ChassisWrapper, physics: Physics, render: Renderer, - displacement: SimpleVector, config: WheelConfig, ) { this.chassisWrapper = chassisWrapper; this.physics = physics; this.render = render; - this.displacementVector = vec3(displacement); + this.displacementVector = vec3(config.displacement); this.config = config; this.pid = new NumberPidController(config.pid); diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts new file mode 100644 index 000000000..a6661ef9e --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts @@ -0,0 +1,155 @@ +import type { + Ev3ChassisConfig, + Ev3ColorSensorConfig, + Ev3Config, + Ev3MeshConfig, + Ev3MotorsConfig, + Ev3UltrasonicSenorConfig, + Ev3WheelsConfig, +} from './types'; + +const noRotation = { + x: 0, + y: 0, + z: 0, + w: 1, +}; + +const tinyConstant = 0.012; + +export const chassisConfig: Ev3ChassisConfig = { + orientation: { + position: { + x: 0, + y: 0.0775, + z: 0, + }, + rotation: noRotation, + }, + dimension: { + height: 0.095, + width: 0.145, + length: 0.18, + }, + mass: 0.6, + type: 'dynamic', + debug: true, +}; + +export const meshConfig: Ev3MeshConfig = { + url: 'https://keen-longma-3c1be1.netlify.app/6_remove_wheels.gltf', + dimension: chassisConfig.dimension, + offset: { + y: 0.02, + }, +}; + +export const wheelConfig: Ev3WheelsConfig = { + displacements: { + frontLeftWheel: { + x: -(chassisConfig.dimension.width / 2), + y: -(chassisConfig.dimension.height / 2), + z: chassisConfig.dimension.length / 2 - tinyConstant, + }, + + frontRightWheel: { + x: chassisConfig.dimension.width / 2, + y: -(chassisConfig.dimension.height / 2), + z: chassisConfig.dimension.length / 2 - tinyConstant, + }, + backLeftWheel: { + x: -(chassisConfig.dimension.width / 2), + y: -(chassisConfig.dimension.height / 2), + z: -(chassisConfig.dimension.length / 2 - tinyConstant), + }, + backRightWheel: { + x: chassisConfig.dimension.width / 2, + y: -(chassisConfig.dimension.height / 2), + z: -(chassisConfig.dimension.length / 2 - tinyConstant), + }, + }, + config: { + pid: { + proportionalGain: 27, + integralGain: 8, + derivativeGain: 40, + }, + gapToFloor: 0.03, + maxRayDistance: 0.05, + debug: true, + }, +}; + +export const motorConfig: Ev3MotorsConfig = { + config: { + pid: { + proportionalGain: 0.25, + derivativeGain: 0, + integralGain: 0, + }, + mesh: { + dimension: { + width: 0.028, + height: 0.0575, + length: 0.0575, + }, + url: 'https://keen-longma-3c1be1.netlify.app/6_wheel.gltf', + }, + }, + displacements: { + leftMotor: { + x: 0.058, + y: 0, + z: 0.03, + }, + rightMotor: { + x: -0.058, + y: 0, + z: 0.03, + }, + }, +}; + +export const colorSensorConfig: Ev3ColorSensorConfig = { + tickRateInSeconds: 0.1, + displacement: { + x: 0.04, + y: -(chassisConfig.dimension.height / 2), + z: 0.01, + }, + size: { + height: 16, + width: 16, + }, + camera: { + type: 'perspective', + aspect: 1, + fov: 10, + near: 0.01, + far: 1, + }, + debug: true, +}; + +const ultrasonicSensorConfig: Ev3UltrasonicSenorConfig = { + displacement: { + x: 0.04, + y: 0, + z: 0.01, + }, + direction: { + x: 0, + y: 0, + z: 1, + }, + debug: true, +}; + +export const ev3Config: Ev3Config = { + chassis: chassisConfig, + motors: motorConfig, + wheels: wheelConfig, + colorSensor: colorSensorConfig, + ultrasonicSensor: ultrasonicSensorConfig, + mesh: meshConfig, +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3.ts deleted file mode 100644 index 22f4bef00..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { type Physics, type Renderer, ControllerMap } from '../../../../engine'; -import { type SimpleVector } from '../../../../engine/Math/Vector'; -import { type PIDConfig } from '../../feedback_control/PidController'; - -import { Wheel } from '../../components/Wheel'; -import { Motor, type MotorConfig } from '../../components/Motor'; -import { ColorSensor, type ColorSensorConfig } from '../../sensor/ColorSensor'; -import { Mesh, type MeshConfig } from '../../components/Mesh'; -import { ChassisWrapper, type ChassisWrapperConfig } from '../../components/Chassis'; -import { UltrasonicSensor } from '../../sensor/UltrasonicSensor'; - -type WheelControllers = { - frontLeftWheel: Wheel; - frontRightWheel: Wheel; - backLeftWheel: Wheel; - backRightWheel: Wheel; -}; - -type MotorControllers = { - leftMotor: Motor; - rightMotor: Motor; -}; - -type SensorControllers = { - colorSensor: ColorSensor; - ultrasonicSensor: UltrasonicSensor; -}; - -type ChassisControllers = { - mesh: Mesh; - chassis: ChassisWrapper; -}; - -type DefaultEv3Controller = WheelControllers & -MotorControllers & -SensorControllers & -ChassisControllers; - -export type Ev3Config = { - chassis: ChassisWrapperConfig; - wheel: { - displacements: Record; - pid: PIDConfig; - gapToFloor: number; - maxRayDistance: number; - }; - motor: MotorConfig & { - displacements: Record; - }; - colorSensor: { - displacement: SimpleVector; - config: ColorSensorConfig; - }; - ultrasonicSensor: { - displacement: SimpleVector; - direction: SimpleVector; - debug: boolean; - }; - mesh: MeshConfig; -}; - -export type DefaultEv3 = ControllerMap; - -export const createDefaultEv3 = ( - physics: Physics, - render: Renderer, - config: Ev3Config, -): DefaultEv3 => { - const chassis = new ChassisWrapper(physics, render, config.chassis); - const mesh = new Mesh(chassis, render, config.mesh); - - const wheelPidConfig = { - pid: config.wheel.pid, - gapToFloor: config.wheel.gapToFloor, - maxRayDistance: config.wheel.maxRayDistance, - debug: true, - }; - - const frontLeftWheel = new Wheel( - chassis, - physics, - render, - config.wheel.displacements.frontLeftWheel, - wheelPidConfig, - ); - - const frontRightWheel = new Wheel( - chassis, - physics, - render, - config.wheel.displacements.frontRightWheel, - wheelPidConfig, - ); - - const backLeftWheel = new Wheel( - chassis, - physics, - render, - config.wheel.displacements.backLeftWheel, - wheelPidConfig, - ); - const backRightWheel = new Wheel( - chassis, - physics, - render, - config.wheel.displacements.backRightWheel, - wheelPidConfig, - ); - - // Motors - const motorPidConfig = { - pid: config.motor.pid, - mesh: config.motor.mesh, - }; - - const leftMotor = new Motor( - chassis, - physics, - render, - config.motor.displacements.leftMotor, - motorPidConfig, - ); - - const rightMotor = new Motor( - chassis, - physics, - render, - config.motor.displacements.rightMotor, - motorPidConfig, - ); - - // Sensors - const colorSensor = new ColorSensor( - chassis, - render, - config.colorSensor.displacement, - config.colorSensor.config, - ); - - const ultrasonicSensor = new UltrasonicSensor( - chassis, - physics, - render, - config.ultrasonicSensor.displacement, - config.ultrasonicSensor.direction, - { debug: config.ultrasonicSensor.debug }, - ); - - const ev3: DefaultEv3 = new ControllerMap({ - frontLeftWheel, - frontRightWheel, - backLeftWheel, - backRightWheel, - leftMotor, - rightMotor, - colorSensor, - ultrasonicSensor, - mesh, - chassis, - }); - - return ev3; -}; diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts new file mode 100644 index 000000000..4892e6bae --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts @@ -0,0 +1,76 @@ +import { type Physics, type Renderer, ControllerMap } from '../../../../engine'; + +import { ChassisWrapper } from '../../components/Chassis'; +import { Mesh } from '../../components/Mesh'; +import { Wheel, type WheelConfig } from '../../components/Wheel'; +import { Motor, type MotorConfig } from '../../components/Motor'; +import { ColorSensor } from '../../sensor/ColorSensor'; +import { UltrasonicSensor } from '../../sensor/UltrasonicSensor'; + +import { + wheelNames, + motorNames, + type DefaultEv3Controller, + type Ev3Config, + type WheelControllers, + type MotorControllers, +} from './types'; + +export type DefaultEv3 = ControllerMap; + +export const createDefaultEv3 = ( + physics: Physics, + render: Renderer, + config: Ev3Config, +): DefaultEv3 => { + const chassis = new ChassisWrapper(physics, render, config.chassis); + const mesh = new Mesh(chassis, render, config.mesh); + + const wheelControllers = wheelNames.reduce((acc, name) => { + const displacement = config.wheels.displacements[name]; + const wheelConfig: WheelConfig = { + ...config.wheels.config, + displacement, + }; + const wheel = new Wheel(chassis, physics, render, wheelConfig); + return { + ...acc, + [name]: wheel, + }; + }, {} as WheelControllers); + + // Motors + const motorControllers = motorNames.reduce((acc, name) => { + const displacement = config.motors.displacements[name]; + const motorConfig: MotorConfig = { + ...config.motors.config, + displacement, + }; + const motor = new Motor(chassis, physics, render, motorConfig); + return { + ...acc, + [name]: motor, + }; + }, {} as MotorControllers); + + // Sensors + const colorSensor = new ColorSensor(chassis, render, config.colorSensor); + + const ultrasonicSensor = new UltrasonicSensor( + chassis, + physics, + render, + config.ultrasonicSensor, + ); + + const ev3: DefaultEv3 = new ControllerMap({ + ...wheelControllers, + ...motorControllers, + colorSensor, + ultrasonicSensor, + mesh, + chassis, + }); + + return ev3; +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts new file mode 100644 index 000000000..dd2ae0dc9 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts @@ -0,0 +1,87 @@ +import type { SimpleVector } from '../../../../engine/Math/Vector'; +import type { ChassisWrapper, ChassisWrapperConfig } from '../../components/Chassis'; +import type { Mesh, MeshConfig } from '../../components/Mesh'; +import type { Motor, MotorConfig } from '../../components/Motor'; +import type { Wheel, WheelConfig } from '../../components/Wheel'; +import type { ColorSensor, ColorSensorConfig } from '../../sensor/ColorSensor'; +import type { UltrasonicSensor, UltrasonicSensorConfig } from '../../sensor/UltrasonicSensor'; + +// ######################### Controller Types ######################### +// Wheels +export const wheelNames = [ + 'frontLeftWheel', + 'frontRightWheel', + 'backLeftWheel', + 'backRightWheel', +] as const; +export type WheelNames = (typeof wheelNames)[number]; +export type WheelControllers = Record; + +// Motors +export const motorNames = ['leftMotor', 'rightMotor'] as const; +export type MotorNames = (typeof motorNames)[number]; +export type MotorControllers = Record; + +// Chassis +export const chassisNames = ['chassis'] as const; +export type ChassisNames = (typeof chassisNames)[number]; +export type ChassisControllers = Record; + +// Mesh +export const meshNames = ['mesh'] as const; +export type MeshNames = (typeof meshNames)[number]; +export type MeshControllers = Record; + +// ColorSensor +export const colorSensorNames = ['colorSensor'] as const; +export type ColorSensorNames = (typeof colorSensorNames)[number]; +export type ColorSensorControllers = Record; + +// UltrasonicSensor +export const ultrasonicSensorNames = ['ultrasonicSensor'] as const; +export type UltrasonicSensorNames = (typeof ultrasonicSensorNames)[number]; +export type UltrasonicSensorControllers = Record< +UltrasonicSensorNames, +UltrasonicSensor +>; + +// Aggregate +export const controllerNames = [ + ...wheelNames, + ...motorNames, + ...chassisNames, + ...meshNames, + ...colorSensorNames, + ...ultrasonicSensorNames, +] as const; +export type DefaultEv3ControllerNames = (typeof controllerNames)[number]; +export type DefaultEv3Controller = WheelControllers & +MotorControllers & +ColorSensorControllers & +UltrasonicSensorControllers & +ChassisControllers & +MeshControllers; + + +// ######################### Config Types ######################### +export type Ev3ChassisConfig = ChassisWrapperConfig; +export type Ev3MeshConfig = MeshConfig; +export type Ev3WheelsConfig = { + displacements: Record; + config: Omit; +}; +export type Ev3MotorsConfig = { + displacements: Record; + config: Omit; +}; +export type Ev3ColorSensorConfig = ColorSensorConfig; +export type Ev3UltrasonicSenorConfig = UltrasonicSensorConfig; + +export type Ev3Config = { + chassis: Ev3ChassisConfig + mesh: Ev3MeshConfig; + wheels: Ev3WheelsConfig; + motors: Ev3MotorsConfig; + colorSensor: Ev3ColorSensorConfig; + ultrasonicSensor: Ev3UltrasonicSenorConfig; +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts index 033af994c..01232db2d 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -17,6 +17,7 @@ export type ColorSensorConfig = { height: number; width: number; }; + displacement: SimpleVector; camera: CameraOptions; tickRateInSeconds: number; debug: boolean; @@ -36,11 +37,10 @@ export class ColorSensor implements Sensor { constructor( chassisWrapper: ChassisWrapper, render: Renderer, - displacement: SimpleVector, config: ColorSensorConfig, ) { this.chassisWrapper = chassisWrapper; - this.displacement = vec3(displacement); + this.displacement = vec3(config.displacement); this.config = config; this.camera = getCamera(config.camera); diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts index 814d2088d..6399d2ebd 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -5,7 +5,9 @@ import { vec3 } from '../../../engine/Math/Convert'; import { type Renderer, type Physics } from '../../../engine'; import * as THREE from 'three'; -type UltrasonicSensorConfig = { +export type UltrasonicSensorConfig = { + displacement: SimpleVector; + direction: SimpleVector; debug: boolean; }; @@ -23,15 +25,13 @@ export class UltrasonicSensor implements Sensor { chassis: ChassisWrapper, physics: Physics, render: Renderer, - displacement: SimpleVector, - direction: SimpleVector, config: UltrasonicSensorConfig, ) { this.chassisWrapper = chassis; this.physics = physics; this.render = render; - this.displacement = vec3(displacement); - this.direction = vec3(direction); + this.displacement = vec3(config.displacement); + this.direction = vec3(config.direction); this.config = config; // Debug arrow diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts index 0a5ec5249..79d8a2c2b 100644 --- a/src/bundles/robot_simulation/controllers/index.ts +++ b/src/bundles/robot_simulation/controllers/index.ts @@ -1,4 +1 @@ -export { Floor } from './environment/Floor'; -export { type DefaultEv3 } from './ev3/ev3/default/defaultEv3'; - -export { Wall } from './environment/Wall'; +export { type DefaultEv3 } from './ev3/ev3/default/ev3'; diff --git a/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts b/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts index 65c1700d4..1e52aef5d 100644 --- a/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts +++ b/src/bundles/robot_simulation/engine/Entity/EntityFactory.ts @@ -1,23 +1,27 @@ -import type { Orientation } from '../Math/Vector'; +import type { Dimension, Orientation } from '../Math/Vector'; import { type Physics } from '../Physics'; import { Entity } from './Entity'; -type RigidBodyTypes = 'fixed' | 'dynamic'; +export const rigidBodyTypes = ['fixed', 'dynamic'] as const; + +export type RigidBodyType = typeof rigidBodyTypes[number]; export type EntityCuboidOptions = { orientation: Orientation; - width: number; - height: number; - length: number; + dimension: Dimension; mass: number; - type: RigidBodyTypes; + type: RigidBodyType; }; +export function isRigidBodyType(bodyType: string): bodyType is RigidBodyType { + return rigidBodyTypes.includes(bodyType as RigidBodyType); +} + export function addCuboid( physics: Physics, options: EntityCuboidOptions, ): Entity { - const { orientation, width, height, length, type, mass } = options; + const { orientation, dimension: { width, height, length }, type, mass } = options; const rigidBodyDesc = physics.RAPIER.RigidBodyDesc[type](); const colliderDesc = physics.RAPIER.ColliderDesc.cuboid( diff --git a/src/bundles/robot_simulation/engine/Math/Vector.ts b/src/bundles/robot_simulation/engine/Math/Vector.ts index fb00bbd12..bc9fadc5f 100644 --- a/src/bundles/robot_simulation/engine/Math/Vector.ts +++ b/src/bundles/robot_simulation/engine/Math/Vector.ts @@ -1,11 +1,17 @@ -export type SimpleVector = { x: number, y: number, z: number }; -export type SimpleQuaternion = { x: number, y: number, z: number, w: number }; +export type SimpleVector = { x: number; y: number; z: number }; +export type SimpleQuaternion = { x: number; y: number; z: number; w: number }; export type Orientation = { position: SimpleVector; rotation: SimpleQuaternion; }; +export type Dimension = { + height: number; + width: number; + length: number; +}; + // Vector3 and Quaternion already extends SimpleVector and SimpleQuaternion // so we can just add the interface to the existing declaration declare module 'three' { diff --git a/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts b/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts index dc0ab8fd5..10b86918e 100644 --- a/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts +++ b/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts @@ -1,11 +1,11 @@ import * as THREE from 'three'; // eslint-disable-next-line import/extensions import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; -import type { SimpleVector } from '../../Math/Vector'; +import type { Dimension } from '../../Math/Vector'; -type GLTFLoaderOptions = SimpleVector; +type GLTFLoaderOptions = Dimension; -export function loadGLTF(url: string, targetSize: GLTFLoaderOptions): Promise { +export function loadGLTF(url: string, dimension: GLTFLoaderOptions): Promise { const loader = new GLTFLoader(); return new Promise((resolve, reject) => { loader.load( @@ -16,9 +16,9 @@ export function loadGLTF(url: string, targetSize: GLTFLoaderOptions): Promise World) { if (storedWorld !== undefined) { diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index bf7883b88..d47e4baef 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -13,9 +13,13 @@ export { } from './ev3_functions'; export { - init_simulation, - createRenderer, + // Physics + createCustomPhysics, createPhysics, + // Renderer + createRenderer, + init_simulation, + createCuboid, createTimer, createWorld, createWall, diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx index 1ddf139e6..cae283947 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { const colorSensor = ev3.get('colorSensor'); diff --git a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx index 44e5104c1..4bcfd0a1b 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/defaultEv3'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; export const UltrasonicSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { const ultrasonicSensor = ev3.get('ultrasonicSensor'); From c7610cfb6c9234830b0573a9cfadf1c6202cbbb9 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 11 Mar 2024 12:10:22 +0000 Subject: [PATCH 55/93] Fix Mesh Factory test --- .../engine/__tests__/Render/MeshFactory.ts | 188 ++++++++++-------- .../robot_simulation/helper_functions.ts | 6 +- .../components/Simulation/index.tsx | 3 +- 3 files changed, 105 insertions(+), 92 deletions(-) diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts b/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts index 0cf967675..d30a37108 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts @@ -1,92 +1,99 @@ -import * as THREE from 'three'; -import {addCuboid} from '../../Render/helpers/MeshFactory'; - +import * as THREE from "three"; +import { addCuboid } from "../../Render/helpers/MeshFactory"; // Mock the necessary Three.js methods and classes -jest.mock('three', () => { - const originalModule = jest.requireActual('three'); +jest.mock("three", () => { + const originalModule = jest.requireActual("three"); + + class Vector3 { + x: number; + y: number; + z: number; + constructor(x = 0, y = 0, z = 0) { + this.x = x; + this.y = y; + this.z = z; + } + + copy(vector) { + this.x = vector.x; + this.y = vector.y; + this.z = vector.z; + return this; // Return this for chaining + } + } + + class Quaternion { + x: number; + y: number; + z: number; + w: number; + constructor(x = 0, y = 0, z = 0, w = 1) { + // Default w to 1 for a neutral quaternion + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } - class Vector3 { - x:number; y:number; z:number;; - constructor(x = 0, y = 0, z = 0) { - this.x = x; - this.y = y; - this.z = z; - } - - copy(vector) { - this.x = vector.x; - this.y = vector.y; - this.z = vector.z; - return this; // Return this for chaining - } - } - - - class Quaternion { - x:number; y:number; z:number; w: number; - constructor(x = 0, y = 0, z = 0, w = 1) { // Default w to 1 for a neutral quaternion - this.x = x; - this.y = y; - this.z = z; - this.w = w; - } - - copy(quaternion) { - this.x = quaternion.x; - this.y = quaternion.y; - this.z = quaternion.z; - this.w = quaternion.w; - return this; // Return this for chaining - } - } - - return { - ...originalModule, - BoxGeometry: jest.fn(), - MeshPhysicalMaterial: jest.fn(), - Mesh: jest.fn().mockImplementation(function (geometry, material) { - this.geometry = geometry; - this.material = material; - this.position = new Vector3(); - this.quaternion = new Quaternion(); - }), - Vector3: Vector3, - Quaternion: Quaternion, - Color: jest.fn().mockImplementation(function (color) { - return { color }; - }), + copy(quaternion) { + this.x = quaternion.x; + this.y = quaternion.y; + this.z = quaternion.z; + this.w = quaternion.w; + return this; // Return this for chaining + } + } + + return { + ...originalModule, + BoxGeometry: jest.fn(), + MeshPhysicalMaterial: jest.fn(), + Mesh: jest.fn().mockImplementation(function (geometry, material) { + this.geometry = geometry; + this.material = material; + this.position = new Vector3(); + this.quaternion = new Quaternion(); + }), + Vector3: Vector3, + Quaternion: Quaternion, + Color: jest.fn().mockImplementation(function (color) { + return { color }; + }), + }; +}); + +describe("addCuboid", () => { + it("creates a cuboid with the correct dimensions and color", () => { + const orientation = { + position: new THREE.Vector3(1, 2, 3), + rotation: new THREE.Quaternion(0, 0, 0, 1), }; - }); - + const width = 4; + const height = 5; + const length = 6; + const color = new THREE.Color("red"); + const debug = false; - describe('addCuboid', () => { - it('creates a cuboid with the correct dimensions and color', () => { - const orientation = { - position: new THREE.Vector3(1, 2, 3), - rotation: new THREE.Quaternion(0, 0, 0, 1), - }; - const width = 4; - const height = 5; - const length = 6; - const color = new THREE.Color('red'); - const debug = false; - - const mesh = addCuboid({ orientation, width, height, length, color, debug }); - - expect(THREE.BoxGeometry).toHaveBeenCalledWith(width, height, length); - expect(THREE.MeshPhysicalMaterial).toHaveBeenCalledWith({ - color, - side: THREE.DoubleSide, - }); - expect(mesh).toHaveProperty('geometry'); - expect(mesh).toHaveProperty('material'); - expect(mesh.position).toEqual({ x: 1, y: 2, z: 3 }); - expect(mesh.quaternion).toEqual({ x: 0, y: 0, z: 0, w: 1 }); + const mesh = addCuboid({ + orientation, + dimension: { width, height, length }, + color, + debug, + }); + + expect(THREE.BoxGeometry).toHaveBeenCalledWith(width, height, length); + expect(THREE.MeshPhysicalMaterial).toHaveBeenCalledWith({ + color, + side: THREE.DoubleSide, }); - - - it('creates a cuboid with wireframe material when debug is true', () => { + expect(mesh).toHaveProperty("geometry"); + expect(mesh).toHaveProperty("material"); + expect(mesh.position).toEqual({ x: 1, y: 2, z: 3 }); + expect(mesh.quaternion).toEqual({ x: 0, y: 0, z: 0, w: 1 }); + }); + + it("creates a cuboid with wireframe material when debug is true", () => { const orientation = { position: new THREE.Vector3(1, 2, 3), rotation: new THREE.Quaternion(0, 0, 0, 1), @@ -94,10 +101,15 @@ jest.mock('three', () => { const width = 4; const height = 5; const length = 6; - const color = new THREE.Color('red'); + const color = new THREE.Color("red"); const debug = true; // Enable debug mode - const mesh = addCuboid({ orientation, width, height, length, color, debug }); + const mesh = addCuboid({ + orientation, + dimension: { width, height, length }, + color, + debug, + }); // Check that the BoxGeometry was called with the correct dimensions expect(THREE.BoxGeometry).toHaveBeenCalledWith(width, height, length); @@ -109,9 +121,9 @@ jest.mock('three', () => { }); // Verify the mesh properties - expect(mesh).toHaveProperty('geometry'); - expect(mesh).toHaveProperty('material'); + expect(mesh).toHaveProperty("geometry"); + expect(mesh).toHaveProperty("material"); expect(mesh.position).toEqual({ x: 1, y: 2, z: 3 }); expect(mesh.quaternion).toEqual({ x: 0, y: 0, z: 0, w: 1 }); }); - }); \ No newline at end of file +}); diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index 3893f807c..0367a2926 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -12,7 +12,7 @@ import { getCamera, type CameraOptions } from './engine/Render/helpers/Camera'; import { createScene } from './engine/Render/helpers/Scene'; import { Paper, type PaperConfig } from './controllers/environment/Paper'; import type { PhysicsConfig } from './engine/Physics'; -import { isRigidBodyType } from './engine/Entity/EntityFactory'; +import { isRigidBodyType, type RigidBodyType } from './engine/Entity/EntityFactory'; import { Cuboid, type CuboidConfig } from './controllers/environment/Cuboid'; import { ev3Config } from './controllers/ev3/ev3/default/config'; import { sceneConfig } from './config'; @@ -121,6 +121,8 @@ export function createCuboid( throw new Error('Invalid body type'); } + const narrowedBodyType = bodyType as RigidBodyType; + const config: CuboidConfig = { position: { x: position_x, @@ -134,7 +136,7 @@ export function createCuboid( }, mass, color, - type: bodyType, + type: narrowedBodyType, }; const cuboid = new Cuboid(physics, renderer, config); diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 73df77bf3..6efdc40d4 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -66,8 +66,7 @@ export default function SimulationCanvas({ } if (sensorRef.current) { - sensorRef.current.replaceChildren(ev3.get('colorSensor') - .getElement()); + sensorRef.current.replaceChildren(ev3.get('colorSensor').renderer.getElement()); } }; From d219432a132da041500d510ff6011b02ba853d04 Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 12 Mar 2024 02:57:43 +0000 Subject: [PATCH 56/93] Update tests --- .../controllers/ev3/components/Chassis.ts | 2 +- .../controllers/ev3/components/Mesh.ts | 2 +- .../controllers/ev3/sensor/types.ts | 2 +- .../robot_simulation/engine/Entity/Entity.ts | 71 +++--- .../robot_simulation/engine/Math/Convert.ts | 7 +- .../robot_simulation/engine/Math/Vector.ts | 4 + .../engine/__tests__/Entity/Entity.ts | 241 ++++++++++++++++++ .../__tests__/Render/{ => helpers}/Camera.ts | 2 +- 8 files changed, 285 insertions(+), 46 deletions(-) create mode 100644 src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts rename src/bundles/robot_simulation/engine/__tests__/Render/{ => helpers}/Camera.ts (94%) diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts index a721918ec..00a91cffc 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -63,7 +63,7 @@ export class ChassisWrapper implements Controller { update(): void { const chassisEntity = this.getEntity(); - this.debugMesh.position.copy(chassisEntity.getPosition()); + this.debugMesh.position.copy(chassisEntity.getTranslation()); this.debugMesh.quaternion.copy(chassisEntity.getRotation()); } } diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index bb9640964..7cb97033d 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -46,7 +46,7 @@ export class Mesh implements Controller { update() { const chassisEntity = this.chassisWrapper.getEntity(); - const chassisPosition = chassisEntity.getPosition(); + const chassisPosition = chassisEntity.getTranslation(); chassisPosition.x -= this.offset.x / 2; chassisPosition.y -= this.offset.y / 2; diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts index 1c1aab00e..d2b984345 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts @@ -1,3 +1,3 @@ import { type Controller } from '../../../engine'; -export type Sensor = Controller & { sense: () => T }; +export type Sensor = Controller & { sense: () => T }; diff --git a/src/bundles/robot_simulation/engine/Entity/Entity.ts b/src/bundles/robot_simulation/engine/Entity/Entity.ts index 371cd1c98..bd826c88d 100644 --- a/src/bundles/robot_simulation/engine/Entity/Entity.ts +++ b/src/bundles/robot_simulation/engine/Entity/Entity.ts @@ -1,9 +1,14 @@ -import * as THREE from 'three'; +import type * as THREE from 'three'; import type Rapier from '@dimforge/rapier3d-compat'; -import { type Orientation, type SimpleQuaternion, type SimpleVector } from '../Math/Vector'; -import { vec3, quat } from '../Math/Convert'; - -type BodyConfiguration = { +import { + simpleVectorLength, + type Orientation, + type SimpleQuaternion, + type SimpleVector, +} from '../Math/Vector'; +import { vec3 } from '../Math/Convert'; + +type EntityConfig = { rapierRigidBody: Rapier.RigidBody; rapierCollider: Rapier.Collider; }; @@ -12,7 +17,7 @@ export class Entity { #rapierRigidBody: Rapier.RigidBody; #rapierCollider: Rapier.Collider; - constructor(configuration: BodyConfiguration) { + constructor(configuration: EntityConfig) { this.#rapierRigidBody = configuration.rapierRigidBody; this.#rapierCollider = configuration.rapierCollider; } @@ -25,7 +30,7 @@ export class Entity { return this.#rapierRigidBody; } - getPosition(): SimpleVector { + getTranslation(): SimpleVector { return this.#rapierRigidBody.translation(); } @@ -33,64 +38,52 @@ export class Entity { return this.#rapierRigidBody.rotation(); } - setOrientation(orientation: Orientation) { + setOrientation(orientation: Orientation): void { this.#rapierRigidBody.setTranslation(orientation.position, true); this.#rapierRigidBody.setRotation(orientation.rotation, true); } - setMass(mass: number) { + setMass(mass: number): void { this.#rapierCollider.setMass(mass); } getMass(): number { return this.#rapierCollider.mass(); } - - applyImpulse( - impulse: THREE.Vector3, - point: THREE.Vector3 = new THREE.Vector3(), - ) { - return this.#rapierRigidBody.applyImpulseAtPoint(impulse, point, true); - } - - rotation(): THREE.Quaternion { - return quat(this.#rapierRigidBody.rotation()); - } - - velocity(): THREE.Vector3 { - return vec3(this.#rapierRigidBody.linvel()); + getVelocity(): SimpleVector { + return this.#rapierRigidBody.linvel(); } - angularVelocity(): THREE.Vector3 { - return vec3(this.#rapierRigidBody.angvel()); + getAngularVelocity(): SimpleVector { + return this.#rapierRigidBody.angvel(); } - translation(): THREE.Vector3 { - return vec3(this.#rapierRigidBody.translation()); + applyImpulse(impulse: SimpleVector, point: SimpleVector): void { + return this.#rapierRigidBody.applyImpulseAtPoint(impulse, point, true); } worldTranslation( - localTranslation: THREE.Vector3 = new THREE.Vector3(), + localTranslation: THREE.Vector3, ): THREE.Vector3 { - const rotation = this.rotation(); - const translation = this.translation(); + const rotation = this.getRotation(); + const translation = this.getTranslation(); return localTranslation.applyQuaternion(rotation) .add(translation); } transformDirection(localDirection: THREE.Vector3): THREE.Vector3 { - const rotation = this.rotation(); + const rotation = this.getRotation(); return localDirection.clone() .applyQuaternion(rotation); } distanceVectorOfPointToRotationalAxis( - localPoint: THREE.Vector3 = new THREE.Vector3(), - ) { + localPoint: THREE.Vector3, + ): SimpleVector { return localPoint .clone() - .projectOnVector(this.angularVelocity()) + .projectOnVector(vec3(this.getAngularVelocity())) .negate() .add(localPoint); } @@ -100,17 +93,17 @@ export class Entity { * @param {THREE.Vector3} localPoint - The point for which to calculate the tangential velocity. * @returns {THREE.Vector3} The tangential velocity vector of the point. */ - tangentialVelocityOfPoint(localPoint = new THREE.Vector3()): THREE.Vector3 { + tangentialVelocityOfPoint(localPoint): THREE.Vector3 { // Calculate the distance vector from the point to the rotational axis const distanceVector = this.distanceVectorOfPointToRotationalAxis(localPoint); // Retrieve the angular velocity of the system - const angularVelocity = this.angularVelocity(); + const angularVelocity = this.getAngularVelocity(); // Calculate the magnitude of the tangential velocity const velocityMagnitude - = distanceVector.length() * angularVelocity.length(); + = simpleVectorLength(distanceVector) * simpleVectorLength(angularVelocity); // Calculate the tangential velocity vector const tangentialVelocity = this.transformDirection(localPoint) @@ -124,9 +117,9 @@ export class Entity { } worldVelocity( - localPoint: THREE.Vector3 = new THREE.Vector3(), + localPoint: THREE.Vector3, ): THREE.Vector3 { return this.tangentialVelocityOfPoint(localPoint) - .add(this.velocity()); + .add(this.getVelocity()); } } diff --git a/src/bundles/robot_simulation/engine/Math/Convert.ts b/src/bundles/robot_simulation/engine/Math/Convert.ts index aca09ae6a..f4155a5d7 100644 --- a/src/bundles/robot_simulation/engine/Math/Convert.ts +++ b/src/bundles/robot_simulation/engine/Math/Convert.ts @@ -1,5 +1,6 @@ import { Euler, Quaternion, Vector3 } from 'three'; +import type { SimpleQuaternion, SimpleVector } from './Vector'; -export const quat = ({ x, y, z, w }) => new Quaternion(x, y, z, w); -export const vec3 = ({ x, y, z }) => new Vector3(x, y, z); -export const euler = ({ x, y, z }) => new Euler(x, y, z); +export const quat = ({ x, y, z, w }: SimpleQuaternion) => new Quaternion(x, y, z, w); +export const vec3 = ({ x, y, z }: SimpleVector) => new Vector3(x, y, z); +export const euler = ({ x, y, z }:SimpleVector) => new Euler(x, y, z); diff --git a/src/bundles/robot_simulation/engine/Math/Vector.ts b/src/bundles/robot_simulation/engine/Math/Vector.ts index bc9fadc5f..950440597 100644 --- a/src/bundles/robot_simulation/engine/Math/Vector.ts +++ b/src/bundles/robot_simulation/engine/Math/Vector.ts @@ -1,6 +1,10 @@ export type SimpleVector = { x: number; y: number; z: number }; export type SimpleQuaternion = { x: number; y: number; z: number; w: number }; +export const simpleVectorLength = (vector: SimpleVector) => Math.sqrt( + vector.x * vector.x + vector.y * vector.y + vector.z * vector.z, +); + export type Orientation = { position: SimpleVector; rotation: SimpleQuaternion; diff --git a/src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts b/src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts new file mode 100644 index 000000000..08043587f --- /dev/null +++ b/src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts @@ -0,0 +1,241 @@ +import { Entity } from "../../Entity/Entity"; +import type Rapier from "@dimforge/rapier3d-compat"; +import { SimpleQuaternion, SimpleVector } from "../../Math/Vector"; +import { vec3 } from "../../Math/Convert"; + +const createRigidBodyMock = ( + translation: SimpleVector, + rotation: SimpleQuaternion +) => { + const rigidBodyMock = { + translation: jest.fn().mockReturnValue(translation), + rotation: jest.fn().mockReturnValue(rotation), + setTranslation: jest.fn(), + setRotation: jest.fn(), + applyImpulseAtPoint: jest.fn(), + linvel: jest.fn().mockReturnValue({ x: 0, y: 0, z: 0 }), + angvel: jest.fn().mockReturnValue({ x: 0, y: 0, z: 0 }), + }; + return rigidBodyMock as unknown as Rapier.RigidBody; +}; + +const createCollider = (mass: number) => { + const colliderMock = { + mass: jest.fn().mockReturnValue(mass), + setMass: jest.fn(), + }; + return colliderMock as unknown as Rapier.Collider; +}; + +describe("Entity", () => { + let entity: Entity; + + describe("unit tests", () => { + let rigidBody: Rapier.RigidBody; + let collider: Rapier.Collider; + let position: SimpleVector; + let rotation: SimpleQuaternion; + beforeEach(() => { + position = { x: 12, y: 0, z: 3 }; + rotation = { x: 0, y: 0, z: 0, w: 1 }; + rigidBody = createRigidBodyMock(position, rotation); + collider = createCollider(1); + entity = new Entity({ + rapierRigidBody: rigidBody, + rapierCollider: collider, + }); + }); + + test("getCollider", () => { + expect(entity.getCollider()).toBeDefined(); + expect(entity.getCollider()).toBe(collider); + }); + + test("getRigidBody", () => { + expect(entity.getRigidBody()).toBeDefined(); + expect(entity.getRigidBody()).toBe(rigidBody); + }); + + test("getTranslation", () => { + expect(entity.getTranslation()).toEqual(position); + }); + + test("getRotation", () => { + expect(entity.getRotation()).toEqual(rotation); + }); + + test("setOrientation", () => { + const orientation = { + position: { x: 1, y: 2, z: 3 }, + rotation: { x: 0, y: 0, z: 0, w: 1 }, + }; + entity.setOrientation(orientation); + expect(rigidBody.setTranslation).toHaveBeenCalledWith( + orientation.position, + true + ); + expect(rigidBody.setRotation).toHaveBeenCalledWith( + orientation.rotation, + true + ); + }); + + test("setMass", () => { + const mass = 2; + entity.setMass(mass); + expect(collider.setMass).toHaveBeenCalledWith(mass); + }); + + test("getMass", () => { + expect(entity.getMass()).toBe(1); + }); + + test("getVelocity", () => { + entity.getVelocity(); + expect(rigidBody.linvel).toHaveBeenCalled(); + }); + + test("getAngularVelocity", () => { + entity.getAngularVelocity(); + expect(rigidBody.angvel).toHaveBeenCalled(); + }); + + test("applyImpulse", () => { + const impulse = { x: 1, y: 2, z: 3 }; + const point = { x: 0, y: 0, z: 0 }; + entity.applyImpulse(impulse, point); + expect(rigidBody.applyImpulseAtPoint).toHaveBeenCalledWith( + impulse, + point, + true + ); + }); + + test("worldTranslation", () => { + const localTranslation = vec3({ x: 1, y: 2, z: 3 }); + entity.worldTranslation(localTranslation); + expect(rigidBody.rotation).toHaveBeenCalled(); + expect(rigidBody.translation).toHaveBeenCalled(); + }); + + test("transformDirection", () => { + const localDirection = vec3({ x: 1, y: 2, z: 3 }); + entity.transformDirection(localDirection); + expect(rigidBody.rotation).toHaveBeenCalled(); + }); + + test("distanceVectorOfPointToRotationalAxis", () => { + const localPoint = vec3({ x: 1, y: 2, z: 3 }); + entity.distanceVectorOfPointToRotationalAxis(localPoint); + expect(rigidBody.angvel).toHaveBeenCalled(); + }); + + test("tangentialVelocityOfPoint", () => { + const localPoint = vec3({ x: 1, y: 2, z: 3 }); + entity.tangentialVelocityOfPoint(localPoint); + expect(rigidBody.angvel).toHaveBeenCalled(); + }); + + test("worldVelocity", () => { + const localPoint = vec3({ x: 1, y: 2, z: 3 }); + entity.worldVelocity(localPoint); + expect(rigidBody.linvel).toHaveBeenCalled(); + expect(rigidBody.angvel).toHaveBeenCalled(); + }); + }); + + describe("end-to-end tests", () => { + describe("worldTranslation", () => { + test("no rotation", () => { + const rigidBodyTranslation = { x: 1, y: 1, z: 1 } + + const rigidBody = createRigidBodyMock(rigidBodyTranslation, {x:0, y:0, z:0, w:1}); + const collider = createCollider(1); + const entity = new Entity({ + rapierRigidBody: rigidBody, + rapierCollider: collider, + }); + + const localTranslation = vec3({ x: 1, y: 2, z: 3 }); + const worldTranslation = entity.worldTranslation(localTranslation.clone()); + expect(worldTranslation).toEqual({ + x: rigidBodyTranslation.x + localTranslation.x, + y: rigidBodyTranslation.y + localTranslation.y, + z: rigidBodyTranslation.z + localTranslation.z, + }); + }); + + test("no local translation", () => { + const rigidBodyTranslation = { x: 1, y: 1, z: 1 } + + const rigidBody = createRigidBodyMock(rigidBodyTranslation, {x:1, y:0, z:0, w:1}); + const collider = createCollider(1); + const entity = new Entity({ + rapierRigidBody: rigidBody, + rapierCollider: collider, + }); + + const localTranslation = vec3({ x: 0, y: 0, z: 0 }); + const worldTranslation = entity.worldTranslation(localTranslation.clone()); + expect(worldTranslation).toEqual(rigidBodyTranslation); + }); + + test("with rotation and local translation", () => { + const rigidBodyTranslation = { x: 12, y: 0, z: 0 } + + // 180 degree rotation around the x axis + const rigidBody = createRigidBodyMock(rigidBodyTranslation, {x:0, y:1, z:0, w:0}); + const collider = createCollider(1); + const entity = new Entity({ + rapierRigidBody: rigidBody, + rapierCollider: collider, + }); + + + + const localTranslation = vec3({ x: 1, y: 0, z: 0 }); + const worldTranslation = entity.worldTranslation(localTranslation.clone()); + expect(worldTranslation).toEqual({ + x: 11, + y: 0, + z: 0, + }); + }); + }); + + describe("transformDirection", () => { + test("no rotation", () => { + const rigidBody = createRigidBodyMock({ x: 1, y: 1, z: 1 }, {x:0, y:0, z:0, w:1}); + const collider = createCollider(1); + const entity = new Entity({ + rapierRigidBody: rigidBody, + rapierCollider: collider, + }); + + const localDirection = vec3({ x: 1, y: 2, z: 3 }); + const worldDirection = entity.transformDirection(localDirection.clone()); + expect(worldDirection).toEqual(localDirection); + }); + + test("with rotation and local translation", () => { + const rigidBody = createRigidBodyMock({ x: 1, y: 1, z: 1 }, {x:0, y:1, z:0, w:0}); + const collider = createCollider(1); + const entity = new Entity({ + rapierRigidBody: rigidBody, + rapierCollider: collider, + }); + + const localDirection = vec3({ x: 1, y: 2, z: 3 }); + const worldDirection = entity.transformDirection(localDirection.clone()); + expect(worldDirection).toEqual( + { + x:-localDirection.x, + y:localDirection.y, + z:-localDirection.z, + } + ); + }); + }); + + }); +}); diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts b/src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts similarity index 94% rename from src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts rename to src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts index 52643ca09..101aa6d96 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Render/Camera.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts @@ -1,6 +1,6 @@ import * as THREE from 'three'; -import { CameraOptions, getCamera } from '../../Render/helpers/Camera'; +import { CameraOptions, getCamera } from '../../../Render/helpers/Camera'; describe('getCamera', () => { From 5d0dabf73d6e6e4946f8507edf5971c5c15fb8bc Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 12 Mar 2024 03:17:58 +0000 Subject: [PATCH 57/93] Add docs for ev3_functions --- src/bundles/robot_simulation/ev3_functions.ts | 99 +++++++++++++++++-- src/bundles/robot_simulation/index.ts | 9 +- 2 files changed, 96 insertions(+), 12 deletions(-) diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 4666daa89..75da056ec 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -10,6 +10,7 @@ import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; import { type UltrasonicSensor } from './controllers/ev3/sensor/UltrasonicSensor'; import { getEv3FromContext, getWorldFromContext } from './helper_functions'; +import { motorConfig } from './controllers/ev3/ev3/default/config'; type MotorFunctionReturnType = Motor | null; @@ -22,25 +23,64 @@ export function ev3_pause(duration: number): void { program.pause(duration); } -// Motor +/** + * Gets the motor connected to port A. + * + * @returns The motor connected to port A + * + * @category EV3 + */ export function ev3_motorA(): MotorFunctionReturnType { const ev3 = getEv3FromContext(); return ev3.get('leftMotor'); } +/** + * Gets the motor connected to port B. + * + * @returns The motor connected to port B + * + * @category EV3 + */ export function ev3_motorB(): MotorFunctionReturnType { const ev3 = getEv3FromContext(); return ev3.get('rightMotor'); } +/** + * Gets the motor connected to port C. + * + * @returns The motor connected to port C + * + * @category EV3 + */ export function ev3_motorC(): MotorFunctionReturnType { return null; } +/** + * Gets the motor connected to port D. + * + * @returns The motor connected to port D + * + * @category EV3 + */ export function ev3_motorD(): MotorFunctionReturnType { return null; } +/** + * Causes the motor to rotate until the position reaches ev3_motorGetPosition() + position with the given speed. + * Note: this works by sending instructions to the motors. + * This will return almost immediately, without waiting for the motor to reach the given absolute position. + * If you wish to wait, use ev3_pause. + * + * @param motor The motor + * @param position The amount to turn + * @param speed The speed to run at, in tacho counts per second + * + * @category EV3 + */ export function ev3_runToRelativePosition( motor: MotorFunctionReturnType, position: number, @@ -50,43 +90,82 @@ export function ev3_runToRelativePosition( return; } - const wheelDiameter = 0.0575; - + const wheelDiameter = motorConfig.config.mesh.dimension.height; const speedInMetersPerSecond = (speed / 360) * Math.PI * wheelDiameter; - const distanceInMetersPerSecond = (position / 360) * Math.PI * wheelDiameter; motor.setSpeedDistance(speedInMetersPerSecond, distanceInMetersPerSecond); } -// Color Sensor - +/** + * Gets the colour sensor connected any of ports 1, 2, 3 or 4. + * + * @returns The colour sensor + * + * @category EV3 + */ export function ev3_colorSensor() { const ev3 = getEv3FromContext(); return ev3.get('colorSensor'); } +/** + * Gets the amount of red seen by the colour sensor. + * + * @param colorSensor The color sensor + * @returns The amount of blue, in sensor-specific units. + * + * @category EV3 + */ export function ev3_colorSensorRed(colorSensor: ColorSensor) { return colorSensor.sense().r; } +/** + * Gets the amount of green seen by the colour sensor. + * + * @param colorSensor The color sensor + * @returns The amount of green, in sensor-specific units. + * + * @category EV3 + */ export function ev3_colorSensorGreen(colorSensor: ColorSensor) { return colorSensor.sense().g; } +/** + * Gets the amount of blue seen by the colour sensor. + * + * @param colorSensor The color sensor + * @returns The amount of blue, in sensor-specific units. + * + * @category EV3 + */ export function ev3_colorSensorBlue(colorSensor: ColorSensor) { return colorSensor.sense().b; } -// Ultrasonic Sensor - +/** + * Gets the ultrasonic sensor connected any of ports 1, 2, 3 or 4. + * + * @returns The ultrasonic sensor + * + * @category EV3 + */ export function ev3_ultrasonicSensor() { const ev3 = getEv3FromContext(); return ev3.get('ultrasonicSensor'); } - +/** + * Gets the distance read by the ultrasonic sensor in centimeters. + * + * @param ultraSonicSensor The ultrasonic sensor + * @returns The distance, in centimeters. + * + * @category EV3 + */ export function ev3_ultrasonicSensorDistance( ultraSonicSensor: UltrasonicSensor, ): number { - return ultraSonicSensor.sense(); + return ultraSonicSensor.sense() * 100; } diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index d47e4baef..0ae0cab0a 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -1,3 +1,10 @@ +/** + * Robot simulator for EV3. + * + * @module robot_simulation + * @author Joel Chan + */ + export { ev3_motorA, ev3_motorB, @@ -13,10 +20,8 @@ export { } from './ev3_functions'; export { - // Physics createCustomPhysics, createPhysics, - // Renderer createRenderer, init_simulation, createCuboid, From 0b79cf4e25b9730b6191446ee82ff141bf1dc2b2 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 18 Mar 2024 02:02:27 +0000 Subject: [PATCH 58/93] Add documentation for ev3_pause --- src/bundles/robot_simulation/ev3_functions.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 75da056ec..5ea7fa885 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -14,7 +14,20 @@ import { motorConfig } from './controllers/ev3/ev3/default/config'; type MotorFunctionReturnType = Motor | null; -// Utility +/** + * @categoryDescription EV3 + * These functions are mocking the the normal EV3 functions found + * at https://docs.sourceacademy.org/EV3/global.html + * @module + */ + +/** + * Pauses for a period of time. + * + * @param duration The time to wait, in milliseconds. + * + * @category EV3 + */ export function ev3_pause(duration: number): void { const world = getWorldFromContext(); const program = world.controllers.controllers.find( From 9d80574f1dd063bbb3fd8f22049dded6c16f7ff3 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 23 Mar 2024 06:38:14 +0000 Subject: [PATCH 59/93] Change the version of three --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35c33078a..1c92177c4 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "save-file": "^2.3.1", "source-academy-utils": "^1.0.0", "source-academy-wabt": "^1.0.4", - "three": "^0.161.2", + "three": "^0.162.0", "tslib": "^2.3.1" }, "jest": { From 51b20c48476bfaad73c87ffb71c6249e5313b888 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 05:58:28 +0000 Subject: [PATCH 60/93] Change the quotes from double to single --- src/jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jest.config.js b/src/jest.config.js index 1771da6d9..c490f46d0 100644 --- a/src/jest.config.js +++ b/src/jest.config.js @@ -29,6 +29,6 @@ export default { '.+\\.js' ], setupFiles: [ - "./jest.polyfills.js" + './jest.polyfills.js' ] }; From 5f43378f136639221600bfd362facb5c5a2db7b1 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 07:34:34 +0000 Subject: [PATCH 61/93] Remove part 2 from robot simulator --- .../controllers/environment/Paper.ts | 35 ---- .../controllers/ev3/components/Chassis.ts | 69 ------- .../controllers/ev3/components/Mesh.ts | 58 ------ .../controllers/ev3/components/Motor.ts | 150 -------------- .../controllers/ev3/components/Wheel.ts | 102 ---------- .../controllers/ev3/ev3/default/config.ts | 155 --------------- .../controllers/ev3/ev3/default/ev3.ts | 76 -------- .../controllers/ev3/ev3/default/types.ts | 87 --------- .../ev3/feedback_control/PidController.ts | 109 ----------- .../controllers/ev3/sensor/ColorSensor.ts | 150 -------------- .../ev3/sensor/UltrasonicSensor.ts | 76 -------- .../controllers/ev3/sensor/types.ts | 3 - .../robot_simulation/controllers/index.ts | 1 - .../controllers/program/Program.ts | 75 ------- .../controllers/program/evaluate.ts | 58 ------ .../robot_simulation/engine/Core/Timer.ts | 1 - src/bundles/robot_simulation/engine/World.ts | 9 +- src/bundles/robot_simulation/ev3_functions.ts | 184 ------------------ .../robot_simulation/helper_functions.ts | 60 +----- src/bundles/robot_simulation/index.ts | 17 -- .../robot_simulation/interrupt/index.ts | 5 - src/common/specialErrors.ts | 8 + src/jest.polyfills.js | 3 +- src/tabs/RobotSimulation/components/Main.tsx | 6 +- .../components/Simulation/index.tsx | 29 +-- .../components/TabPanels/ColorSensorPanel.tsx | 29 --- .../components/TabPanels/ConsolePanel.tsx | 57 ------ .../components/TabPanels/MonitoringPanel.tsx | 3 - .../components/TabPanels/MotorPidPanel.tsx | 57 ------ .../TabPanels/UltrasonicSensorPanel.tsx | 24 --- .../components/TabPanels/WheelPidPanel.tsx | 63 ------ .../TabPanels/tabComponents/LastUpdated.tsx | 15 -- 32 files changed, 27 insertions(+), 1747 deletions(-) delete mode 100644 src/bundles/robot_simulation/controllers/environment/Paper.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Motor.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts delete mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/types.ts delete mode 100644 src/bundles/robot_simulation/controllers/program/Program.ts delete mode 100644 src/bundles/robot_simulation/controllers/program/evaluate.ts delete mode 100644 src/bundles/robot_simulation/ev3_functions.ts delete mode 100644 src/bundles/robot_simulation/interrupt/index.ts create mode 100644 src/common/specialErrors.ts delete mode 100644 src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx delete mode 100644 src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx delete mode 100644 src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx delete mode 100644 src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx delete mode 100644 src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx delete mode 100644 src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx delete mode 100644 src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx diff --git a/src/bundles/robot_simulation/controllers/environment/Paper.ts b/src/bundles/robot_simulation/controllers/environment/Paper.ts deleted file mode 100644 index 08ab66a13..000000000 --- a/src/bundles/robot_simulation/controllers/environment/Paper.ts +++ /dev/null @@ -1,35 +0,0 @@ -import * as THREE from 'three'; - -import type { Renderer } from '../../engine'; - -export type PaperConfig = { - url: string; - dimension: { - width: number; - height: number; - }; -}; - -export class Paper { - render: Renderer; - config: PaperConfig; - paper: THREE.Mesh; - - constructor(render: Renderer, config: PaperConfig) { - this.render = render; - this.config = config; - - const plane = new THREE.PlaneGeometry(this.config.dimension.width, this.config.dimension.height); // Creating a 1x1 plane for the carpet - this.paper = new THREE.Mesh(plane); - } - - async start() { - const texture = new THREE.TextureLoader() - .load(this.config.url); - const material = new THREE.MeshStandardMaterial({ map: texture }); - this.paper.position.set(0, 0.001, 0); // Position the plane at the floor - this.paper.rotation.x = -Math.PI / 2; - this.paper.material = material; - this.render.add(this.paper); - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts deleted file mode 100644 index 00a91cffc..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts +++ /dev/null @@ -1,69 +0,0 @@ -import * as THREE from 'three'; - -import { - type Physics, - type Controller, - EntityFactory, - type Entity, - MeshFactory, - type Renderer, -} from '../../../engine'; -import { type EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; - -export type ChassisWrapperConfig = EntityCuboidOptions & { - debug: boolean; -}; - -/** - * Wrapper for the chassis entity. It is needed because the chassis entity can only be initialized - * after the physics engine has been started. Therefore, the chassis entity needs to be wrapped in - * a controller. - * - * We also use this class to add an optional debug mesh to the chassis. - */ -export class ChassisWrapper implements Controller { - physics: Physics; - render: Renderer; - config: ChassisWrapperConfig; - - chassis: Entity | null = null; - debugMesh: THREE.Mesh; - - constructor( - physics: Physics, - render: Renderer, - config: ChassisWrapperConfig, - ) { - this.physics = physics; - this.render = render; - this.config = config; - - // Debug mesh. - this.debugMesh = MeshFactory.addCuboid({ - orientation: config.orientation, - dimension: config.dimension, - color: new THREE.Color(0x00ff00), - debug: true, - }); - // Set visible based on config. - this.debugMesh.visible = config.debug; - render.add(this.debugMesh); - } - - getEntity(): Entity { - if (this.chassis === null) { - throw new Error('Chassis not initialized'); - } - return this.chassis; - } - - async start(): Promise { - this.chassis = EntityFactory.addCuboid(this.physics, this.config); - } - - update(): void { - const chassisEntity = this.getEntity(); - this.debugMesh.position.copy(chassisEntity.getTranslation()); - this.debugMesh.quaternion.copy(chassisEntity.getRotation()); - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts deleted file mode 100644 index 7cb97033d..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { type Controller, type Renderer } from '../../../engine'; -// eslint-disable-next-line import/extensions -import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; -import { type ChassisWrapper } from './Chassis'; -import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; -import type { Dimension, SimpleVector } from '../../../engine/Math/Vector'; - -export type MeshConfig = { - url: string; - dimension: Dimension; - offset?: Partial; -}; - -/** - * This represents the mesh of the robot. In reality, the mesh could be part of the chassis, - * but for the sake of clarity it is split into its own controller. - */ -export class Mesh implements Controller { - chassisWrapper: ChassisWrapper; - render: Renderer; - config: MeshConfig; - offset: SimpleVector; - - mesh: GLTF | null = null; - - constructor( - chassisWrapper: ChassisWrapper, - render: Renderer, - config: MeshConfig, - ) { - this.chassisWrapper = chassisWrapper; - this.render = render; - this.config = config; - this.offset = { - x: this.config?.offset?.x || 0, - y: this.config?.offset?.y || 0, - z: this.config?.offset?.z || 0, - }; - } - - async start(): Promise { - this.mesh = await loadGLTF(this.config.url, this.config.dimension); - - this.render.add(this.mesh.scene); - } - - update() { - const chassisEntity = this.chassisWrapper.getEntity(); - const chassisPosition = chassisEntity.getTranslation(); - - chassisPosition.x -= this.offset.x / 2; - chassisPosition.y -= this.offset.y / 2; - chassisPosition.z -= this.offset.z / 2; - - this.mesh?.scene.position.copy(chassisPosition); - this.mesh?.scene.quaternion.copy(chassisEntity.getRotation()); - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts deleted file mode 100644 index b95f3712d..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { type Controller, type Physics, type Renderer } from '../../../engine'; -import { type Dimension, type SimpleVector } from '../../../engine/Math/Vector'; -import { vec3 } from '../../../engine/Math/Convert'; -import { VectorPidController } from '../feedback_control/PidController'; -import type * as THREE from 'three'; -import { type ChassisWrapper } from './Chassis'; -import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; - -// eslint-disable-next-line import/extensions -import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; -import type { PhysicsTimingInfo } from '../../../engine/Physics'; -import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; - -type WheelSide = 'left' | 'right'; - -export type MotorConfig = { - displacement: SimpleVector; - pid: { - proportionalGain: number; - derivativeGain: number; - integralGain: number; - }; - mesh: { - url: string; - dimension: Dimension; - }; -}; -/** - * This represents the motor of the robot and is responsible for moving the robot. It is also - * responsible for the visual representation of the wheel and the friction. - */ -export class Motor implements Controller { - chassisWrapper: ChassisWrapper; - physics: Physics; - render: Renderer; - displacementVector: THREE.Vector3; - config: MotorConfig; - - motorVelocity: number; - meshRotation: number; - pid: VectorPidController; - - callbackHandler = new CallbackHandler(); - wheelSide: WheelSide; - - mesh: GLTF | null = null; - - constructor( - chassisWrapper: ChassisWrapper, - physics: Physics, - render: Renderer, - config: MotorConfig, - ) { - this.chassisWrapper = chassisWrapper; - this.physics = physics; - this.render = render; - this.displacementVector = vec3(config.displacement); - this.config = config; - - this.pid = new VectorPidController(config.pid); - this.motorVelocity = 0; - this.meshRotation = 0; - this.wheelSide = config.displacement.x > 0 ? 'right' : 'left'; - } - - setSpeedDistance(speed: number, distance: number) { - this.motorVelocity = speed; - - this.callbackHandler.addCallback(() => { - this.motorVelocity = 0; - }, (distance / speed) * 1000); - } - - async start(): Promise { - this.mesh = await loadGLTF(this.config.mesh.url, this.config.mesh.dimension); - this.render.add(this.mesh.scene); - } - - fixedUpdate(timingInfo: PhysicsTimingInfo): void { - this.callbackHandler.checkCallbacks(timingInfo); - const chassis = this.chassisWrapper.getEntity(); - - // Calculate the target motor velocity from the chassis perspective - const targetMotorVelocity = vec3({ - x: 0, - y: 0, - z: this.motorVelocity, - }); - - // Transform it to the global perspective - const targetMotorGlobalVelocity - = chassis.transformDirection(targetMotorVelocity); - - // Calculate the actual motor velocity from the global perspective - const actualMotorGlobalVelocity = chassis.worldVelocity( - this.displacementVector.clone(), - ); - - // Calculate the PID output with the PID controller - const pidOutput = this.pid.calculate( - actualMotorGlobalVelocity, - targetMotorGlobalVelocity, - ); - - // Find the global position of the motor - const motorGlobalPosition = chassis.worldTranslation( - this.displacementVector.clone(), - ); - - // Calculate the impulse to apply to the chassis - const impulse = pidOutput - .projectOnPlane( - vec3({ - x: 0, - y: 1, - z: 0, - }), - ) - .multiplyScalar(chassis.getMass()); - - // Apply the impulse to the chassis - chassis.applyImpulse(impulse, motorGlobalPosition); - } - - update(timingInfo: PhysicsTimingInfo): void { - const chassisEntity = this.chassisWrapper.getEntity(); - - // Calculate the new wheel position, adjusting the y-coordinate to half the mesh height - const wheelPosition = chassisEntity.worldTranslation(this.displacementVector.clone()); - wheelPosition.y = this.config.mesh.dimension.height / 2; // Ensure the wheel is placed correctly vertically - - // If mesh is loaded, update its position and orientation - if (this.mesh) { - this.mesh.scene.position.copy(wheelPosition); - this.mesh.scene.quaternion.copy(chassisEntity.getRotation()); - - // Calculate rotation adjustment based on motor velocity and frame duration - const radiansPerFrame = 2 * (this.motorVelocity / this.config.mesh.dimension.height) * timingInfo.frameDuration / 1000; - - // Apply rotation changes to simulate wheel turning - this.meshRotation += radiansPerFrame; - this.mesh.scene.rotateX(this.meshRotation); - - // If the wheel is on the left side, flip it to face the correct direction - if (this.wheelSide === 'left') { - this.mesh.scene.rotateZ(Math.PI); - } - } - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts deleted file mode 100644 index b7a0984e7..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { type Renderer, type Controller, type Physics } from '../../../engine'; -import { type SimpleVector } from '../../../engine/Math/Vector'; -import { vec3 } from '../../../engine/Math/Convert'; -import { NumberPidController } from '../feedback_control/PidController'; -import type * as THREE from 'three'; -import { type ChassisWrapper } from './Chassis'; -import type { PhysicsTimingInfo } from '../../../engine/Physics'; -import { DebugArrow } from '../../../engine/Render/debug/DebugArrow'; - -export type WheelConfig = { - pid: { - proportionalGain: number; - derivativeGain: number; - integralGain: number; - }; - displacement: SimpleVector; - gapToFloor: number; - maxRayDistance:number; - debug: boolean; -}; - -export class Wheel implements Controller { - chassisWrapper: ChassisWrapper; - physics: Physics; - render: Renderer; - config: WheelConfig; - - pid: NumberPidController; - displacementVector: THREE.Vector3; - downVector: THREE.Vector3; - arrowHelper: DebugArrow; - - constructor( - chassisWrapper: ChassisWrapper, - physics: Physics, - render: Renderer, - config: WheelConfig, - ) { - this.chassisWrapper = chassisWrapper; - this.physics = physics; - this.render = render; - this.displacementVector = vec3(config.displacement); - this.config = config; - - this.pid = new NumberPidController(config.pid); - this.downVector = vec3({ - x: 0, - y: -1, - z: 0, - }); - - // Debug arrow. - this.arrowHelper = new DebugArrow({ debug: config.debug }); - render.add(this.arrowHelper.getMesh()); - } - - fixedUpdate(timingInfo: PhysicsTimingInfo): void { - const chassis = this.chassisWrapper.getEntity(); - - const globalDisplacement = chassis.worldTranslation( - this.displacementVector.clone(), - ); - const globalDownDirection = chassis.transformDirection( - this.downVector.clone(), - ); - - const result = this.physics.castRay( - globalDisplacement, - globalDownDirection, - this.config.maxRayDistance, - chassis.getCollider(), - ); - - // Wheels are not touching the ground - if (result === null) { - return; - } - - let { distance: wheelDistance, normal } = result; - - // If distance is zero, the ray originate from inside the floor/wall. - // If that is true, we assume the normal is pointing up. - if (wheelDistance === 0) { - normal = { - x: 0, - y: 1, - z: 0, - }; - } - - const error = this.pid.calculate(wheelDistance, this.config.gapToFloor); - - const force = vec3(normal) - .normalize() - .multiplyScalar((error * chassis.getMass() * timingInfo.timestep) / 1000); - - chassis.applyImpulse(force, globalDisplacement); - - // Debug arrow. - this.arrowHelper.update(globalDisplacement, force.clone(), force.length() * 1000); - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts deleted file mode 100644 index a6661ef9e..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts +++ /dev/null @@ -1,155 +0,0 @@ -import type { - Ev3ChassisConfig, - Ev3ColorSensorConfig, - Ev3Config, - Ev3MeshConfig, - Ev3MotorsConfig, - Ev3UltrasonicSenorConfig, - Ev3WheelsConfig, -} from './types'; - -const noRotation = { - x: 0, - y: 0, - z: 0, - w: 1, -}; - -const tinyConstant = 0.012; - -export const chassisConfig: Ev3ChassisConfig = { - orientation: { - position: { - x: 0, - y: 0.0775, - z: 0, - }, - rotation: noRotation, - }, - dimension: { - height: 0.095, - width: 0.145, - length: 0.18, - }, - mass: 0.6, - type: 'dynamic', - debug: true, -}; - -export const meshConfig: Ev3MeshConfig = { - url: 'https://keen-longma-3c1be1.netlify.app/6_remove_wheels.gltf', - dimension: chassisConfig.dimension, - offset: { - y: 0.02, - }, -}; - -export const wheelConfig: Ev3WheelsConfig = { - displacements: { - frontLeftWheel: { - x: -(chassisConfig.dimension.width / 2), - y: -(chassisConfig.dimension.height / 2), - z: chassisConfig.dimension.length / 2 - tinyConstant, - }, - - frontRightWheel: { - x: chassisConfig.dimension.width / 2, - y: -(chassisConfig.dimension.height / 2), - z: chassisConfig.dimension.length / 2 - tinyConstant, - }, - backLeftWheel: { - x: -(chassisConfig.dimension.width / 2), - y: -(chassisConfig.dimension.height / 2), - z: -(chassisConfig.dimension.length / 2 - tinyConstant), - }, - backRightWheel: { - x: chassisConfig.dimension.width / 2, - y: -(chassisConfig.dimension.height / 2), - z: -(chassisConfig.dimension.length / 2 - tinyConstant), - }, - }, - config: { - pid: { - proportionalGain: 27, - integralGain: 8, - derivativeGain: 40, - }, - gapToFloor: 0.03, - maxRayDistance: 0.05, - debug: true, - }, -}; - -export const motorConfig: Ev3MotorsConfig = { - config: { - pid: { - proportionalGain: 0.25, - derivativeGain: 0, - integralGain: 0, - }, - mesh: { - dimension: { - width: 0.028, - height: 0.0575, - length: 0.0575, - }, - url: 'https://keen-longma-3c1be1.netlify.app/6_wheel.gltf', - }, - }, - displacements: { - leftMotor: { - x: 0.058, - y: 0, - z: 0.03, - }, - rightMotor: { - x: -0.058, - y: 0, - z: 0.03, - }, - }, -}; - -export const colorSensorConfig: Ev3ColorSensorConfig = { - tickRateInSeconds: 0.1, - displacement: { - x: 0.04, - y: -(chassisConfig.dimension.height / 2), - z: 0.01, - }, - size: { - height: 16, - width: 16, - }, - camera: { - type: 'perspective', - aspect: 1, - fov: 10, - near: 0.01, - far: 1, - }, - debug: true, -}; - -const ultrasonicSensorConfig: Ev3UltrasonicSenorConfig = { - displacement: { - x: 0.04, - y: 0, - z: 0.01, - }, - direction: { - x: 0, - y: 0, - z: 1, - }, - debug: true, -}; - -export const ev3Config: Ev3Config = { - chassis: chassisConfig, - motors: motorConfig, - wheels: wheelConfig, - colorSensor: colorSensorConfig, - ultrasonicSensor: ultrasonicSensorConfig, - mesh: meshConfig, -}; diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts deleted file mode 100644 index 4892e6bae..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { type Physics, type Renderer, ControllerMap } from '../../../../engine'; - -import { ChassisWrapper } from '../../components/Chassis'; -import { Mesh } from '../../components/Mesh'; -import { Wheel, type WheelConfig } from '../../components/Wheel'; -import { Motor, type MotorConfig } from '../../components/Motor'; -import { ColorSensor } from '../../sensor/ColorSensor'; -import { UltrasonicSensor } from '../../sensor/UltrasonicSensor'; - -import { - wheelNames, - motorNames, - type DefaultEv3Controller, - type Ev3Config, - type WheelControllers, - type MotorControllers, -} from './types'; - -export type DefaultEv3 = ControllerMap; - -export const createDefaultEv3 = ( - physics: Physics, - render: Renderer, - config: Ev3Config, -): DefaultEv3 => { - const chassis = new ChassisWrapper(physics, render, config.chassis); - const mesh = new Mesh(chassis, render, config.mesh); - - const wheelControllers = wheelNames.reduce((acc, name) => { - const displacement = config.wheels.displacements[name]; - const wheelConfig: WheelConfig = { - ...config.wheels.config, - displacement, - }; - const wheel = new Wheel(chassis, physics, render, wheelConfig); - return { - ...acc, - [name]: wheel, - }; - }, {} as WheelControllers); - - // Motors - const motorControllers = motorNames.reduce((acc, name) => { - const displacement = config.motors.displacements[name]; - const motorConfig: MotorConfig = { - ...config.motors.config, - displacement, - }; - const motor = new Motor(chassis, physics, render, motorConfig); - return { - ...acc, - [name]: motor, - }; - }, {} as MotorControllers); - - // Sensors - const colorSensor = new ColorSensor(chassis, render, config.colorSensor); - - const ultrasonicSensor = new UltrasonicSensor( - chassis, - physics, - render, - config.ultrasonicSensor, - ); - - const ev3: DefaultEv3 = new ControllerMap({ - ...wheelControllers, - ...motorControllers, - colorSensor, - ultrasonicSensor, - mesh, - chassis, - }); - - return ev3; -}; diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts deleted file mode 100644 index dd2ae0dc9..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { SimpleVector } from '../../../../engine/Math/Vector'; -import type { ChassisWrapper, ChassisWrapperConfig } from '../../components/Chassis'; -import type { Mesh, MeshConfig } from '../../components/Mesh'; -import type { Motor, MotorConfig } from '../../components/Motor'; -import type { Wheel, WheelConfig } from '../../components/Wheel'; -import type { ColorSensor, ColorSensorConfig } from '../../sensor/ColorSensor'; -import type { UltrasonicSensor, UltrasonicSensorConfig } from '../../sensor/UltrasonicSensor'; - -// ######################### Controller Types ######################### -// Wheels -export const wheelNames = [ - 'frontLeftWheel', - 'frontRightWheel', - 'backLeftWheel', - 'backRightWheel', -] as const; -export type WheelNames = (typeof wheelNames)[number]; -export type WheelControllers = Record; - -// Motors -export const motorNames = ['leftMotor', 'rightMotor'] as const; -export type MotorNames = (typeof motorNames)[number]; -export type MotorControllers = Record; - -// Chassis -export const chassisNames = ['chassis'] as const; -export type ChassisNames = (typeof chassisNames)[number]; -export type ChassisControllers = Record; - -// Mesh -export const meshNames = ['mesh'] as const; -export type MeshNames = (typeof meshNames)[number]; -export type MeshControllers = Record; - -// ColorSensor -export const colorSensorNames = ['colorSensor'] as const; -export type ColorSensorNames = (typeof colorSensorNames)[number]; -export type ColorSensorControllers = Record; - -// UltrasonicSensor -export const ultrasonicSensorNames = ['ultrasonicSensor'] as const; -export type UltrasonicSensorNames = (typeof ultrasonicSensorNames)[number]; -export type UltrasonicSensorControllers = Record< -UltrasonicSensorNames, -UltrasonicSensor ->; - -// Aggregate -export const controllerNames = [ - ...wheelNames, - ...motorNames, - ...chassisNames, - ...meshNames, - ...colorSensorNames, - ...ultrasonicSensorNames, -] as const; -export type DefaultEv3ControllerNames = (typeof controllerNames)[number]; -export type DefaultEv3Controller = WheelControllers & -MotorControllers & -ColorSensorControllers & -UltrasonicSensorControllers & -ChassisControllers & -MeshControllers; - - -// ######################### Config Types ######################### -export type Ev3ChassisConfig = ChassisWrapperConfig; -export type Ev3MeshConfig = MeshConfig; -export type Ev3WheelsConfig = { - displacements: Record; - config: Omit; -}; -export type Ev3MotorsConfig = { - displacements: Record; - config: Omit; -}; -export type Ev3ColorSensorConfig = ColorSensorConfig; -export type Ev3UltrasonicSenorConfig = UltrasonicSensorConfig; - -export type Ev3Config = { - chassis: Ev3ChassisConfig - mesh: Ev3MeshConfig; - wheels: Ev3WheelsConfig; - motors: Ev3MotorsConfig; - colorSensor: Ev3ColorSensorConfig; - ultrasonicSensor: Ev3UltrasonicSenorConfig; -}; diff --git a/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts b/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts deleted file mode 100644 index 701ff0795..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as THREE from 'three'; - -export type PIDConfig = { - proportionalGain: number; - integralGain: number; - derivativeGain: number; -}; - -type NullaryFunction = () => T; -type BinaryFunction = (a: T, b: T) => T; -type ScaleFunction = (value: T, scale: number) => T; - -type PIDControllerOptions = { - zero: NullaryFunction; - add: BinaryFunction; - subtract: BinaryFunction; - scale: ScaleFunction; - - proportionalGain: number; - integralGain: number; - derivativeGain: number; -}; - -class PIDController { - zero: NullaryFunction; - add: BinaryFunction; - subtract: BinaryFunction; - scale: ScaleFunction; - - proportionalGain: number; - integralGain: number; - derivativeGain: number; - - errorsSum: T; - previousError: T; - - constructor({ - zero, - add, - subtract, - scale, - proportionalGain, - integralGain, - derivativeGain, - }: PIDControllerOptions) { - this.zero = zero; - this.add = add; - this.subtract = subtract; - this.scale = scale; - - this.proportionalGain = proportionalGain; - this.integralGain = integralGain; - this.derivativeGain = derivativeGain; - - this.errorsSum = this.zero(); - this.previousError = this.zero(); - } - - calculate(currentValue: T, setpoint: T): T { - const error = this.subtract(setpoint, currentValue); - this.errorsSum = this.add(this.errorsSum, error); - - const proportional = this.scale(error, this.proportionalGain); - const integral = this.scale(this.errorsSum, this.integralGain); - const derivative = this.scale(this.subtract(error, this.previousError), this.derivativeGain); - this.previousError = error; - - return this.add(this.add(proportional, integral), derivative); - } -} - -export class NumberPidController extends PIDController { - constructor({ - proportionalGain, - integralGain, - derivativeGain, - }: PIDConfig) { - super({ - zero: () => 0, - add: (a, b) => a + b, - subtract: (a, b) => a - b, - scale: (value, scale) => value * scale, - proportionalGain, - integralGain, - derivativeGain, - }); - } -} - -export class VectorPidController extends PIDController { - constructor({ - proportionalGain, - integralGain, - derivativeGain, - }: PIDConfig) { - super({ - zero: () => new THREE.Vector3(0, 0, 0), - add: (a, b) => a.clone() - .add(b), - subtract: (a, b) => a.clone() - .sub(b), - scale: (value, scale) => value.clone() - .multiplyScalar(scale), - proportionalGain, - integralGain, - derivativeGain, - }); - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts deleted file mode 100644 index 01232db2d..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as THREE from 'three'; -import { Renderer } from '../../../engine'; -import { type Sensor } from './types'; -import { type ChassisWrapper } from '../components/Chassis'; -import { type SimpleVector } from '../../../engine/Math/Vector'; -import { vec3 } from '../../../engine/Math/Convert'; -import type { PhysicsTimingInfo } from '../../../engine/Physics'; -import { - getCamera, - type CameraOptions, -} from '../../../engine/Render/helpers/Camera'; - -type Color = { r: number; g: number; b: number }; - -export type ColorSensorConfig = { - size: { - height: number; - width: number; - }; - displacement: SimpleVector; - camera: CameraOptions; - tickRateInSeconds: number; - debug: boolean; -}; - -export class ColorSensor implements Sensor { - chassisWrapper: ChassisWrapper; - displacement: THREE.Vector3; - config: ColorSensorConfig; - - camera: THREE.Camera; - renderer: Renderer; - accumulator = 0; - colorSensed: Color; - tempCanvas: HTMLCanvasElement; - - constructor( - chassisWrapper: ChassisWrapper, - render: Renderer, - config: ColorSensorConfig, - ) { - this.chassisWrapper = chassisWrapper; - this.displacement = vec3(config.displacement); - this.config = config; - - this.camera = getCamera(config.camera); - // We create a new renderer with the same scene. But we use a different camera. - this.renderer = new Renderer(render.scene(), this.camera, { - width: this.config.size.width, - height: this.config.size.height, - control: 'none', - }); - - this.colorSensed = { - r: 0, - g: 0, - b: 0, - }; - - this.tempCanvas = document.createElement('canvas'); - this.tempCanvas.width = this.config.size.width; - this.tempCanvas.height = this.config.size.height; - - if (config.debug) { - const helper = new THREE.CameraHelper(this.camera); - render.add(helper); - } - } - - getColorSensorPosition() { - const chassis = this.chassisWrapper.getEntity(); - const colorSensorPosition = chassis.worldTranslation( - this.displacement.clone(), - ); - return colorSensorPosition; - } - - sense(): Color { - return this.colorSensed; - } - - // Even though we are rendering, we use fixedUpdate because the student's code can be affected - // by the values of sense() and could affect the determinism of the simulation. - fixedUpdate(timingInfo: PhysicsTimingInfo) { - this.accumulator += timingInfo.timestep; - - const tickRateInMilliseconds = this.config.tickRateInSeconds * 1000; - - // We check the accumulator to see if it's time update the color sensor. - // If it's not time, we return early. - if (this.accumulator < tickRateInMilliseconds) { - return; - } - this.accumulator -= tickRateInMilliseconds; - - // We move the camera to the right position - this.camera.position.copy(this.getColorSensorPosition()); - // Point it downwards - this.camera.lookAt( - this.camera.position.x, - this.camera.position.y - 1, // 1 unit below its current position - this.camera.position.z, - ); - - // We render to load the color sensor data into the renderer. - this.renderer.render(); - - // We get the HTMLCanvasElement from the renderer - const rendererCanvas = this.renderer.getElement(); - - // Get the context from the temp canvas - const tempCtx = this.tempCanvas.getContext('2d', { - willReadFrequently: true, - })!; - - // Draw the renderer canvas to the temp canvas - tempCtx.drawImage(rendererCanvas, 0, 0); - - // Get the image data from the temp canvas - const imageData = tempCtx.getImageData( - 0, - 0, - this.config.size.width, - this.config.size.height, - {}, - ); - - // Calculate the average color - const averageColor = { - r: 0, - g: 0, - b: 0, - }; - - for (let i = 0; i < imageData.data.length; i += 4) { - const r = imageData.data[i]; - const g = imageData.data[i + 1]; - const b = imageData.data[i + 2]; - averageColor.r += r; - averageColor.g += g; - averageColor.b += b; - } - - averageColor.r /= imageData.data.length; - averageColor.g /= imageData.data.length; - averageColor.b /= imageData.data.length; - - this.colorSensed = averageColor; - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts deleted file mode 100644 index 6399d2ebd..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { type SimpleVector } from '../../../engine/Math/Vector'; -import { type ChassisWrapper } from '../components/Chassis'; -import { type Sensor } from './types'; -import { vec3 } from '../../../engine/Math/Convert'; -import { type Renderer, type Physics } from '../../../engine'; -import * as THREE from 'three'; - -export type UltrasonicSensorConfig = { - displacement: SimpleVector; - direction: SimpleVector; - debug: boolean; -}; - -export class UltrasonicSensor implements Sensor { - chassisWrapper: ChassisWrapper; - physics: Physics; - displacement: THREE.Vector3; - direction: THREE.Vector3; - distanceSensed: number = 0; - render: Renderer; - config: UltrasonicSensorConfig; - debugArrow: THREE.ArrowHelper; - - constructor( - chassis: ChassisWrapper, - physics: Physics, - render: Renderer, - config: UltrasonicSensorConfig, - ) { - this.chassisWrapper = chassis; - this.physics = physics; - this.render = render; - this.displacement = vec3(config.displacement); - this.direction = vec3(config.direction); - this.config = config; - - // Debug arrow - this.debugArrow = new THREE.ArrowHelper(); - this.debugArrow.visible = false; - this.render.add(this.debugArrow); - } - - sense(): number { - return this.distanceSensed; - } - - fixedUpdate(): void { - const chassis = this.chassisWrapper.getEntity(); - const globalDisplacement = chassis.worldTranslation( - this.displacement.clone(), - ); - const globalDirection = chassis.transformDirection(this.direction.clone()); - - const result = this.physics.castRay( - globalDisplacement, - globalDirection, - 1, - this.chassisWrapper.getEntity() - .getCollider(), - ); - - if (this.config.debug) { - this.debugArrow.visible = true; - this.debugArrow.position.copy(globalDisplacement); - this.debugArrow.setDirection(globalDirection.normalize()); - } - - if (result === null) { - return; - } - - const { distance: wheelDistance } = result; - - this.distanceSensed = wheelDistance; - } -} diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts deleted file mode 100644 index d2b984345..000000000 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { type Controller } from '../../../engine'; - -export type Sensor = Controller & { sense: () => T }; diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts index 79d8a2c2b..e69de29bb 100644 --- a/src/bundles/robot_simulation/controllers/index.ts +++ b/src/bundles/robot_simulation/controllers/index.ts @@ -1 +0,0 @@ -export { type DefaultEv3 } from './ev3/ev3/default/ev3'; diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts deleted file mode 100644 index 7edb56da2..000000000 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { type IOptions } from 'js-slang'; -import { runECEvaluator } from './evaluate'; -import context from 'js-slang/context'; -import { type Controller } from '../../engine'; -import { CallbackHandler } from '../../engine/Core/CallbackHandler'; -import { ProgramError } from './error'; -import type { PhysicsTimingInfo } from '../../engine/Physics'; - -type ProgramConfig = { - stepsPerTick: number; -}; - -export const program_controller_identifier = 'program_controller'; - -export class Program implements Controller { - code: string; - iterator: ReturnType | null; - isPaused: boolean; - callbackHandler = new CallbackHandler(); - name: string; - - constructor(code: string) { - this.name = program_controller_identifier; - this.code = code; - this.iterator = null; - this.isPaused = false; - } - - pause(pauseDuration: number) { - this.isPaused = true; - this.callbackHandler.addCallback(() => { - this.isPaused = false; - }, pauseDuration); - } - - start() { - const options: Partial = { - originalMaxExecTime: Infinity, - scheduler: 'preemptive', - stepLimit: Infinity, - throwInfiniteLoops: false, - useSubst: false, - }; - - context.errors = []; - - this.iterator = runECEvaluator(this.code, context, options); - } - - fixedUpdate() { - try { - if (!this.iterator) { - throw Error('Program not started'); - } - - if (this.isPaused) { - return; - } - - // steps per tick - for (let i = 0; i < 11; i++) { - const result = this.iterator.next(); - } - } catch (e) { - console.error(e); - throw new ProgramError( - 'Error in program execution. Please check your code and try again.', - ); - } - } - - update(frameTiming: PhysicsTimingInfo): void { - this.callbackHandler.checkCallbacks(frameTiming); - } -} diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/controllers/program/evaluate.ts deleted file mode 100644 index 70ebd46fc..000000000 --- a/src/bundles/robot_simulation/controllers/program/evaluate.ts +++ /dev/null @@ -1,58 +0,0 @@ -import * as _ from 'lodash'; - -import { type IOptions } from 'js-slang/dist'; - -import { Variant, type Context, type RecursivePartial } from 'js-slang/dist/types'; -import { Control, Stash, generateCSEMachineStateStream } from 'js-slang/dist/cse-machine/interpreter'; -import { parse } from 'js-slang/dist/parser/parser'; - -const DEFAULT_SOURCE_OPTIONS = { - scheduler: 'async', - steps: 1000, - stepLimit: -1, - executionMethod: 'auto', - variant: Variant.DEFAULT, - originalMaxExecTime: 1000, - useSubst: false, - isPrelude: false, - throwInfiniteLoops: true, - envSteps: -1, - importOptions: { - wrapSourceModules: true, - checkImports: true, - loadTabs: true, - }, -}; - - -export function* runECEvaluator( - code: string, - context: Context, - options: RecursivePartial, -): Generator<{ steps: number }, void, undefined> { - const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); - const program = parse(code, context); - - if (!program) { - return; - } - - try { - context.runtime.isRunning = true; - context.runtime.control = new Control(program); - context.runtime.stash = new Stash(); - yield* generateCSEMachineStateStream( - context, - context.runtime.control, - context.runtime.stash, - theOptions.envSteps, - theOptions.stepLimit, - theOptions.isPrelude, - ); - // eslint-disable-next-line no-useless-catch - } catch (error) { - throw error; - } finally { - context.runtime.isRunning = false; - } -} diff --git a/src/bundles/robot_simulation/engine/Core/Timer.ts b/src/bundles/robot_simulation/engine/Core/Timer.ts index 1d7ed5546..4f899984d 100644 --- a/src/bundles/robot_simulation/engine/Core/Timer.ts +++ b/src/bundles/robot_simulation/engine/Core/Timer.ts @@ -11,7 +11,6 @@ export class Timer { private _timeSpentPaused = 0; private _frameDuration = 0; - private _startTime: number | null = null; private _pausedAt: number | null = null; private _currentTime: number | null = null; diff --git a/src/bundles/robot_simulation/engine/World.ts b/src/bundles/robot_simulation/engine/World.ts index 93a5c7dc5..95239fcda 100644 --- a/src/bundles/robot_simulation/engine/World.ts +++ b/src/bundles/robot_simulation/engine/World.ts @@ -1,12 +1,10 @@ +import { ProgramError } from '../controllers/program/error'; import { type Controller, ControllerGroup } from './Core/Controller'; import { TypedEventTarget } from './Core/Events'; +import { type RobotConsole } from './Core/RobotConsole'; +import { type Timer } from './Core/Timer'; import { TimeStampedEvent, type Physics } from './Physics'; import { type Renderer } from './Render/Renderer'; -import { type Timer } from './Core/Timer'; - -import { type RobotConsole } from './Core/RobotConsole'; -import { ProgramError } from '../controllers/program/error'; - export const worldStates = [ 'unintialized', @@ -109,7 +107,6 @@ export class World extends TypedEventTarget { new TimeStampedEvent('afterRender', physicsTimingInfo), ); - if (this.state === 'running') { window.requestAnimationFrame(this.step.bind(this)); } diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts deleted file mode 100644 index 5ea7fa885..000000000 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ /dev/null @@ -1,184 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - -import { - program_controller_identifier, - type Program, -} from './controllers/program/Program'; - -import { type Motor } from './controllers/ev3/components/Motor'; -import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; -import { type UltrasonicSensor } from './controllers/ev3/sensor/UltrasonicSensor'; - -import { getEv3FromContext, getWorldFromContext } from './helper_functions'; -import { motorConfig } from './controllers/ev3/ev3/default/config'; - -type MotorFunctionReturnType = Motor | null; - -/** - * @categoryDescription EV3 - * These functions are mocking the the normal EV3 functions found - * at https://docs.sourceacademy.org/EV3/global.html - * @module - */ - -/** - * Pauses for a period of time. - * - * @param duration The time to wait, in milliseconds. - * - * @category EV3 - */ -export function ev3_pause(duration: number): void { - const world = getWorldFromContext(); - const program = world.controllers.controllers.find( - (controller) => controller.name === program_controller_identifier, - ) as Program; - program.pause(duration); -} - -/** - * Gets the motor connected to port A. - * - * @returns The motor connected to port A - * - * @category EV3 - */ -export function ev3_motorA(): MotorFunctionReturnType { - const ev3 = getEv3FromContext(); - return ev3.get('leftMotor'); -} - -/** - * Gets the motor connected to port B. - * - * @returns The motor connected to port B - * - * @category EV3 - */ -export function ev3_motorB(): MotorFunctionReturnType { - const ev3 = getEv3FromContext(); - return ev3.get('rightMotor'); -} - -/** - * Gets the motor connected to port C. - * - * @returns The motor connected to port C - * - * @category EV3 - */ -export function ev3_motorC(): MotorFunctionReturnType { - return null; -} - -/** - * Gets the motor connected to port D. - * - * @returns The motor connected to port D - * - * @category EV3 - */ -export function ev3_motorD(): MotorFunctionReturnType { - return null; -} - -/** - * Causes the motor to rotate until the position reaches ev3_motorGetPosition() + position with the given speed. - * Note: this works by sending instructions to the motors. - * This will return almost immediately, without waiting for the motor to reach the given absolute position. - * If you wish to wait, use ev3_pause. - * - * @param motor The motor - * @param position The amount to turn - * @param speed The speed to run at, in tacho counts per second - * - * @category EV3 - */ -export function ev3_runToRelativePosition( - motor: MotorFunctionReturnType, - position: number, - speed: number, -): void { - if (motor === null) { - return; - } - - const wheelDiameter = motorConfig.config.mesh.dimension.height; - const speedInMetersPerSecond = (speed / 360) * Math.PI * wheelDiameter; - const distanceInMetersPerSecond = (position / 360) * Math.PI * wheelDiameter; - - motor.setSpeedDistance(speedInMetersPerSecond, distanceInMetersPerSecond); -} - -/** - * Gets the colour sensor connected any of ports 1, 2, 3 or 4. - * - * @returns The colour sensor - * - * @category EV3 - */ -export function ev3_colorSensor() { - const ev3 = getEv3FromContext(); - return ev3.get('colorSensor'); -} - -/** - * Gets the amount of red seen by the colour sensor. - * - * @param colorSensor The color sensor - * @returns The amount of blue, in sensor-specific units. - * - * @category EV3 - */ -export function ev3_colorSensorRed(colorSensor: ColorSensor) { - return colorSensor.sense().r; -} - -/** - * Gets the amount of green seen by the colour sensor. - * - * @param colorSensor The color sensor - * @returns The amount of green, in sensor-specific units. - * - * @category EV3 - */ -export function ev3_colorSensorGreen(colorSensor: ColorSensor) { - return colorSensor.sense().g; -} - -/** - * Gets the amount of blue seen by the colour sensor. - * - * @param colorSensor The color sensor - * @returns The amount of blue, in sensor-specific units. - * - * @category EV3 - */ -export function ev3_colorSensorBlue(colorSensor: ColorSensor) { - return colorSensor.sense().b; -} - -/** - * Gets the ultrasonic sensor connected any of ports 1, 2, 3 or 4. - * - * @returns The ultrasonic sensor - * - * @category EV3 - */ -export function ev3_ultrasonicSensor() { - const ev3 = getEv3FromContext(); - return ev3.get('ultrasonicSensor'); -} -/** - * Gets the distance read by the ultrasonic sensor in centimeters. - * - * @param ultraSonicSensor The ultrasonic sensor - * @returns The distance, in centimeters. - * - * @category EV3 - */ -export function ev3_ultrasonicSensorDistance( - ultraSonicSensor: UltrasonicSensor, -): number { - return ultraSonicSensor.sense() * 100; -} diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index 0367a2926..f898da155 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -1,22 +1,15 @@ -import { Program } from './controllers/program/Program'; -import { - createDefaultEv3, - type DefaultEv3, -} from './controllers/ev3/ev3/default/ev3'; +import context from 'js-slang/context'; +import { interrupt } from '../../common/specialErrors'; +import { sceneConfig } from './config'; +import { Cuboid, type CuboidConfig } from './controllers/environment/Cuboid'; import { type Controller, Physics, Renderer, Timer, World } from './engine'; -import context from 'js-slang/context'; -import { interrupt } from './interrupt'; import { RobotConsole } from './engine/Core/RobotConsole'; -import { getCamera, type CameraOptions } from './engine/Render/helpers/Camera'; -import { createScene } from './engine/Render/helpers/Scene'; -import { Paper, type PaperConfig } from './controllers/environment/Paper'; -import type { PhysicsConfig } from './engine/Physics'; import { isRigidBodyType, type RigidBodyType } from './engine/Entity/EntityFactory'; -import { Cuboid, type CuboidConfig } from './controllers/environment/Cuboid'; -import { ev3Config } from './controllers/ev3/ev3/default/config'; -import { sceneConfig } from './config'; +import type { PhysicsConfig } from './engine/Physics'; import type { RenderConfig } from './engine/Render/Renderer'; +import { getCamera, type CameraOptions } from './engine/Render/helpers/Camera'; +import { createScene } from './engine/Render/helpers/Scene'; const storedWorld = context.moduleContexts.robot_simulation.state?.world; @@ -29,14 +22,6 @@ export function getWorldFromContext(): World { return world as World; } -export function getEv3FromContext(): DefaultEv3 { - const ev3 = context.moduleContexts.robot_simulation.state?.ev3; - if (ev3 === undefined) { - throw new Error('ev3 not initialized'); - } - return ev3 as DefaultEv3; -} - // Physics export function createCustomPhysics( gravity: number, @@ -114,7 +99,7 @@ export function createCuboid( length: number, height: number, mass: number, - color: string | number, + color: number | string, bodyType: string, ) { if (isRigidBodyType(bodyType) === false) { @@ -177,30 +162,6 @@ export function createWall(physics: Physics, renderer: Renderer) { return wall; } -export function createPaper( - render: Renderer, - url: string, - width: number, - height: number, -) { - const paperConfig: PaperConfig = { - url, - dimension: { - width, - height, - }, - }; - const paper = new Paper(render, paperConfig); - return paper; -} - -export function createCSE() { - const code = context.unTypecheckedCode[0]; - const program = new Program(code); - return program; -} - - export function addControllerToWorld(controller: Controller, world: World) { world.addController(controller); } @@ -212,11 +173,6 @@ export function saveToContext(key: string, value: any) { context.moduleContexts.robot_simulation.state[key] = value; } -export function createEv3(physics: Physics, renderer: Renderer): DefaultEv3 { - const ev3 = createDefaultEv3(physics, renderer, ev3Config); - return ev3; -} - // Initialization export function init_simulation(worldFactory: () => World) { if (storedWorld !== undefined) { diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 0ae0cab0a..862f33518 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -5,20 +5,6 @@ * @author Joel Chan */ -export { - ev3_motorA, - ev3_motorB, - ev3_motorC, - ev3_motorD, - ev3_runToRelativePosition, - ev3_colorSensorRed, - ev3_colorSensorGreen, - ev3_pause, - ev3_colorSensorBlue, - ev3_ultrasonicSensor, - ev3_ultrasonicSensorDistance, -} from './ev3_functions'; - export { createCustomPhysics, createPhysics, @@ -28,10 +14,7 @@ export { createTimer, createWorld, createWall, - createEv3, - createPaper, createFloor, - createCSE, addControllerToWorld, createRobotConsole, saveToContext, diff --git a/src/bundles/robot_simulation/interrupt/index.ts b/src/bundles/robot_simulation/interrupt/index.ts deleted file mode 100644 index 789ab2521..000000000 --- a/src/bundles/robot_simulation/interrupt/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/* eslint-disable @typescript-eslint/no-throw-literal */ - -export function interrupt() { - throw 'source_academy_interrupt'; -} diff --git a/src/common/specialErrors.ts b/src/common/specialErrors.ts new file mode 100644 index 000000000..54aa1fbbf --- /dev/null +++ b/src/common/specialErrors.ts @@ -0,0 +1,8 @@ +/** + * This function is used to interrupt the frontend execution. + * When called, the frontend will notify that the program has ended successfully + * and display a message that the program is stopped by a module. + */ +export function interrupt() { + throw 'source_academy_interrupt'; +} diff --git a/src/jest.polyfills.js b/src/jest.polyfills.js index fc9ef790b..9018b43bb 100644 --- a/src/jest.polyfills.js +++ b/src/jest.polyfills.js @@ -1,4 +1,5 @@ -const { TextDecoder, TextEncoder } = require("node:util"); +/* eslint-disable @typescript-eslint/no-var-requires */ +const { TextDecoder, TextEncoder } = require('node:util'); // According to the Jest docs, https://mswjs.io/docs/migrations/1.x-to-2.x#environment Object.defineProperties(globalThis, { diff --git a/src/tabs/RobotSimulation/components/Main.tsx b/src/tabs/RobotSimulation/components/Main.tsx index 5c3b621ff..e28362849 100644 --- a/src/tabs/RobotSimulation/components/Main.tsx +++ b/src/tabs/RobotSimulation/components/Main.tsx @@ -1,8 +1,8 @@ -import TabUi from './TabUi'; -import { Modal } from './Modal'; import { useState } from 'react'; -import SimulationCanvas from './Simulation'; import { type DebuggerContext } from '../../../typings/type_helpers'; +import { Modal } from './Modal'; +import SimulationCanvas from './Simulation'; +import TabUi from './TabUi'; export default function Main({ context }: { context:DebuggerContext }): JSX.Element { const [isCanvasShowing, setIsCanvasShowing] = useState(false); diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 6efdc40d4..48fd0731b 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -1,17 +1,9 @@ +import { Tabs } from '@blueprintjs/core'; import { useRef, type CSSProperties, useEffect, useState } from 'react'; -import type { DebuggerContext } from '../../../../typings/type_helpers'; import { type World } from '../../../../bundles/robot_simulation/engine'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; import { type WorldState } from '../../../../bundles/robot_simulation/engine/World'; - -import { Tab, Tabs } from '@blueprintjs/core'; -import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; -import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; -import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; -import { MonitoringPanel } from '../TabPanels/MonitoringPanel'; -import { UltrasonicSensorPanel } from '../TabPanels/UltrasonicSensorPanel'; -import { ConsolePanel } from '../TabPanels/ConsolePanel'; +import type { DebuggerContext } from '../../../../typings/type_helpers'; const WrapperStyle: CSSProperties = { display: 'flex', @@ -44,17 +36,11 @@ export default function SimulationCanvas({ isOpen: boolean; }) { const ref = useRef(null); - const sensorRef = useRef(null); const [currentState, setCurrentState] = useState('unintialized'); const world = context.context.moduleContexts.robot_simulation.state .world as World; - const ev3 = context.context.moduleContexts.robot_simulation.state - .ev3 as DefaultEv3; - - const robotConsole = world.robotConsole; - useEffect(() => { const startThreeAndRapierEngines = async () => { setCurrentState(world.state); @@ -64,10 +50,6 @@ export default function SimulationCanvas({ if (ref.current) { ref.current.replaceChildren(world.render.getElement()); } - - if (sensorRef.current) { - sensorRef.current.replaceChildren(ev3.get('colorSensor').renderer.getElement()); - } }; if (currentState === 'unintialized') { @@ -99,12 +81,7 @@ export default function SimulationCanvas({
- } /> - } /> - } /> - }/> - }/> - } /> + {/* This will be added in part 2 */}
diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx deleted file mode 100644 index cae283947..000000000 --- a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; - -export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { - const colorSensor = ev3.get('colorSensor'); - const sensorVisionRef = useRef(null); - const [_, update] = useState(0); - const colorSensed = colorSensor.sense(); - - useEffect(() => { - if (sensorVisionRef.current) { - sensorVisionRef.current.replaceChildren(colorSensor.renderer.getElement()); - } - - // Hacky - setInterval(() => { - update((i) => i + 1); - }, 1000); - }, []); - - return <> -
-
-

Red: {colorSensed.r}

-

Green: {colorSensed.g}

-

Blue: {colorSensed.b}

-
- ; -}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx deleted file mode 100644 index 92793383c..000000000 --- a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { type LogEntry, type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; -import { useFetchFromSimulation } from '../../hooks/fetchFromSimulation'; -import { LastUpdated, getTimeString } from './tabComponents/LastUpdated'; - - -const getLogString = (log: LogEntry) => { - const logLevelText :Record = { - source: 'Runtime Source Error', - error: 'Error', - }; - - const timeString = getTimeString(new Date(log.timestamp)); - return `[${timeString}] ${logLevelText[log.level]}: ${log.message}`; -}; - -export const ConsolePanel = ({ - robot_console, -}: { - robot_console?: RobotConsole; -}) => { - const [timing, logs] = useFetchFromSimulation(() => { - if (robot_console === undefined) { - return null; - } - return robot_console.getLogs(); - }, 1000); - - if (timing === null) { - return
Not fetched yet
; - } - - if (logs === null) { - return ( -
- Console not found. Ensure that the world is initialized properly. -
- ); - } - - if (logs.length === 0) { - return ( -
- -

There is currently no logs

-
- ); - } - - return ( -
- -
    - {logs.map((log, i) =>
  • {getLogString(log)}
  • )} -
-
- ); -}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx deleted file mode 100644 index 0fef2f299..000000000 --- a/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; - -export const MonitoringPanel = ({ ev3 }: { ev3: DefaultEv3 }) => <>hi; diff --git a/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx deleted file mode 100644 index 9568e95cc..000000000 --- a/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { type CSSProperties } from 'react'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; -import { NumericInput } from '@blueprintjs/core'; - -const RowStyle: CSSProperties = { - display: 'flex', - flexDirection: 'row', - gap: '0.6rem', -}; - -export const MotorPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { - const onChangeProportional = (value: number) => { - ev3.get('leftMotor').pid.proportionalGain = value; - ev3.get('rightMotor').pid.proportionalGain = value; - }; - const onChangeIntegral = (value: number) => { - ev3.get('leftMotor').pid.integralGain = value; - ev3.get('rightMotor').pid.integralGain = value; - }; - const onChangeDerivative = (value: number) => { - ev3.get('leftMotor').pid.derivativeGain = value; - ev3.get('rightMotor').pid.derivativeGain = value; - }; - - - return ( -
-
- Proportional Gain: - -
-
- Integral Gain: - -
-
- Derivative Gain: - -
-
- ); -}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx deleted file mode 100644 index 4bcfd0a1b..000000000 --- a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useEffect, useState } from 'react'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; - -export const UltrasonicSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { - const ultrasonicSensor = ev3.get('ultrasonicSensor'); - const [_, update] = useState(0); - - const distanceSensed = ultrasonicSensor.sense(); - - useEffect(() => { - // Hacky - setInterval(() => { - update((i) => i + 1); - }, 1000); - }, []); - - return ( - <> -
-

Distance: {distanceSensed}

-
- - ); -}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx deleted file mode 100644 index 4090922ab..000000000 --- a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { type CSSProperties } from 'react'; -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; -import { NumericInput } from '@blueprintjs/core'; - -const RowStyle: CSSProperties = { - display: 'flex', - flexDirection: 'row', - gap: '0.6rem', -}; - -export const WheelPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { - const onChangeProportional = (value: number) => { - ev3.get('backLeftWheel').pid.proportionalGain = value; - ev3.get('backRightWheel').pid.proportionalGain = value; - ev3.get('frontLeftWheel').pid.proportionalGain = value; - ev3.get('frontRightWheel').pid.proportionalGain = value; - }; - const onChangeIntegral = (value: number) => { - ev3.get('backLeftWheel').pid.integralGain = value; - ev3.get('backRightWheel').pid.integralGain = value; - ev3.get('frontLeftWheel').pid.integralGain = value; - ev3.get('frontRightWheel').pid.integralGain = value; - }; - const onChangeDerivative = (value: number) => { - ev3.get('backLeftWheel').pid.derivativeGain = value; - ev3.get('backRightWheel').pid.derivativeGain = value; - ev3.get('frontLeftWheel').pid.derivativeGain = value; - ev3.get('frontRightWheel').pid.derivativeGain = value; - }; - - - return ( -
-
- Proportional Gain: - -
-
- Integral Gain: - -
-
- Derivative Gain: - -
-
- ); -}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx deleted file mode 100644 index 4310909da..000000000 --- a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export const getTimeString = (date: Date) => { - const options: Intl.DateTimeFormatOptions = { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false, // Use 24-hour format. Set to true for 12-hour format if preferred. - }; - return date.toLocaleTimeString([], options); -}; - -export const LastUpdated = ({ time }: { time: Date }) => { - const timeString = getTimeString(time); - - return Last updated: {timeString}; -}; From dfc2bf3fab799abb8eb8505b10826a681f6bc2ec Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 07:53:02 +0000 Subject: [PATCH 62/93] Remove unused libraries in package.json --- package.json | 2 -- yarn.lock | 64 +++++++++++----------------------------------------- 2 files changed, 13 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 6db3403c5..e79bc8add 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,6 @@ "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@types/three": "^0.161.2", - "@typescript-eslint/eslint-plugin": "^6.6.0", - "@typescript-eslint/parser": "^6.6.0", "@vitejs/plugin-react": "^4.0.4", "acorn": "^8.8.1", "acorn-jsx": "^5.3.2", diff --git a/yarn.lock b/yarn.lock index 997c41bce..e90f642fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1523,23 +1523,6 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/eslint-plugin@^6.6.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" - integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== - dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/type-utils" "6.21.0" - "@typescript-eslint/utils" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" - debug "^4.3.4" - graphemer "^1.4.0" - ignore "^5.2.4" - natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" - "@typescript-eslint/parser@7.4.0": version "7.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.4.0.tgz#540f4321de1e52b886c0fa68628af1459954c1f1" @@ -1551,17 +1534,6 @@ "@typescript-eslint/visitor-keys" "7.4.0" debug "^4.3.4" -"@typescript-eslint/parser@^6.6.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" - integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== - dependencies: - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" - debug "^4.3.4" - "@typescript-eslint/scope-manager@5.52.0": version "5.52.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.52.0.tgz#a993d89a0556ea16811db48eabd7c5b72dcb83d1" @@ -1586,16 +1558,6 @@ "@typescript-eslint/types" "7.4.0" "@typescript-eslint/visitor-keys" "7.4.0" -"@typescript-eslint/type-utils@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" - integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== - dependencies: - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/utils" "6.21.0" - debug "^4.3.4" - ts-api-utils "^1.0.1" - "@typescript-eslint/type-utils@7.4.0": version "7.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz#cfcaab21bcca441c57da5d3a1153555e39028cbd" @@ -1662,19 +1624,6 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.21.0", "@typescript-eslint/utils@^6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" - integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== - dependencies: - "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/typescript-estree" "6.21.0" - semver "^7.5.4" - "@typescript-eslint/utils@7.4.0": version "7.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.4.0.tgz#d889a0630cab88bddedaf7c845c64a00576257bd" @@ -1702,6 +1651,19 @@ eslint-utils "^3.0.0" semver "^7.3.7" +"@typescript-eslint/utils@^6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + semver "^7.5.4" + "@typescript-eslint/visitor-keys@5.52.0": version "5.52.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.52.0.tgz#e38c971259f44f80cfe49d97dbffa38e3e75030f" From 20d9107e996eaf04b3404d93570254b8c2869a83 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 08:08:35 +0000 Subject: [PATCH 63/93] Fix eslint errors --- .../controllers/environment/Cuboid.ts | 6 +++--- src/bundles/robot_simulation/engine/Core/Events.ts | 4 ++-- src/bundles/robot_simulation/engine/Entity/Entity.ts | 4 ++-- src/bundles/robot_simulation/engine/Physics.ts | 12 ++++++------ .../engine/__tests__/Render/helpers/Camera.ts | 6 ++---- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/environment/Cuboid.ts b/src/bundles/robot_simulation/controllers/environment/Cuboid.ts index 0bc6f23ad..b40047630 100644 --- a/src/bundles/robot_simulation/controllers/environment/Cuboid.ts +++ b/src/bundles/robot_simulation/controllers/environment/Cuboid.ts @@ -6,18 +6,18 @@ import { type Physics, type Renderer, } from '../../engine'; -import type { Dimension, SimpleVector } from '../../engine/Math/Vector'; -import type { RenderCuboidOptions } from '../../engine/Render/helpers/MeshFactory'; import type { EntityCuboidOptions, RigidBodyType, } from '../../engine/Entity/EntityFactory'; +import type { Dimension, SimpleVector } from '../../engine/Math/Vector'; +import type { RenderCuboidOptions } from '../../engine/Render/helpers/MeshFactory'; export type CuboidConfig = { position: SimpleVector; dimension: Dimension; mass: number; - color: string | number; + color: number | string; type: RigidBodyType; }; diff --git a/src/bundles/robot_simulation/engine/Core/Events.ts b/src/bundles/robot_simulation/engine/Core/Events.ts index f00c0c625..58402cdbd 100644 --- a/src/bundles/robot_simulation/engine/Core/Events.ts +++ b/src/bundles/robot_simulation/engine/Core/Events.ts @@ -1,6 +1,6 @@ export type Listener = ( event: EventMap[EventName] -) => void | Promise; +) => Promise | void; type ValueIsEvent = { [EventName in keyof EventMap]: Event; @@ -15,7 +15,7 @@ export class TypedEventTarget> { this.listeners = {}; } - addEventListener( + addEventListener( type: EventName, listener: Listener, ): void { diff --git a/src/bundles/robot_simulation/engine/Entity/Entity.ts b/src/bundles/robot_simulation/engine/Entity/Entity.ts index bd826c88d..bb3bd40ff 100644 --- a/src/bundles/robot_simulation/engine/Entity/Entity.ts +++ b/src/bundles/robot_simulation/engine/Entity/Entity.ts @@ -1,12 +1,12 @@ -import type * as THREE from 'three'; import type Rapier from '@dimforge/rapier3d-compat'; +import type * as THREE from 'three'; +import { vec3 } from '../Math/Convert'; import { simpleVectorLength, type Orientation, type SimpleQuaternion, type SimpleVector, } from '../Math/Vector'; -import { vec3 } from '../Math/Convert'; type EntityConfig = { rapierRigidBody: Rapier.RigidBody; diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 1c5bc2d51..098edbea1 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -2,9 +2,9 @@ import rapier from '@dimforge/rapier3d-compat'; import type * as THREE from 'three'; -import { type SimpleVector } from './Math/Vector'; -import { type FrameTimingInfo } from './Core/Timer'; import { TypedEventTarget } from './Core/Events'; +import { type FrameTimingInfo } from './Core/Timer'; +import { type SimpleVector } from './Math/Vector'; export type PhysicsTimingInfo = FrameTimingInfo & { stepCount: number; @@ -41,7 +41,7 @@ type InitializedInternals = { stepCount: number; }; -type PhysicsInternals = NotInitializedInternals | InitializedInternals; +type PhysicsInternals = InitializedInternals | NotInitializedInternals; export class Physics extends TypedEventTarget { RAPIER: typeof rapier; @@ -96,9 +96,9 @@ export class Physics extends TypedEventTarget { maxDistance: number, excludeCollider?: rapier.Collider, ): { - distance: number; - normal: SimpleVector; - } | null { + distance: number; + normal: SimpleVector; + } | null { if (this.internals.initialized === false) { throw Error("Physics engine hasn't been initialized yet"); } diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts b/src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts index 101aa6d96..22d587781 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Render/helpers/Camera.ts @@ -2,7 +2,6 @@ import * as THREE from 'three'; import { CameraOptions, getCamera } from '../../../Render/helpers/Camera'; - describe('getCamera', () => { test('returns a PerspectiveCamera when type is "perspective"', () => { const cameraOptions: CameraOptions = { @@ -13,7 +12,6 @@ describe('getCamera', () => { far: 1000, }; - const camera = getCamera(cameraOptions); expect(camera).toBeInstanceOf(THREE.PerspectiveCamera); const perspectiveCamera = camera as THREE.PerspectiveCamera; @@ -33,9 +31,9 @@ describe('getCamera', () => { test('throws an error when type is unknown', () => { const cameraOptions: CameraOptions = { - // @ts-expect-error + // @ts-expect-error This is testing for an unknown type type: 'unknown', }; expect(() => getCamera(cameraOptions)).toThrowError('Unknown camera type'); }); -}); \ No newline at end of file +}); From 45af6f8bed963092fffc3af2b9f88d4a996c0f32 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 08:18:52 +0000 Subject: [PATCH 64/93] Fix more eslint errors --- .../engine/Helpers/FpsMonitor.ts | 50 ------------- .../engine/Render/Renderer.ts | 2 +- .../engine/__tests__/Core/CallbackHandler.ts | 18 ++--- .../engine/__tests__/Core/Controller.ts | 58 +++++++-------- .../engine/__tests__/Core/Events.ts | 58 +++++++-------- .../engine/__tests__/Core/Timer.ts | 38 +++++----- .../engine/__tests__/Entity/Entity.ts | 70 +++++++++---------- .../engine/__tests__/Physics.ts | 14 ++-- .../engine/__tests__/Render/MeshFactory.ts | 26 +++---- 9 files changed, 140 insertions(+), 194 deletions(-) delete mode 100644 src/bundles/robot_simulation/engine/Helpers/FpsMonitor.ts diff --git a/src/bundles/robot_simulation/engine/Helpers/FpsMonitor.ts b/src/bundles/robot_simulation/engine/Helpers/FpsMonitor.ts deleted file mode 100644 index 5a8f3ce2e..000000000 --- a/src/bundles/robot_simulation/engine/Helpers/FpsMonitor.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { DefaultEv3 } from '../../controllers'; -import type { Controller } from '../Core/Controller'; -import type { PhysicsTimingInfo } from '../Physics'; -import type { Renderer } from '../Render/Renderer'; - -export class FpsMonitor implements Controller { - render: Renderer; - overlayDiv: HTMLDivElement; - textContainer: HTMLDivElement; - ev3: DefaultEv3; - added = false; - - constructor(render: Renderer, ev3: DefaultEv3) { - this.ev3 = ev3; - this.render = render; - this.overlayDiv = document.createElement('div'); - this.overlayDiv.style.position = 'absolute'; - this.overlayDiv.style.color = 'black'; - this.overlayDiv.style.top = '0'; - this.overlayDiv.style.left = '0'; - this.overlayDiv.style.width = '100%'; // Cover the full area of the container - this.overlayDiv.style.height = '100%'; - this.overlayDiv.style.pointerEvents = 'none'; // Allows clicking through the div if needed - this.overlayDiv.id = 'fpsMonitor'; - this.textContainer = document.createElement('div'); - this.textContainer.style.position = 'absolute'; - this.textContainer.style.fontSize = '20px'; - this.textContainer.style.top = '300px'; - this.textContainer.style.left = '700px'; - this.textContainer.style.backgroundColor = 'white'; - - this.overlayDiv.appendChild(this.textContainer); - } - - fixedUpdate(timingInfo: PhysicsTimingInfo): void { - const fps = timingInfo.framesPerSecond.toFixed(2); - const distance = this.ev3.get('ultrasonicSensor') - .sense(); - const canvas = this.render.getElement(); - - if (timingInfo.stepCount % 2 === 0) { - this.textContainer.innerHTML = `FPS: ${fps}
Distance: ${distance}`; - } - - if (!this.added) { - canvas.parentNode!.appendChild(this.overlayDiv); - this.added = true; - } - } -} diff --git a/src/bundles/robot_simulation/engine/Render/Renderer.ts b/src/bundles/robot_simulation/engine/Render/Renderer.ts index b7b521827..fdee56c98 100644 --- a/src/bundles/robot_simulation/engine/Render/Renderer.ts +++ b/src/bundles/robot_simulation/engine/Render/Renderer.ts @@ -2,11 +2,11 @@ import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; -import { type FrameTimingInfo } from '../Core/Timer'; import { type GLTF, GLTFLoader, } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { type FrameTimingInfo } from '../Core/Timer'; type ControlType = 'none' | 'orbit'; diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts b/src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts index 5c858061f..c263000ab 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Core/CallbackHandler.ts @@ -1,5 +1,5 @@ -import { CallbackHandler } from "../../Core/CallbackHandler"; -import { PhysicsTimingInfo } from "../../Physics"; +import { CallbackHandler } from '../../Core/CallbackHandler'; +import { PhysicsTimingInfo } from '../../Physics'; // Helper function to create a PhysicsTimingInfo object // CallbackHandler only uses the stepCount and timestep properties @@ -14,8 +14,8 @@ const createTimingInfo = ({ return { stepCount, timestep } as PhysicsTimingInfo; }; -describe("CallbackHandler", () => { - test("adds callbacks correctly", () => { +describe('CallbackHandler', () => { + test('adds callbacks correctly', () => { const handler = new CallbackHandler(); const mockCallback = jest.fn(); @@ -26,7 +26,7 @@ describe("CallbackHandler", () => { expect(handler.callbackStore[0].callback).toBe(mockCallback); }); - test("executes callback after correct delay", () => { + test('executes callback after correct delay', () => { jest.useFakeTimers(); const handler = new CallbackHandler(); const mockCallback = jest.fn(); @@ -37,7 +37,7 @@ describe("CallbackHandler", () => { expect(mockCallback).toHaveBeenCalled(); }); - test("removes callback after execution", () => { + test('removes callback after execution', () => { const handler = new CallbackHandler(); const mockCallback = jest.fn(); @@ -47,7 +47,7 @@ describe("CallbackHandler", () => { expect(handler.callbackStore.length).toBe(0); }); - test("handles multiple callbacks correctly", () => { + test('handles multiple callbacks correctly', () => { const handler = new CallbackHandler(); const mockCallback1 = jest.fn(); const mockCallback2 = jest.fn(); @@ -63,7 +63,7 @@ describe("CallbackHandler", () => { expect(mockCallback2).toHaveBeenCalled(); }); - test("does not execute callback before its time", () => { + test('does not execute callback before its time', () => { const handler = new CallbackHandler(); const mockCallback = jest.fn(); @@ -73,7 +73,7 @@ describe("CallbackHandler", () => { expect(mockCallback).not.toHaveBeenCalled(); }); - test("correctly handles step count changes", () => { + test('correctly handles step count changes', () => { const handler = new CallbackHandler(); const mockCallback = jest.fn(); diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts b/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts index 7815c4d6e..a1c71753b 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Core/Controller.ts @@ -2,8 +2,8 @@ import { Controller, ControllerGroup, ControllerMap, -} from "../../Core/Controller"; -import { PhysicsTimingInfo } from "../../Physics"; +} from '../../Core/Controller'; +import { PhysicsTimingInfo } from '../../Physics'; // Helper function to create a PhysicsTimingInfo object // CallbackHandler only uses the stepCount and timestep properties @@ -12,26 +12,26 @@ const createTimingInfo = () => { return { stepCount: 1, timestep: 2 } as PhysicsTimingInfo; }; -describe("ControllerMap methods", () => { +describe('ControllerMap methods', () => { // Define test cases in an array of arrays. Each inner array represents parameters for a single test case. const methodsTestData: Array< [string, jest.Mock, { async: boolean; args?: any[] }] > = [ - ["start", jest.fn(), { async: true }], - ["update", jest.fn(), { async: false, args: [createTimingInfo()] }], - ["fixedUpdate", jest.fn(), { async: false, args: [createTimingInfo()] }], - ["onDestroy", jest.fn(), { async: false }], + ['start', jest.fn(), { async: true }], + ['update', jest.fn(), { async: false, args: [createTimingInfo()] }], + ['fixedUpdate', jest.fn(), { async: false, args: [createTimingInfo()] }], + ['onDestroy', jest.fn(), { async: false }], ]; test.each(methodsTestData)( - "%s calls %s on all contained controllers", + '%s calls %s on all contained controllers', async (methodName, mockMethod, { async, args = [] }) => { const notCalledMethod = jest.fn(); const controllerMap = new ControllerMap( { first: { [methodName]: mockMethod }, second: { [methodName]: mockMethod }, - // @ts-expect-error + // @ts-expect-error this test checks for a method that does not exist third: { notMethodName: notCalledMethod }, }, { [methodName]: mockMethod, notMethodName: notCalledMethod } @@ -54,7 +54,7 @@ describe("ControllerMap methods", () => { ); test.each(methodsTestData)( - "no calls if missing callbacks object", + 'no calls if missing callbacks object', async (methodName, mockMethod, { async, args = [] }) => { mockMethod.mockClear(); const controllerMap = new ControllerMap({}); @@ -67,15 +67,15 @@ describe("ControllerMap methods", () => { } ); - test("get returns the correct controller for a given key", () => { + test('get returns the correct controller for a given key', () => { // Setup: Create a couple of mock controllers const mockController1 = { - name: "Controller1", + name: 'Controller1', start: jest.fn(), update: jest.fn(), }; const mockController2 = { - name: "Controller2", + name: 'Controller2', start: jest.fn(), update: jest.fn(), }; @@ -85,32 +85,32 @@ describe("ControllerMap methods", () => { controller2: mockController2, }); - expect(controllerMap.get("controller1")).toBe(mockController1); - expect(controllerMap.get("controller2")).toBe(mockController2); + expect(controllerMap.get('controller1')).toBe(mockController1); + expect(controllerMap.get('controller2')).toBe(mockController2); - // @ts-expect-error - expect(controllerMap.get("controller3")).toBeUndefined(); + // @ts-expect-error This test checks for a key that does not exist + expect(controllerMap.get('controller3')).toBeUndefined(); }); }); -describe("ControllerGroup", () => { +describe('ControllerGroup', () => { // Define test data for each method const methodsTestData: Array<[string, { async: boolean; args: any[] }]> = [ - ["start", { async: true, args: [] }], - ["update", { async: false, args: [{ stepCount: 1, timestep: 20 }] }], // Assuming createTimingInfo() returns something similar - ["fixedUpdate", { async: false, args: [{ stepCount: 2, timestep: 15 }] }], - ["onDestroy", { async: false, args: [] }], + ['start', { async: true, args: [] }], + ['update', { async: false, args: [{ stepCount: 1, timestep: 20 }] }], // Assuming createTimingInfo() returns something similar + ['fixedUpdate', { async: false, args: [{ stepCount: 2, timestep: 15 }] }], + ['onDestroy', { async: false, args: [] }], ]; test.each(methodsTestData)( - "%s method behavior", + '%s method behavior', async (methodName, { async, args }) => { const mockMethod = jest.fn(); const notCalledMethod = jest.fn(); const controller: Controller = { [methodName]: mockMethod, - // @ts-expect-error - "notMethodName": notCalledMethod, + // @ts-expect-error This test checks for a method that does not exist + 'notMethodName': notCalledMethod, }; const controllerGroup = new ControllerGroup(); controllerGroup.addController(controller); @@ -123,7 +123,7 @@ describe("ControllerGroup", () => { } // Assertions - if (methodName === "onDestroy") { + if (methodName === 'onDestroy') { // Special case for onDestroy to check if controllers are cleared expect(controllerGroup.controllers).toHaveLength(0); } else { @@ -138,12 +138,12 @@ describe("ControllerGroup", () => { ); test.each(methodsTestData)( - "%s no method calls if controller does not have method name", + '%s no method calls if controller does not have method name', async (methodName, { async, args }) => { const notCalledMethod = jest.fn(); const controller: Controller = { - // @ts-expect-error - "notMethodName": notCalledMethod, + // @ts-expect-error This test checks for a method that does not exist + 'notMethodName': notCalledMethod, }; const controllerGroup = new ControllerGroup(); controllerGroup.addController(controller); diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Events.ts b/src/bundles/robot_simulation/engine/__tests__/Core/Events.ts index 489f99e4a..b3d38448d 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Core/Events.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Core/Events.ts @@ -1,4 +1,4 @@ -import { TypedEventTarget } from "../../Core/Events"; +import { TypedEventTarget } from '../../Core/Events'; class StringEvent extends Event { public data: string; @@ -23,63 +23,63 @@ interface EventMap { event2: NumberEvent; } -describe("TypedEventTarget", () => { +describe('TypedEventTarget', () => { let eventTarget: TypedEventTarget; beforeEach(() => { eventTarget = new TypedEventTarget(); }); - test("addEventListener adds a listener for the specified event type", () => { + test('addEventListener adds a listener for the specified event type', () => { const listener = jest.fn(); - eventTarget.addEventListener("event1", listener); - const event = new StringEvent("event1", "Hello"); - eventTarget.dispatchEvent("event1", event); + eventTarget.addEventListener('event1', listener); + const event = new StringEvent('event1', 'Hello'); + eventTarget.dispatchEvent('event1', event); expect(listener).toHaveBeenCalledWith(event); }); - test("addEventListener adds multiple listeners for the same event type", () => { + test('addEventListener adds multiple listeners for the same event type', () => { const listener1 = jest.fn(); const listener2 = jest.fn(); - eventTarget.addEventListener("event1", listener1); - eventTarget.addEventListener("event1", listener2); - const event = new StringEvent("event1", "Hello"); - eventTarget.dispatchEvent("event1", event); + eventTarget.addEventListener('event1', listener1); + eventTarget.addEventListener('event1', listener2); + const event = new StringEvent('event1', 'Hello'); + eventTarget.dispatchEvent('event1', event); expect(listener1).toHaveBeenCalledWith(event); expect(listener2).toHaveBeenCalledWith(event); }); - test("addEventListener adds listeners for different event types", () => { + test('addEventListener adds listeners for different event types', () => { const listener1 = jest.fn(); const listener2 = jest.fn(); - eventTarget.addEventListener("event1", listener1); - eventTarget.addEventListener("event2", listener2); - const event1 = new StringEvent("event1", "Hello"); - const event2 = new NumberEvent("event2", 42); - eventTarget.dispatchEvent("event1", event1); - eventTarget.dispatchEvent("event2", event2); + eventTarget.addEventListener('event1', listener1); + eventTarget.addEventListener('event2', listener2); + const event1 = new StringEvent('event1', 'Hello'); + const event2 = new NumberEvent('event2', 42); + eventTarget.dispatchEvent('event1', event1); + eventTarget.dispatchEvent('event2', event2); expect(listener1).toHaveBeenCalledWith(event1); expect(listener2).toHaveBeenCalledWith(event2); }); - test("dispatchEvent dispatches the event to the registered listeners", () => { + test('dispatchEvent dispatches the event to the registered listeners', () => { const listener1 = jest.fn(); const listener2 = jest.fn(); - eventTarget.addEventListener("event1", listener1); - eventTarget.addEventListener("event2", listener2); - const event1 = new StringEvent("event1", "Hello"); - const event2 = new NumberEvent("event2", 42); - eventTarget.dispatchEvent("event1", event1); - eventTarget.dispatchEvent("event2", event2); + eventTarget.addEventListener('event1', listener1); + eventTarget.addEventListener('event2', listener2); + const event1 = new StringEvent('event1', 'Hello'); + const event2 = new NumberEvent('event2', 42); + eventTarget.dispatchEvent('event1', event1); + eventTarget.dispatchEvent('event2', event2); expect(listener1).toHaveBeenCalledWith(event1); expect(listener2).toHaveBeenCalledWith(event2); }); - test("dispatchEvent returns true if there are listeners for the event type", () => { + test('dispatchEvent returns true if there are listeners for the event type', () => { const listener = jest.fn(); - eventTarget.addEventListener("event1", listener); - const event = new StringEvent("event1", "Hello"); - const result = eventTarget.dispatchEvent("event1", event); + eventTarget.addEventListener('event1', listener); + const event = new StringEvent('event1', 'Hello'); + const result = eventTarget.dispatchEvent('event1', event); expect(result).toBe(true); }); }); diff --git a/src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts b/src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts index 6050420cc..7eb057ab7 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Core/Timer.ts @@ -69,25 +69,25 @@ describe('Timer', () => { }); it('should correctly handle time after pause and resume', () => { - const startTimestamp = 1000; - const pauseTimestamp = 2000; - const resumeTimestamp = 3000; - const finalTimestamp = 4000; - - timer.step(startTimestamp); // Start the timer - timer.pause(); + const startTimestamp = 1000; + const pauseTimestamp = 2000; + const resumeTimestamp = 3000; + const finalTimestamp = 4000; - timer.step(pauseTimestamp); // Paused at 2000ms - timer.step(resumeTimestamp); // Resumed at 3000ms - const frameTimingInfo = timer.step(finalTimestamp); // Stepped at 4000ms - - // Total elapsed time should be 3000ms (4000 - 1000) - // Time spent paused is 1000ms (3000 - 2000) - // Therefore, elapsed simulated time should be 2000ms (3000 - 1000) - expect(frameTimingInfo.elapsedTimeSimulated).toBe(2000); - expect(frameTimingInfo.elapsedTimeReal).toBe(3000); - expect(frameTimingInfo.frameDuration).toBe(1000); // Time since last step - expect(frameTimingInfo.framesPerSecond).toBeCloseTo(1); // 1000ms frame duration - }); + timer.step(startTimestamp); // Start the timer + timer.pause(); + + timer.step(pauseTimestamp); // Paused at 2000ms + timer.step(resumeTimestamp); // Resumed at 3000ms + const frameTimingInfo = timer.step(finalTimestamp); // Stepped at 4000ms + + // Total elapsed time should be 3000ms (4000 - 1000) + // Time spent paused is 1000ms (3000 - 2000) + // Therefore, elapsed simulated time should be 2000ms (3000 - 1000) + expect(frameTimingInfo.elapsedTimeSimulated).toBe(2000); + expect(frameTimingInfo.elapsedTimeReal).toBe(3000); + expect(frameTimingInfo.frameDuration).toBe(1000); // Time since last step + expect(frameTimingInfo.framesPerSecond).toBeCloseTo(1); // 1000ms frame duration + }); }); }); diff --git a/src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts b/src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts index 08043587f..fdb620906 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Entity/Entity.ts @@ -1,7 +1,7 @@ -import { Entity } from "../../Entity/Entity"; -import type Rapier from "@dimforge/rapier3d-compat"; -import { SimpleQuaternion, SimpleVector } from "../../Math/Vector"; -import { vec3 } from "../../Math/Convert"; +import type Rapier from '@dimforge/rapier3d-compat'; +import { Entity } from '../../Entity/Entity'; +import { vec3 } from '../../Math/Convert'; +import { SimpleQuaternion, SimpleVector } from '../../Math/Vector'; const createRigidBodyMock = ( translation: SimpleVector, @@ -27,10 +27,10 @@ const createCollider = (mass: number) => { return colliderMock as unknown as Rapier.Collider; }; -describe("Entity", () => { +describe('Entity', () => { let entity: Entity; - describe("unit tests", () => { + describe('unit tests', () => { let rigidBody: Rapier.RigidBody; let collider: Rapier.Collider; let position: SimpleVector; @@ -46,25 +46,25 @@ describe("Entity", () => { }); }); - test("getCollider", () => { + test('getCollider', () => { expect(entity.getCollider()).toBeDefined(); expect(entity.getCollider()).toBe(collider); }); - test("getRigidBody", () => { + test('getRigidBody', () => { expect(entity.getRigidBody()).toBeDefined(); expect(entity.getRigidBody()).toBe(rigidBody); }); - test("getTranslation", () => { + test('getTranslation', () => { expect(entity.getTranslation()).toEqual(position); }); - test("getRotation", () => { + test('getRotation', () => { expect(entity.getRotation()).toEqual(rotation); }); - test("setOrientation", () => { + test('setOrientation', () => { const orientation = { position: { x: 1, y: 2, z: 3 }, rotation: { x: 0, y: 0, z: 0, w: 1 }, @@ -80,27 +80,27 @@ describe("Entity", () => { ); }); - test("setMass", () => { + test('setMass', () => { const mass = 2; entity.setMass(mass); expect(collider.setMass).toHaveBeenCalledWith(mass); }); - test("getMass", () => { + test('getMass', () => { expect(entity.getMass()).toBe(1); }); - test("getVelocity", () => { + test('getVelocity', () => { entity.getVelocity(); expect(rigidBody.linvel).toHaveBeenCalled(); }); - test("getAngularVelocity", () => { + test('getAngularVelocity', () => { entity.getAngularVelocity(); expect(rigidBody.angvel).toHaveBeenCalled(); }); - test("applyImpulse", () => { + test('applyImpulse', () => { const impulse = { x: 1, y: 2, z: 3 }; const point = { x: 0, y: 0, z: 0 }; entity.applyImpulse(impulse, point); @@ -111,32 +111,32 @@ describe("Entity", () => { ); }); - test("worldTranslation", () => { + test('worldTranslation', () => { const localTranslation = vec3({ x: 1, y: 2, z: 3 }); entity.worldTranslation(localTranslation); expect(rigidBody.rotation).toHaveBeenCalled(); expect(rigidBody.translation).toHaveBeenCalled(); }); - test("transformDirection", () => { + test('transformDirection', () => { const localDirection = vec3({ x: 1, y: 2, z: 3 }); entity.transformDirection(localDirection); expect(rigidBody.rotation).toHaveBeenCalled(); }); - test("distanceVectorOfPointToRotationalAxis", () => { + test('distanceVectorOfPointToRotationalAxis', () => { const localPoint = vec3({ x: 1, y: 2, z: 3 }); entity.distanceVectorOfPointToRotationalAxis(localPoint); expect(rigidBody.angvel).toHaveBeenCalled(); }); - test("tangentialVelocityOfPoint", () => { + test('tangentialVelocityOfPoint', () => { const localPoint = vec3({ x: 1, y: 2, z: 3 }); entity.tangentialVelocityOfPoint(localPoint); expect(rigidBody.angvel).toHaveBeenCalled(); }); - test("worldVelocity", () => { + test('worldVelocity', () => { const localPoint = vec3({ x: 1, y: 2, z: 3 }); entity.worldVelocity(localPoint); expect(rigidBody.linvel).toHaveBeenCalled(); @@ -144,10 +144,10 @@ describe("Entity", () => { }); }); - describe("end-to-end tests", () => { - describe("worldTranslation", () => { - test("no rotation", () => { - const rigidBodyTranslation = { x: 1, y: 1, z: 1 } + describe('end-to-end tests', () => { + describe('worldTranslation', () => { + test('no rotation', () => { + const rigidBodyTranslation = { x: 1, y: 1, z: 1 }; const rigidBody = createRigidBodyMock(rigidBodyTranslation, {x:0, y:0, z:0, w:1}); const collider = createCollider(1); @@ -165,8 +165,8 @@ describe("Entity", () => { }); }); - test("no local translation", () => { - const rigidBodyTranslation = { x: 1, y: 1, z: 1 } + test('no local translation', () => { + const rigidBodyTranslation = { x: 1, y: 1, z: 1 }; const rigidBody = createRigidBodyMock(rigidBodyTranslation, {x:1, y:0, z:0, w:1}); const collider = createCollider(1); @@ -175,13 +175,13 @@ describe("Entity", () => { rapierCollider: collider, }); - const localTranslation = vec3({ x: 0, y: 0, z: 0 }); + const localTranslation = vec3({ x: 0, y: 0, z: 0 }); const worldTranslation = entity.worldTranslation(localTranslation.clone()); expect(worldTranslation).toEqual(rigidBodyTranslation); }); - test("with rotation and local translation", () => { - const rigidBodyTranslation = { x: 12, y: 0, z: 0 } + test('with rotation and local translation', () => { + const rigidBodyTranslation = { x: 12, y: 0, z: 0 }; // 180 degree rotation around the x axis const rigidBody = createRigidBodyMock(rigidBodyTranslation, {x:0, y:1, z:0, w:0}); @@ -191,9 +191,7 @@ describe("Entity", () => { rapierCollider: collider, }); - - - const localTranslation = vec3({ x: 1, y: 0, z: 0 }); + const localTranslation = vec3({ x: 1, y: 0, z: 0 }); const worldTranslation = entity.worldTranslation(localTranslation.clone()); expect(worldTranslation).toEqual({ x: 11, @@ -203,8 +201,8 @@ describe("Entity", () => { }); }); - describe("transformDirection", () => { - test("no rotation", () => { + describe('transformDirection', () => { + test('no rotation', () => { const rigidBody = createRigidBodyMock({ x: 1, y: 1, z: 1 }, {x:0, y:0, z:0, w:1}); const collider = createCollider(1); const entity = new Entity({ @@ -217,7 +215,7 @@ describe("Entity", () => { expect(worldDirection).toEqual(localDirection); }); - test("with rotation and local translation", () => { + test('with rotation and local translation', () => { const rigidBody = createRigidBodyMock({ x: 1, y: 1, z: 1 }, {x:0, y:1, z:0, w:0}); const collider = createCollider(1); const entity = new Entity({ diff --git a/src/bundles/robot_simulation/engine/__tests__/Physics.ts b/src/bundles/robot_simulation/engine/__tests__/Physics.ts index dd68973d7..32d201e99 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Physics.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Physics.ts @@ -1,6 +1,6 @@ // physics.test.js -import { Physics } from '../Physics'; import rapier from '@dimforge/rapier3d-compat'; +import { Physics } from '../Physics'; // Mock rapier jest.mock('@dimforge/rapier3d-compat', () => { @@ -40,7 +40,6 @@ describe('Physics', () => { expect(physics.internals).toHaveProperty('accumulator', 0); }); - test('createRigidBody throws if not initialized', () => { expect(() => physics.createRigidBody({})).toThrow("Physics engine hasn't been initialized yet"); }); @@ -65,11 +64,11 @@ describe('Physics', () => { const globalPosition = {}; // Mocked global position const globalDirection = {}; // Mocked global direction const maxDistance = 100; - + // Mock the return value of castRayAndGetNormal const expectedResult = { toi: 10, normal: { x: 0, y: 1, z: 0 } }; physics.internals.world.castRayAndGetNormal.mockReturnValue(expectedResult); - + const result = physics.castRay(globalPosition, globalDirection, maxDistance); expect(result).toEqual({ distance: expectedResult.toi, @@ -83,11 +82,11 @@ describe('Physics', () => { const globalPosition = {}; // Mocked global position const globalDirection = {}; // Mocked global direction const maxDistance = 100; - + // Mock the return value of castRayAndGetNormal const expectedResult = null; physics.internals.world.castRayAndGetNormal.mockReturnValue(expectedResult); - + const result = physics.castRay(globalPosition, globalDirection, maxDistance); expect(result).toEqual(null); }); @@ -110,11 +109,10 @@ describe('Physics', () => { physics.createCollider({}, {}, 100); }).toThrow("Physics engine hasn't been initialized yet"); }); - + test('step throws if not initialized', () => { expect(() => { physics.step({ frameDuration: 1000 / 60 }); }).toThrow("Physics engine hasn't been initialized yet"); }); }); - diff --git a/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts b/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts index d30a37108..250dbcefc 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Render/MeshFactory.ts @@ -1,9 +1,9 @@ -import * as THREE from "three"; -import { addCuboid } from "../../Render/helpers/MeshFactory"; +import * as THREE from 'three'; +import { addCuboid } from '../../Render/helpers/MeshFactory'; // Mock the necessary Three.js methods and classes -jest.mock("three", () => { - const originalModule = jest.requireActual("three"); +jest.mock('three', () => { + const originalModule = jest.requireActual('three'); class Vector3 { x: number; @@ -63,8 +63,8 @@ jest.mock("three", () => { }; }); -describe("addCuboid", () => { - it("creates a cuboid with the correct dimensions and color", () => { +describe('addCuboid', () => { + it('creates a cuboid with the correct dimensions and color', () => { const orientation = { position: new THREE.Vector3(1, 2, 3), rotation: new THREE.Quaternion(0, 0, 0, 1), @@ -72,7 +72,7 @@ describe("addCuboid", () => { const width = 4; const height = 5; const length = 6; - const color = new THREE.Color("red"); + const color = new THREE.Color('red'); const debug = false; const mesh = addCuboid({ @@ -87,13 +87,13 @@ describe("addCuboid", () => { color, side: THREE.DoubleSide, }); - expect(mesh).toHaveProperty("geometry"); - expect(mesh).toHaveProperty("material"); + expect(mesh).toHaveProperty('geometry'); + expect(mesh).toHaveProperty('material'); expect(mesh.position).toEqual({ x: 1, y: 2, z: 3 }); expect(mesh.quaternion).toEqual({ x: 0, y: 0, z: 0, w: 1 }); }); - it("creates a cuboid with wireframe material when debug is true", () => { + it('creates a cuboid with wireframe material when debug is true', () => { const orientation = { position: new THREE.Vector3(1, 2, 3), rotation: new THREE.Quaternion(0, 0, 0, 1), @@ -101,7 +101,7 @@ describe("addCuboid", () => { const width = 4; const height = 5; const length = 6; - const color = new THREE.Color("red"); + const color = new THREE.Color('red'); const debug = true; // Enable debug mode const mesh = addCuboid({ @@ -121,8 +121,8 @@ describe("addCuboid", () => { }); // Verify the mesh properties - expect(mesh).toHaveProperty("geometry"); - expect(mesh).toHaveProperty("material"); + expect(mesh).toHaveProperty('geometry'); + expect(mesh).toHaveProperty('material'); expect(mesh.position).toEqual({ x: 1, y: 2, z: 3 }); expect(mesh.quaternion).toEqual({ x: 0, y: 0, z: 0, w: 1 }); }); From 64dbf7db92988ed4395032093e9c59cfd66fcee6 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 08:24:43 +0000 Subject: [PATCH 65/93] Fix more eslint errors --- src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts | 1 - src/tabs/RobotSimulation/index.tsx | 8 -------- 2 files changed, 9 deletions(-) diff --git a/src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts b/src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts index 463fb04a2..e7da37684 100644 --- a/src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts +++ b/src/tabs/RobotSimulation/hooks/fetchFromSimulation.ts @@ -1,6 +1,5 @@ import { useEffect, useState } from 'react'; - export const useFetchFromSimulation = (fetchFn:() => T, fetchInterval: number) => { const [fetchTime, setFetchTime] = useState(null); const [fetchedData, setFetchedData] = useState(null); diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/index.tsx index 4211d2135..9f797962c 100644 --- a/src/tabs/RobotSimulation/index.tsx +++ b/src/tabs/RobotSimulation/index.tsx @@ -7,14 +7,6 @@ import Main from './components/Main'; * @author Joel Chan */ -/** - * React Component props for the Tab. - */ -type Props = { - children?: never; - className?: never; - context?: any; -}; export default { /** From 9b594b387799189efa8f8de93bc5ef06a1d84adc Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 08:30:20 +0000 Subject: [PATCH 66/93] More linting errors --- src/tabs/RobotSimulation/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/index.tsx index 9f797962c..a497dcf36 100644 --- a/src/tabs/RobotSimulation/index.tsx +++ b/src/tabs/RobotSimulation/index.tsx @@ -7,7 +7,6 @@ import Main from './components/Main'; * @author Joel Chan */ - export default { /** * This function will be called to determine if the component will be From 097e45d428eb65dd6f3ec20d9234bb0f33cac847 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 08:56:26 +0000 Subject: [PATCH 67/93] update yarn.lock --- yarn.lock | 2146 ++++++++++++++--------------------------------------- 1 file changed, 575 insertions(+), 1571 deletions(-) diff --git a/yarn.lock b/yarn.lock index e90f642fd..0e46f8e8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,40 +23,11 @@ "@babel/highlight" "^7.24.1" picocolors "^1.0.0" -"@babel/code-frame@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== - dependencies: - "@babel/highlight" "^7.24.2" - picocolors "^1.0.0" - "@babel/compat-data@^7.23.5": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== -"@babel/core@^7.1.0", "@babel/core@^7.13.10": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" - integrity sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ== - dependencies: - "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.1" - "@babel/helper-compilation-targets" "^7.23.6" - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.24.1" - "@babel/parser" "^7.24.1" - "@babel/template" "^7.24.0" - "@babel/traverse" "^7.24.1" - "@babel/types" "^7.24.0" - convert-source-map "^2.0.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.3" - semver "^6.3.1" - "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.22.9", "@babel/core@^7.23.9": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.1.tgz#b802f931b6498dcb8fed5a4710881a45abbc2784" @@ -88,13 +59,6 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== - dependencies: - "@babel/types" "^7.22.5" - "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" @@ -106,21 +70,6 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz#db58bf57137b623b916e24874ab7188d93d7f68f" - integrity sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-member-expression-to-functions" "^7.23.0" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.24.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - semver "^6.3.1" - "@babel/helper-environment-visitor@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" @@ -141,13 +90,6 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-member-expression-to-functions@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" - integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== - dependencies: - "@babel/types" "^7.23.0" - "@babel/helper-module-imports@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" @@ -166,32 +108,11 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-optimise-call-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" - integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== - dependencies: - "@babel/types" "^7.22.5" - "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== -"@babel/helper-plugin-utils@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz#945681931a52f15ce879fd5b86ce2dae6d3d7f2a" - integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== - -"@babel/helper-replace-supers@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" - integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== - dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-member-expression-to-functions" "^7.23.0" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -199,13 +120,6 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" - integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== - dependencies: - "@babel/types" "^7.22.5" - "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" @@ -247,16 +161,6 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/highlight@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" - integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - picocolors "^1.0.0" - "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.19.4", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" @@ -297,13 +201,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" - integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/plugin-syntax-jsx@^7.7.2": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" @@ -360,13 +257,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-typescript@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" - integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/plugin-syntax-typescript@^7.7.2": version "7.20.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" @@ -374,15 +264,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" - integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== - dependencies: - "@babel/helper-module-transforms" "^7.23.3" - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-simple-access" "^7.22.5" - "@babel/plugin-transform-react-jsx-self@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz#ca2fdc11bc20d4d46de01137318b13d04e481d8e" @@ -397,27 +278,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-typescript@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.1.tgz#5c05e28bb76c7dfe7d6c5bed9951324fd2d3ab07" - integrity sha512-liYSESjX2fZ7JyBFkYG78nfvHlMKE6IpNdTVnxmlYUR+j5ZLsitFbaAE+eJSK2zPPkNWNw4mXL51rQ8WrvdK0w== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.24.1" - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/plugin-syntax-typescript" "^7.24.1" - -"@babel/preset-typescript@^7.13.0": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec" - integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== - dependencies: - "@babel/helper-plugin-utils" "^7.24.0" - "@babel/helper-validator-option" "^7.23.5" - "@babel/plugin-syntax-jsx" "^7.24.1" - "@babel/plugin-transform-modules-commonjs" "^7.24.1" - "@babel/plugin-transform-typescript" "^7.24.1" - "@babel/runtime@^7.11.2", "@babel/runtime@^7.17.8": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" @@ -425,14 +285,7 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.23.2": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" - integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.20.13" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA== @@ -522,34 +375,16 @@ dependencies: "@box2d/core" "^0.10.0" -"@cnakazawa/watch@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" - integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== - dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" - "@commander-js/extra-typings@^12.0.0": - version "12.0.1" - resolved "https://registry.yarnpkg.com/@commander-js/extra-typings/-/extra-typings-12.0.1.tgz#4a74a9ccf1d19ef24e71df59359c6d90a605a469" - integrity sha512-OvkMobb1eMqOCuJdbuSin/KJkkZr7n24/UNV+Lcz/0Dhepf3r2p9PaGwpRpAWej7A+gQnny4h8mGhpFl4giKkg== + version "12.0.0" + resolved "https://registry.yarnpkg.com/@commander-js/extra-typings/-/extra-typings-12.0.0.tgz#a3ef893e75dcf08bb1e51fc7e9fe8ed2d9246bf4" + integrity sha512-7zGCwtRKOJ978LCuEZbQ9ZmLdrRkNNASphEO5i9MZb6HfOk7KfsA3f4oXqYDhko4tNrU3GmZTlHqQ/nRlYtYSw== "@dimforge/rapier3d-compat@^0.11.2": version "0.11.2" resolved "https://registry.yarnpkg.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.11.2.tgz#ae2b335f545decf1e82ff45bb10368e143de0fcb" integrity sha512-vdWmlkpS3G8nGAzLuK7GYTpNdrkn/0NKCe0l1Jqxc7ZZOB3N0q9uG/Ap9l9bothWuAvxscIt0U97GVLr0lXWLg== -"@epilot/esbuild-jest@^0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@epilot/esbuild-jest/-/esbuild-jest-0.5.2.tgz#a544278922230f49baca6d92eb6d3376ccaa0c56" - integrity sha512-gtKtKKhNk7Qm5b/vwoqeoTucNINh92TQs4EEKG+86GmXdZfx4RcxX/tL99dE6zr+aXE/VjE54TFiZwleGS/4ig== - dependencies: - "@babel/core" "^7.13.10" - "@babel/plugin-transform-modules-commonjs" "^7.13.8" - "@babel/preset-typescript" "^7.13.0" - babel-jest "^26.6.3" - "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" @@ -778,7 +613,7 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.7.0": +"@jest/environment@^29.4.3", "@jest/environment@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7" integrity sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw== @@ -803,7 +638,7 @@ expect "^29.7.0" jest-snapshot "^29.7.0" -"@jest/fake-timers@^29.7.0": +"@jest/fake-timers@^29.4.3", "@jest/fake-timers@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565" integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ== @@ -891,27 +726,6 @@ jest-haste-map "^29.7.0" slash "^3.0.0" -"@jest/transform@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" - integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^26.6.2" - babel-plugin-istanbul "^6.0.0" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-regex-util "^26.0.0" - jest-util "^26.6.2" - micromatch "^4.0.2" - pirates "^4.0.1" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - "@jest/transform@^29.7.0": version "29.7.0" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.7.0.tgz#df2dd9c346c7d7768b8a06639994640c642e284c" @@ -933,18 +747,7 @@ slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - -"@jest/types@^29.6.3": +"@jest/types@^29.4.3", "@jest/types@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== @@ -1258,17 +1061,6 @@ mkdirp "^1.0.4" path-browserify "^1.0.1" -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": - version "7.20.5" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" - integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== - dependencies: - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - "@types/babel__core@^7.1.14": version "7.20.0" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" @@ -1330,13 +1122,6 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.52.tgz#7f1f57ad5b741f3d5b210d3b1f145640d89bf8fe" integrity sha512-BZWrtCU0bMVAIliIV+HJO1f1PR41M7NKjfxrFJwwhKI1KwhwOxYw1SXg9ao+CIMt774nFuGiG6eU+udtbEI9oQ== -"@types/graceful-fs@^4.1.2": - version "4.1.9" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" - integrity sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ== - dependencies: - "@types/node" "*" - "@types/graceful-fs@^4.1.3": version "4.1.6" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" @@ -1492,13 +1277,6 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== -"@types/yargs@^15.0.0": - version "15.0.19" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.19.tgz#328fb89e46109ecbdb70c295d96ff2f46dfd01b9" - integrity sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA== - dependencies: - "@types/yargs-parser" "*" - "@types/yargs@^17.0.8": version "17.0.22" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.22.tgz#7dd37697691b5f17d020f3c63e7a45971ff71e9a" @@ -1506,16 +1284,16 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz#de61c3083842fc6ac889d2fc83c9a96b55ab8328" - integrity sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw== +"@typescript-eslint/eslint-plugin@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.3.1.tgz#0d8f38a6c8a1802139e62184ee7a68ed024f30a1" + integrity sha512-STEDMVQGww5lhCuNXVSQfbfuNII5E08QWkvAw5Qwf+bj2WT+JkG1uc+5/vXA3AOYMDHVOSpL+9rcbEUiHIm2dw== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "7.4.0" - "@typescript-eslint/type-utils" "7.4.0" - "@typescript-eslint/utils" "7.4.0" - "@typescript-eslint/visitor-keys" "7.4.0" + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/type-utils" "7.3.1" + "@typescript-eslint/utils" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -1523,15 +1301,15 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.4.0.tgz#540f4321de1e52b886c0fa68628af1459954c1f1" - integrity sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ== +"@typescript-eslint/parser@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.3.1.tgz#c4ba7dc2744318a5e4506596cbc3a0086255c526" + integrity sha512-Rq49+pq7viTRCH48XAbTA+wdLRrB/3sRq4Lpk0oGDm0VmnjBrAOVXH/Laalmwsv2VpekiEfVFwJYVk6/e8uvQw== dependencies: - "@typescript-eslint/scope-manager" "7.4.0" - "@typescript-eslint/types" "7.4.0" - "@typescript-eslint/typescript-estree" "7.4.0" - "@typescript-eslint/visitor-keys" "7.4.0" + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/typescript-estree" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" debug "^4.3.4" "@typescript-eslint/scope-manager@5.52.0": @@ -1550,21 +1328,21 @@ "@typescript-eslint/types" "6.21.0" "@typescript-eslint/visitor-keys" "6.21.0" -"@typescript-eslint/scope-manager@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz#acfc69261f10ece7bf7ece1734f1713392c3655f" - integrity sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw== +"@typescript-eslint/scope-manager@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.3.1.tgz#73fd0cb4211a7be23e49e5b6efec8820caa6ec36" + integrity sha512-fVS6fPxldsKY2nFvyT7IP78UO1/I2huG+AYu5AMjCT9wtl6JFiDnsv4uad4jQ0GTFzcUV5HShVeN96/17bTBag== dependencies: - "@typescript-eslint/types" "7.4.0" - "@typescript-eslint/visitor-keys" "7.4.0" + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" -"@typescript-eslint/type-utils@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz#cfcaab21bcca441c57da5d3a1153555e39028cbd" - integrity sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw== +"@typescript-eslint/type-utils@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.3.1.tgz#cbf90d3d7e788466aa8a5c0ab3f46103f098aa0d" + integrity sha512-iFhaysxFsMDQlzJn+vr3OrxN8NmdQkHks4WaqD4QBnt5hsq234wcYdyQ9uquzJJIDAj5W4wQne3yEsYA6OmXGw== dependencies: - "@typescript-eslint/typescript-estree" "7.4.0" - "@typescript-eslint/utils" "7.4.0" + "@typescript-eslint/typescript-estree" "7.3.1" + "@typescript-eslint/utils" "7.3.1" debug "^4.3.4" ts-api-utils "^1.0.1" @@ -1578,10 +1356,10 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== -"@typescript-eslint/types@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.4.0.tgz#ee9dafa75c99eaee49de6dcc9348b45d354419b6" - integrity sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw== +"@typescript-eslint/types@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.3.1.tgz#ae104de8efa4227a462c0874d856602c5994413c" + integrity sha512-2tUf3uWggBDl4S4183nivWQ2HqceOZh1U4hhu4p1tPiIJoRRXrab7Y+Y0p+dozYwZVvLPRI6r5wKe9kToF9FIw== "@typescript-eslint/typescript-estree@5.52.0": version "5.52.0" @@ -1610,13 +1388,13 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/typescript-estree@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz#12dbcb4624d952f72c10a9f4431284fca24624f4" - integrity sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg== +"@typescript-eslint/typescript-estree@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.3.1.tgz#598848195fad34c7aa73f548bd00a4d4e5f5e2bb" + integrity sha512-tLpuqM46LVkduWP7JO7yVoWshpJuJzxDOPYIVWUUZbW+4dBpgGeUdl/fQkhuV0A8eGnphYw3pp8d2EnvPOfxmQ== dependencies: - "@typescript-eslint/types" "7.4.0" - "@typescript-eslint/visitor-keys" "7.4.0" + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/visitor-keys" "7.3.1" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -1624,17 +1402,17 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.4.0.tgz#d889a0630cab88bddedaf7c845c64a00576257bd" - integrity sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg== +"@typescript-eslint/utils@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.3.1.tgz#fc28fd508ccf89495012561b7c02a6fdad162460" + integrity sha512-jIERm/6bYQ9HkynYlNZvXpzmXWZGhMbrOvq3jJzOSOlKXsVjrrolzWBjDW6/TvT5Q3WqaN4EkmcfdQwi9tDjBQ== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "7.4.0" - "@typescript-eslint/types" "7.4.0" - "@typescript-eslint/typescript-estree" "7.4.0" + "@typescript-eslint/scope-manager" "7.3.1" + "@typescript-eslint/types" "7.3.1" + "@typescript-eslint/typescript-estree" "7.3.1" semver "^7.5.4" "@typescript-eslint/utils@^5.10.0": @@ -1680,12 +1458,12 @@ "@typescript-eslint/types" "6.21.0" eslint-visitor-keys "^3.4.1" -"@typescript-eslint/visitor-keys@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz#0c8ff2c1f8a6fe8d7d1a57ebbd4a638e86a60a94" - integrity sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA== +"@typescript-eslint/visitor-keys@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.3.1.tgz#6ddef14a3ce2a79690f01176f5305c34d7b93d8c" + integrity sha512-9RMXwQF8knsZvfv9tdi+4D/j7dMG28X/wMJ8Jj6eOHyHWwDW4ngQJcqEczSsqIKKjFiLFr40Mnr7a5ulDD3vmw== dependencies: - "@typescript-eslint/types" "7.4.0" + "@typescript-eslint/types" "7.3.1" eslint-visitor-keys "^3.4.1" "@ungap/structured-clone@^1.2.0": @@ -1852,14 +1630,6 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1893,27 +1663,12 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -aria-query@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" - integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== +aria-query@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.1.3.tgz#19db27cd101152773631396f7a95a3b58c22c35e" + integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ== dependencies: - dequal "^2.0.3" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== + deep-equal "^2.0.5" array-buffer-byte-length@^1.0.1: version "1.0.1" @@ -1923,7 +1678,7 @@ array-buffer-byte-length@^1.0.1: call-bind "^1.0.5" is-array-buffer "^3.0.4" -array-includes@^3.1.6, array-includes@^3.1.7: +array-includes@^3.1.5, array-includes@^3.1.6, array-includes@^3.1.7: version "3.1.7" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== @@ -1939,10 +1694,16 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== +array.prototype.filter@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz#423771edeb417ff5914111fff4277ea0624c0d0e" + integrity sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" array.prototype.findlastindex@^1.2.3: version "1.2.4" @@ -1955,7 +1716,7 @@ array.prototype.findlastindex@^1.2.3: es-errors "^1.3.0" es-shim-unscopables "^1.0.2" -array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: +array.prototype.flat@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== @@ -1965,7 +1726,7 @@ array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.2: +array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== @@ -1989,15 +1750,10 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== - -ast-types-flow@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" - integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== +ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag== astring@^1.4.3, astring@^1.8.6: version "1.8.6" @@ -2026,11 +1782,6 @@ atob-lite@^2.0.0: resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" integrity sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw== -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -2038,31 +1789,17 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axe-core@=4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" - integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== - -axobject-query@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" - integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== - dependencies: - dequal "^2.0.3" +axe-core@^4.6.2: + version "4.6.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.6.3.tgz#fc0db6fdb65cc7a80ccf85286d91d64ababa3ece" + integrity sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg== -babel-jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" - integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== +axobject-query@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" + integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg== dependencies: - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.6.2" - chalk "^4.0.0" - graceful-fs "^4.2.4" - slash "^3.0.0" + deep-equal "^2.0.5" babel-jest@^29.7.0: version "29.7.0" @@ -2077,7 +1814,7 @@ babel-jest@^29.7.0: graceful-fs "^4.2.9" slash "^3.0.0" -babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: +babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== @@ -2088,16 +1825,6 @@ babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" - integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" - "@types/babel__traverse" "^7.0.6" - babel-plugin-jest-hoist@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" @@ -2126,14 +1853,6 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" - integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== - dependencies: - babel-plugin-jest-hoist "^26.6.2" - babel-preset-current-node-syntax "^1.0.0" - babel-preset-jest@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" @@ -2152,19 +1871,6 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - basic-auth@^1.0.3: version "1.1.0" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.1.0.tgz#45221ee429f7ee1e5035be3f51533f1cdfd29884" @@ -2178,9 +1884,9 @@ bidi-js@^1.0.2: require-from-string "^2.0.2" binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== bindings@^1.5.0: version "1.5.0" @@ -2218,22 +1924,6 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2310,22 +2000,7 @@ cacache@^16.1.0: tar "^6.1.11" unique-filename "^2.0.0" -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== @@ -2370,9 +2045,9 @@ camera-unproject@1.0.1: integrity sha512-IAta9EeGGa9rLJsw9Fk0lrZycDg2fF6nl6AvJ+QrkROxc4IaawosU9PQjoqgFYrOe1+kqJlod/W2TAZkTpxZQg== caniuse-lite@^1.0.30001587: - version "1.0.30001600" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz#93a3ee17a35aa6a9f0c6ef1b2ab49507d1ab9079" - integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== + version "1.0.30001599" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce" + integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA== capital-case@^1.0.4: version "1.0.4" @@ -2383,13 +2058,6 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -capture-exit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" - chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -2408,9 +2076,9 @@ chalk@^4.0.0, chalk@^4.1.2: supports-color "^7.1.0" chalk@^5.0.1: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + version "5.2.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.2.0.tgz#249623b7d66869c673699fb66d65723e54dfcfb3" + integrity sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA== change-case@^4.1.2: version "4.1.2" @@ -2436,9 +2104,9 @@ char-regex@^1.0.2: integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== "chokidar@>=3.0.0 <4.0.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -2466,34 +2134,24 @@ ci-info@^2.0.0: integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== ci-info@^3.2.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" - integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + version "3.8.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" + integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cjs-module-lexer@^1.0.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" - integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== class-transformer@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - classnames@^2.3.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" - integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + version "2.3.2" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== clean-stack@^2.0.0: version "2.2.0" @@ -2515,17 +2173,9 @@ co@^4.6.0: integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== collect-v8-coverage@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" - integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== color-convert@^1.9.0: version "1.9.3" @@ -2581,11 +2231,6 @@ commist@^1.0.0: leven "^2.1.0" minimist "^1.1.0" -component-emitter@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" - integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2607,9 +2252,9 @@ console-control-strings@^1.1.0: integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== console-table-printer@^2.11.1: - version "2.12.0" - resolved "https://registry.yarnpkg.com/console-table-printer/-/console-table-printer-2.12.0.tgz#c1547684f7c34c5f129be7e524d9f62288d79cf5" - integrity sha512-Q/Ax+UOpZw0oPZGmv8bH8/W5NpC2rAYy6cX20BVLGQ45v944oL+srmLTZAse/5a3vWDl0MXR/0GTEdsz2dDTbg== + version "2.11.1" + resolved "https://registry.yarnpkg.com/console-table-printer/-/console-table-printer-2.11.1.tgz#c2dfe56e6343ea5bcfa3701a4be29fe912dbd9c7" + integrity sha512-8LfFpbF/BczoxPwo2oltto5bph8bJkGOATXsg3E9ddMJOGnWJciKHldx2zDj5XIBflaKzPfVCjOTl6tMh7lErg== dependencies: simple-wcswidth "^1.0.1" @@ -2622,7 +2267,7 @@ constant-case@^3.0.4: tslib "^2.0.3" upper-case "^2.0.2" -convert-source-map@^1.4.0: +convert-source-map@^1.6.0: version "1.9.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== @@ -2632,11 +2277,6 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== - core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -2667,7 +2307,7 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-spawn@^6.0.0, cross-spawn@^6.0.5: +cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -2710,9 +2350,9 @@ cssstyle@^2.3.0: cssom "~0.3.6" csstype@^3.0.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" - integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + version "3.1.1" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" + integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== damerau-levenshtein@^1.0.8: version "1.0.8" @@ -2756,9 +2396,9 @@ data-view-byte-offset@^1.0.0: is-data-view "^1.0.1" dayjs@^1.10.4: - version "1.11.10" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" - integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + version "1.11.7" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" + integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== debounce@^1.2.1: version "1.2.1" @@ -2772,13 +2412,6 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, d dependencies: ms "2.1.2" -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -2791,11 +2424,6 @@ decimal.js@^10.4.2: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== -decode-uri-component@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" - integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== - decompress-response@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" @@ -2808,20 +2436,43 @@ dedent@^1.0.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== +deep-equal@^2.0.5: + version "2.2.0" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.2.0.tgz#5caeace9c781028b9ff459f33b779346637c43e6" + integrity sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw== + dependencies: + call-bind "^1.0.2" + es-get-iterator "^1.1.2" + get-intrinsic "^1.1.3" + is-arguments "^1.1.1" + is-array-buffer "^3.0.1" + is-date-object "^1.0.5" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + isarray "^2.0.5" + object-is "^1.1.5" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + side-channel "^1.0.4" + which-boxed-primitive "^1.0.2" + which-collection "^1.0.1" + which-typed-array "^1.1.9" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@^0.1.3: +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" - integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + version "4.3.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.0.tgz#65491893ec47756d44719ae520e0e2609233b59b" + integrity sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og== define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" @@ -2832,7 +2483,7 @@ define-data-property@^1.0.1, define-data-property@^1.1.4: es-errors "^1.3.0" gopd "^1.0.1" -define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -2841,28 +2492,6 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -2878,11 +2507,6 @@ depd@^1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== -dequal@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" - integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== - detect-gpu@^5.0.28: version "5.0.38" resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-5.0.38.tgz#1c05ce728ea1229d16db15b865631609bf0d6952" @@ -2891,9 +2515,9 @@ detect-gpu@^5.0.28: webgl-constants "^1.1.1" detect-libc@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" - integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== detect-newline@^3.0.0: version "3.1.0" @@ -2970,14 +2594,14 @@ dtype@^2.0.0: integrity sha512-s2YVcLKdFGS0hpFqJaTwscsyt0E8nNFdmo73Ocd81xNPj4URI4rj6D60A+vFMIw7BXWlb4yRkEwfBqcZzPGiZg== duplexify@^4.1.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.3.tgz#a07e1c0d0a2c001158563d32592ba58bddb0236f" - integrity sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA== + version "4.1.2" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" + integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== dependencies: end-of-stream "^1.4.1" inherits "^2.0.3" readable-stream "^3.1.1" - stream-shift "^1.0.2" + stream-shift "^1.0.0" ecstatic@^3.3.2: version "3.3.2" @@ -2990,9 +2614,9 @@ ecstatic@^3.3.2: url-join "^2.0.5" electron-to-chromium@^1.4.668: - version "1.4.717" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.717.tgz#99db370cae8cd090d5b01f8748e9ad369924d0f8" - integrity sha512-6Fmg8QkkumNOwuZ/5mIbMU9WI3H2fmn5ajcVya64I5Yr5CcNmO7vcLt0Y7c96DCiMO5/9G+4sI2r6eEvdg1F7A== + version "1.4.710" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.710.tgz#d0ec4ea8a97df4c5eaeb8c69d45bf81f248b3855" + integrity sha512-w+9yAVHoHhysCa+gln7AzbO9CdjFcL/wN/5dd+XW/Msl2d/4+WisEaCF1nty0xbAKaxdaJfgLB2296U7zZB7BA== emittery@^0.13.1: version "0.13.1" @@ -3024,9 +2648,9 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: once "^1.4.0" entities@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + version "4.4.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" + integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== env-paths@^2.2.0: version "2.2.1" @@ -3045,10 +2669,10 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2: - version "1.23.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" - integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== +es-abstract@^1.20.4, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0: + version "1.23.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.1.tgz#85edfef168cd1bf9d45be3c83192a3a4b24db2d0" + integrity sha512-r+YVn6hTqQb+P5kK0u3KeDqrmhHKm+OhU/Mw4jSL4eQtOxXmp75fXIUUb3sUqFZOlb/YtW5JRaIfEC3UyjYUZQ== dependencies: array-buffer-byte-length "^1.0.1" arraybuffer.prototype.slice "^1.0.3" @@ -3087,8 +2711,8 @@ es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23 regexp.prototype.flags "^1.5.2" safe-array-concat "^1.1.2" safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.9" - string.prototype.trimend "^1.0.8" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" string.prototype.trimstart "^1.0.7" typed-array-buffer "^1.0.2" typed-array-byte-length "^1.0.1" @@ -3097,6 +2721,11 @@ es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23 unbox-primitive "^1.0.2" which-typed-array "^1.1.15" +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + es-define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" @@ -3104,30 +2733,25 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" -es-errors@^1.2.1, es-errors@^1.3.0: +es-errors@^1.0.0, es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-iterator-helpers@^1.0.15: - version "1.0.18" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" - integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== +es-get-iterator@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" + integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw== dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.0" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" + call-bind "^1.0.2" + get-intrinsic "^1.1.3" has-symbols "^1.0.3" - internal-slot "^1.0.7" - iterator.prototype "^1.1.2" - safe-array-concat "^1.1.2" + is-arguments "^1.1.1" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.7" + isarray "^2.0.5" + stop-iteration-iterator "^1.0.0" es-object-atoms@^1.0.0: version "1.0.0" @@ -3190,9 +2814,9 @@ esbuild@^0.18.10, esbuild@^0.18.20: "@esbuild/win32-x64" "0.18.20" escalade@^3.1.1: - version "3.1.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-string-regexp@^1.0.5: version "1.0.5" @@ -3210,13 +2834,14 @@ escape-string-regexp@^4.0.0: integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== escodegen@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" - integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== dependencies: esprima "^4.0.1" estraverse "^5.2.0" esutils "^2.0.2" + optionator "^0.8.1" optionalDependencies: source-map "~0.6.1" @@ -3278,26 +2903,26 @@ eslint-plugin-jest@^27.9.0: "@typescript-eslint/utils" "^5.10.0" eslint-plugin-jsx-a11y@^6.5.1: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz#2fa9c701d44fcd722b7c771ec322432857fcbad2" - integrity sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA== + version "6.7.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.7.1.tgz#fca5e02d115f48c9a597a6894d5bcec2f7a76976" + integrity sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== dependencies: - "@babel/runtime" "^7.23.2" - aria-query "^5.3.0" - array-includes "^3.1.7" - array.prototype.flatmap "^1.3.2" - ast-types-flow "^0.0.8" - axe-core "=4.7.0" - axobject-query "^3.2.1" + "@babel/runtime" "^7.20.7" + aria-query "^5.1.3" + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" + ast-types-flow "^0.0.7" + axe-core "^4.6.2" + axobject-query "^3.1.1" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" - es-iterator-helpers "^1.0.15" - hasown "^2.0.0" - jsx-ast-utils "^3.3.5" - language-tags "^1.0.9" + has "^1.0.3" + jsx-ast-utils "^3.3.3" + language-tags "=1.0.5" minimatch "^3.1.2" - object.entries "^1.1.7" - object.fromentries "^2.0.7" + object.entries "^1.1.6" + object.fromentries "^2.0.6" + semver "^6.3.0" eslint-plugin-react-hooks@^4.4.0: version "4.6.0" @@ -3424,39 +3049,16 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter3@^4.0.0: +eventemitter3@^4.0.0, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -exec-sh@^0.3.2: - version "0.3.6" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" - integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - execa@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" @@ -3492,19 +3094,6 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - expand-template@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" @@ -3526,44 +3115,15 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - 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" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.2.12, fast-glob@^3.2.9: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -3576,15 +3136,15 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-sta resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + version "1.15.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" @@ -3617,16 +3177,6 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -3663,18 +3213,17 @@ find-yarn-workspace-root@^2.0.0: micromatch "^4.0.2" flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^3.2.9" - keyv "^4.5.3" + flatted "^3.1.0" rimraf "^3.0.2" -flatted@^3.2.9: - version "3.3.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" - integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== flatten-vertex-data@^1.0.2: version "1.0.2" @@ -3695,11 +3244,6 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== - form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -3709,13 +3253,6 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== - dependencies: - map-cache "^0.2.2" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -3743,17 +3280,17 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.2: +function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: +function.prototype.name@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== @@ -3792,7 +3329,7 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== @@ -3808,13 +3345,6 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - get-stream@^5.0.0: version "5.2.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" @@ -3836,11 +3366,6 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== - github-from-package@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" @@ -3922,9 +3447,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + version "13.20.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== dependencies: type-fest "^0.20.2" @@ -3987,7 +3512,7 @@ gpu.js@^2.16.0: gpu-mock.js "^1.3.0" webgpu "^0.1.16" -graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -4041,36 +3566,12 @@ has-unicode@^2.0.1: resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + function-bind "^1.1.1" hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: version "2.0.2" @@ -4204,9 +3705,9 @@ ignore@^5.2.0, ignore@^5.2.4: integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== immutable@^4.0.0: - version "4.3.5" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" - integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== + version "4.3.4" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" + integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== import-fresh@^3.2.1: version "3.3.0" @@ -4252,12 +3753,17 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -internal-slot@^1.0.7: +internal-slot@^1.0.4, internal-slot@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.7.tgz#c06dcca3ed874249881007b0a5523b172a190802" integrity sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g== @@ -4266,22 +3772,20 @@ internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" -ip-address@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" - integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== - dependencies: - jsbn "1.1.0" - sprintf-js "^1.1.3" +ip@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" + integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== -is-accessor-descriptor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz#3223b10628354644b86260db29b3e693f5ceedd4" - integrity sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA== +is-arguments@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== dependencies: - hasown "^2.0.0" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" -is-array-buffer@^3.0.4: +is-array-buffer@^3.0.1, is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" integrity sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw== @@ -4294,13 +3798,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-async-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" - integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== - dependencies: - has-tostringtag "^1.0.0" - is-base64@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-base64/-/is-base64-0.1.0.tgz#a6f20610c6ef4863a51cba32bc0222544b932622" @@ -4338,11 +3835,6 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-buffer@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" @@ -4367,13 +3859,6 @@ is-core-module@^2.13.0, is-core-module@^2.13.1: dependencies: hasown "^2.0.0" -is-data-descriptor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz#2109164426166d32ea38c405c1e0945d9e6a4eeb" - integrity sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw== - dependencies: - hasown "^2.0.0" - is-data-view@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" @@ -4388,51 +3873,16 @@ is-date-object@^1.0.1, is-date-object@^1.0.5: dependencies: has-tostringtag "^1.0.0" -is-descriptor@^0.1.0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.7.tgz#2727eb61fd789dcd5bdf0ed4569f551d2fe3be33" - integrity sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg== - dependencies: - is-accessor-descriptor "^1.0.1" - is-data-descriptor "^1.0.1" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.3.tgz#92d27cb3cd311c4977a4db47df457234a13cb306" - integrity sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw== - dependencies: - is-accessor-descriptor "^1.0.1" - is-data-descriptor "^1.0.1" - is-docker@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-finalizationregistry@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" - integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== - dependencies: - call-bind "^1.0.2" - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" @@ -4443,13 +3893,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -4462,10 +3905,10 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-map@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" - integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== +is-map@^2.0.1, is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== is-negative-zero@^2.0.3: version "2.0.3" @@ -4479,13 +3922,6 @@ is-number-object@^1.0.4: dependencies: has-tostringtag "^1.0.0" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== - dependencies: - kind-of "^3.0.2" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -4496,13 +3932,6 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" @@ -4516,10 +3945,10 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-set@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" - integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== +is-set@^2.0.1, is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: version "1.0.3" @@ -4528,11 +3957,6 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: dependencies: call-bind "^1.0.7" -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -4559,15 +3983,10 @@ is-typed-array@^1.1.13: dependencies: which-typed-array "^1.1.14" -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-weakmap@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" - integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== +is-weakmap@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" + integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== is-weakref@^1.0.2: version "1.0.2" @@ -4576,18 +3995,13 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" -is-weakset@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007" - integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ== +is-weakset@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" + integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== dependencies: - call-bind "^1.0.7" - get-intrinsic "^1.2.4" - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + call-bind "^1.0.2" + get-intrinsic "^1.1.1" is-wsl@^2.1.1: version "2.2.0" @@ -4601,11 +4015,6 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== -isarray@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -4616,22 +4025,10 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" - integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4: version "5.2.1" @@ -4656,12 +4053,12 @@ istanbul-lib-instrument@^6.0.0: semver "^7.5.4" istanbul-lib-report@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" - integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== dependencies: istanbul-lib-coverage "^3.0.0" - make-dir "^4.0.0" + make-dir "^3.0.0" supports-color "^7.1.0" istanbul-lib-source-maps@^4.0.0: @@ -4674,28 +4071,17 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.7" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" - integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterator.prototype@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" - integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== - dependencies: - define-properties "^1.2.1" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - reflect.getprototypeof "^1.0.4" - set-function-name "^2.0.1" - its-fine@^1.0.6: - version "1.1.3" - resolved "https://registry.yarnpkg.com/its-fine/-/its-fine-1.1.3.tgz#703219c696b8093940ce8ce6c3a52258750d2989" - integrity sha512-mncCA+yb6tuh5zK26cHqKlsSyxm4zdm4YgJpxycyx6p9fgxgK5PLu3iDVpKhzTn57Yrv3jk/r0aK0RFTT1OjFw== + 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" @@ -4818,17 +4204,17 @@ jest-each@^29.7.0: pretty-format "^29.7.0" jest-environment-jsdom@^29.4.1: - version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz#d206fa3551933c3fd519e5dfdb58a0f5139a837f" - integrity sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA== + version "29.4.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.4.3.tgz#bd8ed3808e6d3f616403fbaf8354f77019613d90" + integrity sha512-rFjf8JXrw3OjUzzmSE5l0XjMj0/MSVEUMCSXBGPDkfwb1T03HZI7iJSL0cGctZApPSyJxbjyKDVxkZuyhHkuTw== dependencies: - "@jest/environment" "^29.7.0" - "@jest/fake-timers" "^29.7.0" - "@jest/types" "^29.6.3" + "@jest/environment" "^29.4.3" + "@jest/fake-timers" "^29.4.3" + "@jest/types" "^29.4.3" "@types/jsdom" "^20.0.0" "@types/node" "*" - jest-mock "^29.7.0" - jest-util "^29.7.0" + jest-mock "^29.4.3" + jest-util "^29.4.3" jsdom "^20.0.0" jest-environment-node@^29.7.0: @@ -4853,27 +4239,6 @@ jest-get-type@^29.6.3: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== -jest-haste-map@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" - integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== - dependencies: - "@jest/types" "^26.6.2" - "@types/graceful-fs" "^4.1.2" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-regex-util "^26.0.0" - jest-serializer "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.1.2" - jest-haste-map@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.7.0.tgz#3c2396524482f5a0506376e6c858c3bbcc17b104" @@ -4936,7 +4301,7 @@ jest-message-util@^29.7.0: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.7.0: +jest-mock@^29.4.3, jest-mock@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347" integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw== @@ -4950,11 +4315,6 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" - integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== - jest-regex-util@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" @@ -5038,14 +4398,6 @@ jest-runtime@^29.7.0: slash "^3.0.0" strip-bom "^4.0.0" -jest-serializer@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" - integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== - dependencies: - "@types/node" "*" - graceful-fs "^4.2.4" - jest-snapshot@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.7.0.tgz#c2c574c3f51865da1bb329036778a69bf88a6be5" @@ -5072,19 +4424,7 @@ jest-snapshot@^29.7.0: pretty-format "^29.7.0" semver "^7.5.3" -jest-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" - integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - micromatch "^4.0.2" - -jest-util@^29.0.0, jest-util@^29.7.0: +jest-util@^29.0.0, jest-util@^29.4.3, jest-util@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== @@ -5122,15 +4462,6 @@ jest-watcher@^29.7.0: jest-util "^29.7.0" string-length "^4.0.1" -jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - jest-worker@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.7.0.tgz#acad073acbbaeb7262bd5389e1bcf43e10058d4a" @@ -5162,9 +4493,9 @@ js-sdsl@4.3.0: integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== js-slang@^1.0.48: - version "1.0.52" - resolved "https://registry.yarnpkg.com/js-slang/-/js-slang-1.0.52.tgz#f24d504e09842fc6567789b45e8c9fc3a86a38d3" - integrity sha512-Ioe/XQFxAwZpDGNsg8MDKbC/0CvLXN+0mwhKrtm2jq6PHGa9oldZqykZZAlPYdC5XifI3DktKrRmuc94AIiKfA== + version "1.0.48" + resolved "https://registry.yarnpkg.com/js-slang/-/js-slang-1.0.48.tgz#e08a52a8472eeb18c573e7a9700865746b7bcdbe" + integrity sha512-XaGdlbzZsMbA46f+5/IUXi2vUx3hlDA8ctneWSXOY7BMPduiUUrDJzL2y27enGZbE4l1V8E1prNehO7zfS6Xuw== dependencies: "@babel/parser" "^7.19.4" "@joeychenofficial/alt-ergo-modified" "^2.4.0" @@ -5202,11 +4533,6 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsbn@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" - integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== - jsdom@^20.0.0: version "20.0.3" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" @@ -5244,11 +4570,6 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -5290,41 +4611,13 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsx-ast-utils@^3.3.5: - version "3.3.5" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" - integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== +jsx-ast-utils@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz#76b3e6e6cece5c69d49a5792c3d01bd1a0cdc7ea" + integrity sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw== dependencies: - array-includes "^3.1.6" - array.prototype.flat "^1.3.1" - object.assign "^4.1.4" - object.values "^1.1.6" - -keyv@^4.5.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== - dependencies: - is-buffer "^1.1.5" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + array-includes "^3.1.5" + object.assign "^4.1.3" klaw-sync@^6.0.0: version "6.0.0" @@ -5338,17 +4631,17 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -language-subtag-registry@^0.3.20: +language-subtag-registry@~0.3.2: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== -language-tags@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" - integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== +language-tags@=1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ== dependencies: - language-subtag-registry "^0.3.20" + language-subtag-registry "~0.3.2" leven@^2.1.0: version "2.1.0" @@ -5368,6 +4661,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -5441,9 +4742,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" lru-cache@^7.7.1: - version "7.18.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + version "7.16.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.16.1.tgz#7acea16fecd9ed11430e78443c2bb81a06d3dea9" + integrity sha512-9kkuMZHnLH/8qXARvYSjNvq8S1GYFFzynQTAfKeaJ0sIrR3PUPuu37Z+EiIANiZBvpfTf2B5y8ecDLSMWlLv+w== lunr@^2.3.9: version "2.3.9" @@ -5455,12 +4756,12 @@ maath@^0.10.7: resolved "https://registry.yarnpkg.com/maath/-/maath-0.10.7.tgz#9289b42a5db8ac5b26407b3bfca4e3bebefe50b4" integrity sha512-zQ2xd7dNOIVTjAS+hj22fyj1EFYmOJX6tzKjZ92r6WDoq8hyFxjuGA2q950tmR4iC/EKXoMQdSipkaJVuUHDTg== -make-dir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" - integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== dependencies: - semver "^7.5.3" + semver "^6.0.0" make-error@1.x: version "1.3.6" @@ -5496,18 +4797,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== - dependencies: - object-visit "^1.0.0" - marked@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" @@ -5531,34 +4820,15 @@ merge2@^1.3.0, merge2@^1.4.1: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== meshline@^3.1.6: - version "3.3.0" - resolved "https://registry.yarnpkg.com/meshline/-/meshline-3.3.0.tgz#8d926d3889ea9c37b37484961fbfbff0a464cd51" - integrity sha512-EKKf2TLnfyqUeA7ryWFKgT9HchTMATvECGZnMQjtlcyxK0sB8shVLVkemBUp9dB3tkDEmoqQDLJCPStjkH8D7A== + 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@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -5615,7 +4885,7 @@ minimatch@^5.0.1, minimatch@^5.1.0: dependencies: brace-expansion "^2.0.1" -minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -5666,10 +4936,10 @@ minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: dependencies: yallist "^4.0.0" -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +minipass@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.3.tgz#00bfbaf1e16e35e804f4aa31a7c1f6b8d9f0ee72" + integrity sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw== minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" @@ -5679,14 +4949,6 @@ minizlib@^2.1.1, minizlib@^2.1.2: minipass "^3.0.0" yallist "^4.0.0" -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" @@ -5736,11 +4998,6 @@ mqtt@^4.3.7: ws "^7.5.5" xtend "^4.0.2" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -5752,31 +5009,14 @@ ms@^2.0.0, ms@^2.1.1: integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== nan@^2.17.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" - integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== - -nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" + 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" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== napi-build-utils@^1.0.1: version "1.0.2" @@ -5852,13 +5092,6 @@ nopt@^6.0.0: dependencies: abbrev "^1.0.0" -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== - dependencies: - remove-trailing-separator "^1.0.1" - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -5869,13 +5102,6 @@ normalize.css@^8.0.1: resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== - dependencies: - path-key "^2.0.0" - npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -5902,42 +5128,34 @@ number-allocator@^1.0.9: js-sdsl "4.3.0" nwsapi@^2.2.2: - version "2.2.7" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" - integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ== + version "2.2.2" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-inspect@^1.13.1: +object-inspect@^1.13.1, object-inspect@^1.9.0: version "1.13.1" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-is@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== - dependencies: - isobject "^3.0.0" - -object.assign@^4.1.4, object.assign@^4.1.5: +object.assign@^4.1.3, object.assign@^4.1.4, object.assign@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== @@ -5947,49 +5165,43 @@ object.assign@^4.1.4, object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" - integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== +object.entries@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.6.tgz#9737d0e5b8291edd340a3e3264bb8a3b00d5fa23" + integrity sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w== dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" -object.fromentries@^2.0.7: - version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" - integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== +object.fromentries@^2.0.6, object.fromentries@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" + integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.2" - es-object-atoms "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" object.groupby@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" - integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + version "1.0.2" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.2.tgz#494800ff5bab78fd0eff2835ec859066e00192ec" + integrity sha512-bzBq58S+x+uo0VjurFT0UktpKHOZmv4/xePiOA1nbB9pMqpGK7rUPNgf+1YC+7mE+0HzhTMqNUuCqvKhj6FnBw== dependencies: - call-bind "^1.0.7" + array.prototype.filter "^1.0.3" + call-bind "^1.0.5" define-properties "^1.2.1" - es-abstract "^1.23.2" + es-abstract "^1.22.3" + es-errors "^1.0.0" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== +object.values@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" + integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== dependencies: - isobject "^3.0.1" - -object.values@^1.1.6, object.values@^1.1.7: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" - integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" @@ -6018,6 +5230,18 @@ opener@^1.5.1: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -6040,11 +5264,6 @@ os@^0.1.2: resolved "https://registry.yarnpkg.com/os/-/os-0.1.2.tgz#f29a50c62908516ba42652de42f7038600cadbc2" integrity sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ== -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -6125,11 +5344,6 @@ pascal-case@^3.1.2: no-case "^3.0.4" tslib "^2.0.3" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== - patch-package@^6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.5.1.tgz#3e5d00c16997e6160291fee06a521c42ac99b621" @@ -6173,7 +5387,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^2.0.0, path-key@^2.0.1: +path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== @@ -6193,12 +5407,21 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +path@^0.12.7: + version "0.12.7" + resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" + integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q== + dependencies: + process "^0.11.1" + util "^0.10.3" + phaser@^3.54.0: - version "3.80.1" - resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.80.1.tgz#d387d7e04042218f74b9d22e261f437e54464440" - integrity sha512-VQGAWoDOkEpAWYkI+PUADv5Ql+SM0xpLuAMBJHz9tBcOLqjJ2wd8bUhxJgOqclQlLTg97NmMd9MhS75w16x1Cw== + version "3.55.2" + resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.55.2.tgz#c1e2e9e70de7085502885e06f46b7eb4bd95e29a" + integrity sha512-amKXsbb2Ht29dGPKvt1edq3yGGYKtq8373GpJYGKPNPnneYY6MtVTOgjHDuZwtmUyK4v86FugkT3hzW/N4tjxQ== dependencies: - eventemitter3 "^5.0.1" + eventemitter3 "^4.0.7" + path "^0.12.7" picocolors@^1.0.0: version "1.0.0" @@ -6215,10 +5438,10 @@ picomatch@^4.0.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.1.tgz#68c26c8837399e5819edce48590412ea07f17a07" integrity sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg== -pirates@^4.0.1, pirates@^4.0.4: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== pkg-dir@^4.2.0: version "4.2.0" @@ -6228,9 +5451,9 @@ pkg-dir@^4.2.0: find-up "^4.0.0" plotly.js-dist@^2.17.1: - version "2.30.1" - resolved "https://registry.yarnpkg.com/plotly.js-dist/-/plotly.js-dist-2.30.1.tgz#98ef3f6e5d5f34d53390c2e8ff5d2668bfcfb4d8" - integrity sha512-Ta0HV6IvEE8auL6EgYMATsG58k+9ypva2Ti3YDGyau0ec9AqH4hZAryyxTcBkWC+jqpasVqSD1khpFByDLrvqg== + version "2.18.2" + resolved "https://registry.yarnpkg.com/plotly.js-dist/-/plotly.js-dist-2.18.2.tgz#f32834e15985b98dbc1c74102ac59f3ced5eda48" + integrity sha512-HZonDoQe7MFHFp7A4U6sd/Inz5Gd5kv4ozr3vM26utbF9GH2WC2OCuNycIQCJzoY8CtlFpJCHHek+JkBUUfrZg== portfinder@^1.0.25: version "1.0.32" @@ -6241,24 +5464,19 @@ portfinder@^1.0.25: debug "^3.2.7" mkdirp "^0.5.6" -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== - possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" integrity sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q== postcss@^8.4.27: - version "8.4.38" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" - integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== dependencies: - nanoid "^3.3.7" + nanoid "^3.3.6" picocolors "^1.0.0" - source-map-js "^1.2.0" + source-map-js "^1.0.2" postinstall-postinstall@^2.1.0: version "2.1.0" @@ -6271,9 +5489,9 @@ potpack@^1.0.1: integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ== prebuild-install@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" - integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== dependencies: detect-libc "^2.0.0" expand-template "^2.0.3" @@ -6293,6 +5511,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + pretty-format@^27.0.0, pretty-format@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" @@ -6316,6 +5539,11 @@ process-nextick-args@^2.0.1: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.11.1: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -6365,21 +5593,21 @@ punycode@^1.4.1: integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== punycode@^2.1.0, punycode@^2.1.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== pure-rand@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2" - integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== + version "6.0.4" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" + integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== qs@^6.11.2, qs@^6.4.0: - version "6.12.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.0.tgz#edd40c3b823995946a8a0b1f208669c7a200db77" - integrity sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg== + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== dependencies: - side-channel "^1.0.6" + side-channel "^1.0.4" querystringify@^2.1.1: version "2.2.0" @@ -6433,9 +5661,9 @@ react-dom@^18.2.0: scheduler "^0.23.0" react-fast-compare@^3.0.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" - integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" + integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== react-hotkeys@^2.0.0: version "2.0.0" @@ -6548,22 +5776,9 @@ readdirp@~3.6.0: picomatch "^2.2.1" reflect-metadata@^0.1.13: - version "0.1.14" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.14.tgz#24cf721fe60677146bb77eeb0e1f9dece3d65859" - integrity sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A== - -reflect.getprototypeof@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" - integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg== - dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-abstract "^1.23.1" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - globalthis "^1.0.3" - which-builtin-type "^1.1.3" + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== regenerator-runtime@^0.13.11: version "0.13.11" @@ -6575,15 +5790,7 @@ regenerator-runtime@^0.14.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexp.prototype.flags@^1.5.2: +regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" integrity sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw== @@ -6603,21 +5810,6 @@ reinterval@^1.1.0: resolved "https://registry.yarnpkg.com/reinterval/-/reinterval-1.1.0.tgz#3361ecfa3ca6c18283380dd0bb9546f390f5ece7" integrity sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ== -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -6650,15 +5842,10 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== - resolve.exports@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" - integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.0.tgz#c1a0028c2d166ec2fbf7d0644584927e76e7400e" + integrity sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg== resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.4: version "1.22.8" @@ -6669,11 +5856,6 @@ resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -6704,17 +5886,12 @@ rimraf@^3.0.2: glob "^7.1.3" rollup@^3.27.1: - version "3.29.4" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" - integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== + version "3.29.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.1.tgz#ba53a179d46ac3cd79e162dca6ab70d93cd26f78" + integrity sha512-c+ebvQz0VIH4KhhCpDsI+Bik0eT8ZFEVZEYw0cGMVqIP8zc+gnwl7iXCamTw7vzv2MeuZFZfdx5JJIq+ehzDlg== optionalDependencies: fsevents "~2.3.2" -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" @@ -6723,9 +5900,9 @@ run-parallel@^1.1.9: queue-microtask "^1.2.2" saar@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/saar/-/saar-1.0.5.tgz#7211e982973c831cd2f71171efedd17ddccd5af7" - integrity sha512-QQYWEb3dRUGWn8WSPb+n0/LNI3Gs2zgakuFl4QuK4co/nUy7TSg3A5/Rl15W+Ucc30BHnGeyVMEyy9rHmiHDiA== + version "1.0.4" + resolved "https://registry.yarnpkg.com/saar/-/saar-1.0.4.tgz#e22ccb688303fddc40341bc436e6c8c162967e43" + integrity sha512-wsbTNV1EnMvbA9xtcr2JG91Dy3eykWC6Dacs1XoTwjWyjbab4npr9e2UVq3o58elryo45z7AOuOpydRXmlvu4A== dependencies: "@react-spring/three" "9.6.1" "@react-three/drei" "^9.97.0" @@ -6761,37 +5938,15 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== - dependencies: - ret "~0.1.10" - "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - sass@^1.66.1: - version "1.72.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.72.0.tgz#5b9978943fcfb32b25a6a5acb102fc9dabbbf41c" - integrity sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA== + version "1.66.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.66.1.tgz#04b51c4671e4650aa393740e66a4e58b44d055b1" + integrity sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -6840,15 +5995,15 @@ semver@^5.5.0, semver@^5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.0, semver@^6.3.1: +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" @@ -6888,16 +6043,6 @@ set-function-name@^2.0.1: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - shallow-equal@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" @@ -6937,17 +6082,16 @@ shiki@^0.14.7: vscode-oniguruma "^1.7.0" vscode-textmate "^8.0.0" -side-channel@^1.0.4, side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: +signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -7004,36 +6148,6 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - socks-proxy-agent@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" @@ -7044,11 +6158,11 @@ socks-proxy-agent@^7.0.0: socks "^2.6.2" socks@^2.6.2: - version "2.8.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.1.tgz#22c7d9dd7882649043cba0eafb49ae144e3457af" - integrity sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ== + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== dependencies: - ip-address "^9.0.5" + ip "^2.0.0" smart-buffer "^4.2.0" source-academy-utils@^1.0.0: @@ -7057,31 +6171,19 @@ source-academy-utils@^1.0.0: integrity sha512-cSx/Rxr0CEOr+KJKILKicOVSVknG82fMEozaituD5mjh92przLW8C4kafzXrfGMjPVb6p7lxFMk5S6QyiYI2/g== source-academy-wabt@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/source-academy-wabt/-/source-academy-wabt-1.1.3.tgz#9a89baea285ac6034cff97d4a15fd580382e6515" - integrity sha512-bR8u48i0Kyb5R0ZZykDYwpe3xOgMLRPkOZ7TraS2L9y28JdxhJV9B7pAXyh4CFog1DIn6XIDVYjUwrUN8OtENQ== + version "1.0.10" + resolved "https://registry.yarnpkg.com/source-academy-wabt/-/source-academy-wabt-1.0.10.tgz#4187804a10b8233dc0f3498b49d07523940b4789" + integrity sha512-eRm9Q+wm9rNKpaX3X+ykKjcLyrV2O6elAIG3qmkuOeOLk3f26QEFfroBvNxLtvVokkItWRHek9T/d5Gqrqo5tQ== dependencies: - "@epilot/esbuild-jest" "^0.5.2" class-transformer "^0.5.1" lodash "^4.17.21" reflect-metadata "^0.1.13" typescript "4" -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" - integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== source-map-support@0.5.13: version "0.5.13" @@ -7091,33 +6193,16 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - source-map@0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== - source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - split2@^3.1.0: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" @@ -7130,11 +6215,6 @@ spring@^0.0.0: resolved "https://registry.yarnpkg.com/spring/-/spring-0.0.0.tgz#27a9f571d49f3f29e90c6b9625364073a8353815" integrity sha512-hQKa8vrkjMLCR5HUMQNRS5oUA0FKSgSvpLSyu6dcNUBc1uCfZ+TssIQXcKQtV4JP8QYGwKtz+6d630ovL4lzeg== -sprintf-js@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -7154,25 +6234,24 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - stats-gl@^2.0.0: - version "2.2.7" - resolved "https://registry.yarnpkg.com/stats-gl/-/stats-gl-2.2.7.tgz#9b605f7dfcd3289f1d7791bda5094b8031b35c60" - integrity sha512-3EjvpmVgUic2YxCM+dxwg68B0tzWqMCAmflmdbqEKuqwZL+huYieqV14crm80NB7r2F4mWaYcLLlcpbhteEagw== + 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== -stream-shift@^1.0.2: +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" + integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ== + dependencies: + internal-slot "^1.0.4" + +stream-shift@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== @@ -7202,7 +6281,7 @@ string-to-arraybuffer@^1.0.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string.prototype.trim@^1.2.9: +string.prototype.trim@^1.2.8: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== @@ -7212,7 +6291,7 @@ string.prototype.trim@^1.2.9: es-abstract "^1.23.0" es-object-atoms "^1.0.0" -string.prototype.trimend@^1.0.8: +string.prototype.trimend@^1.0.7: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== @@ -7222,13 +6301,13 @@ string.prototype.trimend@^1.0.8: es-object-atoms "^1.0.0" string.prototype.trimstart@^1.0.7: - version "1.0.8" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" - integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + version "1.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== dependencies: - call-bind "^1.0.7" - define-properties "^1.2.1" - es-object-atoms "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" string_decoder@^1.1.1: version "1.3.0" @@ -7259,11 +6338,6 @@ strip-bom@^4.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -7286,7 +6360,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -7337,13 +6411,13 @@ tar-stream@^2.1.4: readable-stream "^3.1.1" tar@^6.1.11, tar@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" - integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + version "6.1.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" - minipass "^5.0.0" + minipass "^4.0.0" minizlib "^2.1.1" mkdirp "^1.0.3" yallist "^4.0.0" @@ -7368,9 +6442,9 @@ three-mesh-bvh@^0.7.0: integrity sha512-3W6KjzmupjfE89GuHPT31kxKWZ4YGZPEZJNysJpiOZfQRsBQQgmK7v/VJPpjG6syhAvTnY+5Fr77EvIkTLpGSw== three-stdlib@^2.21.1, three-stdlib@^2.29.4: - version "2.29.6" - resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.29.6.tgz#d1d17a7e5d48921ebb3aed9f5131bb85968e4d29" - integrity sha512-nj9bHkzhhwfmqQcM/keC2RDb0bHhbw6bRXTy81ehzi8F1rtp6pJ5eS0/vl1Eg5RMFqXOMyxJ6sDHPoLU+IrVZg== + 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" @@ -7423,21 +6497,6 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -7445,16 +6504,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - tough-cookie@^4.1.2: version "4.1.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" @@ -7559,6 +6608,13 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -7607,9 +6663,9 @@ typed-array-byte-offset@^1.0.2: is-typed-array "^1.1.13" typed-array-length@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" - integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== + version "1.0.5" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.5.tgz#57d44da160296d8663fd63180a1802ebf25905d5" + integrity sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA== dependencies: call-bind "^1.0.7" for-each "^0.3.3" @@ -7618,13 +6674,6 @@ typed-array-length@^1.0.5: is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -7641,13 +6690,12 @@ typedoc@^0.25.12: shiki "^0.14.7" typescript-eslint@^7.3.1: - version "7.4.0" - resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-7.4.0.tgz#2a9b35e99a6292e59ff72bce3ef56e10d8b20b40" - integrity sha512-8GYQsb/joknlAZEAs/kqonfrsAc98C5DoellmwHREPqKwSTKSY2YB93IwmvNuX6+WE5QkKc31X9wHo/UcpYXpw== + version "7.3.1" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-7.3.1.tgz#9f4808abea3b33c4dd3bb51dd801471e91d1bd58" + integrity sha512-psqcnHPRCdVIDbgj6RvfpwUKqMcNxIw7eizgxYi46X2BmXK6LxYqPD+SbDfPuA9JW+yPItY6aKJLRNbW7lZ4rA== dependencies: - "@typescript-eslint/eslint-plugin" "7.4.0" - "@typescript-eslint/parser" "7.4.0" - "@typescript-eslint/utils" "7.4.0" + "@typescript-eslint/eslint-plugin" "7.3.1" + "@typescript-eslint/parser" "7.3.1" typescript@4: version "4.9.5" @@ -7674,16 +6722,6 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - union@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/union/-/union-0.5.0.tgz#b2c11be84f60538537b846edb9ba266ba0090075" @@ -7716,17 +6754,9 @@ universalify@^0.2.0: integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== update-browserslist-db@^1.0.13: version "1.0.13" @@ -7757,11 +6787,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== - url-join@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728" @@ -7788,16 +6813,18 @@ use-sync-external-store@1.2.0, use-sync-external-store@^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== -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + 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" @@ -7809,18 +6836,18 @@ uuid@^9.0.1: integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== v8-to-istanbul@^9.0.1: - version "9.2.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" - integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + version "9.1.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" + integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^2.0.0" + convert-source-map "^1.6.0" vite@^4.5.2: - version "4.5.3" - resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.3.tgz#d88a4529ea58bae97294c7e2e6f0eab39a50fb1a" - integrity sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg== + version "4.5.2" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.2.tgz#d6ea8610e099851dad8c7371599969e0f8b97e82" + integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w== dependencies: esbuild "^0.18.10" postcss "^8.4.27" @@ -7845,7 +6872,7 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" -walker@^1.0.7, walker@^1.0.8, walker@~1.0.5: +walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== @@ -7910,33 +6937,15 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-builtin-type@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" - integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== - dependencies: - function.prototype.name "^1.1.5" - has-tostringtag "^1.0.0" - is-async-function "^2.0.0" - is-date-object "^1.0.5" - is-finalizationregistry "^1.0.2" - is-generator-function "^1.0.10" - is-regex "^1.1.4" - is-weakref "^1.0.2" - isarray "^2.0.5" - which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.9" - which-collection@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" - integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + version "1.0.1" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" + integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== dependencies: - is-map "^2.0.3" - is-set "^2.0.3" - is-weakmap "^2.0.2" - is-weakset "^2.0.3" + is-map "^2.0.1" + is-set "^2.0.1" + is-weakmap "^2.0.1" + is-weakset "^2.0.1" which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: version "1.1.15" @@ -7970,6 +6979,11 @@ wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" +word-wrap@~1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -7984,16 +6998,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - write-file-atomic@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" @@ -8015,9 +7019,9 @@ ws@^7.5.5: integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== ws@^8.11.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" - integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== + version "8.12.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.12.1.tgz#c51e583d79140b5e42e39be48c934131942d4a8f" + integrity sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== xml-name-validator@^4.0.0: version "4.0.0" @@ -8067,9 +7071,9 @@ yargs-parser@^21.0.1, yargs-parser@^21.1.1: integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.3.1: - version "17.7.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + version "17.7.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.0.tgz#b21e9af1e0a619a2a9c67b1133219b2975a07985" + integrity sha512-dwqOPg5trmrre9+v8SUo2q/hAwyKoVfu8OC1xPHKJGNdxAvPl4sKxL4vBnh3bQz/ZvvGAFeA5H3ou2kcOY8sQQ== dependencies: cliui "^8.0.1" escalade "^3.1.1" @@ -8098,8 +7102,8 @@ zustand@^3.7.1: integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA== zustand@^4.3.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.2.tgz#fddbe7cac1e71d45413b3682cdb47b48034c3848" - integrity sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g== + version "4.5.1" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.1.tgz#2088956ee454759fb8b866ca335a2373e76736c5" + integrity sha512-XlauQmH64xXSC1qGYNv00ODaQ3B+tNPoy22jv2diYiP4eoDKr9LA+Bh5Bc3gplTrFdb6JVI+N4kc1DZ/tbtfPg== dependencies: use-sync-external-store "1.2.0" From 51210f247cfb8c6ae2dfd1d31de9200ea4f6a316 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 09:09:14 +0000 Subject: [PATCH 68/93] Change the react components to React.FC --- src/tabs/RobotSimulation/components/Main.tsx | 14 +++++++++----- src/tabs/RobotSimulation/components/Modal.tsx | 4 ++-- .../components/Simulation/index.tsx | 11 +++++------ src/tabs/RobotSimulation/components/TabUi.tsx | 6 ++++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/tabs/RobotSimulation/components/Main.tsx b/src/tabs/RobotSimulation/components/Main.tsx index e28362849..f7437a8bf 100644 --- a/src/tabs/RobotSimulation/components/Main.tsx +++ b/src/tabs/RobotSimulation/components/Main.tsx @@ -1,10 +1,14 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; import { type DebuggerContext } from '../../../typings/type_helpers'; import { Modal } from './Modal'; -import SimulationCanvas from './Simulation'; -import TabUi from './TabUi'; +import { SimulationCanvas } from './Simulation'; +import { TabUi } from './TabUi'; -export default function Main({ context }: { context:DebuggerContext }): JSX.Element { +type MainProps = { + context: DebuggerContext; +}; + +export const Main: React.FC = ({ context }) => { const [isCanvasShowing, setIsCanvasShowing] = useState(false); return ( @@ -24,4 +28,4 @@ export default function Main({ context }: { context:DebuggerContext }): JSX.Elem ); -} +}; diff --git a/src/tabs/RobotSimulation/components/Modal.tsx b/src/tabs/RobotSimulation/components/Modal.tsx index f4d12556a..a91fa58af 100644 --- a/src/tabs/RobotSimulation/components/Modal.tsx +++ b/src/tabs/RobotSimulation/components/Modal.tsx @@ -1,4 +1,4 @@ -import { type CSSProperties, type ReactNode } from 'react'; +import React, { type CSSProperties, type ReactNode } from 'react'; type ModalProps = { isOpen: boolean; @@ -44,7 +44,7 @@ export const childWrapperStyle: CSSProperties = { alignItems: 'center', }; -export function Modal({ children, isOpen, onClose }: ModalProps) { +export const Modal: React.FC = ({ children, isOpen, onClose }) => { return (
=({context,isOpen})=> { const ref = useRef(null); const [currentState, setCurrentState] = useState('unintialized'); @@ -86,4 +85,4 @@ export default function SimulationCanvas({
); -} +}; diff --git a/src/tabs/RobotSimulation/components/TabUi.tsx b/src/tabs/RobotSimulation/components/TabUi.tsx index 31d25f2ad..a44630e46 100644 --- a/src/tabs/RobotSimulation/components/TabUi.tsx +++ b/src/tabs/RobotSimulation/components/TabUi.tsx @@ -1,8 +1,10 @@ +import type React from 'react'; + type TabUiProps = { onOpenCanvas: () => void; }; -export default function TabUi({ onOpenCanvas }: TabUiProps) { +export const TabUi :React.FC = ({ onOpenCanvas })=> { return (

Welcome to robot simulator.

@@ -15,4 +17,4 @@ export default function TabUi({ onOpenCanvas }: TabUiProps) {
); -} +}; From 25f1208a3a225dd0272e1e9528e61c6ae6c14e20 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 09:09:41 +0000 Subject: [PATCH 69/93] Remove the empty file --- src/bundles/robot_simulation/controllers/index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/bundles/robot_simulation/controllers/index.ts diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts deleted file mode 100644 index e69de29bb..000000000 From 3820da092bda4416cb46494a71a68783cf28a538 Mon Sep 17 00:00:00 2001 From: joel chan Date: Wed, 27 Mar 2024 09:15:33 +0000 Subject: [PATCH 70/93] More formatting changes --- src/tabs/RobotSimulation/components/Main.tsx | 2 +- src/tabs/RobotSimulation/components/Modal.tsx | 2 +- .../RobotSimulation/components/Simulation/index.tsx | 12 ++++++------ src/tabs/RobotSimulation/components/TabUi.tsx | 2 +- src/tabs/RobotSimulation/index.tsx | 10 ++++------ 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/tabs/RobotSimulation/components/Main.tsx b/src/tabs/RobotSimulation/components/Main.tsx index f7437a8bf..eec8276a9 100644 --- a/src/tabs/RobotSimulation/components/Main.tsx +++ b/src/tabs/RobotSimulation/components/Main.tsx @@ -24,7 +24,7 @@ export const Main: React.FC = ({ context }) => { setIsCanvasShowing(false); }} > - + ); diff --git a/src/tabs/RobotSimulation/components/Modal.tsx b/src/tabs/RobotSimulation/components/Modal.tsx index a91fa58af..df5b747b8 100644 --- a/src/tabs/RobotSimulation/components/Modal.tsx +++ b/src/tabs/RobotSimulation/components/Modal.tsx @@ -59,4 +59,4 @@ export const Modal: React.FC = ({ children, isOpen, onClose }) => {
{children}
); -} +}; diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index ec08f8d58..77b5bb579 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -33,10 +33,12 @@ type SimulationCanvasProps = { isOpen: boolean; }; -export const SimulationCanvas :React.FC =({context,isOpen})=> { +export const SimulationCanvas: React.FC = ({ + context, + isOpen, +}) => { const ref = useRef(null); - const [currentState, setCurrentState] - = useState('unintialized'); + const [currentState, setCurrentState] = useState('unintialized'); const world = context.context.moduleContexts.robot_simulation.state .world as World; @@ -79,9 +81,7 @@ export const SimulationCanvas :React.FC =({context,isOpen
{currentState}
- - {/* This will be added in part 2 */} - + {/* This will be added in part 2 */}
); diff --git a/src/tabs/RobotSimulation/components/TabUi.tsx b/src/tabs/RobotSimulation/components/TabUi.tsx index a44630e46..23de6702d 100644 --- a/src/tabs/RobotSimulation/components/TabUi.tsx +++ b/src/tabs/RobotSimulation/components/TabUi.tsx @@ -4,7 +4,7 @@ type TabUiProps = { onOpenCanvas: () => void; }; -export const TabUi :React.FC = ({ onOpenCanvas })=> { +export const TabUi: React.FC = ({ onOpenCanvas }) => { return (

Welcome to robot simulator.

diff --git a/src/tabs/RobotSimulation/index.tsx b/src/tabs/RobotSimulation/index.tsx index a497dcf36..96fd388df 100644 --- a/src/tabs/RobotSimulation/index.tsx +++ b/src/tabs/RobotSimulation/index.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import { type DebuggerContext } from '../../typings/type_helpers'; -import Main from './components/Main'; +import { Main } from './components/Main'; /** * Robot Simulation @@ -15,10 +14,9 @@ export default { * @returns {boolean} */ toSpawn(context: DebuggerContext) { - const worldState = context.context.moduleContexts.robot_simulation.state?.world?.state; - return ( - worldState !== undefined - ); + const worldState = + context.context.moduleContexts.robot_simulation.state?.world?.state; + return worldState !== undefined; }, /** From ace23172545bdb0b6257719918928e868bd97bdc Mon Sep 17 00:00:00 2001 From: joel chan Date: Thu, 28 Mar 2024 14:56:57 +0000 Subject: [PATCH 71/93] Change RecursivePartial to DeepPartial --- .../robot_simulation/controllers/utils/mergeConfig.ts | 7 ++----- .../robot_simulation/engine/Render/debug/DebugArrow.ts | 4 ++-- src/common/deepPartial.ts | 3 +++ 3 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 src/common/deepPartial.ts diff --git a/src/bundles/robot_simulation/controllers/utils/mergeConfig.ts b/src/bundles/robot_simulation/controllers/utils/mergeConfig.ts index bd480da85..530b751f4 100644 --- a/src/bundles/robot_simulation/controllers/utils/mergeConfig.ts +++ b/src/bundles/robot_simulation/controllers/utils/mergeConfig.ts @@ -1,10 +1,7 @@ import * as _ from 'lodash'; +import type { DeepPartial } from '../../../../common/deepPartial'; -export type RecursivePartial = T extends object ? { - [P in keyof T]?: RecursivePartial; -} : T; - -export const mergeConfig = (defaultConfig:T, userConfig?: RecursivePartial) :T => { +export const mergeConfig = (defaultConfig:T, userConfig?: DeepPartial) :T => { if (userConfig === undefined) { return defaultConfig; } diff --git a/src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts b/src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts index 083cc0f71..eb07291fc 100644 --- a/src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts +++ b/src/bundles/robot_simulation/engine/Render/debug/DebugArrow.ts @@ -1,8 +1,8 @@ import * as THREE from 'three'; +import type { DeepPartial } from '../../../../../common/deepPartial'; import { mergeConfig, - type RecursivePartial, } from '../../../controllers/utils/mergeConfig'; type DebugArrowConfig = { @@ -19,7 +19,7 @@ export class DebugArrow { config: DebugArrowConfig; arrow: THREE.ArrowHelper; - constructor(config?: RecursivePartial) { + constructor(config?: DeepPartial) { this.config = mergeConfig(defaultDebugArrowConfig, config); this.arrow = new THREE.ArrowHelper(); this.arrow.setColor(this.config.color); diff --git a/src/common/deepPartial.ts b/src/common/deepPartial.ts new file mode 100644 index 000000000..a8e013841 --- /dev/null +++ b/src/common/deepPartial.ts @@ -0,0 +1,3 @@ +export type DeepPartial = T extends object ? { + [P in keyof T]?: DeepPartial; +} : T; From bbf6f5290052b87a8d3cef9728cc1333e167af66 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 1 Apr 2024 05:56:56 +0000 Subject: [PATCH 72/93] Revert "Remove part 2 from robot simulator" This reverts commit 5f43378f136639221600bfd362facb5c5a2db7b1. --- .../controllers/environment/Paper.ts | 35 ++++ .../controllers/ev3/components/Chassis.ts | 69 +++++++ .../controllers/ev3/components/Mesh.ts | 58 ++++++ .../controllers/ev3/components/Motor.ts | 148 ++++++++++++++ .../controllers/ev3/components/Wheel.ts | 102 ++++++++++ .../controllers/ev3/ev3/default/config.ts | 155 +++++++++++++++ .../controllers/ev3/ev3/default/ev3.ts | 76 ++++++++ .../controllers/ev3/ev3/default/types.ts | 87 +++++++++ .../ev3/feedback_control/PidController.ts | 109 +++++++++++ .../controllers/ev3/sensor/ColorSensor.ts | 150 ++++++++++++++ .../ev3/sensor/UltrasonicSensor.ts | 76 ++++++++ .../controllers/ev3/sensor/types.ts | 3 + .../robot_simulation/controllers/index.ts | 1 + .../controllers/program/Program.ts | 75 +++++++ .../controllers/program/evaluate.ts | 58 ++++++ src/bundles/robot_simulation/ev3_functions.ts | 183 ++++++++++++++++++ .../robot_simulation/helper_functions.ts | 43 ++++ src/bundles/robot_simulation/index.ts | 17 ++ .../components/Simulation/index.tsx | 32 ++- .../components/TabPanels/ColorSensorPanel.tsx | 29 +++ .../components/TabPanels/ConsolePanel.tsx | 56 ++++++ .../components/TabPanels/MonitoringPanel.tsx | 3 + .../components/TabPanels/MotorPidPanel.tsx | 56 ++++++ .../TabPanels/UltrasonicSensorPanel.tsx | 24 +++ .../components/TabPanels/WheelPidPanel.tsx | 62 ++++++ .../TabPanels/tabComponents/LastUpdated.tsx | 15 ++ 26 files changed, 1719 insertions(+), 3 deletions(-) create mode 100644 src/bundles/robot_simulation/controllers/environment/Paper.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Motor.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts create mode 100644 src/bundles/robot_simulation/controllers/ev3/sensor/types.ts create mode 100644 src/bundles/robot_simulation/controllers/index.ts create mode 100644 src/bundles/robot_simulation/controllers/program/Program.ts create mode 100644 src/bundles/robot_simulation/controllers/program/evaluate.ts create mode 100644 src/bundles/robot_simulation/ev3_functions.ts create mode 100644 src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx diff --git a/src/bundles/robot_simulation/controllers/environment/Paper.ts b/src/bundles/robot_simulation/controllers/environment/Paper.ts new file mode 100644 index 000000000..08ab66a13 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/environment/Paper.ts @@ -0,0 +1,35 @@ +import * as THREE from 'three'; + +import type { Renderer } from '../../engine'; + +export type PaperConfig = { + url: string; + dimension: { + width: number; + height: number; + }; +}; + +export class Paper { + render: Renderer; + config: PaperConfig; + paper: THREE.Mesh; + + constructor(render: Renderer, config: PaperConfig) { + this.render = render; + this.config = config; + + const plane = new THREE.PlaneGeometry(this.config.dimension.width, this.config.dimension.height); // Creating a 1x1 plane for the carpet + this.paper = new THREE.Mesh(plane); + } + + async start() { + const texture = new THREE.TextureLoader() + .load(this.config.url); + const material = new THREE.MeshStandardMaterial({ map: texture }); + this.paper.position.set(0, 0.001, 0); // Position the plane at the floor + this.paper.rotation.x = -Math.PI / 2; + this.paper.material = material; + this.render.add(this.paper); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts new file mode 100644 index 000000000..fc333df67 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/components/Chassis.ts @@ -0,0 +1,69 @@ +import * as THREE from 'three'; + +import { + type Physics, + type Controller, + EntityFactory, + type Entity, + MeshFactory, + type Renderer, +} from '../../../engine'; +import { type EntityCuboidOptions } from '../../../engine/Entity/EntityFactory'; + +export type ChassisWrapperConfig = EntityCuboidOptions & { + debug: boolean; +}; + +/** + * Wrapper for the chassis entity. It is needed because the chassis entity can only be initialized + * after the physics engine has been started. Therefore, the chassis entity needs to be wrapped in + * a controller. + * + * We also use this class to add an optional debug mesh to the chassis. + */ +export class ChassisWrapper implements Controller { + physics: Physics; + render: Renderer; + config: ChassisWrapperConfig; + + chassis: Entity | null = null; + debugMesh: THREE.Mesh; + + constructor( + physics: Physics, + render: Renderer, + config: ChassisWrapperConfig, + ) { + this.physics = physics; + this.render = render; + this.config = config; + + // Debug mesh. + this.debugMesh = MeshFactory.addCuboid({ + orientation: config.orientation, + dimension: config.dimension, + color: new THREE.Color(0x00ff00), + debug: true, + }); + // Set visible based on config. + this.debugMesh.visible = config.debug; + render.add(this.debugMesh); + } + + getEntity(): Entity { + if (this.chassis === null) { + throw new Error('Chassis not initialized'); + } + return this.chassis; + } + + async start(): Promise { + this.chassis = EntityFactory.addCuboid(this.physics, this.config); + } + + update(): void { + const chassisEntity = this.getEntity(); + this.debugMesh.position.copy(chassisEntity.getTranslation()); + this.debugMesh.quaternion.copy(chassisEntity.getRotation()); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts new file mode 100644 index 000000000..25993d4a5 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -0,0 +1,58 @@ +import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { type Controller, type Renderer } from '../../../engine'; +// eslint-disable-next-line import/extensions +import type { Dimension, SimpleVector } from '../../../engine/Math/Vector'; +import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; +import { type ChassisWrapper } from './Chassis'; + +export type MeshConfig = { + url: string; + dimension: Dimension; + offset?: Partial; +}; + +/** + * This represents the mesh of the robot. In reality, the mesh could be part of the chassis, + * but for the sake of clarity it is split into its own controller. + */ +export class Mesh implements Controller { + chassisWrapper: ChassisWrapper; + render: Renderer; + config: MeshConfig; + offset: SimpleVector; + + mesh: GLTF | null = null; + + constructor( + chassisWrapper: ChassisWrapper, + render: Renderer, + config: MeshConfig, + ) { + this.chassisWrapper = chassisWrapper; + this.render = render; + this.config = config; + this.offset = { + x: this.config?.offset?.x || 0, + y: this.config?.offset?.y || 0, + z: this.config?.offset?.z || 0, + }; + } + + async start(): Promise { + this.mesh = await loadGLTF(this.config.url, this.config.dimension); + + this.render.add(this.mesh.scene); + } + + update() { + const chassisEntity = this.chassisWrapper.getEntity(); + const chassisPosition = chassisEntity.getTranslation(); + + chassisPosition.x -= this.offset.x / 2; + chassisPosition.y -= this.offset.y / 2; + chassisPosition.z -= this.offset.z / 2; + + this.mesh?.scene.position.copy(chassisPosition); + this.mesh?.scene.quaternion.copy(chassisEntity.getRotation()); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts new file mode 100644 index 000000000..dd2cda839 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/components/Motor.ts @@ -0,0 +1,148 @@ +import type * as THREE from 'three'; +import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { type Controller, type Physics, type Renderer } from '../../../engine'; +import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; +import { vec3 } from '../../../engine/Math/Convert'; +import { type Dimension, type SimpleVector } from '../../../engine/Math/Vector'; +import type { PhysicsTimingInfo } from '../../../engine/Physics'; +import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; +import { VectorPidController } from '../feedback_control/PidController'; +import { type ChassisWrapper } from './Chassis'; + +type WheelSide = 'left' | 'right'; + +export type MotorConfig = { + displacement: SimpleVector; + pid: { + proportionalGain: number; + derivativeGain: number; + integralGain: number; + }; + mesh: { + url: string; + dimension: Dimension; + }; +}; +/** + * This represents the motor of the robot and is responsible for moving the robot. It is also + * responsible for the visual representation of the wheel and the friction. + */ +export class Motor implements Controller { + chassisWrapper: ChassisWrapper; + physics: Physics; + render: Renderer; + displacementVector: THREE.Vector3; + config: MotorConfig; + + motorVelocity: number; + meshRotation: number; + pid: VectorPidController; + + callbackHandler = new CallbackHandler(); + wheelSide: WheelSide; + + mesh: GLTF | null = null; + + constructor( + chassisWrapper: ChassisWrapper, + physics: Physics, + render: Renderer, + config: MotorConfig, + ) { + this.chassisWrapper = chassisWrapper; + this.physics = physics; + this.render = render; + this.displacementVector = vec3(config.displacement); + this.config = config; + + this.pid = new VectorPidController(config.pid); + this.motorVelocity = 0; + this.meshRotation = 0; + this.wheelSide = config.displacement.x > 0 ? 'right' : 'left'; + } + + setSpeedDistance(speed: number, distance: number) { + this.motorVelocity = speed; + + this.callbackHandler.addCallback(() => { + this.motorVelocity = 0; + }, (distance / speed) * 1000); + } + + async start(): Promise { + this.mesh = await loadGLTF(this.config.mesh.url, this.config.mesh.dimension); + this.render.add(this.mesh.scene); + } + + fixedUpdate(timingInfo: PhysicsTimingInfo): void { + this.callbackHandler.checkCallbacks(timingInfo); + const chassis = this.chassisWrapper.getEntity(); + + // Calculate the target motor velocity from the chassis perspective + const targetMotorVelocity = vec3({ + x: 0, + y: 0, + z: this.motorVelocity, + }); + + // Transform it to the global perspective + const targetMotorGlobalVelocity + = chassis.transformDirection(targetMotorVelocity); + + // Calculate the actual motor velocity from the global perspective + const actualMotorGlobalVelocity = chassis.worldVelocity( + this.displacementVector.clone(), + ); + + // Calculate the PID output with the PID controller + const pidOutput = this.pid.calculate( + actualMotorGlobalVelocity, + targetMotorGlobalVelocity, + ); + + // Find the global position of the motor + const motorGlobalPosition = chassis.worldTranslation( + this.displacementVector.clone(), + ); + + // Calculate the impulse to apply to the chassis + const impulse = pidOutput + .projectOnPlane( + vec3({ + x: 0, + y: 1, + z: 0, + }), + ) + .multiplyScalar(chassis.getMass()); + + // Apply the impulse to the chassis + chassis.applyImpulse(impulse, motorGlobalPosition); + } + + update(timingInfo: PhysicsTimingInfo): void { + const chassisEntity = this.chassisWrapper.getEntity(); + + // Calculate the new wheel position, adjusting the y-coordinate to half the mesh height + const wheelPosition = chassisEntity.worldTranslation(this.displacementVector.clone()); + wheelPosition.y = this.config.mesh.dimension.height / 2; // Ensure the wheel is placed correctly vertically + + // If mesh is loaded, update its position and orientation + if (this.mesh) { + this.mesh.scene.position.copy(wheelPosition); + this.mesh.scene.quaternion.copy(chassisEntity.getRotation()); + + // Calculate rotation adjustment based on motor velocity and frame duration + const radiansPerFrame = 2 * (this.motorVelocity / this.config.mesh.dimension.height) * timingInfo.frameDuration / 1000; + + // Apply rotation changes to simulate wheel turning + this.meshRotation += radiansPerFrame; + this.mesh.scene.rotateX(this.meshRotation); + + // If the wheel is on the left side, flip it to face the correct direction + if (this.wheelSide === 'left') { + this.mesh.scene.rotateZ(Math.PI); + } + } + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts new file mode 100644 index 000000000..95c4450ec --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -0,0 +1,102 @@ +import type * as THREE from 'three'; +import { type Renderer, type Controller, type Physics } from '../../../engine'; +import { vec3 } from '../../../engine/Math/Convert'; +import { type SimpleVector } from '../../../engine/Math/Vector'; +import type { PhysicsTimingInfo } from '../../../engine/Physics'; +import { DebugArrow } from '../../../engine/Render/debug/DebugArrow'; +import { NumberPidController } from '../feedback_control/PidController'; +import { type ChassisWrapper } from './Chassis'; + +export type WheelConfig = { + pid: { + proportionalGain: number; + derivativeGain: number; + integralGain: number; + }; + displacement: SimpleVector; + gapToFloor: number; + maxRayDistance:number; + debug: boolean; +}; + +export class Wheel implements Controller { + chassisWrapper: ChassisWrapper; + physics: Physics; + render: Renderer; + config: WheelConfig; + + pid: NumberPidController; + displacementVector: THREE.Vector3; + downVector: THREE.Vector3; + arrowHelper: DebugArrow; + + constructor( + chassisWrapper: ChassisWrapper, + physics: Physics, + render: Renderer, + config: WheelConfig, + ) { + this.chassisWrapper = chassisWrapper; + this.physics = physics; + this.render = render; + this.displacementVector = vec3(config.displacement); + this.config = config; + + this.pid = new NumberPidController(config.pid); + this.downVector = vec3({ + x: 0, + y: -1, + z: 0, + }); + + // Debug arrow. + this.arrowHelper = new DebugArrow({ debug: config.debug }); + render.add(this.arrowHelper.getMesh()); + } + + fixedUpdate(timingInfo: PhysicsTimingInfo): void { + const chassis = this.chassisWrapper.getEntity(); + + const globalDisplacement = chassis.worldTranslation( + this.displacementVector.clone(), + ); + const globalDownDirection = chassis.transformDirection( + this.downVector.clone(), + ); + + const result = this.physics.castRay( + globalDisplacement, + globalDownDirection, + this.config.maxRayDistance, + chassis.getCollider(), + ); + + // Wheels are not touching the ground + if (result === null) { + return; + } + + let { distance: wheelDistance, normal } = result; + + // If distance is zero, the ray originate from inside the floor/wall. + // If that is true, we assume the normal is pointing up. + if (wheelDistance === 0) { + normal = { + x: 0, + y: 1, + z: 0, + }; + } + + const error = this.pid.calculate(wheelDistance, this.config.gapToFloor); + + const force = vec3(normal) + .normalize() + .multiplyScalar((error * chassis.getMass() * timingInfo.timestep) / 1000); + + chassis.applyImpulse(force, globalDisplacement); + + // Debug arrow. + this.arrowHelper.update(globalDisplacement, force.clone(), force.length() * 1000); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts new file mode 100644 index 000000000..a6661ef9e --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts @@ -0,0 +1,155 @@ +import type { + Ev3ChassisConfig, + Ev3ColorSensorConfig, + Ev3Config, + Ev3MeshConfig, + Ev3MotorsConfig, + Ev3UltrasonicSenorConfig, + Ev3WheelsConfig, +} from './types'; + +const noRotation = { + x: 0, + y: 0, + z: 0, + w: 1, +}; + +const tinyConstant = 0.012; + +export const chassisConfig: Ev3ChassisConfig = { + orientation: { + position: { + x: 0, + y: 0.0775, + z: 0, + }, + rotation: noRotation, + }, + dimension: { + height: 0.095, + width: 0.145, + length: 0.18, + }, + mass: 0.6, + type: 'dynamic', + debug: true, +}; + +export const meshConfig: Ev3MeshConfig = { + url: 'https://keen-longma-3c1be1.netlify.app/6_remove_wheels.gltf', + dimension: chassisConfig.dimension, + offset: { + y: 0.02, + }, +}; + +export const wheelConfig: Ev3WheelsConfig = { + displacements: { + frontLeftWheel: { + x: -(chassisConfig.dimension.width / 2), + y: -(chassisConfig.dimension.height / 2), + z: chassisConfig.dimension.length / 2 - tinyConstant, + }, + + frontRightWheel: { + x: chassisConfig.dimension.width / 2, + y: -(chassisConfig.dimension.height / 2), + z: chassisConfig.dimension.length / 2 - tinyConstant, + }, + backLeftWheel: { + x: -(chassisConfig.dimension.width / 2), + y: -(chassisConfig.dimension.height / 2), + z: -(chassisConfig.dimension.length / 2 - tinyConstant), + }, + backRightWheel: { + x: chassisConfig.dimension.width / 2, + y: -(chassisConfig.dimension.height / 2), + z: -(chassisConfig.dimension.length / 2 - tinyConstant), + }, + }, + config: { + pid: { + proportionalGain: 27, + integralGain: 8, + derivativeGain: 40, + }, + gapToFloor: 0.03, + maxRayDistance: 0.05, + debug: true, + }, +}; + +export const motorConfig: Ev3MotorsConfig = { + config: { + pid: { + proportionalGain: 0.25, + derivativeGain: 0, + integralGain: 0, + }, + mesh: { + dimension: { + width: 0.028, + height: 0.0575, + length: 0.0575, + }, + url: 'https://keen-longma-3c1be1.netlify.app/6_wheel.gltf', + }, + }, + displacements: { + leftMotor: { + x: 0.058, + y: 0, + z: 0.03, + }, + rightMotor: { + x: -0.058, + y: 0, + z: 0.03, + }, + }, +}; + +export const colorSensorConfig: Ev3ColorSensorConfig = { + tickRateInSeconds: 0.1, + displacement: { + x: 0.04, + y: -(chassisConfig.dimension.height / 2), + z: 0.01, + }, + size: { + height: 16, + width: 16, + }, + camera: { + type: 'perspective', + aspect: 1, + fov: 10, + near: 0.01, + far: 1, + }, + debug: true, +}; + +const ultrasonicSensorConfig: Ev3UltrasonicSenorConfig = { + displacement: { + x: 0.04, + y: 0, + z: 0.01, + }, + direction: { + x: 0, + y: 0, + z: 1, + }, + debug: true, +}; + +export const ev3Config: Ev3Config = { + chassis: chassisConfig, + motors: motorConfig, + wheels: wheelConfig, + colorSensor: colorSensorConfig, + ultrasonicSensor: ultrasonicSensorConfig, + mesh: meshConfig, +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts new file mode 100644 index 000000000..4b792c2cb --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/ev3.ts @@ -0,0 +1,76 @@ +import { type Physics, type Renderer, ControllerMap } from '../../../../engine'; + +import { ChassisWrapper } from '../../components/Chassis'; +import { Mesh } from '../../components/Mesh'; +import { Motor, type MotorConfig } from '../../components/Motor'; +import { Wheel, type WheelConfig } from '../../components/Wheel'; +import { ColorSensor } from '../../sensor/ColorSensor'; +import { UltrasonicSensor } from '../../sensor/UltrasonicSensor'; + +import { + wheelNames, + motorNames, + type DefaultEv3Controller, + type Ev3Config, + type WheelControllers, + type MotorControllers, +} from './types'; + +export type DefaultEv3 = ControllerMap; + +export const createDefaultEv3 = ( + physics: Physics, + render: Renderer, + config: Ev3Config, +): DefaultEv3 => { + const chassis = new ChassisWrapper(physics, render, config.chassis); + const mesh = new Mesh(chassis, render, config.mesh); + + const wheelControllers = wheelNames.reduce((acc, name) => { + const displacement = config.wheels.displacements[name]; + const wheelConfig: WheelConfig = { + ...config.wheels.config, + displacement, + }; + const wheel = new Wheel(chassis, physics, render, wheelConfig); + return { + ...acc, + [name]: wheel, + }; + }, {} as WheelControllers); + + // Motors + const motorControllers = motorNames.reduce((acc, name) => { + const displacement = config.motors.displacements[name]; + const motorConfig: MotorConfig = { + ...config.motors.config, + displacement, + }; + const motor = new Motor(chassis, physics, render, motorConfig); + return { + ...acc, + [name]: motor, + }; + }, {} as MotorControllers); + + // Sensors + const colorSensor = new ColorSensor(chassis, render, config.colorSensor); + + const ultrasonicSensor = new UltrasonicSensor( + chassis, + physics, + render, + config.ultrasonicSensor, + ); + + const ev3: DefaultEv3 = new ControllerMap({ + ...wheelControllers, + ...motorControllers, + colorSensor, + ultrasonicSensor, + mesh, + chassis, + }); + + return ev3; +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts new file mode 100644 index 000000000..dd2ae0dc9 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts @@ -0,0 +1,87 @@ +import type { SimpleVector } from '../../../../engine/Math/Vector'; +import type { ChassisWrapper, ChassisWrapperConfig } from '../../components/Chassis'; +import type { Mesh, MeshConfig } from '../../components/Mesh'; +import type { Motor, MotorConfig } from '../../components/Motor'; +import type { Wheel, WheelConfig } from '../../components/Wheel'; +import type { ColorSensor, ColorSensorConfig } from '../../sensor/ColorSensor'; +import type { UltrasonicSensor, UltrasonicSensorConfig } from '../../sensor/UltrasonicSensor'; + +// ######################### Controller Types ######################### +// Wheels +export const wheelNames = [ + 'frontLeftWheel', + 'frontRightWheel', + 'backLeftWheel', + 'backRightWheel', +] as const; +export type WheelNames = (typeof wheelNames)[number]; +export type WheelControllers = Record; + +// Motors +export const motorNames = ['leftMotor', 'rightMotor'] as const; +export type MotorNames = (typeof motorNames)[number]; +export type MotorControllers = Record; + +// Chassis +export const chassisNames = ['chassis'] as const; +export type ChassisNames = (typeof chassisNames)[number]; +export type ChassisControllers = Record; + +// Mesh +export const meshNames = ['mesh'] as const; +export type MeshNames = (typeof meshNames)[number]; +export type MeshControllers = Record; + +// ColorSensor +export const colorSensorNames = ['colorSensor'] as const; +export type ColorSensorNames = (typeof colorSensorNames)[number]; +export type ColorSensorControllers = Record; + +// UltrasonicSensor +export const ultrasonicSensorNames = ['ultrasonicSensor'] as const; +export type UltrasonicSensorNames = (typeof ultrasonicSensorNames)[number]; +export type UltrasonicSensorControllers = Record< +UltrasonicSensorNames, +UltrasonicSensor +>; + +// Aggregate +export const controllerNames = [ + ...wheelNames, + ...motorNames, + ...chassisNames, + ...meshNames, + ...colorSensorNames, + ...ultrasonicSensorNames, +] as const; +export type DefaultEv3ControllerNames = (typeof controllerNames)[number]; +export type DefaultEv3Controller = WheelControllers & +MotorControllers & +ColorSensorControllers & +UltrasonicSensorControllers & +ChassisControllers & +MeshControllers; + + +// ######################### Config Types ######################### +export type Ev3ChassisConfig = ChassisWrapperConfig; +export type Ev3MeshConfig = MeshConfig; +export type Ev3WheelsConfig = { + displacements: Record; + config: Omit; +}; +export type Ev3MotorsConfig = { + displacements: Record; + config: Omit; +}; +export type Ev3ColorSensorConfig = ColorSensorConfig; +export type Ev3UltrasonicSenorConfig = UltrasonicSensorConfig; + +export type Ev3Config = { + chassis: Ev3ChassisConfig + mesh: Ev3MeshConfig; + wheels: Ev3WheelsConfig; + motors: Ev3MotorsConfig; + colorSensor: Ev3ColorSensorConfig; + ultrasonicSensor: Ev3UltrasonicSenorConfig; +}; diff --git a/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts b/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts new file mode 100644 index 000000000..accc41d3c --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/feedback_control/PidController.ts @@ -0,0 +1,109 @@ +import * as THREE from 'three'; + +export type PIDConfig = { + proportionalGain: number; + integralGain: number; + derivativeGain: number; +}; + +type NullaryFunction = () => T; +type BinaryFunction = (a: T, b: T) => T; +type ScaleFunction = (value: T, scale: number) => T; + +type PIDControllerOptions = { + zero: NullaryFunction; + add: BinaryFunction; + subtract: BinaryFunction; + scale: ScaleFunction; + + proportionalGain: number; + integralGain: number; + derivativeGain: number; +}; + +class PIDController { + zero: NullaryFunction; + add: BinaryFunction; + subtract: BinaryFunction; + scale: ScaleFunction; + + proportionalGain: number; + integralGain: number; + derivativeGain: number; + + errorsSum: T; + previousError: T; + + constructor({ + zero, + add, + subtract, + scale, + proportionalGain, + integralGain, + derivativeGain, + }: PIDControllerOptions) { + this.zero = zero; + this.add = add; + this.subtract = subtract; + this.scale = scale; + + this.proportionalGain = proportionalGain; + this.integralGain = integralGain; + this.derivativeGain = derivativeGain; + + this.errorsSum = this.zero(); + this.previousError = this.zero(); + } + + calculate(currentValue: T, setpoint: T): T { + const error = this.subtract(setpoint, currentValue); + this.errorsSum = this.add(this.errorsSum, error); + + const proportional = this.scale(error, this.proportionalGain); + const integral = this.scale(this.errorsSum, this.integralGain); + const derivative = this.scale(this.subtract(error, this.previousError), this.derivativeGain); + this.previousError = error; + + return this.add(this.add(proportional, integral), derivative); + } +} + +export class NumberPidController extends PIDController { + constructor({ + proportionalGain, + integralGain, + derivativeGain, + }: PIDConfig) { + super({ + zero: () => 0, + add: (a, b) => a + b, + subtract: (a, b) => a - b, + scale: (value, scale) => value * scale, + proportionalGain, + integralGain, + derivativeGain, + }); + } +} + +export class VectorPidController extends PIDController { + constructor({ + proportionalGain, + integralGain, + derivativeGain, + }: PIDConfig) { + super({ + zero: () => new THREE.Vector3(0, 0, 0), + add: (a, b) => a.clone() + .add(b), + subtract: (a, b) => a.clone() + .sub(b), + scale: (value, scale) => value.clone() + .multiplyScalar(scale), + proportionalGain, + integralGain, + derivativeGain, + }); + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts new file mode 100644 index 000000000..5f1a953e5 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -0,0 +1,150 @@ +import * as THREE from 'three'; +import { Renderer } from '../../../engine'; +import { vec3 } from '../../../engine/Math/Convert'; +import { type SimpleVector } from '../../../engine/Math/Vector'; +import type { PhysicsTimingInfo } from '../../../engine/Physics'; +import { + getCamera, + type CameraOptions, +} from '../../../engine/Render/helpers/Camera'; +import { type ChassisWrapper } from '../components/Chassis'; +import { type Sensor } from './types'; + +type Color = { r: number; g: number; b: number }; + +export type ColorSensorConfig = { + size: { + height: number; + width: number; + }; + displacement: SimpleVector; + camera: CameraOptions; + tickRateInSeconds: number; + debug: boolean; +}; + +export class ColorSensor implements Sensor { + chassisWrapper: ChassisWrapper; + displacement: THREE.Vector3; + config: ColorSensorConfig; + + camera: THREE.Camera; + renderer: Renderer; + accumulator = 0; + colorSensed: Color; + tempCanvas: HTMLCanvasElement; + + constructor( + chassisWrapper: ChassisWrapper, + render: Renderer, + config: ColorSensorConfig, + ) { + this.chassisWrapper = chassisWrapper; + this.displacement = vec3(config.displacement); + this.config = config; + + this.camera = getCamera(config.camera); + // We create a new renderer with the same scene. But we use a different camera. + this.renderer = new Renderer(render.scene(), this.camera, { + width: this.config.size.width, + height: this.config.size.height, + control: 'none', + }); + + this.colorSensed = { + r: 0, + g: 0, + b: 0, + }; + + this.tempCanvas = document.createElement('canvas'); + this.tempCanvas.width = this.config.size.width; + this.tempCanvas.height = this.config.size.height; + + if (config.debug) { + const helper = new THREE.CameraHelper(this.camera); + render.add(helper); + } + } + + getColorSensorPosition() { + const chassis = this.chassisWrapper.getEntity(); + const colorSensorPosition = chassis.worldTranslation( + this.displacement.clone(), + ); + return colorSensorPosition; + } + + sense(): Color { + return this.colorSensed; + } + + // Even though we are rendering, we use fixedUpdate because the student's code can be affected + // by the values of sense() and could affect the determinism of the simulation. + fixedUpdate(timingInfo: PhysicsTimingInfo) { + this.accumulator += timingInfo.timestep; + + const tickRateInMilliseconds = this.config.tickRateInSeconds * 1000; + + // We check the accumulator to see if it's time update the color sensor. + // If it's not time, we return early. + if (this.accumulator < tickRateInMilliseconds) { + return; + } + this.accumulator -= tickRateInMilliseconds; + + // We move the camera to the right position + this.camera.position.copy(this.getColorSensorPosition()); + // Point it downwards + this.camera.lookAt( + this.camera.position.x, + this.camera.position.y - 1, // 1 unit below its current position + this.camera.position.z, + ); + + // We render to load the color sensor data into the renderer. + this.renderer.render(); + + // We get the HTMLCanvasElement from the renderer + const rendererCanvas = this.renderer.getElement(); + + // Get the context from the temp canvas + const tempCtx = this.tempCanvas.getContext('2d', { + willReadFrequently: true, + })!; + + // Draw the renderer canvas to the temp canvas + tempCtx.drawImage(rendererCanvas, 0, 0); + + // Get the image data from the temp canvas + const imageData = tempCtx.getImageData( + 0, + 0, + this.config.size.width, + this.config.size.height, + {}, + ); + + // Calculate the average color + const averageColor = { + r: 0, + g: 0, + b: 0, + }; + + for (let i = 0; i < imageData.data.length; i += 4) { + const r = imageData.data[i]; + const g = imageData.data[i + 1]; + const b = imageData.data[i + 2]; + averageColor.r += r; + averageColor.g += g; + averageColor.b += b; + } + + averageColor.r /= imageData.data.length; + averageColor.g /= imageData.data.length; + averageColor.b /= imageData.data.length; + + this.colorSensed = averageColor; + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts new file mode 100644 index 000000000..106e8e3b8 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -0,0 +1,76 @@ +import * as THREE from 'three'; +import { type Renderer, type Physics } from '../../../engine'; +import { vec3 } from '../../../engine/Math/Convert'; +import { type SimpleVector } from '../../../engine/Math/Vector'; +import { type ChassisWrapper } from '../components/Chassis'; +import { type Sensor } from './types'; + +export type UltrasonicSensorConfig = { + displacement: SimpleVector; + direction: SimpleVector; + debug: boolean; +}; + +export class UltrasonicSensor implements Sensor { + chassisWrapper: ChassisWrapper; + physics: Physics; + displacement: THREE.Vector3; + direction: THREE.Vector3; + distanceSensed: number = 0; + render: Renderer; + config: UltrasonicSensorConfig; + debugArrow: THREE.ArrowHelper; + + constructor( + chassis: ChassisWrapper, + physics: Physics, + render: Renderer, + config: UltrasonicSensorConfig, + ) { + this.chassisWrapper = chassis; + this.physics = physics; + this.render = render; + this.displacement = vec3(config.displacement); + this.direction = vec3(config.direction); + this.config = config; + + // Debug arrow + this.debugArrow = new THREE.ArrowHelper(); + this.debugArrow.visible = false; + this.render.add(this.debugArrow); + } + + sense(): number { + return this.distanceSensed; + } + + fixedUpdate(): void { + const chassis = this.chassisWrapper.getEntity(); + const globalDisplacement = chassis.worldTranslation( + this.displacement.clone(), + ); + const globalDirection = chassis.transformDirection(this.direction.clone()); + + const result = this.physics.castRay( + globalDisplacement, + globalDirection, + 1, + this.chassisWrapper.getEntity() + .getCollider(), + ); + + if (this.config.debug) { + this.debugArrow.visible = true; + this.debugArrow.position.copy(globalDisplacement); + this.debugArrow.setDirection(globalDirection.normalize()); + } + + if (result === null) { + return; + } + + const { distance: wheelDistance } = result; + + this.distanceSensed = wheelDistance; + } +} diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts new file mode 100644 index 000000000..6cd0cc8df --- /dev/null +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/types.ts @@ -0,0 +1,3 @@ +import { type Controller } from '../../../engine'; + +export type Sensor = Controller & { sense: () => T }; diff --git a/src/bundles/robot_simulation/controllers/index.ts b/src/bundles/robot_simulation/controllers/index.ts new file mode 100644 index 000000000..778141b54 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/index.ts @@ -0,0 +1 @@ +export { type DefaultEv3 } from './ev3/ev3/default/ev3'; diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts new file mode 100644 index 000000000..091e97bcc --- /dev/null +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -0,0 +1,75 @@ +import { type IOptions } from 'js-slang'; +import context from 'js-slang/context'; +import { type Controller } from '../../engine'; +import { CallbackHandler } from '../../engine/Core/CallbackHandler'; +import type { PhysicsTimingInfo } from '../../engine/Physics'; +import { ProgramError } from './error'; +import { runECEvaluator } from './evaluate'; + +type ProgramConfig = { + stepsPerTick: number; +}; + +export const program_controller_identifier = 'program_controller'; + +export class Program implements Controller { + code: string; + iterator: ReturnType | null; + isPaused: boolean; + callbackHandler = new CallbackHandler(); + name: string; + + constructor(code: string) { + this.name = program_controller_identifier; + this.code = code; + this.iterator = null; + this.isPaused = false; + } + + pause(pauseDuration: number) { + this.isPaused = true; + this.callbackHandler.addCallback(() => { + this.isPaused = false; + }, pauseDuration); + } + + start() { + const options: Partial = { + originalMaxExecTime: Infinity, + scheduler: 'preemptive', + stepLimit: Infinity, + throwInfiniteLoops: false, + useSubst: false, + }; + + context.errors = []; + + this.iterator = runECEvaluator(this.code, context, options); + } + + fixedUpdate() { + try { + if (!this.iterator) { + throw Error('Program not started'); + } + + if (this.isPaused) { + return; + } + + // steps per tick + for (let i = 0; i < 11; i++) { + const result = this.iterator.next(); + } + } catch (e) { + console.error(e); + throw new ProgramError( + 'Error in program execution. Please check your code and try again.', + ); + } + } + + update(frameTiming: PhysicsTimingInfo): void { + this.callbackHandler.checkCallbacks(frameTiming); + } +} diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/controllers/program/evaluate.ts new file mode 100644 index 000000000..ac21350f8 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/program/evaluate.ts @@ -0,0 +1,58 @@ +import * as _ from 'lodash'; + +import { type IOptions } from 'js-slang/dist'; + +import { Variant, type Context, type RecursivePartial } from 'js-slang/dist/types'; +import { Control, Stash, generateCSEMachineStateStream } from 'js-slang/dist/cse-machine/interpreter'; +import { parse } from 'js-slang/dist/parser/parser'; + +const DEFAULT_SOURCE_OPTIONS = { + scheduler: 'async', + steps: 1000, + stepLimit: -1, + executionMethod: 'auto', + variant: Variant.DEFAULT, + originalMaxExecTime: 1000, + useSubst: false, + isPrelude: false, + throwInfiniteLoops: true, + envSteps: -1, + importOptions: { + wrapSourceModules: true, + checkImports: true, + loadTabs: true, + }, +}; + + +export function* runECEvaluator( + code: string, + context: Context, + options: RecursivePartial, +): Generator<{ steps: number }, void, undefined> { + const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); + const program = parse(code, context); + + if (!program) { + return; + } + + try { + context.runtime.isRunning = true; + context.runtime.control = new Control(program); + context.runtime.stash = new Stash(); + yield* generateCSEMachineStateStream( + context, + context.runtime.control, + context.runtime.stash, + theOptions.envSteps, + theOptions.stepLimit, + theOptions.isPrelude, + ); + // eslint-disable-next-line no-useless-catch + } catch (error) { + throw error; + } finally { + context.runtime.isRunning = false; + } +} diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts new file mode 100644 index 000000000..ed0876f44 --- /dev/null +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -0,0 +1,183 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import { type Motor } from './controllers/ev3/components/Motor'; +import { motorConfig } from './controllers/ev3/ev3/default/config'; +import { type ColorSensor } from './controllers/ev3/sensor/ColorSensor'; +import { type UltrasonicSensor } from './controllers/ev3/sensor/UltrasonicSensor'; +import { + program_controller_identifier, + type Program, +} from './controllers/program/Program'; + +import { getEv3FromContext, getWorldFromContext } from './helper_functions'; + +type MotorFunctionReturnType = Motor | null; + +/** + * @categoryDescription EV3 + * These functions are mocking the the normal EV3 functions found + * at https://docs.sourceacademy.org/EV3/global.html + * @module + */ + +/** + * Pauses for a period of time. + * + * @param duration The time to wait, in milliseconds. + * + * @category EV3 + */ +export function ev3_pause(duration: number): void { + const world = getWorldFromContext(); + const program = world.controllers.controllers.find( + (controller) => controller.name === program_controller_identifier, + ) as Program; + program.pause(duration); +} + +/** + * Gets the motor connected to port A. + * + * @returns The motor connected to port A + * + * @category EV3 + */ +export function ev3_motorA(): MotorFunctionReturnType { + const ev3 = getEv3FromContext(); + return ev3.get('leftMotor'); +} + +/** + * Gets the motor connected to port B. + * + * @returns The motor connected to port B + * + * @category EV3 + */ +export function ev3_motorB(): MotorFunctionReturnType { + const ev3 = getEv3FromContext(); + return ev3.get('rightMotor'); +} + +/** + * Gets the motor connected to port C. + * + * @returns The motor connected to port C + * + * @category EV3 + */ +export function ev3_motorC(): MotorFunctionReturnType { + return null; +} + +/** + * Gets the motor connected to port D. + * + * @returns The motor connected to port D + * + * @category EV3 + */ +export function ev3_motorD(): MotorFunctionReturnType { + return null; +} + +/** + * Causes the motor to rotate until the position reaches ev3_motorGetPosition() + position with the given speed. + * Note: this works by sending instructions to the motors. + * This will return almost immediately, without waiting for the motor to reach the given absolute position. + * If you wish to wait, use ev3_pause. + * + * @param motor The motor + * @param position The amount to turn + * @param speed The speed to run at, in tacho counts per second + * + * @category EV3 + */ +export function ev3_runToRelativePosition( + motor: MotorFunctionReturnType, + position: number, + speed: number, +): void { + if (motor === null) { + return; + } + + const wheelDiameter = motorConfig.config.mesh.dimension.height; + const speedInMetersPerSecond = (speed / 360) * Math.PI * wheelDiameter; + const distanceInMetersPerSecond = (position / 360) * Math.PI * wheelDiameter; + + motor.setSpeedDistance(speedInMetersPerSecond, distanceInMetersPerSecond); +} + +/** + * Gets the colour sensor connected any of ports 1, 2, 3 or 4. + * + * @returns The colour sensor + * + * @category EV3 + */ +export function ev3_colorSensor() { + const ev3 = getEv3FromContext(); + return ev3.get('colorSensor'); +} + +/** + * Gets the amount of red seen by the colour sensor. + * + * @param colorSensor The color sensor + * @returns The amount of blue, in sensor-specific units. + * + * @category EV3 + */ +export function ev3_colorSensorRed(colorSensor: ColorSensor) { + return colorSensor.sense().r; +} + +/** + * Gets the amount of green seen by the colour sensor. + * + * @param colorSensor The color sensor + * @returns The amount of green, in sensor-specific units. + * + * @category EV3 + */ +export function ev3_colorSensorGreen(colorSensor: ColorSensor) { + return colorSensor.sense().g; +} + +/** + * Gets the amount of blue seen by the colour sensor. + * + * @param colorSensor The color sensor + * @returns The amount of blue, in sensor-specific units. + * + * @category EV3 + */ +export function ev3_colorSensorBlue(colorSensor: ColorSensor) { + return colorSensor.sense().b; +} + +/** + * Gets the ultrasonic sensor connected any of ports 1, 2, 3 or 4. + * + * @returns The ultrasonic sensor + * + * @category EV3 + */ +export function ev3_ultrasonicSensor() { + const ev3 = getEv3FromContext(); + return ev3.get('ultrasonicSensor'); +} +/** + * Gets the distance read by the ultrasonic sensor in centimeters. + * + * @param ultraSonicSensor The ultrasonic sensor + * @returns The distance, in centimeters. + * + * @category EV3 + */ +export function ev3_ultrasonicSensorDistance( + ultraSonicSensor: UltrasonicSensor, +): number { + return ultraSonicSensor.sense() * 100; +} diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index f898da155..fc1c0b4cb 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -2,6 +2,13 @@ import context from 'js-slang/context'; import { interrupt } from '../../common/specialErrors'; import { sceneConfig } from './config'; import { Cuboid, type CuboidConfig } from './controllers/environment/Cuboid'; +import { Paper, type PaperConfig } from './controllers/environment/Paper'; +import { ev3Config } from './controllers/ev3/ev3/default/config'; +import { + createDefaultEv3, + type DefaultEv3, +} from './controllers/ev3/ev3/default/ev3'; +import { Program } from './controllers/program/Program'; import { type Controller, Physics, Renderer, Timer, World } from './engine'; import { RobotConsole } from './engine/Core/RobotConsole'; @@ -22,6 +29,14 @@ export function getWorldFromContext(): World { return world as World; } +export function getEv3FromContext(): DefaultEv3 { + const ev3 = context.moduleContexts.robot_simulation.state?.ev3; + if (ev3 === undefined) { + throw new Error('ev3 not initialized'); + } + return ev3 as DefaultEv3; +} + // Physics export function createCustomPhysics( gravity: number, @@ -162,6 +177,29 @@ export function createWall(physics: Physics, renderer: Renderer) { return wall; } +export function createPaper( + render: Renderer, + url: string, + width: number, + height: number, +) { + const paperConfig: PaperConfig = { + url, + dimension: { + width, + height, + }, + }; + const paper = new Paper(render, paperConfig); + return paper; +} + +export function createCSE() { + const code = context.unTypecheckedCode[0]; + const program = new Program(code); + return program; +} + export function addControllerToWorld(controller: Controller, world: World) { world.addController(controller); } @@ -173,6 +211,11 @@ export function saveToContext(key: string, value: any) { context.moduleContexts.robot_simulation.state[key] = value; } +export function createEv3(physics: Physics, renderer: Renderer): DefaultEv3 { + const ev3 = createDefaultEv3(physics, renderer, ev3Config); + return ev3; +} + // Initialization export function init_simulation(worldFactory: () => World) { if (storedWorld !== undefined) { diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 862f33518..0ae0cab0a 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -5,6 +5,20 @@ * @author Joel Chan */ +export { + ev3_motorA, + ev3_motorB, + ev3_motorC, + ev3_motorD, + ev3_runToRelativePosition, + ev3_colorSensorRed, + ev3_colorSensorGreen, + ev3_pause, + ev3_colorSensorBlue, + ev3_ultrasonicSensor, + ev3_ultrasonicSensorDistance, +} from './ev3_functions'; + export { createCustomPhysics, createPhysics, @@ -14,7 +28,10 @@ export { createTimer, createWorld, createWall, + createEv3, + createPaper, createFloor, + createCSE, addControllerToWorld, createRobotConsole, saveToContext, diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index 77b5bb579..a5d292d0e 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -1,10 +1,18 @@ -import { Tabs } from '@blueprintjs/core'; +import { Tab, Tabs } from '@blueprintjs/core'; import { useRef, type CSSProperties, useEffect, useState } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; import { type World } from '../../../../bundles/robot_simulation/engine'; import { type WorldState } from '../../../../bundles/robot_simulation/engine/World'; import type { DebuggerContext } from '../../../../typings/type_helpers'; +import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; +import { ConsolePanel } from '../TabPanels/ConsolePanel'; +import { MonitoringPanel } from '../TabPanels/MonitoringPanel'; +import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; +import { UltrasonicSensorPanel } from '../TabPanels/UltrasonicSensorPanel'; +import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; + const WrapperStyle: CSSProperties = { display: 'flex', flexDirection: 'column', @@ -38,10 +46,17 @@ export const SimulationCanvas: React.FC = ({ isOpen, }) => { const ref = useRef(null); - const [currentState, setCurrentState] = useState('unintialized'); + const sensorRef = useRef(null); + const [currentState, setCurrentState] + = useState('unintialized'); const world = context.context.moduleContexts.robot_simulation.state .world as World; + const ev3 = context.context.moduleContexts.robot_simulation.state + .ev3 as DefaultEv3; + + const robotConsole = world.robotConsole; + useEffect(() => { const startThreeAndRapierEngines = async () => { setCurrentState(world.state); @@ -51,6 +66,10 @@ export const SimulationCanvas: React.FC = ({ if (ref.current) { ref.current.replaceChildren(world.render.getElement()); } + + if (sensorRef.current) { + sensorRef.current.replaceChildren(ev3.get('colorSensor').renderer.getElement()); + } }; if (currentState === 'unintialized') { @@ -81,7 +100,14 @@ export const SimulationCanvas: React.FC = ({
{currentState}
- {/* This will be added in part 2 */} + + } /> + } /> + } /> + }/> + }/> + } /> +
); diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx new file mode 100644 index 000000000..29f454710 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx @@ -0,0 +1,29 @@ +import { useEffect, useRef, useState } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; + +export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { + const colorSensor = ev3.get('colorSensor'); + const sensorVisionRef = useRef(null); + const [_, update] = useState(0); + const colorSensed = colorSensor.sense(); + + useEffect(() => { + if (sensorVisionRef.current) { + sensorVisionRef.current.replaceChildren(colorSensor.renderer.getElement()); + } + + // Hacky + setInterval(() => { + update((i) => i + 1); + }, 1000); + }, []); + + return <> +
+
+

Red: {colorSensed.r}

+

Green: {colorSensed.g}

+

Blue: {colorSensed.b}

+
+ ; +}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx new file mode 100644 index 000000000..55bb41254 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx @@ -0,0 +1,56 @@ +import { type LogEntry, type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; +import { useFetchFromSimulation } from '../../hooks/fetchFromSimulation'; +import { LastUpdated, getTimeString } from './tabComponents/LastUpdated'; + +const getLogString = (log: LogEntry) => { + const logLevelText :Record = { + source: 'Runtime Source Error', + error: 'Error', + }; + + const timeString = getTimeString(new Date(log.timestamp)); + return `[${timeString}] ${logLevelText[log.level]}: ${log.message}`; +}; + +export const ConsolePanel = ({ + robot_console, +}: { + robot_console?: RobotConsole; +}) => { + const [timing, logs] = useFetchFromSimulation(() => { + if (robot_console === undefined) { + return null; + } + return robot_console.getLogs(); + }, 1000); + + if (timing === null) { + return
Not fetched yet
; + } + + if (logs === null) { + return ( +
+ Console not found. Ensure that the world is initialized properly. +
+ ); + } + + if (logs.length === 0) { + return ( +
+ +

There is currently no logs

+
+ ); + } + + return ( +
+ +
    + {logs.map((log, i) =>
  • {getLogString(log)}
  • )} +
+
+ ); +}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx new file mode 100644 index 000000000..7f4827f48 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx @@ -0,0 +1,3 @@ +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; + +export const MonitoringPanel = ({ ev3 }: { ev3: DefaultEv3 }) => <>hi; diff --git a/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx new file mode 100644 index 000000000..b6e65e376 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx @@ -0,0 +1,56 @@ +import { NumericInput } from '@blueprintjs/core'; +import { type CSSProperties } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; + +const RowStyle: CSSProperties = { + display: 'flex', + flexDirection: 'row', + gap: '0.6rem', +}; + +export const MotorPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { + const onChangeProportional = (value: number) => { + ev3.get('leftMotor').pid.proportionalGain = value; + ev3.get('rightMotor').pid.proportionalGain = value; + }; + const onChangeIntegral = (value: number) => { + ev3.get('leftMotor').pid.integralGain = value; + ev3.get('rightMotor').pid.integralGain = value; + }; + const onChangeDerivative = (value: number) => { + ev3.get('leftMotor').pid.derivativeGain = value; + ev3.get('rightMotor').pid.derivativeGain = value; + }; + + return ( +
+
+ Proportional Gain: + +
+
+ Integral Gain: + +
+
+ Derivative Gain: + +
+
+ ); +}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx new file mode 100644 index 000000000..9a922de8c --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; + +export const UltrasonicSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { + const ultrasonicSensor = ev3.get('ultrasonicSensor'); + const [_, update] = useState(0); + + const distanceSensed = ultrasonicSensor.sense(); + + useEffect(() => { + // Hacky + setInterval(() => { + update((i) => i + 1); + }, 1000); + }, []); + + return ( + <> +
+

Distance: {distanceSensed}

+
+ + ); +}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx new file mode 100644 index 000000000..69a18de41 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx @@ -0,0 +1,62 @@ +import { NumericInput } from '@blueprintjs/core'; +import { type CSSProperties } from 'react'; +import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; + +const RowStyle: CSSProperties = { + display: 'flex', + flexDirection: 'row', + gap: '0.6rem', +}; + +export const WheelPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { + const onChangeProportional = (value: number) => { + ev3.get('backLeftWheel').pid.proportionalGain = value; + ev3.get('backRightWheel').pid.proportionalGain = value; + ev3.get('frontLeftWheel').pid.proportionalGain = value; + ev3.get('frontRightWheel').pid.proportionalGain = value; + }; + const onChangeIntegral = (value: number) => { + ev3.get('backLeftWheel').pid.integralGain = value; + ev3.get('backRightWheel').pid.integralGain = value; + ev3.get('frontLeftWheel').pid.integralGain = value; + ev3.get('frontRightWheel').pid.integralGain = value; + }; + const onChangeDerivative = (value: number) => { + ev3.get('backLeftWheel').pid.derivativeGain = value; + ev3.get('backRightWheel').pid.derivativeGain = value; + ev3.get('frontLeftWheel').pid.derivativeGain = value; + ev3.get('frontRightWheel').pid.derivativeGain = value; + }; + + return ( +
+
+ Proportional Gain: + +
+
+ Integral Gain: + +
+
+ Derivative Gain: + +
+
+ ); +}; diff --git a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx new file mode 100644 index 000000000..4310909da --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx @@ -0,0 +1,15 @@ +export const getTimeString = (date: Date) => { + const options: Intl.DateTimeFormatOptions = { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, // Use 24-hour format. Set to true for 12-hour format if preferred. + }; + return date.toLocaleTimeString([], options); +}; + +export const LastUpdated = ({ time }: { time: Date }) => { + const timeString = getTimeString(time); + + return Last updated: {timeString}; +}; From 055970527e205d77ff03fd7b4421f334b08294e0 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 1 Apr 2024 06:01:20 +0000 Subject: [PATCH 73/93] Add configuation to Program controller --- .../robot_simulation/controllers/program/Program.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index 091e97bcc..def721ba5 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -1,8 +1,10 @@ import { type IOptions } from 'js-slang'; import context from 'js-slang/context'; +import type { DeepPartial } from '../../../../common/deepPartial'; import { type Controller } from '../../engine'; import { CallbackHandler } from '../../engine/Core/CallbackHandler'; import type { PhysicsTimingInfo } from '../../engine/Physics'; +import { mergeConfig } from '../utils/mergeConfig'; import { ProgramError } from './error'; import { runECEvaluator } from './evaluate'; @@ -10,6 +12,10 @@ type ProgramConfig = { stepsPerTick: number; }; +const defaultProgramConfig: ProgramConfig = { + stepsPerTick: 11, +}; + export const program_controller_identifier = 'program_controller'; export class Program implements Controller { @@ -18,8 +24,10 @@ export class Program implements Controller { isPaused: boolean; callbackHandler = new CallbackHandler(); name: string; + config: ProgramConfig; - constructor(code: string) { + constructor(code: string, config?: DeepPartial) { + this.config = mergeConfig(defaultProgramConfig, config); this.name = program_controller_identifier; this.code = code; this.iterator = null; @@ -58,7 +66,7 @@ export class Program implements Controller { } // steps per tick - for (let i = 0; i < 11; i++) { + for (let i = 0; i < this.config.stepsPerTick; i++) { const result = this.iterator.next(); } } catch (e) { From ec9e5948fcd269011392283781e5934547dec4ac Mon Sep 17 00:00:00 2001 From: joel chan Date: Fri, 12 Apr 2024 08:40:35 +0000 Subject: [PATCH 74/93] Documentation for the functions --- src/bundles/robot_simulation/ev3_functions.ts | 2 +- .../robot_simulation/helper_functions.ts | 304 ++++++++++++++++-- 2 files changed, 287 insertions(+), 19 deletions(-) diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index ed0876f44..4cb04c424 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -17,7 +17,7 @@ type MotorFunctionReturnType = Motor | null; * @categoryDescription EV3 * These functions are mocking the the normal EV3 functions found * at https://docs.sourceacademy.org/EV3/global.html - * @module + * @module robot_simulation */ /** diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index fc1c0b4cb..00be5805f 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -12,15 +12,27 @@ import { Program } from './controllers/program/Program'; import { type Controller, Physics, Renderer, Timer, World } from './engine'; import { RobotConsole } from './engine/Core/RobotConsole'; -import { isRigidBodyType, type RigidBodyType } from './engine/Entity/EntityFactory'; +import { + isRigidBodyType, + type RigidBodyType, +} from './engine/Entity/EntityFactory'; import type { PhysicsConfig } from './engine/Physics'; import type { RenderConfig } from './engine/Render/Renderer'; import { getCamera, type CameraOptions } from './engine/Render/helpers/Camera'; import { createScene } from './engine/Render/helpers/Scene'; -const storedWorld = context.moduleContexts.robot_simulation.state?.world; +/** + * @categoryDescription Configuration + * These functions are use to configure the simulation world. + * @module + */ -// Helper functions to get objects from context +/** + * A helper function that retrieves the world from the context + * + * @private + * @category helper + */ export function getWorldFromContext(): World { const world = context.moduleContexts.robot_simulation.state?.world; if (world === undefined) { @@ -29,6 +41,12 @@ export function getWorldFromContext(): World { return world as World; } +/** + * A helper function that retrieves the EV3 from context + * + * @private + * @category helper + */ export function getEv3FromContext(): DefaultEv3 { const ev3 = context.moduleContexts.robot_simulation.state?.ev3; if (ev3 === undefined) { @@ -37,10 +55,23 @@ export function getEv3FromContext(): DefaultEv3 { return ev3 as DefaultEv3; } -// Physics +/** + * Create a physics engine with the provided gravity and timestep. A physics engine + * with default gravity and timestep can be created using {@link createPhysics}. + * + * The returned Physics object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @param gravity The gravity of the world + * @param timestep The timestep of the world + * @returns Physics + * + * @category Configuration + */ export function createCustomPhysics( gravity: number, - timestep: number, + timestep: number ): Physics { const physicsConfig: PhysicsConfig = { gravity: { @@ -54,11 +85,33 @@ export function createCustomPhysics( return physics; } +/** + * Create a physics engine with default gravity and timestep. Default gravity is -9.81 and timestep is 1/20. + * A custom physics engine can be created using {@link createCustomPhysics}. + * + * The returned Physics object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @returns Physics + * + * @category Configuration + */ export function createPhysics(): Physics { return createCustomPhysics(-9.81, 1 / 20); } -// Renderer +/** + * Creates a renderer for the simulation. + * + * The returned Renderer object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @returns Renderer + * + * @category Configuration + */ export function createRenderer(): Renderer { const sceneCameraOptions: CameraOptions = { type: 'perspective', @@ -80,30 +133,126 @@ export function createRenderer(): Renderer { return renderer; } -// Timer +/** + * Creates a Timer for the simulation. + * + * The returned Timer object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @returns Timer + * + * @category Configuration + */ export function createTimer(): Timer { const timer = new Timer(); return timer; } -// Robot console -export function createRobotConsole() { +/** + * Creates a RobotConsole for the simulation. + * + * The RobotConsole is used to display messages and errors to the user. The console + * messages can be seen in the console tab of the simulator. + * + * The returned RobotConsole object is designed to be passed into {@link createWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @returns RobotConsole + * + * @category Configuration + */ +export function createRobotConsole(): RobotConsole { const robot_console = new RobotConsole(); return robot_console; } -// Create world +/** + * Creates a custom world with the provided {@link createPhysics | physics}, {@link createRenderer | renderer}, {@link createTimer | timer} and {@link createRobotConsole | console} . + * + * A world is responsible for managing the physics, rendering, timing and console of the simulation. + * It also manages the controllers that are added to the world, ensuring that the appropriate functions + * are called at the correct time. + * + * The returned World object is designed to be returned by the {@link init_simulation} callback. + * + * You can add controllers to the world using {@link addControllerToWorld}. + * + * **This is a configuration function and should be called within {@link init_simulation}.** + * + * @example + * An empty simulation + * ``` + * init_simulation(() => { + * const physics = createPhysics(); + * const renderer = createRenderer(); + * const timer = createTimer(); + * const robot_console = createRobotConsole(); + * const world = createWorld(physics, renderer, timer, robot_console); + * + * return world; + * }); + * ``` + * + * @param physics The physics engine of the world. See {@link createPhysics} + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @param timer The timer of the world. See {@link createTimer} + * @param robotConsole The console of the world. See {@link createRobotConsole} + * @returns World + * + * @category Configuration + */ export function createWorld( physics: Physics, renderer: Renderer, timer: Timer, - robotConsole: RobotConsole, -) { + robotConsole: RobotConsole +): World { const world = new World(physics, renderer, timer, robotConsole); return world; } -// Environment +/** + * Creates a cuboid. joel-todo: The dynamic version wont work + * + * This function is used to create the {@link createFloor | floor} and {@link createWall | wall} controllers. + * + * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @param physics The physics engine passed to the world + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @param position_x The x position of the cuboid + * @param position_y The y position of the cuboid + * @param position_z The z position of the cuboid + * @param width The width of the cuboid in meters + * @param length The length of the cuboid in meters + * @param height The height of the cuboid in meters + * @param mass The mass of the cuboid in kg + * @param color The color of the cuboid. Can be a hex code or a string. {@see https://threejs.org/docs/#api/en/math/Color} + * @param bodyType "rigid" or "dynamic". Determines if the cuboid is fixed or can move. + * @returns Cuboid + * + * @example + * ``` + * init_simulation(() => { + * const physics = createPhysics(); + * const renderer = createRenderer(); + * const timer = createTimer(); + * const robot_console = createRobotConsole(); + * const world = createWorld(physics, renderer, timer, robot_console); + * + * const cuboid = createCuboid(...); + * addControllerToWorld(cuboid, world); + * + * return world; + * }); + * ``` + * + * @category Controller + */ export function createCuboid( physics: Physics, renderer: Renderer, @@ -115,7 +264,7 @@ export function createCuboid( height: number, mass: number, color: number | string, - bodyType: string, + bodyType: string ) { if (isRigidBodyType(bodyType) === false) { throw new Error('Invalid body type'); @@ -143,6 +292,19 @@ export function createCuboid( return cuboid; } +/** + * Create a floor. This function is a wrapper around {@link createCuboid}. + * + * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @param physics The physics engine of the world. See {@link createPhysics} + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @returns Cuboid + * + * @category Controller + */ export function createFloor(physics: Physics, renderer: Renderer) { const floor = createCuboid( physics, @@ -155,11 +317,24 @@ export function createFloor(physics: Physics, renderer: Renderer) { 1, // height 1, // mass 'white', // color - 'fixed', // bodyType + 'fixed' // bodyType ); return floor; } +/** + * Creates a wall. This function is a wrapper around {@link createCuboid}. + * + * The returned Cuboid object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @param physics The physics engine of the world. See {@link createPhysics} + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @returns Cuboid + * + * @category Controller + */ export function createWall(physics: Physics, renderer: Renderer) { const wall = createCuboid( physics, @@ -172,16 +347,32 @@ export function createWall(physics: Physics, renderer: Renderer) { 2, // height 1, // mass 'yellow', // color - 'fixed', // bodyType + 'fixed' // bodyType ); return wall; } +/** + * Creates a paper on the floor. + * + * The returned Paper object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @param render The renderer engine of the world. See {@link createRenderer} + * @param url The url of the image to be displayed on the paper. + * @param width The width of the paper in meters. + * @param height The height of the paper in meters. + * + * @returns Paper + * + * @category Controller + */ export function createPaper( render: Renderer, url: string, width: number, - height: number, + height: number ) { const paperConfig: PaperConfig = { url, @@ -194,16 +385,59 @@ export function createPaper( return paper; } +/** + * Creates a CSE machine as a Program Object. The CSE machine is used to evaluate the code written + * by the user. The execution of the code will be automatically synchronized with the simulation + * to ensure that the code is executed at the correct time. + * + * The returned Program object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @returns Program + * + * @category Controller + * + */ export function createCSE() { const code = context.unTypecheckedCode[0]; const program = new Program(code); return program; } +/** + * Add a controller to the world. + * + * The controller is a unit of computation modelled after Unity's MonoBehaviour. It is used to + * encapsulate the logic of the simulation. Controllers can be used to create robots, sensors, + * actuators, and other objects in the simulation. + * + * The controller should be added to the world using this function in order for the simulation to + * access the controller's logic. + * + * **This is a Utility function and should be called within {@link init_simulation}.* + * + * @param controller + * @param world + * + * @category Utility + */ export function addControllerToWorld(controller: Controller, world: World) { world.addController(controller); } +/** + * Save a value to the context. + * + * There are 2 important values to be saved. The world and the ev3. + * The world needs to be saved in order for the simulation to access the physics, renderer, timer and console. + * The ev3 needs to be saved in order for the "ev3_" functions to access the EV3 + * + * @param key The key to save the value as + * @param value The value to save + * + * @returns void + */ export function saveToContext(key: string, value: any) { if (!context.moduleContexts.robot_simulation.state) { context.moduleContexts.robot_simulation.state = {}; @@ -211,13 +445,47 @@ export function saveToContext(key: string, value: any) { context.moduleContexts.robot_simulation.state[key] = value; } +/** + * Create an EV3. + * + * The resulting EV3 should be saved to the context using {@link saveToContext}. + * + * The returned EV3 object is designed to be added to the world using {@link addControllerToWorld}. + * + * **This is a Controller function and should be called within {@link init_simulation}.** + * + * @example + * ``` + * init_simulation(() => { + * ... + * const ev3 = createEv3(physics, renderer); + * saveToContext('ev3', ev3); + * }) + * ``` + * + * @param physics The physics engine of the world. See {@link createPhysics} + * @param renderer The renderer engine of the world. See {@link createRenderer} + * @returns EV3 + */ export function createEv3(physics: Physics, renderer: Renderer): DefaultEv3 { const ev3 = createDefaultEv3(physics, renderer, ev3Config); return ev3; } -// Initialization +/** + * Initialize the simulation world. This function is to be called before the robot code. + * This function is used to describe the simulation environment and the controllers. + * + * The callback function takes in no parameters and returns a world created by {@link createWorld}. + * The world should be configured with the physics, renderer, timer and console. + * The controllers should be added to the world using {@link addControllerToWorld}. + * The world should be saved to the context using {@link saveToContext}. + * + * @param worldFactory A callback function that returns the world object. Type signature: () => World + * @returns void + */ export function init_simulation(worldFactory: () => World) { + const storedWorld = context.moduleContexts.robot_simulation.state?.world; if (storedWorld !== undefined) { return; } From 97eb970f2d35fb9cfa4ad26bfead845dd3863493 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 13 Apr 2024 07:48:26 +0000 Subject: [PATCH 75/93] Add tests --- src/__mocks__/emptyModule.js | 1 + .../__tests__/ev3/components/Chassis.ts | 87 +++++++++++++ .../__tests__/ev3/components/Mesh.ts | 93 ++++++++++++++ .../__tests__/ev3/components/Motor.ts | 118 ++++++++++++++++++ .../__tests__/ev3/components/Wheel.ts | 109 ++++++++++++++++ .../__tests__/ev3/ev3/default/ev3.ts | 56 +++++++++ .../ev3/feedback_control/PidController.ts | 95 ++++++++++++++ .../__tests__/ev3/sensor/ColorSensor.ts | 102 +++++++++++++++ .../__tests__/ev3/sensor/UltrasonicSensor.ts | 73 +++++++++++ .../controllers/__tests__/program/Program.ts | 78 ++++++++++++ .../__tests__/utils/mergeConfig.ts | 49 ++++++++ .../controllers/ev3/components/Wheel.ts | 3 +- .../ev3/sensor/UltrasonicSensor.ts | 4 +- .../controllers/program/Program.ts | 2 +- .../controllers/program/evaluate.ts | 8 +- .../engine/Render/Renderer.ts | 2 - .../engine/Render/helpers/GLTF.ts | 1 - src/jest.config.js | 3 +- .../components/TabPanels/ColorSensorPanel.tsx | 8 +- 19 files changed, 879 insertions(+), 13 deletions(-) create mode 100644 src/__mocks__/emptyModule.js create mode 100644 src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/ev3/components/Motor.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/ev3/components/Wheel.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/ev3/ev3/default/ev3.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/ev3/feedback_control/PidController.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/ColorSensor.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/program/Program.ts create mode 100644 src/bundles/robot_simulation/controllers/__tests__/utils/mergeConfig.ts diff --git a/src/__mocks__/emptyModule.js b/src/__mocks__/emptyModule.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/src/__mocks__/emptyModule.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts new file mode 100644 index 000000000..21e175e21 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts @@ -0,0 +1,87 @@ +import * as THREE from 'three'; +import { Physics, Renderer, EntityFactory , MeshFactory } from '../../../../engine'; + +import { ChassisWrapper } from '../../../ev3/components/Chassis'; + +jest.mock('../../../../engine', () => ({ + Physics: jest.fn(), + Renderer: jest.fn(), + EntityFactory: { addCuboid: jest.fn() }, + MeshFactory: { addCuboid: jest.fn() } +})); +jest.mock('../../../../engine/Entity/EntityFactory'); + +jest.mock('three', () => { + const three = jest.requireActual('three'); + return { + ...three, + Mesh: jest.fn().mockImplementation(() => ({ + position: { copy: jest.fn() }, + quaternion: { copy: jest.fn() }, + visible: false, + })), + Color: jest.fn() + }; +}); + +const mockedMeshFactory = MeshFactory as jest.Mocked; +const mockedEntityFactory = EntityFactory as jest.Mocked; + +describe('ChassisWrapper', () => { + let physicsMock; + let rendererMock; + let chassisWrapper; + let config; + + beforeEach(() => { + physicsMock = jest.fn() as unknown as Physics; + rendererMock = {add:jest.fn()} as unknown as Renderer; + config = { + dimension: { width: 1, height: 1, depth: 1 }, + orientation: { x: 0, y: 0, z: 0, w: 1 }, + debug: true + }; + + mockedMeshFactory.addCuboid.mockReturnValue(new THREE.Mesh()); + chassisWrapper = new ChassisWrapper(physicsMock, rendererMock, config); + }); + + it('should initialize with a debug mesh if debug is true', () => { + expect(MeshFactory.addCuboid).toHaveBeenCalledWith({ + orientation: config.orientation, + dimension: config.dimension, + color: expect.any(THREE.Color), + debug: true + }); + expect(rendererMock.add).toBeCalled(); + expect(chassisWrapper.debugMesh.visible).toBe(true); + }); + + it('should throw if getEntity is called before chassis is initialized', () => { + expect(chassisWrapper.chassis).toBe(null); + expect(() => chassisWrapper.getEntity()).toThrow('Chassis not initialized'); + }); + + it('should correctly initialize the chassis entity on start', async () => { + const mockEntity = { getTranslation: jest.fn(), getRotation: jest.fn() }; + mockedEntityFactory.addCuboid.mockReturnValue(mockEntity as any); + await chassisWrapper.start(); + + expect(chassisWrapper.chassis).toBe(mockEntity); + expect(EntityFactory.addCuboid).toHaveBeenCalledWith(physicsMock, config); + }); + + it('should update the position and orientation of the debug mesh to match the chassis entity', () => { + const mockEntity = { + getTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), + getRotation: jest.fn().mockReturnValue(new THREE.Quaternion()) + }; + mockedEntityFactory.addCuboid.mockReturnValue(mockEntity as any); + chassisWrapper.chassis = mockEntity; + + chassisWrapper.update(); + + expect(chassisWrapper.debugMesh.position.copy).toHaveBeenCalledWith(mockEntity.getTranslation()); + expect(chassisWrapper.debugMesh.quaternion.copy).toHaveBeenCalledWith(mockEntity.getRotation()); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts new file mode 100644 index 000000000..ee523870f --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts @@ -0,0 +1,93 @@ +import * as THREE from 'three'; +import { Renderer } from '../../../../engine'; +import { loadGLTF } from '../../../../engine/Render/helpers/GLTF'; +import { ChassisWrapper } from '../../../ev3/components/Chassis'; +import { Mesh } from '../../../ev3/components/Mesh'; + +jest.mock('three', () => { + const three = jest.requireActual('three'); + return { + ...three, + GLTF: jest.fn().mockImplementation(() => ({ + scene: {}, + })), + }; +}); + +jest.mock('../../../../engine/Render/helpers/GLTF', () => ({ + loadGLTF: jest.fn().mockResolvedValue({ + scene: { + position: { + copy: jest.fn(), + }, + quaternion: { + copy: jest.fn(), + }, + }, + }), +})); + +jest.mock('../../../ev3/components/Chassis', () => ({ + ChassisWrapper: jest.fn().mockImplementation(() => ({ + getEntity: jest.fn().mockReturnValue({ + getTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), + getRotation: jest.fn().mockReturnValue(new THREE.Quaternion()), + }), + })), +})); + +jest.mock('../../../../engine', () => ({ + Renderer: jest.fn().mockImplementation(() => ({ + add: jest.fn(), + })), +})); + +describe('Mesh', () => { + let mesh; + let mockChassisWrapper; + let mockRenderer; + let mockConfig; + + beforeEach(() => { + mockRenderer = { add: jest.fn() } as unknown as Renderer; + mockChassisWrapper = { + getEntity: jest.fn().mockReturnValue({ + getTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), + getRotation: jest.fn().mockReturnValue(new THREE.Quaternion()), + }), + } as unknown as ChassisWrapper; + mockConfig = { + url: 'path/to/mesh', + dimension: { width: 1, height: 2, depth: 3 }, + offset: { x: 0.5, y: 0.5, z: 0.5 }, + }; + + // mockLoadGLTF.mockResolvedValue({ + // scene: new THREE.GLTF().scene + // } as any); + + mesh = new Mesh(mockChassisWrapper, mockRenderer, mockConfig); + }); + + it('should initialize correctly with given configurations', () => { + expect(mesh.config.url).toBe(mockConfig.url); + expect(mesh.offset.x).toBe(0.5); + }); + + it('should load the mesh and add it to the renderer on start', async () => { + await mesh.start(); + expect(loadGLTF).toHaveBeenCalledWith(mockConfig.url, mockConfig.dimension); + expect(mockRenderer.add).toHaveBeenCalledWith(expect.any(Object)); // Checks if mesh scene is added to renderer + }); + + it('should update mesh position and orientation according to chassis', async () => { + await mesh.start(); + mesh.update(); + + const chassisEntity = mockChassisWrapper.getEntity(); + expect(chassisEntity.getTranslation).toHaveBeenCalled(); + expect(chassisEntity.getRotation).toHaveBeenCalled(); + expect(mesh.mesh.scene.position.copy).toHaveBeenCalled(); + expect(mesh.mesh.scene.quaternion.copy).toHaveBeenCalled(); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Motor.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Motor.ts new file mode 100644 index 000000000..f50f13848 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Motor.ts @@ -0,0 +1,118 @@ +import * as THREE from 'three'; +import { Renderer, Physics } from '../../../../engine'; +import { loadGLTF } from '../../../../engine/Render/helpers/GLTF'; +import { ChassisWrapper } from '../../../ev3/components/Chassis'; +import { Motor } from '../../../ev3/components/Motor'; +import { ev3Config } from '../../../ev3/ev3/default/config'; + +jest.mock('three', () => { + const originalModule = jest.requireActual('three'); + return { + ...originalModule, + }; +}); + +jest.mock('../../../../engine/Render/helpers/GLTF', () => ({ + loadGLTF: jest.fn().mockResolvedValue({ + scene: { + position: { + copy: jest.fn(), + }, + quaternion: { + copy: jest.fn(), + }, + rotateX: jest.fn(), + rotateZ: jest.fn() + } + }), +})); + +jest.mock('../../../../engine', () => ({ + Physics: jest.fn(), + Renderer: jest.fn().mockImplementation(() => ({ + add: jest.fn(), + })), +})); + +jest.mock('../../../ev3/components/Chassis', () => ({ + ChassisWrapper: jest.fn().mockImplementation(() => ({ + getEntity: jest.fn().mockReturnValue({ + transformDirection: jest.fn().mockImplementation((v) => v), + worldVelocity: jest.fn().mockReturnValue(new THREE.Vector3()), + worldTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), + applyImpulse: jest.fn(), + getMass: jest.fn().mockReturnValue(1), + getRotation: jest.fn(), + }), + })), +})); + +describe('Motor', () => { + let motor; + let mockChassisWrapper; + let mockPhysics; + let mockRenderer; + let mockConfig; + + beforeEach(() => { + mockPhysics = { + applyImpulse: jest.fn(), + } as unknown as Physics; + mockRenderer = { add: jest.fn() } as unknown as Renderer; + mockConfig = { + displacement: { x: 1, y: 0, z: 0 }, + pid: { + proportionalGain: 1, + integralGain: 0.1, + derivativeGain: 0.01, + }, + mesh: { + url: 'path/to/mesh', + dimension: { height: 1, width: 1, depth: 1 }, + }, + }; + const config = ev3Config.motors[0]; + mockChassisWrapper = new ChassisWrapper(mockPhysics, mockRenderer, config); + motor = new Motor( + mockChassisWrapper, + mockPhysics, + mockRenderer, + mockConfig + ); + }); + + it('should initialize correctly and load the mesh', async () => { + await motor.start(); + expect(loadGLTF).toHaveBeenCalledWith( + mockConfig.mesh.url, + mockConfig.mesh.dimension + ); + expect(mockRenderer.add).toHaveBeenCalled(); + }); + + it('sets motor velocity and schedules stop with distance', () => { + motor.setSpeedDistance(10, 100); + expect(motor.motorVelocity).toBe(10); + }); + + it('updates the motor velocity and applies impulse', () => { + motor.fixedUpdate({ deltaTime: 1 }); + expect(mockChassisWrapper.getEntity().applyImpulse).toHaveBeenCalled(); + }); + + it('updates mesh', async () => { + await motor.start(); + motor.update({frameDuration: 1}); + + expect(motor.mesh.scene.position.copy).toBeCalled(); + expect(motor.mesh.scene.quaternion.copy).toBeCalled(); + }); + + it('rotates the mesh if on the left side', async () => { + motor.wheelSide = 'left'; + await motor.start(); + motor.update({frameDuration: 1}); + + expect(motor.mesh.scene.rotateZ).toBeCalled(); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Wheel.ts new file mode 100644 index 000000000..2112bea79 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Wheel.ts @@ -0,0 +1,109 @@ +import * as THREE from 'three'; +import { Wheel } from '../../../ev3/components/Wheel'; + +jest.mock('../../../../engine/Render/debug/DebugArrow', () => ({ + DebugArrow: jest.fn().mockImplementation(() => ({ + getMesh: jest.fn().mockReturnValue({}), + update: jest.fn(), + })), +})); + +jest.mock('three', () => { + const originalModule = jest.requireActual('three'); + return { + ...originalModule, + }; +}); + +describe('Wheel', () => { + let wheel; + let mockChassisWrapper; + let mockPhysics; + let mockRenderer; + let mockConfig; + + beforeEach(() => { + mockPhysics = { + castRay: jest.fn(), + }; + mockRenderer = { + add: jest.fn(), + }; + mockChassisWrapper = { + getEntity: jest.fn().mockReturnValue({ + worldTranslation: jest.fn().mockImplementation(() => new THREE.Vector3()), + transformDirection: jest.fn().mockImplementation(() => new THREE.Vector3()), + applyImpulse: jest.fn(), + getMass: jest.fn().mockReturnValue(1), + getCollider: jest.fn().mockReturnValue({}), + }), + }, + mockConfig = { + displacement: { x: 1, y: 0, z: 0 }, + pid: { + proportionalGain: 1, + integralGain: 0.1, + derivativeGain: 0.01, + }, + gapToFloor: 0.5, + maxRayDistance: 5, + debug: true, + }; + wheel = new Wheel( + mockChassisWrapper, + mockPhysics, + mockRenderer, + mockConfig + ); + }); + + it('should initialize with a debug arrow if debug is true', () => { + expect(wheel.arrowHelper).toBeDefined(); + expect(mockRenderer.add).toHaveBeenCalled(); + }); + + it('should correctly calculate physics interactions in fixedUpdate', () => { + const timingInfo = { timestep: 16 }; // 16 ms timestep + const mockResult = { + distance: 0.3, + normal: new THREE.Vector3(0, 1, 0), + }; + mockPhysics.castRay.mockReturnValue(mockResult); + + wheel.fixedUpdate(timingInfo); + + expect(mockPhysics.castRay).toHaveBeenCalledWith( + expect.any(THREE.Vector3), + expect.any(THREE.Vector3), + mockConfig.maxRayDistance, + expect.anything() + ); + expect(mockChassisWrapper.getEntity().applyImpulse).toHaveBeenCalled(); + expect(wheel.arrowHelper.update).toHaveBeenCalled(); + }); + + it('should handle null result from castRay indicating no ground contact', () => { + const timingInfo = { timestep: 16 }; + mockPhysics.castRay.mockReturnValue(null); + + wheel.fixedUpdate(timingInfo); + + expect(mockChassisWrapper.getEntity().applyImpulse).not.toHaveBeenCalled(); + }); + + it('if wheelDistance is 0, the normal should be pointing up', () => { + const timingInfo = { timestep: 16 }; // 16 ms timestep + const mockResult = { + distance: 0, + normal: new THREE.Vector3(0, 0, 0), + }; + mockPhysics.castRay.mockReturnValue(mockResult); + + wheel.fixedUpdate(timingInfo); + + expect(mockChassisWrapper.getEntity().applyImpulse).toHaveBeenCalledWith( + expect.objectContaining({x: 0, z: 0}), // y value can be anything + expect.any(THREE.Vector3) + ); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/ev3/default/ev3.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/ev3/default/ev3.ts new file mode 100644 index 000000000..133a0dcbd --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/ev3/default/ev3.ts @@ -0,0 +1,56 @@ +import { Physics, Renderer, ControllerMap } from '../../../../../engine'; +import { ChassisWrapper } from '../../../../ev3/components/Chassis'; +import { Mesh } from '../../../../ev3/components/Mesh'; +import { Motor } from '../../../../ev3/components/Motor'; +import { Wheel } from '../../../../ev3/components/Wheel'; +import { ev3Config } from '../../../../ev3/ev3/default/config'; +import { createDefaultEv3 } from '../../../../ev3/ev3/default/ev3'; +import { ColorSensor } from '../../../../ev3/sensor/ColorSensor'; +import { UltrasonicSensor } from '../../../../ev3/sensor/UltrasonicSensor'; + +jest.mock('../../../../ev3/components/Chassis', () => { + return { ChassisWrapper: jest.fn() }; +}); +jest.mock('../../../../ev3/components/Mesh', () => { + return { Mesh: jest.fn() }; +}); +jest.mock('../../../../ev3/components/Motor', () => { + return { Motor: jest.fn() }; +}); +jest.mock('../../../../ev3/components/Wheel', () => { + return { Wheel: jest.fn() }; +}); +jest.mock('../../../../ev3/sensor/ColorSensor', () => { + return { ColorSensor: jest.fn() }; +}); +jest.mock('../../../../ev3/sensor/UltrasonicSensor', () => { + return { UltrasonicSensor: jest.fn() }; +}); +jest.mock('../../../../../engine', () => { + return { + Physics: jest.fn(), + Renderer: jest.fn(), + ControllerMap: jest.fn().mockImplementation(() => { + return { add: jest.fn() }; + }) + }; +}); + +describe('createDefaultEv3', () => { + const mockPhysics = new Physics({gravity:{x:0, y:-1, z:0}, timestep: 0.01}); + const mockRenderer = jest.fn() as unknown as Renderer; + const mockConfig =ev3Config; + + it('should correctly create all components and return a controller map', () => { + createDefaultEv3(mockPhysics, mockRenderer, mockConfig); + + expect(ChassisWrapper).toHaveBeenCalledWith(mockPhysics, mockRenderer, mockConfig.chassis); + expect(Mesh).toHaveBeenCalledWith(expect.any(ChassisWrapper), mockRenderer, mockConfig.mesh); + expect(Wheel).toHaveBeenCalledTimes(4); + expect(Motor).toHaveBeenCalledTimes(2); + expect(ColorSensor).toHaveBeenCalledWith(expect.any(ChassisWrapper), mockRenderer, mockConfig.colorSensor); + expect(UltrasonicSensor).toHaveBeenCalledWith(expect.any(ChassisWrapper), mockPhysics, mockRenderer, mockConfig.ultrasonicSensor); + + expect(ControllerMap).toHaveBeenCalled(); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/feedback_control/PidController.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/feedback_control/PidController.ts new file mode 100644 index 000000000..f23f747d1 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/feedback_control/PidController.ts @@ -0,0 +1,95 @@ +import * as THREE from 'three'; +import { NumberPidController, VectorPidController } from '../../../ev3/feedback_control/PidController'; + +jest.mock('three', () => { + const three = jest.requireActual('three'); + return { + ...three, + }; +}); + +const resetPid = (pidController:NumberPidController) => { + pidController.errorsSum = 0; + pidController.previousError = 0; +}; + +const resetVectorPid = (pidController:VectorPidController) => { + pidController.errorsSum = new THREE.Vector3(); + pidController.previousError = new THREE.Vector3(); +}; + +describe('NumberPidController', () => { + let pidController; + + beforeEach(() => { + pidController = new NumberPidController({ + proportionalGain: 0.1, + integralGain: 0.01, + derivativeGain: 0.05, + }); + }); + + it('should initialize correctly', () => { + expect(pidController.proportionalGain).toEqual(0.1); + expect(pidController.integralGain).toEqual(0.01); + expect(pidController.derivativeGain).toEqual(0.05); + }); + + it('should calculate correct PID output', () => { + const setpoint = 10; + let output; + + output = pidController.calculate(2, setpoint); + expect(output).toBeGreaterThan(0); + resetPid(pidController); + + output = pidController.calculate(9, setpoint); + expect(output).toBeGreaterThan(0); + resetPid(pidController); + + output = pidController.calculate(11, setpoint); + expect(output).toBeLessThan(0); + resetPid(pidController); + + output = pidController.calculate(10, setpoint); + expect(output).toBeCloseTo(0); + }); +}); + +describe('VectorPidController', () => { + let pidController; + + beforeEach(() => { + pidController = new VectorPidController({ + proportionalGain: 0.1, + integralGain: 0.01, + derivativeGain: 0.05, + }); + }); + + it('should initialize correctly', () => { + expect(pidController.proportionalGain).toEqual(0.1); + }); + + it('should calculate correct PID output for vector inputs', () => { + const setpoint = new THREE.Vector3(10, 10, 10); + let output; + + output = pidController.calculate(new THREE.Vector3(0, 0, 0), setpoint); + expect(output.length()).toBeGreaterThan(0); // Output should push towards setpoint + resetVectorPid(pidController); + + output = pidController.calculate(new THREE.Vector3(9, 9, 9), setpoint); + expect(output.length()).toBeGreaterThan(0); + resetVectorPid(pidController); + + output = pidController.calculate(new THREE.Vector3(11, 11, 11), setpoint); + expect(output.length()).toBeGreaterThan(0); + resetVectorPid(pidController); + + output = pidController.calculate(new THREE.Vector3(10, 10, 10), setpoint); + expect(output.x).toBeCloseTo(0); + expect(output.y).toBeCloseTo(0); + expect(output.z).toBeCloseTo(0); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/ColorSensor.ts new file mode 100644 index 000000000..dfd45cdb9 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/ColorSensor.ts @@ -0,0 +1,102 @@ +import * as THREE from 'three'; +import { ColorSensor } from '../../../ev3/sensor/ColorSensor'; + +jest.mock('three', () => { + const three = jest.requireActual('three'); + return { + ...three, + }; +}); + +jest.mock('../../../../engine', () => ({ + Renderer: jest.fn().mockImplementation(() => ({ + scene: jest.fn(), + render: jest.fn(), + getElement: jest.fn(() => document.createElement('canvas')), + })), +})); + +jest.mock('../../../../engine/Render/helpers/Camera', () => ({ + getCamera: jest.fn().mockImplementation(() => { + return new THREE.PerspectiveCamera(); + }), +})); + +describe('ColorSensor', () => { + let sensor; + let mockChassisWrapper; + let mockRenderer; + let mockConfig; + + beforeEach(() => { + mockChassisWrapper = { + getEntity: jest.fn(() => ({ + worldTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), + })), + }; + mockRenderer = { + add: jest.fn(), + scene: jest.fn(), + render: jest.fn(), + getElement: jest.fn(() => document.createElement('canvas')), + }; + mockConfig = { + tickRateInSeconds: 0.1, + displacement: { + x: 0.04, + y: 0.2, + z: 0.01, + }, + size: { + height: 16, + width: 16, + }, + camera: { + type: 'perspective', + aspect: 1, + fov: 10, + near: 0.01, + far: 1, + }, + debug: true, + }; + + sensor = new ColorSensor(mockChassisWrapper, mockRenderer, mockConfig); + + const mockCtx = { + getImageData: jest.fn(() => ({ + data: new Uint8ClampedArray([255, 255, 255, 255]), + })), + putImageData: jest.fn(), + drawImage: jest.fn(), + fillRect: jest.fn(), + clearRect: jest.fn(), + canvas: {}, + }; + + HTMLCanvasElement.prototype.getContext = jest + .fn() + .mockImplementation((_) => { + return mockCtx; + }); + }); + + it('should initialize correctly', () => { + expect(sensor).toBeDefined(); + expect(mockRenderer.add).toHaveBeenCalled(); + }); + + it('should update color only after accumulating sufficient time', () => { + const timingInfo = { timestep: 50 }; + sensor.fixedUpdate(timingInfo); + expect(mockRenderer.render).not.toHaveBeenCalled(); + sensor.fixedUpdate(timingInfo); + }); + + it('should give correct response for sense', () => { + const colorSensed = { r: 10, g: 20, b: 30 }; + sensor.colorSensed = colorSensed; + const result = sensor.sense(); + expect(result).toEqual(colorSensed); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts new file mode 100644 index 000000000..3b2df33b2 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/sensor/UltrasonicSensor.ts @@ -0,0 +1,73 @@ +import * as THREE from 'three'; +import { UltrasonicSensor } from '../../../ev3/sensor/UltrasonicSensor'; + +jest.mock('three', () => ({ + Vector3: jest.fn().mockImplementation(() => ({ + clone: jest.fn().mockReturnThis(), + normalize: jest.fn().mockReturnThis(), + copy: jest.fn() + })), + ArrowHelper: jest.fn().mockImplementation(() => ({ + visible: false, + position: { + copy: jest.fn() + }, + setDirection: jest.fn() + })) +})); + +describe('UltrasonicSensor', () => { + let sensor: UltrasonicSensor; + let mockChassisWrapper; + let mockPhysics; + let mockRenderer; + let mockConfig; + + beforeEach(() => { + mockChassisWrapper = { + getEntity: jest.fn(() => ({ + worldTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), + transformDirection: jest.fn().mockReturnValue(new THREE.Vector3()), + getCollider: jest.fn() + })) + }; + mockPhysics = { + castRay: jest.fn().mockReturnValue({ distance: 5 }) + }; + mockRenderer = { + add: jest.fn() + }; + mockConfig = { + displacement: { x: 1, y: 1, z: 1 }, + direction: { x: 0, y: 1, z: 0 }, + debug: true + }; + + sensor = new UltrasonicSensor(mockChassisWrapper, mockPhysics, mockRenderer, mockConfig); + }); + + it('should create instances and set initial properties', () => { + expect(sensor).toBeDefined(); + expect(THREE.Vector3).toHaveBeenCalledTimes(2); // Called for displacement and direction + expect(mockRenderer.add).toHaveBeenCalledWith(sensor.debugArrow); + }); + + it('should return initial distance sensed as 0', () => { + expect(sensor.sense()).toEqual(0); + }); + + it('should calculate distance when fixedUpdate is called', () => { + sensor.fixedUpdate(); + expect(sensor.distanceSensed).toEqual(5); + expect(mockPhysics.castRay).toHaveBeenCalled(); + expect(sensor.debugArrow.visible).toBeTruthy(); + expect(sensor.debugArrow.setDirection).toHaveBeenCalled(); + }); + + it('should handle null results from castRay indicating no collision detected', () => { + mockPhysics.castRay.mockReturnValue(null); + sensor.fixedUpdate(); + expect(sensor.distanceSensed).toEqual(0); + expect(mockPhysics.castRay).toHaveBeenCalled(); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts b/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts new file mode 100644 index 000000000..220d8c006 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts @@ -0,0 +1,78 @@ +import { CallbackHandler } from '../../../engine/Core/CallbackHandler'; +import { Program, program_controller_identifier } from '../../program/Program'; +import { runECEvaluator } from '../../program/evaluate'; + +jest.mock('../../../engine/Core/CallbackHandler'); +jest.mock('../../program/evaluate'); + +const mockedRunECEvaluator = runECEvaluator as jest.MockedFunction; +const mockedCallbackHandler = CallbackHandler as jest.MockedClass; + +describe('Program', () => { + let program: Program; + const mockCode = 'const x = 1;'; + + beforeEach(() => { + mockedCallbackHandler.mockClear(); + mockedRunECEvaluator.mockClear(); + + program = new Program(mockCode); + }); + + it('should initialize with default configuration if none provided', () => { + expect(program.config.stepsPerTick).toEqual(11); + expect(program.name).toEqual(program_controller_identifier); + expect(program.isPaused).toBeFalsy(); + }); + + it('should merge user configuration with default', () => { + const customProgram = new Program(mockCode, { stepsPerTick: 20 }); + expect(customProgram.config.stepsPerTick).toEqual(20); + }); + + it('should start the evaluator with correct options', () => { + const mockIterator = { next: jest.fn() } as any; + mockedRunECEvaluator.mockReturnValue(mockIterator); + + program.start(); + + expect(mockedRunECEvaluator).toHaveBeenCalledWith(mockCode, expect.anything(), expect.anything()); + expect(program.iterator).toBe(mockIterator); + }); + + it('should handle pause and resume correctly', () => { + const mockIterator = { next: jest.fn() } as any; + mockedRunECEvaluator.mockReturnValue(mockIterator); + + program.start(); + const tick = {stepCount:0,timestep: 1000} as any; + program.update(tick); + program.pause(900); + expect(program.isPaused).toBeTruthy(); + expect(CallbackHandler.prototype.addCallback).toHaveBeenCalledWith(expect.any(Function), 900); + + program.fixedUpdate(); + expect(mockIterator.next).not.toBeCalled(); + }); + + it('should process fixed number of steps per tick', () => { + const mockIterator = { next: jest.fn() } as any; + mockedRunECEvaluator.mockReturnValue(mockIterator); + + program.start(); + program.fixedUpdate(); + + expect(mockIterator.next).toHaveBeenCalledTimes(11); + }); + + it('should catch errors during fixedUpdate', () => { + expect(() => program.fixedUpdate()).toThrow('Error in program execution. Please check your code and try again.'); + }); + + it('should check callbacks on update', () => { + const mockTimingInfo = { deltaTime: 1 / 60 } as any; + program.update(mockTimingInfo); + + expect(mockedCallbackHandler.prototype.checkCallbacks).toHaveBeenCalledWith(mockTimingInfo); + }); +}); diff --git a/src/bundles/robot_simulation/controllers/__tests__/utils/mergeConfig.ts b/src/bundles/robot_simulation/controllers/__tests__/utils/mergeConfig.ts new file mode 100644 index 000000000..4ad579dd6 --- /dev/null +++ b/src/bundles/robot_simulation/controllers/__tests__/utils/mergeConfig.ts @@ -0,0 +1,49 @@ +import { mergeConfig } from '../../utils/mergeConfig'; // Update the path accordingly + +describe('mergeConfig function', () => { + // Test default configuration alone + it('should return the default configuration when user configuration is undefined', () => { + const defaultConfig = { color: 'blue', size: 'large' }; + const result = mergeConfig(defaultConfig); + expect(result).toEqual(defaultConfig); + }); + + // Test overriding some properties + it('should override default properties with user properties', () => { + const defaultConfig = { color: 'blue', size: 'large', theme: 'light' }; + const userConfig = { color: 'red' }; // only overriding the color + const expectedConfig = { color: 'red', size: 'large', theme: 'light' }; + const result = mergeConfig(defaultConfig, userConfig); + expect(result).toEqual(expectedConfig); + }); + + // Test adding new properties + it('should add new properties from user configuration', () => { + type Config = { color: string, size: string, opacity?: number }; + const defaultConfig = { color: 'blue', size: 'large' }; + const userConfig = { opacity: 0.5 }; + const expectedConfig = { color: 'blue', size: 'large', opacity: 0.5 }; + const result = mergeConfig(defaultConfig, userConfig); + expect(result).toEqual(expectedConfig); + }); + + // Test nested objects + it('should correctly merge nested configurations', () => { + const defaultConfig = { window: { color: 'blue', size: 'large' }, isEnabled: true }; + const userConfig = { window: { color: 'red' }, isEnabled: false }; + const expectedConfig = { + window: { color: 'red', size: 'large' }, + isEnabled: false + }; + const result = mergeConfig(defaultConfig, userConfig); + expect(result).toEqual(expectedConfig); + }); + + // Test to ensure that defaultConfig is not mutated + it('should not mutate the default configuration object', () => { + const defaultConfig = { color: 'blue', size: 'large' }; + const userConfig = { color: 'red' }; + mergeConfig(defaultConfig, userConfig); + expect(defaultConfig).toEqual({ color: 'blue', size: 'large' }); // Check defaultConfig remains unchanged + }); +}); diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts index 95c4450ec..5074fb9c6 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Wheel.ts @@ -76,7 +76,8 @@ export class Wheel implements Controller { return; } - let { distance: wheelDistance, normal } = result; + const wheelDistance = result.distance; + let normal = result.normal; // If distance is zero, the ray originate from inside the floor/wall. // If that is true, we assume the normal is pointing up. diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts index 106e8e3b8..95478a8c2 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -1,7 +1,8 @@ import * as THREE from 'three'; -import { type Renderer, type Physics } from '../../../engine'; import { vec3 } from '../../../engine/Math/Convert'; import { type SimpleVector } from '../../../engine/Math/Vector'; +import {type Physics } from '../../../engine/Physics'; +import { type Renderer } from '../../../engine/Render/Renderer'; import { type ChassisWrapper } from '../components/Chassis'; import { type Sensor } from './types'; @@ -73,4 +74,5 @@ export class UltrasonicSensor implements Sensor { this.distanceSensed = wheelDistance; } + } diff --git a/src/bundles/robot_simulation/controllers/program/Program.ts b/src/bundles/robot_simulation/controllers/program/Program.ts index def721ba5..a6fa5d5cc 100644 --- a/src/bundles/robot_simulation/controllers/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/program/Program.ts @@ -1,8 +1,8 @@ import { type IOptions } from 'js-slang'; import context from 'js-slang/context'; import type { DeepPartial } from '../../../../common/deepPartial'; -import { type Controller } from '../../engine'; import { CallbackHandler } from '../../engine/Core/CallbackHandler'; +import { type Controller } from '../../engine/Core/Controller'; import type { PhysicsTimingInfo } from '../../engine/Physics'; import { mergeConfig } from '../utils/mergeConfig'; import { ProgramError } from './error'; diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/controllers/program/evaluate.ts index ac21350f8..41b764586 100644 --- a/src/bundles/robot_simulation/controllers/program/evaluate.ts +++ b/src/bundles/robot_simulation/controllers/program/evaluate.ts @@ -1,12 +1,12 @@ -import * as _ from 'lodash'; import { type IOptions } from 'js-slang/dist'; -import { Variant, type Context, type RecursivePartial } from 'js-slang/dist/types'; import { Control, Stash, generateCSEMachineStateStream } from 'js-slang/dist/cse-machine/interpreter'; import { parse } from 'js-slang/dist/parser/parser'; +import { Variant, type Context, type RecursivePartial } from 'js-slang/dist/types'; +import * as _ from 'lodash'; -const DEFAULT_SOURCE_OPTIONS = { +export const DEFAULT_SOURCE_OPTIONS = { scheduler: 'async', steps: 1000, stepLimit: -1, @@ -24,7 +24,6 @@ const DEFAULT_SOURCE_OPTIONS = { }, }; - export function* runECEvaluator( code: string, context: Context, @@ -32,6 +31,7 @@ export function* runECEvaluator( ): Generator<{ steps: number }, void, undefined> { const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); const program = parse(code, context); + console.log(program); if (!program) { return; diff --git a/src/bundles/robot_simulation/engine/Render/Renderer.ts b/src/bundles/robot_simulation/engine/Render/Renderer.ts index fdee56c98..223487612 100644 --- a/src/bundles/robot_simulation/engine/Render/Renderer.ts +++ b/src/bundles/robot_simulation/engine/Render/Renderer.ts @@ -1,7 +1,5 @@ -/* eslint-disable import/extensions */ import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; - import { type GLTF, GLTFLoader, diff --git a/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts b/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts index 10b86918e..773068a3d 100644 --- a/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts +++ b/src/bundles/robot_simulation/engine/Render/helpers/GLTF.ts @@ -1,5 +1,4 @@ import * as THREE from 'three'; -// eslint-disable-next-line import/extensions import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import type { Dimension } from '../../Math/Vector'; diff --git a/src/jest.config.js b/src/jest.config.js index c490f46d0..c67898054 100644 --- a/src/jest.config.js +++ b/src/jest.config.js @@ -20,7 +20,8 @@ export default { moduleNameMapper: { // Module Name settings required to make chalk work with jest '#(.*)': '/node_modules/$1', - '^js-slang/context': '/__mocks__/context.ts' + '^js-slang/context': '/__mocks__/context.ts', + '^three.+.js': '/__mocks__/emptyModule.js', // 'lowdb': '/node_modules/lowdb/lib', // 'steno': '/node_modules/steno/lib', }, diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx index 29f454710..e24dbb018 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx @@ -1,6 +1,10 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, type CSSProperties } from 'react'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; +const panelWrapperStyle: CSSProperties = { + 'padding': '10px' +}; + export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { const colorSensor = ev3.get('colorSensor'); const sensorVisionRef = useRef(null); @@ -19,7 +23,7 @@ export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { }, []); return <> -
+

Red: {colorSensed.r}

Green: {colorSensed.g}

From 1d7003c64472ee0ee69cbb1e6c362fa9db5686ca Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 13 Apr 2024 07:58:47 +0000 Subject: [PATCH 76/93] Minor changes to tests --- .../controllers/__tests__/ev3/components/Chassis.ts | 1 + .../controllers/__tests__/program/Program.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts index 21e175e21..7579d71e7 100644 --- a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Chassis.ts @@ -44,6 +44,7 @@ describe('ChassisWrapper', () => { mockedMeshFactory.addCuboid.mockReturnValue(new THREE.Mesh()); chassisWrapper = new ChassisWrapper(physicsMock, rendererMock, config); + }); it('should initialize with a debug mesh if debug is true', () => { diff --git a/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts b/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts index 220d8c006..84f88b07e 100644 --- a/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts @@ -17,6 +17,8 @@ describe('Program', () => { mockedRunECEvaluator.mockClear(); program = new Program(mockCode); + + jest.spyOn(console, 'error').mockImplementation(jest.fn()); }); it('should initialize with default configuration if none provided', () => { @@ -69,10 +71,11 @@ describe('Program', () => { expect(() => program.fixedUpdate()).toThrow('Error in program execution. Please check your code and try again.'); }); - it('should check callbacks on update', () => { - const mockTimingInfo = { deltaTime: 1 / 60 } as any; - program.update(mockTimingInfo); + // it('should check callbacks on update', () => { + // program.start(); + // const mockTimingInfo = { deltaTime: 1 / 60 } as any; + // program.update(mockTimingInfo); - expect(mockedCallbackHandler.prototype.checkCallbacks).toHaveBeenCalledWith(mockTimingInfo); - }); + // expect(mockedCallbackHandler.prototype.checkCallbacks).toHaveBeenCalledWith(mockTimingInfo); + // }); }); From 3443ebbada2e776d4fa628eb095db75a9f0c692e Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 13 Apr 2024 08:38:49 +0000 Subject: [PATCH 77/93] Fix up the tabs --- .../components/Simulation/index.tsx | 2 - .../components/TabPanels/ColorSensorPanel.tsx | 52 +++++++++++-------- .../components/TabPanels/ConsolePanel.tsx | 32 +++++++----- .../components/TabPanels/MonitoringPanel.tsx | 3 -- .../components/TabPanels/MotorPidPanel.tsx | 7 +-- .../TabPanels/UltrasonicSensorPanel.tsx | 35 ++++++++----- .../components/TabPanels/WheelPidPanel.tsx | 7 +-- .../TabPanels/tabComponents/Wrapper.tsx | 10 ++++ src/tabs/RobotSimulation/components/TabUi.tsx | 2 +- 9 files changed, 91 insertions(+), 59 deletions(-) delete mode 100644 src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx create mode 100644 src/tabs/RobotSimulation/components/TabPanels/tabComponents/Wrapper.tsx diff --git a/src/tabs/RobotSimulation/components/Simulation/index.tsx b/src/tabs/RobotSimulation/components/Simulation/index.tsx index a5d292d0e..4726003fc 100644 --- a/src/tabs/RobotSimulation/components/Simulation/index.tsx +++ b/src/tabs/RobotSimulation/components/Simulation/index.tsx @@ -8,7 +8,6 @@ import type { DebuggerContext } from '../../../../typings/type_helpers'; import { ColorSensorPanel } from '../TabPanels/ColorSensorPanel'; import { ConsolePanel } from '../TabPanels/ConsolePanel'; -import { MonitoringPanel } from '../TabPanels/MonitoringPanel'; import { MotorPidPanel } from '../TabPanels/MotorPidPanel'; import { UltrasonicSensorPanel } from '../TabPanels/UltrasonicSensorPanel'; import { WheelPidPanel } from '../TabPanels/WheelPidPanel'; @@ -101,7 +100,6 @@ export const SimulationCanvas: React.FC = ({
- } /> } /> } /> }/> diff --git a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx index e24dbb018..644cc42ab 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/ColorSensorPanel.tsx @@ -1,33 +1,43 @@ -import { useEffect, useRef, useState, type CSSProperties } from 'react'; +import React, { useEffect, useRef } from 'react'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; +import { useFetchFromSimulation } from '../../hooks/fetchFromSimulation'; +import { LastUpdated } from './tabComponents/LastUpdated'; +import { TabWrapper } from './tabComponents/Wrapper'; -const panelWrapperStyle: CSSProperties = { - 'padding': '10px' -}; - -export const ColorSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { +export const ColorSensorPanel: React.FC<{ ev3: DefaultEv3 }> = ({ ev3 }) => { const colorSensor = ev3.get('colorSensor'); const sensorVisionRef = useRef(null); - const [_, update] = useState(0); - const colorSensed = colorSensor.sense(); + + const [timing, color] = useFetchFromSimulation(() => { + if (ev3.get('colorSensor') === undefined) { + return null; + } + return colorSensor.sense(); + }, 1000); useEffect(() => { if (sensorVisionRef.current) { - sensorVisionRef.current.replaceChildren(colorSensor.renderer.getElement()); + sensorVisionRef.current.replaceChildren( + colorSensor.renderer.getElement() + ); } + }, [timing]); + + if (timing === null) { + return Loading color sensor; + } - // Hacky - setInterval(() => { - update((i) => i + 1); - }, 1000); - }, []); + if (color === null) { + return Color sensor not found; + } - return <> -
+ return ( + +
-

Red: {colorSensed.r}

-

Green: {colorSensed.g}

-

Blue: {colorSensed.b}

-
- ; +

Red: {color.r}

+

Green: {color.g}

+

Blue: {color.b}

+ + ); }; diff --git a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx index 55bb41254..8ff7e73c4 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/ConsolePanel.tsx @@ -1,9 +1,13 @@ -import { type LogEntry, type RobotConsole } from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; +import { + type LogEntry, + type RobotConsole, +} from '../../../../bundles/robot_simulation/engine/Core/RobotConsole'; import { useFetchFromSimulation } from '../../hooks/fetchFromSimulation'; import { LastUpdated, getTimeString } from './tabComponents/LastUpdated'; +import { TabWrapper } from './tabComponents/Wrapper'; const getLogString = (log: LogEntry) => { - const logLevelText :Record = { + const logLevelText: Record = { source: 'Runtime Source Error', error: 'Error', }; @@ -12,11 +16,9 @@ const getLogString = (log: LogEntry) => { return `[${timeString}] ${logLevelText[log.level]}: ${log.message}`; }; -export const ConsolePanel = ({ - robot_console, -}: { +export const ConsolePanel: React.FC<{ robot_console?: RobotConsole; -}) => { +}> = ({ robot_console }) => { const [timing, logs] = useFetchFromSimulation(() => { if (robot_console === undefined) { return null; @@ -25,32 +27,34 @@ export const ConsolePanel = ({ }, 1000); if (timing === null) { - return
Not fetched yet
; + return Not fetched yet; } if (logs === null) { return ( -
+ Console not found. Ensure that the world is initialized properly. -
+ ); } if (logs.length === 0) { return ( -
+

There is currently no logs

-
+ ); } return ( -
+
    - {logs.map((log, i) =>
  • {getLogString(log)}
  • )} + {logs.map((log, i) => ( +
  • {getLogString(log)}
  • + ))}
-
+ ); }; diff --git a/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx deleted file mode 100644 index 7f4827f48..000000000 --- a/src/tabs/RobotSimulation/components/TabPanels/MonitoringPanel.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; - -export const MonitoringPanel = ({ ev3 }: { ev3: DefaultEv3 }) => <>hi; diff --git a/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx index b6e65e376..ec9a88f43 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/MotorPidPanel.tsx @@ -1,6 +1,7 @@ import { NumericInput } from '@blueprintjs/core'; import { type CSSProperties } from 'react'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; +import { TabWrapper } from './tabComponents/Wrapper'; const RowStyle: CSSProperties = { display: 'flex', @@ -8,7 +9,7 @@ const RowStyle: CSSProperties = { gap: '0.6rem', }; -export const MotorPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { +export const MotorPidPanel: React.FC<{ ev3: DefaultEv3 }> = ({ ev3 }) => { const onChangeProportional = (value: number) => { ev3.get('leftMotor').pid.proportionalGain = value; ev3.get('rightMotor').pid.proportionalGain = value; @@ -23,7 +24,7 @@ export const MotorPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { }; return ( -
+
Proportional Gain: { minorStepSize={null} />
-
+ ); }; diff --git a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx index 9a922de8c..e69676fd7 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx @@ -1,24 +1,35 @@ -import { useEffect, useState } from 'react'; +import React from 'react'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers/ev3/ev3/default/ev3'; +import { useFetchFromSimulation } from '../../hooks/fetchFromSimulation'; +import { LastUpdated } from './tabComponents/LastUpdated'; +import { TabWrapper } from './tabComponents/Wrapper'; -export const UltrasonicSensorPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { +export const UltrasonicSensorPanel: React.FC<{ ev3: DefaultEv3 }> = ({ + ev3, +}) => { const ultrasonicSensor = ev3.get('ultrasonicSensor'); - const [_, update] = useState(0); - const distanceSensed = ultrasonicSensor.sense(); + const [timing, distanceSensed] = useFetchFromSimulation(() => { + if (ultrasonicSensor === undefined) { + return null; + } + return ultrasonicSensor.sense(); + }, 1000); - useEffect(() => { - // Hacky - setInterval(() => { - update((i) => i + 1); - }, 1000); - }, []); + if (timing === null) { + return Loading color sensor; + } + + if (distanceSensed === null) { + return Color sensor not found; + } return ( - <> + +

Distance: {distanceSensed}

- +
); }; diff --git a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx index 69a18de41..b4f5fcd30 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/WheelPidPanel.tsx @@ -1,6 +1,7 @@ import { NumericInput } from '@blueprintjs/core'; import { type CSSProperties } from 'react'; import { type DefaultEv3 } from '../../../../bundles/robot_simulation/controllers'; +import { TabWrapper } from './tabComponents/Wrapper'; const RowStyle: CSSProperties = { display: 'flex', @@ -8,7 +9,7 @@ const RowStyle: CSSProperties = { gap: '0.6rem', }; -export const WheelPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { +export const WheelPidPanel: React.FC<{ ev3: DefaultEv3 }> = ({ ev3 }) => { const onChangeProportional = (value: number) => { ev3.get('backLeftWheel').pid.proportionalGain = value; ev3.get('backRightWheel').pid.proportionalGain = value; @@ -29,7 +30,7 @@ export const WheelPidPanel = ({ ev3 }: { ev3: DefaultEv3 }) => { }; return ( -
+
Proportional Gain: { minorStepSize={null} />
-
+ ); }; diff --git a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/Wrapper.tsx b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/Wrapper.tsx new file mode 100644 index 000000000..d7d5aa806 --- /dev/null +++ b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/Wrapper.tsx @@ -0,0 +1,10 @@ +import type { CSSProperties } from 'react'; + +const panelWrapperStyle: CSSProperties = { + 'padding': '10px' +}; +export const TabWrapper:React.FC<{ + children?: React.ReactNode; +}> = ({children}) => { + return
{children}
; +}; diff --git a/src/tabs/RobotSimulation/components/TabUi.tsx b/src/tabs/RobotSimulation/components/TabUi.tsx index 23de6702d..00b9375c6 100644 --- a/src/tabs/RobotSimulation/components/TabUi.tsx +++ b/src/tabs/RobotSimulation/components/TabUi.tsx @@ -1,4 +1,4 @@ -import type React from 'react'; +import React from 'react'; type TabUiProps = { onOpenCanvas: () => void; From 989d6fff0435555263ebfe4811497b4e68978c99 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 13 Apr 2024 09:10:54 +0000 Subject: [PATCH 78/93] Add some ev3 functions --- src/bundles/robot_simulation/ev3_functions.ts | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 4cb04c424..1e632811b 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -30,7 +30,7 @@ type MotorFunctionReturnType = Motor | null; export function ev3_pause(duration: number): void { const world = getWorldFromContext(); const program = world.controllers.controllers.find( - (controller) => controller.name === program_controller_identifier, + (controller) => controller.name === program_controller_identifier ) as Program; program.pause(duration); } @@ -96,7 +96,7 @@ export function ev3_motorD(): MotorFunctionReturnType { export function ev3_runToRelativePosition( motor: MotorFunctionReturnType, position: number, - speed: number, + speed: number ): void { if (motor === null) { return; @@ -109,6 +109,51 @@ export function ev3_runToRelativePosition( motor.setSpeedDistance(speedInMetersPerSecond, distanceInMetersPerSecond); } +/** + * Causes the motor to rotate for a specified duration at the specified speed. + * + * Note: this works by sending instructions to the motors. This will return almost immediately, + * without waiting for the motor to actually run for the specified duration. + * If you wish to wait, use ev3_pause. + * + * @param motor + * @param time + * @param speed + * @returns void + */ +export function ev3_runForTime( + motor: MotorFunctionReturnType, + time: number, + speed: number +) { + if (motor === null) { + return; + } + const wheelDiameter = motorConfig.config.mesh.dimension.height; + const speedInMetersPerSecond = (speed / 360) * Math.PI * wheelDiameter; + const distanceInMetersPerSecond = speedInMetersPerSecond * time; + + motor.setSpeedDistance(speedInMetersPerSecond, distanceInMetersPerSecond); +} + +/** + * Gets the motor's current speed, in tacho counts per second. + * + * Returns 0 if the motor is not connected. + * + * @param motor + * @returns number + * + * @category EV3 + */ +export function ev3_motorGetSpeed(motor: MotorFunctionReturnType): number { + if (motor === null) { + return 0; + } + + return motor.motorVelocity; +} + /** * Gets the colour sensor connected any of ports 1, 2, 3 or 4. * @@ -177,7 +222,19 @@ export function ev3_ultrasonicSensor() { * @category EV3 */ export function ev3_ultrasonicSensorDistance( - ultraSonicSensor: UltrasonicSensor, + ultraSonicSensor: UltrasonicSensor ): number { return ultraSonicSensor.sense() * 100; } + +/** + * Checks if the peripheral is connected. + * + * @param obj The peripheral to check. + * @returns boolean + * + * @category EV3 + */ +export function ev3_connected(obj: any) { + return obj !== null; +} From 746ddb78bbdee03e53dacd4c2665231c3e7dcf8a Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 13 Apr 2024 09:11:21 +0000 Subject: [PATCH 79/93] Add category --- src/bundles/robot_simulation/ev3_functions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 1e632811b..8c11a5639 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -120,6 +120,8 @@ export function ev3_runToRelativePosition( * @param time * @param speed * @returns void + * + * @category EV3 */ export function ev3_runForTime( motor: MotorFunctionReturnType, From 53869e00da936d39be65e469b0f30bc507b18ba8 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 15 Apr 2024 02:24:04 +0000 Subject: [PATCH 80/93] Minor change to ultrasonic sense() --- .../controllers/ev3/sensor/UltrasonicSensor.ts | 2 +- .../robot_simulation/controllers/program/evaluate.ts | 6 ++---- src/bundles/robot_simulation/ev3_functions.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts index 95478a8c2..05ae2aecb 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/UltrasonicSensor.ts @@ -42,7 +42,7 @@ export class UltrasonicSensor implements Sensor { } sense(): number { - return this.distanceSensed; + return this.distanceSensed * 100; } fixedUpdate(): void { diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/controllers/program/evaluate.ts index 41b764586..7f769641b 100644 --- a/src/bundles/robot_simulation/controllers/program/evaluate.ts +++ b/src/bundles/robot_simulation/controllers/program/evaluate.ts @@ -1,9 +1,7 @@ -import { type IOptions } from 'js-slang/dist'; - import { Control, Stash, generateCSEMachineStateStream } from 'js-slang/dist/cse-machine/interpreter'; import { parse } from 'js-slang/dist/parser/parser'; -import { Variant, type Context, type RecursivePartial } from 'js-slang/dist/types'; +import { Variant, type Context } from 'js-slang/dist/types'; import * as _ from 'lodash'; export const DEFAULT_SOURCE_OPTIONS = { @@ -27,7 +25,7 @@ export const DEFAULT_SOURCE_OPTIONS = { export function* runECEvaluator( code: string, context: Context, - options: RecursivePartial, + options: any, ): Generator<{ steps: number }, void, undefined> { const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); const program = parse(code, context); diff --git a/src/bundles/robot_simulation/ev3_functions.ts b/src/bundles/robot_simulation/ev3_functions.ts index 8c11a5639..60601975e 100644 --- a/src/bundles/robot_simulation/ev3_functions.ts +++ b/src/bundles/robot_simulation/ev3_functions.ts @@ -226,7 +226,7 @@ export function ev3_ultrasonicSensor() { export function ev3_ultrasonicSensorDistance( ultraSonicSensor: UltrasonicSensor ): number { - return ultraSonicSensor.sense() * 100; + return ultraSonicSensor.sense(); } /** From 5f4930fb42ffb89eea273982db1a810a238e7938 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 15 Apr 2024 02:44:10 +0000 Subject: [PATCH 81/93] Uncomment test in Program --- .../controllers/__tests__/program/Program.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts b/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts index 84f88b07e..eae138830 100644 --- a/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts +++ b/src/bundles/robot_simulation/controllers/__tests__/program/Program.ts @@ -71,11 +71,11 @@ describe('Program', () => { expect(() => program.fixedUpdate()).toThrow('Error in program execution. Please check your code and try again.'); }); - // it('should check callbacks on update', () => { - // program.start(); - // const mockTimingInfo = { deltaTime: 1 / 60 } as any; - // program.update(mockTimingInfo); + it('should check callbacks on update', () => { + program.start(); + const mockTimingInfo = { deltaTime: 1 / 60 } as any; + program.update(mockTimingInfo); - // expect(mockedCallbackHandler.prototype.checkCallbacks).toHaveBeenCalledWith(mockTimingInfo); - // }); + expect(mockedCallbackHandler.prototype.checkCallbacks).toHaveBeenCalledWith(mockTimingInfo); + }); }); From af110201e1e8c183e16f9153d69ccf3d0d505c82 Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 16 Apr 2024 13:17:19 +0000 Subject: [PATCH 82/93] Make paper position movable --- src/bundles/robot_simulation/helper_functions.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index 00be5805f..bad4e1385 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -372,7 +372,10 @@ export function createPaper( render: Renderer, url: string, width: number, - height: number + height: number, + x: number, + y: number, + rotation: number ) { const paperConfig: PaperConfig = { url, @@ -380,6 +383,8 @@ export function createPaper( width, height, }, + position: {x, y}, + rotation: rotation * Math.PI / 180, }; const paper = new Paper(render, paperConfig); return paper; From 67d7c384bade0c0d948ef0301f9494db60a3f403 Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 16 Apr 2024 13:18:17 +0000 Subject: [PATCH 83/93] Make some changes to colorSensor --- .../robot_simulation/controllers/environment/Paper.ts | 5 ++++- .../controllers/ev3/ev3/default/config.ts | 8 ++++---- .../controllers/ev3/sensor/ColorSensor.ts | 11 ++++++++++- .../robot_simulation/controllers/program/evaluate.ts | 1 - src/bundles/robot_simulation/index.ts | 1 + 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/environment/Paper.ts b/src/bundles/robot_simulation/controllers/environment/Paper.ts index 08ab66a13..81fd70eaa 100644 --- a/src/bundles/robot_simulation/controllers/environment/Paper.ts +++ b/src/bundles/robot_simulation/controllers/environment/Paper.ts @@ -8,6 +8,8 @@ export type PaperConfig = { width: number; height: number; }; + position: {x: number, y: number}; + rotation: number, }; export class Paper { @@ -27,8 +29,9 @@ export class Paper { const texture = new THREE.TextureLoader() .load(this.config.url); const material = new THREE.MeshStandardMaterial({ map: texture }); - this.paper.position.set(0, 0.001, 0); // Position the plane at the floor + this.paper.position.set(this.config.position.x, 0.001, this.config.position.y); this.paper.rotation.x = -Math.PI / 2; + this.paper.rotation.z = this.config.rotation; this.paper.material = material; this.render.add(this.paper); } diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts index a6661ef9e..b50400fb5 100644 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts @@ -110,12 +110,12 @@ export const motorConfig: Ev3MotorsConfig = { }, }; -export const colorSensorConfig: Ev3ColorSensorConfig = { +export const colorSensorConfig = { tickRateInSeconds: 0.1, displacement: { - x: 0.04, + x: -0.05, y: -(chassisConfig.dimension.height / 2), - z: 0.01, + z: 0.09, }, size: { height: 16, @@ -129,7 +129,7 @@ export const colorSensorConfig: Ev3ColorSensorConfig = { far: 1, }, debug: true, -}; +} satisfies Ev3ColorSensorConfig; const ultrasonicSensorConfig: Ev3UltrasonicSenorConfig = { displacement: { diff --git a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts index 5f1a953e5..8beabfc1e 100644 --- a/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts +++ b/src/bundles/robot_simulation/controllers/ev3/sensor/ColorSensor.ts @@ -29,6 +29,7 @@ export class ColorSensor implements Sensor { config: ColorSensorConfig; camera: THREE.Camera; + spotLight: THREE.SpotLight; renderer: Renderer; accumulator = 0; colorSensed: Color; @@ -44,6 +45,8 @@ export class ColorSensor implements Sensor { this.config = config; this.camera = getCamera(config.camera); + this.spotLight = new THREE.SpotLight(0xffffff, 0.2, 1, 15/180 * Math.PI); + render.add(this.spotLight); // We create a new renderer with the same scene. But we use a different camera. this.renderer = new Renderer(render.scene(), this.camera, { width: this.config.size.width, @@ -95,12 +98,18 @@ export class ColorSensor implements Sensor { // We move the camera to the right position this.camera.position.copy(this.getColorSensorPosition()); - // Point it downwards this.camera.lookAt( this.camera.position.x, this.camera.position.y - 1, // 1 unit below its current position this.camera.position.z, ); + this.spotLight.position.copy(this.camera.position); + this.spotLight.target.position.set( + this.camera.position.x, + this.camera.position.y - 1, + this.camera.position.z, + ); + this.spotLight.target.updateMatrixWorld(); // We render to load the color sensor data into the renderer. this.renderer.render(); diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/controllers/program/evaluate.ts index 7f769641b..2f1f4c36d 100644 --- a/src/bundles/robot_simulation/controllers/program/evaluate.ts +++ b/src/bundles/robot_simulation/controllers/program/evaluate.ts @@ -29,7 +29,6 @@ export function* runECEvaluator( ): Generator<{ steps: number }, void, undefined> { const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); const program = parse(code, context); - console.log(program); if (!program) { return; diff --git a/src/bundles/robot_simulation/index.ts b/src/bundles/robot_simulation/index.ts index 0ae0cab0a..d74463c62 100644 --- a/src/bundles/robot_simulation/index.ts +++ b/src/bundles/robot_simulation/index.ts @@ -14,6 +14,7 @@ export { ev3_colorSensorRed, ev3_colorSensorGreen, ev3_pause, + ev3_colorSensor, ev3_colorSensorBlue, ev3_ultrasonicSensor, ev3_ultrasonicSensorDistance, From a288331d5c58e2120bd83d8a9524f6862792e9a4 Mon Sep 17 00:00:00 2001 From: joel chan Date: Tue, 16 Apr 2024 13:31:08 +0000 Subject: [PATCH 84/93] Add better docs --- src/bundles/robot_simulation/helper_functions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index bad4e1385..64e280c64 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -363,6 +363,9 @@ export function createWall(physics: Physics, renderer: Renderer) { * @param url The url of the image to be displayed on the paper. * @param width The width of the paper in meters. * @param height The height of the paper in meters. + * @param x The x position of the paper. + * @param y The y position of the paper. + * @param rotation The rotation of the paper in degrees. * * @returns Paper * From 492af2bb3be194646da516b4445bcfe243b55081 Mon Sep 17 00:00:00 2001 From: joel chan Date: Sat, 20 Apr 2024 16:39:30 +0000 Subject: [PATCH 85/93] Add configuration to the wall function --- .../controllers/ev3/ev3/default/config.ts | 4 +-- .../robot_simulation/helper_functions.ts | 31 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts index b50400fb5..06a02d701 100644 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/config.ts @@ -113,9 +113,9 @@ export const motorConfig: Ev3MotorsConfig = { export const colorSensorConfig = { tickRateInSeconds: 0.1, displacement: { - x: -0.05, + x: -0.06, y: -(chassisConfig.dimension.height / 2), - z: 0.09, + z: 0.11, }, size: { height: 16, diff --git a/src/bundles/robot_simulation/helper_functions.ts b/src/bundles/robot_simulation/helper_functions.ts index 64e280c64..d2f2aa213 100644 --- a/src/bundles/robot_simulation/helper_functions.ts +++ b/src/bundles/robot_simulation/helper_functions.ts @@ -331,20 +331,33 @@ export function createFloor(physics: Physics, renderer: Renderer) { * * @param physics The physics engine of the world. See {@link createPhysics} * @param renderer The renderer engine of the world. See {@link createRenderer} + * @param x The x position of the wall + * @param y The y position of the wall + * @param width The width of the wall in meters + * @param length The length of the wall in meters + * @param height The height of the wall in meters * @returns Cuboid * * @category Controller */ -export function createWall(physics: Physics, renderer: Renderer) { +export function createWall( + physics: Physics, + renderer: Renderer, + x: number, + y: number, + width: number, + length: number, + height: number +) { const wall = createCuboid( physics, renderer, - 0, // position_x - 1, // position_y - 1, // position_z - 1, // width - 0.1, // length - 2, // height + x, // position_x + height / 2, + y, // position_y + width, // width + length, // length + height, // height 1, // mass 'yellow', // color 'fixed' // bodyType @@ -386,8 +399,8 @@ export function createPaper( width, height, }, - position: {x, y}, - rotation: rotation * Math.PI / 180, + position: { x, y }, + rotation: (rotation * Math.PI) / 180, }; const paper = new Paper(render, paperConfig); return paper; From 3f2517388cde34c23ef29281e92e02ee6856fcaa Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 22 Apr 2024 07:37:44 +0000 Subject: [PATCH 86/93] Fix lockfile --- yarn.lock | 5 ----- 1 file changed, 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8c4f36acc..727f1e988 100644 --- a/yarn.lock +++ b/yarn.lock @@ -385,11 +385,6 @@ resolved "https://registry.yarnpkg.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.11.2.tgz#ae2b335f545decf1e82ff45bb10368e143de0fcb" integrity sha512-vdWmlkpS3G8nGAzLuK7GYTpNdrkn/0NKCe0l1Jqxc7ZZOB3N0q9uG/Ap9l9bothWuAvxscIt0U97GVLr0lXWLg== -"@dimforge/rapier3d-compat@^0.11.2": - version "0.11.2" - resolved "https://registry.yarnpkg.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.11.2.tgz#ae2b335f545decf1e82ff45bb10368e143de0fcb" - integrity sha512-vdWmlkpS3G8nGAzLuK7GYTpNdrkn/0NKCe0l1Jqxc7ZZOB3N0q9uG/Ap9l9bothWuAvxscIt0U97GVLr0lXWLg== - "@esbuild/android-arm64@0.18.20": version "0.18.20" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" From 94739f15d5cb54f15dd96b29b0b0b93e3ad35c3a Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 22 Apr 2024 07:38:44 +0000 Subject: [PATCH 87/93] Linting in evaluate --- .../controllers/program/evaluate.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/program/evaluate.ts b/src/bundles/robot_simulation/controllers/program/evaluate.ts index 2f1f4c36d..4107595ed 100644 --- a/src/bundles/robot_simulation/controllers/program/evaluate.ts +++ b/src/bundles/robot_simulation/controllers/program/evaluate.ts @@ -1,5 +1,8 @@ - -import { Control, Stash, generateCSEMachineStateStream } from 'js-slang/dist/cse-machine/interpreter'; +import { + Control, + Stash, + generateCSEMachineStateStream, +} from 'js-slang/dist/cse-machine/interpreter'; import { parse } from 'js-slang/dist/parser/parser'; import { Variant, type Context } from 'js-slang/dist/types'; import * as _ from 'lodash'; @@ -25,7 +28,7 @@ export const DEFAULT_SOURCE_OPTIONS = { export function* runECEvaluator( code: string, context: Context, - options: any, + options: any ): Generator<{ steps: number }, void, undefined> { const theOptions = _.merge({ ...DEFAULT_SOURCE_OPTIONS }, options); const program = parse(code, context); @@ -44,9 +47,9 @@ export function* runECEvaluator( context.runtime.stash, theOptions.envSteps, theOptions.stepLimit, - theOptions.isPrelude, + theOptions.isPrelude ); - // eslint-disable-next-line no-useless-catch + // eslint-disable-next-line no-useless-catch } catch (error) { throw error; } finally { From 2d4e4544f2564436eba8132b77d19a3a90884e72 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 22 Apr 2024 07:42:11 +0000 Subject: [PATCH 88/93] React FC added and fixed spelling --- .../components/TabPanels/UltrasonicSensorPanel.tsx | 4 ++-- .../components/TabPanels/tabComponents/LastUpdated.tsx | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx index e69676fd7..dfa64a3fe 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/UltrasonicSensorPanel.tsx @@ -17,11 +17,11 @@ export const UltrasonicSensorPanel: React.FC<{ ev3: DefaultEv3 }> = ({ }, 1000); if (timing === null) { - return Loading color sensor; + return Loading ultrasonic sensor; } if (distanceSensed === null) { - return Color sensor not found; + return Ultrasonic sensor not found; } return ( diff --git a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx index 4310909da..116de13ca 100644 --- a/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx +++ b/src/tabs/RobotSimulation/components/TabPanels/tabComponents/LastUpdated.tsx @@ -1,3 +1,5 @@ +import type React from 'react'; + export const getTimeString = (date: Date) => { const options: Intl.DateTimeFormatOptions = { hour: '2-digit', @@ -8,7 +10,11 @@ export const getTimeString = (date: Date) => { return date.toLocaleTimeString([], options); }; -export const LastUpdated = ({ time }: { time: Date }) => { +export const LastUpdated: React.FC<{ time: Date }> = ({ + time, +}: { + time: Date; +}) => { const timeString = getTimeString(time); return Last updated: {timeString}; From 4d5b9f4dfbf531b867abf7bb3f5e7ddd34d1a369 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 22 Apr 2024 07:44:13 +0000 Subject: [PATCH 89/93] Fix lint --- .../controllers/ev3/ev3/default/types.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts b/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts index dd2ae0dc9..19b14d97d 100644 --- a/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts +++ b/src/bundles/robot_simulation/controllers/ev3/ev3/default/types.ts @@ -41,8 +41,8 @@ export type ColorSensorControllers = Record; export const ultrasonicSensorNames = ['ultrasonicSensor'] as const; export type UltrasonicSensorNames = (typeof ultrasonicSensorNames)[number]; export type UltrasonicSensorControllers = Record< -UltrasonicSensorNames, -UltrasonicSensor + UltrasonicSensorNames, + UltrasonicSensor >; // Aggregate @@ -55,13 +55,7 @@ export const controllerNames = [ ...ultrasonicSensorNames, ] as const; export type DefaultEv3ControllerNames = (typeof controllerNames)[number]; -export type DefaultEv3Controller = WheelControllers & -MotorControllers & -ColorSensorControllers & -UltrasonicSensorControllers & -ChassisControllers & -MeshControllers; - +export type DefaultEv3Controller = ChassisControllers & ColorSensorControllers & MeshControllers & MotorControllers & UltrasonicSensorControllers & WheelControllers; // ######################### Config Types ######################### export type Ev3ChassisConfig = ChassisWrapperConfig; From 6ce6ed367329ffff9a3906af0899c6721ff7d5e9 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 22 Apr 2024 08:24:29 +0000 Subject: [PATCH 90/93] Add residual factor to the simulation --- src/bundles/robot_simulation/engine/Physics.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bundles/robot_simulation/engine/Physics.ts b/src/bundles/robot_simulation/engine/Physics.ts index 098edbea1..0ed38a07d 100644 --- a/src/bundles/robot_simulation/engine/Physics.ts +++ b/src/bundles/robot_simulation/engine/Physics.ts @@ -9,6 +9,7 @@ import { type SimpleVector } from './Math/Vector'; export type PhysicsTimingInfo = FrameTimingInfo & { stepCount: number; timestep: number; + residualFactor: number; }; export class TimeStampedEvent extends Event { @@ -67,7 +68,7 @@ export class Physics extends TypedEventTarget { this.internals = { initialized: true, world, - accumulator: 0, + accumulator: world.timestep, stepCount: 0, }; } @@ -134,15 +135,16 @@ export class Physics extends TypedEventTarget { const maxFrameTime = 0.05; const frameDuration = timing.frameDuration / 1000; - this.internals.accumulator -= Math.min(frameDuration, maxFrameTime); + this.internals.accumulator += Math.min(frameDuration, maxFrameTime); const currentPhysicsTimingInfo = { ...timing, stepCount: this.internals.stepCount, timestep: this.configuration.timestep * 1000, + residualFactor: this.internals.accumulator / this.configuration.timestep, }; - while (this.internals.accumulator <= 0) { + while (this.internals.accumulator >= this.configuration.timestep) { this.dispatchEvent( 'beforePhysicsUpdate', new TimeStampedEvent('beforePhysicsUpdate', currentPhysicsTimingInfo), @@ -158,7 +160,8 @@ export class Physics extends TypedEventTarget { new TimeStampedEvent('afterPhysicsUpdate', currentPhysicsTimingInfo), ); - this.internals.accumulator += this.configuration.timestep; + this.internals.accumulator -= this.configuration.timestep; + currentPhysicsTimingInfo.residualFactor = this.internals.accumulator / this.configuration.timestep; } return currentPhysicsTimingInfo; From 82c67f1aa485e6547a7d086bdffbc791ab8da2ae Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 22 Apr 2024 08:24:47 +0000 Subject: [PATCH 91/93] Change physics test --- src/bundles/robot_simulation/engine/__tests__/Physics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bundles/robot_simulation/engine/__tests__/Physics.ts b/src/bundles/robot_simulation/engine/__tests__/Physics.ts index 32d201e99..8530350c2 100644 --- a/src/bundles/robot_simulation/engine/__tests__/Physics.ts +++ b/src/bundles/robot_simulation/engine/__tests__/Physics.ts @@ -37,7 +37,7 @@ describe('Physics', () => { expect(rapier.init).toHaveBeenCalled(); expect(physics.internals).toHaveProperty('initialized', true); expect(physics.internals).toHaveProperty('world'); - expect(physics.internals).toHaveProperty('accumulator', 0); + expect(physics.internals).toHaveProperty('accumulator', physics.configuration.timestep); }); test('createRigidBody throws if not initialized', () => { From da83ed1ec6f85cdc8fcc675d8ae44b0319d97325 Mon Sep 17 00:00:00 2001 From: joel chan Date: Mon, 22 Apr 2024 08:47:11 +0000 Subject: [PATCH 92/93] Update mesh logic --- .../__tests__/ev3/components/Mesh.ts | 20 ++++++++-- .../controllers/ev3/components/Mesh.ts | 40 ++++++++++++++----- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts index ee523870f..082e14986 100644 --- a/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/__tests__/ev3/components/Mesh.ts @@ -55,6 +55,21 @@ describe('Mesh', () => { getTranslation: jest.fn().mockReturnValue(new THREE.Vector3()), getRotation: jest.fn().mockReturnValue(new THREE.Quaternion()), }), + config: { + orientation: { + position: { + x: 0, + y: 0.0775, + z: 0, + }, + rotation: { + x: 0, + y: 0, + z: 0, + w: 1, + }, + }, + } } as unknown as ChassisWrapper; mockConfig = { url: 'path/to/mesh', @@ -82,11 +97,8 @@ describe('Mesh', () => { it('should update mesh position and orientation according to chassis', async () => { await mesh.start(); - mesh.update(); + mesh.update({residualFactor: 0.5}); - const chassisEntity = mockChassisWrapper.getEntity(); - expect(chassisEntity.getTranslation).toHaveBeenCalled(); - expect(chassisEntity.getRotation).toHaveBeenCalled(); expect(mesh.mesh.scene.position.copy).toHaveBeenCalled(); expect(mesh.mesh.scene.quaternion.copy).toHaveBeenCalled(); }); diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index 25993d4a5..57a193ec8 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -1,7 +1,10 @@ +import * as THREE from 'three'; + import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { type Controller, type Renderer } from '../../../engine'; // eslint-disable-next-line import/extensions -import type { Dimension, SimpleVector } from '../../../engine/Math/Vector'; +import type { Dimension, SimpleQuaternion, SimpleVector } from '../../../engine/Math/Vector'; +import type { PhysicsTimingInfo } from '../../../engine/Physics'; import { loadGLTF } from '../../../engine/Render/helpers/GLTF'; import { type ChassisWrapper } from './Chassis'; @@ -23,6 +26,11 @@ export class Mesh implements Controller { mesh: GLTF | null = null; + previousTranslation: SimpleVector | null= null; + previousRotation: SimpleQuaternion | null = null; + currentTranslation: SimpleVector; + currentRotation: SimpleQuaternion; + constructor( chassisWrapper: ChassisWrapper, render: Renderer, @@ -36,6 +44,8 @@ export class Mesh implements Controller { y: this.config?.offset?.y || 0, z: this.config?.offset?.z || 0, }; + this.currentTranslation = this.chassisWrapper.config.orientation.position; + this.currentRotation = new THREE.Quaternion(0,0,0,1); } async start(): Promise { @@ -44,15 +54,27 @@ export class Mesh implements Controller { this.render.add(this.mesh.scene); } - update() { - const chassisEntity = this.chassisWrapper.getEntity(); - const chassisPosition = chassisEntity.getTranslation(); + fixedUpdate(): void { + this.previousTranslation = this.currentTranslation; + this.previousRotation = this.currentRotation; + this.currentRotation = this.chassisWrapper.getEntity().getRotation(); + this.currentTranslation = this.chassisWrapper.getEntity().getTranslation(); + } + + update(timingInfo: PhysicsTimingInfo) { + const vecCurrentTranslation = new THREE.Vector3().copy(this.currentTranslation); + const vecPreviousTranslation = new THREE.Vector3().copy(this.previousTranslation || this.currentTranslation); + const quatCurrentRotation = new THREE.Quaternion().copy(this.currentRotation); + const quatPreviousRotation = new THREE.Quaternion().copy(this.previousRotation || this.currentRotation); + + const estimatedTranslation = vecPreviousTranslation.lerp(vecCurrentTranslation, timingInfo.residualFactor); + const estimatedRotation = quatPreviousRotation.slerp(quatCurrentRotation, timingInfo.residualFactor); - chassisPosition.x -= this.offset.x / 2; - chassisPosition.y -= this.offset.y / 2; - chassisPosition.z -= this.offset.z / 2; + estimatedTranslation.x -= this.offset.x / 2; + estimatedTranslation.y -= this.offset.y / 2; + estimatedTranslation.z -= this.offset.z / 2; - this.mesh?.scene.position.copy(chassisPosition); - this.mesh?.scene.quaternion.copy(chassisEntity.getRotation()); + this.mesh?.scene.position.copy(estimatedTranslation); + this.mesh?.scene.quaternion.copy(estimatedRotation); } } From eec2e39579a9c4660c62f8b95387a0a15aeb1b27 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:17:06 +0800 Subject: [PATCH 93/93] Remove unused ESLint disable directive --- src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts index 57a193ec8..fee0ded29 100644 --- a/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts +++ b/src/bundles/robot_simulation/controllers/ev3/components/Mesh.ts @@ -2,7 +2,6 @@ import * as THREE from 'three'; import { type GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { type Controller, type Renderer } from '../../../engine'; -// eslint-disable-next-line import/extensions import type { Dimension, SimpleQuaternion, SimpleVector } from '../../../engine/Math/Vector'; import type { PhysicsTimingInfo } from '../../../engine/Physics'; import { loadGLTF } from '../../../engine/Render/helpers/GLTF';