Refactor camera orthographic attribute to projection enum.

PiperOrigin-RevId: 847069028
Change-Id: I7f686fe654e9b56519cab8bd2f37c9cc40091d3f
This commit is contained in:
Yuval Tassa
2025-12-20 03:27:42 -08:00
committed by Copybara-Service
parent 44c8ef890d
commit b8a4ac5d48
20 changed files with 102 additions and 44 deletions
+10
View File
@@ -166,6 +166,16 @@ elements. These values are used in ``m->geom_type`` and ``m->site_type``.
.. mujoco-include:: mjtGeom
.. _mjtProjection:
mjtProjection
~~~~~~~~~~~~~
Type of camera projection. Used in ``m->cam_projection``.
.. mujoco-include:: mjtProjection
.. _mjtCamLight:
mjtCamLight
+5 -5
View File
@@ -2961,11 +2961,11 @@ 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:
.. _body-camera-projection:
: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.
:at:`projection`: :at-val:`[perspective, orthographic], "perspective"`
Whether the camera uses a perspective (the default) or orthographic projection. Setting this
attribute to "orthographic" changes the semantic of the :ref:`fovy<body-camera-fovy>` attribute, see below.
.. _body-camera-fovy:
@@ -8666,7 +8666,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-projection:
.. _default-camera-fovy:
+2 -2
View File
@@ -302,7 +302,7 @@
| :ref:`camera | \* | :class: mjcf-attributes |
| <body-camera>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<body-camera-name>` | :ref:`class<body-camera-class>` | :ref:`orthographic<body-camera-orthographic>` | :ref:`fovy<body-camera-fovy>` | |
| | | | :ref:`name<body-camera-name>` | :ref:`class<body-camera-class>` | :ref:`projection<body-camera-projection>` | :ref:`fovy<body-camera-fovy>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`ipd<body-camera-ipd>` | :ref:`resolution<body-camera-resolution>` | :ref:`pos<body-camera-pos>` | :ref:`quat<body-camera-quat>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
@@ -1490,7 +1490,7 @@
| :ref:`camera | ? | :class: mjcf-attributes |
| <default-camera>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`orthographic<default-camera-orthographic>` | :ref:`fovy<default-camera-fovy>` | :ref:`ipd<default-camera-ipd>` | :ref:`resolution<default-camera-resolution>` | |
| | | | :ref:`projection<default-camera-projection>` | :ref:`fovy<default-camera-fovy>` | :ref:`ipd<default-camera-ipd>` | :ref:`resolution<default-camera-resolution>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`pos<default-camera-pos>` | :ref:`quat<default-camera-quat>` | :ref:`axisangle<default-camera-axisangle>` | :ref:`xyaxes<default-camera-xyaxes>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+12 -1
View File
@@ -5,6 +5,17 @@ Changelog
Upcoming version (not yet released)
-----------------------------------
.. admonition:: Breaking API changes
:class: attention
- The ``mjModel.cam_orthographic`` field has been renamed to ``cam_projection``, with the semantic of a new enum type
:ref:`mjtProjection`. This will allow for more projection types in the future like fisheye cameras.
Relatedly, the ``camera/orthographic`` MJCF attribute for cameras has been renamed to
:ref:`camera/projection<body-camera-projection>` and now accepts the values ``orthographic`` and ``perspective``.
**Migration:** Replace ``orthographic = "false/true"`` with ``projection="perspective/orthographic"``,
respectively.
General
^^^^^^^
- Non-breaking ABI changes:
@@ -793,7 +804,7 @@ General
:width: 240px
8. 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>`
``camera/orthographic`` and :ref:`global/orthographic<visual-global-orthographic>`
attributes, respectively.
9. Added :ref:`maxhullvert<asset-mesh-maxhullvert>`, the maximum number of vertices in a mesh's convex hull.
10. Added :ref:`mj_setKeyframe` for saving the current state into a model keyframe.
+6 -2
View File
@@ -538,6 +538,10 @@ typedef enum mjtGeom_ { // type of geometric shape
mjGEOM_NONE = 1001 // missing geom type
} mjtGeom;
typedef enum mjtProjection_ { // type of camera projection
mjPROJ_PERSPECTIVE = 0, // perspective
mjPROJ_ORTHOGRAPHIC // orthographic
} mjtProjection;
typedef enum mjtCamLight_ { // tracking mode for camera and light
mjCAMLIGHT_FIXED = 0, // pos and rot fixed in body
mjCAMLIGHT_TRACK, // pos tracks body, rot fixed in global
@@ -1223,7 +1227,7 @@ 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_orthographic; // orthographic camera; 0: no, 1: yes (ncam x 1)
int* cam_projection; // projection type (mjtProjection) (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)
@@ -2102,7 +2106,7 @@ typedef struct mjsCamera_ { // camera specification
mjString* targetbody; // target body for tracking/targeting
// intrinsics
int orthographic; // is camera orthographic
mjtProjection proj; // camera projection type
double fovy; // y-field of view
double ipd; // inter-pupilary distance
float intrinsic[4]; // camera intrinsics (length)
+7 -1
View File
@@ -124,6 +124,12 @@ typedef enum mjtGeom_ { // type of geometric shape
} mjtGeom;
typedef enum mjtProjection_ { // type of camera projection
mjPROJ_PERSPECTIVE = 0, // perspective
mjPROJ_ORTHOGRAPHIC // orthographic
} mjtProjection;
typedef enum mjtCamLight_ { // tracking mode for camera and light
mjCAMLIGHT_FIXED = 0, // pos and rot fixed in body
mjCAMLIGHT_TRACK, // pos tracks body, rot fixed in global
@@ -882,7 +888,7 @@ 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_orthographic; // orthographic camera; 0: no, 1: yes (ncam x 1)
int* cam_projection; // projection type (mjtProjection) (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)
+1 -1
View File
@@ -373,7 +373,7 @@ typedef struct mjsCamera_ { // camera specification
mjString* targetbody; // target body for tracking/targeting
// intrinsics
int orthographic; // is camera orthographic
mjtProjection proj; // camera projection type
double fovy; // y-field of view
double ipd; // inter-pupilary distance
float intrinsic[4]; // camera intrinsics (length)
+1 -1
View File
@@ -302,7 +302,7 @@
X ( mjtNum, cam_poscom0, ncam, 3 ) \
X ( mjtNum, cam_pos0, ncam, 3 ) \
X ( mjtNum, cam_mat0, ncam, 9 ) \
X ( int, cam_orthographic, ncam, 1 ) \
X ( int, cam_projection, ncam, 1 ) \
X ( mjtNum, cam_fovy, ncam, 1 ) \
X ( mjtNum, cam_ipd, ncam, 1 ) \
X ( int, cam_resolution, ncam, 2 ) \
+9
View File
@@ -101,6 +101,15 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjGEOM_NONE', 1001),
]),
)),
('mjtProjection',
EnumDecl(
name='mjtProjection',
declname='enum mjtProjection_',
values=dict([
('mjPROJ_PERSPECTIVE', 0),
('mjPROJ_ORTHOGRAPHIC', 1),
]),
)),
('mjtCamLight',
EnumDecl(
name='mjtCamLight',
+5 -5
View File
@@ -2244,11 +2244,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
array_extent=('ncam', 9),
),
StructFieldDecl(
name='cam_orthographic',
name='cam_projection',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='orthographic camera; 0: no, 1: yes',
doc='projection type (mjtProjection)',
array_extent=('ncam',),
),
StructFieldDecl(
@@ -7566,9 +7566,9 @@ STRUCTS: Mapping[str, StructDecl] = dict([
doc='target body for tracking/targeting',
),
StructFieldDecl(
name='orthographic',
type=ValueType(name='int'),
doc='is camera orthographic',
name='proj',
type=ValueType(name='mjtProjection'),
doc='camera projection type',
),
StructFieldDecl(
name='fovy',
+4 -2
View File
@@ -295,8 +295,10 @@ class SpecsTest(absltest.TestCase):
self.assertEqual(site.info, 'info')
# Add camera.
cam = body.add_camera(orthographic=1, resolution=[10, 20])
self.assertEqual(cam.orthographic, 1)
cam = body.add_camera(
proj=mujoco.mjtProjection.mjPROJ_ORTHOGRAPHIC, resolution=[10, 20]
)
self.assertEqual(cam.proj, 1)
np.testing.assert_array_equal(cam.resolution, [10, 20])
# Add frame.
+2 -2
View File
@@ -522,7 +522,7 @@ void mjv_cameraFrustum(float zver[2], float zhor[2], float zclip[2], const mjMod
if (cid < 0 || cid >= m->ncam) {
mjERROR("fixed camera id is outside valid range");
}
orthographic = m->cam_orthographic[cid];
orthographic = m->cam_projection[cid] == mjPROJ_ORTHOGRAPHIC;
fovy = m->cam_fovy[cid];
// if positive sensorsize, get sensorsize and intrinsic
@@ -2763,7 +2763,7 @@ void mjv_updateCamera(const mjModel* m, const mjData* d, mjvCamera* cam, mjvScen
mjERROR("fixed camera id is outside valid range");
}
ipd = m->cam_ipd[cid];
orthographic = m->cam_orthographic[cid];
orthographic = m->cam_projection[cid] == mjPROJ_ORTHOGRAPHIC;
break;
default:
+1 -1
View File
@@ -2864,7 +2864,7 @@ void mjCModel::CopyTree(mjModel* m) {
m->cam_targetbodyid[cid] = pc->targetbodyid;
mjuu_copyvec(m->cam_pos+3*cid, pc->pos, 3);
mjuu_copyvec(m->cam_quat+4*cid, pc->quat, 4);
m->cam_orthographic[cid] = pc->orthographic;
m->cam_projection[cid] = pc->proj;
m->cam_fovy[cid] = (mjtNum)pc->fovy;
m->cam_ipd[cid] = (mjtNum)pc->ipd;
mjuu_copyvec(m->cam_resolution+2*cid, pc->resolution, 2);
+2
View File
@@ -27,6 +27,7 @@
// keyword maps (defined in implementation files)
extern const int joint_sz;
extern const int bodysleep_sz;
extern const int projection_sz;
extern const int camlight_sz;
extern const int lighttype_sz;
extern const int integrator_sz;
@@ -53,6 +54,7 @@ extern const mjMap TFAuto_map[];
extern const mjMap joint_map[];
extern const mjMap bodysleep_map[];
extern const mjMap geom_map[];
extern const mjMap projection_map[];
extern const mjMap camlight_map[];
extern const mjMap lighttype_map[];
extern const mjMap integrator_map[];
+11 -4
View File
@@ -171,7 +171,7 @@ std::vector<const char*> MJCF[nMJCF] = {
"hfield", "mesh", "fitscale", "rgba", "fluidshape", "fluidcoef", "user"},
{"site", "?", "type", "group", "pos", "quat", "material",
"size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"camera", "?", "orthographic", "fovy", "ipd", "resolution", "pos", "quat",
{"camera", "?", "projection", "fovy", "ipd", "resolution", "pos", "quat",
"axisangle", "xyaxes", "zaxis", "euler", "mode", "focal", "focalpixel",
"principal", "principalpixel", "sensorsize", "user"},
{"light", "?", "pos", "dir", "bulbradius", "intensity", "range",
@@ -285,7 +285,7 @@ std::vector<const char*> MJCF[nMJCF] = {
{"attach", "*", "model", "body", "prefix"},
{"site", "*", "name", "class", "type", "group", "pos", "quat",
"material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"camera", "*", "name", "class", "orthographic", "fovy", "ipd", "resolution", "pos",
{"camera", "*", "name", "class", "projection", "fovy", "ipd", "resolution", "pos",
"quat", "axisangle", "xyaxes", "zaxis", "euler", "mode", "target",
"focal", "focalpixel", "principal", "principalpixel", "sensorsize", "user"},
{"light", "*", "name", "class", "directional", "type", "castshadow", "active",
@@ -590,6 +590,13 @@ const mjMap geom_map[mjNGEOMTYPES] = {
};
// projection type
const int projection_sz = 2;
const mjMap projection_map[projection_sz] = {
{"perspective", mjPROJ_PERSPECTIVE},
{"orthographic", mjPROJ_ORTHOGRAPHIC}
};
// camlight type
const int camlight_sz = 5;
const mjMap camlight_map[camlight_sz] = {
@@ -1926,8 +1933,8 @@ void mjXReader::OneCamera(XMLElement* elem, mjsCamera* camera) {
ReadAlternative(elem, camera->alt);
ReadAttr(elem, "ipd", 1, &camera->ipd, text);
if (MapValue(elem, "orthographic", &n, bool_map, 2)) {
camera->orthographic = (n == 1);
if (MapValue(elem, "projection", &n, projection_map, 2)) {
camera->proj = (mjtProjection)n;
}
bool has_principal = ReadAttr(elem, "principalpixel", 2, camera->principal_pixel, text) ||
+1 -1
View File
@@ -573,7 +573,7 @@ void mjXWriter::OneCamera(XMLElement* elem, const mjCCamera* camera, mjCDef* def
WriteAttr(elem, "ipd", 1, &camera->ipd, &def->Camera().ipd);
WriteAttrKey(elem, "mode", camlight_map, camlight_sz, camera->mode, def->Camera().mode);
WriteAttr(elem, "resolution", 2, camera->resolution, def->Camera().resolution);
WriteAttrKey(elem, "orthographic", bool_map, 2, camera->orthographic, def->Camera().orthographic);
WriteAttrKey(elem, "projection", projection_map, projection_sz, camera->proj, def->Camera().proj);
// camera intrinsics if specified
if (camera->sensor_size[0] > 0 && camera->sensor_size[1] > 0) {
+3 -3
View File
@@ -13,7 +13,7 @@
</asset>
<default>
<camera orthographic="true"/>
<camera projection="orthographic"/>
</default>
<worldbody>
@@ -26,9 +26,9 @@
</body>
<!-- box should pefectly fit within field-of-view -->
<camera name="fovy = 1" pos=".5 .5 2" orthographic="true" fovy="1"/>
<camera name="fovy = 1" pos=".5 .5 2" projection="orthographic" 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 -->
<camera name="fovy = 2" pos="0 0 2" fovy="2"/> <!-- inherits projection="orthographic" from defaults -->
</worldbody>
</mujoco>
+4 -4
View File
@@ -2274,11 +2274,11 @@ TEST_F(XMLReaderTest, Orthographic) {
</visual>
<default>
<camera orthographic="true"/>
<camera projection="orthographic"/>
</default>
<worldbody>
<camera name="fovy=1" pos=".5 .5 2" orthographic="true" fovy="1"/>
<camera name="fovy=1" pos=".5 .5 2" projection="orthographic" fovy="1"/>
<camera name="fovy=2" pos="0 0 2" fovy="2"/>
</worldbody>
</mujoco>
@@ -2288,8 +2288,8 @@ TEST_F(XMLReaderTest, Orthographic) {
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_projection[0], mjPROJ_ORTHOGRAPHIC);
EXPECT_EQ(model->cam_projection[1], mjPROJ_ORTHOGRAPHIC);
EXPECT_EQ(model->cam_fovy[0], 1);
EXPECT_EQ(model->cam_fovy[1], 2);
+5 -1
View File
@@ -221,6 +221,10 @@ public enum mjtGeom : int{
mjGEOM_TRIANGLE = 108,
mjGEOM_NONE = 1001,
}
public enum mjtProjection : int{
mjPROJ_PERSPECTIVE = 0,
mjPROJ_ORTHOGRAPHIC = 1,
}
public enum mjtCamLight : int{
mjCAMLIGHT_FIXED = 0,
mjCAMLIGHT_TRACK = 1,
@@ -5474,7 +5478,7 @@ public unsafe struct mjModel_ {
public double* cam_poscom0;
public double* cam_pos0;
public double* cam_mat0;
public int* cam_orthographic;
public int* cam_projection;
public double* cam_fovy;
public double* cam_ipd;
public int* cam_resolution;
+11 -8
View File
@@ -3129,11 +3129,11 @@ struct MjsCamera {
*(ptr_->targetbody) = value;
}
}
int orthographic() const {
return ptr_->orthographic;
mjtProjection proj() const {
return ptr_->proj;
}
void set_orthographic(int value) {
ptr_->orthographic = value;
void set_proj(mjtProjection value) {
ptr_->proj = value;
}
double fovy() const {
return ptr_->fovy;
@@ -4162,8 +4162,8 @@ struct MjModel {
emscripten::val cam_mat0() const {
return emscripten::val(emscripten::typed_memory_view(ptr_->ncam * 9, ptr_->cam_mat0));
}
emscripten::val cam_orthographic() const {
return emscripten::val(emscripten::typed_memory_view(ptr_->ncam, ptr_->cam_orthographic));
emscripten::val cam_projection() const {
return emscripten::val(emscripten::typed_memory_view(ptr_->ncam, ptr_->cam_projection));
}
emscripten::val cam_fovy() const {
return emscripten::val(emscripten::typed_memory_view(ptr_->ncam, ptr_->cam_fovy));
@@ -10503,6 +10503,9 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
.value("mjPLUGIN_SENSOR", mjPLUGIN_SENSOR)
.value("mjPLUGIN_PASSIVE", mjPLUGIN_PASSIVE)
.value("mjPLUGIN_SDF", mjPLUGIN_SDF);
enum_<mjtProjection>("mjtProjection")
.value("mjPROJ_PERSPECTIVE", mjPROJ_PERSPECTIVE)
.value("mjPROJ_ORTHOGRAPHIC", mjPROJ_ORTHOGRAPHIC);
enum_<mjtRndFlag>("mjtRndFlag")
.value("mjRND_SHADOW", mjRND_SHADOW)
.value("mjRND_WIREFRAME", mjRND_WIREFRAME)
@@ -11032,10 +11035,10 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
.property("cam_ipd", &MjModel::cam_ipd)
.property("cam_mat0", &MjModel::cam_mat0)
.property("cam_mode", &MjModel::cam_mode)
.property("cam_orthographic", &MjModel::cam_orthographic)
.property("cam_pos", &MjModel::cam_pos)
.property("cam_pos0", &MjModel::cam_pos0)
.property("cam_poscom0", &MjModel::cam_poscom0)
.property("cam_projection", &MjModel::cam_projection)
.property("cam_quat", &MjModel::cam_quat)
.property("cam_resolution", &MjModel::cam_resolution)
.property("cam_sensorsize", &MjModel::cam_sensorsize)
@@ -11733,10 +11736,10 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
.property("intrinsic", &MjsCamera::intrinsic)
.property("ipd", &MjsCamera::ipd, &MjsCamera::set_ipd, reference())
.property("mode", &MjsCamera::mode, &MjsCamera::set_mode, reference())
.property("orthographic", &MjsCamera::orthographic, &MjsCamera::set_orthographic, reference())
.property("pos", &MjsCamera::pos)
.property("principal_length", &MjsCamera::principal_length)
.property("principal_pixel", &MjsCamera::principal_pixel)
.property("proj", &MjsCamera::proj, &MjsCamera::set_proj, reference())
.property("quat", &MjsCamera::quat)
.property("resolution", &MjsCamera::resolution)
.property("sensor_size", &MjsCamera::sensor_size)