diff --git a/frontend/javascripts/libs/UpdatableTexture.ts b/frontend/javascripts/libs/UpdatableTexture.ts index bcd933c4388..afeabd74bf3 100644 --- a/frontend/javascripts/libs/UpdatableTexture.ts +++ b/frontend/javascripts/libs/UpdatableTexture.ts @@ -1,4 +1,16 @@ -import * as THREE from "three"; +import { + LinearFilter, + LinearMipMapLinearFilter, + type MagnificationTextureFilter, + type Mapping, + type MinificationTextureFilter, + type PixelFormat, + Texture, + type TextureDataType, + type WebGLRenderer, + WebGLUtils, + type Wrapping, +} from "three"; import type { TypedArray } from "viewer/constants"; /* The UpdatableTexture class exposes a way to partially update a texture. @@ -17,24 +29,24 @@ import type { TypedArray } from "viewer/constants"; */ let originalTexSubImage2D: WebGL2RenderingContext["texSubImage2D"] | null = null; -class UpdatableTexture extends THREE.Texture { +class UpdatableTexture extends Texture { isUpdatableTexture: boolean = true; - renderer!: THREE.WebGLRenderer; + renderer!: WebGLRenderer; gl!: WebGL2RenderingContext; - utils!: THREE.WebGLUtils; + utils!: WebGLUtils; width: number | undefined; height: number | undefined; constructor( width: number, height: number, - format?: THREE.PixelFormat, - type?: THREE.TextureDataType, - mapping?: THREE.Mapping, - wrapS?: THREE.Wrapping, - wrapT?: THREE.Wrapping, - magFilter?: THREE.MagnificationTextureFilter, - minFilter?: THREE.MinificationTextureFilter, + format?: PixelFormat, + type?: TextureDataType, + mapping?: Mapping, + wrapS?: Wrapping, + wrapT?: Wrapping, + magFilter?: MagnificationTextureFilter, + minFilter?: MinificationTextureFilter, anisotropy?: number, ) { const imageData = { width, height, data: new Uint32Array(0) }; @@ -52,18 +64,18 @@ class UpdatableTexture extends THREE.Texture { anisotropy, ); - this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter; - this.minFilter = minFilter !== undefined ? minFilter : THREE.LinearMipMapLinearFilter; + this.magFilter = magFilter !== undefined ? magFilter : LinearFilter; + this.minFilter = minFilter !== undefined ? minFilter : LinearMipMapLinearFilter; this.generateMipmaps = false; this.flipY = false; this.unpackAlignment = 1; this.needsUpdate = true; } - setRenderer(renderer: THREE.WebGLRenderer) { + setRenderer(renderer: WebGLRenderer) { this.renderer = renderer; this.gl = this.renderer.getContext() as WebGL2RenderingContext; - this.utils = new THREE.WebGLUtils(this.gl, this.renderer.extensions); + this.utils = new WebGLUtils(this.gl, this.renderer.extensions); } isInitialized() { diff --git a/frontend/javascripts/libs/compute_bvh_async.ts b/frontend/javascripts/libs/compute_bvh_async.ts index 0c4b88db88b..b252e83eb31 100644 --- a/frontend/javascripts/libs/compute_bvh_async.ts +++ b/frontend/javascripts/libs/compute_bvh_async.ts @@ -1,11 +1,11 @@ -import type * as THREE from "three"; +import type { BufferGeometry } from "three"; import type { MeshBVH } from "three-mesh-bvh"; // @ts-ignore import { GenerateMeshBVHWorker } from "three-mesh-bvh/src/workers/GenerateMeshBVHWorker"; const bvhWorker = new GenerateMeshBVHWorker(); const bvhQueue: Array<{ - geometry: THREE.BufferGeometry; + geometry: BufferGeometry; resolve: (bvh: MeshBVH) => void; reject: (error: unknown) => void; }> = []; @@ -32,7 +32,7 @@ async function processBvhQueue() { } } -export async function computeBvhAsync(geometry: THREE.BufferGeometry): Promise { +export async function computeBvhAsync(geometry: BufferGeometry): Promise { return new Promise((resolve, reject) => { bvhQueue.push({ geometry, resolve, reject }); processBvhQueue(); diff --git a/frontend/javascripts/libs/cuckoo/abstract_cuckoo_table.ts b/frontend/javascripts/libs/cuckoo/abstract_cuckoo_table.ts index 14863a87588..1ec98d7a374 100644 --- a/frontend/javascripts/libs/cuckoo/abstract_cuckoo_table.ts +++ b/frontend/javascripts/libs/cuckoo/abstract_cuckoo_table.ts @@ -1,5 +1,5 @@ import type UpdatableTexture from "libs/UpdatableTexture"; -import * as THREE from "three"; +import { type PixelFormat, type PixelFormatGPU, RGBAIntegerFormat, UnsignedIntType } from "three"; import { getRenderer } from "viewer/controller/renderer"; import { createUpdatableTexture } from "viewer/geometries/materials/plane_material_factory_helpers"; @@ -30,14 +30,14 @@ export abstract class AbstractCuckooTable { } static getTextureType() { - return THREE.UnsignedIntType; + return UnsignedIntType; } - static getTextureFormat(): THREE.PixelFormat { - return THREE.RGBAIntegerFormat; + static getTextureFormat(): PixelFormat { + return RGBAIntegerFormat; } - static getInternalFormat(): THREE.PixelFormatGPU { + static getInternalFormat(): PixelFormatGPU { return "RGBA32UI"; } diff --git a/frontend/javascripts/libs/cuckoo/cuckoo_table_uint32.ts b/frontend/javascripts/libs/cuckoo/cuckoo_table_uint32.ts index af840da0afe..08d39d3e8d8 100644 --- a/frontend/javascripts/libs/cuckoo/cuckoo_table_uint32.ts +++ b/frontend/javascripts/libs/cuckoo/cuckoo_table_uint32.ts @@ -1,4 +1,4 @@ -import * as THREE from "three"; +import { type PixelFormatGPU, RGIntegerFormat } from "three"; import type { NumberLike } from "viewer/store"; import { AbstractCuckooTable, EMPTY_KEY_VALUE } from "./abstract_cuckoo_table"; @@ -18,9 +18,9 @@ export class CuckooTableUint32 extends AbstractCuckooTable { return 2; } static getTextureFormat() { - return THREE.RGIntegerFormat; + return RGIntegerFormat; } - static getInternalFormat(): THREE.PixelFormatGPU { + static getInternalFormat(): PixelFormatGPU { return "RG32UI"; } static fromCapacity(requestedCapacity: number): CuckooTableUint32 { diff --git a/frontend/javascripts/libs/mjs.ts b/frontend/javascripts/libs/mjs.ts index 16adb115cee..b8a75eecdbc 100644 --- a/frontend/javascripts/libs/mjs.ts +++ b/frontend/javascripts/libs/mjs.ts @@ -2,6 +2,7 @@ // https://github.com/imbcmdth/mjs/blob/master/index.js // for all functions in M4x4, V2 and V3. import _ from "lodash"; +import type { Vector3 as ThreeVector3 } from "three"; import type { Vector2, Vector3, Vector4 } from "viewer/constants"; import { chunk3 } from "viewer/model/helpers/chunk"; @@ -274,6 +275,7 @@ function round(v: Vector3Like, r?: Float32Array | null | undefined) { } // @ts-ignore TS claims that the implementation doesn't match the overloading +function divide3(a: ThreeVector3, k: ThreeVector3, r?: ThreeVector3): Vector3; function divide3(a: Vector3, k: Vector3, r?: Vector3): Vector3; function divide3(a: Float32Array, k: Float32Array, r?: Float32Array) { if (r == null) r = new Float32Array(3); diff --git a/frontend/javascripts/libs/order_points_with_mst.ts b/frontend/javascripts/libs/order_points_with_mst.ts index c3d64a58948..1f33e4624db 100644 --- a/frontend/javascripts/libs/order_points_with_mst.ts +++ b/frontend/javascripts/libs/order_points_with_mst.ts @@ -1,6 +1,6 @@ -import type * as THREE from "three"; +import type { Vector3 } from "three"; -export function orderPointsWithMST(points: THREE.Vector3[]): THREE.Vector3[] { +export function orderPointsWithMST(points: Vector3[]): Vector3[] { /* * Find the order of points with the shortest distance heuristically. * This is done by computing the MST of the points and then traversing @@ -63,7 +63,7 @@ interface Edge { dist: number; } -function computeMST(points: THREE.Vector3[]): number[][] { +function computeMST(points: Vector3[]): number[][] { const edges: Edge[] = []; const numPoints = points.length; @@ -110,7 +110,7 @@ function traverseMstDfs(mst: number[][], startIdx = 0): number[] { return orderedPoints; } -function computePathLength(points: THREE.Vector3[], order: number[]): number { +function computePathLength(points: Vector3[], order: number[]): number { let length = 0; for (let i = 0; i < order.length - 1; i++) { length += points[order[i]].distanceTo(points[order[i + 1]]); diff --git a/frontend/javascripts/libs/parse_stl_buffer.ts b/frontend/javascripts/libs/parse_stl_buffer.ts index c5b08661851..892e254b27d 100644 --- a/frontend/javascripts/libs/parse_stl_buffer.ts +++ b/frontend/javascripts/libs/parse_stl_buffer.ts @@ -1,7 +1,7 @@ // @ts-nocheck /* eslint-disable */ -import * as THREE from "three"; +import { BufferAttribute, BufferGeometry, Float32BufferAttribute, LoaderUtils, Vector3 } from "three"; /** * @author aleeper / http://adamleeper.com/ * @author mrdoob / http://mrdoob.com/ @@ -103,7 +103,7 @@ export default function parse(data) { var dataOffset = 84; var faceLength = 12 * 4 + 2; - var geometry = new THREE.BufferGeometry(); + var geometry = new BufferGeometry(); var vertices = []; var normals = []; @@ -141,11 +141,11 @@ export default function parse(data) { } } - geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(vertices), 3)); - geometry.setAttribute("normal", new THREE.BufferAttribute(new Float32Array(normals), 3)); + geometry.setAttribute("position", new BufferAttribute(new Float32Array(vertices), 3)); + geometry.setAttribute("normal", new BufferAttribute(new Float32Array(normals), 3)); if (hasColors) { - geometry.setAttribute("color", new THREE.BufferAttribute(new Float32Array(colors), 3)); + geometry.setAttribute("color", new BufferAttribute(new Float32Array(colors), 3)); geometry.hasColors = true; geometry.alpha = alpha; } @@ -154,7 +154,7 @@ export default function parse(data) { } function parseASCII(data) { - var geometry = new THREE.BufferGeometry(); + var geometry = new BufferGeometry(); var patternFace = /facet([\s\S]*?)endfacet/g; var faceCounter = 0; var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source; @@ -162,7 +162,7 @@ export default function parse(data) { var patternNormal = new RegExp("normal" + patternFloat + patternFloat + patternFloat, "g"); var vertices = []; var normals = []; - var normal = new THREE.Vector3(); + var normal = new Vector3(); var result; while ((result = patternFace.exec(data)) !== null) { @@ -200,14 +200,14 @@ export default function parse(data) { faceCounter++; } - geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3)); - geometry.setAttribute("normal", new THREE.Float32BufferAttribute(normals, 3)); + geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3)); + geometry.setAttribute("normal", new Float32BufferAttribute(normals, 3)); return geometry; } function ensureString(buffer) { if (typeof buffer !== "string") { - return THREE.LoaderUtils.decodeText(new Uint8Array(buffer)); + return LoaderUtils.decodeText(new Uint8Array(buffer)); } return buffer; diff --git a/frontend/javascripts/libs/stl_exporter.ts b/frontend/javascripts/libs/stl_exporter.ts index 21a22b74b9b..8ada67bf0c8 100644 --- a/frontend/javascripts/libs/stl_exporter.ts +++ b/frontend/javascripts/libs/stl_exporter.ts @@ -1,5 +1,5 @@ /* eslint-disable */ -import * as THREE from "three"; +import { Scene, Vector3 } from "three"; // Original Source: https://github.com/mrdoob/three.js/blob/master/examples/js/exporters/STLExporter.js // Manual changes: @@ -34,7 +34,7 @@ class ChunkedDataView { } class STLExporter { - parse(scene: THREE.Scene, options: any = {}) { + parse(scene: Scene, options: any = {}) { const binary = options.binary !== undefined ? options.binary : false; // const objects: any[] = []; @@ -44,7 +44,7 @@ class STLExporter { const geometry = object.geometry; if (geometry.isBufferGeometry !== true) { - throw new Error("STLExporter: Geometry is not of type THREE.BufferGeometry."); + throw new Error("STLExporter: Geometry is not of type BufferGeometry."); } const index = geometry.index; @@ -82,12 +82,12 @@ class STLExporter { outputString += "solid exported\n"; } - const vA = new THREE.Vector3(); - const vB = new THREE.Vector3(); - const vC = new THREE.Vector3(); - const cb = new THREE.Vector3(); - const ab = new THREE.Vector3(); - const normal = new THREE.Vector3(); + const vA = new Vector3(); + const vB = new Vector3(); + const vC = new Vector3(); + const cb = new Vector3(); + const ab = new Vector3(); + const normal = new Vector3(); for (let i = 0, il = objects.length; i < il; i++) { const object = objects[i].object3d; diff --git a/frontend/javascripts/libs/trackball_controls.ts b/frontend/javascripts/libs/trackball_controls.ts index f6d00abd4b3..776bc8b0c9f 100644 --- a/frontend/javascripts/libs/trackball_controls.ts +++ b/frontend/javascripts/libs/trackball_controls.ts @@ -1,5 +1,5 @@ import window, { document } from "libs/window"; -import * as THREE from "three"; +import {EventDispatcher, OrthographicCamera, Quaternion, Vector2, Vector3 } from "three"; /** * The MIT License @@ -30,9 +30,9 @@ import * as THREE from "three"; interface ITrackballControls { new ( - object: THREE.OrthographicCamera, + object: OrthographicCamera, domElement: HTMLElement, - target: THREE.Vector3, + target: Vector3, updateCallback: (args: any) => void, ): ITrackballControls; @@ -50,7 +50,7 @@ interface ITrackballControls { minDistance: number; maxDistance: number; keys: number[]; - target: THREE.Vector3; + target: Vector3; rotateCamera: () => void; destroy: () => void; @@ -59,9 +59,9 @@ interface ITrackballControls { const TrackballControls = function ( this: any, - object: THREE.OrthographicCamera, + object: OrthographicCamera, domElement: HTMLElement, - target: THREE.Vector3, + target: Vector3, updateCallback: (args: any) => void, ) { const _this = this; @@ -95,28 +95,28 @@ const TrackballControls = function ( // [A, S, D] this.keys = [65, 83, 68]; // internals - this.target = target || new THREE.Vector3(); + this.target = target || new Vector3(); this.lastTarget = this.target.clone(); - const lastPosition = new THREE.Vector3(); + const lastPosition = new Vector3(); let _state = STATE.NONE; let _prevState = STATE.NONE; - const _eye = new THREE.Vector3(); + const _eye = new Vector3(); - const _rotateStart = new THREE.Vector3(); + const _rotateStart = new Vector3(); - const _rotateEnd = new THREE.Vector3(); + const _rotateEnd = new Vector3(); - const _zoomStart = new THREE.Vector2(); + const _zoomStart = new Vector2(); - const _zoomEnd = new THREE.Vector2(); + const _zoomEnd = new Vector2(); let _touchZoomDistanceStart = 0; let _touchZoomDistanceEnd = 0; - const _panStart = new THREE.Vector2(); + const _panStart = new Vector2(); - const _panEnd = new THREE.Vector2(); + const _panEnd = new Vector2(); // for reset this.target0 = this.target.clone(); @@ -154,7 +154,7 @@ const TrackballControls = function ( this.getMouseOnScreen = function getMouseOnScreen( pageX: number, pageY: number, - vector: THREE.Vector2, + vector: Vector2, ) { const screenBounds = _this.getScreenBounds(); @@ -165,9 +165,9 @@ const TrackballControls = function ( }; this.getMouseProjectionOnBall = (() => { - const objectUp = new THREE.Vector3(); - const mouseOnBall = new THREE.Vector3(); - return (pageX: number, pageY: number, projection: THREE.Vector3) => { + const objectUp = new Vector3(); + const mouseOnBall = new Vector3(); + return (pageX: number, pageY: number, projection: Vector3) => { const screenBounds = _this.getScreenBounds(); mouseOnBall.set( @@ -199,8 +199,8 @@ const TrackballControls = function ( })(); this.rotateCamera = (() => { - const axis = new THREE.Vector3(); - const quaternion = new THREE.Quaternion(); + const axis = new Vector3(); + const quaternion = new Quaternion(); return () => { let angle = Math.acos( _rotateStart.dot(_rotateEnd) / _rotateStart.length() / _rotateEnd.length(), @@ -250,9 +250,9 @@ const TrackballControls = function ( }; this.panCamera = (() => { - const mouseChange = new THREE.Vector2(); - const objectUp = new THREE.Vector3(); - const pan = new THREE.Vector3(); + const mouseChange = new Vector2(); + const objectUp = new Vector3(); + const pan = new Vector3(); return () => { mouseChange.copy(_panEnd).sub(_panStart); @@ -579,5 +579,5 @@ const TrackballControls = function ( this.update(); } as any as ITrackballControls; -TrackballControls.prototype = Object.create(THREE.EventDispatcher.prototype); +TrackballControls.prototype = Object.create(EventDispatcher.prototype); export default TrackballControls; diff --git a/frontend/javascripts/libs/visibility_aware_raycaster.ts b/frontend/javascripts/libs/visibility_aware_raycaster.ts index 48ca8a8e1e8..591e60e37fb 100644 --- a/frontend/javascripts/libs/visibility_aware_raycaster.ts +++ b/frontend/javascripts/libs/visibility_aware_raycaster.ts @@ -1,21 +1,20 @@ -import * as THREE from "three"; +import { type Intersection, type Object3D, Raycaster } from "three"; -export type RaycastIntersection = - THREE.Intersection; +export type RaycastIntersection = Intersection; -function ascSort(a: RaycastIntersection, b: RaycastIntersection) { +function ascSort(a: RaycastIntersection, b: RaycastIntersection) { return a.distance - b.distance; } -export default class VisibilityAwareRaycaster extends THREE.Raycaster { - // A modified version of the Raycaster.js from three.js. - // The original version can be found here: https://github.com/mrdoob/three.js/blob/dev/src/core/Raycaster.js. +export default class VisibilityAwareRaycaster extends Raycaster { + // A modified version of the Raycaster.js from js. + // The original version can be found here: https://github.com/mrdoob/js/blob/dev/src/core/Raycaster.js. // Types retrieved from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/Raycaster.d.ts. - intersectObjects( - objects: THREE.Object3D[], + intersectObjects( + objects: Object3D[], recursive?: boolean, - intersects: THREE.Intersection[] = [], - ): THREE.Intersection[] { + intersects: Intersection[] = [], + ): Intersection[] { for (let i = 0, l = objects.length; i < l; i++) { if (objects[i].visible) { this.intersectObject(objects[i], recursive, intersects); @@ -26,11 +25,11 @@ export default class VisibilityAwareRaycaster extends THREE.Raycaster { return intersects; } - intersectObject( - object: THREE.Object3D, + intersectObject( + object: Object3D, recursive?: boolean, - intersects: THREE.Intersection[] = [], - ): THREE.Intersection[] { + intersects: Intersection[] = [], + ): Intersection[] { if (object.layers.test(this.layers)) { object.raycast(this, intersects); } diff --git a/frontend/javascripts/libs/window.ts b/frontend/javascripts/libs/window.ts index 22b3e6d18fd..0cfc2eb598d 100644 --- a/frontend/javascripts/libs/window.ts +++ b/frontend/javascripts/libs/window.ts @@ -1,6 +1,6 @@ // This module should be used to access the window object, so it can be mocked in the unit tests -import type * as THREE from "three"; +import type { ShaderMaterial } from "three"; import type { ArbitraryFunction, ArbitraryObject } from "types/globals"; import type TextureBucketManager from "viewer/model/bucket_data_handling/texture_bucket_manager"; @@ -75,7 +75,7 @@ const _window: Window & Olvy?: Olvy; OlvyConfig?: ArbitraryObject | null; managers?: Array; - materials?: Record; + materials?: Record; } = typeof window === "undefined" ? ({ diff --git a/frontend/javascripts/test/api/api_skeleton_latest.spec.ts b/frontend/javascripts/test/api/api_skeleton_latest.spec.ts index 1b2e6707c93..88cf0c741a3 100644 --- a/frontend/javascripts/test/api/api_skeleton_latest.spec.ts +++ b/frontend/javascripts/test/api/api_skeleton_latest.spec.ts @@ -14,7 +14,7 @@ import { import { enforceSkeletonTracing } from "viewer/model/accessors/skeletontracing_accessor"; import { setViewportAction } from "viewer/model/actions/view_mode_actions"; import { setRotationAction } from "viewer/model/actions/flycam_actions"; -import * as THREE from "three"; +import { MathUtils, Matrix4, Euler, Quaternion } from "three"; import { eulerAngleToReducerInternalMatrix, reducerInternalMatrixToEulerAngle, @@ -23,20 +23,20 @@ import testRotations from "test/fixtures/test_rotations"; import { map3 } from "libs/utils"; const toRadian = (arr: Vector3): Vector3 => [ - THREE.MathUtils.degToRad(arr[0]), - THREE.MathUtils.degToRad(arr[1]), - THREE.MathUtils.degToRad(arr[2]), + MathUtils.degToRad(arr[0]), + MathUtils.degToRad(arr[1]), + MathUtils.degToRad(arr[2]), ]; function applyRotationInFlycamReducerSpace( flycamRotationInRadian: Vector3, - rotationToApply: THREE.Euler, + rotationToApply: Euler, ): Vector3 { // eulerAngleToReducerInternalMatrix and reducerInternalMatrixToEulerAngle are tested in rotation_helpers.spec.ts. // Calculate expected rotation and make it a quaternion for equal comparison. const rotationMatrix = eulerAngleToReducerInternalMatrix(flycamRotationInRadian); const rotationMatrixWithViewport = rotationMatrix.multiply( - new THREE.Matrix4().makeRotationFromEuler(rotationToApply), + new Matrix4().makeRotationFromEuler(rotationToApply), ); const resultingAngle = reducerInternalMatrixToEulerAngle(rotationMatrixWithViewport); return resultingAngle; @@ -340,9 +340,7 @@ describe("API Skeleton", () => { flycamRotation, OrthoBaseRotations[planeId], ); - const rotationQuaternion = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...rotationForComparison), - ); + const rotationQuaternion = new Quaternion().setFromEuler(new Euler(...rotationForComparison)); Store.dispatch(setRotationAction(flycamRotation)); Store.dispatch(setViewportAction(planeId)); api.tracing.createNode([10, 10, 10], { activate: true }); @@ -363,12 +361,12 @@ describe("API Skeleton", () => { viewport: OrthoViewToNumber[planeId], mag: 0, }); - const newNodeQuaternion = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...toRadian(newNode.rotation)), + const newNodeQuaternion = new Quaternion().setFromEuler( + new Euler(...toRadian(newNode.rotation)), ); expect( rotationQuaternion.angleTo(newNodeQuaternion), - `Node rotation ${newNode.rotation} is not nearly equal to ${map3(THREE.MathUtils.radToDeg, rotationForComparison)} in viewport ${planeId}.`, + `Node rotation ${newNode.rotation} is not nearly equal to ${map3(MathUtils.radToDeg, rotationForComparison)} in viewport ${planeId}.`, ).toBeLessThan(0.000001); } }); @@ -382,9 +380,7 @@ describe("API Skeleton", () => { rotationInRadian, OrthoBaseRotations[planeId], ); - const rotationQuaternion = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...resultingAngle), - ); + const rotationQuaternion = new Quaternion().setFromEuler(new Euler(...resultingAngle)); // Test node creation. Store.dispatch(setRotationAction(testRotation)); Store.dispatch(setViewportAction(planeId)); @@ -394,12 +390,12 @@ describe("API Skeleton", () => { const newNode = skeletonTracing.trees .getOrThrow(skeletonTracing.activeTreeId || -1) .nodes.getOrThrow(skeletonTracing.activeNodeId || -1); - const newNodeQuaternion = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...toRadian(newNode.rotation)), + const newNodeQuaternion = new Quaternion().setFromEuler( + new Euler(...toRadian(newNode.rotation)), ); expect( rotationQuaternion.angleTo(newNodeQuaternion), - `Node rotation ${newNode.rotation} is not nearly equal to ${map3(THREE.MathUtils.radToDeg, resultingAngle)} in viewport ${planeId}.`, + `Node rotation ${newNode.rotation} is not nearly equal to ${map3(MathUtils.radToDeg, resultingAngle)} in viewport ${planeId}.`, ).toBeLessThan(0.000001); } } diff --git a/frontend/javascripts/test/misc/rotation_helpers.spec.ts b/frontend/javascripts/test/misc/rotation_helpers.spec.ts index 63337fc0e90..d7dfb63273c 100644 --- a/frontend/javascripts/test/misc/rotation_helpers.spec.ts +++ b/frontend/javascripts/test/misc/rotation_helpers.spec.ts @@ -4,22 +4,18 @@ import { reducerInternalMatrixToEulerAngle, } from "viewer/model/helpers/rotation_helpers"; import type { Vector3 } from "viewer/constants"; -import * as THREE from "three"; +import { MathUtils, Quaternion, Euler } from "three"; import { map3 } from "libs/utils"; import testRotations from "test/fixtures/test_rotations"; describe("Rotation Helper Functions", () => { it("should result in an equal rotation after transforming into flycam reducer rotation space and back.", () => { for (const testRotation of testRotations) { - const inputRotationInRadian = map3(THREE.MathUtils.degToRad, testRotation); + const inputRotationInRadian = map3(MathUtils.degToRad, testRotation); const rotationMatrix = eulerAngleToReducerInternalMatrix(inputRotationInRadian); const resultingAngle = reducerInternalMatrixToEulerAngle(rotationMatrix); - const inputQuaternion = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...inputRotationInRadian), - ); - const outputQuaternion = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...resultingAngle), - ); + const inputQuaternion = new Quaternion().setFromEuler(new Euler(...inputRotationInRadian)); + const outputQuaternion = new Quaternion().setFromEuler(new Euler(...resultingAngle)); expect( inputQuaternion.angleTo(outputQuaternion), `Angle ${testRotation} is not converted properly`, @@ -29,35 +25,29 @@ describe("Rotation Helper Functions", () => { // This tests goal is to test and document that the output after converting back from the 'flycam rotation reducer space' the result needs to be interpreted as a XYZ Euler angle. it("should *not* result in an equal rotation after transforming into flycam reducer rotation space and back if interpreted in wrong euler order.", () => { const testRotation = [30, 90, 40] as Vector3; - const inputRotationInRadian = map3(THREE.MathUtils.degToRad, testRotation); + const inputRotationInRadian = map3(MathUtils.degToRad, testRotation); const rotationMatrix = eulerAngleToReducerInternalMatrix(inputRotationInRadian); const resultingAngle = reducerInternalMatrixToEulerAngle(rotationMatrix); - const inputQuaternionZYX = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...inputRotationInRadian, "ZYX"), - ); - const outputQuaternionZYX = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...resultingAngle, "ZYX"), + const inputQuaternionZYX = new Quaternion().setFromEuler( + new Euler(...inputRotationInRadian, "ZYX"), ); + const outputQuaternionZYX = new Quaternion().setFromEuler(new Euler(...resultingAngle, "ZYX")); expect( inputQuaternionZYX.angleTo(outputQuaternionZYX), `Angle ${testRotation} is equal although interpreted as 'ZYX' euler order. `, ).toBeGreaterThan(0.001); - const inputQuaternionYZX = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...inputRotationInRadian, "YZX"), - ); - const outputQuaternionYZX = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...resultingAngle, "YZX"), + const inputQuaternionYZX = new Quaternion().setFromEuler( + new Euler(...inputRotationInRadian, "YZX"), ); + const outputQuaternionYZX = new Quaternion().setFromEuler(new Euler(...resultingAngle, "YZX")); expect( inputQuaternionYZX.angleTo(outputQuaternionYZX), `Angle ${testRotation} is equal although interpreted as 'YZX' euler order. `, ).toBeGreaterThan(0.001); - const inputQuaternionXYZ = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...inputRotationInRadian, "XYZ"), - ); - const outputQuaternionXYZ = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...resultingAngle, "XYZ"), + const inputQuaternionXYZ = new Quaternion().setFromEuler( + new Euler(...inputRotationInRadian, "XYZ"), ); + const outputQuaternionXYZ = new Quaternion().setFromEuler(new Euler(...resultingAngle, "XYZ")); expect( inputQuaternionXYZ.angleTo(outputQuaternionXYZ), `Angle ${testRotation} is not equal although interpreted as 'XYZ' euler order should be correct. `, diff --git a/frontend/javascripts/test/mocks/updatable_texture.mock.ts b/frontend/javascripts/test/mocks/updatable_texture.mock.ts index d704bbe7b20..3eb7be0b1d9 100644 --- a/frontend/javascripts/test/mocks/updatable_texture.mock.ts +++ b/frontend/javascripts/test/mocks/updatable_texture.mock.ts @@ -1,4 +1,11 @@ -import * as THREE from "three"; +import { + RedFormat, + RedIntegerFormat, + RGFormat, + RGIntegerFormat, + RGBAFormat, + RGBAIntegerFormat, +} from "three"; import { vi } from "vitest"; /* @@ -7,12 +14,12 @@ import { vi } from "vitest"; * since RGBAFormat is also used for 3 channels which would make the key not unique. */ const formatToChannelCount = new Map([ - [THREE.RedFormat, 1], - [THREE.RedIntegerFormat, 1], - [THREE.RGFormat, 2], - [THREE.RGIntegerFormat, 2], - [THREE.RGBAFormat, 4], - [THREE.RGBAIntegerFormat, 4], + [RedFormat, 1], + [RedIntegerFormat, 1], + [RGFormat, 2], + [RGIntegerFormat, 2], + [RGBAFormat, 4], + [RGBAIntegerFormat, 4], ]); class MockUpdatableTexture { diff --git a/frontend/javascripts/test/model/accessors/view_mode_accessors.spec.ts b/frontend/javascripts/test/model/accessors/view_mode_accessors.spec.ts index 0f5c983cc09..e88c1d452ae 100644 --- a/frontend/javascripts/test/model/accessors/view_mode_accessors.spec.ts +++ b/frontend/javascripts/test/model/accessors/view_mode_accessors.spec.ts @@ -9,7 +9,7 @@ import { M4x4, V3 } from "libs/mjs"; import Dimensions from "viewer/model/dimensions"; import { setRotationAction } from "viewer/model/actions/flycam_actions"; import FlycamReducer from "viewer/model/reducers/flycam_reducer"; -import * as THREE from "three"; +import { MathUtils, Vector3 as ThreeVector3, Euler } from "three"; import { map3 } from "libs/utils"; import { getBaseVoxelFactorsInUnit } from "viewer/model/scaleinfo"; import { almostEqual } from "test/libs/transform_spec_helpers"; @@ -177,7 +177,7 @@ describe("View mode accessors", () => { // When using the rotation of the flycam for calculations, one has to invert the z value and interpret the resulting euler angle as ZYX. // More info about this at https://www.notion.so/scalableminds/3D-Rotations-3D-Scene-210b51644c6380c2a4a6f5f3c069738a?source=copy_link#22bb51644c6380138fdac454d4dac2f0. const rotationCorrected = [rotation[0], rotation[1], -rotation[2]] as Vector3; - const rotationInRadian = map3(THREE.MathUtils.degToRad, rotationCorrected); + const rotationInRadian = map3(MathUtils.degToRad, rotationCorrected); for (const offset of testOffsets) { const stateWithCorrectPlaneActive = update(initialState, { viewModeData: { plane: { activeViewport: { $set: OrthoViews.PLANE_XY } } }, @@ -193,8 +193,8 @@ describe("View mode accessors", () => { clickPositionAtViewportCenter, ); // Applying the rotation of 83° around the x axis to the offset. - const rotatedOffset = new THREE.Vector3(offset[0], offset[1], 0) - .applyEuler(new THREE.Euler(...rotationInRadian, "ZYX")) + const rotatedOffset = new ThreeVector3(offset[0], offset[1], 0) + .applyEuler(new Euler(...rotationInRadian, "ZYX")) .toArray(); const expectedPosition = [...V3.add(initialFlycamPosition, rotatedOffset)] as Vector3; almostEqual( @@ -213,7 +213,7 @@ describe("View mode accessors", () => { // When using the rotation of the flycam for calculations, one has to invert the z value and interpret the resulting euler angle as ZYX. // More info about this at https://www.notion.so/scalableminds/3D-Rotations-3D-Scene-210b51644c6380c2a4a6f5f3c069738a?source=copy_link#22bb51644c6380138fdac454d4dac2f0. const rotationCorrected = [rotation[0], rotation[1], -rotation[2]] as Vector3; - const rotationInRadian = map3(THREE.MathUtils.degToRad, rotationCorrected); + const rotationInRadian = map3(MathUtils.degToRad, rotationCorrected); for (const offset of testOffsets) { const stateWithAnisotropicScale = update(initialState, { dataset: { dataSource: { scale: { factor: { $set: anisotropicDatasetScale } } } }, @@ -230,9 +230,9 @@ describe("View mode accessors", () => { ); // Applying the rotation of 83° around the x axis to the offset and the apply the dataset scale. const scaleFactor = getBaseVoxelFactorsInUnit(rotatedState.dataset.dataSource.scale); - const rotatedAndScaledOffset = new THREE.Vector3(offset[0], offset[1], 0) - .applyEuler(new THREE.Euler(...rotationInRadian, "ZYX")) - .multiply(new THREE.Vector3(...scaleFactor)) + const rotatedAndScaledOffset = new ThreeVector3(offset[0], offset[1], 0) + .applyEuler(new Euler(...rotationInRadian, "ZYX")) + .multiply(new ThreeVector3(...scaleFactor)) .toArray(); const expectedPosition = [ ...V3.add(initialFlycamPosition, V3.round(rotatedAndScaledOffset)), @@ -259,7 +259,7 @@ describe("View mode accessors", () => { // When using the rotation of the flycam for calculations, one has to invert the z value and interpret the resulting euler angle as ZYX. // More info about this at https://www.notion.so/scalableminds/3D-Rotations-3D-Scene-210b51644c6380c2a4a6f5f3c069738a?source=copy_link#22bb51644c6380138fdac454d4dac2f0. const rotationCorrected = [rotation[0], rotation[1], -rotation[2]] as Vector3; - const rotationInRadian = map3(THREE.MathUtils.degToRad, rotationCorrected); + const rotationInRadian = map3(MathUtils.degToRad, rotationCorrected); for (const offset of testOffsets) { for (const planeId of OrthoViewValuesWithoutTDView) { const stateWithAnisotropicScale = update(initialState, { @@ -307,7 +307,7 @@ describe("View mode accessors", () => { // When using the rotation of the flycam for calculations, one has to invert the z value and interpret the resulting euler angle as ZYX. // More info about this at https://www.notion.so/scalableminds/3D-Rotations-3D-Scene-210b51644c6380c2a4a6f5f3c069738a?source=copy_link#22bb51644c6380138fdac454d4dac2f0. const rotationCorrected = [rotation[0], rotation[1], -rotation[2]] as Vector3; - const rotationInRadian = map3(THREE.MathUtils.degToRad, rotationCorrected); + const rotationInRadian = map3(MathUtils.degToRad, rotationCorrected); for (const offset of testOffsets) { for (const planeId of OrthoViewValuesWithoutTDView) { const stateWithAnisotropicScale = update(initialState, { diff --git a/frontend/javascripts/viewer/api/api_latest.ts b/frontend/javascripts/viewer/api/api_latest.ts index 5eacd61109a..03718e25811 100644 --- a/frontend/javascripts/viewer/api/api_latest.ts +++ b/frontend/javascripts/viewer/api/api_latest.ts @@ -17,7 +17,7 @@ import { coalesce, mod } from "libs/utils"; import window, { location } from "libs/window"; import _ from "lodash"; import messages from "messages"; -import * as THREE from "three"; +import { Euler, MathUtils, Quaternion } from "three"; import TWEEN from "tween.js"; import { type APICompoundType, APICompoundTypeEnum, type ElementClass } from "types/api_types"; import type { AdditionalCoordinate } from "types/api_types"; @@ -1417,9 +1417,7 @@ class TracingApi { // As the node rotation was calculated in the way the flycam reducer does its matrix rotation // by using the rotation_helpers.ts there is no need to invert z and we must use XYZ ordered euler angles. const curRotation = getRotationInRadian(flycam, false); - const startQuaternion = new THREE.Quaternion().setFromEuler( - new THREE.Euler(...curRotation, "XYZ"), - ); + const startQuaternion = new Quaternion().setFromEuler(new Euler(...curRotation, "XYZ")); const isNotRotated = V3.equals(curRotation, [0, 0, 0]); const dimensionToSkip = skipCenteringAnimationInThirdDimension && activeViewport !== OrthoViews.TDView && isNotRotated @@ -1428,9 +1426,9 @@ class TracingApi { if (rotation == null) { rotation = curRotation; } else { - rotation = Utils.map3(THREE.MathUtils.degToRad, rotation); + rotation = Utils.map3(MathUtils.degToRad, rotation); } - const endQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(...rotation, "XYZ")); + const endQuaternion = new Quaternion().setFromEuler(new Euler(...rotation, "XYZ")); type Tweener = { positionX: number; @@ -1457,16 +1455,13 @@ class TracingApi { setPositionAction([this.positionX, this.positionY, this.positionZ], dimensionToSkip), ); // Interpolating rotation via quaternions to get shortest rotation. - const interpolatedQuaternion = new THREE.Quaternion().slerpQuaternions( + const interpolatedQuaternion = new Quaternion().slerpQuaternions( startQuaternion, endQuaternion, t, ); - const interpolatedEuler = new THREE.Euler().setFromQuaternion( - interpolatedQuaternion, - "XYZ", - ); - const interpolatedEulerInDegree = Utils.map3(THREE.MathUtils.radToDeg, [ + const interpolatedEuler = new Euler().setFromQuaternion(interpolatedQuaternion, "XYZ"); + const interpolatedEulerInDegree = Utils.map3(MathUtils.radToDeg, [ interpolatedEuler.x, interpolatedEuler.y, interpolatedEuler.z, diff --git a/frontend/javascripts/viewer/constants.ts b/frontend/javascripts/viewer/constants.ts index 1f73f93a136..5d4f0827401 100644 --- a/frontend/javascripts/viewer/constants.ts +++ b/frontend/javascripts/viewer/constants.ts @@ -1,4 +1,4 @@ -import * as THREE from "three"; +import { Euler, Matrix4 } from "three"; export type AdditionalCoordinate = { name: string; value: number }; export const ViewModeValues = ["orthogonal", "flight", "oblique"] as ViewMode[]; @@ -132,19 +132,19 @@ export const OrthoViewCrosshairColors: OrthoViewMap<[number, number]> = { // See the following or an explanation about the relative orientation of the viewports toward the XY viewport. // https://www.notion.so/scalableminds/3D-Rotations-3D-Scene-210b51644c6380c2a4a6f5f3c069738a?source=copy_link#22bb51644c63800e8682e92a5c91a519 export const OrthoBaseRotations = { - [OrthoViews.PLANE_XY]: new THREE.Euler(0, 0, 0), - [OrthoViews.PLANE_YZ]: new THREE.Euler(0, (3 / 2) * Math.PI, 0), - [OrthoViews.PLANE_XZ]: new THREE.Euler(Math.PI / 2, 0, 0), - [OrthoViews.TDView]: new THREE.Euler(Math.PI / 4, Math.PI / 4, Math.PI / 4), + [OrthoViews.PLANE_XY]: new Euler(0, 0, 0), + [OrthoViews.PLANE_YZ]: new Euler(0, (3 / 2) * Math.PI, 0), + [OrthoViews.PLANE_XZ]: new Euler(Math.PI / 2, 0, 0), + [OrthoViews.TDView]: new Euler(Math.PI / 4, Math.PI / 4, Math.PI / 4), }; -function correctCameraViewingDirection(baseEuler: THREE.Euler): THREE.Euler { - const cameraCorrectionEuler = new THREE.Euler(Math.PI, 0, 0); - const correctedEuler = new THREE.Euler(); +function correctCameraViewingDirection(baseEuler: Euler): Euler { + const cameraCorrectionEuler = new Euler(Math.PI, 0, 0); + const correctedEuler = new Euler(); correctedEuler.setFromRotationMatrix( - new THREE.Matrix4() + new Matrix4() .makeRotationFromEuler(baseEuler) - .multiply(new THREE.Matrix4().makeRotationFromEuler(cameraCorrectionEuler)), + .multiply(new Matrix4().makeRotationFromEuler(cameraCorrectionEuler)), "ZYX", ); @@ -157,7 +157,7 @@ export const OrthoCamerasBaseRotations = { [OrthoViews.PLANE_XY]: correctCameraViewingDirection(OrthoBaseRotations[OrthoViews.PLANE_XY]), [OrthoViews.PLANE_YZ]: correctCameraViewingDirection(OrthoBaseRotations[OrthoViews.PLANE_YZ]), [OrthoViews.PLANE_XZ]: correctCameraViewingDirection(OrthoBaseRotations[OrthoViews.PLANE_XZ]), - [OrthoViews.TDView]: new THREE.Euler(Math.PI / 4, Math.PI / 4, Math.PI / 4), + [OrthoViews.TDView]: new Euler(Math.PI / 4, Math.PI / 4, Math.PI / 4), }; export type BorderTabType = { diff --git a/frontend/javascripts/viewer/controller/camera_controller.ts b/frontend/javascripts/viewer/controller/camera_controller.ts index 55c0aa5dcb0..0b56f007c84 100644 --- a/frontend/javascripts/viewer/controller/camera_controller.ts +++ b/frontend/javascripts/viewer/controller/camera_controller.ts @@ -2,7 +2,13 @@ import { V3 } from "libs/mjs"; import * as Utils from "libs/utils"; import _ from "lodash"; import * as React from "react"; -import * as THREE from "three"; +import { + Euler, + Matrix4, + type OrthographicCamera, + Quaternion, + Vector3 as ThreeVector3, +} from "three"; import TWEEN from "tween.js"; import type { OrthoView, OrthoViewMap, OrthoViewRects, Vector3 } from "viewer/constants"; import { @@ -24,7 +30,7 @@ import type { CameraData } from "viewer/store"; import Store from "viewer/store"; type Props = { - cameras: OrthoViewMap; + cameras: OrthoViewMap; onCameraPositionChanged: () => void; onTDCameraChanged: (userTriggered?: boolean) => void; setTargetAndFixPosition: () => void; @@ -40,15 +46,15 @@ function getQuaternionFromCamera(_up: Vector3, position: Vector3, center: Vector const correctedUp = V3.normalize(V3.cross(forward, right)); // Create a basis matrix - const rotationMatrix = new THREE.Matrix4(); + const rotationMatrix = new Matrix4(); rotationMatrix.makeBasis( - new THREE.Vector3(...right), - new THREE.Vector3(...correctedUp), - new THREE.Vector3(...forward), + new ThreeVector3(...right), + new ThreeVector3(...correctedUp), + new ThreeVector3(...forward), ); // Convert to quaternion - const quat = new THREE.Quaternion(); + const quat = new Quaternion(); quat.setFromRotationMatrix(rotationMatrix); return quat; } @@ -82,10 +88,10 @@ class CameraController extends React.PureComponent { // @ts-expect-error ts-migrate(2564) FIXME: Property 'storePropertyUnsubscribers' has no initi... Remove this comment to see the full error message storePropertyUnsubscribers: Array<(...args: Array) => any>; // Properties are only created here to avoid creating new objects for each update call. - flycamRotationEuler = new THREE.Euler(); - flycamRotationMatrix = new THREE.Matrix4(); - baseRotationMatrix = new THREE.Matrix4(); - totalRotationMatrix = new THREE.Matrix4(); + flycamRotationEuler = new Euler(); + flycamRotationMatrix = new Matrix4(); + baseRotationMatrix = new Matrix4(); + totalRotationMatrix = new Matrix4(); componentDidMount() { // Take the whole diagonal extent of the dataset to get the possible maximum extent of the dataset. @@ -233,7 +239,7 @@ class CameraController extends React.PureComponent { tdCamera.right = cameraData.right; tdCamera.top = cameraData.top; tdCamera.bottom = cameraData.bottom; - tdCamera.up = new THREE.Vector3(...cameraData.up); + tdCamera.up = new ThreeVector3(...cameraData.up); tdCamera.updateProjectionMatrix(); this.props.onCameraPositionChanged(); } @@ -302,11 +308,11 @@ export function rotate3DViewTo( [OrthoViews.TDView]: [0, 0, -1], }; - const positionOffsetVector = new THREE.Vector3(...positionOffsetMap[id]); - const upVector = new THREE.Vector3(...upVectorMap[id]); + const positionOffsetVector = new ThreeVector3(...positionOffsetMap[id]); + const upVector = new ThreeVector3(...upVectorMap[id]); // Rotate the positionOffsetVector and upVector by the flycam rotation. - const rotatedOffset = positionOffsetVector.applyEuler(new THREE.Euler(...flycamRotation, "ZYX")); - const rotatedUp = upVector.applyEuler(new THREE.Euler(...flycamRotation, "ZYX")); + const rotatedOffset = positionOffsetVector.applyEuler(new Euler(...flycamRotation, "ZYX")); + const rotatedUp = upVector.applyEuler(new Euler(...flycamRotation, "ZYX")); const position = [ flycamPos[0] + rotatedOffset.x, flycamPos[1] + rotatedOffset.y, @@ -330,7 +336,7 @@ export function rotate3DViewTo( const updateCameraTDView = (tweenState: TweenState, t: number) => { const { left, right, top, bottom } = tweenState; - const tweenedQuat = new THREE.Quaternion(); + const tweenedQuat = new Quaternion(); tweenedQuat.slerpQuaternions(startQuaternion, targetQuaternion, t); const tweened = getCameraFromQuaternion(tweenedQuat); // Use forward vector and currentFlycamPos (lookAt target) to calculate the current diff --git a/frontend/javascripts/viewer/controller/combinations/skeleton_handlers.ts b/frontend/javascripts/viewer/controller/combinations/skeleton_handlers.ts index 6df28f7764a..8636c3d97d9 100644 --- a/frontend/javascripts/viewer/controller/combinations/skeleton_handlers.ts +++ b/frontend/javascripts/viewer/controller/combinations/skeleton_handlers.ts @@ -1,7 +1,7 @@ import { V3 } from "libs/mjs"; import { values } from "libs/utils"; import _ from "lodash"; -import * as THREE from "three"; +import { Euler, Matrix4, Scene, Vector3 as ThreeVector3 } from "three"; import type { AdditionalCoordinate } from "types/api_types"; import type { OrthoView, Point2, Vector3, Viewport } from "viewer/constants"; import { OrthoBaseRotations, OrthoViewToNumber, OrthoViews } from "viewer/constants"; @@ -170,9 +170,9 @@ export function handleOpenContextMenu( } // Already defined here at toplevel to avoid object recreation with each call. Make sure to not do anything async between read and writes. -const flycamRotationEuler = new THREE.Euler(); -const flycamRotationMatrix = new THREE.Matrix4(); -const movementVector = new THREE.Vector3(); +const flycamRotationEuler = new Euler(); +const flycamRotationMatrix = new Matrix4(); +const movementVector = new ThreeVector3(); export function moveNode( dx: number, @@ -425,7 +425,7 @@ export function maybeGetNodeIdFromPosition( // render the clicked viewport with picking enabled // we need a dedicated pickingScene, since we only want to render all nodes and no planes / bounding box / edges etc. const pickingNode = skeleton.startPicking(isTouch); - const pickingScene = new THREE.Scene(); + const pickingScene = new Scene(); pickingScene.add(pickingNode); const camera = planeView.getCameraForPlane(plane); diff --git a/frontend/javascripts/viewer/controller/combinations/tool_controls.ts b/frontend/javascripts/viewer/controller/combinations/tool_controls.ts index 47d57f61959..e9eccf62790 100644 --- a/frontend/javascripts/viewer/controller/combinations/tool_controls.ts +++ b/frontend/javascripts/viewer/controller/combinations/tool_controls.ts @@ -3,7 +3,7 @@ import type { ModifierKeys } from "libs/input"; import { V3 } from "libs/mjs"; import * as Utils from "libs/utils"; import { document } from "libs/window"; -import * as THREE from "three"; +import { Color } from "three"; import { ContourModeEnum, type OrthoView, @@ -758,7 +758,7 @@ export class QuickSelectToolController { } const [h, s, l] = getSegmentColorAsHSLA(state, volumeTracing.activeCellId); - const activeCellColor = new THREE.Color().setHSL(h, s, l); + const activeCellColor = new Color().setHSL(h, s, l); quickSelectGeometry.setColor(activeCellColor); startPos = calculateGlobalPos(state, pos).rounded; currentPos = startPos; diff --git a/frontend/javascripts/viewer/controller/custom_lod.ts b/frontend/javascripts/viewer/controller/custom_lod.ts index 52bbb986b51..66d31a315b8 100644 --- a/frontend/javascripts/viewer/controller/custom_lod.ts +++ b/frontend/javascripts/viewer/controller/custom_lod.ts @@ -1,15 +1,15 @@ -import * as THREE from "three"; +import { Group, LOD } from "three"; import { getTDViewZoom } from "viewer/model/accessors/view_mode_accessor"; import Store from "viewer/store"; -export default class CustomLOD extends THREE.LOD { - noLODGroup: THREE.Group; +export default class CustomLOD extends LOD { + noLODGroup: Group; lodLevelCount: number; constructor() { super(); this.lodLevelCount = 0; - this.noLODGroup = new THREE.Group(); + this.noLODGroup = new Group(); this.add(this.noLODGroup); } @@ -43,23 +43,23 @@ export default class CustomLOD extends THREE.LOD { } } - addNoLODSupportedMesh(meshGroup: THREE.Group) { + addNoLODSupportedMesh(meshGroup: Group) { this.noLODGroup.add(meshGroup); } - addLODMesh(meshGroup: THREE.Group, level: number) { + addLODMesh(meshGroup: Group, level: number) { while (this.lodLevelCount <= level) { - this.addLevel(new THREE.Group(), this.lodLevelCount); + this.addLevel(new Group(), this.lodLevelCount); this.lodLevelCount++; } this.levels[level].object.add(meshGroup); } - removeNoLODSupportedMesh(meshGroup: THREE.Group) { + removeNoLODSupportedMesh(meshGroup: Group) { this.noLODGroup.remove(meshGroup); } - removeLODMesh(meshGroup: THREE.Group, level: number) { + removeLODMesh(meshGroup: Group, level: number) { this.levels[level].object.remove(meshGroup); } } diff --git a/frontend/javascripts/viewer/controller/mesh_helpers.ts b/frontend/javascripts/viewer/controller/mesh_helpers.ts index 299eb33e2d4..57fb802baad 100644 --- a/frontend/javascripts/viewer/controller/mesh_helpers.ts +++ b/frontend/javascripts/viewer/controller/mesh_helpers.ts @@ -1,14 +1,14 @@ import type { meshApi } from "admin/rest_api"; import { V3 } from "libs/mjs"; import _ from "lodash"; -import type * as THREE from "three"; +import type { BufferGeometry } from "three"; import type { Vector3 } from "viewer/constants"; -export type BufferGeometryWithInfo = THREE.BufferGeometry & { +export type BufferGeometryWithInfo = BufferGeometry & { vertexSegmentMapping?: VertexSegmentMapping; }; -export type UnmergedBufferGeometryWithInfo = THREE.BufferGeometry & { +export type UnmergedBufferGeometryWithInfo = BufferGeometry & { unmappedSegmentId: number; vertexSegmentMapping?: VertexSegmentMapping; }; diff --git a/frontend/javascripts/viewer/controller/renderer.ts b/frontend/javascripts/viewer/controller/renderer.ts index 66a36f563c8..c3b6b167b63 100644 --- a/frontend/javascripts/viewer/controller/renderer.ts +++ b/frontend/javascripts/viewer/controller/renderer.ts @@ -1,9 +1,9 @@ import { notifyAboutDisposedRenderer } from "libs/UpdatableTexture"; import { document } from "libs/window"; -import * as THREE from "three"; +import { WebGLRenderer } from "three"; import { Store } from "viewer/singletons"; -let renderer: THREE.WebGLRenderer | null = null; +let renderer: WebGLRenderer | null = null; export function destroyRenderer(): void { if (renderer == null) { @@ -14,7 +14,7 @@ export function destroyRenderer(): void { notifyAboutDisposedRenderer(); } -function getRenderer(): THREE.WebGLRenderer { +function getRenderer(): WebGLRenderer { if (renderer != null) { return renderer; } @@ -23,7 +23,7 @@ function getRenderer(): THREE.WebGLRenderer { renderer = ( renderCanvasElement != null ? // Create a WebGL2 renderer - new THREE.WebGLRenderer({ + new WebGLRenderer({ canvas: renderCanvasElement, // This prevents flickering when rendering to a buffer instead of the canvas preserveDrawingBuffer: true, @@ -38,7 +38,7 @@ function getRenderer(): THREE.WebGLRenderer { antialias: Store.getState().userConfiguration.antialiasRendering, }) : {} - ) as THREE.WebGLRenderer; + ) as WebGLRenderer; return renderer; } diff --git a/frontend/javascripts/viewer/controller/scene_controller.ts b/frontend/javascripts/viewer/controller/scene_controller.ts index a38a11bf85d..5ee7816be4d 100644 --- a/frontend/javascripts/viewer/controller/scene_controller.ts +++ b/frontend/javascripts/viewer/controller/scene_controller.ts @@ -4,7 +4,23 @@ import * as Utils from "libs/utils"; import window from "libs/window"; import _ from "lodash"; -import * as THREE from "three"; +import { + BoxGeometry, + BufferGeometry, + EdgesGeometry, + Euler, + Group, + Line, + LineBasicMaterial, + LineSegments, + Matrix4, + Mesh, + MeshBasicMaterial, + type Object3D, + Scene, + Vector3 as ThreeVector3, + type WebGLRenderer, +} from "three"; import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from "three-mesh-bvh"; import type { BoundingBoxMinMaxType } from "types/bounding_box"; import type { OrthoView, OrthoViewMap, OrthoViewWithoutTDMap, Vector3 } from "viewer/constants"; @@ -60,9 +76,9 @@ import type CustomLOD from "./custom_lod"; import SegmentMeshController from "./segment_mesh_controller"; // Add the extension functions -THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; -THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; -THREE.Mesh.prototype.raycast = acceleratedRaycast; +BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; +BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; +Mesh.prototype.raycast = acceleratedRaycast; const CUBE_COLOR = 0x999999; const LAYER_CUBE_COLOR = 0xffff99; @@ -76,11 +92,11 @@ class SceneController { isPlaneVisible: OrthoViewMap; clippingDistanceInUnit: number; datasetBoundingBox!: Cube; - userBoundingBoxGroup!: THREE.Group; - layerBoundingBoxGroup!: THREE.Group; + userBoundingBoxGroup!: Group; + layerBoundingBoxGroup!: Group; userBoundingBoxes!: Array; layerBoundingBoxes!: { [layerName: string]: Cube }; - annotationToolsGeometryGroup!: THREE.Group; + annotationToolsGeometryGroup!: Group; highlightedBBoxId: number | null | undefined; taskCubeByTracingId: Record = {}; contour!: ContourGeometry; @@ -88,17 +104,17 @@ class SceneController { lineMeasurementGeometry!: LineMeasurementGeometry; areaMeasurementGeometry!: ContourGeometry; planes!: OrthoViewWithoutTDMap; - rootNode!: THREE.Group; - renderer!: THREE.WebGLRenderer; - scene!: THREE.Scene; - rootGroup!: THREE.Group; + rootNode!: Group; + renderer!: WebGLRenderer; + scene!: Scene; + rootGroup!: Group; segmentMeshController: SegmentMeshController; storePropertyUnsubscribers: Array<() => void>; - splitBoundaryMesh: THREE.Mesh | null = null; + splitBoundaryMesh: Mesh | null = null; // Created as instance properties to avoid creating objects in each update call. - private rotatedPositionOffsetVector = new THREE.Vector3(); - private flycamRotationEuler = new THREE.Euler(); + private rotatedPositionOffsetVector = new ThreeVector3(); + private flycamRotationEuler = new Euler(); // This class collects all the meshes displayed in the Skeleton View and updates position and scale of each // element depending on the provided flycam. @@ -118,9 +134,9 @@ class SceneController { this.renderer = getRenderer(); this.createMeshes(); this.bindToEvents(); - this.scene = new THREE.Scene(); + this.scene = new Scene(); this.highlightedBBoxId = null; - this.rootGroup = new THREE.Group(); + this.rootGroup = new Group(); this.scene.add( this.rootGroup.add( this.rootNode, @@ -135,7 +151,7 @@ class SceneController { // scene.scale does not have an effect. // The dimension(s) with the highest mag will not be distorted. this.rootGroup.scale.copy( - new THREE.Vector3(...Store.getState().dataset.dataSource.scale.factor), + new ThreeVector3(...Store.getState().dataset.dataSource.scale.factor), ); this.setupDebuggingMethods(); } @@ -148,20 +164,20 @@ class SceneController { position: Vector3, zoomStep: number, mag: Vector3, - optColor?: string, + optColor?: string | null | undefined, ) => { const bucketSize = [ constants.BUCKET_WIDTH * mag[0], constants.BUCKET_WIDTH * mag[1], constants.BUCKET_WIDTH * mag[2], ]; - const boxGeometry = new THREE.BoxGeometry(...bucketSize); - const edgesGeometry = new THREE.EdgesGeometry(boxGeometry); - const material = new THREE.LineBasicMaterial({ + const boxGeometry = new BoxGeometry(...bucketSize); + const edgesGeometry = new EdgesGeometry(boxGeometry); + const material = new LineBasicMaterial({ color: optColor || (zoomStep === 0 ? 0xff00ff : 0x00ffff), linewidth: 1, }); - const cube = new THREE.LineSegments(edgesGeometry, material); + const cube = new LineSegments(edgesGeometry, material); cube.position.x = position[0] + bucketSize[0] / 2; cube.position.y = position[1] + bucketSize[1] / 2; cube.position.z = position[2] + bucketSize[2] / 2; @@ -174,12 +190,12 @@ class SceneController { // Shrink voxels a bit so that it's easier to identify individual voxels. const cubeLength = _cubeLength.map((el) => el * 0.9); - const boxGeometry = new THREE.BoxGeometry(...cubeLength); - const material = new THREE.MeshBasicMaterial({ + const boxGeometry = new BoxGeometry(...cubeLength); + const material = new MeshBasicMaterial({ color: optColor || 0xff00ff, opacity: 0.5, }); - const cube = new THREE.Mesh(boxGeometry, material); + const cube = new Mesh(boxGeometry, material); cube.position.x = position[0] + cubeLength[0] / 2; cube.position.y = position[1] + cubeLength[1] / 2; cube.position.z = position[2] + cubeLength[2] / 2; @@ -187,19 +203,19 @@ class SceneController { return cube; }; - let renderedLines: THREE.Line[] = []; + let renderedLines: Line[] = []; // Utility function for visual debugging // @ts-ignore window.addLine = (a: Vector3, b: Vector3) => { - const material = new THREE.LineBasicMaterial({ + const material = new LineBasicMaterial({ color: 0x0000ff, }); const points = []; - points.push(new THREE.Vector3(...a)); - points.push(new THREE.Vector3(...b)); - const geometry = new THREE.BufferGeometry().setFromPoints(points); - const line = new THREE.Line(geometry, material); + points.push(new ThreeVector3(...a)); + points.push(new ThreeVector3(...b)); + const geometry = new BufferGeometry().setFromPoints(points); + const line = new Line(geometry, material); this.rootNode.add(line); renderedLines.push(line); }; @@ -215,14 +231,14 @@ class SceneController { }; // @ts-ignore - window.removeBucketMesh = (mesh: THREE.LineSegments) => this.rootNode.remove(mesh); + window.removeBucketMesh = (mesh: LineSegments) => this.rootNode.remove(mesh); } createMeshes(): void { this.userBoundingBoxes = []; - this.userBoundingBoxGroup = new THREE.Group(); - this.layerBoundingBoxGroup = new THREE.Group(); - this.annotationToolsGeometryGroup = new THREE.Group(); + this.userBoundingBoxGroup = new Group(); + this.layerBoundingBoxGroup = new Group(); + this.annotationToolsGeometryGroup = new Group(); const state = Store.getState(); // Cubes const { min, max } = getDatasetBoundingBox(state.dataset); @@ -248,19 +264,19 @@ class SceneController { this.planes[OrthoViews.PLANE_YZ].setBaseRotation(OrthoBaseRotations[OrthoViews.PLANE_YZ]); this.planes[OrthoViews.PLANE_XZ].setBaseRotation(OrthoBaseRotations[OrthoViews.PLANE_XZ]); - const planeGroup = new THREE.Group(); + const planeGroup = new Group(); for (const plane of _.values(this.planes)) { planeGroup.add(...plane.getMeshes()); } // Apply the inverse dataset scale factor to all planes to remove the scaling of the root group // to avoid shearing effects on rotated ortho viewport planes. For more info see plane.ts. planeGroup.scale.copy( - new THREE.Vector3(1, 1, 1).divide( - new THREE.Vector3(...Store.getState().dataset.dataSource.scale.factor), + new ThreeVector3(1, 1, 1).divide( + new ThreeVector3(...Store.getState().dataset.dataSource.scale.factor), ), ); - this.rootNode = new THREE.Group().add( + this.rootNode = new Group().add( this.userBoundingBoxGroup, this.layerBoundingBoxGroup, this.annotationToolsGeometryGroup.add( @@ -285,8 +301,8 @@ class SceneController { return () => {}; } - let splitBoundaryMesh: THREE.Mesh | null = null; - let splines: THREE.Object3D[] = []; + let splitBoundaryMesh: Mesh | null = null; + let splines: Object3D[] = []; try { const objects = computeSplitBoundaryMeshWithSplines(points); splitBoundaryMesh = objects.splitBoundaryMesh; @@ -297,7 +313,7 @@ class SceneController { return () => {}; } - const surfaceGroup = new THREE.Group(); + const surfaceGroup = new Group(); if (splitBoundaryMesh != null) { surfaceGroup.add(splitBoundaryMesh); } @@ -514,12 +530,12 @@ class SceneController { app.vent.emit("rerender"); } - getRootNode(): THREE.Object3D { + getRootNode(): Object3D { return this.rootNode; } setUserBoundingBoxes(bboxes: Array): void { - const newUserBoundingBoxGroup = new THREE.Group(); + const newUserBoundingBoxGroup = new Group(); this.userBoundingBoxes = bboxes.map(({ boundingBox, isVisible, color, id }) => { const { min, max } = boundingBox; const bbColor: Vector3 = [color[0] * 255, color[1] * 255, color[2] * 255]; @@ -540,9 +556,9 @@ class SceneController { this.rootNode.add(this.userBoundingBoxGroup); } - private applyTransformToGroup(transform: Transform, group: THREE.Group | CustomLOD) { + private applyTransformToGroup(transform: Transform, group: Group | CustomLOD) { if (transform.affineMatrix) { - const matrix = new THREE.Matrix4(); + const matrix = new Matrix4(); // @ts-ignore matrix.set(...transform.affineMatrix); // We need to disable matrixAutoUpdate as otherwise the update to the matrix will be lost. @@ -600,7 +616,7 @@ class SceneController { const dataset = state.dataset; const layers = getDataLayers(dataset); - const newLayerBoundingBoxGroup = new THREE.Group(); + const newLayerBoundingBoxGroup = new Group(); this.layerBoundingBoxes = Object.fromEntries( layers.map((layer) => { const boundingBox = getLayerBoundingBox(dataset, layer.name); @@ -619,7 +635,7 @@ class SceneController { state.datasetConfiguration.nativelyRenderedLayerName, )?.affineMatrix; if (transformMatrix) { - const matrix = new THREE.Matrix4(); + const matrix = new Matrix4(); // @ts-ignore matrix.set(...transformMatrix); mesh.applyMatrix4(matrix); @@ -723,7 +739,7 @@ class SceneController { plane.destroy(); } - this.rootNode = new THREE.Group(); + this.rootNode = new Group(); } bindToEvents(): void { diff --git a/frontend/javascripts/viewer/controller/segment_mesh_controller.ts b/frontend/javascripts/viewer/controller/segment_mesh_controller.ts index 032afb21c46..8cd93cb9cae 100644 --- a/frontend/javascripts/viewer/controller/segment_mesh_controller.ts +++ b/frontend/javascripts/viewer/controller/segment_mesh_controller.ts @@ -1,7 +1,18 @@ import app from "app"; import { mergeVertices } from "libs/BufferGeometryUtils"; import _ from "lodash"; -import * as THREE from "three"; +import { + AmbientLight, + BufferAttribute, + BufferGeometry, + Color, + DirectionalLight, + FrontSide, + Group, + Mesh, + MeshLambertMaterial, + Vector3 as ThreeVector3, +} from "three"; import { acceleratedRaycast } from "three-mesh-bvh"; import TWEEN from "tween.js"; import type { AdditionalCoordinate } from "types/api_types"; @@ -16,32 +27,31 @@ import { import Store from "viewer/store"; import { computeBvhAsync } from "libs/compute_bvh_async"; -import type { BufferAttribute } from "three"; import Constants from "viewer/constants"; import { NO_LOD_MESH_INDEX } from "viewer/model/sagas/meshes/common_mesh_saga"; import type { BufferGeometryWithInfo } from "./mesh_helpers"; // Add the raycast function. Assumes the BVH is available on // the `boundsTree` variable -THREE.Mesh.prototype.raycast = acceleratedRaycast; +Mesh.prototype.raycast = acceleratedRaycast; -const hslToSRGB = (hsl: Vector3) => new THREE.Color().setHSL(...hsl).convertSRGBToLinear(); +const hslToSRGB = (hsl: Vector3) => new Color().setHSL(...hsl).convertSRGBToLinear(); -const WHITE = new THREE.Color(1, 1, 1); +const WHITE = new Color(1, 1, 1); const ACTIVATED_COLOR = hslToSRGB([0.7, 0.9, 0.75]); const HOVERED_COLOR = hslToSRGB([0.65, 0.9, 0.75]); const ACTIVATED_COLOR_VEC3 = ACTIVATED_COLOR.toArray() as Vector3; const HOVERED_COLOR_VEC3 = HOVERED_COLOR.toArray() as Vector3; -type MeshMaterial = THREE.MeshLambertMaterial & { originalColor: Vector3 }; +type MeshMaterial = MeshLambertMaterial & { originalColor: Vector3 }; type HighlightState = Vector2 | "full" | null; -export type MeshSceneNode = THREE.Mesh & { +export type MeshSceneNode = Mesh & { hoveredState?: HighlightState; activeState?: HighlightState; parent: SceneGroupForMeshes; isMerged: boolean; }; -export type SceneGroupForMeshes = THREE.Group & { segmentId: number; children: MeshSceneNode[] }; +export type SceneGroupForMeshes = Group & { segmentId: number; children: MeshSceneNode[] }; const setRangeToColor = ( geometry: BufferGeometryWithInfo, @@ -56,13 +66,13 @@ const setRangeToColor = ( } }; -type GroupForLOD = THREE.Group & { +type GroupForLOD = Group & { children: SceneGroupForMeshes[]; forEach: (callback: (el: SceneGroupForMeshes) => void) => void; }; export default class SegmentMeshController { - lightsGroup: THREE.Group; + lightsGroup: Group; // meshesLayerLODRootGroup holds a CustomLOD for each segmentation layer with meshes. // Each CustomLOD group can hold multiple meshes. // meshesLayerLODRootGroup @@ -74,7 +84,7 @@ export default class SegmentMeshController { // - CustomLOD // - LOD X // - meshes - meshesLayerLODRootGroup: THREE.Group; + meshesLayerLODRootGroup: Group; meshesGroupsPerSegmentId: Record< string, // additionalCoordinatesString @@ -91,8 +101,8 @@ export default class SegmentMeshController { > = {}; constructor() { - this.lightsGroup = new THREE.Group(); - this.meshesLayerLODRootGroup = new THREE.Group(); + this.lightsGroup = new Group(); + this.meshesLayerLODRootGroup = new Group(); this.addLights(); } @@ -116,8 +126,8 @@ export default class SegmentMeshController { ): Promise { // Currently, this function is only used by ad hoc meshing. if (vertices.length === 0) return; - let bufferGeometry = new THREE.BufferGeometry(); - bufferGeometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3)); + let bufferGeometry = new BufferGeometry(); + bufferGeometry.setAttribute("position", new BufferAttribute(vertices, 3)); bufferGeometry = mergeVertices(bufferGeometry); bufferGeometry.computeVertexNormals(); @@ -144,10 +154,10 @@ export default class SegmentMeshController { isMerged: boolean, ): MeshSceneNode { const color = this.getColorObjectForSegment(segmentId, layerName); - const meshMaterial = new THREE.MeshLambertMaterial({ + const meshMaterial = new MeshLambertMaterial({ vertexColors: true, }) as MeshMaterial; - meshMaterial.side = THREE.FrontSide; + meshMaterial.side = FrontSide; meshMaterial.transparent = true; const colorArray = color.convertSRGBToLinear().toArray() as Vector3; meshMaterial.originalColor = colorArray; @@ -159,12 +169,12 @@ export default class SegmentMeshController { for (let i = 0; i < geometry.attributes.position.count; i++) { colorBuffer.set(colorArray, i * 3); } - geometry.setAttribute("color", new THREE.BufferAttribute(colorBuffer, 3)); + geometry.setAttribute("color", new BufferAttribute(colorBuffer, 3)); // mesh.parent is still null at this moment, but when the mesh is // added to the group later, parent will be set. We'll ignore // this detail for now via the casting. - const mesh = new THREE.Mesh(geometry, meshMaterial) as any as MeshSceneNode; + const mesh = new Mesh(geometry, meshMaterial) as any as MeshSceneNode; mesh.isMerged = isMerged; const tweenAnimation = new TWEEN.Tween({ @@ -202,7 +212,7 @@ export default class SegmentMeshController { const targetGroup: SceneGroupForMeshes = _.get( this.meshesGroupsPerSegmentId, keys, - new THREE.Group(), + new Group(), ); _.setWith(this.meshesGroupsPerSegmentId, keys, targetGroup, Object); let layerLODGroup = this.meshesLayerLODRootGroup.getObjectByName(layerName) as @@ -235,11 +245,11 @@ export default class SegmentMeshController { scale[1] / dsScaleFactor[1], scale[2] / dsScaleFactor[2], ]; - targetGroup.scale.copy(new THREE.Vector3(...adaptedScale)); + targetGroup.scale.copy(new ThreeVector3(...adaptedScale)); } const meshChunk = this.constructMesh(segmentId, layerName, geometry, opacity, isMerged); - const group = new THREE.Group() as SceneGroupForMeshes; + const group = new Group() as SceneGroupForMeshes; group.add(meshChunk); group.segmentId = segmentId; @@ -293,7 +303,7 @@ export default class SegmentMeshController { segmentId: number, layerName: string, additionalCoordinates?: AdditionalCoordinate[] | null, - ): THREE.Group | null { + ): Group | null { const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const meshGroups = this.getMeshGroups(additionalCoordKey, layerName, segmentId); @@ -372,7 +382,7 @@ export default class SegmentMeshController { getColorObjectForSegment(segmentId: number, layerName: string) { const [hue, saturation, light] = getSegmentColorAsHSLA(Store.getState(), segmentId, layerName); - const color = new THREE.Color().setHSL(hue, saturation, light); + const color = new Color().setHSL(hue, saturation, light); color.convertSRGBToLinear(); return color; @@ -393,7 +403,7 @@ export default class SegmentMeshController { // Note that the PlaneView also attaches a directional light directly to the TD camera, // so that the light moves along the cam. - const ambientLight = new THREE.AmbientLight("white", settings.ambientIntensity); + const ambientLight = new AmbientLight("white", settings.ambientIntensity); this.lightsGroup.add(ambientLight); const lightPositions: Vector3[] = [ @@ -407,10 +417,10 @@ export default class SegmentMeshController { [-1, -1, -1], ]; - const directionalLights: THREE.DirectionalLight[] = []; + const directionalLights: DirectionalLight[] = []; lightPositions.forEach((pos, index) => { - const light = new THREE.DirectionalLight( + const light = new DirectionalLight( WHITE, // @ts-ignore settings[`dirLight${index + 1}Intensity`] || 1, @@ -426,7 +436,7 @@ export default class SegmentMeshController { layerName: string, segmentId: number, lod: number, - ): THREE.Group | null { + ): Group | null { const additionalCoordKey = getAdditionalCoordinatesAsString(additionalCoordinates); const keys = [additionalCoordKey, layerName, segmentId, lod]; @@ -437,7 +447,7 @@ export default class SegmentMeshController { additionalCoordKey: string, layerName: string, segmentId: number, - ): Record | null { + ): Record | null { const keys = [additionalCoordKey, layerName, segmentId]; return _.get(this.meshesGroupsPerSegmentId, keys, null); } @@ -534,7 +544,7 @@ export default class SegmentMeshController { } } - const setMaterialToUniformColor = (material: MeshMaterial, color: THREE.Color) => { + const setMaterialToUniformColor = (material: MeshMaterial, color: Color) => { material.vertexColors = false; material.color = color; material.needsUpdate = true; @@ -550,16 +560,14 @@ export default class SegmentMeshController { const isUniformColor = (mesh.activeState || mesh.hoveredState) === "full" || !mesh.isMerged; if (isUniformColor) { - let newColor = mesh.hoveredState - ? HOVERED_COLOR - : new THREE.Color(...mesh.material.originalColor); + let newColor = mesh.hoveredState ? HOVERED_COLOR : new Color(...mesh.material.originalColor); // Update the material for all meshes that belong to the current // segment ID. Only for adhoc meshes, these will contain multiple // children. For precomputed meshes, this will only affect one // mesh in the scene graph. parent.traverse((child) => { - if (child instanceof THREE.Mesh) { + if (child instanceof Mesh) { setMaterialToUniformColor(child.material, newColor); } }); diff --git a/frontend/javascripts/viewer/controller/td_controller.tsx b/frontend/javascripts/viewer/controller/td_controller.tsx index 0cc00e43e1e..ac8fea5a703 100644 --- a/frontend/javascripts/viewer/controller/td_controller.tsx +++ b/frontend/javascripts/viewer/controller/td_controller.tsx @@ -5,7 +5,7 @@ import * as Utils from "libs/utils"; import _ from "lodash"; import * as React from "react"; import { connect } from "react-redux"; -import * as THREE from "three"; +import { type OrthographicCamera, Vector3 as ThreeVector3 } from "three"; import type { VoxelSize } from "types/api_types"; import { type OrthoView, @@ -41,7 +41,7 @@ import type { CameraData, StoreAnnotation, WebknossosState } from "viewer/store" import Store from "viewer/store"; import type PlaneView from "viewer/view/plane_view"; -export function threeCameraToCameraData(camera: THREE.OrthographicCamera): CameraData { +export function threeCameraToCameraData(camera: OrthographicCamera): CameraData { const { position, up, near, far, left, right, top, bottom } = camera; const objToArr = ({ x, y, z }: { x: number; y: number; z: number }): Vector3 => [x, y, z]; @@ -84,7 +84,7 @@ function getTDViewMouseControlsSkeleton(planeView: PlaneView): Record; + cameras: OrthoViewMap; planeView?: PlaneView; annotation?: StoreAnnotation; }; @@ -163,7 +163,7 @@ class TDController extends React.PureComponent { this.controls = new TrackballControls( tdCamera, view, - new THREE.Vector3(...pos), + new ThreeVector3(...pos), this.onTDCameraChanged, ); this.controls.noZoom = true; @@ -315,10 +315,10 @@ class TDController extends React.PureComponent { if (invertedDiff.every((el) => el === 0)) return; this.oldUnitPos = nmPosition; - const nmVector = new THREE.Vector3(...invertedDiff); + const nmVector = new ThreeVector3(...invertedDiff); // moves camera by the nm vector const camera = this.props.cameras[OrthoViews.TDView]; - const rotation = THREE.Vector3.prototype.multiplyScalar.call(camera.rotation.clone(), -1); + const rotation = ThreeVector3.prototype.multiplyScalar.call(camera.rotation.clone(), -1); // reverse euler order // @ts-expect-error ts-migrate(2339) FIXME: Property 'order' does not exist on type 'Vector3'. rotation.order = rotation.order.split("").reverse().join(""); diff --git a/frontend/javascripts/viewer/geometries/arbitrary_plane.ts b/frontend/javascripts/viewer/geometries/arbitrary_plane.ts index 305e6b39b37..905164fd7ce 100644 --- a/frontend/javascripts/viewer/geometries/arbitrary_plane.ts +++ b/frontend/javascripts/viewer/geometries/arbitrary_plane.ts @@ -1,5 +1,5 @@ import _ from "lodash"; -import * as THREE from "three"; +import { DoubleSide, Matrix4, Mesh, PlaneGeometry, type Scene, ShaderMaterial } from "three"; import constants, { OrthoViews } from "viewer/constants"; import getSceneController from "viewer/controller/scene_controller_provider"; import PlaneMaterialFactory from "viewer/geometries/materials/plane_material_factory"; @@ -20,10 +20,12 @@ import Store from "viewer/store"; // The result is then projected on a flat surface. const renderDebuggerPlane = false; type ArbitraryMeshes = { - mainPlane: THREE.Mesh; - debuggerPlane: THREE.Mesh | null | undefined; + mainPlane: Mesh; + debuggerPlane: Mesh | null | undefined; }; +const flipYRotationMatrix = new Matrix4().makeRotationY(Math.PI); + class ArbitraryPlane { meshes: ArbitraryMeshes; isDirty: boolean; @@ -44,7 +46,7 @@ class ArbitraryPlane { this.materialFactory.stopListening(); } - addToScene(scene: THREE.Scene) { + addToScene(scene: Scene) { _.values(this.meshes).forEach((mesh) => { if (mesh) { scene.add(mesh); @@ -56,12 +58,12 @@ class ArbitraryPlane { if (this.isDirty) { const matrix = getZoomedMatrix(Store.getState().flycam); - const updateMesh = (mesh: THREE.Mesh | null | undefined) => { + const updateMesh = (mesh: Mesh | null | undefined) => { if (!mesh) { return; } - const meshMatrix = new THREE.Matrix4(); + const meshMatrix = new Matrix4(); meshMatrix.set( matrix[0], matrix[4], @@ -82,7 +84,7 @@ class ArbitraryPlane { ); mesh.matrix.identity(); mesh.matrix.multiply(meshMatrix); - mesh.matrix.multiply(new THREE.Matrix4().makeRotationY(Math.PI)); + mesh.matrix.multiply(flipYRotationMatrix); mesh.matrixWorldNeedsUpdate = true; }; @@ -94,18 +96,18 @@ class ArbitraryPlane { } createMeshes(): ArbitraryMeshes { - const adaptPlane = (_plane: THREE.Mesh) => { + const adaptPlane = (_plane: Mesh) => { _plane.rotation.x = Math.PI; _plane.matrixAutoUpdate = false; - _plane.material.side = THREE.DoubleSide; + _plane.material.side = DoubleSide; return _plane; }; this.materialFactory = new PlaneMaterialFactory(OrthoViews.PLANE_XY, false, 4); const textureMaterial = this.materialFactory.setup().getMaterial(); const mainPlane = adaptPlane( - new THREE.Mesh( - new THREE.PlaneGeometry(constants.VIEWPORT_WIDTH, constants.VIEWPORT_WIDTH, 1, 1), + new Mesh( + new PlaneGeometry(constants.VIEWPORT_WIDTH, constants.VIEWPORT_WIDTH, 1, 1), textureMaterial, ), ); @@ -117,7 +119,7 @@ class ArbitraryPlane { } createDebuggerPlane() { - const debuggerMaterial = new THREE.ShaderMaterial({ + const debuggerMaterial = new ShaderMaterial({ uniforms: this.materialFactory.uniforms, vertexShader: ` uniform float sphericalCapRadius; @@ -158,8 +160,8 @@ class ArbitraryPlane { }); debuggerMaterial.transparent = true; shaderEditor.addMaterial(99, debuggerMaterial); - const debuggerPlane = new THREE.Mesh( - new THREE.PlaneGeometry(constants.VIEWPORT_WIDTH, constants.VIEWPORT_WIDTH, 50, 50), + const debuggerPlane = new Mesh( + new PlaneGeometry(constants.VIEWPORT_WIDTH, constants.VIEWPORT_WIDTH, 50, 50), debuggerMaterial, ); return debuggerPlane; diff --git a/frontend/javascripts/viewer/geometries/compute_split_boundary_mesh_with_splines.ts b/frontend/javascripts/viewer/geometries/compute_split_boundary_mesh_with_splines.ts index 03471484bcf..edfd650593e 100644 --- a/frontend/javascripts/viewer/geometries/compute_split_boundary_mesh_with_splines.ts +++ b/frontend/javascripts/viewer/geometries/compute_split_boundary_mesh_with_splines.ts @@ -1,11 +1,23 @@ import { orderPointsWithMST } from "libs/order_points_with_mst"; import _ from "lodash"; -import * as THREE from "three"; +import { + BufferGeometry, + CatmullRomCurve3, + DoubleSide, + Float32BufferAttribute, + Line, + LineBasicMaterial, + MathUtils, + Mesh, + MeshStandardMaterial, + type Object3D, + Vector3 as ThreeVector3, +} from "three"; import type { Vector3 } from "viewer/constants"; export default function computeSplitBoundaryMeshWithSplines(points: Vector3[]): { - splines: THREE.Object3D[]; - splitBoundaryMesh: THREE.Mesh; + splines: Object3D[]; + splitBoundaryMesh: Mesh; } { /** * Generates a smooth, interpolated 3D boundary mesh and corresponding spline visualizations @@ -34,7 +46,7 @@ export default function computeSplitBoundaryMeshWithSplines(points: Vector3[]): * to ensure a valid 3D surface can still be formed. * */ - const splines: THREE.Object3D[] = []; + const splines: Object3D[] = []; const unfilteredPointsByZ = _.groupBy(points, (p) => p[2]); const pointsByZ = _.omitBy(unfilteredPointsByZ, (value) => value.length < 2); @@ -56,7 +68,7 @@ export default function computeSplitBoundaryMeshWithSplines(points: Vector3[]): ]); } - const curvesByZ: Record = {}; + const curvesByZ: Record = {}; // Create curves for existing z-values const curves = _.compact( @@ -71,7 +83,7 @@ export default function computeSplitBoundaryMeshWithSplines(points: Vector3[]): adaptedZ += 0.1; } const points2D = orderPointsWithMST( - pointsByZ[zValue].map((p) => new THREE.Vector3(p[0], p[1], adaptedZ)), + pointsByZ[zValue].map((p) => new ThreeVector3(p[0], p[1], adaptedZ)), ); if (curveIdx > 0) { @@ -82,7 +94,7 @@ export default function computeSplitBoundaryMeshWithSplines(points: Vector3[]): const prevCurvePoints = curvesByZ[zValues[curveIdx - 1]].points; const distActual = currentCurvePoints[0].distanceTo(prevCurvePoints[0]); - const distFlipped = (currentCurvePoints.at(-1) as THREE.Vector3).distanceTo( + const distFlipped = (currentCurvePoints.at(-1) as ThreeVector3).distanceTo( prevCurvePoints[0], ); @@ -92,7 +104,7 @@ export default function computeSplitBoundaryMeshWithSplines(points: Vector3[]): } } - const curve = new THREE.CatmullRomCurve3(points2D); + const curve = new CatmullRomCurve3(points2D); curvesByZ[zValue] = curve; return curve; }), @@ -126,24 +138,24 @@ export default function computeSplitBoundaryMeshWithSplines(points: Vector3[]): const upperPoint = upperCurvePoints[i]; const alpha = (z - lowerZ) / (upperZ - lowerZ); // Interpolation factor - return new THREE.Vector3( - THREE.MathUtils.lerp(lowerPoint.x, upperPoint.x, alpha), - THREE.MathUtils.lerp(lowerPoint.y, upperPoint.y, alpha), + return new ThreeVector3( + MathUtils.lerp(lowerPoint.x, upperPoint.x, alpha), + MathUtils.lerp(lowerPoint.y, upperPoint.y, alpha), z, ); }); // Create the interpolated curve - const interpolatedCurve = new THREE.CatmullRomCurve3(interpolatedPoints); + const interpolatedCurve = new CatmullRomCurve3(interpolatedPoints); curvesByZ[z] = interpolatedCurve; } // Generate and display all curves Object.values(curvesByZ).forEach((curve) => { const curvePoints = curve.getPoints(numDivisions); - const geometry = new THREE.BufferGeometry().setFromPoints(curvePoints); - const material = new THREE.LineBasicMaterial({ color: 0xff0000 }); - const splineObject = new THREE.Line(geometry, material); + const geometry = new BufferGeometry().setFromPoints(curvePoints); + const material = new LineBasicMaterial({ color: 0xff0000 }); + const splineObject = new Line(geometry, material); splines.push(splineObject); }); @@ -172,25 +184,25 @@ export default function computeSplitBoundaryMeshWithSplines(points: Vector3[]): } } - // Convert to Three.js BufferGeometry - const geometry = new THREE.BufferGeometry(); - geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3)); + // Convert to js BufferGeometry + const geometry = new BufferGeometry(); + geometry.setAttribute("position", new Float32BufferAttribute(vertices, 3)); geometry.setIndex(indices); geometry.computeVertexNormals(); // Smooth shading geometry.computeBoundsTree(); // Material and Mesh - const material = new THREE.MeshStandardMaterial({ + const material = new MeshStandardMaterial({ color: 0x0077ff, // A soft blue color metalness: 0.5, // Slight metallic effect roughness: 1, // Some surface roughness for a natural look - side: THREE.DoubleSide, // Render both sides + side: DoubleSide, // Render both sides flatShading: false, // Ensures smooth shading with computed normals opacity: 0.8, transparent: true, wireframe: false, }); - const splitBoundaryMesh = new THREE.Mesh(geometry, material); + const splitBoundaryMesh = new Mesh(geometry, material); return { splines, splitBoundaryMesh, diff --git a/frontend/javascripts/viewer/geometries/crosshair.ts b/frontend/javascripts/viewer/geometries/crosshair.ts index 53be190ce51..817667107f7 100644 --- a/frontend/javascripts/viewer/geometries/crosshair.ts +++ b/frontend/javascripts/viewer/geometries/crosshair.ts @@ -1,9 +1,20 @@ -import * as THREE from "three"; +import { + DoubleSide, + Group, + Matrix4, + Mesh, + MeshBasicMaterial, + RingGeometry, + type Scene, + Vector3, +} from "three"; import { getZoomedMatrix } from "viewer/model/accessors/flycam_accessor"; import Store from "viewer/store"; +const flipYRotationMatrix = new Matrix4().makeRotationY(Math.PI); + class Crosshair { - mesh: THREE.Group; + mesh: Group; WIDTH: number; COLOR: string; SCALE_MIN: number; @@ -47,9 +58,9 @@ class Crosshair { m[11], m[15], ); - mesh.matrix.multiply(new THREE.Matrix4().makeRotationY(Math.PI)); - mesh.matrix.multiply(new THREE.Matrix4().makeTranslation(0, 0, 0.5)); - mesh.matrix.scale(new THREE.Vector3(this.scale, this.scale, this.scale)); + mesh.matrix.multiply(flipYRotationMatrix); + mesh.matrix.multiply(new Matrix4().makeTranslation(0, 0, 0.5)); + mesh.matrix.scale(new Vector3(this.scale, this.scale, this.scale)); mesh.matrixWorldNeedsUpdate = true; this.isDirty = false; } @@ -63,20 +74,20 @@ class Crosshair { } } - addToScene(scene: THREE.Scene) { + addToScene(scene: Scene) { scene.add(this.mesh); } - createMesh(): THREE.Group { + createMesh(): Group { const createCircle = (radius: number) => { - const geometry = new THREE.RingGeometry(radius, radius + 4, 64); - const material = new THREE.MeshBasicMaterial({ color: this.COLOR, side: THREE.DoubleSide }); - return new THREE.Mesh(geometry, material); + const geometry = new RingGeometry(radius, radius + 4, 64); + const material = new MeshBasicMaterial({ color: this.COLOR, side: DoubleSide }); + return new Mesh(geometry, material); }; const outerCircle = createCircle(this.WIDTH / 2); const innerCircle = createCircle(4); - const mesh = new THREE.Group(); + const mesh = new Group(); mesh.add(outerCircle); mesh.add(innerCircle); diff --git a/frontend/javascripts/viewer/geometries/cube.ts b/frontend/javascripts/viewer/geometries/cube.ts index 32e23b52dd0..e20ca4a173e 100644 --- a/frontend/javascripts/viewer/geometries/cube.ts +++ b/frontend/javascripts/viewer/geometries/cube.ts @@ -1,6 +1,6 @@ import app from "app"; import _ from "lodash"; -import * as THREE from "three"; +import { BufferGeometry, Line, LineBasicMaterial, Vector3 as ThreeVector3 } from "three"; import type { OrthoView, OrthoViewWithoutTDMap, Vector3 } from "viewer/constants"; import { OrthoViewValuesWithoutTDView, OrthoViews } from "viewer/constants"; import { getPosition } from "viewer/model/accessors/flycam_accessor"; @@ -23,8 +23,8 @@ class Cube { // current W position. Without the cross sections, the bounding box' wireframe // would only be visible when the current position matches the edge positions // of the bounding box. - crossSections: OrthoViewWithoutTDMap; - cube: THREE.Line; + crossSections: OrthoViewWithoutTDMap; + cube: Line; min: Vector3; max: Vector3; readonly showCrossSections: boolean; @@ -47,11 +47,11 @@ class Cube { this.initialized = false; this.visible = true; this.isHighlighted = properties.isHighlighted; - this.cube = new THREE.Line(new THREE.BufferGeometry(), this.getLineMaterial()); + this.cube = new Line(new BufferGeometry(), this.getLineMaterial()); this.crossSections = { - PLANE_XY: new THREE.Line(new THREE.BufferGeometry(), this.getLineMaterial()), - PLANE_XZ: new THREE.Line(new THREE.BufferGeometry(), this.getLineMaterial()), - PLANE_YZ: new THREE.Line(new THREE.BufferGeometry(), this.getLineMaterial()), + PLANE_XY: new Line(new BufferGeometry(), this.getLineMaterial()), + PLANE_XZ: new Line(new BufferGeometry(), this.getLineMaterial()), + PLANE_YZ: new Line(new BufferGeometry(), this.getLineMaterial()), }; if (this.min != null && this.max != null) { @@ -69,15 +69,17 @@ class Cube { } getLineMaterial() { - return this.isHighlighted - ? new THREE.LineBasicMaterial({ - color: Store.getState().uiInformation.theme === "light" ? 0xeeeeee : 0xffffff, - linewidth: this.lineWidth, - }) - : new THREE.LineBasicMaterial({ - color: this.color, - linewidth: this.lineWidth, - }); + return new LineBasicMaterial({ + color: this.getLineColor(), + linewidth: this.lineWidth, + }); + } + + getLineColor(): number { + if (this.isHighlighted) { + return Store.getState().uiInformation.theme === "light" ? 0xeeeeee : 0xffffff; + } + return this.color; } setCorners(min: Vector3, max: Vector3) { @@ -89,7 +91,7 @@ class Cube { // box, we subtract Number.EPSILON. max = [max[0] - Number.EPSILON, max[1] - Number.EPSILON, max[2] - Number.EPSILON]; - const vec = (x: number, y: number, z: number) => new THREE.Vector3(x, y, z); + const vec = (x: number, y: number, z: number) => new ThreeVector3(x, y, z); this.cube.geometry.setFromPoints([ vec(min[0], min[1], min[2]), @@ -168,7 +170,7 @@ class Cube { } } - getMeshes(): Array { + getMeshes(): Line[] { return [this.cube].concat(_.values(this.crossSections)); } @@ -179,7 +181,10 @@ class Cube { this.isHighlighted = highlighted; this.getMeshes().forEach((mesh) => { - mesh.material = this.getLineMaterial(); + // @ts-ignore We don't use material arrays + const meshMaterial: LineBasicMaterial = mesh.material; + meshMaterial.color.setHex(this.getLineColor()); + meshMaterial.linewidth = this.lineWidth; }); app.vent.emit("rerender"); } diff --git a/frontend/javascripts/viewer/geometries/helper_geometries.ts b/frontend/javascripts/viewer/geometries/helper_geometries.ts index a9df66d4b26..40dd3a2b0d1 100644 --- a/frontend/javascripts/viewer/geometries/helper_geometries.ts +++ b/frontend/javascripts/viewer/geometries/helper_geometries.ts @@ -1,19 +1,37 @@ import app from "app"; import { V3 } from "libs/mjs"; import ResizableBuffer from "libs/resizable_buffer"; -import * as THREE from "three"; +import { + BufferAttribute, + BufferGeometry, + Color, + DataTexture, + DoubleSide, + DynamicDrawUsage, + Euler, + Group, + Line, + LineBasicMaterial, + Mesh, + MeshBasicMaterial, + PlaneGeometry, + RGBAFormat, + RepeatWrapping, + Vector3 as ThreeVector3, + Vector2, +} from "three"; import { type OrthoView, OrthoViews, type Vector3 } from "viewer/constants"; import Dimensions from "viewer/model/dimensions"; import { getBaseVoxelInUnit } from "viewer/model/scaleinfo"; import Store from "viewer/store"; -export const CONTOUR_COLOR_NORMAL = new THREE.Color(0x0000ff); -export const CONTOUR_COLOR_DELETE = new THREE.Color(0xff0000); +export const CONTOUR_COLOR_NORMAL = new Color(0x0000ff); +export const CONTOUR_COLOR_DELETE = new Color(0xff0000); export class ContourGeometry { - color: THREE.Color; - line: THREE.Line; - connectingLine: THREE.Line; + color: Color; + line: Line; + connectingLine: Line; vertexBuffer: ResizableBuffer; connectingLinePositions: Float32Array; viewport: OrthoView; @@ -24,28 +42,25 @@ export class ContourGeometry { this.viewport = OrthoViews.PLANE_XY; this.showConnectingLine = showConnectingLine; - const edgeGeometry = new THREE.BufferGeometry(); - const positionAttribute = new THREE.BufferAttribute(new Float32Array(3), 3); - positionAttribute.setUsage(THREE.DynamicDrawUsage); + const edgeGeometry = new BufferGeometry(); + const positionAttribute = new BufferAttribute(new Float32Array(3), 3); + positionAttribute.setUsage(DynamicDrawUsage); edgeGeometry.setAttribute("position", positionAttribute); - this.line = new THREE.Line( + this.line = new Line( edgeGeometry, - new THREE.LineBasicMaterial({ + new LineBasicMaterial({ linewidth: 2, }), ); - const connectingLineGeometry = new THREE.BufferGeometry(); + const connectingLineGeometry = new BufferGeometry(); this.connectingLinePositions = new Float32Array(6); - const connectingLinePositionAttribute = new THREE.BufferAttribute( - this.connectingLinePositions, - 3, - ); - connectingLinePositionAttribute.setUsage(THREE.DynamicDrawUsage); + const connectingLinePositionAttribute = new BufferAttribute(this.connectingLinePositions, 3); + connectingLinePositionAttribute.setUsage(DynamicDrawUsage); connectingLineGeometry.setAttribute("position", connectingLinePositionAttribute); - positionAttribute.setUsage(THREE.DynamicDrawUsage); - this.connectingLine = new THREE.Line( + positionAttribute.setUsage(DynamicDrawUsage); + this.connectingLine = new Line( connectingLineGeometry, - new THREE.LineBasicMaterial({ + new LineBasicMaterial({ linewidth: 2, }), ); @@ -57,7 +72,7 @@ export class ContourGeometry { reset() { this.viewport = OrthoViews.PLANE_XY; this.line.material.color = this.color; - this.connectingLine.material.color = new THREE.Color(0x00ffff); + this.connectingLine.material.color = new Color(0x00ffff); this.vertexBuffer.clear(); this.connectingLinePositions.fill(0); this.finalizeMesh(); @@ -104,8 +119,8 @@ export class ContourGeometry { const mesh = this.line; if (mesh.geometry.attributes.position.array !== this.vertexBuffer.getBuffer()) { // Need to rebuild Geometry - const positionAttribute = new THREE.BufferAttribute(this.vertexBuffer.getBuffer(), 3); - positionAttribute.setUsage(THREE.DynamicDrawUsage); + const positionAttribute = new BufferAttribute(this.vertexBuffer.getBuffer(), 3); + positionAttribute.setUsage(DynamicDrawUsage); mesh.geometry.dispose(); mesh.geometry.setAttribute("position", positionAttribute); } @@ -125,16 +140,13 @@ export class ContourGeometry { const points = this.vertexBuffer.getBuffer(); let previousPointIndex = pointCount - 1; const dimIndices = Dimensions.getIndices(this.viewport); - const scaleVector = new THREE.Vector2( - voxelSizeFactor[dimIndices[0]], - voxelSizeFactor[dimIndices[1]], - ); + const scaleVector = new Vector2(voxelSizeFactor[dimIndices[0]], voxelSizeFactor[dimIndices[1]]); for (let i = 0; i < pointCount; i++) { - const start = new THREE.Vector2( + const start = new Vector2( points[previousPointIndex * 3 + dimIndices[0]], points[previousPointIndex * 3 + dimIndices[1]], ).multiply(scaleVector); - const end = new THREE.Vector2( + const end = new Vector2( points[i * 3 + dimIndices[0]], points[i * 3 + dimIndices[1]], ).multiply(scaleVector); @@ -160,42 +172,42 @@ export class ContourGeometry { } const rotations = { - [OrthoViews.PLANE_XY]: new THREE.Euler(0, 0, 0), - [OrthoViews.PLANE_YZ]: new THREE.Euler(Math.PI, -(1 / 2) * Math.PI, Math.PI), - [OrthoViews.PLANE_XZ]: new THREE.Euler((1 / 2) * Math.PI, 0, 0), + [OrthoViews.PLANE_XY]: new Euler(0, 0, 0), + [OrthoViews.PLANE_YZ]: new Euler(Math.PI, -(1 / 2) * Math.PI, Math.PI), + [OrthoViews.PLANE_XZ]: new Euler((1 / 2) * Math.PI, 0, 0), [OrthoViews.TDView]: null, }; export class QuickSelectGeometry { - color: THREE.Color; - meshGroup: THREE.Group; - centerMarkerColor: THREE.Color; - rectangle: THREE.Mesh; - centerMarker: THREE.Mesh; + color: Color; + meshGroup: Group; + centerMarkerColor: Color; + rectangle: Mesh; + centerMarker: Mesh; constructor() { this.color = CONTOUR_COLOR_NORMAL; - this.centerMarkerColor = new THREE.Color(0xff00ff); + this.centerMarkerColor = new Color(0xff00ff); - const geometry = new THREE.PlaneGeometry(1, 1); - const material = new THREE.MeshBasicMaterial({ - side: THREE.DoubleSide, + const geometry = new PlaneGeometry(1, 1); + const material = new MeshBasicMaterial({ + side: DoubleSide, transparent: true, opacity: 0.5, }); - this.rectangle = new THREE.Mesh(geometry, material); + this.rectangle = new Mesh(geometry, material); const baseWidth = getBaseVoxelInUnit(Store.getState().dataset.dataSource.scale.factor); - const centerGeometry = new THREE.PlaneGeometry(baseWidth, baseWidth); - const centerMaterial = new THREE.MeshBasicMaterial({ + const centerGeometry = new PlaneGeometry(baseWidth, baseWidth); + const centerMaterial = new MeshBasicMaterial({ color: this.centerMarkerColor, - side: THREE.DoubleSide, + side: DoubleSide, transparent: true, opacity: 0.9, }); - this.centerMarker = new THREE.Mesh(centerGeometry, centerMaterial); + this.centerMarker = new Mesh(centerGeometry, centerMaterial); - this.meshGroup = new THREE.Group(); + this.meshGroup = new Group(); this.meshGroup.add(this.rectangle); this.meshGroup.add(this.centerMarker); @@ -237,13 +249,13 @@ export class QuickSelectGeometry { this.rectangle.setRotationFromEuler(rotation); this.centerMarker.setRotationFromEuler(rotation); this.centerMarker.scale.copy( - new THREE.Vector3( + new ThreeVector3( ...Dimensions.transDim(scaleFactor.map((el) => 1 / el) as Vector3, activeViewport), ), ); } - setColor(color: THREE.Color) { + setColor(color: Color) { this.color = color; // Copy this.color into this.centerMarkerColor this.centerMarkerColor.copy(this.color); @@ -309,9 +321,9 @@ export class QuickSelectGeometry { attachTextureMask(ndData: Uint8Array, width: number, height: number) { // Attach the array as a binary mask so that the rectangle preview // is only rendered where the passed array is 1. - const texture = new THREE.DataTexture(ndData, width, height, THREE.RGBAFormat); - texture.wrapS = THREE.RepeatWrapping; - texture.wrapT = THREE.RepeatWrapping; + const texture = new DataTexture(ndData, width, height, RGBAFormat); + texture.wrapS = RepeatWrapping; + texture.wrapT = RepeatWrapping; texture.needsUpdate = true; const rectangle = this.rectangle; @@ -329,8 +341,8 @@ export class QuickSelectGeometry { // This class is used to display connected line segments and is used by the LineMeasurementTool. export class LineMeasurementGeometry { - color: THREE.Color; - line: THREE.Line; + color: Color; + line: Line; vertexBuffer: ResizableBuffer; viewport: OrthoView; visible: boolean; @@ -342,13 +354,13 @@ export class LineMeasurementGeometry { this.color = CONTOUR_COLOR_NORMAL; this.visible = false; - const lineGeometry = new THREE.BufferGeometry(); - const positionAttribute = new THREE.BufferAttribute(new Float32Array(3), 3); - positionAttribute.setUsage(THREE.DynamicDrawUsage); + const lineGeometry = new BufferGeometry(); + const positionAttribute = new BufferAttribute(new Float32Array(3), 3); + positionAttribute.setUsage(DynamicDrawUsage); lineGeometry.setAttribute("position", positionAttribute); - this.line = new THREE.Line( + this.line = new Line( lineGeometry, - new THREE.LineBasicMaterial({ + new LineBasicMaterial({ linewidth: 2, }), ); @@ -407,8 +419,8 @@ export class LineMeasurementGeometry { const mesh = this.line; if (mesh.geometry.attributes.position.array !== this.vertexBuffer.getBuffer()) { // Need to rebuild Geometry - const positionAttribute = new THREE.BufferAttribute(this.vertexBuffer.getBuffer(), 3); - positionAttribute.setUsage(THREE.DynamicDrawUsage); + const positionAttribute = new BufferAttribute(this.vertexBuffer.getBuffer(), 3); + positionAttribute.setUsage(DynamicDrawUsage); mesh.geometry.dispose(); mesh.geometry.setAttribute("position", positionAttribute); } @@ -420,7 +432,7 @@ export class LineMeasurementGeometry { } getDistance(voxelSizeFactor: Vector3): number { - const scaleVector = new THREE.Vector3(...voxelSizeFactor); + const scaleVector = new ThreeVector3(...voxelSizeFactor); const points = this.vertexBuffer.getBuffer(); const pointCount = this.vertexBuffer.getLength(); if (pointCount < 2) { @@ -428,8 +440,8 @@ export class LineMeasurementGeometry { } let accDistanceInUnit = 0; for (let i = 0; i < pointCount - 1; i++) { - const start = new THREE.Vector3(...points.subarray(i * 3, (i + 1) * 3)).multiply(scaleVector); - const end = new THREE.Vector3(...points.subarray((i + 1) * 3, (i + 2) * 3)).multiply( + const start = new ThreeVector3(...points.subarray(i * 3, (i + 1) * 3)).multiply(scaleVector); + const end = new ThreeVector3(...points.subarray((i + 1) * 3, (i + 2) * 3)).multiply( scaleVector, ); accDistanceInUnit += start.distanceTo(end); diff --git a/frontend/javascripts/viewer/geometries/materials/edge_shader.ts b/frontend/javascripts/viewer/geometries/materials/edge_shader.ts index 704b537b38e..a80a9cbeeea 100644 --- a/frontend/javascripts/viewer/geometries/materials/edge_shader.ts +++ b/frontend/javascripts/viewer/geometries/materials/edge_shader.ts @@ -1,7 +1,7 @@ import { M4x4 } from "libs/mjs"; import type TPS3D from "libs/thin_plate_spline"; import _ from "lodash"; -import * as THREE from "three"; +import { type DataTexture, GLSL3, RawShaderMaterial } from "three"; import { COLOR_TEXTURE_WIDTH_FIXED } from "viewer/geometries/materials/node_shader"; import type { Uniforms } from "viewer/geometries/materials/plane_material_factory"; import { getTransformsForSkeletonLayer } from "viewer/model/accessors/dataset_layer_transformation_accessor"; @@ -14,25 +14,25 @@ import { import { Store } from "viewer/singletons"; class EdgeShader { - material: THREE.RawShaderMaterial; + material: RawShaderMaterial; uniforms: Uniforms = {}; scaledTps: TPS3D | null = null; oldVertexShaderCode: string | null = null; storePropertyUnsubscribers: Array<() => void> = []; - constructor(treeColorTexture: THREE.DataTexture) { + constructor(treeColorTexture: DataTexture) { this.setupUniforms(treeColorTexture); - this.material = new THREE.RawShaderMaterial({ + this.material = new RawShaderMaterial({ uniforms: this.uniforms, vertexShader: this.getVertexShader(), fragmentShader: this.getFragmentShader(), transparent: true, - glslVersion: THREE.GLSL3, + glslVersion: GLSL3, }); shaderEditor.addMaterial("edge", this.material); } - setupUniforms(treeColorTexture: THREE.DataTexture): void { + setupUniforms(treeColorTexture: DataTexture): void { this.uniforms = { activeTreeId: { value: Number.NaN, @@ -96,7 +96,7 @@ class EdgeShader { ]; } - getMaterial(): THREE.RawShaderMaterial { + getMaterial(): RawShaderMaterial { return this.material; } diff --git a/frontend/javascripts/viewer/geometries/materials/node_shader.ts b/frontend/javascripts/viewer/geometries/materials/node_shader.ts index cfe3d6b5ccd..d0611102a04 100644 --- a/frontend/javascripts/viewer/geometries/materials/node_shader.ts +++ b/frontend/javascripts/viewer/geometries/materials/node_shader.ts @@ -1,7 +1,7 @@ import { M4x4 } from "libs/mjs"; import type TPS3D from "libs/thin_plate_spline"; import _ from "lodash"; -import * as THREE from "three"; +import { type DataTexture, GLSL3, RawShaderMaterial } from "three"; import { ViewModeValues, ViewModeValuesIndices } from "viewer/constants"; import type { Uniforms } from "viewer/geometries/materials/plane_material_factory"; import { getTransformsForSkeletonLayer } from "viewer/model/accessors/dataset_layer_transformation_accessor"; @@ -25,25 +25,25 @@ export const COLOR_TEXTURE_WIDTH = 1024.0; export const COLOR_TEXTURE_WIDTH_FIXED = COLOR_TEXTURE_WIDTH.toFixed(1); class NodeShader { - material: THREE.RawShaderMaterial; + material: RawShaderMaterial; uniforms: Uniforms = {}; scaledTps: TPS3D | null = null; oldVertexShaderCode: string | null = null; storePropertyUnsubscribers: Array<() => void> = []; - constructor(treeColorTexture: THREE.DataTexture) { + constructor(treeColorTexture: DataTexture) { this.setupUniforms(treeColorTexture); - this.material = new THREE.RawShaderMaterial({ + this.material = new RawShaderMaterial({ uniforms: this.uniforms, vertexShader: this.getVertexShader(), fragmentShader: this.getFragmentShader(), transparent: true, - glslVersion: THREE.GLSL3, + glslVersion: GLSL3, }); shaderEditor.addMaterial("node", this.material); } - setupUniforms(treeColorTexture: THREE.DataTexture): void { + setupUniforms(treeColorTexture: DataTexture): void { const state = Store.getState(); const { additionalCoordinates } = state.flycam; this.uniforms = { @@ -156,7 +156,7 @@ class NodeShader { ); } - getMaterial(): THREE.RawShaderMaterial { + getMaterial(): RawShaderMaterial { return this.material; } diff --git a/frontend/javascripts/viewer/geometries/materials/plane_material_factory.ts b/frontend/javascripts/viewer/geometries/materials/plane_material_factory.ts index 83d06b0dea5..502f36abdff 100644 --- a/frontend/javascripts/viewer/geometries/materials/plane_material_factory.ts +++ b/frontend/javascripts/viewer/geometries/materials/plane_material_factory.ts @@ -4,7 +4,7 @@ import { V3 } from "libs/mjs"; import type TPS3D from "libs/thin_plate_spline"; import * as Utils from "libs/utils"; import _ from "lodash"; -import * as THREE from "three"; +import { DoubleSide, Euler, Matrix4, ShaderMaterial, Vector3 as ThreeVector3 } from "three"; import type { ValueOf } from "types/globals"; import { WkDevFlags } from "viewer/api/wk_dev"; import { BLEND_MODES, Identity4x4, type OrthoView, type Vector3 } from "viewer/constants"; @@ -71,7 +71,7 @@ export type Uniforms = Record< } >; -const DEFAULT_COLOR = new THREE.Vector3(255, 255, 255); +const DEFAULT_COLOR = new ThreeVector3(255, 255, 255); function sanitizeName(name: string | null | undefined): string { if (WkDevFlags.bucketDebugging.disableLayerNameSanitization) { @@ -116,7 +116,7 @@ function getTextureLayerInfos(): Params["textureLayerInfos"] { class PlaneMaterialFactory { planeID: OrthoView; isOrthogonal: boolean; - material: THREE.ShaderMaterial | undefined | null; + material: ShaderMaterial | undefined | null; uniforms: Uniforms = {}; attributes: Record = {}; shaderId: number; @@ -166,7 +166,7 @@ class PlaneMaterialFactory { // configured by the clippingDistance setting. It is necessary to calculate the position of the data that should be rendered by subtracting // the offset in the shader. Note, that the position offset should already be in world scale. positionOffset: { - value: new THREE.Vector3(0, 0, 0), + value: new ThreeVector3(0, 0, 0), }, zoomValue: { value: 1, @@ -187,10 +187,10 @@ class PlaneMaterialFactory { value: false, }, globalMousePosition: { - value: new THREE.Vector3(0, 0, 0), + value: new ThreeVector3(0, 0, 0), }, activeSegmentPosition: { - value: new THREE.Vector3(-1, -1, -1), + value: new ThreeVector3(-1, -1, -1), }, brushSizeInPixel: { value: 0, @@ -217,10 +217,10 @@ class PlaneMaterialFactory { value: OrthoViewValues.indexOf(this.planeID), }, bboxMin: { - value: new THREE.Vector3(0, 0, 0), + value: new ThreeVector3(0, 0, 0), }, bboxMax: { - value: new THREE.Vector3(0, 0, 0), + value: new ThreeVector3(0, 0, 0), }, renderBucketIndices: { value: false, @@ -253,7 +253,7 @@ class PlaneMaterialFactory { }, blendMode: { value: 1.0 }, isFlycamRotated: { value: false }, - inverseFlycamRotationMatrix: { value: new THREE.Matrix4() }, + inverseFlycamRotationMatrix: { value: new Matrix4() }, }; const activeMagIndices = getActiveMagIndicesForLayers(Store.getState()); @@ -442,7 +442,7 @@ class PlaneMaterialFactory { for (const [name, value] of Object.entries(additionalUniforms)) { this.uniforms[name] = value; } - this.material = new THREE.ShaderMaterial( + this.material = new ShaderMaterial( _.extend(options, { uniforms: this.uniforms, vertexShader: this.getVertexShader(), @@ -466,7 +466,7 @@ class PlaneMaterialFactory { this.uniforms.useBilinearFiltering.value = isEnabled; }; - this.material.side = THREE.DoubleSide; + this.material.side = DoubleSide; } startListeningForUniforms() { @@ -602,10 +602,10 @@ class PlaneMaterialFactory { const state = Store.getState(); const position = getPosition(state.flycam); - const toOrigin = new THREE.Matrix4().makeTranslation(...Utils.map3((p) => -p, position)); - const backToFlycamCenter = new THREE.Matrix4().makeTranslation(...position); - const invertRotation = new THREE.Matrix4() - .makeRotationFromEuler(new THREE.Euler(rotation[0], rotation[1], rotation[2], "ZYX")) + const toOrigin = new Matrix4().makeTranslation(...Utils.map3((p) => -p, position)); + const backToFlycamCenter = new Matrix4().makeTranslation(...position); + const invertRotation = new Matrix4() + .makeRotationFromEuler(new Euler(rotation[0], rotation[1], rotation[2], "ZYX")) .invert(); const inverseFlycamRotationMatrix = toOrigin .multiply(invertRotation) @@ -965,7 +965,7 @@ class PlaneMaterialFactory { if (settings.color != null) { const color = this.convertColor(settings.color); - this.uniforms[`${name}_color`].value = new THREE.Vector3(...color); + this.uniforms[`${name}_color`].value = new ThreeVector3(...color); } } @@ -973,7 +973,7 @@ class PlaneMaterialFactory { this.uniforms[`${name}_gammaCorrectionValue`].value = gammaCorrectionValue; } - getMaterial(): THREE.ShaderMaterial { + getMaterial(): ShaderMaterial { if (this.material == null) { throw new Error("Tried to access material, but it is null."); } diff --git a/frontend/javascripts/viewer/geometries/materials/plane_material_factory_helpers.ts b/frontend/javascripts/viewer/geometries/materials/plane_material_factory_helpers.ts index 9088b649fca..5b2f2c35da7 100644 --- a/frontend/javascripts/viewer/geometries/materials/plane_material_factory_helpers.ts +++ b/frontend/javascripts/viewer/geometries/materials/plane_material_factory_helpers.ts @@ -1,25 +1,33 @@ import UpdatableTexture from "libs/UpdatableTexture"; -import * as THREE from "three"; +import { + ClampToEdgeWrapping, + NearestFilter, + type PixelFormat, + type PixelFormatGPU, + type TextureDataType, + UVMapping, + type WebGLRenderer, +} from "three"; // This function has to be in its own file as non-resolvable cycles are created otherwise export function createUpdatableTexture( width: number, height: number, - type: THREE.TextureDataType, - renderer: THREE.WebGLRenderer, - format: THREE.PixelFormat, - internalFormat?: THREE.PixelFormatGPU, + type: TextureDataType, + renderer: WebGLRenderer, + format: PixelFormat, + internalFormat?: PixelFormatGPU, ): UpdatableTexture { const newTexture = new UpdatableTexture( width, height, format, type, - THREE.UVMapping, - THREE.ClampToEdgeWrapping, - THREE.ClampToEdgeWrapping, - THREE.NearestFilter, - THREE.NearestFilter, + UVMapping, + ClampToEdgeWrapping, + ClampToEdgeWrapping, + NearestFilter, + NearestFilter, ); newTexture.setRenderer(renderer); diff --git a/frontend/javascripts/viewer/geometries/plane.ts b/frontend/javascripts/viewer/geometries/plane.ts index 1fc8b89f090..70ac95da68f 100644 --- a/frontend/javascripts/viewer/geometries/plane.ts +++ b/frontend/javascripts/viewer/geometries/plane.ts @@ -1,6 +1,17 @@ import { V3 } from "libs/mjs"; import _ from "lodash"; -import * as THREE from "three"; +import { + BufferAttribute, + BufferGeometry, + Euler, + Line, + LineBasicMaterial, + LineSegments, + Matrix4, + Mesh, + PlaneGeometry, + Vector3 as ThreeVector3, +} from "three"; import type { OrthoView, Vector3 } from "viewer/constants"; import constants, { OrthoViewColors, @@ -32,24 +43,24 @@ 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: Mesh; planeID: OrthoView; materialFactory!: PlaneMaterialFactory; displayCrosshair: boolean; // @ts-expect-error ts-migrate(2564) FIXME: Property 'crosshair' has no initializer and is not... Remove this comment to see the full error message - crosshair: Array; + crosshair: Array; // @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; + TDViewBorders: Line; lastScaleFactors: [number, number]; // baseRotation is the base rotation the plane has in an unrotated scene. It will be applied additional to the flycams rotation. // Different baseRotations for each of the planes ensures that the planes stay orthogonal to each other. - baseRotation: THREE.Euler; + baseRotation: Euler; storePropertyUnsubscribers: Array<() => void> = []; datasetScaleFactor: Vector3 = [1, 1, 1]; // Properties are only created here to avoid new creating objects for each setRotation call. - baseRotationMatrix = new THREE.Matrix4(); - flycamRotationMatrix = new THREE.Matrix4(); + baseRotationMatrix = new Matrix4(); + flycamRotationMatrix = new Matrix4(); constructor(planeID: OrthoView) { this.planeID = planeID; @@ -59,7 +70,7 @@ class Plane { // dimension with the highest mag. In all other dimensions, the plane // is smaller in voxels, so that it is squared in nm. // --> scaleInfo.baseVoxel - this.baseRotation = new THREE.Euler(0, 0, 0); + this.baseRotation = new Euler(0, 0, 0); this.bindToEvents(); this.createMeshes(); } @@ -67,7 +78,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 PlaneGeometry(pWidth, pWidth, PLANE_SUBDIVISION, PLANE_SUBDIVISION); this.materialFactory = new PlaneMaterialFactory( this.planeID, @@ -75,12 +86,12 @@ class Plane { OrthoViewValues.indexOf(this.planeID), ); const textureMaterial = this.materialFactory.setup().getMaterial(); - this.plane = new THREE.Mesh(planeGeo, textureMaterial); + this.plane = new Mesh(planeGeo, textureMaterial); // Create crosshairs this.crosshair = new Array(2); for (let i = 0; i <= 1; i++) { - const crosshairGeometry = new THREE.BufferGeometry(); + const crosshairGeometry = new BufferGeometry(); // biome-ignore format: don't format array const crosshairVertices = new Float32Array([ (-pWidth / 2) * i, (-pWidth / 2) * (1 - i), 0, @@ -88,9 +99,9 @@ class Plane { 25 * i, 25 * (1 - i), 0, (pWidth / 2) * i, (pWidth / 2) * (1 - i), 0, ]); - crosshairGeometry.setAttribute("position", new THREE.BufferAttribute(crosshairVertices, 3)); + crosshairGeometry.setAttribute("position", new BufferAttribute(crosshairVertices, 3)); - this.crosshair[i] = new THREE.LineSegments( + this.crosshair[i] = new LineSegments( crosshairGeometry, this.getLineBasicMaterial(OrthoViewCrosshairColors[this.planeID][i], 1), ); @@ -102,15 +113,15 @@ class Plane { // Create borders const vertices = [ - new THREE.Vector3(-pWidth / 2, -pWidth / 2, 0), - new THREE.Vector3(-pWidth / 2, pWidth / 2, 0), - new THREE.Vector3(pWidth / 2, pWidth / 2, 0), - new THREE.Vector3(pWidth / 2, -pWidth / 2, 0), - new THREE.Vector3(-pWidth / 2, -pWidth / 2, 0), + new ThreeVector3(-pWidth / 2, -pWidth / 2, 0), + new ThreeVector3(-pWidth / 2, pWidth / 2, 0), + new ThreeVector3(pWidth / 2, pWidth / 2, 0), + new ThreeVector3(pWidth / 2, -pWidth / 2, 0), + new ThreeVector3(-pWidth / 2, -pWidth / 2, 0), ]; - const tdBorderGeometry = new THREE.BufferGeometry().setFromPoints(vertices); + const tdBorderGeometry = new BufferGeometry().setFromPoints(vertices); - this.TDViewBorders = new THREE.Line( + this.TDViewBorders = new Line( tdBorderGeometry, this.getLineBasicMaterial(OrthoViewColors[this.planeID], 1), ); @@ -122,7 +133,7 @@ class Plane { getLineBasicMaterial = _.memoize( (color: number, linewidth: number) => - new THREE.LineBasicMaterial({ + new LineBasicMaterial({ color, linewidth, }), @@ -155,12 +166,12 @@ class Plane { this.getMeshes().map((mesh) => mesh.scale.set(...scaleVector)); } - setBaseRotation = (rotVec: THREE.Euler): void => { + setBaseRotation = (rotVec: Euler): void => { this.baseRotation.copy(rotVec); this.baseRotationMatrix.makeRotationFromEuler(this.baseRotation); }; - updateToFlycamRotation = (flycamRotationVec: THREE.Euler): void => { + updateToFlycamRotation = (flycamRotationVec: Euler): void => { // rotVec must be in "ZYX" order as this is how the flycam operates (see flycam_reducer setRotationReducer) this.flycamRotationMatrix.makeRotationFromEuler(flycamRotationVec); const combinedMatrix = this.flycamRotationMatrix.multiply(this.baseRotationMatrix); diff --git a/frontend/javascripts/viewer/geometries/skeleton.ts b/frontend/javascripts/viewer/geometries/skeleton.ts index d98497be1c1..582f6f9c5fc 100644 --- a/frontend/javascripts/viewer/geometries/skeleton.ts +++ b/frontend/javascripts/viewer/geometries/skeleton.ts @@ -1,6 +1,17 @@ import * as Utils from "libs/utils"; import _ from "lodash"; -import * as THREE from "three"; +import { + BufferAttribute, + BufferGeometry, + DataTexture, + FloatType, + Group, + LineSegments, + Object3D, + Points, + RGBAFormat, + type RawShaderMaterial, +} from "three"; import type { AdditionalCoordinate } from "types/api_types"; import type { Vector3, Vector4 } from "viewer/constants"; import EdgeShader from "viewer/geometries/materials/edge_shader"; @@ -18,8 +29,8 @@ import Store from "viewer/throttled_store"; const MAX_CAPACITY = 1000; -type BufferGeometryWithBufferAttributes = THREE.BufferGeometry & { - attributes: Record; +type BufferGeometryWithBufferAttributes = BufferGeometry & { + attributes: Record; }; type BufferHelper = typeof NodeBufferHelperType | typeof EdgeBufferHelperType; @@ -27,66 +38,63 @@ type Buffer = { capacity: number; nextIndex: number; geometry: BufferGeometryWithBufferAttributes; - mesh: THREE.Object3D; + mesh: Points | LineSegments; }; type BufferPosition = { buffer: Buffer; index: number; }; type BufferCollection = { - buffers: Array; + buffers: Buffer[]; idToBufferPosition: Map; - freeList: Array; + freeList: BufferPosition[]; helper: BufferHelper; - material: THREE.RawShaderMaterial; + material: RawShaderMaterial; }; -type BufferOperation = (position: BufferPosition) => Array; +type BufferOperation = (position: BufferPosition) => BufferAttribute[]; const NodeBufferHelperType = { - setAttributes(geometry: THREE.BufferGeometry, capacity: number): void { - geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(capacity * 3), 3)); + setAttributes(geometry: BufferGeometry, capacity: number): void { + geometry.setAttribute("position", new BufferAttribute(new Float32Array(capacity * 3), 3)); const additionalCoordLength = (Store.getState().flycam.additionalCoordinates ?? []).length; for (const idx of _.range(0, additionalCoordLength)) { geometry.setAttribute( `additionalCoord_${idx}`, - new THREE.BufferAttribute(new Float32Array(capacity), 1), + new BufferAttribute(new Float32Array(capacity), 1), ); } - geometry.setAttribute("radius", new THREE.BufferAttribute(new Float32Array(capacity), 1)); - geometry.setAttribute("type", new THREE.BufferAttribute(new Float32Array(capacity), 1)); - geometry.setAttribute("isCommented", new THREE.BufferAttribute(new Float32Array(capacity), 1)); - geometry.setAttribute("nodeId", new THREE.BufferAttribute(new Float32Array(capacity), 1)); - geometry.setAttribute("treeId", new THREE.BufferAttribute(new Float32Array(capacity), 1)); + geometry.setAttribute("radius", new BufferAttribute(new Float32Array(capacity), 1)); + geometry.setAttribute("type", new BufferAttribute(new Float32Array(capacity), 1)); + geometry.setAttribute("isCommented", new BufferAttribute(new Float32Array(capacity), 1)); + geometry.setAttribute("nodeId", new BufferAttribute(new Float32Array(capacity), 1)); + geometry.setAttribute("treeId", new BufferAttribute(new Float32Array(capacity), 1)); }, - buildMesh(geometry: THREE.BufferGeometry, material: THREE.RawShaderMaterial): THREE.Object3D { - return new THREE.Points(geometry, material); + buildMesh(geometry: BufferGeometry, material: RawShaderMaterial): Points { + return new Points(geometry, material); }, supportsPicking: true, }; const EdgeBufferHelperType = { - setAttributes(geometry: THREE.BufferGeometry, capacity: number): void { - geometry.setAttribute( - "position", - new THREE.BufferAttribute(new Float32Array(capacity * 2 * 3), 3), - ); + setAttributes(geometry: BufferGeometry, capacity: number): void { + geometry.setAttribute("position", new BufferAttribute(new Float32Array(capacity * 2 * 3), 3)); const additionalCoordLength = (Store.getState().flycam.additionalCoordinates ?? []).length; for (const idx of _.range(0, additionalCoordLength)) { geometry.setAttribute( `additionalCoord_${idx}`, - new THREE.BufferAttribute(new Float32Array(capacity * 2), 1), + new BufferAttribute(new Float32Array(capacity * 2), 1), ); } - geometry.setAttribute("treeId", new THREE.BufferAttribute(new Float32Array(capacity * 2), 1)); + geometry.setAttribute("treeId", new BufferAttribute(new Float32Array(capacity * 2), 1)); }, - buildMesh(geometry: THREE.BufferGeometry, material: THREE.RawShaderMaterial): THREE.Object3D { - return new THREE.LineSegments(geometry, material); + buildMesh(geometry: BufferGeometry, material: RawShaderMaterial): LineSegments { + return new LineSegments(geometry, material); }, supportsPicking: false, @@ -102,8 +110,8 @@ const EdgeBufferHelperType = { */ class Skeleton { - rootGroup: THREE.Group; - pickingNode: THREE.Object3D; + rootGroup: Group; + pickingNode: Object3D; // @ts-expect-error ts-migrate(2564) FIXME: Property 'prevTracing' has no initializer and is n... Remove this comment to see the full error message prevTracing: SkeletonTracing; // @ts-expect-error ts-migrate(2564) FIXME: Property 'nodes' has no initializer and is not def... Remove this comment to see the full error message @@ -111,7 +119,7 @@ class Skeleton { // @ts-expect-error ts-migrate(2564) FIXME: Property 'edges' has no initializer and is not def... Remove this comment to see the full error message edges: BufferCollection; // @ts-expect-error ts-migrate(2564) FIXME: Property 'treeColorTexture' has no initializer and... Remove this comment to see the full error message - treeColorTexture: THREE.DataTexture; + treeColorTexture: DataTexture; supportsPicking: boolean; stopStoreListening: () => void; @@ -123,8 +131,8 @@ class Skeleton { supportsPicking: boolean, ) { this.supportsPicking = supportsPicking; - this.rootGroup = new THREE.Group(); - this.pickingNode = new THREE.Object3D(); + this.rootGroup = new Group(); + this.pickingNode = new Object3D(); const skeletonTracing = skeletonTracingSelectorFn(Store.getState()); if (skeletonTracing != null) { this.reset(skeletonTracing); @@ -165,12 +173,12 @@ class Skeleton { const nodeCount = sum(trees.values().map((tree) => tree.nodes.size())); const edgeCount = sum(trees.values().map((tree) => tree.edges.size())); - this.treeColorTexture = new THREE.DataTexture( + this.treeColorTexture = new DataTexture( new Float32Array(COLOR_TEXTURE_WIDTH * COLOR_TEXTURE_WIDTH * 4), COLOR_TEXTURE_WIDTH, COLOR_TEXTURE_WIDTH, - THREE.RGBAFormat, - THREE.FloatType, + RGBAFormat, + FloatType, ); this.nodeShader = new NodeShader(this.treeColorTexture); this.edgeShader = new EdgeShader(this.treeColorTexture); @@ -219,7 +227,7 @@ class Skeleton { initializeBufferCollection( initialCapacity: number, - material: THREE.RawShaderMaterial, + material: RawShaderMaterial, helper: BufferHelper, ): BufferCollection { const initialBuffer = this.initializeBuffer( @@ -236,12 +244,8 @@ class Skeleton { }; } - initializeBuffer( - capacity: number, - material: THREE.RawShaderMaterial, - helper: BufferHelper, - ): Buffer { - const geometry = new THREE.BufferGeometry() as BufferGeometryWithBufferAttributes; + initializeBuffer(capacity: number, material: RawShaderMaterial, helper: BufferHelper): Buffer { + const geometry = new BufferGeometry() as BufferGeometryWithBufferAttributes; helper.setAttributes(geometry, capacity); const mesh = helper.buildMesh(geometry, material); // Frustum culling is disabled because nodes that are transformed @@ -491,15 +495,15 @@ class Skeleton { this.prevTracing = skeletonTracing; } - getAllNodes(): Array { + getAllNodes(): Object3D[] { return this.nodes.buffers.map((buffer) => buffer.mesh); } - getRootGroup(): THREE.Object3D { + getRootGroup(): Object3D { return this.rootGroup; } - startPicking(isTouch: boolean): THREE.Object3D { + startPicking(isTouch: boolean): Object3D { this.pickingNode.matrixAutoUpdate = false; this.pickingNode.matrix.copy(this.rootGroup.matrixWorld); this.nodes.material.uniforms.isTouch.value = isTouch ? 1 : 0; @@ -518,7 +522,7 @@ class Skeleton { * Combine node, edge and tree ids to a single unique id. * @param numbers - Array of node/edge id and treeId */ - combineIds(...numbers: Array): string { + combineIds(...numbers: number[]): string { return numbers.join(","); } @@ -553,31 +557,27 @@ class Skeleton { */ createNode(treeId: number, node: Node | UpdateActionNode | CreateActionNode) { const id = this.combineIds(node.id, treeId); - this.create( - id, - this.nodes, - ({ buffer, index }: BufferPosition): Array => { - const attributes = buffer.geometry.attributes; - const untransformedPosition = - "untransformedPosition" in node ? node.untransformedPosition : node.position; - attributes.position.set(untransformedPosition, index * 3); - - if (node.additionalCoordinates) { - for (const idx of _.range(0, node.additionalCoordinates.length)) { - const attributeAdditionalCoordinates = - buffer.geometry.attributes[`additionalCoord_${idx}`]; - attributeAdditionalCoordinates.set([node.additionalCoordinates[idx].value], index); - } + this.create(id, this.nodes, ({ buffer, index }: BufferPosition): BufferAttribute[] => { + const attributes = buffer.geometry.attributes; + const untransformedPosition = + "untransformedPosition" in node ? node.untransformedPosition : node.position; + attributes.position.set(untransformedPosition, index * 3); + + if (node.additionalCoordinates) { + for (const idx of _.range(0, node.additionalCoordinates.length)) { + const attributeAdditionalCoordinates = + buffer.geometry.attributes[`additionalCoord_${idx}`]; + attributeAdditionalCoordinates.set([node.additionalCoordinates[idx].value], index); } - attributes.radius.array[index] = node.radius; - attributes.type.array[index] = NodeTypes.NORMAL; - // @ts-expect-error ts-migrate(2542) FIXME: Index signature in type 'any[] | ArrayLike... Remove this comment to see the full error message - attributes.isCommented.array[index] = false; - attributes.nodeId.array[index] = node.id; - attributes.treeId.array[index] = treeId; - return _.values(attributes); - }, - ); + } + attributes.radius.array[index] = node.radius; + attributes.type.array[index] = NodeTypes.NORMAL; + // @ts-expect-error ts-migrate(2542) FIXME: Index signature in type 'any[] | ArrayLike... Remove this comment to see the full error message + attributes.isCommented.array[index] = false; + attributes.nodeId.array[index] = node.id; + attributes.treeId.array[index] = treeId; + return _.values(attributes); + }); } /** diff --git a/frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts b/frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts index 727e660455d..ce56bed6b04 100644 --- a/frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts +++ b/frontend/javascripts/viewer/model/accessors/dataset_layer_transformation_accessor.ts @@ -3,7 +3,7 @@ import MultiKeyMap from "libs/multi_key_map"; import { mod } from "libs/utils"; import _ from "lodash"; import memoizeOne from "memoize-one"; -import * as THREE from "three"; +import { Euler, Matrix4, Quaternion, Vector3 as ThreeVector3 } from "three"; import type { APIDataLayer, APIDataset, @@ -106,7 +106,7 @@ export function getRotationSettingsFromTransformationIn90DegreeSteps( export function fromCenterToOrigin(bbox: BoundingBox): AffineTransformation { const center = bbox.getCenter(); - const translationMatrix = new THREE.Matrix4() + const translationMatrix = new Matrix4() .makeTranslation(-center[0], -center[1], -center[2]) .transpose(); // Column-major to row-major return { type: "affine", matrix: flatToNestedMatrix(translationMatrix.toArray()) }; @@ -114,7 +114,7 @@ export function fromCenterToOrigin(bbox: BoundingBox): AffineTransformation { export function fromOriginToCenter(bbox: BoundingBox): AffineTransformation { const center = bbox.getCenter(); - const translationMatrix = new THREE.Matrix4() + const translationMatrix = new Matrix4() .makeTranslation(center[0], center[1], center[2]) .transpose(); // Column-major to row-major return { type: "affine", matrix: flatToNestedMatrix(translationMatrix.toArray()) }; @@ -124,15 +124,15 @@ export function getRotationMatrixAroundAxis( axis: "x" | "y" | "z", rotationAndMirroringSettings: RotationAndMirroringSettings, ): AffineTransformation { - const euler = new THREE.Euler(); + const euler = new Euler(); const rotationInRadians = rotationAndMirroringSettings.rotationInDegrees * (Math.PI / 180); euler[axis] = rotationInRadians; - let rotationMatrix = new THREE.Matrix4().makeRotationFromEuler(euler); + let rotationMatrix = new Matrix4().makeRotationFromEuler(euler); if (rotationAndMirroringSettings.isMirrored) { - const scaleVector = new THREE.Vector3(1, 1, 1); + const scaleVector = new ThreeVector3(1, 1, 1); scaleVector[axis] = -1; rotationMatrix = rotationMatrix.multiply( - new THREE.Matrix4().makeScale(scaleVector.x, scaleVector.y, scaleVector.z), + new Matrix4().makeScale(scaleVector.x, scaleVector.y, scaleVector.z), ); } rotationMatrix = rotationMatrix.transpose(); // Column-major to row-major @@ -363,18 +363,18 @@ export const invertAndTranspose = _.memoize((mat: Matrix4x4) => { return M4x4.transpose(M4x4.inverse(mat)); }); -const translation = new THREE.Vector3(); -const scale = new THREE.Vector3(); -const quaternion = new THREE.Quaternion(); -const IDENTITY_QUATERNION = new THREE.Quaternion(); +const translation = new ThreeVector3(); +const scale = new ThreeVector3(); +const quaternion = new Quaternion(); +const IDENTITY_QUATERNION = new Quaternion(); -const NON_SCALED_VECTOR = new THREE.Vector3(1, 1, 1); +const NON_SCALED_VECTOR = new ThreeVector3(1, 1, 1); function isTranslationOnly(transformation?: AffineTransformation) { if (!transformation) { return false; } - const threeMatrix = new THREE.Matrix4() + const threeMatrix = new Matrix4() .fromArray(nestedToFlatMatrix(transformation.matrix)) .transpose(); threeMatrix.decompose(translation, quaternion, scale); @@ -385,7 +385,7 @@ function isOnlyRotatedOrMirrored(transformation?: AffineTransformation) { if (!transformation) { return false; } - const threeMatrix = new THREE.Matrix4() + const threeMatrix = new Matrix4() .fromArray(nestedToFlatMatrix(transformation.matrix)) .transpose(); threeMatrix.decompose(translation, quaternion, scale); diff --git a/frontend/javascripts/viewer/model/accessors/flycam_accessor.ts b/frontend/javascripts/viewer/model/accessors/flycam_accessor.ts index 7698d6c3baa..2f77ec7dbc5 100644 --- a/frontend/javascripts/viewer/model/accessors/flycam_accessor.ts +++ b/frontend/javascripts/viewer/model/accessors/flycam_accessor.ts @@ -3,7 +3,7 @@ import { M4x4, V3 } from "libs/mjs"; import { map3, mod } from "libs/utils"; import _ from "lodash"; import memoizeOne from "memoize-one"; -import * as THREE from "three"; +import { type Euler, MathUtils, Matrix4, Object3D } from "three"; import type { AdditionalCoordinate, VoxelSize } from "types/api_types"; import { baseDatasetViewConfiguration } from "types/schemas/dataset_view_configuration.schema"; import type { @@ -302,7 +302,7 @@ function _getFlooredPosition(flycam: Flycam): Vector3 { } // Avoiding object creation with each call. -const flycamMatrixObject = new THREE.Matrix4(); +const flycamMatrixObject = new Matrix4(); // Returns the current rotation of the flycam in radians as an euler xyz tuple. // As the order in which the angles are applied is zyx (see flycam_reducer setRotationReducer), @@ -311,7 +311,7 @@ const flycamMatrixObject = new THREE.Matrix4(); function _getRotationInRadianFromMatrix(flycamMatrix: Matrix4x4, invertZ: boolean = true): Vector3 { // Somehow z rotation is inverted but the others are not. const zInvertFactor = invertZ ? -1 : 1; - const object = new THREE.Object3D(); + const object = new Object3D(); flycamMatrixObject.fromArray(flycamMatrix).transpose(); object.applyMatrix4(flycamMatrixObject); return [ @@ -344,20 +344,15 @@ function _isRotated(flycam: Flycam): boolean { return !V3.equals(getRotationInRadian(flycam), [0, 0, 0]); } -function _getFlycamRotationWithAppendedRotation( - flycam: Flycam, - rotationToAppend: THREE.Euler, -): Vector3 { +function _getFlycamRotationWithAppendedRotation(flycam: Flycam, rotationToAppend: Euler): Vector3 { const flycamRotation = getRotationInRadian(flycam, false); // Perform same operations as the flycam reducer does. First default 180° around z. let rotFlycamMatrix = eulerAngleToReducerInternalMatrix(flycamRotation); // Apply rotation - rotFlycamMatrix = rotFlycamMatrix.multiply( - new THREE.Matrix4().makeRotationFromEuler(rotationToAppend), - ); + rotFlycamMatrix = rotFlycamMatrix.multiply(new Matrix4().makeRotationFromEuler(rotationToAppend)); const rotationInRadian = reducerInternalMatrixToEulerAngle(rotFlycamMatrix); - const rotationInDegree = map3(THREE.MathUtils.radToDeg, rotationInRadian); + const rotationInDegree = map3(MathUtils.radToDeg, rotationInRadian); return rotationInDegree; } diff --git a/frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts b/frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts index 914d52a710b..47c9fae8ba8 100644 --- a/frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts +++ b/frontend/javascripts/viewer/model/accessors/view_mode_accessor.ts @@ -1,7 +1,7 @@ import { V3 } from "libs/mjs"; import _ from "lodash"; import memoizeOne from "memoize-one"; -import * as THREE from "three"; +import { Euler, Matrix4, Vector3 as ThreeVector3 } from "three"; import type { OrthoView, OrthoViewExtents, @@ -95,11 +95,11 @@ export function getViewportScale(state: WebknossosState, viewport: Viewport): [n export type PositionWithRounding = { rounded: Vector3; floating: Vector3 }; // Avoiding object creation with each call. -const flycamRotationEuler = new THREE.Euler(); -const flycamRotationMatrix = new THREE.Matrix4(); -const flycamPositionMatrix = new THREE.Matrix4(); -const rotatedDiff = new THREE.Vector3(); -const planeRatioVector = new THREE.Vector3(); +const flycamRotationEuler = new Euler(); +const flycamRotationMatrix = new Matrix4(); +const flycamPositionMatrix = new Matrix4(); +const rotatedDiff = new ThreeVector3(); +const planeRatioVector = new ThreeVector3(); function _calculateMaybeGlobalPos( state: WebknossosState, @@ -173,17 +173,17 @@ function _calculateInViewportPos( flycamRotationInRadian: Vector3, planeRatio: Vector3, zoomStep: number, -): THREE.Vector3 { +): ThreeVector3 { // Difference in world space - const positionDiff = new THREE.Vector3(...V3.sub(globalPosition, flycamPosition)); + const positionDiff = new ThreeVector3(...V3.sub(globalPosition, flycamPosition)); // Inverse rotate the world difference vector into local plane-aligned space - const inverseRotationMatrix = new THREE.Matrix4() - .makeRotationFromEuler(new THREE.Euler(...flycamRotationInRadian, "ZYX")) + const inverseRotationMatrix = new Matrix4() + .makeRotationFromEuler(new Euler(...flycamRotationInRadian, "ZYX")) .invert(); // Unscale from voxel ratio (undo voxel scaling) - const posInScreenSpaceScaling = positionDiff.divide(new THREE.Vector3(...planeRatio)); + const posInScreenSpaceScaling = positionDiff.divide(new ThreeVector3(...planeRatio)); const rotatedIntoScreenSpace = posInScreenSpaceScaling.applyMatrix4(inverseRotationMatrix); const unzoomedPosition = rotatedIntoScreenSpace.multiplyScalar(1 / zoomStep); return unzoomedPosition; diff --git a/frontend/javascripts/viewer/model/bucket_data_handling/bucket.ts b/frontend/javascripts/viewer/model/bucket_data_handling/bucket.ts index b38978d9fba..4f931934920 100644 --- a/frontend/javascripts/viewer/model/bucket_data_handling/bucket.ts +++ b/frontend/javascripts/viewer/model/bucket_data_handling/bucket.ts @@ -3,7 +3,7 @@ import { castForArrayType, mod } from "libs/utils"; import window from "libs/window"; import _ from "lodash"; import { type Emitter, createNanoEvents } from "nanoevents"; -import * as THREE from "three"; +import { Color } from "three"; import type { BucketDataArray, ElementClass } from "types/api_types"; import type { AdditionalCoordinate } from "types/api_types"; import type { BoundingBoxMinMaxType } from "types/bounding_box"; @@ -98,8 +98,7 @@ export class DataBucket { readonly elementClass: ElementClass; readonly zoomedAddress: BucketAddress; visualizedMesh: Record | null | undefined; - // @ts-expect-error ts-migrate(2564) FIXME: Property 'visualizationColor' has no initializer a... Remove this comment to see the full error message - visualizationColor: THREE.Color; + visualizationColor: Color | null | undefined; // If dirty, the bucket's data was potentially edited and needs to be // saved to the server. dirty: boolean; @@ -738,12 +737,12 @@ export class DataBucket { } const colors = [ - new THREE.Color(0, 0, 0), - new THREE.Color(255, 0, 0), - new THREE.Color(0, 255, 0), - new THREE.Color(0, 0, 255), - new THREE.Color(255, 0, 255), - new THREE.Color(255, 255, 0), + new Color(0, 0, 0), + new Color(1, 0, 0), + new Color(0, 1, 0), + new Color(0, 0, 1), + new Color(1, 0, 1), + new Color(1, 1, 0), ]; const zoomStep = getActiveMagIndexForLayer(Store.getState(), this.cube.layerName); @@ -768,7 +767,7 @@ export class DataBucket { } setVisualizationColor(colorDescriptor: string | number) { - const color = new THREE.Color(colorDescriptor); + const color = new Color(colorDescriptor); this.visualizationColor = color; if (this.visualizedMesh != null) { diff --git a/frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts b/frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts index 343e610aaf7..370f2a0c1d5 100644 --- a/frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts +++ b/frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts @@ -11,7 +11,7 @@ import { } from "libs/utils"; import _ from "lodash"; import { type Emitter, createNanoEvents } from "nanoevents"; -import * as THREE from "three"; +import { type Mesh, Ray, Raycaster, Vector3 as ThreeVector3 } from "three"; import type { AdditionalAxis, BucketDataArray, ElementClass } from "types/api_types"; import type { AdditionalCoordinate } from "types/api_types"; import type { BoundingBoxMinMaxType } from "types/bounding_box"; @@ -569,7 +569,7 @@ class DataCube { zoomStep: number, progressCallback: ProgressCallback, use3D: boolean, - splitBoundaryMesh: THREE.Mesh | null, + splitBoundaryMesh: Mesh | null, ): Promise<{ bucketsWithLabeledVoxelsMap: LabelMasksByBucketAndW; wasBoundingBoxExceeded: boolean; @@ -1074,7 +1074,7 @@ class DataCube { export default DataCube; -function checkLineIntersection(bentMesh: THREE.Mesh, pointAVec3: Vector3, pointBVec3: Vector3) { +function checkLineIntersection(bentMesh: Mesh, pointAVec3: Vector3, pointBVec3: Vector3) { /* Returns true if an intersection is found */ const geometry = bentMesh.geometry; @@ -1084,16 +1084,16 @@ function checkLineIntersection(bentMesh: THREE.Mesh, pointAVec3: Vector3, pointB geometry.computeBoundsTree(); } const scale = Store.getState().dataset.dataSource.scale.factor; - const pointA = new THREE.Vector3(...V3.scale3(pointAVec3, scale)); - const pointB = new THREE.Vector3(...V3.scale3(pointBVec3, scale)); + const pointA = new ThreeVector3(...V3.scale3(pointAVec3, scale)); + const pointB = new ThreeVector3(...V3.scale3(pointBVec3, scale)); // Create a ray from A to B - const ray = new THREE.Ray(); + const ray = new Ray(); ray.origin.copy(pointA); ray.direction.subVectors(pointB, pointA).normalize(); // Perform raycast - const raycaster = new THREE.Raycaster(); + const raycaster = new Raycaster(); raycaster.ray = ray; raycaster.far = pointA.distanceTo(pointB); raycaster.firstHitOnly = true; diff --git a/frontend/javascripts/viewer/model/bucket_data_handling/data_rendering_logic.tsx b/frontend/javascripts/viewer/model/bucket_data_handling/data_rendering_logic.tsx index 3e81ecd9afc..b1c9b1e53b5 100644 --- a/frontend/javascripts/viewer/model/bucket_data_handling/data_rendering_logic.tsx +++ b/frontend/javascripts/viewer/model/bucket_data_handling/data_rendering_logic.tsx @@ -2,7 +2,18 @@ import ErrorHandling from "libs/error_handling"; import Toast from "libs/toast"; import { document } from "libs/window"; import _ from "lodash"; -import * as THREE from "three"; +import { + ByteType, + FloatType, + type PixelFormat, + type PixelFormatGPU, + RGBAFormat, + RGIntegerFormat, + ShortType, + type TextureDataType, + UnsignedByteType, + UnsignedShortType, +} from "three"; import type { ElementClass } from "types/api_types"; import constants from "viewer/constants"; import type { TypedArrayConstructor } from "../helpers/typed_buffer"; @@ -353,10 +364,10 @@ export const getSupportedValueRangeForElementClass = _.memoize( ); export function getDtypeConfigForElementClass(elementClass: ElementClass): { - textureType: THREE.TextureDataType; + textureType: TextureDataType; TypedArrayClass: TypedArrayConstructor; - pixelFormat: THREE.PixelFormat; - internalFormat: THREE.PixelFormatGPU | undefined; + pixelFormat: PixelFormat; + internalFormat: PixelFormatGPU | undefined; glslPrefix: "" | "u" | "i"; isSigned: boolean; packingDegree: number; @@ -367,9 +378,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { switch (elementClass) { case "int8": return { - textureType: THREE.ByteType, + textureType: ByteType, TypedArrayClass: Int8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: "RGBA8_SNORM", glslPrefix: "", isSigned: true, @@ -377,9 +388,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { }; case "uint8": return { - textureType: THREE.UnsignedByteType, + textureType: UnsignedByteType, TypedArrayClass: Uint8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: false, @@ -388,9 +399,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { case "uint24": // Since uint24 layers are multi-channel, their intensity ranges are equal to uint8 return { - textureType: THREE.UnsignedByteType, + textureType: UnsignedByteType, TypedArrayClass: Uint8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: false, @@ -399,9 +410,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { case "uint16": return { - textureType: THREE.UnsignedShortType, + textureType: UnsignedShortType, TypedArrayClass: Uint16Array, - pixelFormat: THREE.RGIntegerFormat, + pixelFormat: RGIntegerFormat, internalFormat: "RG16UI", glslPrefix: "u", isSigned: false, @@ -410,9 +421,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { case "int16": return { - textureType: THREE.ShortType, + textureType: ShortType, TypedArrayClass: Int16Array, - pixelFormat: THREE.RGIntegerFormat, + pixelFormat: RGIntegerFormat, internalFormat: "RG16I", glslPrefix: "i", isSigned: true, @@ -421,9 +432,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { case "uint32": return { - textureType: THREE.UnsignedByteType, + textureType: UnsignedByteType, TypedArrayClass: Uint8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: false, @@ -432,9 +443,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { case "int32": return { - textureType: THREE.UnsignedByteType, + textureType: UnsignedByteType, TypedArrayClass: Uint8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: true, @@ -443,9 +454,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { case "uint64": return { - textureType: THREE.UnsignedByteType, + textureType: UnsignedByteType, TypedArrayClass: Uint8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: false, @@ -454,9 +465,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { case "int64": return { - textureType: THREE.UnsignedByteType, + textureType: UnsignedByteType, TypedArrayClass: Uint8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: true, @@ -465,9 +476,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { case "float": return { - textureType: THREE.FloatType, + textureType: FloatType, TypedArrayClass: Float32Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: true, @@ -477,9 +488,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { // We do not fully support double case "double": return { - textureType: THREE.UnsignedByteType, + textureType: UnsignedByteType, TypedArrayClass: Uint8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: true, @@ -488,9 +499,9 @@ export function getDtypeConfigForElementClass(elementClass: ElementClass): { default: return { - textureType: THREE.UnsignedByteType, + textureType: UnsignedByteType, TypedArrayClass: Uint8Array, - pixelFormat: THREE.RGBAFormat, + pixelFormat: RGBAFormat, internalFormat: undefined, glslPrefix: "", isSigned: false, diff --git a/frontend/javascripts/viewer/model/bucket_data_handling/layer_rendering_manager.ts b/frontend/javascripts/viewer/model/bucket_data_handling/layer_rendering_manager.ts index 50b401c75f0..191f49e423d 100644 --- a/frontend/javascripts/viewer/model/bucket_data_handling/layer_rendering_manager.ts +++ b/frontend/javascripts/viewer/model/bucket_data_handling/layer_rendering_manager.ts @@ -8,7 +8,7 @@ import { M4x4, type Matrix4x4, V3 } from "libs/mjs"; import Toast from "libs/toast"; import _ from "lodash"; import memoizeOne from "memoize-one"; -import type * as THREE from "three"; +import type { DataTexture } from "three"; import type { AdditionalCoordinate } from "types/api_types"; import type { BucketAddress, Vector3, Vector4, ViewMode } from "viewer/constants"; import { @@ -173,7 +173,7 @@ export default class LayerRenderingManager { } } - getDataTextures(): Array { + getDataTextures(): Array { if (!this.textureBucketManager) { // Initialize lazily since SceneController.renderer is not available earlier this.setupDataTextures(); diff --git a/frontend/javascripts/viewer/model/bucket_data_handling/texture_bucket_manager.ts b/frontend/javascripts/viewer/model/bucket_data_handling/texture_bucket_manager.ts index f981aa2e221..12665f88d33 100644 --- a/frontend/javascripts/viewer/model/bucket_data_handling/texture_bucket_manager.ts +++ b/frontend/javascripts/viewer/model/bucket_data_handling/texture_bucket_manager.ts @@ -4,7 +4,7 @@ import type { CuckooTableVec5 } from "libs/cuckoo/cuckoo_table_vec5"; import { waitForCondition } from "libs/utils"; import window from "libs/window"; import _ from "lodash"; -import type * as THREE from "three"; +import type { DataTexture } from "three"; import type { ElementClass } from "types/api_types"; import { WkDevFlags } from "viewer/api/wk_dev"; import constants, { type TypedArray } from "viewer/constants"; @@ -259,7 +259,7 @@ export default class TextureBucketManager { }); } - getTextures(): Array { + getTextures(): Array { return [this.lookUpCuckooTable._texture].concat(this.dataTextures); } diff --git a/frontend/javascripts/viewer/model/helpers/rotation_helpers.ts b/frontend/javascripts/viewer/model/helpers/rotation_helpers.ts index 306afa035bb..b9ba2048c8e 100644 --- a/frontend/javascripts/viewer/model/helpers/rotation_helpers.ts +++ b/frontend/javascripts/viewer/model/helpers/rotation_helpers.ts @@ -1,12 +1,12 @@ import { V3 } from "libs/mjs"; import { mod } from "libs/utils"; -import * as THREE from "three"; +import { Euler, Matrix4 } from "three"; import type { Vector3 } from "viewer/constants"; // Pre definitions to avoid redundant object creation. -const matrix = new THREE.Matrix4(); -const euler = new THREE.Euler(); -const invertedEulerMatrix = new THREE.Matrix4(); +const matrix = new Matrix4(); +const euler = new Euler(); +const invertedEulerMatrix = new Matrix4(); // This function performs the same operations as done in the flycam reducer for the setRotation action. // When rotation calculation are needed in the flycam matrix space, this function can be used to @@ -15,7 +15,7 @@ const invertedEulerMatrix = new THREE.Matrix4(); // should be used to transform the result back. // For some more info look at // https://www.notion.so/scalableminds/3D-Rotations-3D-Scene-210b51644c6380c2a4a6f5f3c069738a?source=copy_link#22bb51644c6380cf8302fb8f6749ae1d. -export function eulerAngleToReducerInternalMatrix(angleInRadian: Vector3): THREE.Matrix4 { +export function eulerAngleToReducerInternalMatrix(angleInRadian: Vector3): Matrix4 { // Perform same operations as the flycam reducer does. First default 180° around z. let matrixLikeInReducer = matrix.makeRotationZ(Math.PI); // Invert angle and interpret as ZYX order @@ -28,12 +28,12 @@ export function eulerAngleToReducerInternalMatrix(angleInRadian: Vector3): THREE } // Pre definitions to avoid redundant object creation. -const rotationFromMatrix = new THREE.Euler(); +const rotationFromMatrix = new Euler(); // The companion function of eulerAngleToReducerInternalMatrix converting a rotation back from the flycam reducer space. // The output is in radian and should be interpreted as if in ZYX order. // Note: The matrix must be a rotation only matrix. -export function reducerInternalMatrixToEulerAngle(matrixInReducerFormat: THREE.Matrix4): Vector3 { +export function reducerInternalMatrixToEulerAngle(matrixInReducerFormat: Matrix4): Vector3 { const localRotationFromMatrix = rotationFromMatrix.setFromRotationMatrix( matrixInReducerFormat.clone().transpose(), "XYZ", diff --git a/frontend/javascripts/viewer/model/reducers/flycam_reducer.ts b/frontend/javascripts/viewer/model/reducers/flycam_reducer.ts index 65833197492..55e090a7563 100644 --- a/frontend/javascripts/viewer/model/reducers/flycam_reducer.ts +++ b/frontend/javascripts/viewer/model/reducers/flycam_reducer.ts @@ -3,7 +3,7 @@ import type { Matrix4x4 } from "libs/mjs"; import { M4x4, V3 } from "libs/mjs"; import * as Utils from "libs/utils"; import _ from "lodash"; -import * as THREE from "three"; +import { Euler, Matrix4, Vector3 as ThreeVector3 } from "three"; import type { Vector3 } from "viewer/constants"; import { ZOOM_STEP_INTERVAL, @@ -43,9 +43,9 @@ export function rotateOnAxis(currentMatrix: Matrix4x4, angle: number, axis: Vect } // Avoid creating new THREE object for some actions. -const flycamRotationEuler = new THREE.Euler(); -const flycamRotationMatrix = new THREE.Matrix4(); -const deltaInWorld = new THREE.Vector3(); +const flycamRotationEuler = new Euler(); +const flycamRotationMatrix = new Matrix4(); +const deltaInWorld = new ThreeVector3(); function rotateOnAxisWithDistance( zoomStep: number, diff --git a/frontend/javascripts/viewer/model/sagas/meshes/common_mesh_saga.ts b/frontend/javascripts/viewer/model/sagas/meshes/common_mesh_saga.ts index 1073bb7294f..6ad0522ee53 100644 --- a/frontend/javascripts/viewer/model/sagas/meshes/common_mesh_saga.ts +++ b/frontend/javascripts/viewer/model/sagas/meshes/common_mesh_saga.ts @@ -4,7 +4,7 @@ import exportToStl from "libs/stl_exporter"; import Toast from "libs/toast"; import Zip from "libs/zipjs_wrapper"; import messages from "messages"; -import type * as THREE from "three"; +import type { Group } from "three"; import { all, call, put, take, takeEvery } from "typed-redux-saga"; import getSceneController from "viewer/controller/scene_controller_provider"; import { @@ -90,7 +90,7 @@ function* downloadMeshCellsAsZIP( } } -const getSTLBlob = (geometry: THREE.Group, segmentId: number): Blob => { +const getSTLBlob = (geometry: Group, segmentId: number): Blob => { const stlDataViews = exportToStl(geometry); // Encode mesh and cell id property const { meshMarker, segmentIdIndex } = stlMeshConstants; diff --git a/frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts b/frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts index 013b71e0cd0..a3844a72ecb 100644 --- a/frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts +++ b/frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts @@ -10,7 +10,7 @@ import { map3 } from "libs/utils"; import _ from "lodash"; import memoizeOne from "memoize-one"; import messages from "messages"; -import * as THREE from "three"; +import { MathUtils, Matrix4 } from "three"; import { actionChannel, all, @@ -92,11 +92,11 @@ import { ensureWkReady } from "./ready_sagas"; import { takeWithBatchActionSupport } from "./saga_helpers"; function getNodeRotationWithoutPlaneRotation(activeNode: Readonly): Vector3 { - // In orthogonal view mode, this active planes default rotation is added to the flycam rotation upon node creation. - // To get the same flycam rotation as was active during node creation, the default rotation is calculated out from the nodes rotation. - const nodeRotationRadian = map3(THREE.MathUtils.degToRad, activeNode.rotation); + // In orthogonal view mode, the active planes' default rotation is added to the flycam rotation upon node creation. + // To get the same flycam rotation as was active during node creation, the default rotation is calculated from the nodes rotation. + const nodeRotationRadian = map3(MathUtils.degToRad, activeNode.rotation); const nodeRotationInReducerFormatMatrix = eulerAngleToReducerInternalMatrix(nodeRotationRadian); - const viewportRotationMatrix = new THREE.Matrix4().makeRotationFromEuler( + const viewportRotationMatrix = new Matrix4().makeRotationFromEuler( OrthoBaseRotations[NumberToOrthoView[activeNode.viewport]], ); // Invert the rotation of the viewport to get the rotation configured during node creation. @@ -105,7 +105,7 @@ function getNodeRotationWithoutPlaneRotation(activeNode: Readonly): viewportRotationMatrixInverted, ); const rotationInRadian = reducerInternalMatrixToEulerAngle(rotationWithoutViewportRotation); - const flycamOnlyRotationInDegree = V3.round(map3(THREE.MathUtils.radToDeg, rotationInRadian)); + const flycamOnlyRotationInDegree = V3.round(map3(MathUtils.radToDeg, rotationInRadian)); return flycamOnlyRotationInDegree; } diff --git a/frontend/javascripts/viewer/view/arbitrary_view.ts b/frontend/javascripts/viewer/view/arbitrary_view.ts index 23435220c16..586608c6a39 100644 --- a/frontend/javascripts/viewer/view/arbitrary_view.ts +++ b/frontend/javascripts/viewer/view/arbitrary_view.ts @@ -1,9 +1,15 @@ import app from "app"; import window from "libs/window"; import _ from "lodash"; -import * as THREE from "three"; +import { + Matrix4, + Object3D, + OrthographicCamera, + PerspectiveCamera, + Vector3 as ThreeVector3, +} from "three"; import TWEEN from "tween.js"; -import type { OrthoViewMap, Viewport } from "viewer/constants"; +import type { OrthoViewMap, Vector3, Viewport } from "viewer/constants"; import Constants, { ARBITRARY_CAM_DISTANCE, ArbitraryViewport, OrthoViews } from "viewer/constants"; import getSceneController, { getSceneControllerOrNull, @@ -20,11 +26,13 @@ import { import { clearCanvas, renderToTexture, setupRenderArea } from "viewer/view/rendering_utils"; type GeometryLike = { - addToScene: (obj: THREE.Object3D) => void; + addToScene: (obj: Object3D) => void; }; +const flipYRotationMatrix = new Matrix4().makeRotationY(Math.PI); + class ArbitraryView { - cameras: OrthoViewMap; + cameras: OrthoViewMap; // @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: ArbitraryPlane; animate: () => void; @@ -34,13 +42,13 @@ class ArbitraryView { isRunning: boolean = false; animationRequestId: number | null | undefined = null; // @ts-expect-error ts-migrate(2322) FIXME: Type 'null' is not assignable to type 'Perspective... Remove this comment to see the full error message - camera: THREE.PerspectiveCamera = null; + camera: PerspectiveCamera = null; // @ts-expect-error ts-migrate(2322) FIXME: Type 'null' is not assignable to type 'Orthographi... Remove this comment to see the full error message - tdCamera: THREE.OrthographicCamera = null; + tdCamera: OrthographicCamera = null; geometries: Array = []; // @ts-expect-error ts-migrate(2564) FIXME: Property 'group' has no initializer and is not def... Remove this comment to see the full error message - group: THREE.Object3D; - cameraPosition: Array; + group: Object3D; + cameraPosition: Vector3; unsubscribeFunctions: Array<() => void> = []; constructor() { @@ -48,18 +56,18 @@ class ArbitraryView { this.setClippingDistance = this.setClippingDistanceImpl.bind(this); const { scene } = getSceneController(); - // Initialize main THREE.js components - this.camera = new THREE.PerspectiveCamera(45, 1, 50, 1000); + // Initialize main js components + this.camera = new PerspectiveCamera(45, 1, 50, 1000); // This name can be used to retrieve the camera from the scene this.camera.name = ArbitraryViewport; this.camera.matrixAutoUpdate = false; scene.add(this.camera); - const tdCamera = new THREE.OrthographicCamera(0, 0, 0, 0); - tdCamera.position.copy(new THREE.Vector3(10, 10, -10)); - tdCamera.up = new THREE.Vector3(0, 0, -1); + const tdCamera = new OrthographicCamera(0, 0, 0, 0); + tdCamera.position.copy(new ThreeVector3(10, 10, -10)); + tdCamera.up = new ThreeVector3(0, 0, -1); tdCamera.matrixAutoUpdate = true; this.tdCamera = tdCamera; - const dummyCamera = new THREE.OrthographicCamera(45, 1, 50, 1000); + const dummyCamera = new OrthographicCamera(0, 0, 0, 0); this.cameras = { TDView: tdCamera, PLANE_XY: dummyCamera, @@ -70,7 +78,7 @@ class ArbitraryView { this.needsRerender = true; } - getCameras(): OrthoViewMap { + getCameras(): OrthoViewMap { return this.cameras; } @@ -92,7 +100,7 @@ class ArbitraryView { }), ); - this.group = new THREE.Object3D(); + this.group = new Object3D(); this.group.add(this.camera); getSceneController().rootGroup.add(this.group); this.resizeImpl(); @@ -165,9 +173,8 @@ class ArbitraryView { m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15], ); - camera.matrix.multiply(new THREE.Matrix4().makeRotationY(Math.PI)); - // @ts-expect-error ts-migrate(2556) FIXME: Expected 3 arguments, but got 0 or more. - camera.matrix.multiply(new THREE.Matrix4().makeTranslation(...this.cameraPosition)); + camera.matrix.multiply(flipYRotationMatrix); + camera.matrix.multiply(new Matrix4().makeTranslation(...this.cameraPosition)); camera.matrixWorldNeedsUpdate = true; clearCanvas(renderer); const storeState = Store.getState(); @@ -254,7 +261,7 @@ class ArbitraryView { }; addGeometry(geometry: GeometryLike): void { - // Adds a new Three.js geometry to the scene. + // Adds a new js geometry to the scene. // This provides the public interface to the GeometryFactory. this.geometries.push(geometry); geometry.addToScene(this.group); diff --git a/frontend/javascripts/viewer/view/plane_view.ts b/frontend/javascripts/viewer/view/plane_view.ts index 1bbf0fd4208..8403767a2f4 100644 --- a/frontend/javascripts/viewer/view/plane_view.ts +++ b/frontend/javascripts/viewer/view/plane_view.ts @@ -2,7 +2,12 @@ import app from "app"; import VisibilityAwareRaycaster from "libs/visibility_aware_raycaster"; import window from "libs/window"; import _ from "lodash"; -import * as THREE from "three"; +import { + DirectionalLight, + OrthographicCamera, + Vector2 as ThreeVector2, + Vector3 as ThreeVector3, +} from "three"; import TWEEN from "tween.js"; import type { OrthoViewMap, Vector2, Vector3, Viewport } from "viewer/constants"; import Constants, { OrthoViewColors, OrthoViewValues, OrthoViews } from "viewer/constants"; @@ -33,10 +38,10 @@ const createDirLight = ( position: Vector3, target: Vector3, intensity: number, - camera: THREE.OrthographicCamera, + camera: OrthographicCamera, ) => { // @ts-ignore - const dirLight = new THREE.DirectionalLight(0x888888, intensity); + const dirLight = new DirectionalLight(0x888888, intensity); dirLight.position.set(...position); camera.add(dirLight); camera.add(dirLight.target); @@ -52,7 +57,7 @@ const MESH_HOVER_THROTTLING_DELAY = 50; let oldRaycasterHit: RaycasterHit = null; class PlaneView { - cameras: OrthoViewMap; + cameras: OrthoViewMap; running: boolean; needsRerender: boolean; unsubscribeFunctions: Array<() => void> = []; @@ -60,13 +65,13 @@ class PlaneView { constructor() { this.running = false; const { scene } = getSceneController(); - // Initialize main THREE.js components - const cameras = {} as OrthoViewMap; + // Initialize main js components + const cameras = {} as OrthoViewMap; for (const plane of OrthoViewValues) { // Let's set up cameras // No need to set any properties, because the cameras controller will deal with that - cameras[plane] = new THREE.OrthographicCamera(0, 0, 0, 0); + cameras[plane] = new OrthographicCamera(0, 0, 0, 0); // This name can be used to retrieve the camera from the scene cameras[plane].name = plane; scene.add(cameras[plane]); @@ -78,14 +83,14 @@ class PlaneView { this.cameras[OrthoViews.PLANE_XY].position.z = -1; this.cameras[OrthoViews.PLANE_YZ].position.x = 1; this.cameras[OrthoViews.PLANE_XZ].position.y = 1; - this.cameras[OrthoViews.TDView].position.copy(new THREE.Vector3(10, 10, -10)); - this.cameras[OrthoViews.PLANE_XY].up = new THREE.Vector3(0, -1, 0); - 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.TDView].position.copy(new ThreeVector3(10, 10, -10)); + this.cameras[OrthoViews.PLANE_XY].up = new ThreeVector3(0, -1, 0); + this.cameras[OrthoViews.PLANE_YZ].up = new ThreeVector3(0, -1, 0); + this.cameras[OrthoViews.PLANE_XZ].up = new ThreeVector3(0, 0, -1); + this.cameras[OrthoViews.TDView].up = new ThreeVector3(0, 0, -1); for (const plane of OrthoViewValues) { - this.cameras[plane].lookAt(new THREE.Vector3(0, 0, 0)); + this.cameras[plane].lookAt(new ThreeVector3(0, 0, 0)); } this.needsRerender = true; @@ -158,7 +163,7 @@ class PlaneView { } // Perform ray casting - const mouse = new THREE.Vector2( + const mouse = new ThreeVector2( (mousePosition[0] / tdViewport.width) * 2 - 1, ((mousePosition[1] / tdViewport.height) * 2 - 1) * -1, ); @@ -252,7 +257,7 @@ class PlaneView { this.draw(); }; - getCameras(): OrthoViewMap { + getCameras(): OrthoViewMap { return this.cameras; } diff --git a/frontend/javascripts/viewer/view/rendering_utils.ts b/frontend/javascripts/viewer/view/rendering_utils.ts index c4e5fd7bfe6..4bf07162503 100644 --- a/frontend/javascripts/viewer/view/rendering_utils.ts +++ b/frontend/javascripts/viewer/view/rendering_utils.ts @@ -1,6 +1,12 @@ import { saveAs } from "file-saver"; import { convertBufferToImage } from "libs/utils"; -import * as THREE from "three"; +import { + type OrthographicCamera, + type PerspectiveCamera, + type Scene, + WebGLRenderTarget, + type WebGLRenderer, +} from "three"; import { ARBITRARY_CAM_DISTANCE, type OrthoView } from "viewer/constants"; import constants, { ArbitraryViewport, @@ -17,7 +23,7 @@ const getBackgroundColor = (): number => Store.getState().uiInformation.theme === "dark" ? 0x000000 : 0xffffff; export const setupRenderArea = ( - renderer: THREE.WebGLRenderer, + renderer: WebGLRenderer, x: number, y: number, viewportWidth: number, @@ -32,14 +38,14 @@ export const setupRenderArea = ( renderer.setScissorTest(true); renderer.setClearColor(color === 0xffffff ? getBackgroundColor() : color, 1); }; -export const clearCanvas = (renderer: THREE.WebGLRenderer) => { +export const clearCanvas = (renderer: WebGLRenderer) => { setupRenderArea(renderer, 0, 0, renderer.domElement.width, renderer.domElement.height, 0xffffff); renderer.clear(); }; export function renderToTexture( plane: OrthoView | typeof ArbitraryViewport, - scene?: THREE.Scene, - camera?: THREE.OrthographicCamera | THREE.PerspectiveCamera, + scene?: Scene, + camera?: OrthographicCamera | PerspectiveCamera, // When withFarClipping is true, the user-specified clipping distance is used. // Note that the data planes might not be included in the rendered texture, since // these are exactly offset by the clipping distance. Currently, `withFarClipping` @@ -52,32 +58,30 @@ export function renderToTexture( const { renderer, scene: defaultScene } = SceneController; const state = Store.getState(); scene = scene || defaultScene; - camera = (camera || scene.getObjectByName(plane)) as - | THREE.OrthographicCamera - | THREE.PerspectiveCamera; + camera = (camera || scene.getObjectByName(plane)) as OrthographicCamera | PerspectiveCamera; // Don't respect withFarClipping for the TDViewport as we don't do any clipping for // nodes there. if (withFarClipping && plane !== OrthoViews.TDView) { - function adaptCameraToCurrentClippingDistance< - T extends THREE.OrthographicCamera | THREE.PerspectiveCamera, - >(camera: T): T { + function adaptCameraToCurrentClippingDistance( + camera: T, + ): T { const isArbitraryMode = constants.MODES_ARBITRARY.includes( state.temporaryConfiguration.viewMode, ); - camera = camera.clone() as T; + const adaptedCamera = camera.clone() as T; // The near value is already set in the camera (done in the CameraController/ArbitraryView). if (isArbitraryMode) { // The far value has to be set, since in normal rendering the far clipping is // achieved by the data plane which is not rendered during node picking - camera.far = ARBITRARY_CAM_DISTANCE; + adaptedCamera.far = ARBITRARY_CAM_DISTANCE; } else { // The far value has to be set, since in normal rendering the far clipping is // achieved by offsetting the plane instead of setting the far property. - camera.far = state.userConfiguration.clippingDistance; + adaptedCamera.far = state.userConfiguration.clippingDistance; } - camera.updateProjectionMatrix(); - return camera; + adaptedCamera.updateProjectionMatrix(); + return adaptedCamera; } camera = adaptCameraToCurrentClippingDistance(camera); @@ -91,7 +95,7 @@ export function renderToTexture( renderer.setViewport(0, 0 + height, width, height); renderer.setScissorTest(false); renderer.setClearColor(clearColor === 0xffffff ? getBackgroundColor() : clearColor, 1); - const renderTarget = new THREE.WebGLRenderTarget(width, height); + const renderTarget = new WebGLRenderTarget(width, height); const buffer = new Uint8Array(width * height * 4); if (plane !== ArbitraryViewport) { diff --git a/webpack.config.js b/webpack.config.js index 433ca1a8773..113255adfbf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -141,6 +141,7 @@ module.exports = function (env = {}) { modules: [srcPath, nodePath, protoPath], alias: { react: path.resolve("./node_modules/react"), + three: path.resolve(__dirname, "node_modules/three/src/Three.js"), // build three js from source instead of using their prebuilt "binaries" to allow for a smaller bundle size }, extensions: [".ts", ".tsx", ".js", ".json"], fallback: {