BufferGeometry.boundingBox的应用:BoxHelper的实现

BufferGeometry.boundingBox的应用:BoxHelper的实现

通过boundingBox实现的BoxHelper

官方提供了一个BoxHelper,查看源码,它是通过boundingBox实现的。

BoxHelper通过LineSegment画出一个长方体外框,即需要通过BufferAttributes指定要画的顶点属性(这里主要是position以及index),通过创建LineSegment对象进行绘制,BoxHelper继承了LineSegemnt

// BoxHelper.js
// class BoxHelper extends LineSegment
constructor( object, color = 0xffff00 ) {
const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );
const positions = new Float32Array( 8 * 3 );
const geometry = new BufferGeometry();
geometry.setIndex( new BufferAttribute( indices, 1 ) );
geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) );
this.object = object;
this.type = 'BoxHelper';
this.matrixAutoUpdate = false;
this.update();
}

在构造器中,并没有给出position的据其位置,这一部分的实现在update方法,就是,在update中,间接的使用了boundingBox

下面看一下update()方法:

// BoxHelper.js
update( object ) {
// 这里就是要确保调用update方法时,不会传入参数
if ( object !== undefined ) {
console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' );
}
// 通过Box3.setFromObject()获取Box的坐标
if ( this.object !== undefined ) {
_box.setFromObject( this.object );
}
// 坐标获取成功
if ( _box.isEmpty() ) return;
const min = _box.min;
const max = _box.max;
// 设置position
/*
5____4
1/___0/|
| 6__|_7
2/___3/
0: max.x, max.y, max.z
1: min.x, max.y, max.z
2: min.x, min.y, max.z
3: max.x, min.y, max.z
4: max.x, max.y, min.z
5: min.x, max.y, min.z
6: min.x, min.y, min.z
7: max.x, min.y, min.z
*/
const position = this.geometry.attributes.position;
const array = position.array;
array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z;
array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z;
array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z;
array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z;
array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z;
array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z;
array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z;
array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z;
// 因为BufferAttribute更新了,所以要重新向GPU发送数据
// 通过设置BufferAttribute.needsUpdate,重新向GPU发送数据
position.needsUpdate = true;
this.geometry.computeBoundingSphere();
}

update()中,最重要的就是通过获取Box3.setFromObject()获取Box的坐标以及设置BufferAttribute.needsUpdatetrue,从而向GPU发送更新后的数据。

接下来看一下Box3.setFromObject()的源码:

setFromObject( object, precise = false ) {
this.makeEmpty();
return this.expandByObject( object, precise );
}

makeEmpty()就是将Box3对象恢复成默认值。

makeEmpty() {
this.min.x = this.min.y = this.min.z = + Infinity;
this.max.x = this.max.y = this.max.z = - Infinity;
return this;
}

接下来才是setFromObject()方法的重点,调用Box3.expandByObject()方法:

expandByObject( object, precise = false ) {
// Computes the world-axis-aligned bounding box of an object (including its children),
// accounting for both the object's, and children's, world transforms
object.updateWorldMatrix( false, false );
const geometry = object.geometry;
if ( geometry !== undefined ) {
if ( precise && geometry.attributes != undefined && geometry.attributes.position !== undefined ) {
// 这里调用的时候传入的为false,所以就不看这部分代码了
// 其实这里是通过更复杂的计算,让结果更精确
} else {
if ( geometry.boundingBox === null ) {
// 计算对象自己的boundingBox
geometry.computeBoundingBox();
}
// 对这个boundingBox变换到世界空间
_box.copy( geometry.boundingBox );
_box.applyMatrix4( object.matrixWorld );
// 把结果“联合”
// 在后续还要递归的将子对象的boundingBox整合进来
// 其实就是取了对象自己和其后代对象的所有boundingBox的最大的max和最小的min
this.union( _box );
}
}
// 对象本身的boundingBox已经计算完毕,还要计算并整合其后代对象
const children = object.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
this.expandByObject( children[ i ], precise );
}
return this;
}

这个方法就是将一个对象及其后代对象的所有boundingBox都整合为一个boundingBox,这样,就能“包裹”住所有的对象。

通过阅读代码也可以看到,最终返回的this是被转换到了世界空间中,所以,在使用BoxHelper时,BoxHelper的实例是添加到scene而不是对象自身的局部空间。放在局部空间中的BoxHelper,首先,它的位置不对;其次,也很难保证它的边框是和世界空间的坐标轴对齐。

不要再动画循环中更新BoxHelper

BoxHelper这个对象很不同,他每次更新都会向GPU重新发送数据。一般其他的对象都是向GPU发送一次数据,后续的变换都是向GPU发送一个变换矩阵,在OpenGL的顶点着色器中,完成坐标变换。而BoxHelper则是在使用Box3完成坐标变换,然后把变换后的顶点发送给GPU。

就像BufferGeometry中对translate的描述:

Translate the geometry. This is typically done as a one time operation, and not during a loop. UseObject3D.positionfor typical real-time mesh translation.

这样直接更新BufferAttribute(即,重新向GPU发送数据的方法),因该是“一次性”的操作,不因该把update放在动画循环中。BoxHelper应该仅在换其他对象时,调用setFromObject()方法,从而触发update()

如果想要实现一个想要跟随对象变换的线框(即,在动画循环要更新线框),应该是使用WireframeGeometry配合LineSegment

------本页内容已结束,喜欢请分享------

感谢您的来访,获取更多精彩文章请收藏本站。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容