Skip to content

Commit c7f5766

Browse files
yuvaltassacopybara-github
authored andcommitted
Add frustum visualization for orthographic cameras.
PiperOrigin-RevId: 847698917 Change-Id: I13633e8ba2d4718b127a5838c404ab74c6180e3d
1 parent 1585252 commit c7f5766

File tree

4 files changed

+43
-32
lines changed

4 files changed

+43
-32
lines changed

doc/XMLreference.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2976,12 +2976,19 @@ and the +Y axis points up. Thus the frame position and orientation are the key a
29762976
for most scenes and should likely be reduced. In either case, the horizontal field of view is computed automatically
29772977
given the window size and the vertical field of view.
29782978

2979+
.. _body-camera-resolution:
2980+
2981+
:at:`resolution`: :at-val:`int(2), "1 1"`
2982+
Resolution of the camera in pixels [width height]. Note that these values are not used for rendering since those
2983+
dimensions are determined by the size of the rendering context. This attribute serves as a convenient
2984+
location to save the required resolution. Setting either value larger than 1
2985+
enables frustum visualization when the :ref:`mjVIS_CAMERA<mjtVisFlag>` visualization flag is active.
2986+
29792987
.. _body-camera-sensorsize:
29802988

29812989
:at:`sensorsize`: :at-val:`real(2), "0 0"`
29822990
Size of the camera sensor in length units. When specified, all intrinsic attributes become active and :at:`fovy`
2983-
is ignored. The field-of-view is then computed automatically from the focal length and sensor size. This
2984-
also enables frustum visualization when the :ref:`mjVIS_CAMERA<mjtVisFlag>` visualization flag is active.
2991+
is ignored. The field-of-view is then computed automatically from the focal length and sensor size.
29852992

29862993
.. _body-camera-focal:
29872994
.. _body-camera-focalpixel:
@@ -2998,13 +3005,6 @@ and the +Y axis points up. Thus the frame position and orientation are the key a
29983005
specified, the pixel value is used. At zero offset, the rendered image is centered on the camera's negative Z axis,
29993006
as in a standard pinhole camera model.
30003007

3001-
.. _body-camera-resolution:
3002-
3003-
:at:`resolution`: :at-val:`int(2), "1 1"`
3004-
Resolution of the camera in pixels [width height]. Note that these values are not used for rendering since those
3005-
dimensions are determined by the size of the rendering context. This attribute serves as a convenient
3006-
location to save the required resolution when creating a context.
3007-
30083008
.. _body-camera-ipd:
30093009

30103010
:at:`ipd`: :at-val:`real, "0.068"`

doc/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Upcoming version (not yet released)
1818

1919
General
2020
^^^^^^^
21+
- Camera frustum visualization is now triggered by setting :ref:`resolution<body-camera-resolution>` to values larger
22+
than 1. Relatedly, frustum visualization also works for :ref:`orthographic<body-camera-projection>` cameras.
2123
- Non-breaking ABI changes:
2224

2325
- The type of the ``sig`` (signature) argument of :ref:`mj_stateSize` and related functions has been changed from

src/engine/engine_vis_visualize.c

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -539,21 +539,13 @@ void mjv_cameraFrustum(float zver[2], float zhor[2], float zclip[2], const mjMod
539539
const float znear = m->vis.map.znear * m->stat.extent;
540540

541541
if (orthographic) {
542-
if (zver) {
543-
zver[0] = zver[1] = fovy / 2;
544-
}
545-
if (zhor) {
546-
zhor[0] = zhor[1] = 0.0f;
547-
}
542+
if (zver) zver[0] = zver[1] = fovy / 2;
543+
if (zhor) zhor[0] = zhor[1] = 0.0f;
548544
} else if (intrinsic) {
549545
getFrustum(zver, zhor, znear, intrinsic, sensorsize);
550546
} else {
551-
if (zver) {
552-
zver[0] = zver[1] = znear * mju_tan(fovy * mjPI/360.0);
553-
}
554-
if (zhor) {
555-
zhor[0] = zhor[1] = 0.0f;
556-
}
547+
if (zver) zver[0] = zver[1] = znear * mju_tan(fovy * mjPI/360.0);
548+
if (zhor) zhor[0] = zhor[1] = 0.0f;
557549
}
558550

559551
if (zclip) {
@@ -2232,8 +2224,8 @@ static void addCameraGeoms(const mjModel* m, mjData* d, const mjvOption* vopt, m
22322224
float cam_rgba[4];
22332225
f2f(cam_rgba, m->vis.rgba.camera, 4);
22342226

2235-
// draw frustum if sensorsize is defined
2236-
if (m->cam_sensorsize[2*i+1] > 0) {
2227+
// draw frustum if resolution larger than (1, 1)
2228+
if (m->cam_resolution[2*i] > 1 || m->cam_resolution[2*i+1] > 1) {
22372229
// when drawing frustum, make camera translucent
22382230
cam_rgba[3] = 0.3;
22392231

@@ -2244,9 +2236,22 @@ static void addCameraGeoms(const mjModel* m, mjData* d, const mjvOption* vopt, m
22442236
mjtNum znear = m->vis.map.znear * m->stat.extent;
22452237
mjtNum zfar = m->vis.scale.frustum * scl;
22462238
float zver[2], zhor[2];
2239+
int orthographic = m->cam_projection[i] == mjPROJ_ORTHOGRAPHIC;
22472240

22482241
// get frustum
2249-
getFrustum(zver, zhor, znear, m->cam_intrinsic + 4*i, m->cam_sensorsize + 2*i);
2242+
if (orthographic) {
2243+
float aspect = (float)m->cam_resolution[2*i] / m->cam_resolution[2*i+1];
2244+
zver[0] = zver[1] = m->cam_fovy[i] / 2;
2245+
zhor[0] = zhor[1] = m->cam_fovy[i] * aspect / 2;
2246+
} else if (m->cam_sensorsize[2*i] && m->cam_sensorsize[2*i+1]) {
2247+
// intrinsic-based perspective camera
2248+
getFrustum(zver, zhor, znear, m->cam_intrinsic+4*i, m->cam_sensorsize+2*i);
2249+
} else {
2250+
// fovy-based perspective camera
2251+
float aspect = (float)m->cam_resolution[2*i] / m->cam_resolution[2*i+1];
2252+
zver[0] = zver[1] = znear * mju_tan(m->cam_fovy[i] * mjPI / 360.0);
2253+
zhor[0] = zhor[1] = zver[0] * aspect;
2254+
}
22502255

22512256
// frustum frame to convert from planes to vertex representation
22522257
mjtNum* cam_xpos = d->cam_xpos+3*i;
@@ -2266,11 +2271,15 @@ static void addCameraGeoms(const mjModel* m, mjData* d, const mjvOption* vopt, m
22662271
mju_addToScl3(vnear[2], y, zver[1]);
22672272
mju_addToScl3(vnear[3], y, zver[1]);
22682273

2269-
// vertices of the far plane
2270-
zhor[0] *= zfar / znear;
2271-
zhor[1] *= zfar / znear;
2272-
zver[0] *= zfar / znear;
2273-
zver[1] *= zfar / znear;
2274+
// vertices of the far plane: scale for perspective, average(width, height) for orthographic
2275+
if (!orthographic) {
2276+
zhor[0] *= zfar / znear;
2277+
zhor[1] *= zfar / znear;
2278+
zver[0] *= zfar / znear;
2279+
zver[1] *= zfar / znear;
2280+
} else {
2281+
zfar = (zhor[0] + zver[0]) / 2;
2282+
}
22742283
mju_addScl3(center, cam_xpos, z, -zfar);
22752284
mju_addScl3(vfar[0], center, x, -zhor[0]);
22762285
mju_addScl3(vfar[1], center, x, zhor[1]);

test/engine/testdata/vis_visualize/orthographic.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<visual>
55
<!-- free camera is orthorgaphic with a 5m vertical field-of-view -->
66
<global orthographic="true" fovy="5"/>
7-
<scale camera="1"/>
7+
<scale camera="0.5"/>
88
</visual>
99

1010
<asset>
@@ -26,9 +26,9 @@
2626
</body>
2727

2828
<!-- box should pefectly fit within field-of-view -->
29-
<camera name="fovy = 1" pos=".5 .5 2" projection="orthographic" fovy="1"/>
29+
<camera name="fovy = 1" pos=".5 .5 2" projection="orthographic" fovy="1" resolution="640 480"/>
3030

3131
<!-- 2 floor squares should pefectly fit within field-of-view -->
32-
<camera name="fovy = 2" pos="0 0 2" fovy="2"/> <!-- inherits projection="orthographic" from defaults -->
32+
<camera name="fovy = 2" pos="0 0 3" fovy="2"/> <!-- inherits projection="orthographic" from defaults -->
3333
</worldbody>
3434
</mujoco>

0 commit comments

Comments
 (0)