From 8434f3bdc9aac023d318822813a9cbf49ac13796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20P=2E?= Date: Wed, 7 Jan 2026 16:55:10 +0100 Subject: [PATCH] Draggable clipping plane (buggy...) TODO: refactor to avoid unnecessary object creation --- index.html | 1 + js/scene.js | 111 ++++++++++++++++++++++++++++++++++----------------- js/state.js | 8 +++- package.json | 3 +- yarn.lock | 5 +++ 5 files changed, 89 insertions(+), 39 deletions(-) diff --git a/index.html b/index.html index 70ef647..6c166ea 100644 --- a/index.html +++ b/index.html @@ -46,6 +46,7 @@ + diff --git a/js/scene.js b/js/scene.js index b3360e4..1b0dd4d 100644 --- a/js/scene.js +++ b/js/scene.js @@ -3,12 +3,6 @@ import { AppState, getSceneStatus, setSceneStatus, getCurrentScene, setCurrentScene } from "./state.js"; import { config } from "../config.js"; -const material = { - color: "#fff", - //color: "#e6f2ff", - emissive: true, -}; - const Scene = {}; Scene.UI = {}; @@ -32,7 +26,7 @@ Scene.UI.toggleClipper = function(triggerSelector, targetSelector) { const trigger = document.querySelector(triggerSelector); const toolbar = document.querySelector(targetSelector); - if (!AppState.clipping.listenerAdded) { + if (!AppState.clipping.listeners.button) { trigger.addEventListener( 'click', () => { @@ -95,7 +89,7 @@ Scene.UI.toggleClipper = function(triggerSelector, targetSelector) { } } ); - AppState.clipping.listenerAdded = true; + AppState.clipping.listeners.button = true; } } @@ -123,6 +117,7 @@ Scene.showEdges = function(object) { * and return it along with its center and size (bad?). * It only uses meshes to prevent "empty" nodes in the scene * from being included in the calculation. + * @todo Use ATON.Node.getBound()? [bounding sphere] */ Scene.getRootBoundingBox = function() { const meshes = []; @@ -149,8 +144,8 @@ Scene.getRootBoundingBox = function() { * @returns {THREE.Mesh} */ Scene.createClippingPlaneMesh = function(rootBBoxSize) { - const averageDim = (Number(rootBBoxSize.x) + Number(rootBBoxSize.y) + Number(rootBBoxSize.z)) / 3; - const planeSize = averageDim * 1.2; + //const averageDim = (Number(rootBBoxSize.x) + Number(rootBBoxSize.y) + Number(rootBBoxSize.z)) / 3; + const planeSize = rootBBoxSize.length() * 1.2; const mesh = new THREE.Mesh( new THREE.PlaneGeometry(planeSize, planeSize), new THREE.MeshBasicMaterial({ color: 0xffff00, opacity: 0.1, side: THREE.DoubleSide, transparent: true }) @@ -159,6 +154,40 @@ Scene.createClippingPlaneMesh = function(rootBBoxSize) { return mesh; } +/** + * + * @param {THREE.Mesh} planeMesh + * @param {String} axis + */ +Scene.dragClipper = function(planeMesh, axis) { + const controls = new THREE.DragControls( + [planeMesh], + AppState.camera, + AppState.renderer.domElement, + ); + + const startPosition = new THREE.Vector3(); + // Only move along the selected axis (exlude the others) + const excludedAxes = ['x', 'y', 'z'].filter(a => a != axis); + + controls.addEventListener('dragstart', function (event) { + startPosition.copy(event.object.position); + ATON.Nav.setUserControl(false); + }); + + const bboxData = AppState.clipping.rootBoundingBox ?? this.getRootBoundingBox(); + controls.addEventListener('drag', function (event) { + const point = event.object.position; + Scene.updateClipper(AppState.clipping.vector, point, bboxData); + for (const a of excludedAxes) { + event.object.position[a] = startPosition[a]; + } + }); + controls.addEventListener('dragend', function (event) { + ATON.Nav.setUserControl(true); + }); +} + /** * @param {String} axis - The axis along which the plane's normal should be directed, * one of 'x', 'y', 'z' @@ -170,55 +199,61 @@ Scene.addClippingPlane = function(axis, orientation = -1) { if (!bboxData) return; - const defaultPoint = bboxData.center.clone(); - const vector = [ axis === 'x' ? orientation : 0, axis === 'y' ? orientation : 0, axis === 'z' ? orientation : 0, ]; + AppState.clipping.vector = vector; + // First, add a default clipping plane // at a default point (calculated...) - Scene.activateClipper(vector, defaultPoint); - console.log(vector, defaultPoint); - - window.addEventListener('mousedown', event => { - // Activate clipping when left-clicking on the scene - if (AppState.clipping.enabled && event.buttons === 1) { - Scene.activateClipper(vector); - } - }); + const defaultPoint = bboxData.center.clone(); + Scene.activateClipper(vector, axis, defaultPoint); } /** * @todo WIP! * Activate clipping plane * @param {Number[]} vector - The vector array to direct the plane + * @param {String} axis - The x,y,z axis + * @param {?THREE.Vector3} point - The queried scene point */ -Scene.activateClipper = function(vector, point = null) { +Scene.activateClipper = function(vector, axis, point = null) { point ??= ATON.getSceneQueriedPoint(); const bboxData = AppState.clipping.rootBoundingBox ?? this.getRootBoundingBox(); if (point) { - console.log('Queried point:', point); + Scene.updateClipper(vector, point, bboxData); + Scene.dragClipper(AppState.clipping.helper, axis); + } +} - // First remove any existing clipping planes - ATON.disableClipPlanes(); - // Normal of the clipping plane along the Y axis facing down - const normal = new THREE.Vector3(...vector).normalize(); - //const constant = -normal.dot(point); - const plane = ATON.addClipPlane(normal, point); - // Add a visible plane helper for the clipping plane - const visiblePlane = Scene.createClippingPlaneMesh(bboxData.size); - visiblePlane.position.copy(point); - visiblePlane.lookAt(point.clone().add(normal)); - // Remove any already visbile helper plane - if (AppState.clipping.helper !== null) AppState.root.remove(AppState.clipping.helper); +/** + * + * @param {THREE.Vector3} vector + * @param {THREE.Vector3} point + * @param {Object} bboxData + */ +Scene.updateClipper = function(vector, point, bboxData) { + // First remove any existing clipping planes + ATON.disableClipPlanes(); + // Normal of the clipping plane along the Y axis facing down + const normal = new THREE.Vector3(...vector).normalize(); + //const constant = -normal.dot(point); + const plane = ATON.addClipPlane(normal, point); + // Add a visible plane helper for the clipping plane + const visiblePlane = AppState.clipping.helper ?? Scene.createClippingPlaneMesh(bboxData.size); + + // Remove any already visbile helper plane + if (!AppState.clipping.helper) { AppState.root.add(visiblePlane); AppState.clipping.helper = visiblePlane; - console.log("I'm clipping, baby!"); - } + } + + visiblePlane.position.copy(point); + visiblePlane.lookAt(point.clone().add(normal)); } /** @@ -383,6 +418,8 @@ Scene.init = function() { Scene.toggleSettingsPanel('settings'); Scene.toggleContentMenu('menu'); + AppState.camera = ATON.Nav._camera; + AppState.renderer = ATON._renderer; } /** * @param {String} btnId - The back-to-map button id diff --git a/js/state.js b/js/state.js index af92dc3..e098330 100644 --- a/js/state.js +++ b/js/state.js @@ -2,6 +2,8 @@ export const AppState = { // The root scene object root: null, initialRotation: null, + camera: ATON.Nav._camera, + renderer: ATON._renderer, scenes : [], ambientOcclusion : true, shadows : true, @@ -11,7 +13,11 @@ export const AppState = { enabled: false, helper : null, rootBoundingBox: null, - listenerAdded: false, + listeners: { + button: false, + plane: false, + }, + vector: null, } } diff --git a/package.json b/package.json index 97407e0..30a3b63 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "author": "Nicolò P. ", "license": "MIT", "dependencies": { - "leaflet": "^1.9.4" + "leaflet": "^1.9.4", + "three": "0.147" } } diff --git a/yarn.lock b/yarn.lock index 9b202fc..90970f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,3 +6,8 @@ leaflet@^1.9.4: version "1.9.4" resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.9.4.tgz#23fae724e282fa25745aff82ca4d394748db7d8d" integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA== + +three@0.147: + version "0.147.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.147.0.tgz#1974af9e8e0c1efb3a8561334d57c0b6c29a7951" + integrity sha512-LPTOslYQXFkmvceQjFTNnVVli2LaVF6C99Pv34fJypp8NbQLbTlu3KinZ0zURghS5zEehK+VQyvWuPZ/Sm8fzw==