Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
August 19, 2022 02:46 pm GMT

3D web - Cannon physics - web3 serie

Hey Reader,

This is the 3rd post of 3D-web3 Series.

1 - Vite config and basic three.js
2 - 3D web - Three.js (fiber & drei)
3 - 3D web - Cannon physics
4 - Web3 (next week available)

"Cannon" is the rigid body physics engine who includes simple collision detection, various body shapes, contacts, friction and constraints.

npm i @react-three/cannon

Simple steps to make it work:
1_ Import and Create a physics world

import { Physics, useBox, ... } from '@react-three/cannon'<Physics>{/* Physics related objects in here please */}</Physics>

2_ Pick a shape that suits your objects contact surface, it could be a box, plane, sphere, etc. Give it a mass, too

const [ref, api] = useBox(() => ({ mass: 1 }))

3_ Take your object, it could be a mesh, line, gltf, anything, and tie it to the reference you have just received. It will now be affected by gravity and other objects inside the physics world.

<mesh ref={ref} geometry={...} material={...} />

4_ You can interact with it by using the api, which lets you apply positions, rotations, velocities, forces and impulses

useFrame(({ clock }) => api.position.set(Math.sin(clock.getElapsedTime()) * 5, 0, 0))

5_ You can use the body api to subscribe to properties to get updates on each frame

const velocity = useRef([0, 0, 0])useEffect(() => {  const unsubscribe = api.velocity.subscribe((v) => (velocity.current = v))  return unsubscribe}, [])

All the steps in "Box.jsx" component looks like:

import { Physics, useBox } from '@react-three/cannon'import { useFrame } from '@react-three/fiber';const Box = () => {    const [ref, api] = useBox(() => ({ mass: 1 }))    useFrame(({ clock }) => api.position.set(Math.sin(clock.getElapsedTime()) * 5, 0, 0))    const velocity = useRef([0, 0, 0])    useEffect(() => {        const unsubscribe = api.velocity.subscribe((v) => (velocity.current = v))        return unsubscribe    }, [])    return (        <Physics>            <mesh ref={ref}>                <boxGeometry attach='geometry' args={[1, 1, 1]} />                <meshStandardMaterial attach="material" color={'#000'} />            </mesh>        </Physics>    )}export default Box

Let's apply this package to our repo.

App logic__

First check previous post if you haven't, to understand why we are not using in code-sand-box our gltf model
Instead, we are using a "yellow box" with an onClick method to change between two "camera modes"

Include the "ActivateSpawner" component which will be the parent of the other 3 components we need.

In camera RIG mode we'll see a "black box" with an onClick method to activate :

a) "Spawner" component: It creates "x" number of bubbles with "y" velocity. "Spawner" has "Bubble" component as child.

b) "PlayerBox" component: Mimics your movement and you've to avoid coming bubbles

Both components have a collider property. So, if "PlayerBox" collides with a "Bubble" component, game will be stoped

We'll be using (previous tutorial "objects/hooks" are not included):

  • From "Fiber": useThree, useFrame
  • From "Cannon": useBox, useSphere
  • From "Three": Vector3

Step_1 Create a "ActivateSpawner" component

Notice we're giving a "mass" of 0 to the box

import React from 'react'import { useBox } from '@react-three/cannon';import { useState } from 'react';import Spawner from './Spawner';import PlayerBox from './PlayerBox';const ActivateSpawner = () => {    const [play, setPlay] = useState(false);    // This box is used to start the game    const [ref] = useBox(() => ({        mass: 0,        position: [-5, 2, -10],        type: 'Dynamic',        args: [1, 1, 1],    }));    return (        <group>            <mesh                ref={ref}                onClick={() => {                    console.log(!play)                    setPlay(!play)                }}            >                <boxGeometry attach='geometry' args={[1, 1, 1]} />                <meshStandardMaterial attach="material" color={'#000'} />            </mesh>            {play && (<>                <Spawner />                <PlayerBox setPlay={setPlay} />            </>            )}        </group>    )}export default ActivateSpawner

Step_2 Create "Spawner" component

Get random data (position, delay, color) for each "bubble" using a for loop and "randomIntBetween(a,b)" & randomIntBetweenAlsoNegatives(a,b) functions

import { Vector3 } from 'three';import Bubble from './Bubble';const Spawner = () => {    function randomIntBetween(min, max) { // min and max included         return Math.floor(Math.random() * (max - min + 1) + min)    }    function randomIntBetweenAlsoNegatives(min, max) { // min and max included         const math = Math.floor(Math.random() * (max - min + 1) + min)        const random = Math.random()        const zeroOrOne = Math.round(random)        if (zeroOrOne) return -(math)        return math    }    const attackersArray = [];    for (let i = 0; i < 20; i++) {        let position = new Vector3(            randomIntBetweenAlsoNegatives(0, 2),            randomIntBetweenAlsoNegatives(0, 2),            0)        let wait = randomIntBetween(1, 12) * 10        let color = `#${Math.random().toString(16).substring(2, 8)}`        const att = [position, wait, color]        attackersArray.push(att)    }    return (        <group>            {attackersArray.map((attackers, key) => {                return <Bubble                    key={key}                    pos={attackers[0]}                    wait={attackers[1]}                    color={attackers[2]}                />            })}        </group>    );};export default Spawner;

Step_3 Create "PlayerBox" component

Use "useThree" hook from '@react-three/fiber' to create a reference to our canvas "camera" object. Now we are able to give same value to our "PlayerBox" using "useFrame" hook

This hook calls you back every frame, which is good for running effects, updating controls, etc.

Add to our "Box" a "collisionFilterGroup" and "collisionFilterMask" properties.
The first defines in which group it is and the second which group it may collide with

import { useBox, } from '@react-three/cannon';import { useFrame } from '@react-three/fiber';import { useThree } from '@react-three/fiber'const PlayerBox = (props) => {    const { camera } = useThree()    const [ref, api] = useBox(() => ({        mass: 0,        type: 'Dynamic',        position: [0, 0, -5],        args: [0.3, 0.3, 0.1], // collision box size        collisionFilterGroup: 1,        // 1 PlayerBox 2 Objetive 3 BulletBox 4 Attackers        collisionFilterMask: 4,        onCollide: (e) => {            props.setPlay(false);            console.log('game over')        },    }));    // Tambien simula el movimiento de la camara (y por lo tnato el del objetivo), para poder tener un collider para el game over    useFrame(() => {        api.position.set(camera.position.x, camera.position.y, -2);    });    return (        <>            <mesh ref={ref}>                <boxBufferGeometry attach='geometry' args={[0.1, 0.1, 0.1]} /> {/* box size */}                <meshStandardMaterial attach="material" color={'#000'} />            </mesh>        </>    );};export default PlayerBox;

Step_4 Create "Bubble" component

In order to use the same "bubble" object to run the same race "x" number of times, add "setTimeout" function to reset the bubble position inside for loop.

import { useSphere } from '@react-three/cannon';import { useFrame } from '@react-three/fiber';const Bubble = (props) => {    let zMovement = -20;    const [ref, api] = useSphere(() => ({        mass: 0,        position: [props.pos.x, props.pos.y, props.pos.z - 200],        type: 'Dynamic',        // args: [1, 1, 1],        // 1 PlayerBox 2 Objetive 3 BulletBox 4 Bubble        collisionFilterGroup: 4,        // No te va a colisionar, sino que vas a colisionar contra el        collisionFilterMask: 1,    }));    useFrame(() => {        api.position.set(            props.pos.x,            props.pos.y,            (zMovement += 0.1) - props.wait        );    });    for (let i = 1; i < 3; i++) {        window.setTimeout(() => {            zMovement = -50;            api.position.set(0, 0, -zMovement);            // 6 segs * i * wait= posicion de cada cubo para hacer que algunos salgan antes que otros        }, 6 * 1000 + props.wait * 100);    }    return (        <mesh ref={ref}>            <sphereGeometry attach='geometry' args={[1, 32, 32]} />            <meshStandardMaterial attach="material" color={props.color} />        </mesh>    );};export default Bubble;

Step_5 Add "ActivateSpawner" in our App.jsx using "physics" node imported from "@react-three/cannon"

All components we've defined will be rendered in our DOM when
cameraMode is false => camera RIG mode setted

import { Canvas } from '@react-three/fiber';import ActivateSpawner from './geometry/ActivateSpawner';...return (...{!cameraMode &&                        < Physics >                            <ActivateSpawner />                        </Physics>                    }...)

Resume of components: ActivateSpawner , Spawner, PlayerBox, Bubble

Web3 will be added in next post

I hope it has been helpful.


Original Link: https://dev.to/uigla/3d-web-cannon-physics-web3-serie-24jh

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To