|
32 | 32 | #include <unordered_map> |
33 | 33 | #include <utility> |
34 | 34 | #include <vector> |
| 35 | +#include <filesystem> |
| 36 | +#include <fstream> |
35 | 37 |
|
36 | 38 | #include <mujoco/mjdata.h> |
37 | 39 | #include <mujoco/mjmacro.h> |
@@ -1166,10 +1168,173 @@ mjCPlugin* mjCModel::AddPlugin() { |
1166 | 1168 |
|
1167 | 1169 | // append spec to spec |
1168 | 1170 | 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 |
1170 | 1180 | specs_.push_back(spec); |
1171 | 1181 | } |
1172 | 1182 |
|
| 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 | + |
1173 | 1338 |
|
1174 | 1339 |
|
1175 | 1340 | //------------------------ API FOR ACCESS TO MODEL ELEMENTS --------------------------------------- |
|
0 commit comments