From ec647523158eb88677cc283ff5cb816897671707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= <39529669+MichaelBuessemeyer@users.noreply.github.com> Date: Fri, 2 May 2025 14:53:04 +0200 Subject: [PATCH 1/5] WIP allowing rotating ortho views --- .../oxalis/controller/scene_controller.ts | 19 ++++-- .../oxalis/geometries/arbitrary_plane.ts | 15 +++-- .../javascripts/oxalis/geometries/plane.ts | 67 ++++++++++++++++--- .../prefetch_strategy_plane.ts | 2 + .../oxalis/model/reducers/flycam_reducer.ts | 8 +-- .../oxalis/model/reducers/settings_reducer.ts | 2 +- .../javascripts/oxalis/shaders/coords.glsl.ts | 7 +- .../oxalis/shaders/filtering.glsl.ts | 1 + .../view/action-bar/dataset_position_view.tsx | 6 +- .../javascripts/oxalis/view/arbitrary_view.ts | 5 ++ .../javascripts/oxalis/view/plane_view.ts | 20 ++++-- 11 files changed, 110 insertions(+), 42 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/scene_controller.ts b/frontend/javascripts/oxalis/controller/scene_controller.ts index 956b4b86618..703870db6cd 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.ts +++ b/frontend/javascripts/oxalis/controller/scene_controller.ts @@ -243,9 +243,11 @@ class SceneController { [OrthoViews.PLANE_YZ]: new Plane(OrthoViews.PLANE_YZ), [OrthoViews.PLANE_XZ]: new Plane(OrthoViews.PLANE_XZ), }; - this.planes[OrthoViews.PLANE_XY].setRotation(new THREE.Euler(Math.PI, 0, 0)); - this.planes[OrthoViews.PLANE_YZ].setRotation(new THREE.Euler(Math.PI, (1 / 2) * Math.PI, 0)); - this.planes[OrthoViews.PLANE_XZ].setRotation(new THREE.Euler((-1 / 2) * Math.PI, 0, 0)); + this.planes[OrthoViews.PLANE_XY].setBaseRotation(new THREE.Euler(Math.PI, 0, 0)); + this.planes[OrthoViews.PLANE_YZ].setBaseRotation( + new THREE.Euler(Math.PI, (1 / 2) * Math.PI, 0), + ); + this.planes[OrthoViews.PLANE_XZ].setBaseRotation(new THREE.Euler((-1 / 2) * Math.PI, 0, 0)); const planeMeshes = _.values(this.planes).flatMap((plane) => plane.getMeshes()); this.rootNode = new THREE.Group().add( @@ -384,15 +386,17 @@ class SceneController { for (const planeId of OrthoViewValuesWithoutTDView) { if (planeId === id) { this.planes[planeId].setOriginalCrosshairColor(); - this.planes[planeId].setVisible(!hidePlanes); + this.planes[planeId].setVisible(id === "PLANE_XY"); const pos = _.clone(originalPosition); + //TODOM: adjust rotation of the plane const ind = Dimensions.getIndices(planeId); // Offset the plane so the user can see the skeletonTracing behind the plane + // TODO: Fix z positioning!!! pos[ind[2]] += planeId === OrthoViews.PLANE_XY ? this.planeShift[ind[2]] : -this.planeShift[ind[2]]; - this.planes[planeId].setPosition(pos, originalPosition); + // this.planes[planeId].setPosition(pos, originalPosition); this.quickSelectGeometry.adaptVisibilityForRendering(originalPosition, ind[2]); } else { @@ -402,10 +406,10 @@ class SceneController { } } else { for (const planeId of OrthoViewValuesWithoutTDView) { - this.planes[planeId].setPosition(originalPosition); + // this.planes[planeId].setPosition(originalPosition); this.planes[planeId].setGrayCrosshairColor(); this.planes[planeId].setVisible( - tdViewDisplayPlanes !== TDViewDisplayModeEnum.NONE, + planeId === "PLANE_XY" && tdViewDisplayPlanes !== TDViewDisplayModeEnum.NONE, this.isPlaneVisible[planeId] && tdViewDisplayPlanes === TDViewDisplayModeEnum.DATA, ); this.planes[planeId].materialFactory.uniforms.is3DViewBeingRendered.value = true; @@ -426,6 +430,7 @@ class SceneController { ); } + // TODO: maybe this needs to be removed!!! if (!optArbitraryPlane) { for (const currentPlane of _.values(this.planes)) { const [scaleX, scaleY] = getPlaneScalingFactor(state, flycam, currentPlane.planeID); diff --git a/frontend/javascripts/oxalis/geometries/arbitrary_plane.ts b/frontend/javascripts/oxalis/geometries/arbitrary_plane.ts index c7dd12856f0..45136788702 100644 --- a/frontend/javascripts/oxalis/geometries/arbitrary_plane.ts +++ b/frontend/javascripts/oxalis/geometries/arbitrary_plane.ts @@ -1,5 +1,5 @@ import _ from "lodash"; -import constants, { OrthoViews } from "oxalis/constants"; +import constants, { OrthoView, OrthoViews } from "oxalis/constants"; import getSceneController from "oxalis/controller/scene_controller_provider"; import PlaneMaterialFactory from "oxalis/geometries/materials/plane_material_factory"; import { getZoomedMatrix } from "oxalis/model/accessors/flycam_accessor"; @@ -27,12 +27,15 @@ type ArbitraryMeshes = { class ArbitraryPlane { meshes: ArbitraryMeshes; isDirty: boolean; + planeID: OrthoView; stopStoreListening: () => void; // @ts-expect-error ts-migrate(2564) FIXME: Property 'materialFactory' has no initializer and ... Remove this comment to see the full error message materialFactory: PlaneMaterialFactory; + baseRotation: THREE.Euler = new THREE.Euler(0, 0, 0); - constructor() { + constructor(planeID: OrthoView) { this.isDirty = true; + this.planeID = planeID; this.meshes = this.createMeshes(); this.stopStoreListening = Store.subscribe(() => { this.isDirty = true; @@ -43,6 +46,9 @@ class ArbitraryPlane { this.stopStoreListening(); this.materialFactory.stopListening(); } + setBaseRotation = (rotation: THREE.Euler) => { + this.baseRotation = rotation; + }; setPosition = (x: number, y: number, z: number) => { // @ts-expect-error ts-migrate(2339) FIXME: Property 'setGlobalPosition' does not exist on typ... Remove this comment to see the full error message @@ -66,6 +72,7 @@ class ArbitraryPlane { return; } + // TODOM: This needs to be done to the plane view planes as well const meshMatrix = new THREE.Matrix4(); meshMatrix.set( matrix[0], @@ -87,7 +94,7 @@ class ArbitraryPlane { ); mesh.matrix.identity(); mesh.matrix.multiply(meshMatrix); - mesh.matrix.multiply(new THREE.Matrix4().makeRotationY(Math.PI)); + mesh.matrix.multiply(new THREE.Matrix4().makeRotationFromEuler(this.baseRotation)); mesh.matrixWorldNeedsUpdate = true; }; @@ -106,7 +113,7 @@ class ArbitraryPlane { return _plane; }; - this.materialFactory = new PlaneMaterialFactory(OrthoViews.PLANE_XY, false, 4); + this.materialFactory = new PlaneMaterialFactory(this.planeID, false, 4); const textureMaterial = this.materialFactory.setup().getMaterial(); const mainPlane = adaptPlane( new THREE.Mesh( diff --git a/frontend/javascripts/oxalis/geometries/plane.ts b/frontend/javascripts/oxalis/geometries/plane.ts index 1e064a2fbfc..6a46f0f5b22 100644 --- a/frontend/javascripts/oxalis/geometries/plane.ts +++ b/frontend/javascripts/oxalis/geometries/plane.ts @@ -1,3 +1,4 @@ +import type { Matrix4x4 } from "libs/mjs"; import _ from "lodash"; import type { OrthoView, Vector3 } from "oxalis/constants"; import constants, { @@ -6,6 +7,7 @@ import constants, { OrthoViewGrayCrosshairColor, OrthoViewValues, } from "oxalis/constants"; +import getSceneController from "oxalis/controller/scene_controller_provider"; import PlaneMaterialFactory from "oxalis/geometries/materials/plane_material_factory"; import Dimensions from "oxalis/model/dimensions"; import { getBaseVoxelFactorsInUnit } from "oxalis/model/scaleinfo"; @@ -31,7 +33,7 @@ class Plane { // This class is supposed to collect all the Geometries that belong to one single plane such as // the plane itself, its texture, borders and crosshairs. // @ts-expect-error ts-migrate(2564) FIXME: Property 'plane' has no initializer and is not def... Remove this comment to see the full error message - plane: THREE.Mesh; + plane: THREE.Mesh; planeID: OrthoView; materialFactory!: PlaneMaterialFactory; displayCrosshair: boolean; @@ -41,11 +43,15 @@ class Plane { // @ts-expect-error ts-migrate(2564) FIXME: Property 'TDViewBorders' has no initializer and is... Remove this comment to see the full error message TDViewBorders: THREE.Line; lastScaleFactors: [number, number]; + isDirty: boolean; + baseRotation: THREE.Euler; + stopStoreListening: () => void; constructor(planeID: OrthoView) { this.planeID = planeID; this.displayCrosshair = true; this.lastScaleFactors = [-1, -1]; + this.isDirty = true; // VIEWPORT_WIDTH means that the plane should be that many voxels wide in the // dimension with the highest mag. In all other dimensions, the plane // is smaller in voxels, so that it is squared in nm. @@ -53,7 +59,11 @@ class Plane { const baseVoxelFactors = getBaseVoxelFactorsInUnit(Store.getState().dataset.dataSource.scale); const scaleArray = Dimensions.transDim(baseVoxelFactors, this.planeID); this.baseScaleVector = new THREE.Vector3(...scaleArray); + this.baseRotation = new THREE.Euler(0, 0, 0); this.createMeshes(); + this.stopStoreListening = Store.subscribe(() => { + this.isDirty = true; + }); } createMeshes(): void { @@ -62,11 +72,13 @@ class Plane { const planeGeo = new THREE.PlaneGeometry(pWidth, pWidth, PLANE_SUBDIVISION, PLANE_SUBDIVISION); this.materialFactory = new PlaneMaterialFactory( this.planeID, - true, + false, OrthoViewValues.indexOf(this.planeID), ); const textureMaterial = this.materialFactory.setup().getMaterial(); this.plane = new THREE.Mesh(planeGeo, textureMaterial); + this.plane.matrixAutoUpdate = false; + this.plane.material.side = THREE.DoubleSide; // create crosshair const crosshairGeometries = []; this.crosshair = new Array(2); @@ -140,6 +152,7 @@ class Plane { }; setScale(xFactor: number, yFactor: number): void { + // TODOM: This scale will likely be overwritten by the flycam matrix if (this.lastScaleFactors[0] === xFactor && this.lastScaleFactors[1] === yFactor) { return; } @@ -156,10 +169,8 @@ class Plane { this.crosshair[1].scale.copy(scaleVec); } - setRotation = (rotVec: THREE.Euler): void => { - [this.plane, this.TDViewBorders, this.crosshair[0], this.crosshair[1]].map((mesh) => - mesh.setRotationFromEuler(rotVec), - ); + setBaseRotation = (rotVec: THREE.Euler): void => { + this.baseRotation.copy(rotVec); }; // In case the plane's position was offset to make geometries @@ -174,10 +185,8 @@ class Plane { this.plane.position.set(x, y, z); if (originalPosition == null) { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'setGlobalPosition' does not exist on typ... Remove this comment to see the full error message this.plane.material.setGlobalPosition(x, y, z); } else { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'setGlobalPosition' does not exist on typ... Remove this comment to see the full error message this.plane.material.setGlobalPosition( originalPosition[0], originalPosition[1], @@ -186,6 +195,46 @@ class Plane { } }; + updateToFlycamMatrix(flycamMatrix: Matrix4x4): void { + // TODO: Copied from ArbitraryPlane. This should be refactored to a common function maybe. + if (this.isDirty) { + const updateMesh = (mesh: THREE.Mesh | THREE.Line | null | undefined) => { + if (!mesh) { + return; + } + + const meshMatrix = new THREE.Matrix4(); + meshMatrix.set( + flycamMatrix[0], + flycamMatrix[4], + flycamMatrix[8], + flycamMatrix[12], + flycamMatrix[1], + flycamMatrix[5], + flycamMatrix[9], + flycamMatrix[13], + flycamMatrix[2], + flycamMatrix[6], + flycamMatrix[10], + flycamMatrix[14], + flycamMatrix[3], + flycamMatrix[7], + flycamMatrix[11], + flycamMatrix[15], + ); + mesh.matrix.identity(); + mesh.matrix.multiply(meshMatrix); + mesh.matrix.multiply(new THREE.Matrix4().makeRotationFromEuler(this.baseRotation)); + mesh.matrixWorldNeedsUpdate = true; + }; + this.getMeshes().forEach(updateMesh); + + this.isDirty = false; + // TODOM: ignore error for now. + getSceneController().update(); + } + } + setVisible = (isVisible: boolean, isDataVisible?: boolean): void => { this.plane.visible = isDataVisible != null ? isDataVisible : isVisible; this.TDViewBorders.visible = isVisible; @@ -195,11 +244,11 @@ class Plane { getMeshes = () => [this.plane, this.TDViewBorders, this.crosshair[0], this.crosshair[1]]; setLinearInterpolationEnabled = (enabled: boolean) => { - // @ts-expect-error ts-migrate(2339) FIXME: Property 'setUseBilinearFiltering' does not exist ... Remove this comment to see the full error message this.plane.material.setUseBilinearFiltering(enabled); }; destroy() { + this.stopStoreListening(); this.materialFactory.destroy(); } } diff --git a/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_plane.ts b/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_plane.ts index 8b1244e88ad..d148cf4821a 100644 --- a/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_plane.ts +++ b/frontend/javascripts/oxalis/model/bucket_data_handling/prefetch_strategy_plane.ts @@ -70,6 +70,8 @@ export class AbstractPrefetchStrategy { return buckets; } } + +// TODOM: in case of rotation, a PrefetchStrategyArbitrary is needed for every viewport export class PrefetchStrategy extends AbstractPrefetchStrategy { velocityRangeStart = 0; velocityRangeEnd = Number.POSITIVE_INFINITY; diff --git a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts index c6f50d5659b..a71804525fc 100644 --- a/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/flycam_reducer.ts @@ -275,13 +275,7 @@ function FlycamReducer(state: OxalisState, action: Action): OxalisState { } case "SET_ROTATION": { - // This action should only be dispatched when *not* being in orthogonal mode, - // because this would lead to incorrect buckets being selected for rendering. - if (state.temporaryConfiguration.viewMode !== "orthogonal") { - return setRotationReducer(state, action.rotation); - } - // No-op - return state; + return setRotationReducer(state, action.rotation); } case "SET_DIRECTION": { diff --git a/frontend/javascripts/oxalis/model/reducers/settings_reducer.ts b/frontend/javascripts/oxalis/model/reducers/settings_reducer.ts index 31428b2f39d..b783e9c5f75 100644 --- a/frontend/javascripts/oxalis/model/reducers/settings_reducer.ts +++ b/frontend/javascripts/oxalis/model/reducers/settings_reducer.ts @@ -183,8 +183,8 @@ function SettingsReducer(state: OxalisState, action: Action): OxalisState { viewMode: action.viewMode, }); if (action.viewMode !== "orthogonal") { - return newState; } + return newState; // Restore rotation because it might have been changed by the user // in flight/oblique mode. Since this affects the matrix (which is // also used in orthogonal mode), the rotation needs to be reset. diff --git a/frontend/javascripts/oxalis/shaders/coords.glsl.ts b/frontend/javascripts/oxalis/shaders/coords.glsl.ts index 66b044c8447..76f5c3c41f2 100644 --- a/frontend/javascripts/oxalis/shaders/coords.glsl.ts +++ b/frontend/javascripts/oxalis/shaders/coords.glsl.ts @@ -54,11 +54,8 @@ export const getWorldCoordUVW: ShaderModule = { // In orthogonal mode, the planes are offset in 3D space to allow skeletons to be rendered before // each plane. Since w (e.g., z for xy plane) is // the same for all texels computed in this shader, we simply use globalPosition[w] instead - <% if (isOrthogonal) { %> - getW(globalPosition) - <% } else { %> - worldCoordUVW.z / voxelSizeFactorUVW.z - <% } %> + // TODOM: if not rotated, getW can be used + worldCoordUVW.z / voxelSizeFactorUVW.z ); return worldCoordUVW; diff --git a/frontend/javascripts/oxalis/shaders/filtering.glsl.ts b/frontend/javascripts/oxalis/shaders/filtering.glsl.ts index 47299b96a14..1b69297e9e9 100644 --- a/frontend/javascripts/oxalis/shaders/filtering.glsl.ts +++ b/frontend/javascripts/oxalis/shaders/filtering.glsl.ts @@ -79,6 +79,7 @@ export const getTrilinearColorFor: ShaderModule = { const getMaybeFilteredColor: ShaderModule = { requirements: [getColorForCoords, getBilinearColorFor, getTrilinearColorFor], code: ` + // TODOM: Consider passing an argument like "isRotated" to determine whether bilinear or trilinear filtering should be used. vec4 getMaybeFilteredColor( float layerIndex, float d_texture_width, diff --git a/frontend/javascripts/oxalis/view/action-bar/dataset_position_view.tsx b/frontend/javascripts/oxalis/view/action-bar/dataset_position_view.tsx index 69049585aca..db82e2f84c7 100644 --- a/frontend/javascripts/oxalis/view/action-bar/dataset_position_view.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/dataset_position_view.tsx @@ -6,7 +6,6 @@ import Toast from "libs/toast"; import { Vector3Input } from "libs/vector_input"; import message from "messages"; import type { Vector3, ViewMode } from "oxalis/constants"; -import constants from "oxalis/constants"; import { getDatasetExtentInVoxel } from "oxalis/model/accessors/dataset_accessor"; import { getPosition, getRotation } from "oxalis/model/accessors/flycam_accessor"; import { setPositionAction, setRotationAction } from "oxalis/model/actions/flycam_actions"; @@ -112,7 +111,6 @@ class DatasetPositionView extends PureComponent { } const rotation = V3.round(getRotation(this.props.flycam)); - const isArbitraryMode = constants.MODES_ARBITRARY.includes(this.props.viewMode); const positionView = (
{ /> - {isArbitraryMode ? ( + { { allowDecimals /> - ) : null} + }
); return ( diff --git a/frontend/javascripts/oxalis/view/arbitrary_view.ts b/frontend/javascripts/oxalis/view/arbitrary_view.ts index f2980f65345..6ef97c767a1 100644 --- a/frontend/javascripts/oxalis/view/arbitrary_view.ts +++ b/frontend/javascripts/oxalis/view/arbitrary_view.ts @@ -43,6 +43,7 @@ class ArbitraryView { group: THREE.Object3D; cameraPosition: Array; unsubscribeFunctions: Array<() => void> = []; + baseRotation = new THREE.Euler(0, 0, 0); constructor() { this.animate = this.animateImpl.bind(this); @@ -75,6 +76,10 @@ class ArbitraryView { return this.cameras; } + setBaseRotation = (rotVec: THREE.Euler): void => { + this.baseRotation.copy(rotVec); + }; + start(): void { if (!this.isRunning) { this.isRunning = true; diff --git a/frontend/javascripts/oxalis/view/plane_view.ts b/frontend/javascripts/oxalis/view/plane_view.ts index cc5567f176d..6c86e7b2fda 100644 --- a/frontend/javascripts/oxalis/view/plane_view.ts +++ b/frontend/javascripts/oxalis/view/plane_view.ts @@ -3,12 +3,18 @@ import VisibilityAwareRaycaster from "libs/visibility_aware_raycaster"; import window from "libs/window"; import _ from "lodash"; import type { OrthoViewMap, Vector2, Vector3, Viewport } from "oxalis/constants"; -import Constants, { OrthoViewColors, OrthoViewValues, OrthoViews } from "oxalis/constants"; +import Constants, { + OrthoViewColors, + OrthoViewValues, + OrthoViewValuesWithoutTDView, + OrthoViews, +} from "oxalis/constants"; import type { VertexSegmentMapping } from "oxalis/controller/mesh_helpers"; import getSceneController, { getSceneControllerOrNull, } from "oxalis/controller/scene_controller_provider"; import type { MeshSceneNode, SceneGroupForMeshes } from "oxalis/controller/segment_mesh_controller"; +import { getZoomedMatrix } from "oxalis/model/accessors/flycam_accessor"; import { AnnotationTool } from "oxalis/model/accessors/tool_accessor"; import { getInputCatcherRect } from "oxalis/model/accessors/view_mode_accessor"; import { getActiveSegmentationTracing } from "oxalis/model/accessors/volumetracing_accessor"; @@ -105,16 +111,20 @@ class PlaneView { // This is the main render function. // All 3D meshes and the trianglesplane are rendered here. TWEEN.update(); - const SceneController = getSceneController(); + const sceneController = getSceneController(); // skip rendering if nothing has changed // This prevents the GPU/CPU from constantly // working and keeps your lap cool // ATTENTION: this limits the FPS to 60 FPS (depending on the keypress update frequency) if (forceRender || this.needsRerender) { - const { renderer, scene } = SceneController; - SceneController.update(); + const { renderer, scene, planes } = sceneController; + sceneController.update(); const storeState = Store.getState(); + const zoomedFlycamMatrix = getZoomedMatrix(storeState.flycam); + for (const planeId of OrthoViewValuesWithoutTDView) { + planes[planeId].updateToFlycamMatrix(zoomedFlycamMatrix); + } const viewport = { [OrthoViews.PLANE_XY]: getInputCatcherRect(storeState, "PLANE_XY"), [OrthoViews.PLANE_YZ]: getInputCatcherRect(storeState, "PLANE_YZ"), @@ -125,7 +135,7 @@ class PlaneView { clearCanvas(renderer); for (const plane of OrthoViewValues) { - SceneController.updateSceneForCam(plane); + sceneController.updateSceneForCam(plane); const { left, top, width, height } = viewport[plane]; if (width > 0 && height > 0) { From 88561b916a1c7361d19e382115ee844f02b2083c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= <39529669+MichaelBuessemeyer@users.noreply.github.com> Date: Fri, 2 May 2025 15:38:06 +0200 Subject: [PATCH 2/5] allow plane td plane indicator and crosshairs to position arbitrarily --- .../javascripts/oxalis/geometries/plane.ts | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/frontend/javascripts/oxalis/geometries/plane.ts b/frontend/javascripts/oxalis/geometries/plane.ts index 6a46f0f5b22..4707f25fe6c 100644 --- a/frontend/javascripts/oxalis/geometries/plane.ts +++ b/frontend/javascripts/oxalis/geometries/plane.ts @@ -77,6 +77,7 @@ class Plane { ); const textureMaterial = this.materialFactory.setup().getMaterial(); this.plane = new THREE.Mesh(planeGeo, textureMaterial); + this.plane.name = `${this.planeID}-plane`; this.plane.matrixAutoUpdate = false; this.plane.material.side = THREE.DoubleSide; // create crosshair @@ -106,6 +107,8 @@ class Plane { // The default renderOrder is 0. In order for the crosshairs to be shown // render them AFTER the plane has been rendered. this.crosshair[i].renderOrder = 1; + this.crosshair[i].name = `${this.planeID}-crosshair-${i}`; + this.crosshair[i].matrixAutoUpdate = false; } // create borders @@ -121,6 +124,8 @@ class Plane { tdViewBordersGeo, this.getLineBasicMaterial(OrthoViewColors[this.planeID], 1), ); + this.TDViewBorders.name = `${this.planeID}-TDViewBorders`; + this.TDViewBorders.matrixAutoUpdate = false; } setDisplayCrosshair = (value: boolean): void => { @@ -198,33 +203,35 @@ class Plane { updateToFlycamMatrix(flycamMatrix: Matrix4x4): void { // TODO: Copied from ArbitraryPlane. This should be refactored to a common function maybe. if (this.isDirty) { + const meshMatrix = new THREE.Matrix4(); + meshMatrix.set( + flycamMatrix[0], + flycamMatrix[4], + flycamMatrix[8], + flycamMatrix[12], + flycamMatrix[1], + flycamMatrix[5], + flycamMatrix[9], + flycamMatrix[13], + flycamMatrix[2], + flycamMatrix[6], + flycamMatrix[10], + flycamMatrix[14], + flycamMatrix[3], + flycamMatrix[7], + flycamMatrix[11], + flycamMatrix[15], + ); const updateMesh = (mesh: THREE.Mesh | THREE.Line | null | undefined) => { if (!mesh) { return; } - - const meshMatrix = new THREE.Matrix4(); - meshMatrix.set( - flycamMatrix[0], - flycamMatrix[4], - flycamMatrix[8], - flycamMatrix[12], - flycamMatrix[1], - flycamMatrix[5], - flycamMatrix[9], - flycamMatrix[13], - flycamMatrix[2], - flycamMatrix[6], - flycamMatrix[10], - flycamMatrix[14], - flycamMatrix[3], - flycamMatrix[7], - flycamMatrix[11], - flycamMatrix[15], - ); mesh.matrix.identity(); mesh.matrix.multiply(meshMatrix); mesh.matrix.multiply(new THREE.Matrix4().makeRotationFromEuler(this.baseRotation)); + if (this.planeID === "PLANE_XY") { + console.log("matrix plane", mesh.name, mesh.matrix); + } mesh.matrixWorldNeedsUpdate = true; }; this.getMeshes().forEach(updateMesh); From b5caa851ccfd5a07eeb2a66f1eec7538d8531878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= <39529669+MichaelBuessemeyer@users.noreply.github.com> Date: Mon, 5 May 2025 10:54:22 +0200 Subject: [PATCH 3/5] WIP: rotate cameras --- .../oxalis/controller/scene_controller.ts | 19 +++++++----- .../javascripts/oxalis/geometries/plane.ts | 2 +- .../javascripts/oxalis/view/plane_view.ts | 30 +++++++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/scene_controller.ts b/frontend/javascripts/oxalis/controller/scene_controller.ts index 703870db6cd..737d8292fc3 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.ts +++ b/frontend/javascripts/oxalis/controller/scene_controller.ts @@ -70,6 +70,13 @@ THREE.Mesh.prototype.raycast = acceleratedRaycast; const CUBE_COLOR = 0x999999; const LAYER_CUBE_COLOR = 0xffff99; +export const OrthoBaseRotations = { + [OrthoViews.PLANE_XY]: new THREE.Euler(Math.PI, 0, 0), + [OrthoViews.PLANE_YZ]: new THREE.Euler(Math.PI, (1 / 2) * Math.PI, 0), + [OrthoViews.PLANE_XZ]: new THREE.Euler((-1 / 2) * Math.PI, 0, 0), + [OrthoViews.TDView]: new THREE.Euler(Math.PI / 4, Math.PI / 4, Math.PI / 4), +}; + const getVisibleSegmentationLayerNames = reuseInstanceOnEquality((storeState: OxalisState) => getVisibleSegmentationLayers(storeState).map((l) => l.name), ); @@ -243,11 +250,9 @@ class SceneController { [OrthoViews.PLANE_YZ]: new Plane(OrthoViews.PLANE_YZ), [OrthoViews.PLANE_XZ]: new Plane(OrthoViews.PLANE_XZ), }; - this.planes[OrthoViews.PLANE_XY].setBaseRotation(new THREE.Euler(Math.PI, 0, 0)); - this.planes[OrthoViews.PLANE_YZ].setBaseRotation( - new THREE.Euler(Math.PI, (1 / 2) * Math.PI, 0), - ); - this.planes[OrthoViews.PLANE_XZ].setBaseRotation(new THREE.Euler((-1 / 2) * Math.PI, 0, 0)); + this.planes[OrthoViews.PLANE_XY].setBaseRotation(OrthoBaseRotations[OrthoViews.PLANE_XY]); + this.planes[OrthoViews.PLANE_YZ].setBaseRotation(OrthoBaseRotations[OrthoViews.PLANE_YZ]); + this.planes[OrthoViews.PLANE_XZ].setBaseRotation(OrthoBaseRotations[OrthoViews.PLANE_XZ]); const planeMeshes = _.values(this.planes).flatMap((plane) => plane.getMeshes()); this.rootNode = new THREE.Group().add( @@ -386,7 +391,7 @@ class SceneController { for (const planeId of OrthoViewValuesWithoutTDView) { if (planeId === id) { this.planes[planeId].setOriginalCrosshairColor(); - this.planes[planeId].setVisible(id === "PLANE_XY"); + this.planes[planeId].setVisible(!hidePlanes); const pos = _.clone(originalPosition); //TODOM: adjust rotation of the plane @@ -409,7 +414,7 @@ class SceneController { // this.planes[planeId].setPosition(originalPosition); this.planes[planeId].setGrayCrosshairColor(); this.planes[planeId].setVisible( - planeId === "PLANE_XY" && tdViewDisplayPlanes !== TDViewDisplayModeEnum.NONE, + tdViewDisplayPlanes !== TDViewDisplayModeEnum.NONE, this.isPlaneVisible[planeId] && tdViewDisplayPlanes === TDViewDisplayModeEnum.DATA, ); this.planes[planeId].materialFactory.uniforms.is3DViewBeingRendered.value = true; diff --git a/frontend/javascripts/oxalis/geometries/plane.ts b/frontend/javascripts/oxalis/geometries/plane.ts index 4707f25fe6c..780ccdb5598 100644 --- a/frontend/javascripts/oxalis/geometries/plane.ts +++ b/frontend/javascripts/oxalis/geometries/plane.ts @@ -69,7 +69,7 @@ class Plane { createMeshes(): void { const pWidth = constants.VIEWPORT_WIDTH; // create plane - const planeGeo = new THREE.PlaneGeometry(pWidth, pWidth, PLANE_SUBDIVISION, PLANE_SUBDIVISION); + const planeGeo = new THREE.PlaneGeometry(pWidth, pWidth, 1, 1); this.materialFactory = new PlaneMaterialFactory( this.planeID, false, diff --git a/frontend/javascripts/oxalis/view/plane_view.ts b/frontend/javascripts/oxalis/view/plane_view.ts index 6c86e7b2fda..fa729a02c16 100644 --- a/frontend/javascripts/oxalis/view/plane_view.ts +++ b/frontend/javascripts/oxalis/view/plane_view.ts @@ -10,6 +10,7 @@ import Constants, { OrthoViews, } from "oxalis/constants"; import type { VertexSegmentMapping } from "oxalis/controller/mesh_helpers"; +import { OrthoBaseRotations } from "oxalis/controller/scene_controller"; import getSceneController, { getSceneControllerOrNull, } from "oxalis/controller/scene_controller_provider"; @@ -19,6 +20,7 @@ import { AnnotationTool } from "oxalis/model/accessors/tool_accessor"; import { getInputCatcherRect } from "oxalis/model/accessors/view_mode_accessor"; import { getActiveSegmentationTracing } from "oxalis/model/accessors/volumetracing_accessor"; import { updateTemporarySettingAction } from "oxalis/model/actions/settings_actions"; +import Dimensions from "oxalis/model/dimensions"; import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers"; import Store from "oxalis/store"; import { getGroundTruthLayoutRect } from "oxalis/view/layouting/default_layout_configs"; @@ -79,6 +81,7 @@ class PlaneView { scene.add(cameras[plane]); } this.cameras = cameras; + // TODOM: next up rotate the cameras as well createDirLight([10, 10, 10], [0, 0, 10], LIGHT_INTENSITY, this.cameras[OrthoViews.TDView]); createDirLight([-10, 10, 10], [0, 0, 10], LIGHT_INTENSITY, this.cameras[OrthoViews.TDView]); @@ -90,6 +93,10 @@ class PlaneView { this.cameras[OrthoViews.PLANE_YZ].up = new THREE.Vector3(0, -1, 0); this.cameras[OrthoViews.PLANE_XZ].up = new THREE.Vector3(0, 0, -1); this.cameras[OrthoViews.TDView].up = new THREE.Vector3(0, 0, -1); + this.cameras[OrthoViews.PLANE_XY].matrixAutoUpdate = false; + this.cameras[OrthoViews.PLANE_YZ].matrixAutoUpdate = false; + this.cameras[OrthoViews.PLANE_XZ].matrixAutoUpdate = false; + this.cameras[OrthoViews.TDView].matrixAutoUpdate = true; for (const plane of OrthoViewValues) { this.cameras[plane].lookAt(new THREE.Vector3(0, 0, 0)); @@ -124,6 +131,29 @@ class PlaneView { const zoomedFlycamMatrix = getZoomedMatrix(storeState.flycam); for (const planeId of OrthoViewValuesWithoutTDView) { planes[planeId].updateToFlycamMatrix(zoomedFlycamMatrix); + // TODOM: camera adjustment here!!! + const camera = this.cameras[planeId]; + const m = zoomedFlycamMatrix; + // biome-ignore format: don't format array + camera.matrix.set( + m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15], + ); + const planeShift = sceneController.planeShift; + camera.matrix.multiply( + new THREE.Matrix4().makeRotationFromEuler(OrthoBaseRotations[planeId]), + ); + const positionOffset: [number, number, number] = [0, 0, 0]; + + const ind = Dimensions.getIndices(planeId); + // Offset the plane so the user can see the skeletonTracing behind the plane + // TODO: Fix z positioning!!! + positionOffset[ind[2]] += + planeId === OrthoViews.PLANE_XY ? planeShift[ind[2]] : -planeShift[ind[2]]; + camera.matrix.multiply(new THREE.Matrix4().makeTranslation(...positionOffset)); + camera.matrixWorldNeedsUpdate = true; } const viewport = { [OrthoViews.PLANE_XY]: getInputCatcherRect(storeState, "PLANE_XY"), From 79d1104568e6faa39009b6e7bfe98760c72b9971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= <39529669+MichaelBuessemeyer@users.noreply.github.com> Date: Mon, 5 May 2025 18:09:07 +0200 Subject: [PATCH 4/5] fix rendering --- .../javascripts/oxalis/shaders/main_data_shaders.glsl.ts | 4 ++-- frontend/javascripts/oxalis/shaders/texture_access.glsl.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/javascripts/oxalis/shaders/main_data_shaders.glsl.ts b/frontend/javascripts/oxalis/shaders/main_data_shaders.glsl.ts index d6e481c282f..c63a3cb77f7 100644 --- a/frontend/javascripts/oxalis/shaders/main_data_shaders.glsl.ts +++ b/frontend/javascripts/oxalis/shaders/main_data_shaders.glsl.ts @@ -480,7 +480,7 @@ void main() { // Remember the original z position, since it can subtly diverge in the // following calculations due to floating point inaccuracies. This can // result in artifacts, such as the crosshair disappearing. - float originalZ = gl_Position.z; + /*float originalZ = gl_Position.z; // Remember, the top of the viewport has Y=1 whereas the left has X=-1. vec3 worldCoordTopLeft = transDim((modelMatrix * vec4(-PLANE_WIDTH/2., PLANE_WIDTH/2., 0., 1.)).xyz); @@ -590,7 +590,7 @@ void main() { } } } - <% }) %> + <% }) %>*/ } `)({ ...params, diff --git a/frontend/javascripts/oxalis/shaders/texture_access.glsl.ts b/frontend/javascripts/oxalis/shaders/texture_access.glsl.ts index 6a03dc8e990..bc784d84324 100644 --- a/frontend/javascripts/oxalis/shaders/texture_access.glsl.ts +++ b/frontend/javascripts/oxalis/shaders/texture_access.glsl.ts @@ -206,8 +206,8 @@ export const getColorForCoords: ShaderModule = { // To avoid rare rendering artifacts, don't use the precomputed // bucket address when being at the border of buckets. - bool beSafe = false; - { + bool beSafe = true; + /*{ renderedMagIdx = outputMagIdx[globalLayerIndex]; vec3 coords = floor(getAbsoluteCoords(worldPositionUVW, renderedMagIdx, globalLayerIndex)); vec3 absoluteBucketPosition = div(coords, bucketWidth); @@ -220,7 +220,7 @@ export const getColorForCoords: ShaderModule = { ) { beSafe = true; } - } + }*/ if (beSafe || !supportsPrecomputedBucketAddress) { From d94c340d8001ee05c7c2d0ec82ae90fc9765e75a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=BC=C3=9Femeyer?= <39529669+MichaelBuessemeyer@users.noreply.github.com> Date: Mon, 5 May 2025 18:09:16 +0200 Subject: [PATCH 5/5] WIP: fix cameras --- .../oxalis/controller/scene_controller.ts | 25 ++++-- .../javascripts/oxalis/geometries/plane.ts | 82 ++++++++++++++++++- .../javascripts/oxalis/view/plane_view.ts | 43 ++++++---- 3 files changed, 123 insertions(+), 27 deletions(-) diff --git a/frontend/javascripts/oxalis/controller/scene_controller.ts b/frontend/javascripts/oxalis/controller/scene_controller.ts index 737d8292fc3..43fb0ce0be5 100644 --- a/frontend/javascripts/oxalis/controller/scene_controller.ts +++ b/frontend/javascripts/oxalis/controller/scene_controller.ts @@ -71,7 +71,7 @@ const CUBE_COLOR = 0x999999; const LAYER_CUBE_COLOR = 0xffff99; export const OrthoBaseRotations = { - [OrthoViews.PLANE_XY]: new THREE.Euler(Math.PI, 0, 0), + [OrthoViews.PLANE_XY]: new THREE.Euler(0, Math.PI, 0), [OrthoViews.PLANE_YZ]: new THREE.Euler(Math.PI, (1 / 2) * Math.PI, 0), [OrthoViews.PLANE_XZ]: new THREE.Euler((-1 / 2) * Math.PI, 0, 0), [OrthoViews.TDView]: new THREE.Euler(Math.PI / 4, Math.PI / 4, Math.PI / 4), @@ -393,15 +393,17 @@ class SceneController { this.planes[planeId].setOriginalCrosshairColor(); this.planes[planeId].setVisible(!hidePlanes); - const pos = _.clone(originalPosition); - //TODOM: adjust rotation of the plane - const ind = Dimensions.getIndices(planeId); // Offset the plane so the user can see the skeletonTracing behind the plane - // TODO: Fix z positioning!!! - pos[ind[2]] += + const positionOffset: [number, number, number] = [0, 0, 0]; + positionOffset[ind[2]] += planeId === OrthoViews.PLANE_XY ? this.planeShift[ind[2]] : -this.planeShift[ind[2]]; - // this.planes[planeId].setPosition(pos, originalPosition); + //TODOM: adjust rotation of the plane + + this.planes[planeId].offsetForRenderingOrthoView( + new THREE.Vector3(...positionOffset), + originalPosition, + ); this.quickSelectGeometry.adaptVisibilityForRendering(originalPosition, ind[2]); } else { @@ -417,6 +419,15 @@ class SceneController { tdViewDisplayPlanes !== TDViewDisplayModeEnum.NONE, this.isPlaneVisible[planeId] && tdViewDisplayPlanes === TDViewDisplayModeEnum.DATA, ); + const ind = Dimensions.getIndices(planeId); + // Offset the plane so the user can see the skeletonTracing behind the plane + const positionOffset: [number, number, number] = [0, 0, 0]; + positionOffset[ind[2]] += + planeId === OrthoViews.PLANE_XY ? this.planeShift[ind[2]] : -this.planeShift[ind[2]]; + this.planes[planeId].dontOffsetForRenderingTDView( + new THREE.Vector3(...positionOffset), + originalPosition, + ); this.planes[planeId].materialFactory.uniforms.is3DViewBeingRendered.value = true; } } diff --git a/frontend/javascripts/oxalis/geometries/plane.ts b/frontend/javascripts/oxalis/geometries/plane.ts index 780ccdb5598..1f3eadfe7d7 100644 --- a/frontend/javascripts/oxalis/geometries/plane.ts +++ b/frontend/javascripts/oxalis/geometries/plane.ts @@ -46,12 +46,15 @@ class Plane { isDirty: boolean; baseRotation: THREE.Euler; stopStoreListening: () => void; + // Stores whether the meshes are currently offset for rendering one of the ortho views. This should not be the case when rendering the TD view. + areMeshesOffsetForOrthoViewRendering: boolean; constructor(planeID: OrthoView) { this.planeID = planeID; this.displayCrosshair = true; this.lastScaleFactors = [-1, -1]; this.isDirty = true; + this.areMeshesOffsetForOrthoViewRendering = false; // VIEWPORT_WIDTH means that the plane should be that many voxels wide in the // dimension with the highest mag. In all other dimensions, the plane // is smaller in voxels, so that it is squared in nm. @@ -69,7 +72,7 @@ class Plane { createMeshes(): void { const pWidth = constants.VIEWPORT_WIDTH; // create plane - const planeGeo = new THREE.PlaneGeometry(pWidth, pWidth, 1, 1); + const planeGeo = new THREE.PlaneGeometry(pWidth, pWidth, 100, 100); this.materialFactory = new PlaneMaterialFactory( this.planeID, false, @@ -156,6 +159,7 @@ class Plane { }); }; + // Is always called after updateToFlycamMatrix. thus, adding the new scale on top should be fine (I think). setScale(xFactor: number, yFactor: number): void { // TODOM: This scale will likely be overwritten by the flycam matrix if (this.lastScaleFactors[0] === xFactor && this.lastScaleFactors[1] === yFactor) { @@ -163,6 +167,13 @@ class Plane { } this.lastScaleFactors[0] = xFactor; this.lastScaleFactors[1] = yFactor; + /*const additionalScale = new THREE.Vector3(xFactor, yFactor, 1); + this.getMeshes().forEach((mesh) => { + console.log("mesh", mesh.name, mesh.matrix.elements); + mesh.matrix.multiply(new THREE.Matrix4().makeScale(...additionalScale.toArray())); + console.log("mesh", mesh.name, mesh.matrix.elements); + mesh.matrixWorldNeedsUpdate = true; + }); const scaleVec = new THREE.Vector3().multiplyVectors( new THREE.Vector3(xFactor, yFactor, 1), @@ -171,7 +182,7 @@ class Plane { this.plane.scale.copy(scaleVec); this.TDViewBorders.scale.copy(scaleVec); this.crosshair[0].scale.copy(scaleVec); - this.crosshair[1].scale.copy(scaleVec); + this.crosshair[1].scale.copy(scaleVec);*/ } setBaseRotation = (rotVec: THREE.Euler): void => { @@ -200,8 +211,68 @@ class Plane { } }; + offsetForRenderingOrthoView = (offset: THREE.Vector3, original: Vector3): void => { + if (this.areMeshesOffsetForOrthoViewRendering) { + return; + } + this.getMeshes().forEach((mesh) => { + mesh.matrix.multiply(new THREE.Matrix4().makeTranslation(offset)); + }); + // TODO: Test whether this.plane.position is up to date with what stored in the matrix + // get position from matrix + const currentPosition = new THREE.Vector3( + this.plane.matrix.elements[12], + this.plane.matrix.elements[13], + this.plane.matrix.elements[14], + ); + this.plane.material.setGlobalPosition( + currentPosition.x + offset.x, + currentPosition.y + offset.y, + currentPosition.z + offset.z, + ); + console.log( + "offsetForRenderingOrthoView", + offset, + currentPosition, + this.plane.material.globalPosition, + "original", + original, + ); + this.areMeshesOffsetForOrthoViewRendering = true; + }; + + dontOffsetForRenderingTDView = (offset: THREE.Vector3, original: Vector3): void => { + if (!this.areMeshesOffsetForOrthoViewRendering) { + return; + } + offset.negate(); + this.getMeshes().forEach((mesh) => { + mesh.matrix.multiply(new THREE.Matrix4().makeTranslation(offset)); + }); + // TODO: Test whether this.plane.position is up to date with what stored in the matrix + const currentPosition = new THREE.Vector3( + this.plane.matrix.elements[12], + this.plane.matrix.elements[13], + this.plane.matrix.elements[14], + ); + this.plane.material.setGlobalPosition( + currentPosition.x + offset.x, + currentPosition.y + offset.y, + currentPosition.z + offset.z, + ); + console.log( + "offsetForRenderingOrthoView", + offset, + this.plane.position, + currentPosition.sub(offset), + "original", + original, + ); + this.areMeshesOffsetForOrthoViewRendering = false; + }; + updateToFlycamMatrix(flycamMatrix: Matrix4x4): void { - // TODO: Copied from ArbitraryPlane. This should be refactored to a common function maybe. + // TODOM: if (this.isDirty) { const meshMatrix = new THREE.Matrix4(); meshMatrix.set( @@ -229,8 +300,11 @@ class Plane { mesh.matrix.identity(); mesh.matrix.multiply(meshMatrix); mesh.matrix.multiply(new THREE.Matrix4().makeRotationFromEuler(this.baseRotation)); + mesh.matrix.multiply( + new THREE.Matrix4().makeScale(this.lastScaleFactors[0], this.lastScaleFactors[1], 1), + ); if (this.planeID === "PLANE_XY") { - console.log("matrix plane", mesh.name, mesh.matrix); + // console.log("matrix plane", mesh.name, mesh.matrix); } mesh.matrixWorldNeedsUpdate = true; }; diff --git a/frontend/javascripts/oxalis/view/plane_view.ts b/frontend/javascripts/oxalis/view/plane_view.ts index fa729a02c16..b087aaec7e0 100644 --- a/frontend/javascripts/oxalis/view/plane_view.ts +++ b/frontend/javascripts/oxalis/view/plane_view.ts @@ -4,6 +4,7 @@ import window from "libs/window"; import _ from "lodash"; import type { OrthoViewMap, Vector2, Vector3, Viewport } from "oxalis/constants"; import Constants, { + ARBITRARY_CAM_DISTANCE, OrthoViewColors, OrthoViewValues, OrthoViewValuesWithoutTDView, @@ -114,6 +115,17 @@ class PlaneView { window.requestAnimationFrame(() => this.animate()); } + logMatrix(m: THREE.Matrix4): void { + const scale = new THREE.Vector3(); + const rotation = new THREE.Quaternion(); + const position = new THREE.Vector3(); + m.decompose(position, rotation, scale); + const euler = new THREE.Euler(); + euler.setFromQuaternion(rotation); + console.log("position", position, "rotation", euler, "scale", scale); + console.log("matrix", m); + } + renderFunction(forceRender: boolean = false): void { // This is the main render function. // All 3D meshes and the trianglesplane are rendered here. @@ -134,25 +146,24 @@ class PlaneView { // TODOM: camera adjustment here!!! const camera = this.cameras[planeId]; const m = zoomedFlycamMatrix; + //this.logMatrix(camera.matrix); // biome-ignore format: don't format array camera.matrix.set( - m[0], m[4], m[8], m[12], - m[1], m[5], m[9], m[13], - m[2], m[6], m[10], m[14], - m[3], m[7], m[11], m[15], - ); - const planeShift = sceneController.planeShift; - camera.matrix.multiply( - new THREE.Matrix4().makeRotationFromEuler(OrthoBaseRotations[planeId]), + m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15], ); - const positionOffset: [number, number, number] = [0, 0, 0]; - - const ind = Dimensions.getIndices(planeId); - // Offset the plane so the user can see the skeletonTracing behind the plane - // TODO: Fix z positioning!!! - positionOffset[ind[2]] += - planeId === OrthoViews.PLANE_XY ? planeShift[ind[2]] : -planeShift[ind[2]]; - camera.matrix.multiply(new THREE.Matrix4().makeTranslation(...positionOffset)); + //this.logMatrix(camera.matrix); + const rotationMatrix = new THREE.Matrix4().makeRotationFromEuler( + OrthoBaseRotations[planeId], + ); + camera.matrix.multiply(rotationMatrix); + camera.matrix.multiply(new THREE.Matrix4().makeTranslation(0, 0, ARBITRARY_CAM_DISTANCE)); + + //this.logMatrix(camera.matrix); + + //this.logMatrix(camera.matrix); camera.matrixWorldNeedsUpdate = true; } const viewport = {