Skip to main content
11 min read

Driving Mechanisms

Not every mechanism is driven the same way. Some machines can be animated by simply updating joint transforms. Others require solving a kinematic system, or handing the problem to the physics engine. And some are not rigid-body mechanisms at all, and are better represented with rendering techniques. This page gives the recommended approach for the most common industrial mechanisms.

Choosing the Right Approach

MechanismRecommended approach
Industrial robots, articulated arms, gantriesUpdate the transform of each joint
Delta robots, four-bar linkages, scissor liftsCompute the kinematics, or let the physics engine solve them
Conveyors and beltsMove the carried bodies with physics, scroll the belt surface's texture
Gear trains and transmissionsRotate each gear from the driving gear ratio
Tanks, silos and hoppersScale the visible level from the measured value
Pipes and process flowAnimate the material, not the geometry
Discrete items on a lineSpawn one entity per item

Serial Kinematic Chains

The easy case, and the one most industrial machines fall into. Each part is carried by the previous one, exactly like the real mechanism:

In the scene graph that is simply a nesting:

Base
└── Arm 1
└── Arm 2
└── End-Effector

Each entity's transform is relative to its parent, so motion propagates for free:

  • Move or rotate the Base and everything below it follows.
  • Rotate Arm 1 and Arm 2 and the End-Effector follow.
  • Rotate Arm 2 and only the End-Effector follows.

Nothing computes this — it falls out of the hierarchy. Which means driving a six-axis robot from live data is six component writes and no mathematics at all.

Updating the mechanism is simply a matter of updating the transform of each joint. A controller publishing all its joint angles in one message is the norm, and it maps directly onto the array form of updates:

const robotMapping: EventMapping = {
channel: "plant/robot-01/state",

// Entities named after the axes the controller reports.
entities: { byName: "axis-{id}" },

// One event, six entities.
updates: event => {
const { joints } = event.payload as { joints: Array<number> };
return joints.map((angle, index) => ({
id: String(index + 1),
update: { local_transform: { eulerOrientation: [0, radiansToDegrees(angle), 0] } },
}));
},
};

That is the whole integration. See mapping events to entities for the general shape.

Parallel and Closed-Loop Mechanisms

Some mechanisms cannot be represented by a parent-child hierarchy, because several links influence the same component at once. Typical examples include delta robots, four-bar linkages, scissor lifts and Stewart platforms.

The hierarchy cannot express this: the end-effector has four parents, and each forearm depends on the position of all the upper arms at once, so there is no propagation order to follow. And your data source will typically give you only the actuated values — the three upper-arm angles — leaving everything else to be derived.

There are two recommended approaches, and they are mutually exclusive per part.

Compute the Kinematics

If the forward kinematics are known, compute the dependent transforms from the actuated joints and update every moving part directly. The agent's updates function is the natural home for it: it receives the event, and it can return as many entity updates as the mechanism has moving parts.

const deltaMapping: EventMapping = {
channel: "plant/delta-01/state",
entities: { byName: "{id}" },

updates: event => {
const { theta1, theta2, theta3 } = event.payload as Record<string, number>;

// Your forward-kinematics solver: actuated angles in, every dependent
// transform out.
const pose = solveDeltaForwardKinematics(theta1, theta2, theta3);

return [
{ id: "upper-arm-1", update: { local_transform: { eulerOrientation: [0, 0, theta1] } } },
{ id: "upper-arm-2", update: { local_transform: { eulerOrientation: [0, 0, theta2] } } },
{ id: "upper-arm-3", update: { local_transform: { eulerOrientation: [0, 0, theta3] } } },
{ id: "forearm-1", update: { local_transform: pose.forearms[0] } },
{ id: "forearm-2", update: { local_transform: pose.forearms[1] } },
{ id: "forearm-3", update: { local_transform: pose.forearms[2] } },
{ id: "platform", update: { local_transform: pose.platform } },
];
},
};

When to choose it: the solver is known — most parallel machines have a closed-form solution published — and you want the scene to be a faithful mirror of the real machine rather than a plausible one. This produces the most accurate representation.

Use the Physics Engine

When analytical kinematics are difficult or unnecessary, build the mechanism out of physics constraints and drive only the actuated joints. The physics solver computes the remaining motion.

Each link is connected by a

Joint
, with a
Constraint
on top of it to free the degrees of freedom that should move — 3dverse has no named joint types, so a hinge or a slider is a constraint unlocking one axis. A
Constraint Actuator
is the motor: it drives a constraint toward a target position, orientation or velocity. Writing its targets from live data is all the mapping has to do:

updates: event => {
const { theta1, theta2, theta3 } = event.payload as Record<string, number>;
return [
{ id: "upper-arm-1", update: { constraint_actuator: { goalOrientation: yawQuaternion(theta1) } } },
{ id: "upper-arm-2", update: { constraint_actuator: { goalOrientation: yawQuaternion(theta2) } } },
{ id: "upper-arm-3", update: { constraint_actuator: { goalOrientation: yawQuaternion(theta3) } } },
];
},

See joints for how to build the constraint itself: the unlockedMotion bitfield, limits and springs, breaking forces, and collision behaviour between the constrained bodies.

When to choose it: the mechanism is hard to solve analytically, or approximately right is good enough and you would rather not own a solver. The cost is that the result is a simulation — it converges, it can overshoot, and it will not match the real machine to the millimeter.

Mechanisms That Are Not Rigid Chains

Rigid bodies connected by joints do not cover everything a plant contains. The rest is approximated — and knowing exactly what is being approximated is what keeps the twin honest.

Conveyors

Moving what sits on the belt is a first-class physics feature. Make the belt a static body, set modifyContact and contactVelocity on its physics_material component, and every rigid body touching it is given that relative velocity — see contact velocity, which has a video walkthrough.

Making the belt surface look like it is moving is a rendering problem, solved by scrolling its texture — below.

Belts, Chains and Gear Trains

Modelling every link of a roller chain and simulating it is not viable, and it is not what anyone does. The standard approach is a static mesh with a scrolling texture. The PBR shader exposes a UV offset as a material parameter:

finalUV = originalUV * scale + offset

offset is documented in UVs and reachable per entity through the dataJSON of the material component.

Meshing gears are the same idea with a different target — write local_transform.orientation per gear, scaled by the gear ratio, from one reported input speed. Neither needs the physics engine, and neither should use it: a simulated gear train is no more accurate than multiplying by a ratio you already know.

Levels, Flow and Deformation

3dverse has no fluid simulation. Physics bodies are rigid and do not deform. Say what you are showing accordingly — these are indications, not simulations:

To showDo this
Flow through a pipe or channelA static mesh with a scrolling material, driven by the reported flow rate
A tank or silo levelScale a mesh along one axis from the reported level, offsetting position by half its height
Discrete items on a lineSpawn one entity per item and move it — the stream defines the population
A surface that changes shapeWeights on a morph_targets component, authored in the DCC tool

The tank case is worth calling out because it is so common and so cheap: a level reading between 0 and 100 becomes a scale and a position, and reads correctly at a glance. Scaling happens about the entity's origin, so lift the entity by half its height to keep its base on the floor.

Which Technique Should I Use?

If your mechanism is...Use...What the data provides
Robot arm, gantry, articulated machineScene hierarchy, one write per jointJoint angles or positions
Delta robot, linkage, scissor liftSolve in the mapping, write every dependent transformThe actuated axes only
Same, when approximate is enoughjoint + constraint + constraint_actuatorTargets for the actuated axes
Conveyor, belt, chainScrolling UV offset (+ contactVelocity for carried bodies)A speed, integrated to an offset
Gear train, transmissionlocal_transform.orientation × gear ratioOne input speed
Tank, silo, hopper levelScale a mesh on one axisA level reading
Pipe or channel flowScrolling material on a static meshA flow rate
Discrete items on a lineSpawn an entity per itemItem identity and position

Next steps