diff --git a/src/user/user_api.cc b/src/user/user_api.cc index 8ff78504..0853531f 100644 --- a/src/user/user_api.cc +++ b/src/user/user_api.cc @@ -147,6 +147,15 @@ mjmMesh* mjm_addMesh(void* model, void* defspec) { +// add height field to model +mjmHField* mjm_addHField(void* model) { + mjCModel* modelC = static_cast(model); + mjCHField* heightField = modelC->AddHField(); + return &heightField->spec; +} + + + // add texture to model mjmTexture* mjm_addTexture(void* model) { mjCModel* modelC = static_cast(model); diff --git a/src/user/user_api.h b/src/user/user_api.h index 9db90f23..00562314 100644 --- a/src/user/user_api.h +++ b/src/user/user_api.h @@ -346,6 +346,19 @@ typedef struct _mjmMesh { // mesh specification } mjmMesh; +typedef struct _mjmHField { // height field specification + mjElement element; // internal, do not modify + mjString name; // name + mjString content_type; // content type of file + mjString file; // file: (nrow, ncol, [elevation data]) + double size[4]; // hfield size (ignore referencing geom size) + int nrow; // number of rows + int ncol; // number of columns + mjFloatVec userdata; // user-provided elevation data + mjString info; // message appended to compiler errors +} mjmHField; + + typedef struct _mjmTexture { // texture specification mjElement element; // internal, do not modify mjString name; // name @@ -623,6 +636,9 @@ MJAPI mjmFlex* mjm_addFlex(void* model); // Add mesh to model. MJAPI mjmMesh* mjm_addMesh(void* model, void* defspec); +// Add height field to model. +MJAPI mjmHField* mjm_addHField(void* model); + // Add texture to model. MJAPI mjmTexture* mjm_addTexture(void* model); @@ -752,6 +768,9 @@ MJAPI void mjm_defaultFlex(mjmFlex& flex); // Default mesh attributes. MJAPI void mjm_defaultMesh(mjmMesh& mesh); +// Default height field attributes. +MJAPI void mjm_defaultHField(mjmHField& hfield); + // Default texture attributes. MJAPI void mjm_defaultTexture(mjmTexture& texture); diff --git a/src/user/user_init.cc b/src/user/user_init.cc index d701b0ea..71cd94c3 100644 --- a/src/user/user_init.cc +++ b/src/user/user_init.cc @@ -203,6 +203,13 @@ void mjm_defaultMesh(mjmMesh& mesh) { +// default height field attributes +void mjm_defaultHField(mjmHField& hfield) { + memset(&hfield, 0, sizeof(mjmHField)); +} + + + // default texture attributes void mjm_defaultTexture(mjmTexture& texture) { memset(&texture, 0, sizeof(mjmTexture)); diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 860d9a0e..875df7e5 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -3387,11 +3387,11 @@ bool mjCModel::CopyBack(const mjModel* m) { mjCHField* phf; for (int i=0; iuserdata().size(); + int size = phf->get_userdata().size(); if (size) { int nrow = m->hfield_nrow[i]; int ncol = m->hfield_ncol[i]; - float* userdata = phf->userdata().data(); + float* userdata = phf->get_userdata().data(); float* modeldata = m->hfield_data + m->hfield_adr[i]; // copy back in reverse row order for (int j=0; j(this) = spec; + file_ = spec_file_; + content_type_ = spec_content_type_; + userdata_ = spec_userdata_; + file = (mjString)&file_; + content_type = (mjString)&content_type_; + userdata = (mjFloatVec)&userdata_; } @@ -2403,6 +2431,8 @@ mjCHField::~mjCHField() { if (data) { mju_free(data); } + userdata_.clear(); + spec_userdata_.clear(); } @@ -2496,16 +2526,19 @@ void mjCHField::LoadPNG(mjResource* resource) { -// user data setter -void mjCHField::set_userdata(std::optional>&& userdata) { - if (userdata.has_value()) { - userdata_ = std::move(userdata.value()); - } -} - - // compiler void mjCHField::Compile(const mjVFS* vfs) { + CopyFromSpec(); + + // copy userdata into data + if (!userdata_.empty()) { + data = (float*) mju_malloc(nrow*ncol*sizeof(float)); + if (!data) { + throw mjCError(this, "could not allocate buffers in hfield"); + } + memcpy(data, userdata_.data(), nrow*ncol*sizeof(float)); + } + // check size parameters for (int i=0; i<4; i++) if (size[i]<=0) @@ -2514,18 +2547,18 @@ void mjCHField::Compile(const mjVFS* vfs) { // remove path from file if necessary if (model->strippath) { - file = mjuu_strippath(file); + file_ = mjuu_strippath(file_); } // load from file if specified - if (!file.empty()) { + if (!file_.empty()) { // make sure hfield was not already specified manually if (nrow || ncol || data) { throw mjCError(this, "hfield '%s' (id = %d) specified from file and manually", name.c_str(), id); } - std::string asset_type = GetAssetContentType(file, content_type); + std::string asset_type = GetAssetContentType(file_, content_type_); // fallback to custom if (asset_type.empty()) { @@ -2536,7 +2569,7 @@ void mjCHField::Compile(const mjVFS* vfs) { throw mjCError(this, "unsupported content type: '%s'", asset_type.c_str()); } - string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file); + string filename = mjuu_makefullname(model->modelfiledir, model->meshdir, file_); mjResource* resource = LoadResource(filename, vfs); try { @@ -2564,7 +2597,7 @@ void mjCHField::Compile(const mjVFS* vfs) { emax = mjMAX(emax, data[i]); } if (emin>emax) { - throw mjCError(this, "invalid data range in hfield '%s'", file.c_str()); + throw mjCError(this, "invalid data range in hfield '%s'", file_.c_str()); } for (int i=0; i& userdata() { return userdata_; } - - // setter for user data - void set_userdata(std::optional>&& userdata); + std::vector& get_userdata() { return userdata_; } private: mjCHField(mjCModel* model); // constructor ~mjCHField(); // destructor - std::vector userdata_; // user-provided elevation data + float* data; // elevation data, row-major format void Compile(const mjVFS* vfs); // compiler void LoadCustom(mjResource* resource); // load from custom format void LoadPNG(mjResource* resource); // load from PNG format + + std::string file_; + std::string content_type_; + std::vector userdata_; + std::string spec_file_; + std::string spec_content_type_; + std::vector spec_userdata_; }; diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index c40510c8..c4a79a6d 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -3136,43 +3136,57 @@ void mjXReader::Asset(XMLElement* section) { // hfield sub-element else if (name=="hfield") { // create hfield - mjCHField* phf = model->AddHField(); - GetXMLPos(elem, phf); + mjmHField* phf = mjm_addHField(model); + + // write error info + mjm_setString(phf->info, + std::string("line = " + std::to_string(elem->GetLineNum()) + ", column = -1").c_str()); // read attributes - ReadAttrTxt(elem, "name", phf->name); - ReadAttrTxt(elem, "content_type", phf->content_type); - ReadAttrTxt(elem, "file", phf->file); + string name, content_type, file; + if (ReadAttrTxt(elem, "name", name)) { + mjm_setString(phf->name, name.c_str()); + } + if (ReadAttrTxt(elem, "content_type", content_type)) { + mjm_setString(phf->content_type, content_type.c_str()); + } + if (ReadAttrTxt(elem, "file", file)) { + mjm_setString(phf->file, file.c_str()); + } ReadAttrInt(elem, "nrow", &phf->nrow); ReadAttrInt(elem, "ncol", &phf->ncol); ReadAttr(elem, "size", 4, phf->size, text, true); // allocate buffer for dynamic hfield, copy user data if given - if (phf->file.empty() && phf->nrow>0 && phf->ncol>0) { + if (file.empty() && phf->nrow>0 && phf->ncol>0) { int nrow = phf->nrow; int ncol = phf->ncol; - phf->data = (float*) mju_malloc(nrow*ncol*sizeof(float)); // read user data - phf->set_userdata(ReadAttrVec(elem, "elevation")); + auto userdata = ReadAttrVec(elem, "elevation"); // user data given, copy into data - if (!phf->userdata().empty()) { - if (phf->userdata().size() != nrow*ncol) { + if (userdata.has_value()) { + if (userdata->size() != nrow*ncol) { throw mjXError(elem, "elevation data length must match nrow*ncol"); } // copy in reverse row order, so XML string is top-to-bottom - const float* userdata = phf->userdata().data(); + std::vector flipped(nrow*ncol); for (int i = 0; i < nrow; i++) { int flip = nrow-1-i; - memcpy(phf->data + flip*ncol, userdata + i*ncol, ncol*sizeof(float)); + for (int j = 0; j < ncol; j++) { + flipped[flip*ncol + j] = userdata->data()[i*ncol + j]; + } } + + mjm_setFloat(phf->userdata, flipped.data(), flipped.size()); } // user data not given, set to 0 else { - memset(phf->data, 0, phf->nrow*phf->ncol*sizeof(float)); + std::vector zero(nrow*ncol); + mjm_setFloat(phf->userdata, zero.data(), zero.size()); } } } diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index c505ec05..6bfaf22a 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -1416,15 +1416,15 @@ void mjXWriter::Asset(XMLElement* root) { // write attributes WriteAttrTxt(elem, "name", phf->name); WriteAttr(elem, "size", 4, phf->size); - if (!phf->file.empty()) { - WriteAttrTxt(elem, "content_type", phf->content_type); - WriteAttrTxt(elem, "file", phf->file); + if (!phf->file_.empty()) { + WriteAttrTxt(elem, "content_type", phf->content_type_); + WriteAttrTxt(elem, "file", phf->file_); } else { WriteAttrInt(elem, "nrow", phf->nrow); WriteAttrInt(elem, "ncol", phf->ncol); - if (!phf->userdata().empty()) { + if (!phf->get_userdata().empty()) { string text; - Vector2String(text, phf->userdata(), phf->ncol); + Vector2String(text, phf->get_userdata(), phf->ncol); WriteAttrTxt(elem, "elevation", text); } }