Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions fission/.gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
public/assetpack.zip filter=lfs diff=lfs merge=lfs -text
src/test/**/*.snap binary linguist-generated=true
163 changes: 28 additions & 135 deletions fission/src/test/mirabuf/MirabufParser.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { describe, expect, test } from "vitest"
import { mirabuf } from "@/proto/mirabuf"
import MirabufCachingService, { MiraType } from "../../mirabuf/MirabufLoader.ts"
import MirabufParser, { type RigidNodeReadOnly } from "../../mirabuf/MirabufParser.ts"
import type { mirabuf } from "@/proto/mirabuf"
import type { Matrix4 } from "three"

describe("Mirabuf Parser Tests", () => {
test("Generate Rigid Nodes (Dozer_v9.mira)", async () => {
Expand All @@ -13,15 +14,12 @@ describe("Mirabuf Parser Tests", () => {
const t = new MirabufParser(spikeMira!)
const rn = [...t.rigidNodes.values()]

expect(filterNonPhysicsNodes(rn, spikeMira!).length).toBe(7)

// Validate joints
const jointValidation = validateJoints(spikeMira!)
expect(jointValidation.isValid).toBe(true)
expect(jointValidation.jointCount).toBe(6)
expect(jointValidation.wheelJoints).toBe(6)
expect(jointValidation.allJoints).toContain(mirabuf.joint.JointMotion.REVOLUTE) // Wheels are revolute joints
expect(jointValidation.allJoints).not.toContain(mirabuf.joint.JointMotion.SLIDER) // Dozer has no slider joints
const physicsNodes = filterNonPhysicsNodes(rn, spikeMira!).length
expect(physicsNodes).toBe(7)
expect([...t.partTreeValues.values()].length).toBe(13)
expect([...t.partToNodeMap.values()].length).toBe(12)
expect(await hashTransforms(t.globalTransforms)).toMatchSnapshot()
expect(t.rootNode).toBe("12")
})

/*
Expand All @@ -39,34 +37,39 @@ describe("Mirabuf Parser Tests", () => {

const t = new MirabufParser(spikeMira!)
const rn = [...t.rigidNodes.values()]
const physicsNodes = filterNonPhysicsNodes(rn, spikeMira!)

expect(filterNonPhysicsNodes(rn, spikeMira!).length).toBe(9)

// Validate joints
const jointValidation = validateJoints(spikeMira!)
expect(jointValidation.isValid).toBe(true)
expect(jointValidation.jointCount).toBe(8)

// Validate joint type distribution
const revoluteJoints = jointValidation.allJoints.filter(j => j === mirabuf.joint.JointMotion.REVOLUTE)
const sliderJoints = jointValidation.allJoints.filter(j => j === mirabuf.joint.JointMotion.SLIDER)

expect(revoluteJoints.length).toBe(6) // Should have 6 revolute joints (4 wheels + 2 additional)
expect(sliderJoints.length).toBe(2) // Should have 2 slider joints
expect(jointValidation.wheelJoints).toBe(4) // Should have 4 wheel joints
expect(physicsNodes.length).toBe(9)
expect([...t.partTreeValues.values()].length).toBe(12)
expect([...t.partToNodeMap.values()].length).toBe(11)
expect(await hashTransforms(t.globalTransforms)).toMatchSnapshot()
expect(t.rootNode).toBe("16")
})

test("Generate Rigid Nodes (FRC Field 2018_v13.mira)", async () => {
const field = await MirabufCachingService.cacheRemote(
"/api/mira/fields/FRC Field 2018_v13.mira",
MiraType.FIELD
).then(x => MirabufCachingService.get(x!.hash))

const t = new MirabufParser(field!)
const physicsNodes = filterNonPhysicsNodes([...t.rigidNodes.values()], field!)

expect(filterNonPhysicsNodes([...t.rigidNodes.values()], field!).length).toBe(34)
expect(physicsNodes.length).toBe(34)
expect([...t.partTreeValues.values()].length).toBe(982)
expect([...t.partToNodeMap.values()].length).toBe(981)
expect(await hashTransforms(t.globalTransforms)).toMatchSnapshot()
expect(t.rootNode).toBe("35merged")
})
})

async function hashTransforms(globalTransforms: Map<string, Matrix4>): Promise<ArrayBuffer> {
return crypto.subtle.digest(
"SHA-1",
new Int16Array([...globalTransforms.values()].flatMap(mat => mat.toArray()).map(n => Math.round(n * 1000)))
)
}

function filterNonPhysicsNodes(nodes: RigidNodeReadOnly[], mira: mirabuf.Assembly): RigidNodeReadOnly[] {
return nodes.filter(x => {
for (const part of x.parts) {
Expand All @@ -80,116 +83,6 @@ function filterNonPhysicsNodes(nodes: RigidNodeReadOnly[], mira: mirabuf.Assembl
})
}

interface JointValidationResult {
isValid: boolean
jointCount: number
allJoints: mirabuf.joint.JointMotion[]
wheelJoints: number
errors: string[]
warnings: string[]
}

function validateJoints(assembly: mirabuf.Assembly): JointValidationResult {
const result: JointValidationResult = {
isValid: true,
jointCount: 0,
allJoints: [],
wheelJoints: 0,
errors: [],
warnings: [],
}

const jointData = assembly.data?.joints
if (!jointData) {
result.errors.push("No joint data found in assembly")
result.isValid = false
return result
}

// Validate joint definitions and instances
const jointDefinitions = jointData.jointDefinitions || {}
const jointInstances = jointData.jointInstances || {}

// Count non-grounded joints
const nonGroundedJoints = Object.entries(jointInstances).filter(([key]) => key !== "grounded")
result.jointCount = nonGroundedJoints.length

// Validate each joint
for (const [jointId, jointInstance] of nonGroundedJoints) {
try {
// Check if joint definition exists
const jointDef = jointDefinitions[jointInstance.jointReference!]
if (!jointDef) {
result.errors.push(
`Joint instance '${jointId}' references missing definition '${jointInstance.jointReference}'`
)
result.isValid = false
continue
}

// Get all joints
if (jointDef.jointMotionType !== null && jointDef.jointMotionType !== undefined)
result.allJoints.push(jointDef.jointMotionType)

// Check for wheel joints
if (
jointDef.userData?.data?.wheel === "true" ||
(jointDef.jointMotionType === mirabuf.joint.JointMotion.REVOLUTE &&
jointDef.userData?.data?.wheelType !== undefined)
) {
result.wheelJoints++
}

// Validate joint motion type specific properties
switch (jointDef.jointMotionType) {
case mirabuf.joint.JointMotion.REVOLUTE:
if (!jointDef.rotational) {
result.errors.push(`Revolute joint '${jointId}' missing rotational definition`)
result.isValid = false
}
break
case mirabuf.joint.JointMotion.SLIDER:
if (!jointDef.prismatic) {
result.errors.push(`Slider joint '${jointId}' missing prismatic definition`)
result.isValid = false
}
break
case mirabuf.joint.JointMotion.BALL:
// Note: Ball joint properties are validated differently in the mirabuf format
if (!jointDef.custom) {
result.warnings.push(`Ball joint '${jointId}' may be missing ball-specific configuration`)
}
break
case mirabuf.joint.JointMotion.CUSTOM:
if (!jointDef.custom) {
result.errors.push(`Custom joint '${jointId}' missing custom definition`)
result.isValid = false
}
break
}

// Validate joint has an origin
if (!jointDef.origin) {
result.warnings.push(`Joint '${jointId}' has no origin defined`)
}
} catch (error) {
result.errors.push(`Error validating joint '${jointId}': ${error}`)
result.isValid = false
}
}

// Validate rigid groups if they exist
if (jointData.rigidGroups) {
for (const rigidGroup of jointData.rigidGroups) {
if (!rigidGroup.occurrences || rigidGroup.occurrences.length < 2) {
result.warnings.push(`Rigid group '${rigidGroup.name}' has fewer than 2 occurrences`)
}
}
}

return result
}

// function printRigidNodeParts(nodes: RigidNodeReadOnly[], mira: mirabuf.Assembly) {
// nodes.forEach(x => {
// console.log(`[ ${x.name} ]:`);
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading