fix(web): correct transform rotation and lay-flat sign

Conjugate the rotation by the Z-reflection (negate rotX/rotY, keep rotZ) and use -90 degrees about X to lay extruded meshes flat so faces point up.
This commit is contained in:
2026-08-08 20:19:13 +08:00
parent dcba0ac4bd
commit a6e9d2dd07
+11 -7
View File
@@ -7,8 +7,10 @@ import type { TTSObject } from '@tts/shared';
*
* TTS is left-handed with Y up and +Z toward the player; three.js is
* right-handed with Y up. Converting by reflecting the Z axis (negating Z in
* position and every rotation angle) maps a TTS placement onto the three.js
* scene while preserving the physical layout.
* position) maps a TTS placement onto the three.js scene while preserving the
* physical layout. The rotation is conjugated by the reflection: `R_three =
* M·R_tts·M` with `M = diag(1,1,-1)`, which negates `rotX`/`rotY` but leaves
* `rotZ` unchanged.
*
* Our viewer meshes are authored for inspection (standing up, extruded along
* Z, sized in arbitrary units), so each class also gets a base-size correction
@@ -43,11 +45,12 @@ const CORRECTION: Record<string, number> = {
/**
* Rotation that lays an extruded mesh flat. The tile/token/card meshes extrude
* along +Z with their face in the XY plane (standing up); rotating +90° about
* X puts the face up (+Y), matching how objects lie on a TTS table. Custom
* models are authored standing up already, so they need no correction.
* along +Z with their face in the XY plane (standing up); rotating 90° about
* X maps the +Z face normal to +Y, so the face points up like an object lying
* on a TTS table. Custom models are authored standing up already, so they need
* no correction.
*/
const LAY_FLAT: [number, number, number] = [Math.PI / 2, 0, 0];
const LAY_FLAT: [number, number, number] = [-Math.PI / 2, 0, 0];
const FLAT_CLASSES = new Set([
'Card',
@@ -78,7 +81,8 @@ export function objectPlacement(object: TTSObject): ObjectPlacement | null {
const corr = CORRECTION[object.Name] ?? 1;
return {
position: [t.posX, t.posY, -t.posZ],
rotation: [-rad(t.rotX), -rad(t.rotY), -rad(t.rotZ)],
// Reflection conjugates the rotation: Rx/Ry negate, Rz keeps its sign.
rotation: [-rad(t.rotX), -rad(t.rotY), rad(t.rotZ)],
scale: [t.scaleX * corr, t.scaleY * corr, t.scaleZ * corr],
layFlat: FLAT_CLASSES.has(object.Name) ? LAY_FLAT : [0, 0, 0],
};