mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
PageScene: extract ViewportController from editor.ts
First step of the editor.ts decomposition (refactor/3d-editor-cleanup).
Pulls the canvas/container DOM seam, the ResizeObserver, and the resize
cascade out of the ~1450-LOC Editor class into a standalone module.
ViewportController owns container, canvReference, canvasRenderCamReference,
the ResizeObserver, lastCanvasSize, plus the onWindowResize / observed
resize logic.
Engine fields are forwarded explicitly through the `engine` constructor
argument:
- cameras, renderer, render aspect ratio: closure-based getters that
resolve at call time
- post-processing: a single resizePostProcessing(w, h) callback rather
than three composer field getters
The single callback is deliberate. The original code creates the main
composer in _configurePostProcessing but the raw composer + custom
outline pass much later in _configurePostProcessingRaw, with the first
viewport.onWindowResize() landing between the two. Three separate
composer getters would have ViewportController peeking at fields that
don't exist yet at that first resize and silently no-op'ing via `?.`.
The callback hides that lifecycle concern inside the post-processing
module, where it belongs.
No `import type Editor`, no circular import. The dead `update*` and
`*MayReset` accessors that the original code carried as defensive
fallbacks (with self-deprecating "we should not need this" comments)
were never actually called and are dropped here. containerMayReset
stays — it has live internal callers.
Editor exposes the controller as `editor.viewport` and routes its
internal call sites through `this.viewport.<method>()`.
Net: editor.ts loses ~107 lines and one cohesive responsibility.
This commit is contained in:
1 parent
2b5e509a29
commit
c352edd38c
2 files changed
+131
-133
No files matched your search
@@ -34,6 +34,7 @@ import { MediaUploadApi } from "~/Classes/ApiManager";
|
||||
import { SceneManager } from "./scene_manager_api";
|
||||
import { CustomOutlinePass } from "./CustomOutlinePass.js";
|
||||
import FindSurfaces from "./FindSurfaces.js";
|
||||
import { ViewportController } from "./editor/ViewportController";
|
||||
|
||||
import Stats from "three/examples/jsm/libs/stats.module.js";
|
||||
import { SparkRenderer } from "@sparkjsdev/spark";
|
||||
@@ -68,8 +69,6 @@ class Editor {
|
||||
sparkRenderer: SparkRenderer | null = null;
|
||||
rawRenderer: THREE.WebGLRenderer | undefined;
|
||||
clock: THREE.Clock | undefined;
|
||||
canvReference: HTMLCanvasElement | null = null;
|
||||
canvasRenderCamReference: HTMLCanvasElement | null = null;
|
||||
|
||||
composer: EffectComposer | undefined;
|
||||
render_composer: EffectComposer | undefined;
|
||||
@@ -120,19 +119,19 @@ class Editor {
|
||||
// getSceneSignals: () => SceneSignal;
|
||||
render_width: number;
|
||||
render_height: number;
|
||||
containerResizeObserver: ResizeObserver | undefined;
|
||||
|
||||
positive_prompt: string;
|
||||
rawRenderPass: RenderPass | undefined;
|
||||
generating_preview: boolean = false;
|
||||
|
||||
recorder: MediaRecorder | undefined;
|
||||
container: HTMLElement | null = null;
|
||||
|
||||
// Owns canvas/container DOM refs and the resize cascade.
|
||||
viewport: ViewportController;
|
||||
|
||||
selectedCanvas: boolean;
|
||||
startRenderHeight: number;
|
||||
startRenderWidth: number;
|
||||
lastCanvasSize: number;
|
||||
// Default params.
|
||||
|
||||
// global names of scene entities
|
||||
@@ -219,7 +218,6 @@ class Editor {
|
||||
this.startRenderWidth = 0;
|
||||
this.startRenderHeight = 0;
|
||||
this.rendering = false;
|
||||
this.lastCanvasSize = 0;
|
||||
this.switchPreviewToggle = false;
|
||||
this.api_manager = new APIManager();
|
||||
this.camera_person_mode = false;
|
||||
@@ -240,6 +238,22 @@ class Editor {
|
||||
|
||||
this.utils = new SceneUtils(this, this.activeScene);
|
||||
this.save_manager = new SaveManager(this);
|
||||
this.viewport = new ViewportController({
|
||||
getCamera: () => this.camera,
|
||||
getRenderCamera: () => this.render_camera,
|
||||
getRenderer: () => this.renderer,
|
||||
getRenderAspectRatio: () => this.getRenderDimensions().aspectRatio,
|
||||
resizePostProcessing: (w, h) => {
|
||||
// Each pass is created at a different point in initialize() —
|
||||
// composer in _configurePostProcessing, render_composer +
|
||||
// customOutlinerPass in _configurePostProcessingRaw. The first
|
||||
// viewport.onWindowResize() runs between the two, so the optional
|
||||
// chaining here is load-bearing, not paranoia.
|
||||
this.composer?.setSize(w, h);
|
||||
this.render_composer?.setSize(w, h);
|
||||
this.customOutlinerPass?.setSize(w, h);
|
||||
},
|
||||
});
|
||||
|
||||
// Scene State
|
||||
this.current_scene_media_token = null;
|
||||
@@ -310,58 +324,6 @@ class Editor {
|
||||
return value === null || value.trim().length === 0;
|
||||
}
|
||||
|
||||
containerMayReset() {
|
||||
//TODO: we should not need this, if the container is reset,
|
||||
// updateSceneContainer should update the reference in the editor
|
||||
if (!this.container) {
|
||||
console.warn(
|
||||
"Editor - Container does not exist, querying from DOM via document.getElementById",
|
||||
);
|
||||
this.container = document.getElementById("video-scene-container");
|
||||
}
|
||||
}
|
||||
updateSceneContainer(newContainer: HTMLDivElement) {
|
||||
this.container = newContainer;
|
||||
this.containerResizeObserver?.disconnect();
|
||||
this.containerResizeObserver?.observe(this.container);
|
||||
}
|
||||
engineCanvasMayReset() {
|
||||
//TODO: we should not need this, if the this canvas is reset,
|
||||
// updateEngineCanvas should update the reference in the editor
|
||||
if (!this.canvReference) {
|
||||
console.warn(
|
||||
"Editor - Engine Canbas does not exist, querying from DOM via document.getElementById",
|
||||
);
|
||||
this.canvReference = document.getElementById(
|
||||
"video-scene",
|
||||
) as HTMLCanvasElement;
|
||||
}
|
||||
}
|
||||
updateEngineCanvas(newCanvas: HTMLCanvasElement) {
|
||||
this.canvReference = newCanvas;
|
||||
if (this.renderer) {
|
||||
this.renderer.domElement = this.canvReference;
|
||||
}
|
||||
}
|
||||
camViewCanvasMayReset() {
|
||||
//TODO: we should not need this, if the this canvas is reset,
|
||||
// updateCamViewCanvas should update the reference in the editor
|
||||
if (!this.canvasRenderCamReference) {
|
||||
console.warn(
|
||||
"Editor - Cam View Canvas does not exist, querying from DOM via document.getElementById",
|
||||
);
|
||||
this.canvasRenderCamReference = document.getElementById(
|
||||
"camera-view",
|
||||
) as HTMLCanvasElement;
|
||||
}
|
||||
}
|
||||
updateCamViewCanvas(newCanvas: HTMLCanvasElement) {
|
||||
this.canvasRenderCamReference = newCanvas;
|
||||
if (this.rawRenderer) {
|
||||
this.rawRenderer.domElement = this.canvasRenderCamReference;
|
||||
}
|
||||
}
|
||||
|
||||
changeRenderCameraAspectRatio(newAspectRatio: CameraAspectRatio) {
|
||||
this.render_camera_aspect_ratio = newAspectRatio;
|
||||
const { width, height, aspectRatio } = this.getRenderDimensions();
|
||||
@@ -392,15 +354,15 @@ class Editor {
|
||||
this.processingRecording = false;
|
||||
|
||||
// Gets the canvas.
|
||||
this.canvReference = editorCanvasEl;
|
||||
this.canvasRenderCamReference = camViewCanvasEl;
|
||||
this.viewport.canvReference = editorCanvasEl;
|
||||
this.viewport.canvasRenderCamReference = camViewCanvasEl;
|
||||
|
||||
// Find the container element
|
||||
this.container = sceneContainerEl;
|
||||
this.viewport.container = sceneContainerEl;
|
||||
|
||||
// Use the container's dimensions
|
||||
const width = this.container.offsetWidth;
|
||||
const height = this.container.offsetHeight;
|
||||
const width = this.viewport.container.offsetWidth;
|
||||
const height = this.viewport.container.offsetHeight;
|
||||
|
||||
// Sets up camera and base position using camera configurations from the store.
|
||||
const mainCameraConfig = usePageSceneStore
|
||||
@@ -456,7 +418,7 @@ class Editor {
|
||||
// Base WebGL render and clock for delta time.
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
canvas: this.canvReference,
|
||||
canvas: this.viewport.canvReference,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
|
||||
@@ -467,7 +429,7 @@ class Editor {
|
||||
|
||||
this.rawRenderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
canvas: this.canvasRenderCamReference,
|
||||
canvas: this.viewport.canvasRenderCamReference,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
|
||||
@@ -516,9 +478,9 @@ class Editor {
|
||||
this.mouse = new THREE.Vector2();
|
||||
this.activeScene.scene.add(this.control);
|
||||
// Resets canvas size.
|
||||
this.onWindowResize();
|
||||
this.viewport.onWindowResize();
|
||||
|
||||
this.setupResizeObserver();
|
||||
this.viewport.setupResizeObserver();
|
||||
|
||||
// saving state of the scene
|
||||
this.current_scene_media_token = null;
|
||||
@@ -537,7 +499,7 @@ class Editor {
|
||||
this.rendering,
|
||||
this.togglePlayback.bind(this),
|
||||
this.deleteObject.bind(this),
|
||||
this.canvReference,
|
||||
this.viewport.canvReference,
|
||||
this.mouse,
|
||||
this.mouse,
|
||||
this.raycaster,
|
||||
@@ -905,8 +867,8 @@ class Editor {
|
||||
}
|
||||
|
||||
_configurePostProcessingRaw() {
|
||||
const width = this.canvasRenderCamReference?.width ?? 0;
|
||||
const height = this.canvasRenderCamReference?.height ?? 0;
|
||||
const width = this.viewport.canvasRenderCamReference?.width ?? 0;
|
||||
const height = this.viewport.canvasRenderCamReference?.height ?? 0;
|
||||
if (
|
||||
this.rawRenderer == undefined ||
|
||||
this.render_camera == undefined ||
|
||||
@@ -983,8 +945,8 @@ class Editor {
|
||||
|
||||
// Configure post processing.
|
||||
_configurePostProcessing() {
|
||||
const width = this.canvReference?.width ?? 0;
|
||||
const height = this.canvReference?.height ?? 0;
|
||||
const width = this.viewport.canvReference?.width ?? 0;
|
||||
const height = this.viewport.canvReference?.height ?? 0;
|
||||
|
||||
if (this.renderer == undefined || this.camera == undefined) {
|
||||
return;
|
||||
@@ -1079,16 +1041,16 @@ class Editor {
|
||||
async renderSingleFrame() {
|
||||
//console.timeEnd("Single Frame Time");
|
||||
//console.time("Single Frame Time");
|
||||
this.containerMayReset();
|
||||
this.viewport.containerMayReset();
|
||||
|
||||
if (!this.rendering && this.container) {
|
||||
if (!this.rendering && this.viewport.container) {
|
||||
if (
|
||||
this.container.clientWidth + this.container.clientHeight !==
|
||||
this.lastCanvasSize
|
||||
this.viewport.container.clientWidth + this.viewport.container.clientHeight !==
|
||||
this.viewport.lastCanvasSize
|
||||
) {
|
||||
this.onWindowResize();
|
||||
this.lastCanvasSize =
|
||||
this.container.clientWidth + this.container.clientHeight;
|
||||
this.viewport.onWindowResize();
|
||||
this.viewport.lastCanvasSize =
|
||||
this.viewport.container.clientWidth + this.viewport.container.clientHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1352,60 +1314,6 @@ class Editor {
|
||||
}); //end updateObjectPanel
|
||||
}
|
||||
|
||||
// Automaticly resize scene.
|
||||
onWindowResize() {
|
||||
this.containerMayReset();
|
||||
if (!this.container) return;
|
||||
|
||||
const width = this.container.clientWidth;
|
||||
const height = this.container.clientHeight;
|
||||
|
||||
if (this.camera == undefined || this.renderer == undefined) {
|
||||
return;
|
||||
}
|
||||
// Set the camera aspect to the desired aspect ratio
|
||||
this.camera.aspect = width / height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
|
||||
// Set the renderer size to the calculated dimensions
|
||||
this.renderer.setSize(width, height);
|
||||
if (this.composer != null) {
|
||||
this.composer.setSize(width, height);
|
||||
}
|
||||
|
||||
this.render_composer?.setSize(width, height);
|
||||
|
||||
if (this.render_camera == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.customOutlinerPass?.setSize(width, height);
|
||||
this.render_camera.aspect = this.getRenderDimensions().aspectRatio;
|
||||
this.render_camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
setupResizeObserver() {
|
||||
this.containerMayReset();
|
||||
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.containerResizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
if (this.camera) {
|
||||
this.camera.aspect = width / height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
}
|
||||
this.renderer?.setSize(width, height);
|
||||
this.renderer?.setPixelRatio(window.devicePixelRatio);
|
||||
}
|
||||
});
|
||||
|
||||
this.containerResizeObserver.observe(this.container);
|
||||
}
|
||||
|
||||
getAssetType(selected: THREE.Object3D<THREE.Object3DEventMap>): AssetType {
|
||||
if (selected.type === "Mesh") {
|
||||
return selected.name === "::CAM::" ? AssetType.CAMERA : AssetType.OBJECT;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// Owns the canvas/container DOM refs, the ResizeObserver, and the resize
|
||||
// cascade that keeps the renderer, post-processing composers, and camera
|
||||
// projection matrices in lockstep with the visible viewport.
|
||||
//
|
||||
// Cameras and renderer are forwarded via closure-based getters that
|
||||
// resolve at call time (so the viewport is robust to construction-order
|
||||
// changes in Editor.initialize). Post-processing is reached through a
|
||||
// single `resizePostProcessing(w, h)` callback rather than three
|
||||
// separate composer getters — that keeps the viewport from peeking at
|
||||
// composer fields that may not exist yet at the first resize, and lets
|
||||
// the post-processing module own its own undefined-pass handling.
|
||||
//
|
||||
// No `import type Editor`, no circular import.
|
||||
|
||||
import type * as THREE from "three";
|
||||
|
||||
export type ViewportEngineRefs = {
|
||||
getCamera: () => THREE.PerspectiveCamera | null;
|
||||
getRenderCamera: () => THREE.PerspectiveCamera | null;
|
||||
getRenderer: () => THREE.WebGLRenderer | undefined;
|
||||
getRenderAspectRatio: () => number;
|
||||
resizePostProcessing: (width: number, height: number) => void;
|
||||
};
|
||||
|
||||
export class ViewportController {
|
||||
container: HTMLElement | null = null;
|
||||
canvReference: HTMLCanvasElement | null = null;
|
||||
canvasRenderCamReference: HTMLCanvasElement | null = null;
|
||||
lastCanvasSize: number = 0;
|
||||
private observer: ResizeObserver | undefined;
|
||||
|
||||
constructor(private readonly engine: ViewportEngineRefs) {}
|
||||
|
||||
containerMayReset() {
|
||||
if (!this.container) {
|
||||
console.warn(
|
||||
"ViewportController - Container does not exist, querying from DOM via document.getElementById",
|
||||
);
|
||||
this.container = document.getElementById("video-scene-container");
|
||||
}
|
||||
}
|
||||
|
||||
// Full resize cascade — cameras + renderer + post-processing.
|
||||
// Called from initialize() and the per-frame check in renderSingleFrame.
|
||||
onWindowResize() {
|
||||
this.containerMayReset();
|
||||
if (!this.container) return;
|
||||
|
||||
const width = this.container.clientWidth;
|
||||
const height = this.container.clientHeight;
|
||||
const camera = this.engine.getCamera();
|
||||
const renderer = this.engine.getRenderer();
|
||||
if (camera == undefined || renderer == undefined) return;
|
||||
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(width, height);
|
||||
this.engine.resizePostProcessing(width, height);
|
||||
|
||||
const renderCamera = this.engine.getRenderCamera();
|
||||
if (renderCamera == undefined) return;
|
||||
|
||||
renderCamera.aspect = this.engine.getRenderAspectRatio();
|
||||
renderCamera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
// Observed resizes from the ResizeObserver use a narrower update —
|
||||
// camera + renderer.setSize + setPixelRatio only — preserved from the
|
||||
// original editor.ts behaviour. Post-processing is intentionally
|
||||
// skipped here, matching the pre-refactor code.
|
||||
setupResizeObserver() {
|
||||
this.containerMayReset();
|
||||
if (!this.container) return;
|
||||
|
||||
this.observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
const camera = this.engine.getCamera();
|
||||
if (camera) {
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
const renderer = this.engine.getRenderer();
|
||||
renderer?.setSize(width, height);
|
||||
renderer?.setPixelRatio(window.devicePixelRatio);
|
||||
}
|
||||
});
|
||||
this.observer.observe(this.container);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user