3 #include <msp/game/transform.h>
4 #include "physicalentity.h"
9 Physics::Physics(Game::Stage &s):
11 observer(stage.get_event_bus())
13 observer.observe<Game::Events::EntityCreated>([this](auto &e){ entity_added(e); });
15 stage.synthesize_initial_events(observer);
18 void Physics::entity_added(const Game::Events::EntityCreated &e)
20 if(Game::Handle<PhysicalEntity> physical = dynamic_handle_cast<PhysicalEntity>(e.entity))
22 for(Game::Handle<Game::Entity> p=e.entity->get_parent(); p; p=p->get_parent())
23 if(p->get_transform())
26 SimulatedEntity sim_body;
27 sim_body.entity = physical;
28 if(physical->is_fixture())
30 entities.insert(entities.begin()+fixture_count, sim_body);
34 entities.push_back(sim_body);
38 void Physics::tick(Time::TimeDelta dt)
40 float dt_secs = dt/Time::sec;
42 for(unsigned i=0; i<fixture_count; ++i)
43 copy_in<true>(entities[i]);
44 for(unsigned i=fixture_count; i<entities.size(); ++i)
45 copy_in<false>(entities[i]);
50 for(unsigned i=0; i<10; ++i)
58 for(unsigned i=0; i<fixture_count; ++i)
59 copy_out<true>(entities[i]);
60 for(unsigned i=fixture_count; i<entities.size(); ++i)
61 copy_out<false>(entities[i]);
64 template<bool is_fixture>
65 void Physics::copy_in(SimulatedEntity &entity)
67 Game::Handle<Game::Transform> transform = entity.entity->get_transform();
68 entity.position = transform->get_position().slice<2>(0);
69 const Geometry::Quaternion<float> &r = transform->get_rotation();
70 entity.rotation = Geometry::atan2<float>(2*(r.a*r.d+r.b*r.c), 1-2*(r.c*r.c+r.d*r.d));
72 if constexpr(is_fixture)
73 entity.inverse_mass = 0.0f;
76 Game::Handle<RigidBody> body = entity.entity->get_body();
77 entity.inverse_mass = 1.0f/body->get_mass();
78 entity.moment_of_inertia = body->get_moment_of_inertia();
79 entity.velocity = body->get_velocity();
80 entity.angular_velocity = body->get_angular_velocity();
84 template<bool is_fixture>
85 void Physics::copy_out(SimulatedEntity &entity)
87 Game::Handle<Game::Transform> transform = entity.entity->get_transform();
88 transform->set_position(compose(entity.position, 0.0f));
89 transform->set_rotation(Geometry::Quaternion<float>::rotation(entity.rotation, LinAl::Vector<float, 3>(0, 0, 1)));
91 if constexpr(!is_fixture)
93 Game::Handle<RigidBody> body = entity.entity->get_body();
94 body->set_velocity(entity.velocity);
95 body->set_angular_velocity(entity.angular_velocity);
99 void Physics::step(float dt_secs)
101 for(unsigned i=fixture_count; i<entities.size(); ++i)
103 SimulatedEntity &entity = entities[i];
105 LinAl::Vector<float, 2> new_velocity = entity.velocity+entity.external_force*dt_secs*entity.inverse_mass;
106 entity.position += (entity.velocity+new_velocity)*(dt_secs/2);
107 entity.velocity = new_velocity;
109 Geometry::Angle<float> new_angular_velocity = entity.angular_velocity+Geometry::Angle<float>::from_radians(entity.external_torque*(dt_secs/entity.moment_of_inertia));
110 entity.rotation = wrap_positive(entity.rotation+(entity.angular_velocity+new_angular_velocity)*(dt_secs/2));
111 entity.angular_velocity = new_angular_velocity;
115 void Physics::detect_collisions()
117 for(auto &c: collisions)
120 for(unsigned i=fixture_count; i<entities.size(); ++i)
122 Game::Handle<PhysicalEntity> entity1 = entities[i].entity;
123 ColliderType type1 = entity1->get_collider()->get_type();
124 for(unsigned j=0; j<i; ++j)
126 Game::Handle<PhysicalEntity> entity2 = entities[j].entity;
127 ColliderType type2 = entity2->get_collider()->get_type();
128 if(type1==ColliderType::CIRCLE && type2==ColliderType::CIRCLE)
129 collide_circle_circle(i, j);
130 else if(type1==ColliderType::CIRCLE && type2==ColliderType::BOX)
131 collide_circle_box(i, j);
132 else if(type1==ColliderType::BOX && type2==ColliderType::CIRCLE)
133 collide_circle_box(j, i);
138 void Physics::solve_collisions()
140 for(auto &e: entities)
142 e.position_adjust = LinAl::Vector<float, 2>();
143 e.collision_count = 0;
146 for(const auto &c: collisions)
151 SimulatedEntity &entity1 = entities[c.body1];
152 SimulatedEntity &entity2 = entities[c.body2];
153 float inv_mass_sum = 1.0f/(entity1.inverse_mass+entity2.inverse_mass);
154 LinAl::Vector<float, 2> delta = c.normal*c.depth*inv_mass_sum;
155 if(c.body1>=fixture_count)
157 entity1.position_adjust += delta*entity1.inverse_mass;
158 ++entity1.collision_count;
160 if(c.body2>=fixture_count)
162 entity2.position_adjust -= delta*entity1.inverse_mass;
163 ++entity2.collision_count;
167 for(auto &e: entities)
168 if(e.collision_count)
169 e.position += e.position_adjust/static_cast<float>(e.collision_count);
172 void Physics::apply_impulses()
174 for(const auto &c: collisions)
176 SimulatedEntity &entity1 = entities[c.body1];
177 SimulatedEntity &entity2 = entities[c.body2];
178 LinAl::Vector<float, 2> v_rel = entity2.velocity-entity1.velocity;
179 float restitution = 1.0f;
180 float inv_mass_sum = entity1.inverse_mass+entity2.inverse_mass;
181 float impulse = (1+restitution)*inner_product(v_rel, c.normal)/inv_mass_sum;
182 entity1.velocity += c.normal*(impulse*entity1.inverse_mass);
183 entity2.velocity -= c.normal*(impulse*entity2.inverse_mass);
187 Physics::Collision &Physics::get_collision(unsigned i, unsigned j)
189 for(auto &c: collisions)
190 if((c.body1==i && c.body2==j) || (c.body1==j && c.body2==i))
193 Collision &c = collisions.emplace_back();
199 void Physics::collide_circle_circle(unsigned i, unsigned j)
201 const LinAl::Vector<float, 2> &pos1 = entities[i].position;
202 const LinAl::Vector<float, 2> &pos2 = entities[j].position;
203 float r1 = entities[i].entity->get_collider()->get_radius();
204 float r2 = entities[j].entity->get_collider()->get_radius();
206 /* Points in the direction the first body needs to move in to clear the
208 LinAl::Vector<float, 2> delta = pos1-pos2;
209 float d_sq = inner_product(delta, delta);
213 Collision &collision = get_collision(i, j);
214 collision.normal = normalize(delta);
215 collision.depth = r1+r2-sqrt(d_sq);
216 collision.point = pos1-collision.normal*(r1-collision.depth/2);
217 if(collision.body1!=i)
218 collision.normal = -collision.normal;
222 void Physics::collide_circle_box(unsigned i, unsigned j)
224 const LinAl::Vector<float, 2> &pos1 = entities[i].position;
225 const LinAl::Vector<float, 2> &pos2 = entities[j].position;
226 float radius = entities[i].entity->get_collider()->get_radius();
227 LinAl::Vector<float, 2> half_size = entities[j].entity->get_collider()->get_size()/2.0f;
229 LinAl::Vector<float, 2> delta = pos1-pos2;
230 float c = cos(entities[j].rotation);
231 float s = sin(entities[j].rotation);
232 LinAl::Vector<float, 2> local_delta(c*delta.x+s*delta.y, c*delta.y-s*delta.x);
233 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));
234 LinAl::Vector<float, 2> local_cdelta = local_delta-local_closest;
235 float d_sq = inner_product(local_cdelta, local_cdelta);
236 if(d_sq<radius*radius)
238 Collision &collision = get_collision(i, j);
239 collision.normal = normalize(LinAl::Vector<float, 2>(c*local_cdelta.x-s*local_cdelta.y, c*local_cdelta.y+s*local_cdelta.x));
240 collision.depth = radius-sqrt(d_sq);
241 collision.point = pos1-collision.normal*(radius-collision.depth/2);
242 if(collision.body1!=i)
243 collision.normal = -collision.normal;