pmndrs / react-three-rapier

🤺 Rapier physics in React
https://react-three-rapier.pmnd.rs
MIT License
1.07k stars 59 forks source link

How to apply forces to dynamically created RigidBodies? #562

Closed raphaelswiggy closed 10 months ago

raphaelswiggy commented 11 months ago

It seems like we need a ref to apply forces to a RigidBody. But what can we do for dynamically created RigidBodies, for which we don't have a predetermined count and cannot create refs in advance? For example, when we want to create a RigidBody on the click of a button.

Here is the code. There is also a link to a codesandbox to test it. What can be done in the allBoxesJump function? codesandbox

import { useState, useRef } from "react";
import * as THREE from "three";
import { Canvas } from "@react-three/fiber";
import { Physics, RigidBody } from "@react-three/rapier";
import "./styles.css";
import { OrbitControls } from "@react-three/drei";

export default function App() {
  const firstBoxRef = useRef();
  const [allBoxes, setAllBoxes] = useState([]);
  const handleAddBox = () => {
    setAllBoxes([
      ...allBoxes,
      <RigidBody key={allBoxes.length}>
        <mesh
          castShadow
          position={[Math.random() * 2 - 1, 10, Math.random() * 2 - 1]}
        >
          <boxGeometry args={[1, 1, 1]} />
          <meshStandardMaterial color={"red"} />
        </mesh>
      </RigidBody>,
    ]);
  };

  const handleFirstBoxJump = () => {
    firstBoxRef.current.applyImpulse({ x: 0, y: 10, z: 0 }, true);
  };

  const allBoxesJump = () => {
    for (let i = 0; i < allBoxes.length; i++) {
      const box = allBoxes[i];
      // How to applyImpulse to these boxes?
      console.log(box);
    }
  };

  return (
    <div className="container">
      <div
        style={{
          top: 0,
        }}
        className="button"
        onClick={handleAddBox}
      >
        Add Box
      </div>
      <div
        style={{
          top: 48,
        }}
        className="button"
        onClick={handleFirstBoxJump}
      >
        First Box Jump
      </div>

      <div
        style={{
          top: 96,
        }}
        className="button"
        onClick={allBoxesJump}
      >
        All Boxs Jump
      </div>
      <Canvas shadows camera={{ position: [3, 0, -5] }}>
        <OrbitControls />
        <ambientLight intensity={0.5} />
        <directionalLight
          intensity={1}
          position={[5, 5, 5]}
          castShadow
          shadow-mapSize-width={2048}
          shadow-mapSize-height={2048}
        />
        <Physics>
          {allBoxes}
          <RigidBody ref={firstBoxRef}>
            <mesh castShadow position={[0, 5, 0]}>
              <boxGeometry args={[1, 1, 1]} />
              <meshStandardMaterial color={"red"} />
            </mesh>
          </RigidBody>
          <RigidBody type="fixed">
            <mesh
              receiveShadow
              position={[0, -2, 0]}
              rotation-x={THREE.MathUtils.degToRad(-90)}
            >
              <planeGeometry args={[50, 50]} />
              <meshStandardMaterial color="gray" />
            </mesh>
          </RigidBody>
        </Physics>
      </Canvas>
    </div>
  );
}
wiledal commented 10 months ago

This is more of a React-problem than a Rapier problem!

You can absolutely attach refs to those boxes, using an array of refs for example.

const [refs] = useState(() => [] as RapierRigidBody[])
const addRef = (ref) => refs.push(ref)

useFrame(() => {
  refs.forEach(body => {
    // do something with body
  })
})

... 

setAllBoxes([
  ...allBoxes,
  <RigidBody key={allBoxes.length} ref={addRef}>
    <mesh

Here's a sloppy example using a map to keep track of the refs. https://codesandbox.io/p/sandbox/relaxed-chandrasekhar-6f4prv?file=%2Fsrc%2FApp.tsx%3A18%2C33