import * as THREE from 'three'; import {lookAtRotation} from '../utils/RotationUtils'; // Reusable instances to avoid allocations in per-frame callers. const tempForward = new THREE.Vector3(); const tempAwayFromCamera = new THREE.Vector3(); /** * Computes an aspect-ratio-preserving plane size whose largest side equals * `maxSize`. Used to scale a generated image so it reads at a comfortable size * regardless of the model's output resolution. * @param imageWidth - Source image width in pixels. * @param imageHeight - Source image height in pixels. * @param maxSize - Largest dimension of the resulting plane, in meters. * @param target - Optional output vector to write into. * @returns `target` set to the plane's [width, height] in meters. */ export function computeBillboardScale( imageWidth: number, imageHeight: number, maxSize: number, target = new THREE.Vector2() ): THREE.Vector2 { if (imageWidth <= 0 || imageHeight <= 0 || maxSize <= 0) { // Degenerate input: fall back to a square so the object is still visible. return target.set(maxSize, maxSize); } const aspect = imageWidth / imageHeight; if (aspect >= 1) { return target.set(maxSize, maxSize / aspect); } return target.set(maxSize * aspect, maxSize); } /** * Computes a world-space pose `distance` meters in front of the camera, * oriented so its front face (+Z) points back toward the user. Copy the result * straight onto a direct child of the scene; for an object under a transformed * parent, convert it into the parent's space first. * @param camera - The user's camera. * @param distance - Distance in front of the camera, in meters. * @param position - Optional output world position. * @param quaternion - Optional output world orientation. * @returns The world position and orientation. */ export function poseInFrontOfCamera( camera: THREE.Camera, distance: number, position = new THREE.Vector3(), quaternion = new THREE.Quaternion() ): {position: THREE.Vector3; quaternion: THREE.Quaternion} { camera.getWorldDirection(tempForward); camera.getWorldPosition(position); position.addScaledVector(tempForward, distance); // lookAtRotation orients local -Z along `forward` (into the scene), so the // plane's +Z normal faces back toward the user. lookAtRotation(tempForward, undefined, quaternion); return {position, quaternion}; } /** * Computes a world-space orientation that turns a plane's front face (+Z) * toward the camera while keeping the object upright (yaw only). Used to * billboard a generated cutout so it faces the user like a standee, without * tilting. Apply it directly to a child of the scene; under a transformed * parent, convert it into the parent's space first. * @param objectPosition - World position of the object. * @param cameraPosition - World position of the camera. * @param target - Optional output world orientation. * @returns `target` oriented so +Z points toward the camera, staying upright. */ export function quaternionFacingCamera( objectPosition: THREE.Vector3, cameraPosition: THREE.Vector3, target = new THREE.Quaternion() ): THREE.Quaternion { const awayFromCamera = tempAwayFromCamera.subVectors( objectPosition, cameraPosition ); // Keep it upright: only yaw toward the camera, never pitch/roll. awayFromCamera.y = 0; if (awayFromCamera.lengthSq() === 0) { return target.identity(); } // lookAtRotation orients -Z along `awayFromCamera`, so +Z faces the camera. return lookAtRotation(awayFromCamera, undefined, target); }