Skip to content

Commit

Permalink
Merge pull request #113 from pmndrs/ballBox
Browse files Browse the repository at this point in the history
Ball box
  • Loading branch information
DennisSmolek authored Jun 9, 2024
2 parents 730bcec + a221e1b commit 54b1c05
Show file tree
Hide file tree
Showing 43 changed files with 15,049 additions and 5,473 deletions.
3 changes: 3 additions & 0 deletions apps/examples/public/joltBolt.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/examples/public/models/joltBolt.glb
Binary file not shown.
39 changes: 23 additions & 16 deletions apps/examples/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Base demo copied from r3/rapier
//import * as THREE from 'three';
import { Environment, CameraControls } from "@react-three/drei";
import { CameraControls } from "@react-three/drei";
import { Canvas, useThree } from "@react-three/fiber";
import { vec3 } from "@react-three/jolt";
import { Perf } from "r3f-perf";
Expand All @@ -27,6 +27,7 @@ import { FourWheelDemo } from "./examples/FourWheelsWithHeightmap";
import { CharacterVirtualDemo } from "./examples/CharacterVirtualDemo";
import { Impulses } from "./examples/Impulses";
import { MotionSources } from "./examples/motionSources";
import { BallBox } from "./examples/BallBox";
const demoContext = createContext<{
debug: boolean;
paused: boolean;
Expand Down Expand Up @@ -60,7 +61,7 @@ const ToggleButton = ({

//* Controls Wrapper. We have to do this to get root state
export function ControlWrapper(props: any) {
const { position = [0, 10, 10], target = [0, 1, 0], ...rest } = props;
const { position = [0, 10, 10], target = [0, 1, 0], transition = true, ...rest } = props;
const { controls } = useThree();
useEffect(() => {
const newPosition = vec3.three(position);
Expand All @@ -74,7 +75,7 @@ export function ControlWrapper(props: any) {
newTarget.x,
newTarget.y,
newTarget.z,
true
transition
);
}, [position]);
return <CameraControls makeDefault {...rest} />;
Expand All @@ -83,6 +84,7 @@ type Routes = {
[key: string]: {
position?: number[];
target?: number[];
transition?: boolean;
background?: string;
element: JSX.Element;
label?: string;
Expand Down Expand Up @@ -146,6 +148,13 @@ const routes: Routes = {
target: [0, 1, -15],
background: "#C1839F",
element: <MotionSources />
},
BallBoxes: {
position: [0, 0, 30],
target: [0, 0, 0],
transition: false,
background: "#141622",
element: <BallBox />
}
};

Expand All @@ -162,6 +171,7 @@ export const App = () => {
const [cameraProps, setCameraProps] = useState<{
position: any;
target: any;
transition: boolean;
} | null>(null);
const location = useLocation();

Expand All @@ -175,7 +185,11 @@ export const App = () => {
// set the camera position
//@ts-ignore
const route = routes[location.pathname.replace("/", "")];
setCameraProps({ position: route.position, target: route.target });
setCameraProps({
position: route.position,
target: route.target,
transition: route.transition!
});
setBackground(route.background || "#3d405b");
}, [location]);

Expand All @@ -194,19 +208,12 @@ export const App = () => {
camera={{ near: 1, fov: 45, position: cameraProps?.position }}
>
<color attach="background" args={[background]} />
<directionalLight
castShadow
position={[10, 10, 10]}
shadow-camera-bottom={-40}
shadow-camera-top={40}
shadow-camera-left={-40}
shadow-camera-right={40}
shadow-mapSize-width={1024}
shadow-bias={-0.0001}
/>
<Environment preset="apartment" />

<ControlWrapper position={cameraProps?.position} target={cameraProps?.target} />
<ControlWrapper
position={cameraProps?.position}
target={cameraProps?.target}
transition={cameraProps?.transition}
/>
<demoContext.Provider value={{ debug, paused, interpolate, physicsKey }}>
<Routes>
{Object.keys(routes).map((key) => (
Expand Down
155 changes: 155 additions & 0 deletions apps/examples/src/examples/BallBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { Physics, RigidBody } from "@react-three/jolt";
import { useDemo } from "../App";
import { useMemo, useEffect, useState, Fragment } from "react";
//import * as THREE from "three";
import { useThree } from "@react-three/fiber";
import InitJolt from "../jolt/Distribution/jolt-physics.wasm-compat";

import { JoltBolt } from "./Bodies/joltBolt";
import { BoxContainer } from "./Bodies/BoxContainer";
import Changer from "./Bodies/Changer";
import Scaler from "./Bodies/Scaler";

// fix typescript to know about permissions
declare global {
interface Window {
DeviceMotionEvent: {
prototype: DeviceMotionEvent;
new (type: string, eventInitDict?: DeviceMotionEventInit): DeviceMotionEvent;
requestPermission?: () => Promise<PermissionState>;
};
}
}
export function BallBox() {
const { debug, paused, interpolate, physicsKey } = useDemo();
const { controls, camera } = useThree();
//disable controls
useEffect(() => {
if (!controls) return;
//@ts-ignore
controls.rotate(0, 0, false);
setTimeout(() => {
//@ts-ignore
controls.enabled = false;
}, 100);
return () => {
//@ts-ignore
controls!.enabled = true;
};
}, [controls, camera]);

const defaultBodySettings = {
mRestitution: 0.5
};

const positions = useMemo(() => {
const allPos = [];

for (let i = 0; i < 15; i++) {
allPos.push([Math.random() * 20 - 10, Math.random() * 10, 0]);
}
return allPos;
}, []);

const [gravity, setGravity] = useState([0, -9.8, 0]);

const updateGravityOnMouse = (e: MouseEvent) => {
// if the right click isn't pressed, don't update the gravity
if (!e.buttons || e.buttons !== 2) return;

const x = (e.clientX / window.innerWidth) * 20 - 10;
const y = (e.clientY / window.innerHeight) * 20 - 10;
setGravity([x, -y, 0]);
};
// attach event listener to mouse move with removal on return
useEffect(() => {
window.addEventListener("mousemove", updateGravityOnMouse);
function preventDefault(e: MouseEvent) {
e.preventDefault();
}
window.addEventListener("contextmenu", preventDefault);
return () => {
window.removeEventListener("mousemove", updateGravityOnMouse);
window.removeEventListener("contextmenu", preventDefault);
};
}, []);

// detect device orientation and set gravity
const updateGravityOnDevice = (e: DeviceMotionEvent) => {
if (!e.accelerationIncludingGravity || e.accelerationIncludingGravity.x === null) return;
const { x, y, z } = e.accelerationIncludingGravity!;
console.log("setting from device", e);
setGravity([x || 0, y || 0, z || 0]);
};

// attach event listener to device orientation with removal on return
useEffect(() => {
//@ts-ignore
if (typeof DeviceMotionEvent.requestPermission === "function") {
//@ts-ignore
DeviceMotionEvent.requestPermission()
.then((permissionState: PermissionState) => {
if (permissionState === "granted") {
window.addEventListener("devicemotion", updateGravityOnDevice);
}
})
.catch(console.error);
} else {
// handle regular non iOS 13+ devices
window.addEventListener("devicemotion", updateGravityOnDevice);
}

return () => {
window.removeEventListener("devicemotion", updateGravityOnDevice);
};
}, []);

return (
<>
<Physics
module={InitJolt}
paused={paused}
key={physicsKey}
interpolate={interpolate}
debug={debug}
gravity={gravity}
defaultBodySettings={defaultBodySettings}
>
<BoxContainer />

<RigidBody
scale={[0.03, 0.03, 0.03]}
rotation={[3.14, 0, 0]}
position={[-1, 3, 1]}
onlyInitialize
>
<JoltBolt />
</RigidBody>

{positions.map((pos) => (
<Fragment key={pos.toString()}>
<Scaler position={pos} />
<Changer
position={[pos[0] - Math.random(), pos[1] + Math.random(), pos[2]]}
/>
</Fragment>
))}
</Physics>
<ambientLight intensity={0.5} />
<directionalLight
position={[-29, 5, 20]}
shadow-camera-bottom={-16}
shadow-camera-top={16}
shadow-camera-left={-16}
shadow-camera-right={16}
shadow-camera-near={0.1}
shadow-camera-far={70}
shadow-mapSize-width={1024}
shadow-bias={0.001}
shadow-normalBias={0.03}
intensity={3}
castShadow
/>
</>
);
}
66 changes: 66 additions & 0 deletions apps/examples/src/examples/Bodies/BoxContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useThree } from "@react-three/fiber";
import { RigidBody, Shape } from "@react-three/jolt";

export function BoxContainer(props: any) {
// we need to get window data
const { viewport } = useThree();
const boxColor = "#38165c";

return (
<group {...props}>
<Wall
scale={[viewport.width, 1, 7]}
color={boxColor}
position={[0, viewport.height / 2 - 1, 2]}
/>
<Wall
scale={[viewport.width, 1, 5]}
color={boxColor}
position={[0, -viewport.height / 2 + 1, 0]}
/>

<Wall
scale={[1, viewport.height, 5]}
color={boxColor}
position={[-viewport.width / 2 + 1, 0, 0]}
/>
<Wall
scale={[1, viewport.height, 5]}
color={boxColor}
position={[viewport.width / 2 - 1, 0, 0]}
/>

<Wall
scale={[viewport.width + 1, viewport.height + 0.01, 1]}
color={boxColor}
position={[0, 0, -1]}
cast={false}
/>
{props.children}
<RigidBody
scale={[viewport.width, viewport.height, 1]}
position={[0, 0, 4]}
type="static"
>
<Shape size={[1, 1, 1]} />
</RigidBody>
</group>
);
}

function Wall({
cast = true,
receive = true,
position = [0, 0, 0],
color = "#114929",
scale = [1, 1, 1]
}) {
return (
<RigidBody position={position} scale={scale} type="static">
<mesh castShadow={cast} receiveShadow={receive}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={color} />
</mesh>
</RigidBody>
);
}
65 changes: 65 additions & 0 deletions apps/examples/src/examples/Bodies/Changer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// this body changes between shapes but keeps the same rigidBdy

// use memo so it doesn't cycle on inputs
import { RigidBody, getShapeSettingsFromGeometry, BodyState } from "@react-three/jolt";
import { memo, useMemo, useRef, useState } from "react";
import * as THREE from "three";
type ChangerProps = {
position?: number[];
};

const Changer: React.FC<ChangerProps> = memo((props) => {
//hold the refs
const meshRef = useRef<THREE.Mesh | null>(null);
const bodyRef = useRef<BodyState>();

// original color: #6200B3
const [activeColor, setActiveColor] = useState("#188FA7");
const currentShape = useRef(0);
//create the geometries
const geometries = useMemo(() => {
const geoArray = [];
// sphere
const sphereGeo = new THREE.SphereGeometry(1.3, 32, 32);
const sphereShape = getShapeSettingsFromGeometry(sphereGeo)!.shapeSettings!.Create().Get();
geoArray.push({ geo: sphereGeo, shape: sphereShape, color: "#188FA7" });

// box
const boxGeo = new THREE.BoxGeometry(2, 2, 2);
const boxShape = getShapeSettingsFromGeometry(boxGeo)!.shapeSettings!.Create().Get();
geoArray.push({ geo: boxGeo, shape: boxShape, color: "#DC6A07" });

//pyramid tetrahedron
const tetraGeo = new THREE.TetrahedronGeometry(2);
const tetraShape = getShapeSettingsFromGeometry(tetraGeo, "convex")!
.shapeSettings!.Create()
.Get();
geoArray.push({ geo: tetraGeo, shape: tetraShape, color: "#70D6EB" });

//torus knot
const torusGeo = new THREE.TorusKnotGeometry(1, 0.4, 64, 8);
const torusShape = getShapeSettingsFromGeometry(torusGeo)!.shapeSettings!.Create().Get();
geoArray.push({ geo: torusGeo, shape: torusShape, color: "#72E1D1" });
return geoArray;
}, []);

// function to change things
const nextShape = () => {
currentShape.current = (currentShape.current + 1) % geometries.length;
const { geo, shape, color } = geometries[currentShape.current];
setActiveColor(color);
(bodyRef.current as BodyState).shape = shape;
if (meshRef.current) meshRef.current.geometry = geo;
};

return (
<RigidBody {...props} ref={bodyRef} onlyInitialize>
<mesh ref={meshRef} onClick={() => nextShape()} castShadow receiveShadow>
<sphereGeometry args={[1.3, 32, 32]} />
<meshStandardMaterial color={activeColor} />
</mesh>
</RigidBody>
);
});

export default Changer;
Loading

0 comments on commit 54b1c05

Please sign in to comment.