Add camera state management to ViewportHost

This commit is contained in:
2026-04-03 01:36:56 +02:00
parent eda8a81448
commit 0c2bca0331

View File

@@ -88,7 +88,7 @@ import {
type ViewportGridPlane, type ViewportGridPlane,
type ViewportViewMode type ViewportViewMode
} from "./viewport-view-modes"; } from "./viewport-view-modes";
import type { ViewportDisplayMode } from "./viewport-layout"; import { areViewportPanelCameraStatesEqual, type ViewportDisplayMode, type ViewportPanelCameraState } from "./viewport-layout";
import { import {
areViewportToolPreviewsEqual, areViewportToolPreviewsEqual,
type CreationTarget, type CreationTarget,
@@ -210,6 +210,7 @@ export class ViewportHost {
private brushSelectionChangeHandler: ((selection: EditorSelection) => void) | null = null; private brushSelectionChangeHandler: ((selection: EditorSelection) => void) | null = null;
private creationPreviewChangeHandler: ((toolPreview: ViewportToolPreview) => void) | null = null; private creationPreviewChangeHandler: ((toolPreview: ViewportToolPreview) => void) | null = null;
private creationCommitHandler: ((toolPreview: CreationViewportToolPreview) => boolean) | null = null; private creationCommitHandler: ((toolPreview: CreationViewportToolPreview) => boolean) | null = null;
private cameraStateChangeHandler: ((cameraState: ViewportPanelCameraState) => void) | null = null;
private toolMode: ToolMode = "select"; private toolMode: ToolMode = "select";
private viewMode: ViewportViewMode = "perspective"; private viewMode: ViewportViewMode = "perspective";
private displayMode: ViewportDisplayMode = "normal"; private displayMode: ViewportDisplayMode = "normal";
@@ -328,6 +329,23 @@ export class ViewportHost {
this.creationCommitHandler = handler; this.creationCommitHandler = handler;
} }
setCameraStateChangeHandler(handler: ((cameraState: ViewportPanelCameraState) => void) | null) {
this.cameraStateChangeHandler = handler;
}
setCameraState(cameraState: ViewportPanelCameraState) {
if (areViewportPanelCameraStatesEqual(this.createCameraStateSnapshot(), cameraState)) {
return;
}
this.cameraTarget.set(cameraState.target.x, cameraState.target.y, cameraState.target.z);
this.cameraSpherical.radius = cameraState.perspectiveOrbit.radius;
this.cameraSpherical.theta = cameraState.perspectiveOrbit.theta;
this.cameraSpherical.phi = cameraState.perspectiveOrbit.phi;
this.orthographicCamera.zoom = cameraState.orthographicZoom;
this.applyViewModePose();
}
setCreationPreview(toolPreview: CreationViewportToolPreview | null) { setCreationPreview(toolPreview: CreationViewportToolPreview | null) {
this.syncCreationPreview(toolPreview); this.syncCreationPreview(toolPreview);
} }
@@ -392,6 +410,7 @@ export class ViewportHost {
this.cameraSpherical.radius = fitDistance; this.cameraSpherical.radius = fitDistance;
this.cameraSpherical.makeSafe(); this.cameraSpherical.makeSafe();
this.applyPerspectiveCameraPose(); this.applyPerspectiveCameraPose();
this.emitCameraStateChange();
return; return;
} }
@@ -404,6 +423,7 @@ export class ViewportHost {
this.orthographicCamera.zoom = Math.min(MAX_ORTHOGRAPHIC_ZOOM, Math.max(MIN_ORTHOGRAPHIC_ZOOM, fitZoom)); this.orthographicCamera.zoom = Math.min(MAX_ORTHOGRAPHIC_ZOOM, Math.max(MIN_ORTHOGRAPHIC_ZOOM, fitZoom));
this.applyOrthographicCameraPose(); this.applyOrthographicCameraPose();
this.emitCameraStateChange();
} }
dispose() { dispose() {
@@ -426,6 +446,7 @@ export class ViewportHost {
this.clearEntityMarkers(); this.clearEntityMarkers();
this.creationPreviewChangeHandler = null; this.creationPreviewChangeHandler = null;
this.creationCommitHandler = null; this.creationCommitHandler = null;
this.cameraStateChangeHandler = null;
this.syncCreationPreview(null); this.syncCreationPreview(null);
this.advancedRenderingComposer?.dispose(); this.advancedRenderingComposer?.dispose();
this.advancedRenderingComposer = null; this.advancedRenderingComposer = null;
@@ -454,6 +475,26 @@ export class ViewportHost {
return this.viewMode === "perspective" ? this.perspectiveCamera : this.orthographicCamera; return this.viewMode === "perspective" ? this.perspectiveCamera : this.orthographicCamera;
} }
private createCameraStateSnapshot(): ViewportPanelCameraState {
return {
target: {
x: this.cameraTarget.x,
y: this.cameraTarget.y,
z: this.cameraTarget.z
},
perspectiveOrbit: {
radius: this.cameraSpherical.radius,
theta: this.cameraSpherical.theta,
phi: this.cameraSpherical.phi
},
orthographicZoom: this.orthographicCamera.zoom
};
}
private emitCameraStateChange() {
this.cameraStateChangeHandler?.(this.createCameraStateSnapshot());
}
private updatePerspectiveCameraSphericalFromPose() { private updatePerspectiveCameraSphericalFromPose() {
this.cameraOffset.copy(this.perspectiveCamera.position).sub(this.cameraTarget); this.cameraOffset.copy(this.perspectiveCamera.position).sub(this.cameraTarget);
this.cameraSpherical.setFromVector3(this.cameraOffset); this.cameraSpherical.setFromVector3(this.cameraOffset);
@@ -1498,6 +1539,7 @@ export class ViewportHost {
this.activeCameraDragPointerId = null; this.activeCameraDragPointerId = null;
this.lastCameraDragClientPosition = null; this.lastCameraDragClientPosition = null;
this.emitCameraStateChange();
}; };
private handlePointerLeave = () => { private handlePointerLeave = () => {
@@ -1518,6 +1560,7 @@ export class ViewportHost {
Math.max(MIN_CAMERA_DISTANCE, this.cameraSpherical.radius * Math.exp(event.deltaY * ZOOM_SPEED)) Math.max(MIN_CAMERA_DISTANCE, this.cameraSpherical.radius * Math.exp(event.deltaY * ZOOM_SPEED))
); );
this.applyPerspectiveCameraPose(); this.applyPerspectiveCameraPose();
this.emitCameraStateChange();
return; return;
} }
@@ -1526,6 +1569,7 @@ export class ViewportHost {
Math.max(MIN_ORTHOGRAPHIC_ZOOM, this.orthographicCamera.zoom * Math.exp(-event.deltaY * ZOOM_SPEED)) Math.max(MIN_ORTHOGRAPHIC_ZOOM, this.orthographicCamera.zoom * Math.exp(-event.deltaY * ZOOM_SPEED))
); );
this.orthographicCamera.updateProjectionMatrix(); this.orthographicCamera.updateProjectionMatrix();
this.emitCameraStateChange();
}; };
private handleAuxClick = (event: MouseEvent) => { private handleAuxClick = (event: MouseEvent) => {