Finish slicing box

This commit is contained in:
Fuhrmann 2025-03-27 10:49:38 +01:00
parent 7c78015894
commit e71980ad17
4 changed files with 395 additions and 210 deletions

View file

@ -30,7 +30,12 @@ import {
OBJExporter, OBJExporter,
OrbitControls, OrbitControls,
} from "three/examples/jsm/Addons.js"; } from "three/examples/jsm/Addons.js";
import { MapTilerProvider, MapView, OpenStreetMapsProvider } from "geo-three"; import {
LODFrustum,
MapTilerProvider,
MapView,
OpenStreetMapsProvider,
} from "geo-three";
import { CustomMapHeightNodeShader } from "./CustomMapHeightNodeShader"; import { CustomMapHeightNodeShader } from "./CustomMapHeightNodeShader";
import { Data, createSVG } from "./utils/create-borehole-svg"; import { Data, createSVG } from "./utils/create-borehole-svg";
@ -361,6 +366,7 @@ export class SceneView extends EventTarget {
this._extent.zmax = this._model.userData.zmax; this._extent.zmax = this._model.userData.zmax;
} }
// Reset clipping box
const box = this._scene.getObjectByName("clipping-box"); const box = this._scene.getObjectByName("clipping-box");
let visible = false; let visible = false;
if (box) { if (box) {
@ -464,7 +470,10 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
); );
// Create the map view for OSM topography // Create the map view for OSM topography
const map = new MapView(MapView.PLANAR, provider, heightProvider); const lod = new LODFrustum();
// @ts-expect-error Type definition for MapView is incorrect - missing parameter for lod
const map = new MapView(MapView.PLANAR, provider, heightProvider, lod);
const customNode = new CustomMapHeightNodeShader(undefined, map); const customNode = new CustomMapHeightNodeShader(undefined, map);
map.setRoot(customNode); map.setRoot(customNode);
map.rotateX(Math.PI / 2); map.rotateX(Math.PI / 2);

View file

@ -26,6 +26,9 @@ export enum Orientation {
X = "X", X = "X",
Y = "Y", Y = "Y",
Z = "Z", Z = "Z",
NX = "NX",
NY = "NY",
NZ = "NZ",
} }
type PlaneMesh = Mesh<PlaneGeometry, MeshBasicMaterial, Object3DEventMap>; type PlaneMesh = Mesh<PlaneGeometry, MeshBasicMaterial, Object3DEventMap>;
@ -37,10 +40,13 @@ type EdgeMesh = LineSegments<
type PlaneMeshMap = { type PlaneMeshMap = {
[key in Orientation]: PlaneMesh; [key in Orientation]: PlaneMesh;
}; };
type EdgeMashMap = { type EdgeMeshMap = {
[key in Orientation]: EdgeMesh; [key in Orientation]: EdgeMesh;
}; };
let currentExtent: Extent;
const BUFFER = 500;
export function buildClippingplanes( export function buildClippingplanes(
renderer: WebGLRenderer, renderer: WebGLRenderer,
camera: PerspectiveCamera, camera: PerspectiveCamera,
@ -50,6 +56,9 @@ export function buildClippingplanes(
scene: Scene, scene: Scene,
visible: boolean visible: boolean
) { ) {
// Set current extent to given extent
currentExtent = { ...extent };
const planesData = [ const planesData = [
{ {
normal: new Vector3(1, 0, 0), normal: new Vector3(1, 0, 0),
@ -61,10 +70,25 @@ export function buildClippingplanes(
d: -extent.ymin, d: -extent.ymin,
orientation: Orientation.Y, orientation: Orientation.Y,
}, },
{
normal: new Vector3(0, 0, 1),
d: -extent.zmin,
orientation: Orientation.Z,
},
{
normal: new Vector3(-1, 0, 0),
d: extent.xmax,
orientation: Orientation.NX,
},
{
normal: new Vector3(0, -1, 0),
d: extent.ymax,
orientation: Orientation.NY,
},
{ {
normal: new Vector3(0, 0, -1), normal: new Vector3(0, 0, -1),
d: extent.zmax, d: extent.zmax,
orientation: Orientation.Z, orientation: Orientation.NZ,
}, },
]; ];
@ -72,7 +96,7 @@ export function buildClippingplanes(
const edgeMeshes: EdgeMesh[] = []; const edgeMeshes: EdgeMesh[] = [];
const planes: Plane[] = []; const planes: Plane[] = [];
const planeMeshMap = {} as Partial<PlaneMeshMap>; const planeMeshMap = {} as Partial<PlaneMeshMap>;
const edgeMeshMap = {} as Partial<EdgeMashMap>; const edgeMeshMap = {} as Partial<EdgeMeshMap>;
// Create plane meshes // Create plane meshes
for (const p of planesData) { for (const p of planesData) {
@ -80,32 +104,35 @@ export function buildClippingplanes(
let planeCenter; let planeCenter;
let width; let width;
let height; let height;
if (p.orientation === Orientation.X) { if (p.orientation === Orientation.X || p.orientation === Orientation.NX) {
name = Orientation.X; name = p.orientation;
width = extent.ymax - extent.ymin; width = extent.ymax - extent.ymin;
height = extent.zmax - extent.zmin; height = extent.zmax - extent.zmin;
planeCenter = new Vector3( planeCenter = new Vector3(
-p.d, p.orientation === Orientation.X ? -p.d : p.d,
extent.ymax - width / 2, extent.ymax - width / 2,
extent.zmin + height / 2 extent.zmax - height / 2
); );
} else if (p.orientation === Orientation.Y) { } else if (
name = Orientation.Y; p.orientation === Orientation.Y ||
p.orientation === Orientation.NY
) {
name = p.orientation;
width = extent.xmax - extent.xmin; width = extent.xmax - extent.xmin;
height = extent.zmax - extent.zmin; height = extent.zmax - extent.zmin;
planeCenter = new Vector3( planeCenter = new Vector3(
extent.xmax - width / 2, extent.xmax - width / 2,
-p.d, p.orientation === Orientation.Y ? -p.d : p.d,
extent.zmin + height / 2 extent.zmax - height / 2
); );
} else { } else {
name = Orientation.Z; name = p.orientation;
width = extent.xmax - extent.xmin; width = extent.xmax - extent.xmin;
height = extent.ymax - extent.ymin; height = extent.ymax - extent.ymin;
planeCenter = new Vector3( planeCenter = new Vector3(
extent.xmax - width / 2, extent.xmax - width / 2,
extent.ymax - height / 2, extent.ymax - height / 2,
p.d p.orientation === Orientation.Z ? -p.d : p.d
); );
} }
@ -137,12 +164,15 @@ export function buildClippingplanes(
edges.position.set(planeCenter.x, planeCenter.y, planeCenter.z); edges.position.set(planeCenter.x, planeCenter.y, planeCenter.z);
// Rotate meshes // Rotate meshes
if (p.orientation === Orientation.X) { if (p.orientation === Orientation.X || p.orientation === Orientation.NX) {
planeMesh.rotateY(Math.PI / 2); planeMesh.rotateY(Math.PI / 2);
planeMesh.rotateZ(Math.PI / 2); planeMesh.rotateZ(Math.PI / 2);
edges.rotateY(Math.PI / 2); edges.rotateY(Math.PI / 2);
edges.rotateZ(Math.PI / 2); edges.rotateZ(Math.PI / 2);
} else if (p.orientation === Orientation.Y) { } else if (
p.orientation === Orientation.Y ||
p.orientation === Orientation.NY
) {
planeMesh.rotateX(Math.PI / 2); planeMesh.rotateX(Math.PI / 2);
edges.rotateX(Math.PI / 2); edges.rotateX(Math.PI / 2);
} }
@ -194,118 +224,163 @@ export function buildClippingplanes(
const plane = event.object.userData.plane; const plane = event.object.userData.plane;
const width = object.geometry.parameters.width; const width = object.geometry.parameters.width;
const height = object.geometry.parameters.height; const height = object.geometry.parameters.height;
let orientation: Orientation; const orientation = object.name as Orientation;
if (object.name === Orientation.Z) {
orientation = Orientation.Z; if (orientation === Orientation.Z || orientation === Orientation.NZ) {
// Fix rotation of dragged mesh // Fix rotation of dragged mesh
event.object.rotation.set(0, 0, 0); event.object.rotation.set(0, 0, 0);
let newZ; let newZ = 0;
if (event.object.position.z > extent.zmax) { if (orientation === Orientation.Z) {
newZ = extent.zmax; if (event.object.position.z < extent.zmin) {
} else if (event.object.position.z < extent.zmin) {
newZ = extent.zmin; newZ = extent.zmin;
} else if (event.object.position.z > currentExtent.zmax - BUFFER) {
newZ = currentExtent.zmax - BUFFER;
} else { } else {
newZ = event.object.position.z; newZ = event.object.position.z;
} }
} else {
if (event.object.position.z > extent.zmax) {
newZ = extent.zmax;
} else if (event.object.position.z < currentExtent.zmin + BUFFER) {
newZ = currentExtent.zmin + BUFFER;
} else {
newZ = event.object.position.z;
}
}
// Reset position of plane // Reset position of plane
plane.constant = newZ; plane.constant = orientation === Orientation.Z ? -newZ : newZ;
// Update current extent
if (orientation === Orientation.Z) {
currentExtent.zmin = newZ;
} else {
currentExtent.zmax = newZ;
}
// Set position of dragged meshes // Set position of dragged meshes
object.position.x = extent.xmax - width / 2; object.position.x = currentExtent.xmax - width / 2;
object.position.y = extent.ymax - height / 2; object.position.y = currentExtent.ymax - height / 2;
object.position.z = newZ; object.position.z = newZ;
const edgeMesh = edgeMeshMap[Orientation.Z]; const edgeMesh = edgeMeshMap[orientation];
if (edgeMesh) { if (edgeMesh) {
edgeMesh.position.x = extent.xmax - width / 2; edgeMesh.position.x = currentExtent.xmax - width / 2;
edgeMesh.position.y = extent.ymax - height / 2; edgeMesh.position.y = currentExtent.ymax - height / 2;
edgeMesh.position.z = newZ; edgeMesh.position.z = newZ;
} }
// Resize other meshes to disable dragging of clipped surface parts // Resize other meshes to disable dragging of clipped surface parts
resizeMeshes( resizeMeshes(
Orientation.Z, orientation,
newZ,
planeMeshMap as PlaneMeshMap, planeMeshMap as PlaneMeshMap,
edgeMeshMap as EdgeMashMap, edgeMeshMap as EdgeMeshMap
extent
); );
} else if (object.name === Orientation.Y) { } else if (
orientation = Orientation.Y; orientation === Orientation.Y ||
orientation === Orientation.NY
) {
// Fix rotation of dragged mesh // Fix rotation of dragged mesh
event.object.rotation.set(Math.PI / 2, 0, 0); event.object.rotation.set(Math.PI / 2, 0, 0);
let newY; let newY = 0;
if (event.object.position.y > extent.ymax) { if (orientation === Orientation.Y) {
newY = extent.ymax; if (event.object.position.y < extent.ymin) {
} else if (event.object.position.y < extent.ymin) {
newY = extent.ymin; newY = extent.ymin;
} else if (event.object.position.y > currentExtent.ymax - BUFFER) {
newY = currentExtent.ymax - BUFFER;
} else { } else {
newY = event.object.position.y; newY = event.object.position.y;
} }
} else {
if (event.object.position.y > extent.ymax) {
newY = extent.ymax;
} else if (event.object.position.y < currentExtent.ymin + BUFFER) {
newY = currentExtent.ymin + BUFFER;
} else {
newY = event.object.position.y;
}
}
// Reset position of plane // Reset position of plane
plane.constant = -newY; plane.constant = orientation === Orientation.Y ? -newY : newY;
// Update current extent
if (orientation === Orientation.Y) {
currentExtent.ymin = newY;
} else {
currentExtent.ymax = newY;
}
// Set position of dragged mesh // Set position of dragged mesh
object.position.x = extent.xmax - width / 2; object.position.x = currentExtent.xmax - width / 2;
object.position.y = newY; object.position.y = newY;
object.position.z = extent.zmin + height / 2; object.position.z = currentExtent.zmax - height / 2;
const edgeMesh = edgeMeshMap[Orientation.Y]; const edgeMesh = edgeMeshMap[orientation];
if (edgeMesh) { if (edgeMesh) {
edgeMesh.position.x = extent.xmax - width / 2; edgeMesh.position.x = currentExtent.xmax - width / 2;
edgeMesh.position.y = newY; edgeMesh.position.y = newY;
edgeMesh.position.z = extent.zmin + height / 2; edgeMesh.position.z = currentExtent.zmax - height / 2;
} }
// Resize other meshes // Resize other meshes
resizeMeshes( resizeMeshes(
Orientation.Y, orientation,
newY,
planeMeshMap as PlaneMeshMap, planeMeshMap as PlaneMeshMap,
edgeMeshMap as EdgeMashMap, edgeMeshMap as EdgeMeshMap
extent
); );
} else { } else {
orientation = Orientation.X;
// Fix rotation of dragged mesh // Fix rotation of dragged mesh
event.object.rotation.set(0, Math.PI / 2, Math.PI / 2); event.object.rotation.set(0, Math.PI / 2, Math.PI / 2);
let newX; let newX = 0;
if (event.object.position.x > extent.xmax) { if (orientation === Orientation.X) {
newX = extent.xmax; if (event.object.position.x < extent.xmin) {
} else if (event.object.position.x < extent.xmin) {
newX = extent.xmin; newX = extent.xmin;
} else if (event.object.position.x > currentExtent.xmax - BUFFER) {
newX = currentExtent.xmax - BUFFER;
} else { } else {
newX = event.object.position.x; newX = event.object.position.x;
} }
} else {
if (event.object.position.x > extent.xmax) {
newX = extent.xmax;
} else if (event.object.position.x < currentExtent.xmin + BUFFER) {
newX = currentExtent.xmin + BUFFER;
} else {
newX = event.object.position.x;
}
}
// Reset position of plane // Reset position of plane
plane.constant = -newX; plane.constant = orientation === Orientation.X ? -newX : newX;
// Update current extent
if (orientation === Orientation.X) {
currentExtent.xmin = newX;
} else {
currentExtent.xmax = newX;
}
// Set position of dragged mesh // Set position of dragged mesh
object.position.x = newX; object.position.x = newX;
object.position.y = extent.ymax - width / 2; object.position.y = currentExtent.ymax - width / 2;
object.position.z = extent.zmin + height / 2; object.position.z = currentExtent.zmax - height / 2;
const edgeMesh = edgeMeshMap[Orientation.X]; const edgeMesh = edgeMeshMap[orientation];
if (edgeMesh) { if (edgeMesh) {
edgeMesh.position.x = newX; edgeMesh.position.x = newX;
edgeMesh.position.y = extent.ymax - width / 2; edgeMesh.position.y = currentExtent.ymax - width / 2;
edgeMesh.position.z = extent.zmin + height / 2; edgeMesh.position.z = currentExtent.zmax - height / 2;
} }
// Resize other meshes // Resize other meshes
resizeMeshes( resizeMeshes(
Orientation.X, orientation,
newX,
planeMeshMap as PlaneMeshMap, planeMeshMap as PlaneMeshMap,
edgeMeshMap as EdgeMashMap, edgeMeshMap as EdgeMeshMap
extent
); );
} }
@ -341,121 +416,157 @@ export function buildClippingplanes(
function resizeMeshes( function resizeMeshes(
orientation: Orientation, orientation: Orientation,
newCoordinate: number,
planeMeshes: PlaneMeshMap, planeMeshes: PlaneMeshMap,
edgeMeshes: EdgeMashMap, edgeMeshes: EdgeMeshMap
extent: Extent
) { ) {
if (orientation === Orientation.X) { if (orientation === Orientation.X || orientation === Orientation.NX) {
// Resize y-clipping plane // Resize y-clipping-planes
let planeMesh = planeMeshes[Orientation.Y]; for (const o of [Orientation.Y, Orientation.NY]) {
let edgeMesh = edgeMeshes[Orientation.Y]; const planeMesh = planeMeshes[o];
let width = extent.xmax - newCoordinate; const width = currentExtent.xmax - currentExtent.xmin;
let height = planeMesh.geometry.parameters.height; const height = planeMesh.geometry.parameters.height;
let planeGeometry = new PlaneGeometry(width, height);
const y = planeMesh.position.y; const y = planeMesh.position.y;
planeMesh.geometry.dispose(); const newPosition = new Vector3(
planeMesh.geometry = planeGeometry; currentExtent.xmax - width / 2,
planeMesh.position.set(
extent.xmax - width / 2,
y, y,
extent.zmin + height / 2 currentExtent.zmax - height / 2
); );
edgeMesh.geometry.dispose(); resizeClippingPlane(
edgeMesh.geometry = new EdgesGeometry(planeGeometry); o,
edgeMesh.position.set(extent.xmax - width / 2, y, extent.zmin + height / 2); planeMeshes,
edgeMeshes,
// Resize z-clipping-plane width,
planeMesh = planeMeshes[Orientation.Z]; height,
edgeMesh = edgeMeshes[Orientation.Z]; newPosition
width = extent.xmax - newCoordinate;
height = planeMesh.geometry.parameters.height;
planeGeometry = new PlaneGeometry(width, height);
const z = planeMesh.position.z;
planeMesh.geometry.dispose();
planeMesh.geometry = planeGeometry;
planeMesh.position.set(
extent.xmax - width / 2,
extent.ymax - height / 2,
z
); );
edgeMesh.geometry.dispose();
edgeMesh.geometry = new EdgesGeometry(planeGeometry);
edgeMesh.position.set(extent.xmax - width / 2, extent.ymax - height / 2, z);
} else if (orientation === Orientation.Y) {
// Resize x-clipping plane
let planeMesh = planeMeshes[Orientation.X];
let edgeMesh = edgeMeshes[Orientation.X];
let width = extent.ymax - newCoordinate;
let height = planeMesh.geometry.parameters.height;
let planeGeometry = new PlaneGeometry(width, height);
const x = planeMesh.position.x;
planeMesh.geometry.dispose();
planeMesh.geometry = planeGeometry;
planeMesh.position.set(
x,
extent.ymax - width / 2,
extent.zmin + height / 2
);
edgeMesh.geometry.dispose();
edgeMesh.geometry = new EdgesGeometry(planeGeometry);
edgeMesh.position.set(x, extent.ymax - width / 2, extent.zmin + height / 2);
// Resize z-clipping-plane
planeMesh = planeMeshes[Orientation.Z];
edgeMesh = edgeMeshes[Orientation.Z];
width = planeMesh.geometry.parameters.width;
height = extent.ymax - newCoordinate;
planeGeometry = new PlaneGeometry(width, height);
const z = planeMesh.position.z;
planeMesh.geometry.dispose();
planeMesh.geometry = planeGeometry;
planeMesh.position.set(
extent.xmax - width / 2,
extent.ymax - height / 2,
z
);
edgeMesh.geometry.dispose();
edgeMesh.geometry = new EdgesGeometry(planeGeometry);
edgeMesh.position.set(extent.xmax - width / 2, extent.ymax - height / 2, z);
} else if (orientation === Orientation.Z) {
// Resize x-clipping-plane
let planeMesh = planeMeshes[Orientation.X];
let edgeMesh = edgeMeshes[Orientation.X];
let width = planeMesh.geometry.parameters.width;
let height = newCoordinate - extent.zmin;
let planeGeometry = new PlaneGeometry(width, height);
const x = planeMesh.position.x;
planeMesh.geometry.dispose();
planeMesh.geometry = planeGeometry;
planeMesh.position.set(
x,
extent.ymax - width / 2,
extent.zmin + height / 2
);
edgeMesh.geometry.dispose();
edgeMesh.geometry = new EdgesGeometry(planeGeometry);
edgeMesh.position.set(x, extent.ymax - width / 2, extent.zmin + height / 2);
// Resize y-clipping plane
planeMesh = planeMeshes[Orientation.Y];
edgeMesh = edgeMeshes[Orientation.Y];
width = planeMesh.geometry.parameters.width;
height = newCoordinate - extent.zmin;
planeGeometry = new PlaneGeometry(width, height);
const y = planeMesh.position.y;
planeMesh.geometry.dispose();
planeMesh.geometry = planeGeometry;
planeMesh.position.set(
extent.xmax - width / 2,
y,
extent.zmin + height / 2
);
edgeMesh.geometry.dispose();
edgeMesh.geometry = new EdgesGeometry(planeGeometry);
edgeMesh.position.set(extent.xmax - width / 2, y, extent.zmin + height / 2);
} }
// Resize z-clipping-planes
for (const o of [Orientation.Z, Orientation.NZ]) {
const planeMesh = planeMeshes[o];
const width = currentExtent.xmax - currentExtent.xmin;
const height = planeMesh.geometry.parameters.height;
const z = planeMesh.position.z;
const newPosition = new Vector3(
currentExtent.xmax - width / 2,
currentExtent.ymax - height / 2,
z
);
resizeClippingPlane(
o,
planeMeshes,
edgeMeshes,
width,
height,
newPosition
);
}
} else if (orientation === Orientation.Y || orientation === Orientation.NY) {
// Resize x-clipping-planes
for (const o of [Orientation.X, Orientation.NX]) {
const planeMesh = planeMeshes[o];
const width = currentExtent.ymax - currentExtent.ymin;
const height = planeMesh.geometry.parameters.height;
const x = planeMesh.position.x;
const newPosition = new Vector3(
x,
currentExtent.ymax - width / 2,
currentExtent.zmax - height / 2
);
resizeClippingPlane(
o,
planeMeshes,
edgeMeshes,
width,
height,
newPosition
);
}
// Resize z-clipping-planes
for (const o of [Orientation.Z, Orientation.NZ]) {
const planeMesh = planeMeshes[o];
const width = planeMesh.geometry.parameters.width;
const height = currentExtent.ymax - currentExtent.ymin;
const z = planeMesh.position.z;
const newPosition = new Vector3(
currentExtent.xmax - width / 2,
currentExtent.ymax - height / 2,
z
);
resizeClippingPlane(
o,
planeMeshes,
edgeMeshes,
width,
height,
newPosition
);
}
} else {
// Resize x-clipping-planes
for (const o of [Orientation.X, Orientation.NX]) {
const height = currentExtent.zmax - currentExtent.zmin;
const planeMesh = planeMeshes[o];
const width = planeMesh.geometry.parameters.width;
const x = planeMesh.position.x;
const newPosition = new Vector3(
x,
currentExtent.ymax - width / 2,
currentExtent.zmax - height / 2
);
resizeClippingPlane(
o,
planeMeshes,
edgeMeshes,
width,
height,
newPosition
);
}
// Resize y-clipping-planes
for (const o of [Orientation.Y, Orientation.NY]) {
const planeMesh = planeMeshes[o];
const width = planeMesh.geometry.parameters.width;
const height = currentExtent.zmax - currentExtent.zmin;
const y = planeMesh.position.y;
const newPosition = new Vector3(
currentExtent.xmax - width / 2,
y,
currentExtent.zmax - height / 2
);
resizeClippingPlane(
o,
planeMeshes,
edgeMeshes,
width,
height,
newPosition
);
}
}
}
function resizeClippingPlane(
orientation: Orientation,
planeMeshes: PlaneMeshMap,
edgeMeshes: EdgeMeshMap,
width: number,
height: number,
position: Vector3
) {
const planeMesh = planeMeshes[orientation];
const edgeMesh = edgeMeshes[orientation];
const planeGeometry = new PlaneGeometry(width, height);
planeMesh.geometry.dispose();
planeMesh.geometry = planeGeometry;
planeMesh.position.copy(position);
edgeMesh.geometry.dispose();
edgeMesh.geometry = new EdgesGeometry(planeGeometry);
edgeMesh.position.copy(position);
} }
// Extract contour and generate cap // Extract contour and generate cap

View file

@ -40,9 +40,14 @@ async function buildMesh(layerData: MappedFeature) {
// Transform coordinates to EPSG 3857 // Transform coordinates to EPSG 3857
const vertices3857 = new Float32Array(vertices.length); const vertices3857 = new Float32Array(vertices.length);
// Reduce coordinate precision
for (let i = 0; i < vertices.length; i += 3) { for (let i = 0; i < vertices.length; i += 3) {
const vertex = Array.from(vertices.slice(i, i + 3)); const vertex = Array.from(vertices.slice(i, i + 3));
vertices3857.set(transform(vertex), i); vertices3857.set(
transform(vertex).map((c) => parseInt(c.toFixed(0))),
i
);
} }
const positions = new BufferAttribute(vertices3857, 3); const positions = new BufferAttribute(vertices3857, 3);

View file

@ -6,10 +6,15 @@ import {
DirectionalLight, DirectionalLight,
Group, Group,
Object3D, Object3D,
AxesHelper,
OrthographicCamera, OrthographicCamera,
Color, Color,
Vector3, Vector3,
BufferGeometry,
BufferAttribute,
DoubleSide,
Mesh,
MeshBasicMaterial,
ConeGeometry,
} from "three"; } from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { OrbitControls } from "three/addons/controls/OrbitControls.js";
@ -28,12 +33,13 @@ let controls: OrbitControls;
let renderer: WebGLRenderer; let renderer: WebGLRenderer;
let camera: PerspectiveCamera; let camera: PerspectiveCamera;
let scene: Scene; let scene: Scene;
let axesHelper: AxesHelper;
let overlayCamera: OrthographicCamera; let overlayCamera: OrthographicCamera;
let overlayScene: Scene; let overlayScene: Scene;
let overlayWidth = 200;
let overlayHeight = 200;
let maxSize = 0; let maxSize = 0;
const compass = new Group();
const UI_WIDTH = 200;
const UI_HEIGHT = 200;
export function buildScene(container: HTMLElement, extent: Extent) { export function buildScene(container: HTMLElement, extent: Extent) {
maxSize = getMaxSize(extent); maxSize = getMaxSize(extent);
const center = getCenter3D(extent); const center = getCenter3D(extent);
@ -63,8 +69,8 @@ export function buildScene(container: HTMLElement, extent: Extent) {
container.appendChild(renderer.domElement); container.appendChild(renderer.domElement);
controls = new OrbitControls(camera, renderer.domElement); controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(center.x, center.y, center.z); // Focus on the center controls.target.set(center.x, center.y, center.z);
controls.enableDamping = true; // Smooth camera movement controls.enableDamping = true;
controls.dampingFactor = 0.1; controls.dampingFactor = 0.1;
controls.maxDistance = maxSize * 3; controls.maxDistance = maxSize * 3;
controls.minDistance = maxSize / 5; controls.minDistance = maxSize / 5;
@ -74,34 +80,32 @@ export function buildScene(container: HTMLElement, extent: Extent) {
// Set wireframe to false on initial load // Set wireframe to false on initial load
scene = new Scene(); scene = new Scene();
scene.userData.wireframe = false; scene.userData.wireframe = false;
scene.background = new Color(0xbfd1e5); const backgroundColor = new Color(0xbfd1e5);
scene.background = backgroundColor;
renderer.setClearColor(backgroundColor);
// Add lights to the scene // Add lights to the scene
buildDefaultLights(scene, extent); buildDefaultLights(scene, extent);
// Create Scene for UI overlay
overlayScene = new Scene(); overlayScene = new Scene();
// Create an overlay camera // Create an overlay camera
overlayWidth = maxSize;
overlayHeight = maxSize;
overlayCamera = new OrthographicCamera( overlayCamera = new OrthographicCamera(
-overlayWidth / 2, -maxSize / 2,
overlayWidth / 2, maxSize / 2,
overlayHeight / 2, maxSize / 2,
-overlayHeight / 2, -maxSize / 2,
0.1, 0.1,
10 * maxSize 10 * maxSize
); );
// Position the camera similarly to how you did with PerspectiveCamera // Sync overlay camera with main camera
overlayCamera.position.copy(camera.position); overlayCamera.position.copy(camera.position);
overlayCamera.up.copy(camera.up); overlayCamera.rotation.copy(camera.rotation);
overlayCamera.lookAt(center);
// Create the AxesHelper // Create compass
axesHelper = new AxesHelper(maxSize); createCompass(center);
axesHelper.position.set(center.x, center.y, center.z);
overlayScene.add(axesHelper);
return { renderer, scene, camera, controls }; return { renderer, scene, camera, controls };
} }
@ -121,32 +125,31 @@ function animate() {
// Update controls for main camera // Update controls for main camera
controls.update(); controls.update();
renderer.autoClear = false;
renderer.render(scene, camera); renderer.render(scene, camera);
// Render the UI overlay
renderOverlay(); renderOverlay();
} }
// Render the overlay scene as an overlay // Render the overlay scene as an overlay
function renderOverlay() { function renderOverlay() {
// Update the overlay camera position and orientation to match the main camera // Sync overlay camera
overlayCamera.position.copy(camera.position); overlayCamera.position.copy(camera.position);
overlayCamera.rotation.copy(camera.rotation); overlayCamera.rotation.copy(camera.rotation);
// Sync compass
const dir = new Vector3(); const dir = new Vector3();
overlayCamera.getWorldDirection(dir); camera.getWorldDirection(dir);
axesHelper.position.set( compass.position.set(
camera.position.x + maxSize * dir.x, camera.position.x + maxSize * dir.x,
camera.position.y + maxSize * dir.y, camera.position.y + maxSize * dir.y,
camera.position.z + maxSize * dir.z camera.position.z + maxSize * dir.z
); );
// Render the overlay scene to the screen (position it in the bottom left) // Render the overlay scene to the screen (position it in the bottom left)
const width = 200;
const height = 200;
renderer.setScissorTest(true); renderer.setScissorTest(true);
renderer.setScissor(10, 10, width, height); renderer.setScissor(10, 10, UI_WIDTH, UI_HEIGHT);
renderer.setViewport(10, 10, width, height); renderer.setViewport(10, 10, UI_WIDTH, UI_HEIGHT);
renderer.render(overlayScene, overlayCamera); renderer.render(overlayScene, overlayCamera);
renderer.setScissorTest(false); // Disable scissor testing for the rest of the scene renderer.setScissorTest(false); // Disable scissor testing for the rest of the scene
renderer.setViewport( renderer.setViewport(
@ -194,3 +197,60 @@ function buildDefaultLights(scene: Scene, extent: Extent) {
lightsGroup.add(...lights); lightsGroup.add(...lights);
scene.add(lightsGroup); scene.add(lightsGroup);
} }
function createCompass(center: Vector3) {
const vertices = new Float32Array(
[
[0.2, 0, 0],
[0, 1, 0],
[-0.2, 0, 0],
[-0.2, 0, 0],
[0, -1, 0],
[0.2, 0, 0],
].flat()
);
const positions = new BufferAttribute(vertices, 3);
const geometry = new BufferGeometry();
geometry.setAttribute("position", positions);
const colors = new Float32Array([
1,
12 / 255,
0,
1,
12 / 255,
0,
1,
12 / 255,
0,
113 / 255,
99 / 255,
183 / 255,
113 / 255,
99 / 255,
183 / 255,
113 / 255,
99 / 255,
183 / 255,
]);
geometry.setAttribute("color", new BufferAttribute(colors, 3));
const material = new MeshBasicMaterial({
side: DoubleSide,
vertexColors: true,
});
const needle = new Mesh(geometry, material);
const coneGeometry = new ConeGeometry(0.1, 0.1, 32);
const coneMaterial = new MeshBasicMaterial({ color: 0x444444 });
const cone = new Mesh(coneGeometry, coneMaterial);
cone.position.z = 0.055;
cone.rotateX(Math.PI / 2);
compass.add(needle, cone);
compass.position.copy(center);
compass.scale.set(maxSize * 0.5, maxSize * 0.5, maxSize * 0.5);
overlayScene.add(compass);
}