|
|
|
#import "Basic";
|
|
|
|
#import "Math";
|
|
|
|
#import "Window_Creation";
|
|
|
|
Debug :: #import "Debug";
|
|
|
|
Input :: #import "Input";
|
|
|
|
Simp :: #import "Simp";
|
|
|
|
|
|
|
|
TIMESTEP: float = 1.0 / 60.0;
|
|
|
|
DEBUGGING :: true;
|
|
|
|
window_width : s32 = 1280;
|
|
|
|
window_height : s32 = 720;
|
|
|
|
|
|
|
|
dbgprint :: print; // so can be
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
negative :: (v: Vector2) -> Vector2 #must {
|
|
|
|
return xy(-v.x, -v.y);
|
|
|
|
}
|
|
|
|
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};
|
|
|
|
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 {
|
|
|
|
alive_for: float;
|
|
|
|
pos: Vector2;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
mass: float = 1.0;
|
|
|
|
moment_of_inertia: float;
|
|
|
|
}
|
|
|
|
|
|
|
|
Body :: struct {
|
|
|
|
pos , vel , force : Vector2; // pos is in world space
|
|
|
|
angle, angle_vel, torque: float;
|
|
|
|
static: bool;
|
|
|
|
|
|
|
|
shape: Shape;
|
|
|
|
}
|
|
|
|
|
|
|
|
local_to_world :: (using b: Body, local_point: Vector2) -> Vector2 {
|
|
|
|
return rotate(local_point, angle) + pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
draw_body :: (using b: Body) {
|
|
|
|
if shape.type == {
|
|
|
|
case .Circle;
|
|
|
|
d: DrawingSettings = .{true, GREEN};
|
|
|
|
POINTS :: 31;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
case .Rectangle;
|
|
|
|
assert(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
draw_rect :: (using r: Shape) {
|
|
|
|
assert(type == .Rectangle);
|
|
|
|
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(upper_left, upper_right);
|
|
|
|
line(upper_right, lower_right);
|
|
|
|
line(lower_right, lower_left);
|
|
|
|
line(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 :: () {
|
|
|
|
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;
|
|
|
|
array_add(*bodies, .{pos = .{0.0, 0.0}, shape = .{type = .Circle, radius = 1.0}});
|
|
|
|
array_add(*bodies, .{pos = .{3.0, 0.0}, shape = .{type = .Circle, radius = 1.0}});
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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));
|
|
|
|
}
|
|
|
|
for * bodies {
|
|
|
|
defer it.force = .{};
|
|
|
|
defer it.torque = 0.0;
|
|
|
|
|
|
|
|
// calculate moment of inertia
|
|
|
|
if it.shape.type == {
|
|
|
|
case .Circle;
|
|
|
|
it.shape.moment_of_inertia = PI * pow(it.shape.radius, 4.0) / 4.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// gravity
|
|
|
|
it.force.y += -9.81;
|
|
|
|
|
|
|
|
if !it.static
|
|
|
|
{
|
|
|
|
it.vel += (it.force/it.shape.mass) * TIMESTEP;
|
|
|
|
it.pos += it.vel * TIMESTEP;
|
|
|
|
it.angle_vel += (it.torque / it.shape.moment_of_inertia) * TIMESTEP;
|
|
|
|
it.angle += it.angle_vel * TIMESTEP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
for bodies draw_body(it);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Simp.immediate_flush();
|
|
|
|
|
|
|
|
Simp.swap_buffers(window);
|
|
|
|
|
|
|
|
for Input.events_this_frame {
|
|
|
|
if it.type == .QUIT then quit = true;
|
|
|
|
|
|
|
|
if it.type == {
|
|
|
|
case .KEYBOARD;
|
|
|
|
if it.key_pressed && it.key_code == .ESCAPE {
|
|
|
|
quit = true;
|
|
|
|
}
|
|
|
|
if it.key_code == .MOUSE_BUTTON_LEFT {
|
|
|
|
panning = cast(bool)it.key_pressed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|