diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 25b1cd41..bda789b6 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -11,6 +11,7 @@ import { useState } from "react"; import { NavLink, NavLinkProps, Route, Routes } from "react-router-dom"; +import { ActiveCollisionTypesExample } from "./examples/active-collision-types/ActiveCollisionTypesExample"; import { AllCollidersExample } from "./examples/all-colliders/AllCollidersExample"; import { AllShapesExample } from "./examples/all-shapes/AllShapesExample"; import { ApiUsage } from "./examples/api-usage/ApiUsageExample"; @@ -33,6 +34,7 @@ import { Kinematics } from "./examples/kinematics/KinematicsExample"; import { LockedTransformsExample } from "./examples/locked-transforms/LockedTransformsExample"; import { ManualStepExample } from "./examples/manual-step/ManualStepExamples"; import { MeshColliderTest } from "./examples/mesh-collider-test/MeshColliderExample"; +import { OneWayPlatform } from "./examples/one-way-platform/OneWayPlatform"; import { PerformanceExample } from "./examples/performance/PeformanceExample"; import Shapes from "./examples/plinko/ShapesExample"; import { RopeJointExample } from "./examples/rope-joint/RopeJointExample"; @@ -41,7 +43,6 @@ import { SnapshotExample } from "./examples/snapshot/SnapshotExample"; import { SpringExample } from "./examples/spring/SpringExample"; import { StutteringExample } from "./examples/stuttering/StutteringExample"; import { Transforms } from "./examples/transforms/TransformsExample"; -import { ActiveCollisionTypesExample } from "./examples/active-collision-types/ActiveCollisionTypesExample"; const demoContext = createContext<{ setDebug?(f: boolean): void; @@ -93,7 +94,8 @@ const Floor = () => { }; const routes: Record = { - "": , + "": , + shapes: Shapes, joints: , components: , cradle: , @@ -206,7 +208,7 @@ export const App = () => { > {Object.keys(routes).map((key) => ( - {key.replace(/-/g, " ") || "Plinko"} + {key.replace(/-/g, " ") || "One Way Platform"} ))} diff --git a/demo/src/examples/one-way-platform/OneWayPlatform.tsx b/demo/src/examples/one-way-platform/OneWayPlatform.tsx new file mode 100644 index 00000000..171e549b --- /dev/null +++ b/demo/src/examples/one-way-platform/OneWayPlatform.tsx @@ -0,0 +1,101 @@ +import { Sphere } from "@react-three/drei"; +import { useThree } from "@react-three/fiber"; +import { + CuboidCollider, + RapierCollider, + RapierRigidBody, + RigidBody, + useRapier +} from "@react-three/rapier"; +import { useCallback, useEffect, useRef } from "react"; +import { Vector3 } from "three"; +import { Demo } from "../../App"; + +export const OneWayPlatform: Demo = () => { + const ref = useRef(null); + const collider = useRef(null); + + const ball = useRef(null); + const { camera } = useThree(); + + useEffect(() => { + camera.position.set(0, 10, 20); + camera.lookAt(0, 0, 0); + camera.updateProjectionMatrix(); + + window.addEventListener("click", () => { + ball.current?.applyImpulse(new Vector3(0, 50, 0), true); + }); + }, []); + + const { filterContactPairHooks, world } = useRapier(); + + const hook = useCallback( + (c1: number, c2: number, b1: number, b2: number) => { + try { + const collider1 = world.getCollider(c1); + const collider2 = world.getCollider(c2); + + const body1 = world.getRigidBody(b1); + const body2 = world.getRigidBody(b2); + + if ( + (body1.userData as any)?.type && + (body1.userData as any).type === "platform" && + (body2.userData as any)?.type && + (body2.userData as any)?.type === "ball" + ) { + // Once we get try to get access to the ball and platform, the "hook" that we pass to filterContactPairHooks crashes + + // why does this crash here? what's wrong with the below setup? + const platformPosition = body1.translation(); + const ballVelocity = body2.linvel(); + const ballPosition = body2.translation(); + + // also doesn't work + // const platformPosition = ref.current!.translation(); + // const ballVelocity = ball.current!.linvel(); + // const ballPosition = ref.current!.translation(); + + // Allow collision if the ball is moving downwards and above the platform + if (ballVelocity.y < 0 && ballPosition.y > platformPosition.y) { + return 1; // Process the collision + } + } + + return 0; // Ignore the collision + } catch (error) { + console.log(error); + return null; + } + }, + [world] + ); + + useEffect(() => { + collider.current?.setActiveHooks(1); + filterContactPairHooks.push(hook); + }, []); + + return ( + + + + + + + + + + + + + + + ); +}; diff --git a/packages/react-three-rapier/src/components/Physics.tsx b/packages/react-three-rapier/src/components/Physics.tsx index 504c097c..a261972e 100644 --- a/packages/react-three-rapier/src/components/Physics.tsx +++ b/packages/react-three-rapier/src/components/Physics.tsx @@ -3,8 +3,10 @@ import { Collider, ColliderHandle, EventQueue, + PhysicsHooks, RigidBody, RigidBodyHandle, + SolverFlags, World } from "@dimforge/rapier3d-compat"; import { useThree } from "@react-three/fiber"; @@ -187,6 +189,19 @@ export interface RapierContext { * Is debug mode enabled */ isDebug: boolean; + + filterContactPairHooks: (( + collider1: ColliderHandle, + collider2: ColliderHandle, + body1: RigidBodyHandle, + body2: RigidBodyHandle + ) => SolverFlags | null)[]; + filterIntersectionPairHooks: (( + collider1: ColliderHandle, + collider2: ColliderHandle, + body1: RigidBodyHandle, + body2: RigidBodyHandle + ) => boolean)[]; } export const rapierContext = createContext( @@ -428,6 +443,34 @@ export const Physics: FC = (props) => { const rigidBodyEvents = useConst(() => new Map()); const colliderEvents = useConst(() => new Map()); const eventQueue = useConst(() => new EventQueue(false)); + + const filterContactPairHooks = useConst< + (( + collider1: ColliderHandle, + collider2: ColliderHandle, + body1: RigidBodyHandle, + body2: RigidBodyHandle + ) => SolverFlags | null)[] + >(() => []); + const filterIntersectionPairHooks = useConst< + (( + collider1: ColliderHandle, + collider2: ColliderHandle, + body1: RigidBodyHandle, + body2: RigidBodyHandle + ) => boolean)[] + >(() => []); + + const hooks = useConst(() => ({ + filterContactPair: (...args) => { + const hook = filterContactPairHooks.find((hook) => hook(...args)); + return hook ? hook(...args) : null; + }, + filterIntersectionPair: (...args) => { + const hook = filterIntersectionPairHooks.find((hook) => hook(...args)); + return hook ? hook(...args) : false; + } + })); const beforeStepCallbacks = useConst(() => new Set()); const afterStepCallbacks = useConst(() => new Set()); @@ -549,7 +592,7 @@ export const Physics: FC = (props) => { }); world.timestep = delta; - world.step(eventQueue); + world.step(eventQueue, hooks); // Trigger afterStep callbacks afterStepCallbacks.forEach((callback) => { @@ -808,7 +851,9 @@ export const Physics: FC = (props) => { afterStepCallbacks, isPaused: paused, isDebug: debug, - step + step, + filterContactPairHooks, + filterIntersectionPairHooks }), [paused, step, debug, colliders, gravity] );