handible
Version:
Revolutionary hand tracking and gesture control for the web. Transform any webcam into a powerful 3D controller with MediaPipe and Three.js.
344 lines (295 loc) • 11.6 kB
JavaScript
// Enhanced threeSetup.js with impressive visuals
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { setSceneObjects } from "./sceneManager.js";
import { handConfig } from "./handTracking.js";
export function setupThreeScene() {
let scene, camera, renderer, controls;
scene = new THREE.Scene();
// Enhanced background with gradient
const gradientTexture = createGradientTexture();
scene.background = gradientTexture;
// Add atmospheric fog for depth
scene.fog = new THREE.Fog(0x87ceeb, 5, 15);
// Enhanced floor with reflection-like material
const floorGeometry = new THREE.PlaneGeometry(15, 15);
const floorMaterial = new THREE.MeshStandardMaterial({
color: 0x2c3e50,
roughness: 0.1,
metalness: 0.8,
envMapIntensity: 1.0
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.position.y = -1.5;
floor.receiveShadow = true;
scene.add(floor);
// Add subtle grid pattern to floor
const gridHelper = new THREE.GridHelper(15, 30, 0x34495e, 0x34495e);
gridHelper.position.y = -1.49; // Slightly above floor
gridHelper.material.opacity = 0.3;
gridHelper.material.transparent = true;
scene.add(gridHelper);
// Enhanced whiteboard with modern glass-like material
const wallGeometry = new THREE.PlaneGeometry(5, 3);
const wallMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
roughness: 0.05,
metalness: 0.1,
clearcoat: 1.0,
clearcoatRoughness: 0.1,
transmission: 0.1,
thickness: 0.5,
ior: 1.5
});
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
wall.position.z = -1;
wall.position.y = 0;
wall.rotation.x = -Math.PI / 12;
wall.receiveShadow = true;
wall.castShadow = true;
wall.userData.isWall = true;
scene.add(wall);
// Add glowing frame around whiteboard
const frameGeometry = new THREE.RingGeometry(2.4, 2.6, 32);
const frameMaterial = new THREE.MeshStandardMaterial({
color: 0x3498db,
emissive: 0x1a5490,
emissiveIntensity: 0.3,
roughness: 0.2,
metalness: 0.8
});
const frame = new THREE.Mesh(frameGeometry, frameMaterial);
frame.position.set(0, 0, 0.02);
frame.rotation.x = -Math.PI / 12;
scene.add(frame);
// Enhanced 3D buttons with glowing effects
const buttonPositions = [
{ x: -1, y: 0.5, color: 0xe74c3c, emissive: 0x8b2626 },
{ x: 0, y: 0.5, color: 0x2ecc71, emissive: 0x1a7041 },
{ x: 1, y: 0.5, color: 0x3498db, emissive: 0x1a5490 }
];
buttonPositions.forEach(pos => {
const buttonGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.1, 32);
const buttonMaterial = new THREE.MeshStandardMaterial({
color: pos.color,
emissive: pos.emissive,
emissiveIntensity: 0.2,
roughness: 0.3,
metalness: 0.7
});
const button = new THREE.Mesh(buttonGeometry, buttonMaterial);
button.position.set(pos.x, pos.y, 0.05);
button.rotation.x = Math.PI / 2;
wall.add(button);
// Add glowing ring around button
const ringGeometry = new THREE.RingGeometry(0.22, 0.25, 32);
const ringMaterial = new THREE.MeshBasicMaterial({
color: pos.color,
transparent: true,
opacity: 0.4
});
const ring = new THREE.Mesh(ringGeometry, ringMaterial);
ring.position.set(pos.x, pos.y, 0.06);
wall.add(ring);
button.userData.isButton = true;
button.userData.defaultColor = pos.color;
button.userData.hoverColor = 0xffd700;
button.userData.activeColor = 0xff6b35;
button.userData.defaultPosition = button.position.clone();
button.userData.glowRing = ring;
button.castShadow = true;
});
// Enhanced 3D slider with modern design
const sliderTrackGeometry = new THREE.BoxGeometry(3, 0.1, 0.1);
const sliderTrackMaterial = new THREE.MeshStandardMaterial({
color: 0x34495e,
roughness: 0.3,
metalness: 0.8,
emissive: 0x1a252f,
emissiveIntensity: 0.1
});
const sliderTrack = new THREE.Mesh(sliderTrackGeometry, sliderTrackMaterial);
sliderTrack.position.set(0, -0.5, 0.05);
sliderTrack.castShadow = true;
wall.add(sliderTrack);
// Enhanced knob with glow effect
const knobGeometry = new THREE.SphereGeometry(0.15, 32, 32);
const knobMaterial = new THREE.MeshStandardMaterial({
color: 0xecf0f1,
roughness: 0.2,
metalness: 0.9,
emissive: 0x95a5a6,
emissiveIntensity: 0.1
});
const knob = new THREE.Mesh(knobGeometry, knobMaterial);
knob.position.set(0, -0.5, 0.05 + 0.05);
// Add inner glow sphere
const innerGlowGeometry = new THREE.SphereGeometry(0.12, 16, 16);
const innerGlowMaterial = new THREE.MeshBasicMaterial({
color: 0x3498db,
transparent: true,
opacity: 0.3
});
const innerGlow = new THREE.Mesh(innerGlowGeometry, innerGlowMaterial);
knob.add(innerGlow);
knob.userData.isKnob = true;
knob.userData.defaultColor = 0xecf0f1;
knob.userData.hoverColor = 0xffd700;
knob.userData.activeColor = 0xff6b35;
knob.userData.innerGlow = innerGlow;
knob.castShadow = true;
wall.add(knob);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0.0, 1);
const canvas = document.getElementById("threeCanvas");
renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputColorSpace = THREE.SRGBColorSpace;
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.enableZoom = true;
controls.minDistance = 1;
controls.maxDistance = 10;
controls.target.set(0, 0, -1);
// Enhanced lighting setup for dramatic effect
const ambientLight = new THREE.AmbientLight(0x404040, 0.3);
scene.add(ambientLight);
// Key light with color temperature
const keyLight = new THREE.DirectionalLight(0xffffff, 1.5);
keyLight.position.set(5, 10, 5);
keyLight.castShadow = true;
keyLight.shadow.mapSize.width = 2048;
keyLight.shadow.mapSize.height = 2048;
keyLight.shadow.camera.near = 0.5;
keyLight.shadow.camera.far = 50;
keyLight.shadow.camera.left = -10;
keyLight.shadow.camera.right = 10;
keyLight.shadow.camera.top = 10;
keyLight.shadow.camera.bottom = -10;
keyLight.shadow.bias = -0.0001;
scene.add(keyLight);
// Fill light with warm color
const fillLight = new THREE.DirectionalLight(0xffa726, 0.4);
fillLight.position.set(-5, 5, 3);
scene.add(fillLight);
// Accent light with cool color
const accentLight = new THREE.PointLight(0x29b6f6, 0.6, 15);
accentLight.position.set(2, 3, 2);
accentLight.castShadow = true;
scene.add(accentLight);
// Add rim light for edge definition
const rimLight = new THREE.SpotLight(0xffffff, 0.8, 10, Math.PI / 4, 0.5, 2);
rimLight.position.set(-3, 4, 3);
rimLight.target.position.set(0, 0, -1);
scene.add(rimLight);
scene.add(rimLight.target);
// Add floating particles for ambiance
createFloatingParticles(scene);
// Add environment reflection probe
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
scene.add(cubeCamera);
// Update materials to use environment map
const envMap = cubeRenderTarget.texture;
[floorMaterial, wallMaterial].forEach(material => {
if (material.envMap !== undefined) {
material.envMap = envMap;
material.needsUpdate = true;
}
});
const objectsGroup = new THREE.Group();
scene.add(objectsGroup);
// Set whiteboard-specific offsets (keep interaction logic intact)
handConfig.xScale = 2;
handConfig.yScale = -2;
handConfig.zMagnification = 2;
handConfig.zOffset = 0;
handConfig.rotationOffset.set(0, 0, 0);
setSceneObjects({ scene, camera, renderer, controls });
// Animation loop for dynamic effects
animate();
function animate() {
requestAnimationFrame(animate);
// Animate particles
animateParticles(scene);
// Update environment map occasionally
if (Math.random() < 0.005) {
cubeCamera.update(renderer, scene);
}
}
}
// Helper function to create gradient background texture
function createGradientTexture() {
const size = 512;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
const gradient = context.createLinearGradient(0, 0, 0, size);
gradient.addColorStop(0, '#1e3c72'); // Deep blue
gradient.addColorStop(0.5, '#2a5298'); // Mid blue
gradient.addColorStop(1, '#87ceeb'); // Sky blue
context.fillStyle = gradient;
context.fillRect(0, 0, size, size);
const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
return texture;
}
// Helper function to create floating particles
function createFloatingParticles(scene) {
const particleCount = 50;
const positions = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 20; // x
positions[i * 3 + 1] = Math.random() * 10 - 2; // y
positions[i * 3 + 2] = (Math.random() - 0.5) * 20; // z
}
const particleGeometry = new THREE.BufferGeometry();
particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const particleMaterial = new THREE.PointsMaterial({
color: 0x74b9ff,
size: 0.02,
transparent: true,
opacity: 0.6,
blending: THREE.AdditiveBlending
});
const particles = new THREE.Points(particleGeometry, particleMaterial);
particles.userData.originalPositions = positions.slice();
particles.userData.velocities = new Float32Array(particleCount * 3);
// Initialize random velocities
for (let i = 0; i < particleCount; i++) {
particles.userData.velocities[i * 3] = (Math.random() - 0.5) * 0.002;
particles.userData.velocities[i * 3 + 1] = Math.random() * 0.001 + 0.0005;
particles.userData.velocities[i * 3 + 2] = (Math.random() - 0.5) * 0.002;
}
scene.add(particles);
}
// Helper function to animate particles
function animateParticles(scene) {
scene.children.forEach(child => {
if (child instanceof THREE.Points) {
const positions = child.geometry.attributes.position.array;
const velocities = child.userData.velocities;
for (let i = 0; i < positions.length; i += 3) {
positions[i] += velocities[i]; // x
positions[i + 1] += velocities[i + 1]; // y
positions[i + 2] += velocities[i + 2]; // z
// Reset particles that go too high
if (positions[i + 1] > 8) {
positions[i + 1] = -2;
positions[i] = (Math.random() - 0.5) * 20;
positions[i + 2] = (Math.random() - 0.5) * 20;
}
}
child.geometry.attributes.position.needsUpdate = true;
}
});
}