Margin and gap redesign (breaking change)

PiperOrigin-RevId: 914329812
Change-Id: I905665e4c1965bdb8b90587e5b1277cfbfe5cce0
This commit is contained in:
Yuval Tassa
2026-05-12 09:43:07 -07:00
committed by Copybara-Service
parent 7f5be412a0
commit a4e49f2dff
29 changed files with 2082 additions and 178 deletions
+27 -24
View File
@@ -376,8 +376,8 @@ adjust it properly through the XML.
:at:`o_margin`: :at-val:`real, "0"`
This attribute replaces the margin parameter of all active contact pairs when :ref:`Contact override <COverride>` is
enabled. Otherwise MuJoCo uses the element-specific margin attribute of :ref:`geom<body-geom>` or
:ref:`pair<contact-pair>` depending on how the contact pair was generated. See also :ref:`Collision` in the
Computation chapter. The related gap parameter does not have a global override.
:ref:`pair<contact-pair>` depending on how the contact pair was generated. See :ref:`margin and gap<coMarginGap>` in
the Computation chapter. The related gap parameter does not have a global override.
.. _option-o_solref:
.. _option-o_solimp:
@@ -2702,18 +2702,20 @@ helps clarify the role of bodies and geoms in MuJoCo.
.. _body-geom-margin:
:at:`margin`: :at-val:`real, "0"`
Distance threshold below which contacts are detected and included in the global array mjData.contact. This however
does not mean that contact force will be generated. A contact is considered active only if the distance between the
two geom surfaces is below margin-gap. Recall that constraint impedance can be a function of distance, as explained
in :ref:`CSolver`. The quantity this function is applied to is the distance between
the two geoms minus the margin plus the gap.
Geometric inflation of the geom surface for the purpose of contact force generation. When the distance between two
geom surfaces is below ``margin``, the contact is considered active and contact forces are generated. The constraint
impedance can be a function of distance, as explained in :ref:`CSolver`. The quantity this function is applied to is
the distance between the two geoms minus the ``margin``. See :ref:`margin and gap<coMarginGap>`.
.. _body-geom-gap:
:at:`gap`: :at-val:`real, "0"`
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are ignored by the
constraint solver but are included in mjData.contact for the purpose of custom computations. When this value is
positive, geom distances between margin and margin-gap correspond to such inactive contacts.
Additional contact detection buffer beyond ``margin``. When this value is positive, contacts are detected at
distance ``margin + gap`` but forces are only generated at distance ``margin``. Contacts with distance between
``margin`` and ``margin + gap`` are included in ``mjData.contact`` as inactive contacts (with ``efc_address`` = -1).
These inactive contacts can be used for custom computations, for example by :ref:`adhesion<actuator-adhesion>`
actuators which use contacts in the gap zone to generate adhesive forces without producing contact forces.
See :ref:`margin and gap<coMarginGap>`.
.. _body-geom-fromto:
@@ -4110,14 +4112,15 @@ friction can only be created with this element.
.. _contact-pair-margin:
:at:`margin`: :at-val:`real, "0"`
Distance threshold below which contacts are detected and included in the global array mjData.contact.
Geometric inflation for the purpose of contact force generation. Contacts are detected at distance ``margin + gap``
and forces are generated at distance ``margin``.
.. _contact-pair-gap:
:at:`gap`: :at-val:`real, "0"`
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are ignored by the
constraint solver but are included in mjData.contact for the purpose of custom computations. When this value is
positive, geom distances between margin and margin-gap correspond to such inactive contacts.
Additional contact detection buffer beyond ``margin``. When this value is positive, contacts with distance between
``margin`` and ``margin + gap`` are included in ``mjData.contact`` as inactive contacts but no contact forces are
generated.
.. _contact-exclude:
@@ -6286,16 +6289,16 @@ This element has nine custom attributes in addition to the common attributes:
This element defines an active adhesion actuator which injects forces at contacts in the normal direction, see
illustration video. The model shown in the video can be found `here
<https://github.com/google-deepmind/mujoco/tree/main/model/adhesion>`_ and includes inline annotations. The transmission target
is a :el:`body`, and adhesive forces are injected into all contacts involving geoms which belong to this body. The force
is divided equally between multiple contacts. When the :at:`gap` attribute is not used, this actuator requires active
contacts and cannot apply a force at a distance, more like the active adhesion on the feet of geckos and insects rather
than an industrial vacuum gripper. In order to enable "suction at a distance", "inflate" the body's geoms by
:at:`margin` and add a corresponding :at:`gap` which activates contacts only after :at:`gap` penetration distance. This
will create a layer around the geom where contacts are detected but are inactive, and can be used for
applying the adhesive force. In the video above, such inactive contacts are blue, while active contacts are orange.
An adhesion actuator's length is always 0. :at:`ctrlrange` is required and must also be nonnegative (no repulsive forces
are allowed). The underlying :el:`general` attributes are set as follows:
<https://github.com/google-deepmind/mujoco/tree/main/model/adhesion>`_ and includes inline annotations. The transmission
target is a :el:`body`, and adhesive forces are injected into all contacts involving geoms which belong to this body.
The force is divided equally between multiple contacts. When the :ref:`gap<body-geom-gap>` attribute is not used, this
actuator requires active contacts and cannot apply a force at a distance, more like the active adhesion on the feet of
geckos and insects rather than an industrial vacuum gripper. In order to enable "suction at a distance", set the
:ref:`gap<body-geom-gap>` attribute of the body's geoms to a positive value. This creates a layer around each geom where
contacts are detected but no contact forces are generated, and the adhesive force can act across this gap. In the video
above, such inactive contacts are blue, while active contacts are orange. An adhesion actuator's length is always 0.
:at:`ctrlrange` is required and must also be nonnegative (no repulsive forces are allowed). The underlying :el:`general`
attributes are set as follows:
=========== ======= =========== ========
Attribute Setting Attribute Setting
+51
View File
@@ -2,6 +2,57 @@
Changelog
=========
Upcoming version (not yet released)
-----------------------------------
.. admonition:: Breaking API changes
:class: attention
- The semantics of the contact ``margin`` and ``gap`` parameters have been redesigned for conceptual clarity and
consistency with `NVIDIA Newton <https://developer.nvidia.com/newton>`__. See the new
:ref:`margin and gap<coMarginGap>` documentation section for details.
Previously, ``margin`` controlled the *detection threshold* (contacts exist when ``dist < margin``) and ``gap``
was subtracted from it to produce the *force threshold* (forces generated when ``dist < margin - gap``). This was
unintuitive: users expected ``margin`` to mean geometric inflation and ``gap`` to mean a spatial gap.
Under the new semantics, ``margin`` is the geometric inflation of the geom surface and ``gap`` is an additional
detection buffer beyond the inflated surface:
- **Detection**: contacts are created when ``dist < margin + gap``.
- **Force generation**: constraint forces are applied when ``dist < margin``.
- **Inactive contacts**: contacts with ``margin < dist ≤ margin + gap`` are included in ``mjData.contact`` but
generate no force (``efc_address = -1``). This is useful for :ref:`adhesion<actuator-adhesion>` actuators and
custom callbacks.
With the default values ``margin = 0``, ``gap = 0``, the behavior is unchanged.
.. image:: images/modeling/margin_gap_light.svg
:width: 80%
:align: center
:class: only-light
.. image:: images/modeling/margin_gap_dark.svg
:width: 80%
:align: center
:class: only-dark
|
**Migration:** Models that use the default ``gap="0"`` (the vast majority) require no changes. For models with
``gap > 0``, apply the following transformation to preserve identical behavior:
.. code-block::
margin_new = margin_old - gap_old
gap_new = gap_old
For example, a geom with the old attributes ``margin="0.1" gap="0.1"`` should be changed to
``margin="0" gap="0.1"``.
Negative ``margin`` values are now permitted (corresponding to ``gap > margin`` under the old semantics). The
constraint ``margin + gap >= 0`` should be maintained to ensure valid collision detection.
Version 3.8.1 (May 11, 2026)
----------------------------
+49 -6
View File
@@ -909,7 +909,7 @@ In addition to the above quantities which are computed online, each contact has
model definition.
.. list-table::
:widths: 1 5
:widths: 2 6
:header-rows: 1
* - Parameter
@@ -920,15 +920,53 @@ model definition.
- Vector of friction coefficients with dimensionality ``condim-1``. See below for semantics of the specific
coefficients.
* - ``margin``
- The distance margin used to determine if the contact should be included in the global contact array
``mjData.contact``.
- The geometric inflation of the geom surfaces. Contacts are detected when the distance is below
``margin + gap``, and contact forces are generated when the distance is below ``margin``.
* - ``gap``
- For custom computations it is sometimes convenient to include contacts in ``mjData.contact`` but not generate
contact forces. This is what ``gap`` does: contact forces are generated only when the normal distance is below
(margin - gap).
- An additional detection buffer beyond ``margin``. Contacts with distance between ``margin`` and
``margin + gap`` are included in ``mjData.contact`` as inactive contacts, but no contact forces are generated.
This is useful for action-at-a-distance effects, for example by :ref:`adhesion<actuator-adhesion>` actuators.
* - ``solref`` and ``solimp``
- :ref:`Solver <Solver>` parameters, explained later.
.. _coMarginGap:
margin and gap
^^^^^^^^^^^^^^
Each geom has a ``margin`` and a ``gap`` parameter, defined in the table above. The values for both parameters are
:ref:`summed<CContact>` when considering contact between the two geoms. Together they define three regimes of contact
detection and force generation, illustrated in the figure below.
.. image:: ../images/modeling/margin_gap_light.svg
:width: 90%
:align: center
:class: only-light
.. image:: ../images/modeling/margin_gap_dark.svg
:width: 90%
:align: center
:class: only-dark
The distance between two geom surfaces determines which regime applies:
- **No contact** (distance > ``margin + gap``): The geom surfaces, including their gap buffers, are not overlapping.
No contact is generated.
- **Inactive contact** (``margin`` < distance ≤ ``margin + gap``): A contact is detected and included in
``mjData.contact``, but no contact force is generated (``efc_address = -1``). These contacts can be used for custom
computations, for example by :ref:`adhesion<actuator-adhesion>` actuators.
- **Active contact** (distance ≤ ``margin``): The contact is active and constraint forces are generated. The constraint
impedance function is applied to the quantity ``distance - margin``, which is non-positive in this regime.
Negative ``margin`` values, corresponding "shrinkgage" of the geometric shape, are permitted. In this case
``margin + gap >= 0`` must be maintained for collision detection to work correctly.
.. _coCondim:
condim
^^^^^^
The contact friction cone can be either elliptic or pyramidal. This is a global setting determined by the choice of
constraint solver: the elliptic solvers work with elliptic cones, while the pyramidal solvers work with pyramidal cones,
as defined later. The ``condim`` parameter determines the contact type, and has the following meaning:
@@ -961,6 +999,11 @@ Note that condim cannot be 2 or 5. This is because the two tangential directions
treated as pairs. The friction coefficients within a pair can be different though, which can be used to model skating
for example.
.. _coCones:
Friction cones
^^^^^^^^^^^^^^
Now we describe the friction cones and the corresponding Jacobians more formally. In this section only, let :math:`f`
denote the vector of constraint forces for a single contact (as opposed to the system-level vector of constraint
forces), :math:`\mu` the vector of friction coefficients, and :math:`n` the contact dimensionality condim. For
+887
View File
@@ -0,0 +1,887 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="684pt" height="460.8pt" viewBox="0 0 684 460.8" xmlns="http://www.w3.org/2000/svg" version="1.1">
<metadata>
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2026-05-12T11:57:09.015687</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
<dc:title>Matplotlib v3.10.6, https://matplotlib.org/</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 460.8
L 684 460.8
L 684 0
L 0 0
L 0 460.8
z
" style="fill: none; opacity: 0"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 54.189474 377.242105
L 629.810526 377.242105
Q 659.178947 377.242105 659.178947 347.873684
L 659.178947 300.884211
Q 659.178947 271.515789 629.810526 271.515789
L 54.189474 271.515789
Q 24.821053 271.515789 24.821053 300.884211
L 24.821053 347.873684
Q 24.821053 377.242105 54.189474 377.242105
z
" clip-path="url(#pad24174a74)" style="fill: #8e8e93; opacity: 0.35; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_3">
<path d="M 54.189474 362.557895
L 629.810526 362.557895
Q 644.494737 362.557895 644.494737 347.873684
L 644.494737 300.884211
Q 644.494737 286.2 629.810526 286.2
L 54.189474 286.2
Q 39.505263 286.2 39.505263 300.884211
L 39.505263 347.873684
Q 39.505263 362.557895 54.189474 362.557895
z
" clip-path="url(#pad24174a74)" style="fill: #3d9140; opacity: 0.55; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_4">
<path d="M 54.189474 347.873684
L 629.810526 347.873684
Q 629.810526 347.873684 629.810526 347.873684
L 629.810526 300.884211
Q 629.810526 300.884211 629.810526 300.884211
L 54.189474 300.884211
Q 54.189474 300.884211 54.189474 300.884211
L 54.189474 347.873684
Q 54.189474 347.873684 54.189474 347.873684
z
" clip-path="url(#pad24174a74)" style="fill: #5b7ea8; opacity: 0.6; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_5">
<path d="M 124.673684 242.147368
C 144.924035 242.147368 164.347709 234.101819 178.666869 219.782659
C 192.986029 205.463499 201.031579 186.039824 201.031579 165.789474
C 201.031579 145.539123 192.986029 126.115449 178.666869 111.796289
C 164.347709 97.477128 144.924035 89.431579 124.673684 89.431579
C 104.423334 89.431579 84.999659 97.477128 70.680499 111.796289
C 56.361339 126.115449 48.315789 145.539123 48.315789 165.789474
C 48.315789 186.039824 56.361339 205.463499 70.680499 219.782659
C 84.999659 234.101819 104.423334 242.147368 124.673684 242.147368
z
" clip-path="url(#pad24174a74)" style="fill: #8e8e93; opacity: 0.35; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_6">
<path d="M 124.673684 224.526316
C 140.250877 224.526316 155.192165 218.337432 166.206904 207.322693
C 177.221642 196.307955 183.410526 181.366666 183.410526 165.789474
C 183.410526 150.212281 177.221642 135.270993 166.206904 124.256254
C 155.192165 113.241516 140.250877 107.052632 124.673684 107.052632
C 109.096492 107.052632 94.155203 113.241516 83.140465 124.256254
C 72.125726 135.270993 65.936842 150.212281 65.936842 165.789474
C 65.936842 181.366666 72.125726 196.307955 83.140465 207.322693
C 94.155203 218.337432 109.096492 224.526316 124.673684 224.526316
z
" clip-path="url(#pad24174a74)" style="fill: #3d9140; opacity: 0.55; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_7">
<path d="M 124.673684 206.905263
C 135.577719 206.905263 146.036621 202.573044 153.746938 194.862727
C 161.457255 187.15241 165.789474 176.693509 165.789474 165.789474
C 165.789474 154.885439 161.457255 144.426537 153.746938 136.71622
C 146.036621 129.005903 135.577719 124.673684 124.673684 124.673684
C 113.769649 124.673684 103.310748 129.005903 95.600431 136.71622
C 87.890114 144.426537 83.557895 154.885439 83.557895 165.789474
C 83.557895 176.693509 87.890114 187.15241 95.600431 194.862727
C 103.310748 202.573044 113.769649 206.905263 124.673684 206.905263
z
" clip-path="url(#pad24174a74)" style="fill: #5b7ea8; opacity: 0.6; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_8">
<path d="M 342 277.389474
C 362.25035 277.389474 381.674025 269.343924 395.993185 255.024764
C 410.312345 240.705604 418.357895 221.281929 418.357895 201.031579
C 418.357895 180.781229 410.312345 161.357554 395.993185 147.038394
C 381.674025 132.719234 362.25035 124.673684 342 124.673684
C 321.74965 124.673684 302.325975 132.719234 288.006815 147.038394
C 273.687655 161.357554 265.642105 180.781229 265.642105 201.031579
C 265.642105 221.281929 273.687655 240.705604 288.006815 255.024764
C 302.325975 269.343924 321.74965 277.389474 342 277.389474
z
" clip-path="url(#pad24174a74)" style="fill: #8e8e93; opacity: 0.35; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_9">
<path d="M 342 259.768421
C 357.577193 259.768421 372.518481 253.579537 383.533219 242.564798
C 394.547958 231.55006 400.736842 216.608772 400.736842 201.031579
C 400.736842 185.454386 394.547958 170.513098 383.533219 159.49836
C 372.518481 148.483621 357.577193 142.294737 342 142.294737
C 326.422807 142.294737 311.481519 148.483621 300.466781 159.49836
C 289.452042 170.513098 283.263158 185.454386 283.263158 201.031579
C 283.263158 216.608772 289.452042 231.55006 300.466781 242.564798
C 311.481519 253.579537 326.422807 259.768421 342 259.768421
z
" clip-path="url(#pad24174a74)" style="fill: #3d9140; opacity: 0.55; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_10">
<path d="M 342 242.147368
C 352.904035 242.147368 363.362937 237.815149 371.073254 230.104832
C 378.783571 222.394516 383.115789 211.935614 383.115789 201.031579
C 383.115789 190.127544 378.783571 179.668642 371.073254 171.958325
C 363.362937 164.248008 352.904035 159.915789 342 159.915789
C 331.095965 159.915789 320.637063 164.248008 312.926746 171.958325
C 305.216429 179.668642 300.884211 190.127544 300.884211 201.031579
C 300.884211 211.935614 305.216429 222.394516 312.926746 230.104832
C 320.637063 237.815149 331.095965 242.147368 342 242.147368
z
" clip-path="url(#pad24174a74)" style="fill: #5b7ea8; opacity: 0.6; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_11">
<path d="M 559.326316 309.694737
C 579.576666 309.694737 599.000341 301.649187 613.319501 287.330027
C 627.638661 273.010867 635.684211 253.587192 635.684211 233.336842
C 635.684211 213.086492 627.638661 193.662817 613.319501 179.343657
C 599.000341 165.024497 579.576666 156.978947 559.326316 156.978947
C 539.075965 156.978947 519.652291 165.024497 505.333131 179.343657
C 491.013971 193.662817 482.968421 213.086492 482.968421 233.336842
C 482.968421 253.587192 491.013971 273.010867 505.333131 287.330027
C 519.652291 301.649187 539.075965 309.694737 559.326316 309.694737
z
" clip-path="url(#pad24174a74)" style="fill: #8e8e93; opacity: 0.35; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_12">
<path d="M 559.326316 292.073684
C 574.903508 292.073684 589.844797 285.8848 600.859535 274.870061
C 611.874274 263.855323 618.063158 248.914035 618.063158 233.336842
C 618.063158 217.759649 611.874274 202.818361 600.859535 191.803623
C 589.844797 180.788884 574.903508 174.6 559.326316 174.6
C 543.749123 174.6 528.807835 180.788884 517.793096 191.803623
C 506.778358 202.818361 500.589474 217.759649 500.589474 233.336842
C 500.589474 248.914035 506.778358 263.855323 517.793096 274.870061
C 528.807835 285.8848 543.749123 292.073684 559.326316 292.073684
z
" clip-path="url(#pad24174a74)" style="fill: #3d9140; opacity: 0.55; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_13">
<path d="M 559.326316 274.452632
C 570.230351 274.452632 580.689252 270.120413 588.399569 262.410096
C 596.109886 254.699779 600.442105 244.240877 600.442105 233.336842
C 600.442105 222.432807 596.109886 211.973906 588.399569 204.263589
C 580.689252 196.553272 570.230351 192.221053 559.326316 192.221053
C 548.422281 192.221053 537.963379 196.553272 530.253062 204.263589
C 522.542745 211.973906 518.210526 222.432807 518.210526 233.336842
C 518.210526 244.240877 522.542745 254.699779 530.253062 262.410096
C 537.963379 270.120413 548.422281 274.452632 559.326316 274.452632
z
" clip-path="url(#pad24174a74)" style="fill: #5b7ea8; opacity: 0.6; stroke: #aaaaaa; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="text_1">
<!-- No contact -->
<g style="fill: #ffffff" transform="translate(81.313684 412.894342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-4e" d="M 628 4666
L 1478 4666
L 3547 763
L 3547 4666
L 4159 4666
L 4159 0
L 3309 0
L 1241 3903
L 1241 0
L 628 0
L 628 4666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6f" d="M 1959 3097
Q 1497 3097 1228 2736
Q 959 2375 959 1747
Q 959 1119 1226 758
Q 1494 397 1959 397
Q 2419 397 2687 759
Q 2956 1122 2956 1747
Q 2956 2369 2687 2733
Q 2419 3097 1959 3097
z
M 1959 3584
Q 2709 3584 3137 3096
Q 3566 2609 3566 1747
Q 3566 888 3137 398
Q 2709 -91 1959 -91
Q 1206 -91 779 398
Q 353 888 353 1747
Q 353 2609 779 3096
Q 1206 3584 1959 3584
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-20" transform="scale(0.015625)"/>
<path id="DejaVuSans-63" d="M 3122 3366
L 3122 2828
Q 2878 2963 2633 3030
Q 2388 3097 2138 3097
Q 1578 3097 1268 2742
Q 959 2388 959 1747
Q 959 1106 1268 751
Q 1578 397 2138 397
Q 2388 397 2633 464
Q 2878 531 3122 666
L 3122 134
Q 2881 22 2623 -34
Q 2366 -91 2075 -91
Q 1284 -91 818 406
Q 353 903 353 1747
Q 353 2603 823 3093
Q 1294 3584 2113 3584
Q 2378 3584 2631 3529
Q 2884 3475 3122 3366
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6e" d="M 3513 2113
L 3513 0
L 2938 0
L 2938 2094
Q 2938 2591 2744 2837
Q 2550 3084 2163 3084
Q 1697 3084 1428 2787
Q 1159 2491 1159 1978
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1366 3272 1645 3428
Q 1925 3584 2291 3584
Q 2894 3584 3203 3211
Q 3513 2838 3513 2113
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-74" d="M 1172 4494
L 1172 3500
L 2356 3500
L 2356 3053
L 1172 3053
L 1172 1153
Q 1172 725 1289 603
Q 1406 481 1766 481
L 2356 481
L 2356 0
L 1766 0
Q 1100 0 847 248
Q 594 497 594 1153
L 594 3053
L 172 3053
L 172 3500
L 594 3500
L 594 4494
L 1172 4494
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-61" d="M 2194 1759
Q 1497 1759 1228 1600
Q 959 1441 959 1056
Q 959 750 1161 570
Q 1363 391 1709 391
Q 2188 391 2477 730
Q 2766 1069 2766 1631
L 2766 1759
L 2194 1759
z
M 3341 1997
L 3341 0
L 2766 0
L 2766 531
Q 2569 213 2275 61
Q 1981 -91 1556 -91
Q 1019 -91 701 211
Q 384 513 384 1019
Q 384 1609 779 1909
Q 1175 2209 1959 2209
L 2766 2209
L 2766 2266
Q 2766 2663 2505 2880
Q 2244 3097 1772 3097
Q 1472 3097 1187 3025
Q 903 2953 641 2809
L 641 3341
Q 956 3463 1253 3523
Q 1550 3584 1831 3584
Q 2591 3584 2966 3190
Q 3341 2797 3341 1997
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-4e"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(74.804688 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(135.986328 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(167.773438 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(222.753906 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(283.935547 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(347.314453 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(386.523438 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(447.802734 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(502.783203 0)"/>
</g>
</g>
<g id="text_2">
<!-- Inactive contact, -->
<g style="fill: #ffffff" transform="translate(275.23 412.894342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-49" d="M 628 4666
L 1259 4666
L 1259 0
L 628 0
L 628 4666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-69" d="M 603 3500
L 1178 3500
L 1178 0
L 603 0
L 603 3500
z
M 603 4863
L 1178 4863
L 1178 4134
L 603 4134
L 603 4863
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-76" d="M 191 3500
L 800 3500
L 1894 563
L 2988 3500
L 3597 3500
L 2284 0
L 1503 0
L 191 3500
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-65" d="M 3597 1894
L 3597 1613
L 953 1613
Q 991 1019 1311 708
Q 1631 397 2203 397
Q 2534 397 2845 478
Q 3156 559 3463 722
L 3463 178
Q 3153 47 2828 -22
Q 2503 -91 2169 -91
Q 1331 -91 842 396
Q 353 884 353 1716
Q 353 2575 817 3079
Q 1281 3584 2069 3584
Q 2775 3584 3186 3129
Q 3597 2675 3597 1894
z
M 3022 2063
Q 3016 2534 2758 2815
Q 2500 3097 2075 3097
Q 1594 3097 1305 2825
Q 1016 2553 972 2059
L 3022 2063
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-2c" d="M 750 794
L 1409 794
L 1409 256
L 897 -744
L 494 -744
L 750 256
L 750 794
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-49"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(29.492188 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(92.871094 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(154.150391 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(209.130859 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(248.339844 0)"/>
<use xlink:href="#DejaVuSans-76" transform="translate(276.123047 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(335.302734 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(396.826172 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(428.613281 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(483.59375 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(544.775391 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(608.154297 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(647.363281 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(708.642578 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(763.623047 0)"/>
<use xlink:href="#DejaVuSans-2c" transform="translate(802.832031 0)"/>
</g>
<!-- no force -->
<g style="fill: #ffffff" transform="translate(309.35125 433.242342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-66" d="M 2375 4863
L 2375 4384
L 1825 4384
Q 1516 4384 1395 4259
Q 1275 4134 1275 3809
L 1275 3500
L 2222 3500
L 2222 3053
L 1275 3053
L 1275 0
L 697 0
L 697 3053
L 147 3053
L 147 3500
L 697 3500
L 697 3744
Q 697 4328 969 4595
Q 1241 4863 1831 4863
L 2375 4863
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-72" d="M 2631 2963
Q 2534 3019 2420 3045
Q 2306 3072 2169 3072
Q 1681 3072 1420 2755
Q 1159 2438 1159 1844
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1341 3275 1631 3429
Q 1922 3584 2338 3584
Q 2397 3584 2469 3576
Q 2541 3569 2628 3553
L 2631 2963
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-6e"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(63.378906 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(124.560547 0)"/>
<use xlink:href="#DejaVuSans-66" transform="translate(156.347656 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(191.552734 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(252.734375 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(291.597656 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(346.578125 0)"/>
</g>
</g>
<g id="text_3">
<!-- Active contact, -->
<g style="fill: #ffffff" transform="translate(499.556316 412.894342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-41" d="M 2188 4044
L 1331 1722
L 3047 1722
L 2188 4044
z
M 1831 4666
L 2547 4666
L 4325 0
L 3669 0
L 3244 1197
L 1141 1197
L 716 0
L 50 0
L 1831 4666
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-41"/>
<use xlink:href="#DejaVuSans-63" transform="translate(66.658203 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(121.638672 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(160.847656 0)"/>
<use xlink:href="#DejaVuSans-76" transform="translate(188.630859 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(247.810547 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(309.333984 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(341.121094 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(396.101562 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(457.283203 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(520.662109 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(559.871094 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(621.150391 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(676.130859 0)"/>
<use xlink:href="#DejaVuSans-2c" transform="translate(715.339844 0)"/>
</g>
<!-- force generated -->
<g style="fill: #ffffff" transform="translate(495.320066 433.242342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-67" d="M 2906 1791
Q 2906 2416 2648 2759
Q 2391 3103 1925 3103
Q 1463 3103 1205 2759
Q 947 2416 947 1791
Q 947 1169 1205 825
Q 1463 481 1925 481
Q 2391 481 2648 825
Q 2906 1169 2906 1791
z
M 3481 434
Q 3481 -459 3084 -895
Q 2688 -1331 1869 -1331
Q 1566 -1331 1297 -1286
Q 1028 -1241 775 -1147
L 775 -588
Q 1028 -725 1275 -790
Q 1522 -856 1778 -856
Q 2344 -856 2625 -561
Q 2906 -266 2906 331
L 2906 616
Q 2728 306 2450 153
Q 2172 0 1784 0
Q 1141 0 747 490
Q 353 981 353 1791
Q 353 2603 747 3093
Q 1141 3584 1784 3584
Q 2172 3584 2450 3431
Q 2728 3278 2906 2969
L 2906 3500
L 3481 3500
L 3481 434
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-64" d="M 2906 2969
L 2906 4863
L 3481 4863
L 3481 0
L 2906 0
L 2906 525
Q 2725 213 2448 61
Q 2172 -91 1784 -91
Q 1150 -91 751 415
Q 353 922 353 1747
Q 353 2572 751 3078
Q 1150 3584 1784 3584
Q 2172 3584 2448 3432
Q 2725 3281 2906 2969
z
M 947 1747
Q 947 1113 1208 752
Q 1469 391 1925 391
Q 2381 391 2643 752
Q 2906 1113 2906 1747
Q 2906 2381 2643 2742
Q 2381 3103 1925 3103
Q 1469 3103 1208 2742
Q 947 2381 947 1747
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-66"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(35.205078 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(96.386719 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(135.25 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(190.230469 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(251.753906 0)"/>
<use xlink:href="#DejaVuSans-67" transform="translate(283.541016 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(347.017578 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(408.541016 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(471.919922 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(533.443359 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(574.556641 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(635.835938 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(675.044922 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(736.568359 0)"/>
</g>
</g>
<g id="patch_14">
<path d="M 450.663158 112.926316
L 650.368421 112.926316
Q 659.178947 112.926316 659.178947 104.115789
L 659.178947 27.757895
Q 659.178947 18.947368 650.368421 18.947368
L 450.663158 18.947368
Q 441.852632 18.947368 441.852632 27.757895
L 441.852632 104.115789
Q 441.852632 112.926316 450.663158 112.926316
z
" clip-path="url(#pad24174a74)" style="fill: #333333; opacity: 0.85; stroke: #aaaaaa; stroke-linejoin: miter"/>
</g>
<g id="patch_15">
<path d="M 459.473684 44.791579
C 461.654491 44.791579 463.746272 43.925135 465.288335 42.383072
C 466.830398 40.841008 467.696842 38.749228 467.696842 36.568421
C 467.696842 34.387614 466.830398 32.295834 465.288335 30.75377
C 463.746272 29.211707 461.654491 28.345263 459.473684 28.345263
C 457.292877 28.345263 455.201097 29.211707 453.659034 30.75377
C 452.11697 32.295834 451.250526 34.387614 451.250526 36.568421
C 451.250526 38.749228 452.11697 40.841008 453.659034 42.383072
C 455.201097 43.925135 457.292877 44.791579 459.473684 44.791579
z
" clip-path="url(#pad24174a74)" style="fill: #5b7ea8; opacity: 0.6; stroke: #aaaaaa; stroke-width: 1.2; stroke-linejoin: miter"/>
</g>
<g id="patch_16">
<path d="M 459.473684 74.16
C 461.654491 74.16 463.746272 73.293556 465.288335 71.751493
C 466.830398 70.209429 467.696842 68.117649 467.696842 65.936842
C 467.696842 63.756035 466.830398 61.664255 465.288335 60.122191
C 463.746272 58.580128 461.654491 57.713684 459.473684 57.713684
C 457.292877 57.713684 455.201097 58.580128 453.659034 60.122191
C 452.11697 61.664255 451.250526 63.756035 451.250526 65.936842
C 451.250526 68.117649 452.11697 70.209429 453.659034 71.751493
C 455.201097 73.293556 457.292877 74.16 459.473684 74.16
z
" clip-path="url(#pad24174a74)" style="fill: #3d9140; opacity: 0.55; stroke: #aaaaaa; stroke-width: 1.2; stroke-linejoin: miter"/>
</g>
<g id="patch_17">
<path d="M 459.473684 103.528421
C 461.654491 103.528421 463.746272 102.661977 465.288335 101.119914
C 466.830398 99.57785 467.696842 97.48607 467.696842 95.305263
C 467.696842 93.124456 466.830398 91.032676 465.288335 89.490612
C 463.746272 87.948549 461.654491 87.082105 459.473684 87.082105
C 457.292877 87.082105 455.201097 87.948549 453.659034 89.490612
C 452.11697 91.032676 451.250526 93.124456 451.250526 95.305263
C 451.250526 97.48607 452.11697 99.57785 453.659034 101.119914
C 455.201097 102.661977 457.292877 103.528421 459.473684 103.528421
z
" clip-path="url(#pad24174a74)" style="fill: #8e8e93; opacity: 0.35; stroke: #aaaaaa; stroke-width: 1.2; stroke-linejoin: miter"/>
</g>
<g id="text_4">
<!-- Geometric shape -->
<g style="fill: #ffffff" transform="translate(477.094737 40.431546) scale(0.14 -0.14)">
<defs>
<path id="DejaVuSans-47" d="M 3809 666
L 3809 1919
L 2778 1919
L 2778 2438
L 4434 2438
L 4434 434
Q 4069 175 3628 42
Q 3188 -91 2688 -91
Q 1594 -91 976 548
Q 359 1188 359 2328
Q 359 3472 976 4111
Q 1594 4750 2688 4750
Q 3144 4750 3555 4637
Q 3966 4525 4313 4306
L 4313 3634
Q 3963 3931 3569 4081
Q 3175 4231 2741 4231
Q 1884 4231 1454 3753
Q 1025 3275 1025 2328
Q 1025 1384 1454 906
Q 1884 428 2741 428
Q 3075 428 3337 486
Q 3600 544 3809 666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6d" d="M 3328 2828
Q 3544 3216 3844 3400
Q 4144 3584 4550 3584
Q 5097 3584 5394 3201
Q 5691 2819 5691 2113
L 5691 0
L 5113 0
L 5113 2094
Q 5113 2597 4934 2840
Q 4756 3084 4391 3084
Q 3944 3084 3684 2787
Q 3425 2491 3425 1978
L 3425 0
L 2847 0
L 2847 2094
Q 2847 2600 2669 2842
Q 2491 3084 2119 3084
Q 1678 3084 1418 2786
Q 1159 2488 1159 1978
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1356 3278 1631 3431
Q 1906 3584 2284 3584
Q 2666 3584 2933 3390
Q 3200 3197 3328 2828
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-73" d="M 2834 3397
L 2834 2853
Q 2591 2978 2328 3040
Q 2066 3103 1784 3103
Q 1356 3103 1142 2972
Q 928 2841 928 2578
Q 928 2378 1081 2264
Q 1234 2150 1697 2047
L 1894 2003
Q 2506 1872 2764 1633
Q 3022 1394 3022 966
Q 3022 478 2636 193
Q 2250 -91 1575 -91
Q 1294 -91 989 -36
Q 684 19 347 128
L 347 722
Q 666 556 975 473
Q 1284 391 1588 391
Q 1994 391 2212 530
Q 2431 669 2431 922
Q 2431 1156 2273 1281
Q 2116 1406 1581 1522
L 1381 1569
Q 847 1681 609 1914
Q 372 2147 372 2553
Q 372 3047 722 3315
Q 1072 3584 1716 3584
Q 2034 3584 2315 3537
Q 2597 3491 2834 3397
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-68" d="M 3513 2113
L 3513 0
L 2938 0
L 2938 2094
Q 2938 2591 2744 2837
Q 2550 3084 2163 3084
Q 1697 3084 1428 2787
Q 1159 2491 1159 1978
L 1159 0
L 581 0
L 581 4863
L 1159 4863
L 1159 2956
Q 1366 3272 1645 3428
Q 1925 3584 2291 3584
Q 2894 3584 3203 3211
Q 3513 2838 3513 2113
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-70" d="M 1159 525
L 1159 -1331
L 581 -1331
L 581 3500
L 1159 3500
L 1159 2969
Q 1341 3281 1617 3432
Q 1894 3584 2278 3584
Q 2916 3584 3314 3078
Q 3713 2572 3713 1747
Q 3713 922 3314 415
Q 2916 -91 2278 -91
Q 1894 -91 1617 61
Q 1341 213 1159 525
z
M 3116 1747
Q 3116 2381 2855 2742
Q 2594 3103 2138 3103
Q 1681 3103 1420 2742
Q 1159 2381 1159 1747
Q 1159 1113 1420 752
Q 1681 391 2138 391
Q 2594 391 2855 752
Q 3116 1113 3116 1747
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-47"/>
<use xlink:href="#DejaVuSans-65" transform="translate(77.490234 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(139.013672 0)"/>
<use xlink:href="#DejaVuSans-6d" transform="translate(200.195312 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(297.607422 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(359.130859 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(398.339844 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(439.453125 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(467.236328 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(522.216797 0)"/>
<use xlink:href="#DejaVuSans-73" transform="translate(554.003906 0)"/>
<use xlink:href="#DejaVuSans-68" transform="translate(606.103516 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(669.482422 0)"/>
<use xlink:href="#DejaVuSans-70" transform="translate(730.761719 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(794.238281 0)"/>
</g>
</g>
<g id="text_5">
<!-- Margin -->
<g style="fill: #ffffff" transform="translate(477.094737 69.799967) scale(0.14 -0.14)">
<defs>
<path id="DejaVuSans-4d" d="M 628 4666
L 1569 4666
L 2759 1491
L 3956 4666
L 4897 4666
L 4897 0
L 4281 0
L 4281 4097
L 3078 897
L 2444 897
L 1241 4097
L 1241 0
L 628 0
L 628 4666
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-4d"/>
<use xlink:href="#DejaVuSans-61" transform="translate(86.279297 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(147.558594 0)"/>
<use xlink:href="#DejaVuSans-67" transform="translate(186.921875 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(250.398438 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(278.181641 0)"/>
</g>
</g>
<g id="text_6">
<!-- Gap (added to margin) -->
<g style="fill: #ffffff" transform="translate(477.094737 99.168388) scale(0.14 -0.14)">
<defs>
<path id="DejaVuSans-28" d="M 1984 4856
Q 1566 4138 1362 3434
Q 1159 2731 1159 2009
Q 1159 1288 1364 580
Q 1569 -128 1984 -844
L 1484 -844
Q 1016 -109 783 600
Q 550 1309 550 2009
Q 550 2706 781 3412
Q 1013 4119 1484 4856
L 1984 4856
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-29" d="M 513 4856
L 1013 4856
Q 1481 4119 1714 3412
Q 1947 2706 1947 2009
Q 1947 1309 1714 600
Q 1481 -109 1013 -844
L 513 -844
Q 928 -128 1133 580
Q 1338 1288 1338 2009
Q 1338 2731 1133 3434
Q 928 4138 513 4856
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-47"/>
<use xlink:href="#DejaVuSans-61" transform="translate(77.490234 0)"/>
<use xlink:href="#DejaVuSans-70" transform="translate(138.769531 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(202.246094 0)"/>
<use xlink:href="#DejaVuSans-28" transform="translate(234.033203 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(273.046875 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(334.326172 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(397.802734 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(461.279297 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(522.802734 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(586.279297 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(618.066406 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(657.275391 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(718.457031 0)"/>
<use xlink:href="#DejaVuSans-6d" transform="translate(750.244141 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(847.65625 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(908.935547 0)"/>
<use xlink:href="#DejaVuSans-67" transform="translate(948.298828 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(1011.775391 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(1039.558594 0)"/>
<use xlink:href="#DejaVuSans-29" transform="translate(1102.9375 0)"/>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="pad24174a74">
<rect x="7.2" y="7.2" width="669.6" height="446.4"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 29 KiB

+886
View File
@@ -0,0 +1,886 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="684pt" height="460.8pt" viewBox="0 0 684 460.8" xmlns="http://www.w3.org/2000/svg" version="1.1">
<metadata>
<rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<cc:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:date>2026-05-12T11:57:08.937609</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
<dc:title>Matplotlib v3.10.6, https://matplotlib.org/</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<defs>
<style type="text/css">*{stroke-linejoin: round; stroke-linecap: butt}</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 460.8
L 684 460.8
L 684 0
L 0 0
z
" style="fill: #ffffff"/>
</g>
<g id="axes_1">
<g id="patch_2">
<path d="M 54.189474 377.242105
L 629.810526 377.242105
Q 659.178947 377.242105 659.178947 347.873684
L 659.178947 300.884211
Q 659.178947 271.515789 629.810526 271.515789
L 54.189474 271.515789
Q 24.821053 271.515789 24.821053 300.884211
L 24.821053 347.873684
Q 24.821053 377.242105 54.189474 377.242105
z
" clip-path="url(#p6ec8682cc1)" style="fill: #b0bec5; opacity: 0.35; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_3">
<path d="M 54.189474 362.557895
L 629.810526 362.557895
Q 644.494737 362.557895 644.494737 347.873684
L 644.494737 300.884211
Q 644.494737 286.2 629.810526 286.2
L 54.189474 286.2
Q 39.505263 286.2 39.505263 300.884211
L 39.505263 347.873684
Q 39.505263 362.557895 54.189474 362.557895
z
" clip-path="url(#p6ec8682cc1)" style="fill: #4caf50; opacity: 0.55; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_4">
<path d="M 54.189474 347.873684
L 629.810526 347.873684
Q 629.810526 347.873684 629.810526 347.873684
L 629.810526 300.884211
Q 629.810526 300.884211 629.810526 300.884211
L 54.189474 300.884211
Q 54.189474 300.884211 54.189474 300.884211
L 54.189474 347.873684
Q 54.189474 347.873684 54.189474 347.873684
z
" clip-path="url(#p6ec8682cc1)" style="fill: #7ba4cc; opacity: 0.6; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_5">
<path d="M 124.673684 242.147368
C 144.924035 242.147368 164.347709 234.101819 178.666869 219.782659
C 192.986029 205.463499 201.031579 186.039824 201.031579 165.789474
C 201.031579 145.539123 192.986029 126.115449 178.666869 111.796289
C 164.347709 97.477128 144.924035 89.431579 124.673684 89.431579
C 104.423334 89.431579 84.999659 97.477128 70.680499 111.796289
C 56.361339 126.115449 48.315789 145.539123 48.315789 165.789474
C 48.315789 186.039824 56.361339 205.463499 70.680499 219.782659
C 84.999659 234.101819 104.423334 242.147368 124.673684 242.147368
z
" clip-path="url(#p6ec8682cc1)" style="fill: #b0bec5; opacity: 0.35; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_6">
<path d="M 124.673684 224.526316
C 140.250877 224.526316 155.192165 218.337432 166.206904 207.322693
C 177.221642 196.307955 183.410526 181.366666 183.410526 165.789474
C 183.410526 150.212281 177.221642 135.270993 166.206904 124.256254
C 155.192165 113.241516 140.250877 107.052632 124.673684 107.052632
C 109.096492 107.052632 94.155203 113.241516 83.140465 124.256254
C 72.125726 135.270993 65.936842 150.212281 65.936842 165.789474
C 65.936842 181.366666 72.125726 196.307955 83.140465 207.322693
C 94.155203 218.337432 109.096492 224.526316 124.673684 224.526316
z
" clip-path="url(#p6ec8682cc1)" style="fill: #4caf50; opacity: 0.55; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_7">
<path d="M 124.673684 206.905263
C 135.577719 206.905263 146.036621 202.573044 153.746938 194.862727
C 161.457255 187.15241 165.789474 176.693509 165.789474 165.789474
C 165.789474 154.885439 161.457255 144.426537 153.746938 136.71622
C 146.036621 129.005903 135.577719 124.673684 124.673684 124.673684
C 113.769649 124.673684 103.310748 129.005903 95.600431 136.71622
C 87.890114 144.426537 83.557895 154.885439 83.557895 165.789474
C 83.557895 176.693509 87.890114 187.15241 95.600431 194.862727
C 103.310748 202.573044 113.769649 206.905263 124.673684 206.905263
z
" clip-path="url(#p6ec8682cc1)" style="fill: #7ba4cc; opacity: 0.6; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_8">
<path d="M 342 277.389474
C 362.25035 277.389474 381.674025 269.343924 395.993185 255.024764
C 410.312345 240.705604 418.357895 221.281929 418.357895 201.031579
C 418.357895 180.781229 410.312345 161.357554 395.993185 147.038394
C 381.674025 132.719234 362.25035 124.673684 342 124.673684
C 321.74965 124.673684 302.325975 132.719234 288.006815 147.038394
C 273.687655 161.357554 265.642105 180.781229 265.642105 201.031579
C 265.642105 221.281929 273.687655 240.705604 288.006815 255.024764
C 302.325975 269.343924 321.74965 277.389474 342 277.389474
z
" clip-path="url(#p6ec8682cc1)" style="fill: #b0bec5; opacity: 0.35; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_9">
<path d="M 342 259.768421
C 357.577193 259.768421 372.518481 253.579537 383.533219 242.564798
C 394.547958 231.55006 400.736842 216.608772 400.736842 201.031579
C 400.736842 185.454386 394.547958 170.513098 383.533219 159.49836
C 372.518481 148.483621 357.577193 142.294737 342 142.294737
C 326.422807 142.294737 311.481519 148.483621 300.466781 159.49836
C 289.452042 170.513098 283.263158 185.454386 283.263158 201.031579
C 283.263158 216.608772 289.452042 231.55006 300.466781 242.564798
C 311.481519 253.579537 326.422807 259.768421 342 259.768421
z
" clip-path="url(#p6ec8682cc1)" style="fill: #4caf50; opacity: 0.55; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_10">
<path d="M 342 242.147368
C 352.904035 242.147368 363.362937 237.815149 371.073254 230.104832
C 378.783571 222.394516 383.115789 211.935614 383.115789 201.031579
C 383.115789 190.127544 378.783571 179.668642 371.073254 171.958325
C 363.362937 164.248008 352.904035 159.915789 342 159.915789
C 331.095965 159.915789 320.637063 164.248008 312.926746 171.958325
C 305.216429 179.668642 300.884211 190.127544 300.884211 201.031579
C 300.884211 211.935614 305.216429 222.394516 312.926746 230.104832
C 320.637063 237.815149 331.095965 242.147368 342 242.147368
z
" clip-path="url(#p6ec8682cc1)" style="fill: #7ba4cc; opacity: 0.6; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_11">
<path d="M 559.326316 309.694737
C 579.576666 309.694737 599.000341 301.649187 613.319501 287.330027
C 627.638661 273.010867 635.684211 253.587192 635.684211 233.336842
C 635.684211 213.086492 627.638661 193.662817 613.319501 179.343657
C 599.000341 165.024497 579.576666 156.978947 559.326316 156.978947
C 539.075965 156.978947 519.652291 165.024497 505.333131 179.343657
C 491.013971 193.662817 482.968421 213.086492 482.968421 233.336842
C 482.968421 253.587192 491.013971 273.010867 505.333131 287.330027
C 519.652291 301.649187 539.075965 309.694737 559.326316 309.694737
z
" clip-path="url(#p6ec8682cc1)" style="fill: #b0bec5; opacity: 0.35; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_12">
<path d="M 559.326316 292.073684
C 574.903508 292.073684 589.844797 285.8848 600.859535 274.870061
C 611.874274 263.855323 618.063158 248.914035 618.063158 233.336842
C 618.063158 217.759649 611.874274 202.818361 600.859535 191.803623
C 589.844797 180.788884 574.903508 174.6 559.326316 174.6
C 543.749123 174.6 528.807835 180.788884 517.793096 191.803623
C 506.778358 202.818361 500.589474 217.759649 500.589474 233.336842
C 500.589474 248.914035 506.778358 263.855323 517.793096 274.870061
C 528.807835 285.8848 543.749123 292.073684 559.326316 292.073684
z
" clip-path="url(#p6ec8682cc1)" style="fill: #4caf50; opacity: 0.55; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="patch_13">
<path d="M 559.326316 274.452632
C 570.230351 274.452632 580.689252 270.120413 588.399569 262.410096
C 596.109886 254.699779 600.442105 244.240877 600.442105 233.336842
C 600.442105 222.432807 596.109886 211.973906 588.399569 204.263589
C 580.689252 196.553272 570.230351 192.221053 559.326316 192.221053
C 548.422281 192.221053 537.963379 196.553272 530.253062 204.263589
C 522.542745 211.973906 518.210526 222.432807 518.210526 233.336842
C 518.210526 244.240877 522.542745 254.699779 530.253062 262.410096
C 537.963379 270.120413 548.422281 274.452632 559.326316 274.452632
z
" clip-path="url(#p6ec8682cc1)" style="fill: #7ba4cc; opacity: 0.6; stroke: #34495e; stroke-width: 1.5; stroke-linejoin: miter"/>
</g>
<g id="text_1">
<!-- No contact -->
<g transform="translate(81.313684 412.894342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-4e" d="M 628 4666
L 1478 4666
L 3547 763
L 3547 4666
L 4159 4666
L 4159 0
L 3309 0
L 1241 3903
L 1241 0
L 628 0
L 628 4666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6f" d="M 1959 3097
Q 1497 3097 1228 2736
Q 959 2375 959 1747
Q 959 1119 1226 758
Q 1494 397 1959 397
Q 2419 397 2687 759
Q 2956 1122 2956 1747
Q 2956 2369 2687 2733
Q 2419 3097 1959 3097
z
M 1959 3584
Q 2709 3584 3137 3096
Q 3566 2609 3566 1747
Q 3566 888 3137 398
Q 2709 -91 1959 -91
Q 1206 -91 779 398
Q 353 888 353 1747
Q 353 2609 779 3096
Q 1206 3584 1959 3584
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-20" transform="scale(0.015625)"/>
<path id="DejaVuSans-63" d="M 3122 3366
L 3122 2828
Q 2878 2963 2633 3030
Q 2388 3097 2138 3097
Q 1578 3097 1268 2742
Q 959 2388 959 1747
Q 959 1106 1268 751
Q 1578 397 2138 397
Q 2388 397 2633 464
Q 2878 531 3122 666
L 3122 134
Q 2881 22 2623 -34
Q 2366 -91 2075 -91
Q 1284 -91 818 406
Q 353 903 353 1747
Q 353 2603 823 3093
Q 1294 3584 2113 3584
Q 2378 3584 2631 3529
Q 2884 3475 3122 3366
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6e" d="M 3513 2113
L 3513 0
L 2938 0
L 2938 2094
Q 2938 2591 2744 2837
Q 2550 3084 2163 3084
Q 1697 3084 1428 2787
Q 1159 2491 1159 1978
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1366 3272 1645 3428
Q 1925 3584 2291 3584
Q 2894 3584 3203 3211
Q 3513 2838 3513 2113
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-74" d="M 1172 4494
L 1172 3500
L 2356 3500
L 2356 3053
L 1172 3053
L 1172 1153
Q 1172 725 1289 603
Q 1406 481 1766 481
L 2356 481
L 2356 0
L 1766 0
Q 1100 0 847 248
Q 594 497 594 1153
L 594 3053
L 172 3053
L 172 3500
L 594 3500
L 594 4494
L 1172 4494
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-61" d="M 2194 1759
Q 1497 1759 1228 1600
Q 959 1441 959 1056
Q 959 750 1161 570
Q 1363 391 1709 391
Q 2188 391 2477 730
Q 2766 1069 2766 1631
L 2766 1759
L 2194 1759
z
M 3341 1997
L 3341 0
L 2766 0
L 2766 531
Q 2569 213 2275 61
Q 1981 -91 1556 -91
Q 1019 -91 701 211
Q 384 513 384 1019
Q 384 1609 779 1909
Q 1175 2209 1959 2209
L 2766 2209
L 2766 2266
Q 2766 2663 2505 2880
Q 2244 3097 1772 3097
Q 1472 3097 1187 3025
Q 903 2953 641 2809
L 641 3341
Q 956 3463 1253 3523
Q 1550 3584 1831 3584
Q 2591 3584 2966 3190
Q 3341 2797 3341 1997
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-4e"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(74.804688 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(135.986328 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(167.773438 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(222.753906 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(283.935547 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(347.314453 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(386.523438 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(447.802734 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(502.783203 0)"/>
</g>
</g>
<g id="text_2">
<!-- Inactive contact, -->
<g transform="translate(275.23 412.894342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-49" d="M 628 4666
L 1259 4666
L 1259 0
L 628 0
L 628 4666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-69" d="M 603 3500
L 1178 3500
L 1178 0
L 603 0
L 603 3500
z
M 603 4863
L 1178 4863
L 1178 4134
L 603 4134
L 603 4863
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-76" d="M 191 3500
L 800 3500
L 1894 563
L 2988 3500
L 3597 3500
L 2284 0
L 1503 0
L 191 3500
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-65" d="M 3597 1894
L 3597 1613
L 953 1613
Q 991 1019 1311 708
Q 1631 397 2203 397
Q 2534 397 2845 478
Q 3156 559 3463 722
L 3463 178
Q 3153 47 2828 -22
Q 2503 -91 2169 -91
Q 1331 -91 842 396
Q 353 884 353 1716
Q 353 2575 817 3079
Q 1281 3584 2069 3584
Q 2775 3584 3186 3129
Q 3597 2675 3597 1894
z
M 3022 2063
Q 3016 2534 2758 2815
Q 2500 3097 2075 3097
Q 1594 3097 1305 2825
Q 1016 2553 972 2059
L 3022 2063
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-2c" d="M 750 794
L 1409 794
L 1409 256
L 897 -744
L 494 -744
L 750 256
L 750 794
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-49"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(29.492188 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(92.871094 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(154.150391 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(209.130859 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(248.339844 0)"/>
<use xlink:href="#DejaVuSans-76" transform="translate(276.123047 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(335.302734 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(396.826172 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(428.613281 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(483.59375 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(544.775391 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(608.154297 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(647.363281 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(708.642578 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(763.623047 0)"/>
<use xlink:href="#DejaVuSans-2c" transform="translate(802.832031 0)"/>
</g>
<!-- no force -->
<g transform="translate(309.35125 433.242342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-66" d="M 2375 4863
L 2375 4384
L 1825 4384
Q 1516 4384 1395 4259
Q 1275 4134 1275 3809
L 1275 3500
L 2222 3500
L 2222 3053
L 1275 3053
L 1275 0
L 697 0
L 697 3053
L 147 3053
L 147 3500
L 697 3500
L 697 3744
Q 697 4328 969 4595
Q 1241 4863 1831 4863
L 2375 4863
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-72" d="M 2631 2963
Q 2534 3019 2420 3045
Q 2306 3072 2169 3072
Q 1681 3072 1420 2755
Q 1159 2438 1159 1844
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1341 3275 1631 3429
Q 1922 3584 2338 3584
Q 2397 3584 2469 3576
Q 2541 3569 2628 3553
L 2631 2963
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-6e"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(63.378906 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(124.560547 0)"/>
<use xlink:href="#DejaVuSans-66" transform="translate(156.347656 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(191.552734 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(252.734375 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(291.597656 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(346.578125 0)"/>
</g>
</g>
<g id="text_3">
<!-- Active contact, -->
<g transform="translate(499.556316 412.894342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-41" d="M 2188 4044
L 1331 1722
L 3047 1722
L 2188 4044
z
M 1831 4666
L 2547 4666
L 4325 0
L 3669 0
L 3244 1197
L 1141 1197
L 716 0
L 50 0
L 1831 4666
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-41"/>
<use xlink:href="#DejaVuSans-63" transform="translate(66.658203 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(121.638672 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(160.847656 0)"/>
<use xlink:href="#DejaVuSans-76" transform="translate(188.630859 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(247.810547 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(309.333984 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(341.121094 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(396.101562 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(457.283203 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(520.662109 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(559.871094 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(621.150391 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(676.130859 0)"/>
<use xlink:href="#DejaVuSans-2c" transform="translate(715.339844 0)"/>
</g>
<!-- force generated -->
<g transform="translate(495.320066 433.242342) scale(0.16 -0.16)">
<defs>
<path id="DejaVuSans-67" d="M 2906 1791
Q 2906 2416 2648 2759
Q 2391 3103 1925 3103
Q 1463 3103 1205 2759
Q 947 2416 947 1791
Q 947 1169 1205 825
Q 1463 481 1925 481
Q 2391 481 2648 825
Q 2906 1169 2906 1791
z
M 3481 434
Q 3481 -459 3084 -895
Q 2688 -1331 1869 -1331
Q 1566 -1331 1297 -1286
Q 1028 -1241 775 -1147
L 775 -588
Q 1028 -725 1275 -790
Q 1522 -856 1778 -856
Q 2344 -856 2625 -561
Q 2906 -266 2906 331
L 2906 616
Q 2728 306 2450 153
Q 2172 0 1784 0
Q 1141 0 747 490
Q 353 981 353 1791
Q 353 2603 747 3093
Q 1141 3584 1784 3584
Q 2172 3584 2450 3431
Q 2728 3278 2906 2969
L 2906 3500
L 3481 3500
L 3481 434
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-64" d="M 2906 2969
L 2906 4863
L 3481 4863
L 3481 0
L 2906 0
L 2906 525
Q 2725 213 2448 61
Q 2172 -91 1784 -91
Q 1150 -91 751 415
Q 353 922 353 1747
Q 353 2572 751 3078
Q 1150 3584 1784 3584
Q 2172 3584 2448 3432
Q 2725 3281 2906 2969
z
M 947 1747
Q 947 1113 1208 752
Q 1469 391 1925 391
Q 2381 391 2643 752
Q 2906 1113 2906 1747
Q 2906 2381 2643 2742
Q 2381 3103 1925 3103
Q 1469 3103 1208 2742
Q 947 2381 947 1747
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-66"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(35.205078 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(96.386719 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(135.25 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(190.230469 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(251.753906 0)"/>
<use xlink:href="#DejaVuSans-67" transform="translate(283.541016 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(347.017578 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(408.541016 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(471.919922 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(533.443359 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(574.556641 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(635.835938 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(675.044922 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(736.568359 0)"/>
</g>
</g>
<g id="patch_14">
<path d="M 450.663158 112.926316
L 650.368421 112.926316
Q 659.178947 112.926316 659.178947 104.115789
L 659.178947 27.757895
Q 659.178947 18.947368 650.368421 18.947368
L 450.663158 18.947368
Q 441.852632 18.947368 441.852632 27.757895
L 441.852632 104.115789
Q 441.852632 112.926316 450.663158 112.926316
z
" clip-path="url(#p6ec8682cc1)" style="fill: #ffffff; opacity: 0.85; stroke: #34495e; stroke-linejoin: miter"/>
</g>
<g id="patch_15">
<path d="M 459.473684 44.791579
C 461.654491 44.791579 463.746272 43.925135 465.288335 42.383072
C 466.830398 40.841008 467.696842 38.749228 467.696842 36.568421
C 467.696842 34.387614 466.830398 32.295834 465.288335 30.75377
C 463.746272 29.211707 461.654491 28.345263 459.473684 28.345263
C 457.292877 28.345263 455.201097 29.211707 453.659034 30.75377
C 452.11697 32.295834 451.250526 34.387614 451.250526 36.568421
C 451.250526 38.749228 452.11697 40.841008 453.659034 42.383072
C 455.201097 43.925135 457.292877 44.791579 459.473684 44.791579
z
" clip-path="url(#p6ec8682cc1)" style="fill: #7ba4cc; opacity: 0.6; stroke: #34495e; stroke-width: 1.2; stroke-linejoin: miter"/>
</g>
<g id="patch_16">
<path d="M 459.473684 74.16
C 461.654491 74.16 463.746272 73.293556 465.288335 71.751493
C 466.830398 70.209429 467.696842 68.117649 467.696842 65.936842
C 467.696842 63.756035 466.830398 61.664255 465.288335 60.122191
C 463.746272 58.580128 461.654491 57.713684 459.473684 57.713684
C 457.292877 57.713684 455.201097 58.580128 453.659034 60.122191
C 452.11697 61.664255 451.250526 63.756035 451.250526 65.936842
C 451.250526 68.117649 452.11697 70.209429 453.659034 71.751493
C 455.201097 73.293556 457.292877 74.16 459.473684 74.16
z
" clip-path="url(#p6ec8682cc1)" style="fill: #4caf50; opacity: 0.55; stroke: #34495e; stroke-width: 1.2; stroke-linejoin: miter"/>
</g>
<g id="patch_17">
<path d="M 459.473684 103.528421
C 461.654491 103.528421 463.746272 102.661977 465.288335 101.119914
C 466.830398 99.57785 467.696842 97.48607 467.696842 95.305263
C 467.696842 93.124456 466.830398 91.032676 465.288335 89.490612
C 463.746272 87.948549 461.654491 87.082105 459.473684 87.082105
C 457.292877 87.082105 455.201097 87.948549 453.659034 89.490612
C 452.11697 91.032676 451.250526 93.124456 451.250526 95.305263
C 451.250526 97.48607 452.11697 99.57785 453.659034 101.119914
C 455.201097 102.661977 457.292877 103.528421 459.473684 103.528421
z
" clip-path="url(#p6ec8682cc1)" style="fill: #b0bec5; opacity: 0.35; stroke: #34495e; stroke-width: 1.2; stroke-linejoin: miter"/>
</g>
<g id="text_4">
<!-- Geometric shape -->
<g transform="translate(477.094737 40.431546) scale(0.14 -0.14)">
<defs>
<path id="DejaVuSans-47" d="M 3809 666
L 3809 1919
L 2778 1919
L 2778 2438
L 4434 2438
L 4434 434
Q 4069 175 3628 42
Q 3188 -91 2688 -91
Q 1594 -91 976 548
Q 359 1188 359 2328
Q 359 3472 976 4111
Q 1594 4750 2688 4750
Q 3144 4750 3555 4637
Q 3966 4525 4313 4306
L 4313 3634
Q 3963 3931 3569 4081
Q 3175 4231 2741 4231
Q 1884 4231 1454 3753
Q 1025 3275 1025 2328
Q 1025 1384 1454 906
Q 1884 428 2741 428
Q 3075 428 3337 486
Q 3600 544 3809 666
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-6d" d="M 3328 2828
Q 3544 3216 3844 3400
Q 4144 3584 4550 3584
Q 5097 3584 5394 3201
Q 5691 2819 5691 2113
L 5691 0
L 5113 0
L 5113 2094
Q 5113 2597 4934 2840
Q 4756 3084 4391 3084
Q 3944 3084 3684 2787
Q 3425 2491 3425 1978
L 3425 0
L 2847 0
L 2847 2094
Q 2847 2600 2669 2842
Q 2491 3084 2119 3084
Q 1678 3084 1418 2786
Q 1159 2488 1159 1978
L 1159 0
L 581 0
L 581 3500
L 1159 3500
L 1159 2956
Q 1356 3278 1631 3431
Q 1906 3584 2284 3584
Q 2666 3584 2933 3390
Q 3200 3197 3328 2828
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-73" d="M 2834 3397
L 2834 2853
Q 2591 2978 2328 3040
Q 2066 3103 1784 3103
Q 1356 3103 1142 2972
Q 928 2841 928 2578
Q 928 2378 1081 2264
Q 1234 2150 1697 2047
L 1894 2003
Q 2506 1872 2764 1633
Q 3022 1394 3022 966
Q 3022 478 2636 193
Q 2250 -91 1575 -91
Q 1294 -91 989 -36
Q 684 19 347 128
L 347 722
Q 666 556 975 473
Q 1284 391 1588 391
Q 1994 391 2212 530
Q 2431 669 2431 922
Q 2431 1156 2273 1281
Q 2116 1406 1581 1522
L 1381 1569
Q 847 1681 609 1914
Q 372 2147 372 2553
Q 372 3047 722 3315
Q 1072 3584 1716 3584
Q 2034 3584 2315 3537
Q 2597 3491 2834 3397
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-68" d="M 3513 2113
L 3513 0
L 2938 0
L 2938 2094
Q 2938 2591 2744 2837
Q 2550 3084 2163 3084
Q 1697 3084 1428 2787
Q 1159 2491 1159 1978
L 1159 0
L 581 0
L 581 4863
L 1159 4863
L 1159 2956
Q 1366 3272 1645 3428
Q 1925 3584 2291 3584
Q 2894 3584 3203 3211
Q 3513 2838 3513 2113
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-70" d="M 1159 525
L 1159 -1331
L 581 -1331
L 581 3500
L 1159 3500
L 1159 2969
Q 1341 3281 1617 3432
Q 1894 3584 2278 3584
Q 2916 3584 3314 3078
Q 3713 2572 3713 1747
Q 3713 922 3314 415
Q 2916 -91 2278 -91
Q 1894 -91 1617 61
Q 1341 213 1159 525
z
M 3116 1747
Q 3116 2381 2855 2742
Q 2594 3103 2138 3103
Q 1681 3103 1420 2742
Q 1159 2381 1159 1747
Q 1159 1113 1420 752
Q 1681 391 2138 391
Q 2594 391 2855 752
Q 3116 1113 3116 1747
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-47"/>
<use xlink:href="#DejaVuSans-65" transform="translate(77.490234 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(139.013672 0)"/>
<use xlink:href="#DejaVuSans-6d" transform="translate(200.195312 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(297.607422 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(359.130859 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(398.339844 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(439.453125 0)"/>
<use xlink:href="#DejaVuSans-63" transform="translate(467.236328 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(522.216797 0)"/>
<use xlink:href="#DejaVuSans-73" transform="translate(554.003906 0)"/>
<use xlink:href="#DejaVuSans-68" transform="translate(606.103516 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(669.482422 0)"/>
<use xlink:href="#DejaVuSans-70" transform="translate(730.761719 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(794.238281 0)"/>
</g>
</g>
<g id="text_5">
<!-- Margin -->
<g transform="translate(477.094737 69.799967) scale(0.14 -0.14)">
<defs>
<path id="DejaVuSans-4d" d="M 628 4666
L 1569 4666
L 2759 1491
L 3956 4666
L 4897 4666
L 4897 0
L 4281 0
L 4281 4097
L 3078 897
L 2444 897
L 1241 4097
L 1241 0
L 628 0
L 628 4666
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-4d"/>
<use xlink:href="#DejaVuSans-61" transform="translate(86.279297 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(147.558594 0)"/>
<use xlink:href="#DejaVuSans-67" transform="translate(186.921875 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(250.398438 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(278.181641 0)"/>
</g>
</g>
<g id="text_6">
<!-- Gap (added to margin) -->
<g transform="translate(477.094737 99.168388) scale(0.14 -0.14)">
<defs>
<path id="DejaVuSans-28" d="M 1984 4856
Q 1566 4138 1362 3434
Q 1159 2731 1159 2009
Q 1159 1288 1364 580
Q 1569 -128 1984 -844
L 1484 -844
Q 1016 -109 783 600
Q 550 1309 550 2009
Q 550 2706 781 3412
Q 1013 4119 1484 4856
L 1984 4856
z
" transform="scale(0.015625)"/>
<path id="DejaVuSans-29" d="M 513 4856
L 1013 4856
Q 1481 4119 1714 3412
Q 1947 2706 1947 2009
Q 1947 1309 1714 600
Q 1481 -109 1013 -844
L 513 -844
Q 928 -128 1133 580
Q 1338 1288 1338 2009
Q 1338 2731 1133 3434
Q 928 4138 513 4856
z
" transform="scale(0.015625)"/>
</defs>
<use xlink:href="#DejaVuSans-47"/>
<use xlink:href="#DejaVuSans-61" transform="translate(77.490234 0)"/>
<use xlink:href="#DejaVuSans-70" transform="translate(138.769531 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(202.246094 0)"/>
<use xlink:href="#DejaVuSans-28" transform="translate(234.033203 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(273.046875 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(334.326172 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(397.802734 0)"/>
<use xlink:href="#DejaVuSans-65" transform="translate(461.279297 0)"/>
<use xlink:href="#DejaVuSans-64" transform="translate(522.802734 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(586.279297 0)"/>
<use xlink:href="#DejaVuSans-74" transform="translate(618.066406 0)"/>
<use xlink:href="#DejaVuSans-6f" transform="translate(657.275391 0)"/>
<use xlink:href="#DejaVuSans-20" transform="translate(718.457031 0)"/>
<use xlink:href="#DejaVuSans-6d" transform="translate(750.244141 0)"/>
<use xlink:href="#DejaVuSans-61" transform="translate(847.65625 0)"/>
<use xlink:href="#DejaVuSans-72" transform="translate(908.935547 0)"/>
<use xlink:href="#DejaVuSans-67" transform="translate(948.298828 0)"/>
<use xlink:href="#DejaVuSans-69" transform="translate(1011.775391 0)"/>
<use xlink:href="#DejaVuSans-6e" transform="translate(1039.558594 0)"/>
<use xlink:href="#DejaVuSans-29" transform="translate(1102.9375 0)"/>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="p6ec8682cc1">
<rect x="7.2" y="7.2" width="669.6" height="446.4"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 29 KiB

+10 -10
View File
@@ -108,7 +108,7 @@ struct mjContact_ { // result of collision detection functions
mjtNum frame[9]; // normal is in [0-2], points from geom[0] to geom[1]
// contact parameters set by mj_collideGeoms
mjtNum includemargin; // include if dist<includemargin=margin-gap
mjtNum includemargin; // margin for force generation
mjtNum friction[5]; // tangent1, 2, spin, roll1, 2
mjtNum solref[mjNREF]; // constraint solver reference, normal direction
mjtNum solreffriction[mjNREF]; // constraint solver reference, friction directions
@@ -1236,8 +1236,8 @@ struct mjModel_ {
mjtNum* geom_pos; // local position offset rel. to body (ngeom x 3)
mjtNum* geom_quat; // local orientation offset rel. to body (ngeom x 4)
mjtNum* geom_friction; // friction for (slide, spin, roll) (ngeom x 3)
mjtNum* geom_margin; // detect contact if dist<margin (ngeom x 1)
mjtNum* geom_gap; // include in solver if dist<margin-gap (ngeom x 1)
mjtNum* geom_margin; // geometric inflation for contact (ngeom x 1)
mjtNum* geom_gap; // additional contact detection buffer (ngeom x 1)
mjtNum* geom_fluid; // fluid interaction parameters (ngeom x mjNFLUID)
mjtNum* geom_user; // user data (ngeom x nuser_geom)
float* geom_rgba; // rgba when material is omitted (ngeom x 4)
@@ -1304,8 +1304,8 @@ struct mjModel_ {
mjtNum* flex_solref; // constraint solver reference: contact (nflex x mjNREF)
mjtNum* flex_solimp; // constraint solver impedance: contact (nflex x mjNIMP)
mjtNum* flex_friction; // friction for (slide, spin, roll) (nflex x 3)
mjtNum* flex_margin; // detect contact if dist<margin (nflex x 1)
mjtNum* flex_gap; // include in solver if dist<margin-gap (nflex x 1)
mjtNum* flex_margin; // geometric inflation for contact (nflex x 1)
mjtNum* flex_gap; // additional contact detection buffer (nflex x 1)
mjtByte* flex_internal; // internal flex collision enabled (nflex x 1)
int* flex_selfcollide; // self collision mode (mjtFlexSelf) (nflex x 1)
int* flex_activelayers; // number of active element layers, 3D only (nflex x 1)
@@ -1474,8 +1474,8 @@ struct mjModel_ {
mjtNum* pair_solref; // solver reference: contact normal (npair x mjNREF)
mjtNum* pair_solreffriction; // solver reference: contact friction (npair x mjNREF)
mjtNum* pair_solimp; // solver impedance: contact (npair x mjNIMP)
mjtNum* pair_margin; // detect contact if dist<margin (npair x 1)
mjtNum* pair_gap; // include in solver if dist<margin-gap (npair x 1)
mjtNum* pair_margin; // geometric inflation for contact (npair x 1)
mjtNum* pair_gap; // additional contact detection buffer (npair x 1)
mjtNum* pair_friction; // tangent1, 2, spin, roll1, 2 (npair x 5)
// excluded body pairs for collision detection
@@ -2127,7 +2127,7 @@ typedef struct mjsGeom_ { // geom specification
mjtNum solref[mjNREF]; // solver reference
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // include in solver if dist < margin-gap
double gap; // additional contact detection buffer
// inertia inference
double mass; // used to compute density
@@ -2238,7 +2238,7 @@ typedef struct mjsFlex_ { // flex specification
mjtNum solref[mjNREF]; // solver reference
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // include in solver if dist<margin-gap
double gap; // additional contact detection buffer
// other properties
int dim; // element dimensionality
@@ -2389,7 +2389,7 @@ typedef struct mjsPair_ { // pair specification
mjtNum solreffriction[mjNREF]; // solver reference, frictional directions
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // include in solver if dist<margin-gap
double gap; // additional contact detection buffer
double friction[5]; // full contact friction
mjString* info; // message appended to errors
} mjsPair;
+2 -1
View File
@@ -344,7 +344,7 @@ of the function :math:`d(r)` is determined by the element-specific parameter vec
For equality constraints, :math:`r` is the constraint violation. For limits, normal directions of elliptic cones and
all directions of pyramidal cones, :math:`r` is the (limit or contact) distance minus the margin at which the
constraint becomes active; for contacts this margin is :ref:`margin<body-geom-margin>`-:ref:`gap<body-geom-gap>`.
constraint becomes active; for contacts this margin is :ref:`margin<body-geom-margin>`.
Limit and contact constraints are active when :math:`r < 0` (penetration).
For frictional constraints, see :ref:`Friction<CSolverFriction>`.
@@ -516,6 +516,7 @@ are as follows:
**margin**, **gap**
The sum of the two geom margins (or gaps respectively) is used. The geom priority is ignored here, because the
margin and gap are distance properties and a one-sided specification makes little sense.
See :ref:`margin and gap<coMarginGap>`.
.. _solmixing:
@@ -321,8 +321,8 @@ class MjcPhysicsCollisionAPI : public UsdAPISchemaBase {
// --------------------------------------------------------------------- //
// MARGIN
// --------------------------------------------------------------------- //
/// Distance threshold below which contacts are detected and included in the
/// global array mjData.contact.
/// Geometric inflation of the geom surface for the purpose of contact force
/// generation.
///
/// | ||
/// | -- | -- |
@@ -346,11 +346,8 @@ class MjcPhysicsCollisionAPI : public UsdAPISchemaBase {
// --------------------------------------------------------------------- //
// GAP
// --------------------------------------------------------------------- //
/// This attribute is used to enable the generation of inactive contacts,
/// i.e., contacts that are ignored by the constraint solver but are included
/// in mjData.contact for the purpose of custom computations. When this value
/// is positive, geom distances between margin and margin-gap correspond to
/// such inactive contacts.
/// Additional contact detection buffer beyond margin. Contacts are detected
/// at distance margin + gap but forces are only generated at distance margin.
///
/// | ||
/// | -- | -- |
+1 -1
View File
@@ -130,7 +130,7 @@ struct mjContact_ { // result of collision detection functions
mjtNum frame[9]; // normal is in [0-2], points from geom[0] to geom[1]
// contact parameters set by mj_collideGeoms
mjtNum includemargin; // include if dist<includemargin=margin-gap
mjtNum includemargin; // margin for force generation
mjtNum friction[5]; // tangent1, 2, spin, roll1, 2
mjtNum solref[mjNREF]; // constraint solver reference, normal direction
mjtNum solreffriction[mjNREF]; // constraint solver reference, friction directions
+6 -6
View File
@@ -899,8 +899,8 @@ struct mjModel_ {
mjtNum* geom_pos; // local position offset rel. to body (ngeom x 3)
mjtNum* geom_quat; // local orientation offset rel. to body (ngeom x 4)
mjtNum* geom_friction; // friction for (slide, spin, roll) (ngeom x 3)
mjtNum* geom_margin; // detect contact if dist<margin (ngeom x 1)
mjtNum* geom_gap; // include in solver if dist<margin-gap (ngeom x 1)
mjtNum* geom_margin; // geometric inflation for contact (ngeom x 1)
mjtNum* geom_gap; // additional contact detection buffer (ngeom x 1)
mjtNum* geom_fluid; // fluid interaction parameters (ngeom x mjNFLUID)
mjtNum* geom_user; // user data (ngeom x nuser_geom)
float* geom_rgba; // rgba when material is omitted (ngeom x 4)
@@ -967,8 +967,8 @@ struct mjModel_ {
mjtNum* flex_solref; // constraint solver reference: contact (nflex x mjNREF)
mjtNum* flex_solimp; // constraint solver impedance: contact (nflex x mjNIMP)
mjtNum* flex_friction; // friction for (slide, spin, roll) (nflex x 3)
mjtNum* flex_margin; // detect contact if dist<margin (nflex x 1)
mjtNum* flex_gap; // include in solver if dist<margin-gap (nflex x 1)
mjtNum* flex_margin; // geometric inflation for contact (nflex x 1)
mjtNum* flex_gap; // additional contact detection buffer (nflex x 1)
mjtByte* flex_internal; // internal flex collision enabled (nflex x 1)
int* flex_selfcollide; // self collision mode (mjtFlexSelf) (nflex x 1)
int* flex_activelayers; // number of active element layers, 3D only (nflex x 1)
@@ -1137,8 +1137,8 @@ struct mjModel_ {
mjtNum* pair_solref; // solver reference: contact normal (npair x mjNREF)
mjtNum* pair_solreffriction; // solver reference: contact friction (npair x mjNREF)
mjtNum* pair_solimp; // solver impedance: contact (npair x mjNIMP)
mjtNum* pair_margin; // detect contact if dist<margin (npair x 1)
mjtNum* pair_gap; // include in solver if dist<margin-gap (npair x 1)
mjtNum* pair_margin; // geometric inflation for contact (npair x 1)
mjtNum* pair_gap; // additional contact detection buffer (npair x 1)
mjtNum* pair_friction; // tangent1, 2, spin, roll1, 2 (npair x 5)
// excluded body pairs for collision detection
+3 -3
View File
@@ -314,7 +314,7 @@ typedef struct mjsGeom_ { // geom specification
mjtNum solref[mjNREF]; // solver reference
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // include in solver if dist < margin-gap
double gap; // additional contact detection buffer
// inertia inference
double mass; // used to compute density
@@ -433,7 +433,7 @@ typedef struct mjsFlex_ { // flex specification
mjtNum solref[mjNREF]; // solver reference
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // include in solver if dist<margin-gap
double gap; // additional contact detection buffer
// other properties
int dim; // element dimensionality
@@ -597,7 +597,7 @@ typedef struct mjsPair_ { // pair specification
mjtNum solreffriction[mjNREF]; // solver reference, frictional directions
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // include in solver if dist<margin-gap
double gap; // additional contact detection buffer
double friction[5]; // full contact friction
mjString* info; // message appended to errors
} mjsPair;
+2 -3
View File
@@ -275,7 +275,7 @@ def _contact_groups(m: Model, d: Data) -> Dict[FunctionKey, Contact]:
if ip.size > 0:
# pair contacts get their params from m.pair_* fields
params.append((
m.pair_margin[ip] - m.pair_gap[ip],
m.pair_margin[ip],
jp.clip(m.pair_friction[ip], min=eps),
m.pair_solref[ip],
m.pair_solreffriction[ip],
@@ -284,7 +284,6 @@ def _contact_groups(m: Model, d: Data) -> Dict[FunctionKey, Contact]:
if geom1.size > 0 and geom2.size > 0:
# other contacts get their params from geom fields
margin = m.geom_margin[geom1] + m.geom_margin[geom2]
gap = m.geom_gap[geom1] + m.geom_gap[geom2]
solmix1, solmix2 = m.geom_solmix[geom1], m.geom_solmix[geom2]
mix = solmix1 / (solmix1 + solmix2)
mix = jp.where((solmix1 < eps) & (solmix2 < eps), 0.5, mix)
@@ -315,7 +314,7 @@ def _contact_groups(m: Model, d: Data) -> Dict[FunctionKey, Contact]:
# unpack 5d friction:
friction = friction[:, [0, 0, 1, 2, 2]]
params.append((margin - gap, friction, solref, solreffriction, solimp))
params.append((margin, friction, solref, solreffriction, solimp))
params = map(jp.concatenate, zip(*params))
includemargin, friction, solref, solreffriction, solimp = params
+1 -1
View File
@@ -942,7 +942,7 @@ class Contact(PyTreeNode):
dist: distance between nearest points; neg: penetration
pos: position of contact point: midpoint between geoms (3,)
frame: normal is in [0-2] (9,)
includemargin: include if dist<includemargin=margin-gap (1,)
includemargin: include if dist<includemargin=margin (1,)
friction: tangent1, 2, spin, roll1, 2 (5,)
solref: constraint solver reference, normal direction (mjNREF,)
solreffriction: constraint solver reference, friction directions (mjNREF,)
@@ -35,6 +35,7 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAX_EPAFACES
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAX_EPAHORIZON
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXCONPAIR
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import _NEW_GAP_SEMANTICS
from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import DisableBit
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
@@ -773,7 +774,10 @@ def ccd_kernel_builder(
if is_collision_sensor:
cutoff = 1.0e32
else:
cutoff = 0.0
if wp.static(_NEW_GAP_SEMANTICS):
cutoff = gap
else:
cutoff = 0.0
dist, ncollision, w1, w2, multiccd_idx = ccd(
opt_ccd_tolerance[worldid % opt_ccd_tolerance.shape[0]],
cutoff,
@@ -793,8 +797,12 @@ def ccd_kernel_builder(
epa_horizon_in[ccdid],
)
if dist >= 0.0 and pairid[1] == -1:
return 0
if wp.static(_NEW_GAP_SEMANTICS):
if dist >= gap and pairid[1] == -1:
return 0
else:
if dist >= 0.0 and pairid[1] == -1:
return 0
# CCD operates on margin-inflated shapes (support() inflates each geom by
# 0.5 * margin). The returned dist is therefore relative to the inflated
@@ -22,6 +22,7 @@ import warp as wp
from mujoco.mjx.third_party.mujoco_warp._src.math import safe_div
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MINMU
from mujoco.mjx.third_party.mujoco_warp._src.types import _NEW_GAP_SEMANTICS
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MINVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import ContactType
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
@@ -197,14 +198,15 @@ def write_contact(
Returns 1 if the contact is active (dist < margin), 0 otherwise.
"""
active = dist_in < margin_in
detected = dist_in < margin_in + gap_in
# skip contact and no collision sensor
if (pairid_in[0] == -2 or not active) and pairid_in[1] == -1:
if (pairid_in[0] == -2 or not detected) and pairid_in[1] == -1:
return 0
contact_type = 0
if pairid_in[0] >= -1 and active:
if pairid_in[0] >= -1 and detected:
contact_type |= ContactType.CONSTRAINT
if pairid_in[1] >= 0:
@@ -217,7 +219,10 @@ def write_contact(
contact_frame_out[cid] = frame_in
contact_geom_out[cid] = geoms_in
contact_worldid_out[cid] = worldid_in
includemargin = margin_in - gap_in
if wp.static(_NEW_GAP_SEMANTICS):
includemargin = margin_in
else:
includemargin = margin_in - gap_in
contact_includemargin_out[cid] = includemargin
contact_dim_out[cid] = condim_in
contact_friction_out[cid] = friction_in
@@ -271,13 +271,14 @@ def _obb_filter(
return True
def _broadphase_filter(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: int, ngeom_margin: int):
def _broadphase_filter(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: int, ngeom_margin: int, ngeom_gap: int):
@wp.func
def func(
# Model:
geom_aabb: wp.array3d[wp.vec3],
geom_rbound: wp.array2d[float],
geom_margin: wp.array2d[float],
geom_gap: wp.array2d[float],
# Data in:
geom_xpos_in: wp.array2d[wp.vec3],
geom_xmat_in: wp.array2d[wp.mat33],
@@ -299,21 +300,25 @@ def _broadphase_filter(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound
rbound1, rbound2 = geom_rbound[rbound_id, geom1], geom_rbound[rbound_id, geom2] # kernel_analyzer: ignore
margin_id = worldid % ngeom_margin if wp.static(ngeom_margin > 1) else 0
margin1, margin2 = geom_margin[margin_id, geom1], geom_margin[margin_id, geom2] # kernel_analyzer: ignore
gap_id = worldid % ngeom_gap if wp.static(ngeom_gap > 1) else 0
gap1, gap2 = geom_gap[gap_id, geom1], geom_gap[gap_id, geom2] # kernel_analyzer: ignore
effective_margin1 = margin1 + gap1
effective_margin2 = margin2 + gap2
xpos1, xpos2 = geom_xpos_in[worldid, geom1], geom_xpos_in[worldid, geom2]
xmat1, xmat2 = geom_xmat_in[worldid, geom1], geom_xmat_in[worldid, geom2]
if rbound1 == 0.0 or rbound2 == 0.0:
if wp.static(opt_broadphase_filter & BroadphaseFilter.PLANE):
return _plane_filter(rbound1, rbound2, margin1, margin2, xpos1, xpos2, xmat1, xmat2)
return _plane_filter(rbound1, rbound2, effective_margin1, effective_margin2, xpos1, xpos2, xmat1, xmat2)
else:
if wp.static(opt_broadphase_filter & BroadphaseFilter.SPHERE):
if not _sphere_filter(rbound1, rbound2, margin1, margin2, xpos1, xpos2):
if not _sphere_filter(rbound1, rbound2, effective_margin1, effective_margin2, xpos1, xpos2):
return False
if wp.static(opt_broadphase_filter & BroadphaseFilter.AABB):
if not _aabb_filter(center1, center2, size1, size2, margin1, margin2, xpos1, xpos2, xmat1, xmat2):
if not _aabb_filter(center1, center2, size1, size2, effective_margin1, effective_margin2, xpos1, xpos2, xmat1, xmat2):
return False
if wp.static(opt_broadphase_filter & BroadphaseFilter.OBB):
if not _obb_filter(center1, center2, size1, size2, margin1, margin2, xpos1, xpos2, xmat1, xmat2):
if not _obb_filter(center1, center2, size1, size2, effective_margin1, effective_margin2, xpos1, xpos2, xmat1, xmat2):
return False
return True
@@ -377,6 +382,7 @@ def _sap_project(opt_broadphase: int):
ngeom: int,
geom_rbound: wp.array2d[float],
geom_margin: wp.array2d[float],
geom_gap: wp.array2d[float],
# Data in:
geom_xpos_in: wp.array2d[wp.vec3],
nworld_in: int,
@@ -397,7 +403,7 @@ def _sap_project(opt_broadphase: int):
# geom is a plane
rbound = MJ_MAXVAL
radius = rbound + geom_margin[worldid % geom_margin.shape[0], geomid]
radius = rbound + geom_margin[worldid % geom_margin.shape[0], geomid] + geom_gap[worldid % geom_gap.shape[0], geomid]
center = wp.dot(direction_in, xpos)
sort_index_out[worldid, geomid] = geomid
@@ -443,7 +449,7 @@ def _sap_range(
@cache_kernel
def _sap_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: int, ngeom_margin: int):
def _sap_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: int, ngeom_margin: int, ngeom_gap: int):
@wp.kernel(module="unique", enable_backward=False)
def kernel(
# Model:
@@ -452,6 +458,7 @@ def _sap_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: i
geom_aabb: wp.array3d[wp.vec3],
geom_rbound: wp.array2d[float],
geom_margin: wp.array2d[float],
geom_gap: wp.array2d[float],
nxn_pairid: wp.array[wp.vec2i],
# Data in:
geom_xpos_in: wp.array2d[wp.vec3],
@@ -502,8 +509,8 @@ def _sap_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: i
continue
if (
wp.static(_broadphase_filter(opt_broadphase_filter, ngeom_aabb, ngeom_rbound, ngeom_margin))(
geom_aabb, geom_rbound, geom_margin, geom_xpos_in, geom_xmat_in, geom1, geom2, worldid
wp.static(_broadphase_filter(opt_broadphase_filter, ngeom_aabb, ngeom_rbound, ngeom_margin, ngeom_gap))(
geom_aabb, geom_rbound, geom_margin, geom_gap, geom_xpos_in, geom_xmat_in, geom1, geom2, worldid
)
or pairid[1] >= 0
):
@@ -586,7 +593,7 @@ def sap_broadphase(m: Model, d: Data, ctx: CollisionContext):
wp.launch(
kernel=_sap_project(m.opt.broadphase),
dim=(d.nworld, m.ngeom),
inputs=[m.ngeom, m.geom_rbound, m.geom_margin, d.geom_xpos, d.nworld, direction],
inputs=[m.ngeom, m.geom_rbound, m.geom_margin, m.geom_gap, d.geom_xpos, d.nworld, direction],
outputs=[
projection_lower.reshape((-1, m.ngeom)),
projection_upper,
@@ -622,7 +629,7 @@ def sap_broadphase(m: Model, d: Data, ctx: CollisionContext):
# assumes each geom has 5 other geoms (batched over all worlds)
nsweep = 5 * nworldgeom
wp.launch(
kernel=_sap_broadphase(m.opt.broadphase_filter, m.geom_aabb.shape[0], m.geom_rbound.shape[0], m.geom_margin.shape[0]),
kernel=_sap_broadphase(m.opt.broadphase_filter, m.geom_aabb.shape[0], m.geom_rbound.shape[0], m.geom_margin.shape[0], m.geom_gap.shape[0]),
dim=nsweep,
inputs=[
m.ngeom,
@@ -630,6 +637,7 @@ def sap_broadphase(m: Model, d: Data, ctx: CollisionContext):
m.geom_aabb,
m.geom_rbound,
m.geom_margin,
m.geom_gap,
m.nxn_pairid,
d.geom_xpos,
d.geom_xmat,
@@ -644,7 +652,7 @@ def sap_broadphase(m: Model, d: Data, ctx: CollisionContext):
@cache_kernel
def _nxn_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: int, ngeom_margin: int):
def _nxn_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: int, ngeom_margin: int, ngeom_gap: int):
@wp.kernel(module="unique", enable_backward=False)
def kernel(
# Model:
@@ -652,6 +660,7 @@ def _nxn_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: i
geom_aabb: wp.array3d[wp.vec3],
geom_rbound: wp.array2d[float],
geom_margin: wp.array2d[float],
geom_gap: wp.array2d[float],
nxn_geom_pair: wp.array[wp.vec2i],
nxn_pairid: wp.array[wp.vec2i],
# Data in:
@@ -672,8 +681,8 @@ def _nxn_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: i
geom2 = geom[1]
if (
wp.static(_broadphase_filter(opt_broadphase_filter, ngeom_aabb, ngeom_rbound, ngeom_margin))(
geom_aabb, geom_rbound, geom_margin, geom_xpos_in, geom_xmat_in, geom1, geom2, worldid
wp.static(_broadphase_filter(opt_broadphase_filter, ngeom_aabb, ngeom_rbound, ngeom_margin, ngeom_gap))(
geom_aabb, geom_rbound, geom_margin, geom_gap, geom_xpos_in, geom_xmat_in, geom1, geom2, worldid
)
or nxn_pairid[elementid][1] >= 0
):
@@ -709,13 +718,14 @@ def nxn_broadphase(m: Model, d: Data, ctx: CollisionContext):
`contype`/`conaffinity`, parent-child relationships, and explicit `<exclude>` tags.
"""
wp.launch(
_nxn_broadphase(m.opt.broadphase_filter, m.geom_aabb.shape[0], m.geom_rbound.shape[0], m.geom_margin.shape[0]),
_nxn_broadphase(m.opt.broadphase_filter, m.geom_aabb.shape[0], m.geom_rbound.shape[0], m.geom_margin.shape[0], m.geom_gap.shape[0]),
dim=(d.nworld, m.nxn_geom_pair_filtered.shape[0]),
inputs=[
m.geom_type,
m.geom_aabb,
m.geom_rbound,
m.geom_margin,
m.geom_gap,
m.nxn_geom_pair_filtered,
m.nxn_pairid_filtered,
d.geom_xpos,
+6 -3
View File
@@ -17,6 +17,7 @@ import enum
from typing import Callable
import mujoco
from mujoco.mjx.third_party.mujoco_warp._src.util_pkg import check_version
import numpy as np
import warp as wp
@@ -26,6 +27,8 @@ MJ_MINIMP = mujoco.mjMINIMP # minimum constraint impedance
MJ_MAXIMP = mujoco.mjMAXIMP # maximum constraint impedance
MJ_MAXCONPAIR = mujoco.mjMAXCONPAIR
MJ_MINMU = mujoco.mjMINMU # minimum friction
# True if MuJoCo >= 3.9.0 (new margin/gap semantics: includemargin = margin)
_NEW_GAP_SEMANTICS = check_version("mujoco>=3.9.0")
# maximum size (by number of edges) of an horizon in EPA algorithm
MJ_MAX_EPAHORIZON = 24
# maximum average number of trianglarfaces EPA can insert at each iteration
@@ -974,7 +977,7 @@ class Model:
geom_quat: local orientation offset rel. to body (*, ngeom, 4)
geom_friction: friction for (slide, spin, roll) (*, ngeom, 3)
geom_margin: detect contact if dist<margin (*, ngeom,)
geom_gap: include in solver if dist<margin-gap (*, ngeom,)
geom_gap: additional contact detection buffer (*, ngeom,)
geom_fluid: fluid interaction parameters (ngeom, mjNFLUID)
geom_rgba: rgba when material is omitted (*, ngeom, 4)
site_type: geom type for rendering (GeomType) (nsite,)
@@ -1082,7 +1085,7 @@ class Model:
pair_solreffriction: solver reference: contact friction (*, npair, mjNREF)
pair_solimp: solver impedance: contact (*, npair, mjNIMP)
pair_margin: detect contact if dist<margin (*, npair,)
pair_gap: include in solver if dist<margin-gap (*, npair,)
pair_gap: additional contact detection buffer (*, npair,)
pair_friction: tangent1, 2, spin, roll1, 2 (*, npair, 5)
exclude_signature: body1 << 16 + body2 (nexclude,)
eq_type: constraint type (EqType) (neq,)
@@ -1663,7 +1666,7 @@ class Contact:
dist: distance between nearest points; neg: penetration (naconmax,)
pos: position of contact point: midpoint between geoms (naconmax, 3)
frame: normal is in [0-2], points from geom[0] to geom[1] (naconmax, 3, 3)
includemargin: include if dist<includemargin=margin-gap (naconmax,)
includemargin: include if dist<includemargin=margin (naconmax,)
friction: tangent1, 2, spin, roll1, 2 (naconmax, 5)
solref: constraint solver reference, normal direction (naconmax, 2)
solreffriction: constraint solver reference, friction directions (naconmax, 2)
+12 -8
View File
@@ -22,21 +22,25 @@ import re
def _parse_version(version_str: str) -> tuple[tuple[int, int | str], ...]:
"""Parse a version string into comparable components.
Both '.' and '-' are treated as separators. Each component is wrapped in a
tuple: (0, int) for numeric parts, (-1, str) for non-numeric. A (0, 0)
sentinel is appended so that stable releases sort above pre-release suffixes
during Python tuple comparison (e.g., 1.2.3 >= 1.2.3.dev). Non-numeric
components are compared lexicographically (e.g., b >= a).
Dot-separated components form the version. Hyphen-separated suffixes (e.g.,
"-foo3") are treated as local build identifiers and stripped before
parsing. Each component is wrapped in a tuple: (0, int) for numeric parts,
(-1, str) for non-numeric. A (0, 0) sentinel is appended so that stable
releases sort above pre-release suffixes during Python tuple comparison
(e.g., 1.2.3 >= 1.2.3.dev). Non-numeric components are compared
lexicographically (e.g., b >= a).
Args:
version_str: Version string like "3.5.0" or "3.5.0.dev869102767".
version_str: Version string like "3.5.0", "3.5.0.dev869102767", or
"3.9.0-foo3".
Returns:
Tuple of (type_order, value) pairs for comparison, where type_order is 0
for integers and -1 for strings, followed by a (0, 0) sentinel.
"""
# Split on both '.' and '-'
parts = re.split(r"[.\-]", version_str)
# Strip local build identifier (e.g., "3.9.0-foo3" -> "3.9.0")
version_str = version_str.split("-", 1)[0]
parts = version_str.split(".")
return tuple([(0, int(p)) if p.isdigit() else (-1, p) for p in parts] + [(0, 0)])
+6 -6
View File
@@ -22,11 +22,11 @@
</default>
<default class="active_adhesion">
<!--
Geoms in the active_adhesion class are "inflated" with a margin of 1cm, but contacts are
activated only at a depth gap of 1cm, leading to a layer of inactive contacts at a 1cm
distance from the surface. However the contacts can be used by the adhesion actuator.
Geoms in the active_adhesion class have a detection gap of 1cm, leading to a layer of
inactive contacts at a 1cm distance from the surface. The contacts can be used by the
adhesion actuator, but no contact forces are generated in the gap zone.
-->
<geom rgba=".8 .5 .5 1" margin=".01" gap=".01"/>
<geom rgba=".8 .5 .5 1" gap=".01"/>
</default>
<default class="object">
<geom rgba=".5 .8 .5 1" density="100"/>
@@ -87,9 +87,9 @@
frictionless point particles. In order to make them stick to the sphere we give the sphere
priority 2, to force condim=3.
Also note the sphere has a margin+gap of 3cm as opposed to the 1cm of the arm box.
Also note the sphere has a gap of 3cm as opposed to the 1cm of the arm box.
-->
<geom type="sphere" size=".03" priority="2" margin=".03" gap=".03"/>
<geom type="sphere" size=".03" priority="2" gap=".03"/>
<site name="pin_left" pos="-.025 0 .025"/>
<site name="pin_right" pos=".025 0 .025"/>
</body>
+10 -10
View File
@@ -2118,7 +2118,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='detect contact if dist<margin',
doc='geometric inflation for contact',
array_extent=('ngeom',),
),
StructFieldDecl(
@@ -2126,7 +2126,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='include in solver if dist<margin-gap',
doc='additional contact detection buffer',
array_extent=('ngeom',),
),
StructFieldDecl(
@@ -2598,7 +2598,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='detect contact if dist<margin',
doc='geometric inflation for contact',
array_extent=('nflex',),
),
StructFieldDecl(
@@ -2606,7 +2606,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='include in solver if dist<margin-gap',
doc='additional contact detection buffer',
array_extent=('nflex',),
),
StructFieldDecl(
@@ -3846,7 +3846,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='detect contact if dist<margin',
doc='geometric inflation for contact',
array_extent=('npair',),
),
StructFieldDecl(
@@ -3854,7 +3854,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='include in solver if dist<margin-gap',
doc='additional contact detection buffer',
array_extent=('npair',),
),
StructFieldDecl(
@@ -5169,7 +5169,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
StructFieldDecl(
name='includemargin',
type=ValueType(name='mjtNum'),
doc='include if dist<includemargin=margin-gap',
doc='margin for force generation',
),
StructFieldDecl(
name='friction',
@@ -7644,7 +7644,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
StructFieldDecl(
name='gap',
type=ValueType(name='double'),
doc='include in solver if dist < margin-gap',
doc='additional contact detection buffer',
),
StructFieldDecl(
name='mass',
@@ -8158,7 +8158,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
StructFieldDecl(
name='gap',
type=ValueType(name='double'),
doc='include in solver if dist<margin-gap',
doc='additional contact detection buffer',
),
StructFieldDecl(
name='dim',
@@ -8917,7 +8917,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
StructFieldDecl(
name='gap',
type=ValueType(name='double'),
doc='include in solver if dist<margin-gap',
doc='additional contact detection buffer',
),
StructFieldDecl(
name='friction',
+61 -56
View File
@@ -882,12 +882,13 @@ void mj_collideTree(const mjModel* m, mjData* d, int bf1, int bf2,
// both are leaves
if (isleaf1 && isleaf2) {
mjtNum margin = mj_assignMargin(m, m->geom_margin[nodeid1] + m->geom_margin[nodeid2]);
mjtNum gap = m->geom_gap[nodeid1] + m->geom_gap[nodeid2];
if (!mj_filterSphere(m, d, nodeid1, nodeid2, margin)) {
if (!mj_filterSphere(m, d, nodeid1, nodeid2, margin + gap)) {
if (mj_collideOBB(m->geom_aabb + 6*nodeid1, m->geom_aabb + 6*nodeid2,
d->geom_xpos + 3*nodeid1, d->geom_xmat + 9*nodeid1,
d->geom_xpos + 3*nodeid2, d->geom_xmat + 9*nodeid2,
margin, NULL, NULL, &initialize)) {
margin + gap, NULL, NULL, &initialize)) {
mj_collideGeomPair(m, d, nodeid1, nodeid2, merged, startadr, pairadr);
if (mark_active) {
d->bvh_active[node1 + bvhadr1] = 1;
@@ -913,15 +914,16 @@ void mj_collideTree(const mjModel* m, mjData* d, int bf1, int bf2,
// both are leaves
if (isleaf1 && isleaf2) {
mjtNum margin = mj_assignMargin(m, m->geom_margin[nodeid1] + m->flex_margin[f2]);
mjtNum gap = m->geom_gap[nodeid1] + m->flex_gap[f2];
if (!filterBitmask(m->geom_contype[nodeid1], m->geom_conaffinity[nodeid1],
m->flex_contype[f2], m->flex_conaffinity[f2]) &&
!filterSphereBox(d->geom_xpos + 3*nodeid1, m->geom_rbound[nodeid1] + margin,
!filterSphereBox(d->geom_xpos + 3*nodeid1, m->geom_rbound[nodeid1] + margin + gap,
bvh2 + 6*node2)) {
if (mj_collideOBB(m->geom_aabb + 6*nodeid1, bvh2 + 6*node2,
d->geom_xpos + 3*nodeid1, d->geom_xmat + 9*nodeid1,
NULL, NULL,
margin, NULL, NULL, &initialize)) {
margin + gap, NULL, NULL, &initialize)) {
// collide unless geom is plane or SDF (handled separately)
if (m->geom_type[nodeid1] != mjGEOM_PLANE &&
m->geom_type[nodeid1] != mjGEOM_SDF) {
@@ -1058,7 +1060,8 @@ static void makeAAMM(const mjModel* m, mjData* d,
// process all body geoms (body is collidable, should have geoms)
for (int i=0; i < body_geomnum; i++) {
int geom = m->body_geomadr[body]+i;
mjtNum margin = override_margin ? override_margin : m->geom_margin[geom];
mjtNum margin = override_margin ? override_margin
: m->geom_margin[geom] + m->geom_gap[geom];
mjtNum _aamm[6];
const mjtNum* aabb = m->geom_aabb + 6*geom;
@@ -1124,7 +1127,8 @@ static void makeAAMM(const mjModel* m, mjData* d,
}
// correct for flex radius and margin
mjtNum margin = override_margin ? override_margin : m->flex_margin[f];
mjtNum margin = override_margin ? override_margin
: m->flex_margin[f] + m->flex_gap[f];
mjtNum bound = m->flex_radius[f] + margin;
aamm[0] -= bound;
aamm[1] -= bound;
@@ -1513,8 +1517,8 @@ int mj_broadphase(const mjModel* m, mjData* d, int* bfpair, int maxpair) {
//----------------------------- narrow-phase collision detection -----------------------------------
// compute contact condim, gap, solref, solimp, friction
static void mj_contactParam(const mjModel* m, int* condim, mjtNum* gap,
// compute contact condim, solref, solimp, friction
static void mj_contactParam(const mjModel* m, int* condim,
mjtNum* solref, mjtNum* solimp, mjtNum* friction,
int g1, int g2, int f1, int f2) {
mjtNum fri[3];
@@ -1522,7 +1526,6 @@ static void mj_contactParam(const mjModel* m, int* condim, mjtNum* gap,
// get parameters from geom1 or flex1
int priority1 = (f1 < 0) ? m->geom_priority[g1] : m->flex_priority[f1];
int condim1 = (f1 < 0) ? m->geom_condim[g1] : m->flex_condim[f1];
mjtNum gap1 = (f1 < 0) ? m->geom_gap[g1] : m->flex_gap[f1];
mjtNum solmix1 = (f1 < 0) ? m->geom_solmix[g1] : m->flex_solmix[f1];
const mjtNum* solref1 = (f1 < 0) ? m->geom_solref+g1*mjNREF : m->flex_solref+f1*mjNREF;
const mjtNum* solimp1 = (f1 < 0) ? m->geom_solimp+g1*mjNIMP : m->flex_solimp+f1*mjNIMP;
@@ -1531,15 +1534,11 @@ static void mj_contactParam(const mjModel* m, int* condim, mjtNum* gap,
// get parameters from geom2 or flex2
int priority2 = (f2 < 0) ? m->geom_priority[g2] : m->flex_priority[f2];
int condim2 = (f2 < 0) ? m->geom_condim[g2] : m->flex_condim[f2];
mjtNum gap2 = (f2 < 0) ? m->geom_gap[g2] : m->flex_gap[f2];
mjtNum solmix2 = (f2 < 0) ? m->geom_solmix[g2] : m->flex_solmix[f2];
const mjtNum* solref2 = (f2 < 0) ? m->geom_solref+g2*mjNREF : m->flex_solref+f2*mjNREF;
const mjtNum* solimp2 = (f2 < 0) ? m->geom_solimp+g2*mjNIMP : m->flex_solimp+f2*mjNIMP;
const mjtNum* friction2 = (f2 < 0) ? m->geom_friction+g2*3 : m->flex_friction+f2*3;
// gap: add
*gap = gap1 + gap2;
// different priority: copy from item with higher priority
if (priority1 > priority2) {
*condim = condim1;
@@ -1719,8 +1718,15 @@ void mj_collideGeoms(const mjModel* m, mjData* d, int ipair, int g1, int g2) {
margin = mj_assignMargin(m, m->pair_margin[ipair]);
}
// set gap: dynamic or pair
if (ipair < 0) {
gap = m->geom_gap[g1] + m->geom_gap[g2];
} else {
gap = m->pair_gap[ipair];
}
// bounding sphere filter
if (mj_filterSphere(m, d, g1, g2, margin)) {
if (mj_filterSphere(m, d, g1, g2, margin + gap)) {
return;
}
@@ -1733,7 +1739,7 @@ void mj_collideGeoms(const mjModel* m, mjData* d, int ipair, int g1, int g2) {
}
// call collision detector to generate contacts
num = collisionFunc(m, d, con, g1, g2, margin);
num = collisionFunc(m, d, con, g1, g2, margin + gap);
// check contacts
if (!num) {
@@ -1746,15 +1752,14 @@ void mj_collideGeoms(const mjModel* m, mjData* d, int ipair, int g1, int g2) {
mjERROR("too many contacts returned by collision function");
}
// set condim, gap, solref, solimp, friction: dynamic
// set condim, solref, solimp, friction: dynamic
if (ipair < 0) {
mj_contactParam(m, &condim, &gap, solref, solimp, friction, g1, g2, -1, -1);
mj_contactParam(m, &condim, solref, solimp, friction, g1, g2, -1, -1);
}
// set condim, gap, solref, solimp, friction: pair
// set condim, solref, solimp, friction: pair
else {
condim = m->pair_dim[ipair];
gap = m->pair_gap[ipair];
mju_copy(solref, m->pair_solref+mjNREF*ipair, mjNREF);
mju_copy(solimp, m->pair_solimp+mjNIMP*ipair, mjNIMP);
mju_copy(friction, m->pair_friction+5*ipair, 5);
@@ -1778,7 +1783,7 @@ void mj_collideGeoms(const mjModel* m, mjData* d, int ipair, int g1, int g2) {
con[i].vert[1] = -1;
// set remaining contact parameters
mj_setContact(m, con + i, condim, margin-gap, solref, solreffriction, solimp, friction);
mj_setContact(m, con + i, condim, margin, solref, solreffriction, solimp, friction);
}
// add to ncon
@@ -1801,9 +1806,10 @@ void mj_collidePlaneFlex(const mjModel* m, mjData* d, int g, int f) {
mjtNum margin = mj_assignMargin(m, m->geom_margin[g] + m->flex_margin[f]);
int condim;
int flex_vertnum = m->flex_vertnum[f];
mjtNum gap, solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum gap = m->geom_gap[g] + m->flex_gap[f];
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, &gap, solref, solimp, friction, g, -1, -1, f);
mj_contactParam(m, &condim, solref, solimp, friction, g, -1, -1, f);
// collide all flex vertices with plane
for (int i=0; i < flex_vertnum; i++) {
@@ -1814,7 +1820,7 @@ void mj_collidePlaneFlex(const mjModel* m, mjData* d, int g, int f) {
mjtNum dist = mju_dot3(dif, nrm);
// no contact
if (dist > margin + radius) {
if (dist > margin + gap + radius) {
continue;
}
@@ -1835,7 +1841,7 @@ void mj_collidePlaneFlex(const mjModel* m, mjData* d, int g, int f) {
con.vert[1] = i;
// set remaining contact parameters
mj_setContact(m, &con, condim, margin-gap, solref, solreffriction, solimp, friction);
mj_setContact(m, &con, condim, margin, solref, solreffriction, solimp, friction);
// add to mjData, abort if too many contacts
if (mj_addContact(m, d, &con)) {
@@ -1854,10 +1860,11 @@ void mj_collideSdfFlex(const mjModel* m, mjData* d, int g, int f) {
// prepare contact parameters (same for all contacts)
mjtNum margin = mj_assignMargin(m, m->geom_margin[g] + m->flex_margin[f]);
mjtNum gap = m->geom_gap[g] + m->flex_gap[f];
int condim;
mjtNum gap, solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, &gap, solref, solimp, friction, g, -1, -1, f);
mj_contactParam(m, &condim, solref, solimp, friction, g, -1, -1, f);
// allocate temporary contact array on stack (zero-initialized)
mj_markStack(d);
@@ -1865,7 +1872,7 @@ void mj_collideSdfFlex(const mjModel* m, mjData* d, int g, int f) {
memset(con, 0, mjMAXCONPAIR * sizeof(mjContact));
// call batched flex-SDF collision
int num = mjc_FlexSDF(m, d, con, g, f, margin);
int num = mjc_FlexSDF(m, d, con, g, f, margin + gap);
// add contacts to mjData
for (int i = 0; i < num; i++) {
@@ -1880,7 +1887,7 @@ void mj_collideSdfFlex(const mjModel* m, mjData* d, int g, int f) {
con[i].vert[1] = -1;
// set remaining contact parameters
mj_setContact(m, con + i, condim, margin-gap, solref, solreffriction, solimp, friction);
mj_setContact(m, con + i, condim, margin, solref, solreffriction, solimp, friction);
// add to mjData, abort if too many contacts
if (mj_addContact(m, d, con + i)) {
@@ -1949,9 +1956,9 @@ void mj_collideFlexInternal(const mjModel* m, mjData* d, int f) {
int condim;
int flex_elemnum = m->flex_elemnum[f];
mjtNum radius = m->flex_radius[f];
mjtNum gap, solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, &gap, solref, solimp, friction, -1, -1, f, f);
mj_contactParam(m, &condim, solref, solimp, friction, -1, -1, f, f);
condim = 1;
// process all elements
@@ -2048,13 +2055,14 @@ void mj_collideFlexSAP(const mjModel* m, mjData* d, int f) {
// test a geom and an elem for collision, add to contact list
void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
mjtNum margin = mj_assignMargin(m, m->geom_margin[g] + m->flex_margin[f]);
mjtNum gap = m->geom_gap[g] + m->flex_gap[f];
int dim = m->flex_dim[f], type = m->geom_type[g];
int num;
// bounding sphere test: only if midphase is disabled
if (mjDISABLED(mjDSBL_MIDPHASE)) {
int eglobal = m->flex_elemadr[f] + e;
if (filterSphereBox(d->geom_xpos+3*g, m->geom_rbound[g]+margin,
if (filterSphereBox(d->geom_xpos+3*g, m->geom_rbound[g]+margin+gap,
d->flexelem_aabb+6*eglobal)) {
return;
}
@@ -2087,17 +2095,17 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
// call raw primitive for corresponding geom type
if (type == mjGEOM_SPHERE) {
num = mjraw_SphereCapsule(con, margin,
num = mjraw_SphereCapsule(con, margin + gap,
d->geom_xpos+3*g, d->geom_xmat+9*g, m->geom_size+3*g,
pos, mat, size);
}
else if (type == mjGEOM_CAPSULE) {
num = mjraw_CapsuleCapsule(con, margin,
num = mjraw_CapsuleCapsule(con, margin + gap,
d->geom_xpos+3*g, d->geom_xmat+9*g, m->geom_size+3*g,
pos, mat, size);
}
else {
num = mjraw_CapsuleBox(con, margin,
num = mjraw_CapsuleBox(con, margin + gap,
pos, mat, size,
d->geom_xpos+3*g, d->geom_xmat+9*g, m->geom_size+3*g);
@@ -2110,13 +2118,13 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
// heightfield : elem
else if (type == mjGEOM_HFIELD) {
num = mjc_HFieldElem(m, d, con, g, f, e, margin);
num = mjc_HFieldElem(m, d, con, g, f, e, margin + gap);
}
// sphere : triangle
else if (type == mjGEOM_SPHERE && dim == 2) {
const mjtNum* vertxpos = d->flexvert_xpos + 3*m->flex_vertadr[f];
num = mjraw_SphereTriangle(con, margin,
num = mjraw_SphereTriangle(con, margin + gap,
d->geom_xpos+3*g, m->geom_size[3*g],
vertxpos + 3*edata[0], vertxpos + 3*edata[1],
vertxpos + 3*edata[2], m->flex_radius[f]);
@@ -2125,7 +2133,7 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
// box : triangle
else if (type == mjGEOM_BOX && dim == 2) {
const mjtNum* vertxpos = d->flexvert_xpos + 3 * m->flex_vertadr[f];
num = mjraw_BoxTriangle(con, margin, d->geom_xpos + 3 * g,
num = mjraw_BoxTriangle(con, margin + gap, d->geom_xpos + 3 * g,
d->geom_xmat + 9 * g, m->geom_size + 3 * g,
vertxpos + 3 * edata[0], vertxpos + 3 * edata[1],
vertxpos + 3 * edata[2], m->flex_radius[f]);
@@ -2135,14 +2143,14 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
else if (type == mjGEOM_CAPSULE && dim == 2) {
const mjtNum* vertxpos = d->flexvert_xpos + 3 * m->flex_vertadr[f];
num = mjraw_CapsuleTriangle(
con, margin, d->geom_xpos + 3 * g, d->geom_xmat + 9 * g,
con, margin + gap, d->geom_xpos + 3 * g, d->geom_xmat + 9 * g,
m->geom_size + 3 * g, vertxpos + 3 * edata[0], vertxpos + 3 * edata[1],
vertxpos + 3 * edata[2], m->flex_radius[f]);
}
// general geom : elem
else {
num = mjc_ConvexElem(m, d, con, g, -1, -1, -1, f, e, margin);
num = mjc_ConvexElem(m, d, con, g, -1, -1, -1, f, e, margin + gap);
}
// check contacts
@@ -2153,9 +2161,9 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
// get contact parameters
int condim;
mjtNum gap, friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, &gap, solref, solimp, friction, g, -1, -1, f);
mj_contactParam(m, &condim, solref, solimp, friction, g, -1, -1, f);
// add contacts
for (int i=0; i < num; i++) {
@@ -2170,7 +2178,7 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
con[i].vert[1] = -1;
// set remaining contact parameters
mj_setContact(m, con + i, condim, margin-gap, solref, solreffriction, solimp, friction);
mj_setContact(m, con + i, condim, margin, solref, solreffriction, solimp, friction);
}
// add to ncon
@@ -2184,17 +2192,19 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
// test two elems for collision, add to contact list
void mj_collideElems(const mjModel* m, mjData* d, int f1, int e1, int f2, int e2) {
mjtNum margin = mj_assignMargin(m, m->flex_margin[f1] + m->flex_margin[f2]);
mjtNum gap = m->flex_gap[f1] + m->flex_gap[f2];
int dim1 = m->flex_dim[f1], dim2 = m->flex_dim[f2];
int num;
// ignore margin in self-collisions
// ignore margin and gap in self-collisions
if (f1 == f2) {
margin = 0;
gap = 0;
}
// bounding box filter (not applied in midphase)
if (filterBox(d->flexelem_aabb+6*(m->flex_elemadr[f1]+e1),
d->flexelem_aabb+6*(m->flex_elemadr[f2]+e2), margin)) {
d->flexelem_aabb+6*(m->flex_elemadr[f2]+e2), margin + gap)) {
return;
}
@@ -2231,12 +2241,12 @@ void mj_collideElems(const mjModel* m, mjData* d, int f1, int e1, int f2, int e2
pos2, mat2, size2);
// raw primitive
num = mjraw_CapsuleCapsule(con, margin, pos1, mat1, size1, pos2, mat2, size2);
num = mjraw_CapsuleCapsule(con, margin + gap, pos1, mat1, size1, pos2, mat2, size2);
}
// general convex collision
else {
num = mjc_ConvexElem(m, d, con, -1, f1, e1, -1, f2, e2, margin);
num = mjc_ConvexElem(m, d, con, -1, f1, e1, -1, f2, e2, margin + gap);
}
// check contacts
@@ -2247,14 +2257,9 @@ void mj_collideElems(const mjModel* m, mjData* d, int f1, int e1, int f2, int e2
// get contact parameters
int condim;
mjtNum gap, friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, &gap, solref, solimp, friction, -1, -1, f1, f2);
// ignore gap in self collision, since margin is ignored
if (f1 == f2) {
gap = 0;
}
mj_contactParam(m, &condim, solref, solimp, friction, -1, -1, f1, f2);
// add contacts
for (int i=0; i < num; i++) {
@@ -2269,7 +2274,7 @@ void mj_collideElems(const mjModel* m, mjData* d, int f1, int e1, int f2, int e2
con[i].vert[1] = -1;
// set remaining contact parameters
mj_setContact(m, con + i, condim, margin-gap, solref, solreffriction, solimp, friction);
mj_setContact(m, con + i, condim, margin, solref, solreffriction, solimp, friction);
}
// add to ncon
@@ -2336,9 +2341,9 @@ void mj_collideElemVert(const mjModel* m, mjData* d, int f, int e, int v) {
// get contact parameters
int condim;
mjtNum gap, friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, &gap, solref, solimp, friction, -1, -1, f, f);
mj_contactParam(m, &condim, solref, solimp, friction, -1, -1, f, f);
// add contacts
for (int i=0; i < num; i++) {
+3 -3
View File
@@ -421,7 +421,7 @@ void ElementSpecGui(mjsElement* element, SpecEditor* editor) {
FIELD(solref, "solver reference");
FIELD(solimp, "solver impedance");
FIELD(margin, "margin for contact detection");
FIELD(gap, "include in solver if dist < margin-gap");
FIELD(gap, "additional contact detection buffer");
FIELD(mass, "used to compute density");
FIELD(density, "used to compute mass and inertia from volume or surface");
FIELD(typeinertia, "selects between surface and volume inertia");
@@ -530,7 +530,7 @@ void ElementSpecGui(mjsElement* element, SpecEditor* editor) {
FIELD(solref, "solver reference");
FIELD(solimp, "solver impedance");
FIELD(margin, "margin for contact detection");
FIELD(gap, "include in solver if dist<margin-gap");
FIELD(gap, "additional contact detection buffer");
FIELD(dim, "element dimensionality");
FIELD(radius, "radius around primitive element");
FIELD(size, "vertex bounding box half sizes in qpos0");
@@ -626,7 +626,7 @@ void ElementSpecGui(mjsElement* element, SpecEditor* editor) {
FIELD(solreffriction, "solver reference, frictional directions");
FIELD(solimp, "solver impedance");
FIELD(margin, "margin for contact detection");
FIELD(gap, "include in solver if dist<margin-gap");
FIELD(gap, "additional contact detection buffer");
FIELD(friction, "full contact friction");
FIELD(info, "message appended to errors");
break;
@@ -312,7 +312,7 @@ class "MjcCollisionAPI" (
)
uniform double mjc:gap = 0 (
displayName = "Gap"
doc = "This attribute is used to enable the generation of inactive contacts, i.e., contacts that are ignored by the constraint solver but are included in mjData.contact for the purpose of custom computations. When this value is positive, geom distances between margin and margin-gap correspond to such inactive contacts."
doc = "Additional contact detection buffer beyond margin. Contacts are detected at distance margin + gap but forces are only generated at distance margin."
)
uniform int mjc:group = 0 (
displayName = "Group"
@@ -320,7 +320,7 @@ class "MjcCollisionAPI" (
)
uniform double mjc:margin = 0 (
displayName = "Margin"
doc = "Distance threshold below which contacts are detected and included in the global array mjData.contact."
doc = "Geometric inflation of the geom surface for the purpose of contact force generation."
)
uniform int mjc:priority = 0 (
displayName = "Priority"
+2 -2
View File
@@ -738,7 +738,7 @@ class "MjcCollisionAPI"
string apiName = "Margin"
}
displayName = "Margin"
doc = """Distance threshold below which contacts are detected and included in the global array mjData.contact."""
doc = """Geometric inflation of the geom surface for the purpose of contact force generation."""
)
uniform double mjc:gap = 0.0 (
@@ -746,7 +746,7 @@ class "MjcCollisionAPI"
string apiName = "Gap"
}
displayName = "Gap"
doc = """This attribute is used to enable the generation of inactive contacts, i.e., contacts that are ignored by the constraint solver but are included in mjData.contact for the purpose of custom computations. When this value is positive, geom distances between margin and margin-gap correspond to such inactive contacts."""
doc = """Additional contact detection buffer beyond margin. Contacts are detected at distance margin + gap but forces are only generated at distance margin."""
)
}
+3 -1
View File
@@ -2019,8 +2019,10 @@ TEST_F(MjGjkTest, CylinderBoxMargin) {
mjData* data = mj_makeData(model);
mj_forward(model, data);
// margin=0.1 means forces generated when dist<0.1
// the contact at dist~0.015 is within margin, so forces are generated
EXPECT_EQ(data->ncon, 1);
EXPECT_LT(data->contact[0].efc_address, 0);
EXPECT_GE(data->contact[0].efc_address, 0);
mj_deleteData(data);
mj_deleteModel(model);
+1 -1
View File
@@ -2,7 +2,7 @@
<!-- This model leads to bad contacts from mjc_BoxBox -->
<default>
<geom rgba="1 1 1 1" margin="1e-3" gap="1e-3"/>
<geom rgba="1 1 1 1" gap="1e-3"/>
<site type="sphere" rgba="0 0 0 0" size="0.001"/>
</default>
@@ -16,7 +16,7 @@
<body pos="0 0 .3">
<freejoint/>
<geom type="sphere" size=".05" rgba=".5 .5 .5 .5" margin="0.1" gap="0.1"/>
<geom type="sphere" size=".05" rgba=".5 .5 .5 .5" gap="0.1"/>
</body>
<body pos="-.5 0 .3">
+1 -1
View File
@@ -43,7 +43,7 @@
<worldbody>
<light pos="0 0 3"/>
<geom name="floor" type="plane" size="4 4 .1" margin="0.01" gap="0.005"/>
<geom name="floor" type="plane" size="4 4 .1" margin="0.005" gap="0.005"/>
<geom type="hfield" hfield="hfield" pos="-.4 .6 .05" rgba="0 0 1 1"/>
<body name="head" pos="0 0 .7" gravcomp="0.5">
<geom type="ellipsoid" size=".2 .2 .4" density="200" material="grid"/>
+1 -1
View File
@@ -43,7 +43,7 @@
<worldbody>
<light pos="0 0 3"/>
<geom name="floor" type="plane" size="4 4 .1" margin="0.01" gap="0.005"/>
<geom name="floor" type="plane" size="4 4 .1" margin="0.005" gap="0.005"/>
<geom type="hfield" hfield="hfield" pos="-.4 .6 .05" rgba="0 0 1 1"/>
<body name="head" pos="0 0 .7" gravcomp="0.5">
<geom type="ellipsoid" size=".2 .2 .4" density="200" material="grid"/>