Skip to content

物理查询

PhysicsQuery 提供了射线、扫掠、重叠等空间查询能力,用于拾取、视线检测、范围检测等场景。这是 Rapier 版相较 Ammo 版新增的公开能力。

ts
import { PhysicsQuery } from '@orillusion/physics-rapier';
import { Vector3 } from '@orillusion/core';

射线检测(raycast)

origin 沿 dir 方向发射射线,返回最近的命中:

ts
const hit = PhysicsQuery.raycast(origin, dir, { maxDistance: 100 });
if (hit) {
    console.log(hit.rigidbody); // 命中的刚体
    console.log(hit.point);     // 命中点(世界坐标)
    console.log(hit.normal);    // 命中表面法线
}

raycastAll(origin, dir, options) 则返回路径上的所有命中。

重叠检测(overlap)

检测某个形状在指定位置/朝向下与哪些物体重叠:

ts
const overlapping = PhysicsQuery.overlap(
    CollisionShapeUtil.createBoxShape(obj, new Vector3(2, 2, 2)),
    pos,   // 位置 Vector3
    rot,   // 朝向 Quaternion
    { excludeSensors: true },
);

方法一览

方法说明
raycast(origin, dir, options?)射线,返回最近命中(或 null
raycastAll(origin, dir, options?)射线,返回所有命中
sweep(shape, from, to, options?)形状扫掠检测
overlap(shape, pos, rot, options?)形状重叠检测
closestPoint(point, options?)最近点查询

常用选项:maxDistance(最大距离)、excludeSensors(是否排除触发器)等。

示例

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { Scene3D, CameraUtil, HoverCameraController, DirectLight, View3D, AtmosphericComponent, Object3D, LitMaterial, Engine3D, BoxGeometry, MeshRenderer, Vector3, PlaneGeometry, Color, ComponentBase, Time } from "@orillusion/core";
import { Physics, Rigidbody, BodyType, CollisionShapeUtil, PhysicsQuery } from "@orillusion/physics-rapier";
import { Graphic3D } from "@orillusion/graphic";

class RotatingScanner extends ComponentBase {
    public targetHitMaterial!: LitMaterial;
    public defaultMaterial!: LitMaterial;
    public boxes: Object3D[] = [];
    private elapsed = 0;
    public graphic3D!: Graphic3D;

    public onUpdate() {
        this.elapsed += Time.delta * 0.001;
        const angle = this.elapsed;
        const origin = new Vector3(0, 5, 0);
        const dir = new Vector3(Math.cos(angle), 0, Math.sin(angle));

        // Reset all
        for (const b of this.boxes) b.getComponent(MeshRenderer).material = this.defaultMaterial;

        const hit = PhysicsQuery.raycast(origin, dir, { maxDistance: 30 });

        // Draw the ray
        this.graphic3D.Clear?.('scanRay');
        const end = hit ? hit.point : new Vector3(origin.x + dir.x * 30, origin.y + dir.y * 30, origin.z + dir.z * 30);
        this.graphic3D.drawLines('scanRay', [origin, end], hit ? new Color(1, 0.3, 0.3) : new Color(0.3, 1, 0.3));

        if (hit && hit.rigidbody) {
            const obj = hit.rigidbody.object3D;
            obj.getComponent(MeshRenderer).material = this.targetHitMaterial;
        }
    }
}

class Sample_RapierRaycast {
    async run() {
        await Physics.init();
        const engine = await Engine3D.init({ renderLoop: () => Physics.update() });
        let scene = new Scene3D();

        // Setup camera
        let camera = CameraUtil.createCamera3DObject(scene);
        camera.perspective(60, engine.aspect, 0.1, 800.0);

        let hoverCtrl = camera.object3D.addComponent(HoverCameraController);
        hoverCtrl.setCamera(0, -25, 100);
        hoverCtrl.dragSmooth = 4;

        // Create directional light
        let lightObj3D = new Object3D();
        lightObj3D.localRotation = new Vector3(-35, -143, 92);

        let light = lightObj3D.addComponent(DirectLight);
        light.lightColor = Color.COLOR_WHITE;
        light.castShadow = true;
        light.enableCSM = true;
        light.intensity = 2.2;
        scene.addChild(light.object3D);

        // init sky
        let atmosphericSky = scene.addComponent(AtmosphericComponent);
        atmosphericSky.sunY = 0.6;

        // Floor
        const floor = new Object3D();
        const fr = floor.addComponent(MeshRenderer);
        fr.geometry = new PlaneGeometry(40, 40);
        const fm = new LitMaterial(); fm.baseColor = new Color(0.4, 0.4, 0.45); fr.material = fm;
        const fb = floor.addComponent(Rigidbody);
        fb.bodyType = BodyType.Static; fb.shape = CollisionShapeUtil.createPlaneShape(20, 0.05);
        scene.addChild(floor);

        const defaultMat = new LitMaterial(); defaultMat.baseColor = new Color(0.6, 0.6, 0.65);
        const hitMat = new LitMaterial(); hitMat.baseColor = new Color(1, 0.4, 0.2);

        // Ring of boxes around the scanner
        const boxes: Object3D[] = [];
        const N = 12;
        for (let i = 0; i < N; i++) {
            const a = (i / N) * Math.PI * 2;
            const r = 8;
            const o = new Object3D(); o.x = Math.cos(a) * r; o.z = Math.sin(a) * r; o.y = 5;
            const mr = o.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(1.2, 1.2, 1.2);
            mr.material = defaultMat;
            const rb = o.addComponent(Rigidbody);
            rb.bodyType = BodyType.Static;
            rb.shape = CollisionShapeUtil.createBoxShape(o, new Vector3(1.2, 1.2, 1.2));
            scene.addChild(o);
            boxes.push(o);
        }

        // Graphic3D for the ray line
        const graphic = new Graphic3D();
        scene.addChild(graphic);

        // Scanner driver placed on the floor (any object will do)
        const scanner = floor.addComponent(RotatingScanner);
        scanner.boxes = boxes;
        scanner.defaultMaterial = defaultMat;
        scanner.targetHitMaterial = hitMat;
        scanner.graphic3D = graphic;

        let view = new View3D();
        view.camera = camera;
        view.scene = scene;
        engine.startRenderView(view);
    }
}

new Sample_RapierRaycast().run();