frame rate independence and font rendering
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 6 Oct 2020 20:11:39 +0000 (15:11 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Tue, 6 Oct 2020 20:11:39 +0000 (15:11 -0500)
js/environment.js
res/tilemap.data
res/tilemap.png
src/gfx/atlas.onyx
src/gfx/font.onyx [new file with mode: 0644]
src/input.onyx
src/main.onyx
src/quad_tree.onyx
src/shaders/quad.frag
tags

index b004561d6fdd90b2ffbf119f85276cd181c4f12d..d011d8dca8e512540355209007b9cc4ff8ce4c99 100644 (file)
@@ -9,7 +9,7 @@ async function start_game() {
     while (true) {
         await anim_frame();
 
-        WASM_EXPORTS.new_frame_callback();
+        WASM_EXPORTS.loop();
     }
 }
 
index e53b2339e3396ee16a9e244915a4d6c1d1e7caca..3c0ea7acc704902b164281857d1a142eb875d773 100644 (file)
Binary files a/res/tilemap.data and b/res/tilemap.data differ
index f165b3ddca8a6950f9f6eeda1d8f26e147b8b902..351e18d35dfdee907babb508fb1148944a4d89b7 100644 (file)
Binary files a/res/tilemap.png and b/res/tilemap.png differ
index ae92c1cdb00240ce2308d5048cdd8dd11eec5e88..03f227cbd2dd3b5231e1fdc657b64c051b5b53b0 100644 (file)
@@ -28,8 +28,18 @@ atlas_map :: proc (use atlas: ^Atlas, id: i32, sprite: AtlasSprite) {
     i32map_put(^map, id, sprite);
 }
 
-atlas_lookup :: proc (use atlas: ^Atlas, id: i32) -> AtlasSprite {
+atlas_lookup :: proc (use atlas: ^Atlas, id: i32, offset := 0) -> AtlasSprite {
     sprite := i32map_get(^map, id, AtlasSprite.{});
+
+    if offset != 0 {
+        sprite.x += ~~offset * sprite.w;
+
+        while sprite.x >= ~~texture.width {
+            sprite.y += sprite.h;
+            sprite.x -= ~~texture.width;
+        }
+    }
+
     sprite.x /= ~~texture.width;
     sprite.y /= ~~texture.width;
     sprite.w /= ~~texture.width;
diff --git a/src/gfx/font.onyx b/src/gfx/font.onyx
new file mode 100644 (file)
index 0000000..f3e4415
--- /dev/null
@@ -0,0 +1,33 @@
+package gfx
+
+use package core
+use package globals
+use package vecmath
+use package main { RenderContext, draw_textured_rect }
+
+#private_file
+font_chars :: "0123456789!\"#$% ABCDEFGHIJKLMNOPQRSTUVWXYZ'()*+,abcdefghijklmnopqrstuvwxyz,-./\\,^:;<=>?[]~|_";
+
+#private_file
+char_to_tex_offset :: proc (ch: u8) -> u32 {
+       loc := 0;
+       for c: font_chars {
+               if c == ch do break;
+               loc += 1;
+       }
+       return loc;
+}
+
+draw_text :: proc (renderer: ^RenderContext, s: string, pos: V2f, scale := 1.0f) {
+       x := pos.x;
+       y := pos.y;
+
+       font_size := scale * 16.0f;
+
+       for ch: s {
+               char_sprite := atlas_lookup(^atlas, 2, char_to_tex_offset(ch));
+               draw_textured_rect(renderer, x, y, font_size, font_size, char_sprite.x, char_sprite.y, char_sprite.w, char_sprite.h);
+
+               x += font_size * 0.85f;
+       }       
+}
\ No newline at end of file
index ba86a0f96ac92d4bde27aa50aa296d21f9708af7..00ea2ee38e29f825d22eaf1d46b611e9635c8a19 100644 (file)
@@ -64,29 +64,29 @@ process_event :: proc (use state: ^InputState, ev: ^event.Event) {
         }
 
         case MouseDown {
-            mouse.dx += (ev.mouse.pos_x - half_window_width) - mouse.x;
-            mouse.dy += (ev.mouse.pos_y - half_window_height) - mouse.y;
-            mouse.x = ev.mouse.pos_x - half_window_width;
-            mouse.y = ev.mouse.pos_y - half_window_height;
+            mouse.dx += ev.mouse.pos_x - mouse.x;
+            mouse.dy += ev.mouse.pos_y - mouse.y;
+            mouse.x = ev.mouse.pos_x;
+            mouse.y = ev.mouse.pos_y;
 
             mouse.buttons_just_down[cast(u32) ev.mouse.button] = !mouse.buttons_down[cast(u32) ev.mouse.button];
             mouse.buttons_down[cast(u32) ev.mouse.button] = true;
         }
 
         case MouseUp {
-            mouse.dx += (ev.mouse.pos_x - half_window_width) - mouse.x;
-            mouse.dy += (ev.mouse.pos_y - half_window_height) - mouse.y;
-            mouse.x = ev.mouse.pos_x - half_window_width;
-            mouse.y = ev.mouse.pos_y - half_window_height;
+            mouse.dx += ev.mouse.pos_x - mouse.x;
+            mouse.dy += ev.mouse.pos_y - mouse.y;
+            mouse.x = ev.mouse.pos_x;
+            mouse.y = ev.mouse.pos_y;
             mouse.buttons_down[cast(u32) ev.mouse.button] = false;
             mouse.buttons_just_down[cast(u32) ev.mouse.button] = false;
         }
 
         case MouseMove {
-            mouse.dx += (ev.mouse.pos_x - half_window_width) - mouse.x;
-            mouse.dy += (ev.mouse.pos_y - half_window_height) - mouse.y;
-            mouse.x = ev.mouse.pos_x - half_window_width;
-            mouse.y = ev.mouse.pos_y - half_window_height;
+            mouse.dx += ev.mouse.pos_x - mouse.x;
+            mouse.dy += ev.mouse.pos_y - mouse.y;
+            mouse.x = ev.mouse.pos_x;
+            mouse.y = ev.mouse.pos_y;
         }
 
         case MouseWheel {
index 50166537031bfdf5a6fb901ad27627c6fe0e64f7..af6df419466bbe24c39cd3819c02ab9449f8f3ef 100644 (file)
@@ -8,6 +8,7 @@ package main
 #include_file "gfx/quad_renderer"
 #include_file "gfx/texture"
 #include_file "gfx/atlas"
+#include_file "gfx/font"
 #include_file "events"
 #include_file "input"
 #include_file "font"
@@ -92,7 +93,7 @@ draw_quad :: proc (use rc: ^RenderContext, quad: ^Quad) {
 }
 
 render_context_ui :: proc (use rc: ^RenderContext) {
-    quad_renderer_update_world(quad_renderer, 1.0f, 1.0f, 0.0f, 0.0f);
+    quad_renderer_update_world(quad_renderer, 1.0f, 1.0f, ~~-half_window_width, ~~-half_window_height);
     quad_rebuffer_data(quad_renderer);
     quad_renderer_draw(quad_renderer, curr_quad_idx);
 
@@ -136,17 +137,17 @@ poll_events :: proc () {
 quad_scratch_buffer : [512 * 1024] u8;
 
 simulating := true;
-update :: proc () {
+update :: proc (dt: f32) {
     input.preupdate(^input_state);
     defer input.postupdate(^input_state);
     poll_events();
 
     if input.key_just_down(^input_state, Key.Space) do simulating = !simulating;
 
-    if input.key_down(^input_state, Key.ArrowUp)    do renderer.trans_y += 15.0f / renderer.scale;
-    if input.key_down(^input_state, Key.ArrowDown)  do renderer.trans_y -= 15.0f / renderer.scale;
-    if input.key_down(^input_state, Key.ArrowLeft)  do renderer.trans_x += 15.0f / renderer.scale;
-    if input.key_down(^input_state, Key.ArrowRight) do renderer.trans_x -= 15.0f / renderer.scale;
+    if input.key_down(^input_state, Key.ArrowUp)    do renderer.trans_y += (500.0f / renderer.scale) * dt;
+    if input.key_down(^input_state, Key.ArrowDown)  do renderer.trans_y -= (500.0f / renderer.scale) * dt;
+    if input.key_down(^input_state, Key.ArrowLeft)  do renderer.trans_x += (500.0f / renderer.scale) * dt;
+    if input.key_down(^input_state, Key.ArrowRight) do renderer.trans_x -= (500.0f / renderer.scale) * dt;
 
     if input_state.mouse.wheel_ups > ~~0   do renderer.scale *= 1.125f;
     if input_state.mouse.wheel_downs > ~~0 do renderer.scale /= 1.125f;
@@ -165,10 +166,10 @@ update :: proc () {
         quadtree_init(^dude_tree, AABB.{ ~~-half_window_width, ~~-half_window_height, ~~window_width, ~~window_height });
         for ^d: dudes do quadtree_insert(^dude_tree, d, quad_alloc);
 
-        for ^d: dudes do dude_update(d, ^dude_tree);
+        for ^d: dudes do dude_update(d, dt, ^dude_tree);
         for ^d: dudes {
-            if d.can_move_x do d.pos.x += d.vel.x;
-            if d.can_move_y do d.pos.y += d.vel.y;
+            if d.can_move_x do d.pos.x += d.vel.x * dt;
+            if d.can_move_y do d.pos.y += d.vel.y * dt;
 
             if !d.can_move_x && !d.can_move_y do d.couldnt_move_count += ~~1;
         }
@@ -204,6 +205,12 @@ draw :: proc () {
     // UI Rendering
     renderer.color = Color4f32.{ 0.0f, 0.0f, 0.0f, 1.0f };
     draw_rect(^renderer, ~~input_state.mouse.x, ~~input_state.mouse.y, 10f, 10f);
+
+    renderer.color = Color4f32.{ 1.0f, 1.0f, 1.0f, 1.0f };
+    draw_rect(^renderer, 10.0f, 10.0f, 500.0f, 50.0f);
+
+    renderer.color = Color4f32.{ 0.0f, 0.0f, 0.0f, 1.0f };
+    draw_text(^renderer, "Hello. Test(12486),$!  0AaQq:", V2f.{ 10.0f, 10.0f }, 1.0f);
     render_context_ui(^renderer);
 }
 
@@ -211,8 +218,19 @@ draw :: proc () {
 debugger :: proc () #foreign "dummy" "breakable" ---
 
 // This procedure is called asynchronously from JS every frame.
-new_frame_callback :: proc () #export {
-    update();
+
+// @CLEANUP: Add local persistant variables so this can go in the loop() body.
+last_time := 0;
+
+loop :: proc () #export {
+    time_now :: proc () -> u32 #foreign "time" "now" ---;
+
+    curr_time := time_now();
+    if last_time == 0 do last_time = curr_time;
+    delta := curr_time - last_time;
+    last_time = curr_time;
+
+    update(~~delta / 1000.0f);
     draw();
 }
 
@@ -237,6 +255,8 @@ main :: proc (args: [] cstring) {
     atlas_map(^atlas, 0, AtlasSprite.{ x = 16.0f, y = 0.0f, w = 16.0f, h = 16.0f });
     atlas_map(^atlas, 1, AtlasSprite.{ x = 32.0f, y = 0.0f, w = 16.0f, h = 16.0f });
 
+    atlas_map(^atlas, 2, AtlasSprite.{ x = 0.0f, y = 160.0f, w = 16.0f, h = 16.0f });
+
     event.init();
     input.init(^input_state);
 
@@ -245,7 +265,7 @@ main :: proc (args: [] cstring) {
     Dude_Color_Table[2] = Color4f32.{ 0.2f, 0.2f, 1.0f, 1.0f };
 
     array_init(^dudes);
-    for i: 0 .. 500 {
+    for i: 0 .. 1000 {
         array_push(^dudes, dude_create_random());
     }
 
@@ -278,32 +298,31 @@ dude_create_random :: proc () -> Dude {
     };
 }
 
-dude_update :: proc (use dude: ^Dude, other_dudes: ^QuadTree(Dude)) {
+dude_update :: proc (use dude: ^Dude, dt: f32, other_dudes: ^QuadTree(Dude)) {
     if random_between(0, 100) < 1 || couldnt_move_count >= ~~2 {
-        vel.x = random_float(-3.0f, 3.0f);
-        vel.y = random_float(-3.0f, 3.0f);
+        vel.x = random_float(-300.0f, 300.0f);
+        vel.y = random_float(-300.0f, 300.0f);
         couldnt_move_count = ~~0;
     }
 
+    dude_try_move(dude, dt, other_dudes);
+
     if tile := tilemap_screen_coord_to_tile(^tilemap, pos); tile != null {
         target_r := cast(u8) cast(u32) (color.r * 255.0f);
         target_g := cast(u8) cast(u32) (color.g * 255.0f);
         target_b := cast(u8) cast(u32) (color.b * 255.0f);
 
-        diff_r := cast(i32) (target_r - tile.r) >>> 2;
-        diff_g := cast(i32) (target_g - tile.g) >>> 2;
-        diff_b := cast(i32) (target_b - tile.b) >>> 2;
+        diff_r := (cast(i32) target_r - cast(i32) tile.r) >>> 2;
+        diff_g := (cast(i32) target_g - cast(i32) tile.g) >>> 2;
+        diff_b := (cast(i32) target_b - cast(i32) tile.b) >>> 2;
 
         tile.r += ~~diff_r;
         tile.g += ~~diff_g;
         tile.b += ~~diff_b;
     }
-
-
-    dude_try_move(dude, other_dudes);
 }
 
-dude_try_move :: proc (use dude: ^Dude, other_dudes: ^QuadTree(Dude)) {
+dude_try_move :: proc (use dude: ^Dude, dt: f32, other_dudes: ^QuadTree(Dude)) {
     old_pos := pos;
 
     potential_dudes : [..] ^Dude;
@@ -315,7 +334,7 @@ dude_try_move :: proc (use dude: ^Dude, other_dudes: ^QuadTree(Dude)) {
 
     collided := false;
 
-    pos.x += vel.x;
+    pos.x += vel.x * dt;
     if pos.x - size < ~~-half_window_width || pos.x + size >= ~~half_window_width do collided = true;
 
     dude_aabb := dude_get_aabb(dude);
@@ -328,11 +347,11 @@ dude_try_move :: proc (use dude: ^Dude, other_dudes: ^QuadTree(Dude)) {
     }
 
     can_move_x = !collided;
-    pos.x -= vel.x;
+    pos.x -= vel.x * dt;
 
     collided = false;
 
-    pos.y += vel.y;
+    pos.y += vel.y * dt;
     if pos.y - size < ~~-half_window_height || pos.y + size >= ~~half_window_height do collided = true;
 
     dude_aabb = dude_get_aabb(dude);
@@ -345,7 +364,7 @@ dude_try_move :: proc (use dude: ^Dude, other_dudes: ^QuadTree(Dude)) {
     }
 
     can_move_y = !collided;
-    pos.y -= vel.y;
+    pos.y -= vel.y * dt;
 }
 
 dude_get_aabb :: proc (use dude: ^Dude) -> AABB {
index 58b3550ce9b7df1fb521db49bc9cbbfc3c5e2cf7..7c3f72dbce50c0b9bd3e2be1d22ffe0dc1b0fac0 100644 (file)
@@ -2,6 +2,7 @@ package quad_tree
 
 use package core
 use package aabb
+use package vecmath
 
 #private_file
 QUAD_TREE_MAX_POINTS :: 4;
@@ -60,7 +61,7 @@ quadtree_subdivide :: proc (use qt: ^QuadTree($T), a: Allocator) {
 }
 
 quadtree_insert :: proc (use qt: ^QuadTree($T), t: ^T, alloc := context.allocator) -> bool {
-    pos := t.pos; // T is expected to have a 'pos' element.
+    pos: V2f = t.pos; // T is expected to have a 'pos' element.
 
     if !aabb_contains(region, pos) do return false;
 
index 59ab7ef2af591e6c15c36a338c2f9bc5561b5971..2ab7cd2fecb260e132766b07d3b6dd935bfd20e3 100644 (file)
@@ -13,6 +13,11 @@ void main() {
     if (v_tex_pos.x < 0.0) {
         fragColor = v_col;
     } else {
-        fragColor = v_col * texture(tex, v_tex_pos);
+       vec4 t_col = texture(tex, v_tex_pos);
+       if (t_col == vec4(1.0, 0.0, 1.0, 1.0)) {
+               fragColor = vec4(0.0, 0.0, 0.0, 0.0);   
+       } else {
+               fragColor = v_col * texture(tex, v_tex_pos);
+           }
     }
 }
diff --git a/tags b/tags
index 4dd576e18f6ee294f7b104f73268cdcacb35135e..92e0848344054cb2e49dadf605654f7312f36022 100644 (file)
--- a/tags
+++ b/tags
@@ -674,7 +674,7 @@ array_to_slice      /usr/share/onyx/core/array.onyx /^array_to_slice :: proc (arr: ^[
 atlas  src/globals.onyx        /^atlas         : Atlas$/
 atlas_apply_to_quad    src/gfx/atlas.onyx      /^atlas_apply_to_quad :: proc (use atlas: ^Atlas, id: i32, quad: ^Quad) {$/
 atlas_create   src/gfx/atlas.onyx      /^atlas_create :: proc (tex: ^Texture) -> Atlas {$/
-atlas_lookup   src/gfx/atlas.onyx      /^atlas_lookup :: proc (use atlas: ^Atlas, id: i32) -> AtlasSprite {$/
+atlas_lookup   src/gfx/atlas.onyx      /^atlas_lookup :: proc (use atlas: ^Atlas, id: i32, offset := 0) -> AtlasSprite {$/
 atlas_map      src/gfx/atlas.onyx      /^atlas_map :: proc (use atlas: ^Atlas, id: i32, sprite: AtlasSprite) {$/
 attachShader   /usr/share/onyx/core/js/webgl.onyx      /^attachShader                   :: proc (program: GLProgram, shader: GLShader) -> GLProgram #foreign "gl" "attachShader" ---$/
 bezier_curve   src/font.onyx   /^bezier_curve :: proc (t: f32, ps: [] V2f) -> V2f {$/
@@ -755,12 +755,13 @@ drawElements      /usr/share/onyx/core/js/webgl.onyx      /^drawElements
 drawElementsInstanced  /usr/share/onyx/core/js/webgl.onyx      /^drawElementsInstanced          :: proc (mode: GLenum, count: GLsizei, type: GLenum, offset: GLint, instanceCount: GLsizei) #foreign "gl" "drawElementsInstanced" ---$/
 draw_quad      src/main.onyx   /^draw_quad :: proc (use rc: ^RenderContext, quad: ^Quad) {$/
 draw_rect      src/main.onyx   /^draw_rect :: proc (use rc: ^RenderContext, x: f32, y: f32, w: f32, h: f32) {$/
+draw_text      src/gfx/font.onyx       /^draw_text :: proc (renderer: ^RenderContext, s: string, pos: V2f, scale := 1.0f) {$/
 draw_textured_rect     src/main.onyx   /^draw_textured_rect :: proc (use rc: ^RenderContext, x: f32, y: f32, w: f32, h: f32, tx: f32, ty: f32, tw: f32, th: f32) {$/
 dude_create_random     src/main.onyx   /^dude_create_random :: proc () -> Dude {$/
 dude_get_aabb  src/main.onyx   /^dude_get_aabb :: proc (use dude: ^Dude) -> AABB {$/
 dude_tree      src/globals.onyx        /^dude_tree     : QuadTree(Dude)$/
-dude_try_move  src/main.onyx   /^dude_try_move :: proc (use dude: ^Dude, other_dudes: ^QuadTree(Dude)) {$/
-dude_update    src/main.onyx   /^dude_update :: proc (use dude: ^Dude, other_dudes: ^QuadTree(Dude)) {$/
+dude_try_move  src/main.onyx   /^dude_try_move :: proc (use dude: ^Dude, dt: f32, other_dudes: ^QuadTree(Dude)) {$/
+dude_update    src/main.onyx   /^dude_update :: proc (use dude: ^Dude, dt: f32, other_dudes: ^QuadTree(Dude)) {$/
 dudes  src/globals.onyx        /^dudes         : [..] Dude$/
 enable /usr/share/onyx/core/js/webgl.onyx      /^enable                         :: proc (cap: GLenum) #foreign "gl" "enable" ---$/
 enableVertexAttribArray        /usr/share/onyx/core/js/webgl.onyx      /^enableVertexAttribArray        :: proc (index: GLuint) #foreign "gl" "enableVertexAttribArray" ---$/
@@ -815,10 +816,12 @@ is_inside_polygon src/vecmath.onyx        /^is_inside_polygon :: proc (polygon: [] V2($
 key_down       src/input.onyx  /^key_down :: proc (use state: ^InputState, key: Key) -> bool {$/
 key_just_down  src/input.onyx  /^key_just_down :: proc (use state: ^InputState, key: Key) -> bool {$/
 key_up src/input.onyx  /^key_up :: proc (use state: ^InputState, key: Key) -> bool {$/
+last_time      src/main.onyx   /^last_time := 0;$/
 lineWidth      /usr/share/onyx/core/js/webgl.onyx      /^lineWidth                      :: proc (width: GLfloat) #foreign "gl" "lineWidth" ---$/
 lines_intersect        src/vecmath.onyx        /^lines_intersect :: proc (p1: V2($T), q1: V2(T), p2: V2(T), q2: V2(T)) -> bool {$/
 linkProgram    /usr/share/onyx/core/js/webgl.onyx      /^linkProgram                    :: proc (program: GLProgram) #foreign "gl" "linkProgram" ---$/
 link_program   src/utils/gl.onyx       /^link_program :: proc (vertex_shader: gl.GLShader, frag_shader: gl.GLShader) -> gl.GLProgram {$/
+loop   src/main.onyx   /^loop :: proc () #export {$/
 main   src/main.onyx   /^main :: proc (args: [] cstring) {$/
 max_f32        /usr/share/onyx/core/intrinsics.onyx    /^max_f32      :: proc (lhs: f32, rhs: f32) -> f32 #intrinsic ---$/
 max_f64        /usr/share/onyx/core/intrinsics.onyx    /^max_f64      :: proc (lhs: f64, rhs: f64) -> f64 #intrinsic ---$/
@@ -833,7 +836,6 @@ min_f64     /usr/share/onyx/core/intrinsics.onyx    /^min_f64      :: proc (lhs: f64, r
 min_poly       /usr/share/onyx/core/math.onyx  /^min_poly :: proc (a: $T, b: T) -> T {$/
 nearest_f32    /usr/share/onyx/core/intrinsics.onyx    /^nearest_f32  :: proc (val: f32) -> f32 #intrinsic ---$/
 nearest_f64    /usr/share/onyx/core/intrinsics.onyx    /^nearest_f64  :: proc (val: f64) -> f64 #intrinsic ---$/
-new_frame_callback     src/main.onyx   /^new_frame_callback :: proc () #export {$/
 null   /usr/share/onyx/core/builtin.onyx       /^null :: cast(rawptr) 0;$/
 or_i32 /usr/share/onyx/core/intrinsics.onyx    /^or_i32       :: proc (lhs: i32, rhs: i32) -> i32 #intrinsic ---$/
 or_i64 /usr/share/onyx/core/intrinsics.onyx    /^or_i64       :: proc (lhs: i64, rhs: i64) -> i64 #intrinsic ---$/
@@ -993,7 +995,7 @@ uniform4i   /usr/share/onyx/core/js/webgl.onyx      /^uniform4i                      ::
 uniformMatrix2 /usr/share/onyx/core/js/webgl.onyx      /^uniformMatrix2                 :: proc (loc: GLUniformLocation, transpose: GLboolean, value: GLMat2) #foreign "gl" "uniformMatrix2" ---$/
 uniformMatrix3 /usr/share/onyx/core/js/webgl.onyx      /^uniformMatrix3                 :: proc (loc: GLUniformLocation, transpose: GLboolean, value: GLMat3) #foreign "gl" "uniformMatrix3" ---$/
 uniformMatrix4 /usr/share/onyx/core/js/webgl.onyx      /^uniformMatrix4                 :: proc (loc: GLUniformLocation, transpose: GLboolean, value: GLMat4) #foreign "gl" "uniformMatrix4" ---$/
-update src/main.onyx   /^update :: proc () {$/
+update src/main.onyx   /^update :: proc (dt: f32) {$/
 useProgram     /usr/share/onyx/core/js/webgl.onyx      /^useProgram                     :: proc (program: GLProgram) #foreign "gl" "useProgram" ---$/
 v2_add src/vecmath.onyx        /^v2_add :: proc (a: V2($T), b: V2(T)) -> V2(T) {$/
 v2_equal       src/vecmath.onyx        /^v2_equal :: proc (a: V2($T), b: V2(T)) -> bool {$/