From c477ae08e8cdddc2c54bb6ed5fe63013d90ebe13 Mon Sep 17 00:00:00 2001 From: GGBond-GIS Date: Thu, 13 Aug 2026 22:00:45 +0800 Subject: [PATCH] fix(sprite): stabilize BillboardY vertical view Keep the last valid yaw when the camera direction has no usable XZ projection. Use the world Y axis so camera roll does not affect BillboardY orientation. --- src/components/BillboardComponent.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/BillboardComponent.ts b/src/components/BillboardComponent.ts index fdd16aeb..c5248cd1 100644 --- a/src/components/BillboardComponent.ts +++ b/src/components/BillboardComponent.ts @@ -54,10 +54,24 @@ export class BillboardComponent extends ComponentBase { if (this.type == BillboardType.BillboardXYZ) { } else if (this.type == BillboardType.BillboardY) { this._cameraPosition.y = 0; + + // A cylindrical billboard has no defined yaw when the camera is + // looking straight up or down. Normalizing that near-zero XZ + // projection amplifies floating-point noise and makes the sprite + // swing between opposite directions as the camera rotates. + // Keep the last valid orientation until a horizontal camera + // direction is available again. + const horizontalLengthSquared = + this._cameraPosition.x * this._cameraPosition.x + + this._cameraPosition.z * this._cameraPosition.z; + if (horizontalLengthSquared <= Vector3.EPSILON * Vector3.EPSILON) return; } this._cameraPosition.normalize(); Vector3.add(this._cameraPosition, this.object3D.localPosition, this._cameraPosition); - this.transform.lookAt(this.object3D.localPosition, this._cameraPosition, camera.transform.up); + // BillboardY must stay locked to the world Y axis. Using the camera's + // up vector introduces roll and becomes unstable at a vertical view. + const up = this.type == BillboardType.BillboardY ? Vector3.Y_AXIS : camera.transform.up; + this.transform.lookAt(this.object3D.localPosition, this._cameraPosition, up); } /**