|
12 | 12 | #include <momentum/character_solver/collision_error_function.h> |
13 | 13 | #include <momentum/character_solver/distance_error_function.h> |
14 | 14 | #include <momentum/character_solver/fixed_axis_error_function.h> |
| 15 | +#include <momentum/character_solver/joint_to_joint_distance_error_function.h> |
15 | 16 | #include <momentum/character_solver/limit_error_function.h> |
16 | 17 | #include <momentum/character_solver/model_parameters_error_function.h> |
17 | 18 | #include <momentum/character_solver/normal_error_function.h> |
@@ -1267,6 +1268,180 @@ or maintaining the width of body parts.)") |
1267 | 1268 | "Returns the number of constraints."); |
1268 | 1269 | } |
1269 | 1270 |
|
| 1271 | +void defJointToJointDistanceErrorFunction(py::module_& m) { |
| 1272 | + py::class_<mm::JointToJointDistanceConstraintT<float>>(m, "JointToJointDistanceConstraint") |
| 1273 | + .def( |
| 1274 | + "__repr__", |
| 1275 | + [](const mm::JointToJointDistanceConstraintT<float>& self) { |
| 1276 | + return fmt::format( |
| 1277 | + "JointToJointDistanceConstraint(joint1={}, joint2={}, offset1=[{:.3f}, {:.3f}, {:.3f}], offset2=[{:.3f}, {:.3f}, {:.3f}], weight={}, target_distance={})", |
| 1278 | + self.joint1, |
| 1279 | + self.joint2, |
| 1280 | + self.offset1.x(), |
| 1281 | + self.offset1.y(), |
| 1282 | + self.offset1.z(), |
| 1283 | + self.offset2.x(), |
| 1284 | + self.offset2.y(), |
| 1285 | + self.offset2.z(), |
| 1286 | + self.weight, |
| 1287 | + self.targetDistance); |
| 1288 | + }) |
| 1289 | + .def_readonly( |
| 1290 | + "joint1", |
| 1291 | + &mm::JointToJointDistanceConstraintT<float>::joint1, |
| 1292 | + "The index of the first joint") |
| 1293 | + .def_readonly( |
| 1294 | + "joint2", |
| 1295 | + &mm::JointToJointDistanceConstraintT<float>::joint2, |
| 1296 | + "The index of the second joint") |
| 1297 | + .def_readonly( |
| 1298 | + "offset1", |
| 1299 | + &mm::JointToJointDistanceConstraintT<float>::offset1, |
| 1300 | + "The offset from joint1 in the local coordinate system of joint1") |
| 1301 | + .def_readonly( |
| 1302 | + "offset2", |
| 1303 | + &mm::JointToJointDistanceConstraintT<float>::offset2, |
| 1304 | + "The offset from joint2 in the local coordinate system of joint2") |
| 1305 | + .def_readonly( |
| 1306 | + "weight", |
| 1307 | + &mm::JointToJointDistanceConstraintT<float>::weight, |
| 1308 | + "The weight of the constraint") |
| 1309 | + .def_readonly( |
| 1310 | + "target_distance", |
| 1311 | + &mm::JointToJointDistanceConstraintT<float>::targetDistance, |
| 1312 | + "The target distance between the two points"); |
| 1313 | + |
| 1314 | + py::class_< |
| 1315 | + mm::JointToJointDistanceErrorFunctionT<float>, |
| 1316 | + mm::SkeletonErrorFunction, |
| 1317 | + std::shared_ptr<mm::JointToJointDistanceErrorFunctionT<float>>>( |
| 1318 | + m, |
| 1319 | + "JointToJointDistanceErrorFunction", |
| 1320 | + R"(Error function that penalizes deviation from a target distance between two points attached to different joints. |
| 1321 | +
|
| 1322 | +This is useful for enforcing distance constraints between different parts of a character, |
| 1323 | +such as maintaining a fixed distance between hands or ensuring two joints stay a certain distance apart.)") |
| 1324 | + .def( |
| 1325 | + "__repr__", |
| 1326 | + [](const mm::JointToJointDistanceErrorFunctionT<float>& self) { |
| 1327 | + return fmt::format( |
| 1328 | + "JointToJointDistanceErrorFunction(weight={}, num_constraints={})", |
| 1329 | + self.getWeight(), |
| 1330 | + self.getConstraints().size()); |
| 1331 | + }) |
| 1332 | + .def( |
| 1333 | + py::init<>( |
| 1334 | + [](const mm::Character& character, |
| 1335 | + float weight) -> std::shared_ptr<mm::JointToJointDistanceErrorFunctionT<float>> { |
| 1336 | + validateWeight(weight, "weight"); |
| 1337 | + auto result = |
| 1338 | + std::make_shared<mm::JointToJointDistanceErrorFunctionT<float>>(character); |
| 1339 | + result->setWeight(weight); |
| 1340 | + return result; |
| 1341 | + }), |
| 1342 | + R"(Initialize a JointToJointDistanceErrorFunction. |
| 1343 | +
|
| 1344 | + :param character: The character to use. |
| 1345 | + :param weight: The weight applied to the error function.)", |
| 1346 | + py::keep_alive<1, 2>(), |
| 1347 | + py::arg("character"), |
| 1348 | + py::kw_only(), |
| 1349 | + py::arg("weight") = 1.0f) |
| 1350 | + .def( |
| 1351 | + "add_constraint", |
| 1352 | + [](mm::JointToJointDistanceErrorFunctionT<float>& self, |
| 1353 | + size_t joint1, |
| 1354 | + const Eigen::Vector3f& offset1, |
| 1355 | + size_t joint2, |
| 1356 | + const Eigen::Vector3f& offset2, |
| 1357 | + float targetDistance, |
| 1358 | + float weight) { |
| 1359 | + validateJointIndex(joint1, "joint1", self.getSkeleton()); |
| 1360 | + validateJointIndex(joint2, "joint2", self.getSkeleton()); |
| 1361 | + validateWeight(weight, "weight"); |
| 1362 | + self.addConstraint(joint1, offset1, joint2, offset2, targetDistance, weight); |
| 1363 | + }, |
| 1364 | + R"(Adds a joint-to-joint distance constraint to the error function. |
| 1365 | +
|
| 1366 | + :param joint1: The index of the first joint. |
| 1367 | + :param offset1: The offset from joint1 in the local coordinate system of joint1. |
| 1368 | + :param joint2: The index of the second joint. |
| 1369 | + :param offset2: The offset from joint2 in the local coordinate system of joint2. |
| 1370 | + :param target_distance: The desired distance between the two points in world space. |
| 1371 | + :param weight: The weight of the constraint.)", |
| 1372 | + py::arg("joint1"), |
| 1373 | + py::arg("offset1"), |
| 1374 | + py::arg("joint2"), |
| 1375 | + py::arg("offset2"), |
| 1376 | + py::arg("target_distance"), |
| 1377 | + py::arg("weight") = 1.0f) |
| 1378 | + .def( |
| 1379 | + "add_constraints", |
| 1380 | + [](mm::JointToJointDistanceErrorFunctionT<float>& self, |
| 1381 | + const py::array_t<int>& joint1, |
| 1382 | + const py::array_t<float>& offset1, |
| 1383 | + const py::array_t<int>& joint2, |
| 1384 | + const py::array_t<float>& offset2, |
| 1385 | + const py::array_t<float>& targetDistance, |
| 1386 | + const std::optional<py::array_t<float>>& weight) { |
| 1387 | + ArrayShapeValidator validator; |
| 1388 | + const int nConsIdx = -1; |
| 1389 | + validator.validate(joint1, "joint1", {nConsIdx}, {"n_cons"}); |
| 1390 | + validateJointIndex(joint1, "joint1", self.getSkeleton()); |
| 1391 | + validator.validate(offset1, "offset1", {nConsIdx, 3}, {"n_cons", "xyz"}); |
| 1392 | + validator.validate(joint2, "joint2", {nConsIdx}, {"n_cons"}); |
| 1393 | + validateJointIndex(joint2, "joint2", self.getSkeleton()); |
| 1394 | + validator.validate(offset2, "offset2", {nConsIdx, 3}, {"n_cons", "xyz"}); |
| 1395 | + validator.validate(targetDistance, "target_distance", {nConsIdx}, {"n_cons"}); |
| 1396 | + validator.validate(weight, "weight", {nConsIdx}, {"n_cons"}); |
| 1397 | + validateWeights(weight, "weight"); |
| 1398 | + |
| 1399 | + auto joint1Acc = joint1.unchecked<1>(); |
| 1400 | + auto offset1Acc = offset1.unchecked<2>(); |
| 1401 | + auto joint2Acc = joint2.unchecked<1>(); |
| 1402 | + auto offset2Acc = offset2.unchecked<2>(); |
| 1403 | + auto targetDistanceAcc = targetDistance.unchecked<1>(); |
| 1404 | + auto weightAcc = |
| 1405 | + weight.has_value() ? std::make_optional(weight->unchecked<1>()) : std::nullopt; |
| 1406 | + |
| 1407 | + py::gil_scoped_release release; |
| 1408 | + |
| 1409 | + for (py::ssize_t i = 0; i < joint1.shape(0); ++i) { |
| 1410 | + self.addConstraint( |
| 1411 | + joint1Acc(i), |
| 1412 | + Eigen::Vector3f(offset1Acc(i, 0), offset1Acc(i, 1), offset1Acc(i, 2)), |
| 1413 | + joint2Acc(i), |
| 1414 | + Eigen::Vector3f(offset2Acc(i, 0), offset2Acc(i, 1), offset2Acc(i, 2)), |
| 1415 | + targetDistanceAcc(i), |
| 1416 | + weightAcc.has_value() ? (*weightAcc)(i) : 1.0f); |
| 1417 | + } |
| 1418 | + }, |
| 1419 | + R"(Adds multiple joint-to-joint distance constraints to the error function. |
| 1420 | +
|
| 1421 | + :param joint1: A numpy array of indices for the first joints. |
| 1422 | + :param offset1: A numpy array of shape (n, 3) for offsets from joint1 in local coordinates. |
| 1423 | + :param joint2: A numpy array of indices for the second joints. |
| 1424 | + :param offset2: A numpy array of shape (n, 3) for offsets from joint2 in local coordinates. |
| 1425 | + :param target_distance: A numpy array of desired distances between point pairs. |
| 1426 | + :param weight: A numpy array of weights for the constraints.)", |
| 1427 | + py::arg("joint1"), |
| 1428 | + py::arg("offset1"), |
| 1429 | + py::arg("joint2"), |
| 1430 | + py::arg("offset2"), |
| 1431 | + py::arg("target_distance"), |
| 1432 | + py::arg("weight") = std::nullopt) |
| 1433 | + .def( |
| 1434 | + "clear_constraints", |
| 1435 | + &mm::JointToJointDistanceErrorFunctionT<float>::clearConstraints, |
| 1436 | + "Clears all joint-to-joint distance constraints from the error function.") |
| 1437 | + .def_property_readonly( |
| 1438 | + "constraints", |
| 1439 | + [](const mm::JointToJointDistanceErrorFunctionT<float>& self) { |
| 1440 | + return self.getConstraints(); |
| 1441 | + }, |
| 1442 | + "Returns the list of joint-to-joint distance constraints."); |
| 1443 | +} |
| 1444 | + |
1270 | 1445 | } // namespace |
1271 | 1446 |
|
1272 | 1447 | void addErrorFunctions(py::module_& m) { |
@@ -2241,6 +2416,9 @@ rotation matrix to a target rotation.)") |
2241 | 2416 |
|
2242 | 2417 | // Vertex-to-vertex distance error function |
2243 | 2418 | defVertexVertexDistanceErrorFunction(m); |
| 2419 | + |
| 2420 | + // Joint-to-joint distance error function |
| 2421 | + defJointToJointDistanceErrorFunction(m); |
2244 | 2422 | } |
2245 | 2423 |
|
2246 | 2424 | } // namespace pymomentum |
0 commit comments