Skip to content

Commit 3444b8e

Browse files
Implement asset deduplication for model attachment
1 parent 0abf184 commit 3444b8e

File tree

3 files changed

+17389
-3
lines changed

3 files changed

+17389
-3
lines changed

src/user/user_model.cc

Lines changed: 166 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
#include <unordered_map>
3333
#include <utility>
3434
#include <vector>
35+
#include <filesystem>
36+
#include <fstream>
3537

3638
#include <mujoco/mjdata.h>
3739
#include <mujoco/mjmacro.h>
@@ -1166,10 +1168,173 @@ mjCPlugin* mjCModel::AddPlugin() {
11661168

11671169
// append spec to spec
11681170
void mjCModel::AppendSpec(mjSpec* spec) {
1169-
// TODO: check if the spec is already in the list
1171+
// Check if this spec already exists in our model (asset deduplication)
1172+
mjSpec* existing = FindDuplicateAsset(spec);
1173+
if (existing) {
1174+
// If we found a duplicate, free the incoming spec and return
1175+
mj_deleteSpec(spec);
1176+
return;
1177+
}
1178+
1179+
// No duplicate found, add the new spec
11701180
specs_.push_back(spec);
11711181
}
11721182

1183+
// Find duplicate assets and return the existing one if found
1184+
mjSpec* mjCModel::FindDuplicateAsset(const mjSpec* spec) {
1185+
if (!spec) {
1186+
return nullptr;
1187+
}
1188+
1189+
// Get the modelname for comparison
1190+
std::string spec_name = mjs_getString(spec->modelname);
1191+
if (spec_name.empty()) {
1192+
return nullptr; // Cannot compare unnamed assets effectively yet
1193+
}
1194+
1195+
// Check if we have an asset with the same name already
1196+
for (auto& existing : specs_) {
1197+
std::string existing_name = mjs_getString(existing->modelname);
1198+
if (existing_name == spec_name) {
1199+
// --- Mesh Comparison ---
1200+
if (spec->mesh.mesh && existing->mesh.mesh) { // Check both exist
1201+
// If mesh file is set, compare the file path
1202+
std::string spec_file = mjs_getString(spec->mesh.file);
1203+
std::string existing_file = mjs_getString(existing->mesh.file);
1204+
1205+
// If file paths differ, they are not the same asset
1206+
if (!spec_file.empty() && spec_file != existing_file) {
1207+
continue;
1208+
}
1209+
1210+
// Compare buffer sizes (metadata check)
1211+
if (spec->mesh.vertex_buffer_size == existing->mesh.vertex_buffer_size &&
1212+
spec->mesh.normal_buffer_size == existing->mesh.normal_buffer_size &&
1213+
spec->mesh.texcoord_buffer_size == existing->mesh.texcoord_buffer_size &&
1214+
spec->mesh.face_buffer_size == existing->mesh.face_buffer_size) {
1215+
1216+
// Metadata matches, proceed to content comparison
1217+
bool content_matches = true;
1218+
1219+
// Compare vertex buffer content
1220+
if (spec->mesh.vertex_buffer_size > 0 &&
1221+
memcmp(spec->mesh.vertex_buffer, existing->mesh.vertex_buffer, spec->mesh.vertex_buffer_size) != 0) {
1222+
content_matches = false;
1223+
}
1224+
1225+
// Compare normal buffer content
1226+
if (content_matches && spec->mesh.normal_buffer_size > 0 &&
1227+
memcmp(spec->mesh.normal_buffer, existing->mesh.normal_buffer, spec->mesh.normal_buffer_size) != 0) {
1228+
content_matches = false;
1229+
}
1230+
1231+
// Compare texcoord buffer content
1232+
if (content_matches && spec->mesh.texcoord_buffer_size > 0 &&
1233+
memcmp(spec->mesh.texcoord_buffer, existing->mesh.texcoord_buffer, spec->mesh.texcoord_buffer_size) != 0) {
1234+
content_matches = false;
1235+
}
1236+
1237+
// Compare face buffer content
1238+
if (content_matches && spec->mesh.face_buffer_size > 0 &&
1239+
memcmp(spec->mesh.face_buffer, existing->mesh.face_buffer, spec->mesh.face_buffer_size) != 0) {
1240+
content_matches = false;
1241+
}
1242+
1243+
// If both metadata and content match, return the existing spec
1244+
if (content_matches) {
1245+
return existing;
1246+
}
1247+
}
1248+
} // End Mesh Comparison
1249+
1250+
// --- Texture Comparison ---
1251+
else if (spec->texture.texture && existing->texture.texture) { // Check both exist
1252+
std::string spec_file = mjs_getString(spec->texture.file);
1253+
std::string existing_file = mjs_getString(existing->texture.file);
1254+
1255+
// If file paths differ, they are not the same asset
1256+
if (!spec_file.empty() && spec_file != existing_file) {
1257+
continue;
1258+
}
1259+
1260+
// Compare dimensions (metadata check)
1261+
if (spec->texture.width == existing->texture.width &&
1262+
spec->texture.height == existing->texture.height) {
1263+
1264+
// Metadata matches, proceed to content comparison
1265+
size_t data_size = (size_t)spec->texture.width * spec->texture.height * 3; // Assuming RGB8
1266+
if (data_size > 0 && spec->texture.rgb && existing->texture.rgb) { // Check data exists
1267+
if (memcmp(spec->texture.rgb, existing->texture.rgb, data_size) == 0) {
1268+
// Metadata and content match
1269+
return existing;
1270+
}
1271+
} else if (data_size == 0) {
1272+
// Dimensions are zero, considered match if metadata matched
1273+
return existing;
1274+
}
1275+
}
1276+
} // End Texture Comparison
1277+
1278+
// --- Height Field Comparison ---
1279+
else if (spec->hfield.hfield && existing->hfield.hfield) { // Check both exist
1280+
std::string spec_file = mjs_getString(spec->hfield.file);
1281+
std::string existing_file = mjs_getString(existing->hfield.file);
1282+
1283+
// If file paths differ, they are not the same asset
1284+
if (!spec_file.empty() && spec_file != existing_file) {
1285+
continue;
1286+
}
1287+
1288+
// Compare dimensions (metadata check)
1289+
if (spec->hfield.nrow == existing->hfield.nrow &&
1290+
spec->hfield.ncol == existing->hfield.ncol) {
1291+
1292+
// Metadata matches, proceed to content comparison
1293+
size_t data_size = (size_t)spec->hfield.nrow * spec->hfield.ncol;
1294+
if (data_size > 0 && spec->hfield.data && existing->hfield.data) { // Check data exists
1295+
// Assuming data is float*, size for memcmp is count * sizeof(type)
1296+
if (memcmp(spec->hfield.data, existing->hfield.data, data_size * sizeof(float)) == 0) {
1297+
// Metadata and content match
1298+
return existing;
1299+
}
1300+
} else if (data_size == 0) {
1301+
// Dimensions are zero, considered match if metadata matched
1302+
return existing;
1303+
}
1304+
}
1305+
} // End Height Field Comparison
1306+
1307+
// --- Skin Comparison (Metadata only for now) ---
1308+
else if (spec->skin.skin && existing->skin.skin) { // Check both exist
1309+
if (spec->skin.vertex_count == existing->skin.vertex_count &&
1310+
spec->skin.face_count == existing->skin.face_count) {
1311+
// Skin vertex and face counts match (metadata only)
1312+
// Consider adding content comparison later if needed
1313+
return existing;
1314+
}
1315+
} // End Skin Comparison
1316+
1317+
// --- Fallback for other/unidentified types with same name ---
1318+
// If none of the specific asset types matched but names are identical,
1319+
// consider them the same. This might need refinement based on how other
1320+
// asset types are uniquely identified.
1321+
else if (!spec->mesh.mesh && !existing->mesh.mesh &&
1322+
!spec->texture.texture && !existing->texture.texture &&
1323+
!spec->hfield.hfield && !existing->hfield.hfield &&
1324+
!spec->skin.skin && !existing->skin.skin) {
1325+
// Neither spec nor existing have the common asset types, rely on name.
1326+
return existing;
1327+
}
1328+
1329+
// If names matched but asset types or content (where checked) didn't,
1330+
// continue checking other assets in specs_
1331+
}
1332+
}
1333+
1334+
// No duplicate found after checking all existing specs
1335+
return nullptr;
1336+
}
1337+
11731338

11741339

11751340
//------------------------ API FOR ACCESS TO MODEL ELEMENTS ---------------------------------------

src/user/user_model.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ class mjCModel : public mjCModel_, private mjSpec {
217217
mjCPlugin* AddPlugin();
218218
void AppendSpec(mjSpec* spec);
219219

220+
// Find duplicate assets and return the existing one if found
221+
mjSpec* FindDuplicateAsset(const mjSpec* spec);
222+
220223
// delete elements marked as discard=true
221224
template <class T> void Delete(std::vector<T*>& elements,
222225
const std::vector<bool>& discard);

0 commit comments

Comments
 (0)