Skip to content

Commit

Permalink
fix(collider): clean up unused RB props when passed to collider (#334)
Browse files Browse the repository at this point in the history
* fix(collider): clean up unused RB props when passed to collider

* chore: changeset
  • Loading branch information
wiledal authored Mar 2, 2023
1 parent bd76f96 commit 1f41278
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 40 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-owls-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-three/rapier": patch
---

Fix collision events firing multiple times on RigidBodies
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Plane, Sphere } from "@react-three/drei";
import { Box, Plane, Sphere } from "@react-three/drei";
import { MeshPhysicalMaterialProps } from "@react-three/fiber";
import {
RigidBody,
RapierRigidBody,
RigidBodyProps
RigidBodyProps,
ContactForceHandler
} from "@react-three/rapier";
import { useRef, useState } from "react";
import { useCallback, useRef, useState } from "react";
import { Color } from "three";
import { Demo } from "../../App";

Expand All @@ -17,14 +18,18 @@ const Ball = ({ onContactForce }: BallProps) => {
<RigidBody
ref={ball}
colliders="ball"
position={[0, 15, 0]}
restitution={0.5}
position={[2, 15, 0]}
restitution={1.5}
onContactForce={(payload) => {
const { totalForceMagnitude } = payload;
if (totalForceMagnitude < 300) {
ball.current?.applyImpulse({ x: 0, y: 65, z: 0 }, true);
}
onContactForce?.(payload);
console.log("contact force", totalForceMagnitude);
}}
onCollisionEnter={() => {
console.log("collision enter");
}}
>
<Sphere castShadow receiveShadow>
Expand All @@ -36,10 +41,10 @@ const Ball = ({ onContactForce }: BallProps) => {
type FloorProps = { color: MeshPhysicalMaterialProps["color"] };
const Floor = ({ color }: FloorProps) => {
return (
<RigidBody colliders="trimesh" type="fixed" restitution={1}>
<Plane args={[10, 10]} rotation={[Math.PI * 1.5, 0, 0]}>
<RigidBody colliders="cuboid" type="fixed">
<Box args={[10, 1, 10]}>
<meshPhysicalMaterial color={color} />
</Plane>
</Box>
</RigidBody>
);
};
Expand All @@ -48,19 +53,22 @@ const startColor = new Color(0xffffff);
export const ContactForceEventsExample: Demo = () => {
const [floorColor, setFloorColor] = useState(0x000000);

const handleContactForce = useCallback<ContactForceHandler>(
({ totalForceMagnitude }) => {
const color = startColor
.clone()
.multiplyScalar(1 - totalForceMagnitude / startForce);
setFloorColor(color.getHex());
},
[]
);

// magic number: this is the start force for where the ball drops from
// and is used to calculate the color change
const startForce = 6500;
return (
<group position={[0, -10, -10]}>
<Ball
onContactForce={({ totalForceMagnitude }) => {
const color = startColor
.clone()
.multiplyScalar(1 - totalForceMagnitude / startForce);
setFloorColor(color.getHex());
}}
/>
<Ball onContactForce={handleContactForce} />
<Floor color={floorColor} />
</group>
);
Expand Down
8 changes: 5 additions & 3 deletions demo/src/examples/sensors/SensorsExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ const Goal = (props: RigidBodyProps) => {
position={[0, 0, 1]}
args={[5, 3, 1]}
sensor
onIntersectionEnter={() => setIntersection(true)}
onIntersectionEnter={() => {
setIntersection(true);
}}
onIntersectionExit={() => setIntersection(false)}
/>
</RigidBody>
Expand All @@ -65,7 +67,7 @@ const Ball = () => {
const rb = useRef<RapierRigidBody>(null);

const restartBall = () => {
rb.current?.setTranslation({ x: 0, y: -7, z: -8 }, true);
rb.current?.setTranslation({ x: 0, y: -7, z: -24 }, true);
rb.current?.setLinvel({ x: 0, y: 0, z: 7 }, true);
};

Expand All @@ -82,7 +84,7 @@ const Ball = () => {
});

return (
<RigidBody ref={rb} colliders="ball">
<RigidBody ref={rb} colliders="ball" restitution={1.5}>
<Sphere material={material} castShadow />
</RigidBody>
);
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@
"test:ui": "vitest --ui",
"ts": "cd packages/react-three-rapier && yarn ts",
"build:docs": "typedoc --out ./packages/react-three-rapier/docs ./packages/react-three-rapier/src/index.ts --excludeInternal",
"prerelease:enter": "changeset pre enter rc",
"prerelease:exit": "changeset pre exit"
"canary:enter": "changeset pre enter canary",
"canary:exit": "changeset pre exit"
},
"simple-git-hooks": {
"commit-msg": "npx --no -- commitlint --edit ${1}",
Expand Down
14 changes: 12 additions & 2 deletions packages/react-three-rapier/src/components/AnyCollider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ import {
ConvexHullArgs
} from "../types";
import {
cleanRigidBodyPropsForCollider,
createColliderFromOptions,
createColliderState,
getActiveCollisionEventsFromProps,
useColliderEvents,
useUpdateColliderOptions
} from "../utils/utils-collider";
Expand Down Expand Up @@ -88,11 +90,19 @@ export const AnyCollider = memo(
useImperativeHandle(forwardedRef, () => getInstance());

const mergedProps = useMemo(() => {
return { ...rigidBodyContext?.options, ...props };
return {
...cleanRigidBodyPropsForCollider(rigidBodyContext?.options),
...props
};
}, [props, rigidBodyContext?.options]);

useUpdateColliderOptions(getInstance, mergedProps, colliderStates);
useColliderEvents(getInstance, mergedProps, colliderEvents);
useColliderEvents(
getInstance,
mergedProps,
colliderEvents,
getActiveCollisionEventsFromProps(rigidBodyContext?.options)
);

return (
<object3D
Expand Down
72 changes: 58 additions & 14 deletions packages/react-three-rapier/src/utils/utils-collider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ interface CreateColliderPropsFromChildren {

export const createColliderPropsFromChildren: CreateColliderPropsFromChildren =
({ object, ignoreMeshColliders = true, options }): ColliderProps[] => {
const colliderProps: ColliderProps[] = [];
const childColliderProps: ColliderProps[] = [];

object.updateWorldMatrix(true, false);
const invertedParentMatrixWorld = object.matrixWorld.clone().invert();
Expand Down Expand Up @@ -292,8 +292,8 @@ export const createColliderPropsFromChildren: CreateColliderPropsFromChildren =
options.colliders || "cuboid"
);

colliderProps.push({
...options,
const colliderProps: ColliderProps = {
...cleanRigidBodyPropsForCollider(options),
args: args,
shape: shape,
rotation: [rotationEuler.x, rotationEuler.y, rotationEuler.z],
Expand All @@ -303,7 +303,9 @@ export const createColliderPropsFromChildren: CreateColliderPropsFromChildren =
_position.z + offset.z * worldScale.z
],
scale: [worldScale.x, worldScale.y, worldScale.z]
});
};

childColliderProps.push(colliderProps);
}
};

Expand All @@ -313,7 +315,7 @@ export const createColliderPropsFromChildren: CreateColliderPropsFromChildren =
object.traverseVisible(colliderFromChild);
}

return colliderProps;
return childColliderProps;
};

export const getColliderArgsFromGeometry = (
Expand Down Expand Up @@ -380,10 +382,29 @@ export const getColliderArgsFromGeometry = (
return { args: [], offset: new Vector3() };
};

export const getActiveCollisionEventsFromProps = (props?: ColliderProps) => {
return {
collision: !!(
props?.onCollisionEnter ||
props?.onCollisionExit ||
props?.onIntersectionEnter ||
props?.onIntersectionExit
),
contactForce: !!props?.onContactForce
};
};

export const useColliderEvents = (
getCollider: () => Collider,
props: ColliderProps,
events: EventMap
events: EventMap,
/**
* The RigidBody can pass down active events to the collider without attaching the event listners
*/
activeEvents: {
collision?: boolean;
contactForce?: boolean;
} = {}
) => {
const {
onCollisionEnter,
Expand All @@ -397,13 +418,14 @@ export const useColliderEvents = (
const collider = getCollider();

if (collider) {
const hasCollisionEvent = !!(
onCollisionEnter ||
onCollisionExit ||
onIntersectionEnter ||
onIntersectionExit
);
const hasContactForceEvent = !!onContactForce;
const {
collision: collisionEventsActive,
contactForce: contactForceEventsActive
} = getActiveCollisionEventsFromProps(props);

const hasCollisionEvent = collisionEventsActive || activeEvents.collision;
const hasContactForceEvent =
contactForceEventsActive || activeEvents.contactForce;

if (hasCollisionEvent && hasContactForceEvent) {
collider.setActiveEvents(
Expand Down Expand Up @@ -434,6 +456,28 @@ export const useColliderEvents = (
onCollisionExit,
onIntersectionEnter,
onIntersectionExit,
onContactForce
onContactForce,
activeEvents
]);
};

export const cleanRigidBodyPropsForCollider = (props: RigidBodyProps = {}) => {
const {
mass,
linearDamping,
angularDamping,
type,
onCollisionEnter,
onCollisionExit,
onIntersectionEnter,
onIntersectionExit,
onContactForce,
children,
canSleep,
ccd,
gravityScale,
...rest
} = props;

return rest;
};
9 changes: 6 additions & 3 deletions packages/react-three-rapier/src/utils/utils-rigidbody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ export const useRigidBodyEvents = (
onCollisionEnter,
onCollisionExit,
onIntersectionEnter,
onIntersectionExit
onIntersectionExit,
onContactForce
} = props;

const eventHandlers = {
Expand All @@ -201,7 +202,8 @@ export const useRigidBodyEvents = (
onCollisionEnter,
onCollisionExit,
onIntersectionEnter,
onIntersectionExit
onIntersectionExit,
onContactForce
};

useEffect(() => {
Expand All @@ -217,6 +219,7 @@ export const useRigidBodyEvents = (
onCollisionEnter,
onCollisionExit,
onIntersectionEnter,
onIntersectionExit
onIntersectionExit,
onContactForce
]);
};

0 comments on commit 1f41278

Please sign in to comment.