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
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:
:at:`fovy`: :at-val:`real, "45"`
Vertical field of view of the camera, expressed in degrees regardless of the global angle setting. The horizontal
field of view is computed automatically given the window size and the vertical field of view.
Vertical field-of-view of the camera. If the camera uses a perspective projection, the field-of-view is expressed in
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:
@@ -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
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:
: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
the visualizer even if no cameras are explicitly defined in the model. It is always expressed in degrees, regardless
of the setting of the angle attribute of :ref:`compiler <compiler>`, and is also represented in the low level model
in degrees. This is because we pass it to OpenGL which uses degrees. The same convention applies to the fovy
attribute of the :ref:`camera <body-camera>` element below.
the visualizer even if no cameras are explicitly defined in the model. If the camera uses a perspective projection,
the field-of-view is expressed in 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. The same convention
applies to the :ref:`camera/fovy <body-camera-fovy>` attribute.
.. _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.
| All site attributes are available here except: name, class.
.. _default-camera-orthographic:
.. _default-camera-fovy:
+14 -12
View File
@@ -277,15 +277,15 @@
| :ref:`camera | \* | :class: mjcf-attributes |
| <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:: |
@@ -1246,11 +1246,11 @@
| :ref:`global | ? | :class: mjcf-attributes |
| <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:: |
@@ -1396,13 +1396,15 @@
| :ref:`camera | ? | :class: mjcf-attributes |
| <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:: |
+11 -5
View File
@@ -13,15 +13,21 @@ General
- Detailed documentation.
- 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
~~~
3. Added support for :ref:`elliptic friction cones<option-cone>`.
4. Fixed a bug that resulted in less-optimal linesearch solutions for some difficult constraint settings.
5. Fixed a bug in the Newton solver that sometimes resulted in less-optimal gradients.
4. 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.
6. Fixed a bug in the Newton solver that sometimes resulted in less-optimal gradients.
Version 3.1.6 (Jun 3, 2024)
---------------------------
+17 -6
View File
@@ -751,7 +751,8 @@ struct mjOption_ { // physics options
typedef struct mjOption_ mjOption;
struct mjVisual_ { // visualization options
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 azimuth; // initial azimuth 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_pos0; // global position rel. to body in qpos0 (ncam x 3)
mjtNum* cam_mat0; // global orientation in qpos0 (ncam x 9)
int* cam_resolution; // [width, height] in pixels (ncam x 2)
mjtNum* cam_fovy; // y-field of view (deg) (ncam x 1)
float* cam_intrinsic; // [focal length; principal point] (ncam x 4)
float* cam_sensorsize; // sensor size (ncam x 2)
int* cam_orthographic; // orthographic camera; 0: no, 1: yes (ncam x 1)
mjtNum* cam_fovy; // y field-of-view (ortho ? len : deg) (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)
// lights
@@ -1858,6 +1860,7 @@ typedef struct mjsCamera_ { // camera specification
mjString* targetbody; // target body for tracking/targeting
// intrinsics
int orthographic; // is camera orthographic
double fovy; // y-field of view
double ipd; // inter-pupilary distance
float intrinsic[4]; // camera intrinsics (length)
@@ -2602,6 +2605,9 @@ struct mjvCamera_ { // abstract camera
mjtNum distance; // distance to lookat point or tracked body
mjtNum azimuth; // camera azimuth (deg)
mjtNum elevation; // camera elevation (deg)
// orthographic / perspective
int orthographic; // 0: perspective; 1: orthographic
};
typedef struct mjvCamera_ mjvCamera;
struct mjvGLCamera_ { // OpenGL camera
@@ -2617,6 +2623,9 @@ struct mjvGLCamera_ { // OpenGL camera
float frustum_top; // top
float frustum_near; // near
float frustum_far; // far
// orthographic / perspective
int orthographic; // 0: perspective; 1: orthographic
};
typedef struct mjvGLCamera_ mjvGLCamera;
struct mjvGeom_ { // abstract geom
@@ -2869,10 +2878,12 @@ struct mjvSceneState_ {
mjtNum* site_size;
float* site_rgba;
int* cam_orthographic;
mjtNum* cam_fovy;
mjtNum* cam_ipd;
float* cam_intrinsic;
int* cam_resolution;
float* cam_sensorsize;
float* cam_intrinsic;
mjtByte* light_directional;
mjtByte* light_castshadow;
+7 -5
View File
@@ -458,7 +458,8 @@ typedef struct mjOption_ mjOption;
struct mjVisual_ { // visualization options
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 azimuth; // initial azimuth 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_pos0; // global position rel. to body in qpos0 (ncam x 3)
mjtNum* cam_mat0; // global orientation in qpos0 (ncam x 9)
int* cam_resolution; // [width, height] in pixels (ncam x 2)
mjtNum* cam_fovy; // y-field of view (deg) (ncam x 1)
float* cam_intrinsic; // [focal length; principal point] (ncam x 4)
float* cam_sensorsize; // sensor size (ncam x 2)
int* cam_orthographic; // orthographic camera; 0: no, 1: yes (ncam x 1)
mjtNum* cam_fovy; // y field-of-view (ortho ? len : deg) (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)
// lights
+1
View File
@@ -345,6 +345,7 @@ typedef struct mjsCamera_ { // camera specification
mjString* targetbody; // target body for tracking/targeting
// intrinsics
int orthographic; // is camera orthographic
double fovy; // y-field of view
double ipd; // inter-pupilary distance
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 azimuth; // camera azimuth (deg)
mjtNum elevation; // camera elevation (deg)
// orthographic / perspective
int orthographic; // 0: perspective; 1: orthographic
};
typedef struct mjvCamera_ mjvCamera;
@@ -211,6 +214,9 @@ struct mjvGLCamera_ { // OpenGL camera
float frustum_top; // top
float frustum_near; // near
float frustum_far; // far
// orthographic / perspective
int orthographic; // 0: perspective; 1: orthographic
};
typedef struct mjvGLCamera_ mjvGLCamera;
@@ -487,10 +493,12 @@ struct mjvSceneState_ {
mjtNum* site_size;
float* site_rgba;
int* cam_orthographic;
mjtNum* cam_fovy;
mjtNum* cam_ipd;
float* cam_intrinsic;
int* cam_resolution;
float* cam_sensorsize;
float* cam_intrinsic;
mjtByte* light_directional;
mjtByte* light_castshadow;
+4 -3
View File
@@ -277,16 +277,17 @@
X ( int, cam_mode, ncam, 1 ) \
X ( int, cam_bodyid, 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_quat, ncam, 4 ) \
X ( mjtNum, cam_poscom0, ncam, 3 ) \
X ( mjtNum, cam_pos0, ncam, 3 ) \
X ( mjtNum, cam_mat0, ncam, 9 ) \
XMJV( int, cam_orthographic, ncam, 1 ) \
XMJV( mjtNum, cam_fovy, 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 ( int, light_mode, nlight, 1 ) \
X ( int, light_bodyid, nlight, 1 ) \
+61 -20
View File
@@ -310,10 +310,15 @@ STRUCTS: Mapping[str, StructDecl] = dict([
name='global',
type=AnonymousStructDecl(
fields=(
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='is the free camera orthographic (0: no, 1: yes)', # pylint: disable=line-too-long
),
StructFieldDecl(
name='fovy',
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(
name='ipd',
@@ -2024,32 +2029,18 @@ STRUCTS: Mapping[str, StructDecl] = dict([
doc='global orientation in qpos0 (ncam x 9)',
),
StructFieldDecl(
name='cam_resolution',
name='cam_orthographic',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='[width, height] in pixels (ncam x 2)',
doc='orthographic camera; 0: no, 1: yes (ncam x 1)',
),
StructFieldDecl(
name='cam_fovy',
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='y-field of view (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)',
doc='y field-of-view (ortho ? len : deg) (ncam x 1)',
),
StructFieldDecl(
name='cam_ipd',
@@ -2058,6 +2049,27 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
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(
name='cam_user',
type=PointerType(
@@ -5367,6 +5379,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='mjtNum'),
doc='camera elevation (deg)',
),
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='0: perspective; 1: orthographic',
),
),
)),
('mjvGLCamera',
@@ -5428,6 +5445,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='float'),
doc='far',
),
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='0: perspective; 1: orthographic',
),
),
)),
('mjvGeom',
@@ -6701,6 +6723,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='',
),
StructFieldDecl(
name='cam_orthographic',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='',
),
StructFieldDecl(
name='cam_fovy',
type=PointerType(
@@ -6716,14 +6745,21 @@ STRUCTS: Mapping[str, StructDecl] = dict([
doc='',
),
StructFieldDecl(
name='cam_intrinsic',
name='cam_resolution',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='',
),
StructFieldDecl(
name='cam_sensorsize',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='',
),
StructFieldDecl(
name='cam_sensorsize',
name='cam_intrinsic',
type=PointerType(
inner_type=ValueType(name='float'),
),
@@ -9216,6 +9252,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='target body for tracking/targeting',
),
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='is camera orthographic',
),
StructFieldDecl(
name='fovy',
type=ValueType(name='double'),
+3
View File
@@ -1390,6 +1390,7 @@ PYBIND11_MODULE(_structs, m) {
});
DefineStructFunctions(mjVisualGlobal);
#define X(var) mjVisualGlobal.def_readwrite(#var, &raw::MjVisualGlobal::var)
X(orthographic);
X(fovy);
X(ipd);
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(azimuth);
X(elevation);
X(orthographic);
#undef X
#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_near);
X(frustum_far);
X(orthographic);
#undef X
#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, "Diffuse", 2, &(vis->headlight.diffuse), "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_EDITFLOAT, "Azimuth", 2, &(vis->global.azimuth), "1"},
{mjITEM_EDITFLOAT, "Elevation", 2, &(vis->global.elevation), "1"},
{mjITEM_BUTTON, "Align", 2, nullptr, "CA"},
{mjITEM_SEPARATOR, "Global", 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, "BVH active", 5, &(vis->global.bvactive), "False\nTrue"},
{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
void mj_defaultVisual(mjVisual* vis) {
// global
vis->global.orthographic = 0;
vis->global.fovy = 45;
vis->global.ipd = 0.068;
vis->global.azimuth = 90;
@@ -257,7 +258,7 @@ void mj_defaultVisual(mjVisual* vis) {
setf4(vis->rgba.actuatornegative, .2, .6, .9, 1.);
setf4(vis->rgba.actuatorpositive, .9, .4, .2, 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.selectpoint, .9, .9, .1, 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) {
memset(cam, 0, sizeof(mjvCamera));
cam->type = mjCAMERA_FREE;
cam->fixedcamid = -1;
cam->trackbodyid = -1;
cam->lookat[0] = m->stat.center[0];
cam->lookat[1] = m->stat.center[1];
cam->lookat[2] = m->stat.center[2];
cam->distance = 1.5 * m->stat.extent;
cam->azimuth = m->vis.global.azimuth;
cam->elevation = m->vis.global.elevation;
cam->type = mjCAMERA_FREE;
cam->fixedcamid = -1;
cam->trackbodyid = -1;
cam->lookat[0] = m->stat.center[0];
cam->lookat[1] = m->stat.center[1];
cam->lookat[2] = m->stat.center[2];
cam->distance = 1.5 * m->stat.extent;
cam->azimuth = m->vis.global.azimuth;
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
mjtNum mjv_frustumHeight(const mjvScene* scn) {
mjtNum height;
const mjvGLCamera* cam1 = scn->camera;
const mjvGLCamera* cam2 = scn->camera + 1;
// check znear
if (scn->camera[0].frustum_near < mjMINVAL || scn->camera[1].frustum_near < mjMINVAL) {
mjERROR("mjvScene frustum_near too small");
if (cam1->orthographic != cam2->orthographic) {
mjERROR("cannot average frustums of perspective and orthographic cameras");
}
// add normalized height for left and right cameras
height = (scn->camera[0].frustum_top-scn->camera[0].frustum_bottom)/scn->camera[0].frustum_near +
(scn->camera[1].frustum_top-scn->camera[1].frustum_bottom)/scn->camera[1].frustum_near;
// get height
mjtNum height;
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
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);
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
mju_addToScl3(cam->lookat, vec, -scl);
break;
@@ -563,6 +579,9 @@ void mjv_initPerturb(const mjModel* m, mjData* d, const mjvScene* scn, mjvPertur
mju_sub3(dif, pert->refselpos, headpos);
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);
}
@@ -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_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;
}
@@ -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
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];
mju_scl3(ray, forward, cam.frustum_near);
mju_addToScl3(ray, up, cam.frustum_bottom + rely*(cam.frustum_top-cam.frustum_bottom));
mju_addToScl3(ray, left, -(cam.frustum_center + (2*relx-1)*halfwidth));
mju_normalize3(ray);
// construct ray for orthographic camera: fixed direction, modify pos
if (cam.orthographic) {
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
*geomid = -1;
mjtNum geomdist = mj_ray(m, d, pos, ray, vopt->geomgroup,
vopt->flags[mjVIS_STATIC], -1, geomid);
mjtNum geomdist = mj_ray(m, d, pos, ray, vopt->geomgroup, vopt->flags[mjVIS_STATIC], -1, geomid);
// find intersection with flexes
int flexbodyid = -1;
@@ -851,7 +890,6 @@ int mjv_select(const mjModel* m, const mjData* d, const mjvOption* vopt,
}
}
// geom
if (best == 0) {
*flexid = -1;
+140 -115
View File
@@ -511,11 +511,11 @@ static int bodycategory(const mjModel* m, int bodyid) {
// computes the camera frustum
static void getFrustum(float zver[2], float zhor[2], float znear,
const float K[4], const float sensorsize[2]) {
zhor[0] = znear / K[0] * (sensorsize[0]/2.f - K[2]);
zhor[1] = znear / K[0] * (sensorsize[0]/2.f + K[2]);
zver[0] = znear / K[1] * (sensorsize[1]/2.f - K[3]);
zver[1] = znear / K[1] * (sensorsize[1]/2.f + K[3]);
const float intrinsic[4], const float sensorsize[2]) {
zhor[0] = znear / intrinsic[0] * (sensorsize[0]/2.f - intrinsic[2]);
zhor[1] = znear / intrinsic[0] * (sensorsize[0]/2.f + intrinsic[2]);
zver[0] = znear / intrinsic[1] * (sensorsize[1]/2.f - intrinsic[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;
category = mjCAT_DECOR;
if (vopt->flags[mjVIS_CAMERA] && (category & catmask)) {
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
// 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;
mju_n2f(thisgeom->pos, d->cam_xpos+3*i, 3);
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
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[2] = scl * m->vis.scale.camera * 0.3;
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++) {
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
objtype = mjOBJ_LIGHT;
@@ -2131,28 +2135,31 @@ void mjv_makeLights(const mjModel* m, const mjData* d, mjvScene* scn) {
// update camera only
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
if (!m || !cam || cam->type == mjCAMERA_USER) {
return;
}
// initialize frustum
float zver[2], zhor[2] = {0, 0};
float znear = m->vis.map.znear * m->stat.extent;
float zfar = m->vis.map.zfar * m->stat.extent;
// define extrinsics
mjtNum move[3];
mjtNum headpos[3], forward[3], up[3], right[3];
// 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) {
case mjCAMERA_FREE:
case mjCAMERA_TRACKING:
// get global ipd
ipd = m->vis.global.ipd;
// compute image size from global fovy
zver[0] = zver[1] = (float)znear * mju_tan(m->vis.global.fovy * (float)(mjPI/360.0));
// get orthographic, fovy
orthographic = m->vis.global.orthographic;
fovy = m->vis.global.fovy;
// move lookat for tracking
if (cam->type == mjCAMERA_TRACKING) {
@@ -2168,10 +2175,10 @@ void mjv_updateCamera(const mjModel* m, const mjData* d, mjvCamera* cam, mjvScen
}
// compute frame
ca = mju_cos(cam->azimuth/180.0*mjPI);
sa = mju_sin(cam->azimuth/180.0*mjPI);
ce = mju_cos(cam->elevation/180.0*mjPI);
se = mju_sin(cam->elevation/180.0*mjPI);
mjtNum ca = mju_cos(cam->azimuth/180.0*mjPI);
mjtNum sa = mju_sin(cam->azimuth/180.0*mjPI);
mjtNum ce = mju_cos(cam->elevation/180.0*mjPI);
mjtNum se = mju_sin(cam->elevation/180.0*mjPI);
forward[0] = ce*ca;
forward[1] = ce*sa;
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);
break;
case mjCAMERA_FIXED: {
// get id and check
int cid = cam->fixedcamid;
case mjCAMERA_FIXED:
// get id, check range
cid = cam->fixedcamid;
if (cid < 0 || cid >= m->ncam) {
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];
// 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]) {
getFrustum(zver, zhor, znear, m->cam_intrinsic + 4*cid, m->cam_sensorsize + 2*cid);
} else {
zver[0] = zver[1] = (float)znear * mju_tan(m->cam_fovy[cid] * (float)(mjPI/360.0));
sensorsize = m->cam_sensorsize + 2*cid;
intrinsic = m->cam_intrinsic + 4*cid;
}
// get pointer to camera orientation matrix
mat = d->cam_xmat + 9*cid;
mjtNum* mat = d->cam_xmat + 9*cid;
// get frame
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[2] = mat[6];
mju_copy3(headpos, d->cam_xpos + 3*cid);
}
break;
break;
default:
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
for (int view=0; view < 2; view++) {
// 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];
}
// set orthographic
scn->camera[view].orthographic = orthographic;
// set symmetric frustum using intrinsic camera matrix
scn->camera[view].frustum_top = zver[1];
scn->camera[view].frustum_bottom = -zver[0];
+15 -5
View File
@@ -694,7 +694,7 @@ static void initLights(mjvScene* scn) {
// set projection and modelview
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;
// 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 *
(cam.frustum_top - cam.frustum_bottom);
// set projection
// prepare projection
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (mjGLAD_GL_ARB_clip_control) {
// reverse Z rendering mapping [znear, zfar] -> [1, 0] (ndc)
glTranslatef(0.0f, 0.0f, 0.5f);
glScalef(1.0f, 1.0f, -0.5f);
}
else {
} else {
// reverse Z rendering mapping without shift [znear, zfar] -> [1, -1] (ndc)
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_bottom,
cam.frustum_top,
cam.frustum_near,
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
if (camProject) {
+1
View File
@@ -2040,6 +2040,7 @@ void mjCModel::CopyTree(mjModel* m) {
m->cam_targetbodyid[cid] = pc->targetbodyid;
copyvec(m->cam_pos+3*cid, pc->pos, 3);
copyvec(m->cam_quat+4*cid, pc->quat, 4);
m->cam_orthographic[cid] = pc->orthographic;
m->cam_fovy[cid] = (mjtNum)pc->fovy;
m->cam_ipd[cid] = (mjtNum)pc->ipd;
copyvec(m->cam_resolution+2*cid, pc->resolution, 2);
+20 -14
View File
@@ -123,8 +123,8 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"visual", "*", "0"},
{"<"},
{"global", "?", "11", "fovy", "ipd", "azimuth", "elevation", "linewidth", "glow",
"offwidth", "offheight", "realtime", "ellipsoidinertia", "bvactive"},
{"global", "?", "12", "orthographic", "fovy", "ipd", "azimuth", "elevation", "linewidth",
"glow", "offwidth", "offheight", "realtime", "ellipsoidinertia", "bvactive"},
{"quality", "?", "5", "shadowsize", "offsamples", "numslices", "numstacks",
"numquads"},
{"headlight", "?", "4", "ambient", "diffuse", "specular", "active"},
@@ -160,9 +160,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
"hfield", "mesh", "fitscale", "rgba", "fluidshape", "fluidcoef", "user"},
{"site", "?", "13", "type", "group", "pos", "quat", "material",
"size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"camera", "?", "16", "fovy", "ipd", "resolution", "pos", "quat", "axisangle", "xyaxes",
"zaxis", "euler", "mode", "focal", "focalpixel", "principal", "principalpixel",
"sensorsize", "user"},
{"camera", "?", "17", "orthographic", "fovy", "ipd", "resolution", "pos", "quat",
"axisangle", "xyaxes", "zaxis", "euler", "mode", "focal", "focalpixel",
"principal", "principalpixel", "sensorsize", "user"},
{"light", "?", "13", "pos", "dir", "bulbradius", "directional", "castshadow", "active",
"attenuation", "cutoff", "exponent", "ambient", "diffuse", "specular", "mode"},
{"pair", "?", "7", "condim", "friction", "solref", "solreffriction", "solimp",
@@ -268,9 +268,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{">"},
{"site", "*", "15", "name", "class", "type", "group", "pos", "quat",
"material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"camera", "*", "19", "name", "class", "fovy", "ipd", "resolution", "pos", "quat",
"axisangle", "xyaxes", "zaxis", "euler", "mode", "target", "focal", "focalpixel",
"principal", "principalpixel", "sensorsize", "user"},
{"camera", "*", "20", "name", "class", "orthographic", "fovy", "ipd", "resolution", "pos",
"quat", "axisangle", "xyaxes", "zaxis", "euler", "mode", "target",
"focal", "focalpixel", "principal", "principalpixel", "sensorsize", "user"},
{"light", "*", "16", "name", "class", "directional", "castshadow", "active",
"pos", "dir", "bulbradius", "attenuation", "cutoff", "exponent", "ambient", "diffuse",
"specular", "mode", "target"},
@@ -1742,6 +1742,10 @@ void mjXReader::OneCamera(XMLElement* elem, mjsCamera* pcam) {
ReadAlternative(elem, pcam->alt);
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) ||
ReadAttr(elem, "principal", 2, pcam->principal_length, text);
bool has_focal = ReadAttr(elem, "focalpixel", 2, pcam->focal_pixel, text) ||
@@ -2948,6 +2952,7 @@ void mjXReader::Visual(XMLElement* section) {
string text, name;
XMLElement* elem;
mjVisual* vis = &model->visual;
int n;
// iterate over child elements
elem = FirstChildElement(section);
@@ -2957,6 +2962,9 @@ void mjXReader::Visual(XMLElement* section) {
// global sub-element
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, "ipd", 1, &vis->global.ipd, 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");
}
}
int ellipsoidinertia;
if (MapValue(elem, "ellipsoidinertia", &ellipsoidinertia, bool_map, 2)) {
vis->global.ellipsoidinertia = (ellipsoidinertia==1);
if (MapValue(elem, "ellipsoidinertia", &n, bool_map, 2)) {
vis->global.ellipsoidinertia = (n==1);
}
int bvactive;
if (MapValue(elem, "bvactive", &bvactive, bool_map, 2)) {
vis->global.bvactive = (bvactive==1);
if (MapValue(elem, "bvactive", &n, bool_map, 2)) {
vis->global.bvactive = (n==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);
WriteAttrKey(elem, "mode", camlight_map, camlight_sz, pcam->mode, def->Camera().mode);
WriteAttr(elem, "resolution", 2, pcam->resolution, def->Camera().resolution);
// resolution if positive
WriteAttr(elem, "resolution", 2, pcam->resolution, def->Camera().resolution);
WriteAttrKey(elem, "orthographic", bool_map, 2, pcam->orthographic, def->Camera().orthographic);
// camera intrinsics if specified
if (pcam->sensor_size[0]>0 && pcam->sensor_size[1]>0) {
@@ -1012,15 +1010,16 @@ void mjXWriter::Visual(XMLElement* root) {
// global
elem = InsertEnd(section, "global");
WriteAttr(elem, "fovy", 1, &vis->global.fovy, &visdef.global.fovy);
WriteAttr(elem, "ipd", 1, &vis->global.ipd, &visdef.global.ipd);
WriteAttr(elem, "azimuth", 1, &vis->global.azimuth, &visdef.global.azimuth);
WriteAttr(elem, "elevation", 1, &vis->global.elevation, &visdef.global.elevation);
WriteAttr(elem, "linewidth", 1, &vis->global.linewidth, &visdef.global.linewidth);
WriteAttr(elem, "glow", 1, &vis->global.glow, &visdef.global.glow);
WriteAttr(elem, "realtime", 1, &vis->global.realtime, &visdef.global.realtime);
WriteAttrInt(elem, "offwidth", vis->global.offwidth, visdef.global.offwidth);
WriteAttrInt(elem, "offheight", vis->global.offheight, visdef.global.offheight);
WriteAttrKey(elem, "orthographic", bool_map, 2, vis->global.orthographic, visdef.global.orthographic);
WriteAttr(elem, "fovy", 1, &vis->global.fovy, &visdef.global.fovy);
WriteAttr(elem, "ipd", 1, &vis->global.ipd, &visdef.global.ipd);
WriteAttr(elem, "azimuth", 1, &vis->global.azimuth, &visdef.global.azimuth);
WriteAttr(elem, "elevation", 1, &vis->global.elevation, &visdef.global.elevation);
WriteAttr(elem, "linewidth", 1, &vis->global.linewidth, &visdef.global.linewidth);
WriteAttr(elem, "glow", 1, &vis->global.glow, &visdef.global.glow);
WriteAttr(elem, "realtime", 1, &vis->global.realtime, &visdef.global.realtime);
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, "bvactive", bool_map, 2, vis->global.bvactive, visdef.global.bvactive);
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(
error.data(),
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);
}
+10
View File
@@ -1118,6 +1118,16 @@ TEST_F(XMLWriterTest, TrimsDefaults) {
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) {
static constexpr char xml[] = R"(
<mujoco>
+10 -4
View File
@@ -5013,6 +5013,7 @@ public unsafe struct mjOption_ {
[StructLayout(LayoutKind.Sequential)]
public unsafe struct global {
public int orthographic;
public float fovy;
public float ipd;
public float azimuth;
@@ -5321,11 +5322,12 @@ public unsafe struct mjModel_ {
public double* cam_poscom0;
public double* cam_pos0;
public double* cam_mat0;
public int* cam_resolution;
public int* cam_orthographic;
public double* cam_fovy;
public float* cam_intrinsic;
public float* cam_sensorsize;
public double* cam_ipd;
public int* cam_resolution;
public float* cam_sensorsize;
public float* cam_intrinsic;
public double* cam_user;
public int* light_mode;
public int* light_bodyid;
@@ -5858,6 +5860,7 @@ public unsafe struct mjvCamera_ {
public double distance;
public double azimuth;
public double elevation;
public int orthographic;
}
[StructLayout(LayoutKind.Sequential)]
@@ -5871,6 +5874,7 @@ public unsafe struct mjvGLCamera_ {
public float frustum_top;
public float frustum_near;
public float frustum_far;
public int orthographic;
}
[StructLayout(LayoutKind.Sequential)]
@@ -6184,10 +6188,12 @@ public unsafe struct model {
public int* site_group;
public double* site_size;
public float* site_rgba;
public int* cam_orthographic;
public double* cam_fovy;
public double* cam_ipd;
public float* cam_intrinsic;
public int* cam_resolution;
public float* cam_sensorsize;
public float* cam_intrinsic;
public byte* light_directional;
public byte* light_castshadow;
public float* light_bulbradius;