--- /dev/null
+#version 300 es
+
+precision mediump float;
+
+uniform vec3 u_planet_colors[32];
+
+in float planet_color_idx;
+out vec4 fragColor;
+
+void main() {
+ fragColor = vec4(u_planet_colors[int(planet_color_idx)], 1.0);
+}
\ No newline at end of file
--- /dev/null
+#version 300 es
+
+precision mediump float;
+
+layout(location = 0) in vec2 a_shape_pos;
+layout(location = 1) in vec2 a_obj_pos;
+layout(location = 2) in float a_mass;
+layout(location = 3) in float a_planet_color_idx;
+
+out float planet_color_idx;
+
+uniform mat4 u_proj;
+uniform mat4 u_camera;
+
+void main() {
+ gl_Position = u_proj * u_camera * vec4(a_shape_pos * a_mass + a_obj_pos, 0, 1);
+
+ planet_color_idx = a_planet_color_idx;
+}
\ No newline at end of file
--- /dev/null
+
+Camera :: struct {
+ offset : V2f;
+ scale : f32;
+ window_width, window_height : i32;
+}
+
+camera_ortho_mat4 :: (cam: Camera, mat: ^Mat4) {
+ mat4_ortho(mat, 0, ~~cam.window_width, 0, ~~cam.window_height, 0.0, 100.0);
+}
+
+camera_world_mat4 :: (cam: Camera, mat: ^Mat4) {
+ mat.data[0] = cam.scale;
+ mat.data[1] = 0.0f;
+ mat.data[2] = 0.0f;
+ mat.data[3] = 0.0f;
+ mat.data[4] = 0.0f;
+ mat.data[5] = cam.scale;
+ mat.data[6] = 0.0f;
+ mat.data[7] = 0.0f;
+ mat.data[8] = 0.0f;
+ mat.data[9] = 0.0f;
+ mat.data[10] = 0.0f;
+ mat.data[11] = 0.0f;
+ mat.data[12] = cam.scale * -cam.offset.x + (1 - cam.scale) * ~~(cam.window_width / 2);
+ mat.data[13] = cam.scale * -cam.offset.y + (1 - cam.scale) * ~~(cam.window_height / 2);
+ mat.data[14] = 0.0f;
+ mat.data[15] = 1.0f;
+}
\ No newline at end of file
--- /dev/null
+#private_file array :: package core.array
+
+Body :: struct {
+ pos : V2f;
+ vel : V2f;
+ acc : V2f;
+
+ mass : f32;
+
+ body_type : u32;
+}
+
+Body_Relation :: struct {
+ distance_range : f32;
+ max_force : f32;
+}
+
+get_force_magnitude_at_distance :: (br: Body_Relation, d: f32) -> f32 {
+ if 0 < d && d < 1 {
+ return global_settings.near_repulsive_force * (1 - d) / d;
+ }
+
+ if 1 <= d && d < br.distance_range + 1 {
+ tmp_x := d - (br.distance_range / 2) - 1;
+
+ return (-4 * br.max_force / (br.distance_range * br.distance_range))
+ * (tmp_x * tmp_x)
+ + br.max_force;
+ }
+
+ return 0;
+}
+
+bodies_collide :: (b1: ^Body, b2: ^Body) -> bool {
+ dist_squared := v2f_smag(b1.pos - b2.pos);
+ radii_sum_squared := (b1.mass + b2.mass) * (b1.mass + b2.mass);
+ return dist_squared < radii_sum_squared;
+}
+
+body_can_move :: (body: ^Body, other_bodies: [..] ^Body, d: V2f) -> bool {
+ body.pos += d;
+ defer body.pos -= d;
+
+ for it: other_bodies {
+ if body == it do continue;
+ if bodies_collide(body, it) do return false;
+ }
+
+ return true;
+}
+
+body_accumulate_move :: (body: ^Body, qt_bodies: ^QuadTree(^Body), dt: f32) {
+ force := V2f.{0,0};
+
+ #persist #thread_local other_bodies: [..] ^Body;
+ if other_bodies.data == null do array.init(^other_bodies);
+ array.clear(^other_bodies);
+ qt_bodies->query(AABB.{ body.pos.x - 300, body.pos.y - 300, 600, 600 }, ^other_bodies);
+
+ for it: other_bodies {
+ if body == it do continue;
+
+ norm_dir := body.pos - it.pos;
+ d := v2f_mag(norm_dir) / global_settings.universe_scale;
+ norm_dir = v2f_norm(norm_dir);
+
+ br := global_settings.body_relations[body.body_type * global_settings.body_type_count + it.body_type];
+ force_mag := get_force_magnitude_at_distance(br, d);
+
+ force += norm_dir * force_mag;
+ }
+
+ force += body.vel * -global_settings.friction;
+
+ body.acc = force * (1 / body.mass);
+ body.vel += body.acc * dt;
+}
+
+body_apply_move :: (body: ^Body, dt: f32) {
+ body.pos += body.vel * dt;
+}
\ No newline at end of file
--- /dev/null
+#private_file array :: package core.array
+
+AABB :: struct {
+ x, y : f32;
+ w, h : f32;
+}
+
+aabb_contains :: (use r: AABB, ox, oy: f32) -> bool {
+ return x <= ox && ox <= x + w && y <= oy && oy <= y + h;
+}
+
+aabb_intersects :: (use r: AABB, other: AABB) -> bool {
+ return x <= other.x + other.w
+ && x + w >= other.x
+ && y <= other.y + other.h
+ && y + h >= other.y;
+}
+
+
+
+
+QuadTree :: struct (T: type_expr) {
+ POINTS_PER_NODE :: 16
+
+ points : [POINTS_PER_NODE] T;
+ point_count : u32;
+
+ region : AABB;
+
+ nw, ne, sw, se : ^QuadTree(T);
+
+ init :: (use q: ^QuadTree($T), initial_region: AABB) {
+ region = initial_region;
+
+ nw = null;
+ ne = null;
+ se = null;
+ sw = null;
+
+ point_count = 0;
+ for i: POINTS_PER_NODE do points[i] = null;
+ }
+
+ subdivide :: (use q: ^QuadTree($T), a: Allocator) {
+ if nw != null do return;
+
+ hw := region.w / 2;
+ hh := region.h / 2;
+
+ nw = make(#type QuadTree(T), allocator=a);
+ ne = make(#type QuadTree(T), allocator=a);
+ se = make(#type QuadTree(T), allocator=a);
+ sw = make(#type QuadTree(T), allocator=a);
+
+ nw->init(.{ region.x, region.y, hw, hh });
+ ne->init(.{ region.x + hw, region.y, hw, hh });
+ se->init(.{ region.x + hw, region.y + hh, hw, hh });
+ sw->init(.{ region.x, region.y + hh, hw, hh });
+ }
+
+ insert :: (use q: ^QuadTree($T), t: T, a: Allocator) -> bool {
+ pos := t.pos;
+
+ if !aabb_contains(region, pos.x, pos.y) do return false;
+
+ if point_count < POINTS_PER_NODE && nw == null {
+ points[point_count] = t;
+ point_count += 1;
+ return true;
+ }
+
+ if nw == null do q->subdivide(a);
+
+ if nw->insert(t, a) do return true;
+ if ne->insert(t, a) do return true;
+ if se->insert(t, a) do return true;
+ if sw->insert(t, a) do return true;
+
+ return false;
+ }
+
+ query :: (use q: ^QuadTree($T), r: AABB, point_list: ^[..] T) {
+ if !aabb_intersects(region, r) do return;
+
+ for i: point_count {
+ if aabb_contains(r, points[i].pos.x, points[i].pos.y) {
+ array.push(point_list, points[i]);
+ }
+ }
+
+ if nw == null do return;
+
+ nw->query(r, point_list);
+ ne->query(r, point_list);
+ se->query(r, point_list);
+ sw->query(r, point_list);
+ }
+}
--- /dev/null
+#private_file memory :: package core.memory
+#private_file random :: package core.random
+
+Body_Color :: struct {
+ r, g, b: f32;
+}
+
+Body_Mass_Range :: struct {
+ min, max: f32;
+}
+
+Sim_Settings :: struct {
+ body_count : u32;
+ body_type_count : u32;
+
+ body_color : [] Body_Color;
+ body_mass_range : [] Body_Mass_Range;
+
+ body_relations : [] Body_Relation;
+
+ friction : f32;
+ near_repulsive_force : f32;
+ universe_scale : f32;
+ thread_count : u32;
+}
+
+global_settings: Sim_Settings;
+
+generate_random_settings :: (settings: ^Sim_Settings) {
+ settings.body_count = 1000;
+
+ num_body_types :: 4;
+ settings.body_type_count = num_body_types;
+
+ memory.alloc_slice(^settings.body_color, num_body_types);
+ settings.body_color[0] = .{ 1.0, 0.0, 0.0 };
+ settings.body_color[1] = .{ 0.0, 1.0, 0.0 };
+ settings.body_color[2] = .{ 0.0, 0.0, 1.0 };
+ settings.body_color[3] = .{ 1.0, 1.0, 1.0 };
+
+ memory.alloc_slice(^settings.body_mass_range, num_body_types);
+ settings.body_mass_range[0] = .{ 4.0, 6.0 };
+ settings.body_mass_range[1] = .{ 4.0, 6.0 };
+ settings.body_mass_range[2] = .{ 4.0, 6.0 };
+ settings.body_mass_range[3] = .{ 4.0, 6.0 };
+
+ settings.friction = 6;
+ settings.near_repulsive_force = 100;
+ settings.universe_scale = 20;
+
+ settings.thread_count = 4;
+
+ memory.alloc_slice(^settings.body_relations, num_body_types * num_body_types);
+ for i: num_body_types {
+ for j: num_body_types {
+ settings.body_relations[i * num_body_types + j] = .{
+ distance_range = random.float(4, 7),
+ max_force = random.float(-200, 200),
+ };
+ }
+ }
+}
\ No newline at end of file
package main
#load "modules/ouit/module"
+#load "./vecmath"
+#load "./quadtree"
+#load "./physics"
+#load "./settings"
+#load "./camera"
-#private_file {
- gl :: package gl
- gfx :: package immediate_mode
+use package core
+use package core.intrinsics.onyx
- ouit :: package ouit
+gl :: package gl
+gfx :: package immediate_mode
+events :: package js_events
+ouit :: package ouit
- use package core
- use package core.intrinsics.onyx
+Sim_State :: struct {
+ bodies : [..] Body;
+ qt_bodies : QuadTree(^Body);
+
+ camera : Camera;
+
+ qt_pool_buffer : [] QuadTree(^Body);
+ qt_pool_allocator : alloc.pool.PoolAllocator(QuadTree(^Body));
+ qt_body_allocator : Allocator;
}
-main :: (args) => {
- ouit.init("main_canvas", handle_event, update, draw);
- ouit.start();
+#thread_local state: Sim_State;
- t: thread.Thread;
- thread.spawn(^t, null, (_) => {
- i := 0;
- while true {
- use package core.intrinsics.atomics
- i += 1;
+sim_state_init :: (state: ^Sim_State) {
+ array.init(^state.bodies);
- __atomic_wait(cast(^i32) null, 0, 1000000000);
- printf("Count: {}\n", i);
- }
- });
+ for i: global_settings.body_count {
+ tmp_body: Body;
+
+ tmp_body.body_type = random.between(0, global_settings.body_type_count - 1);
+ tmp_body.pos = V2f.{ random.float(-1000, 1000), random.float(-1000, 1000) };
+ tmp_body.vel = V2f.{ 0, 0 };
+
+ bmr := ^global_settings.body_mass_range[tmp_body.body_type];
+ tmp_body.mass = random.float(bmr.min, bmr.max);
+
+ state.bodies << tmp_body;
+ }
+
+ state.camera.scale = 1;
+
+ memory.alloc_slice(^state.qt_pool_buffer, 1024);
+ state.qt_pool_allocator = alloc.pool.make(state.qt_pool_buffer);
+ state.qt_body_allocator = alloc.pool.make_allocator(^state.qt_pool_allocator);
+
+ state.qt_bodies->init(AABB.{ -2000, -2000, 4000, 4000 });
+}
+
+body_program : gl.GLProgram;
+body_buffer : gl.GLBuffer;
+circle_mesh : gl.GLVertexArrayObject;
+
+create_circle_mesh :: () -> gl.GLVertexArrayObject {
+ vao := gl.createVertexArray();
+ gl.bindVertexArray(vao);
+
+ circle_points : [12] V2f;
+ for i: circle_points.count {
+ t := cast(f32) i / 12;
+ circle_points[i].x = math.cos(t * 2 * math.PI);
+ circle_points[i].y = math.sin(t * 2 * math.PI);
+ }
+
+ vertex_buffer := gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
+ gl.bufferData(gl.ARRAY_BUFFER, .{ cast(^void) ^circle_points, sizeof typeof circle_points }, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, sizeof V2f, 0);
+ gl.bindBuffer(gl.ARRAY_BUFFER, -1);
+
+ circle_indicies : [12] u8;
+ for i: circle_indicies.count do circle_indicies[i] = ~~i;
+
+ index_buffer := gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, .{ cast(^void) ^circle_indicies, sizeof typeof circle_indicies }, gl.STATIC_DRAW);
+
+ gl.bindVertexArray(-1);
+
+ return vao;
}
-x: f32 = 0;
-y: f32 = 0;
-sx: f32;
-sy: f32;
handle_event :: (ev) => {
switch ev.kind {
case .KeyDown {
switch ev.keyboard.keycode {
- case 38 do sy = -100.0f;
- case 40 do sy = 100.0f;
- case 39 do sx = 100.0f;
- case 37 do sx = -100.0f;
+ case 38 do state.camera.offset.y -= 6;
+ case 40 do state.camera.offset.y += 6;
+ case 39 do state.camera.offset.x += 6;
+ case 37 do state.camera.offset.x -= 6;
+ case 65 do state.camera.scale /= 1.02;
+ case 81 do state.camera.scale *= 1.02;
}
}
case .Resize {
gl.setSize(ev.resize.width, ev.resize.height);
+ gl.viewport(0, 0, state.camera.window_width, state.camera.window_height);
+ state.camera.window_width = ev.resize.width;
+ state.camera.window_height = ev.resize.height;
+
gfx.set_window_size(ev.resize.width, ev.resize.height);
}
+
+ case .FileDropped {
+ printf("File Dropped: {*}\n", ^ev.file);
+
+ data_buffer := memory.make_slice(u8, ev.file.size);
+ name_buffer := memory.make_slice(u8, ev.file.name_length);
+ defer {
+ memory.free_slice(^data_buffer);
+ memory.free_slice(^name_buffer);
+ }
+ events.get_requested_file_data(ev.file.file_id, data_buffer, name_buffer);
+
+ printf("File name: {}\n", name_buffer);
+ printf("File contents: {}\n", data_buffer);
+ }
}
}
update :: (dt: f32) {
- x += sx * dt;
- y += sy * dt;
+ state.qt_bodies->init(.{ -2000, -2000, 4000, 4000 });
+ state.qt_pool_allocator = alloc.pool.make(state.qt_pool_buffer);
+
+ for ^it: state.bodies {
+ state.qt_bodies->insert(it, state.qt_body_allocator);
+ }
- sx *= 0.9;
- sy *= 0.9;
+ for i: state.bodies.count {
+ body_accumulate_move(^state.bodies[i], ^state.qt_bodies, dt);
+ }
+
+ for i: state.bodies.count {
+ body_apply_move(^state.bodies[i], dt);
+ }
}
draw :: () {
+ gl.bindBuffer(gl.ARRAY_BUFFER, body_buffer);
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, .{ ~~state.bodies.data, state.bodies.count * sizeof Body });
+ gl.bindBuffer(gl.ARRAY_BUFFER, -1);
+
+ gl.useProgram(body_program);
+
+ ortho_mat: Mat4;
+ camera_ortho_mat4(state.camera, ^ortho_mat);
+ ortho_mat_loc := gl.getUniformLocation(body_program, "u_proj");
+ gl.uniformMatrix4(ortho_mat_loc, false, ortho_mat.data);
+
+ camera_mat: Mat4;
+ camera_world_mat4(state.camera, ^camera_mat);
+ camera_mat_loc := gl.getUniformLocation(body_program, "u_camera");
+ gl.uniformMatrix4(camera_mat_loc, false, camera_mat.data);
+
gl.clearColor(.1, .1, .1, 1);
- gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
- gfx.rect(.{ x, y }, .{ 50, 50 }, .{ 1, 0, 0 });
+ gl.bindVertexArray(circle_mesh);
+ gl.drawElementsInstanced(gl.TRIANGLE_FAN, 12, gl.UNSIGNED_BYTE, 0, state.bodies.count);
+ gl.bindVertexArray(-1);
+}
+
+main :: (args) => {
+ ouit.init("main_canvas", handle_event, update, draw);
+
+ generate_random_settings(^global_settings);
+
+ circle_mesh = create_circle_mesh();
+
+ body_vert_shader, _ := gfx.compile_shader(#file_contents "res/body_vertex.glsl", gl.VERTEX_SHADER);
+ body_frag_shader, _ := gfx.compile_shader(#file_contents "res/body_fragment.glsl", gl.FRAGMENT_SHADER);
+ body_program, _ = gfx.link_program(body_vert_shader, body_frag_shader);
+ gl.useProgram(body_program);
+
+ planet_colors_loc := gl.getUniformLocation(body_program, "u_planet_colors");
+ planet_colors := cast(^f32) global_settings.body_color.data;
+ gl.uniform3fv(planet_colors_loc, .{ ~~planet_colors, global_settings.body_type_count });
+
+ sim_state_init(^state);
+
+ gl.bindVertexArray(circle_mesh);
+ body_buffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, body_buffer);
+ gl.bufferData(gl.ARRAY_BUFFER, .{ ~~state.bodies.data, sizeof Body * state.bodies.count }, gl.STREAM_DRAW);
+
+ for i: 1 .. 4 {
+ gl.enableVertexAttribArray(i);
+ gl.vertexAttribDivisor(i, 1);
+ }
+
+ gl.vertexAttribPointer(1, 2, gl.FLOAT, false, sizeof Body, ~~^(cast(^Body) null).pos.x);
+ gl.vertexAttribPointer(2, 1, gl.FLOAT, false, sizeof Body, ~~^(cast(^Body) null).mass);
+ gl.vertexAttribPointer(3, 1, gl.BYTE, false, sizeof Body, ~~^(cast(^Body) null).body_type);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, -1);
+ gl.bindVertexArray(-1);
+
+ ouit.start();
+}
- gfx.flush();
-}
\ No newline at end of file
--- /dev/null
+#private_file {
+ math :: package core.math
+ memory :: package core.memory
+}
+
+V2f :: struct {
+ x, y: f32;
+}
+
+#operator+ (a, b: V2f) => V2f.{ a.x + b.x, a.y + b.y };
+#operator- (a, b: V2f) => V2f.{ a.x - b.x, a.y - b.y };
+#operator* (a: V2f, s: f32) => V2f.{ a.x * s, a.y * s };
+
+#operator+= (a: ^V2f, b: V2f) {
+ a.x += b.x;
+ a.y += b.y;
+}
+
+#operator-= (a: ^V2f, b: V2f) {
+ a.x -= b.x;
+ a.y -= b.y;
+}
+
+#operator*= (a: ^V2f, s: f32) {
+ a.x += s;
+ a.y += s;
+}
+
+v2f_dot :: macro (a, b: V2f) => a.x * b.x + a.y * b.y;
+v2f_smag :: macro (a: V2f) => a.x * a.x + a.y * a.y;
+v2f_mag :: macro (a: V2f) => (package core.math).sqrt(v2f_smag(a));
+v2f_norm :: macro (a: V2f) -> V2f {
+ mag := v2f_mag(a);
+ return .{ a.x / mag, a.y / mag };
+}
+v2f_proj :: macro (a, onto: V2f) -> V2f {
+ dp := v2f_dot(a, onto) / v2f_mag(onto);
+ return onto * dp;
+}
+
+
+
+Mat4 :: struct {
+ data: [16] f32;
+}
+
+mat4_identity :: (use mat: ^Mat4) {
+ memory.set(^data, 0, 16 * sizeof f32);
+ data[0 * 4 + 0] = 1;
+ data[1 * 4 + 1] = 1;
+ data[2 * 4 + 2] = 1;
+ data[3 * 4 + 3] = 1;
+}
+
+mat4_ortho :: (use math: ^Mat4, left, right, top, bottom, near, far: f32) {
+ memory.set(^data, 0, 16 * sizeof f32);
+
+ data[0 * 4 + 0] = 2.0f / (right - left);
+ data[1 * 4 + 1] = 2.0f / (top - bottom);
+ data[2 * 4 + 2] = -2.0f / (far - near);
+ data[3 * 4 + 3] = 1.0f;
+
+ data[3 * 4 + 0] = -(right + left) / (right - left);
+ data[3 * 4 + 1] = -(top + bottom) / (top - bottom);
+ data[3 * 4 + 2] = -(far + near) / (far - near);
+}
+
+mat4_mul :: macro (a, b, use out: ^Mat4) {
+ memory.set(^data, 0, 16 * sizeof f32);
+
+ for row: 4 do for col: 4 {
+ for i: 4 do data[row * 4 + col] += a[row * 4 + i] * b[i * 4 + col];
+ }
+}
\ No newline at end of file