From f367cefdd37038f853aad7e874e6309bfd74013f Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Tue, 19 Oct 2021 14:01:21 -0500 Subject: [PATCH] made it work --- res/body_fragment.glsl | 12 +++ res/body_vertex.glsl | 19 ++++ src/camera.onyx | 29 ++++++ src/physics.onyx | 81 ++++++++++++++++ src/quadtree.onyx | 98 +++++++++++++++++++ src/settings.onyx | 62 ++++++++++++ src/sim.onyx | 209 ++++++++++++++++++++++++++++++++++------- src/vecmath.onyx | 74 +++++++++++++++ 8 files changed, 549 insertions(+), 35 deletions(-) create mode 100644 res/body_fragment.glsl create mode 100644 res/body_vertex.glsl create mode 100644 src/camera.onyx create mode 100644 src/physics.onyx create mode 100644 src/quadtree.onyx create mode 100644 src/settings.onyx create mode 100644 src/vecmath.onyx diff --git a/res/body_fragment.glsl b/res/body_fragment.glsl new file mode 100644 index 0000000..4df8484 --- /dev/null +++ b/res/body_fragment.glsl @@ -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 index 0000000..1d6ba67 --- /dev/null +++ b/res/body_vertex.glsl @@ -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 index 0000000..f054392 --- /dev/null +++ b/src/camera.onyx @@ -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 index 0000000..855ae0c --- /dev/null +++ b/src/physics.onyx @@ -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 index 0000000..b49f001 --- /dev/null +++ b/src/quadtree.onyx @@ -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 index 0000000..e3d3bd1 --- /dev/null +++ b/src/settings.onyx @@ -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 diff --git a/src/sim.onyx b/src/sim.onyx index 1bf9456..a450d00 100644 --- a/src/sim.onyx +++ b/src/sim.onyx @@ -1,69 +1,208 @@ 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 index 0000000..6b35887 --- /dev/null +++ b/src/vecmath.onyx @@ -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 -- 2.25.1