Partial broken refactor into testkit

main
Cameron Murphy Reikes 2 years ago
parent cb5b780534
commit d5c8b6e8c8

@ -1,16 +1,9 @@
#import "Basic"; #import "Basic";
#import "Math"; #import "Math";
#import "Window_Creation"; #import "testkit.jai";
Debug :: #import "Debug";
Input :: #import "Input";
Simp :: #import "Simp";
TIMESTEP: float = 1.0 / 60.0; TIMESTEP: float = 1.0 / 60.0;
DEBUGGING :: true; DEBUGGING :: true;
window_width : s32 = 1280;
window_height : s32 = 720;
dbgprint :: print; // so can be
xy :: (x: int, y: int) -> Vector2 { xy :: (x: int, y: int) -> Vector2 {
return xy(cast(float)x, cast(float)y); return xy(cast(float)x, cast(float)y);
@ -33,75 +26,25 @@ normalize_or_zero :: inline (using _v: Vector2) -> Vector2 #must {
normalize_or_zero(*v); normalize_or_zero(*v);
return v; return v;
} }
length_squared :: inline (a: Vector2) -> float {
return a.x*a.x + a.y*a.y;
}
negative :: (v: Vector2) -> Vector2 #must { negative :: (v: Vector2) -> Vector2 #must {
return xy(-v.x, -v.y); return xy(-v.x, -v.y);
} }
WHITE :: Vector4.{1.0, 1.0, 1.0, 1.0}; cross :: (a: Vector2, b: Vector2) -> float {
RED :: Vector4.{1.0, 0.0, 0.0, 1.0}; return a.x * b.y - a.y * b.x;
GREEN :: Vector4.{0.0, 1.0, 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);
} }
LastingPip :: struct { LastingPip :: struct {
alive_for: float; alive_for: float;
pos: Vector2; pos: Vector2;
d: DrawingSettings; d: DrawingSettings;
} }
pips : [10] 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;
}
}
}
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);
}
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.zoom;
}
Simp.immediate_quad(at + xy(-size, size), at + xy(size, size), at + xy(size, -size), at + xy(-size, -size), color = color);
}
hit_impulse :: (body_pos: Vector2, mouse_pos: Vector2) -> Vector2 {
return .{0, -15.0};
//return normalize(body_pos - mouse_pos) * 15.0;
}
ShapeType :: enum { ShapeType :: enum {
Circle; Circle;
Rectangle; Rectangle;
@ -112,43 +55,119 @@ Shape :: struct {
offset: Vector2; // in local space of body offset: Vector2; // in local space of body
radius: float; // only valid on circle radius: float; // only valid on circle
halfsize: Vector2; // only valid on rectangle halfsize: Vector2; // only valid on rectangle
mass: float = 1.0; invmass: float = 1.0;
moment_of_inertia: float; inv_moment_of_inertia: float;
} }
Body :: struct { Body :: struct {
pos , vel , force : Vector2; // pos is in world space pos , vel , force : Vector2; // pos is in world space
angle, angle_vel, torque: float; angle, angle_vel, torque: float;
static: bool; elasticity: float; // 1 is bouncy, 0 is absorbant
shape: Shape; shape: Shape;
} }
local_to_world :: (using b: Body, local_point: Vector2) -> Vector2 { point_query :: (bodies: [] Body, world_point: Vector2) -> *Body {
return rotate(local_point, angle) + pos; for *bodies {
if length_squared(world_to_local(it, world_point)) < it.shape.radius*it.shape.radius return it;
}
return null;
} }
draw_body :: (using b: Body) { 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 == { if shape.type == {
case .Circle; case .Circle;
d: DrawingSettings = .{true, GREEN}; points: int = 32;
POINTS :: 31; if shape.radius > 100.0 points = 128;
drawing_circle_at: Vector2 = local_to_world(b, shape.offset); drawing_circle_at: Vector2 = local_to_world(b, shape.offset);
for 0..POINTS { for 0..points {
theta: float = (it/cast(float)POINTS) * PI*2.0; theta: float = (it/cast(float)points) * PI*2.0;
theta_next: float = (it+1)/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; from := drawing_circle_at + .{cos(theta),sin(theta)} * shape.radius;
to := drawing_circle_at + .{cos(theta_next),sin(theta_next)} * shape.radius; to := drawing_circle_at + .{cos(theta_next),sin(theta_next)} * shape.radius;
line(d, from, to); line(d, from, to);
} }
pip(d, pos);
case .Rectangle; case .Rectangle;
assert(false); using b.shape;
}
}
/*
draw_rect :: (using r: Shape) {
assert(type == .Rectangle);
facing_to_right := rotate(xy(halfsize.x,0.0), angle); facing_to_right := rotate(xy(halfsize.x,0.0), angle);
facing_to_up := rotate(xy(0.0,halfsize.y), angle); facing_to_up := rotate(xy(0.0,halfsize.y), angle);
@ -157,93 +176,22 @@ draw_rect :: (using r: Shape) {
lower_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; lower_right := pos + facing_to_right - facing_to_up;
line(upper_left, upper_right); line(d, upper_left, upper_right);
line(upper_right, lower_right); line(d, upper_right, lower_right);
line(lower_right, lower_left); line(d, lower_right, lower_left);
line(lower_left, upper_left); line(d, lower_left, upper_left);
} }
*/
mouse :: () -> Vector2 {
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;
} }
Camera :: struct {
pos: Vector2;
zoom: float = 1.0;
};
camera : Camera;
screen_to_world :: (screen: Vector2) -> Vector2 {
using camera;
return (screen + pos)*zoom;
}
world_to_screen :: (world: Vector2) -> Vector2 {
using camera;
// world = (screen + pos)*zoom;
// world/zoom = screen + pos;
// world/zoom - pos = screen;
return (world/zoom) - pos;
}
main :: () { main :: () {
window := create_window(window_width, window_height, "A Window");
// 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);
camera.pos = -xy(window_width, window_height)/2.0;
camera.zoom = 0.01;
Simp.set_render_target(window);
font = Simp.get_font_at_size(".", "Roboto-Regular.ttf", 18);
assert(font != null);
bodies: [..]Body; bodies: [..]Body;
array_add(*bodies, .{pos = .{0.0, 0.0}, shape = .{type = .Circle, radius = 1.0}}); array_add(*bodies, .{pos = .{0.0, 0.0}, shape = .{type = .Circle, radius = 1.0}, elasticity = 0.5});
array_add(*bodies, .{pos = .{3.0, 0.0}, shape = .{type = .Circle, radius = 1.0}}); 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});
quit := false;
last_time := get_time();
panning: bool = false;
last_mouse_pos := mouse();
unprocessed_time: float = 0.0;
while !quit {
dt := cast(float)(get_time() - last_time);
last_time = get_time();
Input.update_window_events();
mouse_delta := mouse() - last_mouse_pos; // using Input.mouse_delta_x seems to incorrectly accumulate
last_mouse_pos = mouse();
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.zoom *= 1.0 - 0.1*Input.mouse_delta_z/120.0;
if panning {
camera.pos -= mouse_delta;
// this doesn't fix it
//Input.mouse_delta_x = 0;
//Input.mouse_delta_y = 0;
}
while !quit {
// process physics // process physics
unprocessed_time += dt; unprocessed_time += dt;
{ {
@ -256,68 +204,65 @@ main :: () {
{ {
//apply_force_at_point(*rects[0], xy(3, 0), rects[0].pos + xy(-0.1, 0.03)); //apply_force_at_point(*rects[0], xy(3, 0), rects[0].pos + xy(-0.1, 0.03));
} }
for * bodies { // iterating by index so can do a sort of iteration that avoids double checking
defer it.force = .{}; // collisions between bodies. This is important because if intersections are double
defer it.torque = 0.0; // 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 // calculate moment of inertia
if it.shape.type == { if a.shape.type == {
case .Circle; case .Circle;
it.shape.moment_of_inertia = PI * pow(it.shape.radius, 4.0) / 4.0; a.shape.inv_moment_of_inertia = 1.0/(PI * pow(a.shape.radius, 4.0) / 4.0);
case;
assert(false);
} }
// gravity // gravity must be impulse for some reason
it.force.y += -9.81 * it.shape.mass; apply_impulse_linear(a, .{0.0, -9.81 * (1.0 / a.shape.invmass) * TIMESTEP});
if !it.static // collisions
{ for it+1..bodies.count-1 {
it.vel += (it.force/it.shape.mass) * TIMESTEP; b := *bodies[it];
it.pos += it.vel * TIMESTEP; contact: Contact;
it.angle_vel += (it.torque / it.shape.moment_of_inertia) * TIMESTEP; if intersect(a, b, *contact) {
it.angle += it.angle_vel * TIMESTEP; resolve(contact);
} }
} }
} }
}
for * bodies {
defer { it.force = .{}; it.torque = 0.0; }
Simp.immediate_begin(); it.vel += (it.force * it.shape.invmass) * TIMESTEP;
Simp.clear_render_target(0.0, 0.0, 0.0, 1.0); it.pos += it.vel * TIMESTEP;
it.angle_vel += (it.torque * it.shape.inv_moment_of_inertia) * TIMESTEP;
// draw grid it.angle += it.angle_vel * TIMESTEP;
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.zoom <= 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.zoom <= help_zoom_max && abs(y) < help_text_max then text(text_d, tprint("%m", y), .{0.0, xx y});
} }
draw_pips(dt);
for bodies draw_body(it);
b := bodies[0];
pip(.{}, local_to_world(b, world_to_local(b, mouse())));
Simp.immediate_flush();
Simp.swap_buffers(window); 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 { for Input.events_this_frame {
if it.type == .QUIT then quit = true;
if it.type == { if it.type == {
case .KEYBOARD;
if it.key_pressed && it.key_code == .ESCAPE {
quit = true;
}
if it.key_code == .MOUSE_BUTTON_LEFT { if it.key_code == .MOUSE_BUTTON_LEFT {
panning = cast(bool)it.key_pressed; panning = cast(bool)it.key_pressed;
to_tap := point_query(bodies, screen_to_world(mouse()));
} }
} }
} }

@ -0,0 +1,223 @@
#import "Window_Creation";
Debug :: #import "Debug";
Simp :: #import "Simp";
Input :: #import "Input";
Camera :: struct {
pos: Vector2;
zoom: float = 1.0;
};
window: Window;
quit: bool;
panning: bool = false;
camera : Camera;
mouse_frozen: bool;
mouse_frozen_at: Vector2;
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);
}
window_width : s32 = 1280;
window_height : s32 = 720;
dbgprint :: print; // so can be detected and removed
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};
ORANGE :: Vector4.{1.0, 0xA5/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));
}
pips : [10] 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.zoom;
}
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);
camera.pos = -xy(window_width, window_height)/2.0;
camera.zoom = 0.01;
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());
}
// query quit to see if should quit. Call this in a loop
frame_start :: () {
last_time := get_time();
last_mouse_pos := mouse();
unprocessed_time: float = 0.0;
dt := cast(float)(get_time() - last_time);
last_time = get_time();
Input.update_window_events();
mouse_delta := mouse() - last_mouse_pos; // using Input.mouse_delta_x seems to incorrectly accumulate
last_mouse_pos = mouse();
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.zoom *= 1.0 - 0.1*Input.mouse_delta_z/120.0;
if panning {
camera.pos -= mouse_delta;
}
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.zoom <= 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.zoom <= 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();
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 {
using camera;
return (screen + pos)*zoom;
}
world_to_screen :: (world: Vector2) -> Vector2 {
using camera;
// world = (screen + pos)*zoom;
// world/zoom = screen + pos;
// world/zoom - pos = screen;
return (world/zoom) - pos;
}
Loading…
Cancel
Save