From 98caf9f1574d8199f23420fdf06d77f2ed35d2df Mon Sep 17 00:00:00 2001 From: Dhruv Arora Date: Wed, 3 Jul 2024 15:19:07 -0700 Subject: [PATCH 01/33] Adding configure pickup in panelui Need to merge transform gizmo code to add a sphere in the scene for implementation. --- fission/src/Synthesis.tsx | 2 +- fission/src/systems/physics/PhysicsSystem.ts | 2 -- fission/src/ui/components/MainHUD.tsx | 5 +++++ .../panels/configuring/ConfigureGamepiecePickupPanel.tsx | 8 +++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index ef9d4eb4d2..a49083cb85 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -203,7 +203,7 @@ const initialPanels: ReactElement[] = [ , , , - , + , , , , diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index dd87a1c79d..b208c6484b 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -723,8 +723,6 @@ class PhysicsSystem extends WorldSystem { let substeps = Math.max(1, Math.floor((lastDeltaT / STANDARD_SIMULATION_PERIOD) * STANDARD_SUB_STEPS)) substeps = Math.min(MAX_SUBSTEPS, Math.max(MIN_SUBSTEPS, substeps)) - console.log(`DeltaT: ${lastDeltaT.toFixed(5)}, Substeps: ${substeps}`) - this._joltInterface.Step(lastDeltaT, substeps) } diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 16d9d98070..dba81fada7 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -148,6 +148,11 @@ const MainHUD: React.FC = () => { addToast(type, type, "This is a test toast to test the toast system") }} /> + } + onClick={() => openPanel("config-gamepiece-pickup")} + /> {userInfo ? ( = ({ panelId, openLocation, sidePadding }) => { const defaultZoneSize = 0.5 const [, setNode] = useState("Click to select") const [, setZoneSize] = useState(defaultZoneSize) + useEffect(() => { + // implementing spherical placeholder for intake placement + const mesh = World.SceneRenderer.CreateSphere(5.0) + }) + return ( Date: Thu, 4 Jul 2024 02:02:01 -0700 Subject: [PATCH 02/33] Added transform gizmo bug fixes and also cancel functionality --- fission/src/systems/scene/SceneRenderer.ts | 8 ++++-- .../ConfigureGamepiecePickupPanel.tsx | 28 +++++++++---------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 05dee621df..9c474d8ad0 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -221,15 +221,17 @@ class SceneRenderer extends WorldSystem { (event: { target: TransformControls; value: unknown }) => { if (!event.value && !Array.from(this.transformControls.keys()).some(tc => tc.dragging)) { this.orbitControls.enabled = true // enable orbit controls when not dragging another transform gizmo + } else if (!event.value && Array.from(this.transformControls.keys()).some(tc => tc.dragging)) { + this.orbitControls.enabled = false // disable orbit controls when dragging another transform gizmo + } else { + this.orbitControls.enabled = !event.value // disable orbit controls when dragging transform gizmo } - this.orbitControls.enabled = !event.value // disable orbit controls when dragging transform gizmo - if (event.target.mode === "translate") { this.transformControls.forEach((_size, tc) => { // disable other transform gizmos when translating if (tc.object === event.target.object && tc.mode !== "translate") { - tc.dragging = !event.value + tc.dragging = false tc.enabled = !event.value return } diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index efa94f9881..e9e6a6f126 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -1,19 +1,24 @@ -import { useEffect, useState } from "react" +import * as THREE from "three" +import { useEffect, useState, useRef } from "react" import { FaGear } from "react-icons/fa6" import Panel, { PanelPropsImpl } from "@/components/Panel" import SelectButton from "@/components/SelectButton" -import Slider from "@/components/Slider" +import TransformGizmos from "@/ui/components/TransformGizmos" import World from "@/systems/World" const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { - const defaultZoneSize = 0.5 const [, setNode] = useState("Click to select") - const [, setZoneSize] = useState(defaultZoneSize) + const transformGizmoRef = useRef() useEffect(() => { // implementing spherical placeholder for intake placement - const mesh = World.SceneRenderer.CreateSphere(5.0) - }) + transformGizmoRef.current = new TransformGizmos( + World.SceneRenderer.CreateSphere(0.5, World.SceneRenderer.CreateToonMaterial(new THREE.Color(0xffffff))) + ) + transformGizmoRef.current.AddMeshToScene() + transformGizmoRef.current.CreateGizmo("scale") + transformGizmoRef.current.CreateGizmo("translate") + }, []) return ( = ({ panelId, open onAccept={() => { // send zone config }} + onCancel={() => { + if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() + }} > - ) } From 2810af419289cd3e6c75f83c070425d802db8801 Mon Sep 17 00:00:00 2001 From: Dhruv Arora Date: Sun, 7 Jul 2024 22:26:54 -0700 Subject: [PATCH 03/33] Readded slider for sizing and configuration for the slider --- fission/src/ui/components/TransformGizmos.tsx | 4 +++ .../ConfigureGamepiecePickupPanel.tsx | 30 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/fission/src/ui/components/TransformGizmos.tsx b/fission/src/ui/components/TransformGizmos.tsx index e9fa45868f..60f8b6c25b 100644 --- a/fission/src/ui/components/TransformGizmos.tsx +++ b/fission/src/ui/components/TransformGizmos.tsx @@ -85,6 +85,10 @@ class TransformGizmos { }) }) } + + public setMeshSize(size: number) { + this.mesh.scale.set(size, size, size) + } } export default TransformGizmos diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index e9e6a6f126..d73bcc9b6c 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -5,18 +5,26 @@ import Panel, { PanelPropsImpl } from "@/components/Panel" import SelectButton from "@/components/SelectButton" import TransformGizmos from "@/ui/components/TransformGizmos" import World from "@/systems/World" +import Slider from "@/ui/components/Slider" + +// slider constants +const MIN_ZONE_SIZE = 0.1 +const MAX_ZONE_SIZE = 1.0 +const DEFAULT_ZONE_SIZE = 0.5 // default zone size const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { const [, setNode] = useState("Click to select") const transformGizmoRef = useRef() + // creating mesh & gizmo for the pickup node useEffect(() => { - // implementing spherical placeholder for intake placement transformGizmoRef.current = new TransformGizmos( - World.SceneRenderer.CreateSphere(0.5, World.SceneRenderer.CreateToonMaterial(new THREE.Color(0xffffff))) + World.SceneRenderer.CreateSphere( + DEFAULT_ZONE_SIZE, + World.SceneRenderer.CreateToonMaterial(new THREE.Color(0xffffff)) + ) ) transformGizmoRef.current.AddMeshToScene() - transformGizmoRef.current.CreateGizmo("scale") transformGizmoRef.current.CreateGizmo("translate") }, []) @@ -28,13 +36,27 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open openLocation={openLocation} sidePadding={sidePadding} onAccept={() => { - // send zone config + // send configuration information to APS + RAM }} onCancel={() => { if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() }} > + {/* Button for user to select pickup node */} + + {/* Slider for user to set size of pickup configuration */} + { + transformGizmoRef.current?.setMeshSize(size) + }} + step={0.01} + /> ) } From 2f70b40d6b1ee0c1b034902ad8e79c60765dbce2 Mon Sep 17 00:00:00 2001 From: Dhruv Arora Date: Mon, 8 Jul 2024 11:19:01 -0700 Subject: [PATCH 04/33] Merging dev --- .github/workflows/FissionESLintFormat.yml | 1 + .github/workflows/FissionPackage.yml | 15 +- fission/package.json | 5 +- fission/prettier.config.js | 1 + fission/src/Synthesis.tsx | 17 +- fission/src/mirabuf/MirabufInstance.ts | 9 - fission/src/mirabuf/MirabufLoader.ts | 291 +++++++++- fission/src/mirabuf/MirabufParser.ts | 4 +- fission/src/mirabuf/MirabufSceneObject.ts | 32 +- fission/src/samples/JoltExample.tsx | 214 -------- fission/src/systems/input/DefaultInputs.ts | 138 +++++ fission/src/systems/input/InputSystem.ts | 310 ++++++++--- fission/src/systems/physics/Mechanism.ts | 9 +- fission/src/systems/physics/PhysicsSystem.ts | 17 +- fission/src/systems/scene/SceneRenderer.ts | 5 +- .../behavior/ArcadeDriveBehavior.ts | 17 +- .../simulation/behavior/GenericArmBehavior.ts | 31 +- .../behavior/GenericElevatorBehavior.ts | 31 +- .../synthesis_brain/SynthesisBrain.ts | 79 ++- fission/src/test/MirabufParser.test.ts | 25 +- fission/src/test/PhysicsSystem.test.ts | 13 +- fission/src/ui/components/Checkbox.tsx | 6 +- fission/src/ui/components/Dropdown.tsx | 32 +- fission/src/ui/components/MainHUD.tsx | 15 + .../modals/configuring/ChangeInputsModal.tsx | 510 +++++++++++++++--- .../configuring/ResetAllInputsModal.tsx | 44 ++ .../mirabuf/ImportLocalMirabufModal.tsx | 43 +- .../src/ui/modals/spawning/SpawningModals.tsx | 178 ++++-- fission/tsconfig.json | 3 +- fission/vite.config.ts | 16 +- 30 files changed, 1522 insertions(+), 589 deletions(-) delete mode 100644 fission/src/samples/JoltExample.tsx create mode 100644 fission/src/systems/input/DefaultInputs.ts create mode 100644 fission/src/ui/modals/configuring/ResetAllInputsModal.tsx diff --git a/.github/workflows/FissionESLintFormat.yml b/.github/workflows/FissionESLintFormat.yml index c72c65dbfb..091c81d765 100644 --- a/.github/workflows/FissionESLintFormat.yml +++ b/.github/workflows/FissionESLintFormat.yml @@ -40,6 +40,7 @@ jobs: id: prettier-validation run: | cd fission + npx prettier --version npm run prettier continue-on-error: true - name: Check Prettier diff --git a/.github/workflows/FissionPackage.yml b/.github/workflows/FissionPackage.yml index 1a9cab15cb..ebe94c8dd3 100644 --- a/.github/workflows/FissionPackage.yml +++ b/.github/workflows/FissionPackage.yml @@ -25,6 +25,7 @@ jobs: - name: Install Dependencies run: | cd fission + rm package-lock.json npm install - name: Get package info @@ -37,11 +38,19 @@ jobs: id: build run: | cd fission - npm run build + npm run build:prod + npm run build:dev - name: Upload Artifact uses: actions/upload-artifact@v4 - id: upload-artifact + id: upload-artifact-prod with: name: "${{ steps.info.outputs.name }}@${{ steps.info.outputs.version }}[${{ steps.date.outputs.timestamp }}]" - path: fission/dist/ \ No newline at end of file + path: fission/dist/prod/ + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + id: upload-artifact-dev + with: + name: "${{ steps.info.outputs.name }}-dev@${{ steps.info.outputs.version }}[${{ steps.date.outputs.timestamp }}]" + path: fission/dist/dev/ \ No newline at end of file diff --git a/fission/package.json b/fission/package.json index 1352a7fe16..256645d327 100644 --- a/fission/package.json +++ b/fission/package.json @@ -6,6 +6,8 @@ "scripts": { "dev": "vite --open", "build": "tsc && vite build", + "build:prod": "tsc && vite build --base=/fission/ --outDir dist/prod", + "build:dev": "tsc && vite build --base=/fission-closed/ --outDir dist/dev", "preview": "vite preview --base=/fission/", "test": "vitest", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives", @@ -13,7 +15,6 @@ "prettier": "bun x prettier src --check || npx prettier src --check", "prettier:fix": "bun x prettier src --write || npx prettier src --write", "format": "(bun run prettier:fix && bun run lint:fix) || (npm run prettier:fix && npm run lint:fix)", - "build:prod": "tsc && vite build --base=/fission/", "assetpack": "curl -o public/assetpack.zip https://synthesis.autodesk.com/Downloadables/assetpack.zip && tar -xf public/assetpack.zip -C public/", "playwright:install": "bun x playwright install || npx playwright install" }, @@ -66,7 +67,7 @@ "protobufjs-cli": "^1.1.2", "tailwindcss": "^3.3.3", "tsconfig-paths": "^4.2.0", - "typescript": "^5.2.2", + "typescript": "^5.4.5", "vite": "^5.1.4", "vite-plugin-glsl": "^1.3.0", "vite-plugin-singlefile": "^0.13.5", diff --git a/fission/prettier.config.js b/fission/prettier.config.js index 1ab6c97d3c..c71dcd95af 100644 --- a/fission/prettier.config.js +++ b/fission/prettier.config.js @@ -18,6 +18,7 @@ const config = { }, }, ], + endOfLine: "lf", } export default config diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index e9e82e3faf..56e8c6ecfd 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -1,6 +1,6 @@ import Scene from "@/components/Scene.tsx" import MirabufSceneObject from "./mirabuf/MirabufSceneObject.ts" -import { LoadMirabufRemote } from "./mirabuf/MirabufLoader.ts" +import MirabufCachingService, { MiraType } from "./mirabuf/MirabufLoader.ts" import { mirabuf } from "./proto/mirabuf" import MirabufParser, { ParseErrorSeverity } from "./mirabuf/MirabufParser.ts" import MirabufInstance from "./mirabuf/MirabufInstance.ts" @@ -25,7 +25,7 @@ import UpdateAvailableModal from "@/modals/UpdateAvailableModal" import ViewModal from "@/modals/ViewModal" import ConnectToMultiplayerModal from "@/modals/aether/ConnectToMultiplayerModal" import ServerHostingModal from "@/modals/aether/ServerHostingModal" -import ChangeInputsModal from "@/modals/configuring/ChangeInputsModal" +import ChangeInputsModal from "@/ui/modals/configuring/ChangeInputsModal.tsx" import ChooseMultiplayerModeModal from "@/modals/configuring/ChooseMultiplayerModeModal" import ChooseSingleplayerModeModal from "@/modals/configuring/ChooseSingleplayerModeModal" import ConfigMotorModal from "@/modals/configuring/ConfigMotorModal" @@ -53,8 +53,9 @@ import ManageAssembliesModal from "@/modals/spawning/ManageAssembliesModal.tsx" import World from "@/systems/World.ts" import { AddRobotsModal, AddFieldsModal, SpawningModal } from "@/modals/spawning/SpawningModals.tsx" import ImportMirabufModal from "@/modals/mirabuf/ImportMirabufModal.tsx" -import Skybox from "./ui/components/Skybox.tsx" import ImportLocalMirabufModal from "@/modals/mirabuf/ImportLocalMirabufModal.tsx" +import ResetAllInputsModal from "./ui/modals/configuring/ResetAllInputsModal.tsx" +import Skybox from "./ui/components/Skybox.tsx" const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" @@ -93,10 +94,12 @@ function Synthesis() { console.log(urlParams) const setup = async () => { - const miraAssembly = await LoadMirabufRemote(mira_path) - .catch(_ => LoadMirabufRemote(DEFAULT_MIRA_PATH)) + const info = await MirabufCachingService.CacheRemote(mira_path, MiraType.ROBOT) + .catch(_ => MirabufCachingService.CacheRemote(DEFAULT_MIRA_PATH, MiraType.ROBOT)) .catch(console.error) + const miraAssembly = await MirabufCachingService.Get(info!.id, MiraType.ROBOT) + await (async () => { if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { return @@ -108,10 +111,11 @@ function Synthesis() { return } - const mirabufSceneObject = new MirabufSceneObject(new MirabufInstance(parser)) + const mirabufSceneObject = new MirabufSceneObject(new MirabufInstance(parser), miraAssembly.info!.name!) World.SceneRenderer.RegisterSceneObject(mirabufSceneObject) })() } + setup() let mainLoopHandle = 0 @@ -181,6 +185,7 @@ const initialModals = [ , , , + , , , , diff --git a/fission/src/mirabuf/MirabufInstance.ts b/fission/src/mirabuf/MirabufInstance.ts index 83307eb6cb..70448dcf31 100644 --- a/fission/src/mirabuf/MirabufInstance.ts +++ b/fission/src/mirabuf/MirabufInstance.ts @@ -143,8 +143,6 @@ class MirabufInstance { const assembly = this._mirabufParser.assembly const instances = assembly.data!.parts!.partInstances! - let totalMeshCount = 0 - for (const instance of Object.values(instances) /* .filter(x => x.info!.name!.startsWith('EyeBall')) */) { const definition = assembly.data!.parts!.partDefinitions![instance.partDefinitionReference!]! const bodies = definition.bodies @@ -170,10 +168,6 @@ class MirabufInstance { ? this._materials.get(appearanceOverride)! : fillerMaterials[nextFillerMaterial++ % fillerMaterials.length] - // if (NORMAL_MATERIALS) { - // material = new THREE.MeshNormalMaterial(); - // } - const threeMesh = new THREE.Mesh(geometry, material) threeMesh.receiveShadow = true threeMesh.castShadow = true @@ -186,11 +180,8 @@ class MirabufInstance { } } } - totalMeshCount += meshes.length this._meshes.set(instance.info!.GUID!, meshes) } - - console.debug(`Created '${totalMeshCount}' meshes for mira file '${this._mirabufParser.assembly.info!.name!}'`) } /** diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 0fe520bd93..3b3ecd925b 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -1,8 +1,29 @@ -import { mirabuf } from "../proto/mirabuf" +import { mirabuf } from "@/proto/mirabuf" import Pako from "pako" -// import * as fs from "fs" + +const MIRABUF_LOCALSTORAGE_GENERATION_KEY = "Synthesis Nonce Key" +const MIRABUF_LOCALSTORAGE_GENERATION = "4543246" + +export type MirabufCacheID = string + +export interface MirabufCacheInfo { + id: MirabufCacheID + cacheKey: string + miraType: MiraType + name?: string + thumbnailStorageID?: string +} + +type MiraCache = { [id: string]: MirabufCacheInfo } + +const robotsDirName = "Robots" +const fieldsDirName = "Fields" +const root = await navigator.storage.getDirectory() +const robotFolderHandle = await root.getDirectoryHandle(robotsDirName, { create: true }) +const fieldFolderHandle = await root.getDirectoryHandle(fieldsDirName, { create: true }) export function UnzipMira(buff: Uint8Array): Uint8Array { + // Check if file is gzipped via magic gzip numbers 31 139 if (buff[0] == 31 && buff[1] == 139) { return Pako.ungzip(buff) } else { @@ -10,17 +31,259 @@ export function UnzipMira(buff: Uint8Array): Uint8Array { } } -export async function LoadMirabufRemote( - fetchLocation: string, - useCache: boolean = true -): Promise { - const miraBuff = await fetch(encodeURI(fetchLocation), useCache ? undefined : { cache: "no-store" }) - .then(x => x.blob()) - .then(x => x.arrayBuffer()) - const byteBuffer = UnzipMira(new Uint8Array(miraBuff)) - return mirabuf.Assembly.decode(byteBuffer) +class MirabufCachingService { + /** + * Get the map of mirabuf keys and paired MirabufCacheInfo from local storage + * + * @param {MiraType} miraType Type of Mirabuf Assembly. + * + * @returns {MiraCache} Map of cached keys and paired MirabufCacheInfo + */ + public static GetCacheMap(miraType: MiraType): MiraCache { + if ( + (window.localStorage.getItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY) ?? "") == MIRABUF_LOCALSTORAGE_GENERATION + ) { + window.localStorage.setItem(MIRABUF_LOCALSTORAGE_GENERATION_KEY, MIRABUF_LOCALSTORAGE_GENERATION) + window.localStorage.setItem(robotsDirName, "{}") + window.localStorage.setItem(fieldsDirName, "{}") + return {} + } + + const key = miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName + const map = window.localStorage.getItem(key) + + if (map) { + return JSON.parse(map) + } else { + window.localStorage.setItem(key, "{}") + return {} + } + } + + /** + * Cache remote Mirabuf file + * + * @param {string} fetchLocation Location of Mirabuf file. + * @param {MiraType} miraType Type of Mirabuf Assembly. + * + * @returns {Promise} Promise with the result of the promise. Metadata on the mirabuf file if successful, undefined if not. + */ + public static async CacheRemote(fetchLocation: string, miraType: MiraType): Promise { + const map = MirabufCachingService.GetCacheMap(miraType) + const target = map[fetchLocation] + + if (target) { + return target + } + + // Grab file remote + const miraBuff = await fetch(encodeURI(fetchLocation), import.meta.env.DEV ? { cache: "no-store" } : undefined) + .then(x => x.blob()) + .then(x => x.arrayBuffer()) + return await MirabufCachingService.StoreInCache(fetchLocation, miraBuff, miraType) + } + + /** + * Cache local Mirabuf file + * + * @param {ArrayBuffer} buffer ArrayBuffer of Mirabuf file. + * @param {MiraType} miraType Type of Mirabuf Assembly. + * + * @returns {Promise} Promise with the result of the promise. Metadata on the mirabuf file if successful, undefined if not. + */ + public static async CacheLocal(buffer: ArrayBuffer, miraType: MiraType): Promise { + const key = await this.HashBuffer(buffer) + + const map = MirabufCachingService.GetCacheMap(miraType) + const target = map[key] + + if (target) { + return target + } + + return await MirabufCachingService.StoreInCache(key, buffer, miraType) + } + + /** + * Caches metadata (name or thumbnailStorageID) for a key + * + * @param {string} key Key to the given Mirabuf file entry in the caching service. Obtainable via GetCacheMaps(). + * @param {MiraType} miraType Type of Mirabuf Assembly. + * @param {string} name (Optional) Name of Mirabuf Assembly. + * @param {string} thumbnailStorageID (Optional) ID of the the thumbnail storage for the Mirabuf Assembly. + */ + public static async CacheInfo( + key: string, + miraType: MiraType, + name?: string, + thumbnailStorageID?: string + ): Promise { + try { + const map: MiraCache = this.GetCacheMap(miraType) + const id = map[key].id + const _name = map[key].name + const _thumbnailStorageID = map[key].thumbnailStorageID + const hi: MirabufCacheInfo = { + id: id, + cacheKey: key, + miraType: miraType, + name: name ?? _name, + thumbnailStorageID: thumbnailStorageID ?? _thumbnailStorageID, + } + map[key] = hi + window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + return true + } catch (e) { + console.error(`Failed to cache info\n${e}`) + return false + } + } + + /** + * Caches and gets local Mirabuf file + * + * @param {ArrayBuffer} buffer ArrayBuffer of Mirabuf file. + * @param {MiraType} miraType Type of Mirabuf Assembly. + * + * @returns {Promise} Promise with the result of the promise. Assembly of the mirabuf file if successful, undefined if not. + */ + public static async CacheAndGetLocal( + buffer: ArrayBuffer, + miraType: MiraType + ): Promise { + const key = await this.HashBuffer(buffer) + const map = MirabufCachingService.GetCacheMap(miraType) + const target = map[key] + const assembly = this.AssemblyFromBuffer(buffer) + + if (!target) { + await MirabufCachingService.StoreInCache(key, buffer, miraType, assembly.info?.name ?? undefined) + } + + return assembly + } + + /** + * Gets a given Mirabuf file from the cache + * + * @param {MirabufCacheID} id ID to the given Mirabuf file in the caching service. Obtainable via GetCacheMaps(). + * @param {MiraType} miraType Type of Mirabuf Assembly. + * + * @returns {Promise} Promise with the result of the promise. Assembly of the mirabuf file if successful, undefined if not. + */ + public static async Get(id: MirabufCacheID, miraType: MiraType): Promise { + try { + const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle( + id, + { + create: false, + } + ) + + // Get assembly from file + if (fileHandle) { + const buff = await fileHandle.getFile().then(x => x.arrayBuffer()) + const assembly = this.AssemblyFromBuffer(buff) + return assembly + } else { + console.error(`Failed to get file handle for ID: ${id}`) + return undefined + } + } catch (e) { + console.error(`Failed to find file from OPFS\n${e}`) + return undefined + } + } + + /** + * Removes a given Mirabuf file from the cache + * + * @param {string} key Key to the given Mirabuf file entry in the caching service. Obtainable via GetCacheMaps(). + * @param {MirabufCacheID} id ID to the given Mirabuf file in the caching service. Obtainable via GetCacheMaps(). + * @param {MiraType} miraType Type of Mirabuf Assembly. + * + * @returns {Promise} Promise with the result of the promise. True if successful, false if not. + */ + public static async Remove(key: string, id: MirabufCacheID, miraType: MiraType): Promise { + try { + window.localStorage.removeItem(key) + + const dir = miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle + await dir.removeEntry(id) + + return true + } catch (e) { + console.error(`Failed to remove\n${e}`) + return false + } + } + + /** + * Removes all Mirabuf files from the caching services. Mostly for debugging purposes. + */ + public static async RemoveAll() { + for await (const key of robotFolderHandle.keys()) { + robotFolderHandle.removeEntry(key) + } + for await (const key of fieldFolderHandle.keys()) { + fieldFolderHandle.removeEntry(key) + } + + window.localStorage.removeItem(robotsDirName) + window.localStorage.removeItem(fieldsDirName) + } + + // Optional name for when assembly is being decoded anyway like in CacheAndGetLocal() + private static async StoreInCache( + key: string, + miraBuff: ArrayBuffer, + miraType: MiraType, + name?: string + ): Promise { + // Store in OPFS + const backupID = Date.now().toString() + try { + const fileHandle = await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle( + backupID, + { create: true } + ) + const writable = await fileHandle.createWritable() + await writable.write(miraBuff) + await writable.close() + + // Local cache map + const map: MiraCache = this.GetCacheMap(miraType) + const info: MirabufCacheInfo = { + id: backupID, + cacheKey: key, + miraType: miraType, + name: name, + } + map[key] = info + window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + + return info + } catch (e) { + console.error("Failed to cache mira " + e) + return undefined + } + } + + private static async HashBuffer(buffer: ArrayBuffer): Promise { + const hashBuffer = await crypto.subtle.digest("SHA-256", buffer) + let hash = "" + new Uint8Array(hashBuffer).forEach(x => (hash = hash + String.fromCharCode(x))) + return btoa(hash).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") + } + + private static AssemblyFromBuffer(buffer: ArrayBuffer): mirabuf.Assembly { + return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(buffer))) + } +} + +export enum MiraType { + ROBOT, + FIELD, } -// export function LoadMirabufLocal(fileLocation: string): mirabuf.Assembly { -// return mirabuf.Assembly.decode(UnzipMira(new Uint8Array(fs.readFileSync(fileLocation)))) -// } +export default MirabufCachingService diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index cf9e423fc6..a9a814e85a 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -1,6 +1,6 @@ import * as THREE from "three" -import { mirabuf } from "../proto/mirabuf" -import { MirabufTransform_ThreeMatrix4 } from "../util/TypeConversions" +import { mirabuf } from "@/proto/mirabuf" +import { MirabufTransform_ThreeMatrix4 } from "@/util/TypeConversions" export enum ParseErrorSeverity { Unimportable = 10, diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 515aa8974c..287d25d39b 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -1,7 +1,6 @@ import { mirabuf } from "@/proto/mirabuf" import SceneObject from "../systems/scene/SceneObject" import MirabufInstance from "./MirabufInstance" -import { LoadMirabufRemote } from "./MirabufLoader" import MirabufParser, { ParseErrorSeverity } from "./MirabufParser" import World from "@/systems/World" import Jolt from "@barclah/jolt-physics" @@ -22,14 +21,16 @@ interface RnDebugMeshes { } class MirabufSceneObject extends SceneObject { + private _assemblyName: string private _mirabufInstance: MirabufInstance + private _mechanism: Mechanism + private _brain: SynthesisBrain | undefined + private _debugBodies: Map | null private _physicsLayerReserve: LayerReserve | undefined = undefined private transformGizmos: TransformGizmos - private _mechanism: Mechanism - get mirabufInstance() { return this._mirabufInstance } @@ -38,10 +39,11 @@ class MirabufSceneObject extends SceneObject { return this._mechanism } - public constructor(mirabufInstance: MirabufInstance) { + public constructor(mirabufInstance: MirabufInstance, assemblyName: string) { super() this._mirabufInstance = mirabufInstance + this._assemblyName = assemblyName this._mechanism = World.PhysicsSystem.CreateMechanismFromParser(this._mirabufInstance.parser) if (this._mechanism.layerReserve) { @@ -91,8 +93,8 @@ class MirabufSceneObject extends SceneObject { // Simulation World.SimulationSystem.RegisterMechanism(this._mechanism) const simLayer = World.SimulationSystem.GetSimulationLayer(this._mechanism)! - const brain = new SynthesisBrain(this._mechanism) - simLayer.SetBrain(brain) + this._brain = new SynthesisBrain(this._mechanism, this._assemblyName) + simLayer.SetBrain(this._brain) } public Update(): void { @@ -170,10 +172,8 @@ class MirabufSceneObject extends SceneObject { }) this._debugBodies?.clear() this._physicsLayerReserve?.Release() - } - public GetRootNodeId(): Jolt.BodyID | undefined { - return this._mechanism.nodeToBody.get(this._mechanism.rootBody) + this._brain?.clearControls() } private CreateMeshForShape(shape: Jolt.Shape): THREE.Mesh { @@ -210,20 +210,14 @@ class MirabufSceneObject extends SceneObject { } } -export async function CreateMirabufFromUrl(path: string): Promise { - const miraAssembly = await LoadMirabufRemote(path).catch(console.error) - - if (!miraAssembly || !(miraAssembly instanceof mirabuf.Assembly)) { - return - } - - const parser = new MirabufParser(miraAssembly) +export async function CreateMirabuf(assembly: mirabuf.Assembly): Promise { + const parser = new MirabufParser(assembly) if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { - console.error(`Assembly Parser produced significant errors for '${miraAssembly.info!.name!}'`) + console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`) return } - return new MirabufSceneObject(new MirabufInstance(parser)) + return new MirabufSceneObject(new MirabufInstance(parser), miraAssembly.info!.name!) } export default MirabufSceneObject diff --git a/fission/src/samples/JoltExample.tsx b/fission/src/samples/JoltExample.tsx deleted file mode 100644 index 13a3c3a10e..0000000000 --- a/fission/src/samples/JoltExample.tsx +++ /dev/null @@ -1,214 +0,0 @@ -/** - * This example will be used to showcase how Jolt physics works. - */ - -import * as THREE from "three" -import Stats from "stats.js" -import JOLT from "../util/loading/JoltSyncLoader.ts" -import { OrbitControls } from "three/addons/controls/OrbitControls.js" - -import { useEffect, useRef } from "react" -import Jolt from "@barclah/jolt-physics" -import { mirabuf } from "../proto/mirabuf" -import { LoadMirabufRemote } from "../mirabuf/MirabufLoader.ts" -import { JoltVec3_ThreeVector3, JoltQuat_ThreeQuaternion } from "../util/TypeConversions.ts" -import { COUNT_OBJECT_LAYERS, LAYER_MOVING, LAYER_NOT_MOVING } from "../util/threejs/MeshCreation.ts" -import MirabufInstance from "../mirabuf/MirabufInstance.ts" -import MirabufParser, { ParseErrorSeverity } from "../mirabuf/MirabufParser.ts" - -const clock = new THREE.Clock() -let time = 0 - -let stats: Stats - -let renderer: THREE.WebGLRenderer -let camera: THREE.PerspectiveCamera -let scene: THREE.Scene - -let joltInterface: Jolt.JoltInterface -// let physicsSystem: Jolt.PhysicsSystem; -// let bodyInterface: Jolt.BodyInterface; - -const dynamicObjects: THREE.Mesh[] = [] - -const MIRA_FILE = "test_mira/Team_2471_(2018)_v7.mira" -// const MIRA_FILE = "test_mira/Dozer_v2.mira" - -let controls: OrbitControls - -// vvv Below are the functions required to initialize everything and draw a basic floor with collisions. vvv - -function setupCollisionFiltering(settings: Jolt.JoltSettings) { - const objectFilter = new JOLT.ObjectLayerPairFilterTable(COUNT_OBJECT_LAYERS) - objectFilter.EnableCollision(LAYER_NOT_MOVING, LAYER_MOVING) - objectFilter.EnableCollision(LAYER_MOVING, LAYER_MOVING) - - const BP_LAYER_NOT_MOVING = new JOLT.BroadPhaseLayer(LAYER_NOT_MOVING) - const BP_LAYER_MOVING = new JOLT.BroadPhaseLayer(LAYER_MOVING) - const COUNT_BROAD_PHASE_LAYERS = 2 - - const bpInterface = new JOLT.BroadPhaseLayerInterfaceTable(COUNT_OBJECT_LAYERS, COUNT_BROAD_PHASE_LAYERS) - bpInterface.MapObjectToBroadPhaseLayer(LAYER_NOT_MOVING, BP_LAYER_NOT_MOVING) - bpInterface.MapObjectToBroadPhaseLayer(LAYER_MOVING, BP_LAYER_MOVING) - - settings.mObjectLayerPairFilter = objectFilter - settings.mBroadPhaseLayerInterface = bpInterface - settings.mObjectVsBroadPhaseLayerFilter = new JOLT.ObjectVsBroadPhaseLayerFilterTable( - settings.mBroadPhaseLayerInterface, - COUNT_BROAD_PHASE_LAYERS, - settings.mObjectLayerPairFilter, - COUNT_OBJECT_LAYERS - ) -} - -function initPhysics() { - const settings = new JOLT.JoltSettings() - setupCollisionFiltering(settings) - joltInterface = new JOLT.JoltInterface(settings) - JOLT.destroy(settings) - - // physicsSystem = joltInterface.GetPhysicsSystem(); - // bodyInterface = physicsSystem.GetBodyInterface(); -} - -function initGraphics() { - camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) - - camera.position.set(-5, 4, 5) - - scene = new THREE.Scene() - - renderer = new THREE.WebGLRenderer() - renderer.setClearColor(0x121212) - renderer.setPixelRatio(window.devicePixelRatio) - renderer.shadowMap.enabled = true - renderer.shadowMap.type = THREE.PCFSoftShadowMap - renderer.setSize(window.innerWidth, window.innerHeight) - - controls = new OrbitControls(camera, renderer.domElement) - controls.update() - - const directionalLight = new THREE.DirectionalLight(0xffffff, 3.0) - directionalLight.position.set(-1.0, 3.0, 2.0) - directionalLight.castShadow = true - scene.add(directionalLight) - - const shadowMapSize = Math.min(4096, renderer.capabilities.maxTextureSize) - const shadowCamSize = 15 - console.debug(`Shadow Map Size: ${shadowMapSize}`) - - directionalLight.shadow.camera.top = shadowCamSize - directionalLight.shadow.camera.bottom = -shadowCamSize - directionalLight.shadow.camera.left = -shadowCamSize - directionalLight.shadow.camera.right = shadowCamSize - directionalLight.shadow.mapSize = new THREE.Vector2(shadowMapSize, shadowMapSize) - directionalLight.shadow.blurSamples = 16 - directionalLight.shadow.normalBias = 0.01 - directionalLight.shadow.bias = 0.0 - - const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) - scene.add(ambientLight) - - // TODO: Add controls. - - // TODO: Add resize event -} - -function updatePhysics(deltaTime: number) { - // If below 55hz run 2 steps. Otherwise things run very slow. - const numSteps = deltaTime > 1.0 / 55.0 ? 2 : 1 - joltInterface.Step(deltaTime, numSteps) -} - -function render() { - stats.update() - requestAnimationFrame(render) - controls.update() - - // Prevents a problem when rendering at 30hz. Referred to as the spiral of death. - let deltaTime = clock.getDelta() - deltaTime = Math.min(deltaTime, 1.0 / 30.0) - - // Update transforms. - for (let i = 0, j = dynamicObjects.length; i < j; i++) { - const threeObj = dynamicObjects[i] - const body = threeObj.userData.body - threeObj.position.copy(JoltVec3_ThreeVector3(body.GetPosition())) - threeObj.quaternion.copy(JoltQuat_ThreeQuaternion(body.GetRotation())) - - if (body.GetBodyType() === JOLT.EBodyType_SoftBody) { - // TODO: Special soft body handle. - } - } - - time += deltaTime - updatePhysics(1.0 / 60.0) - // controls.update(deltaTime); // TODO: Add controls? - renderer.render(scene, camera) -} - -function MyThree() { - console.log("Running...") - - const refContainer = useRef(null) - const urlParams = new URLSearchParams(document.location.search) - let mira_path = MIRA_FILE - - urlParams.forEach((v, k) => console.debug(`${k}: ${v}`)) - - if (urlParams.has("mira")) { - mira_path = `test_mira/${urlParams.get("mira")!}` - console.debug(`Selected Mirabuf File: ${mira_path}`) - } - console.log(urlParams) - - const addMiraToScene = (assembly: mirabuf.Assembly | undefined) => { - if (!assembly) { - console.error("Assembly is undefined") - return - } - - const parser = new MirabufParser(assembly) - if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { - console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`) - return - } - - const instance = new MirabufInstance(parser) - instance.AddToScene(scene) - } - - useEffect(() => { - LoadMirabufRemote(mira_path) - .then((assembly: mirabuf.Assembly | undefined) => addMiraToScene(assembly)) - .catch(_ => - LoadMirabufRemote(MIRA_FILE).then((assembly: mirabuf.Assembly | undefined) => addMiraToScene(assembly)) - ) - .catch(console.error) - - initGraphics() - - if (refContainer.current) { - refContainer.current.innerHTML = "" - refContainer.current.appendChild(renderer.domElement) - - stats = new Stats() - stats.dom.style.position = "absolute" - stats.dom.style.top = "0px" - refContainer.current.appendChild(stats.dom) - } - - initPhysics() - render() - - // createFloor(); - }, []) - - return ( -
-
-
- ) -} - -export default MyThree diff --git a/fission/src/systems/input/DefaultInputs.ts b/fission/src/systems/input/DefaultInputs.ts new file mode 100644 index 0000000000..ef436a0b48 --- /dev/null +++ b/fission/src/systems/input/DefaultInputs.ts @@ -0,0 +1,138 @@ +import { AxisInput, Input, EmptyModifierState } from "./InputSystem" + +export type InputScheme = { + schemeName: string + usesGamepad: boolean + inputs: Input[] +} + +class DefaultInputs { + private static wasd: InputScheme = { + schemeName: "WASD", + usesGamepad: false, + inputs: [ + new AxisInput("arcadeDrive", "KeyW", "KeyS"), + new AxisInput("arcadeTurn", "KeyD", "KeyA"), + + new AxisInput("joint 1", "Digit1", "Digit1", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 2", "Digit2", "Digit2", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 3", "Digit3", "Digit3", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 4", "Digit4", "Digit4", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + new AxisInput("joint 5", "Digit5", "Digit5", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: false, + alt: false, + shift: true, + meta: false, + }), + ], + } + + private static arrowKeys: InputScheme = { + schemeName: "Arrow Keys", + usesGamepad: false, + inputs: [ + new AxisInput("arcadeDrive", "ArrowUp", "ArrowDown"), + new AxisInput("arcadeTurn", "ArrowRight", "ArrowLeft"), + + new AxisInput("joint 1", "Slash", "Slash", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: true, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 2", "Period", "Period", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: true, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 3", "Comma", "Comma", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: true, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 4", "KeyM", "KeyM", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: true, + alt: false, + shift: false, + meta: false, + }), + new AxisInput("joint 5", "KeyN", "true", -1, false, false, -1, -1, false, EmptyModifierState, { + ctrl: false, + alt: false, + shift: false, + meta: false, + }), + ], + } + + private static fullController: InputScheme = { + schemeName: "Full Controller", + usesGamepad: true, + inputs: [ + new AxisInput("arcadeDrive", "", "", 1, true), + new AxisInput("arcadeTurn", "", "", 2, false), + + new AxisInput("joint 1", "", "", -1, false, true, 3, 0), + new AxisInput("joint 2", "", "", -1, false, true, 1, 2), + new AxisInput("joint 3", "", "", -1, false, true, 4, 5), + new AxisInput("joint 3", "", "", -1, false, true, 15, 14), + new AxisInput("joint 3", "", "", -1, false, true, 12, 13), + ], + } + + private static leftStick: InputScheme = { + schemeName: "Left Stick", + usesGamepad: true, + inputs: [ + new AxisInput("arcadeDrive", "", "", 1, true), + new AxisInput("arcadeTurn", "", "", 0, false), + + new AxisInput("joint 2", "", "", -1, false, true, 15, 14), + new AxisInput("joint 1", "", "", -1, false, true, 12, 13), + ], + } + + private static rightStick: InputScheme = { + schemeName: "Right Stick", + usesGamepad: true, + inputs: [ + new AxisInput("arcadeDrive", "", "", 3, true), + new AxisInput("arcadeTurn", "", "", 2, false), + + new AxisInput("joint 1", "", "", -1, false, true, 3, 0), + new AxisInput("joint 2", "", "", -1, false, true, 1, 2), + ], + } + + public static ALL_INPUT_SCHEMES: InputScheme[] = [ + this.wasd, + this.arrowKeys, + this.fullController, + this.leftStick, + this.rightStick, + ] +} + +export default DefaultInputs diff --git a/fission/src/systems/input/InputSystem.ts b/fission/src/systems/input/InputSystem.ts index afd524ddda..56f4ccf8bb 100644 --- a/fission/src/systems/input/InputSystem.ts +++ b/fission/src/systems/input/InputSystem.ts @@ -1,86 +1,190 @@ import WorldSystem from "../WorldSystem" +import { InputScheme } from "./DefaultInputs" -declare global { - type ModifierState = { - alt: boolean - ctrl: boolean - shift: boolean - meta: boolean - } +export type ModifierState = { + alt: boolean + ctrl: boolean + shift: boolean + meta: boolean +} +export const EmptyModifierState: ModifierState = { ctrl: false, alt: false, shift: false, meta: false } - type Input = { - name: string - keyCode: string - isGlobal: boolean - modifiers: ModifierState +// Represents any input +abstract class Input { + public inputName: string + public isGlobal: boolean + + constructor(inputName: string, isGlobal: boolean) { + this.inputName = inputName + this.isGlobal = isGlobal } + + // Returns the current value of the input. Range depends on input type + abstract getValue(useGamepad: boolean): number + + // Creates a copy to avoid modifying the default inputs by reference + abstract getCopy(): Input } -export const emptyModifierState: ModifierState = { ctrl: false, alt: false, shift: false, meta: false } - -// When a robot is loaded, default inputs replace any unassigned inputs -const defaultInputs: { [key: string]: Input } = { - intake: { name: "intake", keyCode: "KeyE", isGlobal: true, modifiers: emptyModifierState }, - shootGamepiece: { name: "shootGamepiece", keyCode: "KeyQ", isGlobal: true, modifiers: emptyModifierState }, - enableGodMode: { name: "enableGodMode", keyCode: "KeyG", isGlobal: true, modifiers: emptyModifierState }, - - arcadeForward: { name: "arcadeForward", keyCode: "KeyW", isGlobal: false, modifiers: emptyModifierState }, - arcadeBackward: { name: "arcadeBackward", keyCode: "KeyS", isGlobal: false, modifiers: emptyModifierState }, - arcadeLeft: { name: "arcadeLeft", keyCode: "KeyA", isGlobal: false, modifiers: emptyModifierState }, - arcadeRight: { name: "arcadeRight", keyCode: "KeyD", isGlobal: false, modifiers: emptyModifierState }, - - shift: { - name: "shift", - keyCode: "ShiftLeft", - isGlobal: true, - modifiers: { ctrl: false, alt: false, shift: true, meta: false }, - }, - escape: { name: "escape", keyCode: "Escape", isGlobal: true, modifiers: emptyModifierState }, - enter: { name: "enter", keyCode: "Enter", isGlobal: true, modifiers: emptyModifierState }, +// A single button +class ButtonInput extends Input { + public keyCode: string + public keyModifiers: ModifierState + + public gamepadButton: number + + public constructor( + inputName: string, + keyCode?: string, + gamepadButton?: number, + isGlobal?: boolean, + keyModifiers?: ModifierState + ) { + super(inputName, isGlobal ?? false) + this.keyCode = keyCode ?? "" + this.keyModifiers = keyModifiers ?? EmptyModifierState + this.gamepadButton = gamepadButton ?? -1 + } + + // Returns 1 if pressed and 0 if not pressed + getValue(useGamepad: boolean): number { + // Gamepad button input + if (useGamepad) { + return InputSystem.isGamepadButtonPressed(this.gamepadButton) ? 1 : 0 + } + + // Keyboard button input + return InputSystem.isKeyPressed(this.keyCode, this.keyModifiers) ? 1 : 0 + } + + getCopy(): Input { + return new ButtonInput(this.inputName, this.keyCode, this.gamepadButton, this.isGlobal, this.keyModifiers) + } } -class InputSystem extends WorldSystem { - public static allInputs: { [key: string]: Input } = {} - private static _currentModifierState: ModifierState +// An axis between two buttons (-1 to 1) +class AxisInput extends Input { + public posKeyCode: string + public posKeyModifiers: ModifierState + public negKeyCode: string + public negKeyModifiers: ModifierState + + public gamepadAxisNumber: number + public joystickInverted: boolean + public useGamepadButtons: boolean + public posGamepadButton: number + public negGamepadButton: number - // Inputs global to all of synthesis like camera controls - public static get globalInputs(): { [key: string]: Input } { - return Object.fromEntries(Object.entries(InputSystem.allInputs).filter(([_, input]) => input.isGlobal)) + public constructor( + inputName: string, + posKeyCode?: string, + negKeyCode?: string, + gamepadAxisNumber?: number, + joystickInverted?: boolean, + useGamepadButtons?: boolean, + posGamepadButton?: number, + negGamepadButton?: number, + isGlobal?: boolean, + posKeyModifiers?: ModifierState, + negKeyModifiers?: ModifierState + ) { + super(inputName, isGlobal ?? false) + + this.posKeyCode = posKeyCode ?? "" + this.posKeyModifiers = posKeyModifiers ?? EmptyModifierState + this.negKeyCode = negKeyCode ?? "" + this.negKeyModifiers = negKeyModifiers ?? EmptyModifierState + + this.gamepadAxisNumber = gamepadAxisNumber ?? -1 + this.joystickInverted = joystickInverted ?? false + + this.useGamepadButtons = useGamepadButtons ?? false + this.posGamepadButton = posGamepadButton ?? -1 + this.negGamepadButton = negGamepadButton ?? -1 } - // Robot specific controls like driving - public static get robotInputs(): { [key: string]: Input } { - return Object.fromEntries(Object.entries(InputSystem.allInputs).filter(([_, input]) => !input.isGlobal)) + // For keyboard: returns 1 if positive pressed, -1 if negative pressed, or 0 if none or both are pressed + // For gamepad axis: returns a range between -1 and 1 with a deadband in the middle + getValue(useGamepad: boolean): number { + // Gamepad joystick axis + if (useGamepad) { + if (!this.useGamepadButtons) + return InputSystem.getGamepadAxis(this.gamepadAxisNumber) * (this.joystickInverted ? -1 : 1) + + // Gamepad button axis + return ( + (InputSystem.isGamepadButtonPressed(this.posGamepadButton) ? 1 : 0) - + (InputSystem.isGamepadButtonPressed(this.negGamepadButton) ? 1 : 0) + ) + } + + // Keyboard button axis + return ( + (InputSystem.isKeyPressed(this.posKeyCode, this.posKeyModifiers) ? 1 : 0) - + (InputSystem.isKeyPressed(this.negKeyCode, this.negKeyModifiers) ? 1 : 0) + ) } + getCopy(): Input { + return new AxisInput( + this.inputName, + this.posKeyCode, + this.negKeyCode, + this.gamepadAxisNumber, + this.joystickInverted, + this.useGamepadButtons, + this.posGamepadButton, + this.negGamepadButton, + this.isGlobal, + this.posKeyModifiers, + this.negKeyModifiers + ) + } +} + +class InputSystem extends WorldSystem { + public static allInputs: Map = new Map() + + public static currentModifierState: ModifierState + // A list of keys currently being pressed private static _keysPressed: { [key: string]: boolean } = {} + private static _gpIndex: number | null + public static gamepad: Gamepad | null + + // The scheme most recently selected in the controls modal + public static selectedScheme: InputScheme | undefined + constructor() { super() - this.HandleKeyDown = this.HandleKeyDown.bind(this) - document.addEventListener("keydown", this.HandleKeyDown) + this.handleKeyDown = this.handleKeyDown.bind(this) + document.addEventListener("keydown", this.handleKeyDown) - this.HandleKeyUp = this.HandleKeyUp.bind(this) - document.addEventListener("keyup", this.HandleKeyUp) + this.handleKeyUp = this.handleKeyUp.bind(this) + document.addEventListener("keyup", this.handleKeyUp) - // TODO: Load saved inputs from mira (robot specific) & global inputs + this.gamepadConnected = this.gamepadConnected.bind(this) + window.addEventListener("gamepadconnected", this.gamepadConnected) - for (const key in defaultInputs) { - if (Object.prototype.hasOwnProperty.call(defaultInputs, key)) { - InputSystem.allInputs[key] = defaultInputs[key] - } - } + this.gamepadDisconnected = this.gamepadDisconnected.bind(this) + window.addEventListener("gamepaddisconnected", this.gamepadDisconnected) + + document.addEventListener("visibilitychange", () => { + if (document.hidden) this.clearKeyData() + }) } public Update(_: number): void { - if (!document.hasFocus()) { - for (const keyCode in InputSystem._keysPressed) delete InputSystem._keysPressed[keyCode] - return - } + InputSystem + // Fetch current gamepad information + if (InputSystem._gpIndex == null) InputSystem.gamepad = null + else InputSystem.gamepad = navigator.getGamepads()[InputSystem._gpIndex] + + if (!document.hasFocus()) this.clearKeyData() - InputSystem._currentModifierState = { + InputSystem.currentModifierState = { ctrl: InputSystem.isKeyPressed("ControlLeft") || InputSystem.isKeyPressed("ControlRight"), alt: InputSystem.isKeyPressed("AltLeft") || InputSystem.isKeyPressed("AltRight"), shift: InputSystem.isKeyPressed("ShiftLeft") || InputSystem.isKeyPressed("ShiftRight"), @@ -89,48 +193,67 @@ class InputSystem extends WorldSystem { } public Destroy(): void { - document.removeEventListener("keydown", this.HandleKeyDown) - document.removeEventListener("keyup", this.HandleKeyUp) + document.removeEventListener("keydown", this.handleKeyDown) + document.removeEventListener("keyup", this.handleKeyUp) + window.removeEventListener("gamepadconnected", this.gamepadConnected) + window.removeEventListener("gamepaddisconnected", this.gamepadDisconnected) } - // Called when any key is pressed - private HandleKeyDown(event: KeyboardEvent) { + // Called when any key is first pressed + private handleKeyDown(event: KeyboardEvent) { InputSystem._keysPressed[event.code] = true } // Called when any key is released - private HandleKeyUp(event: KeyboardEvent) { + private handleKeyUp(event: KeyboardEvent) { InputSystem._keysPressed[event.code] = false } - // Returns true if the given key is currently down - private static isKeyPressed(key: string): boolean { - return !!InputSystem._keysPressed[key] + private clearKeyData() { + for (const keyCode in InputSystem._keysPressed) delete InputSystem._keysPressed[keyCode] } - // If an input exists, return true if it is pressed - public static getInput(inputName: string): boolean { - // Checks if there is an input assigned to this action - if (inputName in this.allInputs) { - const targetInput = this.allInputs[inputName] + // Called once when a gamepad is first connected + private gamepadConnected(event: GamepadEvent) { + console.log( + "Gamepad connected at index %d: %s. %d buttons, %d axes.", + event.gamepad.index, + event.gamepad.id, + event.gamepad.buttons.length, + event.gamepad.axes.length + ) - // Check for input modifiers - if (!this.CompareModifiers(InputSystem._currentModifierState, targetInput.modifiers)) return false + InputSystem._gpIndex = event.gamepad.index + } - return this.isKeyPressed(targetInput.keyCode) - } + // Called once when a gamepad is first disconnected + private gamepadDisconnected(event: GamepadEvent) { + console.log("Gamepad disconnected from index %d: %s", event.gamepad.index, event.gamepad.id) + + InputSystem._gpIndex = null + } + + // Returns true if the given key is currently down + public static isKeyPressed(key: string, modifiers?: ModifierState): boolean { + if (modifiers != null && !InputSystem.compareModifiers(InputSystem.currentModifierState, modifiers)) + return false - // If the input does not exist, returns false - return false + return !!InputSystem._keysPressed[key] } - // Combines two inputs into a positive/negative axis - public static GetAxis(positive: string, negative: string) { - return (this.getInput(positive) ? 1 : 0) - (this.getInput(negative) ? 1 : 0) + // If an input exists, return true if it is pressed + public static getInput(inputName: string, assemblyId: string): number { + // Looks for an input assigned to this action + const targetScheme = this.allInputs.get(assemblyId) + const targetInput = targetScheme?.inputs.find(input => input.inputName == inputName) + + if (targetScheme == null || targetInput == null) return 0 + + return targetInput.getValue(targetScheme.usesGamepad) } // Returns true if two modifier states are identical - private static CompareModifiers(state1: ModifierState, state2: ModifierState): boolean { + private static compareModifiers(state1: ModifierState, state2: ModifierState): boolean { if (!state1 || !state2) return false return ( @@ -140,6 +263,33 @@ class InputSystem extends WorldSystem { state1.shift == state2.shift ) } + + // Returns a number between -1 and 1 with a deadband + public static getGamepadAxis(axisNumber: number): number { + if (InputSystem.gamepad == null) return 0 + + if (axisNumber < 0 || axisNumber >= InputSystem.gamepad.axes.length) return 0 + + const value = InputSystem.gamepad.axes[axisNumber] + + // Return value with a deadband + return Math.abs(value) < 0.15 ? 0 : value + } + + // Returns true if a gamepad is connected and a certain button is pressed + public static isGamepadButtonPressed(buttonNumber: number): boolean { + if (InputSystem.gamepad == null) return false + + if (buttonNumber < 0 || buttonNumber >= InputSystem.gamepad.buttons.length) return false + + const button = InputSystem.gamepad.buttons[buttonNumber] + if (button == null) return false + + return button.pressed + } } export default InputSystem +export { Input } +export { ButtonInput } +export { AxisInput } diff --git a/fission/src/systems/physics/Mechanism.ts b/fission/src/systems/physics/Mechanism.ts index 0761203f4c..79ac14e7e8 100644 --- a/fission/src/systems/physics/Mechanism.ts +++ b/fission/src/systems/physics/Mechanism.ts @@ -13,12 +13,19 @@ class Mechanism { public constraints: Array public stepListeners: Array public layerReserve: LayerReserve | undefined + public controllable: boolean - public constructor(rootBody: string, bodyMap: Map, layerReserve?: LayerReserve) { + public constructor( + rootBody: string, + bodyMap: Map, + controllable: boolean, + layerReserve?: LayerReserve + ) { this.rootBody = rootBody this.nodeToBody = bodyMap this.constraints = [] this.stepListeners = [] + this.controllable = controllable this.layerReserve = layerReserve } diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 0ac2741f1d..a7e57e87e0 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -50,7 +50,7 @@ const SUSPENSION_MIN_FACTOR = 0.1 const SUSPENSION_MAX_FACTOR = 0.3 /** - * The PhysicsSystem handles all Jolt Phyiscs interactions within Synthesis. + * The PhysicsSystem handles all Jolt Physics interactions within Synthesis. * This system can create physical representations of objects such as Robots, * Fields, and Game pieces, and simulate them. */ @@ -117,7 +117,7 @@ class PhysicsSystem extends WorldSystem { * * @param halfExtents The half extents of the Box. * @param mass Mass of the Box. Leave undefined to make Box static. - * @param position Posiition of the Box (default: 0, 0, 0) + * @param position Position of the Box (default: 0, 0, 0) * @param rotation Rotation of the Box (default 0, 0, 0, 1) * @returns Reference to Jolt Body */ @@ -211,11 +211,9 @@ class PhysicsSystem extends WorldSystem { public CreateMechanismFromParser(parser: MirabufParser): Mechanism { const layer = parser.assembly.dynamic ? new LayerReserve() : undefined - // const layer = undefined; - console.log(`Using layer ${layer?.layer}`) const bodyMap = this.CreateBodiesFromParser(parser, layer) const rootBody = parser.rootNode - const mechanism = new Mechanism(rootBody, bodyMap, layer) + const mechanism = new Mechanism(rootBody, bodyMap, parser.assembly.dynamic, layer) this.CreateJointsFromParser(parser, mechanism) return mechanism } @@ -224,7 +222,7 @@ class PhysicsSystem extends WorldSystem { * Creates all the joints for a mirabuf assembly given an already compiled mapping of rigid nodes to bodies. * * @param parser Mirabuf parser with complete set of rigid nodes and assembly data. - * @param mechainsm Mapping of the name of rigid groups to Jolt bodies. Retrieved from CreateBodiesFromParser. + * @param mechanism Mapping of the name of rigid groups to Jolt bodies. Retrieved from CreateBodiesFromParser. */ public CreateJointsFromParser(parser: MirabufParser, mechanism: Mechanism) { const jointData = parser.assembly.data!.joints! @@ -322,7 +320,7 @@ class PhysicsSystem extends WorldSystem { * @param jointDefinition Joint definition. * @param bodyA Parent body to connect. * @param bodyB Child body to connect. - * @param versionNum Version number of the export. Used for compatability purposes. + * @param versionNum Version number of the export. Used for compatibility purposes. * @returns Resulting Jolt Hinge Constraint. */ private CreateHingeConstraint( @@ -361,7 +359,7 @@ class PhysicsSystem extends WorldSystem { hingeConstraintSettings.mHingeAxis1 ) - // Some values that are meant to be exactly PI are perceived as being past it, causing unexpected beavior. + // Some values that are meant to be exactly PI are perceived as being past it, causing unexpected behavior. // This safety check caps the values to be within [-PI, PI] wth minimal difference in precision. const piSafetyCheck = (v: number) => Math.min(3.14158, Math.max(-3.14158, v)) @@ -728,7 +726,6 @@ class PhysicsSystem extends WorldSystem { this._joltBodyInterface.RemoveBody(x) // this._joltBodyInterface.DestroyBody(x); }) - console.log("Mechanism destroyed") } public GetBody(bodyId: Jolt.BodyID) { @@ -744,8 +741,6 @@ class PhysicsSystem extends WorldSystem { let substeps = Math.max(1, Math.floor((lastDeltaT / STANDARD_SIMULATION_PERIOD) * STANDARD_SUB_STEPS)) substeps = Math.min(MAX_SUBSTEPS, Math.max(MIN_SUBSTEPS, substeps)) - // console.log(`DeltaT: ${lastDeltaT.toFixed(5)}, Substeps: ${substeps}`) - this._joltInterface.Step(lastDeltaT, substeps) } diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 9c474d8ad0..cf728cbcbd 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -236,7 +236,10 @@ class SceneRenderer extends WorldSystem { return } }) - } else if (event.target.mode === "scale" && InputSystem.getInput("shift")) { + } else if ( + event.target.mode === "scale" && + (InputSystem.isKeyPressed("ShiftRight") || InputSystem.isKeyPressed("ShiftLeft")) + ) { // scale uniformly if shift is pressed transformControl.axis = "XYZE" } else if (event.target.mode === "rotate") { diff --git a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts index c9bb881a85..3f2a96b0d1 100644 --- a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts +++ b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts @@ -4,8 +4,9 @@ import Behavior from "./Behavior" import InputSystem from "@/systems/input/InputSystem" class ArcadeDriveBehavior extends Behavior { - leftWheels: WheelDriver[] - rightWheels: WheelDriver[] + private leftWheels: WheelDriver[] + private rightWheels: WheelDriver[] + private _assemblyName: string private _driveSpeed = 30 private _turnSpeed = 30 @@ -14,12 +15,14 @@ class ArcadeDriveBehavior extends Behavior { leftWheels: WheelDriver[], rightWheels: WheelDriver[], leftStimuli: WheelRotationStimulus[], - rightStimuli: WheelRotationStimulus[] + rightStimuli: WheelRotationStimulus[], + assemblyName: string ) { super(leftWheels.concat(rightWheels), leftStimuli.concat(rightStimuli)) this.leftWheels = leftWheels this.rightWheels = rightWheels + this._assemblyName = assemblyName } // Sets the drivetrains target linear and rotational velocity @@ -32,10 +35,10 @@ class ArcadeDriveBehavior extends Behavior { } public Update(_: number): void { - this.DriveSpeeds( - InputSystem.GetAxis("arcadeForward", "arcadeBackward") * this._driveSpeed, - InputSystem.GetAxis("arcadeRight", "arcadeLeft") * this._turnSpeed - ) + const driveInput = InputSystem.getInput("arcadeDrive", this._assemblyName) + const turnInput = InputSystem.getInput("arcadeTurn", this._assemblyName) + + this.DriveSpeeds(driveInput * this._driveSpeed, turnInput * this._turnSpeed) } } diff --git a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts index 890dfb4325..db4d038ad8 100644 --- a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts @@ -1,36 +1,21 @@ import HingeDriver from "../driver/HingeDriver" import HingeStimulus from "../stimulus/HingeStimulus" import Behavior from "./Behavior" -import InputSystem, { emptyModifierState } from "@/systems/input/InputSystem" +import InputSystem from "@/systems/input/InputSystem" class GenericArmBehavior extends Behavior { private _hingeDriver: HingeDriver - - private _positiveInput: string - private _negativeInput: string + private _inputName: string + private _assemblyName: string private _rotationalSpeed = 6 - constructor(hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, jointIndex: number) { + constructor(hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, jointIndex: number, assemblyName: string) { super([hingeDriver], [hingeStimulus]) - this._hingeDriver = hingeDriver - this._positiveInput = "joint " + jointIndex + " Positive" - this._negativeInput = "joint " + jointIndex + " Negative" - - // TODO: load inputs from mira - InputSystem.allInputs[this._positiveInput] = { - name: this._positiveInput, - keyCode: "Digit" + jointIndex.toString(), - isGlobal: false, - modifiers: emptyModifierState, - } - InputSystem.allInputs[this._negativeInput] = { - name: this._negativeInput, - keyCode: "Digit" + jointIndex.toString(), - isGlobal: false, - modifiers: { ctrl: false, alt: false, shift: true, meta: false }, - } + this._hingeDriver = hingeDriver + this._inputName = "joint " + jointIndex + this._assemblyName = assemblyName } // Sets the arms target rotational velocity @@ -39,7 +24,7 @@ class GenericArmBehavior extends Behavior { } public Update(_: number): void { - this.rotateArm(InputSystem.GetAxis(this._positiveInput, this._negativeInput) * this._rotationalSpeed) + this.rotateArm(InputSystem.getInput(this._inputName, this._assemblyName) * this._rotationalSpeed) } } diff --git a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts index e31282a28c..e3165c40a8 100644 --- a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts @@ -1,36 +1,21 @@ import SliderDriver from "../driver/SliderDriver" import SliderStimulus from "../stimulus/SliderStimulus" import Behavior from "./Behavior" -import InputSystem, { emptyModifierState } from "@/systems/input/InputSystem" +import InputSystem from "@/systems/input/InputSystem" class GenericElevatorBehavior extends Behavior { private _sliderDriver: SliderDriver - - private _positiveInput: string - private _negativeInput: string + private _inputName: string + private _assemblyName: string private _linearSpeed = 2.5 - constructor(sliderDriver: SliderDriver, sliderStimulus: SliderStimulus, jointIndex: number) { + constructor(sliderDriver: SliderDriver, sliderStimulus: SliderStimulus, jointIndex: number, assemblyName: string) { super([sliderDriver], [sliderStimulus]) - this._sliderDriver = sliderDriver - this._positiveInput = "joint " + jointIndex + " Positive" - this._negativeInput = "joint " + jointIndex + " Negative" - - // TODO: load inputs from mira - InputSystem.allInputs[this._positiveInput] = { - name: this._positiveInput, - keyCode: "Digit" + jointIndex.toString(), - isGlobal: false, - modifiers: emptyModifierState, - } - InputSystem.allInputs[this._negativeInput] = { - name: this._negativeInput, - keyCode: "Digit" + jointIndex.toString(), - isGlobal: false, - modifiers: { ctrl: false, alt: false, shift: true, meta: false }, - } + this._sliderDriver = sliderDriver + this._inputName = "joint " + jointIndex + this._assemblyName = assemblyName } // Changes the elevators target position @@ -39,7 +24,7 @@ class GenericElevatorBehavior extends Behavior { } public Update(_: number): void { - this.moveElevator(InputSystem.GetAxis(this._positiveInput, this._negativeInput) * this._linearSpeed) + this.moveElevator(InputSystem.getInput(this._inputName, this._assemblyName) * this._linearSpeed) } } diff --git a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts index cbce7c4b61..384e566a1c 100644 --- a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts +++ b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts @@ -14,29 +14,41 @@ import GenericArmBehavior from "../behavior/GenericArmBehavior" import SliderDriver from "../driver/SliderDriver" import SliderStimulus from "../stimulus/SliderStimulus" import GenericElevatorBehavior from "../behavior/GenericElevatorBehavior" +import InputSystem, { AxisInput } from "@/systems/input/InputSystem" +import DefaultInputs from "@/systems/input/DefaultInputs" class SynthesisBrain extends Brain { private _behaviors: Behavior[] = [] private _simLayer: SimulationLayer - _leftWheelIndices: number[] = [] - // Tracks how many joins have been made for unique controls - _currentJointIndex = 1 + private _currentJointIndex = 1 + + private static _currentRobotIndex = 0 + + // Tracks how many robots are spawned for control identification + private _assemblyName: string - public constructor(mechanism: Mechanism) { + public constructor(mechanism: Mechanism, assemblyName: string) { super(mechanism) this._simLayer = World.SimulationSystem.GetSimulationLayer(mechanism)! + this._assemblyName = "[" + SynthesisBrain._currentRobotIndex.toString() + "] " + assemblyName if (!this._simLayer) { console.log("SimulationLayer is undefined") return } - this.ConfigureArcadeDriveBehavior() - this.ConfigureArmBehaviors() - this.ConfigureElevatorBehaviors() + // Only adds controls to mechanisms that are controllable (ignores fields) + if (mechanism.controllable) { + this.configureArcadeDriveBehavior() + this.configureArmBehaviors() + this.configureElevatorBehaviors() + this.configureInputs() + + SynthesisBrain._currentRobotIndex++ + } } public Enable(): void {} @@ -49,8 +61,12 @@ class SynthesisBrain extends Brain { this._behaviors = [] } + public clearControls(): void { + InputSystem.allInputs.delete(this._assemblyName) + } + // Creates an instance of ArcadeDriveBehavior and automatically configures it - public ConfigureArcadeDriveBehavior() { + public configureArcadeDriveBehavior() { const wheelDrivers: WheelDriver[] = this._simLayer.drivers.filter( driver => driver instanceof WheelDriver ) as WheelDriver[] @@ -89,11 +105,13 @@ class SynthesisBrain extends Brain { } } - this._behaviors.push(new ArcadeDriveBehavior(leftWheels, rightWheels, leftStimuli, rightStimuli)) + this._behaviors.push( + new ArcadeDriveBehavior(leftWheels, rightWheels, leftStimuli, rightStimuli, this._assemblyName) + ) } // Creates instances of ArmBehavior and automatically configures them - public ConfigureArmBehaviors() { + public configureArmBehaviors() { const hingeDrivers: HingeDriver[] = this._simLayer.drivers.filter( driver => driver instanceof HingeDriver ) as HingeDriver[] @@ -102,13 +120,15 @@ class SynthesisBrain extends Brain { ) as HingeStimulus[] for (let i = 0; i < hingeDrivers.length; i++) { - this._behaviors.push(new GenericArmBehavior(hingeDrivers[i], hingeStimuli[i], this._currentJointIndex)) + this._behaviors.push( + new GenericArmBehavior(hingeDrivers[i], hingeStimuli[i], this._currentJointIndex, this._assemblyName) + ) this._currentJointIndex++ } } // Creates instances of ElevatorBehavior and automatically configures them - public ConfigureElevatorBehaviors() { + public configureElevatorBehaviors() { const sliderDrivers: SliderDriver[] = this._simLayer.drivers.filter( driver => driver instanceof SliderDriver ) as SliderDriver[] @@ -118,11 +138,44 @@ class SynthesisBrain extends Brain { for (let i = 0; i < sliderDrivers.length; i++) { this._behaviors.push( - new GenericElevatorBehavior(sliderDrivers[i], sliderStimuli[i], this._currentJointIndex) + new GenericElevatorBehavior( + sliderDrivers[i], + sliderStimuli[i], + this._currentJointIndex, + this._assemblyName + ) ) this._currentJointIndex++ } } + + public configureInputs() { + const scheme = DefaultInputs.ALL_INPUT_SCHEMES[SynthesisBrain._currentRobotIndex] + + InputSystem.allInputs.set(this._assemblyName, { + schemeName: this._assemblyName, + usesGamepad: scheme?.usesGamepad ?? false, + inputs: [], + }) + const inputList = InputSystem.allInputs.get(this._assemblyName)!.inputs + + if (scheme) { + const arcadeDrive = scheme.inputs.find(i => i.inputName === "arcadeDrive") + if (arcadeDrive) inputList.push(arcadeDrive.getCopy()) + else inputList.push(new AxisInput("arcadeDrive")) + + const arcadeTurn = scheme.inputs.find(i => i.inputName === "arcadeTurn") + if (arcadeTurn) inputList.push(arcadeTurn.getCopy()) + else inputList.push(new AxisInput("arcadeTurn")) + + for (let i = 1; i < this._currentJointIndex; i++) { + const controlPreset = scheme.inputs.find(input => input.inputName == "joint " + i) + + if (controlPreset) inputList.push(controlPreset.getCopy()) + else inputList.push(new AxisInput("joint " + i)) + } + } + } } export default SynthesisBrain diff --git a/fission/src/test/MirabufParser.test.ts b/fission/src/test/MirabufParser.test.ts index 7eb1ce1746..e96f35cff3 100644 --- a/fission/src/test/MirabufParser.test.ts +++ b/fission/src/test/MirabufParser.test.ts @@ -1,13 +1,14 @@ import { describe, test, expect } from "vitest" - import { mirabuf } from "../proto/mirabuf" import MirabufParser, { RigidNodeReadOnly } from "../mirabuf/MirabufParser" -import { LoadMirabufRemote } from "@/mirabuf/MirabufLoader" -// import { LoadMirabufLocal } from "../mirabuf/MirabufLoader" +import MirabufCachingService, { MiraType } from "../mirabuf/MirabufLoader" describe("Mirabuf Parser Tests", () => { test("Generate Rigid Nodes (Dozer_v9.mira)", async () => { - const spikeMira = await LoadMirabufRemote("/api/mira/Robots/Dozer_v9.mira") + const spikeMira = await MirabufCachingService.CacheRemote( + "/api/mira/Robots/Dozer_v9.mira", + MiraType.ROBOT + ).then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const t = new MirabufParser(spikeMira!) const rn = t.rigidNodes @@ -15,17 +16,21 @@ describe("Mirabuf Parser Tests", () => { expect(filterNonPhysicsNodes(rn, spikeMira!).length).toBe(7) }) - test("Generate Rigid Nodes (FRC_Field_2018_v14.mira)", async () => { - const field = await LoadMirabufRemote("/api/mira/Fields/FRC Field 2018_v13.mira") - + test("Generate Rigid Nodes (FRC Field 2018_v13.mira)", async () => { + const field = await MirabufCachingService.CacheRemote( + "/api/mira/Fields/FRC Field 2018_v13.mira", + MiraType.FIELD + ).then(x => MirabufCachingService.Get(x!.id, MiraType.FIELD)) const t = new MirabufParser(field!) expect(filterNonPhysicsNodes(t.rigidNodes, field!).length).toBe(34) }) - test("Generate Rigid Nodes (Team_2471_(2018)_v7.mira)", async () => { - const mm = await LoadMirabufRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira") - + test("Generate Rigid Nodes (Team 2471 (2018)_v7.mira)", async () => { + const mm = await MirabufCachingService.CacheRemote( + "/api/mira/Robots/Team 2471 (2018)_v7.mira", + MiraType.ROBOT + ).then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) const t = new MirabufParser(mm!) expect(filterNonPhysicsNodes(t.rigidNodes, mm!).length).toBe(10) diff --git a/fission/src/test/PhysicsSystem.test.ts b/fission/src/test/PhysicsSystem.test.ts index 55687fc96f..f60447705c 100644 --- a/fission/src/test/PhysicsSystem.test.ts +++ b/fission/src/test/PhysicsSystem.test.ts @@ -1,10 +1,9 @@ import { test, expect, describe, assert } from "vitest" import PhysicsSystem, { LayerReserve } from "../systems/physics/PhysicsSystem" -// import { LoadMirabufLocal } from "@/mirabuf/MirabufLoader" import MirabufParser from "@/mirabuf/MirabufParser" import * as THREE from "three" import Jolt from "@barclah/jolt-physics" -import { LoadMirabufRemote } from "@/mirabuf/MirabufLoader" +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" describe("Physics Sansity Checks", () => { test("Convex Hull Shape (Cube)", () => { @@ -78,7 +77,9 @@ describe("GodMode", () => { describe("Mirabuf Physics Loading", () => { test("Body Loading (Dozer)", async () => { - const assembly = await LoadMirabufRemote("/api/mira/Robots/Dozer_v9.mira") + const assembly = await MirabufCachingService.CacheRemote("/api/mira/Robots/Dozer_v9.mira", MiraType.ROBOT).then( + x => MirabufCachingService.Get(x!.id, MiraType.ROBOT) + ) const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() const mapping = physSystem.CreateBodiesFromParser(parser, new LayerReserve()) @@ -87,7 +88,11 @@ describe("Mirabuf Physics Loading", () => { }) test("Body Loading (Team_2471_(2018)_v7.mira)", async () => { - const assembly = await LoadMirabufRemote("/api/mira/Robots/Team 2471 (2018)_v7.mira") + const assembly = await MirabufCachingService.CacheRemote( + "/api/mira/Robots/Team 2471 (2018)_v7.mira", + MiraType.ROBOT + ).then(x => MirabufCachingService.Get(x!.id, MiraType.ROBOT)) + const parser = new MirabufParser(assembly!) const physSystem = new PhysicsSystem() const mapping = physSystem.CreateBodiesFromParser(parser, new LayerReserve()) diff --git a/fission/src/ui/components/Checkbox.tsx b/fission/src/ui/components/Checkbox.tsx index 59acc610a4..ace5b67f29 100644 --- a/fission/src/ui/components/Checkbox.tsx +++ b/fission/src/ui/components/Checkbox.tsx @@ -1,4 +1,4 @@ -import React from "react" +import React, { useState } from "react" import Stack, { StackDirection } from "./Stack" import Label, { LabelSize } from "./Label" import { Switch } from "@mui/base/Switch" @@ -11,7 +11,8 @@ type CheckboxProps = { onClick?: (checked: boolean) => void } -const Checkbox: React.FC = ({ label, className, onClick }) => { +const Checkbox: React.FC = ({ label, className, defaultState, stateOverride, onClick }) => { + const [state] = useState(defaultState) return ( ) diff --git a/fission/src/ui/components/Dropdown.tsx b/fission/src/ui/components/Dropdown.tsx index 05b684d4a0..742172a93a 100644 --- a/fission/src/ui/components/Dropdown.tsx +++ b/fission/src/ui/components/Dropdown.tsx @@ -30,8 +30,36 @@ type DropdownProps = { onSelect: (opt: string) => void } -const Dropdown: React.FC = ({ label, options, onSelect }) => { - const [optionList, _setOptionList] = useState(options) +const Dropdown: React.FC = ({ label, className, options, onSelect }) => { + const [expanded, setExpanded] = useState(false) + const [optionList, setOptionList] = useState(options) + + type DropdownOptionProps = { + value: string + children?: ReactNode + className?: string + } + + const DropdownOption: React.FC = ({ children, value, className }) => ( + { + const newOptions = options.filter(item => item !== value) + newOptions.unshift(value) + setOptionList(newOptions) + if (onSelect) onSelect(value) + }} + className={`block relative duration-100 hover:backdrop-brightness-90 w-full h-full px-2 py-2 ${className}`} + style={{ + maxWidth: "249px", + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + paddingRight: "25px", + }} + > + {children} + + ) return ( <> diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index dba81fada7..3bdbb86518 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -18,6 +18,7 @@ import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import { Button } from "@mui/base/Button" +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" import Jolt from "@barclah/jolt-physics" type ButtonProps = { @@ -139,6 +140,20 @@ const MainHUD: React.FC = () => { icon={} onClick={() => openPanel("driver-station")} /> + {/* MiraMap and OPFS Temp Buttons */} + } + onClick={() => { + console.log(MirabufCachingService.GetCacheMap(MiraType.ROBOT)) + console.log(MirabufCachingService.GetCacheMap(MiraType.FIELD)) + }} + /> + } + onClick={() => MirabufCachingService.RemoveAll()} + /> } onClick={() => openModal("drivetrain")} /> { +// TODO: assumes all inputs are keyboard buttons +const transformKeyName = (keyCode: string, keyModifiers: ModifierState) => { let prefix = "" - if (control.modifiers) { - if (control.modifiers.meta) prefix += "Meta + " - if (control.modifiers.shift) prefix += "Shift + " - if (control.modifiers.ctrl) prefix += "Ctrl + " - if (control.modifiers.alt) prefix += "Alt + " + if (keyModifiers) { + if (keyModifiers.meta) prefix += "Meta + " + if (keyModifiers.shift) prefix += "Shift + " + if (keyModifiers.ctrl) prefix += "Ctrl + " + if (keyModifiers.alt) prefix += "Alt + " } - return prefix + keyCodeToCharacter(control.keyCode) + const displayName = prefix + keyCodeToCharacter(keyCode) + if (displayName == "") return "N/A" + + return displayName } // Converts camelCase to Title Case for the inputs modal @@ -26,6 +35,7 @@ const toTitleCase = (camelCase: string) => { return finalResult } +// Special characters only const codeToCharacterMap: { [code: string]: string } = { Slash: "/", Comma: ",", @@ -35,11 +45,31 @@ const codeToCharacterMap: { [code: string]: string } = { BackQuote: "`", Minus: "-", Equal: "=", - Backslash: "\\", //TODO + Backslash: "\\", Semicolon: ";", Quote: '"', } +const gamepadButtons: string[] = [ + "A", + "B", + "X", + "Y", + "Left Bumper", + "Right Bumper", + "Back", + "Start", + "Left Stick", + "Right Stick", + "UNKNOWN", + "UNKNOWN2", + "Dpad Up", + "Dpad Down", + "Dpad Left", + "Dpad Right", +] +const gamepadAxes: string[] = ["N/A", "Left X", "Left Y", "Right X", "Right Y"] + // Converts a key code to displayable character (ex: KeyA -> "A") const keyCodeToCharacter = (code: string) => { if (code.startsWith("Key")) return code.charAt(3) @@ -48,88 +78,420 @@ const keyCodeToCharacter = (code: string) => { if (code in codeToCharacterMap) return codeToCharacterMap[code] + if (code.startsWith("Gamepad")) return gamepadButtons[parseInt(code.substring(8))] + return code } +const moveElementToTop = (arr: string[], element: string | undefined) => { + if (element == undefined) { + return arr + } + + arr = arr.includes(element) ? [element, ...arr.filter(item => item !== element)] : arr + return arr +} + const ChangeInputsModal: React.FC = ({ modalId }) => { - const [loadedRobot, setLoadedRobot] = useState("") + const { openModal } = useModalControlContext() + + const [selectedScheme, setSelectedScheme] = useState(undefined) + const [useGamepad, setUseGamepad] = useState(false) + const [selectedInput, setSelectedInput] = useState("") + const [chosenKey, setChosenKey] = useState("") - const [modifierState, setModifierState] = useState(emptyModifierState) + const [chosenButton, setChosenButton] = useState(-1) + const [modifierState, setModifierState] = useState(EmptyModifierState) - useEffect(() => { - setTimeout(() => setLoadedRobot("Dozer v9"), 1) - }) + const [chosenGamepadAxis, setChosenGamepadAxis] = useState(-1) + const [chosenResetScheme, setChosenResetScheme] = useState("WASD") + const [useButtons, setUseButtons] = useState({}) + + // If there is a robot spawned, set it as the selected robot + if (selectedScheme == null && InputSystem.allInputs.size > 0) { + setTimeout(() => { + if (!InputSystem.selectedScheme) InputSystem.selectedScheme = InputSystem.allInputs.values().next().value + + setUseButtons({}) + setSelectedScheme(InputSystem.selectedScheme) + setUseGamepad(InputSystem.selectedScheme?.usesGamepad ?? false) + }, 1) + } + + const axisInputs = selectedScheme?.inputs.filter((input): input is AxisInput => input instanceof AxisInput) + + // Stores the states for using buttons or a joystick axis for gamepad axis inputs + type UseButtonsState = { + [key: string]: boolean + } + + if (axisInputs && Object.keys(useButtons).length == 0) { + axisInputs.forEach(input => { + useButtons[input.inputName] = input.useGamepadButtons + }) + } + + // Assign keyboard inputs when a key is pressed + if (!useGamepad && selectedInput && chosenKey) { + if (selectedInput.startsWith("pos")) { + const input = selectedScheme?.inputs.find( + input => input.inputName == selectedInput.substring(3) + ) as AxisInput + input.posKeyCode = chosenKey + input.posKeyModifiers = modifierState + } else if (selectedInput.startsWith("neg")) { + const input = selectedScheme?.inputs.find( + input => input.inputName == selectedInput.substring(3) + ) as AxisInput + input.negKeyCode = chosenKey + input.negKeyModifiers = modifierState + } else { + const input = selectedScheme?.inputs.find(input => input.inputName == selectedInput) as ButtonInput + input.keyCode = chosenKey + input.keyModifiers = modifierState + } - if (selectedInput && chosenKey) { - const selected = InputSystem.allInputs[selectedInput] - selected.keyCode = chosenKey - selected.modifiers = modifierState setChosenKey("") setSelectedInput("") - setModifierState(emptyModifierState) + setModifierState(EmptyModifierState) + } + // Assign gamepad button inputs when a button is pressed + else if (useGamepad && selectedInput && chosenButton != -1) { + if (selectedInput.startsWith("pos")) { + const input = selectedScheme?.inputs.find( + input => input.inputName == selectedInput.substring(3) + ) as AxisInput + input.posGamepadButton = chosenButton + } else if (selectedInput.startsWith("neg")) { + const input = selectedScheme?.inputs.find( + input => input.inputName == selectedInput.substring(3) + ) as AxisInput + input.negGamepadButton = chosenButton + } else { + const input = selectedScheme?.inputs.find(input => input.inputName == selectedInput) as ButtonInput + + input.gamepadButton = chosenButton + } + + setChosenButton(-1) + setSelectedInput("") + } + + // Assign gamepad axis inputs when a gamepad axis is selected + if (useGamepad && selectedInput && chosenGamepadAxis != -1) { + const selected = selectedScheme?.inputs.find(input => input.inputName == selectedInput) as AxisInput + selected.gamepadAxisNumber = chosenGamepadAxis - 1 + + setChosenGamepadAxis(-1) + setSelectedInput("") + } + + useEffect(() => { + const checkGamepadState = () => { + if (InputSystem.gamepad !== null) { + const pressedButtons = InputSystem.gamepad.buttons + .map((button, index) => (button.pressed ? index : null)) + .filter(index => index !== null) + .map(index => index!) + + if (pressedButtons.length > 0) setChosenButton(pressedButtons[0]) + else setChosenButton(-1) + } + requestAnimationFrame(checkGamepadState) + } + + checkGamepadState() + }) + + const KeyboardButtonSelection = (c: ButtonInput) => { + return ( + { + setSelectedInput(c.inputName) + }} + /> + ) + } + + const KeyboardAxisSelection = (c: AxisInput) => { + return ( +
+ + {/* Positive key */} + + { + setSelectedInput("pos" + c.inputName) + }} + /> + {/* Negative key */} + { + setSelectedInput("neg" + c.inputName) + }} + /> + +
+ ) + } + + const JoystickButtonSelection = (c: ButtonInput) => { + return ( + { + setSelectedInput(c.inputName) + }} + /> + ) + } + + const JoystickAxisSelection = (c: AxisInput) => { + return ( + { + setSelectedInput(c.inputName) + setChosenGamepadAxis(gamepadAxes.indexOf(value)) + }} + /> + ) + } + + const GamepadButtonAxisSelection = (c: AxisInput) => { + return ( +
+ + + {/* // Positive gamepad button */} + { + setSelectedInput("pos" + c.inputName) + }} + /> + {/* // Negative gamepad button */} + { + setSelectedInput("neg" + c.inputName) + }} + /> + +
+ ) } return ( } modalId={modalId}> - -
{ - setChosenKey(selectedInput ? e.code : "") - setModifierState({ - ctrl: e.ctrlKey, - alt: e.altKey, - shift: e.shiftKey, - meta: e.metaKey, - }) - }} - > - {loadedRobot ? ( - <> - - {Object.values(InputSystem.robotInputs).map(c => ( - { - setSelectedInput(c.name) + {Array.from(InputSystem.allInputs.keys()).length > 0 ? ( + <> + +
+ + { + const newScheme = InputSystem.allInputs.get(value) + if (newScheme == selectedScheme) return + + setSelectedScheme(undefined) + InputSystem.selectedScheme = newScheme }} /> - ))} - - ) : ( - - )} -
-
{ - setChosenKey(selectedInput ? e.key : "") - setModifierState({ - ctrl: e.ctrlKey, - alt: e.altKey, - shift: e.shiftKey, - meta: e.metaKey, - }) - }} - > - - {Object.values(InputSystem.globalInputs).map(c => ( - { - setSelectedInput(c.name) + {selectedScheme ? ( + <> + { + setUseGamepad(val) + if (selectedScheme) selectedScheme.usesGamepad = val + }} + /> + + {/* */} +
+ scheme.schemeName + )} + onSelect={value => { + setChosenResetScheme(value) + }} + /> +
+
+
{ + setChosenKey(selectedInput ? e.code : "") + setModifierState({ + ctrl: e.ctrlKey, + alt: e.altKey, + shift: e.shiftKey, + meta: e.metaKey, + }) }} - /> - ))} -
-
+ > + {selectedScheme ? ( + <> + + {selectedScheme.inputs.map(c => { + if (!useGamepad) { + // Keyboard button + if (c instanceof ButtonInput) { + return KeyboardButtonSelection(c) + } + // Keyboard Axis + else if (c instanceof AxisInput) { + return KeyboardAxisSelection(c) + } + } else { + // Joystick Button + if (c instanceof ButtonInput) { + return JoystickButtonSelection(c) + } + + // Gamepad axis + else if (c instanceof AxisInput) { + return ( +
+ {useButtons[c.inputName] + ? GamepadButtonAxisSelection(c) + : // Gamepad joystick axis + JoystickAxisSelection(c)} + + {/* // Button to switch between two buttons and a joystick axis */} + { + setUseButtons(prevState => ({ + ...prevState, + [c.inputName]: val, + })) + c.useGamepadButtons = val + }} + /> + {/* // Button to invert the joystick axis */} + { + c.joystickInverted = val + }} + /> +
+ ) + } + } + })} +
+ + ) : ( + + )} +
+
+ + ) : ( + + )}
) } diff --git a/fission/src/ui/modals/configuring/ResetAllInputsModal.tsx b/fission/src/ui/modals/configuring/ResetAllInputsModal.tsx new file mode 100644 index 0000000000..87471434d8 --- /dev/null +++ b/fission/src/ui/modals/configuring/ResetAllInputsModal.tsx @@ -0,0 +1,44 @@ +import React from "react" +import Modal, { ModalPropsImpl } from "@/components/Modal" +import { GrFormClose } from "react-icons/gr" +import { useModalControlContext } from "@/ui/ModalContext" +import InputSystem from "@/systems/input/InputSystem" +import DefaultInputs from "@/systems/input/DefaultInputs" + +const ResetAllInputsModal: React.FC = ({ modalId }) => { + const { openModal } = useModalControlContext() + + return ( + } + modalId={modalId} + onAccept={() => { + let i = 0 + InputSystem.allInputs.forEach(currentScheme => { + const scheme = DefaultInputs.ALL_INPUT_SCHEMES[i] + if (!currentScheme || !scheme) return + + scheme.inputs.forEach(newInput => { + const currentInput = currentScheme.inputs.find(i => i.inputName == newInput.inputName) + + if (currentInput) { + const inputIndex = currentScheme.inputs.indexOf(currentInput) + + currentScheme.inputs[inputIndex] = newInput.getCopy() + } + }) + currentScheme.usesGamepad = scheme.usesGamepad + + i++ + }) + openModal("change-inputs") + }} + onCancel={() => { + openModal("change-inputs") + }} + > + ) +} + +export default ResetAllInputsModal diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index 236c8f901e..95536e2249 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -4,8 +4,10 @@ import { FaPlus } from "react-icons/fa6" import { ChangeEvent, useRef, useState } from "react" import Label, { LabelSize } from "@/components/Label" import { useTooltipControlContext } from "@/ui/TooltipContext" -import { CreateMirabufFromUrl } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" +import MirabufCachingService, { MiraType } from "@/mirabuf/MirabufLoader" +import Dropdown from "@/ui/components/Dropdown" +import { CreateMirabuf } from "@/mirabuf/MirabufSceneObject" const ImportLocalMirabufModal: React.FC = ({ modalId }) => { // update tooltip based on type of drivetrain, receive message from Synthesis @@ -14,6 +16,7 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { const fileUploadRef = useRef(null) const [selectedFile, setSelectedFile] = useState(undefined) + const [miraType, setSelectedType] = useState(undefined) const uploadClicked = () => { if (fileUploadRef.current) { @@ -28,26 +31,39 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { } } + const typeSelected = (type: string) => { + switch (type) { + case "Robot": + setSelectedType(MiraType.ROBOT) + break + case "Field": + setSelectedType(MiraType.FIELD) + break + } + } + return ( } modalId={modalId} - acceptEnabled={selectedFile !== undefined} - onAccept={() => { - if (selectedFile) { - console.log(`Mira: '${selectedFile}'`) + acceptEnabled={selectedFile !== undefined && miraType !== undefined} + onAccept={async () => { + if (selectedFile && miraType != undefined) { showTooltip("controls", [ { control: "WASD", description: "Drive" }, { control: "E", description: "Intake" }, { control: "Q", description: "Dispense" }, ]) - CreateMirabufFromUrl(URL.createObjectURL(selectedFile)).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) + const hashBuffer = await selectedFile.arrayBuffer() + await MirabufCachingService.CacheAndGetLocal(hashBuffer, MiraType.ROBOT) + .then(x => CreateMirabuf(x!)) + .then(x => { + if (x) { + World.SceneRenderer.RegisterSceneObject(x) + } + }) } }} > @@ -62,6 +78,13 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { ) : ( <> )} + { + typeSelected(selected) + }} + /> ) diff --git a/fission/src/ui/modals/spawning/SpawningModals.tsx b/fission/src/ui/modals/spawning/SpawningModals.tsx index c74caecec6..a115808f68 100644 --- a/fission/src/ui/modals/spawning/SpawningModals.tsx +++ b/fission/src/ui/modals/spawning/SpawningModals.tsx @@ -5,28 +5,46 @@ import Stack, { StackDirection } from "../../components/Stack" import Button, { ButtonSize } from "../../components/Button" import { useModalControlContext } from "@/ui/ModalContext" import Label, { LabelSize } from "@/components/Label" -import { CreateMirabufFromUrl } from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" import { useTooltipControlContext } from "@/ui/TooltipContext" +import MirabufCachingService, { MirabufCacheInfo, MiraType } from "@/mirabuf/MirabufLoader" +import { CreateMirabuf } from "@/mirabuf/MirabufSceneObject" -interface MirabufEntry { +interface MirabufRemoteInfo { displayName: string src: string } -interface MirabufCardProps { - entry: MirabufEntry - select: (entry: MirabufEntry) => void +interface MirabufRemoteCardProps { + info: MirabufRemoteInfo + select: (info: MirabufRemoteInfo) => void } -const MirabufCard: React.FC = ({ entry, select }) => { +const MirabufRemoteCard: React.FC = ({ info, select }) => { return (
- -
+ ) +} + +interface MirabufCacheCardProps { + info: MirabufCacheInfo + select: (info: MirabufCacheInfo) => void +} + +const MirabufCacheCard: React.FC = ({ info, select }) => { + return ( +
+ +
) } @@ -35,7 +53,17 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { const { showTooltip } = useTooltipControlContext() const { closeModal } = useModalControlContext() - const [remoteRobots, setRemoteRobots] = useState(null) + const [cachedRobots, setCachedRobots] = useState(undefined) + + // prettier-ignore + useEffect(() => { + (async () => { + const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) + setCachedRobots(Object.values(map)) + })() + }, []) + + const [remoteRobots, setRemoteRobots] = useState(undefined) // prettier-ignore useEffect(() => { @@ -43,18 +71,14 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { fetch("/api/mira/manifest.json") .then(x => x.json()) .then(x => { - const robots: MirabufEntry[] = [] + const map = MirabufCachingService.GetCacheMap(MiraType.ROBOT) + const robots: MirabufRemoteInfo[] = [] for (const src of x["robots"]) { if (typeof src == "string") { - robots.push({ - src: `/api/mira/Robots/${src}`, - displayName: src, - }) + const str = `/api/mira/Robots/${src}` + if (!map[str]) robots.push({ displayName: src, src: str }) } else { - robots.push({ - src: src["src"], - displayName: src["displayName"], - }) + if (!map[src["src"]]) robots.push({ displayName: src["displayName"], src: src["src"] }) } } setRemoteRobots(robots) @@ -62,30 +86,53 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { })() }, []) - const selectRobot = (entry: MirabufEntry) => { - console.log(`Mira: '${entry.src}'`) - showTooltip("controls", [ - { control: "WASD", description: "Drive" }, - { control: "E", description: "Intake" }, - { control: "Q", description: "Dispense" }, - ]) + const selectCache = async (info: MirabufCacheInfo) => { + const assembly = await MirabufCachingService.Get(info.id, MiraType.ROBOT) + + if (assembly) { + showTooltip("controls", [ + { control: "WASD", description: "Drive" }, + { control: "E", description: "Intake" }, + { control: "Q", description: "Dispense" }, + ]) + + CreateMirabuf(assembly).then(x => { + if (x) { + World.SceneRenderer.RegisterSceneObject(x) + } + }) - CreateMirabufFromUrl(entry.src).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) + if (!info.name) + MirabufCachingService.CacheInfo(info.cacheKey, MiraType.ROBOT, assembly.info?.name ?? undefined) + } else { + console.error("Failed to spawn robot") + } closeModal() } + const selectRemote = async (info: MirabufRemoteInfo) => { + const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.ROBOT) + + if (!cacheInfo) { + console.error("Failed to cache robot") + closeModal() + } else { + selectCache(cacheInfo) + } + } + return ( } modalId={modalId} acceptEnabled={false}>
+ + {cachedRobots ? cachedRobots!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} - {remoteRobots ? remoteRobots!.map(x => MirabufCard({ entry: x, select: selectRobot })) : <>} + {remoteRobots ? remoteRobots!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>}
) @@ -94,7 +141,17 @@ export const AddRobotsModal: React.FC = ({ modalId }) => { export const AddFieldsModal: React.FC = ({ modalId }) => { const { closeModal } = useModalControlContext() - const [remoteFields, setRemoteFields] = useState(null) + const [cachedFields, setCachedFields] = useState(undefined) + + // prettier-ignore + useEffect(() => { + (async () => { + const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) + setCachedFields(Object.values(map)) + })() + }, []) + + const [remoteFields, setRemoteFields] = useState(undefined) // prettier-ignore useEffect(() => { @@ -102,18 +159,16 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { fetch("/api/mira/manifest.json") .then(x => x.json()) .then(x => { - const fields: MirabufEntry[] = [] + // TODO: Skip already cached fields + const map = MirabufCachingService.GetCacheMap(MiraType.FIELD) + const fields: MirabufRemoteInfo[] = [] for (const src of x["fields"]) { if (typeof src == "string") { - fields.push({ - src: `/api/mira/Fields/${src}`, - displayName: src, - }) + const newSrc = `/api/mira/Fields/${src}` + if (!map[newSrc]) fields.push({ displayName: src, src: newSrc }) } else { - fields.push({ - src: src["src"], - displayName: src["displayName"], - }) + if (!map[src["src"]]) + fields.push({ displayName: src["displayName"], src: src["src"] }) } } setRemoteFields(fields) @@ -121,24 +176,47 @@ export const AddFieldsModal: React.FC = ({ modalId }) => { })() }, []) - const selectField = (entry: MirabufEntry) => { - console.log(`Mira: '${entry.src}'`) - CreateMirabufFromUrl(entry.src).then(x => { - if (x) { - World.SceneRenderer.RegisterSceneObject(x) - } - }) + const selectCache = async (info: MirabufCacheInfo) => { + const assembly = await MirabufCachingService.Get(info.id, MiraType.FIELD) + + if (assembly) { + CreateMirabuf(assembly).then(x => { + if (x) { + World.SceneRenderer.RegisterSceneObject(x) + } + }) + + if (!info.name) + MirabufCachingService.CacheInfo(info.cacheKey, MiraType.FIELD, assembly.info?.name ?? undefined) + } else { + console.error("Failed to spawn field") + } closeModal() } + const selectRemote = async (info: MirabufRemoteInfo) => { + const cacheInfo = await MirabufCachingService.CacheRemote(info.src, MiraType.FIELD) + + if (!cacheInfo) { + console.error("Failed to cache field") + closeModal() + } else { + selectCache(cacheInfo) + } + } + return ( } modalId={modalId} acceptEnabled={false}>
+ + {cachedFields ? cachedFields!.map(x => MirabufCacheCard({ info: x, select: selectCache })) : <>} - {remoteFields ? remoteFields!.map(x => MirabufCard({ entry: x, select: selectField })) : <>} + {remoteFields ? remoteFields!.map(x => MirabufRemoteCard({ info: x, select: selectRemote })) : <>}
) diff --git a/fission/tsconfig.json b/fission/tsconfig.json index 4de5d03907..23ee4bdff2 100644 --- a/fission/tsconfig.json +++ b/fission/tsconfig.json @@ -5,7 +5,8 @@ "lib": [ "ES2020", "DOM", - "DOM.Iterable" + "DOM.Iterable", + "DOM.AsyncIterable" ], "module": "ESNext", "skipLibCheck": true, diff --git a/fission/vite.config.ts b/fission/vite.config.ts index 9d57b34383..5f19b30927 100644 --- a/fission/vite.config.ts +++ b/fission/vite.config.ts @@ -27,8 +27,8 @@ export default defineConfig({ { find: '@/components', replacement: path.resolve(__dirname, 'src', 'ui', 'components') }, { find: '@/modals', replacement: path.resolve(__dirname, 'src', 'ui', 'modals') }, { find: '@/panels', replacement: path.resolve(__dirname, 'src', 'ui', 'panels') }, - { find: '@', replacement: path.resolve(__dirname, 'src') } - ] + { find: '@', replacement: path.resolve(__dirname, 'src') }, + ], }, test: { globals: true, @@ -37,8 +37,8 @@ export default defineConfig({ enabled: true, name: 'chromium', headless: true, - provider: 'playwright' - } + provider: 'playwright', + }, }, server: { // this ensures that the browser opens upon server start @@ -51,17 +51,17 @@ export default defineConfig({ target: `http://localhost:${serverPort}${basePath}`, changeOrigin: true, secure: false, - rewrite: (path) => path.replace(/^\/api\/mira/, '/Downloadables/Mira') + rewrite: path => path.replace(/^\/api\/mira/, '/Downloadables/Mira'), }, '/api/auth': { target: `http://localhost:${dockerServerPort}/`, changeOrigin: true, - secure: false - } + secure: false, + }, }, }, build: { target: 'esnext', }, - base: basePath + base: basePath, }) From e7da8c3c4f8e3d1320d90518643183ad76ec2400 Mon Sep 17 00:00:00 2001 From: Dhruv Arora Date: Mon, 8 Jul 2024 11:23:35 -0700 Subject: [PATCH 05/33] Quick fix Removing duplicate line of code after merge --- fission/src/systems/scene/SceneRenderer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index f48b547321..1831624669 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -139,8 +139,6 @@ class SceneRenderer extends WorldSystem { ) }) - this._skybox.position.copy(this._mainCamera.position) - this._renderer.render(this._scene, this._mainCamera) } From 7ed699590bb00cdf25953b9112b4b009efe40b4c Mon Sep 17 00:00:00 2001 From: Dhruv Arora Date: Mon, 8 Jul 2024 14:52:46 -0700 Subject: [PATCH 06/33] Added scaling functionality with slider --- fission/src/Synthesis.tsx | 7 ++++++- fission/src/mirabuf/MirabufInstance.ts | 6 +++--- fission/src/ui/components/TransformGizmos.tsx | 4 ---- .../configuring/ConfigureGamepiecePickupPanel.tsx | 11 +++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index ffe2485d2c..4a231d62d7 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -216,7 +216,12 @@ const initialPanels: ReactElement[] = [ , , , - , + , , , , diff --git a/fission/src/mirabuf/MirabufInstance.ts b/fission/src/mirabuf/MirabufInstance.ts index 43903d09bb..390c6f3ef6 100644 --- a/fission/src/mirabuf/MirabufInstance.ts +++ b/fission/src/mirabuf/MirabufInstance.ts @@ -3,7 +3,7 @@ import { mirabuf } from "../proto/mirabuf" import MirabufParser, { ParseErrorSeverity } from "./MirabufParser.ts" import World from "@/systems/World.ts" -const WIREFRAME = true +const WIREFRAME = false export enum MaterialStyle { Regular = 0, @@ -168,8 +168,8 @@ class MirabufInstance { const material: THREE.Material = WIREFRAME ? new THREE.MeshStandardMaterial({ wireframe: true, color: 0x000000 }) : appearanceOverride && this._materials.has(appearanceOverride) - ? this._materials.get(appearanceOverride)! - : fillerMaterials[nextFillerMaterial++ % fillerMaterials.length] + ? this._materials.get(appearanceOverride)! + : fillerMaterials[nextFillerMaterial++ % fillerMaterials.length] const threeMesh = new THREE.Mesh(geometry, material) threeMesh.receiveShadow = true diff --git a/fission/src/ui/components/TransformGizmos.tsx b/fission/src/ui/components/TransformGizmos.tsx index 60f8b6c25b..e9fa45868f 100644 --- a/fission/src/ui/components/TransformGizmos.tsx +++ b/fission/src/ui/components/TransformGizmos.tsx @@ -85,10 +85,6 @@ class TransformGizmos { }) }) } - - public setMeshSize(size: number) { - this.mesh.scale.set(size, size, size) - } } export default TransformGizmos diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index d73bcc9b6c..9b2cabbdfe 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -10,19 +10,17 @@ import Slider from "@/ui/components/Slider" // slider constants const MIN_ZONE_SIZE = 0.1 const MAX_ZONE_SIZE = 1.0 -const DEFAULT_ZONE_SIZE = 0.5 // default zone size +const DEFAULT_ZONE_SIZE = 1.0 // default zone size const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { const [, setNode] = useState("Click to select") const transformGizmoRef = useRef() + // let currentSize = DEFAULT_ZONE_SIZE // creating mesh & gizmo for the pickup node useEffect(() => { transformGizmoRef.current = new TransformGizmos( - World.SceneRenderer.CreateSphere( - DEFAULT_ZONE_SIZE, - World.SceneRenderer.CreateToonMaterial(new THREE.Color(0xffffff)) - ) + World.SceneRenderer.CreateSphere(0.5, World.SceneRenderer.CreateToonMaterial(new THREE.Color(0xffffff))) ) transformGizmoRef.current.AddMeshToScene() transformGizmoRef.current.CreateGizmo("translate") @@ -36,6 +34,7 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open openLocation={openLocation} sidePadding={sidePadding} onAccept={() => { + if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() // send configuration information to APS + RAM }} onCancel={() => { @@ -53,7 +52,7 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open label="Zone Size" format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} onChange={(size: number) => { - transformGizmoRef.current?.setMeshSize(size) + transformGizmoRef.current?.mesh.scale.set(size, size, size) }} step={0.01} /> From 49b9e5ef2c72bbb407080a26241f0489063cdcb8 Mon Sep 17 00:00:00 2001 From: Dhruv Arora Date: Mon, 8 Jul 2024 17:34:46 -0700 Subject: [PATCH 07/33] Raycasting work and basic implementation of pickup configuration --- fission/src/mirabuf/MirabufSceneObject.ts | 2 +- fission/src/ui/components/SelectButton.tsx | 49 ++++++++++++++----- fission/src/ui/components/TransformGizmos.tsx | 4 +- .../ConfigureGamepiecePickupPanel.tsx | 20 ++++++-- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index a003f8da47..45cdd45c7d 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -60,7 +60,7 @@ class MirabufSceneObject extends SceneObject { ) ) this.transformGizmos.AddMeshToScene() - this.transformGizmos.CreateGizmo("translate") + this.transformGizmos.CreateGizmo("translate", 5.0) // disabling physics initially for all rigid node bodies this._mirabufInstance.parser.rigidNodes.forEach(rn => { diff --git a/fission/src/ui/components/SelectButton.tsx b/fission/src/ui/components/SelectButton.tsx index 49f4b58e4b..cc99282559 100644 --- a/fission/src/ui/components/SelectButton.tsx +++ b/fission/src/ui/components/SelectButton.tsx @@ -2,7 +2,30 @@ import React, { useCallback, useEffect, useRef, useState } from "react" import Button, { ButtonSize } from "./Button" import Label, { LabelSize } from "./Label" import Stack, { StackDirection } from "./Stack" -import { Random } from "@/util/Random" +import World from "@/systems/World" +import { ThreeVector3_JoltVec3 } from "@/util/TypeConversions" + +// raycasting constants +const RAY_MAX_LENGTH = 20.0 + +function SelectNode(e: MouseEvent) { + const origin = World.SceneRenderer.mainCamera.position + + const worldSpace = World.SceneRenderer.PixelToWorldSpace(e.clientX, e.clientY) + const dir = worldSpace.sub(origin).normalize().multiplyScalar(RAY_MAX_LENGTH) + + const res = World.PhysicsSystem.RayCast(ThreeVector3_JoltVec3(origin), ThreeVector3_JoltVec3(dir)) + + if (res) { + console.log(res) + World.PhysicsSystem.GetBody(res.data.mBodyID) + // TODO: check if body is a node on an assembly and not the floor + + return true + } + + return false +} type SelectButtonProps = { colorClass?: string @@ -29,18 +52,20 @@ const SelectButton: React.FC = ({ colorClass, size, placehold ) useEffect(() => { - // simulate receiving a selection from Synthesis - if (selecting) { - timeoutRef.current = setTimeout( - () => { - if (selecting) { - const v = `node_${Math.floor(Random() * 10).toFixed(0)}` - onReceiveSelection(v) - } - }, - Math.floor(Random() * 2_750) + 250 - ) + const onClick = (e: MouseEvent) => { + if (selecting) { + if (SelectNode(e)) { + onReceiveSelection("node") + } + } } + + World.SceneRenderer.renderer.domElement.addEventListener("click", onClick) + + return () => { + World.SceneRenderer.renderer.domElement.removeEventListener("click", onClick) + } + }, [selecting, onReceiveSelection]) // should send selecting state when clicked and then receive string value to set selecting to false diff --git a/fission/src/ui/components/TransformGizmos.tsx b/fission/src/ui/components/TransformGizmos.tsx index e9fa45868f..396600824a 100644 --- a/fission/src/ui/components/TransformGizmos.tsx +++ b/fission/src/ui/components/TransformGizmos.tsx @@ -43,8 +43,8 @@ class TransformGizmos { * * @param mode The type of gizmo to create */ - public CreateGizmo(mode: "translate" | "rotate" | "scale") { - const gizmo = World.SceneRenderer.AddTransformGizmo(this._mesh, mode, this._gizmos.length ? 3.0 : 5.0) + public CreateGizmo(mode: "translate" | "rotate" | "scale", size: number = 1.5) { + const gizmo = World.SceneRenderer.AddTransformGizmo(this._mesh, mode, size) this._gizmos.push(gizmo) } diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index 9b2cabbdfe..d25f6abe80 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -12,10 +12,10 @@ const MIN_ZONE_SIZE = 0.1 const MAX_ZONE_SIZE = 1.0 const DEFAULT_ZONE_SIZE = 1.0 // default zone size + const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { - const [, setNode] = useState("Click to select") + // const [, setNode] = useState("Click to select") const transformGizmoRef = useRef() - // let currentSize = DEFAULT_ZONE_SIZE // creating mesh & gizmo for the pickup node useEffect(() => { @@ -24,6 +24,20 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open ) transformGizmoRef.current.AddMeshToScene() transformGizmoRef.current.CreateGizmo("translate") + + // const onClick = (e: MouseEvent) => { + // if (searchingForNode.current) { + // if (SelectNode(e)) { + // searchingForNode.current = false + // } + // } + // } + + // World.SceneRenderer.renderer.domElement.addEventListener("click", onClick) + + // return () => { + // World.SceneRenderer.renderer.domElement.removeEventListener("click", onClick) + // } }, []) return ( @@ -42,7 +56,7 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open }} > {/* Button for user to select pickup node */} - + {/* Slider for user to set size of pickup configuration */} Date: Mon, 8 Jul 2024 17:54:53 -0700 Subject: [PATCH 08/33] Added storing data functionality --- .../systems/simulation/SimulationSystem.ts | 23 +++++++++++++++++++ fission/src/ui/components/SelectButton.tsx | 21 ++++++++--------- .../ConfigureGamepiecePickupPanel.tsx | 18 ++++++++++++--- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/fission/src/systems/simulation/SimulationSystem.ts b/fission/src/systems/simulation/SimulationSystem.ts index 331739f8a7..4a2dc2cf72 100644 --- a/fission/src/systems/simulation/SimulationSystem.ts +++ b/fission/src/systems/simulation/SimulationSystem.ts @@ -11,8 +11,31 @@ import HingeStimulus from "./stimulus/HingeStimulus" import WheelRotationStimulus from "./stimulus/WheelStimulus" import SliderStimulus from "./stimulus/SliderStimulus" import ChassisStimulus from "./stimulus/ChassisStimulus" +import Jolt from "@barclah/jolt-physics" class SimulationSystem extends WorldSystem { + /** + * TEMPORARY - beginning of block + * + * Storing the pickup and ejector configurations + * + * Key: Body + * Value: Postion + */ + private _pickupConfigurations: Map = new Map() + private _ejectorConfigurations: Map = new Map() + + public AddPickupConfiguration(body: Jolt.Body, position: THREE.Vector3) { + this._pickupConfigurations.set(body, position) + } + + public AddEjectorConfiguration(body: Jolt.Body, position: THREE.Vector3) { + this._ejectorConfigurations.set(body, position) + } + /** + * TEMPORARY - end of block + */ + private _simMechanisms: Map constructor() { diff --git a/fission/src/ui/components/SelectButton.tsx b/fission/src/ui/components/SelectButton.tsx index cc99282559..126e0a93d9 100644 --- a/fission/src/ui/components/SelectButton.tsx +++ b/fission/src/ui/components/SelectButton.tsx @@ -4,6 +4,7 @@ import Label, { LabelSize } from "./Label" import Stack, { StackDirection } from "./Stack" import World from "@/systems/World" import { ThreeVector3_JoltVec3 } from "@/util/TypeConversions" +import Jolt from "@barclah/jolt-physics" // raycasting constants const RAY_MAX_LENGTH = 20.0 @@ -18,20 +19,18 @@ function SelectNode(e: MouseEvent) { if (res) { console.log(res) - World.PhysicsSystem.GetBody(res.data.mBodyID) + return World.PhysicsSystem.GetBody(res.data.mBodyID) // TODO: check if body is a node on an assembly and not the floor - - return true } - return false + return null } type SelectButtonProps = { colorClass?: string size?: ButtonSize placeholder?: string - onSelect?: (value: string) => void + onSelect?: (value: Jolt.Body) => void className?: string } @@ -41,10 +40,10 @@ const SelectButton: React.FC = ({ colorClass, size, placehold const timeoutRef = useRef() const onReceiveSelection = useCallback( - (value: string) => { + (value: Jolt.Body) => { // TODO remove this when communication works clearTimeout(timeoutRef.current) - setValue(value) + setValue("Node") setSelecting(false) if (onSelect) onSelect(value) }, @@ -54,8 +53,9 @@ const SelectButton: React.FC = ({ colorClass, size, placehold useEffect(() => { const onClick = (e: MouseEvent) => { if (selecting) { - if (SelectNode(e)) { - onReceiveSelection("node") + const body = SelectNode(e) + if (body) { + onReceiveSelection(body) } } } @@ -65,7 +65,6 @@ const SelectButton: React.FC = ({ colorClass, size, placehold return () => { World.SceneRenderer.renderer.domElement.removeEventListener("click", onClick) } - }, [selecting, onReceiveSelection]) // should send selecting state when clicked and then receive string value to set selecting to false @@ -81,7 +80,7 @@ const SelectButton: React.FC = ({ colorClass, size, placehold // send selecting state if (selecting) { // cancel selection - onReceiveSelection("") + clearTimeout(timeoutRef.current) } else { setSelecting(true) } diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index d25f6abe80..7514dbcbbf 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -6,16 +6,17 @@ import SelectButton from "@/components/SelectButton" import TransformGizmos from "@/ui/components/TransformGizmos" import World from "@/systems/World" import Slider from "@/ui/components/Slider" +import Jolt from "@barclah/jolt-physics" // slider constants const MIN_ZONE_SIZE = 0.1 const MAX_ZONE_SIZE = 1.0 const DEFAULT_ZONE_SIZE = 1.0 // default zone size - const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { // const [, setNode] = useState("Click to select") const transformGizmoRef = useRef() + const bodyAttachmentRef = useRef() // creating mesh & gizmo for the pickup node useEffect(() => { @@ -24,7 +25,7 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open ) transformGizmoRef.current.AddMeshToScene() transformGizmoRef.current.CreateGizmo("translate") - + // const onClick = (e: MouseEvent) => { // if (searchingForNode.current) { // if (SelectNode(e)) { @@ -49,14 +50,25 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open sidePadding={sidePadding} onAccept={() => { if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() + + console.log(bodyAttachmentRef) // send configuration information to APS + RAM + if (bodyAttachmentRef.current && transformGizmoRef.current) { + World.SimulationSystem.AddPickupConfiguration( + bodyAttachmentRef.current, + transformGizmoRef.current.mesh.position + ) + } }} onCancel={() => { if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() }} > {/* Button for user to select pickup node */} - + (bodyAttachmentRef.current = body)} + /> {/* Slider for user to set size of pickup configuration */} Date: Mon, 8 Jul 2024 18:45:42 -0700 Subject: [PATCH 09/33] Added configuration for shot trajectory --- .../ConfigureGamepiecePickupPanel.tsx | 17 +-------- .../ConfigureShotTrajectoryPanel.tsx | 37 +++++++++++++++++-- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index 7514dbcbbf..ed078d5fc7 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -1,5 +1,5 @@ import * as THREE from "three" -import { useEffect, useState, useRef } from "react" +import { useEffect, useRef } from "react" import { FaGear } from "react-icons/fa6" import Panel, { PanelPropsImpl } from "@/components/Panel" import SelectButton from "@/components/SelectButton" @@ -25,20 +25,6 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open ) transformGizmoRef.current.AddMeshToScene() transformGizmoRef.current.CreateGizmo("translate") - - // const onClick = (e: MouseEvent) => { - // if (searchingForNode.current) { - // if (SelectNode(e)) { - // searchingForNode.current = false - // } - // } - // } - - // World.SceneRenderer.renderer.domElement.addEventListener("click", onClick) - - // return () => { - // World.SceneRenderer.renderer.domElement.removeEventListener("click", onClick) - // } }, []) return ( @@ -51,7 +37,6 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open onAccept={() => { if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() - console.log(bodyAttachmentRef) // send configuration information to APS + RAM if (bodyAttachmentRef.current && transformGizmoRef.current) { World.SimulationSystem.AddPickupConfiguration( diff --git a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx index 29f493200b..a375d163d7 100644 --- a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx @@ -1,13 +1,28 @@ -import { useState } from "react" +import { useEffect, useRef, useState } from "react" import { FaGear } from "react-icons/fa6" import Panel, { PanelPropsImpl } from "@/components/Panel" import SelectButton from "@/components/SelectButton" import Slider from "@/components/Slider" +import Jolt from "@barclah/jolt-physics" +import World from "@/systems/World" +import TransformGizmos from "@/ui/components/TransformGizmos" +import * as THREE from "three" const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { const defaultShootSpeed = 5 - const [, setNode] = useState("Click to select") const [, setShootSpeed] = useState(defaultShootSpeed) + const transformGizmoRef = useRef() + const bodyAttachmentRef = useRef() + + // creating mesh & gizmo for the Ejector node + useEffect(() => { + transformGizmoRef.current = new TransformGizmos( + new THREE.Mesh(new THREE.BoxGeometry(0.5, 2.0), new THREE.MeshBasicMaterial({ color: 0xffffff })) + ) + transformGizmoRef.current.AddMeshToScene() + transformGizmoRef.current.CreateGizmo("translate", 1.5) + transformGizmoRef.current.CreateGizmo("rotate", 2.0) + }, []) return ( = ({ panelId, openL openLocation={openLocation} sidePadding={sidePadding} onAccept={() => { + if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() + // send node and speed config + if (bodyAttachmentRef.current && transformGizmoRef.current) { + World.SimulationSystem.AddPickupConfiguration( + bodyAttachmentRef.current, + transformGizmoRef.current.mesh.position + ) + } + }} + onCancel={() => { + if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() + // cancel node and speed config }} > - + (bodyAttachmentRef.current = body)} + /> Date: Tue, 9 Jul 2024 23:25:59 -0700 Subject: [PATCH 10/33] Bug fix + ran prettier + fixed transform gizmo size from merge --- fission/src/mirabuf/MirabufSceneObject.ts | 2 +- fission/src/systems/physics/PhysicsSystem.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 26f99ea126..5368286c9b 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -209,7 +209,7 @@ class MirabufSceneObject extends SceneObject { ) ) this._transformGizmos.AddMeshToScene() - this._transformGizmos.CreateGizmo("translate") + this._transformGizmos.CreateGizmo("translate", 5.0) this.DisablePhysics() } diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index cc8e8b5acd..dd0e7dc2cd 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -704,11 +704,10 @@ class PhysicsSystem extends WorldSystem { const min = new JOLT.Vec3(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY) const max = new JOLT.Vec3(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY) - partDefinition.bodies!.forEach(body => { const vertArr = body.triangleMesh?.mesh?.verts const indexArr = body.triangleMesh?.mesh?.indices - if (!vertsArr || !indexArr) return + if (!vertArr || !indexArr) return for (let i = 0; i < vertArr.length; i += 3) { const vert = MirabufFloatArr_JoltFloat3(vertArr, i) settings.mTriangleVertices.push_back(vert) From 88b1ea394fed6e348860f6790e91e477476943a8 Mon Sep 17 00:00:00 2001 From: arorad1 Date: Wed, 10 Jul 2024 12:57:09 -0700 Subject: [PATCH 11/33] Prevented select node from selecting a field or the ground need to fix bug of onCancel function not being run when another MainHUD panel being opened --- fission/src/ui/components/SelectButton.tsx | 7 +++++-- .../ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/fission/src/ui/components/SelectButton.tsx b/fission/src/ui/components/SelectButton.tsx index 126e0a93d9..3764e62d1d 100644 --- a/fission/src/ui/components/SelectButton.tsx +++ b/fission/src/ui/components/SelectButton.tsx @@ -19,8 +19,11 @@ function SelectNode(e: MouseEvent) { if (res) { console.log(res) - return World.PhysicsSystem.GetBody(res.data.mBodyID) - // TODO: check if body is a node on an assembly and not the floor + const body = World.PhysicsSystem.GetBody(res.data.mBodyID) + if (!body.IsDynamic()) { + return null + } + return body } return null diff --git a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx index a375d163d7..a0c4e0a782 100644 --- a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx @@ -32,8 +32,6 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL openLocation={openLocation} sidePadding={sidePadding} onAccept={() => { - if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() - // send node and speed config if (bodyAttachmentRef.current && transformGizmoRef.current) { World.SimulationSystem.AddPickupConfiguration( @@ -41,10 +39,13 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL transformGizmoRef.current.mesh.position ) } + + if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() }} onCancel={() => { - if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() // cancel node and speed config + + if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() }} > Date: Wed, 10 Jul 2024 14:29:34 -0700 Subject: [PATCH 12/33] Ejector and intake config integration with preferences --- fission/src/Synthesis.tsx | 4 +- .../systems/preferences/PreferenceTypes.ts | 12 +- .../systems/simulation/SimulationSystem.ts | 23 --- fission/src/ui/components/MainHUD.tsx | 2 +- .../modals/configuring/ChangeInputsModal.tsx | 23 ++- .../configuring/ConfigureRobotModal.tsx | 38 +++++ .../ConfigureGamepiecePickupPanel.tsx | 119 ++++++++++----- .../ConfigureShotTrajectoryPanel.tsx | 135 +++++++++++++----- .../configuring/scoring/ZoneConfigPanel.tsx | 3 +- 9 files changed, 252 insertions(+), 107 deletions(-) create mode 100644 fission/src/ui/modals/configuring/ConfigureRobotModal.tsx diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index f80c02f39d..4a24842320 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -58,6 +58,7 @@ import ImportLocalMirabufModal from "@/modals/mirabuf/ImportLocalMirabufModal.ts import APS from "./aps/APS.ts" import ResetAllInputsModal from "./ui/modals/configuring/ResetAllInputsModal.tsx" import Skybox from "./ui/components/Skybox.tsx" +import ConfigureRobotModal from "./ui/modals/configuring/ConfigureRobotModal.tsx" const DEFAULT_MIRA_PATH = "/api/mira/Robots/Team 2471 (2018)_v7.mira" @@ -214,6 +215,7 @@ const initialModals = [ , , , + , ] const initialPanels: ReactElement[] = [ @@ -227,7 +229,7 @@ const initialPanels: ReactElement[] = [ openLocation="right" sidePadding={8} />, - , + , , , , diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index 11d844971f..01e569b425 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -1,5 +1,6 @@ import { Vector3Tuple, Vector4Tuple } from "three" import { InputScheme } from "../input/DefaultInputs" +import Jolt from "@barclah/jolt-physics" export type GlobalPreference = | "ScreenMode" @@ -26,13 +27,16 @@ export const DefaultGlobalPreferences: { [key: string]: unknown } = { } export type IntakePreferences = { - location: Vector3Tuple, + position: [number, number, number] diameter: number + parentBody: Jolt.Body | undefined } export type EjectorPreferences = { - location: Vector3Tuple, + position: [number, number, number] + direction: [number, number, number, number] ejectorVelocity: number + parentBody: Jolt.Body | undefined } export type RobotPreferences = { @@ -64,8 +68,8 @@ export type FieldPreferences = { export function DefaultRobotPreferences(): RobotPreferences { return { inputsSchemes: [], - intake: { location: [0, 0, 0], diameter: 1 }, - ejector: { location: [0, 0, 0], ejectorVelocity: 1 } + intake: { position: [0, 0, 0], diameter: 1, parentBody: undefined }, + ejector: { position: [0, 0, 0], direction: [0,0,0,0], ejectorVelocity: 1, parentBody: undefined } }; } diff --git a/fission/src/systems/simulation/SimulationSystem.ts b/fission/src/systems/simulation/SimulationSystem.ts index 4a2dc2cf72..331739f8a7 100644 --- a/fission/src/systems/simulation/SimulationSystem.ts +++ b/fission/src/systems/simulation/SimulationSystem.ts @@ -11,31 +11,8 @@ import HingeStimulus from "./stimulus/HingeStimulus" import WheelRotationStimulus from "./stimulus/WheelStimulus" import SliderStimulus from "./stimulus/SliderStimulus" import ChassisStimulus from "./stimulus/ChassisStimulus" -import Jolt from "@barclah/jolt-physics" class SimulationSystem extends WorldSystem { - /** - * TEMPORARY - beginning of block - * - * Storing the pickup and ejector configurations - * - * Key: Body - * Value: Postion - */ - private _pickupConfigurations: Map = new Map() - private _ejectorConfigurations: Map = new Map() - - public AddPickupConfiguration(body: Jolt.Body, position: THREE.Vector3) { - this._pickupConfigurations.set(body, position) - } - - public AddEjectorConfiguration(body: Jolt.Body, position: THREE.Vector3) { - this._ejectorConfigurations.set(body, position) - } - /** - * TEMPORARY - end of block - */ - private _simMechanisms: Map constructor() { diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 21a4de5f89..8e3a2a452d 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -189,7 +189,7 @@ const MainHUD: React.FC = () => { } - onClick={() => openPanel("config-gamepiece-pickup")} + onClick={() => openModal("config-robot")} /> {userInfo ? ( diff --git a/fission/src/ui/modals/configuring/ChangeInputsModal.tsx b/fission/src/ui/modals/configuring/ChangeInputsModal.tsx index a798e614d2..f205179fbe 100644 --- a/fission/src/ui/modals/configuring/ChangeInputsModal.tsx +++ b/fission/src/ui/modals/configuring/ChangeInputsModal.tsx @@ -336,11 +336,15 @@ const ChangeInputsModal: React.FC = ({ modalId }) => { } return ( - } modalId={modalId} - onAccept={() => { + } + modalId={modalId} + onAccept={() => { PreferencesSystem.savePreferences() InputSystem.selectedScheme = undefined - }}> + }} + > {Object.keys(PreferencesSystem.getAllRobotPreferences()).length > 0 ? ( <> @@ -349,14 +353,19 @@ const ChangeInputsModal: React.FC = ({ modalId }) => { { const roboName = value.substring(4) const controlSchemeIndex: number = +value.charAt(1) - const newScheme = PreferencesSystem.getAllRobotPreferences()[roboName].inputsSchemes[controlSchemeIndex] - if (newScheme == selectedScheme) - return + const newScheme = + PreferencesSystem.getAllRobotPreferences()[roboName].inputsSchemes[ + controlSchemeIndex + ] + if (newScheme == selectedScheme) return setSelectedScheme(undefined) InputSystem.selectedScheme = newScheme diff --git a/fission/src/ui/modals/configuring/ConfigureRobotModal.tsx b/fission/src/ui/modals/configuring/ConfigureRobotModal.tsx new file mode 100644 index 0000000000..74ac80b92c --- /dev/null +++ b/fission/src/ui/modals/configuring/ConfigureRobotModal.tsx @@ -0,0 +1,38 @@ +import Button, { ButtonSize } from "@/ui/components/Button" +import Modal, { ModalPropsImpl } from "@/ui/components/Modal" +import Stack, { StackDirection } from "@/ui/components/Stack" +import { useModalControlContext } from "@/ui/ModalContext" +import { usePanelControlContext } from "@/ui/PanelContext" +import { FaGear } from "react-icons/fa6" + +export const ConfigureRobotModal: React.FC = ({ modalId }) => { + const { openPanel } = usePanelControlContext() + const { closeModal } = useModalControlContext() + + return ( + } modalId={modalId}> + + + ) + })} + + + ) : ( + <> + {/* Button for user to select pickup node */} + (bodyAttachmentRef.current = body)} + /> + + {/* Slider for user to set size of pickup configuration */} + { + transformGizmoRef.current?.mesh.scale.set(size, size, size) + }} + step={0.01} + /> + + )} ) } diff --git a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx index a375d163d7..a2b695b732 100644 --- a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx @@ -1,32 +1,74 @@ +import * as THREE from "three" import { useEffect, useRef, useState } from "react" import { FaGear } from "react-icons/fa6" import Panel, { PanelPropsImpl } from "@/components/Panel" import SelectButton from "@/components/SelectButton" -import Slider from "@/components/Slider" -import Jolt from "@barclah/jolt-physics" -import World from "@/systems/World" import TransformGizmos from "@/ui/components/TransformGizmos" -import * as THREE from "three" +import Slider from "@/ui/components/Slider" +import Jolt from "@barclah/jolt-physics" +import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" +import Label from "@/ui/components/Label" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import { EjectorPreferences } from "@/systems/preferences/PreferenceTypes" +import Button from "@/ui/components/Button" + +// slider constants +const MIN_VELOCITY = 0.1 +const MAX_VELOCITY = 1.0 const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { - const defaultShootSpeed = 5 - const [, setShootSpeed] = useState(defaultShootSpeed) + // const [, setNode] = useState("Click to select") const transformGizmoRef = useRef() const bodyAttachmentRef = useRef() - // creating mesh & gizmo for the Ejector node + const [selectedEjector, setSelectedEjector] = useState(undefined) + const [ejectorVelocity, setEjectorVelocity] = useState(0) + + // creating mesh & gizmo for the pickup node + const setupGizmo = () => { + if (selectedEjector == undefined) return + + if (transformGizmoRef.current == undefined) { + transformGizmoRef.current = new TransformGizmos( + new THREE.Mesh(new THREE.BoxGeometry(0.5, 2.0), new THREE.MeshBasicMaterial({ color: 0xffffff })) + ) + transformGizmoRef.current.AddMeshToScene() + transformGizmoRef.current.CreateGizmo("translate", 1.5) + transformGizmoRef.current.CreateGizmo("rotate", 2.0) + } + + const position = selectedEjector.position + const direction = selectedEjector.direction + + transformGizmoRef.current?.mesh.position.set(position[0], position[1], position[2]) + transformGizmoRef.current?.mesh.rotation.setFromQuaternion(new THREE.Quaternion(direction[0], direction[1], direction[2], direction[3])) + } + + // Saves zone preferences to local storage + const saveEjectorPreferences = () => { + if (selectedEjector == undefined) return + + const position = transformGizmoRef.current?.mesh.position + const direction = transformGizmoRef.current?.mesh.quaternion + + if (position == undefined || direction == undefined) return + + selectedEjector.ejectorVelocity = ejectorVelocity + selectedEjector.position = [position.x, position.y, position.z] + selectedEjector.direction = [direction.x, direction.y, direction.z, direction.w] + + selectedEjector.parentBody = bodyAttachmentRef.current + + PreferencesSystem.savePreferences() + } + useEffect(() => { - transformGizmoRef.current = new TransformGizmos( - new THREE.Mesh(new THREE.BoxGeometry(0.5, 2.0), new THREE.MeshBasicMaterial({ color: 0xffffff })) - ) - transformGizmoRef.current.AddMeshToScene() - transformGizmoRef.current.CreateGizmo("translate", 1.5) - transformGizmoRef.current.CreateGizmo("rotate", 2.0) - }, []) + setupGizmo() + }) return ( } panelId={panelId} openLocation={openLocation} @@ -34,32 +76,53 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL onAccept={() => { if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() - // send node and speed config - if (bodyAttachmentRef.current && transformGizmoRef.current) { - World.SimulationSystem.AddPickupConfiguration( - bodyAttachmentRef.current, - transformGizmoRef.current.mesh.position - ) - } + saveEjectorPreferences() }} onCancel={() => { if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() - // cancel node and speed config }} > - (bodyAttachmentRef.current = body)} - /> - + {selectedEjector == undefined ? ( + <> + + {/** Scroll view for selecting a robot to configure */} +
+ {Object.keys(SynthesisBrain.numberRobotsSpawned).map(robot => { + return ( + + ) + })} +
+ + ) : ( + <> + {/* Button for user to select the parent node */} + (bodyAttachmentRef.current = body)} + /> + + {/* Slider for user to set velocity of ejector configuration */} + + setEjectorVelocity(vel) + } + step={0.01} + /> + + )}
) } diff --git a/fission/src/ui/panels/configuring/scoring/ZoneConfigPanel.tsx b/fission/src/ui/panels/configuring/scoring/ZoneConfigPanel.tsx index 4fe84b26cf..cd58189d8d 100644 --- a/fission/src/ui/panels/configuring/scoring/ZoneConfigPanel.tsx +++ b/fission/src/ui/panels/configuring/scoring/ZoneConfigPanel.tsx @@ -2,7 +2,6 @@ import { useState } from "react" import Input from "@/components/Input" import Panel, { PanelPropsImpl } from "@/components/Panel" import Button from "@/components/Button" -import SelectButton from "@/components/SelectButton" import Checkbox from "@/components/Checkbox" import Slider from "@/components/Slider" import NumberInput from "@/components/NumberInput" @@ -37,7 +36,7 @@ const ZoneConfigPanel: React.FC = ({ panelId, openLocation, side onClick={() => setAlliance(alliance == "blue" ? "red" : "blue")} colorOverrideClass={`bg-match-${alliance}-alliance`} /> - setParent(p)} /> + {/* setParent(p)} /> */} setPoints(v || 1)} /> From 56e38de8b373e0725fa8ed4fec4c2c47f1c01e15 Mon Sep 17 00:00:00 2001 From: arorad1 Date: Wed, 10 Jul 2024 15:13:58 -0700 Subject: [PATCH 13/33] Ran prettier --- fission/src/Synthesis.tsx | 7 +++- fission/src/systems/input/InputSystem.ts | 2 +- .../systems/preferences/PreferenceTypes.ts | 29 +++++++------- .../systems/preferences/PreferencesSystem.ts | 11 +++++- .../behavior/ArcadeDriveBehavior.ts | 29 ++++++++------ .../simulation/behavior/GenericArmBehavior.ts | 8 +++- .../behavior/GenericElevatorBehavior.ts | 12 +++++- .../synthesis_brain/SynthesisBrain.ts | 39 ++++++++++++------- fission/src/ui/components/MainHUD.tsx | 10 +---- .../modals/configuring/ChangeInputsModal.tsx | 6 ++- .../configuring/ConfigureRobotModal.tsx | 2 +- .../ui/modals/configuring/SettingsModal.tsx | 31 +++++++++++---- .../ConfigureGamepiecePickupPanel.tsx | 4 +- .../ConfigureShotTrajectoryPanel.tsx | 15 ++++--- 14 files changed, 132 insertions(+), 73 deletions(-) diff --git a/fission/src/Synthesis.tsx b/fission/src/Synthesis.tsx index 4a24842320..cf1f2e3fe7 100644 --- a/fission/src/Synthesis.tsx +++ b/fission/src/Synthesis.tsx @@ -229,7 +229,12 @@ const initialPanels: ReactElement[] = [ openLocation="right" sidePadding={8} />, - , + , , , , diff --git a/fission/src/systems/input/InputSystem.ts b/fission/src/systems/input/InputSystem.ts index 33b77746b4..442c330a89 100644 --- a/fission/src/systems/input/InputSystem.ts +++ b/fission/src/systems/input/InputSystem.ts @@ -244,7 +244,7 @@ class InputSystem extends WorldSystem { public static getInput(inputName: string, assemblyName: string, assemblyIndex: number): number { const targetScheme = PreferencesSystem.getRobotPreferences(assemblyName).inputsSchemes[assemblyIndex] - const targetInput = targetScheme?.inputs.find(input => input.inputName == inputName) as (Input) + const targetInput = targetScheme?.inputs.find(input => input.inputName == inputName) as Input if (targetScheme == null || targetInput == null) return 0 diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index 01e569b425..1934bfe2c9 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -40,22 +40,22 @@ export type EjectorPreferences = { } export type RobotPreferences = { - inputsSchemes: InputScheme[], - intake: IntakePreferences, + inputsSchemes: InputScheme[] + intake: IntakePreferences ejector: EjectorPreferences } export type Alliance = "Blue" | "Red" export type ScoringZonePreferences = { - name: string, - alliance: Alliance, - parent: string, - points: number, - destroyGamepiece: boolean, - persistentPoints: boolean, - localPosition: Vector3Tuple, - localRotation: Vector4Tuple, + name: string + alliance: Alliance + parent: string + points: number + destroyGamepiece: boolean + persistentPoints: boolean + localPosition: Vector3Tuple + localRotation: Vector4Tuple localScale: Vector3Tuple } @@ -64,15 +64,14 @@ export type FieldPreferences = { scoringZones: ScoringZonePreferences[] } - export function DefaultRobotPreferences(): RobotPreferences { return { inputsSchemes: [], - intake: { position: [0, 0, 0], diameter: 1, parentBody: undefined }, - ejector: { position: [0, 0, 0], direction: [0,0,0,0], ejectorVelocity: 1, parentBody: undefined } - }; + intake: { position: [0, 0, 0], diameter: 1, parentBody: undefined }, + ejector: { position: [0, 0, 0], direction: [0, 0, 0, 0], ejectorVelocity: 1, parentBody: undefined }, + } } export function DefaultFieldPreferences(): FieldPreferences { return { defaultSpawnLocation: [0, 1, 0], scoringZones: [] } -} \ No newline at end of file +} diff --git a/fission/src/systems/preferences/PreferencesSystem.ts b/fission/src/systems/preferences/PreferencesSystem.ts index e7292b7bde..9a34a08bb6 100644 --- a/fission/src/systems/preferences/PreferencesSystem.ts +++ b/fission/src/systems/preferences/PreferencesSystem.ts @@ -1,4 +1,13 @@ -import { DefaultFieldPreferences, DefaultGlobalPreferences, DefaultRobotPreferences, FieldPreferences, FieldPreferencesKey, GlobalPreference, RobotPreferences, RobotPreferencesKey } from "./PreferenceTypes" +import { + DefaultFieldPreferences, + DefaultGlobalPreferences, + DefaultRobotPreferences, + FieldPreferences, + FieldPreferencesKey, + GlobalPreference, + RobotPreferences, + RobotPreferencesKey, +} from "./PreferenceTypes" class PreferenceEvent extends Event { public prefName: GlobalPreference diff --git a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts index 292b68db7a..374f5684d9 100644 --- a/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts +++ b/fission/src/systems/simulation/behavior/ArcadeDriveBehavior.ts @@ -4,20 +4,27 @@ import Behavior from "./Behavior" import InputSystem from "@/systems/input/InputSystem" class ArcadeDriveBehavior extends Behavior { - private leftWheels: WheelDriver[]; - private rightWheels: WheelDriver[]; - private _assemblyName: string; + private leftWheels: WheelDriver[] + private rightWheels: WheelDriver[] + private _assemblyName: string private _assemblyIndex: number private _driveSpeed = 30 private _turnSpeed = 30 - constructor(leftWheels: WheelDriver[], rightWheels: WheelDriver[], leftStimuli: WheelRotationStimulus[], rightStimuli: WheelRotationStimulus[], assemblyName: string, assemblyIndex: number) { - super(leftWheels.concat(rightWheels), leftStimuli.concat(rightStimuli)); - - this.leftWheels = leftWheels; - this.rightWheels = rightWheels; - this._assemblyName = assemblyName; + constructor( + leftWheels: WheelDriver[], + rightWheels: WheelDriver[], + leftStimuli: WheelRotationStimulus[], + rightStimuli: WheelRotationStimulus[], + assemblyName: string, + assemblyIndex: number + ) { + super(leftWheels.concat(rightWheels), leftStimuli.concat(rightStimuli)) + + this.leftWheels = leftWheels + this.rightWheels = rightWheels + this._assemblyName = assemblyName this._assemblyIndex = assemblyIndex } @@ -31,8 +38,8 @@ class ArcadeDriveBehavior extends Behavior { } public Update(_: number): void { - const driveInput = InputSystem.getInput("arcadeDrive", this._assemblyName, this._assemblyIndex); - const turnInput = InputSystem.getInput("arcadeTurn", this._assemblyName, this._assemblyIndex); + const driveInput = InputSystem.getInput("arcadeDrive", this._assemblyName, this._assemblyIndex) + const turnInput = InputSystem.getInput("arcadeTurn", this._assemblyName, this._assemblyIndex) this.DriveSpeeds(driveInput * this._driveSpeed, turnInput * this._turnSpeed) } diff --git a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts index 1cb799ee15..fc9e9bbd5d 100644 --- a/fission/src/systems/simulation/behavior/GenericArmBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericArmBehavior.ts @@ -11,7 +11,13 @@ class GenericArmBehavior extends Behavior { private _rotationalSpeed = 6 - constructor(hingeDriver: HingeDriver, hingeStimulus: HingeStimulus, jointIndex: number, assemblyName: string, assemblyIndex: number) { + constructor( + hingeDriver: HingeDriver, + hingeStimulus: HingeStimulus, + jointIndex: number, + assemblyName: string, + assemblyIndex: number + ) { super([hingeDriver], [hingeStimulus]) this._hingeDriver = hingeDriver diff --git a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts index 6f057d1daf..f1c01bde56 100644 --- a/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts +++ b/fission/src/systems/simulation/behavior/GenericElevatorBehavior.ts @@ -11,7 +11,13 @@ class GenericElevatorBehavior extends Behavior { private _linearSpeed = 2.5 - constructor(sliderDriver: SliderDriver, sliderStimulus: SliderStimulus, jointIndex: number, assemblyName: string, assemblyIndex: number) { + constructor( + sliderDriver: SliderDriver, + sliderStimulus: SliderStimulus, + jointIndex: number, + assemblyName: string, + assemblyIndex: number + ) { super([sliderDriver], [sliderStimulus]) this._sliderDriver = sliderDriver @@ -26,7 +32,9 @@ class GenericElevatorBehavior extends Behavior { } public Update(_: number): void { - this.moveElevator(InputSystem.getInput(this._inputName, this._assemblyName, this._assemblyIndex) * this._linearSpeed) + this.moveElevator( + InputSystem.getInput(this._inputName, this._assemblyName, this._assemblyIndex) * this._linearSpeed + ) } } diff --git a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts index b6d890e1e7..2efaed27fe 100644 --- a/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts +++ b/fission/src/systems/simulation/synthesis_brain/SynthesisBrain.ts @@ -39,7 +39,7 @@ class SynthesisBrain extends Brain { public constructor(mechanism: Mechanism, assemblyName: string) { super(mechanism) - + this._simLayer = World.SimulationSystem.GetSimulationLayer(mechanism)! this._assemblyName = assemblyName @@ -51,19 +51,17 @@ class SynthesisBrain extends Brain { // Only adds controls to mechanisms that are controllable (ignores fields) if (mechanism.controllable) { if (SynthesisBrain.numberRobotsSpawned[assemblyName] == undefined) - SynthesisBrain.numberRobotsSpawned[assemblyName] = 0 - else - SynthesisBrain.numberRobotsSpawned[assemblyName]++ + SynthesisBrain.numberRobotsSpawned[assemblyName] = 0 + else SynthesisBrain.numberRobotsSpawned[assemblyName]++ this._assemblyIndex = SynthesisBrain.numberRobotsSpawned[assemblyName] SynthesisBrain.robotsSpawned.push(`[${this._assemblyIndex}] ${assemblyName}`) - + this.configureArcadeDriveBehavior() this.configureArmBehaviors() this.configureElevatorBehaviors() this.configureInputs() - } - else { + } else { this.configureField() } @@ -81,8 +79,8 @@ class SynthesisBrain extends Brain { } public clearControls(): void { - const index = SynthesisBrain.robotsSpawned.indexOf(`[${this._assemblyIndex}] ${this._assemblyName}`); - SynthesisBrain.robotsSpawned.splice(index, 1); + const index = SynthesisBrain.robotsSpawned.indexOf(`[${this._assemblyIndex}] ${this._assemblyName}`) + SynthesisBrain.robotsSpawned.splice(index, 1) } // Creates an instance of ArcadeDriveBehavior and automatically configures it @@ -126,7 +124,14 @@ class SynthesisBrain extends Brain { } this._behaviors.push( - new ArcadeDriveBehavior(leftWheels, rightWheels, leftStimuli, rightStimuli, this._assemblyName, this._assemblyIndex) + new ArcadeDriveBehavior( + leftWheels, + rightWheels, + leftStimuli, + rightStimuli, + this._assemblyName, + this._assemblyIndex + ) ) } @@ -141,7 +146,13 @@ class SynthesisBrain extends Brain { for (let i = 0; i < hingeDrivers.length; i++) { this._behaviors.push( - new GenericArmBehavior(hingeDrivers[i], hingeStimuli[i], this._currentJointIndex, this._assemblyName, this._assemblyIndex) + new GenericArmBehavior( + hingeDrivers[i], + hingeStimuli[i], + this._currentJointIndex, + this._assemblyName, + this._assemblyIndex + ) ) this._currentJointIndex++ } @@ -208,10 +219,10 @@ class SynthesisBrain extends Brain { } private configureField() { - const fieldPrefs = PreferencesSystem.getFieldPreferences(this._assemblyName) - console.log("Loaded field prefs " + fieldPrefs) + const fieldPrefs = PreferencesSystem.getFieldPreferences(this._assemblyName) + console.log("Loaded field prefs " + fieldPrefs) - /** Put any scoring zone or other field configuration here */ + /** Put any scoring zone or other field configuration here */ } private static parseInputs(rawInputs: InputScheme) { diff --git a/fission/src/ui/components/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 8e3a2a452d..c68ca28bb5 100644 --- a/fission/src/ui/components/MainHUD.tsx +++ b/fission/src/ui/components/MainHUD.tsx @@ -131,9 +131,7 @@ const MainHUD: React.FC = () => { } - onClick={() => - (PreferencesSystem.clearPreferences()) - } + onClick={() => PreferencesSystem.clearPreferences()} /> { addToast(type, type, "This is a test toast to test the toast system") }} /> - } - onClick={() => openModal("config-robot")} - /> + } onClick={() => openModal("config-robot")} /> {userInfo ? ( = ({ modalId }) => { // If there is a robot spawned, set it as the selected robot if (selectedScheme == null && Object.keys(PreferencesSystem.getAllRobotPreferences()).length > 0) { setTimeout(() => { - if (!InputSystem.selectedScheme) - InputSystem.selectedScheme = Object.values(PreferencesSystem.getAllRobotPreferences())[0].inputsSchemes[0] + if (!InputSystem.selectedScheme) + InputSystem.selectedScheme = Object.values( + PreferencesSystem.getAllRobotPreferences() + )[0].inputsSchemes[0] setUseButtons({}) setSelectedScheme(InputSystem.selectedScheme) diff --git a/fission/src/ui/modals/configuring/ConfigureRobotModal.tsx b/fission/src/ui/modals/configuring/ConfigureRobotModal.tsx index 74ac80b92c..fd3cbd7b5d 100644 --- a/fission/src/ui/modals/configuring/ConfigureRobotModal.tsx +++ b/fission/src/ui/modals/configuring/ConfigureRobotModal.tsx @@ -35,4 +35,4 @@ export const ConfigureRobotModal: React.FC = ({ modalId }) => { ) } -export default ConfigureRobotModal \ No newline at end of file +export default ConfigureRobotModal diff --git a/fission/src/ui/modals/configuring/SettingsModal.tsx b/fission/src/ui/modals/configuring/SettingsModal.tsx index 4fb19777df..d3f9cb42b7 100644 --- a/fission/src/ui/modals/configuring/SettingsModal.tsx +++ b/fission/src/ui/modals/configuring/SettingsModal.tsx @@ -25,13 +25,25 @@ const SettingsModal: React.FC = ({ modalId }) => { const { openModal } = useModalControlContext() const [screenMode, setScreenMode] = useState(PreferencesSystem.getGlobalPreference("ScreenMode")) - const [qualitySettings, setQualitySettings] = useState(PreferencesSystem.getGlobalPreference("QualitySettings")) - const [zoomSensitivity, setZoomSensitivity] = useState(PreferencesSystem.getGlobalPreference("ZoomSensitivity")) - const [pitchSensitivity, setPitchSensitivity] = useState(PreferencesSystem.getGlobalPreference("PitchSensitivity")) - const [yawSensitivity, setYawSensitivity] = useState(PreferencesSystem.getGlobalPreference("YawSensitivity")) - const [reportAnalytics, setReportAnalytics] = useState(PreferencesSystem.getGlobalPreference("ReportAnalytics")) + const [qualitySettings, setQualitySettings] = useState( + PreferencesSystem.getGlobalPreference("QualitySettings") + ) + const [zoomSensitivity, setZoomSensitivity] = useState( + PreferencesSystem.getGlobalPreference("ZoomSensitivity") + ) + const [pitchSensitivity, setPitchSensitivity] = useState( + PreferencesSystem.getGlobalPreference("PitchSensitivity") + ) + const [yawSensitivity, setYawSensitivity] = useState( + PreferencesSystem.getGlobalPreference("YawSensitivity") + ) + const [reportAnalytics, setReportAnalytics] = useState( + PreferencesSystem.getGlobalPreference("ReportAnalytics") + ) const [useMetric, setUseMetric] = useState(PreferencesSystem.getGlobalPreference("UseMetric")) - const [renderScoringZones, setRenderScoringZones] = useState(PreferencesSystem.getGlobalPreference("RenderScoringZones")) + const [renderScoringZones, setRenderScoringZones] = useState( + PreferencesSystem.getGlobalPreference("RenderScoringZones") + ) const saveSettings = () => { PreferencesSystem.setGlobalPreference("ScreenMode", screenMode) @@ -42,7 +54,7 @@ const SettingsModal: React.FC = ({ modalId }) => { PreferencesSystem.setGlobalPreference("ReportAnalytics", reportAnalytics) PreferencesSystem.setGlobalPreference("UseMetric", useMetric) PreferencesSystem.setGlobalPreference("RenderScoringZones", renderScoringZones) - + PreferencesSystem.savePreferences() } @@ -58,7 +70,10 @@ const SettingsModal: React.FC = ({ modalId }) => { ("ScreenMode"))} + options={moveElementToTop( + screenModeOptions, + PreferencesSystem.getGlobalPreference("ScreenMode") + )} onSelect={selected => { setScreenMode(selected) }} diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index e722a0c181..90091609df 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -54,14 +54,14 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open selectedZone.diameter = scale.x selectedZone.position = [position.x, position.y, position.z] - + selectedZone.parentBody = bodyAttachmentRef.current PreferencesSystem.savePreferences() } useEffect(() => { - setupGizmo() + setupGizmo() }) return ( diff --git a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx index a2b695b732..281e547b60 100644 --- a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx @@ -30,7 +30,7 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL if (transformGizmoRef.current == undefined) { transformGizmoRef.current = new TransformGizmos( - new THREE.Mesh(new THREE.BoxGeometry(0.5, 2.0), new THREE.MeshBasicMaterial({ color: 0xffffff })) + new THREE.Mesh(new THREE.BoxGeometry(0.25, 1.0, 0.25), new THREE.MeshBasicMaterial({ color: 0xffffff })) ) transformGizmoRef.current.AddMeshToScene() transformGizmoRef.current.CreateGizmo("translate", 1.5) @@ -41,7 +41,9 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL const direction = selectedEjector.direction transformGizmoRef.current?.mesh.position.set(position[0], position[1], position[2]) - transformGizmoRef.current?.mesh.rotation.setFromQuaternion(new THREE.Quaternion(direction[0], direction[1], direction[2], direction[3])) + transformGizmoRef.current?.mesh.rotation.setFromQuaternion( + new THREE.Quaternion(direction[0], direction[1], direction[2], direction[3]) + ) } // Saves zone preferences to local storage @@ -93,7 +95,10 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL value={robot} onClick={() => { setSelectedEjector(PreferencesSystem.getRobotPreferences(robot)?.ejector) - setEjectorVelocity(PreferencesSystem.getRobotPreferences(robot)?.ejector.ejectorVelocity ?? MIN_VELOCITY) + setEjectorVelocity( + PreferencesSystem.getRobotPreferences(robot)?.ejector.ejectorVelocity ?? + MIN_VELOCITY + ) }} key={robot} > @@ -116,9 +121,7 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL defaultValue={selectedEjector.ejectorVelocity} label="Velocity" format={{ minimumFractionDigits: 2, maximumFractionDigits: 2 }} - onChange={(vel: number) => - setEjectorVelocity(vel) - } + onChange={(vel: number) => setEjectorVelocity(vel)} step={0.01} /> From f7a9c9085ad0a1aee941f114ce082ae6fc5662dd Mon Sep 17 00:00:00 2001 From: arorad1 Date: Thu, 11 Jul 2024 18:41:51 -0700 Subject: [PATCH 14/33] Created a working formula to calculate the position of the pickup object need to optimize the code and add documentation --- fission/src/mirabuf/MirabufSceneObject.ts | 34 +++++-- .../systems/preferences/PreferenceTypes.ts | 3 +- .../ConfigureGamepiecePickupPanel.tsx | 91 +++++++++++++++---- 3 files changed, 101 insertions(+), 27 deletions(-) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index b623823aaf..faa91f2bf6 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -12,6 +12,8 @@ import Mechanism from "@/systems/physics/Mechanism" import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" import InputSystem from "@/systems/input/InputSystem" import TransformGizmos from "@/ui/components/TransformGizmos" +import { EjectorPreferences, IntakePreferences } from "@/systems/preferences/PreferenceTypes" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" const DEBUG_BODIES = true @@ -27,11 +29,14 @@ class MirabufSceneObject extends SceneObject { private _brain: SynthesisBrain | undefined private _debugBodies: Map | null - private _physicsLayerReserve: LayerReserve | undefined = undefined + private _physicsLayerReserve: LayerReserve | undefined - private _transformGizmos: TransformGizmos | undefined = undefined + private _transformGizmos: TransformGizmos | undefined private _deleteGizmoOnEscape: boolean = true + private _intakePreferences: IntakePreferences | undefined + private _ejectorPreferences: EjectorPreferences | undefined + get mirabufInstance() { return this._mirabufInstance } @@ -40,6 +45,18 @@ class MirabufSceneObject extends SceneObject { return this._mechanism } + get assemblyName() { + return this._assemblyName + } + + get intakePreferences() { + return this._intakePreferences + } + + get ejectorPreferences() { + return this._ejectorPreferences + } + public constructor(mirabufInstance: MirabufInstance, assemblyName: string) { super() @@ -54,6 +71,8 @@ class MirabufSceneObject extends SceneObject { this._debugBodies = null this.EnableTransformControls() // adding transform gizmo to mirabuf object on its creation + + this.getPreferences() } public Setup(): void { @@ -85,6 +104,8 @@ class MirabufSceneObject extends SceneObject { } public Update(): void { + // console.log(World.PhysicsSystem.GetBody(this.GetRootNodeId()!).GetPosition().GetZ()) + this._mirabufInstance.parser.rigidNodes.forEach(rn => { if (!this._mirabufInstance.meshes.size) return // if this.dispose() has been ran then return const body = World.PhysicsSystem.GetBody(this._mechanism.GetBodyByNodeId(rn.id)!) @@ -168,10 +189,6 @@ class MirabufSceneObject extends SceneObject { this._brain?.clearControls() } - public GetRootNodeId(): Jolt.BodyID | undefined { - return this._mechanism.nodeToBody.get(this._mechanism.rootBody) - } - private CreateMeshForShape(shape: Jolt.Shape): THREE.Mesh { const scale = new JOLT.Vec3(1, 1, 1) const triangleContext = new JOLT.ShapeGetTriangles( @@ -218,6 +235,11 @@ class MirabufSceneObject extends SceneObject { this.DisablePhysics() } + private getPreferences(): void { + this._intakePreferences = PreferencesSystem.getRobotPreferences(this.assemblyName)?.intake + this._ejectorPreferences = PreferencesSystem.getRobotPreferences(this.assemblyName)?.ejector + } + private EnablePhysics() { this._mirabufInstance.parser.rigidNodes.forEach(rn => { World.PhysicsSystem.EnablePhysicsForBody(this._mechanism.GetBodyByNodeId(rn.id)!) diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index 1934bfe2c9..c5ff1c4cf9 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -28,6 +28,7 @@ export const DefaultGlobalPreferences: { [key: string]: unknown } = { export type IntakePreferences = { position: [number, number, number] + rotation: number diameter: number parentBody: Jolt.Body | undefined } @@ -67,7 +68,7 @@ export type FieldPreferences = { export function DefaultRobotPreferences(): RobotPreferences { return { inputsSchemes: [], - intake: { position: [0, 0, 0], diameter: 1, parentBody: undefined }, + intake: { position: [0, 0, 0], rotation: 0, diameter: 1, parentBody: undefined }, ejector: { position: [0, 0, 0], direction: [0, 0, 0, 0], ejectorVelocity: 1, parentBody: undefined }, } } diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index 90091609df..aaa3781744 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -7,26 +7,24 @@ import TransformGizmos from "@/ui/components/TransformGizmos" import World from "@/systems/World" import Slider from "@/ui/components/Slider" import Jolt from "@barclah/jolt-physics" -import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" import Label from "@/ui/components/Label" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import { IntakePreferences } from "@/systems/preferences/PreferenceTypes" import Button from "@/ui/components/Button" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" // slider constants const MIN_ZONE_SIZE = 0.1 const MAX_ZONE_SIZE = 1.0 const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, openLocation, sidePadding }) => { - // const [, setNode] = useState("Click to select") const transformGizmoRef = useRef() const bodyAttachmentRef = useRef() - const [selectedZone, setSelectedZone] = useState(undefined) + const [selectedRobot, setSelectedRobot] = useState(undefined) // creating mesh & gizmo for the pickup node const setupGizmo = () => { - if (selectedZone == undefined) return + if (!selectedRobot?.intakePreferences) return if (transformGizmoRef.current == undefined) { transformGizmoRef.current = new TransformGizmos( @@ -36,29 +34,81 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open transformGizmoRef.current.CreateGizmo("translate") } - const scale = selectedZone.diameter - const position = selectedZone.position - - transformGizmoRef.current?.mesh.scale.set(scale, scale, scale) - transformGizmoRef.current?.mesh.position.set(position[0], position[1], position[2]) + /* setting position and scale of the mesh in relation to the position, scale, and rotation of the robot */ + const scale = selectedRobot.intakePreferences.diameter + const robotPosition = World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetPosition() + const theta = calculateRobotAngle() + + const calculatedX = - Math.cos(theta) * selectedRobot.intakePreferences.position[0] + Math.sin(theta) * selectedRobot.intakePreferences.position[2] + const calculatedZ = Math.sin(theta) * selectedRobot.intakePreferences.position[0] + Math.cos(theta) * selectedRobot.intakePreferences.position[2] + + const position = [ + robotPosition.GetX() + calculatedX, + robotPosition.GetY() + selectedRobot.intakePreferences.position[1], + robotPosition.GetZ() + calculatedZ, + ] + + transformGizmoRef.current.mesh.scale.set(scale, scale, scale) + transformGizmoRef.current.mesh.position.set(position[0], position[1], position[2]) } // Saves zone preferences to local storage const saveZonePreferences = () => { - if (selectedZone == undefined) return + if (!selectedRobot?.intakePreferences) return const scale = transformGizmoRef.current?.mesh.scale const position = transformGizmoRef.current?.mesh.position if (scale == undefined || position == undefined) return - selectedZone.diameter = scale.x - selectedZone.position = [position.x, position.y, position.z] + selectedRobot.intakePreferences.diameter = scale.x + const robotPosition = World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetPosition() + + selectedRobot.intakePreferences.rotation = calculateRobotAngle() + selectedRobot.intakePreferences.position = [ + position.x - robotPosition.GetX(), + position.y - robotPosition.GetY(), + position.z - robotPosition.GetZ(), + ] - selectedZone.parentBody = bodyAttachmentRef.current + const theta = selectedRobot.intakePreferences.rotation + const calculatedX = Math.cos(theta) * selectedRobot.intakePreferences.position[0] - Math.sin(theta) * selectedRobot.intakePreferences.position[2] + const calculatedZ = Math.sin(theta) * selectedRobot.intakePreferences.position[0] + Math.cos(theta) * selectedRobot.intakePreferences.position[2] + selectedRobot.intakePreferences.position = [ + calculatedX, + selectedRobot.intakePreferences.position[1], + calculatedZ + ] + + selectedRobot.intakePreferences.parentBody = bodyAttachmentRef.current PreferencesSystem.savePreferences() } + + const calculateRobotAngle = () => { + const robotRotationY = World.PhysicsSystem.GetBody(selectedRobot!.GetRootNodeId()!).GetRotation().GetEulerAngles().GetY() + const robotRotationZ = Math.abs(World.PhysicsSystem.GetBody(selectedRobot!.GetRootNodeId()!).GetRotation().GetEulerAngles().GetZ()) + + if (robotRotationY > 0 && robotRotationZ < 2) { + return robotRotationY + } else if (robotRotationY > 0 && robotRotationZ > 2) { + return Math.PI - robotRotationY + } else if (robotRotationY < 0 && robotRotationZ > 2) { + return Math.PI - robotRotationY + } else { + return 2 * Math.PI + robotRotationY + } + } + + const listRobots = () => { + const assemblies = [...World.SceneRenderer.sceneObjects.values()].filter(x => { + return x instanceof MirabufSceneObject + }) + // .filter(x => { + // return (x as MirabufSceneObject).assemblyType == "robot" + // }) + return assemblies + } useEffect(() => { setupGizmo() @@ -80,23 +130,24 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() }} > - {selectedZone == undefined ? ( + {selectedRobot?.intakePreferences == undefined ? ( <> {/** Scroll view for selecting a robot to configure */}
- {Object.keys(SynthesisBrain.numberRobotsSpawned).map(robot => { + {listRobots().map(mirabufSceneObject => { return ( ) })}
+ {/* TODO: remove the accept button on this version */} ) : ( <> @@ -110,7 +161,7 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open { From 1e749855c18906faf63e05e6069ddd299d3fe1b6 Mon Sep 17 00:00:00 2001 From: arorad1 Date: Thu, 11 Jul 2024 19:21:32 -0700 Subject: [PATCH 15/33] Add documentation + fixed the fields showing in the select panel + optimized calculations --- .../ConfigureGamepiecePickupPanel.tsx | 87 ++++++++++++------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index aaa3781744..361b1fe0ca 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -22,8 +22,13 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open const [selectedRobot, setSelectedRobot] = useState(undefined) - // creating mesh & gizmo for the pickup node - const setupGizmo = () => { + /** + * Creating a mesh to mimic the pickup node + * - adds a transform gizmo to the Mesh + * - sets the position of the mesh in relation to the position and rotation of the robot + * - sets the scale of the mesh to the previously saved configuration + */ + const setupGizmo = (): void => { if (!selectedRobot?.intakePreferences) return if (transformGizmoRef.current == undefined) { @@ -38,10 +43,16 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open const scale = selectedRobot.intakePreferences.diameter const robotPosition = World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetPosition() const theta = calculateRobotAngle() - - const calculatedX = - Math.cos(theta) * selectedRobot.intakePreferences.position[0] + Math.sin(theta) * selectedRobot.intakePreferences.position[2] - const calculatedZ = Math.sin(theta) * selectedRobot.intakePreferences.position[0] + Math.cos(theta) * selectedRobot.intakePreferences.position[2] + // Re-calculating the position of the pickup node in relation to the robot based on the robot's local rotation and position + const calculatedX = + -Math.cos(theta) * selectedRobot.intakePreferences.position[0] + + Math.sin(theta) * selectedRobot.intakePreferences.position[2] + const calculatedZ = + Math.sin(theta) * selectedRobot.intakePreferences.position[0] + + Math.cos(theta) * selectedRobot.intakePreferences.position[2] + + // Calculating the position of the pickup mesh relative to the robot const position = [ robotPosition.GetX() + calculatedX, robotPosition.GetY() + selectedRobot.intakePreferences.position[1], @@ -52,61 +63,71 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open transformGizmoRef.current.mesh.position.set(position[0], position[1], position[2]) } - // Saves zone preferences to local storage - const saveZonePreferences = () => { + /** + * Saves pickup configuration preferences to local storage + */ + const savePickupPreferences = (): void => { if (!selectedRobot?.intakePreferences) return const scale = transformGizmoRef.current?.mesh.scale const position = transformGizmoRef.current?.mesh.position + const robotPosition = World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetPosition() if (scale == undefined || position == undefined) return selectedRobot.intakePreferences.diameter = scale.x - const robotPosition = World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetPosition() - selectedRobot.intakePreferences.rotation = calculateRobotAngle() - selectedRobot.intakePreferences.position = [ - position.x - robotPosition.GetX(), - position.y - robotPosition.GetY(), - position.z - robotPosition.GetZ(), - ] - const theta = selectedRobot.intakePreferences.rotation - const calculatedX = Math.cos(theta) * selectedRobot.intakePreferences.position[0] - Math.sin(theta) * selectedRobot.intakePreferences.position[2] - const calculatedZ = Math.sin(theta) * selectedRobot.intakePreferences.position[0] + Math.cos(theta) * selectedRobot.intakePreferences.position[2] - selectedRobot.intakePreferences.position = [ - calculatedX, - selectedRobot.intakePreferences.position[1], - calculatedZ - ] + // resetting the position of the pickup node in relation to the robot at the default position it faces + const calculatedX = + Math.cos(selectedRobot.intakePreferences.rotation) * (position.x - robotPosition.GetX()) - + Math.sin(selectedRobot.intakePreferences.rotation) * (position.z - robotPosition.GetZ()) + const calculatedZ = + Math.sin(selectedRobot.intakePreferences.rotation) * (position.x - robotPosition.GetX()) + + Math.cos(selectedRobot.intakePreferences.rotation) * (position.z - robotPosition.GetZ()) + selectedRobot.intakePreferences.position = [calculatedX, position.y - robotPosition.GetY(), calculatedZ] selectedRobot.intakePreferences.parentBody = bodyAttachmentRef.current PreferencesSystem.savePreferences() } - - const calculateRobotAngle = () => { - const robotRotationY = World.PhysicsSystem.GetBody(selectedRobot!.GetRootNodeId()!).GetRotation().GetEulerAngles().GetY() - const robotRotationZ = Math.abs(World.PhysicsSystem.GetBody(selectedRobot!.GetRootNodeId()!).GetRotation().GetEulerAngles().GetZ()) + + /** + * @returns The angle of the robot in radians + */ + const calculateRobotAngle = (): number => { + const robotRotation = World.PhysicsSystem.GetBody(selectedRobot!.GetRootNodeId()!) + .GetRotation() + .GetEulerAngles() + const robotRotationY = robotRotation.GetY() + const robotRotationZ = Math.abs(robotRotation.GetZ()) if (robotRotationY > 0 && robotRotationZ < 2) { + // if the robot is between 0 - pi/2 rotation on a cartesian plane return robotRotationY } else if (robotRotationY > 0 && robotRotationZ > 2) { + // if the robot is between pi/2 - pi rotation return Math.PI - robotRotationY } else if (robotRotationY < 0 && robotRotationZ > 2) { + // if the robot is between pi - 3pi/2 rotation return Math.PI - robotRotationY } else { + // if the robot is between 3pi/2 - 2pi rotation return 2 * Math.PI + robotRotationY } } - const listRobots = () => { + /** + * @returns A list of all robots as MirabufSceneObjects + */ + const listRobots = (): MirabufSceneObject[] => { + // filtering out robots that are not dynamic and not MirabufSceneObjects const assemblies = [...World.SceneRenderer.sceneObjects.values()].filter(x => { - return x instanceof MirabufSceneObject - }) - // .filter(x => { - // return (x as MirabufSceneObject).assemblyType == "robot" - // }) + return ( + x instanceof MirabufSceneObject && + World.PhysicsSystem.GetBody((x as MirabufSceneObject).GetRootNodeId()!).IsDynamic() + ) + }) as MirabufSceneObject[] return assemblies } @@ -124,7 +145,7 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open onAccept={() => { if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() - saveZonePreferences() + savePickupPreferences() }} onCancel={() => { if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() From 25422d4770836b3f4f604010b1150c115a3be9ec Mon Sep 17 00:00:00 2001 From: arorad1 Date: Fri, 12 Jul 2024 11:52:31 -0700 Subject: [PATCH 16/33] Fixed filtering robots and fields + reimplemented the selectNode feature --- fission/src/mirabuf/MirabufSceneObject.ts | 5 ++++ fission/src/ui/components/SelectButton.tsx | 17 +++++++----- .../ConfigureGamepiecePickupPanel.tsx | 27 +++++++++++++++---- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index faa91f2bf6..4284d53c2c 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -14,6 +14,7 @@ import InputSystem from "@/systems/input/InputSystem" import TransformGizmos from "@/ui/components/TransformGizmos" import { EjectorPreferences, IntakePreferences } from "@/systems/preferences/PreferenceTypes" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import { MiraType } from "./MirabufLoader" const DEBUG_BODIES = true @@ -57,6 +58,10 @@ class MirabufSceneObject extends SceneObject { return this._ejectorPreferences } + public get miraType(): MiraType { + return this._mirabufInstance.parser.assembly.dynamic ? MiraType.ROBOT : MiraType.FIELD + } + public constructor(mirabufInstance: MirabufInstance, assemblyName: string) { super() diff --git a/fission/src/ui/components/SelectButton.tsx b/fission/src/ui/components/SelectButton.tsx index 3764e62d1d..85f75d7392 100644 --- a/fission/src/ui/components/SelectButton.tsx +++ b/fission/src/ui/components/SelectButton.tsx @@ -18,7 +18,6 @@ function SelectNode(e: MouseEvent) { const res = World.PhysicsSystem.RayCast(ThreeVector3_JoltVec3(origin), ThreeVector3_JoltVec3(dir)) if (res) { - console.log(res) const body = World.PhysicsSystem.GetBody(res.data.mBodyID) if (!body.IsDynamic()) { return null @@ -33,7 +32,7 @@ type SelectButtonProps = { colorClass?: string size?: ButtonSize placeholder?: string - onSelect?: (value: Jolt.Body) => void + onSelect?: (value: Jolt.Body) => boolean className?: string } @@ -44,11 +43,15 @@ const SelectButton: React.FC = ({ colorClass, size, placehold const onReceiveSelection = useCallback( (value: Jolt.Body) => { - // TODO remove this when communication works - clearTimeout(timeoutRef.current) - setValue("Node") - setSelecting(false) - if (onSelect) onSelect(value) + if (onSelect) { + if (onSelect(value)) { + setValue("Node") + clearTimeout(timeoutRef.current) + setSelecting(false) + } else { + setSelecting(true) + } + } }, [setValue, setSelecting, onSelect] ) diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index 361b1fe0ca..05e89f2cc8 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -11,6 +11,7 @@ import Label from "@/ui/components/Label" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import Button from "@/ui/components/Button" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import { MiraType } from "@/mirabuf/MirabufLoader" // slider constants const MIN_ZONE_SIZE = 0.1 @@ -123,14 +124,30 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open const listRobots = (): MirabufSceneObject[] => { // filtering out robots that are not dynamic and not MirabufSceneObjects const assemblies = [...World.SceneRenderer.sceneObjects.values()].filter(x => { - return ( - x instanceof MirabufSceneObject && - World.PhysicsSystem.GetBody((x as MirabufSceneObject).GetRootNodeId()!).IsDynamic() - ) + if (x instanceof MirabufSceneObject) { + return x.miraType === MiraType.ROBOT + } + return false }) as MirabufSceneObject[] return assemblies } + /** + * Checks if the body is a child of the selected MirabufSceneObject + * + * @param body The Jolt body to check if it is a child of the selected robot + */ + const checkSelectedNode = (body: Jolt.Body): boolean => { + let returnValue = false + selectedRobot?.mirabufInstance?.parser.rigidNodes.forEach(rn => { + if (World.PhysicsSystem.GetBody(selectedRobot.mechanism.GetBodyByNodeId(rn.id)!).GetID() === body.GetID()) { + bodyAttachmentRef.current = body + returnValue = true + } + }) + return returnValue + } + useEffect(() => { setupGizmo() }) @@ -175,7 +192,7 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open {/* Button for user to select pickup node */} (bodyAttachmentRef.current = body)} + onSelect={(body: Jolt.Body) => checkSelectedNode(body)} /> {/* Slider for user to set size of pickup configuration */} From bb15c58879acc67aa16db18a7993e778a5417df3 Mon Sep 17 00:00:00 2001 From: arorad1 Date: Fri, 12 Jul 2024 14:30:11 -0700 Subject: [PATCH 17/33] Optimized getting rotation and added implementation for configuring the shot trajectory Rotating the shot trajectory rectangular prism is currently not functional --- fission/src/mirabuf/MirabufSceneObject.ts | 2 - .../systems/preferences/PreferenceTypes.ts | 6 +- .../ConfigureGamepiecePickupPanel.tsx | 32 ++-- .../ConfigureShotTrajectoryPanel.tsx | 140 +++++++++++++++--- 4 files changed, 129 insertions(+), 51 deletions(-) diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 4284d53c2c..b0332f431d 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -109,8 +109,6 @@ class MirabufSceneObject extends SceneObject { } public Update(): void { - // console.log(World.PhysicsSystem.GetBody(this.GetRootNodeId()!).GetPosition().GetZ()) - this._mirabufInstance.parser.rigidNodes.forEach(rn => { if (!this._mirabufInstance.meshes.size) return // if this.dispose() has been ran then return const body = World.PhysicsSystem.GetBody(this._mechanism.GetBodyByNodeId(rn.id)!) diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index c5ff1c4cf9..aa8e0b80aa 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -28,7 +28,6 @@ export const DefaultGlobalPreferences: { [key: string]: unknown } = { export type IntakePreferences = { position: [number, number, number] - rotation: number diameter: number parentBody: Jolt.Body | undefined } @@ -36,6 +35,7 @@ export type IntakePreferences = { export type EjectorPreferences = { position: [number, number, number] direction: [number, number, number, number] + relativeRotation: number ejectorVelocity: number parentBody: Jolt.Body | undefined } @@ -68,8 +68,8 @@ export type FieldPreferences = { export function DefaultRobotPreferences(): RobotPreferences { return { inputsSchemes: [], - intake: { position: [0, 0, 0], rotation: 0, diameter: 1, parentBody: undefined }, - ejector: { position: [0, 0, 0], direction: [0, 0, 0, 0], ejectorVelocity: 1, parentBody: undefined }, + intake: { position: [0, 0, 0], diameter: 1, parentBody: undefined }, + ejector: { position: [0, 0, 0], direction: [0, 0, 0, 0], relativeRotation: 0, ejectorVelocity: 1, parentBody: undefined }, } } diff --git a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx index 05e89f2cc8..bf0d86d8cb 100644 --- a/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureGamepiecePickupPanel.tsx @@ -12,6 +12,7 @@ import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import Button from "@/ui/components/Button" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import { MiraType } from "@/mirabuf/MirabufLoader" +import JOLT from "@/util/loading/JoltSyncLoader" // slider constants const MIN_ZONE_SIZE = 0.1 @@ -77,15 +78,15 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open if (scale == undefined || position == undefined) return selectedRobot.intakePreferences.diameter = scale.x - selectedRobot.intakePreferences.rotation = calculateRobotAngle() + const theta = calculateRobotAngle() // resetting the position of the pickup node in relation to the robot at the default position it faces const calculatedX = - Math.cos(selectedRobot.intakePreferences.rotation) * (position.x - robotPosition.GetX()) - - Math.sin(selectedRobot.intakePreferences.rotation) * (position.z - robotPosition.GetZ()) + Math.cos(theta) * (position.x - robotPosition.GetX()) - + Math.sin(theta) * (position.z - robotPosition.GetZ()) const calculatedZ = - Math.sin(selectedRobot.intakePreferences.rotation) * (position.x - robotPosition.GetX()) + - Math.cos(selectedRobot.intakePreferences.rotation) * (position.z - robotPosition.GetZ()) + Math.sin(theta) * (position.x - robotPosition.GetX()) + + Math.cos(theta) * (position.z - robotPosition.GetZ()) selectedRobot.intakePreferences.position = [calculatedX, position.y - robotPosition.GetY(), calculatedZ] selectedRobot.intakePreferences.parentBody = bodyAttachmentRef.current @@ -97,24 +98,11 @@ const ConfigureGamepiecePickupPanel: React.FC = ({ panelId, open * @returns The angle of the robot in radians */ const calculateRobotAngle = (): number => { - const robotRotation = World.PhysicsSystem.GetBody(selectedRobot!.GetRootNodeId()!) - .GetRotation() - .GetEulerAngles() - const robotRotationY = robotRotation.GetY() - const robotRotationZ = Math.abs(robotRotation.GetZ()) - - if (robotRotationY > 0 && robotRotationZ < 2) { - // if the robot is between 0 - pi/2 rotation on a cartesian plane - return robotRotationY - } else if (robotRotationY > 0 && robotRotationZ > 2) { - // if the robot is between pi/2 - pi rotation - return Math.PI - robotRotationY - } else if (robotRotationY < 0 && robotRotationZ > 2) { - // if the robot is between pi - 3pi/2 rotation - return Math.PI - robotRotationY + const robotRotation = World.PhysicsSystem.GetBody(selectedRobot!.GetRootNodeId()!).GetRotation().GetRotationAngle(new JOLT.Vec3(0, 1, 0)) // getting the rotation of the robot on the Y axis + if (robotRotation > 0) { + return robotRotation } else { - // if the robot is between 3pi/2 - 2pi rotation - return 2 * Math.PI + robotRotationY + return 2 * Math.PI + robotRotation } } diff --git a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx index 281e547b60..9eeba751b3 100644 --- a/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx +++ b/fission/src/ui/panels/configuring/ConfigureShotTrajectoryPanel.tsx @@ -6,11 +6,14 @@ import SelectButton from "@/components/SelectButton" import TransformGizmos from "@/ui/components/TransformGizmos" import Slider from "@/ui/components/Slider" import Jolt from "@barclah/jolt-physics" -import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" import Label from "@/ui/components/Label" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" -import { EjectorPreferences } from "@/systems/preferences/PreferenceTypes" import Button from "@/ui/components/Button" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import World from "@/systems/World" +import { MiraType } from "@/mirabuf/MirabufLoader" +import JOLT from "@/util/loading/JoltSyncLoader" +import { JoltQuat_ThreeQuaternion, ThreeQuaternion_JoltQuat } from "@/util/TypeConversions" // slider constants const MIN_VELOCITY = 0.1 @@ -21,12 +24,11 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL const transformGizmoRef = useRef() const bodyAttachmentRef = useRef() - const [selectedEjector, setSelectedEjector] = useState(undefined) - const [ejectorVelocity, setEjectorVelocity] = useState(0) + const [selectedRobot, setSelectedRobot] = useState(undefined) // creating mesh & gizmo for the pickup node const setupGizmo = () => { - if (selectedEjector == undefined) return + if (!selectedRobot?.ejectorPreferences) return if (transformGizmoRef.current == undefined) { transformGizmoRef.current = new TransformGizmos( @@ -37,33 +39,122 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL transformGizmoRef.current.CreateGizmo("rotate", 2.0) } - const position = selectedEjector.position - const direction = selectedEjector.direction + + const robotPosition = World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetPosition() + const theta = calculateRobotAngle() + + // Re-calculating the position of the pickup node in relation to the robot based on the robot's local rotation and position + const calculatedX = + -Math.cos(theta) * selectedRobot.ejectorPreferences.position[0] + + Math.sin(theta) * selectedRobot.ejectorPreferences.position[2] + const calculatedZ = + Math.sin(theta) * selectedRobot.ejectorPreferences.position[0] + + Math.cos(theta) * selectedRobot.ejectorPreferences.position[2] + + // Calculating the position of the pickup mesh relative to the robot + const position = [ + robotPosition.GetX() + calculatedX, + robotPosition.GetY() + selectedRobot.ejectorPreferences.position[1], + robotPosition.GetZ() + calculatedZ, + ] + const direction = selectedRobot.ejectorPreferences.direction transformGizmoRef.current?.mesh.position.set(position[0], position[1], position[2]) + transformGizmoRef.current?.mesh.rotation.setFromVector3(new THREE.Vector3(1, 1, 1)) transformGizmoRef.current?.mesh.rotation.setFromQuaternion( new THREE.Quaternion(direction[0], direction[1], direction[2], direction[3]) ) + + // setting the rotation of the mesh in relation to the robot + // transformGizmoRef.current?.mesh.rotateY(calculateRobotAngle() - selectedRobot.ejectorPreferences.relativeRotation) + const rotation = ThreeQuaternion_JoltQuat(transformGizmoRef.current!.mesh.quaternion) + // rotation.SetY(calculateMeshAngle() - selectedRobot.ejectorPreferences.relativeRotation) + rotation.SetY(Math.PI) + transformGizmoRef.current?.mesh.rotation.setFromQuaternion(JoltQuat_ThreeQuaternion(rotation)) } // Saves zone preferences to local storage const saveEjectorPreferences = () => { - if (selectedEjector == undefined) return + if (!selectedRobot?.ejectorPreferences) return const position = transformGizmoRef.current?.mesh.position const direction = transformGizmoRef.current?.mesh.quaternion + const robotPosition = World.PhysicsSystem.GetBody(selectedRobot.GetRootNodeId()!).GetPosition() + const theta = calculateRobotAngle() if (position == undefined || direction == undefined) return - selectedEjector.ejectorVelocity = ejectorVelocity - selectedEjector.position = [position.x, position.y, position.z] - selectedEjector.direction = [direction.x, direction.y, direction.z, direction.w] + // resetting the position of the pickup node in relation to the robot at the default position it faces + const calculatedX = + Math.cos(theta) * (position.x - robotPosition.GetX()) - + Math.sin(theta) * (position.z - robotPosition.GetZ()) + const calculatedZ = + Math.sin(theta) * (position.x - robotPosition.GetX()) + + Math.cos(theta) * (position.z - robotPosition.GetZ()) + - selectedEjector.parentBody = bodyAttachmentRef.current + selectedRobot.ejectorPreferences.position = [calculatedX, position.y - robotPosition.GetY(), calculatedZ] + selectedRobot.ejectorPreferences.direction = [direction.x, direction.y, direction.z, direction.w] + selectedRobot.ejectorPreferences.relativeRotation = theta + console.log(selectedRobot.ejectorPreferences.relativeRotation) + + selectedRobot.ejectorPreferences.parentBody = bodyAttachmentRef.current PreferencesSystem.savePreferences() } + /** + * @returns The angle of the robot in radians + */ + const calculateRobotAngle = (): number => { + const robotRotation = World.PhysicsSystem.GetBody(selectedRobot!.GetRootNodeId()!).GetRotation().GetRotationAngle(new JOLT.Vec3(0, 1, 0)) // getting the rotation of the robot on the Y axis + if (robotRotation > 0) { + return robotRotation + } else { + return 2 * Math.PI + robotRotation + } + } + + const calculateMeshAngle = (): number => { + const meshRotation = ThreeQuaternion_JoltQuat(transformGizmoRef.current!.mesh.quaternion).GetRotationAngle(new JOLT.Vec3(0, 1, 0)) + if (meshRotation > 0) { + return meshRotation + } else { + return 2 * Math.PI + meshRotation + } + } + + /** + * @returns A list of all robots as MirabufSceneObjects + */ + const listRobots = (): MirabufSceneObject[] => { + // filtering out robots that are not dynamic and not MirabufSceneObjects + const assemblies = [...World.SceneRenderer.sceneObjects.values()].filter(x => { + if (x instanceof MirabufSceneObject) { + return x.miraType === MiraType.ROBOT + } + return false + }) as MirabufSceneObject[] + return assemblies + } + + /** + * Checks if the body is a child of the selected MirabufSceneObject + * + * @param body The Jolt body to check if it is a child of the selected robot + */ + const checkSelectedNode = (body: Jolt.Body): boolean => { + let returnValue = false + selectedRobot?.mirabufInstance?.parser.rigidNodes.forEach(rn => { + if (World.PhysicsSystem.GetBody(selectedRobot.mechanism.GetBodyByNodeId(rn.id)!).GetID() === body.GetID()) { + bodyAttachmentRef.current = body + returnValue = true + } + }) + return returnValue + } + + useEffect(() => { setupGizmo() }) @@ -84,44 +175,45 @@ const ConfigureShotTrajectoryPanel: React.FC = ({ panelId, openL if (transformGizmoRef.current) transformGizmoRef.current.RemoveGizmos() }} > - {selectedEjector == undefined ? ( + {selectedRobot?.ejectorPreferences == undefined ? ( <> {/** Scroll view for selecting a robot to configure */}
- {Object.keys(SynthesisBrain.numberRobotsSpawned).map(robot => { + {listRobots().map(mirabufSceneObject => { return ( ) })}
+ {/* TODO: remove the accept button on this version */} ) : ( <> {/* Button for user to select the parent node */} (bodyAttachmentRef.current = body)} + onSelect={(body: Jolt.Body) => checkSelectedNode(body)} /> {/* Slider for user to set velocity of ejector configuration */} setEjectorVelocity(vel)} + onChange={(vel: number) => { + if (selectedRobot.ejectorPreferences) { + selectedRobot.ejectorPreferences.ejectorVelocity = vel; + } + }} step={0.01} /> From 040eed6044470c09f652d2067ec3c14f9fe7065e Mon Sep 17 00:00:00 2001 From: KyroVibe Date: Sat, 13 Jul 2024 03:18:29 -0600 Subject: [PATCH 18/33] Solved gizmo transformation delta issue for ejector configuration. Updated the ejector config gizmo to be a low poly cone, plus orientation adjustments. Fixed issue with ejector velocity saving constantly. Refactoring most of ejector configuration. Created pausing for the physics system. Created physics body associations for associating additional data to bodies. Updated slider to more closely mimic MUI sliders. Added proper node selection for ejector config. --- fission/src/mirabuf/MirabufParser.ts | 8 +- fission/src/mirabuf/MirabufSceneObject.ts | 32 +- fission/src/systems/physics/Mechanism.ts | 3 +- fission/src/systems/physics/PhysicsSystem.ts | 75 +++++ .../systems/preferences/PreferenceTypes.ts | 12 +- fission/src/systems/scene/SceneRenderer.ts | 4 +- fission/src/ui/components/SelectButton.tsx | 13 +- fission/src/ui/components/Slider.tsx | 18 +- .../ConfigureShotTrajectoryPanel.tsx | 294 ++++++++++-------- fission/src/util/TypeConversions.ts | 5 + 10 files changed, 294 insertions(+), 170 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index a9a814e85a..ea7b5d03ba 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -2,6 +2,8 @@ import * as THREE from "three" import { mirabuf } from "@/proto/mirabuf" import { MirabufTransform_ThreeMatrix4 } from "@/util/TypeConversions" +export type RigidNodeId = string + export enum ParseErrorSeverity { Unimportable = 10, LikelyIssues = 6, @@ -400,11 +402,11 @@ class MirabufParser { */ class RigidNode { public isRoot: boolean - public id: string + public id: RigidNodeId public parts: Set = new Set() public isDynamic: boolean - public constructor(id: string, isDynamic?: boolean) { + public constructor(id: RigidNodeId, isDynamic?: boolean) { this.id = id this.isDynamic = isDynamic ?? true this.isRoot = false @@ -414,7 +416,7 @@ class RigidNode { export class RigidNodeReadOnly { private _original: RigidNode - public get id(): string { + public get id(): RigidNodeId { return this._original.id } diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index b0332f431d..891f7cfd1a 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -1,13 +1,13 @@ import { mirabuf } from "@/proto/mirabuf" import SceneObject from "../systems/scene/SceneObject" import MirabufInstance from "./MirabufInstance" -import MirabufParser, { ParseErrorSeverity } from "./MirabufParser" +import MirabufParser, { ParseErrorSeverity, RigidNodeId } from "./MirabufParser" import World from "@/systems/World" import Jolt from "@barclah/jolt-physics" import { JoltMat44_ThreeMatrix4 } from "@/util/TypeConversions" import * as THREE from "three" import JOLT from "@/util/loading/JoltSyncLoader" -import { LayerReserve } from "@/systems/physics/PhysicsSystem" +import { BodyAssociated, LayerReserve } from "@/systems/physics/PhysicsSystem" import Mechanism from "@/systems/physics/Mechanism" import SynthesisBrain from "@/systems/simulation/synthesis_brain/SynthesisBrain" import InputSystem from "@/systems/input/InputSystem" @@ -62,6 +62,10 @@ class MirabufSceneObject extends SceneObject { return this._mirabufInstance.parser.assembly.dynamic ? MiraType.ROBOT : MiraType.FIELD } + public get rootNodeId(): string { + return this._mirabufInstance.parser.rootNode + } + public constructor(mirabufInstance: MirabufInstance, assemblyName: string) { super() @@ -101,6 +105,10 @@ class MirabufSceneObject extends SceneObject { }) } + this._mechanism.nodeToBody.forEach((bodyId, node) => { + World.PhysicsSystem.SetBodyAssociation(new RigidNodeAssociate(this, node, bodyId)) + }) + // Simulation World.SimulationSystem.RegisterMechanism(this._mechanism) const simLayer = World.SimulationSystem.GetSimulationLayer(this._mechanism)! @@ -176,6 +184,11 @@ class MirabufSceneObject extends SceneObject { } public Dispose(): void { + + this._mechanism.nodeToBody.forEach((bodyId) => { + World.PhysicsSystem.RemoveBodyAssocation(bodyId) + }) + World.SimulationSystem.UnregisterMechanism(this._mechanism) World.PhysicsSystem.DestroyMechanism(this._mechanism) this._mirabufInstance.Dispose(World.SceneRenderer.scene) @@ -270,4 +283,19 @@ export async function CreateMirabuf(assembly: mirabuf.Assembly): Promise + public nodeToBody: Map public constraints: Array public stepListeners: Array public layerReserve: LayerReserve | undefined diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index dd0e7dc2cd..bf408b0d59 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -15,6 +15,8 @@ import MirabufParser, { GAMEPIECE_SUFFIX, GROUNDED_JOINT_ID, RigidNodeReadOnly } import WorldSystem from "../WorldSystem" import Mechanism from "./Mechanism" +export type BodyIndexAndSequence = number + /** * Layers used for determining enabled/disabled collisions. */ @@ -62,6 +64,10 @@ class PhysicsSystem extends WorldSystem { private _bodies: Array private _constraints: Array + private _pauseCounter = 0; + + private _bodyAssociations: Map; + /** * Creates a PhysicsSystem object. */ @@ -90,6 +96,64 @@ class PhysicsSystem extends WorldSystem { ) ground.SetFriction(FLOOR_FRICTION) this._joltBodyInterface.AddBody(ground.GetID(), JOLT.EActivation_Activate) + + this._bodyAssociations = new Map() + } + + /** + * Get association to a given Jolt Body. + * + * @param bodyId BodyID to check for association + * @returns Association for given Body + */ + public GetBodyAssociation(bodyId: Jolt.BodyID): T | undefined { + const res = this._bodyAssociations.get(bodyId.GetIndexAndSequenceNumber()) + if (res) { + // Avoids error, simply returns undefined if invalid + return (res as unknown) as T + } else { + return res + } + } + + /** + * Sets assocation for a body + * + * @param assocation Assocation. See {@link BodyAssociated} + */ + public SetBodyAssociation(assocation: T) { + this._bodyAssociations.set(assocation.associatedBody, assocation) + } + + public RemoveBodyAssocation(bodyId: Jolt.BodyID) { + this._bodyAssociations.delete(bodyId.GetIndexAndSequenceNumber()) + } + + /** + * Holds a pause. + * + * The pause works off of a request counter. + */ + public HoldPause() { + this._pauseCounter++; + } + + /** + * Forces all holds on the pause to be released. + */ + public ForceUnpause() { + this._pauseCounter = 0; + } + + /** + * Releases a pause. + * + * The pause works off of a request counter. + */ + public ReleasePause() { + if (this._pauseCounter > 0) { + this._pauseCounter--; + } } /** @@ -815,6 +879,10 @@ class PhysicsSystem extends WorldSystem { } public Update(deltaT: number): void { + if (this._pauseCounter > 0) { + return + } + const diffDeltaT = deltaT - lastDeltaT lastDeltaT = lastDeltaT + Math.min(TIMESTEP_ADJUSTMENT, Math.max(-TIMESTEP_ADJUSTMENT, diffDeltaT)) @@ -1001,4 +1069,11 @@ export type RayCastHit = { ray: Jolt.RayCast } +/** + * An interface to create an association between a body and anything. + */ +export interface BodyAssociated { + readonly associatedBody: BodyIndexAndSequence +} + export default PhysicsSystem diff --git a/fission/src/systems/preferences/PreferenceTypes.ts b/fission/src/systems/preferences/PreferenceTypes.ts index 0c460301e8..3ebf3f506a 100644 --- a/fission/src/systems/preferences/PreferenceTypes.ts +++ b/fission/src/systems/preferences/PreferenceTypes.ts @@ -33,11 +33,9 @@ export type IntakePreferences = { } export type EjectorPreferences = { - position: [number, number, number] - direction: [number, number, number, number] - relativeRotation: number + deltaTransformation: number[] ejectorVelocity: number - parentBody: Jolt.Body | undefined + parentNode: string | undefined } export type RobotPreferences = { @@ -70,11 +68,9 @@ export function DefaultRobotPreferences(): RobotPreferences { inputsSchemes: [], intake: { position: [0, 0, 0], diameter: 1, parentBody: undefined }, ejector: { - position: [0, 0, 0], - direction: [0, 0, 0, 0], - relativeRotation: 0, + deltaTransformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], ejectorVelocity: 1, - parentBody: undefined, + parentNode: undefined, }, } } diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 97310b0f76..6953b28f34 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -229,9 +229,7 @@ class SceneRenderer extends WorldSystem { transformControl.attach(obj) // allowing the transform gizmos to rotate with the object - if (mode === "translate") { - transformControl.space = "local" - } + transformControl.space = "local" transformControl.addEventListener( "dragging-changed", diff --git a/fission/src/ui/components/SelectButton.tsx b/fission/src/ui/components/SelectButton.tsx index 85f75d7392..7381883937 100644 --- a/fission/src/ui/components/SelectButton.tsx +++ b/fission/src/ui/components/SelectButton.tsx @@ -31,13 +31,13 @@ function SelectNode(e: MouseEvent) { type SelectButtonProps = { colorClass?: string size?: ButtonSize + value?: string placeholder?: string onSelect?: (value: Jolt.Body) => boolean className?: string } -const SelectButton: React.FC = ({ colorClass, size, placeholder, onSelect, className }) => { - const [value, setValue] = useState() +const SelectButton: React.FC = ({ colorClass, size, value, placeholder, onSelect, className }) => { const [selecting, setSelecting] = useState(false) const timeoutRef = useRef() @@ -45,7 +45,6 @@ const SelectButton: React.FC = ({ colorClass, size, placehold (value: Jolt.Body) => { if (onSelect) { if (onSelect(value)) { - setValue("Node") clearTimeout(timeoutRef.current) setSelecting(false) } else { @@ -53,7 +52,7 @@ const SelectButton: React.FC = ({ colorClass, size, placehold } } }, - [setValue, setSelecting, onSelect] + [setSelecting, onSelect] ) useEffect(() => { @@ -76,10 +75,10 @@ const SelectButton: React.FC = ({ colorClass, size, placehold // should send selecting state when clicked and then receive string value to set selecting to false return ( - - + +