added graphics transform and state
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 3 Nov 2021 15:07:15 +0000 (10:07 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 3 Nov 2021 15:07:15 +0000 (10:07 -0500)
include/gfx.h
include/utils.h
misc/onyx/heartbreak_graphics.onyx
src/gfx.c
src/heartbreak_graphics.c
src/utils.c
tests/simp.onyx

index 2585c1ae70f96e9167ce117fa0bcebb75d827ad7..e5617045e4d1f3bd6605240715f54723d0f32b34 100644 (file)
@@ -3,6 +3,7 @@
 
 
 #include "bh.h"
+#include "utils.h"
 #include <GLES3/gl3.h>
 #include <GLFW/glfw3.h>
 
@@ -43,6 +44,8 @@ typedef struct ImmediateRenderer {
     GLint vertex_buffer;
 
     Shader simple_shader;
+
+    bh_arr(mat3) world_transforms;
 } ImmediateRenderer;
 
 void gfx_immediate_renderer_init(ImmediateRenderer *ir);
@@ -55,4 +58,12 @@ void gfx_immediate_renderer_push_triangle(ImmediateRenderer *ir,
     f32 x3, f32 y3, f32 u3, f32 v3);
 void gfx_immediate_renderer_update_window_size(ImmediateRenderer *ir, f32 width, f32 height);
 
+void gfx_immediate_renderer_push_state(ImmediateRenderer *ir);
+void gfx_immediate_renderer_pop_state(ImmediateRenderer *ir);
+void gfx_immediate_renderer_origin(ImmediateRenderer *ir);
+void gfx_immediate_renderer_translate(ImmediateRenderer *ir, f32 dx, f32 dy);
+void gfx_immediate_renderer_scale(ImmediateRenderer *ir, f32 sx, f32 sy);
+void gfx_immediate_renderer_rotate(ImmediateRenderer *ir, f32 angle);
+void gfx_immediate_renderer_shear(ImmediateRenderer *ir, f32 kx, f32 ky);
+
 #endif
\ No newline at end of file
index c0734e1db47170d9b5124cf7f9fabd1d10526109..2efe859f747ec6cc87496bd459be69c6c2492812 100644 (file)
@@ -19,4 +19,12 @@ b32 wasm_name_equals_string(const wasm_name_t* name1, const char* name2);
 wasm_extern_t* wasm_extern_lookup_by_name(wasm_module_t* module, wasm_instance_t* instance, const char* name);
 
 
+// Linear algebra
+typedef struct mat3 {
+    f32 data[9];
+} mat3;
+
+void mat3_identity(mat3 *out);
+void mat3_mul(mat3 *out, mat3 a, mat3 b);
+
 #endif
\ No newline at end of file
index ea9de1c98d3d31d5366148af345ca850c9fa759f..2d9c6471438d6b0cf00e81a0b0a2af9de63679f2 100644 (file)
@@ -1,13 +1,31 @@
 package heartbreak.graphics
 
+// State
+
 setClearColor :: (r, g, b: f32, a: f32 = 1) -> void #foreign "heartbreak" "graphics_set_clear_color" ---
-clear         :: () -> void #foreign "heartbreak" "graphics_clear" ---
+setColor  :: (r, g, b: f32, a: f32 = 1) -> void        #foreign "heartbreak" "graphics_set_color" ---
+
+//
+// Drawing
+//
 
 FillMode :: enum {
     Fill :: 0x01;
     Line :: 0x02;
 }
 
-setColor  :: (r, g, b: f32, a: f32 = 1) -> void        #foreign "heartbreak" "graphics_set_color" ---
+clear         :: () -> void #foreign "heartbreak" "graphics_clear" ---
 rectangle :: (mode: FillMode, x, y, w, h: f32) -> void #foreign "heartbreak" "graphics_rectangle" ---
-circle    :: (mode: FillMode, x, y, r: f32, segments: i32 = 24) -> void #foreign "heartbreak" "graphics_circle" ---
\ No newline at end of file
+circle    :: (mode: FillMode, x, y, r: f32, segments: i32 = 24) -> void #foreign "heartbreak" "graphics_circle" ---
+ellipse   :: (mode: FillMode, x, y, r1, r2: f32, segments: i32 = 24) -> void #foreign "heartbreak" "graphics_ellipse" ---
+
+//
+// Transforms
+//
+origin :: () -> void #foreign "heartbreak" "graphics_origin" ---
+translate :: (x, y: f32) -> void #foreign "heartbreak" "graphics_translate" ---
+scale :: (x, y: f32) -> void #foreign "heartbreak" "graphics_scale" ---
+shear :: (x, y: f32) -> void #foreign "heartbreak" "graphics_shear" ---
+rotate :: (angle: f32) -> void #foreign "heartbreak" "graphics_rotate" ---
+push :: () -> void #foreign "heartbreak" "graphics_push" ---
+pop  :: () -> void #foreign "heartbreak" "graphics_pop" ---
index f81ffa70da2ed8ae27bce6c3ce259444b4f07f84..581b7b6d2cdb90e31eb12697702fbaf2fc0d83e2 100644 (file)
--- a/src/gfx.c
+++ b/src/gfx.c
@@ -1,5 +1,5 @@
 #include "gfx.h"
-#include "utils.h"
+#include <math.h>
 
 static const char* SIMPLE_SHADER_VERTEX = "#version 300 es\n"
 "precision mediump float;\n"
@@ -8,13 +8,13 @@ static const char* SIMPLE_SHADER_VERTEX = "#version 300 es\n"
 "layout(location = 2) in vec4 a_color;\n"
 "\n"
 "uniform mat4 u_view;\n"
-"uniform mat4 u_world;\n"
+"uniform mat3 u_world;\n"
 "\n"
 "out vec4 v_color;\n"
 "out vec2 v_texture;\n"
 "\n"
 "void main() {\n"
-"   gl_Position = u_view * u_world * vec4(a_vertex, 0, 1);\n"
+"   gl_Position = u_view * vec4((u_world * vec3(a_vertex, 1)).xy, 0, 1);\n"
 "   v_color = a_color;\n"
 "   v_texture = a_texture;\n"
 "}";
@@ -94,6 +94,10 @@ Shader gfx_shader_make_from_source(const char *vertex_src, const char *fragment_
     return shader;
 }
 
+static void gfx_immediate_renderer_update_world_transform(ImmediateRenderer *ir) {
+    glUniformMatrix3fv(ir->simple_shader.world_uniform, 1, 0, bh_arr_last(ir->world_transforms).data);
+}
+
 void gfx_immediate_renderer_init(ImmediateRenderer *ir) {
     ir->vertex_allocator = bh_heap_allocator();
     ir->vertex_data      = bh_alloc(ir->vertex_allocator, sizeof(ImmediateVertex) * VERTEX_DATA_MAX_COUNT);
@@ -129,17 +133,26 @@ void gfx_immediate_renderer_init(ImmediateRenderer *ir) {
     };
 
     glUniformMatrix4fv(ir->simple_shader.view_uniform,  1, 0, identity_matrix);
-    glUniformMatrix4fv(ir->simple_shader.world_uniform, 1, 0, identity_matrix);
+
+    mat3 world_mat;
+    mat3_identity(&world_mat);
+    bh_arr_new(bh_heap_allocator(), ir->world_transforms, 4);
+    bh_arr_push(ir->world_transforms, world_mat);
+    gfx_immediate_renderer_update_world_transform(ir);
 
     ir->current_color = (ImmediateColor) { 1, 1, 1, 1 };
 }
 
 void gfx_immediate_renderer_flush(ImmediateRenderer *ir) {
+    if (ir->vertex_count == 0) return;
+
     glBindBuffer(GL_ARRAY_BUFFER, ir->vertex_buffer);
     glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(ImmediateVertex) * ir->vertex_count, ir->vertex_data);
     glBindBuffer(GL_ARRAY_BUFFER, -1);
 
     glUseProgram(ir->simple_shader.program);
+    gfx_immediate_renderer_update_world_transform(ir);
+
     glBindVertexArray(ir->vertex_array);
     glDrawArrays(GL_TRIANGLES, 0, ir->vertex_count);
     glBindVertexArray(-1);
@@ -189,4 +202,88 @@ void gfx_immediate_renderer_update_window_size(ImmediateRenderer *ir, f32 width,
 
     glViewport(0, 0, width, height);
     glUniformMatrix4fv(ir->simple_shader.view_uniform, 1, false, camera_matrix);
+}
+
+void gfx_immediate_renderer_push_state(ImmediateRenderer *ir) {
+    gfx_immediate_renderer_flush(ir);
+    mat3 new_top = bh_arr_last(ir->world_transforms);
+    bh_arr_push(ir->world_transforms, new_top);
+}
+
+void gfx_immediate_renderer_pop_state(ImmediateRenderer *ir) {
+    gfx_immediate_renderer_flush(ir);
+    bh_arr_pop(ir->world_transforms);
+}
+
+void gfx_immediate_renderer_origin(ImmediateRenderer *ir) {
+    gfx_immediate_renderer_flush(ir);
+    mat3* top = &bh_arr_last(ir->world_transforms);
+    mat3_identity(top);
+}
+
+void gfx_immediate_renderer_translate(ImmediateRenderer *ir, f32 dx, f32 dy) {
+    gfx_immediate_renderer_flush(ir);
+
+    mat3 trans = (mat3) {
+        { 1, 0, 0,
+          0, 1, 0,
+          dx, dy, 1,
+        }
+    };
+
+    mat3 new_top;
+    mat3_mul(&new_top, bh_arr_last(ir->world_transforms), trans);
+
+    bh_arr_last(ir->world_transforms) = new_top;
+}
+
+void gfx_immediate_renderer_scale(ImmediateRenderer *ir, f32 sx, f32 sy) {
+    gfx_immediate_renderer_flush(ir);
+
+    mat3 scale = (mat3) {
+        { sx, 0, 0,
+          0, sy, 0,
+          0, 0, 1,
+        }
+    };
+
+    mat3 new_top;
+    mat3_mul(&new_top, bh_arr_last(ir->world_transforms), scale);
+
+    bh_arr_last(ir->world_transforms) = new_top;
+}
+
+void gfx_immediate_renderer_rotate(ImmediateRenderer *ir, f32 angle) {
+    gfx_immediate_renderer_flush(ir);
+
+    f32 cangle = cos(angle);
+    f32 sangle = sin(angle);
+
+    mat3 rotate = (mat3) {
+        { cangle, -sangle, 0,
+          sangle, cangle, 0,
+          0, 0, 1,
+        }
+    };
+
+    mat3 new_top;
+    mat3_mul(&new_top, bh_arr_last(ir->world_transforms), rotate);
+
+    bh_arr_last(ir->world_transforms) = new_top;
+}
+
+void gfx_immediate_renderer_shear(ImmediateRenderer *ir, f32 kx, f32 ky) {
+    gfx_immediate_renderer_flush(ir);
+
+    mat3 shear = (mat3) {
+        { 1, ky, 0,
+          kx, 1, 0,
+          0, 0, 1,
+        }
+    };
+
+    mat3 new_top;
+    mat3_mul(&new_top, bh_arr_last(ir->world_transforms), shear);
+
+    bh_arr_last(ir->world_transforms) = new_top;
 }
\ No newline at end of file
index 449a6668d1e5236b3cf39c3cecf895640e642dc0..5c327ada902991ee0cd0869a25a1958ea8497c19 100644 (file)
@@ -82,12 +82,87 @@ HEARTBREAK_DEF(circle, (WASM_I32, WASM_F32, WASM_F32, WASM_F32, WASM_I32), ()) {
     return NULL;
 }
 
+HEARTBREAK_DEF(ellipse, (WASM_I32, WASM_F32, WASM_F32, WASM_F32, WASM_F32, WASM_I32), ()) {
+    // @TODO: Use fill mode
+
+    f32 x = params->data[1].of.f32;
+    f32 y = params->data[2].of.f32;
+    f32 r1 = params->data[3].of.f32;
+    f32 r2 = params->data[4].of.f32;
+    i32 segments = params->data[5].of.i32;
+
+    f32 cx1 = x + r1;
+    f32 cy1 = y;
+    f32 cx2 = x + cos(PI * 2 / segments) * r1;
+    f32 cy2 = y + sin(PI * 2 / segments) * r2;
+    f32 cx3, cy3;
+
+    fori (i, 2, segments) {
+        cx3 = x + cos(i * PI * 2 / segments) * r1;
+        cy3 = y + sin(i * PI * 2 / segments) * r2;
+
+        gfx_immediate_renderer_push_triangle(&renderer,
+            cx1, cy1, 0, 0,
+            cx2, cy2, 0, 0,
+            cx3, cy3, 0, 0);
+
+        cx2 = cx3;
+        cy2 = cy3;
+    }
+
+    return NULL;
+}
+
+HEARTBREAK_DEF(origin, (), ()) {
+    gfx_immediate_renderer_origin(&renderer);
+    return NULL;
+}
+
+HEARTBREAK_DEF(translate, (WASM_F32, WASM_F32), ()) {
+    gfx_immediate_renderer_translate(&renderer, params->data[0].of.f32, params->data[1].of.f32);
+    return NULL;
+}
+
+HEARTBREAK_DEF(scale, (WASM_F32, WASM_F32), ()) {
+    gfx_immediate_renderer_scale(&renderer, params->data[0].of.f32, params->data[1].of.f32);
+    return NULL;
+}
+
+HEARTBREAK_DEF(shear, (WASM_F32, WASM_F32), ()) {
+    gfx_immediate_renderer_shear(&renderer, params->data[0].of.f32, params->data[1].of.f32);
+    return NULL;
+}
+
+HEARTBREAK_DEF(rotate, (WASM_F32), ()) {
+    gfx_immediate_renderer_rotate(&renderer, params->data[0].of.f32);
+    return NULL;
+}
+
+HEARTBREAK_DEF(push, (), ()) {
+    gfx_immediate_renderer_push_state(&renderer);
+    return NULL;
+}
+
+HEARTBREAK_DEF(pop, (), ()) {
+    gfx_immediate_renderer_pop_state(&renderer);
+    return NULL;
+}
+
 HEARTBREAK_MODULE {
     HEARTBREAK_FUNC(set_clear_color)
     HEARTBREAK_FUNC(clear)
     HEARTBREAK_FUNC(set_color)
     HEARTBREAK_FUNC(rectangle)
     HEARTBREAK_FUNC(circle)
+    HEARTBREAK_FUNC(ellipse)
+
+    HEARTBREAK_FUNC(origin)
+    HEARTBREAK_FUNC(translate)
+    HEARTBREAK_FUNC(scale)
+    HEARTBREAK_FUNC(shear)
+    HEARTBREAK_FUNC(rotate)
+    HEARTBREAK_FUNC(push)
+    HEARTBREAK_FUNC(pop)
 
     { NULL }
 };
\ No newline at end of file
index 5647b481021ba6bb2aaf14c72490301135852b28..b7b397d705bf06d8cb269856653a238f0556fa88 100644 (file)
@@ -39,3 +39,24 @@ wasm_extern_t* wasm_extern_lookup_by_name(wasm_module_t* module, wasm_instance_t
     return exports.data[idx];
 }
 
+//
+// Linear Algebra
+//
+void mat3_identity(mat3 *out) {
+    fori (i, 0, 9) out->data[i] = 0;
+    out->data[0] = 1;
+    out->data[1 * 3 + 1] = 1;
+    out->data[2 * 3 + 2] = 1;
+}
+
+void mat3_mul(mat3 *out, mat3 a, mat3 b) {
+    fori (r, 0, 3) {
+        fori (c, 0, 3) {
+            out->data[r * 3 + c] = 0;
+
+            fori (k, 0, 3) {
+                out->data[r * 3 + c] += a.data[r * 3 + k] * b.data[k * 3 + c];
+            }
+        }
+    }
+}
\ No newline at end of file
index 9e9ea347779b0bb8de45f89f7ba6a3a6c224e876..4d2894a7455606456f847ade5b6d41adbe19d6b7 100644 (file)
@@ -33,8 +33,13 @@ draw :: () {
     hb.graphics.rectangle(.Fill, 100, 100, 200, 100);
 
     mx, my := hb.input.mouseGetPos();
+    hb.graphics.push();
+    hb.graphics.origin();
+    hb.graphics.rotate(t);
+    hb.graphics.translate(~~ mx, ~~ my);
     hb.graphics.setColor(1, 0, 0);
-    hb.graphics.circle(.Fill, ~~mx, ~~my, 25);
+    hb.graphics.ellipse(.Fill, 0, 0, 25, 15 + 10 * math.sin(t));
+    hb.graphics.pop();
 
     w := cast(f32) hb.window.getWidth();
     h := cast(f32) hb.window.getHeight();