Skip to content

Add opt-in transform orthonormalization to the Collada importer#138

Merged
Renanse merged 2 commits into
masterfrom
collada-orthonormalize
Jun 26, 2026
Merged

Add opt-in transform orthonormalization to the Collada importer#138
Renanse merged 2 commits into
masterfrom
collada-orthonormalize

Conversation

@Renanse

@Renanse Renanse commented Jun 26, 2026

Copy link
Copy Markdown
Owner

What

Adds ColladaImporter.setOrthonormalizeTransforms(boolean) (off by default). When enabled, the rotational upper-left 3×3 of every baked node and animation-channel transform is cleaned with Matrix4.orthonormalizeLocal() (Gram-Schmidt, added in #137) right before it becomes a Transform — preserving the translation column.

Wiring:

  • New flag on ColladaImporter, following the existing setLoadTextures/setFlipTransparency pattern.
  • Applied at the two fromHomogeneousMatrix choke points: ColladaNodeUtils.getNodeTransforms (node/joint transforms) and ColladaAnimUtils.bakeTransforms (animation samples).
  • ColladaAnimUtils is now handed the ColladaImporter in its constructor so it can read the flag, mirroring how ColladaNodeUtils and ColladaMaterialUtils already take it.

Why

Autodesk FBX→Collada and OpenCollada exporters frequently bake enough floating-point rounding error into <matrix> transforms that the 3×3 rotational block fails isOrthonormal(). On skeletal import this trips the long-standing "supplied transform with non-rotational matrices. May have unexpected results." warning flood and degrades the rotation extracted for each channel. This gives users a one-call opt-in to clean those matrices at import. Addresses the importer half of #32.

Design notes

  • Opt-in on purpose. Orthonormalizing discards any scale/shear baked into a <matrix>, and we can't tell intentional shear from rounding noise — so it's off by default and documented as "only enable when your source encodes pure rotation + translation." This matches the conclusion reached in the Resolve Non-rotational matrices at import time in Animation Manager from collada #32 discussion (don't auto-enforce).
  • Best-effort, never fatal. A genuinely rank-deficient block (e.g. a collapsed/zero-scale transform) makes orthonormalizeLocal() throw ArithmeticException; that's caught per-transform and the matrix is left as-is with a warning, rather than aborting the whole load.

Tests

New TestColladaOrthonormalize loads an inline .dae whose single node carries a slightly-skewed near-identity <matrix> plus a translation:

  • off by default → the node's transform reports a non-rotational matrix (current behavior preserved).
  • enabled → the 3×3 is orthonormal (isRotationMatrix() true) and the (5, 6, 7) translation is preserved.

Full ardor3d-collada suite green (7 tests, 0 failures). The node path is covered directly; the animation-channel path shares the identical mechanism at the sibling choke point.

Follow-up

Depends on #137 (merged). Issue #32 can be closed once this lands; I'll leave the issue comment to you.

🤖 Generated with Claude Code

Update: temp-pool cleanup

Folded in a fix for a pre-existing leak in the two methods this PR already touches: bakeTransforms and getNodeTransforms each fetched two Matrix4.fetchTempInstance() temps and never released them. Their bodies are now wrapped in try/finally so both temps are returned to the per-thread pool on all paths (including exceptions). finalMat is still consumed by fromHomogeneousMatrix in the return expression before the finally runs, so there is no use-after-release.

Some exporters (notably Autodesk FBX-to-Collada and OpenCollada) bake
enough floating-point rounding error into <matrix> transforms that the
rotational 3x3 block is no longer orthonormal. On skeletal import this
trips Ardor3D's "supplied transform with non-rotational matrices" warning
flood and degrades the rotation extracted for animation channels
(issue #32).

Add ColladaImporter.setOrthonormalizeTransforms(boolean), off by default.
When enabled, the upper-left 3x3 of each baked node and animation channel
transform is cleaned with Matrix4.orthonormalizeLocal() (added in #137)
before it becomes a Transform, with the translation preserved. A
rank-deficient block is left as-is with a warning rather than aborting the
whole load.

The importer is now threaded into ColladaAnimUtils so it can read the
flag, mirroring ColladaNodeUtils/ColladaMaterialUtils. Enabling the flag
discards any scale or shear carried in those matrices, so it is opt-in and
documented as such.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Renanse, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 11 minutes and 13 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4564061b-8a61-429e-af5f-66cef54f8956

📥 Commits

Reviewing files that changed from the base of the PR and between c299b50 and e1e2092.

📒 Files selected for processing (4)
  • ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaAnimUtils.java
  • ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaImporter.java
  • ardor3d-collada/src/main/java/com/ardor3d/extension/model/collada/jdom/ColladaNodeUtils.java
  • ardor3d-collada/src/test/java/com/ardor3d/extension/model/collada/jdom/TestColladaOrthonormalize.java
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch collada-orthonormalize

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

bakeTransforms (ColladaAnimUtils) and getNodeTransforms (ColladaNodeUtils)
each fetched two Matrix4 instances via Matrix4.fetchTempInstance() but
never released them, leaking them from the per-thread math pool on every
call. Wrap each body in try/finally so both temps are returned to the pool
on all paths, including exceptions. finalMat is still fully consumed by
fromHomogeneousMatrix in the return expression before the finally runs, so
there is no use-after-release.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Renanse Renanse merged commit 81dce93 into master Jun 26, 2026
2 checks passed
@Renanse Renanse deleted the collada-orthonormalize branch June 26, 2026 23:53
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.

1 participant