From 67f0bdaca5a417d4a5c28e044a95fba5bca603b0 Mon Sep 17 00:00:00 2001 From: farfalk Date: Mon, 9 Oct 2023 22:21:55 +0200 Subject: [PATCH 1/6] Raycasting for selectability of foreground objects only --- js/outliner/mesh.js | 14 ++++++++++++++ js/preview/preview.js | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/js/outliner/mesh.js b/js/outliner/mesh.js index de0e7fbbd..e55355acd 100644 --- a/js/outliner/mesh.js +++ b/js/outliner/mesh.js @@ -23,6 +23,20 @@ class MeshFace extends Face { } return this; } + getCenter(global){ + let center = [0, 0, 0]; + let len = 0; + for (let vkey in this.vertices) { + center.V3_add(this.mesh.vertices[vkey]); + len++; + } + center.V3_divide(len); + if (global) { + return this.mesh.localToWorld(Reusable.vec1.set(...center)).toArray(); + } else { + return center; + } + } getNormal(normalize) { let vertices = this.getSortedVertices(); if (vertices.length < 3) return [0, 0, 0]; diff --git a/js/preview/preview.js b/js/preview/preview.js index 080e06691..bc134ea72 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -1242,6 +1242,35 @@ class Preview { ] } + const isForeground_raycaster = new THREE.Raycaster(); + isForeground_raycaster.near = Number.EPSILON; // to avoid self-intersecting + function isForeground(parentElement, selectionTarget){ + if(Project.view_mode === 'wireframe'){ + // if View Mode is set to wireframe, all elements are visible. + // So it makes sense to make them all selectable! + // Also mirrors what happens in Blender. + return true; + } + let center = null; + let position = null; + let origin = parentElement.origin; + if(selectionTarget.mesh){ + // selectionTarget is a Mesh or a MeshFace + center = selectionTarget.getCenter(true); + } else { + // selectionTarget is a vertex position + center = selectionTarget; + } + position = [center[0]+origin[0], center[1]+origin[1], center[2]+origin[2]]; + let rc_origin = new THREE.Vector3(position[0], position[1], position[2]); + let rc_direction = new THREE.Vector3(Preview.selected.camera.position.x, Preview.selected.camera.position.y, Preview.selected.camera.position.z); + rc_direction = rc_direction.sub(rc_origin); + rc_direction.normalize(); + isForeground_raycaster.set(rc_origin, rc_direction); + let intersects = isForeground_raycaster.intersectObjects(Outliner.elements.map( el => el.mesh)); + return (intersects.length == 0); + } + unselectAll() Outliner.elements.forEach((element) => { let isSelected; @@ -1283,7 +1312,7 @@ class Preview { } else if (selection_mode == 'vertex') { for (let vkey in element.vertices) { let point = vertex_points[vkey]; - if (!mesh_selection.vertices.includes(vkey) && pointInRectangle(point, rect_start, rect_end)) { + if (!mesh_selection.vertices.includes(vkey) && pointInRectangle(point, rect_start, rect_end) && isForeground(element, element.vertices[vkey])) { mesh_selection.vertices.push(vkey); } } @@ -1300,7 +1329,7 @@ class Preview { if (lineIntersectsReactangle(p1, p2, rect_start, rect_end)) { mesh_selection.vertices.safePush(vkey, vkey2); let edge = [vkey, vkey2]; - if (!mesh_selection.edges.find(edge2 => sameMeshEdge(edge, edge2))) { + if (!mesh_selection.edges.find(edge2 => sameMeshEdge(edge, edge2)) && (isForeground(element, element.vertices[vkey]) && isForeground(element, element.vertices[vkey2]))) { mesh_selection.edges.push(edge); } } @@ -1310,6 +1339,9 @@ class Preview { } else { for (let fkey in element.faces) { let face = element.faces[fkey]; + if(!isForeground(element, face)){ + continue; + } let vertices = face.getSortedVertices(); let face_intersects; for (let i = 0; i < vertices.length; i++) { @@ -1323,7 +1355,7 @@ class Preview { } } if (selection_mode == 'object') { - if (face_intersects) { + if (face_intersects && !isForeground(element, element)) { isSelected = true; break; } From 3f8a49ab41ee77e1968ab9b67d6d5a7596879f52 Mon Sep 17 00:00:00 2001 From: farfalk Date: Mon, 9 Oct 2023 22:44:15 +0200 Subject: [PATCH 2/6] Fix object mode selection --- js/preview/preview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/preview/preview.js b/js/preview/preview.js index bc134ea72..abbb391f9 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -1355,7 +1355,7 @@ class Preview { } } if (selection_mode == 'object') { - if (face_intersects && !isForeground(element, element)) { + if (face_intersects && isForeground(element, element)) { isSelected = true; break; } From 9156fe5f193ed56c1107860e00abbf59482af044 Mon Sep 17 00:00:00 2001 From: farfalk Date: Sun, 15 Oct 2023 10:37:29 +0200 Subject: [PATCH 3/6] Performance improvements --- js/preview/preview.js | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/js/preview/preview.js b/js/preview/preview.js index abbb391f9..67fbec9b2 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -296,6 +296,7 @@ class Preview { } this.raycaster = new THREE.Raycaster(); + this.isForeground_raycaster = new THREE.Raycaster(); // specialized in selection foreground objects this.mouse = new THREE.Vector2(); addEventListeners(this.canvas, 'mousedown touchstart', event => { this.click(event)}, { passive: false }) addEventListeners(this.canvas, 'mousemove touchmove', event => { @@ -1242,8 +1243,6 @@ class Preview { ] } - const isForeground_raycaster = new THREE.Raycaster(); - isForeground_raycaster.near = Number.EPSILON; // to avoid self-intersecting function isForeground(parentElement, selectionTarget){ if(Project.view_mode === 'wireframe'){ // if View Mode is set to wireframe, all elements are visible. @@ -1262,13 +1261,28 @@ class Preview { center = selectionTarget; } position = [center[0]+origin[0], center[1]+origin[1], center[2]+origin[2]]; - let rc_origin = new THREE.Vector3(position[0], position[1], position[2]); - let rc_direction = new THREE.Vector3(Preview.selected.camera.position.x, Preview.selected.camera.position.y, Preview.selected.camera.position.z); - rc_direction = rc_direction.sub(rc_origin); + let rc_origin = Preview.selected.camera.position; + let rc_target = new THREE.Vector3(position[0], position[1], position[2]); + let rc_direction = new THREE.Vector3().copy(rc_target); + rc_direction.sub(rc_origin); + let far_distance = rc_direction.length(); rc_direction.normalize(); - isForeground_raycaster.set(rc_origin, rc_direction); - let intersects = isForeground_raycaster.intersectObjects(Outliner.elements.map( el => el.mesh)); - return (intersects.length == 0); + Preview.selected.isForeground_raycaster.camera = Preview.selected.camera; + Preview.selected.isForeground_raycaster.near = 0.0; + Preview.selected.isForeground_raycaster.far = far_distance; + Preview.selected.isForeground_raycaster.set(rc_origin, rc_direction); + let intersects = Preview.selected.isForeground_raycaster.intersectObjects(Outliner.elements.map(el => el.mesh), false); + let actualIntersects = []; + intersects.forEach(intersect => { + // remove face-level self-intersection + let faceCheck = !(Math.abs(intersect.distance-far_distance)<0.0001); + // if selectionTarget is a Mesh, remove object level self-intersection + let objectCheck = !((selectionTarget instanceof Mesh) && (intersect.object == selectionTarget.mesh)); + if (faceCheck && objectCheck) { + actualIntersects.push(intersect); + } + }) + return (actualIntersects.length == 0); } unselectAll() From eac7850914d17bad546810a2761599c92a2805e2 Mon Sep 17 00:00:00 2001 From: farfalk Date: Sun, 15 Oct 2023 20:00:05 +0200 Subject: [PATCH 4/6] remove duplicate MeshFace.getCenter() method. --- js/outliner/mesh.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/js/outliner/mesh.js b/js/outliner/mesh.js index e55355acd..de0e7fbbd 100644 --- a/js/outliner/mesh.js +++ b/js/outliner/mesh.js @@ -23,20 +23,6 @@ class MeshFace extends Face { } return this; } - getCenter(global){ - let center = [0, 0, 0]; - let len = 0; - for (let vkey in this.vertices) { - center.V3_add(this.mesh.vertices[vkey]); - len++; - } - center.V3_divide(len); - if (global) { - return this.mesh.localToWorld(Reusable.vec1.set(...center)).toArray(); - } else { - return center; - } - } getNormal(normalize) { let vertices = this.getSortedVertices(); if (vertices.length < 3) return [0, 0, 0]; From e471e6d47af3ab896c0cf26a4f0a65707c1156f0 Mon Sep 17 00:00:00 2001 From: farfalk Date: Sun, 15 Oct 2023 20:00:18 +0200 Subject: [PATCH 5/6] Better checks, but performance worsened a bit --- js/preview/preview.js | 60 +++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/js/preview/preview.js b/js/preview/preview.js index 67fbec9b2..972b35b13 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -1250,39 +1250,43 @@ class Preview { // Also mirrors what happens in Blender. return true; } - let center = null; - let position = null; - let origin = parentElement.origin; - if(selectionTarget.mesh){ - // selectionTarget is a Mesh or a MeshFace - center = selectionTarget.getCenter(true); - } else { - // selectionTarget is a vertex position - center = selectionTarget; - } - position = [center[0]+origin[0], center[1]+origin[1], center[2]+origin[2]]; + // remove invisible objects and get their meshes + let collidingObjects = Outliner.elements.filter(object => object.visibility).map(el => el.mesh) + // get the center of the Blockbench Mesh/MeshFace object, or consider the target a simple vertex position + let center = selectionTarget.mesh ? selectionTarget.getCenter(true) : selectionTarget; + // add the origin of the parent Blockbench Mesh + let position = [center[0]+parentElement.origin[0], center[1]+parentElement.origin[1], center[2]+parentElement.origin[2]]; let rc_origin = Preview.selected.camera.position; - let rc_target = new THREE.Vector3(position[0], position[1], position[2]); - let rc_direction = new THREE.Vector3().copy(rc_target); + let rc_direction = new THREE.Vector3(position[0], position[1], position[2]); rc_direction.sub(rc_origin); let far_distance = rc_direction.length(); rc_direction.normalize(); Preview.selected.isForeground_raycaster.camera = Preview.selected.camera; Preview.selected.isForeground_raycaster.near = 0.0; - Preview.selected.isForeground_raycaster.far = far_distance; + Preview.selected.isForeground_raycaster.far = far_distance; // check only until the target object Preview.selected.isForeground_raycaster.set(rc_origin, rc_direction); - let intersects = Preview.selected.isForeground_raycaster.intersectObjects(Outliner.elements.map(el => el.mesh), false); - let actualIntersects = []; - intersects.forEach(intersect => { - // remove face-level self-intersection - let faceCheck = !(Math.abs(intersect.distance-far_distance)<0.0001); - // if selectionTarget is a Mesh, remove object level self-intersection - let objectCheck = !((selectionTarget instanceof Mesh) && (intersect.object == selectionTarget.mesh)); - if (faceCheck && objectCheck) { - actualIntersects.push(intersect); + // Obtain and check the intersections + // if selectionTarget is a Mesh, remove object level self-intersection + const objectCheck = (intersect)=>{ return !((selectionTarget instanceof Mesh) && (intersect.object == selectionTarget.mesh))}; + // remove face-level self-intersection + const faceCheck = (dist)=>{ return !(dist<0.001)}; + // final check with faces - if near enough, compare normals: if they're aligned enough it's most likely the same object + let fN = selectionTarget.getNormal(true); + let fNV = new THREE.Vector3(fN[0], fN[1], fN[2]); + const finalCheck = (intersect, dist)=>{ + // TODO why is this necessary? + if (!(selectionTarget instanceof MeshFace)){ + return true; } + let dot = fNV.dot(intersect.face.normal); + return !(dist<0.1 && dot>0.95); + } + let intersects = Preview.selected.isForeground_raycaster.intersectObjects(collidingObjects, false); + intersects = intersects.filter(intersect => { + let dist = Math.abs(intersect.distance-far_distance) + return (objectCheck(intersect) && faceCheck(dist) && finalCheck(intersect, dist)) }) - return (actualIntersects.length == 0); + return (intersects.length == 0); } unselectAll() @@ -1353,9 +1357,6 @@ class Preview { } else { for (let fkey in element.faces) { let face = element.faces[fkey]; - if(!isForeground(element, face)){ - continue; - } let vertices = face.getSortedVertices(); let face_intersects; for (let i = 0; i < vertices.length; i++) { @@ -1368,8 +1369,11 @@ class Preview { break; } } + if(face_intersects && !isForeground(element, face)){ + continue; + } if (selection_mode == 'object') { - if (face_intersects && isForeground(element, element)) { + if (face_intersects && isForeground(element, face)) { isSelected = true; break; } From f62d59f0c33c18fa2bd2c62799e1a98c074f8c25 Mon Sep 17 00:00:00 2001 From: farfalk Date: Mon, 16 Oct 2023 12:05:18 +0200 Subject: [PATCH 6/6] Fix selection of vertices and edges --- js/preview/preview.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/preview/preview.js b/js/preview/preview.js index 972b35b13..723a54be3 100644 --- a/js/preview/preview.js +++ b/js/preview/preview.js @@ -1271,8 +1271,12 @@ class Preview { // remove face-level self-intersection const faceCheck = (dist)=>{ return !(dist<0.001)}; // final check with faces - if near enough, compare normals: if they're aligned enough it's most likely the same object - let fN = selectionTarget.getNormal(true); - let fNV = new THREE.Vector3(fN[0], fN[1], fN[2]); + let fN = null; + let fNV = null; + if(selectionTarget instanceof MeshFace){ + fN = selectionTarget.getNormal(true); + fNV = new THREE.Vector3(fN[0], fN[1], fN[2]); + } const finalCheck = (intersect, dist)=>{ // TODO why is this necessary? if (!(selectionTarget instanceof MeshFace)){