You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

272 lines
8.3 KiB
Plaintext

#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()));
}
}
}
}
}