Commit Graph

5240 Commits

Author SHA1 Message Date
Yuval Tassa 790f8fac30 Generate the MJCF grammar table and enforce its constraints.
The hand-written MJCF[] table in xml_native_reader.cc is replaced by
mjcf_table.inc, emitted from mjcf.schema by generate_mjcf_table.py and
checked for freshness by doc_test. nMJCF is now self-sizing. The two
tables are identical as trees of (tag, cardinality, attribute-set);
within-row attribute order changes where the schema factors shared
groups and projects default-context rows, and top-level rows follow
the schema's dependency order -- neither affects validation, which is
set-based, nor XMLschema.rst, whose generator orders sections itself
(regenerated here, reading the .inc instead of the reader source).

The schema's constraint declarations become enforcement: the emitter
writes a companion MJCF_constraints[] array (row-indexed into MJCF[]),
and mjXSchema::Check evaluates each element's constraints after its
attribute check, with uniform messages derived from the declaration:
"at most one of 'fovy', 'sensorsize' can be specified", "attributes
'reftype', 'refname' must be specified together", and so on.
Multi-attribute bundles render as ('site1', 'site2').

Fifteen hand-written co-occurrence checks across fourteen elements are
deleted -- connect/weld semantics mixing and completeness, the actuator
transmission mutex, camera fovy/sensorsize, light directional/type,
inertial fullinertia-versus-orientation, rangefinder and the distance
family, contact's matching criteria, user-sensor pairing, the frame
family's reftype/refname, size memory exclusivities, mesh builtin
exclusions, and attach body/frame (newly declared). Tests assert the
uniform messages.

Two findings along the way: sensorsize-requires-resolution is a
value-level compiler rule (positive resolution), not a presence rule --
a presence constraint would be wrong and is not declared; and Size()'s
nstack/njmax range checks tested the spec value before assignment, so
they never validated the parsed value -- now they do.

Verified by compiling all 81 models in the model/ corpus.

PiperOrigin-RevId: 958064622
Change-Id: I802cf5c0aee08a62926e36a281320ff9e34c0668
2026-08-02 16:30:02 -07:00
Yuval Tassa 3f8db4c17a Add mjcf.schema and its declarative schema language.
The complete MJCF surface in one hand-maintained file: 144 elements, 8
shared attribute groups, 47 enums, 1,497 typed attributes with
defaults, the presence-constraint inventory previously visible only as
hand-written reader checks, and the save policies previously visible
only as hand-written writer logic.

The language is a small IDL: elements bound to their mjSpec structs,
typed attributes with arities and defaults, enum keyword sets with C
bindings, reusable and variant groups, explicit name/reference
namespaces (id<ns>/ref<ns>, following dm_control's
identifier/reference model), child cardinalities, presence constraints
(exclusive/together/requires/oneof over attribute bundles), bitwise
flag sets, identity constants (set field = CONST), fixed char arrays
(chars[n], arity counting characters), numeric range facets, and two
escape hatches: reading=custom (no typed binding is generated; both
reading and saving are hand-written) and writing=custom (the binding
drives the reader, the save policy is hand-written).
doc/generate/mjcf_schema.py is the dependency-free parser and semantic
validator; errors report file:line; 55 unit tests. The language is
documented by the cheat-sheet legend at the top of the schema file.

The schema was bootstrapped by extraction from the sources of record --
the MJCF[] table, the mjMap keyword tables, the ~660 ReadAttr*/MapValue
call sites, mjspec.h struct fields, and the default-constructors in
user_init.c and engine_init.c -- then hand-curated. Same-tag elements
that differ by context are distinct declarations carrying an xml=
facet; worldbody, frame and replicate carry alias=body, mirroring
mjXSchema::NameMatch. The top-level order is by dependency, what a
saved file should read like: front matter, declarations before use,
the tree, the sections that reference it, the data tail.
PiperOrigin-RevId: 958060695
Change-Id: Ie10fd9f0ef202a3626f4d635d02c8731a4d287df
2026-08-02 16:10:14 -07:00
Yuval Tassa fb07a9ca50 Fix missing contacts for deeply penetrating boxes.
When penetration exceeds a box's smallest half-size, the midpoint
between the contact surfaces can land outside both boxes. The outside-
box filter in mjc_BoxBox then removed every contact of the manifold,
returning nothing for a visibly overlapping pair, letting boxes fall
through each other. Fixes #1800.

If the filter would remove all contacts, restore the penetrating ones.
This is strictly additive: configurations where any contact survives
the filter are unchanged.

The removed midpoints are bitwise-identical to the witness midpoints
computed by mj_geomDistance for these configurations, so the positions
follow the engine-wide contact position convention; re-anchoring them
onto a box surface would not.

Fixes #1800

PiperOrigin-RevId: 957867315
Change-Id: Ia9c858661d4badeb2a832d25455e33402936011d
2026-08-02 01:34:59 -07:00
Yuval Tassa 8655446f25 Fix spurious deep contacts in box-box collision.
In the edge-edge path of the box-box collider, when line clipping yields
no points, the corner generators accept points whose projection
parameters are out of range and clamp them into the valid range. The
depth of such a point is the Euclidean distance between two unrelated
points, mixing lateral offset into penetration depth, and on the
penetrating side it is admitted with no margin check. For thin boxes
meeting edge-to-face within margin, this produced a contact with
penetration three orders of magnitude larger than the boxes' true
separation, exploding resting stacks.

No contact can penetrate deeper than the support-interval overlap along
the separating axis, which the SAT stage has already computed. Enforce
this bound on all points emitted by the edge-edge path. The bound
carries margin plus relative and size-scaled slack covering rounding
error: the depth of the deepest legitimate point is algebraically equal
to the bound, so an exact comparison would drop real contacts. The
slack is precision-dependent: in mjUSESINGLE builds the two
computations of the same overlap disagree by tens of ulps, and slack
calibrated for double precision rejects real single-precision contacts.

Differential fuzzing against the nativeccd oracle over 200k random
near-contact thin-box configurations, in both precisions: impossibly
deep contacts drop from 1018 to 4 (worst excess from 2.5x the bounding
diameter to 0.001x), with no legitimate shallow-penetration contact
lost.

PiperOrigin-RevId: 957628830
Change-Id: Iaa1f10742f91c55bf831296cba0936eb50ea09b0
2026-08-01 07:48:32 -07:00
Matija Kecman b389b28b87 Support parsing .mjz files in the Studio parser module
PiperOrigin-RevId: 957616406
Change-Id: I582d23039b4aec745e0525392c062102f4d9ae25
2026-08-01 06:47:01 -07:00
Copybara-Service 2bb152b77b Merge pull request #3445 from giusenso:fix/actuator-velocity-index
PiperOrigin-RevId: 957597256
Change-Id: I10a1c85fb49f00d60aa65d0cbdedc110423adb95
2026-08-01 05:10:55 -07:00
Yuval Tassa 279df98cd0 Add the pid actuator: setpoint inputs, integral action, slew rate limiting.
<pid kp kv|dampratio [ki imax] [slewmax]> is a PID controller with real position and velocity setpoint inputs on a single force output, plus an optional feedforward input. With a zero velocity setpoint it reproduces <position> bit-exactly; the input signature is any subset of [pos, vel, ff], selected with input="..." and recorded as mjtCtrlInput bits in
actuator_ctrlspec; absent setpoint inputs are fixed at zero, so the control vector contains no inert entries.

kp and kv are single-sourced in the affine bias parameters (biasprm[1,2]) with no gainprm mirror: every consumer of the position-servo shape
(dampratio conversion, inheritrange, qDeriv) reads one location, which is what makes the bit-exact <position> parity possible. Controller state uses dyntype 'pid' with slot-gated activations in the order [slew, integral], following the dcmotor slot idiom: slewmax (dynprm[1]) rate limits the effective position setpoint through an activation holding it;
ki (gainprm[0]) integrates the position error -- wrapped on rotational transmissions -- with anti-windup clamping of the integrand at imax (dynprm[0]). Both features require the pos input. Servo input unpacking is shared with the dcmotor controller (unpackServoInputs); per-input ranges are exposed as posrange/velrange/ffrange.

This subsumes the functionality of the mujoco.pid plugin with proper activation state: correct under all integrators, visible to keyframes, act sensors and reset. Migration: kp/ki/kd map to kp/ki/kv, plugin imax is in force units (divide by ki), slewmax carries over; the single ctrl becomes input="pos".

PiperOrigin-RevId: 957588898
Change-Id: Id2786836ca6e76f58e5b5cc8323fc23be0a53784
2026-08-01 04:28:43 -07:00
Yuval Tassa 7bc1aa9b05 Add Filament lighting and shadow features and fixes.
- Add a hidden directional light to Filament scenes with no lights to work around soft shadow blackout bug.
- Respect quality/shadowsize as default shadow map resolution.
- Interpret light/bulbradius in correct units for each shadow type and update XML reference docs.
- Support updating the shadow map resolution of live Filament lights.
- Clamp light intensities to non-negative values in the Filament debug panel.

PiperOrigin-RevId: 957305808
Change-Id: Ic7a3430aaa3b117be7fbb7ace836d68e54cdf65a
2026-07-31 13:28:01 -07:00
Matija Kecman 0ca53dacf8 Update SDL2 copybara transformations
PiperOrigin-RevId: 957294480
Change-Id: Ied7131cafe83b11379e275ab4287609674251db5
2026-07-31 13:04:33 -07:00
Yuval Tassa e295b31c60 Improve wireframe rendering support in Filament renderer and Studio.
- Draw complete wireframes in the Filament renderer.
- Bind the 'W' key to toggle the wireframe render flag in Studio.

PiperOrigin-RevId: 957230911
Change-Id: Ib583c81ddcfa834686c7cf01d2b12842479993ea
2026-07-31 11:00:38 -07:00
Matija Kecman 6c78c98418 MuJoCo Web Viewer: update code to support the web viewer
PiperOrigin-RevId: 957147035
Change-Id: Ibe5d68b2654a7faac23c297d4f492929209c078d
2026-07-31 08:09:38 -07:00
Matija Kecman a1e90c24b3 MuJoCo Web Viewer: top-level web_viewer.py component
PiperOrigin-RevId: 957119030
Change-Id: If6c8265dfa62f9a6378898644935ee335f3fa8b2
2026-07-31 08:09:05 -07:00
Matija Kecman f8344301e9 MuJoCo Web Viewer: server component
PiperOrigin-RevId: 957086932
Change-Id: Idcac876206a2df7e962481c12c2fc74ff9997948
2026-07-31 08:08:32 -07:00
Yuval Tassa e9197e1e9d Use logarithmic sliders in the object launcher.
Projectile parameters are positive scale factors, best adjusted
multiplicatively. Replace the input boxes with logarithmic sliders
spanning 0.1x to 10x the seeded default, which sits at the geometric
midpoint of the range; mass spans 0.1x to 100x since heavy projectiles
are a common want. Ctrl+Click still allows typing values outside the
slider range, and each slider gets a reset button restoring its
default. Adds an ImGui_SliderLog widget operating on mjtNum.

PiperOrigin-RevId: 957071873
Change-Id: Id46eae28f73308d98681ccbeb2764c949f98f9d9
2026-07-31 08:08:00 -07:00
Copybara-Service de26b05861 Merge pull request #3439 from smallquail:flex-metric-blocks
PiperOrigin-RevId: 957063540
Change-Id: I86e7b2ec92c7b2acd1f1f7140e3b8a7578c48dbf
2026-07-31 08:07:22 -07:00
Matija Kecman f1f52a92b8 Add headless EGL/OSMesa OpenGL UI context rendering for web viewer
PiperOrigin-RevId: 957060514
Change-Id: I8a0df22255c5a938d18e172c638a8c83dfe64b1b
2026-07-31 04:36:14 -07:00
Yuval Tassa 47e1b1bd1f Seed object launcher defaults from model statistics.
PiperOrigin-RevId: 957053250
Change-Id: Iba103877136c31bf1310f2b55bd8cfc5593a87ec
2026-07-31 04:14:10 -07:00
Yuval Tassa 0f90722c1a Arm the object launcher by opening its window.
The launch key binding was gated behind a checkbox because it stayed
active with the plugin window closed. Condition it on the window being
open instead: the arming state is visible, so the checkbox is redundant
and the press-proof chord can relax to plain Enter. Text fields and
dialogs are unaffected: plugin chords are not checked while ImGui
captures the keyboard, and built-in shortcuts dispatch first.

PiperOrigin-RevId: 956796864
Change-Id: I4e5b2bdcaafbcc1b1ba113dd37ef8314b3ade49e
2026-07-30 16:26:17 -07:00
Yuval Tassa 24297edfda Add close button to Studio plugin windows.
Plugin windows could only be closed from the Plugins menu. Pass the
plugin's active flag to ImGui::Begin so the window gets a close button
that writes the same persisted state as the menu toggle. Skip the
update callback while the window is collapsed.

PiperOrigin-RevId: 956795070
Change-Id: I61cb5c26e7af935b6bb296f1b89142a72b0daca1
2026-07-30 16:22:52 -07:00
Matija Kecman 4dd70d2367 MuJoCo Web Viewer: add web client containing code that runs in the browser
PiperOrigin-RevId: 956698568
Change-Id: Ia4bebcb25b488d255994018da03e9115187b890c
2026-07-30 13:17:48 -07:00
Haroon Qureshi 8b78378868 Add mjv_camera2GLCamera to mujoco.h.
PiperOrigin-RevId: 956698065
Change-Id: I36856d7142bb5e798c5a90c19609288ddd7c4aac
2026-07-30 13:16:37 -07:00
Yuval Tassa a3425c72a7 Ignore unsupported light types in the classic renderer.
The classic renderer supports only directional and spot lights, but initLights() set GL light colors for every scene light before dispatching on type, and the render passes enabled GL light slots without filtering. Since no position was ever set for unsupported types (mjLIGHT_POINT, mjLIGHT_IMAGE), OpenGL's default eye-space position turned them into phantom headlights with the model light's diffuse color, washing out the scene. A cast-shadow image light additionally triggered a full shadow pass with an unset light projection.

Filter all light setup and enable sites on a shared type predicate, so unsupported light types contribute nothing. The no-lights ambient fallback now applies when no supported lights are present.

PiperOrigin-RevId: 956696548
Change-Id: Ic29763df7f9330f21d94f14501ae2559fd460f2b
2026-07-30 13:13:42 -07:00
Taylor Howell b46d76683d Import Warp v1.15.0
PiperOrigin-RevId: 956678131
Change-Id: I09a672b7335a85a17c95773ffc29eb9272744986
2026-07-30 12:39:05 -07:00
Yuval Tassa 98ecadcdf2 Add reset buttons to Studio sliders.
Slider rows in the joints and controls tabs could only be reset through
a hidden right-click context menu. Replace it with a visible reset
button at the right edge of each row, disabled while the value is at
its reset target so the button column doubles as a modified indicator.
Rows split the remaining width equally between slider and label, and
labels clip at the button column. Adds an ImGui_ResetButton widget.

PiperOrigin-RevId: 956627705
Change-Id: I9c178f6cd1cc806c7ffd6f531acf7f2d470d9346
2026-07-30 11:10:26 -07:00
Haroon Qureshi e662350357 Simple window class for python.
PiperOrigin-RevId: 956544312
Change-Id: I52d0e2f555592bed3889f05a1406e7633eb6d468
2026-07-30 08:39:09 -07:00
Giuseppe Sensolini Arrà a670a6ac10 Merge branch 'google-deepmind:main' into fix/actuator-velocity-index 2026-07-30 16:53:10 +02:00
Haroon Qureshi 58a54ef474 Move resource provider registration into a separate library.
PiperOrigin-RevId: 956506812
Change-Id: I9acde018e282171f41e0f8c756cd02b39c35dc67
2026-07-30 07:19:08 -07:00
Yuval Tassa 6c86e12567 Make the selection outline occlusion-aware in the filament renderer.
PiperOrigin-RevId: 956489408
Change-Id: I948493587eb154cac6e0fc31d5df987967405ff8
2026-07-30 06:38:18 -07:00
Yuval Tassa 60889f5e85 Add favicon to MuJoCo Live.
PiperOrigin-RevId: 956476524
Change-Id: I3f33cd09764e1236999b7d269045a2f8c2a8236e
2026-07-30 06:09:12 -07:00
Taylor Howell 82471cbdfb MuJoCo Warp documentation: Large scenes initialize trees asleep
![docs](https://screenshot-v2.corp.google.com/image/222i7rtheo5eg.png)

PiperOrigin-RevId: 956462670
Change-Id: I0c6f211126e5d86bfacebff49591c17cb503b062
2026-07-30 05:34:13 -07:00
Matija Kecman c2e95b4161 Add StatePayload and serialization helpers for web viewer
PiperOrigin-RevId: 956400340
Change-Id: Ibe049afe10eca85e5fb6d718a78863a641b864bb
2026-07-30 03:01:23 -07:00
Taylor Howell 6a7a723093 MuJoCo Warp documentation: Per-world assets
PiperOrigin-RevId: 956399999
Change-Id: I8d951e5e52e16442325c66cc33ac693bf68939e3
2026-07-30 03:00:29 -07:00
Giuseppe Sensolini 4c2653273c Fix actuator_velocity indexing in the LuGre bristle update.
mj_nextActivation indexed actuator_velocity with the actuator id, but the
array is laid out by output address. The two coincide only when all
preceding actuators are single-output, so a DC motor placed after a
multi-output SO3 actuator integrated its bristle state with another
actuator's velocity, while act_dot (computed in mj_fwdActuation with the
correct velocity) disagreed with the actual evolution of the state.
Fixes DCMotorTest.LuGreBristleVelocityOrderInvariance.
2026-07-29 17:48:05 +02:00
Giuseppe Sensolini f95d50c12f Add regression test for LuGre bristle velocity indexing.
The DC motor's LuGre bristle state must integrate the velocity of its own
transmission, so actuator ordering cannot affect it. The test places a
multi-output SO3 actuator before the DC motor, making the motor's actuator
id and output address diverge, and requires the bristle state to match the
motor-first ordering. Currently fails: the exact ZOH update in
mj_nextActivation reads actuator_velocity[actuator_id] instead of the
motor's own actuator_velocity[outadr], so the bristle integrates the SO3
actuator's velocity.
2026-07-29 17:48:05 +02:00
Yuval Tassa fba29294d3 Update lighting and visual settings in the fridge door model.
PiperOrigin-RevId: 955888127
Change-Id: I1af5a2d4cf2a6fb3325b04b4cb6fd4cceeb92d60
2026-07-29 08:08:46 -07:00
Yuval Tassa 6a4a289c3e Open macOS file dialogs in the directory of the seeded path.
OpenFileDialog and SaveFileDialog hardcoded the user's Documents directory
and placed the full seed path in the name field. Studio seeds dialogs with
the current model's path, so File > Open now starts in the model's
directory with the filename pre-filled, falling back to Documents when no
path is available. Also avoids passing a potentially non-NUL-terminated
string_view to stringWithUTF8String.

PiperOrigin-RevId: 955869713
Change-Id: I7563f2cb2e39089883d5e37a3d259055c26e146c
2026-07-29 07:25:15 -07:00
Yuval Tassa 5800dfc929 Improve Studio help window with fixes, completions, and task grouping.
Fix, complete, and group Studio's help window by task.

PiperOrigin-RevId: 955869367
Change-Id: I5ae7b3661dd80a1273d8c9fecee303c361c102c3
2026-07-29 07:24:11 -07:00
Michael Moss 22f4a286f3 Update deprecated node 20 to node 24.
PiperOrigin-RevId: 955851551
Change-Id: I03dcfaa29e39c6b0b685982d3a12c4da82adf1ba
2026-07-29 06:42:30 -07:00
Alessio 55d13aec5f Replace the flex metric factorization with a block preconditioner
Every step, the flex block of the implicit effective metric M + K was
factorized by sparse Cholesky, because K depends on the configuration. On
model/flex/bag.xml, added here, that is roughly half the step, against a
comparable share for the constraint solve it exists to accelerate.

Keep only the metric's per-vertex 3x3 diagonal blocks, prefactored. Neither
consumer needs the exact inverse: the CG constraint solver only wants a
preconditioner, and qacc_smooth can come from an iterative solve using those
blocks. They are O(n) to build and to apply, but weaker, so CG runs about twice
the iterations and qacc_smooth becomes an iteration rather than a direct solve.
Net, the bag model steps roughly twice as fast.

The preconditioner, by metric state. Inactive, meaning no flex elasticity or an
explicit integrator: M^-1, unchanged. Bending only (nefmK == 0): M^-1 plus the
exact constant bending factor from mj_setConst on the dofs it covers,
unchanged; that factor is built at model compile time and costs nothing per
step. Per-step stiffness: M^-1 plus the 3x3 blocks, where before it was a
per-step sparse Cholesky, or, when M couples across the flex block, an inner
PCG of up to 50 iterations run once per outer CG iteration.

Only models carrying per-step stretch stiffness change in wall-clock. Both
ponchos hold their timing and take slightly fewer CG iterations than before,
because the preconditioner is now symmetric: it applies M^-1 and the covered
blocks to disjoint sets of dofs, where previously the two overlapped and the
operator was not symmetric, which PCG requires.

mjd_effSolve is the accurate solve of (M + K)x = b; what used to carry that
name only preconditions and is now mjd_effPrec. Its CG guarded the division by
pAp with mjMINVAL, an absolute floor on a quantity that scales with the square
of the right-hand side, so a small b aborted the solve while the curvature was
healthy: four flex models were quietly left short of tolerance. For an SPD
metric the guard is positivity, and with that the same solves converge. The qacc_smooth call site in
mj_fwdAcceleration is textually unchanged but now reaches the iterative solve,
which converges on opt.tolerance rather than a hardcoded threshold, floored in
mjUSESINGLE builds where the squared target is unreachable in float. Reaching
the iteration cap names the ill-conditioned flex stiffness and then reports it
through mjWARN_INERTIA, rather than returning an under-converged result.
Covered dofs are located by walking the covered rows of the stiffness matrix,
as they need not be 3-aligned from dof 0: any joint declared before a flexcomp
shifts them.

mjData.efm_L_rownnz, efm_L_rowadr and efm_L_colind described the sparsity of
the deleted factorization and are removed: left NULL with nonzero mjxmacro
extents they made the Python bindings hand back uninitialized arrays.
efm_active loses the value 2 for the same reason, nothing selects a solve path
on preconditioner exactness any more. Both are recorded under breaking changes.

model/flex/bag.xml is added because no shipped model carried per-step stretch
stiffness. The ponchos are bending-only and trampoline.xml uses an explicit
integrator, so the metric never activates there. It is excluded from
WriteReadCompareTest: stretch stiffness amplifies rest geometry that XML rounds
on save.
2026-07-29 14:36:15 +01:00
Michael Moss 559c2af751 Update MuJoCo Studio icons to have less padding.
PiperOrigin-RevId: 955416676
Change-Id: Ieb13b3ed37f5495a647a6ff58099150a79ba02ee
2026-07-28 12:53:25 -07:00
Copybara-Service d400914e97 Merge pull request #3437 from smallquail:flex-rotation-fix
PiperOrigin-RevId: 955127380
Change-Id: Ie31ab56f7d64dd1a2266828c0fcd3683b55768a5
2026-07-28 02:17:23 -07:00
Alessio d895888da4 Use the inlined mji_ 3x3 helpers throughout engine_derivative.c
Convert the four remaining mju_mulMat{,T}{Vec,Mat}3 call sites in this
file to their inlined mji_ equivalents, so the file is consistent after
the review change to the stretch paths. Aliasing is unaffected: the
inlined variants mark the result restrict, and every call already
required a distinct result (the public versions overwrite res while
reading vec).
2026-07-28 09:14:26 +01:00
Alessio a6743d8e0d Address review: blank line, and use the inlined mji_ 3x3 helpers
Per review on #3437: add the missing blank line above the new comment
block, and use the internal inlined mji_mulMat{,T}{Vec,Mat}3 helpers
rather than the public mju_ ones, matching fe9dc584. Six call sites in
the two stretch paths; pre-existing mju_ calls elsewhere in the file are
left alone.
2026-07-28 09:09:38 +01:00
Michael Moss 1db801460b Update MuJoCo version to 3.11.1 following the 3.11.0 release
PiperOrigin-RevId: 954833728
Change-Id: I1c006cdd72072c6821d7b3c28d8ac320de19b8c5
2026-07-27 14:35:26 -07:00
Michael Moss b85fdca54f Update changelog for the 3.11.0 release.
PiperOrigin-RevId: 954795939
Change-Id: I357468dd9072a5d1bab5af2529039e4924db12ed
2026-07-27 13:22:37 -07:00
Matija Kecman ca5337161d Export netimgui files in MuJoCo Copybara configuration for use with the upcoming Web Viewer
PiperOrigin-RevId: 954769526
Change-Id: I4cdee083082f7d55c2babd5e664ec0283a2aaf63
2026-07-27 12:33:41 -07:00
Alessio 55b43414dd Fix stretch stiffness basis for flexes in rotated parent bodies
The implicit effective metric assembles the stretch stiffness from
world-space edge vectors, but a flex vertex body's slide dofs are
expressed in its parent body's frame. When that frame is rotated the
assembled operator is therefore not the Jacobian of the passive stretch
force, which mj_flexPassiveStretch already maps into the dof frame with
xmat^T. The metric is then inconsistent with the force it linearizes:
implicit integration loses its stability guarantee, and models that the
same flex handles comfortably in an unrotated frame diverge.

Apply the matching change of basis in both places that build or apply the
stretch stiffness: mjd_flexStretch_mul rotates the input dof vector into
world and the scattered result back, and mjd_flexStiff_assemble sandwiches
each 3x3 block as R_bi^T * blk * R_bj. Both are no-ops when the parent is
unrotated. Bending needs no change: its blocks are isotropic, and
R^T (q I) R = q I.

This completes the fix in fe9dc584, which covered the passive force paths
and the interp (trilinear) derivative but not the standard stretch one.

On a mesh flex inside a body with a 90-degree rotation, the metric's
directional agreement with the force Jacobian goes from cos = 0.57 to
cos = 1.0, and a hanging sheet that previously reached 176% strain
settles at 0.87%.
2026-07-27 20:08:31 +01:00
Yuval Tassa c7b6e0b8dd Fix the array size of biasprm in mjsActuator specification.
Note mjNBIAS == mjNGAIN, this change is a no-op.

PiperOrigin-RevId: 954712145
Change-Id: I096246e5d060596c4025ea4c23e8e9e887c8cd50
2026-07-27 10:45:29 -07:00
Matija Kecman e4d0a88ffd Refactor viewer loop and shutdown handling to support conditional rendering and clean exits.
This change introduces a get_frame method to the viewer protocol, replacing the previous is_running check. This allows the viewer loop to skip rendering and lifecycle events when no active frame is ready (for example, when no browser is connected to the web viewer).

Additionally, this change:
- Adds a shutdown hook to ViewerHandle to wait for the viewer to cleanly release resources before exiting.
- Implements an ExitEvent handler in the viewer protocol to stop the run loop.
- Adds an http_port configuration option for the web viewer.
- Prevents double-closing in the viewer.

PiperOrigin-RevId: 954603868
Change-Id: Iaba7d131dc94191de51150464514dd10fa7834b0
2026-07-27 06:55:17 -07:00
Matija Kecman e23b504201 Change how ImGui clip rect scaling and scissor computation works in imgui_bridge to fix flickering and clipping errors in the web viewer
Avoid mutating ImGui draw lists in place by scaling clip rects locally during scissor computation instead of calling ScaleClipRects. This prevents clip rects from growing exponentially on persistent draw lists, such as streamed remote UI. Additionally, clamp the scaled clip rects to the viewport boundaries to prevent Filament's scissor test from rejecting out-of-bounds rects, while avoiding resetting to full-window scissors which would disable clipping.

The draw geometry is now immutable in the Update function (ImGui texture state is still mutated).

PiperOrigin-RevId: 954277141
Change-Id: Ifdf6866bb4eabaca44b020ea40acc246428b4f0b
2026-07-26 13:35:03 -07:00