Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 216f9a9

Browse files
committed
feat(rapier): add filterContactPair and filterIntersectionPair hooks
- Add NgtrFilterContactPairCallback and NgtrFilterIntersectionPairCallback types - Add filterContactPair() and filterIntersectionPair() CIFs to physics-hooks.ts - Rename physics-step-callback.ts to physics-hooks.ts - Update NgtrPhysics to support PhysicsHooks with world.step() - Expose collider computed signal on all collider directives - Add exportAs to all collider directives - Add one-way-platform example demonstrating filter hooks usage
1 parent 40f41f2 commit 216f9a9

File tree

9 files changed

+449
-96
lines changed

9 files changed

+449
-96
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
CUSTOM_ELEMENTS_SCHEMA,
5+
effect,
6+
inject,
7+
signal,
8+
viewChild,
9+
} from '@angular/core';
10+
import { RigidBody } from '@dimforge/rapier3d-compat';
11+
import { injectStore, NgtArgs } from 'angular-three';
12+
import {
13+
beforePhysicsStep,
14+
filterContactPair,
15+
NgtrCuboidCollider,
16+
NgtrPhysics,
17+
NgtrRigidBody,
18+
} from 'angular-three-rapier';
19+
import * as THREE from 'three';
20+
import { ResetOrbitControls } from '../reset-orbit-controls';
21+
22+
@Component({
23+
selector: 'app-one-way-platform',
24+
template: `
25+
<ngt-group>
26+
<!-- Ball -->
27+
<ngt-object3D
28+
#ballRigidBody="rigidBody"
29+
rigidBody
30+
[options]="{ colliders: 'ball' }"
31+
[position]="[0, -5, 0]"
32+
>
33+
<ngt-mesh castShadow receiveShadow>
34+
<ngt-sphere-geometry />
35+
<ngt-mesh-physical-material color="red" />
36+
</ngt-mesh>
37+
</ngt-object3D>
38+
39+
<!-- Platform visual -->
40+
<ngt-mesh>
41+
<ngt-box-geometry *args="[10, 0.1, 10]" />
42+
<ngt-mesh-standard-material
43+
[color]="filteringEnabled() ? 'orange' : 'grey'"
44+
[opacity]="0.5"
45+
[transparent]="true"
46+
/>
47+
</ngt-mesh>
48+
49+
<!-- Platform collider -->
50+
<ngt-object3D #platformRigidBody="rigidBody" rigidBody="fixed">
51+
<ngt-object3D [cuboidCollider]="[10, 0.1, 10]" />
52+
</ngt-object3D>
53+
</ngt-group>
54+
`,
55+
imports: [NgtrRigidBody, NgtrCuboidCollider, NgtArgs],
56+
changeDetection: ChangeDetectionStrategy.OnPush,
57+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
58+
hostDirectives: [ResetOrbitControls],
59+
host: { '(document:click)': 'onDocumentClick()' },
60+
})
61+
export default class OneWayPlatform {
62+
filteringEnabled = signal(true);
63+
64+
private ballRigidBodyRef = viewChild.required<NgtrRigidBody>('ballRigidBody');
65+
private platformRigidBodyRef = viewChild.required<NgtrRigidBody>('platformRigidBody');
66+
private platformColliderRef = viewChild.required(NgtrCuboidCollider);
67+
68+
private physics = inject(NgtrPhysics);
69+
private store = injectStore();
70+
71+
// Cache for storing body states before physics step
72+
private bodyStateCache = new Map<number, { position: THREE.Vector3; velocity: THREE.Vector3 }>();
73+
74+
constructor() {
75+
// Setup camera
76+
effect(() => {
77+
const camera = this.store.camera();
78+
camera.position.set(0, 10, 20);
79+
camera.lookAt(0, 0, 0);
80+
camera.updateProjectionMatrix();
81+
});
82+
83+
// Update collider activeHooks based on filteringEnabled
84+
effect(() => {
85+
const rapier = this.physics.rapier();
86+
if (!rapier) return;
87+
88+
const collider = this.platformColliderRef().collider();
89+
if (!collider) return;
90+
91+
collider.setActiveHooks(this.filteringEnabled() ? rapier.ActiveHooks.FILTER_CONTACT_PAIRS : 0);
92+
});
93+
94+
// Cache body states BEFORE the physics step
95+
beforePhysicsStep(() => {
96+
const platformRigidBody = this.platformRigidBodyRef().rigidBody();
97+
const ballRigidBody = this.ballRigidBodyRef().rigidBody();
98+
99+
if (!platformRigidBody || !ballRigidBody) return;
100+
101+
this.cacheBodyState(platformRigidBody);
102+
this.cacheBodyState(ballRigidBody);
103+
});
104+
105+
// Filter contact pairs for one-way platform behavior
106+
filterContactPair((_c1, _c2, b1, b2) => {
107+
const rapier = this.physics.rapier();
108+
if (!rapier) return null;
109+
110+
// If filtering is disabled, let default collision behavior happen
111+
if (!this.filteringEnabled()) return null;
112+
113+
const state1 = this.bodyStateCache.get(b1);
114+
const state2 = this.bodyStateCache.get(b2);
115+
116+
if (!state1 || !state2) return null;
117+
118+
const platformHandle = this.platformRigidBodyRef().rigidBody()?.handle;
119+
const ballHandle = this.ballRigidBodyRef().rigidBody()?.handle;
120+
121+
// Determine which is platform and which is ball
122+
let platformState: { position: THREE.Vector3; velocity: THREE.Vector3 } | undefined;
123+
let ballState: { position: THREE.Vector3; velocity: THREE.Vector3 } | undefined;
124+
125+
if (platformHandle === b1 && ballHandle === b2) {
126+
platformState = state1;
127+
ballState = state2;
128+
} else if (platformHandle === b2 && ballHandle === b1) {
129+
platformState = state2;
130+
ballState = state1;
131+
} else {
132+
return null; // Not our platform/ball pair
133+
}
134+
135+
// Allow collision only if the ball is moving downwards and above the platform
136+
if (ballState.velocity.y < 0 && ballState.position.y > platformState.position.y) {
137+
return rapier.SolverFlags.COMPUTE_IMPULSE; // Process the collision
138+
}
139+
140+
return rapier.SolverFlags.EMPTY; // Ignore the collision (pass through)
141+
});
142+
}
143+
144+
protected onDocumentClick() {
145+
const ballRigidBody = this.ballRigidBodyRef().rigidBody();
146+
if (!ballRigidBody) return;
147+
ballRigidBody.applyImpulse(new THREE.Vector3(0, 50, 0), true);
148+
}
149+
150+
private cacheBodyState(rigidBody: RigidBody) {
151+
const handle = rigidBody.handle;
152+
const pos = rigidBody.translation();
153+
const vel = rigidBody.linvel();
154+
155+
let state = this.bodyStateCache.get(handle);
156+
if (!state) {
157+
state = {
158+
position: new THREE.Vector3(),
159+
velocity: new THREE.Vector3(),
160+
};
161+
this.bodyStateCache.set(handle, state);
162+
}
163+
164+
state.position.set(pos.x, pos.y, pos.z);
165+
state.velocity.set(vel.x, vel.y, vel.z);
166+
}
167+
}

apps/examples/src/app/rapier/rapier.routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ const routes: Routes = [
8686
path: 'all-shapes',
8787
loadComponent: () => import('./all-shapes/all-shapes'),
8888
},
89+
{
90+
path: 'one-way-platform',
91+
providers: [provideResetOrbitControls(20)],
92+
loadComponent: () => import('./one-way-platform/one-way-platform'),
93+
},
8994
{
9095
path: '',
9196
redirectTo: 'basic',

libs/rapier/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export * from './lib/interaction-groups';
44
export * from './lib/joints';
55
export * from './lib/mesh-collider';
66
export * from './lib/physics';
7-
export * from './lib/physics-step-callback';
7+
export * from './lib/physics-hooks';
88
export * from './lib/rigid-body';
99

1010
export type * from './lib/types';

0 commit comments

Comments
 (0)