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.
765 lines
24 KiB
Plaintext
765 lines
24 KiB
Plaintext
#import "Window_Creation";
|
|
Debug :: #import "Debug";
|
|
Simp :: #import "Simp";
|
|
Input :: #import "Input";
|
|
#import "Math";
|
|
#import "Basic";
|
|
|
|
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;
|
|
}
|
|
|
|
local_to_world :: (using b: Body, local_point: Vector2) -> Vector2 {
|
|
return rotate(local_point, angle) + pos;
|
|
}
|
|
world_to_local :: (using b: Body, world_point: Vector2) -> Vector2 {
|
|
// world = rotate(local, angle) + pos
|
|
// world - pos = rotate(local, angle)
|
|
// rotate(world - pos, -angle) = local
|
|
return rotate(world_point - pos, -angle);
|
|
}
|
|
|
|
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;
|
|
}
|
|
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;
|
|
};
|
|
|
|
degrees :: (d: float) -> float {
|
|
return d * PI/180.0;
|
|
}
|
|
|
|
/*
|
|
A transform is an offset, a rotation, and a scaling
|
|
transforms define new basis vectors which in turn define a new space, the space of that transform
|
|
when a transform transforms a point in one space, it moves it into its space. It takes whatever point you give it, and acts as if its offset was from the transform's point of view, not where the point came from.
|
|
An entity's transform, when applied to a point, converts from local to world space. The input is local, the output is world
|
|
The inverse does the opposite, world to local space.
|
|
*/
|
|
Transform2D :: struct {
|
|
pos: Vector2;
|
|
angle: float;
|
|
scale: Vector2 = .{1.0, 1.0};
|
|
};
|
|
approx_eq :: (v1: Vector2, v2: Vector2) -> bool {
|
|
return abs(v1.x - v2.x) < 0.01 && abs(v1.y - v2.y) < 0.01;
|
|
}
|
|
|
|
#run {
|
|
point := xy(5, 6);
|
|
t := Transform2D.{pos = .{1, 2}, angle = 0.0, scale = .{2.0, 1.0}};
|
|
print("% | % | %\n", inv(t), xform(inv(t), xform(t, point)), point);
|
|
assert(approx_eq(point, xform(inv(t), xform(t, point))));
|
|
}
|
|
|
|
inv :: (t: Transform2D) -> Transform2D {
|
|
// v is a vector, v || s means xy(v.x*s.x, v.y*s.y), elementwise product. For
|
|
// scaling vector
|
|
|
|
// inv(t) * t * pos = pos
|
|
// inv(t) * ( rotate(xy(pos.x*t.scale.x, pos.y*t.scale.y), t.angle) + t.pos ) = pos
|
|
// p' = rotate(xy(pos.x*t.scale.x, pos.y*t.scale.y), t.angle) + t.pos
|
|
// inv(t) * p' = pos
|
|
// in = inv(t)
|
|
// rotate(xy(p'.x*in.scale.x, p'.y*in.scale.y), in.angle) + in.pos = pos
|
|
// rotate(xy(p'.x*in.scale.x, p'.y*in.scale.y), in.angle) = pos - in.pos
|
|
// p' || in.scale = rotate(pos - in.pos, -in.angle)
|
|
// p' = rotate(pos - in.pos, -in.angle) || 1.0/in.scale
|
|
// rotate(xy(pos.x*t.scale.x, pos.y*t.scale.y), t.angle) + t.pos = rotate(pos - in.pos, -in.angle) || 1.0/in.scale
|
|
// rotate(pos || t.scale, t.angle) + t.pos = rotate(pos - in.pos, -in.angle) || 1.0/in.scale
|
|
// rotate(pos - in.pos, -in.angle) || 1.0/in.scale = rotate(pos || t.scale, t.angle) + t.pos
|
|
// ^^ trying to find all the in., so want to make the left side look like the right side
|
|
|
|
return .{
|
|
pos = -t.pos,
|
|
scale = .{1.0/t.scale.x, 1.0/t.scale.y},
|
|
angle = -t.angle,
|
|
};
|
|
|
|
|
|
// THIS IS WRONG: I think that: rotate(v, theta) || scale == rotate(v || scale, theta)
|
|
V :: Vector2.{0.5, 0.6};
|
|
THETA :: #run degrees(39.0);
|
|
SCALE :: Vector2.{2.0, 3.0};
|
|
apply_scale :: (to: Vector2, scale: Vector2) -> Vector2 {
|
|
return .{to.x*scale.x, to.y*scale.y};
|
|
}
|
|
#run {
|
|
/*
|
|
rotation:
|
|
x' = x * cos(angle) + y * -sin(angle)
|
|
y' = x * sin(angle) + y * cos(angle)
|
|
|
|
want to solve for inner
|
|
rotate(v, theta) || scale_in = rotate(v || inner, theta)
|
|
where x = v.x, y = v.y
|
|
|
|
(x * cos(angle) + y * -sin(angle))*scale_in.x = x * inner.x * cos(angle) + y * inner.y -sin(angle)
|
|
(x * sin(angle) + y * cos(angle))*scale_in.y = x * inner.x * sin(angle) + y * inner.y * cos(angle)
|
|
|
|
system of equations, all variables known except for inner. Solving for inner.x:
|
|
((x * cos(angle) + y * -sin(angle))*scale_in.x - y * inner.y -sin(angle)) / (x * cos(angle)) = inner.x
|
|
|
|
now reinserting into the other equation...
|
|
(x * sin(angle) + y * cos(angle))*scale_in.y = x * ((x * cos(angle) + y * -sin(angle))*scale_in.x - y * inner.y -sin(angle)) / (x * cos(angle)) * sin(angle) + y * inner.y * cos(angle)
|
|
(x * sin(angle) + y * cos(angle))*scale_in.y = (x * (x * cos(angle) + y * -sin(angle))*scale_in.x - x * y * inner.y -sin(angle)) / (x * cos(angle)) * sin(angle) + y * inner.y * cos(angle)
|
|
|
|
|
|
*/
|
|
/*
|
|
left := apply_scale(rotate(V, THETA), SCALE);
|
|
right := rotate(apply_scale(V, SCALE), THETA);
|
|
print("% %\n", left, right);
|
|
assert(approx_eq(left, right));*/
|
|
}
|
|
//#assert approx_eq(apply_scale(rotate(V, THETA), SCALE), rotate(apply_scale(V, SCALE), THETA));
|
|
|
|
return .{};
|
|
/*return .{
|
|
pos = -
|
|
}*/
|
|
}
|
|
|
|
xform :: (using t: Transform2D, p: Vector2) -> Vector2 {
|
|
result: Vector2 = p;
|
|
result.x *= scale.x;
|
|
result.y *= scale.y;
|
|
result = rotate(result, angle);
|
|
result += pos;
|
|
return result;
|
|
}
|
|
|
|
xform :: (from: Transform2D, other: Transform2D) -> Transform2D {
|
|
result: Transform2D;
|
|
result.pos = xform(from, other.pos);
|
|
result.angle = from.angle + other.angle;
|
|
result.scale.x = from.scale.x * other.scale.x;
|
|
result.scale.y = from.scale.y * other.scale.y;
|
|
return result;
|
|
}
|
|
|
|
xform_inv :: (using t: Transform2D, p: Vector2) -> Vector2 {
|
|
result: Vector2 = p;
|
|
result -= pos;
|
|
result = rotate(result, -angle);
|
|
result.x /= scale.x;
|
|
result.y /= scale.y;
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#scope_file;
|
|
// ------------------------------------------------------------------------
|
|
// demo stuff visualization/debugging stuff
|
|
|
|
|
|
window: Window_Type;
|
|
quit: bool;
|
|
dt: float;
|
|
panning: bool = false;
|
|
window_width: s32 = 1280;
|
|
window_height: s32 = 720;
|
|
// the camera is a transform which goes from its parent space (screen) to its space (world space)
|
|
camera: Transform2D = .{scale = .{0.01, 0.01}, pos = #run -xy(window_width, window_height)/2.0 * 0.01};
|
|
mouse_delta_screen: Vector2;
|
|
mouse_frozen: bool;
|
|
mouse_frozen_at: Vector2;
|
|
|
|
last_time: float64;
|
|
last_mouse_pos: Vector2;
|
|
|
|
|
|
dbgprint :: print; // so can be detected and removed
|
|
|
|
#scope_file;
|
|
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;
|
|
}
|
|
xy :: (x: int, y: int) -> Vector2 {
|
|
return xy(cast(float)x, cast(float)y);
|
|
}
|
|
#scope_export;
|
|
|
|
WHITE :: Vector4.{1.0, 1.0, 1.0, 1.0};
|
|
RED :: Vector4.{1.0, 0.0, 0.0, 1.0};
|
|
GREEN :: Vector4.{0.0, 1.0, 0.0, 1.0};
|
|
BLUE :: Vector4.{0.0, 0.0, 1.0, 1.0};
|
|
ORANGE :: Vector4.{1.0, cast(float)0xA5/ cast(float)255, 0.0, 1.0};
|
|
DrawingSettings :: struct {
|
|
world_space: bool = true;
|
|
color: Vector4 = WHITE;
|
|
};
|
|
line :: (using d: DrawingSettings, _from: Vector2, _to: Vector2, thickness: float = 1.0) {
|
|
width := thickness;
|
|
from := _from;
|
|
to := _to;
|
|
if world_space {
|
|
from = world_to_screen(from);
|
|
to = world_to_screen(to);
|
|
}
|
|
Simp.set_shader_for_color(true);
|
|
normal := rotate(unit_vector(to - from), PI/2.0);
|
|
Simp.immediate_quad(from + normal*width, from - normal*width, to + normal*width, to - normal*width, color = color);
|
|
}
|
|
vector :: (d: DrawingSettings, from: Vector2, vector: Vector2) {
|
|
line(d, from, from+vector);
|
|
|
|
arrow_head_length := length(vector) * 0.5;
|
|
|
|
// arrow head
|
|
line(d, from + vector, from + vector + rotate(normalize(vector)*arrow_head_length, PI/2.0 + PI/4.0));
|
|
line(d, from + vector, from + vector + rotate(normalize(vector)*arrow_head_length, -PI/2.0 + -PI/4.0));
|
|
}
|
|
|
|
LastingPip :: struct {
|
|
alive_for: float;
|
|
pos: Vector2;
|
|
d: DrawingSettings;
|
|
};
|
|
|
|
pips : [100] LastingPip;
|
|
push_pip :: (d: DrawingSettings, at: Vector2) {
|
|
for * pips
|
|
{
|
|
if it.alive_for <= 0.0
|
|
{
|
|
it.alive_for = 1.0;
|
|
it.pos = at;
|
|
it.d = d;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
draw_pips :: (dt: float) {
|
|
for * pips if it.alive_for > 0.0
|
|
{
|
|
d := it.d;
|
|
d.color.w *= it.alive_for;
|
|
pip(d, it.pos);
|
|
it.alive_for -= dt;
|
|
}
|
|
}
|
|
pip :: (using d: DrawingSettings, _at: Vector2, size: float = 0.05) {
|
|
at := _at;
|
|
Simp.set_shader_for_color(true);
|
|
if world_space {
|
|
at = world_to_screen(at);
|
|
size /= camera.scale.x;
|
|
}
|
|
Simp.immediate_quad(at + xy(-size, size), at + xy(size, size), at + xy(size, -size), at + xy(-size, -size), color = color);
|
|
}
|
|
|
|
font: *Simp.Dynamic_Font;
|
|
text :: (using d: DrawingSettings, t: string, p: Vector2) {
|
|
drawing_at := p;
|
|
if world_space {
|
|
drawing_at = world_to_screen(drawing_at);
|
|
}
|
|
Simp.draw_text(font, xx drawing_at.x, xx drawing_at.y, t, color = color);
|
|
}
|
|
|
|
init :: () {
|
|
window = create_window(window_width, window_height, "Test Kit");
|
|
// Actual render size in pixels can be different from the window dimensions we specified above (for example on high-resolution displays on macOS/iOS).
|
|
window_width, window_height = Simp.get_render_dimensions(window);
|
|
|
|
Simp.set_render_target(window);
|
|
|
|
font = Simp.get_font_at_size(".", "Roboto-Regular.ttf", 18);
|
|
assert(font != null);
|
|
}
|
|
// screen coordinates
|
|
mouse_screen :: () -> Vector2 {
|
|
if mouse_frozen return mouse_frozen_at;
|
|
x, y := get_mouse_pointer_position();
|
|
pos: Vector2 = xy(cast(float)x, cast(float)y);
|
|
|
|
// simp is lower left is (0, 0) and y+ is up
|
|
pos.y = window_height - pos.y;
|
|
|
|
return pos;
|
|
}
|
|
mouse_world :: () -> Vector2 {
|
|
return screen_to_world(mouse_screen());
|
|
}
|
|
|
|
// query quit to see if should quit. Call this in a loop
|
|
frame_start :: () {
|
|
|
|
dt = cast(float)(get_time() - last_time);
|
|
last_time = get_time();
|
|
Input.update_window_events();
|
|
mouse_delta_screen = mouse_screen() - last_mouse_pos; // using Input.mouse_delta_x seems to incorrectly accumulate
|
|
last_mouse_pos = mouse_screen();
|
|
|
|
for Input.get_window_resizes() {
|
|
Simp.update_window(it.window); // Simp will do nothing if it doesn't care about this window.
|
|
|
|
if it.window == window {
|
|
should_reinit := (it.width != window_width) || (it.height != window_height);
|
|
|
|
window_width = it.width;
|
|
window_height = it.height;
|
|
|
|
//if should_reinit my_init_fonts(); // Resize the font for the new window size.
|
|
}
|
|
}
|
|
|
|
camera.scale.x *= 1.0 - 0.1*Input.mouse_delta_z/120.0;
|
|
camera.scale.y = camera.scale.x;
|
|
if panning {
|
|
camera.pos -= mouse_delta_screen*camera.scale.x;
|
|
}
|
|
Simp.immediate_begin();
|
|
Simp.clear_render_target(0.0, 0.0, 0.0, 1.0);
|
|
|
|
// draw grid
|
|
grid_d: DrawingSettings = .{true,.{0.2, 0.2, 0.2, 0.5}};
|
|
text_d := grid_d;
|
|
text_d.color = .{0.6, 0.6, 0.6, 1.0};
|
|
help_text_max :: 8;
|
|
help_zoom_max :: 0.04;
|
|
for x: -30..30 {
|
|
line(grid_d, xy(x, 30), xy(x, -30));
|
|
if camera.scale.x <= help_zoom_max && abs(x) < help_text_max then text(text_d, tprint("%m", x), .{xx x, 0.0});
|
|
}
|
|
for y: -30..30 {
|
|
line(grid_d, xy(30, y), xy(-30, y));
|
|
if camera.scale.x <= help_zoom_max && abs(y) < help_text_max then text(text_d, tprint("%m", y), .{0.0, xx y});
|
|
}
|
|
|
|
draw_pips(dt);
|
|
|
|
if mouse_frozen pip(.{false, ORANGE}, mouse_frozen_at, size = 20.0);
|
|
|
|
|
|
// handle inputs
|
|
for Input.events_this_frame {
|
|
if it.type == .QUIT then quit = true;
|
|
|
|
if it.type == {
|
|
case .KEYBOARD;
|
|
if it.key_pressed {
|
|
if it.key_code == {
|
|
case .ESCAPE;
|
|
quit = true;
|
|
|
|
case #char "T";
|
|
if !mouse_frozen mouse_frozen_at = mouse_screen();
|
|
mouse_frozen = !mouse_frozen;
|
|
}
|
|
}
|
|
if it.key_code == .MOUSE_BUTTON_LEFT {
|
|
panning = cast(bool)it.key_pressed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
frame_end :: () {
|
|
Simp.immediate_flush();
|
|
Simp.swap_buffers(window);
|
|
}
|
|
|
|
screen_to_world :: (screen: Vector2) -> Vector2 {
|
|
return xform(camera, screen);
|
|
}
|
|
world_to_screen :: (world: Vector2) -> Vector2 {
|
|
return xform_inv(camera, world);
|
|
}
|
|
|
|
draw_body :: (d: DrawingSettings, b: Body) {
|
|
draw_shape(d, b.shape);
|
|
}
|
|
|
|
|
|
draw_shape :: (d: DrawingSettings, shape: Shape) {
|
|
#if false {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
demo_circle :: () {
|
|
init();
|
|
|
|
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});
|
|
|
|
unprocessed_time: float = 0.0;
|
|
while !quit {
|
|
frame_start();
|
|
defer frame_end();
|
|
// 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_screen())));
|
|
|
|
hovering: *Body = point_query(bodies, screen_to_world(mouse_screen()));
|
|
for * bodies {
|
|
d: DrawingSettings = .{true, GREEN};
|
|
if hovering == it {
|
|
d.color = RED;
|
|
vector(d, screen_to_world(mouse_screen()), hit_impulse(it.pos, screen_to_world(mouse_screen())) * TIMESTEP);
|
|
}
|
|
draw_body(d, it);
|
|
}
|
|
|
|
for Input.events_this_frame {
|
|
if it.type == {
|
|
case .KEYBOARD;
|
|
if it.key_code == .MOUSE_BUTTON_LEFT {
|
|
panning = cast(bool)it.key_pressed;
|
|
to_tap := point_query(bodies, screen_to_world(mouse_screen()));
|
|
if to_tap != null apply_impulse(to_tap, mouse_world(), hit_impulse(to_tap.pos, mouse_screen()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
demo_transforms_print :: () {
|
|
point := xy(1, 1);
|
|
t: Transform2D;
|
|
t.pos = xy(5, 0);
|
|
t.angle = degrees(45.0);
|
|
print("%\n", xform(t, point));
|
|
print("%\n", xform_inv(t, xform(t, point)));
|
|
}
|
|
|
|
demo_transforms :: () {
|
|
init();
|
|
|
|
|
|
t: Transform2D;
|
|
point := xy(2, 1);
|
|
|
|
while !quit {
|
|
frame_start();
|
|
|
|
pip(.{color = GREEN}, t.pos);
|
|
pip(.{}, xform(t, point));
|
|
|
|
POINTS :: 64;
|
|
cur_point := point;
|
|
cur_point_inv := point;
|
|
START_COLOR :: WHITE;
|
|
END_COLOR :: BLUE;
|
|
END_COLOR_INV :: ORANGE;
|
|
for 0..POINTS-1 {
|
|
progress: float = cast(float)it / cast(float)POINTS;
|
|
cur_point = xform(t, cur_point);
|
|
cur_point_inv = xform_inv(t, cur_point_inv);
|
|
pip(.{color = lerp(START_COLOR, END_COLOR, progress)}, cur_point);
|
|
pip(.{color = lerp(START_COLOR, END_COLOR_INV, progress)}, cur_point_inv);
|
|
}
|
|
print("\n");
|
|
|
|
|
|
horizontal: float = 0.0;
|
|
vertical: float = 0.0;
|
|
for Input.input_button_states if it & .DOWN {
|
|
keycode: Input.Key_Code = xx it_index;
|
|
if keycode == #char "D" horizontal += 1.0;
|
|
if keycode == #char "A" horizontal -= 1.0;
|
|
if keycode == #char "W" vertical += 1.0;
|
|
if keycode == #char "S" vertical -= 1.0;
|
|
if keycode == .MOUSE_BUTTON_RIGHT t.pos += mouse_delta_screen*camera.scale.x;
|
|
}
|
|
print("should be equal: % %\n", point, xform_inv(t, xform(t, point)));
|
|
t.angle += horizontal*dt*degrees(45.0);
|
|
t.scale.x += vertical*dt;
|
|
print("%\n", t);
|
|
frame_end();
|
|
}
|
|
}
|
|
|
|
demo_transforms2 :: () {
|
|
init();
|
|
|
|
draw_transform :: (d: DrawingSettings, t: Transform2D) {
|
|
horizontal := xform(t, xy(1.0, 0.0));
|
|
vertical := xform(t, xy(0.0, 1.0));
|
|
line(d, t.pos, vertical);
|
|
line(d, t.pos, horizontal);
|
|
pip(d, t.pos);
|
|
}
|
|
|
|
t1: Transform2D;
|
|
t2: Transform2D;
|
|
point := xy(2, 1);
|
|
while !quit {
|
|
print("%\n", camera);
|
|
frame_start();
|
|
defer frame_end();
|
|
|
|
combined := xform(t1, t2);
|
|
draw_transform(.{color = GREEN}, t1);
|
|
draw_transform(.{color = BLUE}, t2);
|
|
draw_transform(.{color = ORANGE}, combined);
|
|
|
|
pip(.{}, xform(t1, point));
|
|
pip(.{color = ORANGE}, xform(combined, point));
|
|
|
|
horizontal: float;
|
|
vertical: float;
|
|
right_panning: Vector2;
|
|
for Input.input_button_states if it & .DOWN {
|
|
keycode: Input.Key_Code = xx it_index;
|
|
if keycode == #char "D" horizontal += 1.0;
|
|
if keycode == #char "A" horizontal -= 1.0;
|
|
if keycode == #char "W" vertical += 1.0;
|
|
if keycode == #char "S" vertical -= 1.0;
|
|
if keycode == .MOUSE_BUTTON_RIGHT right_panning = mouse_delta_screen*camera.scale.x;
|
|
}
|
|
t1.angle += horizontal*dt*degrees(45.0);
|
|
t2.angle += vertical*dt*degrees(45.0);
|
|
t1.pos += right_panning;
|
|
//t1.scale.x += horizontal*dt;
|
|
t2.pos += 2.0*right_panning;
|
|
}
|
|
}
|
|
|
|
demo_contact :: () {
|
|
init();
|
|
|
|
size := Vector2.{1.0, 1.0};
|
|
|
|
from := Shape.{type = .Rectangle, halfsize = size/2.0};
|
|
to := Shape.{type = .Rectangle, offset = xy(0, 1.0), halfsize = size/2.0};
|
|
|
|
while !quit {
|
|
frame_start();
|
|
frame_end();
|
|
}
|
|
}
|
|
|
|
//main :: () { demo_circle(); }
|
|
//main :: () { demo_transforms_print(); }
|
|
//main :: () { demo_transforms(); }
|
|
main :: () { demo_transforms2(); }
|
|
//main :: () { demo_contact(); }
|