Working clipping functionality
This commit is contained in:
parent
13be63c40a
commit
20d99b5815
4 changed files with 313 additions and 50 deletions
|
@ -2,6 +2,7 @@ import {
|
||||||
DoubleSide,
|
DoubleSide,
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
|
Object3DEventMap,
|
||||||
PerspectiveCamera,
|
PerspectiveCamera,
|
||||||
Plane,
|
Plane,
|
||||||
PlaneGeometry,
|
PlaneGeometry,
|
||||||
|
@ -10,60 +11,304 @@ import {
|
||||||
} from "three";
|
} from "three";
|
||||||
import { DragControls, OrbitControls } from "three/examples/jsm/Addons.js";
|
import { DragControls, OrbitControls } from "three/examples/jsm/Addons.js";
|
||||||
import { Extent } from "./build-scene";
|
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,
|
renderer: WebGLRenderer,
|
||||||
camera: PerspectiveCamera,
|
camera: PerspectiveCamera,
|
||||||
orbitControls: OrbitControls,
|
orbitControls: OrbitControls,
|
||||||
extent: Extent
|
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 planeMeshes: Mesh<
|
||||||
const height = extent.ymax - extent.ymin;
|
PlaneGeometry,
|
||||||
const d = extent.zmax;
|
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
|
// Visual representation of the clipping plane
|
||||||
// Plane is given in Hesse normal form
|
// Plane is given in Hesse normal form
|
||||||
const normalVector = new Vector3(0, 0, -1);
|
const plane = new Plane(p.normal, p.d);
|
||||||
const plane = new Plane(normalVector, d);
|
|
||||||
|
|
||||||
// Dragging Mechanism
|
// Dragging Mechanism
|
||||||
const planeMesh = new Mesh(
|
const planeMesh = new Mesh(
|
||||||
new PlaneGeometry(width, height),
|
new PlaneGeometry(width, height),
|
||||||
new MeshBasicMaterial({
|
new MeshBasicMaterial({
|
||||||
visible: true,
|
visible: true,
|
||||||
color: 0xff0000,
|
color: 0xff0000,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.1,
|
opacity: 0.1,
|
||||||
side: DoubleSide,
|
side: DoubleSide,
|
||||||
})
|
clipIntersection: false,
|
||||||
);
|
})
|
||||||
planeMesh.position.set(center.x, center.y, d);
|
);
|
||||||
|
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(
|
const dragControls = new DragControls(
|
||||||
[planeMesh],
|
planeMeshes,
|
||||||
camera,
|
camera,
|
||||||
renderer.domElement
|
renderer.domElement
|
||||||
);
|
);
|
||||||
|
|
||||||
// Disable OrbitControls when dragging starts
|
|
||||||
dragControls.addEventListener("dragstart", () => {
|
dragControls.addEventListener("dragstart", () => {
|
||||||
|
// Disable OrbitControls when dragging starts
|
||||||
orbitControls.enabled = false;
|
orbitControls.enabled = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-enable OrbitControls when dragging ends
|
|
||||||
dragControls.addEventListener("dragend", () => {
|
dragControls.addEventListener("dragend", () => {
|
||||||
|
// Reenable OrbitControls when dragging ends
|
||||||
orbitControls.enabled = true;
|
orbitControls.enabled = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
dragControls.addEventListener("drag", (event) => {
|
dragControls.addEventListener("drag", (event) => {
|
||||||
const newZ = event.object.position.z;
|
const object = event.object as PlaneMesh;
|
||||||
plane.constant = newZ;
|
const plane = event.object.userData.plane;
|
||||||
planeMesh.position.x = center.x;
|
const width = object.geometry.parameters.width;
|
||||||
planeMesh.position.y = center.y;
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,16 @@ import {
|
||||||
GridHelper,
|
GridHelper,
|
||||||
Sprite,
|
Sprite,
|
||||||
SpriteMaterial,
|
SpriteMaterial,
|
||||||
Vector3,
|
|
||||||
Vector4,
|
Vector4,
|
||||||
} from "three";
|
} from "three";
|
||||||
import { Extent } from "./build-scene";
|
import { Extent } from "./build-scene";
|
||||||
import { getCenter3D } from "./utils";
|
import { getCenter3D } from "./utils";
|
||||||
|
|
||||||
|
enum Orientation {
|
||||||
|
Horizontal,
|
||||||
|
Vertical,
|
||||||
|
}
|
||||||
|
|
||||||
export function buildGrid(extent: Extent) {
|
export function buildGrid(extent: Extent) {
|
||||||
const center = getCenter3D(extent);
|
const center = getCenter3D(extent);
|
||||||
// Calculate the width and height of the grid
|
// Calculate the width and height of the grid
|
||||||
|
@ -38,7 +42,7 @@ export function buildGrid(extent: Extent) {
|
||||||
const z = positionAttr.getZ(i);
|
const z = positionAttr.getZ(i);
|
||||||
const v = new Vector4(x + center.x, z + center.y, 0, 1);
|
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);
|
startingPointsVertical.push(v);
|
||||||
} else if (i % 2 == 0) {
|
} else if (i % 2 == 0) {
|
||||||
startingPointsHorizontal.push(v);
|
startingPointsHorizontal.push(v);
|
||||||
|
@ -47,12 +51,20 @@ export function buildGrid(extent: Extent) {
|
||||||
|
|
||||||
const annotations = [];
|
const annotations = [];
|
||||||
for (let point of startingPointsHorizontal) {
|
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);
|
annotations.push(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let point of startingPointsVertical) {
|
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);
|
annotations.push(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,31 +72,39 @@ export function buildGrid(extent: Extent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to create annotation (sprite with text)
|
// 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({
|
const spriteMaterial = new SpriteMaterial({
|
||||||
map: new CanvasTexture(generateTextCanvas(text)), // Create text texture
|
map: new CanvasTexture(generateTextCanvas(text, orientation)), // Create text texture
|
||||||
transparent: true,
|
transparent: true,
|
||||||
});
|
});
|
||||||
const sprite = new Sprite(spriteMaterial);
|
const sprite = new Sprite(spriteMaterial);
|
||||||
sprite.position.set(position.x, position.y, position.z);
|
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;
|
return sprite;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to generate a text canvas for the annotation
|
// 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 canvas = document.createElement("canvas");
|
||||||
const context = canvas.getContext("2d");
|
const context = canvas.getContext("2d");
|
||||||
|
|
||||||
if (context) {
|
if (context) {
|
||||||
// Set a background color for the canvas to make it visible
|
canvas.width = 800;
|
||||||
canvas.width = 800; // Set a fixed width for the canvas
|
canvas.height = 160;
|
||||||
canvas.height = 160; // Set a fixed height for the canvas
|
|
||||||
|
|
||||||
// Set the text style
|
// Set the text style
|
||||||
context.font = "45px Arial";
|
context.font = "45px Arial";
|
||||||
context.fillStyle = "black"; // Text color
|
context.fillStyle = "black";
|
||||||
context.fillText(text, 400, 80); // Draw the text on the canvas
|
|
||||||
|
if (orientation === Orientation.Horizontal) {
|
||||||
|
context.fillText(text, 300, 160);
|
||||||
|
} else {
|
||||||
|
context.fillText(text, 100, 90);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
|
|
|
@ -54,7 +54,7 @@ async function buildMesh(layerData: MappedFeature, clippingPlanes: Plane[]) {
|
||||||
side: DoubleSide,
|
side: DoubleSide,
|
||||||
wireframe: false,
|
wireframe: false,
|
||||||
clippingPlanes: clippingPlanes,
|
clippingPlanes: clippingPlanes,
|
||||||
clipIntersection: true,
|
clipIntersection: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// material.onBeforeCompile = (materialShader) => {
|
// material.onBeforeCompile = (materialShader) => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { buildMeshes } from "./build-meshes";
|
||||||
import { Extent, buildScene } from "./build-scene";
|
import { Extent, buildScene } from "./build-scene";
|
||||||
import { getMetadata } from "./get-metadata";
|
import { getMetadata } from "./get-metadata";
|
||||||
import { MODEL_ID, SERVICE_URL } from "../config";
|
import { MODEL_ID, SERVICE_URL } from "../config";
|
||||||
import { createClippingPlane } from "./build-clipping-plane";
|
import { createClippingPlanes } from "./build-clipping-plane";
|
||||||
import { buildGrid } from "./build-grid";
|
import { buildGrid } from "./build-grid";
|
||||||
|
|
||||||
export async function init(container: HTMLElement) {
|
export async function init(container: HTMLElement) {
|
||||||
|
@ -25,17 +25,15 @@ export async function init(container: HTMLElement) {
|
||||||
extent
|
extent
|
||||||
);
|
);
|
||||||
|
|
||||||
const { planeMesh, plane } = createClippingPlane(
|
const { planeMeshes, planes } = createClippingPlanes(
|
||||||
renderer,
|
renderer,
|
||||||
camera,
|
camera,
|
||||||
controls,
|
controls,
|
||||||
extent
|
extent
|
||||||
);
|
);
|
||||||
scene.add(planeMesh);
|
scene.add(...planeMeshes);
|
||||||
|
|
||||||
const clippingPlanes = [plane];
|
const meshes = await buildMeshes(mappedFeatures, planes);
|
||||||
|
|
||||||
const meshes = await buildMeshes(mappedFeatures, clippingPlanes);
|
|
||||||
const mappedFeaturesGroup = new Group();
|
const mappedFeaturesGroup = new Group();
|
||||||
mappedFeaturesGroup.add(...meshes);
|
mappedFeaturesGroup.add(...meshes);
|
||||||
scene.add(mappedFeaturesGroup);
|
scene.add(mappedFeaturesGroup);
|
||||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue