简体   繁体   中英

ThreeJS: Find neighbor faces in PlaneBufferGeometry

I'm looking for a better/faster way to find the neighbor faces (that share the same edge) in my PlaneBufferGeometry . Currently my THREE.Raycaster intersects fine with my object (using intersectObject ). But I need to find all surrounding faces.

At this moment I use the following dirty way to find the 'next door' faces. It works well in my scenario, but doesn't feel right:

 let rc = new THREE.Raycaster(); let intersects = []; for (let i = -1; i <= 1; i++) { for (let j = -1; j <= 1; j++) { let v = new THREE.Vector3(x + i, y, z + j); rc.set(v, new THREE.Vector3(0, -1, 0)); rc.near = 0; rc.far = 2; let subIntersects = rc.intersectObject(mesh); for (let n = 0; n < subIntersects.length; n++) { intersects.push(subIntersects[n]); } } }

Is there for instance a way to quickly find these faces in the mesh.geometry.attributes.position.array ? Any suggestions are welcome. Thanks in advance!

You mentioned 50k items in the position array. That doens't seem too slow to me. This example is only 10x10 so 100 items in the array so it's easy to see it's working, but I had it set to 200x200 which is 40k items and it seemed fast enough?

 'use strict'; function main() { const canvas = document.querySelector('#c'); const renderer = new THREE.WebGLRenderer({canvas}); const fov = 60; const aspect = 2; // the canvas default const near = 0.1; const far = 200; const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); camera.position.z = 1; const scene = new THREE.Scene(); scene.background = new THREE.Color('#444'); scene.add(camera); const planeGeometry = new THREE.PlaneBufferGeometry(1, 1, 20, 20); const material = new THREE.MeshBasicMaterial({color: 'blue'}); const plane = new THREE.Mesh(planeGeometry, material); scene.add(plane); const edgeGeometry = new THREE.BufferGeometry(); const positionNumComponents = 3; edgeGeometry.setAttribute('position', planeGeometry.getAttribute('position')); edgeGeometry.setIndex([]); const edgeMaterial = new THREE.MeshBasicMaterial({ color: 'yellow', wireframe: true, depthTest: false, }); const edges = new THREE.Mesh(edgeGeometry, edgeMaterial); scene.add(edges); function resizeRendererToDisplaySize(renderer) { const canvas = renderer.domElement; const width = canvas.clientWidth; const height = canvas.clientHeight; const needResize = canvas.width !== width || canvas.height !== height; if (needResize) { renderer.setSize(width, height, false); } return needResize; } class PickHelper { constructor() { this.raycaster = new THREE.Raycaster(); } pick(normalizedPosition, scene, camera, time) { // cast a ray through the frustum this.raycaster.setFromCamera(normalizedPosition, camera); // get the list of objects the ray intersected const intersectedObjects = this.raycaster.intersectObjects(scene.children, [plane]); if (intersectedObjects.length) { // pick the first object. It's the closest one const intersection = intersectedObjects[0]; const faceIndex = intersection.faceIndex; const indexAttribute = planeGeometry.getIndex(); const indices = indexAttribute.array; const vertIds = indices.slice(faceIndex * 3, faceIndex * 3 + 3); const neighbors = []; // note: self will be added to list for (let i = 0; i < indices.length; i += 3) { for (let j = 0; j < 3; ++j) { const p0Ndx = indices[i + j]; const p1Ndx = indices[i + (j + 1) % 3]; if ((p0Ndx === vertIds[0] && p1Ndx === vertIds[1]) || (p0Ndx === vertIds[1] && p1Ndx === vertIds[0]) || (p0Ndx === vertIds[1] && p1Ndx === vertIds[2]) || (p0Ndx === vertIds[2] && p1Ndx === vertIds[1]) || (p0Ndx === vertIds[2] && p1Ndx === vertIds[0]) || (p0Ndx === vertIds[0] && p1Ndx === vertIds[2])) { neighbors.push(...indices.slice(i, i + 3)); break; } } } const edgeIndices = edgeGeometry.getIndex(); edgeIndices.array = new Uint16Array(neighbors); edgeIndices.count = neighbors.length; edgeIndices.needsUpdate = true; } } } const pickPosition = {x: 0, y: 0}; const pickHelper = new PickHelper(); clearPickPosition(); function render(time) { time *= 0.001; // convert to seconds; if (resizeRendererToDisplaySize(renderer)) { const canvas = renderer.domElement; camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } pickHelper.pick(pickPosition, scene, camera, time); renderer.render(scene, camera); requestAnimationFrame(render); } requestAnimationFrame(render); function getCanvasRelativePosition(event) { const rect = canvas.getBoundingClientRect(); return { x: event.clientX - rect.left, y: event.clientY - rect.top, }; } function setPickPosition(event) { const pos = getCanvasRelativePosition(event); pickPosition.x = (pos.x / canvas.clientWidth ) * 2 - 1; pickPosition.y = (pos.y / canvas.clientHeight) * -2 + 1; // note we flip Y } function clearPickPosition() { // unlike the mouse which always has a position // if the user stops touching the screen we want // to stop picking. For now we just pick a value // unlikely to pick something pickPosition.x = -100000; pickPosition.y = -100000; } window.addEventListener('mousemove', setPickPosition); window.addEventListener('mouseout', clearPickPosition); window.addEventListener('mouseleave', clearPickPosition); window.addEventListener('touchstart', (event) => { // prevent the window from scrolling event.preventDefault(); setPickPosition(event.touches[0]); }, {passive: false}); window.addEventListener('touchmove', (event) => { setPickPosition(event.touches[0]); }); window.addEventListener('touchend', clearPickPosition); } main();
 body { margin: 0; } canvas { width: 100vw; height: 100vh; display: block; }
 <script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/build/three.js"></script> <canvas id="c"></canvas>

Like I mentioned in the comment. If you know it's PlaneBufferGeometry then you can look in the three.js code and see the exact layout of faces so given a faceIndex you can just compute the neighbors directly. The code above is generic, at least for BufferGeometry with an index.

Looking at the code I'm pretty sure it's

// it looks like this is the grid order for PlaneBufferGeometry
//
//  b --c
//  |\\1|
//  |0\\|
//  a-- d


const facesAcrossRow = planeGeometry.parameters.widthSegments * 2;
const col = faceIndex % facesAcrossRow
const row = faceIndex / facesAcrossRow | 0;

const neighboringFaceIndices = [];

// check left face
if (col > 0) {
  neighboringFaceIndices.push(row * facesAcrossRow + col - 1);
}

// check right face
if (col < facesAcrossRow - 1) {
  neighboringFaceIndices.push(row * facesAcrossRow + col + 1);
}

// check up. there can only be one up if we're in an odd triangle (b,c,d)
if (col % 2 && row < planeGeometry.parameters.heightSegments) {
  // add the even neighbor in the next row
  neighboringFaceIndices.push((row + 1) * facesAcrossRow + col - 1);
}

// check down. there can only be one down if we're in an even triangle (a,b,d)
if (col % 2 === 0 && row > 0) {
  // add the odd neighbor in the previous row
  neighboringFaceIndices.push((row - 1) * facesAcrossRow + col + 1);
}

Trying that out

 'use strict'; function main() { const canvas = document.querySelector('#c'); const renderer = new THREE.WebGLRenderer({canvas}); const fov = 60; const aspect = 2; // the canvas default const near = 0.1; const far = 200; const camera = new THREE.PerspectiveCamera(fov, aspect, near, far); camera.position.z = 1; const scene = new THREE.Scene(); scene.background = new THREE.Color('#444'); scene.add(camera); const planeGeometry = new THREE.PlaneBufferGeometry(1, 1, 20, 20); const material = new THREE.MeshBasicMaterial({color: 'blue'}); const plane = new THREE.Mesh(planeGeometry, material); scene.add(plane); const edgeGeometry = new THREE.BufferGeometry(); const positionNumComponents = 3; edgeGeometry.setAttribute('position', planeGeometry.getAttribute('position')); edgeGeometry.setIndex([]); const edgeMaterial = new THREE.MeshBasicMaterial({ color: 'yellow', wireframe: true, depthTest: false, }); const edges = new THREE.Mesh(edgeGeometry, edgeMaterial); scene.add(edges); function resizeRendererToDisplaySize(renderer) { const canvas = renderer.domElement; const width = canvas.clientWidth; const height = canvas.clientHeight; const needResize = canvas.width !== width || canvas.height !== height; if (needResize) { renderer.setSize(width, height, false); } return needResize; } class PickHelper { constructor() { this.raycaster = new THREE.Raycaster(); } pick(normalizedPosition, scene, camera, time) { // cast a ray through the frustum this.raycaster.setFromCamera(normalizedPosition, camera); // get the list of objects the ray intersected const intersectedObjects = this.raycaster.intersectObjects(scene.children, [plane]); if (intersectedObjects.length) { // pick the first object. It's the closest one const intersection = intersectedObjects[0]; const faceIndex = intersection.faceIndex; const indexAttribute = planeGeometry.getIndex(); const indices = indexAttribute.array; // it looks like this is the grid order for PlaneBufferGeometry // // b --c // |\\\\1| // |0\\\\| // a-- d const facesAcrossRow = planeGeometry.parameters.widthSegments * 2; const col = faceIndex % facesAcrossRow const row = faceIndex / facesAcrossRow | 0; const neighboringFaceIndices = []; // check left face if (col > 0) { neighboringFaceIndices.push(row * facesAcrossRow + col - 1); } // check right face if (col < facesAcrossRow - 1) { neighboringFaceIndices.push(row * facesAcrossRow + col + 1); } // check up. there can only be one up if we're in an odd triangle (b,c,d) if (col % 2 && row < planeGeometry.parameters.heightSegments) { // add the even neighbor in the next row neighboringFaceIndices.push((row + 1) * facesAcrossRow + col - 1); } // check down. there can only be one down if we're in an even triangle (a,b,d) if (col % 2 === 0 && row > 0) { // add the odd neighbor in the previous row neighboringFaceIndices.push((row - 1) * facesAcrossRow + col + 1); } const neighbors = []; for (const faceIndex of neighboringFaceIndices) { neighbors.push(...indices.slice(faceIndex * 3, faceIndex * 3 + 3)); } const edgeIndices = edgeGeometry.getIndex(); edgeIndices.array = new Uint16Array(neighbors); edgeIndices.count = neighbors.length; edgeIndices.needsUpdate = true; } } } const pickPosition = {x: 0, y: 0}; const pickHelper = new PickHelper(); clearPickPosition(); function render(time) { time *= 0.001; // convert to seconds; if (resizeRendererToDisplaySize(renderer)) { const canvas = renderer.domElement; camera.aspect = canvas.clientWidth / canvas.clientHeight; camera.updateProjectionMatrix(); } pickHelper.pick(pickPosition, scene, camera, time); renderer.render(scene, camera); requestAnimationFrame(render); } requestAnimationFrame(render); function getCanvasRelativePosition(event) { const rect = canvas.getBoundingClientRect(); return { x: event.clientX - rect.left, y: event.clientY - rect.top, }; } function setPickPosition(event) { const pos = getCanvasRelativePosition(event); pickPosition.x = (pos.x / canvas.clientWidth ) * 2 - 1; pickPosition.y = (pos.y / canvas.clientHeight) * -2 + 1; // note we flip Y } function clearPickPosition() { // unlike the mouse which always has a position // if the user stops touching the screen we want // to stop picking. For now we just pick a value // unlikely to pick something pickPosition.x = -100000; pickPosition.y = -100000; } window.addEventListener('mousemove', setPickPosition); window.addEventListener('mouseout', clearPickPosition); window.addEventListener('mouseleave', clearPickPosition); window.addEventListener('touchstart', (event) => { // prevent the window from scrolling event.preventDefault(); setPickPosition(event.touches[0]); }, {passive: false}); window.addEventListener('touchmove', (event) => { setPickPosition(event.touches[0]); }); window.addEventListener('touchend', clearPickPosition); } main();
 body { margin: 0; } canvas { width: 100vw; height: 100vh; display: block; }
 <script src="https://threejsfundamentals.org/threejs/resources/threejs/r112/build/three.js"></script> <canvas id="c"></canvas>

For something more complex than a PlaneBufferGeometry you could also pre-generate a map of faceIndexs to neighbors if the code at the top is too slow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM