Skip to content

Use matrix traits as transform source-of-truth#661

Merged
micheal-parks merged 27 commits into
mainfrom
matrix-trait
May 14, 2026
Merged

Use matrix traits as transform source-of-truth#661
micheal-parks merged 27 commits into
mainfrom
matrix-trait

Conversation

@micheal-parks
Copy link
Copy Markdown
Member

@micheal-parks micheal-parks commented May 7, 2026

This PR replaces our current pose traits with matrices, and introduces a world matrix resolver that runs on trait updates. This has the following benefits:

  • Matrices over poses makes both world transforms and edited / live transforms much simpler and performant to calculate
  • Three.js objects are no longer queried on the fly to obtain world position and rotation
  • Matrix updates for Three.js objects now only happen on network response, not every frame (the default in Three.js)
  • We can now move to instanced rendering of all objects based on WorldMatrix, solving transparency and performance issues
  • We can more easily render based on trait since the Entity now is the source of truth for all position and rotation data

Followups

After this, the remaining refactor steps are:

  • Remove <Portal> and render all objects based on MatrixWorld.
  • Instance render all geometries.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 7, 2026

🦋 Changeset detected

Latest commit: 256e06c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@viamrobotics/motion-tools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 7, 2026

PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-05-14 20:20 UTC

@micheal-parks micheal-parks marked this pull request as ready for review May 11, 2026 16:17
@micheal-parks
Copy link
Copy Markdown
Member Author

@claude review plz

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 11, 2026

Claude finished @micheal-parks's task in 3m 56s —— View job


PR Review

  • Read CLAUDE.md
  • Gather full diff and modified files
  • Review core ECS changes (traits.ts, worldMatrix.ts)
  • Review component changes
  • Review hook and utility changes
  • Review tests
  • Post inline comments
  • Post final summary

This is a substantial, well-reasoned refactor. Replacing the Pose SoA traits with Matrix4 AoS traits is the right call — the comment in traits.ts explaining why AoS wins over SoA for this access pattern is exactly the kind of documentation that should be here. The worldMatrix.ts system is clean: it's a plain function (no Svelte lifecycle), it's testable in isolation, it batches via microtask, and the cache prevents redundant recomputation of sibling parents. The test suite covers the core cases well.

One bug

See inline: onpointerenter in useEntityEvents.svelte.ts stores a raw local hover matrix without composing with WorldMatrix, while onpointermove does compose it. For entities with a parent transform, the tooltip placement will be wrong on the first hover frame. Easy fix — just mirror the onpointermove composition logic.

Other issues (see inline comments)

Severity File Issue
Bug useEntityEvents.svelte.ts:50 onpointerenter InstancedMatrix not composed with WorldMatrix
Minor worldMatrix.ts:160 flushWorldMatrix exported but never used anywhere; awkward second parameter
Minor worldMatrix.ts:133 Initial seed loop misses LiveMatrix / Scale entities (safe today, fragile future)
Minor Details.svelte:83 Drawing entities no longer show local position — intentional?
Nit worldMatrix.ts:61 One Matrix4 allocation per entity per flush; could write into stored matrix directly
Nit provideWorldMatrix.svelte.ts:13 Implicit $effect cleanup — consider being explicit

Things that look good

  • The composeRenderedMatrix / solveEditedMatrix pair in transform.ts is well-named and the algebra is correct: live × baseline⁻¹ × edited forward, baseline × live⁻¹ × target to solve for edited.
  • toLocalRenderedMatrix in worldMatrix.ts correctly mirrors the blend order in Frame.svelte, so WorldMatrix stays in sync with the actual rendered scene graph.
  • updateGeometryTrait now guards with value equality before calling entity.set — this avoids spurious ECS change events that would cascade into unnecessary WorldMatrix recomputes.
  • The FrameEditSession snapshot/restore pattern is intact and correctly snapshots EditedMatrix instead of a raw pose.
  • The recomputeWorldMatrix cache correctly prevents O(n²) work when a parent is dirtied and all children are in the expanded set.
  • transform.spec.ts deletion is fine — its coverage is now subsumed by worldMatrix.spec.ts which tests the composed system end-to-end.

Comment thread src/lib/components/Entities/hooks/useEntityEvents.svelte.ts Outdated
Comment thread src/lib/ecs/worldMatrix.ts Outdated
Comment thread src/lib/ecs/worldMatrix.ts Outdated
Comment thread src/lib/ecs/worldMatrix.ts
Comment thread src/lib/ecs/provideWorldMatrix.svelte.ts
Comment thread src/lib/components/overlay/Details.svelte Outdated
@micheal-parks
Copy link
Copy Markdown
Member Author

@claude final review plz

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 11, 2026

Claude finished @micheal-parks's task in 5m 28s —— View job


Final Review

  • Read CLAUDE.md
  • Diff full PR changes
  • Read key modified files
  • Check previous review issues
  • Review remaining code
  • Post inline comments
  • Post final summary

All issues from the first review have been addressed. This is in good shape to merge.

Previous issues — resolved

Issue Status
onpointerenter not composing InstancedMatrix with WorldMatrix ✅ Fixed — buildHoverMatrix + compose is now applied in both onpointerenter and onpointermove
flushWorldMatrix exported but never used ✅ Removed entirely
Initial seed loop missing LiveMatrix / Scale ✅ All four types now seeded
One Matrix4 allocation per entity per flush recomputeWorldMatrix reuses entity.get(WorldMatrix) storage
$effect implicit cleanup ✅ Pattern is idiomatic Svelte 5 — implicit return is correct
Drawing entities no longer show local position in Details localPose falls back to matrix.current, so draw-API entities show read-only local position

New findings (minor)

See inline comments:

Severity File Issue
Nit transform.ts:200 isFiniteMatrix exported but never imported — either delete or wire into FrameEditSession.validate()
Minor Details.svelte:253 worldPosition in clipboard changed from metres → mm; probably intentional alignment with the display, but worth confirming
Nit Details.svelte:99 createPose() allocates a fresh object on every reactive re-run; could pool like tempPose elsewhere

Things that look correct

  • useTrait.svelte.ts: the version counter for AoS traits is correctly scoped — onAdd triggers via $state.raw reassignment, onChange triggers via version++. Logic is sound.
  • stageFrameTransform in SelectedTransformControls.svelte: unit chain is correct — vector3ToPose (m→mm), poseToMatrix (mm→m), solveEditedMatrix (all m), matrixToPose (m→mm) → stagePose.
  • HoveredEntityTooltip.svelte receives x/y/z in metres from HoveredEntity.svelte decompose and uses them as Three.js position props — correct.
  • BatchedArrows.svelte: origin from matrix decompose is in metres; old code did pose.xyz * 0.001 — same result, no regression.
  • worldMatrix.ts: scale applied before premultiply(parentWorld) — correct local-space scale, standard TRS composition.

Comment thread src/lib/transform.ts Outdated
Comment thread src/lib/components/overlay/Details.svelte
Comment thread src/lib/components/overlay/Details.svelte

// Initialize the state with the current value of the trait.
let value = $derived(entity?.get(trait))
let value = $state.raw<TraitRecord<T>>()
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reactivity bugfix, will need to add it to my upstream koota PR

}
}

useTask(
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥳

@micheal-parks micheal-parks changed the title Use matrix traits and set WorldMatrix trait as positional source-of-truth Use matrix traits and set WorldMatrix trait as transform source-of-truth May 11, 2026
@micheal-parks micheal-parks changed the title Use matrix traits and set WorldMatrix trait as transform source-of-truth Use matrix traits as transform source-of-truth May 11, 2026
Copy link
Copy Markdown
Member

@mattmacf98 mattmacf98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved with comments to:

  1. double check microtask is 1-1 with animation frame
  2. move transform math stuff to lib/three (to be renamed lib/math)

@micheal-parks micheal-parks merged commit 7e983d0 into main May 14, 2026
7 checks passed
@micheal-parks micheal-parks deleted the matrix-trait branch May 14, 2026 20:19
@claude claude Bot mentioned this pull request May 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants