Skip to content

Game Piece Asset Support [AARD-1902] #1190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 51 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
b4237fb
chore: remote --open from bun run dev
azaleacolburn Jun 30, 2025
b7f00f4
feat: add game pieces to configure asset panel
azaleacolburn Jun 30, 2025
96c74ac
feat(wip): start changing mirabuf parser
azaleacolburn Jun 30, 2025
b1731f4
feat: refactor ancestral break function
azaleacolburn Jul 1, 2025
cd697ae
feat: recursively parse gamepieces
azaleacolburn Jul 1, 2025
0dfe38f
feat(wip): manually create ground joint
azaleacolburn Jul 1, 2025
62ffc2c
chore: remote --open from bun run dev
azaleacolburn Jun 30, 2025
5563f31
feat: add game pieces to configure asset panel
azaleacolburn Jun 30, 2025
397e549
feat(wip): start changing mirabuf parser
azaleacolburn Jun 30, 2025
65e21e1
feat: refactor ancestral break function
azaleacolburn Jul 1, 2025
5fbdad8
feat: recursively parse gamepieces
azaleacolburn Jul 1, 2025
6977e3d
feat(wip): manually create ground joint
azaleacolburn Jul 1, 2025
b7f68cc
Merge branch 'colbura/1902/game-piece-asset-support' of github.com:Au…
azaleacolburn Jul 2, 2025
207b499
feat: game pieces spawn as independent assets
azaleacolburn Jul 2, 2025
063c3c5
feat: game pieces are properly separated into separate assemblies
azaleacolburn Jul 3, 2025
527c574
Merge branch 'dev' of github.com:Autodesk/synthesis into colbura/1902…
azaleacolburn Jul 3, 2025
46506bc
feat: game pieces can now be configured like all other assets
azaleacolburn Jul 3, 2025
c12a1da
feat: add new error function
azaleacolburn Jul 3, 2025
16d6460
refactor: parser errors are generated by separate function
azaleacolburn Jul 4, 2025
aa8c90c
Merge branch 'dev' of github.com:Autodesk/synthesis into colbura/1902…
azaleacolburn Jul 4, 2025
ce59c85
Merge branch 'dev' of github.com:Autodesk/synthesis into colbura/1902…
azaleacolburn Jul 7, 2025
0f374a5
feat: game piece assemblies load in the correct position
azaleacolburn Jul 8, 2025
68b0468
chore: remove old logs
azaleacolburn Jul 8, 2025
46921c7
fix: build and lint
azaleacolburn Jul 8, 2025
8371f7f
chore: format
azaleacolburn Jul 8, 2025
a0cd719
Merge branch 'dev' of github.com:Autodesk/synthesis into colbura/1902…
azaleacolburn Jul 8, 2025
012dd94
feat: game pieces can be cached
azaleacolburn Jul 8, 2025
1d59de8
fix: remote game pieces handlede properly
azaleacolburn Jul 8, 2025
cf27e05
chore: format
azaleacolburn Jul 8, 2025
b81dcbe
fix: tests
azaleacolburn Jul 8, 2025
9e94986
feat: game pieces are removed when a new field is spawned
azaleacolburn Jul 8, 2025
17d7c5c
fix: game pieces use the general dynamic layer
azaleacolburn Jul 8, 2025
bfafbd7
chore: format
azaleacolburn Jul 8, 2025
aff8e94
Update fission/src/mirabuf/MirabufParser.ts
azaleacolburn Jul 9, 2025
a2b0702
fix(wip): default for part instances during node filtering
azaleacolburn Jul 10, 2025
e788d25
testing(wip pls squash): logging and testing
azaleacolburn Jul 10, 2025
ded6bca
feat: remove all pieces whenever any field is removed
azaleacolburn Jul 14, 2025
154926e
fix(wip): cache game pieces properly
azaleacolburn Jul 14, 2025
f737962
reactor: cleaned up filtering on CreateMirabuf function
azaleacolburn Jul 15, 2025
d7f409b
fix: cached game pieces no longer leak into cached fields list
azaleacolburn Jul 15, 2025
23c4001
feat: separate assembly game pieces can be set as ejectable
azaleacolburn Jul 15, 2025
e1e6937
feat: game pieces interact with scoring zones
azaleacolburn Jul 15, 2025
0ee5339
fix: game pieces are encoded correctly
azaleacolburn Jul 15, 2025
e126ab6
Merge branch 'dev' of github.com:Autodesk/synthesis into colbura/1902…
azaleacolburn Jul 15, 2025
f963ab6
fix: when spawned from cache, game pieces are recognized as such
azaleacolburn Jul 16, 2025
9d33a93
chore: format
azaleacolburn Jul 16, 2025
ae4efb5
refactor: add mirabuf test folder
azaleacolburn Jul 16, 2025
10ab860
Merge branch 'dev' of github.com:Autodesk/synthesis into colbura/1902…
azaleacolburn Jul 16, 2025
81a4651
chore: clean up binary search function
azaleacolburn Jul 16, 2025
761245f
fix: build and test
azaleacolburn Jul 17, 2025
d396a5b
fix: download all supports pieces
azaleacolburn Jul 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
4 changes: 3 additions & 1 deletion fission/src/mirabuf/IntakeSensorSceneObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import SceneObject from "@/systems/scene/SceneObject"
import MirabufSceneObject, { RigidNodeAssociate } from "./MirabufSceneObject"
import Jolt from "@azaleacolburn/jolt-physics"
import * as THREE from "three"
import { LAYER_GENERAL_DYNAMIC } from "@/systems/physics/PhysicsSystem"
import World from "@/systems/World"
import JOLT from "@/util/loading/JoltSyncLoader"
import {
Expand Down Expand Up @@ -130,7 +131,8 @@ class IntakeSensorSceneObject extends SceneObject {

private intakeCollision(gpID: Jolt.BodyID) {
const associate = <RigidNodeAssociate>World.physicsSystem.getBodyAssociation(gpID)
if (associate?.isGamePiece) {
const inGPLayer = World.physicsSystem.getBody(gpID).GetObjectLayer() === LAYER_GENERAL_DYNAMIC
if (associate?.isGamePiece || inGPLayer) {
associate.robotLastInContactWith = this._parentAssembly
this._parentAssembly.setEjectable(gpID)
}
Expand Down
5 changes: 2 additions & 3 deletions fission/src/mirabuf/MirabufInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
124 changes: 86 additions & 38 deletions fission/src/mirabuf/MirabufLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -132,7 +138,7 @@ class MirabufCachingService {
const miraBuff = await resp.arrayBuffer()

World.analyticsSystem?.event("Remote Download", {
type: miraType === MiraType.ROBOT ? "robot" : "field",
type: miraType === MiraType.ROBOT ? "robot" : miraType === MiraType.FIELD ? "field" : "piece",
fileSize: miraBuff.byteLength,
})

Expand All @@ -145,6 +151,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,
Expand Down Expand Up @@ -175,7 +182,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,
})

Expand Down Expand Up @@ -220,9 +227,15 @@ 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 defaultName = map[key].name
const defaultStorageID = map[key].thumbnailStorageID

const info: MirabufCacheInfo = {
id: id,
cacheKey: key,
Expand All @@ -232,8 +245,15 @@ class MirabufCachingService {
thumbnailStorageID: thumbnailStorageID ?? defaultStorageID,
}
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}`)
Expand Down Expand Up @@ -326,33 +346,40 @@ class MirabufCachingService {
* @returns {Promise<mirabufAssembly | undefined>} Promise with the result of the promise. Assembly of the mirabuf file if successful, undefined if not.
*/
public static async get(id: MirabufCacheID, miraType: MiraType): Promise<mirabuf.Assembly | undefined> {
const cache =
miraType == MiraType.ROBOT ? backUpRobots : miraType == MiraType.FIELD ? backUpFields : backUpPieces

try {
// Get buffer from hashMap. If not in hashMap, check OPFS. Otherwise, buff is undefined
const cache = miraType == MiraType.ROBOT ? backUpRobots : backUpFields
const buff =
cache[id]?.buffer ??
(await (async () => {
const fileHandle = canOPFS
? await (miraType == MiraType.ROBOT ? robotFolderHandle : fieldFolderHandle).getFileHandle(id, {
create: false,
})
: undefined
return fileHandle ? await fileHandle.getFile().then(x => x.arrayBuffer()) : undefined
})())

// If we have buffer, get assembly
if (buff) {
const assembly = this.assemblyFromBuffer(buff)
World.analyticsSystem?.event("Cache Get", {
key: id,
type: miraType == MiraType.ROBOT ? "robot" : "field",
assemblyName: assembly.info!.name!,
fileSize: buff.byteLength,
const getOPFSBuffer = async () => {
const dirHandle =
miraType == MiraType.ROBOT
? robotFolderHandle
: miraType == MiraType.FIELD
? fieldFolderHandle
: pieceFolderHandle
if (!canOPFS) return

const fileHandle = await dirHandle.getFileHandle(id, {
create: false,
})
return assembly
} else {
return await fileHandle.getFile().then(x => x.arrayBuffer())
}

const buff = cache[id]?.buffer ?? (await getOPFSBuffer())
if (!buff) {
console.error(`Failed to find arrayBuffer for id: ${id}`)
return undefined
}

const assembly = this.assemblyFromBuffer(buff)
World.analyticsSystem?.event("Cache Get", {
key: id,
type: miraType == MiraType.ROBOT ? "robot" : miraType == MiraType.FIELD ? "field" : "piece",
assemblyName: assembly.info!.name!,
fileSize: buff.byteLength,
})
return assembly
} catch (e) {
console.error(`Failed to find file\n${e}`)
return undefined
Expand All @@ -374,24 +401,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) {
Expand Down Expand Up @@ -419,6 +456,7 @@ class MirabufCachingService {

backUpRobots = {}
backUpFields = {}
backUpPieces = {}
}

/**
Expand Down Expand Up @@ -475,12 +513,14 @@ class MirabufCachingService {
key: string,
miraBuff: ArrayBuffer,
miraType?: MiraType,
// Optional name for when assembly is being decoded anyway like in CacheAndGetLocal()
name?: string
): Promise<MirabufCacheInfo | undefined> {
try {
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
}

Expand All @@ -493,28 +533,36 @@ 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,
})

// Store buffer
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)
await writable.close()
}

// 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,
Expand All @@ -534,8 +582,7 @@ class MirabufCachingService {

private static async hashBuffer(buffer: ArrayBuffer): Promise<string> {
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer)
let hash = ""
new Uint8Array(hashBuffer).forEach(x => (hash = hash + String.fromCharCode(x)))
const hash: string = String.fromCharCode(...new Uint8Array(hashBuffer))
return btoa(hash).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
}

Expand All @@ -547,6 +594,7 @@ class MirabufCachingService {
export enum MiraType {
ROBOT = 1,
FIELD,
PIECE,
}

export default MirabufCachingService
Loading
Loading