Skip to content

Commit 7afee4b

Browse files
committed
Add useBody hook that infers shape with three-to-cannon
1 parent 90dfc12 commit 7afee4b

File tree

7 files changed

+455
-39
lines changed

7 files changed

+455
-39
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import type { BodyProps, PlaneProps } from '@react-three/cannon'
2+
import { Debug, Physics, useBody, usePlane } from '@react-three/cannon'
3+
import { OrbitControls } from '@react-three/drei'
4+
import { Canvas } from '@react-three/fiber'
5+
import { Suspense, useRef } from 'react'
6+
import type { Group, Mesh } from 'three'
7+
8+
function BoundingSphere(props: BodyProps) {
9+
const [ref] = useBody(
10+
() => ({
11+
mass: 1,
12+
...props,
13+
}),
14+
useRef<Mesh>(null),
15+
{ type: 'Sphere' },
16+
)
17+
18+
return (
19+
<mesh ref={ref} receiveShadow>
20+
<boxBufferGeometry args={[0.75, 0.75, 0.75]} />
21+
<meshNormalMaterial />
22+
</mesh>
23+
)
24+
}
25+
26+
function Sphere(props: BodyProps) {
27+
const [ref] = useBody(
28+
() => ({
29+
mass: 1,
30+
...props,
31+
}),
32+
useRef<Mesh>(null),
33+
)
34+
35+
return (
36+
<mesh ref={ref} receiveShadow>
37+
<sphereBufferGeometry args={[0.6]} />
38+
<meshNormalMaterial />
39+
</mesh>
40+
)
41+
}
42+
43+
function BoundingBox(props: BodyProps) {
44+
const [ref] = useBody(
45+
() => ({
46+
mass: 1,
47+
...props,
48+
}),
49+
useRef<Mesh>(null),
50+
{ type: 'Box' },
51+
)
52+
53+
return (
54+
<mesh ref={ref} receiveShadow>
55+
<sphereBufferGeometry args={[0.5]} />
56+
<meshNormalMaterial />
57+
</mesh>
58+
)
59+
}
60+
61+
function Box(props: BodyProps) {
62+
const [ref] = useBody(
63+
() => ({
64+
mass: 1,
65+
...props,
66+
}),
67+
useRef<Mesh>(null),
68+
)
69+
70+
return (
71+
<mesh ref={ref} receiveShadow>
72+
<boxBufferGeometry args={[1, 1, 1]} />
73+
<meshNormalMaterial />
74+
</mesh>
75+
)
76+
}
77+
78+
function BoundingCylinder(props: BodyProps) {
79+
const [ref] = useBody(
80+
() => ({
81+
mass: 1,
82+
...props,
83+
}),
84+
useRef<Mesh>(null),
85+
{ type: 'Cylinder' },
86+
)
87+
88+
return (
89+
<mesh ref={ref} receiveShadow>
90+
<sphereBufferGeometry args={[0.5]} />
91+
<meshNormalMaterial />
92+
</mesh>
93+
)
94+
}
95+
96+
function Cylinder(props: BodyProps) {
97+
const [ref] = useBody(
98+
() => ({
99+
mass: 1,
100+
...props,
101+
}),
102+
useRef<Mesh>(null),
103+
)
104+
105+
return (
106+
<mesh ref={ref} receiveShadow>
107+
<cylinderBufferGeometry args={[0.4, 0.6, 1.2, 10]} />
108+
<meshNormalMaterial />
109+
</mesh>
110+
)
111+
}
112+
113+
function ConvexPolyhedron(props: BodyProps) {
114+
const [ref] = useBody(
115+
() => ({
116+
mass: 1,
117+
...props,
118+
}),
119+
useRef<Group>(null),
120+
{ type: 'ConvexPolyhedron' },
121+
)
122+
123+
return (
124+
<group ref={ref}>
125+
<mesh receiveShadow>
126+
<boxBufferGeometry args={[1.5, 0.5, 0.5]} />
127+
<meshNormalMaterial />
128+
</mesh>
129+
<mesh receiveShadow rotation={[0, Math.PI / 2, 0]}>
130+
<boxBufferGeometry args={[1.5, 0.5, 0.5]} />
131+
<meshNormalMaterial />
132+
</mesh>
133+
</group>
134+
)
135+
}
136+
137+
function Trimesh(props: BodyProps) {
138+
const [ref] = useBody(
139+
() => ({
140+
mass: 1,
141+
...props,
142+
}),
143+
useRef<Mesh>(null),
144+
{ type: 'Trimesh' },
145+
)
146+
147+
return (
148+
<mesh ref={ref} receiveShadow>
149+
<torusKnotBufferGeometry args={[0.5, 0, 100, 100]} />
150+
<meshNormalMaterial />
151+
</mesh>
152+
)
153+
}
154+
155+
function Plane(props: PlaneProps) {
156+
usePlane(() => ({ type: 'Static', ...props }))
157+
return null
158+
}
159+
160+
function ShapeInference() {
161+
return (
162+
<>
163+
<Canvas shadows camera={{ fov: 50, position: [4, 7, 6] }}>
164+
<color attach="background" args={['#555']} />
165+
<ambientLight intensity={0.5} />
166+
<spotLight
167+
position={[15, 15, 15]}
168+
angle={0.3}
169+
penumbra={1}
170+
intensity={2}
171+
castShadow
172+
shadow-mapSize-width={2048}
173+
shadow-mapSize-height={2048}
174+
/>
175+
<Suspense fallback={null}>
176+
<Physics gravity={[0, -10, 0]}>
177+
<Debug color="white">
178+
<Box position={[-4, 4, -1]} />
179+
<Cylinder position={[-2, 6, -1]} />
180+
<Sphere position={[-0, 8, -1]} />
181+
<ConvexPolyhedron position={[2, 10, -1]} />
182+
<Trimesh position={[4, 12, -1]} rotation={[Math.PI / 2, 0, 0]} />
183+
184+
<BoundingBox position={[-2, 14, 1]} />
185+
<BoundingSphere position={[0, 16, 1]} />
186+
<BoundingCylinder position={[2, 18, 1]} />
187+
188+
<Plane rotation={[-Math.PI / 2, 0, 0]} />
189+
</Debug>
190+
</Physics>
191+
</Suspense>
192+
<OrbitControls />
193+
</Canvas>
194+
</>
195+
)
196+
}
197+
198+
export default ShapeInference

packages/react-three-cannon-examples/src/demos/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const fileDemos = [
1212
'KinematicCube',
1313
'Paused',
1414
'SphereDebug',
15+
'ShapeInference',
1516
'Triggers',
1617
'Trimesh',
1718
] as const

packages/react-three-cannon/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"dependencies": {
3333
"@pmndrs/cannon-worker-api": "^2.1.0",
3434
"cannon-es": "^0.19.0",
35-
"cannon-es-debugger": "^1.0.0"
35+
"cannon-es-debugger": "^1.0.0",
36+
"three-to-cannon": "^4.1.0"
3637
},
3738
"devDependencies": {
3839
"@babel/core": "^7.17.8",

packages/react-three-cannon/src/hooks.ts

+40-17
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import { DynamicDrawUsage, Euler, InstancedMesh, MathUtils, Object3D, Quaternion
4545
import { useDebugContext } from './debug-context'
4646
import type { CannonEvents } from './physics-context'
4747
import { usePhysicsContext } from './physics-context'
48+
import type { ThreeToCannonOptions } from './three-to-cannon'
49+
import { threeToCannon } from './three-to-cannon'
4850

4951
export type AtomicApi<K extends AtomicName> = {
5052
set: (value: AtomicProps[K]) => void
@@ -153,12 +155,13 @@ function setupCollision(
153155
type GetByIndex<T extends BodyProps> = (index: number) => T
154156
type ArgFn<T> = (args: T) => unknown[]
155157

156-
function useBody<B extends BodyProps<unknown[]>, O extends Object3D>(
157-
type: BodyShapeType,
158+
function useBodyCommon<B extends BodyProps<unknown[]>, O extends Object3D>(
159+
type: BodyShapeType | null,
158160
fn: GetByIndex<B>,
159161
argsFn: ArgFn<B['args']>,
160162
fwdRef: Ref<O> = null,
161163
deps: DependencyList = [],
164+
threeToCannonOptions?: ThreeToCannonOptions,
162165
): Api<O> {
163166
const ref = useForwardedRef(fwdRef)
164167

@@ -184,23 +187,35 @@ function useBody<B extends BodyProps<unknown[]>, O extends Object3D>(
184187
? new Array(objectCount).fill(0).map((_, i) => `${object.uuid}/${i}`)
185188
: [object.uuid]
186189

187-
const props: (B & { args: unknown })[] =
190+
let shapeType: BodyShapeType = type || 'Particle'
191+
let inferredProps: BodyProps<unknown[]> = {}
192+
193+
if (!type) {
194+
const result = threeToCannon(object, threeToCannonOptions)
195+
196+
if (result) {
197+
shapeType = result.shape
198+
inferredProps = result.props
199+
}
200+
}
201+
202+
const props =
188203
object instanceof InstancedMesh
189204
? uuid.map((id, i) => {
190-
const props = fn(i)
205+
const props = { ...inferredProps, ...fn(i) }
191206
prepare(temp, props)
192207
object.setMatrixAt(i, temp.matrix)
193208
object.instanceMatrix.needsUpdate = true
194209
refs[id] = object
195-
debugApi?.add(id, props, type)
210+
debugApi?.add(id, props, shapeType)
196211
setupCollision(events, props, id)
197212
return { ...props, args: argsFn(props.args) }
198213
})
199214
: uuid.map((id, i) => {
200-
const props = fn(i)
215+
const props = { ...inferredProps, ...fn(i) }
201216
prepare(object, props)
202217
refs[id] = object
203-
debugApi?.add(id, props, type)
218+
debugApi?.add(id, props, shapeType)
204219
setupCollision(events, props, id)
205220
return { ...props, args: argsFn(props.args) }
206221
})
@@ -210,7 +225,7 @@ function useBody<B extends BodyProps<unknown[]>, O extends Object3D>(
210225
props: props.map(({ onCollide, onCollideBegin, onCollideEnd, ...serializableProps }) => {
211226
return { onCollide: Boolean(onCollide), ...serializableProps }
212227
}),
213-
type,
228+
type: shapeType,
214229
uuid,
215230
})
216231
return () => {
@@ -366,44 +381,52 @@ function makeTriplet(v: Vector3 | Triplet): Triplet {
366381
return v instanceof Vector3 ? [v.x, v.y, v.z] : v
367382
}
368383

384+
export function useBody<O extends Object3D>(
385+
fn: GetByIndex<PlaneProps>,
386+
fwdRef: Ref<O> = null,
387+
threeToCannonOptions?: ThreeToCannonOptions,
388+
deps?: DependencyList,
389+
) {
390+
return useBodyCommon(null, fn, (args) => args || [], fwdRef, deps, threeToCannonOptions)
391+
}
369392
export function usePlane<O extends Object3D>(
370393
fn: GetByIndex<PlaneProps>,
371394
fwdRef?: Ref<O>,
372395
deps?: DependencyList,
373396
) {
374-
return useBody('Plane', fn, () => [], fwdRef, deps)
397+
return useBodyCommon('Plane', fn, () => [], fwdRef, deps)
375398
}
376399
export function useBox<O extends Object3D>(fn: GetByIndex<BoxProps>, fwdRef?: Ref<O>, deps?: DependencyList) {
377400
const defaultBoxArgs: Triplet = [1, 1, 1]
378-
return useBody('Box', fn, (args = defaultBoxArgs): Triplet => args, fwdRef, deps)
401+
return useBodyCommon('Box', fn, (args = defaultBoxArgs): Triplet => args, fwdRef, deps)
379402
}
380403
export function useCylinder<O extends Object3D>(
381404
fn: GetByIndex<CylinderProps>,
382405
fwdRef?: Ref<O>,
383406
deps?: DependencyList,
384407
) {
385-
return useBody('Cylinder', fn, (args = [] as []) => args, fwdRef, deps)
408+
return useBodyCommon('Cylinder', fn, (args = [] as []) => args, fwdRef, deps)
386409
}
387410
export function useHeightfield<O extends Object3D>(
388411
fn: GetByIndex<HeightfieldProps>,
389412
fwdRef?: Ref<O>,
390413
deps?: DependencyList,
391414
) {
392-
return useBody('Heightfield', fn, (args) => args, fwdRef, deps)
415+
return useBodyCommon('Heightfield', fn, (args) => args, fwdRef, deps)
393416
}
394417
export function useParticle<O extends Object3D>(
395418
fn: GetByIndex<ParticleProps>,
396419
fwdRef?: Ref<O>,
397420
deps?: DependencyList,
398421
) {
399-
return useBody('Particle', fn, () => [], fwdRef, deps)
422+
return useBodyCommon('Particle', fn, () => [], fwdRef, deps)
400423
}
401424
export function useSphere<O extends Object3D>(
402425
fn: GetByIndex<SphereProps>,
403426
fwdRef?: Ref<O>,
404427
deps?: DependencyList,
405428
) {
406-
return useBody(
429+
return useBodyCommon(
407430
'Sphere',
408431
fn,
409432
(args: SphereArgs = [1]): SphereArgs => {
@@ -419,15 +442,15 @@ export function useTrimesh<O extends Object3D>(
419442
fwdRef?: Ref<O>,
420443
deps?: DependencyList,
421444
) {
422-
return useBody<TrimeshProps, O>('Trimesh', fn, (args) => args, fwdRef, deps)
445+
return useBodyCommon<TrimeshProps, O>('Trimesh', fn, (args) => args, fwdRef, deps)
423446
}
424447

425448
export function useConvexPolyhedron<O extends Object3D>(
426449
fn: GetByIndex<ConvexPolyhedronProps>,
427450
fwdRef?: Ref<O>,
428451
deps?: DependencyList,
429452
) {
430-
return useBody<ConvexPolyhedronProps, O>(
453+
return useBodyCommon<ConvexPolyhedronProps, O>(
431454
'ConvexPolyhedron',
432455
fn,
433456
([vertices, faces, normals, axes, boundingSphereRadius] = []): ConvexPolyhedronArgs<Triplet> => [
@@ -446,7 +469,7 @@ export function useCompoundBody<O extends Object3D>(
446469
fwdRef?: Ref<O>,
447470
deps?: DependencyList,
448471
) {
449-
return useBody('Compound', fn, (args) => args as unknown[], fwdRef, deps)
472+
return useBodyCommon('Compound', fn, (args) => args as unknown[], fwdRef, deps)
450473
}
451474

452475
type ConstraintApi<A extends Object3D, B extends Object3D> = [

0 commit comments

Comments
 (0)