Skip to content

Commit

Permalink
feat: add collision events to colliders, add updatePriority, fix id…
Browse files Browse the repository at this point in the history
…le world stepping
  • Loading branch information
hmans authored Sep 15, 2022
1 parent 3da4f7c commit 720b03d
Show file tree
Hide file tree
Showing 18 changed files with 2,145 additions and 104 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-lies-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-three/rapier": patch
---

Add collision events to individual Colliders (@hmans)
2 changes: 1 addition & 1 deletion .changeset/long-coats-join.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"@react-three/rapier": patch
---

Adds the `<Physics updatePriority={...}>` prop, allowing the user to configure the update priority at which to run the components update loop.
Adds the `<Physics updatePriority={...}>` prop, allowing the user to configure the update priority at which to run the components update loop. (@hmans)
2 changes: 1 addition & 1 deletion .changeset/tasty-cameras-bake.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"@react-three/rapier": patch
---

The physics world stepping would needlessly catch up all the time missed while the host application was suspended in a background tab. This has been fixed.
The physics world stepping would needlessly catch up all the time missed while the host application was suspended in a background tab. This has been fixed. (@hmans)
14 changes: 14 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
tab_width = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
24 changes: 15 additions & 9 deletions demo/src/collision-events/CollisionEventsExample.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { Box, Cone, Cylinder, Html, Sphere } from "@react-three/drei";
import { Box, Sphere } from "@react-three/drei";
import {
CapsuleCollider,
CollisionEnterHandler,
HeightfieldCollider,
interactionGroups,
RigidBody,
UseRigidBodyOptions,
UseRigidBodyOptions
} from "@react-three/rapier";
import { useSuzanne } from "../all-shapes/AllShapes";
import { PlaneGeometry } from "three";
import React, {
import {
createContext,
Dispatch,
ReactNode,
SetStateAction,
useContext,
useState,
useState
} from "react";
import { PlaneGeometry } from "three";
import { useSuzanne } from "../all-shapes/AllShapes";

const Suzanne = ({ color }: { color: string }) => {
const { nodes: suzanne } = useSuzanne();
Expand Down Expand Up @@ -72,7 +74,7 @@ const Collisioner = ({
setExplosions: Dispatch<SetStateAction<ReactNode[]>>;
};

const handleCollisionEnter = ({ manifold }: any) => {
const handleCollisionEnter: CollisionEnterHandler = ({ manifold }) => {
setColor("red");

console.log("enter", manifold?.solverContactPoint(0));
Expand Down Expand Up @@ -139,14 +141,18 @@ export const CollisionEventsExample = () => {
{(color) => <Suzanne color={color} />}
</Collisioner>

<Collisioner colliders={false}>
<Collisioner colliders={false} collisionGroups={interactionGroups([1])}>
{(color) => (
<>
<mesh>
<capsuleGeometry args={[1, 2, 16, 16]} />
<meshPhysicalMaterial color={color} />
</mesh>
<CapsuleCollider args={[1, 1]} />
<CapsuleCollider args={[1, 1]}
collisionGroups={interactionGroups([2])}
onCollisionEnter={({ collider }) => console.log("ENTER collider / collider", collider)}
onCollisionExit={({ collider }) => console.log("EXIT collider / collider", collider)}
/>
</>
)}
</Collisioner>
Expand Down
13 changes: 13 additions & 0 deletions jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
verbose: true,
preset: "ts-jest",
testMatch: ["**/?(*.)+(spec|test).+(ts|tsx)"],
testPathIgnorePatterns: ["node_modules"],
testEnvironment: "jsdom",
moduleFileExtensions: ["js", "ts", "tsx"],
globals: {
"ts-jest": {
isolatedModules: true
}
}
}
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
"@changesets/cli": "^2.22.0",
"@manypkg/cli": "^0.19.1",
"@preconstruct/cli": "^2.1.5",
"@types/jest": "^29.0.1",
"buffer": "^6.0.3",
"jest": "^29.0.3",
"jest-environment-jsdom": "^29.0.3",
"np": "^7.6.1",
"ts-jest": "^29.0.0",
"typescript": "^4.6.3"
},
"scripts": {
"ci": "preconstruct validate; manypkg check",
"ci": "preconstruct validate && manypkg check && yarn test",
"test": "jest",
"postinstall": "preconstruct dev",
"build": "preconstruct build",
"dev": "preconstruct dev",
Expand Down
3 changes: 2 additions & 1 deletion packages/react-three-rapier/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"@types/three": "^0.139.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"three": "^0.139.2"
"three": "^0.139.2",
"three-stdlib": "^2.15.0"
},
"peerDependencies": {
"@react-three/fiber": "^8.0.12",
Expand Down
64 changes: 58 additions & 6 deletions packages/react-three-rapier/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const Scene = () => {
instancedApi.at(40).applyImpulse({ x: 0, y: 10, z: 0 });

// Or update all instances as if they were in an array
instancedApi.forEach((api) => {
instancedApi.forEach(api => {
api.applyImpulse({ x: 0, y: 10, z: 0 });
});
}, []);
Expand All @@ -153,13 +153,13 @@ const Scene = () => {
const rotations = Array.from({ length: COUNT }, (_, index) => [
Math.random(),
Math.random(),
Math.random(),
Math.random()
]);

const scales = Array.from({ length: COUNT }, (_, index) => [
Math.random(),
Math.random(),
Math.random(),
Math.random()
]);

return (
Expand Down Expand Up @@ -212,7 +212,7 @@ const Scene = () => {

## Events

You can subscribe collision and state events on the RigidBody.
You can subscribe to collision and state events on the RigidBody:

```tsx
const RigidBottle = () => {
Expand All @@ -235,6 +235,58 @@ return (
}
```

You may also subscribe to collision events on individual colliders:

```tsx
<CuboidCollider
onCollisionEnter={payload => {
/* ... */
}}
onCollisionExit={payload => {
/* ... */
}}
/>
```

The `payload` object for all collision callbacks contains the following properties:

- `target`
The other rigidbody that was involved in the collision event.
- `collider`
The other collider that was involved in the collision event.
- `manifold` (enter only)
The [contact manifold](https://rapier.rs/javascript3d/classes/TempContactManifold.html) generated by the collision event.
- `flipped` (enter only)
`true` if the data in the `manifold` [is flipped](https://rapier.rs/javascript3d/classes/World.html#contactPair).

### Configuring collision and solver groups

Both `<RigidBody>` as well as all collider components allow you to configure `collisionsGroups` and `solverGroups` properties that configures which groups the colliders are in, and what other groups they should interact with in potential collision and solving events (you will find more details on this in the [Rapier documentation](https://rapier.rs/docs/user_guides/javascript/colliders/#collision-groups-and-solver-groups).)

Since these are set as bitmasks and bitmasks can get a bit unwieldy to generate, this library provides a helper called `interactionGroups` that can be used to generate bitmasks from numbers and arrays of groups, where groups are identified using numbers from 1 to 16.

The first argument is the group, or an array of groups, that the collider is a member of; the second argument is the group, or an array of groups, that the collider should interact with.

Here the collider is in group 1, and interacts with colliders from groups 1, 2 and 3:

```tsx
<CapsuleCollider collisionGroups={interactionGroups(1, [1, 2, 3])} />
```

This collider is in multiple groups, but only interacts with colliders from a single group:

```tsx
<CapsuleCollider collisionGroups={interactionGroups([1, 5], 7)} />
```

When the second argument is omitted, the collider will interact with all groups:

```tsx
<CapsuleCollider collisionGroups={interactionGroups(12)} />
```

> **Note** Please remember that in Rapier, for a collision (or solving) event to occur, both colliders involved in the event must match the related interaction groups -- a one-way match will be ignored.

## Joints

WIP
Expand All @@ -255,7 +307,7 @@ In order, but also not necessarily:
- [x] Colliders outside RigidBodies
- [x] InstancedMesh support
- [x] Timestep improvements for determinism
- [ ] Normalize and improve collision events (add events to single Colliders, InstancedRigidBodies, etc)
- [x] Normalize and improve collision events (add events to single Colliders)
- [ ] Add collision events to InstancedRigidBodies
- [ ] Docs
- [ ] CodeSandbox examples
- [ ] Helpers, for things like Vehicle, Rope, Player, etc
42 changes: 35 additions & 7 deletions packages/react-three-rapier/src/AnyCollider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,28 @@ import {
TrimeshArgs,
ConeArgs,
CylinderArgs,
ConvexHullArgs,
ConvexHullArgs
} from "./types";
import { createColliderFromOptions, vectorArrayToVector3 } from "./utils";

// Colliders
const AnyCollider = ({
children,
onCollisionEnter,
onCollisionExit,
...props
}: UseColliderOptions<any> & { children?: ReactNode }) => {
const { world } = useRapier();
const { world, colliderEvents } = useRapier();
const rigidBodyContext = useRigidBodyContext();
const ref = useRef<Object3D>(null);

useEffect(() => {
const scale = ref.current!.getWorldScale(new Vector3());
const colliders: Collider[] = [];
const hasCollisionEvents =
rigidBodyContext?.hasCollisionEvents ||
!!onCollisionEnter ||
!!onCollisionExit;

// If this is an InstancedRigidBody api
if (rigidBodyContext && "at" in rigidBodyContext.api) {
Expand All @@ -46,32 +52,54 @@ const AnyCollider = ({

colliders.push(
createColliderFromOptions({
options: props,
options: {
solverGroups: rigidBodyContext.options.solverGroups,
collisionGroups: rigidBodyContext.options.collisionGroups,
...props
},
world,
rigidBody: body.raw(),
scale: instanceScale,
hasCollisionEvents: rigidBodyContext?.hasCollisionEvents,
hasCollisionEvents
})
);
});
} else {
colliders.push(
createColliderFromOptions({
options: props,
options: {
solverGroups:
rigidBodyContext?.options.solverGroups || props.solverGroups,
collisionGroups:
rigidBodyContext?.options.collisionGroups ||
props.collisionGroups,
...props
},
world,
// Initiate with a rigidbody, or undefined, because colliders can exist without a rigid body
rigidBody:
rigidBodyContext && "raw" in rigidBodyContext.api
? rigidBodyContext.api.raw()
: undefined,
scale,
hasCollisionEvents: rigidBodyContext?.hasCollisionEvents,
hasCollisionEvents
})
);
}

/* Register collision events. */
colliders.forEach(collider =>
colliderEvents.set(collider.handle, {
onCollisionEnter,
onCollisionExit
})
);

return () => {
colliders.forEach((collider) => world.removeCollider(collider));
colliders.forEach(collider => {
colliderEvents.delete(collider.handle);
world.removeCollider(collider);
});
};
}, []);

Expand Down
Loading

0 comments on commit 720b03d

Please sign in to comment.