init
This commit is contained in:
304
.opencode/skills/threejs/references/14-physics-vr.md
Normal file
304
.opencode/skills/threejs/references/14-physics-vr.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# Physics & VR/XR
|
||||
|
||||
Integrate physics simulations and virtual reality.
|
||||
|
||||
## Physics Integration
|
||||
|
||||
Three.js doesn't include physics - use external libraries:
|
||||
|
||||
### Rapier Physics (Recommended)
|
||||
|
||||
Rust-based, high-performance:
|
||||
|
||||
```javascript
|
||||
import { RapierPhysics } from 'three/addons/physics/RapierPhysics.js';
|
||||
|
||||
// Initialize
|
||||
const physics = await RapierPhysics();
|
||||
|
||||
// Create physics body
|
||||
const box = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(),
|
||||
new THREE.MeshStandardMaterial()
|
||||
);
|
||||
scene.add(box);
|
||||
|
||||
// Add physics (mass > 0 = dynamic)
|
||||
physics.addMesh(box, 1); // mass = 1
|
||||
|
||||
// Static ground
|
||||
const ground = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(10, 0.5, 10),
|
||||
new THREE.MeshStandardMaterial()
|
||||
);
|
||||
ground.position.y = -2;
|
||||
scene.add(ground);
|
||||
physics.addMesh(ground); // no mass = static
|
||||
|
||||
// Update in animation loop
|
||||
function animate() {
|
||||
physics.step();
|
||||
renderer.render(scene, camera);
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
```
|
||||
|
||||
### Ammo Physics
|
||||
|
||||
Port of Bullet physics engine:
|
||||
|
||||
```javascript
|
||||
import { AmmoPhysics } from 'three/addons/physics/AmmoPhysics.js';
|
||||
|
||||
const physics = await AmmoPhysics();
|
||||
|
||||
// Same API as Rapier
|
||||
physics.addMesh(mesh, mass);
|
||||
|
||||
function animate() {
|
||||
physics.step();
|
||||
renderer.render(scene, camera);
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
```
|
||||
|
||||
### Jolt Physics
|
||||
|
||||
High-performance alternative:
|
||||
|
||||
```javascript
|
||||
import { JoltPhysics } from 'three/addons/physics/JoltPhysics.js';
|
||||
|
||||
const physics = await JoltPhysics();
|
||||
physics.addMesh(mesh, mass);
|
||||
```
|
||||
|
||||
### Physics Constraints
|
||||
|
||||
```javascript
|
||||
// After initialization
|
||||
const physics = await RapierPhysics();
|
||||
|
||||
// Point-to-point constraint
|
||||
physics.addConstraint(meshA, meshB, 'fixed');
|
||||
physics.addConstraint(meshA, meshB, 'spring', { stiffness: 100 });
|
||||
|
||||
// Remove constraint
|
||||
physics.removeConstraint(constraint);
|
||||
```
|
||||
|
||||
## VR/XR Setup
|
||||
|
||||
### Basic WebXR
|
||||
|
||||
```javascript
|
||||
import { VRButton } from 'three/addons/webxr/VRButton.js';
|
||||
|
||||
// Enable XR
|
||||
renderer.xr.enabled = true;
|
||||
|
||||
// Add VR button to page
|
||||
document.body.appendChild(VRButton.createButton(renderer));
|
||||
|
||||
// Animation loop for VR
|
||||
renderer.setAnimationLoop(() => {
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
// Stop using requestAnimationFrame, use setAnimationLoop instead
|
||||
```
|
||||
|
||||
### AR Mode
|
||||
|
||||
```javascript
|
||||
import { ARButton } from 'three/addons/webxr/ARButton.js';
|
||||
|
||||
renderer.xr.enabled = true;
|
||||
document.body.appendChild(ARButton.createButton(renderer));
|
||||
|
||||
// AR-specific features
|
||||
const session = renderer.xr.getSession();
|
||||
session.requestHitTestSource({ space: viewerSpace }).then((hitTestSource) => {
|
||||
// Use hit testing for placing objects
|
||||
});
|
||||
```
|
||||
|
||||
### VR Controllers
|
||||
|
||||
```javascript
|
||||
// Get controllers
|
||||
const controller1 = renderer.xr.getController(0);
|
||||
const controller2 = renderer.xr.getController(1);
|
||||
|
||||
scene.add(controller1);
|
||||
scene.add(controller2);
|
||||
|
||||
// Controller events
|
||||
controller1.addEventListener('selectstart', () => {
|
||||
console.log('Trigger pressed');
|
||||
});
|
||||
|
||||
controller1.addEventListener('selectend', () => {
|
||||
console.log('Trigger released');
|
||||
});
|
||||
|
||||
// Add visual controller models
|
||||
import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js';
|
||||
|
||||
const controllerModelFactory = new XRControllerModelFactory();
|
||||
|
||||
const grip1 = renderer.xr.getControllerGrip(0);
|
||||
grip1.add(controllerModelFactory.createControllerModel(grip1));
|
||||
scene.add(grip1);
|
||||
|
||||
const grip2 = renderer.xr.getControllerGrip(1);
|
||||
grip2.add(controllerModelFactory.createControllerModel(grip2));
|
||||
scene.add(grip2);
|
||||
```
|
||||
|
||||
### Hand Tracking
|
||||
|
||||
```javascript
|
||||
import { OculusHandModel } from 'three/addons/webxr/OculusHandModel.js';
|
||||
|
||||
const hand1 = renderer.xr.getHand(0);
|
||||
const handModel1 = new OculusHandModel(hand1);
|
||||
hand1.add(handModel1);
|
||||
scene.add(hand1);
|
||||
|
||||
const hand2 = renderer.xr.getHand(1);
|
||||
const handModel2 = new OculusHandModel(hand2);
|
||||
hand2.add(handModel2);
|
||||
scene.add(hand2);
|
||||
```
|
||||
|
||||
### Teleportation
|
||||
|
||||
```javascript
|
||||
const raycaster = new THREE.Raycaster();
|
||||
const tempMatrix = new THREE.Matrix4();
|
||||
|
||||
function handleController(controller) {
|
||||
const intersections = getIntersections(controller);
|
||||
|
||||
if (intersections.length > 0) {
|
||||
const intersection = intersections[0];
|
||||
|
||||
// Teleport on button release
|
||||
controller.addEventListener('selectend', () => {
|
||||
const offset = intersection.point.y;
|
||||
camera.position.y += offset;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getIntersections(controller) {
|
||||
tempMatrix.identity().extractRotation(controller.matrixWorld);
|
||||
raycaster.ray.origin.setFromMatrixPosition(controller.matrixWorld);
|
||||
raycaster.ray.direction.set(0, 0, -1).applyMatrix4(tempMatrix);
|
||||
return raycaster.intersectObjects(scene.children, true);
|
||||
}
|
||||
```
|
||||
|
||||
### Spatial Audio for VR
|
||||
|
||||
```javascript
|
||||
const listener = new THREE.AudioListener();
|
||||
camera.add(listener);
|
||||
|
||||
const sound = new THREE.PositionalAudio(listener);
|
||||
const audioLoader = new THREE.AudioLoader();
|
||||
|
||||
audioLoader.load('sound.ogg', (buffer) => {
|
||||
sound.setBuffer(buffer);
|
||||
sound.setRefDistance(1);
|
||||
sound.setLoop(true);
|
||||
sound.play();
|
||||
});
|
||||
|
||||
// Attach to object
|
||||
object.add(sound);
|
||||
|
||||
// Update listener in VR
|
||||
renderer.setAnimationLoop(() => {
|
||||
// Listener automatically updates with camera in VR
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
```
|
||||
|
||||
### Room-Scale VR
|
||||
|
||||
```javascript
|
||||
// Request room-scale experience
|
||||
navigator.xr.requestSession('immersive-vr', {
|
||||
requiredFeatures: ['local-floor']
|
||||
}).then((session) => {
|
||||
// Session setup
|
||||
});
|
||||
|
||||
// Get play area bounds
|
||||
session.requestReferenceSpace('bounded-floor').then((space) => {
|
||||
const bounds = space.boundsGeometry;
|
||||
// Create visual boundary
|
||||
});
|
||||
```
|
||||
|
||||
### Performance Tips for VR
|
||||
|
||||
- Target 90 FPS (11.1ms per frame)
|
||||
- Use lower polygon counts
|
||||
- Reduce shadow quality
|
||||
- Limit post-processing
|
||||
- Use instancing for repeated objects
|
||||
- Enable foveated rendering if available
|
||||
- Test on actual VR hardware
|
||||
|
||||
```javascript
|
||||
// Foveated rendering (Quest 2+)
|
||||
const gl = renderer.getContext();
|
||||
const ext = gl.getExtension('WEBGL_foveated_rendering');
|
||||
if (ext) {
|
||||
ext.foveatedRenderingModeWEBGL(gl.FOVEATED_RENDERING_MODE_ENABLE_WEBGL);
|
||||
}
|
||||
```
|
||||
|
||||
## Mixed Reality (MR)
|
||||
|
||||
```javascript
|
||||
import { XRButton } from 'three/addons/webxr/XRButton.js';
|
||||
|
||||
// Request MR features
|
||||
document.body.appendChild(
|
||||
XRButton.createButton(renderer, {
|
||||
requiredFeatures: ['hand-tracking', 'layers'],
|
||||
optionalFeatures: ['local-floor', 'bounded-floor']
|
||||
})
|
||||
);
|
||||
|
||||
// Passthrough mode (Quest Pro, etc.)
|
||||
const session = renderer.xr.getSession();
|
||||
const baseLayer = session.renderState.baseLayer;
|
||||
baseLayer.compositionDisabled = true; // enable passthrough
|
||||
```
|
||||
|
||||
## Common VR Patterns
|
||||
|
||||
```javascript
|
||||
// Detect if in VR
|
||||
if (renderer.xr.isPresenting) {
|
||||
// In VR mode
|
||||
}
|
||||
|
||||
// Get VR camera (for raycasting)
|
||||
const vrCamera = renderer.xr.getCamera(camera);
|
||||
|
||||
// Different behavior for VR vs desktop
|
||||
renderer.setAnimationLoop(() => {
|
||||
if (renderer.xr.isPresenting) {
|
||||
// VR rendering logic
|
||||
} else {
|
||||
// Desktop rendering logic
|
||||
}
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user