Change row-flipping logic for heightfields.

Move row-flipping logic from `mjCModel::CopyBack` to `mjXWriter`.

PiperOrigin-RevId: 842220322
Change-Id: I6fb09db6c9e8689f6e435f82f58dd3e7c4314478
This commit is contained in:
Yuval Tassa
2025-12-09 07:01:16 -08:00
committed by Copybara-Service
parent 925c533580
commit c064abc3da
5 changed files with 43 additions and 10 deletions
+2 -1
View File
@@ -1559,7 +1559,8 @@ also known as terrain map, is a 2D matrix of elevation data. The data can be spe
:at:`elevation`: :at-val:`real(nrow*ncol), optional`
This attribute specifies the elevation data matrix. Values are automatically normalized to lie between 0 and 1 by
first subtracting the minimum value and then dividing by the (maximum-minimum) difference, if not 0. If not provided,
values are set to 0.
values are set to 0. Note that the row order of data in :ref:`mjModel` and :ref:`mjsHField` is flipped w.r.t. the
order in XML i.e., it is bottom-to-top.
.. _asset-hfield-size:
+1 -5
View File
@@ -5318,11 +5318,7 @@ bool mjCModel::CopyBack(const mjModel* m) {
int ncol = m->hfield_ncol[i];
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 < nrow; j++) {
int flip = nrow-1-j;
mjuu_copyvec(userdata + flip*ncol, modeldata+j*ncol, ncol);
}
memcpy(userdata, modeldata, nrow*ncol*sizeof(float));
}
}
+15 -3
View File
@@ -1585,11 +1585,23 @@ void mjXWriter::Asset(XMLElement* root) {
WriteAttrTxt(elem, "content_type", hfield->content_type_);
WriteAttrTxt(elem, "file", hfield->file_);
} else {
WriteAttrInt(elem, "nrow", hfield->nrow);
WriteAttrInt(elem, "ncol", hfield->ncol);
int nrow = hfield->nrow;
int ncol = hfield->ncol;
WriteAttrInt(elem, "nrow", nrow);
WriteAttrInt(elem, "ncol", ncol);
if (!hfield->get_userdata().empty()) {
// copy in reverse row order, so XML string is top-to-bottom
std::vector<float> flipped(nrow * ncol);
const std::vector<float>& userdata = hfield->get_userdata();
for (int i = 0; i < nrow; i++) {
int flip = nrow - 1 - i;
for (int j = 0; j < ncol; j++) {
flipped[i * ncol + j] = userdata[flip * ncol + j];
}
}
string text;
Vector2String(text, hfield->get_userdata(), hfield->ncol);
Vector2String(text, flipped, ncol);
WriteAttrTxt(elem, "elevation", text);
}
}
+19
View File
@@ -0,0 +1,19 @@
<mujoco>
<asset>
<hfield name="hfield" nrow="5" ncol="5" size=".8 .8 .8 .3"
elevation="1 0 1 1 0
0 1 1 0 1
1 .5 1 0 1
0 1 0 0 1
0 1 0 0 1"/>
</asset>
<include file="ray_swing.xml"/>
<worldbody>
<body pos="-.5 .002 0" euler="10 50 -40" mocap="true">
<geom type="hfield" hfield="hfield"/>
</body>
</worldbody>
</mujoco>
+6 -1
View File
@@ -38,10 +38,11 @@
namespace mujoco {
namespace {
using ::testing::ElementsAre;
using ::testing::FloatEq;
using ::testing::HasSubstr;
using ::testing::Not;
using ::testing::NotNull;
using ::testing::FloatEq;
using XMLWriterTest = PluginTest;
@@ -998,6 +999,10 @@ TEST_F(XMLWriterTest, WritesHfield) {
int size = model->hfield_nrow[0]*model->hfield_ncol[0];
EXPECT_EQ(size, 6);
// check that the data is normalized and in row-major, bottom-to-top order
EXPECT_THAT(AsVector(model->hfield_data, 6),
ElementsAre(.8, 1, .4, .6, 0, .2));
// save and read, compare data
mjModel* mtemp = LoadModelFromString(SaveAndReadXml(model));
ASSERT_THAT(mtemp, NotNull());