From 608115ab9530875e77081a2f98270842781fb0bc Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Wed, 7 Jan 2026 10:19:35 -0800 Subject: [PATCH] Add `output` attribute to camera elements. This attribute allows specifying the intended output types (e.g., RGB, depth, normal) for each camera in the XML. The output types are stored as bit flags in `mjModel`. PiperOrigin-RevId: 853316261 Change-Id: I1ad2e94102593cf402756f8f4c80509371ca86d2 --- doc/XMLreference.rst | 16 ++++++++++ doc/XMLschema.rst | 6 ++++ doc/changelog.rst | 2 ++ doc/includes/references.h | 11 +++++++ include/mujoco/mjmodel.h | 12 +++++++ include/mujoco/mjspec.h | 1 + include/mujoco/mjxmacro.h | 1 + python/mujoco/introspect/enums.py | 13 ++++++++ python/mujoco/introspect/structs.py | 13 ++++++++ src/user/user_init.c | 26 +-------------- src/user/user_model.cc | 1 + src/xml/xml_base.h | 2 ++ src/xml/xml_native_reader.cc | 22 +++++++++++-- src/xml/xml_native_writer.cc | 13 ++++++++ test/xml/xml_native_reader_test.cc | 46 ++++++++++++++++++++++++++ test/xml/xml_native_writer_test.cc | 48 ++++++++++++++++++++++++++++ unity/Runtime/Bindings/MjBindings.cs | 9 ++++++ wasm/codegen/generated/bindings.cc | 18 +++++++++++ 18 files changed, 233 insertions(+), 27 deletions(-) diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index bbf794a0..0ff9e211 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -2988,6 +2988,20 @@ and the +Y axis points up. Thus the frame position and orientation are the key a location to save the required resolution. Setting either value larger than 1 enables frustum visualization when the :ref:`mjVIS_CAMERA` visualization flag is active. +.. _body-camera-output: + +:at:`output`: :at-val:`[rgb, depth, distance, normal, segmentation], "rgb"` + Types of output images supported by the camera. + + - :at-val:`rgb`: RGB image. + - :at-val:`depth`: Depth image (distance from camera plane). + - :at-val:`distance`: Distance image (distance from camera origin). + - :at-val:`normal`: Surface normal image. + - :at-val:`segmentation`: Segmentation image. + + This attribute is not used for rendering, but serves as a convenient location to save the output types supported by + the camera. The :at:`output` attribute can contain multiple types, e.g. :at-val:`"rgb normal"`. + .. _body-camera-sensorsize: :at:`sensorsize`: :at-val:`real(2), "0 0"` @@ -8715,6 +8729,8 @@ if omitted. .. _default-camera-resolution: +.. _default-camera-output: + .. _default-camera-ipd: .. _default-camera-pos: diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index e750334e..e9eca9d5 100755 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -1045,6 +1045,9 @@ .. grid-item:: :ref:`resolution` + .. grid-item:: + :ref:`output` + .. grid-item:: :ref:`pos` @@ -4705,6 +4708,9 @@ .. grid-item:: :ref:`resolution` + .. grid-item:: + :ref:`output` + .. grid-item:: :ref:`pos` diff --git a/doc/changelog.rst b/doc/changelog.rst index edb8faa9..37d7592d 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -30,6 +30,8 @@ General attribute. In this case, the sensor will cast multiple ray, one for each pixel. - Rangefinder sensors can now now report various kinds of information besides ray distances, including surface normals. See :ref:`rangefinder` for details. +- Cameras now have an :ref:`output` attribute, parsed into the ``mjModel.cam_output`` bitfield. + Unused by the renderer, it serves as a convenient location to store a camera's supported output types. - Non-breaking ABI changes: - The type of the ``sig`` (signature) argument of :ref:`mj_stateSize` and related functions has been changed from diff --git a/doc/includes/references.h b/doc/includes/references.h index 519bfe52..10a40eb1 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -786,6 +786,15 @@ typedef enum mjtRayDataField_ { // data fields returned by rangefinder sensors mjNRAYDATA // number of rangefinder sensor data fields } mjtRayDataField; +typedef enum mjtCamOutBit_ { // camera output type bitflags + mjCAMOUT_RGB = 1<<0, // RGB image + mjCAMOUT_DEPTH = 1<<1, // depth image (distance from camera plane) + mjCAMOUT_DIST = 1<<2, // distance image (distance from camera origin) + mjCAMOUT_NORMAL = 1<<3, // normal image + mjCAMOUT_SEG = 1<<4, // segmentation image + + mjNCAMOUT = 5 // number of camera output types +} mjtCamOutBit; typedef enum mjtSameFrame_ { // frame alignment of bodies with their children mjSAMEFRAME_NONE = 0, // no alignment mjSAMEFRAME_BODY, // frame is same as body frame @@ -1240,6 +1249,7 @@ struct mjModel_ { 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) + int* cam_output; // output types (mjtCamOut bit flags) (ncam x 1) 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) @@ -2117,6 +2127,7 @@ typedef struct mjsCamera_ { // camera specification // intrinsics mjtProjection proj; // camera projection type int resolution[2]; // resolution (pixel) + int output; // bit flags for output type double fovy; // y-field of view double ipd; // inter-pupillary distance float intrinsic[4]; // camera intrinsics (length) diff --git a/include/mujoco/mjmodel.h b/include/mujoco/mjmodel.h index 64036073..7118ec54 100644 --- a/include/mujoco/mjmodel.h +++ b/include/mujoco/mjmodel.h @@ -416,6 +416,17 @@ typedef enum mjtRayDataField_ { // data fields returned by rangefinder sensors } mjtRayDataField; +typedef enum mjtCamOutBit_ { // camera output type bitflags + mjCAMOUT_RGB = 1<<0, // RGB image + mjCAMOUT_DEPTH = 1<<1, // depth image (distance from camera plane) + mjCAMOUT_DIST = 1<<2, // distance image (distance from camera origin) + mjCAMOUT_NORMAL = 1<<3, // normal image + mjCAMOUT_SEG = 1<<4, // segmentation image + + mjNCAMOUT = 5 // number of camera output types +} mjtCamOutBit; + + typedef enum mjtSameFrame_ { // frame alignment of bodies with their children mjSAMEFRAME_NONE = 0, // no alignment mjSAMEFRAME_BODY, // frame is same as body frame @@ -903,6 +914,7 @@ struct mjModel_ { 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) + int* cam_output; // output types (mjtCamOut bit flags) (ncam x 1) 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) diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index 70a0c403..b44a8711 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -375,6 +375,7 @@ typedef struct mjsCamera_ { // camera specification // intrinsics mjtProjection proj; // camera projection type int resolution[2]; // resolution (pixel) + int output; // bit flags for output type double fovy; // y-field of view double ipd; // inter-pupillary distance float intrinsic[4]; // camera intrinsics (length) diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index 077b44d3..efac7fd1 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -306,6 +306,7 @@ X ( mjtNum, cam_fovy, ncam, 1 ) \ X ( mjtNum, cam_ipd, ncam, 1 ) \ X ( int, cam_resolution, ncam, 2 ) \ + X ( int, cam_output, ncam, 1 ) \ X ( float, cam_sensorsize, ncam, 2 ) \ X ( float, cam_intrinsic, ncam, 4 ) \ X ( mjtNum, cam_user, ncam, MJ_M(nuser_cam) ) diff --git a/python/mujoco/introspect/enums.py b/python/mujoco/introspect/enums.py index 9dc5da46..2c0924b1 100644 --- a/python/mujoco/introspect/enums.py +++ b/python/mujoco/introspect/enums.py @@ -430,6 +430,19 @@ ENUMS: Mapping[str, EnumDecl] = dict([ ('mjNRAYDATA', 6), ]), )), + ('mjtCamOutBit', + EnumDecl( + name='mjtCamOutBit', + declname='enum mjtCamOutBit_', + values=dict([ + ('mjCAMOUT_RGB', 1), + ('mjCAMOUT_DEPTH', 2), + ('mjCAMOUT_DIST', 4), + ('mjCAMOUT_NORMAL', 8), + ('mjCAMOUT_SEG', 16), + ('mjNCAMOUT', 5), + ]), + )), ('mjtSameFrame', EnumDecl( name='mjtSameFrame', diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index d45c7582..bf27c58d 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -2275,6 +2275,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([ doc='resolution: pixels [width, height]', array_extent=('ncam', 2), ), + StructFieldDecl( + name='cam_output', + type=PointerType( + inner_type=ValueType(name='int'), + ), + doc='output types (mjtCamOut bit flags)', + array_extent=('ncam',), + ), StructFieldDecl( name='cam_sensorsize', type=PointerType( @@ -7578,6 +7586,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), doc='resolution (pixel)', ), + StructFieldDecl( + name='output', + type=ValueType(name='int'), + doc='bit flags for output type', + ), StructFieldDecl( name='fovy', type=ValueType(name='double'), diff --git a/src/user/user_init.c b/src/user/user_init.c index acd9d3dd..82db341b 100644 --- a/src/user/user_init.c +++ b/src/user/user_init.c @@ -21,7 +21,6 @@ #include "user/user_api.h" - // default model attributes void mjs_defaultSpec(mjSpec* spec) { memset(spec, 0, sizeof(mjSpec)); @@ -66,14 +65,12 @@ void mjs_defaultSpec(mjSpec* spec) { } - // default orientation attributes void mjs_defaultOrientation(mjsOrientation* orient) { memset(orient, 0, sizeof(mjsOrientation)); } - // default body attributes void mjs_defaultBody(mjsBody* body) { memset(body, 0, sizeof(mjsBody)); @@ -88,7 +85,6 @@ void mjs_defaultBody(mjsBody* body) { } - // default frame attributes void mjs_defaultFrame(mjsFrame* frame) { memset(frame, 0, sizeof(mjsFrame)); @@ -96,7 +92,6 @@ void mjs_defaultFrame(mjsFrame* frame) { } - // default joint attributes void mjs_defaultJoint(mjsJoint* joint) { memset(joint, 0, sizeof(mjsJoint)); @@ -110,7 +105,6 @@ void mjs_defaultJoint(mjsJoint* joint) { } - // default geom attributes void mjs_defaultGeom(mjsGeom* geom) { memset(geom, 0, sizeof(mjsGeom)); @@ -153,7 +147,6 @@ void mjs_defaultGeom(mjsGeom* geom) { } - // default site attributes void mjs_defaultSite(mjsSite* site) { memset(site, 0, sizeof(mjsSite)); @@ -172,7 +165,6 @@ void mjs_defaultSite(mjsSite* site) { } - // default cam attributes void mjs_defaultCamera(mjsCamera* cam) { memset(cam, 0, sizeof(mjsCamera)); @@ -187,10 +179,10 @@ void mjs_defaultCamera(mjsCamera* cam) { cam->fovy = 45; cam->ipd = 0.068; cam->resolution[0] = cam->resolution[1] = 1; + cam->output = mjCAMOUT_RGB; } - // default light attributes void mjs_defaultLight(mjsLight* light) { memset(light, 0, sizeof(mjsLight)); @@ -215,7 +207,6 @@ void mjs_defaultLight(mjsLight* light) { } - // default flex attributes void mjs_defaultFlex(mjsFlex* flex) { memset(flex, 0, sizeof(mjsFlex)); @@ -242,7 +233,6 @@ void mjs_defaultFlex(mjsFlex* flex) { } - // default mesh attributes void mjs_defaultMesh(mjsMesh* mesh) { memset(mesh, 0, sizeof(mjsMesh)); @@ -253,14 +243,12 @@ void mjs_defaultMesh(mjsMesh* mesh) { } - // default height field attributes void mjs_defaultHField(mjsHField* hfield) { memset(hfield, 0, sizeof(mjsHField)); } - // default skin attributes void mjs_defaultSkin(mjsSkin* skin) { memset(skin, 0, sizeof(mjsSkin)); @@ -269,7 +257,6 @@ void mjs_defaultSkin(mjsSkin* skin) { } - // default texture attributes void mjs_defaultTexture(mjsTexture* texture) { memset(texture, 0, sizeof(mjsTexture)); @@ -285,7 +272,6 @@ void mjs_defaultTexture(mjsTexture* texture) { } - // default material attributes void mjs_defaultMaterial(mjsMaterial* material) { memset(material, 0, sizeof(mjsMaterial)); @@ -298,7 +284,6 @@ void mjs_defaultMaterial(mjsMaterial* material) { } - // default pair attributes void mjs_defaultPair(mjsPair* pair) { memset(pair, 0, sizeof(mjsPair)); @@ -312,7 +297,6 @@ void mjs_defaultPair(mjsPair* pair) { } - // default equality attributes void mjs_defaultEquality(mjsEquality* equality) { memset(equality, 0, sizeof(mjsEquality)); @@ -324,7 +308,6 @@ void mjs_defaultEquality(mjsEquality* equality) { } - // default tendon attributes void mjs_defaultTendon(mjsTendon* tendon) { memset(tendon, 0, sizeof(mjsTendon)); @@ -338,7 +321,6 @@ void mjs_defaultTendon(mjsTendon* tendon) { } - // default actuator attributes void mjs_defaultActuator(mjsActuator* actuator) { memset(actuator, 0, sizeof(mjsActuator)); @@ -364,7 +346,6 @@ void mjs_defaultActuator(mjsActuator* actuator) { } - // default sensor attributes void mjs_defaultSensor(mjsSensor* sensor) { memset(sensor, 0, sizeof(mjsSensor)); @@ -375,35 +356,30 @@ void mjs_defaultSensor(mjsSensor* sensor) { } - // Default numeric attributes. void mjs_defaultNumeric(mjsNumeric* numeric) { memset(numeric, 0, sizeof(mjsNumeric)); } - // Default text attributes. void mjs_defaultText(mjsText* text) { memset(text, 0, sizeof(mjsText)); } - // Default tuple attributes. void mjs_defaultTuple(mjsTuple* tuple) { memset(tuple, 0, sizeof(mjsTuple)); } - // Default keyframe attributes. void mjs_defaultKey(mjsKey* key) { memset(key, 0, sizeof(mjsKey)); } - // default plugin attributes void mjs_defaultPlugin(mjsPlugin* plugin) { memset(plugin, 0, sizeof(mjsPlugin)); diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 4001c5cb..dc1fa88b 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -2868,6 +2868,7 @@ void mjCModel::CopyTree(mjModel* m) { m->cam_fovy[cid] = (mjtNum)pc->fovy; m->cam_ipd[cid] = (mjtNum)pc->ipd; mjuu_copyvec(m->cam_resolution+2*cid, pc->resolution, 2); + m->cam_output[cid] = pc->output; mjuu_copyvec(m->cam_sensorsize+2*cid, pc->sensor_size, 2); mjuu_copyvec(m->cam_intrinsic+4*cid, pc->intrinsic, 4); mjuu_copyvec(m->cam_user+nuser_cam*cid, pc->get_userdata().data(), nuser_cam); diff --git a/src/xml/xml_base.h b/src/xml/xml_base.h index 4eee0698..3a1bed5b 100644 --- a/src/xml/xml_base.h +++ b/src/xml/xml_base.h @@ -45,6 +45,7 @@ extern const int gain_sz; extern const int bias_sz; extern const int stage_sz; extern const int datatype_sz; +extern const int camout_sz; extern const int reduce_sz; extern const mjMap angle_map[]; extern const mjMap enable_map[]; @@ -77,6 +78,7 @@ extern const mjMap stage_map[]; extern const mjMap datatype_map[]; extern const mjMap condata_map[]; extern const mjMap raydata_map[]; +extern const mjMap camout_map[]; extern const mjMap reduce_map[]; extern const mjMap meshtype_map[]; extern const mjMap meshinertia_map[]; diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 9c05102d..eb164dd3 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -171,7 +171,7 @@ std::vector MJCF[nMJCF] = { "hfield", "mesh", "fitscale", "rgba", "fluidshape", "fluidcoef", "user"}, {"site", "?", "type", "group", "pos", "quat", "material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"}, - {"camera", "?", "projection", "fovy", "ipd", "resolution", "pos", "quat", + {"camera", "?", "projection", "fovy", "ipd", "resolution", "output", "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 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", "projection", "fovy", "ipd", "resolution", "pos", + {"camera", "*", "name", "class", "projection", "fovy", "ipd", "resolution", "output", "pos", "quat", "axisangle", "xyaxes", "zaxis", "euler", "mode", "target", "focal", "focalpixel", "principal", "principalpixel", "sensorsize", "user"}, {"light", "*", "name", "class", "directional", "type", "castshadow", "active", @@ -793,6 +793,13 @@ const mjMap raydata_map[mjNRAYDATA] = { {"depth", mjRAYDATA_DEPTH} }; +// camera output type +const int camout_sz = mjNCAMOUT; +const mjMap camout_map[mjNCAMOUT] = {{"rgb", mjCAMOUT_RGB}, + {"depth", mjCAMOUT_DEPTH}, + {"distance", mjCAMOUT_DIST}, + {"normal", mjCAMOUT_NORMAL}, + {"segmentation", mjCAMOUT_SEG}}; // contact reduction type const int reduce_sz = 4; @@ -1953,6 +1960,17 @@ void mjXReader::OneCamera(XMLElement* elem, mjsCamera* camera) { ReadAttr(elem, "focalpixel", 2, camera->focal_pixel, text); ReadAttr(elem, "focal", 2, camera->focal_length, text); ReadAttr(elem, "resolution", 2, camera->resolution, text); + + // read output attribute as space-separated bitflags + std::vector outvals(mjNCAMOUT); + int nout = MapValues(elem, "output", outvals.data(), camout_map, mjNCAMOUT); + if (nout) { + camera->output = 0; + for (int i = 0; i < nout; ++i) { + camera->output |= outvals[i]; + } + } + bool sensorsize = ReadAttr(elem, "sensorsize", 2, camera->sensor_size, text); bool fovy = ReadAttr(elem, "fovy", 1, &camera->fovy, text); if (fovy && sensorsize) { diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index a2bc6961..1840f605 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -573,6 +573,19 @@ 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); + + // write output attribute if different from default + if (camera->output != def->Camera().output) { + int data[mjNCAMOUT]; + int ndata = 0; + for (int i = 0; i < mjNCAMOUT; i++) { + if (camera->output & camout_map[i].value) { + data[ndata++] = camout_map[i].value; + } + } + WriteAttrKeys(elem, "output", camout_map, camout_sz, data, ndata, 0); + } + WriteAttrKey(elem, "projection", projection_map, projection_sz, camera->proj, def->Camera().proj); // camera intrinsics if specified diff --git a/test/xml/xml_native_reader_test.cc b/test/xml/xml_native_reader_test.cc index 862c1743..0932f1d3 100644 --- a/test/xml/xml_native_reader_test.cc +++ b/test/xml/xml_native_reader_test.cc @@ -3244,5 +3244,51 @@ TEST_F(XMLReaderTest, LightRadius) { mj_deleteModel(model); } +TEST_F(XMLReaderTest, CameraOutput) { + static constexpr char xml[] = R"( + + + + + + + + )"; + std::array error; + mjModel* model = LoadModelFromString(xml, error.data(), error.size()); + ASSERT_THAT(model, NotNull()) << error.data(); + EXPECT_EQ(model->ncam, 3); + EXPECT_EQ(model->cam_output[0], mjCAMOUT_RGB); // default + EXPECT_EQ(model->cam_output[1], mjCAMOUT_DEPTH); + EXPECT_EQ(model->cam_output[2], + mjCAMOUT_RGB | mjCAMOUT_NORMAL | mjCAMOUT_SEG); + mj_deleteModel(model); +} + +TEST_F(XMLReaderTest, CameraOutputDefault) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + )"; + std::array error; + mjModel* model = LoadModelFromString(xml, error.data(), error.size()); + ASSERT_THAT(model, NotNull()) << error.data(); + EXPECT_EQ(model->ncam, 3); + EXPECT_EQ(model->cam_output[0], mjCAMOUT_RGB); // main default + EXPECT_EQ(model->cam_output[1], mjCAMOUT_DEPTH | mjCAMOUT_DIST); + EXPECT_EQ(model->cam_output[2], mjCAMOUT_NORMAL); + mj_deleteModel(model); +} + } // namespace } // namespace mujoco diff --git a/test/xml/xml_native_writer_test.cc b/test/xml/xml_native_writer_test.cc index 82bafd81..d5181652 100644 --- a/test/xml/xml_native_writer_test.cc +++ b/test/xml/xml_native_writer_test.cc @@ -1672,5 +1672,53 @@ TEST_F(XMLWriterTest, ExpandAttach) { mj_deleteVFS(vfs.get()); } +TEST_F(XMLWriterTest, WritesCameraOutput) { + static constexpr char xml[] = R"( + + + + + + + )"; + mjModel* model = LoadModelFromString(xml); + ASSERT_THAT(model, NotNull()); + std::string saved_xml = SaveAndReadXml(model); + // default output="rgb" should not be written + EXPECT_THAT(saved_xml, Not(HasSubstr("output=\"rgb\""))); + // non-default output should be written + EXPECT_THAT(saved_xml, HasSubstr("output=\"depth normal\"")); + mj_deleteModel(model); +} + +TEST_F(XMLWriterTest, WritesCameraOutputDefault) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + )"; + mjModel* model = LoadModelFromString(xml); + ASSERT_THAT(model, NotNull()); + std::string saved_xml = SaveAndReadXml(model); + // save and reload to verify round-trip + mjModel* mtemp = LoadModelFromString(saved_xml); + ASSERT_THAT(mtemp, NotNull()); + EXPECT_EQ(mtemp->ncam, 3); + EXPECT_EQ(mtemp->cam_output[0], mjCAMOUT_RGB); + EXPECT_EQ(mtemp->cam_output[1], mjCAMOUT_DEPTH | mjCAMOUT_DIST); + EXPECT_EQ(mtemp->cam_output[2], mjCAMOUT_SEG); + mj_deleteModel(mtemp); + mj_deleteModel(model); +} + } // namespace } // namespace mujoco diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 30b42ecd..d59ea93d 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -440,6 +440,14 @@ public enum mjtRayDataField : int{ mjRAYDATA_DEPTH = 5, mjNRAYDATA = 6, } +public enum mjtCamOutBit : int{ + mjCAMOUT_RGB = 1, + mjCAMOUT_DEPTH = 2, + mjCAMOUT_DIST = 4, + mjCAMOUT_NORMAL = 8, + mjCAMOUT_SEG = 16, + mjNCAMOUT = 5, +} public enum mjtSameFrame : int{ mjSAMEFRAME_NONE = 0, mjSAMEFRAME_BODY = 1, @@ -5491,6 +5499,7 @@ public unsafe struct mjModel_ { public double* cam_fovy; public double* cam_ipd; public int* cam_resolution; + public int* cam_output; public float* cam_sensorsize; public float* cam_intrinsic; public double* cam_user; diff --git a/wasm/codegen/generated/bindings.cc b/wasm/codegen/generated/bindings.cc index e56faa41..5e00d5f2 100644 --- a/wasm/codegen/generated/bindings.cc +++ b/wasm/codegen/generated/bindings.cc @@ -3138,6 +3138,12 @@ struct MjsCamera { emscripten::val resolution() const { return emscripten::val(emscripten::typed_memory_view(2, ptr_->resolution)); } + int output() const { + return ptr_->output; + } + void set_output(int value) { + ptr_->output = value; + } double fovy() const { return ptr_->fovy; } @@ -4174,6 +4180,9 @@ struct MjModel { emscripten::val cam_resolution() const { return emscripten::val(emscripten::typed_memory_view(ptr_->ncam * 2, ptr_->cam_resolution)); } + emscripten::val cam_output() const { + return emscripten::val(emscripten::typed_memory_view(ptr_->ncam, ptr_->cam_output)); + } emscripten::val cam_sensorsize() const { return emscripten::val(emscripten::typed_memory_view(ptr_->ncam * 2, ptr_->cam_sensorsize)); } @@ -10191,6 +10200,13 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .value("mjCAMLIGHT_TRACKCOM", mjCAMLIGHT_TRACKCOM) .value("mjCAMLIGHT_TARGETBODY", mjCAMLIGHT_TARGETBODY) .value("mjCAMLIGHT_TARGETBODYCOM", mjCAMLIGHT_TARGETBODYCOM); + enum_("mjtCamOutBit") + .value("mjCAMOUT_RGB", mjCAMOUT_RGB) + .value("mjCAMOUT_DEPTH", mjCAMOUT_DEPTH) + .value("mjCAMOUT_DIST", mjCAMOUT_DIST) + .value("mjCAMOUT_NORMAL", mjCAMOUT_NORMAL) + .value("mjCAMOUT_SEG", mjCAMOUT_SEG) + .value("mjNCAMOUT", mjNCAMOUT); enum_("mjtCamera") .value("mjCAMERA_FREE", mjCAMERA_FREE) .value("mjCAMERA_TRACKING", mjCAMERA_TRACKING) @@ -11043,6 +11059,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) { .property("cam_ipd", &MjModel::cam_ipd) .property("cam_mat0", &MjModel::cam_mat0) .property("cam_mode", &MjModel::cam_mode) + .property("cam_output", &MjModel::cam_output) .property("cam_pos", &MjModel::cam_pos) .property("cam_pos0", &MjModel::cam_pos0) .property("cam_poscom0", &MjModel::cam_poscom0) @@ -11744,6 +11761,7 @@ 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("output", &MjsCamera::output, &MjsCamera::set_output, reference()) .property("pos", &MjsCamera::pos) .property("principal_length", &MjsCamera::principal_length) .property("principal_pixel", &MjsCamera::principal_pixel)