Skip to content

Rotate glTF models on import.#1505

Merged
elalish merged 3 commits into
elalish:masterfrom
tonious:going_up
Jan 29, 2026
Merged

Rotate glTF models on import.#1505
elalish merged 3 commits into
elalish:masterfrom
tonious:going_up

Conversation

@tonious

@tonious tonious commented Jan 24, 2026

Copy link
Copy Markdown
Collaborator

GLTF defines 'up' as +Y, while ManifoldCAD defines it as '+Z'.

This PR rotates models on import so that up is up. It also documents that behaviour.

Part of the Villianous Task List.

(Smaller than usual 😅 )

@codecov

codecov Bot commented Jan 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.05%. Comparing base (db58939) to head (4e63ec2).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1505   +/-   ##
=======================================
  Coverage   92.05%   92.05%           
=======================================
  Files          35       35           
  Lines        6039     6039           
=======================================
  Hits         5559     5559           
  Misses        480      480           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@elalish elalish left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks!

Comment thread bindings/wasm/lib/import-model.ts Outdated

const halfRoot2 = Math.sqrt(2) / 2;
const original = sourceNode.getRotation();
const rotated = multiplyQuat(original, [-halfRoot2, 0, 0, halfRoot2])

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I might just use euler2quat here for readability.

Comment thread bindings/wasm/lib/import-model.test.ts Outdated
if (mesh.position(i)[2] > 0) above++;
}

expect(above).toEqual(1);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The only trouble with this test is it doesn't check the X and Y axes. We should document which axis we rotate about, and ensure it comes back to the original after an export/import cycle.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That's fair.

I've called out the rotation and scaling specifically at both import and export.
Now the test uses an object that has no rotational or mirrored symmetry.

@elalish elalish left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks!

sourceNode.setScale([scale[0] * 1000, scale[1] * 1000, scale[2] * 1000]);

const original = sourceNode.getRotation();
const rotated = multiplyQuat(original, euler2quat([90, 0, 0]));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can you explain why this round-trip works when it looks like the same rotation is applied on import and export, rather than opposites? Your test seems to imply it's all working, but I just want to double check.

@tonious tonious Jan 27, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Y'know, I thought it was because multiplying quaternions is not transitive. But flipping the sign and reversing didn't work, so I've got some deeper flaw here.

It should be the inverse of exportTransform just to adhere to the principle of least astonishment.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It's a quaternion WXYZ vs XYZW thing. Time for more documentation. (Ouch! That hurt me as a developer! 😁 )

But while I'm at it, euler2quat currently works in ZYX order, while Manifold.rotate() works in XYZ order. Should I change this to match? I'll have to mess with the Tetrahedron Puzzle.

@elalish elalish Jan 29, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Ouch, yeah euler2quat should definitely match Manifold.rotate. Why will that change tet puzzle? Were we actually doing the rotations wrong compared to our documentation? EDIT: ah, indeed. You know, I think that actually confused me when I was writing that example, but I just messed around until it worked instead of noticing the actual problem.

@elalish elalish left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Good excuse to catch that bug!

@elalish elalish merged commit 634aee5 into elalish:master Jan 29, 2026
36 checks passed
@tonious tonious deleted the going_up branch January 29, 2026 15:04
@pca006132 pca006132 mentioned this pull request Feb 26, 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.

2 participants