Add orthographic cameras.

Orthographic cameras are specified by setting the `orthographic` attribute of the `<camera>` element. The `fovy` attribute is still used to specify the field-of-view, but its semantic is different for orthographic cameras. For orthographic cameras, the field-of-view is expressed in units of length, rather than degrees.

Other related changes:

* Fix bug in the ordering of `cam_xxx` elements in `mjModel`.
* Make camera visualization translucent only when the frustum is visualized.
* Added a button to `simulate` to toggle between perspective and orthographic free cameras.

https://youtu.be/ZXBTEIDWHhs

PiperOrigin-RevId: 642293435
Change-Id: Id090a421ad88ab404b5b27ddbbd6bfc81ad49bc5
This commit is contained in:
Yuval Tassa
2024-06-11 09:21:39 -07:00
committed by Copybara-Service
parent 171b0d6e06
commit 07fc95ca9a
23 changed files with 490 additions and 235 deletions
+24 -6
View File
@@ -2615,11 +2615,20 @@ and the +Y axis points up. Thus the frame position and orientation are the key a
When the camera mode is "targetbody" or "targetbodycom", this attribute becomes required. It specifies which body When the camera mode is "targetbody" or "targetbodycom", this attribute becomes required. It specifies which body
should be targeted by the camera. In all other modes this attribute is ignored. should be targeted by the camera. In all other modes this attribute is ignored.
.. _body-camera-orthographic:
:at:`orthographic`: :at-val:`[false, true], "false"`
Whether the camera uses a perspective projection (the default) or an orthographic projection. Setting this attribute
changes the semantic of the :ref:`fovy<body-camera-fovy>` attribute, see below.
.. _body-camera-fovy: .. _body-camera-fovy:
:at:`fovy`: :at-val:`real, "45"` :at:`fovy`: :at-val:`real, "45"`
Vertical field of view of the camera, expressed in degrees regardless of the global angle setting. The horizontal Vertical field-of-view of the camera. If the camera uses a perspective projection, the field-of-view is expressed in
field of view is computed automatically given the window size and the vertical field of view. degrees, regardless of the global :ref:`compiler/angle <compiler-angle>` setting. If the camera uses an orthographic
projection, the field-of-view is expressed in units of length; note that in this case the default of 45 is too large
for most scenes and should likely be reduced. In either case, the horizontal field of view is computed automatically
given the window size and the vertical field of view.
.. _body-camera-resolution: .. _body-camera-resolution:
@@ -7157,14 +7166,22 @@ coordinated visual settings corresponding to a "theme", and then include this fi
While all settings in mjVisual are global, the settings here could not be fit into any of the other subsections. So this While all settings in mjVisual are global, the settings here could not be fit into any of the other subsections. So this
is effectively a miscellaneous subsection. is effectively a miscellaneous subsection.
.. _visual-global-orthographic:
:at:`orthographic`: :at-val:`[false, true], "false"`
Whether the free camera uses a perspective projection (the default) or an orthographic projection. Setting this
attribute changes the semantic of the :ref:`global/fovy<visual-global-fovy>` attribute, see below.
.. _visual-global-fovy: .. _visual-global-fovy:
:at:`fovy`: :at-val:`real, "45"` :at:`fovy`: :at-val:`real, "45"`
This attribute specifies the vertical field of view of the free camera, i.e., the camera that is always available in This attribute specifies the vertical field of view of the free camera, i.e., the camera that is always available in
the visualizer even if no cameras are explicitly defined in the model. It is always expressed in degrees, regardless the visualizer even if no cameras are explicitly defined in the model. If the camera uses a perspective projection,
of the setting of the angle attribute of :ref:`compiler <compiler>`, and is also represented in the low level model the field-of-view is expressed in degrees, regardless of the global :ref:`compiler/angle <compiler-angle>` setting.
in degrees. This is because we pass it to OpenGL which uses degrees. The same convention applies to the fovy If the camera uses an orthographic projection, the field-of-view is expressed in units of length; note that in this
attribute of the :ref:`camera <body-camera>` element below. case the default of 45 is too large for most scenes and should likely be reduced. In either case, the horizontal
field of view is computed automatically given the window size and the vertical field of view. The same convention
applies to the :ref:`camera/fovy <body-camera-fovy>` attribute.
.. _visual-global-ipd: .. _visual-global-ipd:
@@ -7869,6 +7886,7 @@ if omitted.
| This element sets the attributes of the dummy :ref:`site <body-site>` element of the defaults class. | This element sets the attributes of the dummy :ref:`site <body-site>` element of the defaults class.
| All site attributes are available here except: name, class. | All site attributes are available here except: name, class.
.. _default-camera-orthographic:
.. _default-camera-fovy: .. _default-camera-fovy:
+14 -12
View File
@@ -277,15 +277,15 @@
| :ref:`camera | \* | :class: mjcf-attributes | | :ref:`camera | \* | :class: mjcf-attributes |
| <body-camera>` | | | | <body-camera>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<body-camera-name>` | :ref:`class<body-camera-class>` | :ref:`fovy<body-camera-fovy>` | :ref:`ipd<body-camera-ipd>` | | | | | | :ref:`name<body-camera-name>` | :ref:`class<body-camera-class>` | :ref:`orthographic<body-camera-orthographic>` | :ref:`fovy<body-camera-fovy>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`resolution<body-camera-resolution>` | :ref:`pos<body-camera-pos>` | :ref:`quat<body-camera-quat>` | :ref:`axisangle<body-camera-axisangle>` | | | | | | :ref:`ipd<body-camera-ipd>` | :ref:`resolution<body-camera-resolution>` | :ref:`pos<body-camera-pos>` | :ref:`quat<body-camera-quat>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`xyaxes<body-camera-xyaxes>` | :ref:`zaxis<body-camera-zaxis>` | :ref:`euler<body-camera-euler>` | :ref:`mode<body-camera-mode>` | | | | | | :ref:`axisangle<body-camera-axisangle>` | :ref:`xyaxes<body-camera-xyaxes>` | :ref:`zaxis<body-camera-zaxis>` | :ref:`euler<body-camera-euler>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`target<body-camera-target>` | :ref:`focal<body-camera-focal>` | :ref:`focalpixel<body-camera-focalpixel>` | :ref:`principal<body-camera-principal>` | | | | | | :ref:`mode<body-camera-mode>` | :ref:`target<body-camera-target>` | :ref:`focal<body-camera-focal>` | :ref:`focalpixel<body-camera-focalpixel>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`principalpixel<body-camera-principalpixel>` | :ref:`sensorsize<body-camera-sensorsize>` | :ref:`user<body-camera-user>` | | | | | | | :ref:`principal<body-camera-principal>` | :ref:`principalpixel<body-camera-principalpixel>` | :ref:`sensorsize<body-camera-sensorsize>` | :ref:`user<body-camera-user>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| body |br| |_| |L| | | .. table:: | | |_| body |br| |_| |L| | | .. table:: |
@@ -1246,11 +1246,11 @@
| :ref:`global | ? | :class: mjcf-attributes | | :ref:`global | ? | :class: mjcf-attributes |
| <visual-global>` | | | | <visual-global>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`fovy<visual-global-fovy>` | :ref:`ipd<visual-global-ipd>` | :ref:`azimuth<visual-global-azimuth>` | :ref:`elevation<visual-global-elevation>` | | | | | | :ref:`orthographic<visual-global-orthographic>` | :ref:`fovy<visual-global-fovy>` | :ref:`ipd<visual-global-ipd>` | :ref:`azimuth<visual-global-azimuth>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`linewidth<visual-global-linewidth>` | :ref:`glow<visual-global-glow>` | :ref:`offwidth<visual-global-offwidth>` | :ref:`offheight<visual-global-offheight>` | | | | | | :ref:`elevation<visual-global-elevation>` | :ref:`linewidth<visual-global-linewidth>` | :ref:`glow<visual-global-glow>` | :ref:`offwidth<visual-global-offwidth>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`realtime<visual-global-realtime>` | :ref:`ellipsoidinertia<visual-global-ellipsoidinertia>` | :ref:`bvactive<visual-global-bvactive>` | | | | | | | :ref:`offheight<visual-global-offheight>` | :ref:`realtime<visual-global-realtime>` | :ref:`ellipsoidinertia<visual-global-ellipsoidinertia>` | :ref:`bvactive<visual-global-bvactive>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: | | |_| visual |br| |_| |L| | | .. table:: |
@@ -1396,13 +1396,15 @@
| :ref:`camera | ? | :class: mjcf-attributes | | :ref:`camera | ? | :class: mjcf-attributes |
| <default-camera>` | | | | <default-camera>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`fovy<default-camera-fovy>` | :ref:`ipd<default-camera-ipd>` | :ref:`resolution<default-camera-resolution>` | :ref:`pos<default-camera-pos>` | | | | | | :ref:`orthographic<default-camera-orthographic>` | :ref:`fovy<default-camera-fovy>` | :ref:`ipd<default-camera-ipd>` | :ref:`resolution<default-camera-resolution>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`quat<default-camera-quat>` | :ref:`axisangle<default-camera-axisangle>` | :ref:`xyaxes<default-camera-xyaxes>` | :ref:`zaxis<default-camera-zaxis>` | | | | | | :ref:`pos<default-camera-pos>` | :ref:`quat<default-camera-quat>` | :ref:`axisangle<default-camera-axisangle>` | :ref:`xyaxes<default-camera-xyaxes>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`euler<default-camera-euler>` | :ref:`mode<default-camera-mode>` | :ref:`focal<default-camera-focal>` | :ref:`focalpixel<default-camera-focalpixel>` | | | | | | :ref:`zaxis<default-camera-zaxis>` | :ref:`euler<default-camera-euler>` | :ref:`mode<default-camera-mode>` | :ref:`focal<default-camera-focal>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`principal<default-camera-principal>` | :ref:`principalpixel<default-camera-principalpixel>` | :ref:`sensorsize<default-camera-sensorsize>` | :ref:`user<default-camera-user>` | | | | | | :ref:`focalpixel<default-camera-focalpixel>` | :ref:`principal<default-camera-principal>` | :ref:`principalpixel<default-camera-principalpixel>` | :ref:`sensorsize<default-camera-sensorsize>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`user<default-camera-user>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| default |br| |_| |L| | | .. table:: | | |_| default |br| |_| |L| | | .. table:: |
+11 -5
View File
@@ -13,15 +13,21 @@ General
- Detailed documentation. - Detailed documentation.
- Python bindings. - Python bindings.
2. Added :ref:`maxhullvert<asset-mesh-maxhullvert>`, the maximum number of vertices in a mesh's convex hull. .. youtube:: ZXBTEIDWHhs
:align: right
:width: 240px
2. Added support for orthographic cameras. This is available for both fixed cameras and the free camera, using the
:ref:`camera/orthographic<body-camera-orthographic>` and :ref:`global/orthographic<visual-global-orthographic>`
attributes, respectively.
3. Added :ref:`maxhullvert<asset-mesh-maxhullvert>`, the maximum number of vertices in a mesh's convex hull.
MJX MJX
~~~ ~~~
4. Added support for :ref:`elliptic friction cones<option-cone>`.
3. Added support for :ref:`elliptic friction cones<option-cone>`. 5. Fixed a bug that resulted in less-optimal linesearch solutions for some difficult constraint settings.
4. Fixed a bug that resulted in less-optimal linesearch solutions for some difficult constraint settings. 6. Fixed a bug in the Newton solver that sometimes resulted in less-optimal gradients.
5. Fixed a bug in the Newton solver that sometimes resulted in less-optimal gradients.
Version 3.1.6 (Jun 3, 2024) Version 3.1.6 (Jun 3, 2024)
--------------------------- ---------------------------
+17 -6
View File
@@ -751,7 +751,8 @@ struct mjOption_ { // physics options
typedef struct mjOption_ mjOption; typedef struct mjOption_ mjOption;
struct mjVisual_ { // visualization options struct mjVisual_ { // visualization options
struct { // global parameters struct { // global parameters
float fovy; // y-field of view for free camera (degrees) int orthographic; // is the free camera orthographic (0: no, 1: yes)
float fovy; // y field-of-view of free camera (orthographic ? length : degree)
float ipd; // inter-pupilary distance for free camera float ipd; // inter-pupilary distance for free camera
float azimuth; // initial azimuth of free camera (degrees) float azimuth; // initial azimuth of free camera (degrees)
float elevation; // initial elevation of free camera (degrees) float elevation; // initial elevation of free camera (degrees)
@@ -1072,11 +1073,12 @@ struct mjModel_ {
mjtNum* cam_poscom0; // global position rel. to sub-com in qpos0 (ncam x 3) mjtNum* cam_poscom0; // global position rel. to sub-com in qpos0 (ncam x 3)
mjtNum* cam_pos0; // global position rel. to body in qpos0 (ncam x 3) mjtNum* cam_pos0; // global position rel. to body in qpos0 (ncam x 3)
mjtNum* cam_mat0; // global orientation in qpos0 (ncam x 9) mjtNum* cam_mat0; // global orientation in qpos0 (ncam x 9)
int* cam_resolution; // [width, height] in pixels (ncam x 2) int* cam_orthographic; // orthographic camera; 0: no, 1: yes (ncam x 1)
mjtNum* cam_fovy; // y-field of view (deg) (ncam x 1) mjtNum* cam_fovy; // y field-of-view (ortho ? len : deg) (ncam x 1)
float* cam_intrinsic; // [focal length; principal point] (ncam x 4)
float* cam_sensorsize; // sensor size (ncam x 2)
mjtNum* cam_ipd; // inter-pupilary distance (ncam x 1) mjtNum* cam_ipd; // inter-pupilary distance (ncam x 1)
int* cam_resolution; // resolution: pixels [width, height] (ncam x 2)
float* cam_sensorsize; // sensor size: length [width, height] (ncam x 2)
float* cam_intrinsic; // [focal length; principal point] (ncam x 4)
mjtNum* cam_user; // user data (ncam x nuser_cam) mjtNum* cam_user; // user data (ncam x nuser_cam)
// lights // lights
@@ -1858,6 +1860,7 @@ typedef struct mjsCamera_ { // camera specification
mjString* targetbody; // target body for tracking/targeting mjString* targetbody; // target body for tracking/targeting
// intrinsics // intrinsics
int orthographic; // is camera orthographic
double fovy; // y-field of view double fovy; // y-field of view
double ipd; // inter-pupilary distance double ipd; // inter-pupilary distance
float intrinsic[4]; // camera intrinsics (length) float intrinsic[4]; // camera intrinsics (length)
@@ -2602,6 +2605,9 @@ struct mjvCamera_ { // abstract camera
mjtNum distance; // distance to lookat point or tracked body mjtNum distance; // distance to lookat point or tracked body
mjtNum azimuth; // camera azimuth (deg) mjtNum azimuth; // camera azimuth (deg)
mjtNum elevation; // camera elevation (deg) mjtNum elevation; // camera elevation (deg)
// orthographic / perspective
int orthographic; // 0: perspective; 1: orthographic
}; };
typedef struct mjvCamera_ mjvCamera; typedef struct mjvCamera_ mjvCamera;
struct mjvGLCamera_ { // OpenGL camera struct mjvGLCamera_ { // OpenGL camera
@@ -2617,6 +2623,9 @@ struct mjvGLCamera_ { // OpenGL camera
float frustum_top; // top float frustum_top; // top
float frustum_near; // near float frustum_near; // near
float frustum_far; // far float frustum_far; // far
// orthographic / perspective
int orthographic; // 0: perspective; 1: orthographic
}; };
typedef struct mjvGLCamera_ mjvGLCamera; typedef struct mjvGLCamera_ mjvGLCamera;
struct mjvGeom_ { // abstract geom struct mjvGeom_ { // abstract geom
@@ -2869,10 +2878,12 @@ struct mjvSceneState_ {
mjtNum* site_size; mjtNum* site_size;
float* site_rgba; float* site_rgba;
int* cam_orthographic;
mjtNum* cam_fovy; mjtNum* cam_fovy;
mjtNum* cam_ipd; mjtNum* cam_ipd;
float* cam_intrinsic; int* cam_resolution;
float* cam_sensorsize; float* cam_sensorsize;
float* cam_intrinsic;
mjtByte* light_directional; mjtByte* light_directional;
mjtByte* light_castshadow; mjtByte* light_castshadow;
+7 -5
View File
@@ -458,7 +458,8 @@ typedef struct mjOption_ mjOption;
struct mjVisual_ { // visualization options struct mjVisual_ { // visualization options
struct { // global parameters struct { // global parameters
float fovy; // y-field of view for free camera (degrees) int orthographic; // is the free camera orthographic (0: no, 1: yes)
float fovy; // y field-of-view of free camera (orthographic ? length : degree)
float ipd; // inter-pupilary distance for free camera float ipd; // inter-pupilary distance for free camera
float azimuth; // initial azimuth of free camera (degrees) float azimuth; // initial azimuth of free camera (degrees)
float elevation; // initial elevation of free camera (degrees) float elevation; // initial elevation of free camera (degrees)
@@ -787,11 +788,12 @@ struct mjModel_ {
mjtNum* cam_poscom0; // global position rel. to sub-com in qpos0 (ncam x 3) mjtNum* cam_poscom0; // global position rel. to sub-com in qpos0 (ncam x 3)
mjtNum* cam_pos0; // global position rel. to body in qpos0 (ncam x 3) mjtNum* cam_pos0; // global position rel. to body in qpos0 (ncam x 3)
mjtNum* cam_mat0; // global orientation in qpos0 (ncam x 9) mjtNum* cam_mat0; // global orientation in qpos0 (ncam x 9)
int* cam_resolution; // [width, height] in pixels (ncam x 2) int* cam_orthographic; // orthographic camera; 0: no, 1: yes (ncam x 1)
mjtNum* cam_fovy; // y-field of view (deg) (ncam x 1) mjtNum* cam_fovy; // y field-of-view (ortho ? len : deg) (ncam x 1)
float* cam_intrinsic; // [focal length; principal point] (ncam x 4)
float* cam_sensorsize; // sensor size (ncam x 2)
mjtNum* cam_ipd; // inter-pupilary distance (ncam x 1) mjtNum* cam_ipd; // inter-pupilary distance (ncam x 1)
int* cam_resolution; // resolution: pixels [width, height] (ncam x 2)
float* cam_sensorsize; // sensor size: length [width, height] (ncam x 2)
float* cam_intrinsic; // [focal length; principal point] (ncam x 4)
mjtNum* cam_user; // user data (ncam x nuser_cam) mjtNum* cam_user; // user data (ncam x nuser_cam)
// lights // lights
+1
View File
@@ -345,6 +345,7 @@ typedef struct mjsCamera_ { // camera specification
mjString* targetbody; // target body for tracking/targeting mjString* targetbody; // target body for tracking/targeting
// intrinsics // intrinsics
int orthographic; // is camera orthographic
double fovy; // y-field of view double fovy; // y-field of view
double ipd; // inter-pupilary distance double ipd; // inter-pupilary distance
float intrinsic[4]; // camera intrinsics (length) float intrinsic[4]; // camera intrinsics (length)
+9 -1
View File
@@ -192,6 +192,9 @@ struct mjvCamera_ { // abstract camera
mjtNum distance; // distance to lookat point or tracked body mjtNum distance; // distance to lookat point or tracked body
mjtNum azimuth; // camera azimuth (deg) mjtNum azimuth; // camera azimuth (deg)
mjtNum elevation; // camera elevation (deg) mjtNum elevation; // camera elevation (deg)
// orthographic / perspective
int orthographic; // 0: perspective; 1: orthographic
}; };
typedef struct mjvCamera_ mjvCamera; typedef struct mjvCamera_ mjvCamera;
@@ -211,6 +214,9 @@ struct mjvGLCamera_ { // OpenGL camera
float frustum_top; // top float frustum_top; // top
float frustum_near; // near float frustum_near; // near
float frustum_far; // far float frustum_far; // far
// orthographic / perspective
int orthographic; // 0: perspective; 1: orthographic
}; };
typedef struct mjvGLCamera_ mjvGLCamera; typedef struct mjvGLCamera_ mjvGLCamera;
@@ -487,10 +493,12 @@ struct mjvSceneState_ {
mjtNum* site_size; mjtNum* site_size;
float* site_rgba; float* site_rgba;
int* cam_orthographic;
mjtNum* cam_fovy; mjtNum* cam_fovy;
mjtNum* cam_ipd; mjtNum* cam_ipd;
float* cam_intrinsic; int* cam_resolution;
float* cam_sensorsize; float* cam_sensorsize;
float* cam_intrinsic;
mjtByte* light_directional; mjtByte* light_directional;
mjtByte* light_castshadow; mjtByte* light_castshadow;
+4 -3
View File
@@ -277,16 +277,17 @@
X ( int, cam_mode, ncam, 1 ) \ X ( int, cam_mode, ncam, 1 ) \
X ( int, cam_bodyid, ncam, 1 ) \ X ( int, cam_bodyid, ncam, 1 ) \
X ( int, cam_targetbodyid, ncam, 1 ) \ X ( int, cam_targetbodyid, ncam, 1 ) \
X ( int, cam_resolution, ncam, 2 ) \
XMJV( float, cam_sensorsize, ncam, 2 ) \
XMJV( float, cam_intrinsic, ncam, 4 ) \
X ( mjtNum, cam_pos, ncam, 3 ) \ X ( mjtNum, cam_pos, ncam, 3 ) \
X ( mjtNum, cam_quat, ncam, 4 ) \ X ( mjtNum, cam_quat, ncam, 4 ) \
X ( mjtNum, cam_poscom0, ncam, 3 ) \ X ( mjtNum, cam_poscom0, ncam, 3 ) \
X ( mjtNum, cam_pos0, ncam, 3 ) \ X ( mjtNum, cam_pos0, ncam, 3 ) \
X ( mjtNum, cam_mat0, ncam, 9 ) \ X ( mjtNum, cam_mat0, ncam, 9 ) \
XMJV( int, cam_orthographic, ncam, 1 ) \
XMJV( mjtNum, cam_fovy, ncam, 1 ) \ XMJV( mjtNum, cam_fovy, ncam, 1 ) \
XMJV( mjtNum, cam_ipd, ncam, 1 ) \ XMJV( mjtNum, cam_ipd, ncam, 1 ) \
XMJV( int, cam_resolution, ncam, 2 ) \
XMJV( float, cam_sensorsize, ncam, 2 ) \
XMJV( float, cam_intrinsic, ncam, 4 ) \
X ( mjtNum, cam_user, ncam, MJ_M(nuser_cam) ) \ X ( mjtNum, cam_user, ncam, MJ_M(nuser_cam) ) \
X ( int, light_mode, nlight, 1 ) \ X ( int, light_mode, nlight, 1 ) \
X ( int, light_bodyid, nlight, 1 ) \ X ( int, light_bodyid, nlight, 1 ) \
+61 -20
View File
@@ -310,10 +310,15 @@ STRUCTS: Mapping[str, StructDecl] = dict([
name='global', name='global',
type=AnonymousStructDecl( type=AnonymousStructDecl(
fields=( fields=(
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='is the free camera orthographic (0: no, 1: yes)', # pylint: disable=line-too-long
),
StructFieldDecl( StructFieldDecl(
name='fovy', name='fovy',
type=ValueType(name='float'), type=ValueType(name='float'),
doc='y-field of view for free camera (degrees)', doc='y field-of-view of free camera (orthographic ? length : degree)', # pylint: disable=line-too-long
), ),
StructFieldDecl( StructFieldDecl(
name='ipd', name='ipd',
@@ -2024,32 +2029,18 @@ STRUCTS: Mapping[str, StructDecl] = dict([
doc='global orientation in qpos0 (ncam x 9)', doc='global orientation in qpos0 (ncam x 9)',
), ),
StructFieldDecl( StructFieldDecl(
name='cam_resolution', name='cam_orthographic',
type=PointerType( type=PointerType(
inner_type=ValueType(name='int'), inner_type=ValueType(name='int'),
), ),
doc='[width, height] in pixels (ncam x 2)', doc='orthographic camera; 0: no, 1: yes (ncam x 1)',
), ),
StructFieldDecl( StructFieldDecl(
name='cam_fovy', name='cam_fovy',
type=PointerType( type=PointerType(
inner_type=ValueType(name='mjtNum'), inner_type=ValueType(name='mjtNum'),
), ),
doc='y-field of view (deg) (ncam x 1)', doc='y field-of-view (ortho ? len : deg) (ncam x 1)',
),
StructFieldDecl(
name='cam_intrinsic',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='[focal length; principal point] (ncam x 4)',
),
StructFieldDecl(
name='cam_sensorsize',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='sensor size (ncam x 2)',
), ),
StructFieldDecl( StructFieldDecl(
name='cam_ipd', name='cam_ipd',
@@ -2058,6 +2049,27 @@ STRUCTS: Mapping[str, StructDecl] = dict([
), ),
doc='inter-pupilary distance (ncam x 1)', doc='inter-pupilary distance (ncam x 1)',
), ),
StructFieldDecl(
name='cam_resolution',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='resolution: pixels [width, height] (ncam x 2)',
),
StructFieldDecl(
name='cam_sensorsize',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='sensor size: length [width, height] (ncam x 2)',
),
StructFieldDecl(
name='cam_intrinsic',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='[focal length; principal point] (ncam x 4)',
),
StructFieldDecl( StructFieldDecl(
name='cam_user', name='cam_user',
type=PointerType( type=PointerType(
@@ -5367,6 +5379,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='mjtNum'), type=ValueType(name='mjtNum'),
doc='camera elevation (deg)', doc='camera elevation (deg)',
), ),
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='0: perspective; 1: orthographic',
),
), ),
)), )),
('mjvGLCamera', ('mjvGLCamera',
@@ -5428,6 +5445,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='float'), type=ValueType(name='float'),
doc='far', doc='far',
), ),
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='0: perspective; 1: orthographic',
),
), ),
)), )),
('mjvGeom', ('mjvGeom',
@@ -6701,6 +6723,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
), ),
doc='', doc='',
), ),
StructFieldDecl(
name='cam_orthographic',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='',
),
StructFieldDecl( StructFieldDecl(
name='cam_fovy', name='cam_fovy',
type=PointerType( type=PointerType(
@@ -6716,14 +6745,21 @@ STRUCTS: Mapping[str, StructDecl] = dict([
doc='', doc='',
), ),
StructFieldDecl( StructFieldDecl(
name='cam_intrinsic', name='cam_resolution',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='',
),
StructFieldDecl(
name='cam_sensorsize',
type=PointerType( type=PointerType(
inner_type=ValueType(name='float'), inner_type=ValueType(name='float'),
), ),
doc='', doc='',
), ),
StructFieldDecl( StructFieldDecl(
name='cam_sensorsize', name='cam_intrinsic',
type=PointerType( type=PointerType(
inner_type=ValueType(name='float'), inner_type=ValueType(name='float'),
), ),
@@ -9216,6 +9252,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
), ),
doc='target body for tracking/targeting', doc='target body for tracking/targeting',
), ),
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='is camera orthographic',
),
StructFieldDecl( StructFieldDecl(
name='fovy', name='fovy',
type=ValueType(name='double'), type=ValueType(name='double'),
+3
View File
@@ -1390,6 +1390,7 @@ PYBIND11_MODULE(_structs, m) {
}); });
DefineStructFunctions(mjVisualGlobal); DefineStructFunctions(mjVisualGlobal);
#define X(var) mjVisualGlobal.def_readwrite(#var, &raw::MjVisualGlobal::var) #define X(var) mjVisualGlobal.def_readwrite(#var, &raw::MjVisualGlobal::var)
X(orthographic);
X(fovy); X(fovy);
X(ipd); X(ipd);
X(azimuth); X(azimuth);
@@ -2123,6 +2124,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(distance); X(distance);
X(azimuth); X(azimuth);
X(elevation); X(elevation);
X(orthographic);
#undef X #undef X
#define X(var) DefinePyArray(mjvCamera, #var, &MjvCameraWrapper::var) #define X(var) DefinePyArray(mjvCamera, #var, &MjvCameraWrapper::var)
@@ -2153,6 +2155,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(frustum_top); X(frustum_top);
X(frustum_near); X(frustum_near);
X(frustum_far); X(frustum_far);
X(orthographic);
#undef X #undef X
#define X(var) DefinePyArray(mjvGLCamera, #var, &MjvGLCameraWrapper::var) #define X(var) DefinePyArray(mjvGLCamera, #var, &MjvGLCameraWrapper::var)
+3 -2
View File
@@ -887,14 +887,15 @@ void MakeVisualizationSection(mj::Simulate* sim, const mjModel* m, int oldstate)
{mjITEM_EDITFLOAT, "Ambient", 2, &(vis->headlight.ambient), "3"}, {mjITEM_EDITFLOAT, "Ambient", 2, &(vis->headlight.ambient), "3"},
{mjITEM_EDITFLOAT, "Diffuse", 2, &(vis->headlight.diffuse), "3"}, {mjITEM_EDITFLOAT, "Diffuse", 2, &(vis->headlight.diffuse), "3"},
{mjITEM_EDITFLOAT, "Specular", 2, &(vis->headlight.specular), "3"}, {mjITEM_EDITFLOAT, "Specular", 2, &(vis->headlight.specular), "3"},
{mjITEM_SEPARATOR, "Initial Free Camera", 1}, {mjITEM_SEPARATOR, "Free Camera", 1},
{mjITEM_RADIO, "Orthographic", 2, &(vis->global.orthographic), "No\nYes"},
{mjITEM_EDITFLOAT, "Field of view", 2, &(vis->global.fovy), "1"},
{mjITEM_EDITNUM, "Center", 2, &(stat->center), "3"}, {mjITEM_EDITNUM, "Center", 2, &(stat->center), "3"},
{mjITEM_EDITFLOAT, "Azimuth", 2, &(vis->global.azimuth), "1"}, {mjITEM_EDITFLOAT, "Azimuth", 2, &(vis->global.azimuth), "1"},
{mjITEM_EDITFLOAT, "Elevation", 2, &(vis->global.elevation), "1"}, {mjITEM_EDITFLOAT, "Elevation", 2, &(vis->global.elevation), "1"},
{mjITEM_BUTTON, "Align", 2, nullptr, "CA"}, {mjITEM_BUTTON, "Align", 2, nullptr, "CA"},
{mjITEM_SEPARATOR, "Global", 1}, {mjITEM_SEPARATOR, "Global", 1},
{mjITEM_EDITNUM, "Extent", 2, &(stat->extent), "1"}, {mjITEM_EDITNUM, "Extent", 2, &(stat->extent), "1"},
{mjITEM_EDITFLOAT, "Field of view", 2, &(vis->global.fovy), "1"},
{mjITEM_RADIO, "Inertia", 5, &(vis->global.ellipsoidinertia), "Box\nEllipsoid"}, {mjITEM_RADIO, "Inertia", 5, &(vis->global.ellipsoidinertia), "Box\nEllipsoid"},
{mjITEM_RADIO, "BVH active", 5, &(vis->global.bvactive), "False\nTrue"}, {mjITEM_RADIO, "BVH active", 5, &(vis->global.bvactive), "False\nTrue"},
{mjITEM_SEPARATOR, "Map", 1}, {mjITEM_SEPARATOR, "Map", 1},
+2 -1
View File
@@ -182,6 +182,7 @@ static void setf4(float* rgba, float r, float g, float b, float a) {
// set visual options to default values // set visual options to default values
void mj_defaultVisual(mjVisual* vis) { void mj_defaultVisual(mjVisual* vis) {
// global // global
vis->global.orthographic = 0;
vis->global.fovy = 45; vis->global.fovy = 45;
vis->global.ipd = 0.068; vis->global.ipd = 0.068;
vis->global.azimuth = 90; vis->global.azimuth = 90;
@@ -257,7 +258,7 @@ void mj_defaultVisual(mjVisual* vis) {
setf4(vis->rgba.actuatornegative, .2, .6, .9, 1.); setf4(vis->rgba.actuatornegative, .2, .6, .9, 1.);
setf4(vis->rgba.actuatorpositive, .9, .4, .2, 1.); setf4(vis->rgba.actuatorpositive, .9, .4, .2, 1.);
setf4(vis->rgba.com, .9, .9, .9, 1.); setf4(vis->rgba.com, .9, .9, .9, 1.);
setf4(vis->rgba.camera, .6, .9, .6, .3); setf4(vis->rgba.camera, .6, .9, .6, 1);
setf4(vis->rgba.light, .6, .6, .9, 1.); setf4(vis->rgba.light, .6, .6, .9, 1.);
setf4(vis->rgba.selectpoint, .9, .9, .1, 1.); setf4(vis->rgba.selectpoint, .9, .9, .1, 1.);
setf4(vis->rgba.connect, .2, .2, .8, 1.); setf4(vis->rgba.connect, .2, .2, .8, 1.);
+10 -9
View File
@@ -359,15 +359,16 @@ void mjv_defaultCamera(mjvCamera* cam) {
void mjv_defaultFreeCamera(const mjModel* m, mjvCamera* cam) { void mjv_defaultFreeCamera(const mjModel* m, mjvCamera* cam) {
memset(cam, 0, sizeof(mjvCamera)); memset(cam, 0, sizeof(mjvCamera));
cam->type = mjCAMERA_FREE; cam->type = mjCAMERA_FREE;
cam->fixedcamid = -1; cam->fixedcamid = -1;
cam->trackbodyid = -1; cam->trackbodyid = -1;
cam->lookat[0] = m->stat.center[0]; cam->lookat[0] = m->stat.center[0];
cam->lookat[1] = m->stat.center[1]; cam->lookat[1] = m->stat.center[1];
cam->lookat[2] = m->stat.center[2]; cam->lookat[2] = m->stat.center[2];
cam->distance = 1.5 * m->stat.extent; cam->distance = 1.5 * m->stat.extent;
cam->azimuth = m->vis.global.azimuth; cam->azimuth = m->vis.global.azimuth;
cam->elevation = m->vis.global.elevation; cam->elevation = m->vis.global.elevation;
cam->orthographic = m->vis.global.orthographic;
} }
+53 -15
View File
@@ -223,16 +223,29 @@ void mjv_cameraInRoom(mjtNum* headpos, mjtNum* forward, mjtNum* up, const mjvSce
// get frustum height at unit distance from camera; average left and right OpenGL cameras // get frustum height at unit distance from camera; average left and right OpenGL cameras
mjtNum mjv_frustumHeight(const mjvScene* scn) { mjtNum mjv_frustumHeight(const mjvScene* scn) {
mjtNum height; const mjvGLCamera* cam1 = scn->camera;
const mjvGLCamera* cam2 = scn->camera + 1;
// check znear if (cam1->orthographic != cam2->orthographic) {
if (scn->camera[0].frustum_near < mjMINVAL || scn->camera[1].frustum_near < mjMINVAL) { mjERROR("cannot average frustums of perspective and orthographic cameras");
mjERROR("mjvScene frustum_near too small");
} }
// add normalized height for left and right cameras // get height
height = (scn->camera[0].frustum_top-scn->camera[0].frustum_bottom)/scn->camera[0].frustum_near + mjtNum height;
(scn->camera[1].frustum_top-scn->camera[1].frustum_bottom)/scn->camera[1].frustum_near; if (!cam1->orthographic) {
// check znear
if (cam1->frustum_near < mjMINVAL || cam2->frustum_near < mjMINVAL) {
mjERROR("mjvScene frustum_near too small");
}
// add normalized height for left and right cameras
height = (cam1->frustum_top - cam1->frustum_bottom) / cam1->frustum_near +
(cam2->frustum_top - cam2->frustum_bottom) / cam2->frustum_near;
} else {
// add height for left and right cameras
height = (cam1->frustum_top - cam1->frustum_bottom) +
(cam2->frustum_top - cam2->frustum_bottom);
}
// average // average
return 0.5*height; return 0.5*height;
@@ -337,6 +350,9 @@ void mjv_moveCamera(const mjModel* m, int action, mjtNum reldx, mjtNum reldy,
mju_sub3(dif, cam->lookat, headpos); mju_sub3(dif, cam->lookat, headpos);
scl = mjv_frustumHeight(scn) * mju_dot3(dif, forward); scl = mjv_frustumHeight(scn) * mju_dot3(dif, forward);
// multiply by mystery coefficient TODO: b/346130949
if (cam->orthographic) scl *= 0.15;
// move lookat point in opposite direction // move lookat point in opposite direction
mju_addToScl3(cam->lookat, vec, -scl); mju_addToScl3(cam->lookat, vec, -scl);
break; break;
@@ -563,6 +579,9 @@ void mjv_initPerturb(const mjModel* m, mjData* d, const mjvScene* scn, mjvPertur
mju_sub3(dif, pert->refselpos, headpos); mju_sub3(dif, pert->refselpos, headpos);
pert->scale = mjv_frustumHeight(scn) * mju_dot3(dif, forward); pert->scale = mjv_frustumHeight(scn) * mju_dot3(dif, forward);
// multiply by mystery coefficient TODO: b/346130949
if (scn->camera[0].orthographic) pert->scale *= 0.15;
mj_freeStack(d); mj_freeStack(d);
} }
@@ -733,6 +752,12 @@ mjvGLCamera mjv_averageCamera(const mjvGLCamera* cam1, const mjvGLCamera* cam2)
cam.frustum_near = 0.5f * (cam1->frustum_near + cam2->frustum_near); cam.frustum_near = 0.5f * (cam1->frustum_near + cam2->frustum_near);
cam.frustum_far = 0.5f * (cam1->frustum_far + cam2->frustum_far); cam.frustum_far = 0.5f * (cam1->frustum_far + cam2->frustum_far);
if (cam1->orthographic != cam2->orthographic) {
mjERROR("cannot average perspective and orthographic cameras");
} else {
cam.orthographic = cam1->orthographic;
}
return cam; return cam;
} }
@@ -755,17 +780,31 @@ int mjv_select(const mjModel* m, const mjData* d, const mjvOption* vopt,
// compute frustum halfwidth so as to match viewport aspect ratio // compute frustum halfwidth so as to match viewport aspect ratio
mjtNum halfwidth = 0.5*aspectratio*(cam.frustum_top - cam.frustum_bottom); mjtNum halfwidth = 0.5*aspectratio*(cam.frustum_top - cam.frustum_bottom);
// construct ray // compute up and left offsets from normalized cursor
mjtNum d_up = cam.frustum_bottom + rely*(cam.frustum_top-cam.frustum_bottom);
mjtNum d_left = -(cam.frustum_center + (2*relx-1)*halfwidth);
// define ray
mjtNum ray[3]; mjtNum ray[3];
mju_scl3(ray, forward, cam.frustum_near);
mju_addToScl3(ray, up, cam.frustum_bottom + rely*(cam.frustum_top-cam.frustum_bottom)); // construct ray for orthographic camera: fixed direction, modify pos
mju_addToScl3(ray, left, -(cam.frustum_center + (2*relx-1)*halfwidth)); if (cam.orthographic) {
mju_normalize3(ray); mju_copy3(ray, forward);
mju_addToScl3(pos, up, d_up);
mju_addToScl3(pos, left, d_left);
}
// construct ray for perspective camera: fixed pos, modify direction
else {
mju_scl3(ray, forward, cam.frustum_near);
mju_addToScl3(ray, up, d_up);
mju_addToScl3(ray, left, d_left);
mju_normalize3(ray);
}
// find intersection with geoms // find intersection with geoms
*geomid = -1; *geomid = -1;
mjtNum geomdist = mj_ray(m, d, pos, ray, vopt->geomgroup, mjtNum geomdist = mj_ray(m, d, pos, ray, vopt->geomgroup, vopt->flags[mjVIS_STATIC], -1, geomid);
vopt->flags[mjVIS_STATIC], -1, geomid);
// find intersection with flexes // find intersection with flexes
int flexbodyid = -1; int flexbodyid = -1;
@@ -851,7 +890,6 @@ int mjv_select(const mjModel* m, const mjData* d, const mjvOption* vopt,
} }
} }
// geom // geom
if (best == 0) { if (best == 0) {
*flexid = -1; *flexid = -1;
+140 -115
View File
@@ -511,11 +511,11 @@ static int bodycategory(const mjModel* m, int bodyid) {
// computes the camera frustum // computes the camera frustum
static void getFrustum(float zver[2], float zhor[2], float znear, static void getFrustum(float zver[2], float zhor[2], float znear,
const float K[4], const float sensorsize[2]) { const float intrinsic[4], const float sensorsize[2]) {
zhor[0] = znear / K[0] * (sensorsize[0]/2.f - K[2]); zhor[0] = znear / intrinsic[0] * (sensorsize[0]/2.f - intrinsic[2]);
zhor[1] = znear / K[0] * (sensorsize[0]/2.f + K[2]); zhor[1] = znear / intrinsic[0] * (sensorsize[0]/2.f + intrinsic[2]);
zver[0] = znear / K[1] * (sensorsize[1]/2.f - K[3]); zver[0] = znear / intrinsic[1] * (sensorsize[1]/2.f - intrinsic[3]);
zver[1] = znear / K[1] * (sensorsize[1]/2.f + K[3]); zver[1] = znear / intrinsic[1] * (sensorsize[1]/2.f + intrinsic[3]);
} }
@@ -1502,11 +1502,97 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
} }
} }
// cameras // cameras and frustums
objtype = mjOBJ_CAMERA; objtype = mjOBJ_CAMERA;
category = mjCAT_DECOR; category = mjCAT_DECOR;
if (vopt->flags[mjVIS_CAMERA] && (category & catmask)) { if (vopt->flags[mjVIS_CAMERA] && (category & catmask)) {
for (int i=0; i < m->ncam; i++) { for (int i=0; i < m->ncam; i++) {
// copy camera rgba
float cam_rgba[4];
f2f(cam_rgba, m->vis.rgba.camera, 4);
// draw frustum if sensorsize is defined
if (m->cam_sensorsize[2*i+1] > 0) {
// when drawing frustum, make camera translucent
cam_rgba[3] = 0.3;
// locals
const float* rgba = m->vis.rgba.frustum;
mjtNum vnear[4][3], vfar[4][3];
mjtNum center[3];
mjtNum znear = m->vis.map.znear * m->stat.extent;
mjtNum zfar = m->vis.scale.frustum * scl;
float zver[2], zhor[2];
// get frustum
getFrustum(zver, zhor, znear, m->cam_intrinsic + 4*i, m->cam_sensorsize + 2*i);
// frustum frame to convert from planes to vertex representation
mjtNum *cam_xpos = d->cam_xpos+3*i;
mjtNum *cam_xmat = d->cam_xmat+9*i;
mjtNum x[] = {cam_xmat[0], cam_xmat[3], cam_xmat[6]};
mjtNum y[] = {cam_xmat[1], cam_xmat[4], cam_xmat[7]};
mjtNum z[] = {cam_xmat[2], cam_xmat[5], cam_xmat[8]};
// vertices of the near plane
mju_addScl3(center, cam_xpos, z, -znear);
mju_addScl3(vnear[0], center, x, -zhor[0]);
mju_addScl3(vnear[1], center, x, zhor[1]);
mju_addScl3(vnear[2], center, x, zhor[1]);
mju_addScl3(vnear[3], center, x, -zhor[0]);
mju_addToScl3(vnear[0], y, -zver[0]);
mju_addToScl3(vnear[1], y, -zver[0]);
mju_addToScl3(vnear[2], y, zver[1]);
mju_addToScl3(vnear[3], y, zver[1]);
// vertices of the far plane
zhor[0] *= zfar / znear;
zhor[1] *= zfar / znear;
zver[0] *= zfar / znear;
zver[1] *= zfar / znear;
mju_addScl3(center, cam_xpos, z, -zfar);
mju_addScl3(vfar[0], center, x, -zhor[0]);
mju_addScl3(vfar[1], center, x, zhor[1]);
mju_addScl3(vfar[2], center, x, zhor[1]);
mju_addScl3(vfar[3], center, x, -zhor[0]);
mju_addToScl3(vfar[0], y, -zver[0]);
mju_addToScl3(vfar[1], y, -zver[0]);
mju_addToScl3(vfar[2], y, zver[1]);
mju_addToScl3(vfar[3], y, zver[1]);
// triangulation and wireframe of the frustum
for (int e=0; e < 4; e++) {
START
mju_sub3(x, vfar[e], vnear[e]);
mju_sub3(y, vnear[(e+1)%4], vnear[e]);
mju_cross(z, x, y);
mjtNum tri1[3] = {mju_normalize3(x), mju_normalize3(y), mju_normalize3(z)};
mjtNum xmat1[9] = {x[0], y[0], z[0], x[1], y[1], z[1], x[2], y[2], z[2]};
mjv_initGeom(thisgeom, mjGEOM_TRIANGLE, tri1, vnear[e], xmat1, rgba);
FINISH
START
mju_sub3(y, vnear[(e+1)%4], vfar[e]);
mju_sub3(x, vfar[(e+1)%4], vfar[e]);
mju_cross(z, x, y);
mjtNum tri2[3] = {mju_normalize3(x), mju_normalize3(y), mju_normalize3(z)};
mjtNum xmat2[9] = {x[0], y[0], z[0], x[1], y[1], z[1], x[2], y[2], z[2]};
mjv_initGeom(thisgeom, mjGEOM_TRIANGLE, tri2, vfar[e], xmat2, rgba);
FINISH
START
mjv_connector(thisgeom, mjGEOM_LINE, 3, vnear[e], vnear[(e+1)%4]);
f2f(thisgeom->rgba, rgba, 4);
FINISH
START
mjv_connector(thisgeom, mjGEOM_LINE, 3, vfar[e], vfar[(e+1)%4]);
f2f(thisgeom->rgba, rgba, 4);
FINISH
START
mjv_connector(thisgeom, mjGEOM_LINE, 3, vnear[e], vfar[e]);
f2f(thisgeom->rgba, rgba, 4);
FINISH
}
}
START START
// construct geom: camera body // construct geom: camera body
@@ -1516,7 +1602,7 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
thisgeom->size[2] = scl * m->vis.scale.camera * 0.4; thisgeom->size[2] = scl * m->vis.scale.camera * 0.4;
mju_n2f(thisgeom->pos, d->cam_xpos+3*i, 3); mju_n2f(thisgeom->pos, d->cam_xpos+3*i, 3);
mju_n2f(thisgeom->mat, d->cam_xmat+9*i, 9); mju_n2f(thisgeom->mat, d->cam_xmat+9*i, 9);
f2f(thisgeom->rgba, m->vis.rgba.camera, 4); f2f(thisgeom->rgba, cam_rgba, 4);
// vopt->label // vopt->label
if (vopt->label == mjLABEL_CAMERA) { if (vopt->label == mjLABEL_CAMERA) {
@@ -1539,7 +1625,7 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
thisgeom->size[1] = scl * m->vis.scale.camera * 0.4; thisgeom->size[1] = scl * m->vis.scale.camera * 0.4;
thisgeom->size[2] = scl * m->vis.scale.camera * 0.3; thisgeom->size[2] = scl * m->vis.scale.camera * 0.3;
mju_n2f(thisgeom->mat, d->cam_xmat+9*i, 9); mju_n2f(thisgeom->mat, d->cam_xmat+9*i, 9);
f2f(thisgeom->rgba, m->vis.rgba.camera, 4); f2f(thisgeom->rgba, cam_rgba, 4);
for (int k=0; k < 3; k++) { for (int k=0; k < 3; k++) {
thisgeom->rgba[k] *= 0.5; // make lens body darker thisgeom->rgba[k] *= 0.5; // make lens body darker
} }
@@ -1582,88 +1668,6 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
} }
} }
// camera frustum
if (vopt->flags[mjVIS_CAMERA]) {
objtype = mjOBJ_CAMERA;
category = mjCAT_DECOR;
const float* rgba = m->vis.rgba.frustum;
mjtNum vnear[4][3], vfar[4][3];
mjtNum center[3];
mjtNum znear = m->vis.map.znear * m->stat.extent;
mjtNum zfar = m->vis.scale.frustum * scl;
float zver[2], zhor[2];
for (int i=0; i < m->ncam; i++) {
if (m->cam_sensorsize[2*i+1] == 0) {
continue;
}
getFrustum(zver, zhor, znear, m->cam_intrinsic + 4*i, m->cam_sensorsize + 2*i);
// frustum frame to convert from planes to vertex representation
mjtNum *cam_xpos = d->cam_xpos+3*i;
mjtNum *cam_xmat = d->cam_xmat+9*i;
mjtNum x[] = {cam_xmat[0], cam_xmat[3], cam_xmat[6]};
mjtNum y[] = {cam_xmat[1], cam_xmat[4], cam_xmat[7]};
mjtNum z[] = {cam_xmat[2], cam_xmat[5], cam_xmat[8]};
// vertices of the near plane
mju_addScl3(center, cam_xpos, z, -znear);
mju_addScl3(vnear[0], center, x, -zhor[0]);
mju_addScl3(vnear[1], center, x, zhor[1]);
mju_addScl3(vnear[2], center, x, zhor[1]);
mju_addScl3(vnear[3], center, x, -zhor[0]);
mju_addToScl3(vnear[0], y, -zver[0]);
mju_addToScl3(vnear[1], y, -zver[0]);
mju_addToScl3(vnear[2], y, zver[1]);
mju_addToScl3(vnear[3], y, zver[1]);
// vertices of the far plane
zhor[0] *= zfar / znear;
zhor[1] *= zfar / znear;
zver[0] *= zfar / znear;
zver[1] *= zfar / znear;
mju_addScl3(center, cam_xpos, z, -zfar);
mju_addScl3(vfar[0], center, x, -zhor[0]);
mju_addScl3(vfar[1], center, x, zhor[1]);
mju_addScl3(vfar[2], center, x, zhor[1]);
mju_addScl3(vfar[3], center, x, -zhor[0]);
mju_addToScl3(vfar[0], y, -zver[0]);
mju_addToScl3(vfar[1], y, -zver[0]);
mju_addToScl3(vfar[2], y, zver[1]);
mju_addToScl3(vfar[3], y, zver[1]);
// triangulation and wireframe of the frustum
for (int e=0; e < 4; e++) {
START
mju_sub3(x, vfar[e], vnear[e]);
mju_sub3(y, vnear[(e+1)%4], vnear[e]);
mju_cross(z, x, y);
mjtNum tri1[3] = {mju_normalize3(x), mju_normalize3(y), mju_normalize3(z)};
mjtNum xmat1[9] = {x[0], y[0], z[0], x[1], y[1], z[1], x[2], y[2], z[2]};
mjv_initGeom(thisgeom, mjGEOM_TRIANGLE, tri1, vnear[e], xmat1, rgba);
FINISH
START
mju_sub3(y, vnear[(e+1)%4], vfar[e]);
mju_sub3(x, vfar[(e+1)%4], vfar[e]);
mju_cross(z, x, y);
mjtNum tri2[3] = {mju_normalize3(x), mju_normalize3(y), mju_normalize3(z)};
mjtNum xmat2[9] = {x[0], y[0], z[0], x[1], y[1], z[1], x[2], y[2], z[2]};
mjv_initGeom(thisgeom, mjGEOM_TRIANGLE, tri2, vfar[e], xmat2, rgba);
FINISH
START
mjv_connector(thisgeom, mjGEOM_LINE, 3, vnear[e], vnear[(e+1)%4]);
f2f(thisgeom->rgba, rgba, 4);
FINISH
START
mjv_connector(thisgeom, mjGEOM_LINE, 3, vfar[e], vfar[(e+1)%4]);
f2f(thisgeom->rgba, rgba, 4);
FINISH
START
mjv_connector(thisgeom, mjGEOM_LINE, 3, vnear[e], vfar[e]);
f2f(thisgeom->rgba, rgba, 4);
FINISH
}
}
}
// lights // lights
objtype = mjOBJ_LIGHT; objtype = mjOBJ_LIGHT;
@@ -2131,28 +2135,31 @@ void mjv_makeLights(const mjModel* m, const mjData* d, mjvScene* scn) {
// update camera only // update camera only
void mjv_updateCamera(const mjModel* m, const mjData* d, mjvCamera* cam, mjvScene* scn) { void mjv_updateCamera(const mjModel* m, const mjData* d, mjvCamera* cam, mjvScene* scn) {
mjtNum ca, sa, ce, se, move[3], *mat;
mjtNum headpos[3], forward[3], up[3], right[3], ipd;
// return if nothing to do // return if nothing to do
if (!m || !cam || cam->type == mjCAMERA_USER) { if (!m || !cam || cam->type == mjCAMERA_USER) {
return; return;
} }
// initialize frustum // define extrinsics
float zver[2], zhor[2] = {0, 0}; mjtNum move[3];
float znear = m->vis.map.znear * m->stat.extent; mjtNum headpos[3], forward[3], up[3], right[3];
float zfar = m->vis.map.zfar * m->stat.extent;
// get headpos, forward[3], up, right, ipd, fovy // define intrinsics
int cid, orthographic = 0;
mjtNum fovy, ipd;
float* intrinsic = NULL;
float* sensorsize = NULL;
// get headpos, forward, up, right, ipd, fovy, orthographic, intrinsic
switch (cam->type) { switch (cam->type) {
case mjCAMERA_FREE: case mjCAMERA_FREE:
case mjCAMERA_TRACKING: case mjCAMERA_TRACKING:
// get global ipd // get global ipd
ipd = m->vis.global.ipd; ipd = m->vis.global.ipd;
// compute image size from global fovy // get orthographic, fovy
zver[0] = zver[1] = (float)znear * mju_tan(m->vis.global.fovy * (float)(mjPI/360.0)); orthographic = m->vis.global.orthographic;
fovy = m->vis.global.fovy;
// move lookat for tracking // move lookat for tracking
if (cam->type == mjCAMERA_TRACKING) { if (cam->type == mjCAMERA_TRACKING) {
@@ -2168,10 +2175,10 @@ void mjv_updateCamera(const mjModel* m, const mjData* d, mjvCamera* cam, mjvScen
} }
// compute frame // compute frame
ca = mju_cos(cam->azimuth/180.0*mjPI); mjtNum ca = mju_cos(cam->azimuth/180.0*mjPI);
sa = mju_sin(cam->azimuth/180.0*mjPI); mjtNum sa = mju_sin(cam->azimuth/180.0*mjPI);
ce = mju_cos(cam->elevation/180.0*mjPI); mjtNum ce = mju_cos(cam->elevation/180.0*mjPI);
se = mju_sin(cam->elevation/180.0*mjPI); mjtNum se = mju_sin(cam->elevation/180.0*mjPI);
forward[0] = ce*ca; forward[0] = ce*ca;
forward[1] = ce*sa; forward[1] = ce*sa;
forward[2] = se; forward[2] = se;
@@ -2184,25 +2191,27 @@ void mjv_updateCamera(const mjModel* m, const mjData* d, mjvCamera* cam, mjvScen
mju_addScl3(headpos, cam->lookat, forward, -cam->distance); mju_addScl3(headpos, cam->lookat, forward, -cam->distance);
break; break;
case mjCAMERA_FIXED: { case mjCAMERA_FIXED:
// get id and check // get id, check range
int cid = cam->fixedcamid; cid = cam->fixedcamid;
if (cid < 0 || cid >= m->ncam) { if (cid < 0 || cid >= m->ncam) {
mjERROR("fixed camera id is outside valid range"); mjERROR("fixed camera id is outside valid range");
} }
// get camera-specific ipd and fovy // get camera-specific ipd, orthographic, fovy
ipd = m->cam_ipd[cid]; ipd = m->cam_ipd[cid];
// get frustum from intrinsics or from fovy orthographic = m->cam_orthographic[cid];
fovy = m->cam_fovy[cid];
// if positive sensorsize, get sensorsize and intrinsic
if (m->cam_sensorsize[2*cid+1]) { if (m->cam_sensorsize[2*cid+1]) {
getFrustum(zver, zhor, znear, m->cam_intrinsic + 4*cid, m->cam_sensorsize + 2*cid); sensorsize = m->cam_sensorsize + 2*cid;
} else { intrinsic = m->cam_intrinsic + 4*cid;
zver[0] = zver[1] = (float)znear * mju_tan(m->cam_fovy[cid] * (float)(mjPI/360.0));
} }
// get pointer to camera orientation matrix // get pointer to camera orientation matrix
mat = d->cam_xmat + 9*cid; mjtNum* mat = d->cam_xmat + 9*cid;
// get frame // get frame
forward[0] = -mat[2]; forward[0] = -mat[2];
@@ -2215,13 +2224,26 @@ void mjv_updateCamera(const mjModel* m, const mjData* d, mjvCamera* cam, mjvScen
right[1] = mat[3]; right[1] = mat[3];
right[2] = mat[6]; right[2] = mat[6];
mju_copy3(headpos, d->cam_xpos + 3*cid); mju_copy3(headpos, d->cam_xpos + 3*cid);
} break;
break;
default: default:
mjERROR("unknown camera type"); mjERROR("unknown camera type");
} }
// convert intrinsics to frustum parameters
float znear = m->vis.map.znear * m->stat.extent;
float zfar = m->vis.map.zfar * m->stat.extent;
float zver[2], zhor[2] = {0, 0};
if (orthographic){
zver[0] = zver[1] = fovy / 2;
} else {
if (!intrinsic) {
zver[0] = zver[1] = znear * mju_tan(fovy * mjPI/360.0);
} else {
getFrustum(zver, zhor, znear, intrinsic, sensorsize);
}
}
// compute GL cameras // compute GL cameras
for (int view=0; view < 2; view++) { for (int view=0; view < 2; view++) {
// set frame // set frame
@@ -2231,6 +2253,9 @@ void mjv_updateCamera(const mjModel* m, const mjData* d, mjvCamera* cam, mjvScen
scn->camera[view].up[i] = (float)up[i]; scn->camera[view].up[i] = (float)up[i];
} }
// set orthographic
scn->camera[view].orthographic = orthographic;
// set symmetric frustum using intrinsic camera matrix // set symmetric frustum using intrinsic camera matrix
scn->camera[view].frustum_top = zver[1]; scn->camera[view].frustum_top = zver[1];
scn->camera[view].frustum_bottom = -zver[0]; scn->camera[view].frustum_bottom = -zver[0];
+15 -5
View File
@@ -694,7 +694,7 @@ static void initLights(mjvScene* scn) {
// set projection and modelview // set projection and modelview
static void setView(int view, mjrRect viewport, const mjvScene* scn, const mjrContext* con, static void setView(int view, mjrRect viewport, const mjvScene* scn, const mjrContext* con,
float* camProject, float* camView) { float camProject[16], float camView[16]) {
mjvGLCamera cam; mjvGLCamera cam;
// copy specified camera for stereo, average for mono (view = -1) // copy specified camera for stereo, average for mono (view = -1)
@@ -709,24 +709,34 @@ static void setView(int view, mjrRect viewport, const mjvScene* scn, const mjrCo
: 0.5f * (float)viewport.width / (float)viewport.height * : 0.5f * (float)viewport.width / (float)viewport.height *
(cam.frustum_top - cam.frustum_bottom); (cam.frustum_top - cam.frustum_bottom);
// set projection // prepare projection
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
if (mjGLAD_GL_ARB_clip_control) { if (mjGLAD_GL_ARB_clip_control) {
// reverse Z rendering mapping [znear, zfar] -> [1, 0] (ndc) // reverse Z rendering mapping [znear, zfar] -> [1, 0] (ndc)
glTranslatef(0.0f, 0.0f, 0.5f); glTranslatef(0.0f, 0.0f, 0.5f);
glScalef(1.0f, 1.0f, -0.5f); glScalef(1.0f, 1.0f, -0.5f);
} } else {
else {
// reverse Z rendering mapping without shift [znear, zfar] -> [1, -1] (ndc) // reverse Z rendering mapping without shift [znear, zfar] -> [1, -1] (ndc)
glScalef(1.0f, 1.0f, -1.0f); glScalef(1.0f, 1.0f, -1.0f);
} }
glFrustum(cam.frustum_center - halfwidth,
// set projection, orthographic or perspective
if (cam.orthographic) {
glOrtho(cam.frustum_center - halfwidth,
cam.frustum_center + halfwidth, cam.frustum_center + halfwidth,
cam.frustum_bottom, cam.frustum_bottom,
cam.frustum_top, cam.frustum_top,
cam.frustum_near, cam.frustum_near,
cam.frustum_far); cam.frustum_far);
} else {
glFrustum(cam.frustum_center - halfwidth,
cam.frustum_center + halfwidth,
cam.frustum_bottom,
cam.frustum_top,
cam.frustum_near,
cam.frustum_far);
}
// save projection matrix if requested // save projection matrix if requested
if (camProject) { if (camProject) {
+1
View File
@@ -2040,6 +2040,7 @@ void mjCModel::CopyTree(mjModel* m) {
m->cam_targetbodyid[cid] = pc->targetbodyid; m->cam_targetbodyid[cid] = pc->targetbodyid;
copyvec(m->cam_pos+3*cid, pc->pos, 3); copyvec(m->cam_pos+3*cid, pc->pos, 3);
copyvec(m->cam_quat+4*cid, pc->quat, 4); copyvec(m->cam_quat+4*cid, pc->quat, 4);
m->cam_orthographic[cid] = pc->orthographic;
m->cam_fovy[cid] = (mjtNum)pc->fovy; m->cam_fovy[cid] = (mjtNum)pc->fovy;
m->cam_ipd[cid] = (mjtNum)pc->ipd; m->cam_ipd[cid] = (mjtNum)pc->ipd;
copyvec(m->cam_resolution+2*cid, pc->resolution, 2); copyvec(m->cam_resolution+2*cid, pc->resolution, 2);
+20 -14
View File
@@ -123,8 +123,8 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"visual", "*", "0"}, {"visual", "*", "0"},
{"<"}, {"<"},
{"global", "?", "11", "fovy", "ipd", "azimuth", "elevation", "linewidth", "glow", {"global", "?", "12", "orthographic", "fovy", "ipd", "azimuth", "elevation", "linewidth",
"offwidth", "offheight", "realtime", "ellipsoidinertia", "bvactive"}, "glow", "offwidth", "offheight", "realtime", "ellipsoidinertia", "bvactive"},
{"quality", "?", "5", "shadowsize", "offsamples", "numslices", "numstacks", {"quality", "?", "5", "shadowsize", "offsamples", "numslices", "numstacks",
"numquads"}, "numquads"},
{"headlight", "?", "4", "ambient", "diffuse", "specular", "active"}, {"headlight", "?", "4", "ambient", "diffuse", "specular", "active"},
@@ -160,9 +160,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
"hfield", "mesh", "fitscale", "rgba", "fluidshape", "fluidcoef", "user"}, "hfield", "mesh", "fitscale", "rgba", "fluidshape", "fluidcoef", "user"},
{"site", "?", "13", "type", "group", "pos", "quat", "material", {"site", "?", "13", "type", "group", "pos", "quat", "material",
"size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"}, "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"camera", "?", "16", "fovy", "ipd", "resolution", "pos", "quat", "axisangle", "xyaxes", {"camera", "?", "17", "orthographic", "fovy", "ipd", "resolution", "pos", "quat",
"zaxis", "euler", "mode", "focal", "focalpixel", "principal", "principalpixel", "axisangle", "xyaxes", "zaxis", "euler", "mode", "focal", "focalpixel",
"sensorsize", "user"}, "principal", "principalpixel", "sensorsize", "user"},
{"light", "?", "13", "pos", "dir", "bulbradius", "directional", "castshadow", "active", {"light", "?", "13", "pos", "dir", "bulbradius", "directional", "castshadow", "active",
"attenuation", "cutoff", "exponent", "ambient", "diffuse", "specular", "mode"}, "attenuation", "cutoff", "exponent", "ambient", "diffuse", "specular", "mode"},
{"pair", "?", "7", "condim", "friction", "solref", "solreffriction", "solimp", {"pair", "?", "7", "condim", "friction", "solref", "solreffriction", "solimp",
@@ -268,9 +268,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{">"}, {">"},
{"site", "*", "15", "name", "class", "type", "group", "pos", "quat", {"site", "*", "15", "name", "class", "type", "group", "pos", "quat",
"material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"}, "material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"camera", "*", "19", "name", "class", "fovy", "ipd", "resolution", "pos", "quat", {"camera", "*", "20", "name", "class", "orthographic", "fovy", "ipd", "resolution", "pos",
"axisangle", "xyaxes", "zaxis", "euler", "mode", "target", "focal", "focalpixel", "quat", "axisangle", "xyaxes", "zaxis", "euler", "mode", "target",
"principal", "principalpixel", "sensorsize", "user"}, "focal", "focalpixel", "principal", "principalpixel", "sensorsize", "user"},
{"light", "*", "16", "name", "class", "directional", "castshadow", "active", {"light", "*", "16", "name", "class", "directional", "castshadow", "active",
"pos", "dir", "bulbradius", "attenuation", "cutoff", "exponent", "ambient", "diffuse", "pos", "dir", "bulbradius", "attenuation", "cutoff", "exponent", "ambient", "diffuse",
"specular", "mode", "target"}, "specular", "mode", "target"},
@@ -1742,6 +1742,10 @@ void mjXReader::OneCamera(XMLElement* elem, mjsCamera* pcam) {
ReadAlternative(elem, pcam->alt); ReadAlternative(elem, pcam->alt);
ReadAttr(elem, "ipd", 1, &pcam->ipd, text); ReadAttr(elem, "ipd", 1, &pcam->ipd, text);
if (MapValue(elem, "orthographic", &n, bool_map, 2)) {
pcam->orthographic = (n==1);
}
bool has_principal = ReadAttr(elem, "principalpixel", 2, pcam->principal_pixel, text) || bool has_principal = ReadAttr(elem, "principalpixel", 2, pcam->principal_pixel, text) ||
ReadAttr(elem, "principal", 2, pcam->principal_length, text); ReadAttr(elem, "principal", 2, pcam->principal_length, text);
bool has_focal = ReadAttr(elem, "focalpixel", 2, pcam->focal_pixel, text) || bool has_focal = ReadAttr(elem, "focalpixel", 2, pcam->focal_pixel, text) ||
@@ -2948,6 +2952,7 @@ void mjXReader::Visual(XMLElement* section) {
string text, name; string text, name;
XMLElement* elem; XMLElement* elem;
mjVisual* vis = &model->visual; mjVisual* vis = &model->visual;
int n;
// iterate over child elements // iterate over child elements
elem = FirstChildElement(section); elem = FirstChildElement(section);
@@ -2957,6 +2962,9 @@ void mjXReader::Visual(XMLElement* section) {
// global sub-element // global sub-element
if (name=="global") { if (name=="global") {
if (MapValue(elem, "orthographic", &n, bool_map, 2)) {
vis->global.orthographic = (n==1);
}
ReadAttr(elem, "fovy", 1, &vis->global.fovy, text); ReadAttr(elem, "fovy", 1, &vis->global.fovy, text);
ReadAttr(elem, "ipd", 1, &vis->global.ipd, text); ReadAttr(elem, "ipd", 1, &vis->global.ipd, text);
ReadAttr(elem, "azimuth", 1, &vis->global.azimuth, text); ReadAttr(elem, "azimuth", 1, &vis->global.azimuth, text);
@@ -2970,13 +2978,11 @@ void mjXReader::Visual(XMLElement* section) {
throw mjXError(elem, "realtime must be greater than 0"); throw mjXError(elem, "realtime must be greater than 0");
} }
} }
int ellipsoidinertia; if (MapValue(elem, "ellipsoidinertia", &n, bool_map, 2)) {
if (MapValue(elem, "ellipsoidinertia", &ellipsoidinertia, bool_map, 2)) { vis->global.ellipsoidinertia = (n==1);
vis->global.ellipsoidinertia = (ellipsoidinertia==1);
} }
int bvactive; if (MapValue(elem, "bvactive", &n, bool_map, 2)) {
if (MapValue(elem, "bvactive", &bvactive, bool_map, 2)) { vis->global.bvactive = (n==1);
vis->global.bvactive = (bvactive==1);
} }
} }
+11 -12
View File
@@ -474,9 +474,7 @@ void mjXWriter::OneCamera(XMLElement* elem, const mjCCamera* pcam, mjCDef* def)
WriteAttr(elem, "ipd", 1, &pcam->ipd, &def->Camera().ipd); WriteAttr(elem, "ipd", 1, &pcam->ipd, &def->Camera().ipd);
WriteAttrKey(elem, "mode", camlight_map, camlight_sz, pcam->mode, def->Camera().mode); WriteAttrKey(elem, "mode", camlight_map, camlight_sz, pcam->mode, def->Camera().mode);
WriteAttr(elem, "resolution", 2, pcam->resolution, def->Camera().resolution); WriteAttr(elem, "resolution", 2, pcam->resolution, def->Camera().resolution);
WriteAttrKey(elem, "orthographic", bool_map, 2, pcam->orthographic, def->Camera().orthographic);
// resolution if positive
WriteAttr(elem, "resolution", 2, pcam->resolution, def->Camera().resolution);
// camera intrinsics if specified // camera intrinsics if specified
if (pcam->sensor_size[0]>0 && pcam->sensor_size[1]>0) { if (pcam->sensor_size[0]>0 && pcam->sensor_size[1]>0) {
@@ -1012,15 +1010,16 @@ void mjXWriter::Visual(XMLElement* root) {
// global // global
elem = InsertEnd(section, "global"); elem = InsertEnd(section, "global");
WriteAttr(elem, "fovy", 1, &vis->global.fovy, &visdef.global.fovy); WriteAttrKey(elem, "orthographic", bool_map, 2, vis->global.orthographic, visdef.global.orthographic);
WriteAttr(elem, "ipd", 1, &vis->global.ipd, &visdef.global.ipd); WriteAttr(elem, "fovy", 1, &vis->global.fovy, &visdef.global.fovy);
WriteAttr(elem, "azimuth", 1, &vis->global.azimuth, &visdef.global.azimuth); WriteAttr(elem, "ipd", 1, &vis->global.ipd, &visdef.global.ipd);
WriteAttr(elem, "elevation", 1, &vis->global.elevation, &visdef.global.elevation); WriteAttr(elem, "azimuth", 1, &vis->global.azimuth, &visdef.global.azimuth);
WriteAttr(elem, "linewidth", 1, &vis->global.linewidth, &visdef.global.linewidth); WriteAttr(elem, "elevation", 1, &vis->global.elevation, &visdef.global.elevation);
WriteAttr(elem, "glow", 1, &vis->global.glow, &visdef.global.glow); WriteAttr(elem, "linewidth", 1, &vis->global.linewidth, &visdef.global.linewidth);
WriteAttr(elem, "realtime", 1, &vis->global.realtime, &visdef.global.realtime); WriteAttr(elem, "glow", 1, &vis->global.glow, &visdef.global.glow);
WriteAttrInt(elem, "offwidth", vis->global.offwidth, visdef.global.offwidth); WriteAttr(elem, "realtime", 1, &vis->global.realtime, &visdef.global.realtime);
WriteAttrInt(elem, "offheight", vis->global.offheight, visdef.global.offheight); WriteAttrInt(elem, "offwidth", vis->global.offwidth, visdef.global.offwidth);
WriteAttrInt(elem, "offheight", vis->global.offheight, visdef.global.offheight);
WriteAttrKey(elem, "ellipsoidinertia", bool_map, 2, vis->global.ellipsoidinertia, visdef.global.ellipsoidinertia); WriteAttrKey(elem, "ellipsoidinertia", bool_map, 2, vis->global.ellipsoidinertia, visdef.global.ellipsoidinertia);
WriteAttrKey(elem, "bvactive", bool_map, 2, vis->global.bvactive, visdef.global.bvactive); WriteAttrKey(elem, "bvactive", bool_map, 2, vis->global.bvactive, visdef.global.bvactive);
if (!elem->FirstAttribute()) { if (!elem->FirstAttribute()) {
+34
View File
@@ -0,0 +1,34 @@
<!-- Test model for visual debugging of orthographic cameras. -->
<mujoco>
<visual>
<!-- free camera is orthorgaphic with a 5m vertical field-of-view -->
<global orthographic="true" fovy="5"/>
<scale camera="1"/>
</visual>
<asset>
<texture name="grid" type="2d" builtin="checker" width="512" height="512" rgb1=".8 .6 .4" rgb2=".2 .3 .4"/>
<material name="grid" texture="grid" texrepeat="10 10" texuniform="false" reflectance=".2"/>
</asset>
<default>
<camera orthographic="true"/>
</default>
<worldbody>
<light pos="0 0 3"/>
<geom type="plane" size="10 10 .01" material="grid"/>
<body pos=".5 .5 .5">
<freejoint/>
<geom type="box" size=".5 .5 .5" pos="0 0 .5"/>
</body>
<!-- box should pefectly fit within field-of-view -->
<camera name="fovy = 1" pos=".5 .5 2" orthographic="true" fovy="1"/>
<!-- 2 floor squares should pefectly fit within field-of-view -->
<camera name="fovy = 2" pos="0 0 2" fovy="2"/> <!-- inherits orthographic="true" from defaults -->
</worldbody>
</mujoco>
+30
View File
@@ -1249,6 +1249,36 @@ TEST_F(XMLReaderTest, InvalidSkinGroup) {
EXPECT_THAT( EXPECT_THAT(
error.data(), error.data(),
HasSubstr("skin group must be between 0 and 5\nElement 'skin', line 7")); HasSubstr("skin group must be between 0 and 5\nElement 'skin', line 7"));
}
TEST_F(XMLReaderTest, Orthographic) {
static constexpr char xml[] = R"(
<mujoco>
<visual>
<global orthographic="true" fovy="5"/>
<map znear="0.01"/>
</visual>
<default>
<camera orthographic="true"/>
</default>
<worldbody>
<camera name="fovy=1" pos=".5 .5 2" orthographic="true" fovy="1"/>
<camera name="fovy=2" pos="0 0 2" fovy="2"/>
</worldbody>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, NotNull()) << error.data();
EXPECT_EQ(model->vis.global.orthographic, 1);
EXPECT_EQ(model->cam_orthographic[0], 1);
EXPECT_EQ(model->cam_orthographic[1], 1);
EXPECT_EQ(model->cam_fovy[0], 1);
EXPECT_EQ(model->cam_fovy[1], 2);
mj_deleteModel(model); mj_deleteModel(model);
} }
+10
View File
@@ -1118,6 +1118,16 @@ TEST_F(XMLWriterTest, TrimsDefaults) {
mj_deleteModel(model); mj_deleteModel(model);
} }
TEST_F(XMLWriterTest, DoesntSaveGlobal) {
static constexpr char xml[] = "<mujoco/>";
std::array<char, 1000> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, Not(HasSubstr("global")));
mj_deleteModel(model);
}
TEST_F(XMLWriterTest, InheritrangeSavedAsRange) { TEST_F(XMLWriterTest, InheritrangeSavedAsRange) {
static constexpr char xml[] = R"( static constexpr char xml[] = R"(
<mujoco> <mujoco>
+10 -4
View File
@@ -5013,6 +5013,7 @@ public unsafe struct mjOption_ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
public unsafe struct global { public unsafe struct global {
public int orthographic;
public float fovy; public float fovy;
public float ipd; public float ipd;
public float azimuth; public float azimuth;
@@ -5321,11 +5322,12 @@ public unsafe struct mjModel_ {
public double* cam_poscom0; public double* cam_poscom0;
public double* cam_pos0; public double* cam_pos0;
public double* cam_mat0; public double* cam_mat0;
public int* cam_resolution; public int* cam_orthographic;
public double* cam_fovy; public double* cam_fovy;
public float* cam_intrinsic;
public float* cam_sensorsize;
public double* cam_ipd; public double* cam_ipd;
public int* cam_resolution;
public float* cam_sensorsize;
public float* cam_intrinsic;
public double* cam_user; public double* cam_user;
public int* light_mode; public int* light_mode;
public int* light_bodyid; public int* light_bodyid;
@@ -5858,6 +5860,7 @@ public unsafe struct mjvCamera_ {
public double distance; public double distance;
public double azimuth; public double azimuth;
public double elevation; public double elevation;
public int orthographic;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@@ -5871,6 +5874,7 @@ public unsafe struct mjvGLCamera_ {
public float frustum_top; public float frustum_top;
public float frustum_near; public float frustum_near;
public float frustum_far; public float frustum_far;
public int orthographic;
} }
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
@@ -6184,10 +6188,12 @@ public unsafe struct model {
public int* site_group; public int* site_group;
public double* site_size; public double* site_size;
public float* site_rgba; public float* site_rgba;
public int* cam_orthographic;
public double* cam_fovy; public double* cam_fovy;
public double* cam_ipd; public double* cam_ipd;
public float* cam_intrinsic; public int* cam_resolution;
public float* cam_sensorsize; public float* cam_sensorsize;
public float* cam_intrinsic;
public byte* light_directional; public byte* light_directional;
public byte* light_castshadow; public byte* light_castshadow;
public float* light_bulbradius; public float* light_bulbradius;