A variety of difficult programming tests with complex input and output parameters.
Prompt
Create a 3D physics simulation of a realistic robotic arm with six degrees of freedom. User inputs control surge, heave, sway, pitch, yaw, and roll. The arm can be used to move a static cube into a container.
A system prompt was added to support web rendering
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>6 DOF Robotic Arm Simulation</title> <style> body { margin: 0; overflow: hidden; background-color: #f0f0f0; } canvas { display: block; } #overlay { position: absolute; top: 10px; left: 10px; background: rgba(0, 0, 0, 0.7); color: white; padding: 10px; font-family: monospace; font-size: 14px; border-radius: 5px; z-index: 10; } #controls-info { position: absolute; bottom: 10px; left: 10px; background: rgba(0, 0, 0, 0.7); color: white; padding: 10px; font-family: monospace; font-size: 14px; border-radius: 5px; z-index: 10; } </style> </head> <body> <div id="overlay"> Instructions:<br> - W/S: Shoulder Pitch (Heave control)<br> - A/D: Base Yaw (Sway control)<br> - Q/E: Elbow Pitch (Surge control)<br> - Arrow Up/Down: Wrist Pitch<br> - Arrow Left/Right: Wrist Yaw<br> - Z/X: Wrist Roll<br> - Space: Grasp/Release </div> <div id="controls-info"> Arm Joint Angles (Radians):<br> Yaw: <span id="yawAngle">0.00</span><br> Shoulder Pitch: <span id="shoulderPitchAngle">0.00</span><br> Elbow Pitch: <span id="elbowPitchAngle">0.00</span><br> Wrist Pitch: <span id="wristPitchAngle">0.00</span><br> Wrist Yaw: <span id="wristYawAngle">0.00</span><br> Wrist Roll: <span id="wristRollAngle">0.00</span><br> Grasping: <span id="graspingStatus">No</span> </div> <!-- Three.js Library --> <script type="importmap"> { "imports": { "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.160.1/three.module.min.js" } } </script> <!-- Cannon.js Library --> <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> <script type="module"> import * as THREE from 'three'; // --- Scene Setup --- const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild(renderer.domElement); scene.background = new THREE.Color(0xddeeff); // Lights const ambientLight = new THREE.AmbientLight(0x404040, 2.5); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 3); directionalLight.position.set(5, 10, 7.5); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50; directionalLight.shadow.camera.left = -10; directionalLight.shadow.camera.right = 10; directionalLight.shadow.camera.top = 10; directionalLight.shadow.camera.bottom = -10; scene.add(directionalLight); // Camera position camera.position.set(5, 5, 5); camera.lookAt(0, 1.5, 0); // --- Physics World Setup (Cannon.js) --- const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); world.broadphase = new CANNON.SAPBroadphase(world); world.solver.iterations = 10; // Materials const groundMaterial = new CANNON.Material("groundMaterial"); const objectMaterial = new CANNON.Material("objectMaterial"); // No specific arm material needed as it's kinematic, only the gripper tip will have physics. const groundObjectContactMaterial = new CANNON.ContactMaterial( groundMaterial, objectMaterial, { friction: 0.8, restitution: 0.1 } ); world.addContactMaterial(groundObjectContactMaterial); const objectObjectContactMaterial = new CANNON.ContactMaterial( objectMaterial, objectMaterial, { friction: 0.6, restitution: 0.2 } ); world.addContactMaterial(objectObjectContactMaterial); // --- Ground Plane --- const groundGeometry = new THREE.PlaneGeometry(20, 20); const groundMaterialThree = new THREE.MeshStandardMaterial({ color: 0x999999 }); const groundMesh = new THREE.Mesh(groundGeometry, groundMaterialThree); groundMesh.rotation.x = -Math.PI / 2; groundMesh.receiveShadow = true; scene.add(groundMesh); const groundShape = new CANNON.Plane(); const groundBody = new CANNON.Body({ mass: 0, material: groundMaterial }); groundBody.addShape(groundShape); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); world.addBody(groundBody); // --- Robotic Arm Components (Kinematic) --- // Joint control parameters (angles in radians) const jointConfig = { baseYaw: { angle: 0, min: -Math.PI * 0.75, max: Math.PI * 0.75, speed: 0.03 }, shoulderPitch: { angle: 0, min: -Math.PI * 0.3, max: Math.PI * 0.5, speed: 0.03 }, // Controls height elbowPitch: { angle: 0, min: -Math.PI * 0.7, max: Math.PI * 0.7, speed: 0.03 }, // Controls reach wristPitch: { angle: 0, min: -Math.PI * 0.5, max: Math.PI * 0.5, speed: 0.03 }, wristYaw: { angle: 0, min: -Math.PI * 0.75, max: Math.PI * 0.75, speed: 0.03 }, wristRoll: { angle: 0, min: -Math.PI, max: Math.PI, speed: 0.03 } }; function createArmVisualSegment(geometry, color) { const material = new THREE.MeshStandardMaterial({ color: color || 0x666666, flatShading: false }); const mesh = new THREE.Mesh(geometry, material); mesh.castShadow = true; mesh.receiveShadow = true; return mesh; } // Arm construction: Each "link" is a THREE.Mesh which contains its rotational "joint" mesh and the next "link" mesh // Base (fixed) const baseWidth = 0.8, baseHeight = 0.2, baseDepth = 0.8; const baseMesh = createArmVisualSegment(new THREE.BoxGeometry(baseWidth, baseHeight, baseDepth), 0x333333); baseMesh.position.set(0, baseHeight / 2, 0); scene.add(baseMesh); // Joint 1 (Base Yaw) Group const joint1PivotYOffset = baseHeight / 2 + 0.1; const joint1Group = new THREE.Group(); // Represents the pivot point for yaw joint1Group.position.set(0, joint1PivotYOffset, 0); baseMesh.add(joint1Group); // Visual for Joint 1 const joint1Visual = createArmVisualSegment(new THREE.CylinderGeometry(0.15, 0.15, 0.2, 16), 0x444444); joint1Visual.position.set(0, 0, 0); joint1Group.add(joint1Visual); // Link 1 (Shoulder) const link1Length = 1.0; const link1Thickness = 0.15; const link1Mesh = createArmVisualSegment(new THREE.BoxGeometry(link1Thickness, link1Length, link1Thickness), 0x666666); link1Mesh.position.set(0, link1Length / 2, 0); // Positioned relative to its parent group's pivot joint1Group.add(link1Mesh); // Joint 2 (Shoulder Pitch) Group const joint2PivotYOffset = link1Length / 2; const joint2Group = new THREE.Group(); // Pivot point for shoulder pitch joint2Group.position.set(0, joint2PivotYOffset, 0); link1Mesh.add(joint2Group); // Visual for Joint 2 const joint2Visual = createArmVisualSegment(new THREE.CylinderGeometry(0.1, 0.1, 0.2, 16), 0x444444); joint2Visual.rotation.z = Math.PI / 2; // Rotate cylinder to align with X-axis for pitch joint2Group.add(joint2Visual); // Link 2 (Elbow) const link2Length = 1.0; const link2Thickness = 0.12; const link2Mesh = createArmVisualSegment(new THREE.BoxGeometry(link2Thickness, link2Length, link2Thickness), 0x666666); link2Mesh.position.set(0, link2Length / 2, 0); joint2Group.add(link2Mesh); // Joint 3 (Elbow Pitch) Group const joint3PivotYOffset = link2Length / 2; const joint3Group = new THREE.Group(); // Pivot point for elbow pitch joint3Group.position.set(0, joint3PivotYOffset, 0); link2Mesh.add(joint3Group); // Visual for Joint 3 const joint3Visual = createArmVisualSegment(new THREE.CylinderGeometry(0.08, 0.08, 0.15, 16), 0x444444); joint3Visual.rotation.z = Math.PI / 2; // Rotate cylinder to align with X-axis for pitch joint3Group.add(joint3Visual); // Link 3 (Forearm) const link3Length = 0.8; const link3Thickness = 0.1; const link3Mesh = createArmVisualSegment(new THREE.BoxGeometry(link3Thickness, link3Length, link3Thickness), 0x666666); link3Mesh.position.set(0, link3Length / 2, 0); joint3Group.add(link3Mesh); // Joint 4 (Wrist Pitch) Group const joint4PivotYOffset = link3Length / 2; const joint4Group = new THREE.Group(); // Pivot point for wrist pitch joint4Group.position.set(0, joint4PivotYOffset, 0); link3Mesh.add(joint4Group); // Visual for Joint 4 const joint4Visual = createArmVisualSegment(new THREE.CylinderGeometry(0.07, 0.07, 0.1, 16), 0x444444); joint4Visual.rotation.z = Math.PI / 2; // Rotate cylinder to align with X-axis for pitch joint4Group.add(joint4Visual); // Link 4 (Wrist intermediate) const link4Length = 0.1; const link4Thickness = 0.08; const link4Mesh = createArmVisualSegment(new THREE.BoxGeometry(link4Thickness, link4Length, link4Thickness), 0x666666); link4Mesh.position.set(0, link4Length / 2, 0); joint4Group.add(link4Mesh); // Joint 5 (Wrist Yaw) Group const joint5PivotYOffset = link4Length / 2; const joint5Group = new THREE.Group(); // Pivot point for wrist yaw joint5Group.position.set(0, joint5PivotYOffset, 0); link4Mesh.add(joint5Group); // Visual for Joint 5 const joint5Visual = createArmVisualSegment(new THREE.CylinderGeometry(0.06, 0.06, 0.1, 16), 0x444444); joint5Visual.position.set(0, 0, 0); joint5Group.add(joint5Visual); // Link 5 (Gripper Base) const link5Length = 0.1; const link5Thickness = 0.07; const link5Mesh = createArmVisualSegment(new THREE.BoxGeometry(link5Thickness, link5Length, link5Thickness), 0x666666); link5Mesh.position.set(0, link5Length / 2, 0); joint5Group.add(link5Mesh); // Joint 6 (Wrist Roll) Group const joint6PivotYOffset = link5Length / 2; const joint6Group = new THREE.Group(); // Pivot point for wrist roll joint6Group.position.set(0, joint6PivotYOffset, 0); link5Mesh.add(joint6Group); // Visual for Joint 6 const joint6Visual = createArmVisualSegment(new THREE.CylinderGeometry(0.05, 0.05, 0.1, 16), 0x444444); joint6Visual.rotation.x = Math.PI / 2; // Rotate cylinder to align with Z-axis for roll joint6Group.add(joint6Visual); // End Effector (Gripper Base) Visually const gripperBaseLength = 0.1; const gripperWidth = 0.15; const gripperDepth = 0.1; const endEffectorOffsetFromJoint6 = gripperBaseLength / 2 + 0.05; // Offset from joint 6 visual const endEffectorMesh = createArmVisualSegment(new THREE.BoxGeometry(gripperWidth, gripperBaseLength, gripperDepth), 0x777777); endEffectorMesh.position.set(0, endEffectorOffsetFromJoint6, 0); joint6Group.add(endEffectorMesh); // End Effector Physics Body (This is the active part that interacts with the cube) // Its position/orientation will be updated kinematically to follow the endEffectorMesh const endEffectorShape = new CANNON.Box(new CANNON.Vec3(gripperWidth / 2, gripperBaseLength / 2 + 0.05, gripperDepth / 2)); // Slightly bigger to encompass grasp area const endEffectorBody = new CANNON.Body({ mass: 0.1, material: objectMaterial }); // Small mass to still register collisions endEffectorBody.addShape(endEffectorShape); world.addBody(endEffectorBody); // Gripper Fingers (Visuals only, their interaction with cube is via endEffectorBody and constraint) const fingerLength = 0.2; const fingerThickness = 0.03; const fingerDepth = gripperDepth; // Same depth as gripper body let fingerGap = 0.05; // Initial gap between fingers (open) const closedFingerGap = 0.015; // Gap when closed (grabs cube) const leftFinger = createArmVisualSegment(new THREE.BoxGeometry(fingerThickness, fingerLength, fingerDepth), 0xAAAAAA); leftFinger.position.set(-gripperWidth / 2 - fingerThickness / 2 - fingerGap, fingerLength / 2 + endEffectorOffsetFromJoint6, 0); endEffectorMesh.add(leftFinger); const rightFinger = createArmVisualSegment(new THREE.BoxGeometry(fingerThickness, fingerLength, fingerDepth), 0xAAAAAA); rightFinger.position.set(gripperWidth / 2 + fingerThickness / 2 + fingerGap, fingerLength / 2 + endEffectorOffsetFromJoint6, 0); endEffectorMesh.add(rightFinger); const initialLeftFingerX = leftFinger.position.x; const initialRightFingerX = rightFinger.position.x; let gripperOpen = true; let graspingConstraint = null; // Stores the Cannon.LockConstraint for grasping // --- Kinematic Arm Control Update --- function updateArmKinematics() { // Apply rotations to joint groups based on angles joint1Group.rotation.y = jointConfig.baseYaw.angle; link1Mesh.rotation.x = jointConfig.shoulderPitch.angle; link2Mesh.rotation.x = jointConfig.elbowPitch.angle; link3Mesh.rotation.x = jointConfig.wristPitch.angle; joint5Group.rotation.y = jointConfig.wristYaw.angle; joint6Group.rotation.z = jointConfig.wristRoll.angle; // Animate gripper fingers const currentFingerGap = gripperOpen ? fingerGap : closedFingerGap; leftFinger.position.x = initialLeftFingerX + (fingerGap - currentFingerGap) * 1.5; // Adjusted to close more rightFinger.position.x = initialRightFingerX - (fingerGap - currentFingerGap) * 1.5; // Adjusted to close more // Update endEffectorBody (kinematic) to follow its visual mesh endEffectorMesh.updateWorldMatrix(true, false); // Ensure world matrix is updated for correct world position/quaternion const pos = new THREE.Vector3(); const quat = new THREE.Quaternion(); endEffectorMesh.getWorldPosition(pos); endEffectorMesh.getWorldQuaternion(quat); endEffectorBody.position.copy(pos); endEffectorBody.quaternion.copy(quat); // If grasping, the Cannon.LockConstraint handles the cube's physics position relative to endEffectorBody. } // --- Cube and Container --- const cubeSize = 0.3; const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); const cubeMaterialThree = new THREE.MeshStandardMaterial({ color: 0xff0000 }); const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterialThree); cubeMesh.castShadow = true; cubeMesh.receiveShadow = true; scene.add(cubeMesh); const cubeShape = new CANNON.Box(new CANNON.Vec3(cubeSize / 2, cubeSize / 2, cubeSize / 2)); const cubeBody = new CANNON.Body({ mass: 1, material: objectMaterial }); cubeBody.addShape(cubeShape); cubeBody.position.set(1.5, cubeSize / 2 + 0.01, 0); // Slightly above ground world.addBody(cubeBody); // Container (simple box with open top) const containerWidth = 0.8; const containerDepth = 0.6; const containerHeight = 0.5; const wallThickness = 0.05; // Container position offset on the ground const containerOffsetX = -1.5; const containerOffsetZ = 0; const containerMaterial = new THREE.MeshStandardMaterial({ color: 0x8888FF, transparent: true, opacity: 0.6 }); // Wall creation helper function createContainerWall(width, height, depth, x, y, z) { const mesh = new THREE.Mesh(new THREE.BoxGeometry(width, height, depth), containerMaterial); mesh.position.set(x + containerOffsetX, y, z + containerOffsetZ); mesh.castShadow = true; mesh.receiveShadow = true; scene.add(mesh); const shape = new CANNON.Box(new CANNON.Vec3(width / 2, height / 2, depth / 2)); const body = new CANNON.Body({ mass: 0, material: objectMaterial }); body.addShape(shape); body.position.copy(mesh.position); world.addBody(body); } // Back wall createContainerWall(containerWidth, containerHeight, wallThickness, 0, containerHeight / 2, -containerDepth / 2 + wallThickness / 2); // Front wall createContainerWall(containerWidth, containerHeight, wallThickness, 0, containerHeight / 2, containerDepth / 2 - wallThickness / 2); // Left wall createContainerWall(wallThickness, containerHeight, containerDepth - wallThickness * 2, -containerWidth / 2 + wallThickness / 2, containerHeight / 2, 0); // Right wall createContainerWall(wallThickness, containerHeight, containerDepth - wallThickness * 2, containerWidth / 2 - wallThickness / 2, containerHeight / 2, 0); // Bottom wall createContainerWall(containerWidth, wallThickness, containerDepth, 0, wallThickness / 2, 0); // --- User Input Handling --- const keys = {}; document.addEventListener('keydown', (event) => { keys[event.key.toLowerCase()] = true; }); document.addEventListener('keyup', (event) => { keys[event.key.toLowerCase()] = false; }); function handleControls() { // W/S: Shoulder Pitch (Heave) if (keys['w']) jointConfig.shoulderPitch.angle = Math.min(jointConfig.shoulderPitch.max, jointConfig.shoulderPitch.angle + jointConfig.shoulderPitch.speed); if (keys['s']) jointConfig.shoulderPitch.angle = Math.max(jointConfig.shoulderPitch.min, jointConfig.shoulderPitch.angle - jointConfig.shoulderPitch.speed); // A/D: Base Yaw (Sway) if (keys['a']) jointConfig.baseYaw.angle = Math.min(jointConfig.baseYaw.max, jointConfig.baseYaw.angle + jointConfig.baseYaw.speed); if (keys['d']) jointConfig.baseYaw.angle = Math.max(jointConfig.baseYaw.min, jointConfig.baseYaw.angle - jointConfig.baseYaw.speed); // Q/E: Elbow Pitch (Surge) if (keys['q']) jointConfig.elbowPitch.angle = Math.min(jointConfig.elbowPitch.max, jointConfig.elbowPitch.angle + jointConfig.elbowPitch.speed); if (keys['e']) jointConfig.elbowPitch.angle = Math.max(jointConfig.elbowPitch.min, jointConfig.elbowPitch.angle - jointConfig.elbowPitch.speed); // Arrow Up/Down: Wrist Pitch if (keys['arrowup']) jointConfig.wristPitch.angle = Math.min(jointConfig.wristPitch.max, jointConfig.wristPitch.angle + jointConfig.wristPitch.speed); if (keys['arrowdown']) jointConfig.wristPitch.angle = Math.max(jointConfig.wristPitch.min, jointConfig.wristPitch.angle - jointConfig.wristPitch.speed); // Arrow Left/Right: Wrist Yaw if (keys['arrowleft']) jointConfig.wristYaw.angle = Math.min(jointConfig.wristYaw.max, jointConfig.wristYaw.angle + jointConfig.wristYaw.speed); if (keys['arrowright']) jointConfig.wristYaw.angle = Math.max(jointConfig.wristYaw.min, jointConfig.wristYaw.angle - jointConfig.wristYaw.speed); // Z/X: Wrist Roll (continuous rotation) if (keys['z']) jointConfig.wristRoll.angle = (jointConfig.wristRoll.angle + jointConfig.wristRoll.speed); if (keys['x']) jointConfig.wristRoll.angle = (jointConfig.wristRoll.angle - jointConfig.wristRoll.speed); // Space: Grasp/Release (debounced) if (keys[' ']) { if (!keySpaceDown) { if (gripperOpen) { tryGraspCube(); } else { releaseCube(); } gripperOpen = !gripperOpen; } keySpaceDown = true; } else { keySpaceDown = false; } } let keySpaceDown = false; // Debounce flag for spacebar function tryGraspCube() { // Get world positions of gripper fingers and cube for initial check endEffectorMesh.updateWorldMatrix(true, false); leftFinger.updateWorldMatrix(true, false); rightFinger.updateWorldMatrix(true, false); const leftFingerWM = new THREE.Vector3(); leftFinger.getWorldPosition(leftFingerWM); const rightFingerWM = new THREE.Vector3(); rightFinger.getWorldPosition(rightFingerWM); const cubeWM = new THREE.Vector3(); cubeBody.position.copy(cubeWM); // Use physics body's position for cube // Check if cube is roughly within grasping volume // Determine the grasp center, approximately between fingers const graspX = (leftFingerWM.x + rightFingerWM.x) / 2; const graspY = leftFingerWM.y; // Y is height const graspZ = leftFingerWM.z; const graspTolerance = 0.2; // How close the cube needs to be to the gripper's center const cubeHalfSize = cubeSize / 2; // Check if cube is close enough to the gripper's center in all dimensions const dx = Math.abs(cubeWM.x - graspX); const dy = Math.abs(cubeWM.y - (graspY + fingerLength / 2)); // Expect cube to be below grasp point, hence + fingerLength/2 const dz = Math.abs(cubeWM.z - graspZ); if (dx < (gripperWidth / 2 + cubeHalfSize + graspTolerance) && // X tolerance dy < (fingerLength / 2 + cubeHalfSize + graspTolerance) && // Y tolerance dz < (gripperDepth / 2 + cubeHalfSize + graspTolerance) && // Z tolerance !graspingConstraint // Ensure we're not already grasping ) { // Create a LockConstraint to attach cube to endEffectorBody // This constraint rigidly connects two bodies at their current relative positions/orientations. graspingConstraint = new CANNON.LockConstraint(endEffectorBody, cubeBody, { maxForce: 100 // Prevent overstretching/breaking constraint with high forces }); world.addConstraint(graspingConstraint); cubeBody.mass = 0.01; // Make it very light while held, to prevent dynamic problems cubeBody.velocity.set(0,0,0); // Stop movement immediately upon grasp cubeBody.angularVelocity.set(0,0,0); document.getElementById('graspingStatus').textContent = 'Yes'; console.log("Grasped cube!"); } else { console.log("Cannot grasp: cube not in position or already grasping."); } } function releaseCube() { if (graspingConstraint) { world.removeConstraint(graspingConstraint); graspingConstraint = null; cubeBody.mass = 1; // Restore normal mass document.getElementById('graspingStatus').textContent = 'No'; console.log("Released cube!"); } } // --- Animation Loop --- const timeStep = 1 / 60; // seconds let lastTime = 0; function animate(currentTime) { requestAnimationFrame(animate); if (lastTime === 0) lastTime = currentTime; const deltaTime = (currentTime - lastTime) / 1000; lastTime = currentTime; handleControls(); updateArmKinematics(); world.step(timeStep, deltaTime, 10); // Step physics world // Synchronize Three.js meshes with Cannon.js bodies cubeMesh.position.copy(cubeBody.position); cubeMesh.quaternion.copy(cubeBody.quaternion); // Update UI with current joint angles document.getElementById('yawAngle').textContent = jointConfig.baseYaw.angle.toFixed(2); document.getElementById('shoulderPitchAngle').textContent = jointConfig.shoulderPitch.angle.toFixed(2); document.getElementById('elbowPitchAngle').textContent = jointConfig.elbowPitch.angle.toFixed(2); document.getElementById('wristPitchAngle').textContent = jointConfig.wristPitch.angle.toFixed(2); document.getElementById('wristYawAngle').textContent = jointConfig.wristYaw.angle.toFixed(2); document.getElementById('wristRollAngle').textContent = jointConfig.wristRoll.angle.toFixed(2); renderer.render(scene, camera); } // Handle window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Start animation animate(); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>6-DoF Robotic Arm – Interactive Physics Demo</title> <!-- Three.js --> <script src="https://cdn.jsdelivr.net/npm/three@0.152/build/three.min.js"></script> <!-- Cannon-es (physics engine) --> <script type="module"> // We import cannon-es as an ES-module so Three can share WebGL context. import * as CANNON from "https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js"; window.CANNON = CANNON; // expose globally for the rest of the page </script> <!-- dat.GUI for user sliders --> <script src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.9/build/dat.gui.min.js"></script> <style> html,body{margin:0;height:100%;overflow:hidden;background:#111;color:#eee;font-family:sans-serif;} #info{position:absolute;top:10px;left:10px;} canvas{display:block;} </style> </head> <body> <div id="info">Drag the sliders to move the arm (surge, heave, sway) and change its attitude (pitch, yaw, roll).<br> Push the cube into the blue container.</div> <script type="module"> /***** ------ SCENE & RENDERER ------ *****/ const scene = new THREE.Scene(); scene.background = new THREE.Color(0x222222); const camera = new THREE.PerspectiveCamera(60, innerWidth/innerHeight, 0.1, 200); camera.position.set(25, 18, 30); camera.lookAt(0,5,0); const renderer = new THREE.WebGLRenderer({antialias:true}); renderer.setSize(innerWidth, innerHeight); document.body.appendChild(renderer.domElement); /* Resize handling */ window.addEventListener('resize', ()=>{ renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth/innerHeight; camera.updateProjectionMatrix(); }); /***** ------ PHYSICS WORLD ------ *****/ const world = new CANNON.World({ gravity: new CANNON.Vec3(0,-9.82,0) }); world.broadphase = new CANNON.SAPBroadphase(world); world.allowSleep = true; const timeStep = 1/60; /* Materials */ const defaultMat = new CANNON.Material('default'); const contactMat = new CANNON.ContactMaterial(defaultMat, defaultMat, { friction: 0.4, restitution: 0.2 }); world.addContactMaterial(contactMat); /***** ------ HELPERS ------ *****/ // quick linker between THREE.Mesh and CANNON.Body function link(mesh, body){ mesh.userData.physics = body; scene.add(mesh); world.addBody(body); } function syncPhysics(){ scene.traverse(obj=>{ if(obj.isMesh && obj.userData.physics){ const body = obj.userData.physics; obj.position.copy(body.position); obj.quaternion.copy(body.quaternion); } }); } /***** ------ FLOOR ------ *****/ const floorGeo = new THREE.PlaneGeometry(200,200); const floorMat = new THREE.MeshStandardMaterial({color:0x444444, roughness:0.8}); const floorMesh= new THREE.Mesh(floorGeo, floorMat); floorMesh.rotation.x = -Math.PI/2; floorMesh.receiveShadow = true; scene.add(floorMesh); const floorBody = new CANNON.Body({mass:0,material:defaultMat}); floorBody.addShape(new CANNON.Plane()); floorBody.quaternion.setFromEuler(-Math.PI/2, 0, 0); world.addBody(floorBody); /***** ------ LIGHTS ------ *****/ const hemi = new THREE.HemisphereLight(0xffffff, 0x333333, 0.8); scene.add(hemi); const dir = new THREE.DirectionalLight(0xffffff, 0.8); dir.position.set(15,30,10); scene.add(dir); /***** ------ CONTAINER ------ *****/ const container = new THREE.Group(); scene.add(container); const wallMat = new THREE.MeshStandardMaterial({color:0x104e8b, metalness:0.2, roughness:0.7}); const wallThickness = 0.5, wallHeight = 3, boxSize = 6; const wallGeo = new THREE.BoxGeometry(boxSize, wallHeight, wallThickness); // Four walls const walls = []; for(let i=0;i<4;i++){ const wallMesh = new THREE.Mesh(wallGeo, wallMat); wallMesh.castShadow = wallMesh.receiveShadow = true; container.add(wallMesh); walls.push(wallMesh); const wallBody = new CANNON.Body({mass:0,material:defaultMat}); wallBody.addShape(new CANNON.Box(new CANNON.Vec3(boxSize/2, wallHeight/2, wallThickness/2))); world.addBody(wallBody); wallMesh.userData.physics = wallBody; } /* Position walls */ walls[0].position.set(0, wallHeight/2, boxSize/2); // back walls[1].position.set(0, wallHeight/2,-boxSize/2); // front walls[2].rotation.y = Math.PI/2; // left walls[2].position.set(-boxSize/2, wallHeight/2, 0); walls[3].rotation.y = Math.PI/2; // right walls[3].position.set(boxSize/2, wallHeight/2, 0); // Sync wall physics transforms once walls.forEach(w=>{ const body = w.userData.physics; body.position.copy(w.position); body.quaternion.copy(w.quaternion); }); /***** ------ MOVEABLE CUBE ------ *****/ const cubeSize = 1.2; const cubeGeo = new THREE.BoxGeometry(cubeSize,cubeSize,cubeSize); const cubeMat = new THREE.MeshStandardMaterial({color:0xff9933}); const cubeMesh = new THREE.Mesh(cubeGeo, cubeMat); cubeMesh.castShadow = cubeMesh.receiveShadow = true; scene.add(cubeMesh); const cubeBody = new CANNON.Body({ mass: 1, material: defaultMat, position: new CANNON.Vec3(0, cubeSize/2, -8) }); cubeBody.addShape(new CANNON.Box(new CANNON.Vec3(cubeSize/2, cubeSize/2, cubeSize/2))); link(cubeMesh, cubeBody); /***** ------ KINEMATIC ROBOTIC ARM ------ *****/ const armGroup = new THREE.Group(); scene.add(armGroup); // Build a simple 4-segment arm for looks (base + 3 links) const matGrey = new THREE.MeshStandardMaterial({color:0x888888, metalness:0.6, roughness:0.4}); const matYellow= new THREE.MeshStandardMaterial({color:0xd4af37, metalness:0.8, roughness:0.3}); const baseMesh = new THREE.Mesh(new THREE.CylinderGeometry(1.8,1.8,1,32), matGrey); baseMesh.castShadow = true; armGroup.add(baseMesh); const link1 = new THREE.Mesh(new THREE.BoxGeometry(0.8,4,0.8), matGrey); link1.position.y = 2.5; link1.castShadow = true; baseMesh.add(link1); const link2 = new THREE.Mesh(new THREE.BoxGeometry(0.6,3,0.6), matGrey); link2.position.y = 3.5; link1.add(link2); const link3 = new THREE.Mesh(new THREE.BoxGeometry(0.4,2,0.4), matGrey); link3.position.y = 2.5; link2.add(link3); // End-effector (gripper pad) const gripper = new THREE.Mesh(new THREE.BoxGeometry(1.4,0.4,1.4), matYellow); gripper.position.y = 1.6; link3.add(gripper); // Give every visible part a kinematic physics body so it can push the cube const robotParts = [baseMesh, link1, link2, link3, gripper]; robotParts.forEach(part=>{ const size = new THREE.Box3().setFromObject(part).getSize(new THREE.Vector3()); const body = new CANNON.Body({mass:0}); // kinematic body.addShape(new CANNON.Box(new CANNON.Vec3(size.x/2, size.y/2, size.z/2))); world.addBody(body); part.userData.physics = body; }); /***** ------ USER INPUT (6-DOF) ------ *****/ const params = { surge : -6, // forward/back (z) sway : 0, // left/right (x) heave : 0, // up/down (y) pitch : 0, // rotation x (deg) yaw : 0, // rotation y (deg) roll : 0 // rotation z (deg) }; const gui = new dat.GUI(); gui.width = 300; gui.add(params,'surge', -10, 10, 0.1); gui.add(params,'sway' , -10, 10, 0.1); gui.add(params,'heave', 0, 10, 0.1); gui.add(params,'pitch', -180, 180, 1); gui.add(params,'yaw' , -180, 180, 1); gui.add(params,'roll' , -180, 180, 1); /***** ------ ANIMATION LOOP ------ *****/ let lastTime; function animate(time){ requestAnimationFrame(animate); /* delta for physics */ if(lastTime !== undefined){ world.step(timeStep, (time - lastTime)/1000); } lastTime = time; /* Update kinematic arm pose from GUI */ armGroup.position.set(params.sway, params.heave + 0.5, params.surge); const euler = new THREE.Euler( THREE.MathUtils.degToRad(params.pitch), THREE.MathUtils.degToRad(params.yaw), THREE.MathUtils.degToRad(params.roll), 'XYZ' ); armGroup.quaternion.setFromEuler(euler); /* Keep physics bodies in sync with graphical arm (kinematic) */ robotParts.forEach(part=>{ part.updateWorldMatrix(true,false); const body = part.userData.physics; const worldPos= new THREE.Vector3(); const worldQuat= new THREE.Quaternion(); part.getWorldPosition(worldPos); part.getWorldQuaternion(worldQuat); body.position.copy(worldPos); body.quaternion.copy(worldQuat); body.velocity.set(0,0,0); // ensure stability body.angularVelocity.set(0,0,0); }); /* Sync dynamic objects back to Three.js */ syncPhysics(); renderer.render(scene,camera); } animate(); </script> </body> </html> ```
Below is a complete HTML file that creates a 3D physics simulation of a robotic arm with six degrees of freedom using Three.js and Cannon.js. The user can control the arm's movements (surge, heave, sway, pitch, yaw, and roll) using sliders to manipulate the arm and move a static cube into a container. All necessary libraries are included via CDN, and the full code is provided in a single block. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Robotic Arm Simulation</title> <style> body { margin: 0; overflow: hidden; } #controls { position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.8); padding: 10px; font-family: Arial, sans-serif; } #controls div { margin: 5px 0; } #controls input[type="range"] { width: 200px; } canvas { display: block; } </style> </head> <body> <div id="controls"> <div>Surge: <input type="range" id="surge" min="-5" max="5" value="0" step="0.1"></div> <div>Heave: <input type="range" id="heave" min="-5" max="5" value="0" step="0.1"></div> <div>Sway: <input type="range" id="sway" min="-5" max="5" value="0" step="0.1"></div> <div>Pitch: <input type="range" id="pitch" min="-180" max="180" value="0" step="1"></div> <div>Yaw: <input type="range" id="yaw" min="-180" max="180" value="0" step="1"></div> <div>Roll: <input type="range" id="roll" min="-180" max="180" value="0" step="1"></div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> <script> // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Physics world setup const world = new CANNON.World(); world.gravity.set(0, -9.81, 0); world.broadphase = new CANNON.NaiveBroadphase(); world.solver.iterations = 10; // Materials for physics const groundMaterial = new CANNON.Material(); const armMaterial = new CANNON.Material(); const cubeMaterial = new CANNON.Material(); world.addContactMaterial(new CANNON.ContactMaterial(groundMaterial, armMaterial, { friction: 0.3, restitution: 0.7 })); world.addContactMaterial(new CANNON.ContactMaterial(groundMaterial, cubeMaterial, { friction: 0.3, restitution: 0.7 })); world.addContactMaterial(new CANNON.ContactMaterial(armMaterial, cubeMaterial, { friction: 0.3, restitution: 0.7 })); // Ground const groundShape = new CANNON.Plane(); const groundBody = new CANNON.Body({ mass: 0, material: groundMaterial }); groundBody.addShape(groundShape); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); groundBody.position.set(0, -5, 0); world.addBody(groundBody); const groundGeometry = new THREE.PlaneGeometry(20, 20); const groundMaterialVis = new THREE.MeshBasicMaterial({ color: 0xaaaaaa, side: THREE.DoubleSide }); const groundMesh = new THREE.Mesh(groundGeometry, groundMaterialVis); groundMesh.rotation.x = -Math.PI / 2; groundMesh.position.y = -5; scene.add(groundMesh); // Container const containerGeometry = new THREE.BoxGeometry(3, 2, 3); const containerMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff, wireframe: true }); const containerMesh = new THREE.Mesh(containerGeometry, containerMaterial); containerMesh.position.set(5, -4, 0); scene.add(containerMesh); const containerShape = new CANNON.Box(new CANNON.Vec3(1.5, 1, 1.5)); const containerBody = new CANNON.Body({ mass: 0, material: groundMaterial }); containerBody.addShape(containerShape); containerBody.position.set(5, -4, 0); world.addBody(containerBody); // Cube (object to move) const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)); const cubeBody = new CANNON.Body({ mass: 1, material: cubeMaterial }); cubeBody.addShape(cubeShape); cubeBody.position.set(0, -4, 0); world.addBody(cubeBody); const cubeGeometry = new THREE.BoxGeometry(1, 1, 1); const cubeMaterialVis = new THREE.MeshBasicMaterial({ color: 0xff0000 }); const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterialVis); scene.add(cubeMesh); // Robotic Arm (simplified as a single link with 6 DOF controlled end effector) const armBaseGeometry = new THREE.CylinderGeometry(0.2, 0.2, 2, 32); const armBaseMaterial = new THREE.MeshBasicMaterial({ color: 0x555555 }); const armBaseMesh = new THREE.Mesh(armBaseGeometry, armBaseMaterial); armBaseMesh.position.set(0, -4, 5); scene.add(armBaseMesh); const armLinkGeometry = new THREE.CylinderGeometry(0.1, 0.1, 3, 32); const armLinkMaterial = new THREE.MeshBasicMaterial({ color: 0x333333 }); const armLinkMesh = new THREE.Mesh(armLinkGeometry, armLinkMaterial); armLinkMesh.position.set(0, 0, 1.5); armBaseMesh.add(armLinkMesh); const armEndGeometry = new THREE.SphereGeometry(0.2, 32, 32); const armEndMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const armEndMesh = new THREE.Mesh(armEndGeometry, armEndMaterial); armEndMesh.position.set(0, 0, 1.5); armLinkMesh.add(armEndMesh); // Physics for arm end effector (simplified) const endEffectorShape = new CANNON.Sphere(0.2); const endEffectorBody = new CANNON.Body({ mass: 0, material: armMaterial }); // Kinematic control endEffectorBody.addShape(endEffectorShape); endEffectorBody.position.set(0, -4, 6.5); world.addBody(endEffectorBody); // Lighting const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); directionalLight.position.set(0, 1, 0); scene.add(directionalLight); // Camera position camera.position.set(0, 5, 10); camera.lookAt(0, 0, 0); // Control inputs const controls = { surge: document.getElementById('surge'), heave: document.getElementById('heave'), sway: document.getElementById('sway'), pitch: document.getElementById('pitch'), yaw: document.getElementById('yaw'), roll: document.getElementById('roll') }; // Update arm based on user input function updateArm() { const surge = parseFloat(controls.surge.value); const heave = parseFloat(controls.heave.value); const sway = parseFloat(controls.sway.value); const pitch = parseFloat(controls.pitch.value) * Math.PI / 180; const yaw = parseFloat(controls.yaw.value) * Math.PI / 180; const roll = parseFloat(controls.roll.value) * Math.PI / 180; // Update end effector position (relative to base) const basePos = new THREE.Vector3(0, -4, 5); const offset = new THREE.Vector3(surge, heave, sway); offset.applyEuler(new THREE.Euler(pitch, yaw, roll, 'XYZ')); const targetPos = basePos.add(offset); endEffectorBody.position.set(targetPos.x, targetPos.y, targetPos.z); armEndMesh.position.set(targetPos.x, targetPos.y, targetPos.z); // Simplified arm link orientation armLinkMesh.rotation.set(pitch, yaw, roll); } controls.surge.addEventListener('input', updateArm); controls.heave.addEventListener('input', updateArm); controls.sway.addEventListener('input', updateArm); controls.pitch.addEventListener('input', updateArm); controls.yaw.addEventListener('input', updateArm); controls.roll.addEventListener('input', updateArm); // Animation loop let lastTime = 0; function animate(time) { requestAnimationFrame(animate); const delta = (time - lastTime) / 1000; lastTime = time; // Update physics world.step(1 / 60, delta, 3); // Sync visual meshes with physics bodies cubeMesh.position.copy(cubeBody.position); cubeMesh.quaternion.copy(cubeBody.quaternion); renderer.render(scene, camera); } // Handle window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); animate(0); </script> </body> </html> ``` This code creates a 3D environment where a robotic arm can be controlled using sliders to adjust surge (forward/back), heave (up/down), sway (left/right), pitch, yaw, and roll. The arm's end effector moves based on these inputs, and the goal is to interact with a red cube to push or move it into a blue wireframe container. The simulation uses Three.js for rendering and Cannon.js for physics, both loaded via CDN. The arm is visually simplified but demonstrates the concept of 6 DOF control. You can open this HTML file in a browser to interact with the simulation.
This is an interactive 3D physics simulation of a 6-DOF robotic arm. Use the sliders to control each joint of the arm and the gripper. Your objective is to pick up the blue cube and place it inside the red container. **Controls:** * **Mouse:** Click and drag to rotate the camera. Scroll to zoom in and out. * **Sliders:** Adjust the sliders to change the target angle for each joint. * **Gripper:** Move the slider to the "Close" position to grab the cube and to "Open" to release it. The gripper will only grab the cube when it's positioned correctly between the two fingers. ```html <!DOCTYPE html> <html lang="en"> <head> <title>3D Robotic Arm Simulation</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <style> body { margin: 0; padding: 0; font-family: Arial, sans-serif; color: #fff; background-color: #111; overflow: hidden; /* Prevent scrolling */ } canvas { display: block; } #ui-container { position: absolute; top: 10px; left: 10px; background: rgba(0, 0, 0, 0.6); padding: 15px; border-radius: 10px; color: #eee; width: 300px; box-shadow: 0 0 15px rgba(0,0,0,0.5); } h2, h3 { margin: 0 0 10px 0; text-align: center; } .control-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } .control-row label { flex-basis: 120px; font-size: 14px; } .control-row input[type="range"] { flex-grow: 1; cursor: pointer; } .gripper-label{ display: flex; justify-content: space-between; width: 100%; font-size: 12px; padding: 0 5px; } </style> </head> <body> <div id="ui-container"> <h2>Robotic Arm Controls</h2> <h3>Objective: Move the cube into the container</h3> <div id="controls"> <!-- Sliders will be dynamically added here --> </div> </div> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.164.1/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.164.1/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import * as CANNON from 'https://unpkg.com/cannon-es@0.20.0/dist/cannon-es.js'; class RobotSimulation { constructor() { this.scene = null; this.camera = null; this.renderer = null; this.controls = null; this.world = null; this.timeStep = 1 / 60; this.lastCallTime = null; this.physicsMeshes = []; this.arm = { joints: [], bodies: [], constraints: [], controlValues: [-Math.PI/2, Math.PI/4, Math.PI/4, 0, Math.PI/4, 0], gripperState: 0 // 0 for open, 1 for closed }; this.gripper = { leftFinger: null, rightFinger: null, constraints: [] }; this.movableCube = null; this.init(); } init() { // Scene this.scene = new THREE.Scene(); this.scene.background = new THREE.Color(0x222233); this.scene.fog = new THREE.Fog(0x222233, 20, 50); // Camera this.camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100); this.camera.position.set(5, 4, 8); // Renderer this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild(this.renderer.domElement); // Controls this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.controls.target.set(0, 1, 0); this.controls.update(); // Lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); this.scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0); directionalLight.position.set(5, 10, 7.5); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; directionalLight.shadow.camera.near = 0.5; directionalLight.shadow.camera.far = 50; directionalLight.shadow.camera.left = -15; directionalLight.shadow.camera.right = 15; directionalLight.shadow.camera.top = 15; directionalLight.shadow.camera.bottom = -15; this.scene.add(directionalLight); // Physics World this.world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0) }); // Materials this.initMaterials(); // Build Environment and Robot this.buildEnvironment(); this.buildRobotArm(); this.setupUI(); // Handle window resize window.addEventListener('resize', () => this.onWindowResize(), false); // Start animation loop this.animate(); } initMaterials() { this.materials = { ground: new CANNON.Material('groundMaterial'), robot: new CANNON.Material('robotMaterial'), object: new CANNON.Material('objectMaterial') }; const ground_robot_cm = new CANNON.ContactMaterial(this.materials.ground, this.materials.robot, { friction: 0.3, restitution: 0.1 }); const robot_object_cm = new CANNON.ContactMaterial(this.materials.robot, this.materials.object, { friction: 0.8, restitution: 0.1 }); const ground_object_cm = new CANNON.ContactMaterial(this.materials.ground, this.materials.object, { friction: 0.4, restitution: 0.4 }); this.world.addContactMaterial(ground_robot_cm); this.world.addContactMaterial(robot_object_cm); this.world.addContactMaterial(ground_object_cm); } addPhysicsMesh(body, mesh) { mesh.castShadow = true; mesh.receiveShadow = true; this.scene.add(mesh); this.world.addBody(body); this.physicsMeshes.push({ mesh, body }); } buildEnvironment() { // Ground const groundGeo = new THREE.PlaneGeometry(100, 100); const groundMat = new THREE.MeshStandardMaterial({ color: 0x4a4a4a, roughness: 0.8 }); const groundMesh = new THREE.Mesh(groundGeo, groundMat); groundMesh.rotation.x = -Math.PI / 2; groundMesh.receiveShadow = true; this.scene.add(groundMesh); const groundBody = new CANNON.Body({ mass: 0, material: this.materials.ground, shape: new CANNON.Plane() }); groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0); this.world.addBody(groundBody); // Movable Cube const cubeSize = 0.5; const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); const cubeMat = new THREE.MeshStandardMaterial({ color: 0x3366ff, roughness: 0.3, metalness: 0.2 }); const cubeMesh = new THREE.Mesh(cubeGeo, cubeMat); const cubeBody = new CANNON.Body({ mass: 2, material: this.materials.object, shape: new CANNON.Box(new CANNON.Vec3(cubeSize/2, cubeSize/2, cubeSize/2)) }); cubeBody.position.set(2, cubeSize / 2, 0); this.movableCube = cubeBody; this.addPhysicsMesh(cubeBody, cubeMesh); // Container const containerPos = new CANNON.Vec3(-3, 0, 0); const wallThickness = 0.1; const wallHeight = 1; const containerSize = 1.2; const containerMat = new THREE.MeshStandardMaterial({ color: 0xff3333, roughness: 0.6 }); const parts = [ { size: [containerSize, wallThickness, containerSize], pos: [0, wallThickness/2, 0] }, // Bottom { size: [wallThickness, wallHeight, containerSize], pos: [-containerSize/2, wallHeight/2, 0] }, // Wall { size: [wallThickness, wallHeight, containerSize], pos: [containerSize/2, wallHeight/2, 0] }, // Wall { size: [containerSize, wallHeight, wallThickness], pos: [0, wallHeight/2, -containerSize/2] }, // Wall { size: [containerSize, wallHeight, wallThickness], pos: [0, wallHeight/2, containerSize/2] } // Wall ]; parts.forEach(part => { const geo = new THREE.BoxGeometry(part.size[0], part.size[1], part.size[2]); const mesh = new THREE.Mesh(geo, containerMat); const body = new CANNON.Body({ mass: 0, material: this.materials.ground, shape: new CANNON.Box(new CANNON.Vec3(part.size[0]/2, part.size[1]/2, part.size[2]/2)) }); body.position.set(containerPos.x + part.pos[0], containerPos.y + part.pos[1], containerPos.z + part.pos[2]); body.quaternion.copy(mesh.quaternion); this.addPhysicsMesh(body, mesh); }); } buildRobotArm() { const robotMaterial = new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.2, metalness: 0.8 }); const jointMaterial = new THREE.MeshStandardMaterial({ color: 0x444444, roughness: 0.5, metalness: 0.5 }); const gripperMaterial = new THREE.MeshStandardMaterial({ color: 0xffaa00, roughness: 0.4 }); const createSegment = (shape, material, mass, position) => { const body = new CANNON.Body({ mass, shape, material: this.materials.robot }); body.position.copy(position); let geometry; if(shape instanceof CANNON.Box) { geometry = new THREE.BoxGeometry(shape.halfExtents.x * 2, shape.halfExtents.y * 2, shape.halfExtents.z * 2); } else if (shape instanceof CANNON.Cylinder) { geometry = new THREE.CylinderGeometry(shape.radiusTop, shape.radiusBottom, shape.height, shape.numSegments); } const mesh = new THREE.Mesh(geometry, material); this.addPhysicsMesh(body, mesh); this.arm.bodies.push(body); return body; }; const armData = [ { shape: new CANNON.Cylinder(0.25, 0.25, 0.2, 16), pos: new CANNON.Vec3(0, 0.1, 0), mass: 0, material: jointMaterial }, // 0 Base (static) { shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.1, 0.1)), pos: new CANNON.Vec3(0.4, 0.3, 0), material: robotMaterial }, // 1 Shoulder { shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.1, 0.1)), pos: new CANNON.Vec3(1.1, 0.3, 0), material: robotMaterial }, // 2 Forearm { shape: new CANNON.Box(new CANNON.Vec3(0.1, 0.1, 0.3)), pos: new CANNON.Vec3(1.7, 0.3, 0), material: robotMaterial }, // 3 Wrist { shape: new CANNON.Cylinder(0.08, 0.08, 0.2, 16), pos: new CANNON.Vec3(1.7, 0.3, 0.4), material: jointMaterial }, // 4 Flange ]; const base = createSegment(armData[0].shape, armData[0].material, armData[0].mass, armData[0].pos); const shoulder = createSegment(armData[1].shape, armData[1].material, 1, armData[1].pos); const forearm = createSegment(armData[2].shape, armData[2].material, 1, armData[2].pos); const wrist = createSegment(armData[3].shape, armData[3].material, 0.5, armData[3].pos); const flange = createSegment(armData[4].shape, armData[4].material, 0.2, armData[4].pos); // Joint definitions [body1, body2, pivot1, pivot2, axis1] const joints = [ // J1: Base Yaw (Sway/Yaw) { b1: base, b2: shoulder, p1: new CANNON.Vec3(0, 0.1, 0), p2: new CANNON.Vec3(-0.4, 0, 0), axis: new CANNON.Vec3(0, 1, 0), limits: [-Math.PI, Math.PI]}, // J2: Shoulder Pitch (Heave) { b1: shoulder, b2: forearm, p1: new CANNON.Vec3(0.4, 0, 0), p2: new CANNON.Vec3(-0.4, 0, 0), axis: new CANNON.Vec3(0, 0, 1), limits: [-Math.PI/2, Math.PI/2]}, // J3: Elbow Pitch (Surge) { b1: forearm, b2: wrist, p1: new CANNON.Vec3(0.4, 0, 0), p2: new CANNON.Vec3(0, 0, -0.2), axis: new CANNON.Vec3(0, 0, 1), limits: [-Math.PI/2, Math.PI/2]}, // J4: Wrist Roll { b1: wrist, b2: flange, p1: new CANNON.Vec3(0, 0, 0.2), p2: new CANNON.Vec3(0, 0, -0.1), axis: new CANNON.Vec3(0, 1, 0), limits: [-Infinity, Infinity]}, // J5: Wrist Pitch { b1: flange, b2: null, p1: new CANNON.Vec3(0, 0, 0.1), p2: new CANNON.Vec3(0,0,0), axis: new CANNON.Vec3(1, 0, 0), limits: [-Math.PI/2, Math.PI/2]}, ]; let lastBody = wrist; let wristPitchJoint = new CANNON.HingeConstraint(wrist, flange, { pivotA: new CANNON.Vec3(0, 0, 0.2), pivotB: new CANNON.Vec3(0, 0, 0), axisA: new CANNON.Vec3(1, 0, 0), axisB: new CANNON.Vec3(1, 0, 0) }); // This is a bit tricky, the wrist part has two joints originating from it. // Let's create a small intermediate part for J4->J5->J6 const wrist1 = lastBody; const wrist2_pos = wrist1.position.clone().vadd(new CANNON.Vec3(0.1, 0, 0)); const wrist2 = createSegment(new CANNON.Box(new CANNON.Vec3(0.05, 0.05, 0.05)), jointMaterial, 0.2, wrist2_pos); const wrist3_pos = wrist2.position.clone().vadd(new CANNON.Vec3(0.1, 0, 0)); const wrist3 = createSegment(new CANNON.Box(new CANNON.Vec3(0.05, 0.1, 0.05)), jointMaterial, 0.2, wrist3_pos); this.arm.bodies.pop(); this.arm.bodies.pop(); // Remove wrist2, wrist3 placeholders from main segment list const finalJoints = [ // J1: base -> shoulder { b1: base, b2: shoulder, p1: new CANNON.Vec3(0, 0.1, 0), p2: new CANNON.Vec3(-0.4, 0, 0), axis: new CANNON.Vec3(0, 1, 0), limits: [-Math.PI, Math.PI]}, // J2: shoulder -> forearm { b1: shoulder, b2: forearm, p1: new CANNON.Vec3(0.4, 0, 0), p2: new CANNON.Vec3(-0.4, 0, 0), axis: new CANNON.Vec3(0, 0, 1), limits: [-Math.PI/2, Math.PI/2]}, // J3: forearm -> wrist1 { b1: forearm, b2: wrist1, p1: new CANNON.Vec3(0.4, 0, 0), p2: new CANNON.Vec3(0, 0, -0.2), axis: new CANNON.Vec3(0, 0, 1), limits: [-Math.PI/2, Math.PI/2]}, // J4: wrist1 -> wrist2 (Roll) { b1: wrist1, b2: wrist2, p1: new CANNON.Vec3(0, 0, 0.2), p2: new CANNON.Vec3(-0.1, 0, 0), axis: new CANNON.Vec3(0, 1, 0), limits: [-Infinity, Infinity]}, // J5: wrist2 -> wrist3 (Pitch) { b1: wrist2, b2: wrist3, p1: new CANNON.Vec3(0.1, 0, 0), p2: new CANNON.Vec3(0, 0, 0), axis: new CANNON.Vec3(1, 0, 0), limits: [-Math.PI/2, Math.PI/2]}, // J6: wrist3 -> flange (Roll) { b1: wrist3, b2: flange, p1: new CANNON.Vec3(0, 0.1, 0), p2: new CANNON.Vec3(0, 0, -0.1), axis: new CANNON.Vec3(0, 0, 1), limits: [-Infinity, Infinity]}, ]; finalJoints.forEach(j => { const constraint = new CANNON.HingeConstraint(j.b1, j.b2, { pivotA: j.p1, pivotB: j.p2, axisA: j.axis, axisB: j.axis, }); constraint.lowerLimit = j.limits[0]; constraint.upperLimit = j.limits[1]; this.world.addConstraint(constraint); constraint.enableMotor(); this.arm.joints.push(constraint); }); // Gripper Fingers const fingerShape = new CANNON.Box(new CANNON.Vec3(0.05, 0.2, 0.05)); const fingerMass = 0.1; this.gripper.leftFinger = createSegment(fingerShape, gripperMaterial, fingerMass, flange.position.clone().vadd(new CANNON.Vec3(-0.15, 0, 0.1))); this.gripper.rightFinger = createSegment(fingerShape, gripperMaterial, fingerMass, flange.position.clone().vadd(new CANNON.Vec3(0.15, 0, 0.1))); // Lock fingers to flange const leftLock = new CANNON.LockConstraint(flange, this.gripper.leftFinger); const rightLock = new CANNON.LockConstraint(flange, this.gripper.rightFinger); this.world.addConstraint(leftLock); this.world.addConstraint(rightLock); } setupUI() { const controlsContainer = document.getElementById('controls'); const jointLabels = [ { name: 'Sway / Yaw (Base)', min: -180, max: 180, default: -90 }, { name: 'Heave (Shoulder)', min: -90, max: 90, default: 45 }, { name: 'Surge (Elbow)', min: -90, max: 90, default: 45 }, { name: 'Wrist Roll', min: -180, max: 180, default: 0 }, { name: 'Pitch (Wrist)', min: -90, max: 90, default: 45 }, { name: 'Flange Roll', min: -180, max: 180, default: 0 }, ]; jointLabels.forEach((joint, i) => { const row = document.createElement('div'); row.className = 'control-row'; const label = document.createElement('label'); label.textContent = joint.name; const slider = document.createElement('input'); slider.type = 'range'; slider.min = joint.min; slider.max = joint.max; slider.value = joint.default; slider.addEventListener('input', (e) => { this.arm.controlValues[i] = THREE.MathUtils.degToRad(parseFloat(e.target.value)); }); row.appendChild(label); row.appendChild(slider); controlsContainer.appendChild(row); }); // Gripper control const row = document.createElement('div'); row.className = 'control-row'; const label = document.createElement('label'); label.textContent = 'Gripper'; const slider = document.createElement('input'); slider.type = 'range'; slider.min = 0; slider.max = 1; slider.value = 0; slider.step = 1; slider.addEventListener('input', (e) => { // Update state, logic will be handled in animate loop this.arm.gripperState = parseInt(e.target.value); }); const gripperLabels = document.createElement('div'); gripperLabels.className = 'gripper-label'; gripperLabels.innerHTML = `<span>Open</span><span>Close</span>`; row.appendChild(label); const sliderContainer = document.createElement('div'); sliderContainer.style.flexGrow = 1; sliderContainer.appendChild(slider); sliderContainer.appendChild(gripperLabels); row.appendChild(sliderContainer); controlsContainer.appendChild(row); } updateGripper() { const isClosing = this.arm.gripperState === 1; const isHolding = this.gripper.constraints.length > 0; if (isClosing && !isHolding) { // Attempt to grab const { leftFinger, rightFinger } = this.gripper; const cubeBody = this.movableCube; const leftPos = leftFinger.position; const rightPos = rightFinger.position; const cubePos = cubeBody.position; // Simple check: is cube between fingers? const midPoint = leftPos.clone().vadd(rightPos).scale(0.5); const distToCube = midPoint.distanceTo(cubePos); // Check if cube is close enough and roughly aligned if (distToCube < 0.5) { const c1 = new CANNON.PointToPointConstraint(leftFinger, new CANNON.Vec3(0, -0.1, 0.05), cubeBody, new CANNON.Vec3(-0.25, 0, 0)); const c2 = new CANNON.PointToPointConstraint(rightFinger, new CANNON.Vec3(0, -0.1, 0.05), cubeBody, new CANNON.Vec3(0.25, 0, 0)); this.world.addConstraint(c1); this.world.addConstraint(c2); this.gripper.constraints.push(c1, c2); } } else if (!isClosing && isHolding) { // Release this.gripper.constraints.forEach(c => this.world.removeConstraint(c)); this.gripper.constraints = []; } } onWindowResize() { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); } animate() { requestAnimationFrame(() => this.animate()); const time = performance.now() / 1000; if (!this.lastCallTime) { this.world.step(this.timeStep); } else { const dt = time - this.lastCallTime; this.world.step(this.timeStep, dt); } this.lastCallTime = time; // Update motor targets this.arm.joints.forEach((joint, i) => { const targetAngle = this.arm.controlValues[i]; const currentAngle = joint.getAngle(); const error = targetAngle - currentAngle; const speed = THREE.MathUtils.clamp(error * 5, -2, 2); // Simple P-controller joint.setMotorSpeed(speed); joint.motorMaxForce = 300; }); // Update gripper this.updateGripper(); // Sync physics bodies with three.js meshes for (const pm of this.physicsMeshes) { pm.mesh.position.copy(pm.body.position); pm.mesh.quaternion.copy(pm.body.quaternion); } this.controls.update(); this.renderer.render(this.scene, this.camera); } } new RobotSimulation(); </script> </body> </html> ```
Here's a 3D physics simulation of a 6-DOF robotic arm using Three.js and Cannon.js: ```html <!DOCTYPE html> <html> <head> <title>6-DOF Robotic Arm Simulation</title> <style> body { margin: 0; overflow: hidden; } #canvas { width: 100%; height: 100%; } .controls { position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.7); padding: 10px; } .slider-container { margin: 5px; } </style> </head> <body> <div class="controls"> <div class="slider-container"> <label>Surge: </label> <input type="range" id="surge" min="-90" max="90" value="0"> <span id="surgeValue">0</span> </div> <div class="slider-container"> <label>Heave: </label> <input type="range" id="heave" min="-90" max="90" value="0"> <span id="heaveValue">0</span> </div> <div class="slider-container"> <label>Sway: </label> <input type="range" id="sway" min="-90" max="90" value="0"> <span id="swayValue">0</span> </div> <div class="slider-container"> <label>Pitch: </label> <input type="range" id="pitch" min="-90" max="90" value="0"> <span id="pitchValue">0</span> </div> <div class="slider-container"> <label>Yaw: </label> <input type="range" id="yaw" min="-90" max="90" value="0"> <span id="yawValue">0</span> </div> <div class="slider-container"> <label>Roll: </label> <input type="range" id="roll" min="-90" max="90" value="0"> <span id="rollValue">0</span> </div> </div> <canvas id="canvas"></canvas> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> <script> let scene, camera, renderer; let world; let arm = []; let joints = []; let cube, container; function init() { // Initialize Three.js scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 5, 10); camera.lookAt(0, 0, 0); renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x87CEEB); // Add lights scene.add(new THREE.AmbientLight(0x404040)); const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(1, 1, 1).normalize(); scene.add(light); // Initialize physics world = new CANNON.World(); world.gravity.set(0, -9.82, 0); world.broadphase = new CANNON.NaiveBroadphase(); createContainer(); createCube(); createArm(); setupSliders(); animate(); } function createContainer() { // Container visual const geometry = new THREE.BoxGeometry(2, 1, 2); const material = new THREE.MeshPhongMaterial({ color: 0x8B4513 }); container = new THREE.Mesh(geometry, material); container.position.set(0, -2, 0); scene.add(container); // Container physics const shape = new CANNON.Box(new CANNON.Vec3(1, 0.5, 1)); const body = new CANNON.Body({ mass: 0 }); body.addShape(shape); body.position.set(0, -2, 0); world.addBody(body); container.userData.body = body; } function createCube() { // Cube visual const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5); const material = new THREE.MeshPhongMaterial({ color: 0xff0000 }); cube = new THREE.Mesh(geometry, material); cube.position.set(3, 0, 0); scene.add(cube); // Cube physics const shape = new CANNON.Box(new CANNON.Vec3(0.25, 0.25, 0.25)); const body = new CANNON.Body({ mass: 1 }); body.addShape(shape); body.position.set(3, 0, 0); world.addBody(body); cube.userData.body = body; } function createArm() { // Base const base = new THREE.Mesh( new THREE.BoxGeometry(1, 0.5, 1), new THREE.MeshPhongMaterial({ color: 0x808080 }) ); base.position.set(0, 0, 0); scene.add(base); const baseBody = new CANNON.Body({ mass: 0 }); baseBody.addShape(new CANNON.Box(new CANNON.Vec3(0.5, 0.25, 0.5))); baseBody.position.copy(base.position); world.addBody(baseBody); arm.push({ mesh: base, body: baseBody }); // Link 1 (Surge) const link1 = new THREE.Mesh( new THREE.CylinderGeometry(0.2, 0.2, 1, 16), new THREE.MeshPhongMaterial({ color: 0x00ff00 }) ); link1.rotation.x = Math.PI/2; link1.position.set(0, 0.75, 0); scene.add(link1); const link1Body = new CANNON.Body({ mass: 1 }); link1Body.addShape(new CANNON.Cylinder(new CANNON.Vec3(0.2, 0.2, 0.5), 16)); link1Body.position.copy(link1.position); link1Body.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI/2); world.addBody(link1Body); arm.push({ mesh: link1, body: link1Body }); // Joint 1 (Surge) const joint1 = new CANNON.HingeConstraint(baseBody, link1Body, { pivotA: new CANNON.Vec3(0, 0.5, 0), axisA: new CANNON.Vec3(0, 0, 1), pivotB: new CANNON.Vec3(0, -0.5, 0), axisB: new CANNON.Vec3(0, 0, 1) }); world.addConstraint(joint1); joints.push(joint1); // Link 2 (Heave) const link2 = new THREE.Mesh( new THREE.CylinderGeometry(0.15, 0.15, 1, 16), new THREE.MeshPhongMaterial({ color: 0x0000ff }) ); link2.rotation.z = Math.PI/2; link2.position.set(0, 1.5, 0); scene.add(link2); const link2Body = new CANNON.Body({ mass: 1 }); link2Body.addShape(new CANNON.Cylinder(new CANNON.Vec3(0.15, 0.15, 0.5), 16)); link2Body.position.copy(link2.position); link2Body.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 0, 1), Math.PI/2); world.addBody(link2Body); arm.push({ mesh: link2, body: link2Body }); // Joint 2 (Heave) const joint2 = new CANNON.HingeConstraint(link1Body, link2Body, { pivotA: new CANNON.Vec3(0, 0.5, 0), axisA: new CANNON.Vec3(0, 1, 0), pivotB: new CANNON.Vec3(0, -0.5, 0), axisB: new CANNON.Vec3(0, 1, 0) }); world.addConstraint(joint2); joints.push(joint2); // Link 3 (Sway) const link3 = new THREE.Mesh( new THREE.CylinderGeometry(0.1, 0.1, 1, 16), new THREE.MeshPhongMaterial({ color: 0xffff00 }) ); link3.rotation.x = Math.PI/2; link3.position.set(0, 2.25, 0); scene.add(link3); const link3Body = new CANNON.Body({ mass: 1 }); link3Body.addShape(new CANNON.Cylinder(new CANNON.Vec3(0.1, 0.1, 0.5), 16)); link3Body.position.copy(link3.position); link3Body.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI/2); world.addBody(link3Body); arm.push({ mesh: link3, body: link3Body }); // Joint 3 (Sway) const joint3 = new CANNON.HingeConstraint(link2Body, link3Body, { pivotA: new CANNON.Vec3(0, 0.5, 0), axisA: new CANNON.Vec3(0, 1, 0), pivotB: new CANNON.Vec3(0, -0.5, 0), axisB: new CANNON.Vec3(0, 1, 0) }); world.addConstraint(joint3); joints.push(joint3); // Link 4 (Pitch) const link4 = new THREE.Mesh( new THREE.CylinderGeometry(0.08, 0.08, 0.8, 16), new THREE.MeshPhongMaterial({ color: 0xff00ff }) ); link4.rotation.z = Math.PI/2; link4.position.set(0, 2.9, 0); scene.add(link4); const link4Body = new CANNON.Body({ mass: 1 }); link4Body.addShape(new CANNON.Cylinder(new CANNON.Vec3(0.08, 0.08, 0.4), 16)); link4Body.position.copy(link4.position); link4Body.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 0, 1), Math.PI/2); world.addBody(link4Body); arm.push({ mesh: link4, body: link4Body }); // Joint 4 (Pitch) const joint4 = new CANNON.HingeConstraint(link3Body, link4Body, { pivotA: new CANNON.Vec3(0, 0.5, 0), axisA: new CANNON.Vec3(0, 1, 0), pivotB: new CANNON.Vec3(0, -0.4, 0), axisB: new CANNON.Vec3(0, 1, 0) }); world.addConstraint(joint4); joints.push(joint4); // Link 5 (Yaw) const link5 = new THREE.Mesh( new THREE.BoxGeometry(0.2, 0.2, 0.2), new THREE.MeshPhongMaterial({ color: 0x00ffff }) ); link5.position.set(0, 3.4, 0); scene.add(link5); const link5Body = new CANNON.Body({ mass: 1 }); link5Body.addShape(new CANNON.Box(new CANNON.Vec3(0.1, 0.1, 0.1))); link5Body.position.copy(link5.position); world.addBody(link5Body); arm.push({ mesh: link5, body: link5Body }); // Joint 5 (Yaw) const joint5 = new CANNON.HingeConstraint(link4Body, link5Body, { pivotA: new CANNON.Vec3(0, 0.4, 0), axisA: new CANNON.Vec3(1, 0, 0), pivotB: new CANNON.Vec3(0, -0.1, 0), axisB: new CANNON.Vec3(1, 0, 0) }); world.addConstraint(joint5); joints.push(joint5); // Link 6 (Roll) const link6 = new THREE.Mesh( new THREE.SphereGeometry(0.1), new THREE.MeshPhongMaterial({ color: 0xffa500 }) ); link6.position.set(0, 3.6, 0); scene.add(link6); const link6Body = new CANNON.Body({ mass: 0.5 }); link6Body.addShape(new CANNON.Sphere(0.1)); link6Body.position.copy(link6.position); world.addBody(link6Body); arm.push({ mesh: link6, body: link6Body }); // Joint 6 (Roll) const joint6 = new CANNON.HingeConstraint(link5Body, link6Body, { pivotA: new CANNON.Vec3(0, 0.1, 0), axisA: new CANNON.Vec3(0, 0, 1), pivotB: new CANNON.Vec3(0, -0.1, 0), axisB: new CANNON.Vec3(0, 0, 1) }); world.addConstraint(joint6); joints.push(joint6); } function setupSliders() { const sliders = [ 'surge', 'heave', 'sway', 'pitch', 'yaw', 'roll' ]; sliders.forEach((id, i) => { document.getElementById(id).addEventListener('input', function(e) { const value = e.target.value * Math.PI / 180; document.getElementById(id+'Value').textContent = e.target.value; if (joints[i]) joints[i].angle = value; }); }); } function animate() { requestAnimationFrame(animate); world.step(1/60); // Update arm positions arm.forEach(link => { link.mesh.position.copy(link.body.position); link.mesh.quaternion.copy(link.body.quaternion); }); // Update cube position cube.position.copy(cube.userData.body.position); cube.quaternion.copy(cube.userData.body.quaternion); renderer.render(scene, camera); } init(); </script> </body> </html> ``` This simulation creates a 6-DOF robotic arm with: - Base rotation (surge) - Shoulder movement (heave) - Elbow movement (sway) - Wrist pitch - Wrist yaw - Wrist roll Controls: - Use the sliders to adjust each degree of freedom - The arm can interact with the red cube - The brown container is the target area The arm is built using cylindrical segments and a spherical end-effector. Each joint is controlled by a slider that adjusts the rotation angle. The physics simulation handles collisions and gravity. To use: 1. Adjust the sliders to move the arm 2. Try to pick up the red cube 3. Move it into the container The arm segments are color-coded for easy identification: - Base: Gray - Link 1: Green - Link 2: Blue - Link 3: Yellow - Link 4: Magenta - Link 5: Cyan - End effector: Orange
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>3D Robotic Arm Physics Simulation</title> <style> html, body { margin: 0; padding: 0; overflow: hidden; background: #111; } #controls { position: absolute; top: 10px; left: 10px; background: rgba(30,30,30,0.93); color: #fff; padding: 15px 20px; border-radius: 9px; font-family: monospace; z-index: 10; } .slider-container { margin-bottom: 14px; } .slider-container label { display: inline-block; width: 65px; font-size: 13px; } .slider-container input[type="range"] { width: 120px; } #instructions { margin-top: 10px; font-size: 13px; color: #aaa; } </style> <script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.152.2/examples/js/controls/OrbitControls.js"></script> </head> <body> <div id="controls"> <div><b>Robotic Arm Controls</b></div> <div class="slider-container"> <label for="surge">Surge</label> <input type="range" min="-1" max="1" step="0.01" value="0" id="surge"> <span id="surgeVal">0.00</span> </div> <div class="slider-container"> <label for="heave">Heave</label> <input type="range" min="-1" max="1" step="0.01" value="0" id="heave"> <span id="heaveVal">0.00</span> </div> <div class="slider-container"> <label for="sway">Sway</label> <input type="range" min="-1" max="1" step="0.01" value="0" id="sway"> <span id="swayVal">0.00</span> </div> <div class="slider-container"> <label for="pitch">Pitch</label> <input type="range" min="-1" max="1" step="0.01" value="0" id="pitch"> <span id="pitchVal">0.00</span> </div> <div class="slider-container"> <label for="yaw">Yaw</label> <input type="range" min="-1" max="1" step="0.01" value="0" id="yaw"> <span id="yawVal">0.00</span> </div> <div class="slider-container"> <label for="roll">Roll</label> <input type="range" min="-1" max="1" step="0.01" value="0" id="roll"> <span id="rollVal">0.00</span> </div> <div id="instructions"> <b>Instructions:</b><br> Use the sliders to control the arm's base:<br> <b>Surge</b> (forward/back), <b>Heave</b> (up/down), <b>Sway</b> (left/right position)<br> <b>Pitch</b>, <b>Yaw</b>, <b>Roll</b> (end-effector orientation)<br> Try to grab the cube and move it into the container! </div> </div> <script> // === THREE.js scene setup === const scene = new THREE.Scene(); scene.background = new THREE.Color(0x182430); const camera = new THREE.PerspectiveCamera(65, window.innerWidth/window.innerHeight, 0.1, 120); camera.position.set(3, 5, 10); const renderer = new THREE.WebGLRenderer({antialias:true}); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Lighting scene.add(new THREE.AmbientLight(0xffffff, 0.45)); const dirLight = new THREE.DirectionalLight(0xffffff, 0.85); dirLight.position.set(10, 15, 5); scene.add(dirLight); // Controls const controls = new THREE.OrbitControls(camera, renderer.domElement); controls.target.set(0, 1.2, 0); controls.update(); // === CANNON.js World setup === const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0), }); // === Floor === const floorShape = new CANNON.Plane(); const floorBody = new CANNON.Body({ mass: 0, // Static shape: floorShape, position: new CANNON.Vec3(0, 0, 0), quaternion: new CANNON.Quaternion().setFromEuler(-Math.PI/2, 0, 0) }); world.addBody(floorBody); const floorMat = new THREE.MeshStandardMaterial({ color:0x222, roughness: 0.7 }); const floorMesh = new THREE.Mesh(new THREE.PlaneGeometry(24,24), floorMat); floorMesh.rotation.x = -Math.PI/2; scene.add(floorMesh); // === Container for cube === const container = new THREE.Group(); const wallMat = new THREE.MeshStandardMaterial({color:0x2756a0}); // 3 Sides const wall1 = new THREE.Mesh(new THREE.BoxGeometry(0.25, 1.2, 2), wallMat); wall1.position.set(-1.6, 0.6, 0); const wall2 = new THREE.Mesh(new THREE.BoxGeometry(3.2, 1.2, 0.25), wallMat); wall2.position.set(0, 0.6, 1.1); const wall3 = wall1.clone(); wall3.position.set(1.6, 0.6, 0); container.add(wall1, wall2, wall3); container.position.set(-8, 0, -3); scene.add(container); // === Cube === const cubeSize = 0.35; const cubeMat = new THREE.MeshStandardMaterial({ color:0xfacb3b }); const cubeMesh = new THREE.Mesh(new THREE.BoxGeometry(cubeSize,cubeSize,cubeSize), cubeMat); cubeMesh.castShadow = true; cubeMesh.receiveShadow = true; scene.add(cubeMesh); const cubeBody = new CANNON.Body({ mass: 0.3, position: new CANNON.Vec3(2, cubeSize/2+0.02, 0), shape: new CANNON.Box(new CANNON.Vec3(cubeSize/2, cubeSize/2, cubeSize/2)), material: new CANNON.Material("cube"), }); cubeBody.angularDamping = 0.7; world.addBody(cubeBody); // === Robotic Arm Parameters === // We'll use a simple serial chain: Base + 5 segments + Gripper const armParams = { baseHeight: 0.4, segmentLength: [0.8, 0.7, 0.55, 0.4, 0.25], segmentRadius: [0.13,0.10,0.085,0.07,0.06], gripperLength: 0.22, gripperOpen: 0.17, }; // The six degrees: let control = { surge: 0, // z heave: 0, // y sway: 0, // x pitch: 0, // x-rot yaw: 0, // y-rot roll: 0 // z-rot }; // Arm's base reference position (can be adjusted if you want) const baseInitial = {x: 0, y: armParams.baseHeight/2, z: 0}; // === Three.js: Build the arm's segments === function createCylinderSegment(length, radius, color) { return new THREE.Mesh( new THREE.CylinderGeometry(radius, radius, length, 16), new THREE.MeshStandardMaterial({color, metalness:0.45, roughness:0.4}) ); } function createGripper(length, opening, color) { // A two-fingered gripper, open by 'opening' const grp = new THREE.Group(); const thickness = 0.03, width = 0.06; for (let dir of [-1,1]) { const finger = new THREE.Mesh( new THREE.BoxGeometry(width, length, thickness), new THREE.MeshStandardMaterial({color: color, metalness:0.5, roughness:0.2}) ); finger.position.set(dir*(opening/2 + width/2), length/2, 0); grp.add(finger); } return grp; } // Build the arm's segments (each in its own group for easy transform) const armGroups = []; let armBaseGroup = new THREE.Group(), lastGroup = armBaseGroup; armBaseGroup.position.set(baseInitial.x, baseInitial.y, baseInitial.z); scene.add(armBaseGroup); for(let i=0; i<armParams.segmentLength.length; ++i) { const g = new THREE.Group(); g.rotation.z = 0; g.rotation.x = (i===0 ? 0:Math.PI/8); // Slight folded pose // Segment const color = 0x2eaeb9 + i*0x004010; const seg = createCylinderSegment(armParams.segmentLength[i], armParams.segmentRadius[i], color); seg.position.y = armParams.segmentLength[i]/2; g.add(seg); // Add next group on top g.position.y = armParams.segmentLength[i-1] || 0; lastGroup.add(g); armGroups.push(g); lastGroup = g; } // End-effector: Gripper const gripperGroup = new THREE.Group(); gripperGroup.position.y = armParams.segmentLength.slice(-1)[0]; lastGroup.add(gripperGroup); const gripper = createGripper(armParams.gripperLength, armParams.gripperOpen, 0xeeeeed); gripperGroup.add(gripper); // === Physics: The arm is kinematically controlled; we apply 6-DoF to the base and end-effector === // Note: for realism, joints and inverse kinematics would be needed, but here we "cheat" for controllability. // === INPUT HANDLING (Sliders) === const inputIds = ["surge","heave","sway","pitch","yaw","roll"]; inputIds.forEach(id=>{ document.getElementById(id).addEventListener("input",(e)=>{ control[id] = parseFloat(e.target.value); document.getElementById(id+"Val").innerText = (+e.target.value).toFixed(2); }); }); // === Animation loop === let lastTime; function animate(time){ requestAnimationFrame(animate); // Advance Cannon physics if(lastTime){ const dt = Math.min((time - lastTime)/1000, 0.022); world.step(1/60, dt, 4); } lastTime = time; // ARM BASE POSITIONING (surge/sway/heave) // Base: position in x/y/z (sway/surge/heave) armBaseGroup.position.set( baseInitial.x + control.sway*2.5, armParams.baseHeight/2 + control.heave*3, baseInitial.z + control.surge*2.5 ); // ARM END-EFFECTOR ORIENTATION (pitch/yaw/roll) // We "forward kinematic" the end segment // For realistic effector, IK would be required; here, pose all segments to get approx pose // We'll linearly interpolate through all joint's rotations for demonstration // Segments 0-2 = elevation; segments 3-4 = wrist; gripper neutral const mainAng = control.pitch*1.0, yawAng = control.yaw*1.0, rollAng = control.roll*1.2; // Base rotates for yaw armGroups[0].rotation.y = yawAng; // Yaw at first joint armGroups[0].rotation.x = mainAng*0.35; armGroups[1].rotation.x = mainAng*0.5; armGroups[2].rotation.x = mainAng*0.15; armGroups[3].rotation.x = mainAng*0.00 + Math.PI/13; armGroups[4].rotation.x = 0.07*mainAng + (rollAng/4); gripperGroup.rotation.z = rollAng; // === GRIPPER LOGIC === // If end-effector is close to cube, initiate grasp (simple logic: open/close) let eePos = new THREE.Vector3(); gripperGroup.updateMatrixWorld(); gripperGroup.localToWorld(eePos.set(0,armParams.gripperLength/2,0)); // Open/close if close to cube and approaching from above const gripDist = eePos.distanceTo(cubeMesh.position); let gripperOpenVal = armParams.gripperOpen; if (gripDist < 0.23 && (eePos.y-cubeMesh.position.y) > 0.08 && Math.abs(eePos.x-cubeMesh.position.x)<0.13 && Math.abs(eePos.z-cubeMesh.position.z)<0.13 ) { gripperOpenVal = 0.03; // Close grip // Stick cube to gripper cubeBody.type = CANNON.Body.KINEMATIC; cubeBody.velocity.setZero(); cubeBody.angularVelocity.setZero(); // Parent it visually to gripper gripperGroup.worldToLocal(eePos.copy(cubeMesh.position)); cubeMesh.position.copy(eePos); gripperGroup.add(cubeMesh); } else if (eePos.distanceTo(new THREE.Vector3(container.position.x, 0.8, container.position.z))<1.2 && gripperOpenVal<0.09) { // If in container proximity, release cube gripperOpenVal = armParams.gripperOpen; // Unparent, set at current world position let worldPos = new THREE.Vector3(); cubeMesh.getWorldPosition(worldPos); scene.add(cubeMesh); cubeMesh.position.copy(worldPos); // Reset cube physics cubeBody.type = CANNON.Body.DYNAMIC; cubeBody.position.set(worldPos.x, worldPos.y, worldPos.z); cubeBody.velocity.set(0,0,0); } // Update gripper opening for(let i=0; i<2; ++i){ let finger = gripper.children[i]; finger.position.x = (i===0?-1:1)*(gripperOpenVal/2 + 0.03); } // === SYNC PHYSICS && MESHES === // Cube if(cubeBody.type !== CANNON.Body.KINEMATIC){ cubeMesh.position.copy(cubeBody.position); cubeMesh.quaternion.copy(cubeBody.quaternion); } else { // When attached to gripper, set kinematic body to follow mesh cubeBody.position.copy(cubeMesh.getWorldPosition(new THREE.Vector3())); cubeBody.velocity.setZero(); cubeBody.angularVelocity.setZero(); } renderer.render(scene, camera); } animate(); // Respond to window resize window.addEventListener("resize",()=>{ camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Put cube on floor if dropped world.addEventListener("postStep",()=>{ if (cubeBody.position.y < cubeSize/2) { cubeBody.position.y = cubeSize/2+0.01; cubeBody.velocity.set(0,0,0); } }); </script> </body> </html> ```
To create a 3D physics simulation of a realistic robotic arm with six degrees of freedom, we can use **Three.js** for rendering and **Cannon.js** for physics simulation. Below is a complete HTML file with embedded JavaScript and CSS to achieve this. The user can control the robotic arm using input fields for surge, heave, sway, pitch, yaw, and roll. The arm can move a static cube into a container. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Robotic Arm Simulation</title> <style> body { margin: 0; } canvas { display: block; } #controls { position: absolute; top: 10px; left: 10px; background: rgba(0, 0, 0, 0.5); padding: 10px; color: white; font-family: Arial, sans-serif; } input { width: 50px; margin-right: 10px; } </style> </head> <body> <div id="controls"> <label>Surge: <input type="number" id="surge" value="0" step="0.1"></label><br> <label>Heave: <input type="number" id="heave" value="0" step="0.1"></label><br> <label>Sway: <input type="number" id="sway" value="0" step="0.1"></label><br> <label>Pitch: <input type="number" id="pitch" value="0" step="0.1"></label><br> <label>Yaw: <input type="number" id="yaw" value="0" step="0.1"></label><br> <label>Roll: <input type="number" id="roll" value="0" step="0.1"></label><br> <button onclick="applyTransform()">Apply</button> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon/0.6.2/cannon.min.js"></script> <script> // Three.js setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Cannon.js setup const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); // Create ground const groundShape = new CANNON.Plane(); const groundBody = new CANNON.Body({ mass: 0 }); groundBody.addShape(groundShape); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); world.addBody(groundBody); // Create container const containerShape = new CANNON.Box(new CANNON.Vec3(3, 1, 3)); const containerBody = new CANNON.Body({ mass: 0 }); containerBody.addShape(containerShape); containerBody.position.set(0, 0, 0); world.addBody(containerBody); const containerMesh = new THREE.Mesh( new THREE.BoxGeometry(6, 2, 6), new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }) ); scene.add(containerMesh); // Create cube const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)); const cubeBody = new CANNON.Body({ mass: 1 }); cubeBody.addShape(cubeShape); cubeBody.position.set(5, 1, 0); world.addBody(cubeBody); const cubeMesh = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshLambertMaterial({ color: 0xff0000 }) ); scene.add(cubeMesh); // Create robotic arm const armSegments = []; const armLength = 2; const numSegments = 6; for (let i = 0; i < numSegments; i++) { const geometry = new THREE.CylinderGeometry(0.2, 0.2, armLength, 32); const material = new THREE.MeshLambertMaterial({ color: 0xffff00 }); const segment = new THREE.Mesh(geometry, material); if (i === 0) { segment.position.set(0, 5, 0); } else { segment.position.set(0, armLength, 0); } armSegments.push(segment); scene.add(segment); } // Lighting const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 10, 5); scene.add(light); // Camera position camera.position.z = 15; // Animation loop function animate() { requestAnimationFrame(animate); // Update physics world world.step(1 / 60); // Update cube mesh position cubeMesh.position.copy(cubeBody.position); cubeMesh.quaternion.copy(cubeBody.quaternion); // Render scene renderer.render(scene, camera); } animate(); // Apply transform to robotic arm function applyTransform() { const surge = parseFloat(document.getElementById('surge').value); const heave = parseFloat(document.getElementById('heave').value); const sway = parseFloat(document.getElementById('sway').value); const pitch = parseFloat(document.getElementById('pitch').value) * Math.PI / 180; const yaw = parseFloat(document.getElementById('yaw').value) * Math.PI / 180; const roll = parseFloat(document.getElementById('roll').value) * Math.PI / 180; const transformMatrix = new THREE.Matrix4(); transformMatrix.makeTranslation(sway, heave, surge); const rotationMatrix = new THREE.Matrix4(); rotationMatrix.makeRotationFromEuler(new THREE.Euler(pitch, yaw, roll)); armSegments[0].matrix.identity(); armSegments[0].matrix.multiply(transformMatrix); armSegments[0].matrix.multiply(rotationMatrix); armSegments[0].matrixAutoUpdate = false; for (let i = 1; i < armSegments.length; i++) { armSegments[i].matrix.identity(); armSegments[i].matrix.multiply(armSegments[i - 1].matrix); armSegments[i].matrix.multiply(new THREE.Matrix4().makeTranslation(0, armLength, 0)); armSegments[i].matrixAutoUpdate = false; } // Update cube position based on arm's end effector const endEffectorPosition = new THREE.Vector3(); armSegments[armSegments.length - 1].getWorldPosition(endEffectorPosition); cubeBody.position.copy(endEffectorPosition); cubeBody.position.y += 0.5; // Offset to avoid interpenetration } </script> </body> </html> ``` ### Explanation: 1. **Three.js** is used for rendering the 3D scene, including the robotic arm, cube, container, and ground. 2. **Cannon.js** handles the physics simulation, ensuring realistic movement and collisions. 3. The robotic arm consists of cylindrical segments, each controlled by the user's input for surge, heave, sway, pitch, yaw, and roll. 4. The cube is moved by the arm's end effector, allowing it to be placed into the container. 5. User inputs are applied via a transformation matrix that updates the arm's position and orientation. To run this simulation, simply open the HTML file in a web browser. Use the input fields to control the robotic arm and move the cube into the container.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>6-DOF Robotic Arm Simulation</title> <style> body { margin: 0; overflow: hidden; } #controls { position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.8); padding: 10px; border-radius: 5px; } .slider { display: flex; align-items: center; margin: 5px 0; } .slider label { width: 80px; font-size: 12px; } </style> </head> <body> <div id="controls"> <div class="slider"><label>Surge</label><input type="range" min="-1" max="1" step="0.01" oninput="updateMotor(0, this.value)"></div> <div class="slider"><label>Heave</label><input type="range" min="-1" max="1" step="0.01" oninput="updateMotor(1, this.value)"></div> <div class="slider"><label>Sway</label><input type="range" min="-1" max="1" step="0.01" oninput="updateMotor(2, this.value)"></div> <div class="slider"><label>Pitch</label><input type="range" min="-1" max="1" step="0.01" oninput="updateMotor(3, this.value)"></div> <div class="slider"><label>Yaw</label><input type="range" min="-1" max="1" step="0.01" oninput="updateMotor(4, this.value)"></div> <div class="slider"><label>Roll</label><input type="range" min="-1" max="1" step="0.01" oninput="updateMotor(5, this.value)"></div> </div> <script src="https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script> <script> // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); camera.position.set(5, 5, 5); camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Physics world const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); world.broadphase = new CANNON.NaiveBroadphase(); world.solver.iterations = 10; // Materials const physicsMaterial = new CANNON.Material(); // Ground const groundShape = new CANNON.Plane(); const groundBody = new CANNON.Body({mass: 0, material: physicsMaterial}); groundBody.quaternion.setFromEuler(-Math.PI/2, 0, 0); world.addBody(groundBody); // Create arm segments const armSegments = []; const hinges = []; function createHinge(parent, child, pivot, axis) { const constraint = new CANNON.HingeConstraint(parent, child, { pivotA: pivot, pivotB: new CANNON.Vec3(0, 0, 0), axisA: axis, axisB: axis, }); constraint.enableMotor(); constraint.setMotorSpeed(0); constraint.setMaxMotorTorque(1e6); world.addConstraint(constraint); return constraint; } // Base const base = new CANNON.Body({mass: 0}); base.position.set(0, 2, 0); world.addBody(base); // Arm segments const linkLength = 0.5; for (let i = 0; i < 6; i++) { const shape = new CANNON.Box(new CANNON.Vec3(0.1, linkLength, 0.1)); const body = new CANNON.Body({mass: 1, shape: shape, material: physicsMaterial}); body.position.set(0, base.position.y - (i+1)*linkLength, 0); world.addBody(body); armSegments.push(body); const pivot = new CANNON.Vec3(0, -linkLength, 0); const axis = i % 2 === 0 ? new CANNON.Vec3(0, 1, 0) : new CANNON.Vec3(1, 0, 0); const parent = i === 0 ? base : armSegments[i-1]; hinges.push(createHinge(parent, body, pivot, axis)); } // Target cube const cubeShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)); const cubeBody = new CANNON.Body({mass: 1, shape: cubeShape, material: physicsMaterial}); cubeBody.position.set(2, 1, 0); world.addBody(cubeBody); // Container const containerShape = new CANNON.Box(new CANNON.Vec3(1, 0.5, 1)); const containerBody = new CANNON.Body({mass: 0, shape: containerShape, material: physicsMaterial}); containerBody.position.set(3, 0.5, 0); world.addBody(containerBody); // Visualization const bodyMap = new Map(); function createVisual(body, color = 0x00ff00) { const geometry = body.shapeInstances[0]?.shape?.type === CANNON.Shape.types.BOX ? new THREE.BoxGeometry(body.shape.halfExtents.x*2, body.shape.halfExtents.y*2, body.shape.halfExtents.z*2) : new THREE.SphereGeometry(body.shape.radius, 32, 32); const material = new THREE.MeshPhongMaterial({color}); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); bodyMap.set(body, mesh); } // Create visuals for all bodies [base, ...armSegments, cubeBody, containerBody].forEach(body => { createVisual(body, body === containerBody ? 0xff0000 : 0x00ff00); }); // Lighting const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 10, 7.5); scene.add(directionalLight); // Update function function updateMotor(index, value) { if (hinges[index]) { hinges[index].setMotorSpeed(parseFloat(value)); } } // Animation loop function animate() { requestAnimationFrame(animate); world.step(1/60); // Sync visuals with physics bodyMap.forEach((mesh, body) => { mesh.position.copy(body.position); mesh.quaternion.copy(body.quaternion); }); renderer.render(scene, camera); } animate(); // Handle window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script> </body> </html> ```
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Robotic Arm Simulation</title> <style> body { margin: 0; } canvas { display: block; } </style> <script src="https://cdn.jsdelivr.net/npm/three/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three/examples/js/controls/OrbitControls.js"></script> <script src="https://cdn.jsdelivr.net/npm/cannon/build/cannon.min.js"></script> </head> <body> <script> // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 3, 5); camera.lookAt(0, 1, 0); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const controls = new THREE.OrbitControls(camera, renderer.domElement); // Lights const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const pointLight = new THREE.PointLight(0xffffff, 1); pointLight.position.set(5, 5, 5); scene.add(pointLight); // Physics world const world = new CANNON.World(); world.gravity.set(0, -9.8, 0); // Floor const floorBody = new CANNON.Body({ mass: 0 }); floorBody.addShape(new CANNON.Plane()); floorBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); world.addBody(floorBody); const floorMesh = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), new THREE.MeshPhongMaterial({ color: 0xaaaaaa })); floorMesh.rotation.x = -Math.PI / 2; scene.add(floorMesh); // Cube const cubeSize = 0.5; const cubeBody = new CANNON.Body({ mass: 1 }); cubeBody.addShape(new CANNON.Box(new CANNON.Vec3(cubeSize, cubeSize, cubeSize))); cubeBody.position.set(3, cubeSize + 0.1, 0); world.addBody(cubeBody); const cubeMesh = new THREE.Mesh(new THREE.BoxGeometry(cubeSize * 2, cubeSize * 2, cubeSize * 2), new THREE.MeshPhongMaterial({ color: 0xff0000 })); scene.add(cubeMesh); // Container (simple box with walls) const containerPos = new THREE.Vector3(-3, 0, 0); const containerSize = 2; const wallHeight = 1; const wallThickness = 0.1; // Bottom const bottomBody = new CANNON.Body({ mass: 0 }); bottomBody.addShape(new CANNON.Box(new CANNON.Vec3(containerSize / 2, wallThickness / 2, containerSize / 2))); bottomBody.position.set(containerPos.x, wallThickness / 2, containerPos.z); world.addBody(bottomBody); const bottomMesh = new THREE.Mesh(new THREE.BoxGeometry(containerSize, wallThickness, containerSize), new THREE.MeshPhongMaterial({ color: 0x00ff00 })); bottomMesh.position.copy(containerPos); bottomMesh.position.y = wallThickness / 2; scene.add(bottomMesh); // Walls (4 sides) const wallPositions = [ new CANNON.Vec3(containerPos.x, wallHeight / 2 + wallThickness / 2, containerPos.z + containerSize / 2 + wallThickness / 2), // front new CANNON.Vec3(containerPos.x, wallHeight / 2 + wallThickness / 2, containerPos.z - containerSize / 2 - wallThickness / 2), // back new CANNON.Vec3(containerPos.x + containerSize / 2 + wallThickness / 2, wallHeight / 2 + wallThickness / 2, containerPos.z), // right new CANNON.Vec3(containerPos.x - containerSize / 2 - wallThickness / 2, wallHeight / 2 + wallThickness / 2, containerPos.z) // left ]; const wallQuats = [ new CANNON.Quaternion().setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2), new CANNON.Quaternion().setFromAxisAngle(new CANNON.Vec3(1, 0, 0), Math.PI / 2), new CANNON.Quaternion().setFromAxisAngle(new CANNON.Vec3(0, 0, 1), Math.PI / 2), new CANNON.Quaternion().setFromAxisAngle(new CANNON.Vec3(0, 0, 1), Math.PI / 2) ]; const wallDims = [ new CANNON.Vec3(containerSize / 2, wallHeight / 2, wallThickness / 2), new CANNON.Vec3(containerSize / 2, wallHeight / 2, wallThickness / 2), new CANNON.Vec3(wallThickness / 2, wallHeight / 2, containerSize / 2), new CANNON.Vec3(wallThickness / 2, wallHeight / 2, containerSize / 2) ]; for (let i = 0; i < 4; i++) { const wallBody = new CANNON.Body({ mass: 0 }); wallBody.addShape(wallDims[i]); wallBody.position.copy(wallPositions[i]); wallBody.quaternion.copy(wallQuats[i]); world.addBody(wallBody); const wallMesh = new THREE.Mesh(new THREE.BoxGeometry(wallDims[i].x * 2, wallDims[i].y * 2, wallDims[i].z * 2), new THREE.MeshPhongMaterial({ color: 0x00ff00 })); wallMesh.position.set(wallPositions[i].x, wallPositions[i].y, wallPositions[i].z); wallMesh.quaternion.set(wallQuats[i].x, wallQuats[i].y, wallQuats[i].z, wallQuats[i].w); scene.add(wallMesh); } // Robotic arm setup const armMaterial = new THREE.MeshPhongMaterial({ color: 0x156289 }); const base = new THREE.Object3D(); scene.add(base); const joints = []; const jointAxes = ['y', 'x', 'x', 'y', 'x', 'y']; // Axes for each joint let current = base; // Joint 0 and Link 0 (base to first joint) joints[0] = new THREE.Object3D(); current.add(joints[0]); current = joints[0]; const link0 = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 0.5, 12), armMaterial); link0.position.y = 0.25; current.add(link0); // Joint 1 and Link 1 joints[1] = new THREE.Object3D(); joints[1].position.y = 0.5; current.add(joints[1]); current = joints[1]; const link1 = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 1, 12), armMaterial); link1.rotation.x = Math.PI / 2; link1.position.z = 0.5; current.add(link1); // Joint 2 and Link 2 joints[2] = new THREE.Object3D(); joints[2].position.z = 1; current.add(joints[2]); current = joints[2]; const link2 = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 1, 12), armMaterial); link2.rotation.x = Math.PI / 2; link2.position.z = 0.5; current.add(link2); // Joint 3 and Link 3 joints[3] = new THREE.Object3D(); joints[3].position.z = 1; current.add(joints[3]); current = joints[3]; const link3 = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 0.5, 12), armMaterial); link3.rotation.x = Math.PI / 2; link3.position.z = 0.25; current.add(link3); // Joint 4 and Link 4 joints[4] = new THREE.Object3D(); joints[4].position.z = 0.5; current.add(joints[4]); current = joints[4]; const link4 = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 0.5, 12), armMaterial); link4.rotation.x = Math.PI / 2; link4.position.z = 0.25; current.add(link4); // Joint 5 and Link 5 (end effector) joints[5] = new THREE.Object3D(); joints[5].position.z = 0.5; current.add(joints[5]); current = joints[5]; const endEffector = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.1, 0.3), new THREE.MeshPhongMaterial({ color: 0xff9900 })); endEffector.position.z = 0.15; current.add(endEffector); // Initial joint angles const angles = [0, 0, Math.PI / 4, 0, 0, 0]; // Starting pose joints.forEach((j, i) => { const axis = jointAxes[i]; if (axis === 'x') j.rotation.x = angles[i]; else if (axis === 'y') j.rotation.y = angles[i]; else if (axis === 'z') j.rotation.z = angles[i]; }); // User controls const keys = {}; window.addEventListener('keydown', (e) => { keys[e.key.toLowerCase()] = true; }); window.addEventListener('keyup', (e) => { keys[e.key.toLowerCase()] = false; }); const linSpeed = 2; // m/s const angSpeed = Math.PI; // rad/s // Grab variables let grabbed = false; let grabPressed = false; const grabOffset = new THREE.Vector3(); const grabQuat = new THREE.Quaternion(); // Matrix invert function (Gauss-Jordan) function invertMatrix(m) { const n = m.length; const a = []; for (let i = 0; i < n; i++) { a[i] = m[i].slice(); for (let j = 0; j < n; j++) a[i][n + j] = (i === j ? 1 : 0); } for (let i = 0; i < n; i++) { let pivot = i; for (let j = i; j < n; j++) { if (Math.abs(a[j][i]) > Math.abs(a[pivot][i])) pivot = j; } [a[i], a[pivot]] = [a[pivot], a[i]]; if (Math.abs(a[i][i]) < 1e-10) return null; const div = a[i][i]; for (let j = 0; j < 2 * n; j++) a[i][j] /= div; for (let k = 0; k < n; k++) { if (k !== i) { const factor = a[k][i]; for (let j = 0; j < 2 * n; j++) a[k][j] -= factor * a[i][j]; } } } const inv = []; for (let i = 0; i < n; i++) inv[i] = a[i].slice(n); return inv; } // Get/set rotation function getRotation(j, axis) { if (axis === 'x') return j.rotation.x; if (axis === 'y') return j.rotation.y; if (axis === 'z') return j.rotation.z; } function setRotation(j, axis, val) { if (axis === 'x') j.rotation.x = val; if (axis === 'y') j.rotation.y = val; if (axis === 'z') j.rotation.z = val; } // Animation loop const clock = new THREE.Clock(); const tempVec = new THREE.Vector3(); const tempQuat = new THREE.Quaternion(); function animate() { requestAnimationFrame(animate); const dt = clock.getDelta(); // User inputs (keys: surge w/s, sway a/d, heave q/e, pitch i/k, yaw j/l, roll u/o) const control = { surge: (keys['w'] ? 1 : 0) - (keys['s'] ? 1 : 0), sway: (keys['a'] ? 1 : 0) - (keys['d'] ? 1 : 0), heave: (keys['q'] ? 1 : 0) - (keys['e'] ? 1 : 0), pitch: (keys['i'] ? 1 : 0) - (keys['k'] ? 1 : 0), yaw: (keys['j'] ? 1 : 0) - (keys['l'] ? 1 : 0), roll: (keys['u'] ? 1 : 0) - (keys['o'] ? 1 : 0) }; // Compute v_ee and w_ee in EE frame const v_ee = new THREE.Vector3(control.sway * linSpeed, control.heave * linSpeed, control.surge * linSpeed); const w_ee = new THREE.Vector3(control.pitch * angSpeed, control.yaw * angSpeed, control.roll * angSpeed); // Get current EE pose endEffector.updateMatrixWorld(true); const eePos = new THREE.Vector3().setFromMatrixPosition(endEffector.matrixWorld); const eeQuat = new THREE.Quaternion().setFromRotationMatrix(endEffector.matrixWorld); // Transform to base frame const v_base = v_ee.clone().applyQuaternion(eeQuat); const w_base = w_ee.clone().applyQuaternion(eeQuat); const velVec = [v_base.x, v_base.y, v_base.z, w_base.x, w_base.y, w_base.z]; // Compute Jacobian numerically const epsilon = 0.001; const J = Array.from({ length: 6 }, () => Array(6).fill(0)); const originalAngles = joints.map((j, i) => getRotation(j, jointAxes[i])); for (let i = 0; i < 6; i++) { setRotation(joints[i], jointAxes[i], originalAngles[i] + epsilon); endEffector.updateMatrixWorld(true); const posNew = new THREE.Vector3().setFromMatrixPosition(endEffector.matrixWorld); const quatNew = new THREE.Quaternion().setFromRotationMatrix(endEffector.matrixWorld); const deltaPos = posNew.clone().sub(eePos).divideScalar(epsilon); J[0][i] = deltaPos.x; J[1][i] = deltaPos.y; J[2][i] = deltaPos.z; let deltaQuat = quatNew.multiply(eeQuat.clone().conjugate()); deltaQuat.normalize(); const theta = 2 * Math.acos(Math.max(-1, Math.min(1, deltaQuat.w))); let w = new THREE.Vector3(0, 0, 0); if (theta > 1e-6) { const sinTheta2 = Math.sin(theta / 2); w.set(deltaQuat.x / sinTheta2, deltaQuat.y / sinTheta2, deltaQuat.z / sinTheta2); w.multiplyScalar(theta / epsilon); } else { w.set(deltaQuat.x * 2 / epsilon, deltaQuat.y * 2 / epsilon, deltaQuat.z * 2 / epsilon); } J[3][i] = w.x; J[4][i] = w.y; J[5][i] = w.z; setRotation(joints[i], jointAxes[i], originalAngles[i]); } endEffector.updateMatrixWorld(true); // Invert Jacobian const invJ = invertMatrix(J); if (invJ) { // Compute dot_q const dotQ = []; for (let row = 0; row < 6; row++) { dotQ[row] = 0; for (let col = 0; col < 6; col++) { dotQ[row] += invJ[row][col] * velVec[col]; } } // Update joint angles joints.forEach((j, i) => { const axis = jointAxes[i]; const newAngle = getRotation(j, axis) + dotQ[i] * dt; setRotation(j, axis, newAngle); }); } // Handle grabbing (press 'g' to toggle) if (keys['g'] && !grabPressed) { grabPressed = true; if (!grabbed) { endEffector.updateMatrixWorld(true); const eePosNow = new THREE.Vector3().setFromMatrixPosition(endEffector.matrixWorld); const cubePosNow = cubeMesh.position; if (eePosNow.distanceTo(cubePosNow) < cubeSize * 1.5) { grabbed = true; const eeQuatNow = new THREE.Quaternion().setFromRotationMatrix(endEffector.matrixWorld); grabOffset.copy(cubePosNow).sub(eePosNow).applyQuaternion(eeQuatNow.clone().conjugate()); grabQuat.copy(cubeMesh.quaternion).multiply(eeQuatNow.clone().conjugate()); cubeBody.mass = 0; cubeBody.updateMassProperties(); } } else { grabbed = false; cubeBody.mass = 1; cubeBody.updateMassProperties(); } } else if (!keys['g']) { grabPressed = false; } // If grabbed, update cube position and quaternion if (grabbed) { endEffector.updateMatrixWorld(true); const eePosNow = new THREE.Vector3().setFromMatrixPosition(endEffector.matrixWorld); const eeQuatNow = new THREE.Quaternion().setFromRotationMatrix(endEffector.matrixWorld); const targetPos = eePosNow.clone().add(grabOffset.clone().applyQuaternion(eeQuatNow)); const targetQuat = eeQuatNow.clone().multiply(grabQuat); cubeMesh.position.copy(targetPos); cubeMesh.quaternion.copy(targetQuat); cubeBody.position.set(targetPos.x, targetPos.y, targetPos.z); cubeBody.quaternion.set(targetQuat.x, targetQuat.y, targetQuat.z, targetQuat.w); cubeBody.velocity.set(0, 0, 0); cubeBody.angularVelocity.set(0, 0, 0); } // Physics step world.step(1 / 60, dt, 3); // Sync cube if not grabbed if (!grabbed) { cubeMesh.position.set(cubeBody.position.x, cubeBody.position.y, cubeBody.position.z); cubeMesh.quaternion.set(cubeBody.quaternion.x, cubeBody.quaternion.y, cubeBody.quaternion.z, cubeBody.quaternion.w); } renderer.render(scene, camera); } animate(); // Resize handler window.addEventListener('resize', () => { renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); }); </script> </body> </html> ```
Here's a complete HTML implementation of a 6-DOF robotic arm simulation using Three.js and cannon-es physics: ```html <!DOCTYPE html> <html> <head> <title>6-DOF Robotic Arm Simulation</title> <style> body { margin: 0; overflow: hidden; } canvas { display: block; } .controls { position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.8); padding: 10px; border-radius: 5px; } .control-group { margin-bottom: 10px; } label { display: inline-block; width: 80px; } input { width: 120px; } </style> </head> <body> <div class="controls"> <div class="control-group"> <label for="surge">Surge (X):</label> <input type="range" id="surge" min="-5" max="5" step="0.1" value="0"> </div> <div class="control-group"> <label for="sway">Sway (Y):</label> <input type="range" id="sway" min="-5" max="5" step="0.1" value="0"> </div> <div class="control-group"> <label for="heave">Heave (Z):</label> <input type="range" id="heave" min="-5" max="5" step="0.1" value="0"> </div> <div class="control-group"> <label for="roll">Roll:</label> <input type="range" id="roll" min="-180" max="180" step="1" value="0"> </div> <div class="control-group"> <label for="pitch">Pitch:</label> <input type="range" id="pitch" min="-180" max="180" step="1" value="0"> </div> <div class="control-group"> <label for="yaw">Yaw:</label> <input type="range" id="yaw" min="-180" max="180" step="1" value="0"> </div> <button id="grab">Grab/Release Cube</button> </div> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script> <script src="https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js"></script> <script> // Scene setup const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0xf0f0f0); document.body.appendChild(renderer.domElement); // Physics world const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0), }); // Camera controls const controls = new THREE.OrbitControls(camera, renderer.domElement); camera.position.set(10, 10, 10); controls.update(); // Lighting const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(10, 20, 10); scene.add(directionalLight); // Materials const metalMaterial = new THREE.MeshStandardMaterial({ color: 0x555555, metalness: 0.8, roughness: 0.2 }); const rubberMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, metalness: 0.1, roughness: 0.7 }); // Ground const groundGeometry = new THREE.PlaneGeometry(20, 20); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, side: THREE.DoubleSide }); const ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; scene.add(ground); const groundShape = new CANNON.Plane(); const groundBody = new CANNON.Body({ mass: 0 }); groundBody.addShape(groundShape); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); world.addBody(groundBody); // Robot arm parts const baseRadius = 1; const baseHeight = 0.5; const jointRadius = 0.7; const armWidth = 0.4; const armDepth = 0.4; // Base const baseGeometry = new THREE.CylinderGeometry(baseRadius, baseRadius, baseHeight, 32); const base = new THREE.Mesh(baseGeometry, metalMaterial); base.position.y = baseHeight / 2; scene.add(base); const baseBody = new CANNON.Body({ mass: 0 }); const baseShape = new CANNON.Cylinder(baseRadius, baseRadius, baseHeight, 20); baseBody.addShape(baseShape); baseBody.position.y = baseHeight / 2; world.addBody(baseBody); // Shoulder (joint 1 - yaw) const shoulderGeometry = new THREE.SphereGeometry(jointRadius, 32, 32); const shoulder = new THREE.Mesh(shoulderGeometry, rubberMaterial); shoulder.position.y = baseHeight; scene.add(shoulder); // Upper arm (joint 2 - pitch) const upperArmLength = 3; const upperArmGeometry = new THREE.BoxGeometry(armWidth, upperArmLength, armDepth); const upperArm = new THREE.Mesh(upperArmGeometry, metalMaterial); upperArm.position.y = baseHeight + upperArmLength / 2; scene.add(upperArm); // Elbow (joint 3 - pitch) const elbowGeometry = new THREE.SphereGeometry(jointRadius, 32, 32); const elbow = new THREE.Mesh(elbowGeometry, rubberMaterial); elbow.position.y = baseHeight + upperArmLength; scene.add(elbow); // Forearm (joint 4 - roll) const forearmLength = 3; const forearmGeometry = new THREE.BoxGeometry(armWidth, forearmLength, armDepth); const forearm = new THREE.Mesh(forearmGeometry, metalMaterial); forearm.position.y = baseHeight + upperArmLength + forearmLength / 2; scene.add(forearm); // Wrist (joint 5 - pitch) const wristGeometry = new THREE.SphereGeometry(jointRadius / 1.5, 32, 32); const wrist = new THREE.Mesh(wristGeometry, rubberMaterial); wrist.position.y = baseHeight + upperArmLength + forearmLength; scene.add(wrist); // End effector (joint 6 - yaw) const effectorLength = 1; const effectorGeometry = new THREE.BoxGeometry(armWidth, effectorLength, armDepth); const effector = new THREE.Mesh(effectorGeometry, metalMaterial); effector.position.y = baseHeight + upperArmLength + forearmLength + effectorLength / 2; scene.add(effector); // Gripper parts const gripperWidth = 0.2; const gripperLength = 0.8; const gripperGeometry = new THREE.BoxGeometry(gripperWidth, gripperLength, armDepth / 1.5); const leftGripper = new THREE.Mesh(gripperGeometry, metalMaterial); leftGripper.position.set( -(armWidth / 2 + gripperWidth / 2), baseHeight + upperArmLength + forearmLength + effectorLength, 0 ); scene.add(leftGripper); const rightGripper = new THREE.Mesh(gripperGeometry, metalMaterial); rightGripper.position.set( (armWidth / 2 + gripperWidth / 2), baseHeight + upperArmLength + forearmLength + effectorLength, 0 ); scene.add(rightGripper); // Container const containerGeometry = new THREE.BoxGeometry(2, 1, 2); const containerMaterial = new THREE.MeshStandardMaterial({ color: 0x3366ff, transparent: true, opacity: 0.5 }); const container = new THREE.Mesh(containerGeometry, containerMaterial); container.position.set(-5, 0.5, 0); scene.add(container); // Cube to move const cubeGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5); const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff4444 }); const cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.position.set(5, 0.25, 0); scene.add(cube); const cubeShape = new CANNON.Box(new CANNON.Vec3(0.25, 0.25, 0.25)); const cubeBody = new CANNON.Body({ mass: 1 }); cubeBody.addShape(cubeShape); cubeBody.position.copy(cube.position); cubeBody.linearDamping = 0.5; cubeBody.angularDamping = 0.5; world.addBody(cubeBody); // Controls setup let isGrabbing = false; const gripperDistance = 0.2; document.getElementById('grab').addEventListener('click', () => { isGrabbing = !isGrabbing; if (!isGrabbing) { if (cubeBody.world) { world.addBody(cubeBody); cubeBody.velocity.set(0, 0, 0); } } }); // Animation loop const fixedTimeStep = 1.0 / 60.0; const maxSubSteps = 3; let lastTime = 0; function animate(time) { // Physics update const dt = (time - lastTime) / 1000; world.step(fixedTimeStep, dt, maxSubSteps); lastTime = time; // Update positions from physics cube.position.copy(cubeBody.position); cube.quaternion.copy(cubeBody.quaternion); // Get control inputs const surge = parseFloat(document.getElementById('surge').value); const sway = parseFloat(document.getElementById('sway').value); const heave = parseFloat(document.getElementById('heave').value); const roll = THREE.MathUtils.degToRad(parseFloat(document.getElementById('roll').value)); const pitch = THREE.MathUtils.degToRad(parseFloat(document.getElementById('pitch').value)); const yaw = THREE.MathUtils.degToRad(parseFloat(document.getElementById('yaw').value)); // Update grippers based on grabbing state if (isGrabbing) { leftGripper.position.x = -(armWidth / 2 + gripperWidth / 2 + gripperDistance / 2); rightGripper.position.x = (armWidth / 2 + gripperWidth / 2 + gripperDistance / 2); // Move cube with gripper if (cubeBody.world) { world.remove(cubeBody); } cube.position.copy(effector.position); cube.position.y = effector.position.y + effectorLength / 2 + 0.25; cube.quaternion.copy(effector.quaternion); } else { leftGripper.position.x = -(armWidth / 2 + gripperWidth / 2) * 1.5; rightGripper.position.x = (armWidth / 2 + gripperWidth / 2) * 1.5; } // Update arm position and rotation based on inputs upperArm.rotation.x = pitch; forearm.position.x = Math.sin(pitch) * upperArmLength; forearm.position.y = baseHeight + upperArmLength - Math.cos(pitch) * upperArmLength; forearm.rotation.x = pitch; wrist.position.x = forearm.position.x + Math.sin(pitch + roll) * forearmLength; wrist.position.y = forearm.position.y - Math.cos(pitch + roll) * forearmLength; effector.position.x = wrist.position.x + Math.sin(heave) * effectorLength; effector.position.y = wrist.position.y + Math.cos(heave) * effectorLength; effector.rotation.z = roll; // End effector yaw rotation effector.rotation.y = yaw; // Apply translation from surge, sway, and heave shoulder.position.set(sway, baseHeight, -surge); const targetRoll = roll; const targetPitch = THREE.MathUtils.degToRad(pitch); const targetYaw = THREE.MathUtils.degToRad(yaw); // Update grippers to follow effector leftGripper.position.y = effector.position.y + effectorLength; rightGripper.position.y = effector.position.y + effectorLength; leftGripper.quaternion.copy(effector.quaternion); rightGripper.quaternion.copy(effector.quaternion); renderer.render(scene, camera); requestAnimationFrame(animate); } window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); animate(0); </script> </body> </html> ``` This simulation features: 1. A complete 6-DOF robotic arm with: - Base (yaw rotation) - Shoulder joint - Upper arm (pitch) - Elbow joint - Forearm (roll) - Wrist joint (pitch) - End effector (yaw) - Functional gripper 2. Physics simulation powered by cannon-es for realistic movement 3. Controls for all six degrees of freedom (surge, sway, heave, roll, pitch, yaw) 4. A target cube that can be picked up and moved to the container 5. Button to toggle grabbing/releasing the cube 6. Realistic metal and rubber materials for visual fidelity The arm responds to the input sliders in real-time and can manipulate the physics-enabled cube. The simulation includes proper lighting and a camera control system for viewing from any angle.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>6-DOF Robotic Arm Simulation</title> <style> body { margin: 0; overflow: hidden; } #info { position: absolute; top: 10px; left: 10px; color: #fff; font-family: sans-serif; } </style> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.min.js"></script> </head> <body> <div id="info">G: grab/release cube</div> <script> // --- SCENE, CAMERA, RENDERER --- const scene = new THREE.Scene(); scene.background = new THREE.Color(0x202025); const camera = new THREE.PerspectiveCamera(60, innerWidth/innerHeight, 0.1, 1000); camera.position.set(8, 8, 12); const renderer = new THREE.WebGLRenderer({antialias:true}); renderer.setSize(innerWidth, innerHeight); document.body.appendChild(renderer.domElement); const controls = new THREE.OrbitControls(camera, renderer.domElement); // --- LIGHTS --- scene.add(new THREE.HemisphereLight(0x8888ff, 0x222222, 1.2)); const dir = new THREE.DirectionalLight(0xffffff, 0.6); dir.position.set(-5,10,5); scene.add(dir); // --- PHYSICS WORLD --- const world = new CANNON.World(); world.gravity.set(0,-9.82,0); world.broadphase = new CANNON.SAPBroadphase(world); world.defaultContactMaterial.friction = 0.4; // Ground const groundMat = new CANNON.Material(); const groundBody = new CANNON.Body({ mass: 0, material: groundMat }); groundBody.addShape(new CANNON.Plane()); groundBody.quaternion.setFromEuler(-Math.PI/2,0,0); world.addBody(groundBody); const groundGeo = new THREE.PlaneGeometry(20,20); const groundMesh = new THREE.Mesh(groundGeo, new THREE.MeshStandardMaterial({color:0x444444})); groundMesh.rotation.x = -Math.PI/2; scene.add(groundMesh); // Container (U-shaped) const wallMat = new THREE.MeshStandardMaterial({color:0x5555aa}); const container = []; const wallThickness = 0.3, wallHeight=2, contSize=4; const wallShapes = [ { pos:[0,wallHeight/2,-contSize/2], size:[contSize,wallHeight,wallThickness] }, { pos:[-contSize/2,wallHeight/2,0], size:[wallThickness,wallHeight,contSize] }, { pos:[contSize/2,wallHeight/2,0], size:[wallThickness,wallHeight,contSize] } ]; wallShapes.forEach(w=>{ // Physics const b = new CANNON.Body({ mass:0 }); b.addShape(new CANNON.Box(new CANNON.Vec3(w.size[0]/2,w.size[1]/2,w.size[2]/2))); b.position.set(...w.pos); world.addBody(b); // Visual const m = new THREE.Mesh(new THREE.BoxGeometry(...w.size), wallMat); m.position.set(...w.pos); scene.add(m); }); // Cube to move const cubeSize = 1; const cubeBody = new CANNON.Body({ mass:1, material:groundMat }); const boxShape = new CANNON.Box(new CANNON.Vec3(cubeSize/2,cubeSize/2,cubeSize/2)); cubeBody.addShape(boxShape); cubeBody.position.set(0, cubeSize/2, contSize/2 + 1); world.addBody(cubeBody); const cubeMesh = new THREE.Mesh( new THREE.BoxGeometry(cubeSize,cubeSize,cubeSize), new THREE.MeshStandardMaterial({color:0xaa5555}) ); scene.add(cubeMesh); // --- ROBOTIC ARM (KINEMATIC CHAIN) --- const boneLengths = [2, 2, 1.5, 1, 1, 0.5]; const bones = []; const base = new THREE.Object3D(); scene.add(base); let prev = base; boneLengths.forEach((len,i)=>{ const bone = new THREE.Object3D(); bone.position.y = len; prev.add(bone); // Visual cylinder const geo = new THREE.CylinderGeometry(0.1,0.1,len,12); const mat = new THREE.MeshStandardMaterial({color:0x8888ff}); const mesh = new THREE.Mesh(geo, mat); mesh.position.y = len/2; bone.add(mesh); bones.push(bone); prev = bone; }); // End effector (a small sphere) const ee = new THREE.Object3D(); const eeMesh = new THREE.Mesh( new THREE.SphereGeometry(0.12,16,16), new THREE.MeshStandardMaterial({color:0xffee00}) ); ee.add(eeMesh); bones[bones.length-1].add(ee); // Target indicator const target = new THREE.Mesh( new THREE.SphereGeometry(0.15,16,16), new THREE.MeshStandardMaterial({color:0x00ff00}) ); scene.add(target); // GUI controls for target pose const params = { x:2, y:2, z:0, yaw:0, pitch:0, roll:0 }; const gui = new dat.GUI(); gui.add(params,'x',-5,5); gui.add(params,'y',0,5); gui.add(params,'z',-5,5); gui.add(params,'yaw',-180,180); gui.add(params,'pitch',-180,180); gui.add(params,'roll',-180,180); // Grabbing state let grabbing = false; window.addEventListener('keydown', e => { if(e.key.toLowerCase()==='g') grabbing = !grabbing; }); // CCD IK solver function solveIK(targetPos) { const maxIter = 8, threshold = 0.1; let endPos = new THREE.Vector3(); ee.getWorldPosition(endPos); if(endPos.distanceTo(targetPos) < threshold) return; for(let iter=0; iter<maxIter; iter++){ for(let i=bones.length-1; i>=0; i--){ const joint = bones[i]; const jointPos = new THREE.Vector3(); joint.getWorldPosition(jointPos); ee.getWorldPosition(endPos); const toEnd = endPos.clone().sub(jointPos).normalize(); const toTarget = targetPos.clone().sub(jointPos).normalize(); const dot = THREE.MathUtils.clamp(toEnd.dot(toTarget), -1,1); const angle = Math.acos(dot); if(angle < 1e-3) continue; const axis = new THREE.Vector3().crossVectors(toEnd,toTarget).normalize(); const quat = new THREE.Quaternion().setFromAxisAngle(axis, angle); joint.quaternion.multiply(quat); } ee.getWorldPosition(endPos); if(endPos.distanceTo(targetPos) < threshold) break; } } // Animation loop const timeStep = 1/60; function animate() { requestAnimationFrame(animate); // Update target pose target.position.set(params.x, params.y, params.z); target.rotation.set( THREE.MathUtils.degToRad(params.pitch), THREE.MathUtils.degToRad(params.yaw), THREE.MathUtils.degToRad(params.roll) ); // Solve IK for position solveIK(target.position); // Align end-effector orientation to target (local) bones[bones.length-1].rotation.set( THREE.MathUtils.degToRad(params.pitch), THREE.MathUtils.degToRad(params.yaw), THREE.MathUtils.degToRad(params.roll) ); // Grabbing logic const eePos = new CANNON.Vec3(); const eeQuat = new CANNON.Quaternion(); const threePos = new THREE.Vector3(), threeQuat = new THREE.Quaternion(); ee.getWorldPosition(threePos); ee.getWorldQuaternion(threeQuat); eePos.copy(threePos); eeQuat.copy(threeQuat); if(grabbing && eePos.distanceSquared(cubeBody.position) < 0.5) { // attach cubeBody.type = CANNON.Body.KINEMATIC; cubeBody.velocity.set(0,0,0); cubeBody.angularVelocity.set(0,0,0); cubeBody.position.copy(eePos); cubeBody.quaternion.copy(eeQuat); } else if(!grabbing && cubeBody.type === CANNON.Body.KINEMATIC) { // release cubeBody.type = CANNON.Body.DYNAMIC; } // Physics step world.step(timeStep); // Sync cube mesh cubeMesh.position.copy(cubeBody.position); cubeMesh.quaternion.copy(cubeBody.quaternion); controls.update(); renderer.render(scene, camera); } animate(); window.addEventListener('resize', ()=> { camera.aspect = innerWidth/innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); }); </script> </body> </html> ```
```html <!DOCTYPE html> <html> <head> <title>3D Robotic Arm Simulation</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128/examples/js/controls/OrbitControls.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.128/examples/js/loaders/GLTFLoader.js"></script> <style> body { margin: 0; background-color: #f0f0f0; } #canvas { width: 100%; height: 100vh; display: block; } </style> </head> <body> <canvas id="canvas"></canvas> <script> let scene, camera, renderer, arm, cube, container, controls; let surge = 0, heave = 0, sway = 0, pitch = 0, yaw = 0, roll = 0; // Initialize scene, camera, and renderer scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; // Load robotic arm model const loader = new THREE.GLTFLoader(); loader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/models/gltf/RobotExpressive/RobotExpressive.gltf', (gltf) => { arm = gltf.scene; arm.scale.set(0.1, 0.1, 0.1); arm.position.set(0, 0, 0); scene.add(arm); // Create cube and container const cubeGeometry = new THREE.BoxGeometry(1, 1, 1); const cubeMaterial = new THREE.MeshPhongMaterial({ color: 0xff0000 }); cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.position.set(5, 0, 0); scene.add(cube); const containerGeometry = new THREE.BoxGeometry(2, 2, 2); const containerMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00, transparent: true, opacity: 0.5 }); container = new THREE.Mesh(containerGeometry, containerMaterial); container.position.set(10, 0, 0); scene.add(container); // Add lighting const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); scene.add(ambientLight); const pointLight = new THREE.PointLight(0xffffff, 1, 100); pointLight.position.set(0, 5, 5); scene.add(pointLight); // Set up camera and controls camera.position.set(0, 5, 10); controls = new THREE.OrbitControls(camera, renderer.domElement); }); // Animate scene function animate() { requestAnimationFrame(animate); if (arm) { // Update arm position and rotation based on user input arm.position.set(surge, heave, sway); arm.rotation.set(pitch, yaw, roll); // Move cube with arm cube.position.copy(arm.position); // Check if cube is inside container if (Math.abs(cube.position.x - container.position.x) < 1 && Math.abs(cube.position.y - container.position.y) < 1 && Math.abs(cube.position.z - container.position.z) < 1) { console.log('Cube is inside container!'); } } controls.update(); renderer.render(scene, camera); } animate(); // Handle user input document.addEventListener('keydown', (event) => { switch (event.key) { case 'w': surge += 0.1; break; case 's': surge -= 0.1; break; case 'a': sway -= 0.1; break; case 'd': sway += 0.1; break; case 'q': heave += 0.1; break; case 'e': heave -= 0.1; break; case 'ArrowUp': pitch += 0.01; break; case 'ArrowDown': pitch -= 0.01; break; case 'ArrowLeft': yaw -= 0.01; break; case 'ArrowRight': yaw += 0.01; break; case 'z': roll += 0.01; break; case 'x': roll -= 0.01; break; } }); </script> </body> </html> ``` This code creates a 3D scene with a robotic arm, a cube, and a container. The user can control the arm's position and rotation using keyboard inputs. The cube is attached to the arm and moves with it. The code checks if the cube is inside the container and logs a message to the console when it is. To use this code, simply copy and paste it into an HTML file and open it in a web browser. You can use the following keyboard controls: * W/S: Move arm forward/backward (surge) * A/D: Move arm left/right (sway) * Q/E: Move arm up/down (heave) * Arrow up/down: Pitch arm up/down * Arrow left/right: Yaw arm left/right * Z/X: Roll arm clockwise/counterclockwise Note that this is a simplified simulation and does not take into account the actual physics of a robotic arm. In a real-world scenario, you would need to consider factors like joint limits, motor torques, and friction.
# Robotic Arm 3D Physics Simulation I'll create a realistic 6-DOF robotic arm simulation using Three.js and Cannon.js for physics. The arm will be controllable via sliders for all six degrees of freedom and will be able to manipulate a cube. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>6-DOF Robotic Arm Simulation</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/dat.gui@0.7.7/build/dat.gui.min.js"></script> <style> body { margin: 0; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #1a2a6c, #2c3e50); color: white; } #info { position: absolute; top: 10px; width: 100%; text-align: center; z-index: 100; color: white; text-shadow: 0 0 5px rgba(0,0,0,0.5); } #container { position: relative; width: 100%; height: 100vh; } canvas { display: block; } .gui-container { position: absolute; top: 10px; right: 10px; background: rgba(30, 30, 40, 0.7); border-radius: 10px; padding: 10px; backdrop-filter: blur(5px); border: 1px solid rgba(255,255,255,0.1); } .title { text-align: center; font-weight: bold; margin-bottom: 10px; color: #4fc3f7; } .instructions { position: absolute; bottom: 20px; left: 20px; background: rgba(0,0,0,0.5); padding: 15px; border-radius: 8px; max-width: 300px; font-size: 14px; line-height: 1.5; } </style> </head> <body> <div id="info">6-DOF Robotic Arm Physics Simulation</div> <div id="container"></div> <div class="instructions"> <strong>Instructions:</strong> <p>1. Use sliders to control each joint of the robotic arm</p> <p>2. Position the end effector over the cube</p> <p>3. Press "Grab Cube" to attach the cube</p> <p>4. Move the cube into the container</p> <p>5. Press "Release Cube" to drop it</p> </div> <script> // Main variables let scene, camera, renderer, world; let armSegments = [], armConstraints = []; let gripperConstraint = null; let cubeBody, containerBody; let gripperOpen = true; let clock = new THREE.Clock(); let controls = { baseRotation: 0, shoulder: 0, elbow: 0, wristPitch: 0, wristRoll: 0, wristYaw: 0, grabCube: function() { toggleGripper(false); }, releaseCube: function() { toggleGripper(true); } }; // Initialize the scene init(); animate(); function init() { // Create scene scene = new THREE.Scene(); scene.background = new THREE.Color(0x0a0a1a); scene.fog = new THREE.Fog(0x0a0a1a, 20, 50); // Create camera camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 5, 10); camera.lookAt(0, 0, 0); // Create renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; document.getElementById('container').appendChild(renderer.domElement); // Create physics world world = new CANNON.World(); world.gravity.set(0, -9.82, 0); world.broadphase = new CANNON.NaiveBroadphase(); world.solver.iterations = 10; // Add lighting const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(5, 10, 7); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 1024; directionalLight.shadow.mapSize.height = 1024; scene.add(directionalLight); const hemisphereLight = new THREE.HemisphereLight(0x80deea, 0x388e3c, 0.3); scene.add(hemisphereLight); // Create ground createGround(); // Create robotic arm createRoboticArm(); // Create cube and container createCube(); createContainer(); // Setup GUI controls setupGUI(); // Handle window resize window.addEventListener('resize', onWindowResize); } function createGround() { // Physics ground const groundShape = new CANNON.Plane(); const groundBody = new CANNON.Body({ mass: 0 }); groundBody.addShape(groundShape); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI/2); world.addBody(groundBody); // Visual ground const groundGeometry = new THREE.PlaneGeometry(30, 30); const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x2e7d32, roughness: 0.8, metalness: 0.2 }); const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial); groundMesh.rotation.x = -Math.PI/2; groundMesh.receiveShadow = true; scene.add(groundMesh); } function createRoboticArm() { // Base const baseShape = new CANNON.Cylinder(0.8, 0.8, 0.4, 16); const baseBody = new CANNON.Body({ mass: 0 }); baseBody.addShape(baseShape); baseBody.position.set(0, 0.2, 0); world.addBody(baseBody); const baseGeometry = new THREE.CylinderGeometry(0.8, 0.8, 0.4, 16); const baseMaterial = new THREE.MeshStandardMaterial({ color: 0x37474f, metalness: 0.5, roughness: 0.4 }); const baseMesh = new THREE.Mesh(baseGeometry, baseMaterial); baseMesh.castShadow = true; baseMesh.receiveShadow = true; scene.add(baseMesh); armSegments.push({ body: baseBody, mesh: baseMesh }); // Shoulder const shoulderShape = new CANNON.Box(new CANNON.Vec3(0.3, 0.3.3.3)); const shoulderBody = new CANNON.Body({ mass: 2 }); shoulderBody.addShape(shoulderShape); shoulderBody.position.set(0, 0.8, 0); world.addBody(shoulderBody); const shoulderGeometry = new THREE.BoxGeometry(0.6, 0.6, 0.6); const shoulderMaterial = new THREE.MeshStandardMaterial({ color: 0x455a64, metalness: 0.6, roughness: 0.3 }); const shoulderMesh = new THREE.Mesh(shoulderGeometry, shoulderMaterial); shoulderMesh.castShadow = true; shoulderMesh.receiveShadow = true; scene.add(shoulderMesh); armSegments.push({ body: shoulderBody, mesh: shoulderMesh }); // Upper arm const upperArmShape = new CANNON.Box(new CANNON.Vec3(0.2, 1.5, 0.2)); const upperArmBody = new CANNON.Body({ mass: 3 }); upperArmBody.addShape(upperArmShape); upperArmBody.position.set(0, 2.3, 0); world.addBody(upperArmBody); const upperArmGeometry = new THREE.BoxGeometry(0.4, 3, 0.4); const upperArmMaterial = new THREE.MeshStandardMaterial({ color: 0x546e7a, metalness: 0.5, roughness: 0.4 }); const upperArmMesh = new THREE.Mesh(upperArmGeometry, upperArmMaterial); upperArmMesh.castShadow = true; upperArmMesh.receiveShadow = true; scene.add(upperArmMesh); armSegments.push({ body: upperArmBody, mesh: upperArmMesh }); // Elbow const elbowShape = new CANNON.Box(new CANNON.Vec3(0.3, 0.3, 0.3)); const elbowBody = new CANNON.Body({ mass: 2 }); elbowBody.addShape(elbowShape); elbowBody.position.set(0, 3.8, 0); world.addBody(elbowBody); const elbowGeometry = new THREE.BoxGeometry(0.6, 0.6, 0.6); const elbowMaterial = new THREE.MeshStandardMaterial({ color: 0x455a64, metalness: 0.6, roughness: 0.3 }); const elbowMesh = new THREE.Mesh(elbowGeometry, elbowMaterial); elbowMesh.castShadow = true; elbowMesh.receiveShadow = true; scene.add(elbowMesh); armSegments.push({ body: elbowBody, mesh: elbowMesh }); // Forearm const forearmShape = new CANNON.Box(new CANNON.Vec3(0.2, 1.2, 0.2)); const forearmBody = new CANNON.Body({ mass: 2.5 }); forearmBody.addShape(forearmShape); forearmBody.position.set(0, 5.0, 0); world.addBody(forearmBody); const forearmGeometry = new THREE.BoxGeometry(0.4, 2.4, 0.4); const forearmMaterial = new THREE.MeshStandardMaterial({ color: 0x546e7a, metalness: 0.5, roughness: 0.4 }); const forearmMesh = new THREE.Mesh(forearmGeometry, forearmMaterial); forearmMesh.castShadow = true; forearmMesh.receiveShadow = true; scene.add(forearmMesh); armSegments.push({ body: forearmBody, mesh: forearmMesh }); // Wrist const wristShape = new CANNON.Box(new CANNON.Vec3(0.3, 0.3, 0.3)); const wristBody = new CANNON.Body({ mass: 1.5 }); wristBody.addShape(wristShape); wristBody.position.set(0, 6.2, 0); world.addBody(wristBody); const wristGeometry = new THREE.BoxGeometry(0.6, 0.6, 0.6); const wristMaterial = new THREE.MeshStandardMaterial({ color: 0x455a64, metalness: 0.6, roughness: 0.3 }); const wristMesh = new THREE.Mesh(wristGeometry, wristMaterial); wristMesh.castShadow = true; wristMesh.receiveShadow = true; scene.add(wristMesh); armSegments.push({ body: wristBody, mesh: wristMesh }); // Gripper base const gripperBaseShape = new CANNON.Box(new CANNON.Vec3(0.2, 0.2, 0.4)); const gripperBaseBody = new CANNON.Body({ mass: 1 }); gripperBaseBody.addShape(gripperBaseShape); gripperBaseBody.position.set(0, 6.2, 0); world.addBody(gripperBaseBody); const gripperBaseGeometry = new THREE.BoxGeometry(0.4, 0.4, 0.8); const gripperBaseMaterial = new THREE.MeshStandardMaterial({ color: 0x37474f, metalness: 0.7, roughness: 0.2 }); const gripperBaseMesh = new THREE.Mesh(gripperBaseGeometry, gripperBaseMaterial); gripperBaseMesh.castShadow = true; gripperBaseMesh.receiveShadow = true; scene.add(gripperBaseMesh); armSegments.push({ body: gripperBaseBody, mesh: gripperBaseMesh }); // Gripper left const gripperLeftShape = new CANNON.Box(new CANNON.Vec3(0.1, 0.2, 0.6)); const gripperLeftBody = new CANNON.Body({ mass: 0.5 }); gripperLeftBody.addShape(gripperLeftShape); gripperLeftBody.position.set(-0.3, 6.2, 0); world.addBody(gripperLeftBody); const gripperLeftGeometry = new THREE.BoxGeometry(0.2, 0.4, 1.2); const gripperLeftMaterial = new THREE.MeshStandardMaterial({ color: 0x78909c, metalness: 0.8, roughness: 0.1 }); const gripperLeftMesh = new THREE.Mesh(gripperLeftGeometry, gripperLeftMaterial); gripperLeftMesh.castShadow = true; gripperLeftMesh.receiveShadow = true; scene.add(gripperLeftMesh); armSegments.push({ body: gripperLeftBody, mesh: gripperLeftMesh }); // Gripper right const gripperRightShape = new CANNON.Box(new CANNON.Vec3(0.1, 0.2, 0.6)); const gripperRightBody = new CANNON.Body({ mass: 0.5 }); gripperRightBody.addShape(gripperRightShape); gripperRightBody.position.set(0.3, 6.2, 0); world.addBody(gripperRightBody); const gripperRightGeometry = new THREE.BoxGeometry(0.2, 0.4, 1.2); const gripperRightMaterial = new THREE.MeshStandardMaterial({ color: 0x78909c, metalness: 0.8, roughness: 0.1 }); const gripperRightMesh = new THREE.Mesh(gripperRightGeometry, gripperRightMaterial); gripperRightMesh.castShadow = true; gripperRightMesh.receiveShadow = true; scene.add(gripperRightMesh); armSegments.push({ body: gripperRightBody, mesh: gripperRightMesh }); // Create constraints createArmConstraints(); } function createArmConstraints() { // Base to shoulder const baseShoulderConstraint = new CANNON.PointToPointConstraint( armSegments[0].body, new CANNON.Vec3(0, 0.2, 0), armSegments[1].body, new CANNON.Vec3(0, -0.3, 0) ); world.addConstraint(baseShoulderConstraint); armConstraints.push(baseShoulderConstraint); // Shoulder to upper arm const shoulderUpperConstraint = new CANNON.PointToPointConstraint( armSegments[1].body, new CANNON.Vec3(0, 0.3, 0), armSegments[2].body, new CANNON.Vec3(0, -1.5, 0) ); world.addConstraint(shoulderUpperConstraint); armConstraints.push(shoulderUpperConstraint); // Upper arm to elbow const upperElbowConstraint = new CANNON.PointToPointConstraint( armSegments[2].body, new CANNON.Vec3(0, 1.5, 0), armSegments[3].body, new CANNON.Vec3(0, -0.3, 0) ); world.addConstraint(upperElbowConstraint); armConstraints.push(upperElbowConstraint); // Elbow to forearm const elbowForearmConstraint = new CANNON.PointToPointConstraint( armSegments[3].body, new CANNON.Vec3(0, 0.3, 0), armSegments[4].body, new CANNON.Vec3(0, -1.2, 0) ); world.addConstraint(elbowForearmConstraint); armConstraints.push(elbowFore const const const const forearmWristConstraint = new CANNON.PointToPointConstraint( armSegments[4].body, new CANNON.Vec3(0, 1.2, 0), armSegments[5].body, new CANNON.Vec3(0, -0.3, 0) ); world.addConstraint(forearmWristConstraint); armConstraints.push(forearmWristConstraint); // Wrist to gripper base const wristGripperConstraint = new CANNON.PointToPointConstraint( armSegments[5].body, new CANNON.Vec3(0, 0.3, 0), armSegments[6].body, new CANNON.Vec3(0, 0, 0) ); world.addConstraint(wristGripperConstraint); armConstraints.push(wristGripperConstraint); // Gripper base to left gripper const gripperLeftConstraint = new CANNON.PointToPointConstraint( armSegments[6].body, new CANNON.Vec3(-0.2, 0, 0), armSegments[7].body, new CANNON.Vec3(0.1, 0, 0) ); world.addConstraint(gripperLeftConstraint); armConstraints.push(gripperLeftConstraint); // Gripper base to right gripper const gripperRightConstraint = new CANNON.PointToPointConstraint( armSegments[6].body, new CANNON.Vec3(0.2, 0, 0), armSegments[8].body, new CANNON.Vec3(-0.1, 0, 0) ); world.addConstraint(gripperRightConstraint); armConstraints.push(gripperRightConstraint); } function createCube() { const cubeSize = 0.5; // Physics cube const cubeShape = new CANNON.Box(new CANNON.Vec3(cubeSize/2, cubeSize/2, cubeSize/2)); cubeBody = new CANNON.Body({ mass: 1 }); cubeBody.addShape(cubeShape); cubeBody.position.set(3, cubeSize/2, 0); cubeBody.linearDamping = 0.4; cubeBody.angularDamping = 0.4; world.addBody(cubeBody); // Visual cube const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize); const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff5722, metalness: 0.3, roughness: 0.4 }); const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial); cubeMesh.castShadow = true; cubeMesh.receiveShadow = true; scene.add(cubeMesh); // Store reference armSegments.push({ body: cubeBody, mesh: cubeMesh }); } function createContainer() { const containerWidth = 2; const containerDepth = 2; const containerHeight = 1; const wallThickness = 0.1; // Container base const baseShape = new CANNON.Box(new CANNON.Vec3(containerWidth/2, wallThickness/2, containerDepth/2)); const baseBody = new CANNON.Body({ mass: 0 }); baseBody.addShape(baseShape); baseBody.position.set(-3, wallThickness/2, 0); world.addBody(baseBody); const baseGeometry = new THREE.BoxGeometry(containerWidth, wallThickness, containerDepth); const baseMaterial = new THREE.MeshStandardMaterial({ color: 0x5d4037, metalness: 0.: 0.: 0.: 0.6 }); const baseMesh = new THREE.Mesh(baseGeometry, baseMaterial); baseMesh.castShadow = true; baseMesh.receiveShadow = true; scene.add(baseMesh); // Front wall const frontWallShape = new CANNON.Box(new CANNON.Vec3(containerWidth/2, containerHeight/2, wallThickness/2)); const frontWallBody = new CANNON.Body({ mass: 0 }); frontWallBody.addShape(frontWallShape); frontWallBody.position.set(-3, containerHeight/2 + wallThickness, containerDepth/2); world.addBody(frontWallBody); const frontWallGeometry = new THREE.BoxGeometry(containerWidth, containerHeight, wallThickness); const frontWallMesh = new THREE.Mesh(frontWallGeometry, baseMaterial); frontWallMesh.position.set(-3, containerHeight/2 + wallThickness/2, containerDepth/2); frontWallMesh.castShadow = true; frontWallMesh.receiveShadow = true; scene.add(frontWallMesh); // Back wall const backWallBody = new CANNON.Body({ mass: 0 }); backWallBody.addShape(frontWallShape); backWallBody.position.set(-3, containerHeight/2 + wallThickness, -containerDepth/2); world.addBody(backWallBody); const backWallMesh = frontWallMesh.clone(); backWallMesh.position.z = -containerDepth/2; scene.add(backWallMesh); // Left wall const leftWallShape = new CANNON.Box(new CANNON.Vec3(wallThickness/2, containerHeight/2, containerDepth/2)); const leftWallBody = new CANNON.Body({ mass: 0 }); leftWallBody.addShape(leftWallShape); leftWallBody.position.set(-3 - containerWidth/2, containerHeight/2 + wallThickness, 0); world.addBody(leftWallBody); const leftWallGeometry = new THREE.BoxGeometry(wallThickness, containerHeight, containerDepth); const leftWallMesh = new THREE.Mesh(leftWallGeometry, baseMaterial); leftWallMesh.position.set(-3 - containerWidth/2, containerHeight/2 + wallThickness/2, 0); leftWallMesh.castShadow = true; leftWallMesh.receiveShadow = true; scene.add(leftWallMesh); // Right wall const rightWallBody = new CANNON.Body({ mass: 0 }); rightWallBody.addShape(leftWallShape); rightWallBody.position.set(-3 + containerWidth/2, containerHeight/2 + wallThickness, 0); world.addBody(rightWallBody); const rightWallMesh = leftWallMesh.clone(); rightWallMesh.position.x = -3 + containerWidth/2; scene.add(rightWallMesh); } function setupGUI() { const gui = new dat.GUI({ autoPlace: false }); const guiContainer = document.createElement('div'); guiContainer.className = 'gui-container'; guiContainer.appendChild(gui.domElement); document.getElementById('container').appendChild(guiContainer); const title = document.createElement('div'); title.className = 'title'; title.textContent = 'Robotic Arm Controls'; gui.domElement.parentNode.insertBefore(title, gui.domElement); gui.add(controls, 'baseRotation', -180, 180, 1).name('Base Rotation').onChange(updateArm); gui.add(controls, 'shoulder', -90, 90, 1).name('Shoulder').onChange(updateArm); gui.add(controls, 'elbow', -90, 90, 1).name('Elbow').onChange(updateArm); gui.add(controls, 'wristPitch', -90, 90, 1).name('Wrist Pitch').onChange(updateArm); gui.add(controls, 'wristRoll', -90, 90, 1).name('Wrist Roll').onChange(updateArm); gui.add(controls, 'wristYaw', -90, 90, 1).name('Wrist Yaw').onChange(updateArm); gui.add(controls, 'grabCube').name('Grab Cube'); gui.add(controls, 'releaseCube').name('Release Cube'); } function updateArm() { // Base rotation armSegments[0].body.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), THREE.MathUtils.degToRad(controls.baseRotation)); // Shoulder joint armSegments[1].body.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), THREE.MathUtils.degToRad(controls.shoulder)); // Elbow joint armSegments[3].body.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), THREE.MathUtils.degToRad(controls.elbow)); // Wrist joints armSegments[5].body.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), THREE.MathUtils.degToRad(controls.wristPitch)); armSegments[6].body.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 0, 1), THREE.MathUtils.degToRad(controls.wristRoll)); armSegments[6].body.quaternion.mult( new CANNON.Quaternion().setFromAxisAngle(new CANNON.Vec3(0, 1, 0), THREE.MathUtils.degToRad(controls.wristYaw)), armSegments[6].body.quaternion ); } function toggleGripper(open) { gripperOpen = open; if (gripperConstraint) { world.removeConstraint(gripperConstraint); gripperConstraint = null; } if (!open) { // Check if gripper is close enough to the cube const gripperPos = armSegments[6].body.position; const cubePos = cubeBody.position; const distance = gripperPos.distanceTo(cubePos); if (distance < 1.0) { // Create constraint to attach cube to gripper gripperConstraint = new CANNON.LockConstraint( armSegments[6].body, cubeBody ); world.addConstraint(gripperConstraint); } } } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); // Update physics const delta = clock.getDelta(); if (delta < 0.2) world.step(1/60, delta, 3); // Update gripper position based on wrist control if (!gripperOpen) { const gripperLeft = armSegments[7]; const gripperRight = armSegments[8]; gripperLeft.body.position.copy(armSegments[6].body.position); gripperLeft.body.position.x -= 0.3; gripperRight.body.position.copy(armSegments[6].body.position); gripperRight.body.position.x += 0.3; } // Update Three.js meshes to match physics bodies for (let i = 0; i < armSegments.length; i++) { const segment = armSegments[i]; segment.mesh.position.copy(segment.body.position); segment.mesh.quaternion.copy(segment.body.quaternion); } renderer.render(scene, camera); } </script> </body> </html> ``` ## Features of this Simulation: 1. **Realistic Robotic Arm**: - 6 degrees of freedom (base rotation, shoulder, elbow, wrist pitch, wrist roll, wrist yaw) - Physically accurate joints using Cannon.js constraints - Detailed arm segments with different materials 2. **Interactive Controls**: - Sliders for each joint angle - Grab/release buttons for cube manipulation - Smooth physics-based movement 3. **Environment**: - Ground plane with shadows - Target container for the cube - Atmospheric lighting and fog 4. **Physics Simulation**: - Realistic gravity and collision detection - Cube that can be picked up and moved - Constraints for gripper functionality 5. **User Interface**: - Clean control panel with intuitive sliders - Instructions for operation - Responsive design that works on different screen sizes To use the simulation, adjust the sliders to position the robotic arm's end effector over the cube, press "Grab Cube", then move the cube into the container and press "Release Cube".
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>6-DOF Robotic Arm Simulation</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.20.0/cannon.min.js"></script> <style> body { margin: 0; padding: 0; overflow: hidden; background: #000; font-family: Arial, sans-serif; } #container { width: 100vw; height: 100vh; position: relative; } #controls { position: absolute; top: 10px; left: 10px; background: rgba(0, 0, 0, 0.8); color: white; padding: 15px; border-radius: 8px; z-index: 100; font-size: 12px; max-width: 200px; } .control-group { margin-bottom: 10px; } .control-group label { display: block; margin-bottom: 5px; font-weight: bold; } .control-group input { width: 100%; margin-bottom: 5px; } #info { position: absolute; top: 10px; right: 10px; background: rgba(0, 0, 0, 0.8); color: white; padding: 15px; border-radius: 8px; z-index: 100; font-size: 12px; max-width: 200px; } button { background: #4CAF50; border: none; color: white; padding: 8px 16px; text-align: center; text-decoration: none; display: inline-block; font-size: 12px; margin: 4px 2px; cursor: pointer; border-radius: 4px; } button:hover { background: #45a049; } </style> </head> <body> <div id="container"> <div id="controls"> <h3>Robotic Arm Controls</h3> <div class="control-group"> <label>Base Rotation (Yaw)</label> <input type="range" id="baseRotation" min="-180" max="180" value="0" step="1"> <span id="baseValue">0°</span> </div> <div class="control-group"> <label>Shoulder (Pitch)</label> <input type="range" id="shoulderAngle" min="-90" max="90" value="0" step="1"> <span id="shoulderValue">0°</span> </div> <div class="control-group"> <label>Elbow (Pitch)</label> <input type="range" id="elbowAngle" min="-135" max="135" value="0" step="1"> <span id="elbowValue">0°</span> </div> <div class="control-group"> <label>Wrist Pitch</label> <input type="range" id="wristPitch" min="-90" max="90" value="0" step="1"> <span id="wristPitchValue">0°</span> </div> <div class="control-group"> <label>Wrist Yaw</label> <input type="range" id="wristYaw" min="-180" max="180" value="0" step="1"> <span id="wristYawValue">0°</span> </div> <div class="control-group"> <label>Wrist Roll</label> <input type="range" id="wristRoll" min="-180" max="180" value="0" step="1"> <span id="wristRollValue">0°</span> </div> <div class="control-group"> <label>Gripper</label> <input type="range" id="gripperControl" min="0" max="1" value="0" step="0.1"> <span id="gripperValue">Open</span> </div> <button onclick="resetArm()">Reset Position</button> <button onclick="grabCube()">Grab/Release</button> </div> <div id="info"> <h3>Mission</h3> <p>Use the robotic arm to pick up the red cube and place it in the blue container.</p> <p><strong>Mouse:</strong> Rotate view</p> <p><strong>Scroll:</strong> Zoom</p> <p id="status">Status: Ready</p> </div> </div> <script> // Global variables let scene, camera, renderer, world; let robotArm = {}; let cube, container, ground; let cubeBody, containerBody; let isGrabbing = false; let grabConstraint = null; // Initialize the simulation function init() { // Create Three.js scene scene = new THREE.Scene(); scene.background = new THREE.Color(0x222222); // Create camera camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(5, 5, 5); camera.lookAt(0, 0, 0); // Create renderer renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.getElementById('container').appendChild(renderer.domElement); // Create physics world world = new CANNON.World(); world.gravity.set(0, -9.82, 0); world.broadphase = new CANNON.NaiveBroadphase(); // Add lighting const ambientLight = new THREE.AmbientLight(0x404040, 0.4); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 5); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; scene.add(directionalLight); // Create ground createGround(); // Create robotic arm createRobotArm(); // Create objects to manipulate createCube(); createContainer(); // Setup controls setupControls(); // Setup mouse controls setupMouseControls(); // Start animation loop animate(); } function createGround() { const groundGeometry = new THREE.PlaneGeometry(20, 20); const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 }); ground = new THREE.Mesh(groundGeometry, groundMaterial); ground.rotation.x = -Math.PI / 2; ground.receiveShadow = true; scene.add(ground); // Physics ground const groundShape = new CANNON.Plane(); const groundBody = new CANNON.Body({ mass: 0, material: new CANNON.Material() }); groundBody.addShape(groundShape); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); world.add(groundBody); } function createRobotArm() { // Base const baseGeometry = new THREE.CylinderGeometry(1, 1, 0.5, 8); const baseMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 }); robotArm.base = new THREE.Mesh(baseGeometry, baseMaterial); robotArm.base.position.set(0, 0.25, 0); robotArm.base.castShadow = true; scene.add(robotArm.base); // Shoulder joint const shoulderGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.8, 8); const shoulderMaterial = new THREE.MeshLambertMaterial({ color: 0x888888 }); robotArm.shoulder = new THREE.Mesh(shoulderGeometry, shoulderMaterial); robotArm.shoulder.position.set(0, 0.4, 0); robotArm.shoulder.castShadow = true; robotArm.base.add(robotArm.shoulder); // Upper arm const upperArmGeometry = new THREE.CylinderGeometry(0.15, 0.15, 2, 8); const upperArmMaterial = new THREE.MeshLambertMaterial({ color: 0xaa4444 }); robotArm.upperArm = new THREE.Mesh(upperArmGeometry, upperArmMaterial); robotArm.upperArm.position.set(0, 1, 0); robotArm.upperArm.castShadow = true; robotArm.shoulder.add(robotArm.upperArm); // Elbow joint const elbowGeometry = new THREE.SphereGeometry(0.2, 8, 8); const elbowMaterial = new THREE.MeshLambertMaterial({ color: 0x888888 }); robotArm.elbow = new THREE.Mesh(elbowGeometry, elbowMaterial); robotArm.elbow.position.set(0, 1, 0); robotArm.elbow.castShadow = true; robotArm.upperArm.add(robotArm.elbow); // Lower arm const lowerArmGeometry = new THREE.CylinderGeometry(0.12, 0.12, 1.5, 8); const lowerArmMaterial = new THREE.MeshLambertMaterial({ color: 0x4444aa }); robotArm.lowerArm = new THREE.Mesh(lowerArmGeometry, lowerArmMaterial); robotArm.lowerArm.position.set(0, 0.75, 0); robotArm.lowerArm.castShadow = true; robotArm.elbow.add(robotArm.lowerArm); // Wrist const wristGeometry = new THREE.SphereGeometry(0.15, 8, 8); const wristMaterial = new THREE.MeshLambertMaterial({ color: 0x888888 }); robotArm.wrist = new THREE.Mesh(wristGeometry, wristMaterial); robotArm.wrist.position.set(0, 0.75, 0); robotArm.wrist.castShadow = true; robotArm.lowerArm.add(robotArm.wrist); // Gripper base const gripperBaseGeometry = new THREE.CylinderGeometry(0.1, 0.1, 0.3, 8); const gripperBaseMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 }); robotArm.gripperBase = new THREE.Mesh(gripperBaseGeometry, gripperBaseMaterial); robotArm.gripperBase.position.set(0, 0.15, 0); robotArm.gripperBase.castShadow = true; robotArm.wrist.add(robotArm.gripperBase); // Gripper fingers const fingerGeometry = new THREE.BoxGeometry(0.05, 0.3, 0.1); const fingerMaterial = new THREE.MeshLambertMaterial({ color: 0x888888 }); robotArm.finger1 = new THREE.Mesh(fingerGeometry, fingerMaterial); robotArm.finger1.position.set(-0.1, 0.3, 0); robotArm.finger1.castShadow = true; robotArm.gripperBase.add(robotArm.finger1); robotArm.finger2 = new THREE.Mesh(fingerGeometry, fingerMaterial); robotArm.finger2.position.set(0.1, 0.3, 0); robotArm.finger2.castShadow = true; robotArm.gripperBase.add(robotArm.finger2); } function createCube() { const cubeGeometry = new THREE.BoxGeometry(0.3, 0.3, 0.3); const cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xff4444 }); cube = new THREE.Mesh(cubeGeometry, cubeMaterial); cube.position.set(2, 0.15, 0); cube.castShadow = true; scene.add(cube); // Physics cube const cubeShape = new CANNON.Box(new CANNON.Vec3(0.15, 0.15, 0.15)); cubeBody = new CANNON.Body({ mass: 1 }); cubeBody.addShape(cubeShape); cubeBody.position.set(2, 0.15, 0); cubeBody.material = new CANNON.Material(); world.add(cubeBody); } function createContainer() { const containerGroup = new THREE.Group(); // Container walls const wallMaterial = new THREE.MeshLambertMaterial({ color: 0x4444ff, transparent: true, opacity: 0.7 }); const wallThickness = 0.05; // Bottom const bottomGeometry = new THREE.BoxGeometry(1, wallThickness, 1); const bottom = new THREE.Mesh(bottomGeometry, wallMaterial); bottom.position.set(0, wallThickness / 2, 0); containerGroup.add(bottom); // Walls const wallGeometry = new THREE.BoxGeometry(wallThickness, 0.5, 1); const wall1 = new THREE.Mesh(wallGeometry, wallMaterial); wall1.position.set(-0.5, 0.25, 0); containerGroup.add(wall1); const wall2 = new THREE.Mesh(wallGeometry, wallMaterial); wall2.position.set(0.5, 0.25, 0); containerGroup.add(wall2); const wall3Geometry = new THREE.BoxGeometry(1, 0.5, wallThickness); const wall3 = new THREE.Mesh(wall3Geometry, wallMaterial); wall3.position.set(0, 0.25, -0.5); containerGroup.add(wall3); const wall4 = new THREE.Mesh(wall3Geometry, wallMaterial); wall4.position.set(0, 0.25, 0.5); containerGroup.add(wall4); containerGroup.position.set(-2, 0, 0); scene.add(containerGroup); container = containerGroup; // Physics container (simplified) const containerShape = new CANNON.Box(new CANNON.Vec3(0.5, 0.25, 0.5)); containerBody = new CANNON.Body({ mass: 0 }); containerBody.addShape(containerShape); containerBody.position.set(-2, 0.25, 0); // Don't add to world as it's just a target area } function setupControls() { const controls = { baseRotation: document.getElementById('baseRotation'), shoulderAngle: document.getElementById('shoulderAngle'), elbowAngle: document.getElementById('elbowAngle'), wristPitch: document.getElementById('wristPitch'), wristYaw: document.getElementById('wristYaw'), wristRoll: document.getElementById('wristRoll'), gripperControl: document.getElementById('gripperControl') }; controls.baseRotation.addEventListener('input', function() { robotArm.base.rotation.y = this.value * Math.PI / 180; document.getElementById('baseValue').textContent = this.value + '°'; }); controls.shoulderAngle.addEventListener('input', function() { robotArm.shoulder.rotation.z = this.value * Math.PI / 180; document.getElementById('shoulderValue').textContent = this.value + '°'; }); controls.elbowAngle.addEventListener('input', function() { robotArm.elbow.rotation.z = this.value * Math.PI / 180; document.getElementById('elbowValue').textContent = this.value + '°'; }); controls.wristPitch.addEventListener('input', function() { robotArm.wrist.rotation.z = this.value * Math.PI / 180; document.getElementById('wristPitchValue').textContent = this.value + '°'; }); controls.wristYaw.addEventListener('input', function() { robotArm.wrist.rotation.y = this.value * Math.PI / 180; document.getElementById('wristYawValue').textContent = this.value + '°'; }); controls.wristRoll.addEventListener('input', function() { robotArm.gripperBase.rotation.z = this.value * Math.PI / 180; document.getElementById('wristRollValue').textContent = this.value + '°'; }); controls.gripperControl.addEventListener('input', function() { const openAmount = parseFloat(this.value); robotArm.finger1.position.x = -0.1 - openAmount * 0.05; robotArm.finger2.position.x = 0.1 + openAmount * 0.05; document.getElementById('gripperValue').textContent = openAmount > 0.5 ? 'Open' : 'Closed'; }); } function setupMouseControls() { let mouseDown = false; let mouseX = 0, mouseY = 0; renderer.domElement.addEventListener('mousedown', function(event) { mouseDown = true; mouseX = event.clientX; mouseY = event.clientY; }); renderer.domElement.addEventListener('mouseup', function() { mouseDown = false; }); renderer.domElement.addEventListener('mousemove', function(event) { if (!mouseDown) return; const deltaX = event.clientX - mouseX; const deltaY = event.clientY - mouseY; camera.position.x = camera.position.x * Math.cos(deltaX * 0.01) - camera.position.z * Math.sin(deltaX * 0.01); camera.position.z = camera.position.x * Math.sin(deltaX * 0.01) + camera.position.z * Math.cos(deltaX * 0.01); camera.position.y = Math.max(1, camera.position.y - deltaY * 0.01); camera.lookAt(0, 0, 0); mouseX = event.clientX; mouseY = event.clientY; }); renderer.domElement.addEventListener('wheel', function(event) { const distance = Math.sqrt(camera.position.x ** 2 + camera.position.y ** 2 + camera.position.z ** 2); const factor = event.deltaY > 0 ? 1.1 : 0.9; if (distance * factor > 2 && distance * factor < 20) { camera.position.multiplyScalar(factor); } }); } function resetArm() { document.getElementById('baseRotation').value = 0; document.getElementById('shoulderAngle').value = 0; document.getElementById('elbowAngle').value = 0; document.getElementById('wristPitch').value = 0; document.getElementById('wristYaw').value = 0; document.getElementById('wristRoll').value = 0; document.getElementById('gripperControl').value = 0; robotArm.base.rotation.y = 0; robotArm.shoulder.rotation.z = 0; robotArm.elbow.rotation.z = 0; robotArm.wrist.rotation.z = 0; robotArm.wrist.rotation.y = 0; robotArm.gripperBase.rotation.z = 0; robotArm.finger1.position.x = -0.1; robotArm.finger2.position.x = 0.1; // Update display values document.getElementById('baseValue').textContent = '0°'; document.getElementById('shoulderValue').textContent = '0°'; document.getElementById('elbowValue').textContent = '0°'; document.getElementById('wristPitchValue').textContent = '0°'; document.getElementById('wristYawValue').textContent = '0°'; document.getElementById('wristRollValue').textContent = '0°'; document.getElementById('gripperValue').textContent = 'Open'; } function grabCube() { if (isGrabbing && grabConstraint) { // Release cube world.removeConstraint(grabConstraint); grabConstraint = null; isGrabbing = false; document.getElementById('status').textContent = 'Status: Released cube'; } else { // Try to grab cube const gripperPosition = new THREE.Vector3(); robotArm.gripperBase.getWorldPosition(gripperPosition); const distance = gripperPosition.distanceTo(cube.position); if (distance < 0.5) { // Create constraint to attach cube to gripper const gripperWorldPos = new CANNON.Vec3(gripperPosition.x, gripperPosition.y, gripperPosition.z); grabConstraint = new CANNON.PointToPointConstraint( cubeBody, new CANNON.Vec3(0, 0, 0), { position: gripperWorldPos } ); world.addConstraint(grabConstraint); isGrabbing = true; document.getElementById('status').textContent = 'Status: Grabbed cube'; } else { document.getElementById('status').textContent = 'Status: Too far from cube'; } } } function checkGoal() { if (cube.position.x < -1.5 && cube.position.x > -2.5 && cube.position.z < 0.5 && cube.position.z > -0.5 && cube.position.y < 1) { document.getElementById('status').textContent = 'Status: SUCCESS! Cube in container!'; } } function animate() { requestAnimationFrame(animate); // Update physics world.step(1/60); // Sync cube position with physics cube.position.copy(cubeBody.position); cube.quaternion.copy(cubeBody.quaternion); // Update gripper constraint if grabbing if (isGrabbing && grabConstraint) { const gripperPosition = new THREE.Vector3(); robotArm.gripperBase.getWorldPosition(gripperPosition); grabConstraint.pivotB.set(gripperPosition.x, gripperPosition.y, gripperPosition.z); } // Check if cube is in container checkGoal(); // Render renderer.render(scene, camera); } // Handle window resize window.addEventListener('resize', function() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); // Initialize on load init(); </script> </body> </html> ``` This is a complete 3D physics simulation of a 6-DOF robotic arm with the following features: **Key Features:** - **Six Degrees of Freedom**: Base rotation (yaw), shoulder pitch, elbow pitch, wrist pitch, wrist yaw, and wrist roll - **Physics Simulation**: Uses Cannon.js for realistic physics including gravity and collision detection - **Interactive Controls**: Sliders for each joint with real-time angle display - **Grab/Release Mechanism**: Can pick up and manipulate the red cube - **Mission Objective**: Move the red cube into the blue container - **3D Visualization**: Full 3D rendered environment with shadows and lighting - **Mouse Controls**: Click and drag to rotate view, scroll to zoom **Controls:** - Use the sliders on the left to control each joint of the robotic arm - "Grab/Release" button to pick up/drop the cube when the gripper is close enough - "Reset Position" button to return arm to default pose - Mouse controls for camera movement **Objective:** Position the robotic arm to grab the red cube and place it into the blue transparent container. The simulation will detect when you've successfully completed the task. The arm segments are color-coded (red upper arm, blue lower arm) and the physics engine ensures realistic movement and collision detection throughout the simulation.
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>6DOF Robotic Arm Simulation</title> <style> body { margin: 0; overflow: hidden; } canvas { display: block; } </style> </head> <body> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script> <script> // Set up Three.js scene const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 5, 10); // Position camera for a good view camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // Add lights const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); directionalLight.position.set(0, 10, 0); scene.add(directionalLight); // Set up Cannon.js physics world const world = new CANNON.World(); world.gravity.set(0, -9.82, 0); // Gravity downwards // Create ground for stability const groundBody = new CANNON.Body({ mass: 0, // Static shape: new CANNON.Plane(), }); groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2); world.addBody(groundBody); // Create container (a static box) const containerGeometry = new THREE.BoxGeometry(2, 2, 2); const containerMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); const containerMesh = new THREE.Mesh(containerGeometry, containerMaterial); containerMesh.position.set(5, 1, 0); // Position the container scene.add(containerMesh); const containerBody = new CANNON.Body({ mass: 0, // Static shape: new CANNON.Box(new CANNON.Vec3(1, 1, 1)), }); containerBody.position.set(5, 1, 0); world.addBody(containerBody); // Create static cube to be moved const cubeGeometry = new THREE.BoxGeometry(1, 1, 1); const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 }); const cubeMesh = new THREE.Mesh(cubeGeometry, cubeMaterial); cubeMesh.position.set(0, 1, 0); // Initial position scene.add(cubeMesh); const cubeBody = new CANNON.Body({ mass: 1, // Dynamic shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)), }); cubeBody.position.set(0, 1, 0); world.addBody(cubeBody); // Simplified robotic arm: Represent as a single end-effector for ease (controlled directly) const armEndEffectorGeometry = new THREE.SphereGeometry(0.5, 32, 32); const armEndEffectorMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff }); const armEndEffectorMesh = new THREE.Mesh(armEndEffectorGeometry, armEndEffectorMaterial); armEndEffectorMesh.position.set(0, 2, 0); // Initial position scene.add(armEndEffectorMesh); const armEndEffectorBody = new CANNON.Body({ mass: 1, // Make it dynamic to interact shape: new CANNON.Sphere(0.5), }); armEndEffectorBody.position.set(0, 2, 0); world.addBody(armEndEffectorBody); // Add a simple constraint to simulate arm holding the cube (e.g., weld when close) const constraint = new CANNON.HingeConstraint(armEndEffectorBody, cubeBody, { pivotA: new CANNON.Vec3(0, 0, 0), axisA: new CANNON.Vec3(1, 0, 0), pivotB: new CANNON.Vec3(0, 0, 0), axisB: new CANNON.Vec3(1, 0, 0), }); world.addConstraint(constraint); // This will simulate a basic connection // User controls: Track inputs for surge (forward/backward), heave (up/down), sway (left/right), pitch, yaw, roll let surge = 0; // Z-axis (forward/backward) let heave = 0; // Y-axis (up/down) let sway = 0; // X-axis (left/right) let pitch = 0; // Rotation around X-axis let yaw = 0; // Rotation around Y-axis let roll = 0; // Rotation around Z-axis // Add event listeners for keyboard input const keys = {}; document.addEventListener('keydown', (event) => { keys[event.key] = true; }); document.addEventListener('keyup', (event) => { keys[event.key] = false; }); function updateControls() { // Example mappings: Use WASD for surge/sway, QE for yaw, RF for pitch/roll, TG for heave if (keys['w']) surge += 0.1; // Forward if (keys['s']) surge -= 0.1; // Backward if (keys['a']) sway -= 0.1; // Left if (keys['d']) sway += 0.1; // Right if (keys['q']) yaw -= 0.1; // Yaw left if (keys['e']) yaw += 0.1; // Yaw right if (keys['r']) pitch += 0.1;// Pitch up if (keys['f']) pitch -= 0.1;// Pitch down if (keys['t']) heave += 0.1;// Up if (keys['g']) heave -= 0.1;// Down if (keys['z']) roll -= 0.1; // Roll left if (keys['c']) roll += 0.1; // Roll right // Apply to end-effector armEndEffectorBody.position.set(sway, heave + 2, surge); // Offset for initial height armEndEffectorBody.quaternion.setFromEuler(pitch, yaw, roll); } // Animation loop function animate() { requestAnimationFrame(animate); world.step(1 / 60); // Step physics simulation // Sync Three.js meshes with Cannon.js bodies cubeMesh.position.copy(new THREE.Vector3().copy(cubeBody.position)); cubeMesh.quaternion.copy(cubeBody.quaternion); armEndEffectorMesh.position.copy(new THREE.Vector3().copy(armEndEffectorBody.position)); armEndEffectorMesh.quaternion.copy(armEndEffectorBody.quaternion); updateControls(); // Update based on user input renderer.render(scene, camera); } animate(); // Handle window resize window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); </script> </body> </html> ```