/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * Axis Helper component - shows XYZ coordinate system following IFC standard (Z-up) * Note: While WebGL uses Y-up internally, IFC convention is Z-up, so we display * the axes with Z pointing upward to match what users expect in IFC/BIM context. */ import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; interface AxisHelperProps { rotationX?: number; rotationY?: number; } export interface AxisHelperRef { updateRotation: (x: number, y: number) => void; } export const AxisHelper = forwardRef(({ rotationX = -25, rotationY = 45 }, ref) => { const size = 50; const axisLength = 20; const labelOffset = 26; const rotationContainerRef = useRef(null); const xLabelRef = useRef(null); const yLabelRef = useRef(null); const zLabelRef = useRef(null); const rafRef = useRef(null); const pendingRotationRef = useRef<{ x: number; y: number } | null>(null); const applyRotation = (x: number, y: number) => { if (!rotationContainerRef.current) return; rotationContainerRef.current.style.transform = `rotateX(${x}deg) rotateY(${y}deg)`; const inverseRotation = `rotateY(${-y}deg) rotateX(${-x}deg)`; if (xLabelRef.current) xLabelRef.current.style.transform = inverseRotation; if (zLabelRef.current) zLabelRef.current.style.transform = inverseRotation; if (yLabelRef.current) yLabelRef.current.style.transform = `translateZ(${labelOffset}px) ${inverseRotation}`; }; useImperativeHandle(ref, () => ({ updateRotation: (x: number, y: number) => { pendingRotationRef.current = { x, y }; if (rafRef.current !== null) { cancelAnimationFrame(rafRef.current); } rafRef.current = requestAnimationFrame(() => { if (pendingRotationRef.current) { applyRotation(pendingRotationRef.current.x, pendingRotationRef.current.y); pendingRotationRef.current = null; } rafRef.current = null; }); }, }), []); useEffect(() => { applyRotation(rotationX, rotationY); return () => { if (rafRef.current !== null) { cancelAnimationFrame(rafRef.current); } }; }, []); // Convert from WebGL convention (Y-up) to IFC display convention (Z-up) // In the viewer, Y is up in 3D space, but we relabel: // - WebGL X -> Display X (right) // - WebGL Y -> Display Z (up in IFC) // - WebGL Z -> Display Y (forward in IFC) return (
{/* X Axis - Red (pointing right) */}
X
{/* Z Axis - Blue (pointing up in IFC) - this is WebGL Y */}
Z
{/* Y Axis - Green (pointing into screen in IFC) - this is WebGL -Z */}
Y
{/* Origin point */}
); }); AxisHelper.displayName = 'AxisHelper';