#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 * it.shape.mass; 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; } } } } }