feat(web): place full setup objects at their real transforms

Model TTSObjectTransform in shared types and convert each object's position, rotation, and scale into three.js placement, with per-class base-size corrections and a lay-flat rotation for extruded meshes.
This commit is contained in:
2026-08-08 18:41:42 +08:00
parent c3c7fe2445
commit dcba0ac4bd
4 changed files with 145 additions and 54 deletions
@@ -0,0 +1,85 @@
import type { TTSObject } from '@tts/shared';
/**
* Convert a TTS object's `Transform` (position/rotation/scale in TTS world
* units) into three.js props so the full-setup view can place objects at their
* real table positions.
*
* 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.
*
* 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
* and a "lay flat" rotation to match TTS world units and orientation.
*/
/** Degrees → radians. */
const rad = (d: number) => (d * Math.PI) / 180;
/**
* Base-size correction: our viewer mesh size → TTS world units at scale 1.
* `scale` in the transform is multiplied by this so objects end up the right
* relative size on the table.
*/
const CORRECTION: Record<string, number> = {
// Card long axis is 2 units in the viewer; 1.0 in TTS.
Card: 0.5,
CardCustom: 0.5,
Deck: 0.5,
DeckCustom: 0.5,
Custom_Deck: 0.5,
// Tile is 2 units in the viewer; 1.0 in TTS.
Tile: 0.5,
Custom_Tile: 0.5,
// Token is 1.8 units in the viewer; 0.7 in TTS.
Custom_Token: 0.7 / 1.8,
// Custom models are loaded at 0.5 scale in the viewer; 1.0 in TTS.
Custom_Model: 2,
Custom_Model_Bag: 2,
Custom_Model_Infinite_Bag: 2,
};
/**
* 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.
*/
const LAY_FLAT: [number, number, number] = [Math.PI / 2, 0, 0];
const FLAT_CLASSES = new Set([
'Card',
'CardCustom',
'Deck',
'DeckCustom',
'Custom_Deck',
'Tile',
'Custom_Tile',
'Custom_Token',
]);
export interface ObjectPlacement {
position: [number, number, number];
rotation: [number, number, number];
scale: [number, number, number];
/** Rotation applied to the mesh before the TTS rotation (lay flat). */
layFlat: [number, number, number];
}
/**
* Derive the three.js placement for an object from its `Transform`. Returns
* null when the object has no transform (shouldn't happen in a real save).
*/
export function objectPlacement(object: TTSObject): ObjectPlacement | null {
const t = object.Transform;
if (!t) return 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)],
scale: [t.scaleX * corr, t.scaleY * corr, t.scaleZ * corr],
layFlat: FLAT_CLASSES.has(object.Name) ? LAY_FLAT : [0, 0, 0],
};
}