From 06505f49f94781e477523fde39d2717f1cf5a28d Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Tue, 13 Feb 2024 02:08:31 -0800 Subject: [PATCH] Add mjmTexture to C API. PiperOrigin-RevId: 606543622 Change-Id: I2db743c1c40116f0503329fb1d004d9b041e83e7 --- src/user/user_api.cc | 22 +++++++++ src/user/user_api.h | 65 +++++++++++++++++++++++++-- src/user/user_init.cc | 15 +++++++ src/user/user_model.cc | 1 + src/user/user_objects.cc | 87 +++++++++++++++++++++--------------- src/user/user_objects.h | 60 +++++++------------------ src/xml/xml_native_reader.cc | 37 ++++++++++----- src/xml/xml_native_writer.cc | 16 +++---- 8 files changed, 202 insertions(+), 101 deletions(-) diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 2aeea3d6..ca43c95a 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -137,6 +137,15 @@ mjmFrame* mjm_addFrame(mjmBody* bodyspec, mjmFrame* parentframe) { +// add texture to model +mjmTexture* mjm_addTexture(void* model) { + mjCModel* modelC = static_cast(model); + mjCTexture* texture = modelC->AddTexture(); + return &texture->spec; +} + + + // add material to model mjmMaterial* mjm_addMaterial(void* model, void* defspec) { mjCModel* modelC = static_cast(model); @@ -312,6 +321,19 @@ void mjm_setString(mjString dest, const char* text) { +// Set specific entry in destination string vector. +mjtByte mjm_setInStringVec(mjStringVec dest, int i, const char* text) { + std::vector* v = reinterpret_cast*>(dest); + if (v->size() <= i) { + mju_error("Requested index in mjm_setInStringVec is out of bounds"); + return 0; + } + v->at(i) = std::string(text); + return 1; +} + + + // split text and copy into string array void mjm_setStringVec(mjStringVec dest, const char* text) { std::vector* v = reinterpret_cast*>(dest); diff --git a/src/user/user_api.h b/src/user/user_api.h index c2327f5a..0de97b41 100644 --- a/src/user/user_api.h +++ b/src/user/user_api.h @@ -38,12 +38,28 @@ typedef struct _mjFloatVec* mjFloatVec; //---------------------------------- enum types (mjt) ---------------------------------------------- -typedef enum _mjtGeomInertia { // type of inertia inference - mjINERTIA_VOLUME, // mass distributed in the volume - mjINERTIA_SHELL, // mass distributed on the surface +typedef enum _mjtGeomInertia { // type of inertia inference + mjINERTIA_VOLUME, // mass distributed in the volume + mjINERTIA_SHELL, // mass distributed on the surface } mjtGeomInertia; +typedef enum _mjtBuiltin { // type of built-in procedural texture + mjBUILTIN_NONE = 0, // no built-in texture + mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2 + mjBUILTIN_CHECKER, // checker pattern: rgb1, rgb2 + mjBUILTIN_FLAT // 2d: rgb1; cube: rgb1-up, rgb2-side, rgb3-down +} mjtBuiltin; + + +typedef enum _mjtMark { // mark type for procedural textures + mjMARK_NONE = 0, // no mark + mjMARK_EDGE, // edges + mjMARK_CROSS, // cross + mjMARK_RANDOM // random dots +} mjtMark; + + //---------------------------------- attribute structs (mjm) --------------------------------------- typedef struct _mjmOrientation { // alternative orientation specifiers @@ -309,6 +325,40 @@ typedef struct _mjmFlex { } mjmFlex; +typedef struct _mjmTexture { // texture specification + mjElement element; // internal, do not modify + mjString name; // name + mjString classname; // class name + mjtTexture type; // texture type + + // method 1: builtin + int builtin; // builtin type (mjtBuiltin) + int mark; // mark type (mjtMark) + double rgb1[3]; // first color for builtin + double rgb2[3]; // second color for builtin + double markrgb[3]; // mark color + double random; // probability of random dots + int height; // height in pixels (square for cube and skybox) + int width; // width in pixels + + // method 2: single file + mjString content_type; // content type of file + mjString file; // png file to load; use for all sides of cube + int gridsize[2]; // size of grid for composite file; (1,1)-repeat + char gridlayout[13]; // row-major: L,R,F,B,U,D for faces; . for unused + + // method 3: separate files + mjStringVec cubefiles; // different file for each side of the cube + + // flip options + mjtByte hflip; // horizontal flip + mjtByte vflip; // vertical flip + + // other + mjString info; // message appended to compiler errors +} mjmTexture; + + typedef struct _mjmMaterial { // material specification mjElement element; // internal, do not modify mjString name; // name @@ -508,6 +558,9 @@ MJAPI mjmFrame* mjm_addFrame(mjmBody* body, mjmFrame* parentframe); // Add flex to model. MJAPI mjmFlex* mjm_addFlex(void* model); +// Add texture to model. +MJAPI mjmTexture* mjm_addTexture(void* model); + // Add material to model. MJAPI mjmMaterial* mjm_addMaterial(void* model, void* defspec); @@ -562,6 +615,9 @@ MJAPI void mjm_setString(mjString dest, const char* text); // Split text to entries and copy to destination string vector. MJAPI void mjm_setStringVec(mjStringVec dest, const char* text); +// Set specific entry in destination string vector. +MJAPI mjtByte mjm_setInStringVec(mjStringVec dest, int i, const char* text); + // Add text entry to destination string vector. MJAPI void mjm_addToStringVec(mjStringVec dest, const char* text); @@ -616,6 +672,9 @@ MJAPI void mjm_defaultLight(mjmLight& light); // Default flex attributes. MJAPI void mjm_defaultFlex(mjmFlex& flex); +// Default texture attributes. +MJAPI void mjm_defaultTexture(mjmTexture& texture); + // Default material attributes. MJAPI void mjm_defaultMaterial(mjmMaterial& material); diff --git a/src/user/user_init.cc b/src/user/user_init.cc index 34c0402c..dba2367c 100644 --- a/src/user/user_init.cc +++ b/src/user/user_init.cc @@ -15,6 +15,7 @@ #include #include #include +#include "cc/array_safety.h" #include "user/user_api.h" #include "user/user_util.h" @@ -191,6 +192,20 @@ void mjm_defaultFlex(mjmFlex& flex) { +// default texture attributes +void mjm_defaultTexture(mjmTexture& texture) { + memset(&texture, 0, sizeof(mjmTexture)); + texture.type = mjTEXTURE_CUBE; + mjuu_setvec(texture.rgb1, 0.8, 0.8, 0.8); + mjuu_setvec(texture.rgb2, 0.5, 0.5, 0.5); + mjuu_setvec(texture.markrgb, 0, 0, 0); + texture.random = 0.01; + texture.gridsize[0] = texture.gridsize[1] = 1; + mujoco::util::strcpy_arr(texture.gridlayout, "............"); +} + + + // default material attributes void mjm_defaultMaterial(mjmMaterial& material) { memset(&material, 0, sizeof(mjmMaterial)); diff --git a/src/user/user_model.cc b/src/user/user_model.cc index b9cbecaf..7201240c 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -904,6 +904,7 @@ void mjCModel::SetDefaultNames(std::vector& assets) { // use filename if name is missing for (int i=0; iCopyFromSpec(); if (assets[i]->name.empty()) { stripped = mjuu_strippath(assets[i]->get_file()); assets[i]->name = mjuu_stripext(stripped); diff --git a/src/user/user_objects.cc b/src/user/user_objects.cc index c159be59..65c59e09 100644 --- a/src/user/user_objects.cc +++ b/src/user/user_objects.cc @@ -2579,35 +2579,50 @@ void mjCHField::Compile(const mjVFS* vfs) { // initialize defaults mjCTexture::mjCTexture(mjCModel* _model) { + mjm_defaultTexture(spec); + // set model pointer model = _model; - // clear user settings: builtin - type = mjTEXTURE_CUBE; - builtin = mjBUILTIN_NONE; - mark = mjMARK_NONE; - mjuu_setvec(rgb1, 0.8, 0.8, 0.8); - mjuu_setvec(rgb2, 0.5, 0.5, 0.5); - mjuu_setvec(markrgb, 0, 0, 0); - random = 0.01; - height = width = 0; - // clear user settings: single file - file.clear(); - gridsize[0] = gridsize[1] = 1; - mju::strcpy_arr(gridlayout, "............"); + spec_file_.clear(); + spec_content_type_.clear(); // clear user settings: separate file - for (int i=0; i<6; i++) { - cubefiles[i].clear(); - } - - // clear flip options - hflip = false; - vflip = false; + spec_cubefiles_.assign(6, ""); // clear internal variables rgb = 0; + + // point to local (needs to be after defaults) + PointToLocal(); + + // in case this camera is not compiled + CopyFromSpec(); +} + + + +void mjCTexture::PointToLocal() { + spec.element = (mjElement)this; + spec.name = (mjString)&name; + spec.classname = (mjString)&classname; + spec.file = (mjString)&spec_file_; + spec.content_type = (mjString)&spec_content_type_; + spec.cubefiles = (mjStringVec)&spec_cubefiles_; + spec.info = (mjString)&info; +} + + + +void mjCTexture::CopyFromSpec() { + *static_cast(this) = spec; + file_ = spec_file_; + content_type_ = spec_content_type_; + cubefiles_ = spec_cubefiles_; + file = (mjString)&file_; + content_type = (mjString)&content_type_; + cubefiles = (mjStringVec)&cubefiles_; } @@ -2930,7 +2945,7 @@ void mjCTexture::LoadCustom(mjResource* resource, void mjCTexture::LoadFlip(string filename, const mjVFS* vfs, std::vector& image, unsigned int& w, unsigned int& h) { - std::string asset_type = GetAssetContentType(filename, content_type); + std::string asset_type = GetAssetContentType(filename, content_type_); // fallback to custom if (asset_type.empty()) { @@ -3017,7 +3032,7 @@ void mjCTexture::Load2D(string filename, const mjVFS* vfs) { rgb = (mjtByte*) mju_malloc(3*width*height); if (!rgb) { throw mjCError(this, "Could not allocate memory for texture '%s' (id %d)", - (const char*)file.c_str(), id); + (const char*)file_.c_str(), id); } memcpy(rgb, image.data(), 3*width*height); image.clear(); @@ -3043,7 +3058,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) { if (w/gridsize[1]!=h/gridsize[0] || (w%gridsize[1]) || (h%gridsize[0])) { throw mjCError(this, "PNG size must be integer multiple of gridsize in texture '%s' (id %d)", - (const char*)file.c_str(), id); + (const char*)file_.c_str(), id); } // assign size: repeated or full @@ -3059,7 +3074,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) { if (!rgb) { throw mjCError(this, "Could not allocate memory for texture '%s' (id %d)", - (const char*)file.c_str(), id); + (const char*)file_.c_str(), id); } // copy: repeated @@ -3090,7 +3105,7 @@ void mjCTexture::LoadCubeSingle(string filename, const mjVFS* vfs) { i = 5; } else if (gridlayout[k]!='.') throw mjCError(this, "gridlayout symbol is not among '.RLUDFB' in texture '%s' (id %d)", - (const char*)file.c_str(), id); + (const char*)file_.c_str(), id); // load if specified if (i>=0) { @@ -3132,14 +3147,14 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { // process nonempty files for (int i=0; i<6; i++) { - if (!cubefiles[i].empty()) { + if (!cubefiles_[i].empty()) { // remove path from file if necessary if (model->strippath) { - cubefiles[i] = mjuu_strippath(cubefiles[i]); + cubefiles_[i] = mjuu_strippath(cubefiles_[i]); } // make filename - string filename = mjuu_makefullname(model->modelfiledir, model->texturedir, cubefiles[i]); + string filename = mjuu_makefullname(model->modelfiledir, model->texturedir, cubefiles_[i]); // load PNG or custom unsigned int w, h; @@ -3150,7 +3165,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { if (w!=h) { throw mjCError(this, "Non-square PNG file '%s' in cube or skybox id %d", - (const char*)cubefiles[i].c_str(), id); + (const char*)cubefiles_[i].c_str(), id); } // first file: set size and allocate data @@ -3169,7 +3184,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { else if (width!=w) { throw mjCError(this, "PNG file '%s' has incompatible size in texture id %d", - (const char*)cubefiles[i].c_str(), id); + (const char*)cubefiles_[i].c_str(), id); } // copy data @@ -3199,6 +3214,8 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) { // compiler void mjCTexture::Compile(const mjVFS* vfs) { + CopyFromSpec(); + // builtin if (builtin!=mjBUILTIN_NONE) { // check size @@ -3230,14 +3247,14 @@ void mjCTexture::Compile(const mjVFS* vfs) { } // single file - else if (!file.empty()) { + else if (!file_.empty()) { // remove path from file if necessary if (model->strippath) { - file = mjuu_strippath(file); + file_ = mjuu_strippath(file_); } // make filename - string filename = mjuu_makefullname(model->modelfiledir, model->texturedir, file); + string filename = mjuu_makefullname(model->modelfiledir, model->texturedir, file_); // dispatch if (type==mjTEXTURE_2D) { @@ -3259,14 +3276,14 @@ void mjCTexture::Compile(const mjVFS* vfs) { // at least one cubefile must be defined bool defined = false; for (int i=0; i<6; i++) { - if (!cubefiles[i].empty()) { + if (!cubefiles_[i].empty()) { defined = true; break; } } if (!defined) { throw mjCError(this, - "No cubefiles defined in cube or skybox texture '%s' (id %d)", + "No cubefiles_ defined in cube or skybox texture '%s' (id %d)", (const char*)name.c_str(), id); } diff --git a/src/user/user_objects.h b/src/user/user_objects.h index 47f4398d..c8a5a742 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -68,24 +68,6 @@ class mjXURDF; // defined in xml_urdf const int mjGEOMINFO[mjNGEOMTYPES] = {3, 0, 1, 2, 3, 2, 3, 0}; -// builtin type for procedural textures -typedef enum _mjtBuiltin { - mjBUILTIN_NONE = 0, // no builtin - mjBUILTIN_GRADIENT, // circular gradient: rgb1->rgb2->rgb3 - mjBUILTIN_CHECKER, // checker pattern: rgb1, rgb2 - mjBUILTIN_FLAT // 2d: rgb1; cube: rgb1-up, rgb2-side, rgb3-down -} mjtBuiltin; - - -// mark type for procedural textures -typedef enum _mjtMark { - mjMARK_NONE = 0, // no mark - mjMARK_EDGE, // paint edges - mjMARK_CROSS, // paint cross - mjMARK_RANDOM // paint random dots -} mjtMark; - - // error information class [[nodiscard]] mjCError { public: @@ -871,7 +853,7 @@ class mjCHField : public mjCBase { //------------------------- class mjCTexture ------------------------------------------------------- // Describes a texture -class mjCTexture : public mjCBase { +class mjCTexture : public mjCBase, private mjmTexture { friend class mjCModel; friend class mjXReader; friend class mjXWriter; @@ -879,32 +861,17 @@ class mjCTexture : public mjCBase { public: ~mjCTexture(); // destructor - std::string get_file() const { return file; } + mjmTexture spec; + using mjCBase::name; + using mjCBase::classname; + using mjCBase::info; - mjtTexture type; // texture type + void CopyFromSpec(void); + void PointToLocal(void); - // method 1: builtin - mjtBuiltin builtin; // builtin type - mjtMark mark; // mark type - double rgb1[3]; // first color for builtin - double rgb2[3]; // second color for builtin - double markrgb[3]; // mark color - double random; // probability of random dots - int height; // height in pixels (square for cube and skybox) - int width; // width in pixels - - // method 2: single file - std::string content_type; // content type of file - std::string file; // png file to load; use for all sides of cube - int gridsize[2]; // size of grid for composite file; (1,1)-repeat - char gridlayout[13]; // row-major: L,R,F,B,U,D for faces; . for unused - - // method 3: separate files - std::string cubefiles[6]; // different file for each side of the cube - - // flip options - bool hflip; // horizontal flip - bool vflip; // vertical flip + std::string get_file() const { return file_; } + std::string get_content_type() const { return content_type_; } + std::vector get_cubefiles() const { return cubefiles_; } private: mjCTexture(mjCModel*); // constructor @@ -928,6 +895,13 @@ class mjCTexture : public mjCBase { unsigned int& w, unsigned int& h); mjtByte* rgb; // rgb data + std::string file_; + std::string content_type_; + std::vector cubefiles_; + + std::string spec_file_; + std::string spec_content_type_; + std::vector spec_cubefiles_; }; diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index c307dc1e..6cba45f9 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -2980,7 +2980,7 @@ void mjXReader::Visual(XMLElement* section) { // asset section parser void mjXReader::Asset(XMLElement* section) { int n; - string text, name; + string text, name, texname, content_type, file; XMLElement* elem; // iterate over child elements @@ -2998,16 +2998,25 @@ void mjXReader::Asset(XMLElement* section) { // texture sub-element if (name=="texture") { // create texture - mjCTexture* ptex = model->AddTexture(); - GetXMLPos(elem, ptex); + mjmTexture* ptex = mjm_addTexture(model); + + // write error info + mjm_setString(ptex->info, + std::string("line = " + std::to_string(elem->GetLineNum()) + ", column = -1").c_str()); // read attributes if (MapValue(elem, "type", &n, texture_map, texture_sz)) { ptex->type = (mjtTexture)n; } - ReadAttrTxt(elem, "name", ptex->name); - ReadAttrTxt(elem, "content_type", ptex->content_type); - ReadAttrTxt(elem, "file", ptex->file); + if (ReadAttrTxt(elem, "name", texname)) { + mjm_setString(ptex->name, texname.c_str()); + } + if (ReadAttrTxt(elem, "content_type", content_type)) { + mjm_setString(ptex->content_type, content_type.c_str()); + } + if (ReadAttrTxt(elem, "file", file)) { + mjm_setString(ptex->file, file.c_str()); + } ReadAttrInt(elem, "width", &ptex->width); ReadAttrInt(elem, "height", &ptex->height); ReadAttr(elem, "rgb1", 3, ptex->rgb1, text); @@ -3042,12 +3051,16 @@ void mjXReader::Asset(XMLElement* section) { } // separate files - ReadAttrTxt(elem, "fileright", ptex->cubefiles[0]); - ReadAttrTxt(elem, "fileleft", ptex->cubefiles[1]); - ReadAttrTxt(elem, "fileup", ptex->cubefiles[2]); - ReadAttrTxt(elem, "filedown", ptex->cubefiles[3]); - ReadAttrTxt(elem, "filefront", ptex->cubefiles[4]); - ReadAttrTxt(elem, "fileback", ptex->cubefiles[5]); + std::vector cubefiles(6); + ReadAttrTxt(elem, "fileright", cubefiles[0]); + ReadAttrTxt(elem, "fileleft", cubefiles[1]); + ReadAttrTxt(elem, "fileup", cubefiles[2]); + ReadAttrTxt(elem, "filedown", cubefiles[3]); + ReadAttrTxt(elem, "filefront", cubefiles[4]); + ReadAttrTxt(elem, "fileback", cubefiles[5]); + for (int i = 0; i < cubefiles.size(); i++) { + mjm_setInStringVec(ptex->cubefiles, i, cubefiles[i].c_str()); + } } // material sub-element diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index 5fe4965f..36fd29fc 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -1358,16 +1358,16 @@ void mjXWriter::Asset(XMLElement* root) { // write textures loaded from files else { // write single file - WriteAttrTxt(elem, "content_type", ptex->content_type); - WriteAttrTxt(elem, "file", ptex->file); + WriteAttrTxt(elem, "content_type", ptex->get_content_type()); + WriteAttrTxt(elem, "file", ptex->get_file()); // write separate files - WriteAttrTxt(elem, "fileright", ptex->cubefiles[0]); - WriteAttrTxt(elem, "fileleft", ptex->cubefiles[1]); - WriteAttrTxt(elem, "fileup", ptex->cubefiles[2]); - WriteAttrTxt(elem, "filedown", ptex->cubefiles[3]); - WriteAttrTxt(elem, "filefront", ptex->cubefiles[4]); - WriteAttrTxt(elem, "fileback", ptex->cubefiles[5]); + WriteAttrTxt(elem, "fileright", ptex->get_cubefiles()[0]); + WriteAttrTxt(elem, "fileleft", ptex->get_cubefiles()[1]); + WriteAttrTxt(elem, "fileup", ptex->get_cubefiles()[2]); + WriteAttrTxt(elem, "filedown", ptex->get_cubefiles()[3]); + WriteAttrTxt(elem, "filefront", ptex->get_cubefiles()[4]); + WriteAttrTxt(elem, "fileback", ptex->get_cubefiles()[5]); if (ptex->hflip) { WriteAttrKey(elem, "hflip", bool_map, 2, 1); }