Fix bugs related to z scaling

This commit is contained in:
Fuhrmann 2025-03-28 13:01:52 +01:00
parent e7ff4c5493
commit e3a4018582
6 changed files with 156 additions and 110 deletions

View file

@ -140,10 +140,14 @@ export function Form() {
const [emptyProfile, setEmptyProfile] = useState<boolean>(false); const [emptyProfile, setEmptyProfile] = useState<boolean>(false);
const { sceneView } = useContext(SceneViewContext) as SceneViewContextType; const { sceneView } = useContext(SceneViewContext) as SceneViewContextType;
function handleChangeSlicingBox() { function handleChangeSlicingBox(e: ChangeEvent<HTMLInputElement>) {
if (!sceneView) return; if (!sceneView) return;
sceneView.toggleClippingBox(); if (e.target.checked) {
sceneView.toggleClippingBox(true);
} else {
sceneView.toggleClippingBox(false);
}
} }
function handleChangeCG() { function handleChangeCG() {
@ -217,10 +221,8 @@ export function Form() {
if (e.target.checked) { if (e.target.checked) {
sceneView.explode(true); sceneView.explode(true);
// setExploded(true);
} else { } else {
sceneView.explode(false); sceneView.explode(false);
// setExploded(false);
} }
} }

View file

@ -14,7 +14,7 @@ export function RangeSlider() {
const z = parseFloat(t.value); const z = parseFloat(t.value);
if (!isNaN(z)) { if (!isNaN(z)) {
setScale(z); setScale(z);
sceneView.scene.scale.set(1, 1, z); sceneView.setZScale(z);
} }
}; };
return ( return (

View file

@ -45,7 +45,6 @@ export type CustomEvent = CustomEventInit<{
export class SceneView extends EventTarget { export class SceneView extends EventTarget {
private _scene: Scene; private _scene: Scene;
private _dragControls: DragControls;
private _model: Group; private _model: Group;
private _camera: PerspectiveCamera; private _camera: PerspectiveCamera;
private _container: HTMLElement; private _container: HTMLElement;
@ -57,12 +56,13 @@ export class SceneView extends EventTarget {
private static _DRAG_THRESHOLD = 5; private static _DRAG_THRESHOLD = 5;
private _callback: EventListenerOrEventListenerObject | null = null; private _callback: EventListenerOrEventListenerObject | null = null;
private _orbitControls: OrbitControls; private _orbitControls: OrbitControls;
private _dragControls: DragControls | null = null;
private _renderer: WebGLRenderer; private _renderer: WebGLRenderer;
private static _DISPLACEMENT = 2000;
constructor( constructor(
scene: Scene, scene: Scene,
model: Group, model: Group,
dragControls: DragControls,
camera: PerspectiveCamera, camera: PerspectiveCamera,
container: HTMLElement, container: HTMLElement,
extent: Extent, extent: Extent,
@ -71,7 +71,6 @@ export class SceneView extends EventTarget {
) { ) {
super(); super();
this._scene = scene; this._scene = scene;
this._dragControls = dragControls;
this._model = model; this._model = model;
this._camera = camera; this._camera = camera;
this._container = container; this._container = container;
@ -84,13 +83,11 @@ export class SceneView extends EventTarget {
static async create(container: HTMLElement, modelId: string) { static async create(container: HTMLElement, modelId: string) {
const data = await init(container, modelId); const data = await init(container, modelId);
if (data) { if (data) {
const { scene, model, dragControls, camera, extent, controls, renderer } = const { scene, model, camera, extent, controls, renderer } = data;
data;
return new SceneView( return new SceneView(
scene, scene,
model, model,
dragControls,
camera, camera,
container, container,
extent, extent,
@ -110,17 +107,11 @@ export class SceneView extends EventTarget {
return this._model; return this._model;
} }
toggleClippingBox() { toggleClippingBox(on = true) {
const box = this._scene?.getObjectByName("clipping-box"); if (on) {
if (box) { this._resetClippingBox();
// Set DragControls
if (box.visible) {
this._dragControls.enabled = false;
} else { } else {
this._dragControls.enabled = true; this._removeClippingBoxObjects();
}
box.visible = !box.visible;
} }
} }
@ -344,57 +335,12 @@ export class SceneView extends EventTarget {
this._orbitControls.reset(); this._orbitControls.reset();
} }
explode(explode: boolean) { private _removeClippingBoxObjects() {
const DISPLACEMENT = 2000; // Remove existing boxes
for (let i = 1; i < this._model.children.length; i++) {
const mesh = this._model.children[i];
if (explode) {
const displacement =
(this._model.children.length - i - 1) * DISPLACEMENT;
mesh.userData.originalPosition = mesh.position.clone();
mesh.translateZ(displacement);
if (i === 1) {
this._model.userData.zmax = this._extent.zmax;
this._extent.zmax += displacement;
}
} else {
if (mesh.userData.originalPosition) {
mesh.position.copy(mesh.userData.originalPosition);
}
}
}
// Reset extent
if (!explode && 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;
if (box) { if (box) {
visible = box.visible;
this._scene.remove(box); this._scene.remove(box);
} }
const { planes, dragControls } = buildClippingplanes(
this._renderer,
this._camera,
this._orbitControls,
this._extent,
this._model.children as Mesh[],
this._scene,
visible
);
this._dragControls.dispose();
this._dragControls = dragControls;
// Add clipping planes to the meshes
for (const mesh of this._model.children) {
((mesh as Mesh).material as Material).clippingPlanes = planes;
}
// Remove existing cap meshes // Remove existing cap meshes
for (const o in Orientation) { for (const o in Orientation) {
@ -405,11 +351,101 @@ export class SceneView extends EventTarget {
capMeshGroup = this._scene.getObjectByName(capMeshGroupName); capMeshGroup = this._scene.getObjectByName(capMeshGroupName);
} }
} }
// Remove drag controls
if (this._dragControls) {
this._dragControls.dispose();
this._dragControls = null;
}
// Remove clipping planes
for (const mesh of this._model.children) {
((mesh as Mesh).material as Material).clippingPlanes = null;
}
}
// Reset clipping box
private _resetClippingBox() {
this._removeClippingBoxObjects();
const { planes, dragControls } = buildClippingplanes(
this._renderer,
this._camera,
this._orbitControls,
this._extent,
this._model.children as Mesh[],
this._scene
);
this._dragControls = dragControls;
// Add clipping planes to the meshes
for (const mesh of this._model.children) {
((mesh as Mesh).material as Material).clippingPlanes = planes;
}
}
// Explode meshes
explode(explode: boolean) {
if (explode) {
// Save previous zmax value
this._scene.userData.zmax = this._extent.zmax;
const maxDisplacement =
this._model.children.length * SceneView._DISPLACEMENT;
this._extent.zmax += maxDisplacement;
} else {
// Reset extent
this._extent.zmax = this._scene.userData.zmax;
}
// Reset clipping box
const box = this._scene.getObjectByName("clipping-box");
if (box && box.visible) {
this._resetClippingBox();
}
for (let i = 1; i < this._model.children.length; i++) {
const mesh = this._model.children[i];
if (explode) {
const displacement =
(this._model.children.length - i - 1) * SceneView._DISPLACEMENT;
mesh.userData.originalPosition = mesh.position.clone();
mesh.translateZ(displacement);
} else {
if (mesh.userData.originalPosition) {
mesh.position.copy(mesh.userData.originalPosition);
}
}
}
}
// Set z scaling factor
setZScale(scale: number) {
// Update extent
//this._extent = {
// ...this._extent,
// zmin: (scale * this._extent.zmin) / this._scene.scale.z,
// zmax: (scale * this._extent.zmax) / this._scene.scale.z,
//};
// Set scale factor
this._scene.scale.set(1, 1, scale);
// Reset clipping box
const box = this._scene.getObjectByName("clipping-box");
if (box && box.visible) {
this._resetClippingBox();
}
} }
} }
async function init(container: HTMLElement, modelId = MODEL_ID) { async function init(container: HTMLElement, modelId = MODEL_ID) {
const modelData = await getMetadata(SERVICE_URL + modelId); const modelData = await getMetadata(SERVICE_URL + modelId);
if (!modelData) return null;
const mappedFeatures = modelData.mappedfeatures; const mappedFeatures = modelData.mappedfeatures;
const modelarea = modelData.modelarea; const modelarea = modelData.modelarea;
@ -436,23 +472,6 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
model.name = "geologic-model"; model.name = "geologic-model";
scene.add(model); scene.add(model);
// Build the clipping planes and add them to the scene
const visible = false;
const { planes, dragControls } = buildClippingplanes(
renderer,
camera,
controls,
extent,
meshes,
scene,
visible
);
// Add clipping planes to the meshes
for (const mesh of meshes) {
mesh.material.clippingPlanes = planes;
}
// Add a coordinate grid to the scene // Add a coordinate grid to the scene
const { gridHelper, annotations } = buildCoordinateGrid(extent); const { gridHelper, annotations } = buildCoordinateGrid(extent);
const { heightGridHelper, heightAnnotations } = buildHeightGrid(extent); const { heightGridHelper, heightAnnotations } = buildHeightGrid(extent);
@ -489,5 +508,12 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
map.visible = false; map.visible = false;
scene.add(map); scene.add(map);
return { scene, model, dragControls, camera, extent, controls, renderer }; return {
scene,
model,
camera,
extent,
controls,
renderer,
};
} }

View file

@ -53,8 +53,7 @@ export function buildClippingplanes(
orbitControls: OrbitControls, orbitControls: OrbitControls,
extent: Extent, extent: Extent,
meshes: Mesh[], meshes: Mesh[],
scene: Scene, scene: Scene
visible: boolean
) { ) {
// Set current extent to given extent // Set current extent to given extent
currentExtent = { ...extent }; currentExtent = { ...extent };
@ -137,7 +136,12 @@ export function buildClippingplanes(
} }
// Plane is given in Hesse normal form: a * x + b* y + c * y + d = 0, where normal = (a, b, c) and d = d // Plane is given in Hesse normal form: a * x + b* y + c * y + d = 0, where normal = (a, b, c) and d = d
const plane = new Plane(p.normal, p.d); const plane = new Plane(
p.normal,
p.orientation === Orientation.Z || p.orientation === Orientation.NZ
? scene.scale.z * p.d
: p.d
);
// Visual representation of the clipping plane // Visual representation of the clipping plane
const planeGeometry = new PlaneGeometry(width, height); const planeGeometry = new PlaneGeometry(width, height);
@ -197,7 +201,6 @@ export function buildClippingplanes(
const clippingBox = new Group(); const clippingBox = new Group();
clippingBox.add(planeMeshGroup, edgeMeshGroup); clippingBox.add(planeMeshGroup, edgeMeshGroup);
clippingBox.name = "clipping-box"; clippingBox.name = "clipping-box";
clippingBox.visible = visible;
scene.add(clippingBox); scene.add(clippingBox);
// Enable DragControls for the clipping planes // Enable DragControls for the clipping planes
@ -207,8 +210,6 @@ export function buildClippingplanes(
renderer.domElement renderer.domElement
); );
dragControls.enabled = visible;
dragControls.addEventListener("dragstart", () => { dragControls.addEventListener("dragstart", () => {
// Disable OrbitControls when dragging starts // Disable OrbitControls when dragging starts
orbitControls.enabled = false; orbitControls.enabled = false;
@ -251,6 +252,7 @@ export function buildClippingplanes(
// Reset position of plane // Reset position of plane
plane.constant = orientation === Orientation.Z ? -newZ : newZ; plane.constant = orientation === Orientation.Z ? -newZ : newZ;
plane.constant = scene.scale.z * plane.constant;
// Update current extent // Update current extent
if (orientation === Orientation.Z) { if (orientation === Orientation.Z) {
@ -578,6 +580,7 @@ function generateCapMeshes(
scene: Scene scene: Scene
) { ) {
const capMeshes: Mesh[] = []; const capMeshes: Mesh[] = [];
const scaleFactor = scene.scale.z;
// Iterate over the list of geologic meshes // Iterate over the list of geologic meshes
for (const mesh of meshes) { for (const mesh of meshes) {
@ -598,17 +601,17 @@ function generateCapMeshes(
const v1 = new Vector3( const v1 = new Vector3(
position[i1], position[i1],
position[i1 + 1], position[i1 + 1],
position[i1 + 2] + mesh.position.z scaleFactor * (position[i1 + 2] + mesh.position.z)
); );
const v2 = new Vector3( const v2 = new Vector3(
position[i2], position[i2],
position[i2 + 1], position[i2 + 1],
position[i2 + 2] + mesh.position.z scaleFactor * (position[i2 + 2] + mesh.position.z)
); );
const v3 = new Vector3( const v3 = new Vector3(
position[i3], position[i3],
position[i3 + 1], position[i3 + 1],
position[i3 + 2] + mesh.position.z scaleFactor * (position[i3 + 2] + mesh.position.z)
); );
// Check if the triangle is cut by the plane // Check if the triangle is cut by the plane
@ -624,7 +627,12 @@ function generateCapMeshes(
if (d3 * d1 < 0) intersections.push(intersectEdge(v3, v1, d3, d1)); if (d3 * d1 < 0) intersections.push(intersectEdge(v3, v1, d3, d1));
if (intersections.length === 2) { if (intersections.length === 2) {
edges.push([intersections[0], intersections[1]]); // Rescale local coordinates and push to edges
const start = intersections[0];
const end = intersections[1];
start.z = start.z / scaleFactor;
end.z = end.z / scaleFactor;
edges.push([start, end]);
} }
} }
@ -634,7 +642,13 @@ function generateCapMeshes(
// Clip cap surfaces with clipping planes // Clip cap surfaces with clipping planes
const clippingPlanes = planes.filter((p) => !p.normal.equals(plane.normal)); const clippingPlanes = planes.filter((p) => !p.normal.equals(plane.normal));
const offset = orientation === Orientation.Z ? 1 : -1; const offset =
orientation === Orientation.NX ||
orientation === Orientation.NY ||
orientation === Orientation.NZ
? 1
: -1;
const material = new MeshStandardMaterial({ const material = new MeshStandardMaterial({
color: (mesh.material as MeshStandardMaterial).color, color: (mesh.material as MeshStandardMaterial).color,
side: DoubleSide, side: DoubleSide,

View file

@ -73,7 +73,7 @@ export function buildScene(container: HTMLElement, extent: Extent) {
controls.target.set(center.x, center.y, center.z); controls.target.set(center.x, center.y, center.z);
controls.enableDamping = true; controls.enableDamping = true;
controls.dampingFactor = 0.1; controls.dampingFactor = 0.1;
controls.maxDistance = maxSize * 3; controls.maxDistance = maxSize * 5;
controls.minDistance = maxSize / 5; controls.minDistance = maxSize / 5;
controls.update(); controls.update();
controls.saveState(); controls.saveState();

View file

@ -20,6 +20,7 @@ export function getCenter3D(extent: Extent) {
} }
export async function getMetadata(serviceUrl: string) { export async function getMetadata(serviceUrl: string) {
try {
const response = await fetch(serviceUrl, { const response = await fetch(serviceUrl, {
method: "GET", method: "GET",
mode: "cors", mode: "cors",
@ -33,6 +34,9 @@ export async function getMetadata(serviceUrl: string) {
} else { } else {
throw new Error("HTTP error status: " + response.status); throw new Error("HTTP error status: " + response.status);
} }
} catch (e) {
console.log(e);
}
} }
export async function request(url: string) { export async function request(url: string) {