- Load settings prior to first frame so dockspace layout restores reliably.
- Save settings on exit in App::~App().
- Persist and restore open/closed state of collapsible sections.
PiperOrigin-RevId: 966657100
Change-Id: I64fb5d02912bb36d096d7ac26ce8e0de03e6617b
The timestep, the solver tolerances, and impratio span several orders of
magnitude, where a linear +/- step is useless. Add ImGui_LogStepper, whose
buttons walk a "well-tempered" logarithmic ladder of round numbers that
closes each decade on a power of ten (see WellTemperedStep). It lays out
like ImGui's own InputScalar stepper -- field, then the - / + buttons, then
the label.
Apply it to the timestep, Tolerance, the LS/Noslip/CCD/Sleep tolerances,
and Imp Ratio. Strictly-positive quantities for which zero is meaningful
(the tolerances) take a zero_below floor: the ladder's bottom rung is 0, so
stepping down snaps to zero and stepping up off zero returns to the floor.
The timestep and impratio, which never want zero, leave it unset.
PiperOrigin-RevId: 965888812
Change-Id: I823e60913ecb2be2cade96baa5bf7de9e2334d5a
The dcmotor input block is any subset of the canonical list [pos, vel,
ff, voltage], selected with input="pos vel ff voltage" and recorded as
mjtCtrlInput bits in actuator_ctrlspec like pid. Tokens are required in
canonical order: the attribute denotes a set, the block always packs
canonically, and accepting permutations invites reading the string as a
layout choice. The mode flag in gainprm[8] is retired (reserved,
written 0).
Controller gains are now in torque space, as for pid: the controller
commands tau = kp*(q*-l) + kd*(v*-ldot) + ki*x_I + tau_ff over the
present inputs (absent setpoints frozen at zero) and converts to drive
voltage V = R/K * tau + K*ldot. The second term compensates back-EMF,
as the current loop of a real torque-mode driver does (torque commands
are current commands): commanded torque is delivered exactly until a
limit binds, and the torque-speed envelope emerges from the Vmax clamp.
The map uses the nameplate R: thermal resistance growth is not
compensated, so a hot motor under-delivers by R/R(T). A stateless
setpoint dcmotor now matches <pid> exactly, for any K and R; the old
back-EMF droop remains available as the physical behavior of the raw
voltage path. Voltage-space datasheet gains convert by K/R. Controller
inputs require a positive motor constant (the map divides by K), and
controller gains require a controller input.
ff and voltage are distinct inputs, different in kind: ff is a torque
feedforward added to the controller output, uniform with pid's ff
(feedforward in the actuator's output space), while voltage is the raw
terminal voltage of the physical device, injected downstream of the
controller and its Vmax clamp, unclamped (ctrlrange bounds it if
desired). input="voltage" is the default: the plain voltage-commanded
motor, whose behavior is unchanged by this commit. The integrator
always accumulates position error; the old velocity mode's integral
term, ki*(int(u)dt - theta), which tracked the integral of the velocity
command, is retired without replacement, keeping ki mode-independent --
commanded integrated velocity belongs to an integrator activation
state, not to controller gains. slewmax rate-limits the first controller
input -- position setpoint (rad/s), velocity setpoint (rad/s^2) or torque
feedforward (N*m/s), each a real driver feature (reference ramping,
ramped-velocity and ramped-torque input modes); the raw voltage input
is never rate-limited and slewmax requires a controller input.
input="none" selects the empty signature: the actuator owns no controls
at all (nu = 0 is now legal with actuators present) and is purely
passive -- LuGre friction, cogging and back-EMF braking as passive
joint forces. This exists because auxiliary dynamic states (the LuGre
bristle) attach to actuators, not joints. The terminal voltage is
identically zero, i.e. a shorted motor (dynamic braking); motorconst=0
decouples the electrical branch. mjINPUT_NONE is a distinct enum value
because ctrlspec = 0 means "unset, use the type default". History and
delay require an input; the controller voltage override and input read
in mj_fwdActuation are gated on a nonempty block.
The analytic velocity derivative of the controller becomes
dV/dw = -kd*R/K + K, whose second term cancels the back-EMF bias
exactly: the net damping of an unclipped torque-mode motor is -kd, and
of a voltage-mode or passive motor -K^2/R. Viewers label inputs via
mj_actuatorInputName: pos, vel, ff, voltage.
The dcmotor LaTeX design doc is updated accordingly: torque-space
units, the tau->V map and its saturation-generated envelope, the
input-block pipeline figure, and a Passive Operation section.
PiperOrigin-RevId: 965795351
Change-Id: Ibc308ca21bd6bad014e77f950ee08feaad449b73
Also add a detailed architectural comment that might be useful in future.
PiperOrigin-RevId: 965500329
Change-Id: Ia6a2a9137f07f726838b0acb28039a394db0efa2
Replaces the box-box collider's manifold generation and post-filtering with a
single structured implementation, and deletes the accumulated repair logic it
obsoletes. Net 319 lines out of the engine.
Algorithm:
- The separating-axis test keeps the closed-form support evaluation and chooses
the axis of maximum separation among the 15 candidates by plain argmax.
Edge-cross axes whose cross product has norm below rounding are skipped: in
the nearly-parallel regime their direction is cancellation noise, previously
the source of arbitrary-normal contacts with box-scale spurious depth. A
winning edge axis within eight degrees of the best face axis is replaced by
that face unless it is better by five percent (ODE's classic fudge): resting
stacks otherwise flip between the edge and face contact codes by rounding
noise from step to step, thrashing the solver warm start until the stack
explodes. The substitution runs after the search rather than filtering during
it, so a worse non-aliasing edge cannot steal the contact the substitution
meant to give to the face.
- Face contacts clip the incident face against the reference face's side planes
(Sutherland-Hodgman). Depth is measured along the reference normal only,
never as a Euclidean distance between unrelated points. Contact position is
midway between the surfaces along the normal, so its distance to either box
is bounded by half the contact depth. Every surviving vertex of the clipped
polygon becomes a contact, so the manifold is the actual contact patch, at
most eight points as before.
- Edge contacts use the closest-point pair between the two supporting edge
segments. A near-zero axis component makes the support-corner sign ambiguous;
both signs are enumerated and the closest witness pair wins.
- Margin is an acceptance band throughout: SAT early-out and clip acceptance.
- The rounding thresholds are stated per precision. The separation tests are
the ones that cost correctness: comparing exactly against the margin reports
a pair overlapping by less than the rounding error of its own support
evaluation as separated, and the boxes pass through each other. Over 239k
overlapping pairs that is eight misses under mjUSESINGLE and none in double;
the collider this replaces misses the same eight. Slack proportional to the
summed half-sizes leaves five, which overlap by 7e-9 to 3e-8 of their own
scale, below single-precision epsilon, where the boxes are not distinguishable
from touching. Erring toward contact is the safe direction: the driver already
excludes a contact whose distance reaches the margin.
Deleted: the conditional acceptance cascade keyed on how many points earlier
generators emitted, the u/v clamping that fabricated contacts from out-of-range
projections, the outside-box removal filter and its missing-fallback hole,
exact-floating-point deduplication, and the edge-path depth clamp. The
structure makes those bug classes unrepresentable rather than filtered: depth
is a projection by construction. Every reported depth is the exact support
overlap along the contact's own normal, verified over 246k overlapping poses to
within two ulps; the face preference costs direction, not depth, deviating from
the minimum-translation axis by at most 8.1 degrees and 5.3% of its depth.
The previous implementation is preserved verbatim as mjc_BoxBoxLegacy in
test/engine/boxbox_legacy.c, a static library that only the box-box tests link,
so the claims above are measured rather than asserted. It needs no private
engine symbols. Three tests compare against it:
- NearAlignedManifoldIsExact sweeps the relative angle of a resting pair across
the regime where the edge-cross axes degenerate into noise, pinning the full
clipped polygon and a contact normal equal to the face normal exactly, where
the previous collider drifts off it.
- AlignedTowerStands settles a twenty-box tower, which comes to rest four
million times quieter than under the previous collider, which never settles
and eventually topples.
- ShallowOverlapSurvivesRounding pins a pair overlapping by 7e-8 of its scale,
reported as separated under mjUSESINGLE without slack on the separation tests.
On stacks of plates across aspect ratios from 4:1 to 25:1, five layouts each,
the collider settles into a tight band of 1e-4 to 3e-4 while the previous one
intermittently blows up to as much as 2.6e-2.
engine_collision_box_fuzz_test.cc cross-validates randomized poses against
GJK/EPA on identical box meshes and against a spherical-Fibonacci support
sweep, with hard gates per sample: no phantom penetration, no missed contact at
zero margin, no contact deeper than the true depth, contacts within half their
own depth of both boxes, and one normal per manifold. Both invocations run in
about a second.
EdgeContactAtDepthBound's tolerance widens to the five percent design band; the
three-orders-of-magnitude depth bug it pins is still caught, the deviation
being 0.13 percent of the depth.
The 100-box pile benchmark steps about 7% faster with 1.6% fewer contacts.
PiperOrigin-RevId: 965114952
Change-Id: Ie98cdcce8d1aed3ff2da938cb29703fd9c241258
- Parallelize asset prefetching (max 4 workers) with retry logic and on-screen error banner, replacing silent failures and cryptic WASM out-of-bounds crashes with explicit missing-file errors.
- Support binary Uint8Array/ArrayBuffer in C++ asset registry via typed_memory_view, preventing Embind string coercion from corrupting binary shader packages into ASCII text.
- Automatically glob .mat files in CMake instead of maintaining a manual list.
- Add missing reflection materials to prefetch arrays.
- Disable caching for index.html in web server so template/script updates apply on normal page reload without stale cache issues, the file is small so caching had a negligible upside.
- Add null resource and payload validation in ObjectManager::LoadMaterial before constructing Filament materials.
PiperOrigin-RevId: 963364509
Change-Id: I7de79df6a5cede4e54683415ee193f1c5a665286
Add public mjprofile.h header with MJ_PROFILE_* macros that get compiled out if Tracy is not enabled.
Integrate Tracy into engine macro timers and build configurations.
PiperOrigin-RevId: 962329763
Change-Id: Iac9d8f9ff0d909beea212a7aa8a6a3dcb512010f