[AI] AI and MuJoCo
John Carlson
yottzumm at gmail.com
Tue Mar 24 03:22:28 PDT 2026
This is apparently the best I could do with MuJoCo on the web. Not much
different than current X_ITE...
Apparently WASM doesn't build with current sources, so I must have
the wrong repository, or AI is lying. There's a wasm folder I didn't
apparently get build, but people are welcome to try.
If someone wants the source, I can post.
Enjoy!
Screen Recording 2026-03-24 051853.mp4
<https://drive.google.com/open?id=11tuCkgLUa3XT6KQV63PpCloe54REFi1s>
On Tue, Mar 24, 2026 at 3:08 AM John Carlson <yottzumm at gmail.com> wrote:
> MuJoCo on the web looks like a way to simulate robots. While taking a
> broad brush to their web-based work, it looks like they are looking at the
> physics of robots, which isn’t a horrible thing, it just looks like the
> robots don’t have any clothes or soft-body physics involved. I have seen
> trampolines and ponchos in their non-web work. It looks like some groups
> are leveraging Three.JS for rendering, and WASM for simulation, much like I
> use Ammo.js for simulation and Three.Js for rendering. So I think if we
> want to do something with MuJoCo, we should separate out the physics engine
> from the rest of the project.
>
> I have little experience interfacing with JavaScript and WASM and mainly
> depends on demo work by others, so I might be able to construct a demo with
> an X3D-based encoding and MuJoCo physics engine using AI, essentially
> stripping out Ammo.Js, but I’m going to need to know how to include MuJoCo
> in a website, that’s not clear yet. So here goes Google AI. I’ll be
> testing this later tonight.
>
> 1. Command setup:
>
> =========
> npm init -y
> npm install three mujoco
> npm install -D vite
>
>
> Package.json
> ==========
> {
> "name": "mujoco-poncho",
> "version": "1.0.0",
> "type": "module",
> "scripts": {
> "dev": "vite",
> "build": "vite build"
> },
> "dependencies": {
> "mujoco": "^3.1.2",
> "three": "^0.160.0"
> },
> "devDependencies": {
> "vite": "^5.0.0"
> }
> }
>
>
>
> Index.html
> ============
>
> <!DOCTYPE html>
> <html lang="en">
> <head>
> <meta charset="UTF-8">
> <meta name="viewport" content="width=device-width, initial-scale=1.0">
> <title>MuJoCo Poncho Drape</title>
> <style>
> body { margin: 0; overflow: hidden; background: #222; }
> #loading { color: white; position: absolute; top: 20px; left: 20px;
> font-family: sans-serif; }
> </style>
> </head>
> <body>
> <div id="loading">Loading MuJoCo WASM...</div>
> <script type="module" src="/main.js"></script>
> </body>
> </html>
>
>
> Scene.xml
> ============
>
> <mujoco model="poncho_drape">
> <compiler angle="degree"/>
> <option timestep="0.005" gravity="0 0 -9.81"/>
>
> <worldbody>
> <!-- Lighting & Floor -->
> <light directional="true" diffuse="0.8 0.8 0.8" pos="0 0 5" dir="0 0 -1"/>
> <geom name="floor" type="plane" size="5 5 0.1" rgba="0.2 0.3 0.4 1"/>
>
> <!-- Static Humanoid Body (Welded to the world so it stands still) -->
> <body name="humanoid" pos="0 0 1.0">
> <!-- Torso -->
> <geom name="torso" type="capsule" size="0.15 0.25" rgba="0.8 0.6 0.4 1"/>
> <!-- Head -->
> <geom name="head" type="sphere" pos="0 0 0.45" size="0.12" rgba="0.8 0.6
> 0.4 1"/>
> <!-- Arms -->
> <geom name="left_arm" type="capsule" pos="-0.25 0 0.1" size="0.05 0.25"
> euler="0 -45 0" rgba="0.8 0.6 0.4 1"/>
> <geom name="right_arm" type="capsule" pos="0.25 0 0.1" size="0.05 0.25"
> euler="0 45 0" rgba="0.8 0.6 0.4 1"/>
> <!-- Legs -->
> <geom name="left_leg" type="capsule" pos="-0.1 0 -0.5" size="0.06 0.35"
> rgba="0.8 0.6 0.4 1"/>
> <geom name="right_leg" type="capsule" pos="0.1 0 -0.5" size="0.06 0.35"
> rgba="0.8 0.6 0.4 1"/>
> </body>
>
> <!-- The Poncho (Cloth Grid) -->
> <!-- Placed slightly above the head, falls under gravity -->
> <composite type="cloth" count="21 21 1" spacing="0.04" offset="0 0 1.8">
> <geom size="0.015" rgba="0.2 0.7 0.3 1" friction="1 0.005 0.0001"/>
> </composite>
> </worldbody>
> </mujoco>
>
>
> Main.js
> ==========
> import * as THREE from 'three';
> import { OrbitControls } from
> 'three/examples/jsm/controls/OrbitControls.js';
> import load_mujoco from 'mujoco';
>
> async function init() {
> // ==========================================
> // 1. Three.js Setup
> // ==========================================
> const scene = new THREE.Scene();
> const camera = new THREE.PerspectiveCamera(45, window.innerWidth /
> window.innerHeight, 0.1, 100);
> camera.position.set(3, 3, 3);
>
> const renderer = new THREE.WebGLRenderer({ antialias: true });
> renderer.setSize(window.innerWidth, window.innerHeight);
> renderer.setPixelRatio(window.devicePixelRatio);
> document.body.appendChild(renderer.domElement);
>
> const controls = new OrbitControls(camera, renderer.domElement);
> controls.target.set(0, 1, 0);
>
> const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
> scene.add(ambientLight);
> const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
> dirLight.position.set(5, 10, 5);
> scene.add(dirLight);
>
> // Coordinate Fix: MuJoCo is Z-up, Three.js is Y-up.
> // By placing all meshes in a rotated group, we avoid complex math
> per-mesh.
> const mujocoGroup = new THREE.Group();
> mujocoGroup.rotation.x = -Math.PI / 2;
> scene.add(mujocoGroup);
>
> // ==========================================
> // 2. MuJoCo & VFS Setup
> // ==========================================
> const mujoco = await load_mujoco();
>
> // Create a virtual file system so the C++ engine can read the XML
> mujoco.FS.mkdir('/working');
> mujoco.FS.mount(mujoco.MEMFS, { root: '.' }, '/working');
>
> // Fetch our XML and write it to WASM memory
> const xmlContent = await fetch('/scene.xml').then(res => res.text());
> mujoco.FS.writeFile('/working/scene.xml', xmlContent);
>
> // Initialize physics states
> const model = mujoco.MjModel.loadFromXML('/working/scene.xml');
> const data = new mujoco.MjData(model);
>
> // Remove loading text
> document.getElementById('loading').style.display = 'none';
>
> // ==========================================
> // 3. Mapping MuJoCo Geoms to Three.js
> // ==========================================
> const meshes =[];
>
> for (let i = 0; i < model.ngeom; i++) {
> const type = model.geom_type[i];
>
> // MuJoCo flat arrays: size is index * 3, rgba is index * 4
> const size = [
> model.geom_size[i * 3 + 0],
> model.geom_size[i * 3 + 1],
> model.geom_size[i * 3 + 2]
> ];
> const rgba =[
> model.geom_rgba[i * 4 + 0],
> model.geom_rgba[i * 4 + 1],
> model.geom_rgba[i * 4 + 2],
> model.geom_rgba[i * 4 + 3]
> ];
>
> let geometry;
> // 0: Plane, 2: Sphere, 3: Capsule, 6: Box
> if (type === 0) {
> geometry = new THREE.PlaneGeometry(size[0] * 2, size[1] * 2);
> } else if (type === 2) {
> geometry = new THREE.SphereGeometry(size[0], 16, 16);
> } else if (type === 3) {
> // MuJoCo capsule sizes: [radius, half-length]
> geometry = new THREE.CapsuleGeometry(size[0], size[1] * 2, 8, 16);
> // CRITICAL: Three.js aligns capsules on Y, MuJoCo aligns them on Z.
> geometry.rotateX(Math.PI / 2);
> } else if (type === 6) {
> geometry = new THREE.BoxGeometry(size[0] * 2, size[1] * 2, size[2] * 2);
> } else {
> geometry = new THREE.SphereGeometry(0.01); // Fallback
> }
>
> const material = new THREE.MeshStandardMaterial({
> color: new THREE.Color(rgba[0], rgba[1], rgba[2]),
> opacity: rgba[3],
> transparent: rgba[3] < 1.0,
> side: THREE.DoubleSide
> });
>
> const mesh = new THREE.Mesh(geometry, material);
> // We will manually overwrite the transform matrix from MuJoCo each frame
> mesh.matrixAutoUpdate = false;
>
> mujocoGroup.add(mesh);
> meshes.push(mesh);
> }
>
> // ==========================================
> // 4. The Animation / Simulation Loop
> // ==========================================
> const tempMatrix = new THREE.Matrix4();
>
> // Calculate how many physics steps to run per frame to match real time
> // Default framerate is ~60fps (0.016s). MuJoCo timestep is 0.005s.
> const stepsPerFrame = Math.ceil(0.016 / model.opt.timestep);
>
> function animate() {
> requestAnimationFrame(animate);
>
> // Step physics simulation
> for (let i = 0; i < stepsPerFrame; i++) {
> mujoco.mj_step(model, data);
> }
>
> // Sync transforms
> for (let i = 0; i < model.ngeom; i++) {
> // Read 3D position
> const px = data.geom_xpos[i * 3 + 0];
> const py = data.geom_xpos[i * 3 + 1];
> const pz = data.geom_xpos[i * 3 + 2];
>
> // Read 3x3 rotation matrix
> const m0 = data.geom_xmat[i * 9 + 0]; const m1 = data.geom_xmat[i * 9 +
> 1]; const m2 = data.geom_xmat[i * 9 + 2];
> const m3 = data.geom_xmat[i * 9 + 3]; const m4 = data.geom_xmat[i * 9 +
> 4]; const m5 = data.geom_xmat[i * 9 + 5];
> const m6 = data.geom_xmat[i * 9 + 6]; const m7 = data.geom_xmat[i * 9 +
> 7]; const m8 = data.geom_xmat[i * 9 + 8];
>
> // Load into Three.js 4x4 Matrix (Row-major order)
> tempMatrix.set(
> m0, m1, m2, px,
> m3, m4, m5, py,
> m6, m7, m8, pz,
> 0, 0, 0, 1
> );
>
> meshes[i].matrix.copy(tempMatrix);
> }
>
> controls.update();
> 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);
> });
> }
>
> init();
>
>
>
> Command to run
> ========
>
>
> npm run dev
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://web3d.org/pipermail/ai_web3d.org/attachments/20260324/f68fc3e5/attachment-0001.html>
More information about the AI
mailing list