made it work
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 19 Oct 2021 19:01:21 +0000 (14:01 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 19 Oct 2021 19:01:21 +0000 (14:01 -0500)
res/body_fragment.glsl [new file with mode: 0644]
res/body_vertex.glsl [new file with mode: 0644]
src/camera.onyx [new file with mode: 0644]
src/physics.onyx [new file with mode: 0644]
src/quadtree.onyx [new file with mode: 0644]
src/settings.onyx [new file with mode: 0644]
src/sim.onyx
src/vecmath.onyx [new file with mode: 0644]

diff --git a/res/body_fragment.glsl b/res/body_fragment.glsl
new file mode 100644 (file)
index 0000000..4df8484
--- /dev/null
@@ -0,0 +1,12 @@
+#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
diff --git a/res/body_vertex.glsl b/res/body_vertex.glsl
new file mode 100644 (file)
index 0000000..1d6ba67
--- /dev/null
@@ -0,0 +1,19 @@
+#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
diff --git a/src/camera.onyx b/src/camera.onyx
new file mode 100644 (file)
index 0000000..f054392
--- /dev/null
@@ -0,0 +1,29 @@
+
+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
diff --git a/src/physics.onyx b/src/physics.onyx
new file mode 100644 (file)
index 0000000..855ae0c
--- /dev/null
@@ -0,0 +1,81 @@
+#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
diff --git a/src/quadtree.onyx b/src/quadtree.onyx
new file mode 100644 (file)
index 0000000..b49f001
--- /dev/null
@@ -0,0 +1,98 @@
+#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);
+    }
+}
diff --git a/src/settings.onyx b/src/settings.onyx
new file mode 100644 (file)
index 0000000..e3d3bd1
--- /dev/null
@@ -0,0 +1,62 @@
+#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
index 1bf9456b49db50edcf462cab109c339fffb400e5..a450d00906c0fa8a9faec8c554549e7d7bb943c2 100644 (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
diff --git a/src/vecmath.onyx b/src/vecmath.onyx
new file mode 100644 (file)
index 0000000..6b35887
--- /dev/null
@@ -0,0 +1,74 @@
+#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