Try to implement constraints in 2d physics engine

This commit is contained in:
Nikita Lisitsa 2021-05-08 22:02:09 +03:00
parent ebe3570b96
commit 62e010094b
3 changed files with 192 additions and 30 deletions

View file

@ -50,4 +50,14 @@ namespace psemek::phys2d
float angular_velocity = 0.f; float angular_velocity = 0.f;
}; };
struct fixed_constraint
{
geom::point<float, 2> point, target;
};
struct joint_constraint
{
geom::point<float, 2> point1, point2;
};
} }

View file

@ -66,9 +66,19 @@ namespace psemek::phys2d
object_info const & get_object(group_handle group, std::size_t index); object_info const & get_object(group_handle group, std::size_t index);
// Constraint management
using constraint_handle = std::uint32_t;
constraint_handle add_constraint(group_handle g, std::size_t index, fixed_constraint const & c);
constraint_handle add_constraint(group_handle g1, std::size_t index1, group_handle g2, std::size_t index2, joint_constraint const & c);
void remove_constraint(constraint_handle handle);
// Global system properties // Global system properties
void set_gravity(geom::vector<float, 2> const & g); void set_gravity(geom::vector<float, 2> const & g);
void set_air_friction(float friction);
// Effects // Effects

View file

@ -460,10 +460,21 @@ namespace psemek::phys2d
util::flat_list<material, engine::material_handle> materials; util::flat_list<material, engine::material_handle> materials;
struct constraint
{
std::function<void()> resolve;
std::vector<std::pair<group_handle, std::size_t>> objects;
};
util::flat_list<constraint> constraints;
std::vector<constraint_handle> constraint_handles;
struct group struct group
{ {
std::vector<object_info> infos; std::vector<object_info> infos;
std::vector<std::vector<constraint_handle>> constraints;
std::vector<static_state> static_states; std::vector<static_state> static_states;
std::vector<static_state> static_states_temp;
std::vector<dynamic_state> dynamic_states; std::vector<dynamic_state> dynamic_states;
std::vector<std::vector<std::set<contact, velocity_comparator>::iterator>> contacts_by_velocity; std::vector<std::vector<std::set<contact, velocity_comparator>::iterator>> contacts_by_velocity;
@ -472,7 +483,8 @@ namespace psemek::phys2d
std::vector<group> groups; std::vector<group> groups;
std::optional<geom::vector<float, 2>> gravity; geom::vector<float, 2> gravity{0.f, 0.f};
float air_friction = 0.f;
std::set<contact, velocity_comparator> contacts_by_velocity; std::set<contact, velocity_comparator> contacts_by_velocity;
std::set<contact, penetration_comparator> contacts_by_penetration; std::set<contact, penetration_comparator> contacts_by_penetration;
@ -483,9 +495,11 @@ namespace psemek::phys2d
void apply_gravity(float dt); void apply_gravity(float dt);
void integrate(float dt); void integrate(float dt);
void resolve_constraints(float dt);
void gather_collisions(); void gather_collisions();
void resolve_impulses(); void resolve_impulses();
void resolve_penetrations(); void resolve_penetrations();
void apply_air_friction(float dt);
void log_energy(); void log_energy();
float energy(); float energy();
@ -518,15 +532,16 @@ namespace psemek::phys2d
apply_gravity(dt); apply_gravity(dt);
integrate(dt); integrate(dt);
resolve_constraints(dt);
gather_collisions(); gather_collisions();
resolve_impulses(); resolve_impulses();
resolve_penetrations(); resolve_penetrations();
// log_energy(); apply_air_friction(dt);
} }
void engine::impl::apply_gravity(float dt) void engine::impl::apply_gravity(float dt)
{ {
if (gravity) if (gravity != geom::vector{0.f, 0.f})
{ {
for (auto & g : groups) for (auto & g : groups)
{ {
@ -534,7 +549,7 @@ namespace psemek::phys2d
{ {
if (g.infos[i].inv_mass == 0.f) continue; if (g.infos[i].inv_mass == 0.f) continue;
g.dynamic_states[i].velocity += *gravity * dt; g.dynamic_states[i].velocity += gravity * dt;
} }
} }
} }
@ -552,6 +567,31 @@ namespace psemek::phys2d
} }
} }
void engine::impl::resolve_constraints(float dt)
{
for (std::size_t gi = 0; gi < groups.size(); ++gi)
for (std::size_t i = 0; i < groups[gi].infos.size(); ++i)
groups[gi].static_states_temp[i] = groups[gi].static_states[i];
(void)dt;
for (int i = 0; i < 4; ++i)
{
for (auto h : constraint_handles)
{
constraints[h].resolve();
}
}
for (std::size_t gi = 0; gi < groups.size(); ++gi)
{
for (std::size_t i = 0; i < groups[gi].infos.size(); ++i)
{
groups[gi].dynamic_states[i].velocity += (groups[gi].static_states[i].position - groups[gi].static_states_temp[i].position) / dt;
groups[gi].dynamic_states[i].angular_velocity += (groups[gi].static_states[i].rotation - groups[gi].static_states_temp[i].rotation) / dt;
}
}
}
void engine::impl::gather_collisions() void engine::impl::gather_collisions()
{ {
profile_function; profile_function;
@ -833,7 +873,6 @@ namespace psemek::phys2d
} }
} }
void engine::impl::resolve_penetrations() void engine::impl::resolve_penetrations()
{ {
profile_function; profile_function;
@ -955,6 +994,25 @@ namespace psemek::phys2d
} }
} }
void engine::impl::apply_air_friction(float dt)
{
if (air_friction != 0.f)
{
float const factor = 1.f - std::min(1.f, air_friction * dt);
for (auto & g : groups)
{
for (std::size_t i = 0; i < g.infos.size(); ++i)
{
if (g.infos[i].inv_mass == 0.f) continue;
g.dynamic_states[i].velocity *= factor;
g.dynamic_states[i].angular_velocity *= factor;
}
}
}
}
float engine::impl::energy() float engine::impl::energy()
{ {
float Eg = 0.f; float Eg = 0.f;
@ -967,8 +1025,7 @@ namespace psemek::phys2d
{ {
if (g.infos[i].inv_mass == 0.f) continue; if (g.infos[i].inv_mass == 0.f) continue;
if (gravity) Eg -= geom::dot(gravity, g.static_states[i].position - geom::point<float, 2>::zero()) / g.infos[i].inv_mass;
Eg -= geom::dot(*gravity, g.static_states[i].position - geom::point<float, 2>::zero()) / g.infos[i].inv_mass;
Ek += geom::length_sqr(g.dynamic_states[i].velocity) / g.infos[i].inv_mass / 2.f; Ek += geom::length_sqr(g.dynamic_states[i].velocity) / g.infos[i].inv_mass / 2.f;
Er += geom::sqr(g.dynamic_states[i].angular_velocity) / g.infos[i].inv_inertia / 2.f; Er += geom::sqr(g.dynamic_states[i].angular_velocity) / g.infos[i].inv_inertia / 2.f;
@ -978,29 +1035,6 @@ namespace psemek::phys2d
return Eg + Ek + Er; return Eg + Ek + Er;
} }
void engine::impl::log_energy()
{
float Eg = 0.f;
float Er = 0.f;
float Ek = 0.f;
for (auto & g : groups)
{
for (std::size_t i = 0; i < g.infos.size(); ++i)
{
if (g.infos[i].inv_mass == 0.f) continue;
if (gravity)
Eg -= geom::dot(*gravity, g.static_states[i].position - geom::point<float, 2>::zero()) / g.infos[i].inv_mass;
Ek += geom::length_sqr(g.dynamic_states[i].velocity) / g.infos[i].inv_mass / 2.f;
Er += geom::sqr(g.dynamic_states[i].angular_velocity) / g.infos[i].inv_inertia / 2.f;
}
}
log::info() << "G " << Eg << " K " << Ek << " R " << Er;
}
engine::engine() engine::engine()
: pimpl_{make_impl()} : pimpl_{make_impl()}
{} {}
@ -1103,7 +1137,9 @@ namespace psemek::phys2d
auto & g = impl().groups[group]; auto & g = impl().groups[group];
g.infos.push_back({shape, material, 1.f / (area * density), 1.f / (inertia * density)}); g.infos.push_back({shape, material, 1.f / (area * density), 1.f / (inertia * density)});
g.constraints.emplace_back();
g.static_states.push_back(ss); g.static_states.push_back(ss);
g.static_states_temp.emplace_back();
g.dynamic_states.push_back(ds); g.dynamic_states.push_back(ds);
return g.infos.size() - 1; return g.infos.size() - 1;
@ -1112,8 +1148,14 @@ namespace psemek::phys2d
void engine::remove_object(group_handle group, std::size_t index) void engine::remove_object(group_handle group, std::size_t index)
{ {
auto & g = impl().groups[group]; auto & g = impl().groups[group];
for (auto h : g.constraints[index])
remove_constraint(h);
g.infos.erase(g.infos.begin() + index); g.infos.erase(g.infos.begin() + index);
g.constraints.erase(g.constraints.begin() + index);
g.static_states.erase(g.static_states.begin() + index); g.static_states.erase(g.static_states.begin() + index);
g.static_states_temp.erase(g.static_states_temp.begin() + index);
g.dynamic_states.erase(g.dynamic_states.begin() + index); g.dynamic_states.erase(g.dynamic_states.begin() + index);
} }
@ -1122,11 +1164,111 @@ namespace psemek::phys2d
return impl().groups[group].infos[index]; return impl().groups[group].infos[index];
} }
engine::constraint_handle engine::add_constraint(group_handle g, std::size_t index, fixed_constraint const & c)
{
impl::constraint constraint;
constraint.objects.push_back({g, index});
constraint.resolve = [=, this]{
auto & ss = impl().groups[g].static_states[index];
geom::vector<float, 2> t;
t[0] = c.target[0] - ss.position[0] - c.point[0] * std::cos(ss.rotation) + c.point[1] * std::sin(ss.rotation);
t[1] = c.target[1] - ss.position[1] - c.point[0] * std::sin(ss.rotation) - c.point[1] * std::cos(ss.rotation);
geom::matrix<float, 2, 3> J;
J[0][0] = -1.f;
J[0][1] = 0.f;
J[0][2] = c.point[0] * std::sin(ss.rotation) + c.point[1] * std::cos(ss.rotation);
J[1][0] = 0.f;
J[1][1] = -1.f;
J[1][2] = - c.point[0] * std::cos(ss.rotation) + c.point[1] * std::sin(ss.rotation);
auto delta = geom::least_squares(J, -t);
if (delta)
{
ss.position[0] += (*delta)[0];
ss.position[1] += (*delta)[1];
ss.rotation += (*delta)[2];
}
};
auto handle = impl().constraints.insert(std::move(constraint));
impl().constraint_handles.push_back(handle);
impl().groups[g].constraints[index].push_back(handle);
return handle;
}
engine::constraint_handle engine::add_constraint(group_handle g1, std::size_t index1, group_handle g2, std::size_t index2, joint_constraint const & c)
{
impl::constraint constraint;
constraint.objects.push_back({g1, index1});
constraint.objects.push_back({g2, index2});
constraint.resolve = [=, this]{
auto & ss1 = impl().groups[g1].static_states[index1];
auto & ss2 = impl().groups[g2].static_states[index2];
geom::vector<float, 2> t;
t[0] = ss2.position[0] + c.point2[0] * std::cos(ss2.rotation) - c.point2[1] * std::sin(ss2.rotation) - ss1.position[0] - c.point1[0] * std::cos(ss1.rotation) + c.point1[1] * std::sin(ss1.rotation);
t[1] = ss2.position[1] + c.point2[0] * std::sin(ss2.rotation) + c.point2[1] * std::cos(ss2.rotation) - ss1.position[1] - c.point1[0] * std::sin(ss1.rotation) - c.point1[1] * std::cos(ss1.rotation);
geom::matrix<float, 2, 6> J;
J[0][0] = -1.f;
J[0][1] = 0.f;
J[0][2] = c.point1[0] * std::sin(ss1.rotation) + c.point1[1] * std::cos(ss1.rotation);
J[0][3] = 1.f;
J[0][4] = 0.f;
J[0][5] = - c.point2[0] * std::sin(ss2.rotation) - c.point2[1] * std::cos(ss2.rotation);
J[1][0] = 0.f;
J[1][1] = -1.f;
J[1][2] = - c.point1[0] * std::cos(ss1.rotation) + c.point1[1] * std::sin(ss1.rotation);
J[1][3] = 0.f;
J[1][4] = 1.f;
J[1][5] = c.point2[0] * std::cos(ss2.rotation) - c.point2[1] * std::sin(ss2.rotation);
auto delta = geom::least_squares(J, -t);
if (delta)
{
ss1.position[0] += (*delta)[0];
ss1.position[1] += (*delta)[1];
ss1.rotation += (*delta)[2];
ss2.position[0] += (*delta)[3];
ss2.position[1] += (*delta)[4];
ss2.rotation += (*delta)[5];
}
};
auto handle = impl().constraints.insert(std::move(constraint));
impl().constraint_handles.push_back(handle);
impl().groups[g1].constraints[index1].push_back(handle);
impl().groups[g2].constraints[index2].push_back(handle);
return handle;
}
void engine::remove_constraint(constraint_handle handle)
{
auto const & constraint = impl().constraints[handle];
for (auto [g, i] : constraint.objects)
{
auto & cs = impl().groups[g].constraints[i];
if (auto it = std::find(cs.begin(), cs.end(), handle); it != cs.end())
cs.erase(it);
}
impl().constraints.erase(handle);
impl().constraint_handles.erase(std::find(impl().constraint_handles.begin(), impl().constraint_handles.end(), handle));
}
void engine::set_gravity(geom::vector<float, 2> const & g) void engine::set_gravity(geom::vector<float, 2> const & g)
{ {
impl().gravity = g; impl().gravity = g;
} }
void engine::set_air_friction(float friction)
{
impl().air_friction = friction;
}
void engine::explode(geom::point<float, 2> const & center, float strength, float attenuation) void engine::explode(geom::point<float, 2> const & center, float strength, float attenuation)
{ {
impl().explode(center, strength, attenuation); impl().explode(center, strength, attenuation);