Skip to content

Commit 5c425de

Browse files
Chau TranChau Tran
Chau Tran
authored and
Chau Tran
committed
feat(cannon): migrate raycast vehicle
1 parent 9805f50 commit 5c425de

File tree

3 files changed

+184
-60
lines changed

3 files changed

+184
-60
lines changed

libs/cannon/services/src/body.ts

Lines changed: 63 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ function capitalize<T extends string>(str: T): Capitalize<T> {
8383
return (str.charAt(0).toUpperCase() + str.slice(1)) as Capitalize<T>;
8484
}
8585

86-
function getUUID(ref: ElementRef<THREE.Object3D>, index?: number): string | null {
86+
export function getUUID(ref: ElementRef<THREE.Object3D>, index?: number): string | null {
8787
const suffix = index === undefined ? '' : `/${index}`;
8888
if (typeof ref === 'function') return null;
8989
return ref && ref.nativeElement && `${ref.nativeElement.uuid}${suffix}`;
@@ -98,7 +98,7 @@ const quaternionToRotation = (callback: (v: Triplet) => void) => {
9898

9999
let incrementingId = 0;
100100

101-
function subscribe<T extends SubscriptionName>(
101+
export function subscribe<T extends SubscriptionName>(
102102
ref: ElementRef<THREE.Object3D>,
103103
worker: CannonWorkerAPI,
104104
subscriptions: Subscriptions,
@@ -245,66 +245,69 @@ function injectBody<TBodyProps extends BodyProps, TObject extends THREE.Object3D
245245
const { refs, worker, subscriptions, scaleOverrides, events } = physicsApi();
246246
const { add: debugAdd, remove: debugRemove } = debugApi?.() || {};
247247

248-
effect((onCleanup) => {
249-
// register deps
250-
deps();
251-
252-
if (!bodyRef.untracked) {
253-
bodyRef.nativeElement = new THREE.Object3D() as TObject;
254-
}
248+
effect(
249+
(onCleanup) => {
250+
// register deps
251+
deps();
252+
253+
if (!bodyRef.untracked) {
254+
bodyRef.nativeElement = new THREE.Object3D() as TObject;
255+
}
256+
257+
const object = bodyRef.untracked;
258+
const currentWorker = worker;
259+
260+
const objectCount =
261+
object instanceof THREE.InstancedMesh
262+
? (object.instanceMatrix.setUsage(THREE.DynamicDrawUsage), object.count)
263+
: 1;
264+
265+
const uuid =
266+
object instanceof THREE.InstancedMesh
267+
? new Array(objectCount).fill(0).map((_, i) => `${object.uuid}/${i}`)
268+
: [object.uuid];
269+
270+
const props: (TBodyProps & { args: unknown })[] =
271+
object instanceof THREE.InstancedMesh
272+
? uuid.map((id, i) => {
273+
const props = getPropsFn(i);
274+
prepare(temp, props);
275+
object.setMatrixAt(i, temp.matrix);
276+
object.instanceMatrix.needsUpdate = true;
277+
refs[id] = object;
278+
debugAdd?.(id, props, type);
279+
setupCollision(events, props, id);
280+
return { ...props, args: argsFn(props.args) };
281+
})
282+
: uuid.map((id, i) => {
283+
const props = getPropsFn(i);
284+
prepare(object, props);
285+
refs[id] = object;
286+
debugAdd?.(id, props, type);
287+
setupCollision(events, props, id);
288+
return { ...props, args: argsFn(props.args) };
289+
});
290+
291+
// Register on mount, unregister on unmount
292+
currentWorker.addBodies({
293+
props: props.map(({ onCollide, onCollideBegin, onCollideEnd, ...serializableProps }) => {
294+
return { onCollide: Boolean(onCollide), ...serializableProps };
295+
}),
296+
type,
297+
uuid,
298+
});
255299

256-
const object = bodyRef.untracked;
257-
const currentWorker = worker;
258-
259-
const objectCount =
260-
object instanceof THREE.InstancedMesh
261-
? (object.instanceMatrix.setUsage(THREE.DynamicDrawUsage), object.count)
262-
: 1;
263-
264-
const uuid =
265-
object instanceof THREE.InstancedMesh
266-
? new Array(objectCount).fill(0).map((_, i) => `${object.uuid}/${i}`)
267-
: [object.uuid];
268-
269-
const props: (TBodyProps & { args: unknown })[] =
270-
object instanceof THREE.InstancedMesh
271-
? uuid.map((id, i) => {
272-
const props = getPropsFn(i);
273-
prepare(temp, props);
274-
object.setMatrixAt(i, temp.matrix);
275-
object.instanceMatrix.needsUpdate = true;
276-
refs[id] = object;
277-
debugAdd?.(id, props, type);
278-
setupCollision(events, props, id);
279-
return { ...props, args: argsFn(props.args) };
280-
})
281-
: uuid.map((id, i) => {
282-
const props = getPropsFn(i);
283-
prepare(object, props);
284-
refs[id] = object;
285-
debugAdd?.(id, props, type);
286-
setupCollision(events, props, id);
287-
return { ...props, args: argsFn(props.args) };
288-
});
289-
290-
// Register on mount, unregister on unmount
291-
currentWorker.addBodies({
292-
props: props.map(({ onCollide, onCollideBegin, onCollideEnd, ...serializableProps }) => {
293-
return { onCollide: Boolean(onCollide), ...serializableProps };
294-
}),
295-
type,
296-
uuid,
297-
});
298-
299-
onCleanup(() => {
300-
uuid.forEach((id) => {
301-
delete refs[id];
302-
debugRemove?.(id);
303-
delete events[id];
300+
onCleanup(() => {
301+
uuid.forEach((id) => {
302+
delete refs[id];
303+
debugRemove?.(id);
304+
delete events[id];
305+
});
306+
currentWorker.removeBodies({ uuid });
304307
});
305-
currentWorker.removeBodies({ uuid });
306-
});
307-
});
308+
},
309+
{ allowSignalWrites: true }
310+
);
308311

309312
const api = computed(() => {
310313
const makeAtomic = <T extends AtomicName>(type: T, index?: number) => {

libs/cannon/services/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './body';
22
export * from './constraint';
33
export * from './ray';
4+
export * from './raycast-vehicle';
45
export * from './spring';
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { Injector, Signal, computed, effect, inject, runInInjectionContext, untracked } from '@angular/core';
2+
import { WheelInfoOptions } from '@pmndrs/cannon-worker-api';
3+
import { NgtAnyRecord, NgtInjectedRef, assertInjectionContext, injectNgtRef } from 'angular-three';
4+
import { NGTC_PHYSICS_API } from 'angular-three-cannon';
5+
import * as THREE from 'three';
6+
import { getUUID, subscribe } from './body';
7+
8+
function isString(v: unknown): v is string {
9+
return typeof v === 'string';
10+
}
11+
12+
export interface NgtcRaycastVehicleProps {
13+
chassisBody: NgtInjectedRef<THREE.Object3D>;
14+
wheelInfos: WheelInfoOptions[];
15+
wheels: Array<NgtInjectedRef<THREE.Object3D>>;
16+
indexForwardAxis?: number;
17+
indexRightAxis?: number;
18+
indexUpAxis?: number;
19+
}
20+
21+
export interface NgtcRaycastVehiclePublicApi {
22+
applyEngineForce: (value: number, wheelIndex: number) => void;
23+
setBrake: (brake: number, wheelIndex: number) => void;
24+
setSteeringValue: (value: number, wheelIndex: number) => void;
25+
sliding: { subscribe: (callback: (sliding: boolean) => void) => void };
26+
remove: () => void;
27+
}
28+
29+
export interface NgtcRaycastVehicleReturn<TObject extends THREE.Object3D = THREE.Object3D> {
30+
ref: NgtInjectedRef<TObject>;
31+
api: Signal<NgtcRaycastVehiclePublicApi>;
32+
}
33+
34+
export function injectRaycastVehicle<TObject extends THREE.Object3D = THREE.Object3D>(
35+
fn: () => NgtcRaycastVehicleProps,
36+
{
37+
ref,
38+
injector,
39+
deps = () => ({}),
40+
}: { ref?: NgtInjectedRef<TObject>; injector?: Injector; deps?: () => NgtAnyRecord } = {}
41+
): NgtcRaycastVehicleReturn<TObject> {
42+
injector = assertInjectionContext(injectRaycastVehicle, injector);
43+
return runInInjectionContext(injector, () => {
44+
const physicsApi = inject(NGTC_PHYSICS_API);
45+
const { worker, subscriptions } = physicsApi();
46+
47+
let instanceRef = injectNgtRef<TObject>();
48+
if (ref) instanceRef = ref;
49+
50+
effect(
51+
(onCleanup) => {
52+
deps();
53+
if (!instanceRef.untracked) {
54+
instanceRef.nativeElement = new THREE.Object3D() as TObject;
55+
}
56+
57+
const currentWorker = worker;
58+
const uuid: string = instanceRef.nativeElement.uuid;
59+
const {
60+
chassisBody,
61+
indexForwardAxis = 2,
62+
indexRightAxis = 0,
63+
indexUpAxis = 1,
64+
wheelInfos,
65+
wheels,
66+
} = untracked(fn);
67+
68+
const chassisBodyUUID = getUUID(chassisBody);
69+
const wheelUUIDs = wheels.map((ref) => getUUID(ref));
70+
71+
if (!chassisBodyUUID || !wheelUUIDs.every(isString)) return;
72+
73+
currentWorker.addRaycastVehicle({
74+
props: [chassisBodyUUID, wheelUUIDs, wheelInfos, indexForwardAxis, indexRightAxis, indexUpAxis],
75+
uuid,
76+
});
77+
onCleanup(() => {
78+
currentWorker.removeRaycastVehicle({ uuid });
79+
});
80+
},
81+
{ allowSignalWrites: true }
82+
);
83+
84+
const api = computed<NgtcRaycastVehiclePublicApi>(() => {
85+
deps();
86+
return {
87+
applyEngineForce(value: number, wheelIndex: number) {
88+
const uuid = getUUID(instanceRef);
89+
uuid &&
90+
worker.applyRaycastVehicleEngineForce({
91+
props: [value, wheelIndex],
92+
uuid,
93+
});
94+
},
95+
setBrake(brake: number, wheelIndex: number) {
96+
const uuid = getUUID(instanceRef);
97+
uuid && worker.setRaycastVehicleBrake({ props: [brake, wheelIndex], uuid });
98+
},
99+
setSteeringValue(value: number, wheelIndex: number) {
100+
const uuid = getUUID(instanceRef);
101+
uuid &&
102+
worker.setRaycastVehicleSteeringValue({
103+
props: [value, wheelIndex],
104+
uuid,
105+
});
106+
},
107+
sliding: {
108+
subscribe: subscribe(instanceRef, worker, subscriptions, 'sliding', undefined, 'vehicles'),
109+
},
110+
111+
remove: () => {
112+
const uuid = getUUID(instanceRef);
113+
uuid && worker.removeRaycastVehicle({ uuid });
114+
},
115+
};
116+
});
117+
118+
return { ref: instanceRef, api };
119+
});
120+
}

0 commit comments

Comments
 (0)