Working clipping functionality

This commit is contained in:
Fuhrmann 2025-03-05 09:25:23 +01:00
parent 13be63c40a
commit 20d99b5815
4 changed files with 313 additions and 50 deletions

View file

@ -2,6 +2,7 @@ import {
DoubleSide,
Mesh,
MeshBasicMaterial,
Object3DEventMap,
PerspectiveCamera,
Plane,
PlaneGeometry,
@ -10,60 +11,304 @@ import {
} from "three";
import { DragControls, OrbitControls } from "three/examples/jsm/Addons.js";
import { Extent } from "./build-scene";
import { getCenter3D } from "./utils";
export function createClippingPlane(
enum Orientation {
X = "x",
Y = "y",
Z = "z",
}
type PlaneMesh = Mesh<PlaneGeometry, MeshBasicMaterial, Object3DEventMap>;
type PlaneMeshMap = {
[key in Orientation]: PlaneMesh;
};
export function createClippingPlanes(
renderer: WebGLRenderer,
camera: PerspectiveCamera,
orbitControls: OrbitControls,
extent: Extent
) {
const center = getCenter3D(extent);
const planesData = [
{
normal: new Vector3(1, 0, 0),
d: -extent.xmin,
orientation: Orientation.X,
},
{
normal: new Vector3(0, 1, 0),
d: -extent.ymin,
orientation: Orientation.Y,
},
{
normal: new Vector3(0, 0, -1),
d: extent.zmax,
orientation: Orientation.Z,
},
];
const width = extent.xmax - extent.xmin;
const height = extent.ymax - extent.ymin;
const d = extent.zmax;
const planeMeshes: Mesh<
PlaneGeometry,
MeshBasicMaterial,
Object3DEventMap
>[] = [];
const planes: Plane[] = [];
let planeMeshMap = {} as Partial<PlaneMeshMap>;
for (let p of planesData) {
let name;
let planeCenter;
let width;
let height;
if (p.orientation === Orientation.X) {
name = Orientation.X;
width = extent.ymax - extent.ymin;
height = extent.zmax - extent.zmin;
planeCenter = new Vector3(
-p.d,
extent.ymax - width / 2,
extent.zmax - height / 2
);
} else if (p.orientation === Orientation.Y) {
name = Orientation.Y;
width = extent.xmax - extent.xmin;
height = extent.zmax - extent.zmin;
planeCenter = new Vector3(
extent.xmax - width / 2,
-p.d,
extent.zmax - height / 2
);
} else {
name = Orientation.Z;
width = extent.xmax - extent.xmin;
height = extent.ymax - extent.ymin;
planeCenter = new Vector3(
extent.xmax - width / 2,
extent.ymax - height / 2,
p.d
);
}
// Visual representation of the clipping Plane
// Plane is given in Hesse normal form
const normalVector = new Vector3(0, 0, -1);
const plane = new Plane(normalVector, d);
// Visual representation of the clipping plane
// Plane is given in Hesse normal form
const plane = new Plane(p.normal, p.d);
// Dragging Mechanism
const planeMesh = new Mesh(
new PlaneGeometry(width, height),
new MeshBasicMaterial({
visible: true,
color: 0xff0000,
transparent: true,
opacity: 0.1,
side: DoubleSide,
})
);
planeMesh.position.set(center.x, center.y, d);
// Dragging Mechanism
const planeMesh = new Mesh(
new PlaneGeometry(width, height),
new MeshBasicMaterial({
visible: true,
color: 0xff0000,
transparent: true,
opacity: 0.1,
side: DoubleSide,
clipIntersection: false,
})
);
planeMesh.name = name;
planeMesh.userData.plane = plane;
planeMesh.position.set(planeCenter.x, planeCenter.y, planeCenter.z);
if (p.orientation === Orientation.X) {
planeMesh.rotateY(Math.PI / 2);
planeMesh.rotateZ(Math.PI / 2);
} else if (p.orientation === Orientation.Y) {
planeMesh.rotateX(Math.PI / 2);
}
planeMeshes.push(planeMesh);
planes.push(plane);
planeMeshMap[p.orientation] = planeMesh;
}
for (let pm of planeMeshes) {
// Let clipping planes clip each other
const clippingPlanes = planes.filter(
(p) => !p.normal.equals(pm.userData.plane.normal)
);
pm.material.clippingPlanes = clippingPlanes;
}
// Enable DragControls for the clipping planes
const dragControls = new DragControls(
[planeMesh],
planeMeshes,
camera,
renderer.domElement
);
// Disable OrbitControls when dragging starts
dragControls.addEventListener("dragstart", () => {
// Disable OrbitControls when dragging starts
orbitControls.enabled = false;
});
// Re-enable OrbitControls when dragging ends
dragControls.addEventListener("dragend", () => {
// Reenable OrbitControls when dragging ends
orbitControls.enabled = true;
});
dragControls.addEventListener("drag", (event) => {
const newZ = event.object.position.z;
plane.constant = newZ;
planeMesh.position.x = center.x;
planeMesh.position.y = center.y;
const object = event.object as PlaneMesh;
const plane = event.object.userData.plane;
const width = object.geometry.parameters.width;
const height = object.geometry.parameters.height;
if (object.name === Orientation.Z) {
// Fix rotation of dragged mesh
event.object.rotation.set(0, 0, 0);
let newZ;
if (event.object.position.z > extent.zmax) {
newZ = extent.zmax;
} else if (event.object.position.z < extent.zmin) {
newZ = extent.zmin;
} else {
newZ = event.object.position.z;
}
// Reset position of plane
plane.constant = newZ;
// Set position of dragged mesh
object.position.x = extent.xmax - width / 2;
object.position.y = extent.ymax - height / 2;
object.position.z = newZ;
// Resize other meshes
resizeMeshes(Orientation.Z, newZ, planeMeshMap as PlaneMeshMap, extent);
} else if (object.name === Orientation.Y) {
// Fix rotation of dragged mesh
event.object.rotation.set(Math.PI / 2, 0, 0);
let newY;
if (event.object.position.y > extent.ymax) {
newY = extent.ymax;
} else if (event.object.position.y < extent.ymin) {
newY = extent.ymin;
} else {
newY = event.object.position.y;
}
// Reset position of plane
plane.constant = -newY;
// Set position of dragged mesh
object.position.x = extent.xmax - width / 2;
object.position.y = newY;
object.position.z = extent.zmax - height / 2;
// Resize other meshes
resizeMeshes(Orientation.Y, newY, planeMeshMap as PlaneMeshMap, extent);
} else {
// Fix rotation of dragged mesh
event.object.rotation.set(0, Math.PI / 2, Math.PI / 2);
let newX;
if (event.object.position.x > extent.xmax) {
newX = extent.xmax;
} else if (event.object.position.x < extent.xmin) {
newX = extent.xmin;
} else {
newX = event.object.position.x;
}
// Reset position of plane
plane.constant = -newX;
// Set position of dragged mesh
object.position.x = newX;
object.position.y = extent.ymax - width / 2;
object.position.z = extent.zmax - height / 2;
// Resize other meshes
resizeMeshes(Orientation.X, newX, planeMeshMap as PlaneMeshMap, extent);
}
});
return { planeMesh, plane };
return { planeMeshes, planes };
}
function resizeMeshes(
orientation: Orientation,
newCoordinate: number,
planeMeshes: PlaneMeshMap,
extent: Extent
) {
if (orientation === Orientation.X) {
// Resize y-clipping plane
let planeMesh = planeMeshes[Orientation.Y];
let width = extent.xmax - newCoordinate;
let height = planeMesh.geometry.parameters.height;
const y = planeMesh.position.y;
planeMesh.geometry.dispose();
planeMesh.geometry = new PlaneGeometry(width, height);
planeMesh.position.set(
extent.xmax - width / 2,
y,
extent.zmax - height / 2
);
// Resize z-clipping-plane
planeMesh = planeMeshes[Orientation.Z];
width = extent.xmax - newCoordinate;
height = planeMesh.geometry.parameters.height;
const z = planeMesh.position.z;
planeMesh.geometry.dispose();
planeMesh.geometry = new PlaneGeometry(width, height);
planeMesh.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 width = extent.ymax - newCoordinate;
let height = planeMesh.geometry.parameters.height;
const x = planeMesh.position.x;
planeMesh.geometry.dispose();
planeMesh.geometry = new PlaneGeometry(width, height);
planeMesh.position.set(
x,
extent.ymax - width / 2,
extent.zmax - height / 2
);
// Resize z-clipping-plane
planeMesh = planeMeshes[Orientation.Z];
width = planeMesh.geometry.parameters.width;
height = extent.ymax - newCoordinate;
const z = planeMesh.position.z;
planeMesh.geometry.dispose();
planeMesh.geometry = new PlaneGeometry(width, height);
planeMesh.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 width = planeMesh.geometry.parameters.width;
let height = newCoordinate - extent.zmin;
const x = planeMesh.position.x;
planeMesh.geometry.dispose();
planeMesh.geometry = new PlaneGeometry(width, height);
planeMesh.position.set(
x,
extent.ymax - width / 2,
extent.zmax - height / 2
);
// Resize y-clipping plane
planeMesh = planeMeshes[Orientation.Y];
width = planeMesh.geometry.parameters.width;
height = newCoordinate - extent.zmin;
const y = planeMesh.position.y;
planeMesh.geometry.dispose();
planeMesh.geometry = new PlaneGeometry(width, height);
planeMesh.position.set(
extent.xmax - width / 2,
y,
extent.zmax - height / 2
);
}
}

View file

@ -3,12 +3,16 @@ import {
GridHelper,
Sprite,
SpriteMaterial,
Vector3,
Vector4,
} from "three";
import { Extent } from "./build-scene";
import { getCenter3D } from "./utils";
enum Orientation {
Horizontal,
Vertical,
}
export function buildGrid(extent: Extent) {
const center = getCenter3D(extent);
// Calculate the width and height of the grid
@ -38,7 +42,7 @@ export function buildGrid(extent: Extent) {
const z = positionAttr.getZ(i);
const v = new Vector4(x + center.x, z + center.y, 0, 1);
if (i % 4 === 0 && i > 4) {
if (i % 4 === 0) {
startingPointsVertical.push(v);
} else if (i % 2 == 0) {
startingPointsHorizontal.push(v);
@ -47,12 +51,20 @@ export function buildGrid(extent: Extent) {
const annotations = [];
for (let point of startingPointsHorizontal) {
const label = createLabel(`${point.x.toFixed(2)}`, point);
const label = createLabel(
`${point.x.toFixed(2)}`,
point,
Orientation.Horizontal
);
annotations.push(label);
}
for (let point of startingPointsVertical) {
const label = createLabel(`${point.y.toFixed(2)}`, point);
const label = createLabel(
`${point.y.toFixed(2)}`,
point,
Orientation.Vertical
);
annotations.push(label);
}
@ -60,31 +72,39 @@ export function buildGrid(extent: Extent) {
}
// Function to create annotation (sprite with text)
function createLabel(text: string, position: Vector4) {
function createLabel(
text: string,
position: Vector4,
orientation: Orientation
) {
const spriteMaterial = new SpriteMaterial({
map: new CanvasTexture(generateTextCanvas(text)), // Create text texture
map: new CanvasTexture(generateTextCanvas(text, orientation)), // Create text texture
transparent: true,
});
const sprite = new Sprite(spriteMaterial);
sprite.position.set(position.x, position.y, position.z);
sprite.scale.set(10000, 5000, 1); // Scale the sprite to make the text readable
sprite.scale.set(5000, 2500, 1); // Scale the sprite to make the text readable
return sprite;
}
// Function to generate a text canvas for the annotation
function generateTextCanvas(text: string) {
function generateTextCanvas(text: string, orientation: Orientation) {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (context) {
// Set a background color for the canvas to make it visible
canvas.width = 800; // Set a fixed width for the canvas
canvas.height = 160; // Set a fixed height for the canvas
canvas.width = 800;
canvas.height = 160;
// Set the text style
context.font = "45px Arial";
context.fillStyle = "black"; // Text color
context.fillText(text, 400, 80); // Draw the text on the canvas
context.fillStyle = "black";
if (orientation === Orientation.Horizontal) {
context.fillText(text, 300, 160);
} else {
context.fillText(text, 100, 90);
}
}
return canvas;

View file

@ -54,7 +54,7 @@ async function buildMesh(layerData: MappedFeature, clippingPlanes: Plane[]) {
side: DoubleSide,
wireframe: false,
clippingPlanes: clippingPlanes,
clipIntersection: true,
clipIntersection: false,
});
// material.onBeforeCompile = (materialShader) => {

View file

@ -3,7 +3,7 @@ import { buildMeshes } from "./build-meshes";
import { Extent, buildScene } from "./build-scene";
import { getMetadata } from "./get-metadata";
import { MODEL_ID, SERVICE_URL } from "../config";
import { createClippingPlane } from "./build-clipping-plane";
import { createClippingPlanes } from "./build-clipping-plane";
import { buildGrid } from "./build-grid";
export async function init(container: HTMLElement) {
@ -25,17 +25,15 @@ export async function init(container: HTMLElement) {
extent
);
const { planeMesh, plane } = createClippingPlane(
const { planeMeshes, planes } = createClippingPlanes(
renderer,
camera,
controls,
extent
);
scene.add(planeMesh);
scene.add(...planeMeshes);
const clippingPlanes = [plane];
const meshes = await buildMeshes(mappedFeatures, clippingPlanes);
const meshes = await buildMeshes(mappedFeatures, planes);
const mappedFeaturesGroup = new Group();
mappedFeaturesGroup.add(...meshes);
scene.add(mappedFeaturesGroup);