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/fission/bun.lockb b/fission/bun.lockb index e8c02486c5..7e72545e8a 100755 Binary files a/fission/bun.lockb and b/fission/bun.lockb differ diff --git a/fission/package-lock.json b/fission/package-lock.json index dc05c5de11..639b15bfd6 100644 --- a/fission/package-lock.json +++ b/fission/package-lock.json @@ -55,7 +55,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-singlefile": "^0.13.5", "vitest": "^1.5.3" diff --git a/fission/package.json b/fission/package.json index 0be7bae093..9c154bf493 100644 --- a/fission/package.json +++ b/fission/package.json @@ -65,7 +65,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-singlefile": "^0.13.5", "vitest": "^1.5.3" 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 b8fe36241f..4084d123d9 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" @@ -92,10 +92,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 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 9c6c57b9a8..8fa995a5b9 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" @@ -157,16 +156,10 @@ 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 } 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/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index dd87a1c79d..3ef4a4fcf0 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -191,8 +191,6 @@ 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) @@ -707,7 +705,6 @@ class PhysicsSystem extends WorldSystem { this._joltBodyInterface.RemoveBody(x) // this._joltBodyInterface.DestroyBody(x); }) - console.log("Mechanism destroyed") } public GetBody(bodyId: Jolt.BodyID) { @@ -723,8 +720,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/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/MainHUD.tsx b/fission/src/ui/components/MainHUD.tsx index 16d9d98070..12368502a4 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")} /> = ({ 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 9bb75bfacc..a137c8bb1c 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,