Skip to content

Commit be575c0

Browse files
authored
Support multi-object raycasting for havok (#17396)
We use multi-object raycasting to see if a trigger is in view, but it can be useful in other ways too (for example, guns that shoot through multiple enemies). Let me know if this API is a good fit for Babylon.js. Another approach could be to create a new `raycastMulti` function in `HavokPlugin`, and reimplement `raycast` as a thin wrapper over it. One possible concern is if a developer sets `maxQueryCollectorHits` to a high number, it will affect *all* raycasts, not just multi-casts. We could perhaps create a separate `queryCollector` for multi-cast use only?
1 parent af3fbe9 commit be575c0

File tree

3 files changed

+112
-14
lines changed

3 files changed

+112
-14
lines changed

packages/dev/core/src/Physics/v2/IPhysicsEnginePlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ export interface IPhysicsEnginePluginV2 {
491491
getBodiesUsingConstraint(constraint: PhysicsConstraint): ConstrainedBodyPair[];
492492

493493
// raycast
494-
raycast(from: Vector3, to: Vector3, result: PhysicsRaycastResult, query?: IRaycastQuery): void;
494+
raycast(from: Vector3, to: Vector3, result: PhysicsRaycastResult | Array<PhysicsRaycastResult>, query?: IRaycastQuery): void;
495495

496496
dispose(): void;
497497
}

packages/dev/core/src/Physics/v2/Plugins/havokPlugin.ts

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import type {
2020
IBasePhysicsCollisionEvent,
2121
ConstrainedBodyPair,
2222
} from "../IPhysicsEnginePlugin";
23-
import type { IRaycastQuery, PhysicsRaycastResult } from "../../physicsRaycastResult";
23+
import type { IRaycastQuery } from "../../physicsRaycastResult";
24+
import { PhysicsRaycastResult } from "../../physicsRaycastResult";
2425
import { Logger } from "../../../Misc/logger";
2526
import type { PhysicsBody } from "../physicsBody";
2627
import type { PhysicsConstraint, Physics6DoFConstraint } from "../physicsConstraint";
@@ -272,6 +273,13 @@ class TriggerEvent {
272273
}
273274
}
274275

276+
export interface HavokPluginParameters {
277+
/**
278+
* Maximum number of raycast hits to process
279+
*/
280+
maxQueryCollectorHits?: number;
281+
}
282+
275283
/**
276284
* The Havok Physics plugin
277285
*/
@@ -292,7 +300,9 @@ export class HavokPlugin implements IPhysicsEnginePluginV2 {
292300
* We only have a single raycast in-flight right now
293301
*/
294302
private _queryCollector;
303+
private _multiQueryCollector: any = undefined;
295304
private _fixedTimeStep: number = 1 / 60;
305+
private _maxQueryCollectorHits: number = 1;
296306
private _tmpVec3 = BuildArray(3, Vector3.Zero);
297307
private _bodies = new Map<bigint, { body: PhysicsBody; index: number }>();
298308
private _shapes = new Map<bigint, PhysicsShape>();
@@ -316,7 +326,8 @@ export class HavokPlugin implements IPhysicsEnginePluginV2 {
316326

317327
public constructor(
318328
private _useDeltaForWorldStep: boolean = true,
319-
hpInjection: any = HK
329+
hpInjection: any = HK,
330+
parameters: HavokPluginParameters = {}
320331
) {
321332
if (typeof hpInjection === "function") {
322333
Logger.Error("Havok is not ready. Please make sure you await HK() before using the plugin.");
@@ -330,7 +341,9 @@ export class HavokPlugin implements IPhysicsEnginePluginV2 {
330341
return;
331342
}
332343
this.world = this._hknp.HP_World_Create()[1];
344+
333345
this._queryCollector = this._hknp.HP_QueryCollector_Create(1)[1];
346+
this.setMaxQueryCollectorHits(parameters.maxQueryCollectorHits ?? 1);
334347
}
335348
/**
336349
* If this plugin is supported
@@ -370,6 +383,35 @@ export class HavokPlugin implements IPhysicsEnginePluginV2 {
370383
return this._fixedTimeStep;
371384
}
372385

386+
/**
387+
* Sets the maximum number of raycast hits to process.
388+
*
389+
* @param maxQueryCollectorHits - The maximum number of raycast hits to process.
390+
*/
391+
public setMaxQueryCollectorHits(maxQueryCollectorHits: number): void {
392+
if (maxQueryCollectorHits === this._maxQueryCollectorHits) {
393+
return;
394+
}
395+
396+
if (this._multiQueryCollector) {
397+
this._hknp.HP_QueryCollector_Release(this._multiQueryCollector);
398+
this._multiQueryCollector = undefined;
399+
}
400+
401+
if (maxQueryCollectorHits > 1) {
402+
this._multiQueryCollector = this._hknp.HP_QueryCollector_Create(maxQueryCollectorHits)[1];
403+
}
404+
}
405+
406+
/**
407+
* Gets the maximum number of raycast hits to process.
408+
*
409+
* @returns The maximum number of raycast hits to process.
410+
*/
411+
public getMaxQueryCollectorHits(): number {
412+
return this._maxQueryCollectorHits;
413+
}
414+
373415
/**
374416
* Executes a single step of the physics engine.
375417
*
@@ -2106,28 +2148,65 @@ export class HavokPlugin implements IPhysicsEnginePluginV2 {
21062148
*
21072149
* @param from - The start point of the raycast.
21082150
* @param to - The end point of the raycast.
2109-
* @param result - The PhysicsRaycastResult object to store the result of the raycast.
2151+
* @param result - The PhysicsRaycastResult object (or array of PhysicsRaycastResults) to store the result of the raycast.
21102152
* @param query - The raycast query options. See [[IRaycastQuery]] for more information.
21112153
*
21122154
* Performs a raycast. It takes in two points, from and to, and a PhysicsRaycastResult object to store the result of the raycast.
21132155
* It then performs the raycast and stores the hit data in the PhysicsRaycastResult object.
2156+
* If result is an empty array, it will be populated with every detected raycast hit.
2157+
* If result is a populated array, it will only fill the PhysicsRaycastResults present in the array.
21142158
*/
2115-
public raycast(from: Vector3, to: Vector3, result: PhysicsRaycastResult, query?: IRaycastQuery): void {
2159+
public raycast(from: Vector3, to: Vector3, result: PhysicsRaycastResult | Array<PhysicsRaycastResult>, query?: IRaycastQuery): void {
21162160
const queryMembership = query?.membership ?? ~0;
21172161
const queryCollideWith = query?.collideWith ?? ~0;
21182162
const shouldHitTriggers = query?.shouldHitTriggers ?? false;
2163+
const bodyToIgnore = query?.ignoreBody ? [BigInt(query.ignoreBody._pluginData.hpBodyId[0])] : [BigInt(0)];
21192164

2120-
result.reset(from, to);
2165+
const results = Array.isArray(result) ? result : [result];
2166+
for (const raycastResult of results) {
2167+
raycastResult.reset(from, to);
2168+
}
21212169

2122-
const bodyToIgnore = query?.ignoreBody ? [BigInt(query.ignoreBody._pluginData.hpBodyId[0])] : [BigInt(0)];
21232170
const hkQuery = [this._bVecToV3(from), this._bVecToV3(to), [queryMembership, queryCollideWith], shouldHitTriggers, bodyToIgnore];
2124-
this._hknp.HP_World_CastRayWithCollector(this.world, this._queryCollector, hkQuery);
2171+
const queryCollector = results.length === 1 || !this._multiQueryCollector ? this._queryCollector : this._multiQueryCollector;
2172+
this._hknp.HP_World_CastRayWithCollector(this.world, queryCollector, hkQuery);
21252173

2126-
if (this._hknp.HP_QueryCollector_GetNumHits(this._queryCollector)[1] > 0) {
2127-
const [, hitData] = this._hknp.HP_QueryCollector_GetCastRayResult(this._queryCollector, 0)[1];
2174+
const numHits = this._hknp.HP_QueryCollector_GetNumHits(queryCollector)[1];
2175+
if (numHits <= 0) {
2176+
return;
2177+
}
21282178

2129-
this._populateHitData(hitData, result);
2130-
result.calculateHitDistance();
2179+
if (!results.length) {
2180+
for (let i = 0; i < numHits; i++) {
2181+
const raycastResult = new PhysicsRaycastResult();
2182+
raycastResult.reset(from, to);
2183+
results.push(raycastResult);
2184+
}
2185+
}
2186+
2187+
// QueryCollector results are not sorted by distance, so we need to sort them manually
2188+
const hitDatas: Array<{ hitData: any; distance: number }> = new Array(numHits);
2189+
for (let i = 0; i < numHits; i++) {
2190+
const [, hitData] = this._hknp.HP_QueryCollector_GetCastRayResult(queryCollector, i)[1];
2191+
2192+
const hitPos = hitData[3];
2193+
from.subtractFromFloatsToRef(hitPos[0], hitPos[1], hitPos[2], this._tmpVec3[0]);
2194+
const distance = this._tmpVec3[0].lengthSquared();
2195+
2196+
hitDatas[i] = {
2197+
hitData,
2198+
distance: distance,
2199+
};
2200+
}
2201+
2202+
hitDatas.sort((a, b) => a.distance - b.distance);
2203+
2204+
for (let i = 0; i < Math.min(numHits, results.length); i++) {
2205+
const raycastResult = results[i];
2206+
const hitData = hitDatas[i];
2207+
2208+
this._populateHitData(hitData.hitData, raycastResult);
2209+
raycastResult.setHitDistance(Math.sqrt(hitData.distance));
21312210
}
21322211
}
21332212

@@ -2404,6 +2483,10 @@ export class HavokPlugin implements IPhysicsEnginePluginV2 {
24042483
this._hknp.HP_QueryCollector_Release(this._queryCollector);
24052484
this._queryCollector = undefined;
24062485
}
2486+
if (this._multiQueryCollector) {
2487+
this._hknp.HP_QueryCollector_Release(this._multiQueryCollector);
2488+
this._multiQueryCollector;
2489+
}
24072490
if (this.world) {
24082491
this._hknp.HP_World_Release(this.world);
24092492
this.world = undefined;

packages/dev/core/src/Physics/v2/physicsEngine.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,12 @@ export class PhysicsEngine implements IPhysicsEngine {
192192
* Does a raycast in the physics world
193193
* @param from when should the ray start?
194194
* @param to when should the ray end?
195-
* @param result resulting PhysicsRaycastResult
195+
* @param result resulting PhysicsRaycastResult or array of PhysicsRaycastResults
196196
* @param query raycast query object
197+
* If result is an empty array, it will be populated with every detected raycast hit.
198+
* If result is a populated array, it will only fill the PhysicsRaycastResults present in the array.
197199
*/
198-
public raycastToRef(from: Vector3, to: Vector3, result: PhysicsRaycastResult, query?: IRaycastQuery): void {
200+
public raycastToRef(from: Vector3, to: Vector3, result: PhysicsRaycastResult | Array<PhysicsRaycastResult>, query?: IRaycastQuery): void {
199201
this._physicsPlugin.raycast(from, to, result, query);
200202
}
201203

@@ -211,4 +213,17 @@ export class PhysicsEngine implements IPhysicsEngine {
211213
this._physicsPlugin.raycast(from, to, result, query);
212214
return result;
213215
}
216+
217+
/**
218+
* Does a raycast through multiple objects in the physics world
219+
* @param from when should the ray start?
220+
* @param to when should the ray end?
221+
* @param query raycast query object
222+
* @returns array of PhysicsRaycastResult
223+
*/
224+
public raycastMulti(from: Vector3, to: Vector3, query?: IRaycastQuery): Array<PhysicsRaycastResult> {
225+
const result: Array<PhysicsRaycastResult> = [];
226+
this._physicsPlugin.raycast(from, to, result, query);
227+
return result;
228+
}
214229
}

0 commit comments

Comments
 (0)