Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
82 lines
3.2 KiB
TypeScript
82 lines
3.2 KiB
TypeScript
export interface ViewportOrbitState {
|
|
yaw: number;
|
|
pitch: number;
|
|
distance: number;
|
|
target: [number, number, number];
|
|
}
|
|
|
|
export interface ViewportCameraState extends ViewportOrbitState {
|
|
position: [number, number, number];
|
|
}
|
|
|
|
export type ViewportView = 'USER' | 'FRONT' | 'BACK' | 'RIGHT' | 'LEFT' | 'TOP' | 'BOTTOM';
|
|
export type ViewportNavigationCommand = ViewportView | 'PROJECTION' | 'FRAME_ALL' | 'FRAME_SELECTED' | 'ZOOM_IN' | 'ZOOM_OUT';
|
|
export interface ViewportNavigationCamera {
|
|
position: [number,number,number];
|
|
quaternion: [number,number,number,number];
|
|
target: [number,number,number];
|
|
projection:'PERSPECTIVE'|'ORTHOGRAPHIC';
|
|
fov:number;aspect:number;near:number;far:number;zoom:number;orthoHeight:number;
|
|
}
|
|
export interface NativeViewportState {
|
|
offset: [number,number,number];
|
|
rotation: [number,number,number,number];
|
|
distance: number; lens: number; near: number; far: number;
|
|
projection: 'PERSPECTIVE'|'ORTHOGRAPHIC'|'CAMERA';
|
|
locked?: boolean;
|
|
}
|
|
export interface BlenderViewportCamera {
|
|
position: [number,number,number]; target: [number,number,number];
|
|
orientation: [number,number,number,number];
|
|
projection: 'PERSPECTIVE'|'ORTHOGRAPHIC';
|
|
fov:number; near:number; far:number; zoom:number; orthoHeight:number;
|
|
}
|
|
|
|
export const VIEWPORT_DEFAULT_ORBIT: Readonly<ViewportOrbitState> = Object.freeze({
|
|
yaw: -Math.PI / 4,
|
|
pitch: 0.55,
|
|
distance: 7,
|
|
target: [0, 0, 0] as [number, number, number],
|
|
});
|
|
|
|
export const VIEWPORT_ORBIT_ROTATE_SENSITIVITY = 0.008;
|
|
export const VIEWPORT_ORBIT_ZOOM_SENSITIVITY = 0.001;
|
|
export const VIEWPORT_ORBIT_MIN_DISTANCE = 0.2;
|
|
export const VIEWPORT_ORBIT_MAX_DISTANCE = 500;
|
|
|
|
export function orbitPosition(state: Pick<ViewportOrbitState, "yaw" | "pitch" | "distance" | "target">): [number, number, number] {
|
|
const horizontal = state.distance * Math.cos(state.pitch);
|
|
return [
|
|
state.target[0] + horizontal * Math.cos(state.yaw),
|
|
state.target[1] + horizontal * Math.sin(state.yaw),
|
|
state.target[2] + state.distance * Math.sin(state.pitch),
|
|
];
|
|
}
|
|
|
|
export function cameraState(state: ViewportOrbitState): ViewportCameraState {
|
|
return { ...state, target: [...state.target] as [number, number, number], position: orbitPosition(state) };
|
|
}
|
|
|
|
export function orbitStateFromPosition(position: readonly number[], target: readonly number[] = [0, 0, 0]): ViewportOrbitState {
|
|
const dx = position[0] - target[0];
|
|
const dy = position[1] - target[1];
|
|
const dz = position[2] - target[2];
|
|
const distance = Math.max(VIEWPORT_ORBIT_MIN_DISTANCE, Math.hypot(dx, dy, dz));
|
|
return {
|
|
yaw: Math.atan2(dy, dx),
|
|
pitch: Math.asin(Math.max(-1, Math.min(1, dz / distance))),
|
|
distance,
|
|
target: [target[0], target[1], target[2]],
|
|
};
|
|
}
|
|
|
|
export function applyOrbitDelta(state: ViewportOrbitState, deltaX: number, deltaY: number, zoom: number): ViewportOrbitState {
|
|
return {
|
|
...state,
|
|
yaw: state.yaw - deltaX * VIEWPORT_ORBIT_ROTATE_SENSITIVITY,
|
|
pitch: Math.max(-1.45, Math.min(1.45, state.pitch + deltaY * VIEWPORT_ORBIT_ROTATE_SENSITIVITY)),
|
|
distance: Math.max(VIEWPORT_ORBIT_MIN_DISTANCE, Math.min(VIEWPORT_ORBIT_MAX_DISTANCE, state.distance * Math.exp(zoom * VIEWPORT_ORBIT_ZOOM_SENSITIVITY))),
|
|
target: [...state.target] as [number, number, number],
|
|
};
|
|
}
|