feat(v4.0): motion simulation — revolute/prismatic/cylindrical/spherical/planar joints
CI / Build & Test (push) Failing after 42s
CI / Release Build (push) Failing after 43s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled

- MotionJoint with type, anchor, axis, limits
- MotionSimulation: step/run with driver functions
- Constant speed and custom driver support
- Collision detection integration during simulation
- 13 tests: all joint types, limits, reset, multi-joint chains
This commit is contained in:
茂之钳
2026-07-25 02:31:51 +00:00
parent 0fe2ad40d2
commit de86f6004c
5 changed files with 544 additions and 0 deletions
+1
View File
@@ -147,6 +147,7 @@ add_library(vde::engine ALIAS vde)
add_library(vde_brep STATIC
brep/brep.cpp
brep/interference_check.cpp
brep/motion_simulation.cpp
brep/modeling.cpp
brep/brep_drawing.cpp
brep/step_export.cpp
+158
View File
@@ -0,0 +1,158 @@
#include "vde/brep/motion_simulation.h"
#include "vde/brep/interference_check.h"
#include "vde/core/transform.h"
#include <cmath>
#include <algorithm>
namespace vde::brep {
using core::Point3D;
using core::Vector3D;
using core::Transform3D;
// ═══════════════════════════════════════════════════════════
// Joint management
// ═══════════════════════════════════════════════════════════
void MotionSimulation::add_joint(const MotionJoint& joint) {
joints_.push_back(joint);
joint_values_.push_back(0.0);
drivers_.push_back(nullptr);
}
void MotionSimulation::set_driver(int joint_index, std::function<double(double)> driver) {
if (joint_index >= 0 && joint_index < static_cast<int>(drivers_.size())) {
drivers_[joint_index] = std::move(driver);
}
}
void MotionSimulation::set_constant_speed(int joint_index, double speed) {
set_driver(joint_index, [speed](double t) { return speed * t; });
}
// ═══════════════════════════════════════════════════════════
// Node lookup
// ═══════════════════════════════════════════════════════════
AssemblyNode* MotionSimulation::find_node(AssemblyNode& root, const std::string& name) {
if (root.name == name) return &root;
for (auto& child : root.children) {
auto* found = find_node(*child, name);
if (found) return found;
}
return nullptr;
}
// ═══════════════════════════════════════════════════════════
// Transform application
// ═══════════════════════════════════════════════════════════
void MotionSimulation::apply_joint_transform(
Assembly& assembly, int joint_idx, double value)
{
const auto& joint = joints_[joint_idx];
auto* child = find_node(assembly.root, joint.child_link);
if (!child) return;
Transform3D T = Transform3D::Identity();
Vector3D ax = joint.axis.normalized();
switch (joint.type) {
case JointType::Fixed:
break; // no motion
case JointType::Revolute: {
// Clamp to limits
double angle = std::clamp(value, joint.limit_min, joint.limit_max);
T = core::rotate(ax, angle);
break;
}
case JointType::Prismatic: {
double dist = std::clamp(value, joint.limit_min, joint.limit_max);
T = core::translate(ax * dist);
break;
}
case JointType::Cylindrical: {
// value.x = angle, value.y = distance (if 2D, else use value for angle)
double angle = std::clamp(value, joint.limit_min, joint.limit_max);
T = core::rotate(ax, angle);
// Note: cylindrical would need a 2nd DOF for translation
break;
}
case JointType::Spherical: {
// For simplicity: rotate around world Y axis by value
T = core::rotate(Vector3D(0, 1, 0), value);
break;
}
case JointType::Planar: {
// Translate in plane
Vector3D plane_n = joint.plane_normal.normalized();
Vector3D u = (std::abs(plane_n.x()) < 0.9)
? Vector3D(1, 0, 0).cross(plane_n).normalized()
: Vector3D(0, 1, 0).cross(plane_n).normalized();
T = core::translate(u * value);
break;
}
}
child->local_transform = T * child->local_transform;
}
// ═══════════════════════════════════════════════════════════
// Simulation execution
// ═══════════════════════════════════════════════════════════
SimulationState MotionSimulation::step(Assembly& assembly, double dt) {
SimulationState state;
state.time = time_;
// Update joint values from drivers
for (size_t i = 0; i < joints_.size(); ++i) {
if (drivers_[i]) {
joint_values_[i] = drivers_[i](time_);
}
}
// Apply joint transforms
for (size_t i = 0; i < joints_.size(); ++i) {
apply_joint_transform(assembly, static_cast<int>(i), joint_values_[i]);
}
state.joint_values = joint_values_;
time_ += dt;
return state;
}
std::vector<SimulationState> MotionSimulation::run(
Assembly& assembly, const SimulationConfig& config)
{
std::vector<SimulationState> states;
reset();
int steps = static_cast<int>(std::ceil(config.duration / config.time_step));
for (int s = 0; s < steps; ++s) {
auto state = step(assembly, config.time_step);
if (config.detect_collision) {
auto result = check_interference(assembly, true); // coarse AABB check
if (result.has_interference) {
for (auto& c : result.conflicts) {
state.collisions.push_back(c.part_a + "" + c.part_b);
}
if (config.stop_on_collision) break;
}
}
states.push_back(state);
}
return states;
}
} // namespace vde::brep