diff --git a/include/mujoco/mjxmacro.h b/include/mujoco/mjxmacro.h index f0d2f808..cba09e7c 100644 --- a/include/mujoco/mjxmacro.h +++ b/include/mujoco/mjxmacro.h @@ -18,143 +18,142 @@ //-------------------------------- mjOption -------------------------------------------------------- -// scalar fields of mjOption -#define MJOPTION_FLOATS \ - X( mjtNum, timestep ) \ - X( mjtNum, impratio ) \ - X( mjtNum, tolerance ) \ - X( mjtNum, ls_tolerance ) \ - X( mjtNum, noslip_tolerance ) \ - X( mjtNum, ccd_tolerance ) \ - X( mjtNum, sleep_tolerance ) \ - X( mjtNum, density ) \ - X( mjtNum, viscosity ) \ - X( mjtNum, o_margin ) - - -#define MJOPTION_INTS \ - X( int, integrator ) \ - X( int, cone ) \ - X( int, jacobian ) \ - X( int, solver ) \ - X( int, iterations ) \ - X( int, ls_iterations ) \ - X( int, noslip_iterations ) \ - X( int, ccd_iterations ) \ - X( int, disableflags ) \ - X( int, enableflags ) \ - X( int, disableactuator ) \ - X( int, sdf_initpoints ) \ - X( int, sdf_iterations ) - - -#define MJOPTION_SCALARS \ - MJOPTION_FLOATS \ - MJOPTION_INTS - - -// vector fields of mjOption -#define MJOPTION_VECTORS \ - X( gravity, 3 ) \ - X( wind, 3 ) \ - X( magnetic, 3 ) \ - X( o_solref, mjNREF ) \ - X( o_solimp, mjNIMP ) \ - X( o_friction, 5 ) +// fields of mjOption +// XVEC means that a field is a vector (i.e. size > 1) +#define MJOPTION_FIELDS \ + X ( mjtNum, timestep, 1 ) \ + X ( mjtNum, impratio, 1 ) \ + X ( mjtNum, tolerance, 1 ) \ + X ( mjtNum, ls_tolerance, 1 ) \ + X ( mjtNum, noslip_tolerance, 1 ) \ + X ( mjtNum, ccd_tolerance, 1 ) \ + X ( mjtNum, sleep_tolerance, 1 ) \ + XVEC( mjtNum, gravity, 3 ) \ + XVEC( mjtNum, wind, 3 ) \ + XVEC( mjtNum, magnetic, 3 ) \ + X ( mjtNum, density, 1 ) \ + X ( mjtNum, viscosity, 1 ) \ + X ( mjtNum, o_margin, 1 ) \ + XVEC( mjtNum, o_solref, mjNREF ) \ + XVEC( mjtNum, o_solimp, mjNIMP ) \ + XVEC( mjtNum, o_friction, 5 ) \ + X ( int, integrator, 1 ) \ + X ( int, cone, 1 ) \ + X ( int, jacobian, 1 ) \ + X ( int, solver, 1 ) \ + X ( int, iterations, 1 ) \ + X ( int, ls_iterations, 1 ) \ + X ( int, noslip_iterations, 1 ) \ + X ( int, ccd_iterations, 1 ) \ + X ( int, disableflags, 1 ) \ + X ( int, enableflags, 1 ) \ + X ( int, disableactuator, 1 ) \ + X ( int, sdf_initpoints, 1 ) \ + X ( int, sdf_iterations, 1 ) //-------------------------------- mjStatistic ----------------------------------------------------- // fields of mjStatistic -#define MJSTATISTIC_FIELDS \ - X( mjtNum, meaninertia, 1 ) \ - X( mjtNum, meanmass, 1 ) \ - X( mjtNum, meansize, 1 ) \ - X( mjtNum, extent, 1 ) \ - X( mjtNum, center, 3 ) +#define MJSTATISTIC_FIELDS \ + X ( meaninertia, 1 ) \ + X ( meanmass, 1 ) \ + X ( meansize, 1 ) \ + X ( extent, 1 ) \ + XVEC( center, 3 ) //-------------------------------- mjVisual -------------------------------------------------------- // fields of mjVisual -#define MJVISUAL_FIELDS \ - X( global, int, cameraid, 1 ) \ - X( global, int, orthographic, 1 ) \ - X( global, float, fovy, 1 ) \ - X( global, float, ipd, 1 ) \ - X( global, float, azimuth, 1 ) \ - X( global, float, elevation, 1 ) \ - X( global, float, linewidth, 1 ) \ - X( global, float, glow, 1 ) \ - X( global, float, realtime, 1 ) \ - X( global, int, offwidth, 1 ) \ - X( global, int, offheight, 1 ) \ - X( global, int, ellipsoidinertia, 1 ) \ - X( global, int, bvactive, 1 ) \ - X( quality, int, shadowsize, 1 ) \ - X( quality, int, offsamples, 1 ) \ - X( quality, int, numslices, 1 ) \ - X( quality, int, numstacks, 1 ) \ - X( quality, int, numquads, 1 ) \ - X( headlight, float, ambient, 3 ) \ - X( headlight, float, diffuse, 3 ) \ - X( headlight, float, specular, 3 ) \ - X( headlight, int, active, 1 ) \ - X( map, float, stiffness, 1 ) \ - X( map, float, stiffnessrot, 1 ) \ - X( map, float, force, 1 ) \ - X( map, float, torque, 1 ) \ - X( map, float, alpha, 1 ) \ - X( map, float, fogstart, 1 ) \ - X( map, float, fogend, 1 ) \ - X( map, float, znear, 1 ) \ - X( map, float, zfar, 1 ) \ - X( map, float, haze, 1 ) \ - X( map, float, shadowclip, 1 ) \ - X( map, float, shadowscale, 1 ) \ - X( map, float, actuatortendon, 1 ) \ - X( scale, float, forcewidth, 1 ) \ - X( scale, float, contactwidth, 1 ) \ - X( scale, float, contactheight, 1 ) \ - X( scale, float, connect, 1 ) \ - X( scale, float, com, 1 ) \ - X( scale, float, camera, 1 ) \ - X( scale, float, light, 1 ) \ - X( scale, float, selectpoint, 1 ) \ - X( scale, float, jointlength, 1 ) \ - X( scale, float, jointwidth, 1 ) \ - X( scale, float, actuatorlength, 1 ) \ - X( scale, float, actuatorwidth, 1 ) \ - X( scale, float, framelength, 1 ) \ - X( scale, float, framewidth, 1 ) \ - X( scale, float, constraint, 1 ) \ - X( scale, float, slidercrank, 1 ) \ - X( scale, float, frustum, 1 ) \ - X( rgba, float, fog, 4 ) \ - X( rgba, float, haze, 4 ) \ - X( rgba, float, force, 4 ) \ - X( rgba, float, inertia, 4 ) \ - X( rgba, float, joint, 4 ) \ - X( rgba, float, actuator, 4 ) \ - X( rgba, float, actuatornegative, 4 ) \ - X( rgba, float, actuatorpositive, 4 ) \ - X( rgba, float, com, 4 ) \ - X( rgba, float, camera, 4 ) \ - X( rgba, float, light, 4 ) \ - X( rgba, float, selectpoint, 4 ) \ - X( rgba, float, connect, 4 ) \ - X( rgba, float, contactpoint, 4 ) \ - X( rgba, float, contactforce, 4 ) \ - X( rgba, float, contactfriction, 4 ) \ - X( rgba, float, contacttorque, 4 ) \ - X( rgba, float, contactgap, 4 ) \ - X( rgba, float, rangefinder, 4 ) \ - X( rgba, float, constraint, 4 ) \ - X( rgba, float, slidercrank, 4 ) \ - X( rgba, float, crankbroken, 4 ) \ - X( rgba, float, frustum, 4 ) \ - X( rgba, float, bv, 4 ) \ - X( rgba, float, bvactive, 4 ) +#define MJVISUAL_GLOBAL_FIELDS \ + X( int, cameraid ) \ + X( int, orthographic ) \ + X( float, fovy ) \ + X( float, ipd ) \ + X( float, azimuth ) \ + X( float, elevation ) \ + X( float, linewidth ) \ + X( float, glow ) \ + X( float, realtime ) \ + X( int, offwidth ) \ + X( int, offheight ) \ + X( int, ellipsoidinertia ) \ + X( int, bvactive ) + +#define MJVISUAL_QUALITY_FIELDS \ + X( shadowsize ) \ + X( offsamples ) \ + X( numslices ) \ + X( numstacks ) \ + X( numquads ) + +#define MJVISUAL_HEADLIGHT_FIELDS \ + XVEC( float, ambient, 3 ) \ + XVEC( float, diffuse, 3 ) \ + XVEC( float, specular, 3 ) \ + X ( int, active, 1 ) + +#define MJVISUAL_MAP_FIELDS \ + X( stiffness ) \ + X( stiffnessrot ) \ + X( force ) \ + X( torque ) \ + X( alpha ) \ + X( fogstart ) \ + X( fogend ) \ + X( znear ) \ + X( zfar ) \ + X( haze ) \ + X( shadowclip ) \ + X( shadowscale ) \ + X( actuatortendon ) + +#define MJVISUAL_SCALE_FIELDS \ + X( forcewidth ) \ + X( contactwidth ) \ + X( contactheight ) \ + X( connect ) \ + X( com ) \ + X( camera ) \ + X( light ) \ + X( selectpoint ) \ + X( jointlength ) \ + X( jointwidth ) \ + X( actuatorlength ) \ + X( actuatorwidth ) \ + X( framelength ) \ + X( framewidth ) \ + X( constraint ) \ + X( slidercrank ) \ + X( frustum ) + +#define MJVISUAL_RGBA_FIELDS \ + X( fog ) \ + X( haze ) \ + X( force ) \ + X( inertia ) \ + X( joint ) \ + X( actuator ) \ + X( actuatornegative ) \ + X( actuatorpositive ) \ + X( com ) \ + X( camera ) \ + X( light ) \ + X( selectpoint ) \ + X( connect ) \ + X( contactpoint ) \ + X( contactforce ) \ + X( contactfriction ) \ + X( contacttorque ) \ + X( contactgap ) \ + X( rangefinder ) \ + X( constraint ) \ + X( slidercrank ) \ + X( crankbroken ) \ + X( frustum ) \ + X( bv ) \ + X( bvactive ) //-------------------------------- mjModel --------------------------------------------------------- diff --git a/python/mujoco/structs.cc b/python/mujoco/structs.cc index 67993b4c..d8a88f0b 100644 --- a/python/mujoco/structs.cc +++ b/python/mujoco/structs.cc @@ -103,50 +103,53 @@ PYBIND11_MODULE(_structs, m) { }); mjOption.def_property_readonly_static("_all_fields", [](py::object) { std::vector fields; -#define X(dtype, name) fields.push_back(#name); - MJOPTION_FLOATS -#undef X -#define X(name, dim0) fields.push_back(#name); - MJOPTION_VECTORS -#undef X -#define X(dtype, name) fields.push_back(#name); - MJOPTION_INTS +#define X(type, var, dim) fields.push_back(#var); +#define XVEC X + MJOPTION_FIELDS +#undef XVEC #undef X return py::tuple(py::cast(fields)); }); DefineStructFunctions(mjOption); -#define X(type, var) \ +#define X(type, var, dim) \ mjOption.def_property( \ #var, [](const MjOptionWrapper& c) { return c.get()->var; }, \ [](MjOptionWrapper& c, type rhs) { c.get()->var = rhs; }); - MJOPTION_SCALARS -#undef X - -#define X(var, dim) DefinePyArray(mjOption, #var, &MjOptionWrapper::var); - MJOPTION_VECTORS +#define XVEC(type, var, dim) \ + DefinePyArray(mjOption, #var, &MjOptionWrapper::var); + MJOPTION_FIELDS +#undef XVEC #undef X mjOption.def_property_readonly_static("_float_fields", [](py::object) { std::vector field_names; -#define X(type, var) field_names.push_back(#var); - MJOPTION_FLOATS +#define X(type, var, dim) \ + if constexpr (std::is_floating_point_v) field_names.push_back(#var); +#define XVEC(type, var, dim) + MJOPTION_FIELDS +#undef XVEC #undef X return py::tuple(py::cast(field_names)); }); mjOption.def_property_readonly_static("_int_fields", [](py::object) { std::vector field_names; -#define X(type, var) field_names.push_back(#var); - MJOPTION_INTS +#define X(type, var, dim) \ + if constexpr (std::is_integral_v) field_names.push_back(#var); +#define XVEC(type, var, dim) + MJOPTION_FIELDS +#undef XVEC #undef X return py::tuple(py::cast(field_names)); }); mjOption.def_property_readonly_static("_floatarray_fields", [](py::object) { std::vector field_names; -#define X(var, sz) field_names.push_back(#var); - MJOPTION_VECTORS +#define X(type, var, dim) +#define XVEC(type, var, dim) field_names.push_back(#var); + MJOPTION_FIELDS +#undef XVEC #undef X return py::tuple(py::cast(field_names)); }); @@ -192,20 +195,9 @@ PYBIND11_MODULE(_structs, m) { return raw::MjVisualGlobal(other); }); DefineStructFunctions(mjVisualGlobal); -#define X(var) mjVisualGlobal.def_readwrite(#var, &raw::MjVisualGlobal::var) - X(cameraid); - X(orthographic); - X(fovy); - X(ipd); - X(azimuth); - X(elevation); - X(linewidth); - X(glow); - X(realtime); - X(offwidth); - X(offheight); - X(ellipsoidinertia); - X(bvactive); +#define X(type, var) \ + mjVisualGlobal.def_readwrite(#var, &raw::MjVisualGlobal::var); + MJVISUAL_GLOBAL_FIELDS #undef X py::class_ mjVisualQuality(mjVisual, "Quality"); @@ -217,12 +209,8 @@ PYBIND11_MODULE(_structs, m) { return raw::MjVisualQuality(other); }); DefineStructFunctions(mjVisualQuality); -#define X(var) mjVisualQuality.def_readwrite(#var, &raw::MjVisualQuality::var) - X(shadowsize); - X(offsamples); - X(numslices); - X(numstacks); - X(numquads); +#define X(var) mjVisualQuality.def_readwrite(#var, &raw::MjVisualQuality::var); + MJVISUAL_QUALITY_FIELDS #undef X py::class_ mjVisualHeadlight(mjVisual, "Headlight"); @@ -234,18 +222,17 @@ PYBIND11_MODULE(_structs, m) { return MjVisualHeadlightWrapper(other); }); DefineStructFunctions(mjVisualHeadlight); -#define X(var) \ - DefinePyArray(mjVisualHeadlight, #var, &MjVisualHeadlightWrapper::var) - X(ambient); - X(diffuse); - X(specular); -#undef X - mjVisualHeadlight.def_property( - "active", - [](const MjVisualHeadlightWrapper& c) { return c.get()->active; }, - [](MjVisualHeadlightWrapper& c, int rhs) { - return c.get()->active = rhs; +#define X(type, var, dim) \ + mjVisualHeadlight.def_property( \ + #var, [](const MjVisualHeadlightWrapper& c) { return c.get()->var; }, \ + [](MjVisualHeadlightWrapper& c, type rhs) { \ + return c.get()->var = rhs; \ }); +#define XVEC(type, var, dim) \ + DefinePyArray(mjVisualHeadlight, #var, &MjVisualHeadlightWrapper::var); + MJVISUAL_HEADLIGHT_FIELDS +#undef XVEC +#undef X py::class_ mjVisualMap(mjVisual, "Map"); mjVisualMap.def("__copy__", [](const raw::MjVisualMap& other) { @@ -255,20 +242,8 @@ PYBIND11_MODULE(_structs, m) { return raw::MjVisualMap(other); }); DefineStructFunctions(mjVisualMap); -#define X(var) mjVisualMap.def_readwrite(#var, &raw::MjVisualMap::var) - X(stiffness); - X(stiffnessrot); - X(force); - X(torque); - X(alpha); - X(fogstart); - X(fogend); - X(znear); - X(zfar); - X(haze); - X(shadowclip); - X(shadowscale); - X(actuatortendon); +#define X(var) mjVisualMap.def_readwrite(#var, &raw::MjVisualMap::var); + MJVISUAL_MAP_FIELDS #undef X py::class_ mjVisualScale(mjVisual, "Scale"); @@ -280,24 +255,8 @@ PYBIND11_MODULE(_structs, m) { return raw::MjVisualScale(other); }); DefineStructFunctions(mjVisualScale); -#define X(var) mjVisualScale.def_readwrite(#var, &raw::MjVisualScale::var) - X(forcewidth); - X(contactwidth); - X(contactheight); - X(connect); - X(com); - X(camera); - X(light); - X(selectpoint); - X(jointlength); - X(jointwidth); - X(actuatorlength); - X(actuatorwidth); - X(framelength); - X(framewidth); - X(constraint); - X(slidercrank); - X(frustum); +#define X(var) mjVisualScale.def_readwrite(#var, &raw::MjVisualScale::var); + MJVISUAL_SCALE_FIELDS #undef X py::class_ mjVisualRgba(mjVisual, "Rgba"); @@ -309,32 +268,8 @@ PYBIND11_MODULE(_structs, m) { return MjVisualRgbaWrapper(other); }); DefineStructFunctions(mjVisualRgba); -#define X(var) DefinePyArray(mjVisualRgba, #var, &MjVisualRgbaWrapper::var) - X(fog); - X(haze); - X(force); - X(inertia); - X(joint); - X(actuator); - X(actuatornegative); - X(actuatorpositive); - X(com); - X(camera); - X(light); - X(selectpoint); - X(connect); - X(contactpoint); - X(contactforce); - X(contactfriction); - X(contacttorque); - X(contactgap); - X(rangefinder); - X(constraint); - X(slidercrank); - X(crankbroken); - X(frustum); - X(bv); - X(bvactive); +#define X(var) DefinePyArray(mjVisualRgba, #var, &MjVisualRgbaWrapper::var); + MJVISUAL_RGBA_FIELDS #undef X #define X(var) \ @@ -927,20 +862,18 @@ This is useful for example when the MJB is not available as a file on disk.)")); }); DefineStructFunctions(mjStatistic); -#define X(var) \ +#define X(var, dim) \ mjStatistic.def_property( \ #var, [](const MjStatisticWrapper& c) { return c.get()->var; }, \ [](MjStatisticWrapper& c, decltype(raw::MjStatistic::var) rhs) { \ c.get()->var = rhs; \ - }) - X(meaninertia); - X(meanmass); - X(meansize); - X(extent); -#undef X + }); +#define XVEC(var, dim) \ + DefinePyArray(mjStatistic, #var, &MjStatisticWrapper::var); -#define X(var) DefinePyArray(mjStatistic, #var, &MjStatisticWrapper::var) - X(center); + MJSTATISTIC_FIELDS + +#undef XVEC #undef X // ==================== MJLROPT ============================================== diff --git a/python/mujoco/structs.h b/python/mujoco/structs.h index 06d8ffa6..5c1e70cb 100644 --- a/python/mujoco/structs.h +++ b/python/mujoco/structs.h @@ -217,11 +217,13 @@ class MjWrapper : public WrapperBase { MjWrapper(raw::MjOption* ptr, pybind11::handle owner); ~MjWrapper() = default; - #define X(var, dim) \ + #define X(type, var, dim) + #define XVEC(type, var, dim) \ py_array_or_tuple_t< \ std::remove_all_extents_t> \ var; - MJOPTION_VECTORS + MJOPTION_FIELDS + #undef XVEC #undef X }; @@ -241,13 +243,13 @@ class MjWrapper MjWrapper(raw::MjVisualHeadlight* ptr, pybind11::handle owner); ~MjWrapper() = default; - #define X(var) \ + #define X(type, var, dim) + #define XVEC(type, var, dim) \ py_array_or_tuple_t< \ std::remove_all_extents_t> \ - var - X(ambient); - X(diffuse); - X(specular); + var; + MJVISUAL_HEADLIGHT_FIELDS + #undef XVEC #undef X }; @@ -268,32 +270,8 @@ class MjWrapper : public WrapperBase { #define X(var) \ py_array_or_tuple_t< \ std::remove_all_extents_t> \ - var - X(fog); - X(haze); - X(force); - X(inertia); - X(joint); - X(actuator); - X(actuatornegative); - X(actuatorpositive); - X(com); - X(camera); - X(light); - X(selectpoint); - X(connect); - X(contactpoint); - X(contactforce); - X(contactfriction); - X(contacttorque); - X(contactgap); - X(rangefinder); - X(constraint); - X(slidercrank); - X(crankbroken); - X(frustum); - X(bv); - X(bvactive); + var; + MJVISUAL_RGBA_FIELDS #undef X }; @@ -330,11 +308,13 @@ class MjWrapper : public WrapperBase { MjWrapper(raw::MjStatistic* ptr, pybind11::handle owner); ~MjWrapper() = default; - #define X(var) \ + #define X(var, dim) + #define XVEC(var, dim) \ py_array_or_tuple_t< \ std::remove_all_extents_t> \ - var - X(center); + var; + MJSTATISTIC_FIELDS + #undef XVEC #undef X }; diff --git a/python/mujoco/structs_wrappers.cc b/python/mujoco/structs_wrappers.cc index 307d611e..eac086f5 100644 --- a/python/mujoco/structs_wrappers.cc +++ b/python/mujoco/structs_wrappers.cc @@ -83,16 +83,20 @@ inline std::size_t NConMax(const mjData* d) { } // namespace // ==================== MJOPTION =============================================== -#define X(var, dim) , var(InitPyArray(std::array{dim}, ptr_->var, owner_)) +#define X(type, var, dim) +#define XVEC(type, var, dim) \ + , var(InitPyArray(std::array{dim}, ptr_->var, owner_)) MjOptionWrapper::MjWrapper() : WrapperBase([]() { raw::MjOption* const opt = new raw::MjOption; mj_defaultOption(opt); return opt; - }()) MJOPTION_VECTORS {} + }()) MJOPTION_FIELDS {} MjOptionWrapper::MjWrapper(raw::MjOption* ptr, py::handle owner) - : WrapperBase(ptr, owner) MJOPTION_VECTORS {} + : WrapperBase(ptr, owner) MJOPTION_FIELDS {} + +#undef XVEC #undef X MjOptionWrapper::MjWrapper(const MjOptionWrapper& other) : MjOptionWrapper() { diff --git a/src/engine/engine_print.c b/src/engine/engine_print.c index b2143c9c..5cee8f49 100644 --- a/src/engine/engine_print.c +++ b/src/engine/engine_print.c @@ -24,6 +24,7 @@ #include #include #include // IWYU pragma: keep +#include #include #include "engine/engine_core_constraint.h" #include "engine/engine_core_util.h" @@ -600,33 +601,111 @@ void mj_printFormattedModel(const mjModel* m, const char* filename, const char* #undef X fprintf(fp, "\n"); - // scalar options + // options fprintf(fp, "OPTION\n"); -#define X( type, name ) \ - fprintf(fp, NAME_FORMAT, " " #name); \ - fprintf(fp, float_format, m->opt.name); \ +#define X(type, name, sz) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + { \ + const char* format = \ + _Generic(m->opt.name, mjtNum: float_format, int: INT_FORMAT); \ + fprintf(fp, format, m->opt.name); \ + } \ + fprintf(fp, "\n"); +#define XVEC(type, name, sz) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + { \ + const char* format = \ + _Generic(m->opt.name[0], mjtNum: float_format, int: INT_FORMAT); \ + for (int i = 0; i < sz; i++) { \ + fprintf(fp, format, m->opt.name[i]); \ + } \ + } \ fprintf(fp, "\n"); - MJOPTION_FLOATS + MJOPTION_FIELDS +#undef XVEC #undef X - -#define X( type, name ) \ - fprintf(fp, NAME_FORMAT, " " #name); \ - fprintf(fp, INT_FORMAT "\n", m->opt.name); - - MJOPTION_INTS -#undef X - - // vector options -#define X( name, sz ) \ - fprintf(fp, NAME_FORMAT, " " #name); \ - for (int i=0; i < sz; i++) { \ - fprintf(fp, float_format, m->opt.name[i]); \ - fprintf(fp, " "); \ - } \ fprintf(fp, "\n"); - MJOPTION_VECTORS + // visual + fprintf(fp, "VISUAL\n"); + + fprintf(fp, " GLOBAL\n"); +#define X(type, name) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + { \ + const char* format = \ + _Generic(m->vis.global.name, float: float_format, int: INT_FORMAT); \ + fprintf(fp, format, m->vis.global.name); \ + } \ + fprintf(fp, "\n"); + + MJVISUAL_GLOBAL_FIELDS +#undef X + + fprintf(fp, " QUALITY\n"); +#define X(name) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + fprintf(fp, INT_FORMAT, m->vis.quality.name); \ + fprintf(fp, "\n"); + + MJVISUAL_QUALITY_FIELDS +#undef X + + fprintf(fp, " HEADLIGHT\n"); +#define X(type, name, sz) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + { \ + const char* format = \ + _Generic(m->vis.headlight.name, float: float_format, int: INT_FORMAT); \ + fprintf(fp, format, m->vis.headlight.name); \ + } \ + fprintf(fp, "\n"); +#define XVEC(type, name, sz) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + { \ + const char* format = _Generic( \ + m->vis.headlight.name[0], \ + float: float_format, \ + int: INT_FORMAT); \ + for (int i = 0; i < sz; i++) { \ + fprintf(fp, format, m->vis.headlight.name[i]); \ + } \ + } \ + fprintf(fp, "\n"); + MJVISUAL_HEADLIGHT_FIELDS +#undef XVEC +#undef X + + fprintf(fp, " MAP\n"); +#define X(name) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + fprintf(fp, float_format, m->vis.map.name); \ + fprintf(fp, "\n"); + + MJVISUAL_MAP_FIELDS +#undef X + + fprintf(fp, " SCALE\n"); +#define X(name) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + fprintf(fp, float_format, m->vis.scale.name); \ + fprintf(fp, "\n"); + + MJVISUAL_SCALE_FIELDS +#undef X + + fprintf(fp, " RGBA\n"); +#define X(name) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + { \ + for (int i = 0; i < 4; i++) { \ + fprintf(fp, float_format, m->vis.rgba.name[i]); \ + } \ + } \ + fprintf(fp, "\n"); + + MJVISUAL_RGBA_FIELDS #undef X fprintf(fp, "\n"); @@ -637,23 +716,20 @@ void mj_printFormattedModel(const mjModel* m, const char* filename, const char* // statistics fprintf(fp, "STATISTIC\n"); - fprintf(fp, NAME_FORMAT, " meaninertia"); - fprintf(fp, float_format, m->stat.meaninertia); +#define X(name, sz) \ + fprintf(fp, NAME_FORMAT, " " #name); \ + { \ + for (int i = 0; i < sz; i++) { \ + fprintf(fp, float_format, ((mjtNum*)(&m->stat.name))[i]); \ + } \ + } \ fprintf(fp, "\n"); - fprintf(fp, NAME_FORMAT, " meanmass"); - fprintf(fp, float_format, m->stat.meanmass); +#define XVEC X + + MJSTATISTIC_FIELDS +#undef XVEC +#undef X fprintf(fp, "\n"); - fprintf(fp, NAME_FORMAT, " meansize"); - fprintf(fp, float_format, m->stat.meansize); - fprintf(fp, "\n"); - fprintf(fp, NAME_FORMAT, " extent"); - fprintf(fp, float_format, m->stat.extent); - fprintf(fp, "\n"); - fprintf(fp, NAME_FORMAT, " center"); - fprintf(fp, float_format, m->stat.center[0]); - fprintf(fp, float_format, m->stat.center[1]); - fprintf(fp, float_format, m->stat.center[2]); - fprintf(fp, "\n\n"); // qpos0 fprintf(fp, NAME_FORMAT, "qpos0"); diff --git a/test/fixture.cc b/test/fixture.cc index 0e4f14bc..4b18832d 100644 --- a/test/fixture.cc +++ b/test/fixture.cc @@ -274,19 +274,16 @@ mjtNum CompareModel(const mjModel* m1, const mjModel* m2, MJMODEL_POINTERS #undef X - // compare scalars in mjOption - #define X(type, name) \ + // compare fields in mjOption + #define X(type, name, n) \ dif = Compare(m1->opt.name, m2->opt.name); \ if (dif > maxdif) {maxdif = dif; field = #name;} - MJOPTION_SCALARS - #undef X - - // compare arrays in mjOption - #define X(name, n) \ + #define XVEC(type, name, n) \ for (int c=0; c < n; c++) { \ dif = Compare(m1->opt.name[c], m2->opt.name[c]); \ if (dif > maxdif) {maxdif = dif; field = #name;} } - MJOPTION_VECTORS + MJOPTION_FIELDS + #undef XVEC #undef X // Return largest difference and field name diff --git a/test/header_test.cc b/test/header_test.cc index 5ed22082..0c6ba253 100644 --- a/test/header_test.cc +++ b/test/header_test.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -110,86 +111,258 @@ TEST_F(HeaderTest, EnumsAreInts) { EXPECT_EQ(sizeof(mjtStereo), sizeof(int)); } -TEST_F(HeaderTest, MjOptionFloatsOrdered) { +TEST_F(HeaderTest, MjOptionFields) { mjOption o; - std::vector> floats; + std::vector> fields; -#define X(type, name) \ - floats.push_back({static_cast(&o.name), #name}); - MJOPTION_FLOATS + // check that all X macros have the correct type and dim +#define XIMPL(type, name, dim) \ + static_assert( \ + std::is_same_v>, \ + "incorrect type for mjOption::" #name); +#define X(type, name, dim) \ + static_assert(dim == 1, "use XVEC for non-scalar fields"); \ + XIMPL(type, name, dim) +#define XVEC(type, name, dim) \ + static_assert(dim > 1, "use X for scalar fields"); \ + XIMPL(type, name, dim) + + MJOPTION_FIELDS + +#undef XVEC +#undef X +#undef XIMPL + + // check that the ordering of X macros agrees with the struct fields +#define X(type, name, dim) \ + fields.push_back({static_cast(&o.name), #name}); +#define XVEC X + MJOPTION_FIELDS +#undef XVEC #undef X - CheckAddressOrdering(floats, "MJOPTION_FLOATS"); -} + CheckAddressOrdering(fields, "MJOPTION_FIELDS"); -TEST_F(HeaderTest, MjOptionVectorsOrdered) { - mjOption o; - std::vector> vectors; - -#define X(name, dim) \ - vectors.push_back({static_cast(&o.name), #name}); - MJOPTION_VECTORS + // check that MJOPTION_FIELDS is a complete list of struct fields + struct ExpectedMjOption { +#define XVEC(type, name, dim) type name[dim]; +#define X XVEC + MJOPTION_FIELDS #undef X - - CheckAddressOrdering(vectors, "MJOPTION_VECTORS"); +#undef XVEC + }; + static_assert(sizeof(mjOption) == sizeof(ExpectedMjOption)); + static_assert(alignof(mjOption) == alignof(ExpectedMjOption)); } TEST_F(HeaderTest, MjStatisticFields) { mjStatistic s; std::vector> fields; + // All fields in mjStatistic are expected to be of type mjtNum. + using ScalarType = mjtNum; + // check that all X macros have the correct type and dim -#define X(type, name, dim) \ - static_assert( \ - std::is_same_v>, \ - "incorrect type for mjStatistic::" #name); \ +#define XIMPL(name, dim) \ + static_assert(std::is_same_v>, \ + "incorrect type for mjStatistic::" #name); +#define X(name, dim) \ + static_assert(dim == 1, "use XVEC for non-scalar fields"); \ + XIMPL(name, dim) +#define XVEC(name, dim) \ + static_assert(dim > 1, "use X for scalar fields"); \ + XIMPL(name, dim) + MJSTATISTIC_FIELDS + +#undef XVEC #undef X +#undef XIMPL // check that the ordering of X macros agrees with the struct fields -#define X(type, name, dim) \ +#define X(name, dim) \ fields.push_back({static_cast(&s.name), #name}); +#define XVEC X MJSTATISTIC_FIELDS +#undef XVEC #undef X CheckAddressOrdering(fields, "MJSTATISTIC_FIELDS"); // check that MJSTATISTIC_FIELDS is a complete list of struct fields struct ExpectedMjStatistic { -#define X(type, name, dim) type name[dim]; +#define XVEC(name, dim) ScalarType name[dim]; +#define X XVEC MJSTATISTIC_FIELDS; +#undef XVEC #undef X }; static_assert(sizeof(mjStatistic) == sizeof(ExpectedMjStatistic)); + static_assert(alignof(mjStatistic) == alignof(ExpectedMjStatistic)); } TEST_F(HeaderTest, MjVisualFields) { mjVisual v; std::vector> fields; + // All member fields in quality, map, scale, and rgba have the same type. + using QualityMemberType = int; + using MapMemberType = float; + using ScaleMemberType = float; + using RgbaMemberType = float[4]; + // check that all X macros have the correct type and dim -#define X(substruct, type, name, dim) \ +#define X(type, name) \ + static_assert( \ + std::is_same_v, \ + "incorrect type for mjVisual::global::" #name); + + MJVISUAL_GLOBAL_FIELDS + +#undef X + +#define X(name) \ + static_assert( \ + std::is_same_v, \ + "incorrect type for mjVisual::quality::" #name); + + MJVISUAL_QUALITY_FIELDS + +#undef X + +#define XIMPL(type, name, dim) \ static_assert( \ - std::is_same_v>, \ - "incorrect type for mjVisual::" #substruct "::" #name); \ - MJVISUAL_FIELDS + std::is_same_v>, \ + "incorrect type for mjVisual::headlight::" #name); +#define X(type, name, dim) \ + static_assert(dim == 1, "use XVEC for non-scalar fields"); \ + XIMPL(type, name, dim) +#define XVEC(type, name, dim) \ + static_assert(dim > 1, "use X for scalar fields"); \ + XIMPL(type, name, dim) + + MJVISUAL_HEADLIGHT_FIELDS + +#undef X +#undef XVEC + +#define X(name) \ + static_assert( \ + std::is_same_v, \ + "incorrect type for mjVisual::map::" #name); + + MJVISUAL_MAP_FIELDS + +#undef X + +#define X(name) \ + static_assert( \ + std::is_same_v, \ + "incorrect type for mjVisual::scale::" #name); + + MJVISUAL_SCALE_FIELDS + +#undef X + +#define X(name) \ + static_assert( \ + std::is_same_v, \ + "incorrect type for mjVisual::rgba::" #name); + + MJVISUAL_RGBA_FIELDS + #undef X // check that the ordering of X macros agrees with the struct fields -#define X(substruct, type, name, dim) \ - fields.push_back({static_cast(&v.substruct.name), #name}); - MJVISUAL_FIELDS +#define X(type, name) \ + fields.push_back({static_cast(&v.global.name), #name}); + MJVISUAL_GLOBAL_FIELDS +#undef X +#define X(name) \ + fields.push_back({static_cast(&v.quality.name), #name}); + MJVISUAL_QUALITY_FIELDS +#undef X +#define X(type, name, dim) \ + fields.push_back({static_cast(&v.headlight.name), #name}); +#define XVEC X + MJVISUAL_HEADLIGHT_FIELDS +#undef XVEC +#undef X +#define X(name) \ + fields.push_back({static_cast(&v.map.name), #name}); + MJVISUAL_MAP_FIELDS +#undef X +#define X(name) \ + fields.push_back({static_cast(&v.scale.name), #name}); + MJVISUAL_SCALE_FIELDS +#undef X +#define X(name) \ + fields.push_back({static_cast(&v.rgba.name), #name}); + MJVISUAL_RGBA_FIELDS #undef X CheckAddressOrdering(fields, "MJVISUAL_FIELDS"); // check that MJVISUAL_FIELDS is a complete list of fields struct ExpectedMjVisual { -#define X(substruct, type, name, dim) type substruct##_##name[dim]; - MJVISUAL_FIELDS; + struct { +#define X(type, name) type name; + MJVISUAL_GLOBAL_FIELDS; #undef X + } global; + struct { +#define X(name) QualityMemberType name; + MJVISUAL_QUALITY_FIELDS; +#undef X + } quality; + struct { +#define X(type, name, dim) type name[dim]; +#define XVEC X + MJVISUAL_HEADLIGHT_FIELDS; +#undef XVEC +#undef X + } headlight; + struct { +#define X(name) MapMemberType name; + MJVISUAL_MAP_FIELDS; +#undef X + } map; + struct { +#define X(name) ScaleMemberType name; + MJVISUAL_SCALE_FIELDS; +#undef X + } scale; + struct { +#define X(name) RgbaMemberType name; + MJVISUAL_RGBA_FIELDS; +#undef X + } rgba; }; + static_assert(sizeof(mjVisual) == sizeof(ExpectedMjVisual)); + static_assert(alignof(mjVisual) == alignof(ExpectedMjVisual)); + + static_assert(sizeof(mjVisual::global) == sizeof(ExpectedMjVisual::global)); + static_assert(alignof(mjVisual::global) == alignof(ExpectedMjVisual::global)); + + static_assert(sizeof(mjVisual::quality) == sizeof(ExpectedMjVisual::quality)); + static_assert(alignof(mjVisual::quality) == + alignof(ExpectedMjVisual::quality)); + + static_assert(sizeof(mjVisual::headlight) == + sizeof(ExpectedMjVisual::headlight)); + static_assert(alignof(mjVisual::headlight) == + alignof(ExpectedMjVisual::headlight)); + + static_assert(sizeof(mjVisual::map) == sizeof(ExpectedMjVisual::map)); + static_assert(alignof(mjVisual::map) == alignof(ExpectedMjVisual::map)); + + static_assert(sizeof(mjVisual::scale) == sizeof(ExpectedMjVisual::scale)); + static_assert(alignof(mjVisual::scale) == alignof(ExpectedMjVisual::scale)); + + static_assert(sizeof(mjVisual::rgba) == sizeof(ExpectedMjVisual::rgba)); + static_assert(alignof(mjVisual::rgba) == alignof(ExpectedMjVisual::rgba)); } TEST_F(HeaderTest, MjModelIntsOrdered) {