Add geom surfacevel: zero-dof conveyors, treadmills and turntables.

https://www.youtube.com/watch?v=PdSdrqhSiZA

The new geom attribute surfacevel (6 numbers: linear and angular velocity in the geom's local frame, angular about the geom frame origin) specifies the velocity of the geom's surface material relative to the geom frame. The relative surface velocity of the two geoms is added to the tangential contact rows of efc_vel in mj_referenceConstraint, so friction drives touching bodies toward the motion of the surface: objects on a conveyor are transported at belt speed, turntables impart omega x r with torsional spin-up for condim >= 4, and surface velocities compose with each other and with body motion. The component along the contact normal is projected out: probe experiments showed that velocity-space emission chatters mass-independently and ingestion merely deepens penetration; normal-direction effects belong to force-space features.

surfacevel is interpreted in the geom frame as authored: for mesh geoms, whose compiled frame absorbs the mesh centering and principal-axes transform, the compiler re-expresses the authored value in the compiled frame.

No special interaction with sleeping: objects being transported do not fall asleep because they are moving; objects at rest on an active surface may sleep like any other resting object.

Includes showcase models (model/surfacevel/): a luggage carousel whose ring is a spinning square-profile supertorus fed by a cascade of belts with matched spinning end rollers, bags dropping in and circulating indefinitely; and a treadmill with a passive humanoid.

PiperOrigin-RevId: 948647785
Change-Id: I0c6559a91cc7ece1237eb8ac2e51986e7342d962
This commit is contained in:
Yuval Tassa
2026-07-15 17:57:28 -07:00
committed by Copybara-Service
parent 97eb1e5d0c
commit 4787c8094c
32 changed files with 781 additions and 11 deletions
+98
View File
@@ -0,0 +1,98 @@
<mujoco model="Luggage carousel">
<visual>
<headlight diffuse=".7 .7 .7" ambient=".25 .25 .25"/>
<quality shadowsize="8192"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1=".35 .5 .65" rgb2=".05 .05 .1" width="32" height="512"/>
<texture name="floor" type="2d" builtin="checker" rgb1=".18 .2 .22" rgb2=".24 .27 .3" width="300" height="300"/>
<material name="floor" texture="floor" texrepeat="12 12" reflectance=".1"/>
<material name="belt" rgba=".3 .45 .7 1"/>
<material name="island" rgba=".6 .3 .15 1"/>
<mesh name="ring" builtin="supertorus" params="64 .2 .4 1" scale="1.7 1.7 .9"/>
<material name="rail" rgba=".55 .55 .58 1"/>
<mesh name="bag1" builtin="supersphere" params="32 .3 .4" scale=".2 .13 .085"/>
<mesh name="bag2" builtin="supersphere" params="32 .55 .5" scale=".17 .12 .075"/>
<mesh name="bag3" builtin="supersphere" params="32 .25 .3" scale=".19 .14 .1"/>
<mesh name="bag4" builtin="supersphere" params="32 .6 .45" scale=".15 .1 .065"/>
<mesh name="bag5" builtin="supersphere" params="32 .4 .6" scale=".16 .11 .07"/>
<mesh name="bag6" builtin="supersphere" params="32 .35 .35" scale=".18 .12 .08"/>
</asset>
<worldbody>
<light pos="2 -4 5" dir="-.3 .5 -.8" diffuse=".6 .6 .6" intensity="3000000" range="20"/>
<light pos="-3 2 4" dir=".5 -.3 -.7" diffuse=".4 .4 .4" intensity="2000000" range="20"/>
<light pos="-1 -4.4 4" dir=".05 .05 -1" diffuse=".6 .55 .45" intensity="2500000" range="25" cutoff="30"/>
<light pos="0 -2.6 4.5" dir="0 .12 -1" diffuse=".6 .55 .45" intensity="2500000" range="25" cutoff="30"/>
<!-- hero downlight over the carousel: tight cone, cool white -->
<light pos="0 0 5.2" dir="0 0 -1" diffuse=".55 .6 .68" intensity="4000000" range="25" cutoff="26"/>
<!-- high ambience: dim blue night-wash point light, no shadows, lifts the
pitch black between the pools without flattening them -->
<light type="point" pos="1 0 7.5" diffuse=".25 .3 .4" intensity="600000" range="40" castshadow="false"/>
<geom name="floor" type="plane" size="8 8 .1" material="floor"/>
<!-- carousel ring: square-profile supertorus, surface spinning about +z -->
<geom name="carousel" type="mesh" mesh="ring" pos="0 0 .24" material="belt" surfacevel="0 0 0 0 0 .4" friction=".1" priority="1"/>
<!-- center island: dome closing the ring hole, only the top 45-degree cap shows -->
<geom name="island" type="sphere" size="1.85" pos="0 0 -0.7314" material="island"/>
<!-- guardrail: capsule ring centered on the top-outer corner of the torus,
so about three quarters of the tube sticks out -->
<replicate count="36" euler="0 0 10">
<geom type="capsule" size="0.1" fromto="2.0322 -0.1778 0.42 2.0322 0.1778 0.42" material="rail"/>
</replicate>
<!-- three-belt cascade, one conveyor definition (origin at belt center).
C2 (low, knee height) holds the definition and runs toward the carousel;
C1 (top, where bags start) and C3 (incline) are attached copies. -->
<frame pos="0 -4.2 .29" euler="0 0 90">
<frame name="conveyor">
<geom name="belt" type="box" size=".74 .3 .06" material="belt" surfacevel=".7 0 0 0 0 0"/>
<geom name="belt_rail1" type="box" size=".74 .02 .1" pos="0 -.33 .04" material="rail"/>
<geom name="belt_rail2" type="box" size=".74 .02 .1" pos="0 .33 .04" material="rail"/>
<geom name="belt_roller1" type="cylinder" size=".06 .3" pos="-.74 0 0" euler="90 0 0" material="belt" surfacevel="0 0 0 0 0 -11.667"/>
<geom name="belt_roller2" type="cylinder" size=".06 .3" pos=".74 0 0" euler="90 0 0" material="belt" surfacevel="0 0 0 0 0 -11.667"/>
</frame>
</frame>
<!-- C1: top conveyor, perpendicular, discharging onto C2's upstream end -->
<frame pos="-1.19 -4.355 .84">
<attach frame="conveyor" prefix="top_"/>
</frame>
<!-- C3: incline continuing C2, tilted 20 degrees, discharging over the ring -->
<frame pos="0 -2.6 0.5950" euler="0 0 90">
<frame euler="0 -24.34 0">
<attach frame="conveyor" prefix="up_"/>
</frame>
</frame>
<!-- bags, distributed along the cascade as if mid-operation -->
<body name="bag1" pos="0 -2.37 .88" euler="24.34 0 0">
<freejoint/>
<geom type="mesh" mesh="bag1" mass="4" rgba=".65 .2 .15 1"/>
</body>
<body name="bag2" pos="0 -3.6 .44" euler="0 0 -20">
<freejoint/>
<geom type="mesh" mesh="bag2" mass="3" rgba=".2 .45 .45 1"/>
</body>
<body name="bag3" pos="-1.6 -4.355 1.01" euler="0 0 75">
<freejoint/>
<geom type="mesh" mesh="bag3" mass="4.5" rgba=".4 .3 .2 1"/>
</body>
<body name="bag4" pos="-.75 -4.355 .97" euler="0 0 -10">
<freejoint/>
<geom type="mesh" mesh="bag4" mass="2.5" rgba=".25 .25 .3 1"/>
</body>
<body name="bag5" pos="-1.15 -4.31 .98" euler="0 0 40">
<freejoint/>
<geom type="mesh" mesh="bag5" mass="2.8" rgba=".6 .5 .2 1"/>
</body>
<body name="bag6" pos="0 -4.7 .44" euler="0 0 55">
<freejoint/>
<geom type="mesh" mesh="bag6" mass="3.5" rgba=".4 .25 .45 1"/>
</body>
</worldbody>
</mujoco>
+72
View File
@@ -0,0 +1,72 @@
<mujoco model="Treadmill">
<!-- A treadmill with no degrees of freedom: the deck surface moves via surfacevel,
with matched spinning end rollers (w = v/r). Standard running-treadmill
dimensions: 0.51 x 1.52 m belt, deck top 0.20 m, console at 1.35 m. The
attached humanoid is passive: it collapses and is swept off the back. -->
<statistic center="0 0 .7" extent="2.5"/>
<visual>
<headlight diffuse=".7 .7 .7" ambient=".3 .3 .3"/>
<quality shadowsize="8192"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1=".4 .5 .6" rgb2=".1 .1 .15"
width="32" height="512"/>
<texture name="tm_floor" type="2d" builtin="checker" rgb1=".25 .27 .28" rgb2=".3 .32 .34"
width="300" height="300"/>
<material name="tm_floor" texture="tm_floor" texrepeat="10 10" reflectance=".1"/>
<material name="tm_belt" rgba=".3 .45 .7 1"/>
<material name="tm_frame" rgba=".2 .2 .22 1"/>
<material name="tm_step" rgba=".5 .5 .52 1"/>
<model name="humanoid" file="../humanoid/humanoid.xml"/>
</asset>
<worldbody>
<!-- soft key (large bulb = soft shadows), shadowless cool fill, dim ambience -->
<light pos="-2.5 -1.5 3" dir=".6 .35 -.7" diffuse=".65 .6 .52" intensity="1000000" range="15"
bulbradius=".15"/>
<light pos="2 1.5 2.5" dir="-.5 -.4 -.6" diffuse=".35 .4 .48" intensity="700000" range="15"
castshadow="false"/>
<light type="point" pos="0 0 4" diffuse=".3 .3 .33" intensity="300000" range="20"
castshadow="false"/>
<geom name="floor" type="plane" size="4 4 .1" material="tm_floor"/>
<!-- deck: belt surface flows front to rear (+x), sweeping the rider off the back;
end rollers have radius = deck half-thickness, spin matched: w = 1.4/.05 -->
<geom name="deck" type="box" size=".76 .255 .05" pos="0 0 .15" material="tm_belt"
surfacevel="1.4 0 0 0 0 0"/>
<geom name="roller_front" type="cylinder" size=".05 .255" pos="-.76 0 .15" euler="90 0 0"
material="tm_belt" surfacevel="0 0 0 0 0 -28"/>
<geom name="roller_rear" type="cylinder" size=".05 .255" pos=".76 0 .15" euler="90 0 0"
material="tm_belt" surfacevel="0 0 0 0 0 -28"/>
<!-- side steps, flush with the belt surface -->
<geom name="step_left" type="box" size=".76 .04 .05" pos="0 -.295 .155" material="tm_step"/>
<geom name="step_right" type="box" size=".76 .04 .05" pos="0 .295 .155" material="tm_step"/>
<!-- motor hood over the front roller -->
<geom name="hood" type="box" size=".16 .3 .05" pos="-.86 0 .25" euler="0 -10 0"
material="tm_frame"/>
<!-- uprights, handrails and console -->
<geom name="upright_left" type="capsule" size=".025" fromto="-.8 -.28 .25 -.95 -.28 1.15"
material="tm_frame"/>
<geom name="upright_right" type="capsule" size=".025" fromto="-.8 .28 .25 -.95 .28 1.15"
material="tm_frame"/>
<geom name="rail_left" type="capsule" size=".02" fromto="-.95 -.28 1.15 -.35 -.28 1.05"
material="tm_step"/>
<geom name="rail_right" type="capsule" size=".02" fromto="-.95 .28 1.15 -.35 .28 1.05"
material="tm_step"/>
<geom name="console" type="box" size=".04 .3 .12" pos="-1 0 1.3" euler="0 -30 0"
material="tm_frame"/>
<geom name="screen" type="box" size=".005 .14 .07" pos="-.963 0 1.32" euler="0 -30 0"
rgba=".1 .25 .2 1"/>
<!-- passive rider -->
<frame pos="-.3 0 .21" euler="0 0 180">
<attach model="humanoid" body="torso" prefix="rider_"/>
</frame>
</worldbody>
</mujoco>