78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
import type { SurfaceBounds } from "./B04_wf1_Surface_Api_Fetch";
|
|
|
|
export const SURFACE_CAMERA_FOV = 50;
|
|
const LIGHT_VIEWER_BACKGROUND = 0xf5f7f9;
|
|
const DARK_VIEWER_BACKGROUND = 0x251f38;
|
|
|
|
export interface SurfaceCameraState {
|
|
direction: [number, number, number];
|
|
distanceMeters: number;
|
|
targetMeters: [number, number, number];
|
|
}
|
|
|
|
export function getReferenceCenter(bounds: SurfaceBounds): [number, number, number] {
|
|
return [
|
|
(bounds.x_min + bounds.x_max) / 2,
|
|
(bounds.y_min + bounds.y_max) / 2,
|
|
(bounds.z_min + bounds.z_max) / 2,
|
|
];
|
|
}
|
|
|
|
export function getTopFitDistance(bounds: SurfaceBounds, aspect: number): number {
|
|
const width = Math.max(bounds.x_max - bounds.x_min, 1);
|
|
const depth = Math.max(bounds.y_max - bounds.y_min, 1);
|
|
const verticalFov = (SURFACE_CAMERA_FOV * Math.PI) / 180;
|
|
const horizontalFov = 2 * Math.atan(Math.tan(verticalFov / 2) * Math.max(aspect, 0.1));
|
|
const verticalDistance = depth / (2 * Math.tan(verticalFov / 2));
|
|
const horizontalDistance = width / (2 * Math.tan(horizontalFov / 2));
|
|
return Math.max(verticalDistance, horizontalDistance, 1) * 1.12;
|
|
}
|
|
|
|
export function targetPlaneMetersPerPixel(distanceMeters: number, viewportHeight: number): number {
|
|
const verticalFov = (SURFACE_CAMERA_FOV * Math.PI) / 180;
|
|
return (
|
|
(2 * Math.tan(verticalFov / 2) * Math.max(distanceMeters, 0.001)) / Math.max(viewportHeight, 1)
|
|
);
|
|
}
|
|
|
|
export function niceScaleDistance(roughMeters: number): number {
|
|
const exponent = Math.floor(Math.log10(Math.max(roughMeters, 0.001)));
|
|
const base = 10 ** exponent;
|
|
const normalized = roughMeters / base;
|
|
const step = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
|
|
return step * base;
|
|
}
|
|
|
|
export function bindSurfaceViewerTheme(
|
|
applyBackground: (color: string | number) => void,
|
|
): () => void {
|
|
const systemDarkTheme = window.matchMedia("(prefers-color-scheme: dark)");
|
|
const update = (): void => {
|
|
const theme = document.documentElement.getAttribute("data-theme");
|
|
const dark = theme === "dark" || (theme !== "light" && systemDarkTheme.matches);
|
|
if (!dark) {
|
|
applyBackground(LIGHT_VIEWER_BACKGROUND);
|
|
return;
|
|
}
|
|
const surfaceRaised = getComputedStyle(document.documentElement)
|
|
.getPropertyValue("--color-surface-raised")
|
|
.trim();
|
|
applyBackground(
|
|
surfaceRaised && CSS.supports("color", surfaceRaised)
|
|
? surfaceRaised
|
|
: DARK_VIEWER_BACKGROUND,
|
|
);
|
|
};
|
|
const observer = new MutationObserver(update);
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ["data-theme"],
|
|
});
|
|
systemDarkTheme.addEventListener("change", update);
|
|
update();
|
|
return () => {
|
|
observer.disconnect();
|
|
systemDarkTheme.removeEventListener("change", update);
|
|
};
|
|
}
|