From 9187bf717b46f949b8d623d8a128f26d7be052f7 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Mon, 27 Apr 2026 00:11:41 +0200 Subject: [PATCH] Refactor path visualization rendering to include object lifecycle management and synchronization --- src/viewport-three/viewport-host.ts | 133 ++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 36 deletions(-) diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index 8e61dbd7..27f8ae47 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -5700,48 +5700,109 @@ export class ViewportHost { continue; } - const line = new Line( - this.createPathLineGeometry(path), - new LineBasicMaterial({ - color: isPathSelected(selection, path.id) - ? PATH_SELECTED_COLOR - : PATH_COLOR - }) - ); - line.userData.pathId = path.id; - - const pointMeshes = path.points.map((point) => { - const mesh = new Mesh( - new SphereGeometry(PATH_POINT_RADIUS, 12, 12), - new MeshBasicMaterial({ - color: isPathSelected(selection, path.id) - ? PATH_POINT_SELECTED_COLOR - : PATH_POINT_COLOR - }) - ); - - mesh.position.set(point.position.x, point.position.y, point.position.z); - mesh.userData.pathId = path.id; - mesh.userData.pathPointId = point.id; - this.pathGroup.add(mesh); - - return { - pointId: point.id, - mesh - }; - }); - - this.pathGroup.add(line); - this.pathRenderObjects.set(path.id, { - line, - pointMeshes - }); + const renderObjects = this.createPathRenderObjects(path, selection); + this.pathGroup.add(renderObjects.line); + for (const pointMesh of renderObjects.pointMeshes) { + this.pathGroup.add(pointMesh.mesh); + } + this.pathRenderObjects.set(path.id, renderObjects); } applyRendererRenderCategory(this.pathGroup, "overlay"); this.refreshPathPresentation(); } + private createPathRenderObjects( + path: ScenePath, + selection: EditorSelection + ): PathRenderObjects { + const line = new Line( + this.createPathLineGeometry(path), + new LineBasicMaterial({ + color: isPathSelected(selection, path.id) + ? PATH_SELECTED_COLOR + : PATH_COLOR + }) + ); + line.userData.pathId = path.id; + + const pointMeshes = path.points.map((point) => { + const mesh = new Mesh( + new SphereGeometry(PATH_POINT_RADIUS, 12, 12), + new MeshBasicMaterial({ + color: isPathSelected(selection, path.id) + ? PATH_POINT_SELECTED_COLOR + : PATH_POINT_COLOR + }) + ); + + mesh.position.set(point.position.x, point.position.y, point.position.z); + mesh.userData.pathId = path.id; + mesh.userData.pathPointId = point.id; + + return { + pointId: point.id, + mesh + }; + }); + + return { + line, + pointMeshes + }; + } + + private disposePathRenderObjects(renderObjects: PathRenderObjects) { + this.pathGroup.remove(renderObjects.line); + renderObjects.line.geometry.dispose(); + renderObjects.line.material.dispose(); + + for (const pointMesh of renderObjects.pointMeshes) { + this.pathGroup.remove(pointMesh.mesh); + pointMesh.mesh.geometry.dispose(); + pointMesh.mesh.material.dispose(); + } + } + + private syncPathRenderObjectForId(pathId: string) { + if (this.currentDocument === null) { + return; + } + + const path = this.currentDocument.paths[pathId]; + const existingRenderObjects = this.pathRenderObjects.get(pathId); + const shouldRender = + path !== undefined && + path.enabled && + (path.visible || this.isSelectedRailCameraRigPathPreviewed(pathId)); + + if (!shouldRender) { + if (existingRenderObjects !== undefined) { + this.disposePathRenderObjects(existingRenderObjects); + this.pathRenderObjects.delete(pathId); + } + return; + } + + if (existingRenderObjects === undefined) { + const renderObjects = this.createPathRenderObjects( + path, + this.currentSelection + ); + this.pathGroup.add(renderObjects.line); + for (const pointMesh of renderObjects.pointMeshes) { + this.pathGroup.add(pointMesh.mesh); + } + this.pathRenderObjects.set(pathId, renderObjects); + applyRendererRenderCategory(this.pathGroup, "overlay"); + this.refreshPathPresentationForId(pathId); + return; + } + + this.updatePathRenderObjectState(path); + this.refreshPathPresentationForId(pathId); + } + private updatePathRenderObjectState(path: ScenePath) { const renderObjects = this.pathRenderObjects.get(path.id);