推荐:用 NSDT编辑器 快速搭建可编程3D场景
在Three.js应用开发中,有时你可能需要为3D场景中的网格绘制2D的包围框,应该怎么做?
朴素的想法是把网格的3D包围框投影到屏幕空间,例如,下图中的绿色框 3D包围框, 当将其投影为 2D 时,得到的红色2D包围框,显然比想要的蓝色框大很多:
正确的做法是先将3D网格投影到屏幕空间,再计算2D包围框。
1、计算单个网格的2D包围框
只需将所有顶点转换为屏幕空间并从中创建一个 2d 边界框:
function computescreenspaceboundingbox(mesh, camera) {
var vertices = mesh.geometry.vertices;
var vertex = new three.vector3();
var min = new three.vector3(1, 1, 1);
var max = new three.vector3(-1, -1, -1);
for (var i = 0; i < vertices.length; i++) {
var vertexworldcoord = vertex.copy(vertices[i]).applymatrix4(mesh.matrixworld);
var vertexscreenspace = vertexworldcoord.project(camera);
min.min(vertexscreenspace);
max.max(vertexscreenspace);
}
return new three.box2(min, max);
}
生成的 box2 位于标准化屏幕坐标 [-1, 1] 中, 可以通过乘以渲染器高度和宽度的一半来获得像素:
function normalizedtopixels(coord, renderwidthpixels, renderheightpixels) {
var halfscreen = new three.vector2(renderwidthpixels/2, renderheightpixels/2)
return coord.clone().multiply(halfscreen);
}
2、计算3DObject的2D包围框
更完善的实现需要考虑组、子节点等各种3DObject,其中可能包含多个网格,这需要一个递归实现,代码如下:
function computescreenspaceboundingbox(obj, camera) {
var min;
var max;
// is this an array of objects?
if(array.isarray(obj)) {
for(var i = 0; i < obj.length; ++i) {
let box2 = computescreenspaceboundingbox(obj[i], camera);
if(min === undefined) {
min = box2.min.clone();
max = box2.max.clone();
} else {
min.min(box2.min);
max.max(box2.max);
}
}
}
// does this object have geometry?
if(obj.geometry !== undefined) {
var vertices = obj.geometry.vertices;
if(vertices === undefined
&& obj.geometry.attributes !== undefined
&& 'position' in obj.geometry.attributes) {
// buffered geometry
var vertex = new three.vector3();
var pos = obj.geometry.attributes.position;
for(var i = 0; i < pos.count * pos.itemsize; i += pos.itemsize)
{
vertex.set(pos.array[i], pos.array[i + 1], pos.array[1 + 2]);
var vertexworldcoord = vertex.applymatrix4(obj.matrixworld);
var vertexscreenspace = vertexworldcoord.project(camera);
if(min === undefined) {
min = vertexscreenspace.clone();
max = vertexscreenspace.clone();
}
min.min(vertexscreenspace);
max.max(vertexscreenspace);
}
} else {
// regular geometry
var vertex = new three.vector3();
for(var i = 0; i < vertices.length; ++i) {
var vertexworldcoord = vertex.copy(vertices[i]).applymatrix4(obj.matrixworld);
var vertexscreenspace = vertexworldcoord.project(camera);
if(min === undefined) {
min = vertexscreenspace.clone();
max = vertexscreenspace.clone();
}
min.min(vertexscreenspace);
max.max(vertexscreenspace);
}
}
}
// does this object have children?
if(obj.children !== undefined) {
for(var i = 0; i < obj.children.length; ++i) {
let box2 = computescreenspaceboundingbox(obj.children[i], camera);
if(min === undefined) {
min = box2.min.clone();
max = box2.max.clone();
} else {
min.min(box2.min);
max.max(box2.max);
}
}
}
return new three.box2(min, max);
}