基本上是我想创建的:
我有一个带有对象的 3D 地图,我想选择屏幕上 2D 框中 x1,y1 到 x2,y2 中的所有对象。
任何想法必须如何完成,因为我对如何开始一无所知。
提前感谢!
prevX
和prevY
是鼠标按下的坐标:
function onDocumentMouseUp(event) {
event.preventDefault();
var x = (event.clientX / window.innerWidth) * 2 - 1;
var y = -(event.clientY / window.innerHeight) * 2 + 1;
var width = (x - prevX); //* window.innerWidth;
var height = (y - prevY); //* window.innerHeight;
var dx = prevX; //* window.innerWidth;
var dy = prevY; //* window.innerHeight;
console.log(
dx + ',' +
dy + "," +
(dx + width) + "," +
(dy + height) +
", width=" + width +
", height=" + height
);
var topLeftCorner3D = new THREE.Vector3(dx, dy, 1).unproject(
camera);
var topRightCorner3D = new THREE.Vector3(dx + width, dy, 1)
.unproject(camera);
var bottomLeftCorner3D = new THREE.Vector3(dx, dy + height,
1).unproject(camera);
var bottomRightCorner3D = new THREE.Vector3(dx + width, dy +
height, 1).unproject(camera);
var topPlane = new THREE.Plane();
var rightPlane = new THREE.Plane();
var bottomPlane = new THREE.Plane();
var leftPlane = new THREE.Plane();
topPlane.setFromCoplanarPoints(camera.position,
topLeftCorner3D, topRightCorner3D);
rightPlane.setFromCoplanarPoints(camera.position,
topRightCorner3D, bottomRightCorner3D);
bottomPlane.setFromCoplanarPoints(camera.position,
bottomRightCorner3D, bottomLeftCorner3D);
leftPlane.setFromCoplanarPoints(camera.position,
bottomLeftCorner3D, topLeftCorner3D);
//var frustum = new THREE.Frustum( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane);
function isObjectInFrustum(object3D) {
var sphere = object3D.geometry.boundingSphere;
var center = sphere.center;
var negRadius = -sphere.radius;
if (topPlane.distanceToPoint(center) < negRadius) { return false; }
if (bottomPlane.distanceToPoint(center) < negRadius) { return false; }
if (rightPlane.distanceToPoint(center) < negRadius) { return false; }
if (leftPlane.distanceToPoint(center) < negRadius) { return false; }
return true;
}
var matches = [];
for (var i = 0; i < window.objects.length; i++) {
if (isObjectInFrustum(window.objects[i])) {
window.objects[i].material = window.selectedMaterial;
}
}
}
在屏幕空间中与框相交等效于与 3D 空间中的金字塔(透视(或立方体(正交视图(相交。我认为您应该根据2D框定义THREE.Frustum
。
对于透视相机:
- 将屏幕空间框角坐标转换为 3D 矢量(从相机位置到给定点的矢量(
var topLeftCorner3D = new THREE。Vector3( topLeftCorner2D.x, topLeftCorner2D.y, 1 (.unproject( 相机(;
- 根据这些点构建 4 个平面(一个平面用于一个盒子侧(。飞机的第三点是相机位置。
topPlane.setFromCoplanarPoints (camera.position, topLeftCorner3D , topRightCorner3D ( rightPlane.setFromCoplanarPoints (camera.position, topRightCorner3D , bottomRightCorner3D ( bottomPlane.setFrom共面点 (camera.position,bottomRightCorner3D , bottomLeftCorner3D ( leftPlane.setFromCoplanarPoints (camera.position, bottomLeftCorner3D , topLeftCorner3D (
- 在这些平面
- 上构建一个基于视锥体的平面,将相机的近平面和远平面添加到其中。
var 视锥体 = 新的三个。视锥体( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane(;
- 将视锥体与物体相交
frustum.intersectsBox(object.geometry.boundingBox(
或
视锥体.相交球体(object.geometry.boundingSphere(
使用 Frustum
对象本身的另一种选择:您可以跳过近平面和远平面,您只需检查对象是否在与 4 个平面接壤的空间中。你可以编写一个像Frustum.intersectSphere()
方法一样只有 4 个平面的函数:
function isObjectInFrustum(object3D) {
var sphere = object3D.geometry.boundingSphere;
var center = sphere.center;
var negRadius = - sphere.radius;
if ( topPlane.distanceToPoint( center )< negRadius ) return false;
if ( bottomPlane.distanceToPoint( center )< negRadius ) return false;
if ( rightPlane.distanceToPoint( center )< negRadius ) return false;
if ( leftPlane.distanceToPoint( center )< negRadius ) return false;
return true;
}
您可以使用对象的边界框。 THREE.Geometry
包含一个boundingBox
属性。默认情况下,这是null
,您应该通过直接调用computeBoundingBox()
来计算它。
scene.traverse( function ( node ) {
if ( node.geometry )
node.geometry.computeBoundingBox();
} );
有了边界框后,边界框 2D 坐标为(如果"y"是向上轴(:
mesh.geometry.boundingBox.min.x
mesh.geometry.boundingBox.min.z
mesh.geometry.boundingBox.max.x
mesh.geometry.boundingBox.max.z
请参阅 http://threejs.org/docs/#Reference/Core/Geometry
(但是,如果您需要具有非矩形形状的网格的确切结果,则需要进一步计算(
有关比子截锥体选择更准确的方法,请参阅具有三个 js 的选框选择。它的工作原理是将边界框的 8 个角投影到屏幕空间上,然后与屏幕矩形相交。另请参阅此处的第三个更快方法(用 ClojureScript 编写(,该方法将 3D 边界球投影到屏幕空间中的边界圆上,以及前两个方法的实现。另请参阅相关的堆栈溢出问题,该问题尝试使用 GPU 执行屏幕投影和使用读取像素进行拾取(我没有研究这种方法,因为我认为 readpixel 会使渲染管道停滞太多,但如果我错了,请告诉我(。
我在 http://jsbin.com/tamoce/3/做了一个亚截锥体选择的工作示例,但它太不准确了。重要的部分是:
var rx1 = ( x1 / window.innerWidth ) * 2 - 1;
var rx2 = ( x2 / window.innerWidth ) * 2 - 1;
var ry1 = -( y1 / window.innerHeight ) * 2 + 1;
var ry2 = -( y2 / window.innerHeight ) * 2 + 1;
var projectionMatrix = new THREE.Matrix4();
projectionMatrix.makeFrustum( rx1, rx2, ry1, ry2, camera.near, camera.far );
camera.updateMatrixWorld();
camera.matrixWorldInverse.getInverse( camera.matrixWorld );
var viewProjectionMatrix = new THREE.Matrix4();
viewProjectionMatrix.multiplyMatrices( projectionMatrix, camera.matrixWorldInverse );
var frustum = new THREE.Frustum();
frustum.setFromMatrix( viewProjectionMatrix );