Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Add useBody hook that infers shape with three-to-cannon #392

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions packages/react-three-cannon-examples/src/demos/demo-ShapeInference.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import type { BodyProps, PlaneProps } from '@react-three/cannon'
import { Debug, Physics, useBody, usePlane } from '@react-three/cannon'
import { OrbitControls } from '@react-three/drei'
import { Canvas } from '@react-three/fiber'
import { Suspense, useRef } from 'react'
import type { Group, Mesh } from 'three'

function BoundingSphere(props: BodyProps) {
const [ref] = useBody(
() => ({
mass: 1,
...props,
}),
useRef<Mesh>(null),
{ type: 'Sphere' },
)

return (
<mesh ref={ref} receiveShadow>
<boxBufferGeometry args={[0.75, 0.75, 0.75]} />
<meshNormalMaterial />
</mesh>
)
}

function Sphere(props: BodyProps) {
const [ref] = useBody(
() => ({
mass: 1,
...props,
}),
useRef<Mesh>(null),
)

return (
<mesh ref={ref} receiveShadow>
<sphereBufferGeometry args={[0.6]} />
<meshNormalMaterial />
</mesh>
)
}

function BoundingBox(props: BodyProps) {
const [ref] = useBody(
() => ({
mass: 1,
...props,
}),
useRef<Mesh>(null),
{ type: 'Box' },
)

return (
<mesh ref={ref} receiveShadow>
<sphereBufferGeometry args={[0.5]} />
<meshNormalMaterial />
</mesh>
)
}

function Box(props: BodyProps) {
const [ref] = useBody(
() => ({
mass: 1,
...props,
}),
useRef<Mesh>(null),
)

return (
<mesh ref={ref} receiveShadow>
<boxBufferGeometry args={[1, 1, 1]} />
<meshNormalMaterial />
</mesh>
)
}

function BoundingCylinder(props: BodyProps) {
const [ref] = useBody(
() => ({
mass: 1,
...props,
}),
useRef<Mesh>(null),
{ type: 'Cylinder' },
)

return (
<mesh ref={ref} receiveShadow>
<sphereBufferGeometry args={[0.5]} />
<meshNormalMaterial />
</mesh>
)
}

function Cylinder(props: BodyProps) {
const [ref] = useBody(
() => ({
mass: 1,
...props,
}),
useRef<Mesh>(null),
)

return (
<mesh ref={ref} receiveShadow>
<cylinderBufferGeometry args={[0.4, 0.6, 1.2, 10]} />
<meshNormalMaterial />
</mesh>
)
}

function ConvexPolyhedron(props: BodyProps) {
const [ref] = useBody(
() => ({
mass: 1,
...props,
}),
useRef<Group>(null),
{ type: 'ConvexPolyhedron' },
)

return (
<group ref={ref}>
<mesh receiveShadow>
<boxBufferGeometry args={[1.5, 0.5, 0.5]} />
<meshNormalMaterial />
</mesh>
<mesh receiveShadow rotation={[0, Math.PI / 2, 0]}>
<boxBufferGeometry args={[1.5, 0.5, 0.5]} />
<meshNormalMaterial />
</mesh>
</group>
)
}

function Trimesh(props: BodyProps) {
const [ref] = useBody(
() => ({
mass: 1,
...props,
}),
useRef<Mesh>(null),
{ type: 'Trimesh' },
)

return (
<mesh ref={ref} receiveShadow>
<torusKnotBufferGeometry args={[0.5, 0.15, 100, 100]} />
<meshNormalMaterial />
</mesh>
)
}

function Plane(props: PlaneProps) {
usePlane(() => ({ type: 'Static', ...props }))
return null
}

function ShapeInference() {
return (
<>
<Canvas shadows camera={{ fov: 50, position: [4, 7, 6] }}>
<color attach="background" args={['#555']} />
<ambientLight intensity={0.5} />
<spotLight
position={[15, 15, 15]}
angle={0.3}
penumbra={1}
intensity={2}
castShadow
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
/>
<Suspense fallback={null}>
<Physics gravity={[0, -10, 0]}>
<Debug color="white">
<Box position={[-4, 4, -1]} />
<Cylinder position={[-2, 6, -1]} />
<Sphere position={[-0, 8, -1]} />
<ConvexPolyhedron position={[2, 10, -1]} />
<Trimesh position={[4, 12, -1]} rotation={[Math.PI / 2, 0, 0]} />

<BoundingBox position={[-2, 14, 1]} />
<BoundingSphere position={[0, 16, 1]} />
<BoundingCylinder position={[2, 18, 1]} />

<Plane rotation={[-Math.PI / 2, 0, 0]} />
</Debug>
</Physics>
</Suspense>
<OrbitControls />
</Canvas>
</>
)
}

export default ShapeInference
1 change: 1 addition & 0 deletions packages/react-three-cannon-examples/src/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const fileDemos = [
'KinematicCube',
'Paused',
'SphereDebug',
'ShapeInference',
'Triggers',
'Trimesh',
] as const
Expand Down
3 changes: 2 additions & 1 deletion packages/react-three-cannon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"dependencies": {
"@pmndrs/cannon-worker-api": "^2.1.0",
"cannon-es": "^0.19.0",
"cannon-es-debugger": "^1.0.0"
"cannon-es-debugger": "^1.0.0",
"three-to-cannon": "^4.1.0"
},
"devDependencies": {
"@babel/core": "^7.17.8",
Expand Down
57 changes: 40 additions & 17 deletions packages/react-three-cannon/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import { DynamicDrawUsage, Euler, InstancedMesh, MathUtils, Object3D, Quaternion
import { useDebugContext } from './debug-context'
import type { CannonEvents } from './physics-context'
import { usePhysicsContext } from './physics-context'
import type { ThreeToCannonOptions } from './three-to-cannon'
import { threeToCannon } from './three-to-cannon'

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

function useBody<B extends BodyProps<unknown[]>, O extends Object3D>(
type: BodyShapeType,
function useBodyCommon<B extends BodyProps<unknown[]>, O extends Object3D>(
type: BodyShapeType | null,
fn: GetByIndex<B>,
argsFn: ArgFn<B['args']>,
fwdRef: Ref<O> = null,
deps: DependencyList = [],
threeToCannonOptions?: ThreeToCannonOptions,
): Api<O> {
const ref = useForwardedRef(fwdRef)

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

const props: (B & { args: unknown })[] =
let shapeType: BodyShapeType = type || 'Particle'
let inferredProps: BodyProps<unknown[]> = {}

if (!type) {
const result = threeToCannon(object, threeToCannonOptions)

if (result) {
shapeType = result.shape
inferredProps = result.props
}
}

const props =
object instanceof InstancedMesh
? uuid.map((id, i) => {
const props = fn(i)
const props = { ...inferredProps, ...fn(i) }
prepare(temp, props)
object.setMatrixAt(i, temp.matrix)
object.instanceMatrix.needsUpdate = true
refs[id] = object
debugApi?.add(id, props, type)
debugApi?.add(id, props, shapeType)
setupCollision(events, props, id)
return { ...props, args: argsFn(props.args) }
})
: uuid.map((id, i) => {
const props = fn(i)
const props = { ...inferredProps, ...fn(i) }
prepare(object, props)
refs[id] = object
debugApi?.add(id, props, type)
debugApi?.add(id, props, shapeType)
setupCollision(events, props, id)
return { ...props, args: argsFn(props.args) }
})
Expand All @@ -210,7 +225,7 @@ function useBody<B extends BodyProps<unknown[]>, O extends Object3D>(
props: props.map(({ onCollide, onCollideBegin, onCollideEnd, ...serializableProps }) => {
return { onCollide: Boolean(onCollide), ...serializableProps }
}),
type,
type: shapeType,
uuid,
})
return () => {
Expand Down Expand Up @@ -366,44 +381,52 @@ function makeTriplet(v: Vector3 | Triplet): Triplet {
return v instanceof Vector3 ? [v.x, v.y, v.z] : v
}

export function useBody<O extends Object3D>(
fn: GetByIndex<PlaneProps>,
fwdRef: Ref<O> = null,
threeToCannonOptions?: ThreeToCannonOptions,
deps?: DependencyList,
) {
return useBodyCommon(null, fn, (args) => args || [], fwdRef, deps, threeToCannonOptions)
}
export function usePlane<O extends Object3D>(
fn: GetByIndex<PlaneProps>,
fwdRef?: Ref<O>,
deps?: DependencyList,
) {
return useBody('Plane', fn, () => [], fwdRef, deps)
return useBodyCommon('Plane', fn, () => [], fwdRef, deps)
}
export function useBox<O extends Object3D>(fn: GetByIndex<BoxProps>, fwdRef?: Ref<O>, deps?: DependencyList) {
const defaultBoxArgs: Triplet = [1, 1, 1]
return useBody('Box', fn, (args = defaultBoxArgs): Triplet => args, fwdRef, deps)
return useBodyCommon('Box', fn, (args = defaultBoxArgs): Triplet => args, fwdRef, deps)
}
export function useCylinder<O extends Object3D>(
fn: GetByIndex<CylinderProps>,
fwdRef?: Ref<O>,
deps?: DependencyList,
) {
return useBody('Cylinder', fn, (args = [] as []) => args, fwdRef, deps)
return useBodyCommon('Cylinder', fn, (args = [] as []) => args, fwdRef, deps)
}
export function useHeightfield<O extends Object3D>(
fn: GetByIndex<HeightfieldProps>,
fwdRef?: Ref<O>,
deps?: DependencyList,
) {
return useBody('Heightfield', fn, (args) => args, fwdRef, deps)
return useBodyCommon('Heightfield', fn, (args) => args, fwdRef, deps)
}
export function useParticle<O extends Object3D>(
fn: GetByIndex<ParticleProps>,
fwdRef?: Ref<O>,
deps?: DependencyList,
) {
return useBody('Particle', fn, () => [], fwdRef, deps)
return useBodyCommon('Particle', fn, () => [], fwdRef, deps)
}
export function useSphere<O extends Object3D>(
fn: GetByIndex<SphereProps>,
fwdRef?: Ref<O>,
deps?: DependencyList,
) {
return useBody(
return useBodyCommon(
'Sphere',
fn,
(args: SphereArgs = [1]): SphereArgs => {
Expand All @@ -419,15 +442,15 @@ export function useTrimesh<O extends Object3D>(
fwdRef?: Ref<O>,
deps?: DependencyList,
) {
return useBody<TrimeshProps, O>('Trimesh', fn, (args) => args, fwdRef, deps)
return useBodyCommon<TrimeshProps, O>('Trimesh', fn, (args) => args, fwdRef, deps)
}

export function useConvexPolyhedron<O extends Object3D>(
fn: GetByIndex<ConvexPolyhedronProps>,
fwdRef?: Ref<O>,
deps?: DependencyList,
) {
return useBody<ConvexPolyhedronProps, O>(
return useBodyCommon<ConvexPolyhedronProps, O>(
'ConvexPolyhedron',
fn,
([vertices, faces, normals, axes, boundingSphereRadius] = []): ConvexPolyhedronArgs<Triplet> => [
Expand All @@ -446,7 +469,7 @@ export function useCompoundBody<O extends Object3D>(
fwdRef?: Ref<O>,
deps?: DependencyList,
) {
return useBody('Compound', fn, (args) => args as unknown[], fwdRef, deps)
return useBodyCommon('Compound', fn, (args) => args as unknown[], fwdRef, deps)
}

type ConstraintApi<A extends Object3D, B extends Object3D> = [
Expand Down
Loading