3 #include <msp/game/transform.h>
4 #include "physicalentity.h"
9 Physics::Physics(Game::Stage &s):
11 event_source(stage.get_event_bus()),
12 observer(stage.get_event_bus())
14 observer.observe<Game::Events::EntityCreated>([this](auto &e){ entity_added(e); });
16 stage.synthesize_initial_events(observer);
19 void Physics::entity_added(const Game::Events::EntityCreated &e)
21 if(Game::Handle<PhysicalEntity> physical = dynamic_handle_cast<PhysicalEntity>(e.entity))
23 for(Game::Handle<Game::Entity> p=e.entity->get_parent(); p; p=p->get_parent())
24 if(p->get_transform())
27 SimulatedEntity sim_body;
28 sim_body.entity = physical;
29 if(physical->is_fixture())
31 entities.insert(entities.begin()+fixture_count, sim_body);
35 entities.push_back(sim_body);
39 void Physics::tick(Time::TimeDelta dt)
41 float dt_secs = dt/Time::sec;
43 for(unsigned i=0; i<fixture_count; ++i)
44 copy_in<true>(entities[i]);
45 for(unsigned i=fixture_count; i<entities.size(); ++i)
46 copy_in<false>(entities[i]);
51 for(unsigned i=0; i<10; ++i)
59 for(unsigned i=0; i<fixture_count; ++i)
60 copy_out<true>(entities[i]);
61 for(unsigned i=fixture_count; i<entities.size(); ++i)
62 copy_out<false>(entities[i]);
64 for(const Collision &c: collisions)
65 event_source.emit<Events::Collision>(entities[c.body1].entity->get_collider(), entities[c.body2].entity->get_collider());
68 template<bool is_fixture>
69 void Physics::copy_in(SimulatedEntity &entity)
71 Game::Handle<Game::Transform> transform = entity.entity->get_transform();
72 entity.position = transform->get_position().slice<2>(0);
73 const Geometry::Quaternion<float> &r = transform->get_rotation();
74 entity.rotation = Geometry::atan2<float>(2*(r.a*r.d+r.b*r.c), 1-2*(r.c*r.c+r.d*r.d));
76 if constexpr(is_fixture)
78 entity.inverse_mass = 0.0f;
79 entity.inverse_momi = 0.0f;
83 Game::Handle<RigidBody> body = entity.entity->get_body();
84 entity.inverse_mass = 1.0f/body->get_mass();
85 entity.inverse_momi = 1.0f/body->get_moment_of_inertia();
86 entity.external_force = body->get_force();
87 entity.external_torque = body->get_torque();
88 entity.velocity = body->get_velocity();
89 entity.angular_velocity = body->get_angular_velocity();
93 template<bool is_fixture>
94 void Physics::copy_out(SimulatedEntity &entity)
96 Game::Handle<Game::Transform> transform = entity.entity->get_transform();
97 transform->set_position(compose(entity.position, 0.0f));
98 transform->set_rotation(Geometry::Quaternion<float>::rotation(entity.rotation, LinAl::Vector<float, 3>(0, 0, 1)));
100 if constexpr(!is_fixture)
102 Game::Handle<RigidBody> body = entity.entity->get_body();
103 body->set_velocity(entity.velocity);
104 body->set_angular_velocity(entity.angular_velocity);
105 body->clear_forces();
109 void Physics::step(float dt_secs)
111 for(unsigned i=fixture_count; i<entities.size(); ++i)
113 SimulatedEntity &entity = entities[i];
115 LinAl::Vector<float, 2> new_velocity = entity.velocity+entity.external_force*(dt_secs*entity.inverse_mass);
116 entity.position += (entity.velocity+new_velocity)*(dt_secs/2);
117 entity.velocity = new_velocity;
119 Geometry::Angle<float> new_angular_velocity = entity.angular_velocity+Geometry::Angle<float>::from_radians(entity.external_torque*dt_secs*entity.inverse_momi);
120 entity.rotation = wrap_positive(entity.rotation+(entity.angular_velocity+new_angular_velocity)*(dt_secs/2));
121 entity.angular_velocity = new_angular_velocity;
125 void Physics::detect_collisions()
127 for(auto &c: collisions)
130 for(unsigned i=fixture_count; i<entities.size(); ++i)
132 Game::Handle<PhysicalEntity> entity1 = entities[i].entity;
133 ColliderType type1 = entity1->get_collider()->get_type();
134 for(unsigned j=0; j<i; ++j)
136 Game::Handle<PhysicalEntity> entity2 = entities[j].entity;
137 ColliderType type2 = entity2->get_collider()->get_type();
138 if(type1==ColliderType::CIRCLE && type2==ColliderType::CIRCLE)
139 collide_circle_circle(i, j);
140 else if(type1==ColliderType::CIRCLE && type2==ColliderType::BOX)
141 collide_circle_box(i, j);
142 else if(type1==ColliderType::BOX && type2==ColliderType::CIRCLE)
143 collide_circle_box(j, i);
148 void Physics::solve_collisions()
150 for(auto &e: entities)
152 e.position_adjust = LinAl::Vector<float, 2>();
153 e.collision_count = 0;
156 for(const auto &c: collisions)
161 SimulatedEntity &entity1 = entities[c.body1];
162 SimulatedEntity &entity2 = entities[c.body2];
163 float inv_mass_sum = 1.0f/(entity1.inverse_mass+entity2.inverse_mass);
164 LinAl::Vector<float, 2> delta = c.normal*c.depth*inv_mass_sum;
165 if(c.body1>=fixture_count)
167 entity1.position_adjust += delta*entity1.inverse_mass;
168 ++entity1.collision_count;
170 if(c.body2>=fixture_count)
172 entity2.position_adjust -= delta*entity1.inverse_mass;
173 ++entity2.collision_count;
177 for(auto &e: entities)
178 if(e.collision_count)
179 e.position += e.position_adjust/static_cast<float>(e.collision_count);
182 void Physics::apply_impulses()
184 for(const auto &c: collisions)
186 SimulatedEntity &entity1 = entities[c.body1];
187 SimulatedEntity &entity2 = entities[c.body2];
188 LinAl::Vector<float, 2> r1 = c.point-entity1.position;
189 LinAl::Vector<float, 2> r2 = c.point-entity2.position;
190 LinAl::Vector<float, 2> v_p1 = entity1.velocity+LinAl::Vector<float, 2>(-r1.y, r1.x)*entity1.angular_velocity.radians();
191 LinAl::Vector<float, 2> v_p2 = entity2.velocity+LinAl::Vector<float, 2>(-r2.y, r2.x)*entity2.angular_velocity.radians();
192 LinAl::Vector<float, 2> v_rel = v_p2-v_p1;
193 LinAl::Vector<float, 2> tangent = v_rel-c.normal*inner_product(v_rel, c.normal);
194 float v_tan = tangent.norm();
195 tangent = (v_tan>1e-5 ? normalize(tangent) : LinAl::Vector<float, 2>(-c.normal.y, c.normal.x));
196 float restitution = 1.0f;
197 float friction_coeff = 0.1f;
198 float inv_mass_sum = entity1.inverse_mass+entity2.inverse_mass;
199 float reaction = (1+restitution)*inner_product(v_rel, c.normal)/inv_mass_sum;
200 float friction = min(reaction*friction_coeff, v_tan/inv_mass_sum);
201 LinAl::Vector<float, 2> impulse = c.normal*reaction+tangent*friction;
202 entity1.velocity += impulse*entity1.inverse_mass;
203 entity2.velocity -= impulse*entity2.inverse_mass;
204 entity1.angular_velocity += Geometry::Angle<float>::from_radians(entity1.inverse_momi*(r1.x*impulse.y-r1.y*impulse.x));
205 entity2.angular_velocity -= Geometry::Angle<float>::from_radians(entity2.inverse_momi*(r2.x*impulse.y-r2.y*impulse.x));
209 Physics::Collision &Physics::get_collision(unsigned i, unsigned j)
211 for(auto &c: collisions)
212 if((c.body1==i && c.body2==j) || (c.body1==j && c.body2==i))
215 Collision &c = collisions.emplace_back();
221 void Physics::collide_circle_circle(unsigned i, unsigned j)
223 const LinAl::Vector<float, 2> &pos1 = entities[i].position;
224 const LinAl::Vector<float, 2> &pos2 = entities[j].position;
225 float r1 = entities[i].entity->get_collider()->get_radius();
226 float r2 = entities[j].entity->get_collider()->get_radius();
228 /* Points in the direction the first body needs to move in to clear the
230 LinAl::Vector<float, 2> delta = pos1-pos2;
231 float d_sq = inner_product(delta, delta);
235 Collision &collision = get_collision(i, j);
236 collision.normal = normalize(delta);
237 collision.depth = r1+r2-sqrt(d_sq);
238 collision.point = pos1-collision.normal*(r1-collision.depth/2);
239 if(collision.body1!=i)
240 collision.normal = -collision.normal;
244 void Physics::collide_circle_box(unsigned i, unsigned j)
246 const LinAl::Vector<float, 2> &pos1 = entities[i].position;
247 const LinAl::Vector<float, 2> &pos2 = entities[j].position;
248 float radius = entities[i].entity->get_collider()->get_radius();
249 LinAl::Vector<float, 2> half_size = entities[j].entity->get_collider()->get_size()/2.0f;
251 LinAl::Vector<float, 2> delta = pos1-pos2;
252 float c = cos(entities[j].rotation);
253 float s = sin(entities[j].rotation);
254 LinAl::Vector<float, 2> local_delta(c*delta.x+s*delta.y, c*delta.y-s*delta.x);
255 LinAl::Vector<float, 2> local_closest(clamp(local_delta.x, -half_size.x, half_size.x), clamp(local_delta.y, -half_size.y, half_size.y));
256 LinAl::Vector<float, 2> local_cdelta = local_delta-local_closest;
257 float d_sq = inner_product(local_cdelta, local_cdelta);
259 if(d_sq<radius*radius)
261 Collision &collision = get_collision(i, j);
264 collision.normal = normalize(LinAl::Vector<float, 2>(c*local_cdelta.x-s*local_cdelta.y, c*local_cdelta.y+s*local_cdelta.x));
265 collision.depth = radius-sqrt(d_sq);
269 LinAl::Vector<float, 2> inside_dist(half_size.x-abs(local_delta.x), half_size.y-abs(local_delta.y));
270 if(inside_dist.x<inside_dist.y)
272 collision.normal = LinAl::Vector<float, 2>(c, s) * (local_delta.x<0 ? -1.0f : 1.0f);
273 collision.depth = radius+inside_dist.x;
277 collision.normal = LinAl::Vector<float, 2>(-s, c) * (local_delta.y<0 ? -1.0f : 1.0f);
278 collision.depth = radius+inside_dist.y;
281 collision.point = pos1-collision.normal*(radius-collision.depth/2);
282 if(collision.body1!=i)
283 collision.normal = -collision.normal;