three OBB 定向包围盒

534 阅读6分钟

定向包围盒(OBB,Oriented Bounding Box) 是一种三维包围盒,它与物体的旋转方向对齐,而不是像轴对齐包围盒(AABB)那样总是与坐标轴对齐。OBB 的优势在于它能够更紧密地包围物体,从而减少包围盒的空白区域。

OBB 通过 applyMatrix4 方法获取物体的边界信息以使用于包围盒计算

OBB 有三个属性十五个方法

OBB( center : Vector3, halfSize : Vector3, rotation : Matrix3 )
    center — OBB 的中心。(可选)
    halfSize — OBB 沿每个轴的正半宽范围。(可选)
    rotation — OBB 的旋转。(可选)
    创建一个新的 OBB。
    
    
    以下为代码示例
    
    // 创建 WebGL 渲染器,启用抗锯齿
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    // 设置渲染器的大小
    renderer.setSize( width, height );
    // 启用局部裁剪
    renderer.localClippingEnabled = true; 
    // 启用阴影
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 设置阴影类型为软阴影
    // 将渲染器的 DOM 元素添加到页面中
    DOMEl.appendChild( renderer.domElement );
    // 创建场景对象
    const scene = new THREE.Scene();
    // 设置清空背景色为黑色
    renderer.setClearColor(0x000000);
    // 创建透视相机
    const camera = new THREE.PerspectiveCamera( 75, width / height, 0.1, 1000 );
    // 设置相机位置
    camera.position.set( 0, 80, 0 );
    // 让相机指向场景的中心
    camera.lookAt( 0, 0, 0 );
    // 创建时钟对象用于计算每帧的时间差
    const clock = new THREE.Clock();
    // 存储物体的数组
    const objects  = [];
    // 存储鼠标坐标的向量
    const mouse = new THREE.Vector2();
    // 创建射线投射器
    const raycaster = new THREE.Raycaster();
    // 创建半球光源,模拟环境光
    const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x222222, 4 );
    hemiLight.position.set( 1, 1, 1 );
    // 将半球光源添加到场景中
    scene.add( hemiLight );
    // 创建盒子的尺寸
    const size = new THREE.Vector3( 10, 5, 6 );
    // 创建盒子几何体
    const geometry = new THREE.BoxGeometry( size.x, size.y, size.z );
    // 为几何体添加用户数据,存储 OBB 信息
    geometry.userData.obb = new OBB();
    geometry.userData.obb.halfSize.copy( size ).multiplyScalar( 0.5 );
    // 创建多个物体并添加到场景中
    for ( let i = 0; i < 100; i ++ ) {
        const object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: 0x00ff00 } ) );
        object.matrixAutoUpdate = false;
        object.position.x = Math.random() * 80 - 40;
        object.position.y = Math.random() * 80 - 40;
        object.position.z = Math.random() * 80 - 40;
        object.rotation.x = Math.random() * 2 * Math.PI;
        object.rotation.y = Math.random() * 2 * Math.PI;
        object.rotation.z = Math.random() * 2 * Math.PI;
        object.scale.x = Math.random() + 0.5;
        object.scale.y = Math.random() + 0.5;
        object.scale.z = Math.random() + 0.5;
        scene.add( object );
        // 为物体添加 OBB 信息
        object.userData.obb = new OBB();
        objects.push( object );
    };
    // 创建一个可视化的碰撞框
    const hitbox = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true } ) );
    // 鼠标点击事件的回调函数
    function onClick( event ) {
        event.preventDefault();
        const rect = DOMEl.getBoundingClientRect();
        mouse.x = ((event.clientX - rect.left) / width) * 2 - 1;
        mouse.y = -((event.clientY - rect.top) / height) * 2 + 1;
        // 根据鼠标坐标生成射线
        raycaster.setFromCamera( mouse, camera );
        const intersectionPoint = new THREE.Vector3();
        const intersections = [];
        // 遍历所有物体,检测射线是否与物体的 OBB 相交
        for ( let i = 0, il = objects.length; i < il; i ++ ) {
            const object = objects[ i ];
            const obb = object.userData.obb;
            const ray = raycaster.ray;
            if ( obb.intersectRay( ray, intersectionPoint ) !== null ) {
                const distance = ray.origin.distanceTo( intersectionPoint );
                intersections.push( { distance: distance, object: object } );
            }
        };
        console.log(intersections,"intersections")
        // 如果有相交的物体,将第一个相交物体添加碰撞框
        if ( intersections.length > 0 ) {
            intersections.sort( sortIntersections );
            intersections[ 0 ].object.add( hitbox );
        } else {
            // 如果没有相交物体,从场景中移除碰撞框
            const parent = hitbox.parent;
            if ( parent ) parent.remove( hitbox );
        }
    }
    // 排序函数,按照距离升序排列
    function sortIntersections( a, b ) {
        return a.distance - b.distance;
    }
    // 添加点击事件监听器
    DOMEl.addEventListener( 'click', onClick );
    // 创建 OrbitControls 实例,用于控制相机
    const controls = new OrbitControls(camera, renderer.domElement);
    // 设置控制器的目标点
    controls.target.set(0, 0, 0); // 将目标点设置为场景的中心
    // 动画函数
    function animate() {
        requestAnimationFrame(animate);
        controls.update(); // 更新 OrbitControls
        const delta = clock.getDelta();
        // 更新每个物体的旋转
        for ( let i = 0, il = objects.length; i < il; i ++ ) {
            const object = objects[ i ];
            object.rotation.x += delta * Math.PI * 0.20;
            object.rotation.y += delta * Math.PI * 0.1;
            object.updateMatrix();
            object.updateMatrixWorld();
            // 更新物体的 OBB 信息
            object.userData.obb.copy( object.geometry.userData.obb );
            object.userData.obb.applyMatrix4( object.matrixWorld );
            object.material.color.setHex( 0x00ff00 );
        }
        // 检查物体之间的碰撞
        for ( let i = 0, il = objects.length; i < il; i ++ ) {
            const object = objects[ i ];
            const obb = object.userData.obb;
            for ( let j = i + 1, jl = objects.length; j < jl; j ++ ) {
                const objectToTest = objects[ j ];
                const obbToTest = objectToTest.userData.obb;
                // 如果物体之间发生碰撞,改变它们的颜色
                if ( obb.intersectsOBB( obbToTest ) === true ) {
                    object.material.color.setHex( 0xff0000 );
                    objectToTest.material.color.setHex( 0xff0000 );
                }
            }
        }
        // 渲染场景
        renderer.render(scene, camera);
    }
    // 启动动画循环
    animate();

属性

  • center : Vector3 OBB 的中心。默认值为 ( 0, 0, 0 )。
  • halfSize : Vector3 OBB 沿每个轴的正半宽范围。默认值为 ( 0, 0, 0 )。
  • rotation : Matrix3 OBB 的旋转。默认为单位矩阵。

方法

  • applyMatrix4 ( matrix : Matrix4 ) : this matrix — 一个 4x4 变换矩阵。 将给定的变换矩阵应用于此 OBB。此方法可用于将包围体与 3D 对象的世界矩阵进行转换,以保持两个实体同步。
  • clampPoint ( point : Vector3, clampedPoint : Vector3 ) : Vector3 point — 应该被限制在该 OBB 范围内的点。 clampedPoint — 结果将被复制到此向量中。 将给定点限制在该 OBB 范围内。
  • clone () : OBB 创建此实例的克隆。
  • containsPoint ( point : Vector3 ) : Boolean point — 要测试的点。 给定点是否位于此 OBB 内。
  • copy ( obb : OBB ) : this obb — 要复制的 OBB。 将给定 OBB 的属性复制到此 OBB。
  • equals ( obb : OBB ) : Boolean obb — 要测试的 OBB。 给定的 OBB 是否等于此 OBB。
  • fromBox3 ( box3 : Box3 ) : this box3 — AABB 根据给定的 AABB 定义 OBB。
  • getSize ( size : Vector3 ) : Vector3 size — 结果将被复制到此向量中。 将此 OBB 的大小返回到给定向量中。
  • intersectsBox3 ( box3 : Box3 ) : Boolean box3 — 要测试的 box3。 给定的 box3 是否与此 OBB 相交。
  • intersectsSphere ( sphere : Sphere ) : Boolean sphere — 要测试的边界球体。 给定的边界球体是否与此 OBB 相交。
  • intersectsOBB ( obb : OBB, epsilon : Number ) : Boolean obb — 要测试的 OBB epsilon — 一个可选的数值,用于抵消算术错误。默认为 Number.EPSILON。 给定的 OBB 是否与此 OBB 相交。
  • intersectsPlane ( plane : Plane ) : Boolean plane — 要测试的平面。 给定平面是否与此 OBB 相交。
  • intersectRay ( ray : Ray ) : Boolean ray — 要测试的射线。 给定射线是否与此 OBB 相交。
  • intersectRay ( ray : Ray, intersectionPoint : Vector3 ) : Vector3 ray — 要测试的射线。 intersectionPoint — 结果将被复制到此向量中。 执行 射线/OBB 相交测试并将相交点存储到给定的 3D 向量。如果没有检测到交叉点则返回 null。
  • set ( center : Vector3, halfSize : Vector3, rotation : Matrix3 ) : this center — OBB 的中心。 halfSize — OBB 沿每个轴的正半宽范围。 rotation — OBB 的旋转。 定义给定值的 OBB。

总结

  1. OBB(有序包围盒):用于提供物体的边界信息,尤其在物体有旋转、缩放和位置变换时,能够提供更精确的碰撞检测和空间计算。
  2. 与几何体的关系:OBB 并不直接包含物体的几何体,它依赖物体的几何尺寸和形状来创建一个包围盒(bounding box)。这个包围盒会根据物体的变换(位置、旋转、缩放)进行更新。
  3. 如何更新边界信息
    • OBB 通过应用物体的 变换矩阵(matrixWorld)来更新包围盒的位置和尺寸,使其能够正确反映物体在世界坐标系中的实际状态。
    • 每当物体的变换发生变化时,OBB 会重新计算和更新包围盒。
  4. OBB 的作用:OBB 是为物体提供一个精确的边界框,帮助进行碰撞检测、光线交集等空间计算,而不需要直接操作物体的几何体。

关键点: - OBB 使用矩阵更新边界信息,确保包围盒随着物体的变换而更新。 - OBB 不需要直接包含几何体,而是通过物体的几何尺寸、位置、旋转等信息来计算包围盒。