From b4237fb4e21ae6e1a0c495d61320bd2a5b35d7fe Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Mon, 30 Jun 2025 09:06:25 -0700 Subject: [PATCH 01/83] chore: remote --open from bun run dev --- fission/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/package.json b/fission/package.json index d3a3989533..870105a5d9 100644 --- a/fission/package.json +++ b/fission/package.json @@ -6,7 +6,7 @@ "scripts": { "init": "(bun run assetpack && bun run playwright:install) || (npm run assetpack && npm run playwright:install)", "host": "vite --open --host", - "dev": "vite --open", + "dev": "vite", "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", From b7f00f4157b2306d0de04ebf0607bef19066b1c2 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Mon, 30 Jun 2025 10:23:10 -0700 Subject: [PATCH 02/83] feat: add game pieces to configure asset panel --- fission/src/mirabuf/MirabufLoader.ts | 7 ++++-- .../assembly-config/ConfigurationType.ts | 1 + .../assembly-config/ConfigurePanel.tsx | 24 +++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index b5c2ae38f5..881f728efd 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -131,6 +131,8 @@ class MirabufCachingService { const miraBuff = await resp.arrayBuffer() + console.log(miraType) + World.AnalyticsSystem?.Event("Remote Download", { type: miraType === MiraType.ROBOT ? "robot" : "field", fileSize: miraBuff.byteLength, @@ -437,8 +439,8 @@ class MirabufCachingService { 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))) + const test = [...new Uint8Array(hashBuffer)] + const hash: string = String.fromCharCode([...test]) return btoa(hash).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") } @@ -450,6 +452,7 @@ class MirabufCachingService { export enum MiraType { ROBOT = 1, FIELD, + PIECE, } export default MirabufCachingService diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurationType.ts b/fission/src/ui/panels/configuring/assembly-config/ConfigurationType.ts index 927beac567..845b51d472 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurationType.ts +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurationType.ts @@ -1,6 +1,7 @@ export enum ConfigurationType { ROBOT, FIELD, + PIECES, INPUTS, } diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index 373bb76a22..9773283842 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -80,8 +80,16 @@ const AssemblySelection: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [u, pendingDeletes]) + const gamePieces = useMemo(() => { + return [...World.SceneRenderer.sceneObjects.values()] + .filter(x => x instanceof MirabufSceneObject && x.miraType === MiraType.PIECE) + .filter(x => !pendingDeletes.includes(x.id)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [u, pendingDeletes]) + + const options = useMemo(() => { - const list = configurationType == ConfigurationType.ROBOT ? robots : fields + const list = configurationType == ConfigurationType.ROBOT ? robots : ConfigurationType.FIELD ? fields : gamePieces return list .filter((assembly): assembly is MirabufSceneObject => assembly != null) .map(assembly => makeSelectionOption(configurationType, assembly)) @@ -92,7 +100,7 @@ const AssemblySelection: React.FC = ({ onAssemblySelected((val as AssemblySelectionOption)?.assemblyObject)} - defaultHeaderText={`Select a ${configurationType == ConfigurationType.ROBOT ? "Robot" : "Field"}`} + defaultHeaderText={`Select a ${configurationType == ConfigurationType.ROBOT ? "Robot" : configurationType == ConfigurationType.FIELD ? "Field" : "Game Piece"}`} onDelete={val => { onStageDelete(val) update() @@ -100,7 +108,7 @@ const AssemblySelection: React.FC = ({ onAddClicked={() => { openPanel("import-mirabuf") }} - noOptionsText={`No ${configurationType == ConfigurationType.ROBOT ? "robots" : "fields"} spawned!`} + noOptionsText={`No ${configurationType == ConfigurationType.ROBOT ? "robots" : configurationType == ConfigurationType.FIELD ? "fields" : "game pieces"} spawned!`} defaultSelectedOption={ selectedAssembly ? makeSelectionOption(configurationType, selectedAssembly) : undefined } @@ -204,6 +212,13 @@ const fieldModes: Map = new Map = new Map([ + [ + ConfigMode.MOVE, + new ConfigModeSelectionOption("Move", ConfigMode.MOVE, "Adjust position of the game piece relative to field."), + ], +]) + interface ConfigModeSelectionProps { configurationType: ConfigurationType onModeSelected: (mode: ConfigMode | undefined) => void @@ -222,7 +237,7 @@ const ConfigModeSelection: React.FC = ({ return ( { onModeSelected((val as ConfigModeSelectionOption)?.configMode) }} @@ -416,6 +431,7 @@ const ConfigurePanel: React.FC = ({ panelId }) => { > Robots Fields + Game Pieces Inputs {configurationType == ConfigurationType.INPUTS ? ( From 96c74acc51c138d49dd293cb917fbb08d9e2a449 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Mon, 30 Jun 2025 11:53:20 -0700 Subject: [PATCH 03/83] feat(wip): start changing mirabuf parser --- fission/src/mirabuf/MirabufParser.ts | 33 ++++++++++++++++------------ fission/src/util/Utility.ts | 7 ++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 1b81165f1c..a74f26f25a 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -175,20 +175,25 @@ class MirabufParser { ) // Create gamepiece rigid nodes from PartInstances with corresponding definitions - Object.values(this._assembly.data!.parts!.partInstances!).forEach((inst: mirabuf.IPartInstance) => { - if (!gamepieceDefinitions.has(inst.partDefinitionReference!)) return - - const instNode = this.BinarySearchDesignTree(inst.info!.GUID!) - if (!instNode) { - this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) - return - } - - const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) - gpRn.isGamePiece = true - this.MovePartToRigidNode(instNode!.value!, gpRn) - if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) - }) + let count = 0 + const pre_filter = Object.values(this._assembly.data!.parts!.partInstances!) + .filter(inst => gamepieceDefinitions.has(inst.partDefinitionReference!)) + .map(inst => this.BinarySearchDesignTree(inst.info!.GUID!)) + .forEach(_ => count++) + .filter(instNode => !instNode) + .forEach((inst: mirabuf.IPartInstance) => { + count-- + + // TODO: Instead of marking them, separate them into a different body entirely + + const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) + gpRn.isGamePiece = true + this.MovePartToRigidNode(instNode!.value!, gpRn) + if (instNode.children) + this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + }) + if (count !== 0) + this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) } private BandageRigidNodes(assembly: mirabuf.Assembly) { diff --git a/fission/src/util/Utility.ts b/fission/src/util/Utility.ts index 6c59d7ed10..16b3d44225 100644 --- a/fission/src/util/Utility.ts +++ b/fission/src/util/Utility.ts @@ -10,3 +10,10 @@ export function getFontSize(element: Element): number { export function clamp(num: number, min: number, max: number): number { return Math.min(Math.max(num, min), max) } + +export function* inspect(iterable: Iterable, fn: (item: T) => void): IterableIterator { + for (const item of iterable) { + fn(item) // side effect + yield item // pass the item along unchanged + } +} From b1731f4446e59d1da3701ed21a75d60811af76ff Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 1 Jul 2025 09:34:15 -0700 Subject: [PATCH 04/83] feat: refactor ancestral break function --- fission/src/mirabuf/MirabufParser.ts | 68 ++++++++++++++++------------ 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index a74f26f25a..875ae0bed8 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -39,6 +39,8 @@ class MirabufParser { private _groundedNode: RigidNode | undefined + private _gamePieces: mirabuf.INode[] + public get errors() { return new Array(...this._errors) } @@ -86,7 +88,9 @@ class MirabufParser { this.InitializeRigidGroups() // 1: from ancestral breaks in joints // Fields Only: Assign Game Piece rigid nodes - if (!assembly.dynamic) this.AssignGamePieceRigidNodes() + if (!assembly.dynamic) { + this._gamePieces = this.PruneGamePieceNodes() + } // 2: Grounded joint const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] @@ -164,36 +168,38 @@ class MirabufParser { }) } - private AssignGamePieceRigidNodes() { + private PruneGamePieceNodes(): mirabuf.INode[] { // Collect all definitions labeled as gamepieces (dynamic = true) const gamepieceDefinitions: Set = new Set( Object.values(this._assembly.data!.parts!.partDefinitions!) .filter(def => def.dynamic) - .map((def: mirabuf.IPartDefinition) => { - return def.info!.GUID! - }) + .map((def: mirabuf.IPartDefinition) => def.info!.GUID!) ) // Create gamepiece rigid nodes from PartInstances with corresponding definitions - let count = 0 - const pre_filter = Object.values(this._assembly.data!.parts!.partInstances!) + const gamePieces = Object.values(this._assembly.data!.parts!.partInstances!) .filter(inst => gamepieceDefinitions.has(inst.partDefinitionReference!)) .map(inst => this.BinarySearchDesignTree(inst.info!.GUID!)) - .forEach(_ => count++) - .filter(instNode => !instNode) - .forEach((inst: mirabuf.IPartInstance) => { - count-- - + .map(instNode => { + if (instNode == null) { + this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) + return + } // TODO: Instead of marking them, separate them into a different body entirely - + // Figure out what we actually need to return here const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) gpRn.isGamePiece = true this.MovePartToRigidNode(instNode!.value!, gpRn) if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + + return instNode }) - if (count !== 0) - this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) + .filter(node => node != null) + + // TODO: Detatch game pieces from tree and remove part instances + + return gamePieces } private BandageRigidNodes(assembly: mirabuf.Assembly) { @@ -331,26 +337,28 @@ class MirabufParser { const valueA = ptv.get(partA)! const valueB = ptv.get(partB)! - while (pathA.value! == pathB.value! && pathA.value! != partA && pathB.value! != partB) { - const ancestorIndexA = this.BinarySearchIndex(valueA, pathA.children!) - const ancestorValueA = ptv.get(pathA.children![ancestorIndexA].value!)! - pathA = pathA.children![ancestorIndexA + (ancestorValueA < valueA ? 1 : 0)] + const traverse = (value: number | undefined, path: mirabuf.INode | undefined) => { + if (!value || !path) return - const ancestorIndexB = this.BinarySearchIndex(valueB, pathB.children!) - const ancestorValueB = ptv.get(pathB.children![ancestorIndexB].value!)! - pathB = pathB.children![ancestorIndexB + (ancestorValueB < valueB ? 1 : 0)] + const ancestorIndex = this.BinarySearchIndex(value, path.children!) + const ancestorValue = ptv.get(path.children![ancestorIndex].value!)! + path = path.children![ancestorIndex + (ancestorValue < value ? 1 : 0)] } - if (pathA.value! == partA && pathA.value! == pathB.value!) { - const ancestorIndexB = this.BinarySearchIndex(valueB, pathB.children!) - const ancestorValueB = ptv.get(pathB.children![ancestorIndexB].value!)! - pathB = pathB.children![ancestorIndexB + (ancestorValueB < valueB ? 1 : 0)] - } else if (pathB.value! == partB && pathA.value! == pathB.value!) { - const ancestorIndexA = this.BinarySearchIndex(valueA, pathA.children!) - const ancestorValueA = ptv.get(pathA.children![ancestorIndexA].value!)! - pathA = pathA.children![ancestorIndexA + (ancestorValueA < valueA ? 1 : 0)] + while (pathA.value! == pathB.value! && pathA.value! != partA && pathB.value! != partB) { + traverse(valueB, pathB) + traverse(valueA, pathA) } + const [value, path] = + pathA.value! == partA && pathA.value! == pathB.value! + ? [valueB, pathB] + : pathB.value! == partB && pathA.value! == pathB.value! + ? [valueA, pathA] + : [undefined, undefined] + + traverse(value, path) + return [pathA.value!, pathB.value!] } From cd697ae5c7e0ea15be0e71a257eefd5d0c3cd361 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 1 Jul 2025 10:50:56 -0700 Subject: [PATCH 05/83] feat: recursively parse gamepieces --- .../src/Resources/PWM_icon/16x16-normal.png | Bin 782 -> 0 bytes fission/src/mirabuf/MirabufInstance.ts | 5 +- fission/src/mirabuf/MirabufParser.ts | 95 ++++++++++-------- fission/src/mirabuf/MirabufSceneObject.ts | 12 ++- .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 9 +- 5 files changed, 71 insertions(+), 50 deletions(-) delete mode 100644 exporter/SynthesisFusionAddin/src/Resources/PWM_icon/16x16-normal.png diff --git a/exporter/SynthesisFusionAddin/src/Resources/PWM_icon/16x16-normal.png b/exporter/SynthesisFusionAddin/src/Resources/PWM_icon/16x16-normal.png deleted file mode 100644 index 07cd465f72d6f27012f2fd9dad871c162e02105a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 782 zcmV+p1M&QcP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0;5SpK~zXf&6Z0~ zQ&AL!KZ*!azy}HxBZ3kYh{QL>$Up}gNem&5oH#Xc$@R=Ay zd`26PhbT}ew6svbwR>(;6S?h>uH@2l&ug!<_B#7ggom{ota;51MbSgjoYH>^g;WMc zuBgyRWssZL<{(tnYF0q9uEOFR#3N9u{<11qn1YpMDBq^ZnzN{}K#A7WK|*skl|o6m z*34*ZR_!vat$@@LEX83p<>IV^;@*?=dhLI)q9;IO2V8joZI_@`PkXmE(@^;i=(_C! zrV?=Z9vnCaBX3~OF}QHc9pXq2oWH3kB0KyV_ML^RkD$3z?{6JhALy9t!iYIlb~VHJ zAPhcn0XnZsNvW_k1ba_GWeqeOheoxl>P76R8#6_!8zI!HvD0p^P@A5^P;*e~ZApjl zgCDW1#>-Mi&X6) z_@j3todA=mC$hwy6-EcT@Zc+b0RYF0fGwHBsHeO3Me9$dh_Ju=WOukr27rE z$O7ASpkevdB_tQ}D`o~Y1;Q_(|1R|15wTx-f8mVWa6-hsyMRq+T%XO&z%I+26mGew zOQh;FGa_Q%SPu9R%l0RA@}WL>F4GP?7D?M!C!Ur{$px1tQ_RQ(bt&wY+E@llF_*sm zGQ8Ph-cv9S{D?VcCgz=2?9_JqtYlqwnN3 zn=_!)N^Q%)ib_TfH>6VaE2(*Lm4QI96s&eu zFr{ptMcYH+tUlTWGV(qs*vjvh$_+;#^EKZ<{FkxxN8D={*uOpS7qM>jiniOs2mk;8 M07*qoM6N<$f{5y5xc~qF diff --git a/fission/src/mirabuf/MirabufInstance.ts b/fission/src/mirabuf/MirabufInstance.ts index 94e169700e..b2768f749d 100644 --- a/fission/src/mirabuf/MirabufInstance.ts +++ b/fission/src/mirabuf/MirabufInstance.ts @@ -227,9 +227,8 @@ class MirabufInstance { batchedMesh.castShadow = true batchedMesh.receiveShadow = true - materialBodyMap.forEach(instances => { - const body = instances[0] - instances[1].forEach(instance => { + materialBodyMap.forEach(([body, instances]) => { + instances.forEach(instance => { const mat = this._mirabufParser.globalTransforms.get(instance.info!.GUID!)! const geometry = new THREE.BufferGeometry() diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 875ae0bed8..f641459bbd 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -39,46 +39,51 @@ class MirabufParser { private _groundedNode: RigidNode | undefined - private _gamePieces: mirabuf.INode[] + private _gamePieces?: MirabufParser[] public get errors() { - return new Array(...this._errors) + return this._errors } - public get maxErrorSeverity() { + + public get maxErrorSeverity(): number { return Math.max(...this._errors.map(x => x[0])) } - public get assembly() { + public get assembly(): mirabuf.Assembly { return this._assembly } - public get partTreeValues() { + public get partTreeValues(): Map { return this._partTreeValues } - public get designHierarchyRoot() { + public get designHierarchyRoot(): mirabuf.INode { return this._designHierarchyRoot } - public get partToNodeMap() { + public get partToNodeMap(): Map { return this._partToNodeMap } - public get globalTransforms() { + public get globalTransforms(): Map { return this._globalTransforms } - public get groundedNode() { + public get groundedNode(): RigidNodeReadOnly | undefined { return this._groundedNode ? new RigidNodeReadOnly(this._groundedNode) : undefined } public get rigidNodes(): Map { return new Map(this._rigidNodes.map(x => [x.id, new RigidNodeReadOnly(x)])) } - public get directedGraph() { + public get directedGraph(): Graph { return this._directedGraph } - public get rootNode() { + public get rootNode(): string { return this._rootNode } + public get gamePieces(): MirabufParser[] | undefined { + return this._gamePieces + } public constructor(assembly: mirabuf.Assembly, progressHandle?: ProgressHandle) { this._assembly = assembly this._errors = new Array() this._globalTransforms = new Map() + this._gamePieces = undefined progressHandle?.Update("Parsing assembly...", 0.3) @@ -89,7 +94,7 @@ class MirabufParser { // Fields Only: Assign Game Piece rigid nodes if (!assembly.dynamic) { - this._gamePieces = this.PruneGamePieceNodes() + this._gamePieces = this.PruneGamePieceNodes().map(assembly => new MirabufParser(assembly)) } // 2: Grounded joint @@ -127,21 +132,18 @@ class MirabufParser { // 8. Retrieve Masses this._rigidNodes.forEach(rn => { - rn.mass = 0 - rn.parts.forEach(part => { - const inst = assembly.data?.parts?.partInstances?.[part] - if (!inst?.partDefinitionReference) return - const def = assembly.data?.parts?.partDefinitions?.[inst.partDefinitionReference!] - rn.mass += def?.massOverride ? def.massOverride : def?.physicalData?.mass ?? 0 - }) + rn.mass = [...rn.parts] + .map(part => assembly.data?.parts?.partInstances?.[part]) + .filter(inst => inst?.partDefinitionReference) + .reduce((acc, inst) => { + const def = assembly.data?.parts?.partDefinitions?.[inst?.partDefinitionReference!] + return acc + (def?.massOverride ?? def?.physicalData?.mass ?? 0) + }, 0) }) this._directedGraph = this.GenerateRigidNodeGraph(assembly, rootNodeId) - if (!this.assembly.data?.parts?.partDefinitions) { - console.warn("Failed to get part definitions") - return - } + if (!this.assembly.data?.parts?.partDefinitions) console.warn("Failed to get part definitions") } private TraverseTree(nodes: mirabuf.INode[], op: (node: mirabuf.INode) => void) { @@ -153,22 +155,23 @@ class MirabufParser { private InitializeRigidGroups() { const jointInstanceKeys = Object.keys(this._assembly.data!.joints!.jointInstances!) as string[] - jointInstanceKeys.forEach(key => { - if (key === GROUNDED_JOINT_ID) return - - const jInst = this._assembly.data!.joints!.jointInstances![key] - const [ancestorA, ancestorB] = this.FindAncestorialBreak(jInst.parentPart!, jInst.childPart!) - const parentRN = this.NewRigidNode() - - this.MovePartToRigidNode(ancestorA, parentRN) - this.MovePartToRigidNode(ancestorB, this.NewRigidNode()) - - if (jInst.parts && jInst.parts.nodes) - this.TraverseTree(jInst.parts.nodes, x => this.MovePartToRigidNode(x.value!, parentRN)) - }) + jointInstanceKeys + .filter(key => key !== GROUNDED_JOINT_ID) + .forEach(key => { + const jInst = this._assembly.data!.joints!.jointInstances![key] + const [ancestorA, ancestorB] = this.FindAncestorialBreak(jInst.parentPart!, jInst.childPart!) + const parentRN = this.NewRigidNode() + + this.MovePartToRigidNode(ancestorA, parentRN) + this.MovePartToRigidNode(ancestorB, this.NewRigidNode()) + + if (jInst.parts && jInst.parts.nodes) + this.TraverseTree(jInst.parts.nodes, x => this.MovePartToRigidNode(x.value!, parentRN)) + }) } - private PruneGamePieceNodes(): mirabuf.INode[] { + // Separates and returns the sub-asmeblies (partInstances) of each game piece + private PruneGamePieceNodes(): mirabuf.Assembly[] { // Collect all definitions labeled as gamepieces (dynamic = true) const gamepieceDefinitions: Set = new Set( Object.values(this._assembly.data!.parts!.partDefinitions!) @@ -179,8 +182,8 @@ class MirabufParser { // Create gamepiece rigid nodes from PartInstances with corresponding definitions const gamePieces = Object.values(this._assembly.data!.parts!.partInstances!) .filter(inst => gamepieceDefinitions.has(inst.partDefinitionReference!)) - .map(inst => this.BinarySearchDesignTree(inst.info!.GUID!)) - .map(instNode => { + .map(inst => { + const instNode = this.BinarySearchDesignTree(inst.info!.GUID!) if (instNode == null) { this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) return @@ -193,7 +196,12 @@ class MirabufParser { if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) - return instNode + const assembly = new mirabuf.Assembly({ + ...inst, + designHierarchy: { nodes: [instNode] }, + }) + + return assembly }) .filter(node => node != null) @@ -204,12 +212,11 @@ class MirabufParser { private BandageRigidNodes(assembly: mirabuf.Assembly) { assembly.data!.joints!.rigidGroups!.forEach(rg => { - let rn: RigidNode | null = null - rg.occurrences!.forEach(y => { + let rn: RigidNode | null = rg.occurrences!.reduce((rn, y) => { const currentRn = this._partToNodeMap.get(y)! - rn = !rn ? currentRn : currentRn.id != rn.id ? this.MergeRigidNodes(currentRn, rn) : rn - }) + return !rn ? currentRn : currentRn.id != rn.id ? this.MergeRigidNodes(currentRn, rn) : rn + }, null) }) } diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index beaebb4d7c..d1eb3fa8bf 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -652,14 +652,22 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { export async function CreateMirabuf( assembly: mirabuf.Assembly, progressHandle?: ProgressHandle -): Promise { +): Promise<{ mainSceneObject: MirabufSceneObject; gamePieces?: MirabufSceneObject[] } | null | undefined> { const parser = new MirabufParser(assembly, progressHandle) if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`) return } - return new MirabufSceneObject(new MirabufInstance(parser), assembly.info!.name!, progressHandle) + const mainSceneObject = new MirabufSceneObject(new MirabufInstance(parser), assembly.info!.name!, progressHandle) + const gamePieces = parser.gamePieces?.map( + parser => new MirabufSceneObject(new MirabufInstance(parser), parser.assembly.info!.name!) + ) + + return { + mainSceneObject, + gamePieces, + } } /** diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index 9442935393..c3e1a3b53d 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -103,7 +103,14 @@ function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle? if (assembly) { CreateMirabuf(assembly).then(x => { if (x) { - World.SceneRenderer.RegisterSceneObject(x) + const { mainSceneObject, gamePieces } = x + + World.SceneRenderer.RegisterSceneObject(mainSceneObject) + gamePieces?.forEach(piece => { + // We have to cache the game pieces while they're in scope + MirabufCachingService.CacheInfo(piece., type, piece.assemblyName ?? undefined) + World.SceneRenderer.RegisterSceneObject(piece) + }) progressHandle.Done() Global_OpenPanel?.("initial-config") From 0dfe38f13e375fe83f762d2699ffdaee280b85be Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 1 Jul 2025 14:15:25 -0700 Subject: [PATCH 06/83] feat(wip): manually create ground joint --- fission/src/mirabuf/MirabufParser.ts | 63 ++++++++++--------- .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 3 +- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index f641459bbd..d7b3bebd45 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -98,38 +98,39 @@ class MirabufParser { } // 2: Grounded joint - const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] - const gNode = this.NewRigidNode() - this.MovePartToRigidNode(gInst.parts!.nodes!.at(0)!.value!, gNode) + if (assembly.data?.joints) { + const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] + const gNode = this.NewRigidNode() + this.MovePartToRigidNode(gInst.parts!.nodes!.at(0)!.value!, gNode) - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - // 3: Traverse and round up - const traverseNodeRoundup = (node: mirabuf.INode, parentNode: RigidNode) => { - const currentNode = this._partToNodeMap.get(node.value!) - if (!currentNode) this.MovePartToRigidNode(node.value!, parentNode) + // 3: Traverse and round up + const traverseNodeRoundup = (node: mirabuf.INode, parentNode: RigidNode) => { + const currentNode = this._partToNodeMap.get(node.value!) + if (!currentNode) this.MovePartToRigidNode(node.value!, parentNode) - if (!node.children) return - node.children.forEach(x => traverseNodeRoundup(x, currentNode ?? parentNode)) - } - this._designHierarchyRoot.children?.forEach(x => traverseNodeRoundup(x, gNode)) - - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + if (!node.children) return + node.children.forEach(x => traverseNodeRoundup(x, currentNode ?? parentNode)) + } + this._designHierarchyRoot.children?.forEach(x => traverseNodeRoundup(x, gNode)) - this.BandageRigidNodes(assembly) // 4: Bandage via RigidGroups - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - // 5. Remove Empty RNs - this._rigidNodes = this._rigidNodes.filter(x => x.parts.size > 0) + this.BandageRigidNodes(assembly) // 4: Bandage via RigidGroups + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - // 6. If field, find grounded node and set isDynamic to false. Also just find grounded node again - this._groundedNode = this.partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!) - if (!assembly.dynamic && this._groundedNode) this._groundedNode.isDynamic = false + // 5. Remove Empty RNs + this._rigidNodes = this._rigidNodes.filter(x => x.parts.size > 0) - // 7. Update root RigidNode - const rootNodeId = this._partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!)?.id ?? this._rigidNodes[0].id - this._rootNode = rootNodeId + // 6. If field, find grounded node and set isDynamic to false. Also just find grounded node again + this._groundedNode = this.partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!) + if (!assembly.dynamic && this._groundedNode) this._groundedNode.isDynamic = false + // 7. Update root RigidNode + const rootNodeId = this._partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!)?.id ?? this._rigidNodes[0].id + this._rootNode = rootNodeId + } // 8. Retrieve Masses this._rigidNodes.forEach(rn => { rn.mass = [...rn.parts] @@ -196,8 +197,13 @@ class MirabufParser { if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + const groundedJoint = new mirabuf.joint.Joint({}) + mirabuf.Assembly.prototype.data.parts const assembly = new mirabuf.Assembly({ ...inst, + data: { + parts: new mirabuf.Parts(), + }, designHierarchy: { nodes: [instNode] }, }) @@ -292,10 +298,11 @@ class MirabufParser { */ private LoadGlobalTransforms() { const root = this._designHierarchyRoot - const partInstances = new Map( - Object.entries(this._assembly.data!.parts!.partInstances!) - ) - const partDefinitions = this._assembly.data!.parts!.partDefinitions! + const parts = this._assembly.data?.parts + if (!parts) return // TODO not sure if we should return or provide a default value + + const partInstances = new Map(Object.entries(parts!.partInstances!)) + const partDefinitions = parts!.partDefinitions! this._globalTransforms.clear() diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index c3e1a3b53d..c4ff35446f 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -108,7 +108,8 @@ function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle? World.SceneRenderer.RegisterSceneObject(mainSceneObject) gamePieces?.forEach(piece => { // We have to cache the game pieces while they're in scope - MirabufCachingService.CacheInfo(piece., type, piece.assemblyName ?? undefined) + // TODO: Use specific cache key + MirabufCachingService.CacheInfo(info.cacheKey, type, piece.assemblyName ?? undefined) World.SceneRenderer.RegisterSceneObject(piece) }) progressHandle.Done() From 62ffc2c1b249bc941778b9512433ce8ba11dfe13 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Mon, 30 Jun 2025 09:06:25 -0700 Subject: [PATCH 07/83] chore: remote --open from bun run dev --- fission/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fission/package.json b/fission/package.json index d3a3989533..870105a5d9 100644 --- a/fission/package.json +++ b/fission/package.json @@ -6,7 +6,7 @@ "scripts": { "init": "(bun run assetpack && bun run playwright:install) || (npm run assetpack && npm run playwright:install)", "host": "vite --open --host", - "dev": "vite --open", + "dev": "vite", "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", From 5563f3133b72b2dd784ec3b4c92ed3af1b0038f9 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Mon, 30 Jun 2025 10:23:10 -0700 Subject: [PATCH 08/83] feat: add game pieces to configure asset panel --- fission/src/mirabuf/MirabufLoader.ts | 7 ++++-- .../assembly-config/ConfigurationType.ts | 1 + .../assembly-config/ConfigurePanel.tsx | 24 +++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index b5c2ae38f5..881f728efd 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -131,6 +131,8 @@ class MirabufCachingService { const miraBuff = await resp.arrayBuffer() + console.log(miraType) + World.AnalyticsSystem?.Event("Remote Download", { type: miraType === MiraType.ROBOT ? "robot" : "field", fileSize: miraBuff.byteLength, @@ -437,8 +439,8 @@ class MirabufCachingService { 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))) + const test = [...new Uint8Array(hashBuffer)] + const hash: string = String.fromCharCode([...test]) return btoa(hash).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") } @@ -450,6 +452,7 @@ class MirabufCachingService { export enum MiraType { ROBOT = 1, FIELD, + PIECE, } export default MirabufCachingService diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurationType.ts b/fission/src/ui/panels/configuring/assembly-config/ConfigurationType.ts index 927beac567..845b51d472 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurationType.ts +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurationType.ts @@ -1,6 +1,7 @@ export enum ConfigurationType { ROBOT, FIELD, + PIECES, INPUTS, } diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index e6bbe5383f..55ee0b3f0b 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -80,8 +80,16 @@ const AssemblySelection: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [u, pendingDeletes]) + const gamePieces = useMemo(() => { + return [...World.SceneRenderer.sceneObjects.values()] + .filter(x => x instanceof MirabufSceneObject && x.miraType === MiraType.PIECE) + .filter(x => !pendingDeletes.includes(x.id)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [u, pendingDeletes]) + + const options = useMemo(() => { - const list = configurationType == ConfigurationType.ROBOT ? robots : fields + const list = configurationType == ConfigurationType.ROBOT ? robots : ConfigurationType.FIELD ? fields : gamePieces return list .filter((assembly): assembly is MirabufSceneObject => assembly != null) .map(assembly => makeSelectionOption(configurationType, assembly)) @@ -92,7 +100,7 @@ const AssemblySelection: React.FC = ({ onAssemblySelected((val as AssemblySelectionOption)?.assemblyObject)} - defaultHeaderText={`Select a ${configurationType == ConfigurationType.ROBOT ? "Robot" : "Field"}`} + defaultHeaderText={`Select a ${configurationType == ConfigurationType.ROBOT ? "Robot" : configurationType == ConfigurationType.FIELD ? "Field" : "Game Piece"}`} onDelete={val => { onStageDelete(val) update() @@ -100,7 +108,7 @@ const AssemblySelection: React.FC = ({ onAddClicked={() => { openPanel("import-mirabuf") }} - noOptionsText={`No ${configurationType == ConfigurationType.ROBOT ? "robots" : "fields"} spawned!`} + noOptionsText={`No ${configurationType == ConfigurationType.ROBOT ? "robots" : configurationType == ConfigurationType.FIELD ? "fields" : "game pieces"} spawned!`} defaultSelectedOption={ selectedAssembly ? makeSelectionOption(configurationType, selectedAssembly) : undefined } @@ -204,6 +212,13 @@ const fieldModes: Map = new Map = new Map([ + [ + ConfigMode.MOVE, + new ConfigModeSelectionOption("Move", ConfigMode.MOVE, "Adjust position of the game piece relative to field."), + ], +]) + interface ConfigModeSelectionProps { configurationType: ConfigurationType onModeSelected: (mode: ConfigMode | undefined) => void @@ -222,7 +237,7 @@ const ConfigModeSelection: React.FC = ({ return ( { onModeSelected((val as ConfigModeSelectionOption)?.configMode) }} @@ -416,6 +431,7 @@ const ConfigurePanel: React.FC = ({ panelId }) => { > Robots Fields + Game Pieces Inputs {configurationType == ConfigurationType.INPUTS ? ( From 397e54943bd201f94501d86ee5f6905290b3f32d Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Mon, 30 Jun 2025 11:53:20 -0700 Subject: [PATCH 09/83] feat(wip): start changing mirabuf parser --- fission/src/mirabuf/MirabufParser.ts | 33 ++++++++++++++++------------ fission/src/util/Utility.ts | 7 ++++++ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 1b81165f1c..a74f26f25a 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -175,20 +175,25 @@ class MirabufParser { ) // Create gamepiece rigid nodes from PartInstances with corresponding definitions - Object.values(this._assembly.data!.parts!.partInstances!).forEach((inst: mirabuf.IPartInstance) => { - if (!gamepieceDefinitions.has(inst.partDefinitionReference!)) return - - const instNode = this.BinarySearchDesignTree(inst.info!.GUID!) - if (!instNode) { - this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) - return - } - - const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) - gpRn.isGamePiece = true - this.MovePartToRigidNode(instNode!.value!, gpRn) - if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) - }) + let count = 0 + const pre_filter = Object.values(this._assembly.data!.parts!.partInstances!) + .filter(inst => gamepieceDefinitions.has(inst.partDefinitionReference!)) + .map(inst => this.BinarySearchDesignTree(inst.info!.GUID!)) + .forEach(_ => count++) + .filter(instNode => !instNode) + .forEach((inst: mirabuf.IPartInstance) => { + count-- + + // TODO: Instead of marking them, separate them into a different body entirely + + const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) + gpRn.isGamePiece = true + this.MovePartToRigidNode(instNode!.value!, gpRn) + if (instNode.children) + this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + }) + if (count !== 0) + this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) } private BandageRigidNodes(assembly: mirabuf.Assembly) { diff --git a/fission/src/util/Utility.ts b/fission/src/util/Utility.ts index 6c59d7ed10..16b3d44225 100644 --- a/fission/src/util/Utility.ts +++ b/fission/src/util/Utility.ts @@ -10,3 +10,10 @@ export function getFontSize(element: Element): number { export function clamp(num: number, min: number, max: number): number { return Math.min(Math.max(num, min), max) } + +export function* inspect(iterable: Iterable, fn: (item: T) => void): IterableIterator { + for (const item of iterable) { + fn(item) // side effect + yield item // pass the item along unchanged + } +} From 65e21e164a2ffeee66b1af79ed5d68c6ddfbf78a Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 1 Jul 2025 09:34:15 -0700 Subject: [PATCH 10/83] feat: refactor ancestral break function --- fission/src/mirabuf/MirabufParser.ts | 68 ++++++++++++++++------------ 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index a74f26f25a..875ae0bed8 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -39,6 +39,8 @@ class MirabufParser { private _groundedNode: RigidNode | undefined + private _gamePieces: mirabuf.INode[] + public get errors() { return new Array(...this._errors) } @@ -86,7 +88,9 @@ class MirabufParser { this.InitializeRigidGroups() // 1: from ancestral breaks in joints // Fields Only: Assign Game Piece rigid nodes - if (!assembly.dynamic) this.AssignGamePieceRigidNodes() + if (!assembly.dynamic) { + this._gamePieces = this.PruneGamePieceNodes() + } // 2: Grounded joint const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] @@ -164,36 +168,38 @@ class MirabufParser { }) } - private AssignGamePieceRigidNodes() { + private PruneGamePieceNodes(): mirabuf.INode[] { // Collect all definitions labeled as gamepieces (dynamic = true) const gamepieceDefinitions: Set = new Set( Object.values(this._assembly.data!.parts!.partDefinitions!) .filter(def => def.dynamic) - .map((def: mirabuf.IPartDefinition) => { - return def.info!.GUID! - }) + .map((def: mirabuf.IPartDefinition) => def.info!.GUID!) ) // Create gamepiece rigid nodes from PartInstances with corresponding definitions - let count = 0 - const pre_filter = Object.values(this._assembly.data!.parts!.partInstances!) + const gamePieces = Object.values(this._assembly.data!.parts!.partInstances!) .filter(inst => gamepieceDefinitions.has(inst.partDefinitionReference!)) .map(inst => this.BinarySearchDesignTree(inst.info!.GUID!)) - .forEach(_ => count++) - .filter(instNode => !instNode) - .forEach((inst: mirabuf.IPartInstance) => { - count-- - + .map(instNode => { + if (instNode == null) { + this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) + return + } // TODO: Instead of marking them, separate them into a different body entirely - + // Figure out what we actually need to return here const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) gpRn.isGamePiece = true this.MovePartToRigidNode(instNode!.value!, gpRn) if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + + return instNode }) - if (count !== 0) - this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) + .filter(node => node != null) + + // TODO: Detatch game pieces from tree and remove part instances + + return gamePieces } private BandageRigidNodes(assembly: mirabuf.Assembly) { @@ -331,26 +337,28 @@ class MirabufParser { const valueA = ptv.get(partA)! const valueB = ptv.get(partB)! - while (pathA.value! == pathB.value! && pathA.value! != partA && pathB.value! != partB) { - const ancestorIndexA = this.BinarySearchIndex(valueA, pathA.children!) - const ancestorValueA = ptv.get(pathA.children![ancestorIndexA].value!)! - pathA = pathA.children![ancestorIndexA + (ancestorValueA < valueA ? 1 : 0)] + const traverse = (value: number | undefined, path: mirabuf.INode | undefined) => { + if (!value || !path) return - const ancestorIndexB = this.BinarySearchIndex(valueB, pathB.children!) - const ancestorValueB = ptv.get(pathB.children![ancestorIndexB].value!)! - pathB = pathB.children![ancestorIndexB + (ancestorValueB < valueB ? 1 : 0)] + const ancestorIndex = this.BinarySearchIndex(value, path.children!) + const ancestorValue = ptv.get(path.children![ancestorIndex].value!)! + path = path.children![ancestorIndex + (ancestorValue < value ? 1 : 0)] } - if (pathA.value! == partA && pathA.value! == pathB.value!) { - const ancestorIndexB = this.BinarySearchIndex(valueB, pathB.children!) - const ancestorValueB = ptv.get(pathB.children![ancestorIndexB].value!)! - pathB = pathB.children![ancestorIndexB + (ancestorValueB < valueB ? 1 : 0)] - } else if (pathB.value! == partB && pathA.value! == pathB.value!) { - const ancestorIndexA = this.BinarySearchIndex(valueA, pathA.children!) - const ancestorValueA = ptv.get(pathA.children![ancestorIndexA].value!)! - pathA = pathA.children![ancestorIndexA + (ancestorValueA < valueA ? 1 : 0)] + while (pathA.value! == pathB.value! && pathA.value! != partA && pathB.value! != partB) { + traverse(valueB, pathB) + traverse(valueA, pathA) } + const [value, path] = + pathA.value! == partA && pathA.value! == pathB.value! + ? [valueB, pathB] + : pathB.value! == partB && pathA.value! == pathB.value! + ? [valueA, pathA] + : [undefined, undefined] + + traverse(value, path) + return [pathA.value!, pathB.value!] } From 5fbdad8ff04e6d709be8b1b4f78de784b3a024a0 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 1 Jul 2025 10:50:56 -0700 Subject: [PATCH 11/83] feat: recursively parse gamepieces --- .../src/Resources/PWM_icon/16x16-normal.png | Bin 782 -> 0 bytes fission/src/mirabuf/MirabufInstance.ts | 5 +- fission/src/mirabuf/MirabufParser.ts | 95 ++++++++++-------- fission/src/mirabuf/MirabufSceneObject.ts | 12 ++- .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 9 +- 5 files changed, 71 insertions(+), 50 deletions(-) delete mode 100644 exporter/SynthesisFusionAddin/src/Resources/PWM_icon/16x16-normal.png diff --git a/exporter/SynthesisFusionAddin/src/Resources/PWM_icon/16x16-normal.png b/exporter/SynthesisFusionAddin/src/Resources/PWM_icon/16x16-normal.png deleted file mode 100644 index 07cd465f72d6f27012f2fd9dad871c162e02105a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 782 zcmV+p1M&QcP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0;5SpK~zXf&6Z0~ zQ&AL!KZ*!azy}HxBZ3kYh{QL>$Up}gNem&5oH#Xc$@R=Ay zd`26PhbT}ew6svbwR>(;6S?h>uH@2l&ug!<_B#7ggom{ota;51MbSgjoYH>^g;WMc zuBgyRWssZL<{(tnYF0q9uEOFR#3N9u{<11qn1YpMDBq^ZnzN{}K#A7WK|*skl|o6m z*34*ZR_!vat$@@LEX83p<>IV^;@*?=dhLI)q9;IO2V8joZI_@`PkXmE(@^;i=(_C! zrV?=Z9vnCaBX3~OF}QHc9pXq2oWH3kB0KyV_ML^RkD$3z?{6JhALy9t!iYIlb~VHJ zAPhcn0XnZsNvW_k1ba_GWeqeOheoxl>P76R8#6_!8zI!HvD0p^P@A5^P;*e~ZApjl zgCDW1#>-Mi&X6) z_@j3todA=mC$hwy6-EcT@Zc+b0RYF0fGwHBsHeO3Me9$dh_Ju=WOukr27rE z$O7ASpkevdB_tQ}D`o~Y1;Q_(|1R|15wTx-f8mVWa6-hsyMRq+T%XO&z%I+26mGew zOQh;FGa_Q%SPu9R%l0RA@}WL>F4GP?7D?M!C!Ur{$px1tQ_RQ(bt&wY+E@llF_*sm zGQ8Ph-cv9S{D?VcCgz=2?9_JqtYlqwnN3 zn=_!)N^Q%)ib_TfH>6VaE2(*Lm4QI96s&eu zFr{ptMcYH+tUlTWGV(qs*vjvh$_+;#^EKZ<{FkxxN8D={*uOpS7qM>jiniOs2mk;8 M07*qoM6N<$f{5y5xc~qF diff --git a/fission/src/mirabuf/MirabufInstance.ts b/fission/src/mirabuf/MirabufInstance.ts index 94e169700e..b2768f749d 100644 --- a/fission/src/mirabuf/MirabufInstance.ts +++ b/fission/src/mirabuf/MirabufInstance.ts @@ -227,9 +227,8 @@ class MirabufInstance { batchedMesh.castShadow = true batchedMesh.receiveShadow = true - materialBodyMap.forEach(instances => { - const body = instances[0] - instances[1].forEach(instance => { + materialBodyMap.forEach(([body, instances]) => { + instances.forEach(instance => { const mat = this._mirabufParser.globalTransforms.get(instance.info!.GUID!)! const geometry = new THREE.BufferGeometry() diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 875ae0bed8..f641459bbd 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -39,46 +39,51 @@ class MirabufParser { private _groundedNode: RigidNode | undefined - private _gamePieces: mirabuf.INode[] + private _gamePieces?: MirabufParser[] public get errors() { - return new Array(...this._errors) + return this._errors } - public get maxErrorSeverity() { + + public get maxErrorSeverity(): number { return Math.max(...this._errors.map(x => x[0])) } - public get assembly() { + public get assembly(): mirabuf.Assembly { return this._assembly } - public get partTreeValues() { + public get partTreeValues(): Map { return this._partTreeValues } - public get designHierarchyRoot() { + public get designHierarchyRoot(): mirabuf.INode { return this._designHierarchyRoot } - public get partToNodeMap() { + public get partToNodeMap(): Map { return this._partToNodeMap } - public get globalTransforms() { + public get globalTransforms(): Map { return this._globalTransforms } - public get groundedNode() { + public get groundedNode(): RigidNodeReadOnly | undefined { return this._groundedNode ? new RigidNodeReadOnly(this._groundedNode) : undefined } public get rigidNodes(): Map { return new Map(this._rigidNodes.map(x => [x.id, new RigidNodeReadOnly(x)])) } - public get directedGraph() { + public get directedGraph(): Graph { return this._directedGraph } - public get rootNode() { + public get rootNode(): string { return this._rootNode } + public get gamePieces(): MirabufParser[] | undefined { + return this._gamePieces + } public constructor(assembly: mirabuf.Assembly, progressHandle?: ProgressHandle) { this._assembly = assembly this._errors = new Array() this._globalTransforms = new Map() + this._gamePieces = undefined progressHandle?.Update("Parsing assembly...", 0.3) @@ -89,7 +94,7 @@ class MirabufParser { // Fields Only: Assign Game Piece rigid nodes if (!assembly.dynamic) { - this._gamePieces = this.PruneGamePieceNodes() + this._gamePieces = this.PruneGamePieceNodes().map(assembly => new MirabufParser(assembly)) } // 2: Grounded joint @@ -127,21 +132,18 @@ class MirabufParser { // 8. Retrieve Masses this._rigidNodes.forEach(rn => { - rn.mass = 0 - rn.parts.forEach(part => { - const inst = assembly.data?.parts?.partInstances?.[part] - if (!inst?.partDefinitionReference) return - const def = assembly.data?.parts?.partDefinitions?.[inst.partDefinitionReference!] - rn.mass += def?.massOverride ? def.massOverride : def?.physicalData?.mass ?? 0 - }) + rn.mass = [...rn.parts] + .map(part => assembly.data?.parts?.partInstances?.[part]) + .filter(inst => inst?.partDefinitionReference) + .reduce((acc, inst) => { + const def = assembly.data?.parts?.partDefinitions?.[inst?.partDefinitionReference!] + return acc + (def?.massOverride ?? def?.physicalData?.mass ?? 0) + }, 0) }) this._directedGraph = this.GenerateRigidNodeGraph(assembly, rootNodeId) - if (!this.assembly.data?.parts?.partDefinitions) { - console.warn("Failed to get part definitions") - return - } + if (!this.assembly.data?.parts?.partDefinitions) console.warn("Failed to get part definitions") } private TraverseTree(nodes: mirabuf.INode[], op: (node: mirabuf.INode) => void) { @@ -153,22 +155,23 @@ class MirabufParser { private InitializeRigidGroups() { const jointInstanceKeys = Object.keys(this._assembly.data!.joints!.jointInstances!) as string[] - jointInstanceKeys.forEach(key => { - if (key === GROUNDED_JOINT_ID) return - - const jInst = this._assembly.data!.joints!.jointInstances![key] - const [ancestorA, ancestorB] = this.FindAncestorialBreak(jInst.parentPart!, jInst.childPart!) - const parentRN = this.NewRigidNode() - - this.MovePartToRigidNode(ancestorA, parentRN) - this.MovePartToRigidNode(ancestorB, this.NewRigidNode()) - - if (jInst.parts && jInst.parts.nodes) - this.TraverseTree(jInst.parts.nodes, x => this.MovePartToRigidNode(x.value!, parentRN)) - }) + jointInstanceKeys + .filter(key => key !== GROUNDED_JOINT_ID) + .forEach(key => { + const jInst = this._assembly.data!.joints!.jointInstances![key] + const [ancestorA, ancestorB] = this.FindAncestorialBreak(jInst.parentPart!, jInst.childPart!) + const parentRN = this.NewRigidNode() + + this.MovePartToRigidNode(ancestorA, parentRN) + this.MovePartToRigidNode(ancestorB, this.NewRigidNode()) + + if (jInst.parts && jInst.parts.nodes) + this.TraverseTree(jInst.parts.nodes, x => this.MovePartToRigidNode(x.value!, parentRN)) + }) } - private PruneGamePieceNodes(): mirabuf.INode[] { + // Separates and returns the sub-asmeblies (partInstances) of each game piece + private PruneGamePieceNodes(): mirabuf.Assembly[] { // Collect all definitions labeled as gamepieces (dynamic = true) const gamepieceDefinitions: Set = new Set( Object.values(this._assembly.data!.parts!.partDefinitions!) @@ -179,8 +182,8 @@ class MirabufParser { // Create gamepiece rigid nodes from PartInstances with corresponding definitions const gamePieces = Object.values(this._assembly.data!.parts!.partInstances!) .filter(inst => gamepieceDefinitions.has(inst.partDefinitionReference!)) - .map(inst => this.BinarySearchDesignTree(inst.info!.GUID!)) - .map(instNode => { + .map(inst => { + const instNode = this.BinarySearchDesignTree(inst.info!.GUID!) if (instNode == null) { this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) return @@ -193,7 +196,12 @@ class MirabufParser { if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) - return instNode + const assembly = new mirabuf.Assembly({ + ...inst, + designHierarchy: { nodes: [instNode] }, + }) + + return assembly }) .filter(node => node != null) @@ -204,12 +212,11 @@ class MirabufParser { private BandageRigidNodes(assembly: mirabuf.Assembly) { assembly.data!.joints!.rigidGroups!.forEach(rg => { - let rn: RigidNode | null = null - rg.occurrences!.forEach(y => { + let rn: RigidNode | null = rg.occurrences!.reduce((rn, y) => { const currentRn = this._partToNodeMap.get(y)! - rn = !rn ? currentRn : currentRn.id != rn.id ? this.MergeRigidNodes(currentRn, rn) : rn - }) + return !rn ? currentRn : currentRn.id != rn.id ? this.MergeRigidNodes(currentRn, rn) : rn + }, null) }) } diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 41b9119bb7..6cfef758fa 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -700,14 +700,22 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { export async function CreateMirabuf( assembly: mirabuf.Assembly, progressHandle?: ProgressHandle -): Promise { +): Promise<{ mainSceneObject: MirabufSceneObject; gamePieces?: MirabufSceneObject[] } | null | undefined> { const parser = new MirabufParser(assembly, progressHandle) if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`) return } - return new MirabufSceneObject(new MirabufInstance(parser), assembly.info!.name!, progressHandle) + const mainSceneObject = new MirabufSceneObject(new MirabufInstance(parser), assembly.info!.name!, progressHandle) + const gamePieces = parser.gamePieces?.map( + parser => new MirabufSceneObject(new MirabufInstance(parser), parser.assembly.info!.name!) + ) + + return { + mainSceneObject, + gamePieces, + } } /** diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index 9442935393..c3e1a3b53d 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -103,7 +103,14 @@ function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle? if (assembly) { CreateMirabuf(assembly).then(x => { if (x) { - World.SceneRenderer.RegisterSceneObject(x) + const { mainSceneObject, gamePieces } = x + + World.SceneRenderer.RegisterSceneObject(mainSceneObject) + gamePieces?.forEach(piece => { + // We have to cache the game pieces while they're in scope + MirabufCachingService.CacheInfo(piece., type, piece.assemblyName ?? undefined) + World.SceneRenderer.RegisterSceneObject(piece) + }) progressHandle.Done() Global_OpenPanel?.("initial-config") From 6977e3d7f7d811877dad8099c70cb271cefb0695 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 1 Jul 2025 14:15:25 -0700 Subject: [PATCH 12/83] feat(wip): manually create ground joint --- fission/src/mirabuf/MirabufParser.ts | 63 ++++++++++--------- .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 3 +- 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index f641459bbd..d7b3bebd45 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -98,38 +98,39 @@ class MirabufParser { } // 2: Grounded joint - const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] - const gNode = this.NewRigidNode() - this.MovePartToRigidNode(gInst.parts!.nodes!.at(0)!.value!, gNode) + if (assembly.data?.joints) { + const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] + const gNode = this.NewRigidNode() + this.MovePartToRigidNode(gInst.parts!.nodes!.at(0)!.value!, gNode) - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - // 3: Traverse and round up - const traverseNodeRoundup = (node: mirabuf.INode, parentNode: RigidNode) => { - const currentNode = this._partToNodeMap.get(node.value!) - if (!currentNode) this.MovePartToRigidNode(node.value!, parentNode) + // 3: Traverse and round up + const traverseNodeRoundup = (node: mirabuf.INode, parentNode: RigidNode) => { + const currentNode = this._partToNodeMap.get(node.value!) + if (!currentNode) this.MovePartToRigidNode(node.value!, parentNode) - if (!node.children) return - node.children.forEach(x => traverseNodeRoundup(x, currentNode ?? parentNode)) - } - this._designHierarchyRoot.children?.forEach(x => traverseNodeRoundup(x, gNode)) - - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + if (!node.children) return + node.children.forEach(x => traverseNodeRoundup(x, currentNode ?? parentNode)) + } + this._designHierarchyRoot.children?.forEach(x => traverseNodeRoundup(x, gNode)) - this.BandageRigidNodes(assembly) // 4: Bandage via RigidGroups - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - // 5. Remove Empty RNs - this._rigidNodes = this._rigidNodes.filter(x => x.parts.size > 0) + this.BandageRigidNodes(assembly) // 4: Bandage via RigidGroups + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - // 6. If field, find grounded node and set isDynamic to false. Also just find grounded node again - this._groundedNode = this.partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!) - if (!assembly.dynamic && this._groundedNode) this._groundedNode.isDynamic = false + // 5. Remove Empty RNs + this._rigidNodes = this._rigidNodes.filter(x => x.parts.size > 0) - // 7. Update root RigidNode - const rootNodeId = this._partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!)?.id ?? this._rigidNodes[0].id - this._rootNode = rootNodeId + // 6. If field, find grounded node and set isDynamic to false. Also just find grounded node again + this._groundedNode = this.partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!) + if (!assembly.dynamic && this._groundedNode) this._groundedNode.isDynamic = false + // 7. Update root RigidNode + const rootNodeId = this._partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!)?.id ?? this._rigidNodes[0].id + this._rootNode = rootNodeId + } // 8. Retrieve Masses this._rigidNodes.forEach(rn => { rn.mass = [...rn.parts] @@ -196,8 +197,13 @@ class MirabufParser { if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + const groundedJoint = new mirabuf.joint.Joint({}) + mirabuf.Assembly.prototype.data.parts const assembly = new mirabuf.Assembly({ ...inst, + data: { + parts: new mirabuf.Parts(), + }, designHierarchy: { nodes: [instNode] }, }) @@ -292,10 +298,11 @@ class MirabufParser { */ private LoadGlobalTransforms() { const root = this._designHierarchyRoot - const partInstances = new Map( - Object.entries(this._assembly.data!.parts!.partInstances!) - ) - const partDefinitions = this._assembly.data!.parts!.partDefinitions! + const parts = this._assembly.data?.parts + if (!parts) return // TODO not sure if we should return or provide a default value + + const partInstances = new Map(Object.entries(parts!.partInstances!)) + const partDefinitions = parts!.partDefinitions! this._globalTransforms.clear() diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index c3e1a3b53d..c4ff35446f 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -108,7 +108,8 @@ function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle? World.SceneRenderer.RegisterSceneObject(mainSceneObject) gamePieces?.forEach(piece => { // We have to cache the game pieces while they're in scope - MirabufCachingService.CacheInfo(piece., type, piece.assemblyName ?? undefined) + // TODO: Use specific cache key + MirabufCachingService.CacheInfo(info.cacheKey, type, piece.assemblyName ?? undefined) World.SceneRenderer.RegisterSceneObject(piece) }) progressHandle.Done() From 207b499feb9875580e5a7ad318fcf98ce0930320 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Wed, 2 Jul 2025 15:49:51 -0700 Subject: [PATCH 13/83] feat: game pieces spawn as independent assets --- fission/src/mirabuf/MirabufParser.ts | 109 +++++++++++++++++++-------- 1 file changed, 77 insertions(+), 32 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index d7b3bebd45..998963023d 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" import { ProgressHandle } from "@/ui/components/ProgressNotificationData" +import { randomUUID } from "crypto" +import { join } from "path" export type RigidNodeId = string @@ -98,39 +100,38 @@ class MirabufParser { } // 2: Grounded joint - if (assembly.data?.joints) { - const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] - const gNode = this.NewRigidNode() - this.MovePartToRigidNode(gInst.parts!.nodes!.at(0)!.value!, gNode) + const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] + const gNode = this.NewRigidNode() + this.MovePartToRigidNode(gInst.parts!.nodes!.at(0)!.value!, gNode) - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - // 3: Traverse and round up - const traverseNodeRoundup = (node: mirabuf.INode, parentNode: RigidNode) => { - const currentNode = this._partToNodeMap.get(node.value!) - if (!currentNode) this.MovePartToRigidNode(node.value!, parentNode) + // 3: Traverse and round up + const traverseNodeRoundup = (node: mirabuf.INode, parentNode: RigidNode) => { + const currentNode = this._partToNodeMap.get(node.value!) + if (!currentNode) this.MovePartToRigidNode(node.value!, parentNode) - if (!node.children) return - node.children.forEach(x => traverseNodeRoundup(x, currentNode ?? parentNode)) - } - this._designHierarchyRoot.children?.forEach(x => traverseNodeRoundup(x, gNode)) + if (!node.children) return + node.children.forEach(x => traverseNodeRoundup(x, currentNode ?? parentNode)) + } + this._designHierarchyRoot.children?.forEach(x => traverseNodeRoundup(x, gNode)) - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - this.BandageRigidNodes(assembly) // 4: Bandage via RigidGroups - // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); + this.BandageRigidNodes(assembly) // 4: Bandage via RigidGroups + // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); - // 5. Remove Empty RNs - this._rigidNodes = this._rigidNodes.filter(x => x.parts.size > 0) + // 5. Remove Empty RNs + this._rigidNodes = this._rigidNodes.filter(x => x.parts.size > 0) - // 6. If field, find grounded node and set isDynamic to false. Also just find grounded node again - this._groundedNode = this.partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!) - if (!assembly.dynamic && this._groundedNode) this._groundedNode.isDynamic = false + // 6. If field, find grounded node and set isDynamic to false. Also just find grounded node again + this._groundedNode = this.partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!) + if (!assembly.dynamic && this._groundedNode) this._groundedNode.isDynamic = false + + // 7. Update root RigidNode + const rootNodeId = this._partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!)?.id ?? this._rigidNodes[0].id + this._rootNode = rootNodeId - // 7. Update root RigidNode - const rootNodeId = this._partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!)?.id ?? this._rigidNodes[0].id - this._rootNode = rootNodeId - } // 8. Retrieve Masses this._rigidNodes.forEach(rn => { rn.mass = [...rn.parts] @@ -197,19 +198,63 @@ class MirabufParser { if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) - const groundedJoint = new mirabuf.joint.Joint({}) - mirabuf.Assembly.prototype.data.parts - const assembly = new mirabuf.Assembly({ - ...inst, + // Create grounded joint + const jointDefinition = new mirabuf.joint.Joint({ + info: { + GUID: GROUNDED_JOINT_ID, + name: "grounded", + }, + jointMotionType: mirabuf.joint.JointMotion.RIGID, + origin: new mirabuf.Vector3(), + }) + const jointInstance = new mirabuf.joint.JointInstance({ + isEndEffector: false, + parentPart: "", + jointReference: jointDefinition.info?.name, + parts: { nodes: [instNode] }, + }) + + const joints = new mirabuf.joint.Joints({ + jointDefinitions: { + [GROUNDED_JOINT_ID]: jointDefinition, + }, + jointInstances: { + [GROUNDED_JOINT_ID]: jointInstance, + }, + rigidGroups: [], + motorDefinitions: {}, + }) + + const partDefinitionReference = inst?.partDefinitionReference ?? "" + const partDefinition = this.assembly.data?.parts?.partDefinitions?.[partDefinitionReference] ?? {} + + const parts = new mirabuf.Parts({ + info: inst.info, + partDefinitions: { + [partDefinitionReference]: partDefinition, + }, + partInstances: { + [inst.info?.GUID ?? ""]: inst, + }, + }) + + const gamePieceAssembly = new mirabuf.Assembly({ + info: inst.info, data: { - parts: new mirabuf.Parts(), + parts, + joints, + materials: this.assembly.data?.materials, + signals: {}, }, + dynamic: true, designHierarchy: { nodes: [instNode] }, + jointHierarchy: {}, + thumbnail: null, }) - return assembly + return gamePieceAssembly }) - .filter(node => node != null) + .filter(asm => asm != null) // TODO: Detatch game pieces from tree and remove part instances From 063c3c57f3efcfc675b1ef6d35043d92aed1cc2d Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 3 Jul 2025 11:08:10 -0700 Subject: [PATCH 14/83] feat: game pieces are properly separated into separate assemblies --- fission/src/mirabuf/MirabufParser.ts | 187 ++++++++++++------- fission/src/mirabuf/MirabufSceneObject.ts | 6 +- fission/src/systems/physics/PhysicsSystem.ts | 7 +- 3 files changed, 131 insertions(+), 69 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 998963023d..d6bf453a40 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -42,6 +42,7 @@ class MirabufParser { private _groundedNode: RigidNode | undefined private _gamePieces?: MirabufParser[] + private _isGamePiece: boolean public get errors() { return this._errors @@ -80,12 +81,16 @@ class MirabufParser { public get gamePieces(): MirabufParser[] | undefined { return this._gamePieces } + public get isGamePiece(): boolean { + return this._isGamePiece + } - public constructor(assembly: mirabuf.Assembly, progressHandle?: ProgressHandle) { + public constructor(assembly: mirabuf.Assembly, isGamePiece: boolean = false, progressHandle?: ProgressHandle) { this._assembly = assembly this._errors = new Array() this._globalTransforms = new Map() this._gamePieces = undefined + this._isGamePiece = isGamePiece progressHandle?.Update("Parsing assembly...", 0.3) @@ -96,7 +101,7 @@ class MirabufParser { // Fields Only: Assign Game Piece rigid nodes if (!assembly.dynamic) { - this._gamePieces = this.PruneGamePieceNodes().map(assembly => new MirabufParser(assembly)) + this._gamePieces = this.PruneGamePieceNodes().map(assembly => new MirabufParser(assembly, true)) } // 2: Grounded joint @@ -185,82 +190,103 @@ class MirabufParser { const gamePieces = Object.values(this._assembly.data!.parts!.partInstances!) .filter(inst => gamepieceDefinitions.has(inst.partDefinitionReference!)) .map(inst => { - const instNode = this.BinarySearchDesignTree(inst.info!.GUID!) + // To fix the issue where some game pieces are child nodes of others, iteratively search the designHierarchy then all the previously pruned gamepieces trees for the current node + const instNode = this.BinarySearchDesignTreePrune(inst.info!.GUID!) if (instNode == null) { + console.error("Failed to find Game piece in Design Tree") this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) return } // TODO: Instead of marking them, separate them into a different body entirely // Figure out what we actually need to return here - const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) - gpRn.isGamePiece = true - this.MovePartToRigidNode(instNode!.value!, gpRn) - if (instNode.children) - this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) - - // Create grounded joint - const jointDefinition = new mirabuf.joint.Joint({ - info: { - GUID: GROUNDED_JOINT_ID, - name: "grounded", - }, - jointMotionType: mirabuf.joint.JointMotion.RIGID, - origin: new mirabuf.Vector3(), - }) - const jointInstance = new mirabuf.joint.JointInstance({ - isEndEffector: false, - parentPart: "", - jointReference: jointDefinition.info?.name, - parts: { nodes: [instNode] }, - }) - - const joints = new mirabuf.joint.Joints({ - jointDefinitions: { - [GROUNDED_JOINT_ID]: jointDefinition, - }, - jointInstances: { - [GROUNDED_JOINT_ID]: jointInstance, - }, - rigidGroups: [], - motorDefinitions: {}, - }) - - const partDefinitionReference = inst?.partDefinitionReference ?? "" - const partDefinition = this.assembly.data?.parts?.partDefinitions?.[partDefinitionReference] ?? {} - - const parts = new mirabuf.Parts({ - info: inst.info, - partDefinitions: { - [partDefinitionReference]: partDefinition, - }, - partInstances: { - [inst.info?.GUID ?? ""]: inst, - }, - }) - - const gamePieceAssembly = new mirabuf.Assembly({ - info: inst.info, - data: { - parts, - joints, - materials: this.assembly.data?.materials, - signals: {}, - }, - dynamic: true, - designHierarchy: { nodes: [instNode] }, - jointHierarchy: {}, - thumbnail: null, - }) - return gamePieceAssembly + // Trick to capture and delete references to gamePiece + // const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) + // gpRn.isGamePiece = true + // this.MovePartToRigidNode(instNode!.value!, gpRn) + // if (instNode.children) + // this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + // this.DeleteRigidNode(gpRn) + + // Delete partInstances + Object.entries(this._assembly.data?.parts?.partInstances!) + .filter(([_key, subInst]) => inst === subInst) + .forEach(([key, _subInst]) => delete this._assembly.data?.parts?.partInstances?.[key]) + + // Delete partDefinitions + // Object.entries(this._assembly.data?.parts?.partDefinitions!) + // .filter(([_key, subInst]) => inst === subInst) + // .forEach(([key, _subInst]) => delete this._assembly.data?.parts?.partDefinitions?.[key]) + + return this.PartInstance_Assembly(inst, instNode) }) .filter(asm => asm != null) // TODO: Detatch game pieces from tree and remove part instances + console.log(`${gamePieces.length}`) return gamePieces } + private PartInstance_Assembly(inst: mirabuf.IPartInstance, instNode: mirabuf.INode): mirabuf.Assembly { + // Create grounded joint + const jointDefinition = new mirabuf.joint.Joint({ + info: { + GUID: GROUNDED_JOINT_ID, + name: "grounded", + }, + jointMotionType: mirabuf.joint.JointMotion.RIGID, + origin: new mirabuf.Vector3(), + }) + const jointInstance = new mirabuf.joint.JointInstance({ + isEndEffector: false, + parentPart: "", + jointReference: jointDefinition.info?.name, + parts: { nodes: [instNode] }, + }) + + const joints = new mirabuf.joint.Joints({ + jointDefinitions: { + [GROUNDED_JOINT_ID]: jointDefinition, + }, + jointInstances: { + [GROUNDED_JOINT_ID]: jointInstance, + }, + rigidGroups: [], + motorDefinitions: {}, + }) + + const partDefinitionReference = inst?.partDefinitionReference ?? "" + const partDefinition = this.assembly.data?.parts?.partDefinitions?.[partDefinitionReference] ?? {} + + const parts = new mirabuf.Parts({ + info: inst.info, + partDefinitions: { + [partDefinitionReference]: partDefinition, + }, + partInstances: { + [inst.info?.GUID ?? ""]: inst, + }, + }) + + console.log(inst.info?.name) + const gamePieceAssembly = new mirabuf.Assembly({ + info: inst.info, + data: { + parts, + joints, + materials: this.assembly.data?.materials, + signals: {}, + }, + dynamic: true, + designHierarchy: { nodes: [instNode] }, + jointHierarchy: {}, + thumbnail: null, + }) + + return gamePieceAssembly + } + private BandageRigidNodes(assembly: mirabuf.Assembly) { assembly.data!.joints!.rigidGroups!.forEach(rg => { let rn: RigidNode | null = rg.occurrences!.reduce((rn, y) => { @@ -316,6 +342,13 @@ class MirabufParser { return node } + private DeleteRigidNode(node: RigidNode) { + const index = this._rigidNodes.indexOf(node) + if (index != -1 && index != null) { + this._rigidNodes.splice(index) + } + } + private MergeRigidNodes(rnA: RigidNode, rnB: RigidNode) { const newRn = this.NewRigidNode("merged") const allParts = new Set([...rnA.parts, ...rnB.parts]) @@ -440,7 +473,10 @@ class MirabufParser { return Math.floor((h + l) / 2.0) } - private BinarySearchDesignTree(target: string): mirabuf.INode | null { + /** + * Old functon, replaced with BinarySearchDesignTreePrune, but has potentially useful functionality on its own + */ + private _BinarySearchDesignTree(target: string): mirabuf.INode | null { let node = this._designHierarchyRoot const targetValue = this._partTreeValues.get(target)! @@ -453,6 +489,29 @@ class MirabufParser { return node.value! == target ? node : null } + private BinarySearchDesignTreePrune(target: string): mirabuf.INode | null { + let parent = this._designHierarchyRoot + let node = this._designHierarchyRoot + const targetValue = this._partTreeValues.get(target)! + + while (node?.value != target && node?.children) { + const i = this.BinarySearchIndex(targetValue, node.children!) + const iValue = this._partTreeValues.get(node.children![i].value!)! + parent = node + node = node.children![i + (iValue < targetValue ? 1 : 0)] + } + + if (node?.value! == target) { + const index = parent?.children?.indexOf(node) + if (index != -1 && index != null) { + // parent?.children?.splice(index) + } + return node + } + + return null + } + private GenerateTreeValues() { let nextValue = 0 const partTreeValues = new Map() diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 6cfef758fa..0be3f26377 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -134,7 +134,11 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { } public get miraType(): MiraType { - return this._mirabufInstance.parser.assembly.dynamic ? MiraType.ROBOT : MiraType.FIELD + return this._mirabufInstance.parser.assembly.dynamic + ? this._mirabufInstance.parser.isGamePiece + ? MiraType.PIECE + : MiraType.ROBOT + : MiraType.FIELD } public get rootNodeId(): string { diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 1988708c05..62fefb682b 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -936,15 +936,14 @@ class PhysicsSystem extends WorldSystem { rn.parts.forEach(partId => { const partInstance = parser.assembly.data!.parts!.partInstances![partId]! - if (partInstance.skipCollider) return + if (!partInstance || partInstance.skipCollider) return const partDefinition = - parser.assembly.data!.parts!.partDefinitions![partInstance.partDefinitionReference!]! + parser.assembly.data!.parts!.partDefinitions![partInstance?.partDefinitionReference!] const partShapeResult = rn.isDynamic ? this.CreateConvexShapeSettingsFromPart(partDefinition) : this.CreateConcaveShapeSettingsFromPart(partDefinition) - // const partShapeResult = this.CreateConvexShapeSettingsFromPart(partDefinition) if (!partShapeResult) return @@ -1552,7 +1551,7 @@ function SetupCollisionFiltering(settings: Jolt.JoltSettings) { function filterNonPhysicsNodes(nodes: RigidNodeReadOnly[], mira: mirabuf.Assembly): RigidNodeReadOnly[] { return nodes.filter(x => { for (const part of x.parts) { - const inst = mira.data!.parts!.partInstances![part]! + const inst = mira.data!.parts!.partInstances![part]! // undefined const def = mira.data!.parts!.partDefinitions![inst.partDefinitionReference!]! if (def.bodies && def.bodies.length > 0) { return true From 46506bc7941c1f1df5a0640cd46a83f7f4c30146 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 3 Jul 2025 13:30:17 -0700 Subject: [PATCH 15/83] feat: game pieces can now be configured like all other assets --- fission/src/mirabuf/MirabufParser.ts | 16 +++++++++------- .../assembly-config/ConfigurePanel.tsx | 8 +++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index d6bf453a40..3b652ff6e9 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -201,12 +201,12 @@ class MirabufParser { // Figure out what we actually need to return here // Trick to capture and delete references to gamePiece - // const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) - // gpRn.isGamePiece = true - // this.MovePartToRigidNode(instNode!.value!, gpRn) - // if (instNode.children) - // this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) - // this.DeleteRigidNode(gpRn) + const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) + gpRn.isGamePiece = true + this.MovePartToRigidNode(instNode!.value!, gpRn) + if (instNode.children) + this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + this.DeleteRigidNode(gpRn) // Delete partInstances Object.entries(this._assembly.data?.parts?.partInstances!) @@ -230,13 +230,15 @@ class MirabufParser { private PartInstance_Assembly(inst: mirabuf.IPartInstance, instNode: mirabuf.INode): mirabuf.Assembly { // Create grounded joint + // const v = inst. + // console.log(`${v?.toString()}`) const jointDefinition = new mirabuf.joint.Joint({ info: { GUID: GROUNDED_JOINT_ID, name: "grounded", }, jointMotionType: mirabuf.joint.JointMotion.RIGID, - origin: new mirabuf.Vector3(), + origin: new mirabuf.Vector3(), // this._assembly.data?.joints?.jointInstances?.[inst.joints?.[0]!].offset, }) const jointInstance = new mirabuf.joint.JointInstance({ isEndEffector: false, diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index 55ee0b3f0b..f18142ca97 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -87,15 +87,17 @@ const AssemblySelection: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [u, pendingDeletes]) + ;[...World.SceneRenderer.sceneObjects.values()].forEach(o => { if (o instanceof MirabufSceneObject) console.log(o.assemblyName) }) + const options = useMemo(() => { - const list = configurationType == ConfigurationType.ROBOT ? robots : ConfigurationType.FIELD ? fields : gamePieces + const list = configurationType == ConfigurationType.ROBOT ? robots : configurationType == ConfigurationType.FIELD ? fields : gamePieces return list .filter((assembly): assembly is MirabufSceneObject => assembly != null) .map(assembly => makeSelectionOption(configurationType, assembly)) }, [configurationType, robots, fields]) - /** Robot or field select menu */ + /** Robot, game piece, or field select menu */ return ( = ({ return ( { onModeSelected((val as ConfigModeSelectionOption)?.configMode) }} From c12a1da6d18d76f94e480c7d2250fd3ca39c0c1c Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Thu, 3 Jul 2025 14:02:13 -0700 Subject: [PATCH 16/83] feat: add new error function --- fission/src/mirabuf/MirabufParser.ts | 28 ++++++++++++------- .../assembly-config/ConfigurePanel.tsx | 3 -- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 3b652ff6e9..8dabeb1eae 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -190,17 +190,12 @@ class MirabufParser { const gamePieces = Object.values(this._assembly.data!.parts!.partInstances!) .filter(inst => gamepieceDefinitions.has(inst.partDefinitionReference!)) .map(inst => { - // To fix the issue where some game pieces are child nodes of others, iteratively search the designHierarchy then all the previously pruned gamepieces trees for the current node const instNode = this.BinarySearchDesignTreePrune(inst.info!.GUID!) if (instNode == null) { - console.error("Failed to find Game piece in Design Tree") - this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) + this.NewError(ParseErrorSeverity.Unimportable, "Failed to find game piece in Design Tree") return } - // TODO: Instead of marking them, separate them into a different body entirely - // Figure out what we actually need to return here - - // Trick to capture and delete references to gamePiece + // Trick to capture and delete references to gamePiece, potentially unnecessary const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) gpRn.isGamePiece = true this.MovePartToRigidNode(instNode!.value!, gpRn) @@ -222,12 +217,14 @@ class MirabufParser { }) .filter(asm => asm != null) - // TODO: Detatch game pieces from tree and remove part instances console.log(`${gamePieces.length}`) return gamePieces } + /* + * Right now, this function is tailored for game pieces; in the future it could be generalized + */ private PartInstance_Assembly(inst: mirabuf.IPartInstance, instNode: mirabuf.INode): mirabuf.Assembly { // Create grounded joint // const v = inst. @@ -419,10 +416,10 @@ class MirabufParser { private FindAncestorialBreak(partA: string, partB: string): [string, string] { if (!this._partTreeValues.has(partA) || !this._partTreeValues.has(partB)) { - this._errors.push([ParseErrorSeverity.LikelyIssues, "Part not found in tree."]) + this.NewError(ParseErrorSeverity.LikelyIssues, "Part not found in tree.") return [partA, partB] } else if (partA == partB) { - this._errors.push([ParseErrorSeverity.LikelyIssues, "Part A and B are the same."]) + this.NewError(ParseErrorSeverity.LikelyIssues, "Part A and B are the same.") } const ptv = this._partTreeValues @@ -532,6 +529,17 @@ class MirabufParser { recursive(this._designHierarchyRoot) this._partTreeValues = partTreeValues } + + private NewError(severity: ParseErrorSeverity, message: string) { + if (severity >= ParseErrorSeverity.LikelyIssues) { + console.error(message) + if (severity == ParseErrorSeverity.Unimportable) + console.error(`Aborting Parse of assembly: ${this._assembly.info?.name}`) + } else { + console.warn(message) + } + this._errors.push([severity, message]) + } } /** diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index f18142ca97..8eb7b07823 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -87,9 +87,6 @@ const AssemblySelection: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [u, pendingDeletes]) - ;[...World.SceneRenderer.sceneObjects.values()].forEach(o => { if (o instanceof MirabufSceneObject) console.log(o.assemblyName) }) - - const options = useMemo(() => { const list = configurationType == ConfigurationType.ROBOT ? robots : configurationType == ConfigurationType.FIELD ? fields : gamePieces return list From 16d6460f3cb28831301b0bc5457542b2b0d8e7a4 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Fri, 4 Jul 2025 11:09:36 -0700 Subject: [PATCH 17/83] refactor: parser errors are generated by separate function --- fission/src/mirabuf/MirabufParser.ts | 21 ++++--- fission/src/mirabuf/MirabufSceneObject.ts | 70 +++++++++++++---------- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 8dabeb1eae..4e2bc2a03e 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -215,7 +215,7 @@ class MirabufParser { return this.PartInstance_Assembly(inst, instNode) }) - .filter(asm => asm != null) + .filter(asm => asm != undefined) console.log(`${gamePieces.length}`) @@ -225,22 +225,20 @@ class MirabufParser { /* * Right now, this function is tailored for game pieces; in the future it could be generalized */ - private PartInstance_Assembly(inst: mirabuf.IPartInstance, instNode: mirabuf.INode): mirabuf.Assembly { - // Create grounded joint - // const v = inst. - // console.log(`${v?.toString()}`) + private PartInstance_Assembly(inst: mirabuf.IPartInstance, instNode: mirabuf.INode): mirabuf.Assembly | undefined { const jointDefinition = new mirabuf.joint.Joint({ info: { GUID: GROUNDED_JOINT_ID, - name: "grounded", + name: GROUNDED_JOINT_ID, }, jointMotionType: mirabuf.joint.JointMotion.RIGID, + // Affects the placement of the joints, cannot affect the placement of the assembly in absolute space origin: new mirabuf.Vector3(), // this._assembly.data?.joints?.jointInstances?.[inst.joints?.[0]!].offset, }) const jointInstance = new mirabuf.joint.JointInstance({ isEndEffector: false, parentPart: "", - jointReference: jointDefinition.info?.name, + jointReference: jointDefinition.info?.GUID, parts: { nodes: [instNode] }, }) @@ -255,7 +253,14 @@ class MirabufParser { motorDefinitions: {}, }) - const partDefinitionReference = inst?.partDefinitionReference ?? "" + const partDefinitionReference = inst?.partDefinitionReference + if (partDefinitionReference == null) { + this.NewError( + ParseErrorSeverity.Unimportable, + "Game piece partInstance does not reference a partDefinition" + ) + return + } const partDefinition = this.assembly.data?.parts?.partDefinitions?.[partDefinitionReference] ?? {} const parts = new mirabuf.Parts({ diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 0be3f26377..301a3d70a0 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -170,36 +170,36 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { this.getPreferences() - // creating nametag for robots - if (this.miraType === MiraType.ROBOT) { - this._nameTag = new SceneOverlayTag(() => - this._brain instanceof SynthesisBrain - ? this._brain.inputSchemeName - : this._brain instanceof WPILibBrain - ? "Magic" - : "Not Configured" - ) - const material = new THREE.MeshBasicMaterial({ - color: 0xff00ff, // purple - transparent: true, - opacity: 0.1, - wireframe: true, - }) - material.depthTest = false - this._centerOfMassIndicator = new THREE.Mesh(new THREE.SphereGeometry(0.02), material) - this._centerOfMassIndicator.visible = PreferencesSystem.getGlobalPreference("ShowCenterOfMassIndicators") - - World.SceneRenderer.scene.add(this._centerOfMassIndicator) - - this._centerOfMassListenerUnsubscribe = PreferencesSystem.addPreferenceEventListener( - "ShowCenterOfMassIndicators", - e => { - if (this._centerOfMassIndicator) { - this._centerOfMassIndicator.visible = e.prefValue - } + // Creating nametag for robots + if (this.miraType !== MiraType.ROBOT) return + + this._nameTag = new SceneOverlayTag(() => + this._brain instanceof SynthesisBrain + ? this._brain.inputSchemeName + : this._brain instanceof WPILibBrain + ? "Magic" + : "Not Configured" + ) + const material = new THREE.MeshBasicMaterial({ + color: 0xff00ff, // purple + transparent: true, + opacity: 0.1, + wireframe: true, + }) + material.depthTest = false + this._centerOfMassIndicator = new THREE.Mesh(new THREE.SphereGeometry(0.02), material) + this._centerOfMassIndicator.visible = PreferencesSystem.getGlobalPreference("ShowCenterOfMassIndicators") + + World.SceneRenderer.scene.add(this._centerOfMassIndicator) + + this._centerOfMassListenerUnsubscribe = PreferencesSystem.addPreferenceEventListener( + "ShowCenterOfMassIndicators", + e => { + if (this._centerOfMassIndicator) { + this._centerOfMassIndicator.visible = e.prefValue } - ) - } + } + ) } public Setup(): void { @@ -615,7 +615,15 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { } public getSupplierData(): ContextData { - const data: ContextData = { title: this.miraType == MiraType.ROBOT ? "A Robot" : "A Field", items: [] } + const data: ContextData = { + title: + this.miraType == MiraType.ROBOT + ? "A Robot" + : this.miraType == MiraType.PIECE + ? "A Game Piece" + : "A Field", + items: [], + } data.items.push( { @@ -705,7 +713,7 @@ export async function CreateMirabuf( assembly: mirabuf.Assembly, progressHandle?: ProgressHandle ): Promise<{ mainSceneObject: MirabufSceneObject; gamePieces?: MirabufSceneObject[] } | null | undefined> { - const parser = new MirabufParser(assembly, progressHandle) + const parser = new MirabufParser(assembly, undefined, progressHandle) if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`) return From 0f374a53ea4cc6dc988ce24bff4e12cae248f3fa Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 8 Jul 2025 10:49:00 -0700 Subject: [PATCH 18/83] feat: game piece assemblies load in the correct position --- fission/src/mirabuf/MirabufParser.ts | 78 +++++++++++++++---- fission/src/mirabuf/MirabufSceneObject.ts | 21 ++++- fission/src/systems/physics/PhysicsSystem.ts | 5 +- .../initial-config/InitialConfigPanel.tsx | 8 +- .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 11 ++- 5 files changed, 94 insertions(+), 29 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 4e2bc2a03e..21c0152f27 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -2,8 +2,6 @@ import * as THREE from "three" import { mirabuf } from "@/proto/mirabuf" import { MirabufTransform_ThreeMatrix4 } from "@/util/TypeConversions" import { ProgressHandle } from "@/ui/components/ProgressNotificationData" -import { randomUUID } from "crypto" -import { join } from "path" export type RigidNodeId = string @@ -43,6 +41,7 @@ class MirabufParser { private _gamePieces?: MirabufParser[] private _isGamePiece: boolean + private _gamePieceTransform?: THREE.Matrix4 public get errors() { return this._errors @@ -84,6 +83,9 @@ class MirabufParser { public get isGamePiece(): boolean { return this._isGamePiece } + public get gamePieceTransform(): THREE.Matrix4 | undefined { + return this._gamePieceTransform + } public constructor(assembly: mirabuf.Assembly, isGamePiece: boolean = false, progressHandle?: ProgressHandle) { this._assembly = assembly @@ -91,6 +93,8 @@ class MirabufParser { this._globalTransforms = new Map() this._gamePieces = undefined this._isGamePiece = isGamePiece + if (assembly.transform && isGamePiece) + this._gamePieceTransform = MirabufTransform_ThreeMatrix4(assembly.transform) progressHandle?.Update("Parsing assembly...", 0.3) @@ -98,16 +102,20 @@ class MirabufParser { this.LoadGlobalTransforms() this.InitializeRigidGroups() // 1: from ancestral breaks in joints + console.log("initialized") // Fields Only: Assign Game Piece rigid nodes if (!assembly.dynamic) { this._gamePieces = this.PruneGamePieceNodes().map(assembly => new MirabufParser(assembly, true)) + // this._gamePieces = [] + // this.AssignGamePieceRigidNodes() } // 2: Grounded joint const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] const gNode = this.NewRigidNode() this.MovePartToRigidNode(gInst.parts!.nodes!.at(0)!.value!, gNode) + console.log("grounded") // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); @@ -120,10 +128,12 @@ class MirabufParser { node.children.forEach(x => traverseNodeRoundup(x, currentNode ?? parentNode)) } this._designHierarchyRoot.children?.forEach(x => traverseNodeRoundup(x, gNode)) + console.log("traversed") // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); this.BandageRigidNodes(assembly) // 4: Bandage via RigidGroups + console.log("bandaged") // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); // 5. Remove Empty RNs @@ -136,6 +146,7 @@ class MirabufParser { // 7. Update root RigidNode const rootNodeId = this._partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!)?.id ?? this._rigidNodes[0].id this._rootNode = rootNodeId + console.log("updated") // 8. Retrieve Masses this._rigidNodes.forEach(rn => { @@ -177,8 +188,36 @@ class MirabufParser { }) } + private AssignGamePieceRigidNodes() { + // Collect all definitions labeled as gamepieces (dynamic = true) + const gamepieceDefinitions: Set = new Set( + Object.values(this._assembly.data!.parts!.partDefinitions!) + .filter(def => def.dynamic) + .map((def: mirabuf.IPartDefinition) => { + return def.info!.GUID! + }) + ) + + // Create gamepiece rigid nodes from PartInstances with corresponding definitions + Object.values(this._assembly.data!.parts!.partInstances!).forEach((inst: mirabuf.IPartInstance) => { + if (!gamepieceDefinitions.has(inst.partDefinitionReference!)) return + + const instNode = this.BinarySearchDesignTreePrune(inst.info!.GUID!) + if (!instNode) { + this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) + return + } + + const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) + gpRn.isGamePiece = true + this.MovePartToRigidNode(instNode!.value!, gpRn) + if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) + }) + } + // Separates and returns the sub-asmeblies (partInstances) of each game piece private PruneGamePieceNodes(): mirabuf.Assembly[] { + console.log(`here`) // Collect all definitions labeled as gamepieces (dynamic = true) const gamepieceDefinitions: Set = new Set( Object.values(this._assembly.data!.parts!.partDefinitions!) @@ -286,6 +325,7 @@ class MirabufParser { designHierarchy: { nodes: [instNode] }, jointHierarchy: {}, thumbnail: null, + transform: inst.transform, }) return gamePieceAssembly @@ -421,10 +461,10 @@ class MirabufParser { private FindAncestorialBreak(partA: string, partB: string): [string, string] { if (!this._partTreeValues.has(partA) || !this._partTreeValues.has(partB)) { - this.NewError(ParseErrorSeverity.LikelyIssues, "Part not found in tree.") + this._errors.push([ParseErrorSeverity.LikelyIssues, "Part not found in tree."]) return [partA, partB] } else if (partA == partB) { - this.NewError(ParseErrorSeverity.LikelyIssues, "Part A and B are the same.") + this._errors.push([ParseErrorSeverity.LikelyIssues, "Part A and B are the same."]) } const ptv = this._partTreeValues @@ -433,27 +473,33 @@ class MirabufParser { const valueA = ptv.get(partA)! const valueB = ptv.get(partB)! - const traverse = (value: number | undefined, path: mirabuf.INode | undefined) => { - if (!value || !path) return + // TODO: Figure out why initial refactor failed + const traverse = (path: mirabuf.INode, value: number) => { const ancestorIndex = this.BinarySearchIndex(value, path.children!) const ancestorValue = ptv.get(path.children![ancestorIndex].value!)! path = path.children![ancestorIndex + (ancestorValue < value ? 1 : 0)] } while (pathA.value! == pathB.value! && pathA.value! != partA && pathB.value! != partB) { - traverse(valueB, pathB) - traverse(valueA, pathA) - } + const ancestorIndexA = this.BinarySearchIndex(valueA, pathA.children!) + const ancestorValueA = ptv.get(pathA.children![ancestorIndexA].value!)! + pathA = pathA.children![ancestorIndexA + (ancestorValueA < valueA ? 1 : 0)] - const [value, path] = - pathA.value! == partA && pathA.value! == pathB.value! - ? [valueB, pathB] - : pathB.value! == partB && pathA.value! == pathB.value! - ? [valueA, pathA] - : [undefined, undefined] + const ancestorIndexB = this.BinarySearchIndex(valueB, pathB.children!) + const ancestorValueB = ptv.get(pathB.children![ancestorIndexB].value!)! + pathB = pathB.children![ancestorIndexB + (ancestorValueB < valueB ? 1 : 0)] + } - traverse(value, path) + if (pathA.value! == partA && pathA.value! == pathB.value!) { + const ancestorIndexB = this.BinarySearchIndex(valueB, pathB.children!) + const ancestorValueB = ptv.get(pathB.children![ancestorIndexB].value!)! + pathB = pathB.children![ancestorIndexB + (ancestorValueB < valueB ? 1 : 0)] + } else if (pathB.value! == partB && pathA.value! == pathB.value!) { + const ancestorIndexA = this.BinarySearchIndex(valueA, pathA.children!) + const ancestorValueA = ptv.get(pathA.children![ancestorIndexA].value!)! + pathA = pathA.children![ancestorIndexA + (ancestorValueA < valueA ? 1 : 0)] + } return [pathA.value!, pathB.value!] } diff --git a/fission/src/mirabuf/MirabufSceneObject.ts b/fission/src/mirabuf/MirabufSceneObject.ts index 301a3d70a0..b987ac14c1 100644 --- a/fission/src/mirabuf/MirabufSceneObject.ts +++ b/fission/src/mirabuf/MirabufSceneObject.ts @@ -4,7 +4,7 @@ import MirabufInstance from "./MirabufInstance" import MirabufParser, { ParseErrorSeverity, RigidNodeId, RigidNodeReadOnly } from "./MirabufParser" import World from "@/systems/World" import Jolt from "@azaleacolburn/jolt-physics" -import { JoltMat44_ThreeMatrix4, JoltVec3_ThreeVector3 } from "@/util/TypeConversions" +import { JoltMat44_ThreeMatrix4, JoltVec3_ThreeVector3, ThreeVector3_JoltRVec3 } from "@/util/TypeConversions" import * as THREE from "three" import JOLT from "@/util/loading/JoltSyncLoader" import { BodyAssociate, LayerReserve } from "@/systems/physics/PhysicsSystem" @@ -234,7 +234,7 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { }) // Simulation - if (this.miraType == MiraType.ROBOT) { + if (this.miraType === MiraType.ROBOT) { World.SimulationSystem.RegisterMechanism(this._mechanism) const simLayer = World.SimulationSystem.GetSimulationLayer(this._mechanism)! this._brain = new SynthesisBrain(this, this._assemblyName) @@ -249,6 +249,21 @@ class MirabufSceneObject extends SceneObject implements ContextSupplier { this.UpdateBatches() + if (this.miraType === MiraType.PIECE) { + const jBodyId = this.mechanism.GetBodyByNodeId(this.mechanism.rootBody) + if (!jBodyId) { + console.warn( + `Jolt Body for SceneObjet ${this.id} with rootBody ${this.mechanism.rootBody} as NodeId not found` + ) + return + } + const position = ThreeVector3_JoltRVec3( + new THREE.Vector3().setFromMatrixPosition(this.mirabufInstance.parser.gamePieceTransform!) + ) + World.PhysicsSystem.SetBodyPosition(jBodyId, position) + this.UpdateMeshTransforms() + } + const bounds = this.ComputeBoundingBox() if (!Number.isFinite(bounds.min.y)) return @@ -713,7 +728,7 @@ export async function CreateMirabuf( assembly: mirabuf.Assembly, progressHandle?: ProgressHandle ): Promise<{ mainSceneObject: MirabufSceneObject; gamePieces?: MirabufSceneObject[] } | null | undefined> { - const parser = new MirabufParser(assembly, undefined, progressHandle) + const parser = new MirabufParser(assembly, false, progressHandle) if (parser.maxErrorSeverity >= ParseErrorSeverity.Unimportable) { console.error(`Assembly Parser produced significant errors for '${assembly.info!.name!}'`) return diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 93fe26cf9a..7cabf35296 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -326,6 +326,7 @@ class PhysicsSystem extends WorldSystem { public CreateMechanismFromParser(parser: MirabufParser): Mechanism { const layer = parser.assembly.dynamic ? new LayerReserve() : undefined + const transform = parser.assembly.transform const bodyMap = this.CreateBodiesFromParser(parser, layer) const rootBody = parser.rootNode const mechanism = new Mechanism(rootBody, bodyMap, parser.assembly.dynamic, layer) @@ -915,6 +916,9 @@ class PhysicsSystem extends WorldSystem { return parser.assembly.dynamic && assemblyMass > MAX_ROBOT_MASS ? MAX_ROBOT_MASS / assemblyMass : 1 })() + const translation = parser.assembly.transform + // console.log(`x: ${translation.GetX()} y: ${translation.GetY()} z: ${translation.GetZ()}`) + nonPhysicsNodes.forEach(rn => { const compoundShapeSettings = new JOLT.StaticCompoundShapeSettings() let shapesAdded = 0 @@ -949,7 +953,6 @@ class PhysicsSystem extends WorldSystem { const partShapeResult = rn.isDynamic ? this.CreateConvexShapeSettingsFromPart(partDefinition) : this.CreateConcaveShapeSettingsFromPart(partDefinition) - if (!partShapeResult) return const [shapeSettings, partMin, partMax] = partShapeResult diff --git a/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx b/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx index ff5575d851..aa7678de25 100644 --- a/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx +++ b/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx @@ -54,8 +54,10 @@ const InitialConfigPanel: React.FC = ({ panelId }) => { InputSystem.brainIndexSchemeMap.set(brainIndex, scheme) setSelectedScheme(scheme) - } else { + } else if (targetAssembly?.miraType === MiraType.FIELD) { setSelectedConfigurationType(ConfigurationType.FIELD) + } else { + setSelectedConfigurationType(ConfigurationType.PIECES) } closePanel(panelId) @@ -89,7 +91,7 @@ const InitialConfigPanel: React.FC = ({ panelId }) => { > {/** A scroll view with buttons to select default and custom input schemes */}
- {targetAssembly ? ( + {targetAssembly?.miraType !== MiraType.PIECE ? ( = ({ panelId }) => { {brainIndex != undefined ? ( {}} + onSelect={() => { }} onEdit={() => openPanel("configure")} onCreateNew={() => openModal("assign-new-scheme")} /> diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index c4ff35446f..c68c89c412 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -88,19 +88,17 @@ function GetCacheInfo(miraType: MiraType): MirabufCacheInfo[] { } function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle?: ProgressHandle) { - // If spawning a field, then remove all other fields if (type == MiraType.FIELD) { World.SceneRenderer.RemoveAllFields() } - if (!progressHandle) { - progressHandle = new ProgressHandle(info.name ?? info.cacheKey) - } + progressHandle ??= new ProgressHandle(info.name ?? info.cacheKey) World.PhysicsSystem.HoldPause(PAUSE_REF_ASSEMBLY_SPAWNING) MirabufCachingService.Get(info.id, type) .then(assembly => { if (assembly) { + console.log("have asm") CreateMirabuf(assembly).then(x => { if (x) { const { mainSceneObject, gamePieces } = x @@ -114,7 +112,8 @@ function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle? }) progressHandle.Done() - Global_OpenPanel?.("initial-config") + // TODO Disable for fields/game pieces + if (gamePieces != undefined && gamePieces.length > 0) Global_OpenPanel?.("initial-config") } else { progressHandle.Fail() } @@ -123,7 +122,7 @@ function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle? if (!info.name) MirabufCachingService.CacheInfo(info.cacheKey, type, assembly.info?.name ?? undefined) } else { progressHandle.Fail() - console.error("Failed to spawn robot") + console.error("Failed to spawn assembly") } }) .catch(() => progressHandle.Fail()) From 68b0468cb10d041b60c3524cdea89a9cb2186fa6 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 8 Jul 2025 11:06:46 -0700 Subject: [PATCH 19/83] chore: remove old logs --- fission/src/mirabuf/MirabufLoader.ts | 3 +- fission/src/mirabuf/MirabufParser.ts | 76 +++++-------------- fission/src/systems/physics/PhysicsSystem.ts | 1 - .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 3 +- 4 files changed, 23 insertions(+), 60 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 881f728efd..4885c3c1d7 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -439,8 +439,7 @@ class MirabufCachingService { private static async HashBuffer(buffer: ArrayBuffer): Promise { const hashBuffer = await crypto.subtle.digest("SHA-256", buffer) - const test = [...new Uint8Array(hashBuffer)] - const hash: string = String.fromCharCode([...test]) + const hash: string = String.fromCharCode(...new Uint8Array(hashBuffer)) return btoa(hash).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "") } diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 21c0152f27..2d1ccfca74 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -102,7 +102,6 @@ class MirabufParser { this.LoadGlobalTransforms() this.InitializeRigidGroups() // 1: from ancestral breaks in joints - console.log("initialized") // Fields Only: Assign Game Piece rigid nodes if (!assembly.dynamic) { @@ -115,7 +114,6 @@ class MirabufParser { const gInst = assembly.data!.joints!.jointInstances![GROUNDED_JOINT_ID] const gNode = this.NewRigidNode() this.MovePartToRigidNode(gInst.parts!.nodes!.at(0)!.value!, gNode) - console.log("grounded") // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); @@ -128,12 +126,10 @@ class MirabufParser { node.children.forEach(x => traverseNodeRoundup(x, currentNode ?? parentNode)) } this._designHierarchyRoot.children?.forEach(x => traverseNodeRoundup(x, gNode)) - console.log("traversed") // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); this.BandageRigidNodes(assembly) // 4: Bandage via RigidGroups - console.log("bandaged") // this.DebugPrintHierarchy(1, ...this._designHierarchyRoot.children!); // 5. Remove Empty RNs @@ -146,7 +142,6 @@ class MirabufParser { // 7. Update root RigidNode const rootNodeId = this._partToNodeMap.get(gInst.parts!.nodes!.at(0)!.value!)?.id ?? this._rigidNodes[0].id this._rootNode = rootNodeId - console.log("updated") // 8. Retrieve Masses this._rigidNodes.forEach(rn => { @@ -188,36 +183,10 @@ class MirabufParser { }) } - private AssignGamePieceRigidNodes() { - // Collect all definitions labeled as gamepieces (dynamic = true) - const gamepieceDefinitions: Set = new Set( - Object.values(this._assembly.data!.parts!.partDefinitions!) - .filter(def => def.dynamic) - .map((def: mirabuf.IPartDefinition) => { - return def.info!.GUID! - }) - ) - - // Create gamepiece rigid nodes from PartInstances with corresponding definitions - Object.values(this._assembly.data!.parts!.partInstances!).forEach((inst: mirabuf.IPartInstance) => { - if (!gamepieceDefinitions.has(inst.partDefinitionReference!)) return - - const instNode = this.BinarySearchDesignTreePrune(inst.info!.GUID!) - if (!instNode) { - this._errors.push([ParseErrorSeverity.LikelyIssues, "Failed to find Game piece in Design Tree"]) - return - } - - const gpRn = this.NewRigidNode(GAMEPIECE_SUFFIX) - gpRn.isGamePiece = true - this.MovePartToRigidNode(instNode!.value!, gpRn) - if (instNode.children) this.TraverseTree(instNode.children, x => this.MovePartToRigidNode(x.value!, gpRn)) - }) - } - - // Separates and returns the sub-asmeblies (partInstances) of each game piece + /* + * Separates and returns the sub-assemblies (partInstances) of each game piece + */ private PruneGamePieceNodes(): mirabuf.Assembly[] { - console.log(`here`) // Collect all definitions labeled as gamepieces (dynamic = true) const gamepieceDefinitions: Set = new Set( Object.values(this._assembly.data!.parts!.partDefinitions!) @@ -248,23 +217,26 @@ class MirabufParser { .forEach(([key, _subInst]) => delete this._assembly.data?.parts?.partInstances?.[key]) // Delete partDefinitions - // Object.entries(this._assembly.data?.parts?.partDefinitions!) - // .filter(([_key, subInst]) => inst === subInst) - // .forEach(([key, _subInst]) => delete this._assembly.data?.parts?.partDefinitions?.[key]) + Object.entries(this._assembly.data?.parts?.partDefinitions!) + .filter(([_key, subInst]) => inst === subInst) + .forEach(([key, _subInst]) => delete this._assembly.data?.parts?.partDefinitions?.[key]) return this.PartInstance_Assembly(inst, instNode) }) .filter(asm => asm != undefined) - console.log(`${gamePieces.length}`) - return gamePieces } /* - * Right now, this function is tailored for game pieces; in the future it could be generalized + * Converts specfic part instances to entire assemblies. Designed and tested for gamePiece instances, but theoretically should generalize */ - private PartInstance_Assembly(inst: mirabuf.IPartInstance, instNode: mirabuf.INode): mirabuf.Assembly | undefined { + private PartInstance_Assembly( + inst: mirabuf.IPartInstance, + instNode: mirabuf.INode, + isEndEffector: boolean = false, + isDynamic: boolean = true + ): mirabuf.Assembly | undefined { const jointDefinition = new mirabuf.joint.Joint({ info: { GUID: GROUNDED_JOINT_ID, @@ -272,10 +244,10 @@ class MirabufParser { }, jointMotionType: mirabuf.joint.JointMotion.RIGID, // Affects the placement of the joints, cannot affect the placement of the assembly in absolute space - origin: new mirabuf.Vector3(), // this._assembly.data?.joints?.jointInstances?.[inst.joints?.[0]!].offset, + origin: new mirabuf.Vector3(), }) const jointInstance = new mirabuf.joint.JointInstance({ - isEndEffector: false, + isEndEffector, parentPart: "", jointReference: jointDefinition.info?.GUID, parts: { nodes: [instNode] }, @@ -289,15 +261,13 @@ class MirabufParser { [GROUNDED_JOINT_ID]: jointInstance, }, rigidGroups: [], + // This probably needs to be changed if this function gets generalized for mix-n-match or something motorDefinitions: {}, }) const partDefinitionReference = inst?.partDefinitionReference if (partDefinitionReference == null) { - this.NewError( - ParseErrorSeverity.Unimportable, - "Game piece partInstance does not reference a partDefinition" - ) + this.NewError(ParseErrorSeverity.Unimportable, "partInstance does not reference a partDefinition") return } const partDefinition = this.assembly.data?.parts?.partDefinitions?.[partDefinitionReference] ?? {} @@ -312,17 +282,18 @@ class MirabufParser { }, }) - console.log(inst.info?.name) const gamePieceAssembly = new mirabuf.Assembly({ info: inst.info, data: { parts, joints, materials: this.assembly.data?.materials, + // This probably needs to be changed if this function gets generalized for mix-n-match or something signals: {}, }, - dynamic: true, + dynamic: isDynamic, designHierarchy: { nodes: [instNode] }, + // This probably needs to be changed if this function gets generalized for mix-n-match or something jointHierarchy: {}, thumbnail: null, transform: inst.transform, @@ -333,7 +304,7 @@ class MirabufParser { private BandageRigidNodes(assembly: mirabuf.Assembly) { assembly.data!.joints!.rigidGroups!.forEach(rg => { - let rn: RigidNode | null = rg.occurrences!.reduce((rn, y) => { + rg.occurrences!.reduce((rn, y) => { const currentRn = this._partToNodeMap.get(y)! return !rn ? currentRn : currentRn.id != rn.id ? this.MergeRigidNodes(currentRn, rn) : rn @@ -435,8 +406,6 @@ class MirabufParser { if (!partInstance || this.globalTransforms.has(child.value!)) return const mat = MirabufTransform_ThreeMatrix4(partInstance.transform!)! - // console.log(`[${partInstance.info!.name!}] -> ${matToString(mat)}`); - this._globalTransforms.set(child.value!, mat.premultiply(parent)) getTransforms(child, mat) }) @@ -452,8 +421,6 @@ class MirabufParser { ? MirabufTransform_ThreeMatrix4(def.baseTransform) : new THREE.Matrix4().identity() - // console.log(`[${partInstance.info!.name!}] -> ${matToString(mat!)}`); - this._globalTransforms.set(partInstance.info!.GUID!, mat) getTransforms(child, mat) }) @@ -474,7 +441,6 @@ class MirabufParser { const valueB = ptv.get(partB)! // TODO: Figure out why initial refactor failed - const traverse = (path: mirabuf.INode, value: number) => { const ancestorIndex = this.BinarySearchIndex(value, path.children!) const ancestorValue = ptv.get(path.children![ancestorIndex].value!)! diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 7cabf35296..790ef72477 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -326,7 +326,6 @@ class PhysicsSystem extends WorldSystem { public CreateMechanismFromParser(parser: MirabufParser): Mechanism { const layer = parser.assembly.dynamic ? new LayerReserve() : undefined - const transform = parser.assembly.transform const bodyMap = this.CreateBodiesFromParser(parser, layer) const rootBody = parser.rootNode const mechanism = new Mechanism(rootBody, bodyMap, parser.assembly.dynamic, layer) diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index c68c89c412..4d9431ba49 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -98,7 +98,6 @@ function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle? MirabufCachingService.Get(info.id, type) .then(assembly => { if (assembly) { - console.log("have asm") CreateMirabuf(assembly).then(x => { if (x) { const { mainSceneObject, gamePieces } = x @@ -113,7 +112,7 @@ function SpawnCachedMira(info: MirabufCacheInfo, type: MiraType, progressHandle? progressHandle.Done() // TODO Disable for fields/game pieces - if (gamePieces != undefined && gamePieces.length > 0) Global_OpenPanel?.("initial-config") + if (gamePieces == undefined || gamePieces.length < 0) Global_OpenPanel?.("initial-config") } else { progressHandle.Fail() } From 46921c7c0cd2c870166a85013d4b24aca759a8e0 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 8 Jul 2025 11:24:36 -0700 Subject: [PATCH 20/83] fix: build and lint --- fission/src/mirabuf/MirabufParser.ts | 31 +++---------------- fission/src/systems/physics/PhysicsSystem.ts | 7 ++--- .../mirabuf/ImportLocalMirabufModal.tsx | 8 +++-- 3 files changed, 11 insertions(+), 35 deletions(-) diff --git a/fission/src/mirabuf/MirabufParser.ts b/fission/src/mirabuf/MirabufParser.ts index 2d1ccfca74..63e4e9e20f 100644 --- a/fission/src/mirabuf/MirabufParser.ts +++ b/fission/src/mirabuf/MirabufParser.ts @@ -149,7 +149,7 @@ class MirabufParser { .map(part => assembly.data?.parts?.partInstances?.[part]) .filter(inst => inst?.partDefinitionReference) .reduce((acc, inst) => { - const def = assembly.data?.parts?.partDefinitions?.[inst?.partDefinitionReference!] + const def = assembly.data?.parts?.partDefinitions?.[inst?.partDefinitionReference as string] return acc + (def?.massOverride ?? def?.physicalData?.mass ?? 0) }, 0) }) @@ -212,12 +212,12 @@ class MirabufParser { this.DeleteRigidNode(gpRn) // Delete partInstances - Object.entries(this._assembly.data?.parts?.partInstances!) + Object.entries(this._assembly.data?.parts?.partInstances ?? {}) .filter(([_key, subInst]) => inst === subInst) .forEach(([key, _subInst]) => delete this._assembly.data?.parts?.partInstances?.[key]) // Delete partDefinitions - Object.entries(this._assembly.data?.parts?.partDefinitions!) + Object.entries(this._assembly.data?.parts?.partDefinitions ?? {}) .filter(([_key, subInst]) => inst === subInst) .forEach(([key, _subInst]) => delete this._assembly.data?.parts?.partDefinitions?.[key]) @@ -440,13 +440,6 @@ class MirabufParser { const valueA = ptv.get(partA)! const valueB = ptv.get(partB)! - // TODO: Figure out why initial refactor failed - const traverse = (path: mirabuf.INode, value: number) => { - const ancestorIndex = this.BinarySearchIndex(value, path.children!) - const ancestorValue = ptv.get(path.children![ancestorIndex].value!)! - path = path.children![ancestorIndex + (ancestorValue < value ? 1 : 0)] - } - while (pathA.value! == pathB.value! && pathA.value! != partA && pathB.value! != partB) { const ancestorIndexA = this.BinarySearchIndex(valueA, pathA.children!) const ancestorValueA = ptv.get(pathA.children![ancestorIndexA].value!)! @@ -489,22 +482,6 @@ class MirabufParser { return Math.floor((h + l) / 2.0) } - /** - * Old functon, replaced with BinarySearchDesignTreePrune, but has potentially useful functionality on its own - */ - private _BinarySearchDesignTree(target: string): mirabuf.INode | null { - let node = this._designHierarchyRoot - const targetValue = this._partTreeValues.get(target)! - - while (node.value != target && node.children) { - const i = this.BinarySearchIndex(targetValue, node.children!) - const iValue = this._partTreeValues.get(node.children![i].value!)! - node = node.children![i + (iValue < targetValue ? 1 : 0)] - } - - return node.value! == target ? node : null - } - private BinarySearchDesignTreePrune(target: string): mirabuf.INode | null { let parent = this._designHierarchyRoot let node = this._designHierarchyRoot @@ -517,7 +494,7 @@ class MirabufParser { node = node.children![i + (iValue < targetValue ? 1 : 0)] } - if (node?.value! == target) { + if (node?.value === target) { const index = parent?.children?.indexOf(node) if (index != -1 && index != null) { // parent?.children?.splice(index) diff --git a/fission/src/systems/physics/PhysicsSystem.ts b/fission/src/systems/physics/PhysicsSystem.ts index 790ef72477..3870b8fd3a 100644 --- a/fission/src/systems/physics/PhysicsSystem.ts +++ b/fission/src/systems/physics/PhysicsSystem.ts @@ -915,9 +915,6 @@ class PhysicsSystem extends WorldSystem { return parser.assembly.dynamic && assemblyMass > MAX_ROBOT_MASS ? MAX_ROBOT_MASS / assemblyMass : 1 })() - const translation = parser.assembly.transform - // console.log(`x: ${translation.GetX()} y: ${translation.GetY()} z: ${translation.GetZ()}`) - nonPhysicsNodes.forEach(rn => { const compoundShapeSettings = new JOLT.StaticCompoundShapeSettings() let shapesAdded = 0 @@ -944,10 +941,10 @@ class PhysicsSystem extends WorldSystem { rn.parts.forEach(partId => { const partInstance = parser.assembly.data!.parts!.partInstances![partId]! - if (!partInstance || partInstance.skipCollider) return + if (!partInstance?.partDefinitionReference || partInstance?.skipCollider) return const partDefinition = - parser.assembly.data!.parts!.partDefinitions![partInstance?.partDefinitionReference!] + parser.assembly.data!.parts!.partDefinitions![partInstance?.partDefinitionReference] const partShapeResult = rn.isDynamic ? this.CreateConvexShapeSettingsFromPart(partDefinition) diff --git a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx index 5eb7db9de8..d8184efeb3 100644 --- a/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx +++ b/fission/src/ui/modals/mirabuf/ImportLocalMirabufModal.tsx @@ -10,7 +10,6 @@ import { SynthesisIcons } from "@/ui/components/StyledComponents" import { ToggleButton, ToggleButtonGroup } from "@/ui/components/ToggleButtonGroup" import { usePanelControlContext } from "@/ui/helpers/UsePanelManager" import { PAUSE_REF_ASSEMBLY_SPAWNING } from "@/systems/physics/PhysicsSystem" -import { Global_OpenPanel } from "@/ui/components/GlobalUIControls" import { SoundPlayer } from "@/systems/sound/SoundPlayer" import buttonPressSound from "@/assets/sound-files/ButtonPress.mp3" @@ -58,9 +57,12 @@ const ImportLocalMirabufModal: React.FC = ({ modalId }) => { .then(x => CreateMirabuf(x!)) .then(x => { if (x) { - World.SceneRenderer.RegisterSceneObject(x) + const { mainSceneObject, gamePieces } = x - Global_OpenPanel?.("initial-config") + World.SceneRenderer.RegisterSceneObject(mainSceneObject) + gamePieces?.forEach(piece => { + World.SceneRenderer.RegisterSceneObject(piece) + }) } }) .finally(() => From 8371f7fdc437e07fe7481a30738b43d85e82f1c7 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 8 Jul 2025 11:24:49 -0700 Subject: [PATCH 21/83] chore: format --- .../assembly-config/ConfigurePanel.tsx | 15 +++++++++++++-- .../initial-config/InitialConfigPanel.tsx | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx index 8eb7b07823..2bc07f908b 100644 --- a/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx +++ b/fission/src/ui/panels/configuring/assembly-config/ConfigurePanel.tsx @@ -88,7 +88,12 @@ const AssemblySelection: React.FC = ({ }, [u, pendingDeletes]) const options = useMemo(() => { - const list = configurationType == ConfigurationType.ROBOT ? robots : configurationType == ConfigurationType.FIELD ? fields : gamePieces + const list = + configurationType == ConfigurationType.ROBOT + ? robots + : configurationType == ConfigurationType.FIELD + ? fields + : gamePieces return list .filter((assembly): assembly is MirabufSceneObject => assembly != null) .map(assembly => makeSelectionOption(configurationType, assembly)) @@ -236,7 +241,13 @@ const ConfigModeSelection: React.FC = ({ return ( { onModeSelected((val as ConfigModeSelectionOption)?.configMode) }} diff --git a/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx b/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx index aa7678de25..12d9ee5647 100644 --- a/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx +++ b/fission/src/ui/panels/configuring/initial-config/InitialConfigPanel.tsx @@ -107,7 +107,7 @@ const InitialConfigPanel: React.FC = ({ panelId }) => { {brainIndex != undefined ? ( { }} + onSelect={() => {}} onEdit={() => openPanel("configure")} onCreateNew={() => openModal("assign-new-scheme")} /> From 012dd94abc9c5c4409dd377d9dddc758fba4f639 Mon Sep 17 00:00:00 2001 From: Azalea Colburn Date: Tue, 8 Jul 2025 11:57:04 -0700 Subject: [PATCH 22/83] feat: game pieces can be cached --- fission/src/mirabuf/MirabufLoader.ts | 80 +++++++++++++---- .../ui/panels/mirabuf/ImportMirabufPanel.tsx | 88 ++++++++++++++++++- 2 files changed, 148 insertions(+), 20 deletions(-) diff --git a/fission/src/mirabuf/MirabufLoader.ts b/fission/src/mirabuf/MirabufLoader.ts index 4885c3c1d7..b36d5bd25d 100644 --- a/fission/src/mirabuf/MirabufLoader.ts +++ b/fission/src/mirabuf/MirabufLoader.ts @@ -27,12 +27,15 @@ type MapCache = { [id: MirabufCacheID]: MirabufCacheInfo } const robotsDirName = "Robots" const fieldsDirName = "Fields" +const piecesDirName = "Pieces" const root = await navigator.storage.getDirectory() const robotFolderHandle = await root.getDirectoryHandle(robotsDirName, { create: true }) const fieldFolderHandle = await root.getDirectoryHandle(fieldsDirName, { create: true }) +const pieceFolderHandle = await root.getDirectoryHandle(piecesDirName, { create: true }) export let backUpRobots: MapCache = {} export let backUpFields: MapCache = {} +export let backUpPieces: MapCache = {} export const canOPFS = await (async () => { try { @@ -65,9 +68,11 @@ export const canOPFS = await (async () => { window.localStorage.setItem(robotsDirName, "{}") window.localStorage.setItem(fieldsDirName, "{}") + window.localStorage.setItem(piecesDirName, "{}") backUpRobots = {} backUpFields = {} + backUpPieces = {} return false } @@ -99,7 +104,8 @@ class MirabufCachingService { return {} } - const key = miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName + const key = + miraType == MiraType.ROBOT ? robotsDirName : miraType == MiraType.FIELD ? fieldsDirName : piecesDirName const map = window.localStorage.getItem(key) if (map) { @@ -134,7 +140,7 @@ class MirabufCachingService { console.log(miraType) World.AnalyticsSystem?.Event("Remote Download", { - type: miraType === MiraType.ROBOT ? "robot" : "field", + type: miraType === MiraType.ROBOT ? "robot" : miraType === MiraType.FIELD ? "field" : "piece", fileSize: miraBuff.byteLength, }) @@ -151,6 +157,7 @@ class MirabufCachingService { // fallback: return raw buffer wrapped in MirabufCacheInfo return { id: Date.now().toString(), + // There isn't a way to know set this to game piece correctly, since you must parse the assembly to know miraType: miraType ?? (this.AssemblyFromBuffer(miraBuff).dynamic ? MiraType.ROBOT : MiraType.FIELD), cacheKey: fetchLocation, buffer: miraBuff, @@ -181,7 +188,7 @@ class MirabufCachingService { } World.AnalyticsSystem?.Event("APS Download", { - type: miraType == MiraType.ROBOT ? "robot" : "field", + type: miraType == MiraType.ROBOT ? "robot" : miraType == MiraType.FIELD ? "field" : "piece", fileSize: miraBuff.byteLength, }) @@ -226,7 +233,12 @@ class MirabufCachingService { try { const map: MapCache = this.GetCacheMap(miraType) const id = map[key].id - const _buffer = miraType == MiraType.ROBOT ? backUpRobots[id].buffer : backUpFields[id].buffer + const _buffer = + miraType == MiraType.ROBOT + ? backUpRobots[id].buffer + : miraType == MiraType.FIELD + ? backUpFields[id].buffer + : backUpPieces[id].buffer const _name = map[key].name const _thumbnailStorageID = map[key].thumbnailStorageID const info: MirabufCacheInfo = { @@ -238,8 +250,15 @@ class MirabufCachingService { thumbnailStorageID: thumbnailStorageID ?? _thumbnailStorageID, } map[key] = info - miraType == MiraType.ROBOT ? (backUpRobots[id] = info) : (backUpFields[id] = info) - window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + miraType == MiraType.ROBOT + ? (backUpRobots[id] = info) + : miraType == MiraType.FIELD + ? (backUpFields[id] = info) + : (backUpPieces[id] = info) + window.localStorage.setItem( + miraType == MiraType.ROBOT ? robotsDirName : miraType == MiraType.FIELD ? fieldsDirName : piecesDirName, + JSON.stringify(map) + ) return true } catch (e) { console.error(`Failed to cache info\n${e}`) @@ -282,12 +301,19 @@ class MirabufCachingService { public static async Get(id: MirabufCacheID, miraType: MiraType): Promise { try { // Get buffer from hashMap. If not in hashMap, check OPFS. Otherwise, buff is undefined - const cache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields + const cache = + miraType == MiraType.ROBOT ? backUpRobots : miraType == MiraType.FIELD ? backUpFields : backUpPieces const buff = cache[id]?.buffer ?? (await (async () => { const fileHandle = canOPFS - ? await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(id, { + ? await ( + miraType == MiraType.ROBOT + ? robotFolderHandle + : miraType == MiraType.FIELD + ? fieldFolderHandle + : pieceFolderHandle + ).getFileHandle(id, { create: false, }) : undefined @@ -299,7 +325,7 @@ class MirabufCachingService { const assembly = this.AssemblyFromBuffer(buff) World.AnalyticsSystem?.Event("Cache Get", { key: id, - type: miraType == MiraType.ROBOT ? "robot" : "field", + type: miraType == MiraType.ROBOT ? "robot" : miraType == MiraType.FIELD ? "field" : "piece", assemblyName: assembly.info!.name!, fileSize: buff.byteLength, }) @@ -328,24 +354,34 @@ class MirabufCachingService { if (map) { delete map[key] window.localStorage.setItem( - miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, + miraType == MiraType.ROBOT + ? robotsDirName + : miraType == MiraType.FIELD + ? fieldsDirName + : piecesDirName, JSON.stringify(map) ) } if (canOPFS) { - const dir = miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle + const dir = + miraType == MiraType.ROBOT + ? robotFolderHandle + : miraType == MiraType.FIELD + ? fieldFolderHandle + : pieceFolderHandle await dir.removeEntry(id) } - const backUpCache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields + const backUpCache = + miraType == MiraType.ROBOT ? backUpRobots : miraType == MiraType.FIELD ? backUpFields : backUpPieces if (backUpCache) { delete backUpCache[id] } World.AnalyticsSystem?.Event("Cache Remove", { key: key, - type: miraType == MiraType.ROBOT ? "robot" : "field", + type: miraType == MiraType.ROBOT ? "robot" : miraType == MiraType.FIELD ? "field" : "piece", }) return true } catch (e) { @@ -373,6 +409,7 @@ class MirabufCachingService { backUpRobots = {} backUpFields = {} + backUpPieces = {} } // Optional name for when assembly is being decoded anyway like in CacheAndGetLocal() @@ -386,6 +423,7 @@ class MirabufCachingService { const backupID = Date.now().toString() if (!miraType) { console.debug("Double loading") + // Piece can't be known without parsing miraType = this.AssemblyFromBuffer(miraBuff).dynamic ? MiraType.ROBOT : MiraType.FIELD } @@ -398,12 +436,15 @@ class MirabufCachingService { name: name, } map[key] = info - window.localStorage.setItem(miraType == MiraType.ROBOT ? robotsDirName : fieldsDirName, JSON.stringify(map)) + window.localStorage.setItem( + miraType == MiraType.ROBOT ? robotsDirName : miraType == MiraType.FIELD ? fieldsDirName : piecesDirName, + JSON.stringify(map) + ) World.AnalyticsSystem?.Event("Cache Store", { name: name ?? "-", key: key, - type: miraType == MiraType.ROBOT ? "robot" : "field", + type: miraType == MiraType.ROBOT ? "robot" : miraType == MiraType.FIELD ? "field" : "piece", fileSize: miraBuff.byteLength, }) @@ -411,7 +452,11 @@ class MirabufCachingService { if (canOPFS) { // Store in OPFS const fileHandle = await ( - miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle + miraType == MiraType.ROBOT + ? robotFolderHandle + : miraType == MiraType.FIELD + ? fieldFolderHandle + : pieceFolderHandle ).getFileHandle(backupID, { create: true }) const writable = await fileHandle.createWritable() await writable.write(miraBuff) @@ -419,7 +464,8 @@ class MirabufCachingService { } // Store in hash - const cache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields + const cache = + miraType == MiraType.ROBOT ? backUpRobots : miraType == MiraType.FIELD ? backUpFields : backUpPieces const mapInfo: MirabufCacheInfo = { id: backupID, miraType: miraType, diff --git a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx index 5a1e52d638..1357f6c726 100644 --- a/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx +++ b/fission/src/ui/panels/mirabuf/ImportMirabufPanel.tsx @@ -11,6 +11,7 @@ import { import MirabufCachingService, { backUpFields, backUpRobots, + backUpPieces, canOPFS, MirabufCacheInfo, MirabufRemoteInfo, @@ -78,11 +79,12 @@ const ItemCard: React.FC = ({ id, name, primaryButtonNode, primar export type MiraManifest = { robots: MirabufRemoteInfo[] fields: MirabufRemoteInfo[] + pieces: MirabufRemoteInfo[] } function GetCacheInfo(miraType: MiraType): MirabufCacheInfo[] { return Object.values( - canOPFS ? MirabufCachingService.GetCacheMap(miraType) : miraType == MiraType.ROBOT ? backUpRobots : backUpFields + canOPFS ? MirabufCachingService.GetCacheMap(miraType) : miraType == MiraType.ROBOT ? backUpRobots : miraType == MiraType.FIELD ? backUpFields : backUpPieces ) } @@ -136,6 +138,7 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { const [cachedRobots, setCachedRobots] = useState(GetCacheInfo(MiraType.ROBOT)) const [cachedFields, setCachedFields] = useState(GetCacheInfo(MiraType.FIELD)) + const [cachedPieces, setCachedPieces] = useState(GetCacheInfo(MiraType.PIECE)) const [manifest, setManifest] = useState() const [viewType, setViewType] = useState(MiraType.ROBOT) @@ -209,9 +212,20 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { if (!map[src["src"]]) fields.push({ displayName: src["displayName"], src: src["src"] }) } } + const pieces: MirabufRemoteInfo[] = [] + for (const src of x["pieces"] ?? []) { + if (typeof src == "string") { + const str = `/api/mira/pieces/${src}` + if (!map[str]) pieces.push({ displayName: src, src: str }) + } else { + if (!map[src["src"]]) pieces.push({ displayName: src["displayName"], src: src["src"] }) + } + } + setManifest({ robots, fields, + pieces, }) }) } @@ -341,6 +355,30 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { [cachedFields, selectCache, setCachedFields] ) + const cachedGamePieces = useMemo( + () => + cachedPieces + .sort((a, b) => a.name?.localeCompare(b.name ?? "") ?? -1) + .map(info => + ItemCard({ + name: info.name || info.cacheKey || "Unnamed Piece", + id: info.id, + primaryButtonNode: SynthesisIcons.AddLarge, + primaryOnClick: () => { + console.log(`Selecting cached game pieces: ${info.cacheKey}`) + selectCache(info, MiraType.PIECE) + }, + secondaryOnClick: () => { + console.log(`Deleting cache of: ${info.cacheKey}`) + MirabufCachingService.Remove(info.cacheKey, info.id, MiraType.PIECE) + + setCachedPieces(GetCacheInfo(MiraType.PIECE)) + }, + }) + ), + [cachedPieces, selectCache, setCachedPieces] + ) + // Generate Item cards for remote robots. const remoteRobotElements = useMemo(() => { const remoteRobots = manifest?.robots.filter( @@ -381,6 +419,27 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { ) }, [manifest?.fields, cachedFields, selectRemote]) + // Generate Item cards for remote fields. + const remoteGamePieces = useMemo(() => { + const remotePieces = manifest?.fields.filter( + path => !cachedPieces.some(info => info.cacheKey.includes(path.src)) + ) + return remotePieces + ?.sort((a, b) => a.displayName.localeCompare(b.displayName)) + .map(path => + ItemCard({ + name: path.displayName, + id: path.src, + primaryButtonNode: SynthesisIcons.DownloadLarge, + primaryOnClick: () => { + console.log(`Selecting remote: ${path}`) + selectRemote(path, MiraType.PIECE) + }, + }) + ) + }, [manifest?.fields, cachedPieces, selectRemote]) + + function downloadAllRemote(cached: MirabufCacheInfo[]): () => void { // eslint-disable-next-line react-hooks/rules-of-hooks return useCallback(() => { @@ -398,6 +457,7 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { const downloadAllRemoteRobots = downloadAllRemote(cachedRobots) const downloadAllRemoteFields = downloadAllRemote(cachedFields) + const downloadAllRemotePieces = downloadAllRemote(cachedPieces) // Generate Item cards for APS robots and fields. const hubElements = useMemo( @@ -446,6 +506,7 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { > Robots Fields + Game Pieces {viewType == MiraType.ROBOT ? ( <> @@ -457,7 +518,7 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { {cachedRobotElements} - ) : ( + ) : viewType == MiraType.FIELD ? ( <> {cachedFieldElements @@ -467,6 +528,15 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { {cachedFieldElements} + ) : (<> + + {cachedGamePieces + ? `${cachedGamePieces.length} Saved Game Piece${cachedGamePieces.length == 1 ? "" : "s"}` + : "Loading Saved Game Pieces"} + + + {cachedGamePieces} + )} = ({ panelId }) => { - ) : ( + ) : viewType == MiraType.FIELD ? ( <> {remoteFieldElements @@ -512,6 +582,18 @@ const ImportMirabufPanel: React.FC = ({ panelId }) => { + ) : (<> + + {remoteGamePieces + ? `${remoteGamePieces.length} Default Game Piece${remoteGamePieces.length == 1 ? "" : "s"}` + : "Loading Default Game Pieces"} + + + {remoteGamePieces} + + + + )}