diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index de6f9aaf..ba447d4b 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -1636,6 +1636,11 @@ still be specified here but this functionality is now deprecated and will be rem :at:`vflip`: :at-val:`[false, true], "false"` If true, images loaded from file are flipped in the vertical direction. Does not affect procedural textures. +.. _asset-texture-nchannel: + +:at:`nchannel`: :at-val:`int, "3"` + The number of channels in the texture image file. This allows loading 4-channel textures (RGBA) or single-channel + textures (e.g., for Physics-Based Rendering properties such as roughness or metallic). .. _asset-material: @@ -1666,7 +1671,12 @@ properties are grouped together. If this attribute is specified, the material has a texture associated with it. Referencing the material from a model element will cause the texture to be applied to that element. Note that the value of this attribute is the name of a texture asset, not a texture file name. Textures cannot be loaded in the material definition; instead they must be - loaded explicitly via the :ref:`texture ` element and then referenced here. + loaded explicitly via the :ref:`texture ` element and then referenced here. The texture referenced + here is used for specifying the RGB values. For advanced rendering (e.g., Physics-Based Rendering), more texture + types need to be specified (e.g., roughness, metallic). In this case, this texture attribute should be omitted, and + the texture types should be specified explicitly via the specific role child elements, e.g., + :ref:`texture `. Note however that the built-in renderer does not support PBR properties, so these + advanced rendering features are only available when using an external renderer. .. _asset-material-texrepeat: @@ -1738,6 +1748,116 @@ properties are grouped together. model element which defines its own local rgba attribute, the local definition has precedence. Note that this "local" definition could in fact come from a defaults class. The remaining material properties always apply. +.. _material-rgb: + +:el-prefix:`material/` |-| **rgb** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify base color / albedo values. + +.. _material-rgb-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly 3 channels. + +.. _material-normal: + +:el-prefix:`material/` |-| **normal** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify the bump map (surface normals). + +.. _material-normal-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly 3 channels. + +.. _material-occlusion: + +:el-prefix:`material/` |-| **occlusion** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify ambient occlusion. + +.. _material-occlusion-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly one channel. + +.. _material-roughness: + +:el-prefix:`material/` |-| **roughness** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify the roughness map. + +.. _material-roughness-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly one channel. + +.. _material-metallic: + +:el-prefix:`material/` |-| **metallic** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify the metallic map. + +.. _material-metallic-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly one channel. + +.. _material-opacity: + +:el-prefix:`material/` |-| **opacity** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify the opacity map (alpha channel, transparency). + +.. _material-opacity-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly one channel. + +.. _material-emissive: + +:el-prefix:`material/` |-| **emissive** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify light emission. + +.. _material-emissive-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly 4 channels. + +.. _material-orm: + +:el-prefix:`material/` |-| **orm** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify a packed ORM map, where occlusion, roughness, and metallic +are joined into the corresponding RGB values of a single texture. + +.. _material-orm-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly 3 channels. + +.. _material-rgba: + +:el-prefix:`material/` |-| **rgba** (?) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This element references a texture asset used to specify a packed map where albedo and opacity are joined into the same +4-channel texture. + +.. _material-rgba-texture: + +:at:`texture`: :at-val:`string, required` + Name of the texture, expected to have exactly 4 channels. + .. _asset-model: diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index e6dc1b77..9fdd9dcd 100644 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -163,7 +163,7 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | | :ref:`markrgb` | :ref:`random` | :ref:`width` | :ref:`height` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`hflip` | :ref:`vflip` | | | | +| | | | :ref:`hflip` | :ref:`vflip` | :ref:`nchannel` | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_| asset |br| |_| |L| | | .. table:: | @@ -177,6 +177,69 @@ | | | | :ref:`reflectance` | :ref:`metallic` | :ref:`roughness` | :ref:`rgba` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`rgb | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`occlusion | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`roughness | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`metallic | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`normal | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`opacity | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`emissive | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`rgba | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| |_2| material |br| |_2| |L| | | .. table:: | +| :ref:`orm | ? | :class: mjcf-attributes | +| ` | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`texture` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | ++------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_| asset |br| |_| |L| | | .. table:: | | :ref:`model | \* | :class: mjcf-attributes | | ` | | | diff --git a/doc/changelog.rst b/doc/changelog.rst index 6c818847..bb2a644a 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -10,15 +10,18 @@ General 1. Renamed ``mjModel.tex_rbg`` to ``mjModel.tex_data``. 2. Added a new disable flag ``mjDSBL_AUTORESETNAN`` to disable automatic reset when NaNs or infinities are produced. +3. Added sub-elements to the MJCF :ref:`material` element, to allow specification of multiple textures +for rendering (e.g., :ref:`occlusion-roughness-metallic`). Note that the MuJoCo renderer doesn't support +these new features, and they are made available for use with external renderers. MJX ^^^ -3. Added more fields to ``mjx.Model`` and ``mjx.Data`` for further compatibility with the corresponding MuJoCo structs. +4. Added more fields to ``mjx.Model`` and ``mjx.Data`` for further compatibility with the corresponding MuJoCo structs. Python bindings ^^^^^^^^^^^^^^^ -4. Added support for asset dictionary argument in ``mujoco.spec.from_file``, ``mujoco.spec.from_string`` and +5. Added support for asset dictionary argument in ``mujoco.spec.from_file``, ``mujoco.spec.from_string`` and ``mujoco.spec.compile``. Version 3.2.0 (Jul 15, 2024) diff --git a/doc/includes/references.h b/doc/includes/references.h index 10e62a77..871069f2 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -2015,6 +2015,7 @@ typedef struct mjsTexture_ { // texture specification double random; // probability of random dots int height; // height in pixels (square for cube and skybox) int width; // width in pixels + int nchannel; // number of channels // method 2: single file mjString* content_type; // content type of file @@ -2035,7 +2036,7 @@ typedef struct mjsTexture_ { // texture specification typedef struct mjsMaterial_ { // material specification mjsElement* element; // element type mjString* name; // name - mjString* texture; // name of texture (empty: none) + mjStringVec* textures; // names of textures (empty: none) mjtByte texuniform; // make texture cube uniform float texrepeat[2]; // texture repetition for 2D mapping float emission; // emission diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index a437c72c..db3c53b9 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -500,6 +500,7 @@ typedef struct mjsTexture_ { // texture specification double random; // probability of random dots int height; // height in pixels (square for cube and skybox) int width; // width in pixels + int nchannel; // number of channels // method 2: single file mjString* content_type; // content type of file @@ -522,7 +523,7 @@ typedef struct mjsTexture_ { // texture specification typedef struct mjsMaterial_ { // material specification mjsElement* element; // element type mjString* name; // name - mjString* texture; // name of texture (empty: none) + mjStringVec* textures; // names of textures (empty: none) mjtByte texuniform; // make texture cube uniform float texrepeat[2]; // texture repetition for 2D mapping float emission; // emission diff --git a/introspect/structs.py b/introspect/structs.py index cd7928bf..5c00f51e 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -9969,6 +9969,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ type=ValueType(name='int'), doc='width in pixels', ), + StructFieldDecl( + name='nchannel', + type=ValueType(name='int'), + doc='number of channels', + ), StructFieldDecl( name='content_type', type=PointerType( @@ -10045,11 +10050,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([ doc='name', ), StructFieldDecl( - name='texture', + name='textures', type=PointerType( - inner_type=ValueType(name='mjString'), + inner_type=ValueType(name='mjStringVec'), ), - doc='name of texture (empty: none)', + doc='names of textures (empty: none)', ), StructFieldDecl( name='texuniform', diff --git a/src/render/render_context.c b/src/render/render_context.c index d47b7ca1..7d37692e 100644 --- a/src/render/render_context.c +++ b/src/render/render_context.c @@ -1304,7 +1304,7 @@ static void makeMaterial(const mjModel* m, mjrContext* con) { } if (m->nmat >= mjMAXMATERIAL-1) { - mju_error("Maximum number of materials is %d", mjMAXMATERIAL); + mju_error("Maximum number of materials is 100, got %d", m->nmat); } for (int i=0; i < m->nmat; i++) { if (m->mat_texid[i*mjNTEXROLE + mjTEXROLE_RGB] >= 0) { @@ -1320,7 +1320,8 @@ static void makeMaterial(const mjModel* m, mjrContext* con) { for (int i=0; i < m->ntex; i++) { if (m->tex_type[i] == mjTEXTURE_SKYBOX) { if (m->nmat >= mjMAXMATERIAL-2) { - mju_error("With skybox, maximum number of materials is %d", mjMAXMATERIAL); + mju_error("With skybox, maximum number of materials is 99, got %d", + m->nmat); } for (int j=0; j < mjNTEXROLE; j++) { con->mat_texid[mjNTEXROLE * (mjMAXMATERIAL-1) + j] = -1; diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 377ed8e6..b6ec8086 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -947,13 +947,11 @@ void mjCModel::DeleteMaterial(std::vector& list, std::string_view name) { -// delete texture with given name or all textures if the name is omitted +// delete all textures template -static void DeleteTexture(std::vector& list, std::string_view name = "") { +static void DeleteAllTextures(std::vector& list) { for (T* plist : list) { - if (name.empty() || plist->get_texture() == name) { - plist->del_texture(); - } + plist->del_textures(); } } @@ -1046,7 +1044,7 @@ void mjCModel::DeleteAll(std::vector& elements) { template <> void mjCModel::DeleteAll(std::vector& elements) { - DeleteTexture(materials_); + DeleteAllTextures(materials_); for (mjCTexture* element : elements) { delete element; } @@ -1202,13 +1200,15 @@ void mjCModel::IndexAssets(bool discard) { for (int i=0; itexture_.empty()) { - mjCBase* texture = FindObject(mjOBJ_TEXTURE, material->texture_); - if (texture) { - material->texid = texture->id; - } else { - throw mjCError(material, "texture '%s' not found in material %d", material->texture_.c_str(), i); + // find textures by name + for (int j=0; jtextures_[j].empty()) { + mjCBase* texture = FindObject(mjOBJ_TEXTURE, material->textures_[j]); + if (texture) { + material->texid[j] = texture->id; + } else { + throw mjCError(material, "texture '%s' not found in material %d", material->textures_[j].c_str(), i); + } } } } @@ -2504,14 +2504,15 @@ void mjCModel::CopyObjects(mjModel* m) { m->tex_type[i] = ptex->type; m->tex_height[i] = ptex->height; m->tex_width[i] = ptex->width; + m->tex_nchannel[i] = ptex->nchannel; m->tex_adr[i] = data_adr; - m->tex_nchannel[i] = 3; // copy rgb data - memcpy(m->tex_data + data_adr, ptex->rgb.data(), 3*ptex->width*ptex->height); + memcpy(m->tex_data + data_adr, ptex->data.data(), + ptex->nchannel * ptex->width * ptex->height); // advance counter - data_adr += 3*ptex->width*ptex->height; + data_adr += ptex->nchannel * ptex->width * ptex->height; } // materials @@ -2521,9 +2522,8 @@ void mjCModel::CopyObjects(mjModel* m) { // set fields for (int j=0; jmat_texid[mjNTEXROLE*i+j] = -1; + m->mat_texid[mjNTEXROLE*i+j] = pmat->texid[j]; } - m->mat_texid[mjNTEXROLE*i+mjTEXROLE_RGB] = pmat->texid; m->mat_texuniform[i] = pmat->texuniform; mjuu_copyvec(m->mat_texrepeat+2*i, pmat->texrepeat, 2); m->mat_emission[i] = pmat->emission; diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index 5d9b670f..c647f4b3 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -3217,7 +3217,7 @@ mjCTexture::mjCTexture(mjCModel* _model) { spec_cubefiles_.assign(6, ""); // clear internal variables - rgb.clear(); + data.clear(); // point to local PointToLocal(); @@ -3266,14 +3266,14 @@ void mjCTexture::CopyFromSpec() { cubefiles_ = spec_cubefiles_; // clear precompiled asset. TODO: use asset cache - rgb.clear(); + data.clear(); } // free data storage allocated by lodepng mjCTexture::~mjCTexture() { - rgb.clear(); + data.clear(); } @@ -3366,21 +3366,21 @@ void mjCTexture::Builtin2D(void) { double pos = 2*sqrt(x*x+y*y) - 1; // interpolate through sigmoid - interp(rgb.data() + 3*(r*width+c), rgb2, rgb1, pos); + interp(data.data() + 3*(r*width+c), rgb2, rgb1, pos); } } } // checker else if (builtin==mjBUILTIN_CHECKER) { - checker(rgb.data(), RGB1, RGB2, width, height); + checker(data.data(), RGB1, RGB2, width, height); } // flat else if (builtin==mjBUILTIN_FLAT) { for (int r=0; r0) { - randomdot(rgb.data(), markrgb, width, height, random); + randomdot(data.data(), markrgb, width, height, random); } } @@ -3446,26 +3446,26 @@ void mjCTexture::BuiltinCube(void) { // set sides interp(RGBi, rgb1, rgb2, elside); - memcpy(rgb.data() + 0 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 0: right - memcpy(rgb.data() + 1 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 1: left - memcpy(rgb.data() + 4 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 4: front - memcpy(rgb.data() + 5 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 5: back + memcpy(data.data() + 0 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 0: right + memcpy(data.data() + 1 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 1: left + memcpy(data.data() + 4 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 4: front + memcpy(data.data() + 5 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 5: back // set up and down - interp(rgb.data() + 2 * 3 * ww + 3 * (r * w + c), rgb1, rgb2, elup); // 2: up - interp(rgb.data() + 3 * 3 * ww + 3 * (r * w + c), rgb1, rgb2, -elup); // 3: down + interp(data.data() + 2 * 3 * ww + 3 * (r * w + c), rgb1, rgb2, elup); // 2: up + interp(data.data() + 3 * 3 * ww + 3 * (r * w + c), rgb1, rgb2, -elup); // 3: down } } } // checker else if (builtin == mjBUILTIN_CHECKER) { - checker(rgb.data() + 0 * 3 * ww, RGB1, RGB2, w, w); - checker(rgb.data() + 1 * 3 * ww, RGB1, RGB2, w, w); - checker(rgb.data() + 2 * 3 * ww, RGB1, RGB2, w, w); - checker(rgb.data() + 3 * 3 * ww, RGB1, RGB2, w, w); - checker(rgb.data() + 4 * 3 * ww, RGB2, RGB1, w, w); - checker(rgb.data() + 5 * 3 * ww, RGB2, RGB1, w, w); + checker(data.data() + 0 * 3 * ww, RGB1, RGB2, w, w); + checker(data.data() + 1 * 3 * ww, RGB1, RGB2, w, w); + checker(data.data() + 2 * 3 * ww, RGB1, RGB2, w, w); + checker(data.data() + 3 * 3 * ww, RGB1, RGB2, w, w); + checker(data.data() + 4 * 3 * ww, RGB2, RGB1, w, w); + checker(data.data() + 5 * 3 * ww, RGB2, RGB1, w, w); } // flat @@ -3473,14 +3473,14 @@ void mjCTexture::BuiltinCube(void) { for (int r = 0; r < w; r++) { for (int c = 0; c < w; c++) { // set sides and up - memcpy(rgb.data() + 0 * 3 * ww + 3 * (r * w + c), RGB1, 3); - memcpy(rgb.data() + 1 * 3 * ww + 3 * (r * w + c), RGB1, 3); - memcpy(rgb.data() + 2 * 3 * ww + 3 * (r * w + c), RGB1, 3); - memcpy(rgb.data() + 4 * 3 * ww + 3 * (r * w + c), RGB1, 3); - memcpy(rgb.data() + 5 * 3 * ww + 3 * (r * w + c), RGB1, 3); + memcpy(data.data() + 0 * 3 * ww + 3 * (r * w + c), RGB1, 3); + memcpy(data.data() + 1 * 3 * ww + 3 * (r * w + c), RGB1, 3); + memcpy(data.data() + 2 * 3 * ww + 3 * (r * w + c), RGB1, 3); + memcpy(data.data() + 4 * 3 * ww + 3 * (r * w + c), RGB1, 3); + memcpy(data.data() + 5 * 3 * ww + 3 * (r * w + c), RGB1, 3); // set down - memcpy(rgb.data() + 3 * 3 * ww + 3 * (r * w + c), RGB2, 3); + memcpy(data.data() + 3 * 3 * ww + 3 * (r * w + c), RGB2, 3); } } } @@ -3491,12 +3491,12 @@ void mjCTexture::BuiltinCube(void) { if (mark == mjMARK_EDGE) { for (int j = 0; j < 6; j++) { for (int r = 0; r < w; r++) { - memcpy(rgb.data() + j * 3 * ww + 3 * (r * w + 0), RGBm, 3); - memcpy(rgb.data() + j * 3 * ww + 3 * (r * w + w - 1), RGBm, 3); + memcpy(data.data() + j * 3 * ww + 3 * (r * w + 0), RGBm, 3); + memcpy(data.data() + j * 3 * ww + 3 * (r * w + w - 1), RGBm, 3); } for (int c = 0; c < w; c++) { - memcpy(rgb.data() + j * 3 * ww + 3 * (0 * w + c), RGBm, 3); - memcpy(rgb.data() + j * 3 * ww + 3 * ((w - 1) * w + c), RGBm, 3); + memcpy(data.data() + j * 3 * ww + 3 * (0 * w + c), RGBm, 3); + memcpy(data.data() + j * 3 * ww + 3 * ((w - 1) * w + c), RGBm, 3); } } } @@ -3505,17 +3505,17 @@ void mjCTexture::BuiltinCube(void) { else if (mark == mjMARK_CROSS) { for (int j = 0; j < 6; j++) { for (int r = 0; r < w; r++) { - memcpy(rgb.data() + j * 3 * ww + 3 * (r * w + w / 2), RGBm, 3); + memcpy(data.data() + j * 3 * ww + 3 * (r * w + w / 2), RGBm, 3); } for (int c = 0; c < w; c++) { - memcpy(rgb.data() + j * 3 * ww + 3 * (w / 2 * w + c), RGBm, 3); + memcpy(data.data() + j * 3 * ww + 3 * (w / 2 * w + c), RGBm, 3); } } } // random dots else if (mark == mjMARK_RANDOM && random > 0) { - randomdot(rgb.data(), markrgb, w, height, random); + randomdot(data.data(), markrgb, w, height, random); } } @@ -3523,14 +3523,23 @@ void mjCTexture::BuiltinCube(void) { void mjCTexture::LoadPNG(mjResource* resource, std::vector& image, unsigned int& w, unsigned int& h) { - PNGImage png_image = PNGImage::Load(this, resource, LCT_RGB); + LodePNGColorType color_type; + if (nchannel == 4) { + color_type = LCT_RGBA; + } else if (nchannel == 3) { + color_type = LCT_RGB; + } else if (nchannel == 1) { + color_type = LCT_GREY; + } else { + throw mjCError(this, "Unsupported number of channels: %s", + std::to_string(nchannel).c_str()); + } + PNGImage png_image = PNGImage::Load(this, resource, color_type); w = png_image.Width(); h = png_image.Height(); image = png_image.MoveData(); } - - // load custom file void mjCTexture::LoadCustom(mjResource* resource, std::vector& image, @@ -3601,6 +3610,10 @@ void mjCTexture::LoadFlip(std::string filename, const mjVFS* vfs, // horizontal flip if (hflip) { + if (nchannel != 3) { + throw mjCError( + this, "currently only 3-channel textures support horizontal flip"); + } for (int r=0; r(this); spec.name = &name; - spec.texture = &spec_texture_; + spec.textures = &spec_textures_; spec.info = &info; - texture = nullptr; + textures = nullptr; } void mjCMaterial::CopyFromSpec() { *static_cast(this) = spec; - texture_ = spec_texture_; + textures_ = spec_textures_; } @@ -3987,8 +4007,10 @@ void mjCMaterial::NameSpace(const mjCModel* m) { if (!name.empty()) { name = m->prefix + name + m->suffix; } - if (!spec_texture_.empty() && model != m) { - spec_texture_ = m->prefix + spec_texture_ + m->suffix; + for (int i=0; iprefix + spec_textures_[i] + m->suffix; + } } } diff --git a/src/user/user_objects.h b/src/user/user_objects.h index 1088f5b5..4e508b04 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -1003,7 +1003,7 @@ class mjCHField : public mjCHField_, private mjsHField { class mjCTexture_ : public mjCBase { protected: - std::vector rgb; // rgb data + std::vector data; // texture data (rgb, roughness, etc.) std::string file_; std::string content_type_; @@ -1063,9 +1063,9 @@ class mjCTexture : public mjCTexture_, private mjsTexture { class mjCMaterial_ : public mjCBase { protected: - int texid; // id of material's texture - std::string texture_; - std::string spec_texture_; + int texid[mjNTEXROLE]; // id of material's textures + std::vector textures_; + std::vector spec_textures_; }; class mjCMaterial : public mjCMaterial_, private mjsMaterial { @@ -1086,8 +1086,8 @@ class mjCMaterial : public mjCMaterial_, private mjsMaterial { void PointToLocal(); void NameSpace(const mjCModel* m); - const std::string& get_texture() const { return texture_; } - void del_texture() { texture_.clear(); } + const std::string& get_texture(int i) const { return textures_[i]; } + void del_textures() { for (auto& t : textures_) t.clear(); } private: void Compile(void); // compiler diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 62b5b50d..6ec5a6a0 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -239,12 +239,23 @@ const char* MJCF[nMJCF][mjXATTRNUM] = { {"<"}, {"bone", "*", "5", "body", "bindpos", "bindquat", "vertid", "vertweight"}, {">"}, - {"texture", "*", "22", "name", "type", "content_type", "file", "gridsize", "gridlayout", + {"texture", "*", "23", "name", "type", "content_type", "file", "gridsize", "gridlayout", "fileright", "fileleft", "fileup", "filedown", "filefront", "fileback", "builtin", "rgb1", "rgb2", "mark", "markrgb", "random", "width", "height", - "hflip", "vflip"}, + "hflip", "vflip", "nchannel"}, {"material", "*", "12", "name", "class", "texture", "texrepeat", "texuniform", "emission", "specular", "shininess", "reflectance", "metallic", "roughness", "rgba"}, + {"<"}, + {"rgb", "?", "1", "texture"}, + {"occlusion", "?", "1", "texture"}, + {"roughness", "?", "1", "texture"}, + {"metallic", "?", "1", "texture"}, + {"normal", "?", "1", "texture"}, + {"opacity", "?", "1", "texture"}, + {"emissive", "?", "1", "texture"}, + {"rgba", "?", "1", "texture"}, + {"orm", "?", "1", "texture"}, + {">"}, {"model", "*", "2", "name", "file"}, {">"}, @@ -577,6 +588,19 @@ const mjMap camlight_map[camlight_sz] = { {"targetbodycom", mjCAMLIGHT_TARGETBODYCOM} }; +// texmat role type +const int texrole_sz = mjNTEXROLE - 1; +const mjMap texrole_map[texrole_sz] = { + {"rgb", mjTEXROLE_RGB}, + {"occlusion", mjTEXROLE_OCCLUSION}, + {"roughness", mjTEXROLE_ROUGHNESS}, + {"metallic", mjTEXROLE_METALLIC}, + {"normal", mjTEXROLE_NORMAL}, + {"opacity", mjTEXROLE_OPACITY}, + {"emissive", mjTEXROLE_EMISSIVE}, + {"rgba", mjTEXROLE_RGBA}, + {"orm", mjTEXROLE_ORM}, +}; // integrator type const int integrator_sz = 4; @@ -1534,9 +1558,26 @@ void mjXReader::OneMaterial(XMLElement* elem, mjsMaterial* pmat) { if (ReadAttrTxt(elem, "name", name)) { mjs_setString(pmat->name, name.c_str()); } + + bool tex_attributes_found = false; if (ReadAttrTxt(elem, "texture", texture)) { - mjs_setString(pmat->texture, texture.c_str()); + mjs_setInStringVec(pmat->textures, mjTEXROLE_RGB, texture.c_str()); + tex_attributes_found = true; } + + XMLElement* tex_elem = FirstChildElement(elem); + while (tex_elem) { + if (tex_attributes_found) { + throw mjXError(tex_elem, "A material with a texture attribute cannot have texture sub-elements"); + } + // texture sub-element + int role = FindKey(texrole_map, texrole_sz, tex_elem->Name()); + string texmat; + ReadAttrTxt(tex_elem, "texture", texmat, true); + mjs_setInStringVec(pmat->textures, role, texmat.c_str()); + tex_elem = NextSiblingElement(tex_elem); + } + if (MapValue(elem, "texuniform", &n, bool_map, 2)) { pmat->texuniform = (n==1); } @@ -3095,6 +3136,9 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) { } ReadAttrInt(elem, "width", &ptex->width); ReadAttrInt(elem, "height", &ptex->height); + if (!ReadAttrInt(elem, "nchannel", &ptex->nchannel)) { + ptex->nchannel = 3; + } ReadAttr(elem, "rgb1", 3, ptex->rgb1, text); ReadAttr(elem, "rgb2", 3, ptex->rgb2, text); ReadAttr(elem, "markrgb", 3, ptex->markrgb, text); diff --git a/src/xml/xml_native_reader.h b/src/xml/xml_native_reader.h index 11743df7..8e4e8cf4 100644 --- a/src/xml/xml_native_reader.h +++ b/src/xml/xml_native_reader.h @@ -99,7 +99,7 @@ class mjXReader : public mjXBase { }; // MJCF schema -#define nMJCF 232 +#define nMJCF 243 extern const char* MJCF[nMJCF][mjXATTRNUM]; #endif // MUJOCO_SRC_XML_XML_NATIVE_READER_H_ diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index d6e4a9ea..5798ef7e 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -305,8 +305,20 @@ void mjXWriter::OneMaterial(XMLElement* elem, const mjCMaterial* pmat, mjCDef* d } // defaults and regular - if (pmat->texture != def->Material().texture) { - WriteAttrTxt(elem, "texture", pmat->get_texture()); + bool has_non_rgb = false; + for (int i=1; itextures_[i].empty()) { + if (i != mjTEXROLE_RGB) { + has_non_rgb = true; + } + } + if (pmat->textures_[i] != def->Material().textures_[i]) { + WriteAttrTxt(elem, "texture", pmat->get_texture(i)); + } + } + if (has_non_rgb) { + // // TODO elem = InsertEnd(section, "role"); + mju_error("mjXWriter: no support for non-RGB textures."); } WriteAttrKey(elem, "texuniform", bool_map, 2, pmat->texuniform, def->Material().texuniform); WriteAttr(elem, "texrepeat", 2, pmat->texrepeat, def->Material().texrepeat); diff --git a/test/xml/xml_native_reader_test.cc b/test/xml/xml_native_reader_test.cc index 622c80fe..817ab79b 100644 --- a/test/xml/xml_native_reader_test.cc +++ b/test/xml/xml_native_reader_test.cc @@ -782,12 +782,108 @@ TEST_F(XMLReaderTest, FallbackIncludePathTest) { std::array error; mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error.data(), error.size()); - ASSERT_THAT(model, NotNull()); + ASSERT_THAT(model, NotNull()) << error.data(); EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "ball"), 2); EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "another_box"), 3); mj_deleteModel(model); } +TEST_F(XMLReaderTest, MaterialTextureTest) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + + + )"; + + MockFilesystem fs("MaterialTextureTest"); + fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng)); + fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng)); + fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml)); + std::string modelpath = fs.FullPath("model.xml"); + + char error[1024]; + mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error, 1024); + + EXPECT_THAT(model, NotNull()) << error; + EXPECT_EQ(model->mat_texid[mjTEXROLE_RGB], 1); + EXPECT_EQ(model->mat_texid[mjTEXROLE_METALLIC], 0); + EXPECT_EQ(model->mat_texid[mjTEXROLE_ROUGHNESS], 0); + EXPECT_EQ(model->mat_texid[mjTEXROLE_OCCLUSION], 0); + + mj_deleteModel(model); +} + +TEST_F(XMLReaderTest, LegacyMaterialTextureTest) { + static constexpr char xml[] = R"( + + + + + + + + + + + )"; + + MockFilesystem fs("LegacyMaterialTextureTest"); + fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng)); + fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng)); + fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml)); + std::string modelpath = fs.FullPath("model.xml"); + + char error[1024]; + mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error, 1024); + + EXPECT_THAT(model, NotNull()) << error; + EXPECT_EQ(model->mat_texid[mjTEXROLE_RGB], 1); + + mj_deleteModel(model); +} + +TEST_F(XMLReaderTest, MaterialTextureFailTest) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + )"; + + MockFilesystem fs("MaterialTextureFailTest"); + fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng)); + fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng)); + fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml)); + std::string modelpath = fs.FullPath("model.xml"); + + std::array error; + mjModel* m = LoadModelFromString(xml, error.data(), error.size()); + EXPECT_THAT(m, IsNull()); + EXPECT_THAT(error.data(), HasSubstr("A material with a texture attribute " + "cannot have texture sub-elements")); +} + TEST_F(XMLReaderTest, IncludeAssetsTest) { static constexpr char xml[] = R"(