
TECH3
VOICE API + PHYSICS
05 2025-2026
In dit onderdeel leren we hoe we de voice API kunnen integreren met 3D en zien we hoe we interactieve en leuke scenes maken. Physics is na dit onderdeel geen geheim meer.

VOICE API + PHYSICS
VOICE API
01



01
VOICE API
SPEECH RECOGNITION
Sommige browsers hebben standaard een Web Speech API ingebouwd. Daarmee kun je via JavaScript spraak herkennen en meteen omzetten naar tekst. Het werkt alleen niet overal even goed; Chrome ondersteunt het het best.
Hoe werkt het?
-
Toegang tot de microfoon van de gebruiker
-
Audio wordt doorgestuurd naar de speech-engine
-
API stuurt transcripties terug via events
-
Zowel live (interim) als definitieve resultaten

01
VOICE API
PROPERTIES & EVENTS
Belangrijke properties
-
continuous: blijft continu luisteren
-
interimResults: toont tussentijdse resultaten
-
lang: taalinstelling (bv. "nl-NL", "en-US")
-
maxAlternatives: aantal mogelijke transcripties
-
…
Belangrijke events
-
onstart: begint met luisteren
-
onresult: nieuwe transcriptie beschikbaar
-
onerror: foutmelding (bv. geen microfoon, API niet beschikbaar)
-
onend: stoppen met luisteren

01
VOICE API
SCENARIO
Je kunt met deze API van alles doen: tekst laten inspreken, apps met je stem bedienen (dat gaan wij doen!), websites handsfree gebruiken en mensen met een beperking beter ondersteunen...
Limitaties
-
Geen support in Firefox, Safari en diverse mobiele browsers
-
Internetverbinding verplicht
-
Privacy- en dataverwerkingsrisico’s (GDPR!)
-
Minder nauwkeurig bij veel achtergrondgeluid
Er zijn andere API's en Speech-to-text libraries die mogelijks wel op Firefox en Safari werken.

01
VOICE API
LET'S START
We gaan eerst een custom hook maken. Die hook zorgt ervoor dat we heel makkelijk kunnen starten en stoppen met luisteren, en dat we het stukje herkende tekst kunnen ophalen.
Door dit in een custom hook te stoppen, kunnen we het makkelijk gebruiken in andere components. Alles zit netjes op één plek.
Maak een folder hooks aan en plaats daarin useSpeechToText.jsx. Vervolgens plaats je de scafold voor je hook.
import { useEffect, useRef, useState } from 'react'
const useSpeechToText = (options = {}) => {
}
export default useSpeechToText
01
VOICE API
LET'S START
We beginnen bij de basis: in onze hook maken we eerst de states en refs aan die we straks nodig hebben.
import { useEffect, useRef, useState } from 'react'
const useSpeechToText = (options = {}) => {
const [isListening, setIsListening] = useState(false);
const [transcript, setTranscript] = useState("");
const recognitionRef = useRef(null);
}
export default useSpeechToText-
isListening→ toont of microfoon actief is -
transcript→ herkende tekst -
recognitionRef→ houdt de SpeechRecognition-instance persistent vast

01
VOICE API
API CHECK
We moeten eerst checken of de voice API wel werkt. Als dat niet zo is, pakken we dat netjes op. Omdat we dit meteen bij het laden van de pagina willen doen, gebruiken we een useEffect zonder dependencies.
useEffect(() => {
if (!('webkitSpeechRecognition' in window)) {
console.error("Speech Recognition API not supported in this browser.");
return;
}
}, []);
-
WebkitSpeechRecognition werkt alleen in Chrome
-
We vermijden crashes door een check uit te voeren
-
Ideaal punt om later een polyfill of fallback in te pluggen
Een polyfill is een stukje JavaScript-code dat nieuwe browserfunctionaliteit emuleert in oudere of niet-ondersteunde browsers.

01
VOICE API
MAKE IT
Nu we zeker weten dat de API werkt, kunnen we de spraakherkenning opstarten. We maken een SpeechRecognition aan en geven de opties
door die we willen gebruiken, zoals taal en live tussentijdse resultaten.
useEffect(() => {
//...
recognitionRef.current = new window.webkitSpeechRecognition();
const recognition = recognitionRef.current;
recognition.interimResults = options.interimResults ?? true;
recognition.lang = options.lang ?? 'en-US';
recognition.continuous = options.continuous ?? false;
}, []);
-
Maakt de native spraakherkenning aan
-
We bewaren deze in een
ref, zodat hij niet herbouwd wordt bij elke render

01
VOICE API
OPTIONAL GRAMMAR
Je kunt een grammar toevoegen, zodat de API beter herkent wat je verwacht. Maar tegenwoordig merken de meeste browsers daar nauwelijks iets van.
useEffect(() => {
//...
if ("webkitSpeechGrammarList" in window) {
const grammar = '#JSGF V1.0;';
const speechRecognitionList = new window.webkitSpeechGrammarList();
speechRecognitionList.addFromString(grammar, 1);
recognition.grammars = speechRecognitionList;
}
}, []);

01
VOICE API
RESULTS
Om te weten wat er gezegd wordt, gebruiken we het onresult event. We gaan door de resultaten heen en voegen elke nieuwe tekst toe aan onze transcript state. Zo zie je live wat er herkend wordt.
useEffect(() => {
//...
recognition.onresult = (event) => {
let text = "";
for (let i = 0; i < event.results.length; i++) {
text += event.results[i][0].transcript;
}
setTranscript(text);
};
}, []);
-
Event bevat een lijst met (interim + final) resultaten
-
We plakken alle resultaten samen
-
Transcript wordt rechtstreeks in React state geplaatst

01
VOICE API
ERROR / STOP
Wat we zeker niet mogen vergeten is het afhandelen van de errors en wanneer onze recognition api stopt. Dit doen we als volgt.
useEffect(() => {
//...
recognition.onerror = (event) => {
console.error("Speech recognition error", event);
};
recognition.onend = () => {
setIsListening(false);
setTranscript("");
};
}, []);
-
Logt foutmeldingen zoals microfoonweigering
-
Hier kan je UI-feedback of retries toevoegen
-
Wordt getriggerd wanneer de API vanzelf stopt
-
Reset states zodat UI weer synchroon is

01
VOICE API
CLEANUP
Ook in de cleanup function van onze useEffect moeten we zien dat onze API stopt.
useEffect(() => {
//...
return () => {
if (recognition && typeof recognition.stop === 'function') {
try { recognition.stop(); } catch (e) {}
}
};
}, []);
-
Voorkomt memory leaks
-
Voorkomt dat de microfoon blijft luisteren na unmount
-
Verwijdert native listeners

01
VOICE API
START / STOP
Onze API is nu opgezet, het enige wat ons nog resteert is het aanmaken van een startfunctie, stopfunctie en deze te returnen.
const startListening = () => {
if (recognitionRef.current && !isListening) {
recognitionRef.current.start();
setIsListening(true);
}
};
const stopListening = () => {
if (recognitionRef.current && isListening) {
recognitionRef.current.stop();
setIsListening(false);
}
};
return { isListening, transcript, startListening, stopListening };Met alles samen heb je nu een mooie API hook die onze de woorden terug zal geven.
VOICE API + PHYSICS
MAKE IT REACT
02





02
MAKE IT REACT
REACTIVE SCENE
We maken nu een kleine demo: een box die van kleur verandert als je "Change" zegt. Een button start de voice API en de functie die de kleur verandert wordt gedeeld via een context. Zo zie je hoe alles samenwerkt.
Maak een component VoiceButton.jsx aan waarin we onze knop teruggeven.
import React, { useContext, useEffect } from 'react'
import useSpeechToText from '../../hooks/useSpeechToText';
const Voice = () => {
const { isListening, transcript, startListening, stopListening } = useSpeechToText();
const listenToggle = () => {
isListening ? stopVoiceInput() : startListening();
}
const stopVoiceInput = () => {
console.log(transcript);
stopListening()
}
return (
<div className="voice-ui">
<button className="voice-button" onClick={listenToggle}>{isListening ? "Stop Listening" : "Speak"}</button>
</div>
)
}
export default Voice


02
MAKE IT REACT
CONTEXT
We willen onze state doorgeven aan onze experience dus maken we een contextProvider aan.
Maak een colorContext aan in /context. Maak je provider en plaats deze in App.
import React, { createContext, useCallback, useState } from 'react'
const ColorContext = createContext({
triggerColorChange: () => {},
color: "#ffffff",
})
export const ColorProvider = ({ children }) => {
const [color, setColor] = useState("#ffffff")
const triggerColorChange = useCallback(() => {
const randomColor = "#" + Math.floor(Math.random() * 16777215).toString(16)
setColor(randomColor)
}, [])
return (
<ColorContext.Provider value={{ triggerColorChange, color }}>
{children}
</ColorContext.Provider>
)
}
export default ColorContext



02
MAKE IT REACT
BACK TO VOICE
Nu we onze context hebben kunnen we onze voice button afwerken.
We zullen checken of het woord 'change' is gezegd.
Maak een useEffect aan en plaats daarin de logica voor het woord.
const { triggerColorChange } = useContext(ColorContext);
useEffect(() => {
if (!transcript || !transcript.length) return;
const words = transcript.trim().split(/\s+/).filter(x => x);
words.forEach(w => console.log("Word:", w));
const normalized = words.map(w => w.toLowerCase().replace(/[^\w]/g, ""));
if (normalized.includes("change")) {
try { triggerColorChange(); } catch (e) { /* ignore */ }
console.log("triggerColorChange called")
}
if (words.length > 0 && isListening) {
try { stopListening(); } catch (e) { /* ignore */ }
}
}, [transcript, isListening, stopListening, triggerColorChange])


02
MAKE IT REACT
EXPERIENCE
Nu onze functionaliteiten werken en de color variable aangepast zal worden als we 'change' zeggen kunnen we nu onze cube veranderen van kleur door de color op de material te plaatsen.
Plaats een ref op de cube en verander de color naar die uit de context.
import { OrbitControls } from "@react-three/drei";
import Box from "./components/Box/Box";
import Ground from "./components/Ground/Ground";
import { useContext, useEffect, useRef } from "react";
import * as THREE from "three";
import ColorContext from "./context/ColorContext";
const Experience = () => {
const cubeRef = useRef(null)
const { color } = useContext(ColorContext)
// Update mesh color whenever the color in context changes
useEffect(() => {
if (cubeRef.current) {
cubeRef.current.material.color.set(color)
}
}, [color])
return (
<>
{/* Controls */}
<OrbitControls makeDefault />
{/* Lights */}
<directionalLight position={[1, 2, 3]} intensity={4.5} />
<ambientLight intensity={1} />
{/* Models */}
<Box ref={cubeRef} rotation-y={Math.PI * 0.25} scale={1.5}/>
{/* Floor */}
<Ground position-y={- 1} rotation-x={- Math.PI * 0.5} scale={10}/>
</>
)
}
export default Experience


02
MAKE IT REACT
CSS
Hier is de Css file om het iets aangenamer te maken.
html, body, #root {
height: 100%;
width: 100%;
margin: 0;
}
body {
overflow: hidden;
}
.full-screen-canvas {
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
z-index: 0;
touch-action: none;
}
.voice-ui {
position: fixed;
top: 16px;
right: 16px;
z-index: 10;
display: flex;
gap: 8px;
align-items: center;
}
.voice-button {
padding: 8px 12px;
border-radius: 6px;
border: none;
background: rgba(0,0,0,0.6);
color: #fff;
cursor: pointer;
backdrop-filter: blur(4px);
}VOICE API + PHYSICS
PHYSICS (RAPIER)
03



03
PHYSICS (RAPIER)
WHAT?
React Three Rapier is de officiële physics integratie voor React Three Fiber.
Het maakt het mogelijk om declaratief echte 3D physics toe te voegen in R3F scenes, zonder WebGL boilerplate.
-
ondersteunt rigid bodies, colliders, constraints, joints, sensors, impulses
-
extreem snel dankzij native Rust physics (sneller dan Cannon / Ammo engines)
-
volledig React gedreven: hooks, components, lifecycle events
-
ideaal voor product configurators, games, AR commerce en simulatie
-
stabiele collisions, stacking en sleeping (geen jank / drift)
-
werkt soepel samen met controls en interactieve UI flows, blijft volledig in sync met React state


03
PHYSICS (RAPIER)
STARTER
We starten met een basis scene zonder bijzondere eigenschappen. De gepresenteerde concepten zijn echter van toepassing op alle 3D-modellen en -omgevingen.
npm install @react-three/rapierEens je de starter file gestart hebt voer je volgende uit in de terminal.
Rapier is succesvol geïnstalleerd, waardoor we nu physics-functionaliteit kunnen toepassen.

03
PHYSICS (RAPIER)
PHYSICS
De Physics-component identificeert objecten die actief deelnemen aan de physics-simulatie. Objecten buiten deze component blijven statisch en hebben geen invloed op de simulatie.
import { Physics } from '@react-three/rapier'
<Physics>
<group>
<Sphere position-x={-2} position-y={2} scale={1} />
<Box ref={cubeRef} rotation-y={Math.PI * 0.25} position-x={2} scale={1.5}/>
</group>
{/* Floor */}
<Ground position-y={- 1} rotation-x={- Math.PI * 0.5} scale={10}/>
</Physics>Importeer de Physics-component uit Rapier en omsluit onze drie meshes hiermee.

03
PHYSICS (RAPIER)
RIGIDBODY
Zodra de Physics-“World” rond de meshes is opgezet, kunnen we specificeren welke objecten door de physics beïnvloed worden door een RigidBody rondom elk object te plaatsen.
import { RigidBody, Physics } from '@react-three/rapier'
<Physics>
<RigidBody>
<Sphere position-x={-2} position-y={2} scale={1} />
</RigidBody>
{/* ... */}
</Physics>Importeer de RigidBody-component en omsluit de geselecteerde mesh hiermee.
De sphere valt door de grond... waarom is dit het geval?

03
PHYSICS (RAPIER)
RIGIDBODY
Het is noodzakelijk een RigidBody rond de plane te plaatsen om interactie tussen de objecten mogelijk te maken.
<RigidBody>
<Sphere position-x={-2} position-y={2} scale={1} />
</RigidBody>Zet de Ground in een RigidBody en zie wat er gebeurt in de simulatie.
Zie je dat de vloer meebeweegt met de sphere? Dat willen we niet. Geef de RigidBody van de vloer het type ‘fixed’ om dit te fixen.
<RigidBody type="fixed">
<Ground position-y={- 1} rotation-x={- Math.PI * 0.5} scale={10}/>
</RigidBody>
03
PHYSICS (RAPIER)
WHAT HAPPENS?
Het integreren van physics via React Three Rapier is eenvoudig. Houd rekening met de volgende aandachtspunten:
-
Het is niet nodig de physics bij elk frame te updaten.
-
Objecten zijn automatisch geassocieerd met hun respectieve RigidBody.
-
Rapier genereert physics-shapes die overeenkomen met de Three.js-objecten.
-
Specificatie van eigenschappen zoals oppervlak, massa of zwaartekracht is niet vereist.
Let op: HMR pakt niet altijd alle wijzigingen. Ververs de pagina soms, want complexe physics kan kleine glitches veroorzaken.

03
PHYSICS (RAPIER)
DEBUGGING
Wil je de physics beter begrijpen? Zet de debug-property op Physics en zie welke objecten botsen en op welke manier. Let op: gebruik debug alleen tijdelijk, het kan de performance drukken.
<Physics debug>
{/* ... */}
</Physics>Activeer debug op het Physics component

03
PHYSICS (RAPIER)
DEBUGGING
Wil je de physics beter begrijpen? Zet de debug-property op Physics en zie welke objecten botsen en op welke manier. Let op: gebruik debug alleen tijdelijk, het kan de performance drukken.
<Physics debug>
{/* ... */}
</Physics>Activeer debug op het Physics component
VOICE API + PHYSICS
COLLIDERS
04



04
COLLIDERS
COLLIDER
Colliders definiëren de geometrische vorm van een RigidBody. Voor de sphere wordt een cube gebruikt, aangeduid als een ‘cuboid’ collider, wat de standaard is voor alle RigidBodies in Rapier.
Plaats een cuboidCollider rond de box door hem in de RigidBody te zetten.
<RigidBody>
<Box ref={cubeRef} rotation-y={Math.PI * 0.25} position-x={2} position-y={2} scale={1.5}/>
</RigidBody>Verander de schaal van de mesh en kijk of de collider correct meebeweegt.
<RigidBody>
<Box ref={cubeRef} rotation-y={Math.PI * 0.25} position-x={2} position-y={2} scale={3}/>
</RigidBody>
04
COLLIDERS
COLLIDER
Wanneer de cube wordt aangepast naar een rechthoek, zal de standaard cuboidCollider zich automatisch optimaliseren om de mesh-vorm te volgen.
Pas de args van je boxGeometry aan om de box in een rechthoek te veranderen.
<RigidBody>
<mesh castShadow position={ [ 2, 2, 0 ] }>
<boxGeometry args={ [ 3, 2, 1 ] } />
<meshStandardMaterial color="mediumpurple" />
</mesh>
</RigidBody>
04
COLLIDERS
COMPOSED
Een RigidBody kan meerdere colliders bevatten. Bij toevoeging van een tweede mesh genereert de RigidBody automatisch een collider die optimaal overeenkomt met de nieuwe mesh.
Voeg een mesh toe aan de Box component.
<RigidBody>
<mesh {...props}>
<boxGeometry />
<meshStandardMaterial color={color || "royalblue"}/>
</mesh>
<mesh>
<boxGeometry args={[3,2,1]}/>
<meshStandardMaterial color={color || "royalblue"}/>
</mesh>
</RigidBody>
04
COLLIDERS
COMPOSED
Wanneer de tweede mesh wordt verplaatst, beïnvloedt dit het zwaartepunt van het object. De RigidBody verwerkt automatisch de invloed van beide colliders.
Verander de positie van de tweede box.
<RigidBody>
<mesh {...props}>
<boxGeometry />
<meshStandardMaterial color={color || "royalblue"}/>
</mesh>
<mesh position-z={-2}>
<boxGeometry args={[3,2,1]}/>
<meshStandardMaterial color={color || "royalblue"}/>
</mesh>
</RigidBody>
04
COLLIDERS
BALL COLLIDER
De huidige collider van de sphere is niet optimaal. In veel gevallen beïnvloedt dit de simulatie niet, tenzij nauwkeurigheid vereist is of het visueel relevant is. Cuboid-colliders zijn bovendien performanter. Je kan wel de collider van de RigidBody aanpassen.
Verander de collider op de RigidBody naar 'ball'
<RigidBody colliders="ball">
<Sphere position-x={-2} position-y={4} scale={1} />
</RigidBody>
04
COLLIDERS
HULL COLLIDER
Bij een Torus zijn cuboid- of ball-colliders niet geschikt. Een hull-collider volgt de contouren van het object zo nauwkeurig mogelijk, waarbij interne gaten worden genegeerd.
Verander onze box naar een Torus met een Hull Collider
<RigidBody colliders="hull">
<mesh castShadow position={ [ 0, 1, - 0.25 ] } rotation={ [ Math.PI * 0.1, 0, 0 ] }>
<torusGeometry args={ [ 1, 0.5, 16, 32 ] } />
<meshStandardMaterial color="mediumpurple" />
</mesh>
</RigidBody>
04
COLLIDERS
TRIMESH
Wil je perfecte matching met je model? Gebruik dan een Trimesh-collider. Deze volgt alle contouren en maakt een collider die precies past.
Verander de collider naar trimesh
<RigidBody colliders="trimesh">
<mesh castShadow position={ [ -2, 1, - 0.25 ] } rotation={ [ Math.PI * 0.1, 0, 0 ] }>
<torusGeometry args={ [ 1, 0.5, 16, 32 ] } />
<meshStandardMaterial color="mediumpurple" />
</mesh>
</RigidBody>Let op: hull- en trimesh-colliders zijn zwaar en vragen veel berekeningen. Gebruik ze alleen als het echt moet. Ze zijn ook gevoeliger voor glitches bij snelle bewegingen.

04
COLLIDERS
TRIMESH
Wil je perfecte matching met je model? Gebruik dan een Trimesh-collider. Deze volgt alle contouren en maakt een collider die precies past.
Verander de collider naar trimesh
<RigidBody colliders="trimesh">
<mesh castShadow position={ [ -2, 1, - 0.25 ] } rotation={ [ Math.PI * 0.1, 0, 0 ] }>
<torusGeometry args={ [ 1, 0.5, 16, 32 ] } />
<meshStandardMaterial color="mediumpurple" />
</mesh>
</RigidBody>Let op: hull- en trimesh-colliders zijn zwaar en vragen veel berekeningen. Gebruik ze alleen als het echt moet. Ze zijn ook gevoeliger voor glitches bij snelle bewegingen.
VOICE API + PHYSICS
CUSTOM COLLIDERS
05





05
CUSTOM COLLIDERS
CUSTOM
Rapier biedt meerdere standaard-colliders, maar je kunt ook zelf custom colliders bouwen.
De verschillende colliders die beschikbaar zijn:



05
CUSTOM COLLIDERS


CUBOID
We zullen nu custom colliders creëren en testen. Schakel de standaard-colliders op de RigidBody uit door deze op false te zetten en importeer de benodigde collider vanuit Rapier.
Plaats de collider op false en voeg je eigen CuboidCollider toe.
import { CuboidCollider, RigidBody, Physics } from '@react-three/rapier'
<RigidBody colliders={ false }>
<CuboidCollider args={ [ 1, 1, 1 ] } />
{/* ... */}
</RigidBody>Onze kubus is een beetje off. Geen probleem: verander gewoon de grootte, positie en rotatie van de RigidBody.
<RigidBody colliders={false} position-x={2} position-y={2} scale={1.5}>
<CuboidCollider args={[.75,.75,.75]} />
<Box ref={cubeRef} scale={1.5}/>
</RigidBody>


05
CUSTOM COLLIDERS


MULTIPLE
Het is mogelijk om meerdere colliders in één RigidBody te definiëren zonder extra meshes. Dit wordt bereikt door een aanvullende CuboidCollider toe te voegen.
Voeg een tweede CuboidCollider toe aan de RigidBody
<RigidBody colliders={ false } position={ [ 0, 1, - 0.25 ] } rotation={ [ Math.PI * 0.1, 0, 0 ] }>
<CuboidCollider args={ [ 1.5, 1.5, 0.5 ] } />
<CuboidCollider args={ [ 1, 1, 1 ] } />
{/* ... */}
</RigidBody>

05
CUSTOM COLLIDERS

BALL
Je kunt in plaats van een Cuboid een Ball Collider gebruiken. Dit creëert een sphere rond het object en functioneert correct binnen de RigidBody.
Importeer de BallCollider en vervang de 2 CuboidColliders door 1 Ball Collider
<RigidBody colliders={false} position-x={2} position-y={2} scale={1.5}>
<BallCollider args={ [ 1.5 ] } />
{/* ... */}
</RigidBody>Naast de standaard-colliders zijn er nog andere types die je zelf kunt toevoegen. Je kunt kiezen tussen handmatig plaatsen of automatisch laten genereren.
Meer info? Bekijk de docs
VOICE API + PHYSICS
FORCES
06



06
FORCES
FORCES
Onze RigidBodies vallen nu vanzelf, maar we kunnen ze ook beïnvloeden met ‘forces’. In dit voorbeeld laten we een kubus springen als we erop klikken.
We plaatsen onze scene terug naar het begin.
<RigidBody colliders="ball">
<Sphere position-x={-2} position-y={2} scale={1} />
</RigidBody>
<RigidBody>
<Box ref={cubeRef} position-x={2} position-y={2} scale={1.5}/>
</RigidBody>

06
FORCES
FORCES
Om krachten op de kubus te zetten, maken we eerst een reference. Voeg daarnaast een onClick-functie toe die de kubus activeert bij klikken.
Maak een cubeRef aan en plaats deze op de kubus.
import { useRef } from 'react'
export default function Experience()
{
const cubeRef = useRef()
}Voeg de reference op de RigidBody
<RigidBody ref={ cube } position={ [ 1.5, 2, 0 ] }>
{/* ... */}
</RigidBody>

06
FORCES
FORCES
Maak een cubeJump function aan en zend deze naar de cube.
const cube = useRef()
const cubeJump = () =>
{
console.log('jump!')
}Voeg de reference op de RigidBody
<RigidBody ref={ cube } position={ [ 1.5, 2, 0 ] }>
<mesh castShadow onClick={ cubeJump }>
{/* ... */}
</mesh>
</RigidBody>Kijk nu eens wat er in de current reference zit in de console.
const cubeJump = () =>
{
console.log(cube.current)
}

06
FORCES
IMPULSE
Plaats een applyImpulse op de kubus-reference in de cubeJump-functie om de sprong te simuleren.
const cubeJump = () =>
{
cube.current.applyImpulse({ x: 0, y: 10, z: 0 })
}Je kan forces toepassen via addForce of applyImpulse. addForce werkt langzaam, zoals wind. applyImpulse is een korte stoot, perfect voor een sprongetje.


06
FORCES
TORQUE
Voeg een applyTorqueImpulse op de reference in onze cubeJump function.
const cubeJump = () =>
{
cubeRef.current.applyImpulse({ x: 0, y: 10, z: 0 })
cubeRef.current.applyTorqueImpulse({ x: 0, y: 1, z: 0 })
}Onze kubus beweegt nu alleen omhoog. Wil je dat hij ook draait bij een tik? Gebruik addTorque voor continue draaiing of applyTorqueImpulse voor een korte draai-impuls.
Gooi die torque lekker random over alle assen, dan ziet het er realistischer uit.
const cubeJump = () =>
{
cubeRef.current.applyImpulse({ x: 0, y: 15, z: 0 })
cubeRef.current.applyTorqueImpulse({ x: (Math.random() * 5) - 0.5, y: (Math.random() * 5) - 0.5, z: (Math.random() * 5) - 0.5 })
}

06
FORCES
USE THE FORCE
Nu kan je met forces spelen in React Three Rapier. Met deze trucs maak je je scene meteen een stuk leuker en interactiever. May the force be with you!

VOICE API + PHYSICS
OBJECT SETTINGS
07








SETTINGS
Er bestaan diverse manieren om het gedrag van objecten en hun onderlinge interacties te beheersen, zoals wrijving, restitutie, massa, zwaartekracht, posities, bewegingsrichtingen, en andere parameters.
Laten we eens kijken naar een paar belangrijke eigenschappen die je kunt aanpassen:

07
OBJECT SETTINGS
- Gravity
- Restitution
- Friction
- Mass
- Position & rotation







07
OBJECT SETTINGS
GRAVITY
De standaardinstelling voor zwaartekracht is -9.81 om aardse omstandigheden te simuleren. Deze waarde kan worden aangepast via een Vector3-array toegewezen aan de gravity-eigenschap.
Verander de gravity naar die van de maan (-1.62).
<Physics debug gravity={ [ 0, - 1.62, 0 ] }>Klik op de cube en je ziet dat hij hoger springt en trager valt. Experimenteer ook met het omkeren van de zwaartekracht of het toepassen op verschillende assen.
<Physics debug gravity={ [ 0, 1.62, 0 ] }><Physics debug gravity={ [ - 1.62, 0, 0 ] }>






07
OBJECT SETTINGS
GRAVITY SCALE
De gravityScale-eigenschap geeft aan in welke mate de zwaartekracht van toepassing is op een RigidBody. De standaardwaarde is 1.
Verander de gravityScale van de cube naar 0.2
<RigidBody ref={ cube } position={ [ 1.5, 2, 0 ] } gravityScale={ 0.2 }>
{/* ... */}
</RigidBody>Gooi er een negatieve waarde in en je object gaat omhoog in plaats van omlaag.
<RigidBody ref={ cube } position={ [ 1.5, 2, 0 ] } gravityScale={ - 0.2 }>
{/* ... */}
</RigidBody>Deze aanpassing is praktisch in scenario’s waarin verschillende gebieden in de scene verschillende zwaartekrachteffecten vereisen.







07
OBJECT SETTINGS
RESTITUTION
De objecten volgen de zwaartekracht, maar vertonen geen stuiterend gedrag. De restitution-eigenschap regelt de veerkracht; de standaardwaarde is 0.
Zet een restitution van 1 op de cube en zie wat gebeurd.
<RigidBody
ref={ cube }
position={ [ 1.5, 2, 0 ] }
gravityScale={ 1 }
restitution={ 1 }
>
{/* ... */}
</RigidBody>






07
OBJECT SETTINGS
RESTITUTION
De kubus stuitert, maar bereikt niet de startpositie. Voeg een restitution-waarde toe aan de vloer toe om dit te corrigeren. Hard oppervlak vs. zacht oppervlak beïnvloedt de bounce.
Pas de restitution van de floor aan naar 1 en observeer het effect. (Bouncy Castle)
<RigidBody
ref={ cube }
position={ [ 1.5, 2, 0 ] }
gravityScale={ 1 }
restitution={ 1 }
>
{/* ... */}
</RigidBody>
<RigidBody type="fixed" restitution={ 1 }>
{/* ... */}
</RigidBody>






07
OBJECT SETTINGS
FRICTION
De friction-eigenschap bepaalt de weerstand tussen oppervlakken. Een hogere waarde veroorzaakt snellere afremming, terwijl een lagere waarde langere bewegingen mogelijk maakt. Standaard: 0.7.
Vooraleer we dit doen zullen we eerst de resitution terug op 0 plaatsen van de floor.
<RigidBody
ref={ cube }
position={ [ 1.5, 2, 0 ] }
gravityScale={ 1 }
restitution={ 0 }
>
{/* ... */}
</RigidBody>
<RigidBody type="fixed" restitution={ 0 }>
{/* ... */}
</RigidBody>






07
OBJECT SETTINGS
FRICTION
Verander de friction van beide objecten op 0 en kijk wat er gebeurt.
<RigidBody
ref={ cube }
position={ [ 1.5, 2, 0 ] }
gravityScale={ 1 }
restitution={ 0 }
friction={ 0 }
>
{/* ... */}
</RigidBody>
<RigidBody
type="fixed"
restitution={ 0 }
friction={ 0 }
>
{/* ... */}
</RigidBody>Klik op het object om te springen en observeer het effect. Cool!
Pas daarna de friction aan naar 0.7 of verwijder deze.







07
OBJECT SETTINGS
MASS
<RigidBody
ref={ cube }
position={ [ 1.5, 2, 0 ] }
gravityScale={ 1 }
restitution={ 0 }
friction={ 0.7 }
colliders={ false }
position-x={2}
position-y={2}
>
{/* ... */}
<CuboidCollider args={ [ 1, 1, 1 ] } />
<Box onClick={cubeJump} scale={2}/>
</RigidBody>De massa van een RigidBody wordt bepaald door de geometrie en het volume van het object. Grotere massa verhoogt de impact bij botsingen, maar verandert de valversnelling niet.
Voor het aanpassen van de massa dient het object te zijn voorzien van een collider. Hier gebruiken we een CuboidCollider.







07
OBJECT SETTINGS
MASS
<CuboidCollider mass={ 0.5 } args={ [ 0.5, 0.5, 0.5 ] } />De massa kan worden aangepast via het mass-attribuut van de betreffende collider.
Pas de mass aan naar 0.5 en klik eens op het object.
<CuboidCollider mass={ 2 } args={ [ 0.5, 0.5, 0.5 ] } />Pas de mass aan naar 2 en klik eens op het object.







07
OBJECT SETTINGS
ACCESS MASS
const cubeJump = () =>
{
const mass = cube.current.mass()
console.log(mass)
}Omdat onze massa nu hoger is, is de kracht van de toegepaste impulse niet sterk genoeg. Voor leerdoeleinden willen we de kracht van de impulse aanpassen, zodat het object ongeacht de massa even hoog springt.
Hiervoor moeten we de massa uit onze reference halen.
In onze cubeJump-functie halen we de massa uit de reference.
const cubeJump = () =>
{
const mass = cube.current.mass()
cube.current.applyImpulse({ x: 0, y: 10 * mass, z: 0 })
}Vermenigvuldig de impulse met de massa om de kracht aan te passen







07
OBJECT SETTINGS
POSITION & ROTATION
Op onze RigidBody kunnen we ook de positie en rotatie aanpassen, zoals we eerder hebben gezien.
Let op: bij dynamische en fixed objecten mag je deze waarden tijdens runtime niet veranderen, omdat dit ongewenst gedrag kan veroorzaken.
Als je tijdens runtime updates nodig hebt, gebruik dan kinematic types.
VOICE API + PHYSICS
TYPES
08



KINEMATIC
We hebben het fixed type gezien voor de ground en het dynamic type voor objecten zoals de Ball en Cube. Er is echter nog een derde type: kinematic. Dit type gebruik je vooral als je iets tijdens runtime wilt bewegen of roteren.
Creëer een lange box die we gaan gebruiken als een draaiende balk in de scene.

08
TYPES
<Physics debug gravity={ [ 0, - 9.81, 0 ] }>
{/* ... */}
<RigidBody
position={ [ 0, - 0.8, 0 ] }
friction={ 0 }
type="kinematicPosition"
>
<mesh castShadow scale={ [ 0.4, 0.4, 4 ] }>
<boxGeometry />
<meshStandardMaterial color="red" />
</mesh>
</RigidBody>
</Physics>

08
TYPES
KINEMATIC
We gaan de positie handmatig aanpassen, dus hebben we een reference naar ons object nodig.
Creëer een reference en voeg deze toe aan de RigidBody van de draaiende balk.
export default function Experience()
{
const spinnerRef = useRef()
<RigidBody
ref={ spinnerRef }
position={ [ 0, - 0.8, 0 ] }
friction={ 0 }
type="kinematicPosition"
>
{/* ... */}
</RigidBody>
}

08
TYPES
KINEMATIC
We zullen onze balk elke seconde aanpassen. Hier moet meteen een belletje rinkelen: dit is een perfect moment om useFrame te gebruiken.
Importeer useFrame en haal de state ervan op.
import { useFrame } from '@react-three/fiber'
export default function Experience()
{
useFrame((state) =>
{
})
}We halen onze tijd op via de clock om onze balk te laten draaien a.d.h.v de tijd.
const time = state.clock.getElapsedTime()
console.log("Time:", time)

08
TYPES
KINEMATIC
Voor het aanpassen van rotatie en positie van een kinematic object dienen de methodes setNextKinematicRotation en setNextKinematicTranslation te worden gebruikt. Omdat rotatie in quaternion-vorm vereist is, wordt een Euler-conversie uitgevoerd.
Importeer THREE en plaats volgende code in de useFrame
import * as THREE from 'three'
useFrame((state) =>
{
const time = state.clock.getElapsedTime()
const eulerRotation = new THREE.Euler(0, time, 0)
const quaternionRotation = new THREE.Quaternion()
quaternionRotation.setFromEuler(eulerRotation)
})

08
TYPES
KINEMATIC
Zet de Kinematic Rotation van de ref op de rotatie die we hebben klaargemaakt.
useFrame((state) =>
{
const time = state.clock.getElapsedTime()
const eulerRotation = new THREE.Euler(0, time, 0)
const quaternionRotation = new THREE.Quaternion()
quaternionRotation.setFromEuler(eulerRotation)
twister.current.setNextKinematicRotation(quaternionRotation)
})De balk draait nu ... wil je deze sneller? Vermenigvuldig dan de tijd met een groter getal.
useFrame((state) =>
{
const time = state.clock.getElapsedTime()
const eulerRotation = new THREE.Euler(0, time * 4, 0)
})

08
TYPES
KINEMATIC
Maak een angle aan a.d.h.v de tijd en gebruik deze om de cos en sin te berekenen.
useFrame((state) =>
{
const angle = time * 0.5
const x = Math.cos(angle)
const z = Math.sin(angle)
})Nu gaan we de positie aanpassen zodat onze balk rond de vloer in een cirkel draait. Hiervoor gebruiken we functies zoals cos voor de x-as en sin voor de z-as.
Nu kan je makkelijk de translation aanpassen door deze aan de x en z mee te geven.
useFrame((state) =>
{
// ...
twister.current.setNextKinematicTranslation({ x: x, y: - 0.8, z: z })
})Wil je een grotere cirkel? Vermenigvuldig gewoon een groter getal bij de cos en sin.
VOICE API + PHYSICS
EVENTS
09




EVENTS
We kunnen luisteren naar bepaalde events op onze RigidBodies, wat het interessant maakt om reacties te programmeren op basis van deze events.
De events die je kunt gebruiken:
-
onCollisionEnter– wordt getriggerd bij aanraking. -
onCollisionExit– wordt getriggerd bij het verlaten van contact. -
onSleep– start als de RigidBody een tijd geen contact heeft. -
onWake– start wanneer de RigidBody weer actief contact maakt.

09
EVENTS
Laten we elk event stap voor stap onderzoeken om te begrijpen hoe ze werken.



09
EVENTS
ENTER
We zullen ervoor zorgen dat er een klein geluidje wordt afgespeeld wanneer een collision plaatsvindt met een bepaald object.
Implementeer een collisionEnter-functie in de Experience en wijs deze toe aan het onCollisionEnter-attribuut van de betreffende RigidBody.
export default function Experience()
{
const collisionEnter = () =>
{
console.log('collision!')
}
<RigidBody
ref={ cube }
position={ [ 1.5, 2, 0 ] }
gravityScale={ 1 }
restitution={ 0 }
friction={ 0.7 }
colliders={ false }
onCollisionEnter={ collisionEnter }
>


09
EVENTS
ENTER
Om onze sound af te spelen, moeten we deze natuurlijk eerst aanmaken.
import { useState, useRef } from 'react'
export default function Experience()
{
const [ hitSound ] = useState(() => new Audio('./oof.mp3'))
// ...
}Gebruik nu deze audio file in onze collisionEnter function.
const collisionEnter = () =>
{
hitSound.currentTime = 0.5
hitSound.volume = Math.random()
hitSound.play()
}Ga terug naar je scene en luister. Hoor je niets? Klik dan eerst eens op je scene.



09
EVENTS
EXIT
Implementeer een console.log in de onCollisionExit-handler om de gebeurtenis te registreren.
<RigidBody
ref={ cube }
position={ [ 1.5, 2, 0 ] }
gravityScale={ 1 }
restitution={ 0 }
friction={ 0.7 }
colliders={ false }
onCollisionEnter={ collisionEnter }
onCollisionExit={ () => { console.log('exit') } }
>
{/* ... */}
</RigidBody>onCollisionExit wordt getriggerd wanneer een object geen contact meer heeft met het betreffende object.



09
EVENTS
ONSLEEP/WAKE
Geef nu een console mee op de sleep en wake function en zie wat er gebeurd in de console.
<RigidBody
ref={ cube }
position={ [ 1.5, 2, 0 ] }
gravityScale={ 1 }
restitution={ 0 }
friction={ 0.7 }
colliders={ false }
onCollisionEnter={ collisionEnter }
onCollisionExit={ () => { console.log('exit') } }
onSleep={ () => { console.log('sleep') } }
onWake={ () => { console.log('wake') } }
>
{/* ... */}
</RigidBody>Objecten die voor een bepaalde periode in rust zijn, worden in een sleep-modus geplaatst. Ze blijven in deze toestand totdat een kracht wordt uitgeoefend, waarna ze de sleep-modus verlaten (wake).



09
EVENTS
CONCLUSION
ENTER

EXIT
SLEEP/WAKE


WEB6/5 - Voice API + Physics
By Niels Minne
WEB6/5 - Voice API + Physics
- 89