WEBLEB
Startseite
Editor
Anmelden
Pro
Deutsch
English
Français
Español
Português
Deutsch
Italiano
हिंदी
HTML
CSS
JS
3D Ragdoll Simulation
Spawn Ragdoll
Reset Scene
Ragdolls:
0
Right-click + drag: Orbit camera
Scroll: Zoom
L key (no mouse):
Grab/drag part at center of view
body { margin: 0; overflow: hidden; background: #222; } #controls { position: absolute; top: 14px; left: 14px; z-index: 10; background: rgba(0,0,0,0.75); color: #fff; padding: 16px 24px; border-radius: 12px; font-family: 'Segoe UI', Arial, sans-serif; box-shadow: 0 2px 16px #000a; user-select: none; } button { font-size: 1em; margin-right: 8px; margin-bottom: 6px; padding: 8px 16px; border: none; border-radius: 5px; background: #4fc3f7; color: #222; cursor: pointer; font-weight: bold; transition: background 0.2s; } button:hover { background: #0288d1; color: #fff; } #gameCanvas { display: block; width: 100vw; height: 100vh; outline: none; background: #87ceeb; }
let scene, camera, renderer, world, controls; let ragdolls = []; let pickedBody = null, pickedConstraint = null; let isGrabbing = false; const ragdollCountSpan = document.getElementById('ragdollCount'); init(); animate(); // --- "L" is the only grab key --- window.addEventListener('keydown', (e) => { if ((e.key === 'l' || e.key === 'L') && !isGrabbing) { tryGrabCenter(); } }); window.addEventListener('keyup', (e) => { if (e.key === 'l' || e.key === 'L') { releaseGrab(); } }); function tryGrabCenter() { // Raycast from center of the screen let raycaster = new THREE.Raycaster(); raycaster.setFromCamera({x:0, y:0}, camera); // center let intersects = []; ragdolls.forEach(rd => rd.parts.forEach(mesh => { let int = raycaster.intersectObject(mesh); if (int.length) intersects.push({mesh, point: int[0].point}); }) ); if (intersects.length === 0) return; // Find closest intersect let closest = intersects.reduce((a, b) => (a.point.distanceTo(camera.position) < b.point.distanceTo(camera.position)) ? a : b ); // Find corresponding CANNON body for (let rd of ragdolls) { let idx = rd.parts.indexOf(closest.mesh); if (idx !== -1) { pickedBody = rd.bodies[idx]; let pivot = pickedBody.pointToLocalFrame(new CANNON.Vec3(closest.point.x, closest.point.y, closest.point.z)); pickedConstraint = new CANNON.PointToPointConstraint( pickedBody, pivot, null, new CANNON.Vec3()); world.addConstraint(pickedConstraint); isGrabbing = true; break; } } } function releaseGrab() { if (pickedConstraint) world.removeConstraint(pickedConstraint); pickedConstraint = null; pickedBody = null; isGrabbing = false; } function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x87ceeb); camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 7, 20); renderer = new THREE.WebGLRenderer({canvas: document.getElementById('gameCanvas'), antialias:true}); renderer.setSize(window.innerWidth, window.innerHeight); controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.12; controls.target.set(0, 3, 0); world = new CANNON.World(); world.gravity.set(0, -20, 0); world.broadphase = new CANNON.NaiveBroadphase(); addRoom(); scene.add(new THREE.AmbientLight(0xffffff, 0.7)); let dir = new THREE.DirectionalLight(0xffffff, 0.8); dir.position.set(5, 15, 8); scene.add(dir); window.addEventListener('resize', onWindowResize); document.getElementById('spawnBtn').onclick = spawnRagdoll; document.getElementById('resetBtn').onclick = resetScene; renderer.domElement.addEventListener('contextmenu', e => e.preventDefault()); spawnRagdoll(); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function addRoom() { let floorGeo = new THREE.BoxGeometry(20,1,20); let floorMat = new THREE.MeshStandardMaterial({color:0x888888}); let floorMesh = new THREE.Mesh(floorGeo, floorMat); floorMesh.position.y = -0.5; scene.add(floorMesh); let floorPhys = new CANNON.Body({mass:0}); floorPhys.addShape(new CANNON.Box(new CANNON.Vec3(10,0.5,10))); floorPhys.position.y = -0.5; world.addBody(floorPhys); const wallColor = 0xcccccc; const wallMat = new THREE.MeshStandardMaterial({color:wallColor, transparent:true, opacity:0.33}); addWall(0,4,-10, 20,8,1, wallMat); addWall(-10,4,0, 1,8,20, wallMat); addWall(10,4,0, 1,8,20, wallMat); } function addWall(x,y,z, sx,sy,sz, mat) { let mesh = new THREE.Mesh(new THREE.BoxGeometry(sx,sy,sz), mat); mesh.position.set(x,y,z); scene.add(mesh); let body = new CANNON.Body({mass:0}); body.addShape(new CANNON.Box(new CANNON.Vec3(sx/2,sy/2,sz/2))); body.position.set(x,y,z); world.addBody(body); } function spawnRagdoll() { let y = 6; let x = (Math.random()-0.5)*4; let color = Math.random()*0xffffff; let parts = []; let bodies = []; let constraints = []; let headMesh = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 16), new THREE.MeshStandardMaterial({color})); scene.add(headMesh); let headBody = new CANNON.Body({mass:0.6}); headBody.addShape(new CANNON.Sphere(0.4)); headBody.position.set(x, y, 0); world.addBody(headBody); parts.push(headMesh); bodies.push(headBody); let torsoMesh = new THREE.Mesh(new THREE.BoxGeometry(0.7,1.2,0.4), new THREE.MeshStandardMaterial({color})); scene.add(torsoMesh); let torsoBody = new CANNON.Body({mass:1.2}); torsoBody.addShape(new CANNON.Box(new CANNON.Vec3(0.35,0.6,0.2))); torsoBody.position.set(x, y-1, 0); world.addBody(torsoBody); parts.push(torsoMesh); bodies.push(torsoBody); let armLen = 0.7, armRad = 0.13; let positions = [[-0.6, y-0.5, 0], [0.6, y-0.5, 0], [-0.6, y-1.2, 0], [0.6, y-1.2, 0]]; for(let i=0;i<4;i++){ let armMesh = new THREE.Mesh(new THREE.CylinderGeometry(armRad,armRad,armLen,12), new THREE.MeshStandardMaterial({color})); scene.add(armMesh); let armBody = new CANNON.Body({mass:0.5}); armBody.addShape(new CANNON.Cylinder(armRad,armRad,armLen,12)); armBody.position.set(x+positions[i][0], positions[i][1], positions[i][2]); world.addBody(armBody); parts.push(armMesh); bodies.push(armBody); } let legLen = 0.8, legRad = 0.15; let legPos = [[-0.25, y-2, 0], [0.25, y-2, 0], [-0.25, y-2.8, 0], [0.25, y-2.8, 0]]; for(let i=0;i<4;i++){ let legMesh = new THREE.Mesh(new THREE.CylinderGeometry(legRad,legRad,legLen,12), new THREE.MeshStandardMaterial({color})); scene.add(legMesh); let legBody = new CANNON.Body({mass:0.7}); legBody.addShape(new CANNON.Cylinder(legRad,legRad,legLen,12)); legBody.position.set(x+legPos[i][0], legPos[i][1], legPos[i][2]); world.addBody(legBody); parts.push(legMesh); bodies.push(legBody); } function constrain(a,b,ptA,ptB) { let c = new CANNON.PointToPointConstraint(bodies[a],ptA,bodies[b],ptB); world.addConstraint(c); constraints.push(c); } constrain(0,1,new CANNON.Vec3(0,-0.4,0),new CANNON.Vec3(0,0.6,0)); constrain(1,2,new CANNON.Vec3(-0.35,0.4,0),new CANNON.Vec3(0,0.35,0)); constrain(1,3,new CANNON.Vec3(0.35,0.4,0),new CANNON.Vec3(0,0.35,0)); constrain(2,4,new CANNON.Vec3(0,-0.35,0),new CANNON.Vec3(0,0.35,0)); constrain(3,5,new CANNON.Vec3(0,-0.35,0),new CANNON.Vec3(0,0.35,0)); constrain(1,6,new CANNON.Vec3(-0.2,-0.6,0),new CANNON.Vec3(0,0.4,0)); constrain(1,7,new CANNON.Vec3(0.2,-0.6,0),new CANNON.Vec3(0,0.4,0)); constrain(6,8,new CANNON.Vec3(0,-0.4,0),new CANNON.Vec3(0,0.4,0)); constrain(7,9,new CANNON.Vec3(0,-0.4,0),new CANNON.Vec3(0,0.4,0)); ragdolls.push({parts, bodies, constraints}); ragdollCountSpan.textContent = ragdolls.length; } function resetScene() { ragdolls.forEach(rd => { rd.parts.forEach(mesh => scene.remove(mesh)); rd.bodies.forEach(body => world.removeBody(body)); rd.constraints.forEach(c => world.removeConstraint(c)); }); ragdolls = []; ragdollCountSpan.textContent = '0'; } function animate() { requestAnimationFrame(animate); world.step(1/60); // Move the grabbed object (if any) to the center ray if (pickedConstraint && pickedBody) { let raycaster = new THREE.Raycaster(); raycaster.setFromCamera({x:0, y:0}, camera); let dist = camera.position.distanceTo(controls.target); let target = raycaster.ray.at(dist, new THREE.Vector3()); pickedConstraint.updatePivotB(new CANNON.Vec3(target.x, target.y, target.z)); } ragdolls.forEach(rd => { rd.parts.forEach((mesh, i) => { let b = rd.bodies[i]; mesh.position.copy(b.position); mesh.quaternion.copy(b.quaternion); }); }); controls.update(); renderer.render(scene, camera); }
Validating your code, please wait...
HTML
CSS
JS
3D Ragdoll Simulation
Spawn Ragdoll
Reset Scene
Ragdolls:
0
Right-click + drag: Orbit camera
Scroll: Zoom
L key (no mouse):
Grab/drag part at center of view
body { margin: 0; overflow: hidden; background: #222; } #controls { position: absolute; top: 14px; left: 14px; z-index: 10; background: rgba(0,0,0,0.75); color: #fff; padding: 16px 24px; border-radius: 12px; font-family: 'Segoe UI', Arial, sans-serif; box-shadow: 0 2px 16px #000a; user-select: none; } button { font-size: 1em; margin-right: 8px; margin-bottom: 6px; padding: 8px 16px; border: none; border-radius: 5px; background: #4fc3f7; color: #222; cursor: pointer; font-weight: bold; transition: background 0.2s; } button:hover { background: #0288d1; color: #fff; } #gameCanvas { display: block; width: 100vw; height: 100vh; outline: none; background: #87ceeb; }
let scene, camera, renderer, world, controls; let ragdolls = []; let pickedBody = null, pickedConstraint = null; let isGrabbing = false; const ragdollCountSpan = document.getElementById('ragdollCount'); init(); animate(); // --- "L" is the only grab key --- window.addEventListener('keydown', (e) => { if ((e.key === 'l' || e.key === 'L') && !isGrabbing) { tryGrabCenter(); } }); window.addEventListener('keyup', (e) => { if (e.key === 'l' || e.key === 'L') { releaseGrab(); } }); function tryGrabCenter() { // Raycast from center of the screen let raycaster = new THREE.Raycaster(); raycaster.setFromCamera({x:0, y:0}, camera); // center let intersects = []; ragdolls.forEach(rd => rd.parts.forEach(mesh => { let int = raycaster.intersectObject(mesh); if (int.length) intersects.push({mesh, point: int[0].point}); }) ); if (intersects.length === 0) return; // Find closest intersect let closest = intersects.reduce((a, b) => (a.point.distanceTo(camera.position) < b.point.distanceTo(camera.position)) ? a : b ); // Find corresponding CANNON body for (let rd of ragdolls) { let idx = rd.parts.indexOf(closest.mesh); if (idx !== -1) { pickedBody = rd.bodies[idx]; let pivot = pickedBody.pointToLocalFrame(new CANNON.Vec3(closest.point.x, closest.point.y, closest.point.z)); pickedConstraint = new CANNON.PointToPointConstraint( pickedBody, pivot, null, new CANNON.Vec3()); world.addConstraint(pickedConstraint); isGrabbing = true; break; } } } function releaseGrab() { if (pickedConstraint) world.removeConstraint(pickedConstraint); pickedConstraint = null; pickedBody = null; isGrabbing = false; } function init() { scene = new THREE.Scene(); scene.background = new THREE.Color(0x87ceeb); camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 7, 20); renderer = new THREE.WebGLRenderer({canvas: document.getElementById('gameCanvas'), antialias:true}); renderer.setSize(window.innerWidth, window.innerHeight); controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.12; controls.target.set(0, 3, 0); world = new CANNON.World(); world.gravity.set(0, -20, 0); world.broadphase = new CANNON.NaiveBroadphase(); addRoom(); scene.add(new THREE.AmbientLight(0xffffff, 0.7)); let dir = new THREE.DirectionalLight(0xffffff, 0.8); dir.position.set(5, 15, 8); scene.add(dir); window.addEventListener('resize', onWindowResize); document.getElementById('spawnBtn').onclick = spawnRagdoll; document.getElementById('resetBtn').onclick = resetScene; renderer.domElement.addEventListener('contextmenu', e => e.preventDefault()); spawnRagdoll(); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function addRoom() { let floorGeo = new THREE.BoxGeometry(20,1,20); let floorMat = new THREE.MeshStandardMaterial({color:0x888888}); let floorMesh = new THREE.Mesh(floorGeo, floorMat); floorMesh.position.y = -0.5; scene.add(floorMesh); let floorPhys = new CANNON.Body({mass:0}); floorPhys.addShape(new CANNON.Box(new CANNON.Vec3(10,0.5,10))); floorPhys.position.y = -0.5; world.addBody(floorPhys); const wallColor = 0xcccccc; const wallMat = new THREE.MeshStandardMaterial({color:wallColor, transparent:true, opacity:0.33}); addWall(0,4,-10, 20,8,1, wallMat); addWall(-10,4,0, 1,8,20, wallMat); addWall(10,4,0, 1,8,20, wallMat); } function addWall(x,y,z, sx,sy,sz, mat) { let mesh = new THREE.Mesh(new THREE.BoxGeometry(sx,sy,sz), mat); mesh.position.set(x,y,z); scene.add(mesh); let body = new CANNON.Body({mass:0}); body.addShape(new CANNON.Box(new CANNON.Vec3(sx/2,sy/2,sz/2))); body.position.set(x,y,z); world.addBody(body); } function spawnRagdoll() { let y = 6; let x = (Math.random()-0.5)*4; let color = Math.random()*0xffffff; let parts = []; let bodies = []; let constraints = []; let headMesh = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 16), new THREE.MeshStandardMaterial({color})); scene.add(headMesh); let headBody = new CANNON.Body({mass:0.6}); headBody.addShape(new CANNON.Sphere(0.4)); headBody.position.set(x, y, 0); world.addBody(headBody); parts.push(headMesh); bodies.push(headBody); let torsoMesh = new THREE.Mesh(new THREE.BoxGeometry(0.7,1.2,0.4), new THREE.MeshStandardMaterial({color})); scene.add(torsoMesh); let torsoBody = new CANNON.Body({mass:1.2}); torsoBody.addShape(new CANNON.Box(new CANNON.Vec3(0.35,0.6,0.2))); torsoBody.position.set(x, y-1, 0); world.addBody(torsoBody); parts.push(torsoMesh); bodies.push(torsoBody); let armLen = 0.7, armRad = 0.13; let positions = [[-0.6, y-0.5, 0], [0.6, y-0.5, 0], [-0.6, y-1.2, 0], [0.6, y-1.2, 0]]; for(let i=0;i<4;i++){ let armMesh = new THREE.Mesh(new THREE.CylinderGeometry(armRad,armRad,armLen,12), new THREE.MeshStandardMaterial({color})); scene.add(armMesh); let armBody = new CANNON.Body({mass:0.5}); armBody.addShape(new CANNON.Cylinder(armRad,armRad,armLen,12)); armBody.position.set(x+positions[i][0], positions[i][1], positions[i][2]); world.addBody(armBody); parts.push(armMesh); bodies.push(armBody); } let legLen = 0.8, legRad = 0.15; let legPos = [[-0.25, y-2, 0], [0.25, y-2, 0], [-0.25, y-2.8, 0], [0.25, y-2.8, 0]]; for(let i=0;i<4;i++){ let legMesh = new THREE.Mesh(new THREE.CylinderGeometry(legRad,legRad,legLen,12), new THREE.MeshStandardMaterial({color})); scene.add(legMesh); let legBody = new CANNON.Body({mass:0.7}); legBody.addShape(new CANNON.Cylinder(legRad,legRad,legLen,12)); legBody.position.set(x+legPos[i][0], legPos[i][1], legPos[i][2]); world.addBody(legBody); parts.push(legMesh); bodies.push(legBody); } function constrain(a,b,ptA,ptB) { let c = new CANNON.PointToPointConstraint(bodies[a],ptA,bodies[b],ptB); world.addConstraint(c); constraints.push(c); } constrain(0,1,new CANNON.Vec3(0,-0.4,0),new CANNON.Vec3(0,0.6,0)); constrain(1,2,new CANNON.Vec3(-0.35,0.4,0),new CANNON.Vec3(0,0.35,0)); constrain(1,3,new CANNON.Vec3(0.35,0.4,0),new CANNON.Vec3(0,0.35,0)); constrain(2,4,new CANNON.Vec3(0,-0.35,0),new CANNON.Vec3(0,0.35,0)); constrain(3,5,new CANNON.Vec3(0,-0.35,0),new CANNON.Vec3(0,0.35,0)); constrain(1,6,new CANNON.Vec3(-0.2,-0.6,0),new CANNON.Vec3(0,0.4,0)); constrain(1,7,new CANNON.Vec3(0.2,-0.6,0),new CANNON.Vec3(0,0.4,0)); constrain(6,8,new CANNON.Vec3(0,-0.4,0),new CANNON.Vec3(0,0.4,0)); constrain(7,9,new CANNON.Vec3(0,-0.4,0),new CANNON.Vec3(0,0.4,0)); ragdolls.push({parts, bodies, constraints}); ragdollCountSpan.textContent = ragdolls.length; } function resetScene() { ragdolls.forEach(rd => { rd.parts.forEach(mesh => scene.remove(mesh)); rd.bodies.forEach(body => world.removeBody(body)); rd.constraints.forEach(c => world.removeConstraint(c)); }); ragdolls = []; ragdollCountSpan.textContent = '0'; } function animate() { requestAnimationFrame(animate); world.step(1/60); // Move the grabbed object (if any) to the center ray if (pickedConstraint && pickedBody) { let raycaster = new THREE.Raycaster(); raycaster.setFromCamera({x:0, y:0}, camera); let dist = camera.position.distanceTo(controls.target); let target = raycaster.ray.at(dist, new THREE.Vector3()); pickedConstraint.updatePivotB(new CANNON.Vec3(target.x, target.y, target.z)); } ragdolls.forEach(rd => { rd.parts.forEach((mesh, i) => { let b = rd.bodies[i]; mesh.position.copy(b.position); mesh.quaternion.copy(b.quaternion); }); }); controls.update(); renderer.render(scene, camera); }