|
|
|
#import "Basic";
|
|
|
|
#import "Math";
|
|
|
|
#import "testkit.jai";
|
|
|
|
|
|
|
|
TIMESTEP: float = 1.0 / 60.0;
|
|
|
|
DEBUGGING :: true;
|
|
|
|
|
|
|
|
xy :: (x: int, y: int) -> Vector2 {
|
|
|
|
return xy(cast(float)x, cast(float)y);
|
|
|
|
}
|
|
|
|
normalize :: (using v: *Vector2) -> float {
|
|
|
|
sq := sqrt(x*x + y*y);
|
|
|
|
|
|
|
|
factor := 1.0 / sq;
|
|
|
|
x *= factor;
|
|
|
|
y *= factor;
|
|
|
|
|
|
|
|
return sq;
|
|
|
|
}
|
|
|
|
normalize :: (v: Vector2) -> Vector2 #must {
|
|
|
|
normalize(*v);
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
normalize_or_zero :: inline (using _v: Vector2) -> Vector2 #must {
|
|
|
|
v := _v;
|
|
|
|
normalize_or_zero(*v);
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
length_squared :: inline (a: Vector2) -> float {
|
|
|
|
return a.x*a.x + a.y*a.y;
|
|
|
|
}
|
|
|
|
negative :: (v: Vector2) -> Vector2 #must {
|
|
|
|
return xy(-v.x, -v.y);
|
|
|
|
}
|
|
|
|
cross :: (a: Vector2, b: Vector2) -> float {
|
|
|
|
return a.x * b.y - a.y * b.x;
|
|
|
|
}
|
|
|
|
LastingPip :: struct {
|
|
|
|
alive_for: float;
|
|
|
|
pos: Vector2;
|
|
|
|
d: DrawingSettings;
|
|
|
|
}
|
|
|
|
|
|
|
|
hit_impulse :: (body_pos: Vector2, mouse_pos: Vector2) -> Vector2 {
|
|
|
|
return .{0, -15.0};
|
|
|
|
//return normalize(body_pos - mouse_pos) * 15.0;
|
|
|
|
}
|
|
|
|
ShapeType :: enum {
|
|
|
|
Circle;
|
|
|
|
Rectangle;
|
|
|
|
};
|
|
|
|
|
|
|
|
Shape :: struct {
|
|
|
|
type: ShapeType;
|
|
|
|
offset: Vector2; // in local space of body
|
|
|
|
radius: float; // only valid on circle
|
|
|
|
halfsize: Vector2; // only valid on rectangle
|
|
|
|
invmass: float = 1.0;
|
|
|
|
inv_moment_of_inertia: float;
|
|
|
|
}
|
|
|
|
|
|
|
|
Body :: struct {
|
|
|
|
pos , vel , force : Vector2; // pos is in world space
|
|
|
|
angle, angle_vel, torque: float;
|
|
|
|
elasticity: float; // 1 is bouncy, 0 is absorbant
|
|
|
|
|
|
|
|
shape: Shape;
|
|
|
|
}
|
|
|
|
|
|
|
|
point_query :: (bodies: [] Body, world_point: Vector2) -> *Body {
|
|
|
|
for *bodies {
|
|
|
|
if length_squared(world_to_local(it, world_point)) < it.shape.radius*it.shape.radius return it;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Contact :: struct {
|
|
|
|
point_a: Vector2;
|
|
|
|
point_b: Vector2;
|
|
|
|
|
|
|
|
normal: Vector2;
|
|
|
|
separation: float; // positive when non-penetrating, negative when penetrating
|
|
|
|
time_of_impact: float;
|
|
|
|
|
|
|
|
body_a: *Body;
|
|
|
|
body_b: *Body;
|
|
|
|
};
|
|
|
|
|
|
|
|
// returns world point
|
|
|
|
center_of_mass :: (using b: Body) -> Vector2 {
|
|
|
|
return b.pos;
|
|
|
|
}
|
|
|
|
apply_impulse_linear :: (using b: *Body, impulse: Vector2) {
|
|
|
|
if shape.invmass != 0.0 {
|
|
|
|
vel += impulse * shape.invmass;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
apply_impulse_angular :: (using b: *Body, angle_impulse: float) {
|
|
|
|
if shape.inv_moment_of_inertia != 0.0 {
|
|
|
|
angle_vel += angle_impulse * shape.inv_moment_of_inertia;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
apply_impulse :: (using b: *Body, world_point: Vector2, impulse: Vector2) {
|
|
|
|
apply_impulse_linear(b, impulse);
|
|
|
|
r: Vector2 = center_of_mass(b) - world_point;
|
|
|
|
dL: float = cross(r, impulse);
|
|
|
|
apply_impulse_angular(b, dL);
|
|
|
|
}
|
|
|
|
|
|
|
|
intersect :: (a: *Body, b: *Body, c: *Contact) -> bool {
|
|
|
|
c.body_a = a;
|
|
|
|
c.body_b = b;
|
|
|
|
|
|
|
|
ab := b.pos - a.pos;
|
|
|
|
c.normal = normalize_or_zero(ab);
|
|
|
|
|
|
|
|
assert(a.shape.type == .Circle);
|
|
|
|
assert(b.shape.type == .Circle);
|
|
|
|
|
|
|
|
c.point_a = a.pos + c.normal * a.shape.radius;
|
|
|
|
c.point_b = b.pos - c.normal * b.shape.radius;
|
|
|
|
|
|
|
|
|
|
|
|
radius_ab: float = a.shape.radius + b.shape.radius;
|
|
|
|
lengthsqr: float = length_squared(ab);
|
|
|
|
|
|
|
|
if lengthsqr <= (radius_ab * radius_ab) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve :: (using c: Contact) {
|
|
|
|
// collision impulse
|
|
|
|
elasticity: float = body_a.elasticity * body_b.elasticity;
|
|
|
|
vab: Vector2 = body_a.vel - body_b.vel;
|
|
|
|
dot_product: float = dot(vab, normal);
|
|
|
|
impulse_j_magnitude: float = -(1.0 + elasticity) * dot_product / (body_a.shape.invmass + body_b.shape.invmass);
|
|
|
|
impulse_j: Vector2 = normal * impulse_j_magnitude;
|
|
|
|
apply_impulse_linear(body_a, impulse_j);
|
|
|
|
apply_impulse_linear(body_b, -impulse_j);
|
|
|
|
|
|
|
|
// move outside eachother, center of mass of both bodies stays the same, but separate them.
|
|
|
|
ta: float = body_a.shape.invmass / (body_a.shape.invmass + body_b.shape.invmass);
|
|
|
|
tb: float = body_b.shape.invmass / (body_a.shape.invmass + body_b.shape.invmass);
|
|
|
|
|
|
|
|
ds: Vector2 = point_b - point_a;
|
|
|
|
body_a.pos += ds * ta;
|
|
|
|
body_b.pos -= ds * tb;
|
|
|
|
}
|
|
|
|
|
|
|
|
draw_body :: (d: DrawingSettings, using b: Body) {
|
|
|
|
if shape.type == {
|
|
|
|
case .Circle;
|
|
|
|
points: int = 32;
|
|
|
|
if shape.radius > 100.0 points = 128;
|
|
|
|
drawing_circle_at: Vector2 = local_to_world(b, shape.offset);
|
|
|
|
for 0..points {
|
|
|
|
theta: float = (it/cast(float)points) * PI*2.0;
|
|
|
|
theta_next: float = (it+1)/cast(float)points * PI*2.0;
|
|
|
|
from := drawing_circle_at + .{cos(theta),sin(theta)} * shape.radius;
|
|
|
|
to := drawing_circle_at + .{cos(theta_next),sin(theta_next)} * shape.radius;
|
|
|
|
line(d, from, to);
|
|
|
|
}
|
|
|
|
pip(d, pos);
|
|
|
|
|
|
|
|
case .Rectangle;
|
|
|
|
using b.shape;
|
|
|
|
facing_to_right := rotate(xy(halfsize.x,0.0), angle);
|
|
|
|
facing_to_up := rotate(xy(0.0,halfsize.y), angle);
|
|
|
|
|
|
|
|
upper_right := pos + facing_to_right + facing_to_up;
|
|
|
|
upper_left := pos - facing_to_right + facing_to_up;
|
|
|
|
lower_left := pos - facing_to_right - facing_to_up;
|
|
|
|
lower_right := pos + facing_to_right - facing_to_up;
|
|
|
|
|
|
|
|
line(d, upper_left, upper_right);
|
|
|
|
line(d, upper_right, lower_right);
|
|
|
|
line(d, lower_right, lower_left);
|
|
|
|
line(d, lower_left, upper_left);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
main :: () {
|
|
|
|
bodies: [..]Body;
|
|
|
|
array_add(*bodies, .{pos = .{0.0, 0.0}, shape = .{type = .Circle, radius = 1.0}, elasticity = 0.5});
|
|
|
|
array_add(*bodies, .{pos = .{0.0, -1003.0}, shape = .{type = .Circle, radius = 1000.0, invmass = 0.0}, elasticity = 1.0}); // ground sphere
|
|
|
|
array_add(*bodies, .{pos = .{3.0, 0.0}, shape = .{type = .Circle, radius = 1.0}, elasticity = 0.3});
|
|
|
|
|
|
|
|
while !quit {
|
|
|
|
// process physics
|
|
|
|
unprocessed_time += dt;
|
|
|
|
{
|
|
|
|
dt: string = "do not use, physics processes with TIMESTEP not dt";
|
|
|
|
while unprocessed_time > TIMESTEP
|
|
|
|
{
|
|
|
|
defer unprocessed_time -= TIMESTEP;
|
|
|
|
|
|
|
|
if get_time() < 0.5
|
|
|
|
{
|
|
|
|
//apply_force_at_point(*rects[0], xy(3, 0), rects[0].pos + xy(-0.1, 0.03));
|
|
|
|
}
|
|
|
|
// iterating by index so can do a sort of iteration that avoids double checking
|
|
|
|
// collisions between bodies. This is important because if intersections are double
|
|
|
|
// tested then they may have operated on shapes which were just separated but not
|
|
|
|
// moved by velocity
|
|
|
|
for 0..bodies.count-1 {
|
|
|
|
a := *bodies[it];
|
|
|
|
// calculate moment of inertia
|
|
|
|
if a.shape.type == {
|
|
|
|
case .Circle;
|
|
|
|
a.shape.inv_moment_of_inertia = 1.0/(PI * pow(a.shape.radius, 4.0) / 4.0);
|
|
|
|
|
|
|
|
case;
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// gravity must be impulse for some reason
|
|
|
|
apply_impulse_linear(a, .{0.0, -9.81 * (1.0 / a.shape.invmass) * TIMESTEP});
|
|
|
|
|
|
|
|
// collisions
|
|
|
|
for it+1..bodies.count-1 {
|
|
|
|
b := *bodies[it];
|
|
|
|
contact: Contact;
|
|
|
|
if intersect(a, b, *contact) {
|
|
|
|
resolve(contact);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for * bodies {
|
|
|
|
defer { it.force = .{}; it.torque = 0.0; }
|
|
|
|
|
|
|
|
it.vel += (it.force * it.shape.invmass) * TIMESTEP;
|
|
|
|
it.pos += it.vel * TIMESTEP;
|
|
|
|
it.angle_vel += (it.torque * it.shape.inv_moment_of_inertia) * TIMESTEP;
|
|
|
|
it.angle += it.angle_vel * TIMESTEP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
b := bodies[0];
|
|
|
|
pip(.{}, local_to_world(b, world_to_local(b, mouse())));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hovering: *Body = point_query(bodies, screen_to_world(mouse()));
|
|
|
|
for * bodies {
|
|
|
|
d: DrawingSettings = .{true, GREEN};
|
|
|
|
if hovering == it {
|
|
|
|
d.color = RED;
|
|
|
|
vector(d, screen_to_world(mouse()), hit_impulse(it.pos, screen_to_world(mouse())) * TIMESTEP);
|
|
|
|
}
|
|
|
|
draw_body(d, it);
|
|
|
|
}
|
|
|
|
|
|
|
|
for Input.events_this_frame {
|
|
|
|
if it.type == {
|
|
|
|
if it.key_code == .MOUSE_BUTTON_LEFT {
|
|
|
|
panning = cast(bool)it.key_pressed;
|
|
|
|
to_tap := point_query(bodies, screen_to_world(mouse()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|