react-three-rapier-unified 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +166 -0
- package/dist/index.cjs.js +31 -0
- package/dist/index.d.ts +1191 -0
- package/dist/index.esm.js +1667 -0
- package/package.json +53 -0
- package/src/addons/attractor/Attractor.tsx +178 -0
- package/src/addons/attractor/AttractorDebugHelper.tsx +59 -0
- package/src/components/AnyCollider.tsx +286 -0
- package/src/components/Debug.tsx +33 -0
- package/src/components/FrameStepper.tsx +36 -0
- package/src/components/InstancedRigidBodies.tsx +135 -0
- package/src/components/MeshCollider.tsx +53 -0
- package/src/components/Physics.tsx +894 -0
- package/src/components/RigidBody.tsx +153 -0
- package/src/hooks/hooks.ts +211 -0
- package/src/hooks/joints.ts +221 -0
- package/src/hooks/use-forwarded-ref.ts +19 -0
- package/src/hooks/use-imperative-instance.ts +33 -0
- package/src/index.ts +55 -0
- package/src/types.ts +541 -0
- package/src/utils/interaction-groups.ts +43 -0
- package/src/utils/shared-objects.ts +10 -0
- package/src/utils/singleton-proxy.ts +51 -0
- package/src/utils/three-object-helpers.ts +25 -0
- package/src/utils/utils-collider.ts +491 -0
- package/src/utils/utils-physics.ts +26 -0
- package/src/utils/utils-rigidbody.ts +231 -0
- package/src/utils/utils.ts +105 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
forwardRef,
|
|
4
|
+
memo,
|
|
5
|
+
ReactNode,
|
|
6
|
+
Ref,
|
|
7
|
+
RefObject,
|
|
8
|
+
useContext,
|
|
9
|
+
useEffect,
|
|
10
|
+
useMemo,
|
|
11
|
+
useRef
|
|
12
|
+
} from "react";
|
|
13
|
+
import { Object3D } from "three";
|
|
14
|
+
import { useChildColliderProps, useRapier } from "../hooks/hooks";
|
|
15
|
+
import { useForwardedRef } from "../hooks/use-forwarded-ref";
|
|
16
|
+
import { useImperativeInstance } from "../hooks/use-imperative-instance";
|
|
17
|
+
import { RapierRigidBody, RigidBodyOptions } from "../types";
|
|
18
|
+
import {
|
|
19
|
+
createRigidBodyState,
|
|
20
|
+
immutableRigidBodyOptions,
|
|
21
|
+
rigidBodyDescFromOptions,
|
|
22
|
+
useRigidBodyEvents,
|
|
23
|
+
useUpdateRigidBodyOptions
|
|
24
|
+
} from "../utils/utils-rigidbody";
|
|
25
|
+
import { AnyCollider } from "./AnyCollider";
|
|
26
|
+
|
|
27
|
+
type RigidBodyContextType = {
|
|
28
|
+
ref: RefObject<Object3D>;
|
|
29
|
+
getRigidBody: () => RapierRigidBody;
|
|
30
|
+
options: RigidBodyOptions;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const RigidBodyContext = createContext<RigidBodyContextType>(undefined!);
|
|
34
|
+
|
|
35
|
+
export const useRigidBodyContext = () => useContext(RigidBodyContext);
|
|
36
|
+
|
|
37
|
+
export interface RigidBodyProps extends RigidBodyOptions {
|
|
38
|
+
children?: ReactNode;
|
|
39
|
+
ref?: Ref<RapierRigidBody>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A rigid body is a physical object that can be simulated by the physics engine.
|
|
44
|
+
* @category Components
|
|
45
|
+
*/
|
|
46
|
+
export const RigidBody = memo((props: RigidBodyProps) => {
|
|
47
|
+
const {
|
|
48
|
+
ref,
|
|
49
|
+
children,
|
|
50
|
+
|
|
51
|
+
type,
|
|
52
|
+
position,
|
|
53
|
+
rotation,
|
|
54
|
+
scale,
|
|
55
|
+
|
|
56
|
+
quaternion,
|
|
57
|
+
transformState,
|
|
58
|
+
...objectProps
|
|
59
|
+
} = props;
|
|
60
|
+
|
|
61
|
+
const objectRef = useRef<Object3D>(null);
|
|
62
|
+
const rigidBodyRef = useForwardedRef(ref);
|
|
63
|
+
const { world, rigidBodyStates, physicsOptions, rigidBodyEvents } =
|
|
64
|
+
useRapier();
|
|
65
|
+
|
|
66
|
+
const mergedOptions = useMemo(() => {
|
|
67
|
+
return {
|
|
68
|
+
...physicsOptions,
|
|
69
|
+
...props,
|
|
70
|
+
children: undefined
|
|
71
|
+
};
|
|
72
|
+
}, [physicsOptions, props]);
|
|
73
|
+
|
|
74
|
+
const immutablePropArray = immutableRigidBodyOptions.flatMap((key) => {
|
|
75
|
+
return Array.isArray(mergedOptions[key])
|
|
76
|
+
? [...mergedOptions[key]]
|
|
77
|
+
: mergedOptions[key];
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const childColliderProps = useChildColliderProps(objectRef, mergedOptions);
|
|
81
|
+
|
|
82
|
+
// Provide a way to eagerly create rigidbody
|
|
83
|
+
const getRigidBody = useImperativeInstance(
|
|
84
|
+
() => {
|
|
85
|
+
const desc = rigidBodyDescFromOptions(mergedOptions);
|
|
86
|
+
const rigidBody = world.createRigidBody(desc);
|
|
87
|
+
|
|
88
|
+
if (typeof ref === "function") {
|
|
89
|
+
ref(rigidBody);
|
|
90
|
+
}
|
|
91
|
+
rigidBodyRef.current = rigidBody;
|
|
92
|
+
|
|
93
|
+
return rigidBody;
|
|
94
|
+
},
|
|
95
|
+
(rigidBody) => {
|
|
96
|
+
if (world.getRigidBody(rigidBody.handle)) {
|
|
97
|
+
world.removeRigidBody(rigidBody);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
immutablePropArray
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Only provide a object state after the ref has been set
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const rigidBody = getRigidBody();
|
|
106
|
+
|
|
107
|
+
const state = createRigidBodyState({
|
|
108
|
+
rigidBody,
|
|
109
|
+
object: objectRef.current!
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
rigidBodyStates.set(
|
|
113
|
+
rigidBody.handle,
|
|
114
|
+
props.transformState ? props.transformState(state) : state
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return () => {
|
|
118
|
+
rigidBodyStates.delete(rigidBody.handle);
|
|
119
|
+
};
|
|
120
|
+
}, [getRigidBody]);
|
|
121
|
+
|
|
122
|
+
useUpdateRigidBodyOptions(getRigidBody, mergedOptions, rigidBodyStates);
|
|
123
|
+
useRigidBodyEvents(getRigidBody, mergedOptions, rigidBodyEvents);
|
|
124
|
+
|
|
125
|
+
const contextValue = useMemo(() => {
|
|
126
|
+
return {
|
|
127
|
+
ref: objectRef as RigidBodyContextType["ref"],
|
|
128
|
+
getRigidBody: getRigidBody,
|
|
129
|
+
options: mergedOptions
|
|
130
|
+
};
|
|
131
|
+
}, [getRigidBody]);
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<RigidBodyContext.Provider value={contextValue}>
|
|
135
|
+
<object3D
|
|
136
|
+
ref={objectRef}
|
|
137
|
+
{...objectProps}
|
|
138
|
+
position={position}
|
|
139
|
+
rotation={rotation}
|
|
140
|
+
quaternion={quaternion}
|
|
141
|
+
scale={scale}
|
|
142
|
+
>
|
|
143
|
+
{children}
|
|
144
|
+
|
|
145
|
+
{childColliderProps.map((colliderProps, index) => (
|
|
146
|
+
<AnyCollider key={index} {...colliderProps} />
|
|
147
|
+
))}
|
|
148
|
+
</object3D>
|
|
149
|
+
</RigidBodyContext.Provider>
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
RigidBody.displayName = "RigidBody";
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
RefObject,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState
|
|
7
|
+
} from "react";
|
|
8
|
+
import {
|
|
9
|
+
rapierContext,
|
|
10
|
+
RapierContext,
|
|
11
|
+
WorldStepCallback
|
|
12
|
+
} from "../components/Physics";
|
|
13
|
+
import { Object3D } from "three";
|
|
14
|
+
|
|
15
|
+
import { ColliderProps, RigidBodyProps } from "..";
|
|
16
|
+
import { createColliderPropsFromChildren } from "../utils/utils-collider";
|
|
17
|
+
|
|
18
|
+
// Utils
|
|
19
|
+
const useMutableCallback = <T>(fn: T) => {
|
|
20
|
+
const ref = useRef<T>(fn);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
ref.current = fn;
|
|
23
|
+
}, [fn]);
|
|
24
|
+
return ref;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// External hooks
|
|
28
|
+
/**
|
|
29
|
+
* Exposes the Rapier context, and world
|
|
30
|
+
* @category Hooks
|
|
31
|
+
*/
|
|
32
|
+
export const useRapier = (): RapierContext => {
|
|
33
|
+
const rapier = useContext(rapierContext);
|
|
34
|
+
if (!rapier)
|
|
35
|
+
throw new Error(
|
|
36
|
+
"react-three-rapier: useRapier must be used within <Physics />!"
|
|
37
|
+
);
|
|
38
|
+
return rapier;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Registers a callback to be called before the physics step
|
|
43
|
+
* @category Hooks
|
|
44
|
+
*/
|
|
45
|
+
export const useBeforePhysicsStep = (callback: WorldStepCallback) => {
|
|
46
|
+
const { beforeStepCallbacks } = useRapier();
|
|
47
|
+
|
|
48
|
+
const ref = useMutableCallback(callback);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
beforeStepCallbacks.add(ref);
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
beforeStepCallbacks.delete(ref);
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Registers a callback to be called after the physics step
|
|
61
|
+
* @category Hooks
|
|
62
|
+
*/
|
|
63
|
+
export const useAfterPhysicsStep = (callback: WorldStepCallback) => {
|
|
64
|
+
const { afterStepCallbacks } = useRapier();
|
|
65
|
+
|
|
66
|
+
const ref = useMutableCallback(callback);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
afterStepCallbacks.add(ref);
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
afterStepCallbacks.delete(ref);
|
|
73
|
+
};
|
|
74
|
+
}, []);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Registers a callback to filter contact pairs.
|
|
79
|
+
*
|
|
80
|
+
* The callback determines if contact computation should happen between two colliders,
|
|
81
|
+
* and how the constraints solver should behave for these contacts.
|
|
82
|
+
*
|
|
83
|
+
* This will only be executed if at least one of the involved colliders contains the
|
|
84
|
+
* `ActiveHooks.FILTER_CONTACT_PAIR` flag in its active hooks.
|
|
85
|
+
*
|
|
86
|
+
* @param callback - Function that returns:
|
|
87
|
+
* - `SolverFlags.COMPUTE_IMPULSE` (1) - Process the collision normally (compute impulses and resolve penetration)
|
|
88
|
+
* - `SolverFlags.EMPTY` (0) - Skip computing impulses for this collision pair (colliders pass through each other)
|
|
89
|
+
* - `null` - Skip this hook; let the next registered hook decide, or use Rapier's default behavior if no hook handles it
|
|
90
|
+
*
|
|
91
|
+
* When multiple hooks are registered, they are called in order until one returns a non-null value.
|
|
92
|
+
* That value is then passed to Rapier's physics engine.
|
|
93
|
+
*
|
|
94
|
+
* @category Hooks
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```tsx
|
|
98
|
+
* import { useFilterContactPair } from '@react-three/rapier';
|
|
99
|
+
* import { SolverFlags } from '@dimforge/rapier3d-compat';
|
|
100
|
+
*
|
|
101
|
+
* useFilterContactPair((collider1, collider2, body1, body2) => {
|
|
102
|
+
* // Only process collisions for specific bodies
|
|
103
|
+
* if (body1 === myBodyHandle) {
|
|
104
|
+
* return SolverFlags.COMPUTE_IMPULSE;
|
|
105
|
+
* }
|
|
106
|
+
* // Let other hooks or default behavior handle it
|
|
107
|
+
* return null;
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export const useFilterContactPair = (
|
|
112
|
+
callback: (
|
|
113
|
+
collider1: number,
|
|
114
|
+
collider2: number,
|
|
115
|
+
body1: number,
|
|
116
|
+
body2: number
|
|
117
|
+
) => number | null
|
|
118
|
+
) => {
|
|
119
|
+
const { filterContactPairHooks } = useRapier();
|
|
120
|
+
|
|
121
|
+
const ref = useMutableCallback(callback);
|
|
122
|
+
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
filterContactPairHooks.add(ref);
|
|
125
|
+
|
|
126
|
+
return () => {
|
|
127
|
+
filterContactPairHooks.delete(ref);
|
|
128
|
+
};
|
|
129
|
+
}, []);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Registers a callback to filter intersection pairs.
|
|
134
|
+
*
|
|
135
|
+
* The callback determines if intersection computation should happen between two colliders
|
|
136
|
+
* (where at least one is a sensor).
|
|
137
|
+
*
|
|
138
|
+
* This will only be executed if at least one of the involved colliders contains the
|
|
139
|
+
* `ActiveHooks.FILTER_INTERSECTION_PAIR` flag in its active hooks.
|
|
140
|
+
*
|
|
141
|
+
* @param callback - Function that returns:
|
|
142
|
+
* - `true` - Allow the intersection to be detected (trigger intersection events)
|
|
143
|
+
* - `false` - Block the intersection (no intersection events will fire)
|
|
144
|
+
*
|
|
145
|
+
* When multiple hooks are registered, the **first hook that returns `false` blocks** the intersection.
|
|
146
|
+
* If all hooks return `true`, the intersection is allowed.
|
|
147
|
+
*
|
|
148
|
+
* @category Hooks
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```tsx
|
|
152
|
+
* import { useFilterIntersectionPair } from '@react-three/rapier';
|
|
153
|
+
*
|
|
154
|
+
* useFilterIntersectionPair((collider1, collider2, body1, body2) => {
|
|
155
|
+
* // Block intersections for specific body pairs
|
|
156
|
+
* if (body1 === myBodyHandle && body2 === otherBodyHandle) {
|
|
157
|
+
* return false;
|
|
158
|
+
* }
|
|
159
|
+
* // Allow all other intersections
|
|
160
|
+
* return true;
|
|
161
|
+
* });
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export const useFilterIntersectionPair = (
|
|
165
|
+
callback: (
|
|
166
|
+
collider1: number,
|
|
167
|
+
collider2: number,
|
|
168
|
+
body1: number,
|
|
169
|
+
body2: number
|
|
170
|
+
) => boolean
|
|
171
|
+
) => {
|
|
172
|
+
const { filterIntersectionPairHooks } = useRapier();
|
|
173
|
+
|
|
174
|
+
const ref = useMutableCallback(callback);
|
|
175
|
+
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
filterIntersectionPairHooks.add(ref);
|
|
178
|
+
|
|
179
|
+
return () => {
|
|
180
|
+
filterIntersectionPairHooks.delete(ref);
|
|
181
|
+
};
|
|
182
|
+
}, []);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Internal hooks
|
|
186
|
+
/**
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
export const useChildColliderProps = <O extends Object3D>(
|
|
190
|
+
ref: RefObject<O | undefined | null>,
|
|
191
|
+
options: RigidBodyProps,
|
|
192
|
+
ignoreMeshColliders = true
|
|
193
|
+
) => {
|
|
194
|
+
const [colliderProps, setColliderProps] = useState<ColliderProps[]>([]);
|
|
195
|
+
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
const object = ref.current;
|
|
198
|
+
|
|
199
|
+
if (object && options.colliders !== false) {
|
|
200
|
+
setColliderProps(
|
|
201
|
+
createColliderPropsFromChildren({
|
|
202
|
+
object: ref.current!,
|
|
203
|
+
options,
|
|
204
|
+
ignoreMeshColliders
|
|
205
|
+
})
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}, [options.colliders]);
|
|
209
|
+
|
|
210
|
+
return colliderProps;
|
|
211
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FixedImpulseJoint,
|
|
3
|
+
ImpulseJoint,
|
|
4
|
+
PrismaticImpulseJoint,
|
|
5
|
+
RevoluteImpulseJoint,
|
|
6
|
+
RopeImpulseJoint,
|
|
7
|
+
SphericalImpulseJoint,
|
|
8
|
+
SpringImpulseJoint
|
|
9
|
+
} from "@dimforge/rapier3d-compat";
|
|
10
|
+
import { RefObject, useRef } from "react";
|
|
11
|
+
import {
|
|
12
|
+
FixedJointParams,
|
|
13
|
+
PrismaticJointParams,
|
|
14
|
+
RapierRigidBody,
|
|
15
|
+
RevoluteJointParams,
|
|
16
|
+
RopeJointParams,
|
|
17
|
+
SphericalJointParams,
|
|
18
|
+
SpringJointParams,
|
|
19
|
+
UseImpulseJoint,
|
|
20
|
+
useRapier
|
|
21
|
+
} from "..";
|
|
22
|
+
import {
|
|
23
|
+
vector3ToRapierVector,
|
|
24
|
+
quaternionToRapierQuaternion
|
|
25
|
+
} from "../utils/utils";
|
|
26
|
+
|
|
27
|
+
import type Rapier from "@dimforge/rapier3d-compat";
|
|
28
|
+
import { useImperativeInstance } from "./use-imperative-instance";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export const useImpulseJoint = <JointType extends ImpulseJoint>(
|
|
34
|
+
body1: RefObject<RapierRigidBody>,
|
|
35
|
+
body2: RefObject<RapierRigidBody>,
|
|
36
|
+
params: Rapier.JointData
|
|
37
|
+
) => {
|
|
38
|
+
const { world } = useRapier();
|
|
39
|
+
const jointRef = useRef<JointType | undefined>(undefined);
|
|
40
|
+
|
|
41
|
+
useImperativeInstance(
|
|
42
|
+
() => {
|
|
43
|
+
if (body1.current && body2.current) {
|
|
44
|
+
const newJoint = world.createImpulseJoint(
|
|
45
|
+
params,
|
|
46
|
+
body1.current,
|
|
47
|
+
body2.current,
|
|
48
|
+
true
|
|
49
|
+
) as JointType;
|
|
50
|
+
|
|
51
|
+
jointRef.current = newJoint;
|
|
52
|
+
|
|
53
|
+
return newJoint;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
(joint) => {
|
|
57
|
+
if (joint) {
|
|
58
|
+
jointRef.current = undefined;
|
|
59
|
+
if (world.getImpulseJoint(joint.handle)) {
|
|
60
|
+
world.removeImpulseJoint(joint, true);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
[]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return jointRef;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A fixed joint ensures that two rigid-bodies don't move relative to each other.
|
|
72
|
+
* Fixed joints are characterized by one local frame (represented by an isometry) on each rigid-body.
|
|
73
|
+
* The fixed-joint makes these frames coincide in world-space.
|
|
74
|
+
*
|
|
75
|
+
* @category Hooks - Joints
|
|
76
|
+
*/
|
|
77
|
+
export const useFixedJoint: UseImpulseJoint<
|
|
78
|
+
FixedJointParams,
|
|
79
|
+
FixedImpulseJoint
|
|
80
|
+
> = (
|
|
81
|
+
body1,
|
|
82
|
+
body2,
|
|
83
|
+
[body1Anchor, body1LocalFrame, body2Anchor, body2LocalFrame]
|
|
84
|
+
) => {
|
|
85
|
+
const { rapier } = useRapier();
|
|
86
|
+
|
|
87
|
+
return useImpulseJoint<FixedImpulseJoint>(
|
|
88
|
+
body1,
|
|
89
|
+
body2,
|
|
90
|
+
rapier.JointData.fixed(
|
|
91
|
+
vector3ToRapierVector(body1Anchor),
|
|
92
|
+
quaternionToRapierQuaternion(body1LocalFrame),
|
|
93
|
+
vector3ToRapierVector(body2Anchor),
|
|
94
|
+
quaternionToRapierQuaternion(body2LocalFrame)
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The spherical joint ensures that two points on the local-spaces of two rigid-bodies always coincide (it prevents any relative
|
|
101
|
+
* translational motion at this points). This is typically used to simulate ragdolls arms, pendulums, etc.
|
|
102
|
+
* They are characterized by one local anchor on each rigid-body. Each anchor represents the location of the
|
|
103
|
+
* points that need to coincide on the local-space of each rigid-body.
|
|
104
|
+
*
|
|
105
|
+
* @category Hooks - Joints
|
|
106
|
+
*/
|
|
107
|
+
export const useSphericalJoint: UseImpulseJoint<
|
|
108
|
+
SphericalJointParams,
|
|
109
|
+
SphericalImpulseJoint
|
|
110
|
+
> = (body1, body2, [body1Anchor, body2Anchor]) => {
|
|
111
|
+
const { rapier } = useRapier();
|
|
112
|
+
|
|
113
|
+
return useImpulseJoint<SphericalImpulseJoint>(
|
|
114
|
+
body1,
|
|
115
|
+
body2,
|
|
116
|
+
rapier.JointData.spherical(
|
|
117
|
+
vector3ToRapierVector(body1Anchor),
|
|
118
|
+
vector3ToRapierVector(body2Anchor)
|
|
119
|
+
)
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The revolute joint prevents any relative movement between two rigid-bodies, except for relative
|
|
125
|
+
* rotations along one axis. This is typically used to simulate wheels, fans, etc.
|
|
126
|
+
* They are characterized by one local anchor as well as one local axis on each rigid-body.
|
|
127
|
+
*
|
|
128
|
+
* @category Hooks - Joints
|
|
129
|
+
*/
|
|
130
|
+
export const useRevoluteJoint: UseImpulseJoint<
|
|
131
|
+
RevoluteJointParams,
|
|
132
|
+
RevoluteImpulseJoint
|
|
133
|
+
> = (body1, body2, [body1Anchor, body2Anchor, axis, limits]) => {
|
|
134
|
+
const { rapier } = useRapier();
|
|
135
|
+
|
|
136
|
+
const params = rapier.JointData.revolute(
|
|
137
|
+
vector3ToRapierVector(body1Anchor),
|
|
138
|
+
vector3ToRapierVector(body2Anchor),
|
|
139
|
+
vector3ToRapierVector(axis)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (limits) {
|
|
143
|
+
params.limitsEnabled = true;
|
|
144
|
+
params.limits = limits;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return useImpulseJoint<RevoluteImpulseJoint>(body1, body2, params);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The prismatic joint prevents any relative movement between two rigid-bodies, except for relative translations along one axis.
|
|
152
|
+
* It is characterized by one local anchor as well as one local axis on each rigid-body. In 3D, an optional
|
|
153
|
+
* local tangent axis can be specified for each rigid-body.
|
|
154
|
+
*
|
|
155
|
+
* @category Hooks - Joints
|
|
156
|
+
*/
|
|
157
|
+
export const usePrismaticJoint: UseImpulseJoint<
|
|
158
|
+
PrismaticJointParams,
|
|
159
|
+
PrismaticImpulseJoint
|
|
160
|
+
> = (body1, body2, [body1Anchor, body2Anchor, axis, limits]) => {
|
|
161
|
+
const { rapier } = useRapier();
|
|
162
|
+
|
|
163
|
+
const params = rapier.JointData.prismatic(
|
|
164
|
+
vector3ToRapierVector(body1Anchor),
|
|
165
|
+
vector3ToRapierVector(body2Anchor),
|
|
166
|
+
vector3ToRapierVector(axis)
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (limits) {
|
|
170
|
+
params.limitsEnabled = true;
|
|
171
|
+
params.limits = limits;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return useImpulseJoint<PrismaticImpulseJoint>(body1, body2, params);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The rope joint limits the max distance between two bodies.
|
|
179
|
+
* @category Hooks - Joints
|
|
180
|
+
*/
|
|
181
|
+
export const useRopeJoint: UseImpulseJoint<
|
|
182
|
+
RopeJointParams,
|
|
183
|
+
RopeImpulseJoint
|
|
184
|
+
> = (body1, body2, [body1Anchor, body2Anchor, length]) => {
|
|
185
|
+
const { rapier } = useRapier();
|
|
186
|
+
|
|
187
|
+
const vBody1Anchor = vector3ToRapierVector(body1Anchor);
|
|
188
|
+
const vBody2Anchor = vector3ToRapierVector(body2Anchor);
|
|
189
|
+
|
|
190
|
+
const params = rapier.JointData.rope(length, vBody1Anchor, vBody2Anchor);
|
|
191
|
+
|
|
192
|
+
return useImpulseJoint<RopeImpulseJoint>(body1, body2, params);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The spring joint applies a force proportional to the distance between two objects.
|
|
197
|
+
* @category Hooks - Joints
|
|
198
|
+
*/
|
|
199
|
+
export const useSpringJoint: UseImpulseJoint<
|
|
200
|
+
SpringJointParams,
|
|
201
|
+
SpringImpulseJoint
|
|
202
|
+
> = (
|
|
203
|
+
body1,
|
|
204
|
+
body2,
|
|
205
|
+
[body1Anchor, body2Anchor, restLength, stiffness, damping]
|
|
206
|
+
) => {
|
|
207
|
+
const { rapier } = useRapier();
|
|
208
|
+
|
|
209
|
+
const vBody1Anchor = vector3ToRapierVector(body1Anchor);
|
|
210
|
+
const vBody2Anchor = vector3ToRapierVector(body2Anchor);
|
|
211
|
+
|
|
212
|
+
const params = rapier.JointData.spring(
|
|
213
|
+
restLength,
|
|
214
|
+
stiffness,
|
|
215
|
+
damping,
|
|
216
|
+
vBody1Anchor,
|
|
217
|
+
vBody2Anchor
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return useImpulseJoint<SpringImpulseJoint>(body1, body2, params);
|
|
221
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ForwardedRef, RefObject, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
// Need to catch the case where forwardedRef is a function... how to do that?
|
|
4
|
+
export const useForwardedRef = <T>(
|
|
5
|
+
forwardedRef: ForwardedRef<T> | undefined,
|
|
6
|
+
defaultValue: T | null = null
|
|
7
|
+
): RefObject<T> => {
|
|
8
|
+
const innerRef = useRef<T>(defaultValue);
|
|
9
|
+
|
|
10
|
+
// Update the forwarded ref when the inner ref changes
|
|
11
|
+
if (forwardedRef && typeof forwardedRef !== "function") {
|
|
12
|
+
if (!forwardedRef.current) {
|
|
13
|
+
forwardedRef.current = innerRef.current;
|
|
14
|
+
}
|
|
15
|
+
return forwardedRef as RefObject<T>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return innerRef as RefObject<T>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DependencyList, useCallback, useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initiate an instance and return a safe getter
|
|
5
|
+
*/
|
|
6
|
+
export const useImperativeInstance = <InstanceType>(
|
|
7
|
+
createFn: () => InstanceType,
|
|
8
|
+
destroyFn: (instance: InstanceType) => void,
|
|
9
|
+
dependencyList: DependencyList
|
|
10
|
+
) => {
|
|
11
|
+
const ref = useRef<InstanceType | undefined>(undefined);
|
|
12
|
+
|
|
13
|
+
const getInstance = useCallback(() => {
|
|
14
|
+
if (!ref.current) {
|
|
15
|
+
ref.current = createFn();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return ref.current;
|
|
19
|
+
}, dependencyList);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// Save the destroy function and instance
|
|
23
|
+
const instance = getInstance();
|
|
24
|
+
const destroy = () => destroyFn(instance);
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
destroy();
|
|
28
|
+
ref.current = undefined;
|
|
29
|
+
};
|
|
30
|
+
}, [getInstance]);
|
|
31
|
+
|
|
32
|
+
return getInstance;
|
|
33
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Core exports from react-three-rapier
|
|
2
|
+
export * from "./types";
|
|
3
|
+
|
|
4
|
+
// Component exports
|
|
5
|
+
export type { RigidBodyProps } from "./components/RigidBody";
|
|
6
|
+
export type {
|
|
7
|
+
InstancedRigidBodiesProps,
|
|
8
|
+
InstancedRigidBodyProps
|
|
9
|
+
} from "./components/InstancedRigidBodies";
|
|
10
|
+
export type {
|
|
11
|
+
CylinderColliderProps,
|
|
12
|
+
BallColliderProps,
|
|
13
|
+
CapsuleColliderProps,
|
|
14
|
+
ConeColliderProps,
|
|
15
|
+
ConvexHullColliderProps,
|
|
16
|
+
CuboidColliderProps,
|
|
17
|
+
HeightfieldColliderProps,
|
|
18
|
+
RoundCuboidColliderProps,
|
|
19
|
+
TrimeshColliderProps,
|
|
20
|
+
ColliderOptionsRequiredArgs
|
|
21
|
+
} from "./components/AnyCollider";
|
|
22
|
+
|
|
23
|
+
export type {
|
|
24
|
+
PhysicsProps,
|
|
25
|
+
RapierContext,
|
|
26
|
+
WorldStepCallback,
|
|
27
|
+
FilterContactPairCallback,
|
|
28
|
+
FilterIntersectionPairCallback
|
|
29
|
+
} from "./components/Physics";
|
|
30
|
+
export type { MeshColliderProps } from "./components/MeshCollider";
|
|
31
|
+
|
|
32
|
+
export { Physics } from "./components/Physics";
|
|
33
|
+
export { RigidBody } from "./components/RigidBody";
|
|
34
|
+
export { MeshCollider } from "./components/MeshCollider";
|
|
35
|
+
export { InstancedRigidBodies } from "./components/InstancedRigidBodies";
|
|
36
|
+
export * from "./components/AnyCollider";
|
|
37
|
+
|
|
38
|
+
// Hooks exports
|
|
39
|
+
export * from "./hooks/joints";
|
|
40
|
+
export {
|
|
41
|
+
useRapier,
|
|
42
|
+
useBeforePhysicsStep,
|
|
43
|
+
useAfterPhysicsStep,
|
|
44
|
+
useFilterContactPair,
|
|
45
|
+
useFilterIntersectionPair
|
|
46
|
+
} from "./hooks/hooks";
|
|
47
|
+
|
|
48
|
+
// Utils exports
|
|
49
|
+
export * from "./utils/interaction-groups";
|
|
50
|
+
export * from "./utils/three-object-helpers";
|
|
51
|
+
|
|
52
|
+
// Addons exports
|
|
53
|
+
export * from "./addons/attractor/Attractor";
|
|
54
|
+
export * from "./addons/attractor/AttractorDebugHelper";
|
|
55
|
+
|