From: Brendan Hansen Date: Wed, 26 Oct 2022 16:31:58 +0000 (-0500) Subject: began refactoring graphics out of this project X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=aa4349df91fc34567725bb16b5aacc322ba6a4da;p=bar-game.git began refactoring graphics out of this project --- diff --git a/.vscode/launch.json b/.vscode/launch.json index ca18c20..66ac8cd 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,10 +12,10 @@ "type": "onyx", "request": "launch", "name": "Launch", - "wasmFile": "game.wasm", + "onyxFiles": ["../src/build"], "workingDir": "${workspaceFolder}/run_tree", - "stopOnEntry": true, - "preLaunchTask": "Build debug game" + "stopOnEntry": true + // "preLaunchTask": "Build debug game" } ] } diff --git a/run_tree/run.sh b/run_tree/run.sh index e9050d4..27fb7ad 100755 --- a/run_tree/run.sh +++ b/run_tree/run.sh @@ -6,4 +6,4 @@ case "$1" in run) onyx-run $dest ;; debug) onyx-run --debug $dest ;; *) onyx run -V -I ../src build $@ ;; -esac \ No newline at end of file +esac diff --git a/src/build.onyx b/src/build.onyx index a03806a..9246fe2 100644 --- a/src/build.onyx +++ b/src/build.onyx @@ -17,7 +17,6 @@ MINOR_VERSION :: 1 #load_all "./entity" #load_all "./entity/components" #load_all "./entity/schematics" -#load_all "./gfx" #load_all "./sfx" #load_all "./utils" @@ -26,3 +25,4 @@ MINOR_VERSION :: 1 #load "./../lib/openal/module" #load "./../lib/stb_truetype/module" #load "./../lib/stb_image/module" +#load_all "./ogre" diff --git a/src/entity/components/background.onyx b/src/entity/components/background.onyx index 3bce989..241071c 100644 --- a/src/entity/components/background.onyx +++ b/src/entity/components/background.onyx @@ -1,5 +1,6 @@ use core +use ogre BackgroundComponent :: struct { use component: Component; diff --git a/src/entity/components/collision_mask.onyx b/src/entity/components/collision_mask.onyx index c9d52b5..707e9c7 100644 --- a/src/entity/components/collision_mask.onyx +++ b/src/entity/components/collision_mask.onyx @@ -1,5 +1,6 @@ use core +use ogre CollisionMaskComponent :: struct { use component: Component; @@ -8,12 +9,16 @@ CollisionMaskComponent :: struct { width: i32 = 50; height: i32 = 40; - #tag Entity_Store.Skip, Editor_Hidden + @Entity_Store.Skip @Editor_Hidden mask: [] bool; - #tag Entity_Store.Skip + @Entity_Store.Skip should_render := false; + init :: (use this: ^CollisionMaskComponent) { + mask = make([] bool, width * height); + } + added :: (use this: ^CollisionMaskComponent, entity: ^Entity) { scene->modify_component(entity, RenderComponent) { comp.func = render; @@ -21,21 +26,21 @@ CollisionMaskComponent :: struct { } update :: (use this: ^CollisionMaskComponent, entity: ^Entity, dt: f32) { - if mask.data == null { - memory.alloc_slice(^mask, width * height); - - for y: height { - for x: width { - mask[y * width + x] = false; - area := Rect.{ ~~(x * grid_size), ~~(y * grid_size), ~~grid_size, ~~grid_size }; - - for scene.entities { - if it.flags & .Solid { - if Rect.intersects(Entity.get_rect(it), area) { - mask[y * width + x] = true; - continue continue; - } - } + memory.set(mask.data, 0, width * height * sizeof bool); + + for scene.entities { + if it.flags & .Solid { + r := it->get_rect(); + + g := cast(f32) grid_size; + x0 := cast(i32) math.floor(r.x / g); + y0 := cast(i32) math.floor(r.y / g); + x1 := cast(i32) math.ceil((r.x + r.w) / g); + y1 := cast(i32) math.ceil((r.y + r.h) / g); + + for y: y0 .. y1 { + for x: x0 .. x1 { + mask[y * width + x] = true; } } } diff --git a/src/entity/components/dispenser.onyx b/src/entity/components/dispenser.onyx index ecc60b0..649c825 100644 --- a/src/entity/components/dispenser.onyx +++ b/src/entity/components/dispenser.onyx @@ -1,5 +1,6 @@ use core +use ogre // // Currently, DispenserComponents only dispense Item_Entity's, and no @@ -9,12 +10,12 @@ use core DispenserComponent :: struct { use base: Component; - #tag Editor_Custom_Field.{render_item_picker} + @Editor_Custom_Field.{render_item_picker} item: str; max_timeout := 2.0f; draw_item := true; - #tag Entity_Store.Skip + @Entity_Store.Skip timeout := 0.0f; added :: (use this: ^DispenserComponent, entity: ^Entity) { diff --git a/src/entity/components/entryway.onyx b/src/entity/components/entryway.onyx index c180189..d313e78 100644 --- a/src/entity/components/entryway.onyx +++ b/src/entity/components/entryway.onyx @@ -1,5 +1,6 @@ use core +use ogre EntrywayComponent :: struct { use component: Component; diff --git a/src/entity/components/money.onyx b/src/entity/components/money.onyx index a4e1689..78a2921 100644 --- a/src/entity/components/money.onyx +++ b/src/entity/components/money.onyx @@ -1,5 +1,6 @@ use core +use ogre #local Transaction :: struct { amount: i32; @@ -12,7 +13,7 @@ MoneyComponent :: struct { money: i32 = 100; money_sprite: Sprite; - #tag Editor_Hidden, Entity_Store.Skip + @Editor_Hidden, Entity_Store.Skip transactions: [..] Transaction; added :: (use this: ^MoneyComponent, entity: ^Entity) { diff --git a/src/entity/components/movement.onyx b/src/entity/components/movement.onyx index ae7f97f..81f3816 100644 --- a/src/entity/components/movement.onyx +++ b/src/entity/components/movement.onyx @@ -1,6 +1,7 @@ use core use glfw3 +use ogre Facing :: enum { diff --git a/src/entity/components/patron.onyx b/src/entity/components/patron.onyx index 1879bc6..bc8ac42 100644 --- a/src/entity/components/patron.onyx +++ b/src/entity/components/patron.onyx @@ -1,5 +1,6 @@ use core +use ogre #local Patron_State :: enum { Walking_To_Seat; @@ -21,9 +22,9 @@ PatronComponent :: struct { annoy_timeout: f32; walk_speed: f32 = 100.0f; - #tag Editor_Hidden, Entity_Store.Skip + @Editor_Hidden, Entity_Store.Skip path: [..] Vector2; - #tag Editor_Hidden, Entity_Store.Skip + @Editor_Hidden, Entity_Store.Skip path_pos: i32; init :: (use this: ^PatronComponent) { diff --git a/src/entity/components/player.onyx b/src/entity/components/player.onyx index 450972b..bb99537 100644 --- a/src/entity/components/player.onyx +++ b/src/entity/components/player.onyx @@ -1,13 +1,14 @@ use core use glfw3 +use ogre PlayerComponent :: struct { use base: Component; holding : Entity_ID; - #tag Editor_Hidden, Entity_Store.Skip + @Editor_Hidden, Entity_Store.Skip nearby_holding, nearby_interact : Entity_ID; update :: (player: ^PlayerComponent, use this: ^Entity, dt: f32) { diff --git a/src/entity/editor.onyx b/src/entity/editor.onyx index 69b02b3..dd45a70 100644 --- a/src/entity/editor.onyx +++ b/src/entity/editor.onyx @@ -10,7 +10,9 @@ use core use opengles use glfw3 -#local type_info :: runtime.info +use ogre +use ogre.ui +use runtime {type_info :: info} Editor_Range :: struct {min, max: f32;} Editor_Disabled :: struct {} @@ -66,7 +68,7 @@ editor_update :: (dt: f32) { mouse_raw := mouse_get_position_vector(); mouse_pos := mouse_raw - scene_render_offset; - if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) && mouse_raw.x < ~~window_width - sidebar_width && mouse_raw.x >= 0 { + if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) && mouse_raw.x < ~~window.width - sidebar_width && mouse_raw.x >= 0 { entity: ^Entity; if active_index == 0 { entity = scene->make(); @@ -87,7 +89,7 @@ editor_update :: (dt: f32) { mouse_raw := mouse_get_position_vector(); mouse_pos := mouse_raw - scene_render_offset; - if mouse_raw.x < ~~window_width - sidebar_width && mouse_pos.x >= 0 { + if mouse_raw.x < ~~window.width - sidebar_width && mouse_pos.x >= 0 { if !dragging && !resizing && (is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) || is_button_just_up(GLFW_MOUSE_BUTTON_RIGHT)) { if active_tab == .Edit do active_index = -1; @@ -200,7 +202,7 @@ editor_draw :: () { { // Draw menu bar x := 0.0f; - w := cast(f32) window_width; + w := cast(f32) window.width; h := menubar_height; y := h * (editor_openness - 1); @@ -229,9 +231,9 @@ editor_draw :: () { { // Draw sidebar, if necessary sidebar_width = editor_openness * 400.0f; w := sidebar_width; - x := ~~ window_width - w; + x := ~~ window.width - w; y := 40.0f; - h := ~~ window_height - y; + h := ~~ window.height - y; immediate_set_color(background_color); immediate_rectangle(x, y, w, h); @@ -471,7 +473,7 @@ editor_draw :: () { w := sidebar_width / 2; h := 200.0f; - x := ~~ window_width - sidebar_width; + x := ~~ window.width - sidebar_width; render := render_field_editor; if custom_editors[it.type] != null_proc { diff --git a/src/entity/entities.onyx b/src/entity/entities.onyx index 8ee2960..7891c70 100644 --- a/src/entity/entities.onyx +++ b/src/entity/entities.onyx @@ -1,9 +1,10 @@ use core use glfw3 +use ogre -#tag Entity_Schematic.{ - (scene) => wall_create(scene, .{0,0}, .{0,0}) +@Entity_Schematic.{ + (scene: ^Scene) => wall_create(scene, .{0,0}, .{0,0}) } #local Wall :: struct { render :: (use this: ^Entity) { @@ -25,9 +26,9 @@ wall_create :: (scene: ^Scene, pos, size: Vector2) -> ^Entity { return this; } -#tag Entity_Schematic.{create} +@Entity_Schematic.{create} #local Door :: struct { - create :: (scene) => door_create(scene, .Zero, .Zero); + create :: (scene: ^Scene) => door_create(scene, .Zero, .Zero); render :: (use this: ^Entity) { immediate_set_color(.{0.7, 0.7, 0.1}); @@ -85,15 +86,3 @@ door_create :: (scene: ^Scene, pos, size: Vector2) -> ^Entity { return this; } - - -// @Relocate -move_towards :: (v: ^$T, target: T, diff: T) { - if math.abs(target - *v) <= diff { - *v = target; - return; - } - - if *v < target do *v += diff; - if *v > target do *v -= diff; -} diff --git a/src/entity/items.onyx b/src/entity/items.onyx index f084e94..a9afb9a 100644 --- a/src/entity/items.onyx +++ b/src/entity/items.onyx @@ -1,5 +1,7 @@ use core +use ogre +use ogre.ui Item :: struct { @@ -87,7 +89,7 @@ item_store_get_item :: (use this: ^Item_Store, id: str) -> ^Item { return items[id]; } -#tag Entity_Schematic.{create} +@Entity_Schematic.{create} #local Item_Entity :: struct { create :: (scene) => { @@ -118,7 +120,7 @@ item_store_get_item :: (use this: ^Item_Store, id: str) -> ^Item { ItemComponent :: struct { use base: Component; - #tag Editor_Custom_Field.{render_item_picker} + @Editor_Custom_Field.{render_item_picker} item: str; get_info :: (use this: ^ItemComponent) -> ^Item { diff --git a/src/entity/scene.onyx b/src/entity/scene.onyx index 5eb877c..af11f37 100644 --- a/src/entity/scene.onyx +++ b/src/entity/scene.onyx @@ -1,6 +1,7 @@ use core -use glfw3 +use ogre +use ogre.ui Entity_Nothing :: cast(Entity_ID) 0 Entity_ID :: #distinct u32 @@ -43,10 +44,10 @@ Entity_ID :: #distinct u32 } Component :: struct { - #tag Editor_Hidden, Entity_Store.Skip + @Editor_Hidden, Entity_Store.Skip use vtable: ^Component_Vtable; - #tag Editor_Hidden, Entity_Store.Skip + @Editor_Hidden, Entity_Store.Skip type: type_expr; } @@ -57,7 +58,7 @@ IsComponent :: interface (c: $C) { RenderComponent :: struct { use base: Component; - #tag Editor_Hidden, Entity_Store.Skip + @Editor_Hidden, Entity_Store.Skip func : (e: ^Entity) -> void; layer := 0; @@ -82,13 +83,13 @@ SpriteRenderComponent :: struct { } } -#tag Entity_Store.Skip +@Entity_Store.Skip SizeComponent :: struct { use base: Component; func : (e: ^Entity) -> Rect; } -#tag Entity_Store.Skip +@Entity_Store.Skip InteractableComponent :: struct { use base: Component; interact: (e: ^Entity, interactor: ^Entity) -> void; @@ -98,7 +99,7 @@ Entity :: struct { id: Entity_ID; flags: Entity_Flags; - #tag Entity_Store.Skip + @Entity_Store.Skip schematic: type_expr; nickname: str; @@ -113,7 +114,7 @@ Entity :: struct { return Rect.{ pos.x - size.x / 2, pos.y - size.y / 2, size.x, size.y }; }; - #tag Entity_Store.Skip, Editor_Hidden + @Entity_Store.Skip, Editor_Hidden components: Map(type_expr, ^Component); has :: (use this: ^Entity, component_type: type_expr) => components->has(component_type); diff --git a/src/entity/schematics/background.onyx b/src/entity/schematics/background.onyx index 8e87669..735bb54 100644 --- a/src/entity/schematics/background.onyx +++ b/src/entity/schematics/background.onyx @@ -3,7 +3,7 @@ use core // #local background_texture: Texture; -#tag Entity_Schematic.{ Background.create } +@Entity_Schematic.{ Background.create } Background :: struct { create :: (scene) => { this := scene->make(); diff --git a/src/entity/schematics/entryway.onyx b/src/entity/schematics/entryway.onyx index 7329eab..9b898d1 100644 --- a/src/entity/schematics/entryway.onyx +++ b/src/entity/schematics/entryway.onyx @@ -1,7 +1,7 @@ use core -#tag Entity_Schematic.{ Entryway.create } +@Entity_Schematic.{ Entryway.create } Entryway :: struct { create :: (scene) => { this := scene->make(); diff --git a/src/entity/schematics/furniture.onyx b/src/entity/schematics/furniture.onyx index 5362952..7fce57a 100644 --- a/src/entity/schematics/furniture.onyx +++ b/src/entity/schematics/furniture.onyx @@ -1,7 +1,8 @@ use core +use ogre -#tag Entity_Schematic.{ +@Entity_Schematic.{ (scene) => Furniture.create(scene, .{0, 0}) } Furniture :: struct { diff --git a/src/entity/schematics/patron.onyx b/src/entity/schematics/patron.onyx index 17d60bd..b6b1a63 100644 --- a/src/entity/schematics/patron.onyx +++ b/src/entity/schematics/patron.onyx @@ -1,8 +1,9 @@ use core +use ogre -#tag Entity_Schematic.{ - (scene) => Patron.create(scene, .{0,0}) +@Entity_Schematic.{ + (scene: ^Scene) => Patron.create(scene, .{0,0}) } Patron :: struct { create :: (scene: ^Scene, pos: Vector2) -> ^Entity { diff --git a/src/entity/schematics/player.onyx b/src/entity/schematics/player.onyx index b13432b..c71e367 100644 --- a/src/entity/schematics/player.onyx +++ b/src/entity/schematics/player.onyx @@ -1,6 +1,7 @@ use core use glfw3 +use ogre Player_Controls :: struct { up : i32; @@ -30,7 +31,7 @@ player_2_controls :: Player_Controls.{ } -#tag Entity_Schematic.{ +@Entity_Schematic.{ (scene: ^Scene) => Player.create(scene, .{0,0}) } Player :: struct { @@ -61,8 +62,8 @@ Player :: struct { } #persist assets: struct { - #tag "assets/images/player.png" - #tag Texture_Wrap.Clamp + @"assets/images/player.png" + @Texture_Wrap.Clamp texture: Texture; } } diff --git a/src/entity/schematics/tap.onyx b/src/entity/schematics/tap.onyx index 60baef6..95e619a 100644 --- a/src/entity/schematics/tap.onyx +++ b/src/entity/schematics/tap.onyx @@ -1,5 +1,6 @@ +use ogre -#tag Entity_Schematic.{ +@Entity_Schematic.{ (scene) => Tap.create(scene, .{0,0}, .{0,0}) } Tap :: struct { diff --git a/src/game.onyx b/src/game.onyx index 427df1c..f798106 100644 --- a/src/game.onyx +++ b/src/game.onyx @@ -2,6 +2,7 @@ use core use glfw3 use opengles +use ogre // // Game Global Variables @@ -88,23 +89,23 @@ game_draw :: () { texture_wrap(^texture, .Clamp); view_rect: Rect; if !editor_shown() { - view_rect = Rect.{0, 0, ~~window_width, ~~window_height}; + view_rect = Rect.{0, 0, ~~window.width, ~~window.height}; } else { // view_rect.x = math.lerp(editor_open_percent(), cast(f32) ((window_width - scene_canvas.width) / 2), 0); - view_rect.x = math.lerp(editor_open_percent(), cast(f32) ((window_width - scene_canvas.width) / 2), cast(f32) ((window_width - scene_canvas.width - 400) / 2)); - view_rect.y = ~~ ((window_height - scene_canvas.height) / 2); + view_rect.x = math.lerp(editor_open_percent(), cast(f32) ((window.width - scene_canvas.width) / 2), cast(f32) ((window.width - scene_canvas.width - 400) / 2)); + view_rect.y = ~~ ((window.height - scene_canvas.height) / 2); view_rect.w = ~~ scene_canvas.width; view_rect.h = ~~ scene_canvas.height; } scene_render_offset = Rect.top_left(view_rect); glDisable(GL_CULL_FACE); - immediate_image(^texture, view_rect.x, ~~window_height - view_rect.y, view_rect.w, -view_rect.h); + immediate_image(^texture, view_rect.x, ~~window.height - view_rect.y, view_rect.w, -view_rect.h); if distortion_enabled { shader_use(distortion_shader); shader_set_uniform(distortion_shader, #cstr "u_texture", 0); shader_set_uniform(distortion_shader, #cstr "u_tex_size", Vector2.{800, 608}); - shader_set_uniform(distortion_shader, #cstr "u_output_size", Vector2.{~~window_width, ~~window_height}); + shader_set_uniform(distortion_shader, #cstr "u_output_size", Vector2.{~~window.width, ~~window.height}); immediate_flush(false); } else { immediate_flush(); diff --git a/src/gfx/canvas.onyx b/src/gfx/canvas.onyx deleted file mode 100644 index c8743d3..0000000 --- a/src/gfx/canvas.onyx +++ /dev/null @@ -1,61 +0,0 @@ - -use core -use opengles - - -Canvas :: struct { - width, height: i32; - - framebuffer: GLint; - depth_stencil_buffer: GLint; - color_texture: GLint; -} - -canvas_make :: (width, height: i32) -> Canvas { - canvas: Canvas; - canvas.width = width; - canvas.height = height; - - glGenFramebuffers(1, ^canvas.framebuffer); - glBindFramebuffer(GL_FRAMEBUFFER, canvas.framebuffer); - - glGenTextures(1, ^canvas.color_texture); - glBindTexture(GL_TEXTURE_2D, canvas.color_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, null); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glBindTexture(GL_TEXTURE_2D, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas.color_texture, 0); - - glGenRenderbuffers(1, ^canvas.depth_stencil_buffer); - glBindRenderbuffer(GL_RENDERBUFFER, canvas.depth_stencil_buffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, canvas.depth_stencil_buffer); - - if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE { - debug_log(.Error, "Framebuffer is not complete!"); - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - return canvas; -} - -canvas_free :: (use canvas: ^Canvas) { - glDeleteFramebuffers(1, ^canvas.framebuffer); - glDeleteTextures(1, ^canvas.color_texture); - glDeleteRenderbuffers(1, ^canvas.depth_stencil_buffer); -} - -canvas_use :: (use canvas: ^Canvas) { - if canvas == null { - glBindFramebuffer(GL_FRAMEBUFFER, 0); - update_view_matrix(window_width, window_height); - } else { - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - update_view_matrix(width, height); - } -} - -canvas_to_texture :: (canvas: ^Canvas) => Texture.{ canvas.color_texture, canvas.width, canvas.height, 3, "" }; - diff --git a/src/gfx/font.onyx b/src/gfx/font.onyx deleted file mode 100644 index 1ece709..0000000 --- a/src/gfx/font.onyx +++ /dev/null @@ -1,266 +0,0 @@ - -use core -use stb_truetype -use opengles - -#local { - font_registry: Map(FontDescriptor, Font); - font_vbo: GLint; - font_vao: GLint; - - font_shader: Shader; - font_color: Color; -} - -fonts_init :: () { - map.init(^font_registry); - - font_shader = shader_make("./assets/shaders/font.glsl"); - shader_use(font_shader); - shader_link_window_matrix_block(font_shader); - shader_link_world_matrix_block(font_shader); - - glGenVertexArrays(1, ^font_vao); - glBindVertexArray(font_vao); - - font_interp_buffer: GLint; - glGenBuffers(1, ^font_interp_buffer); - glBindBuffer(GL_ARRAY_BUFFER, font_interp_buffer); - font_interp_data := f32.[ - 0.0, 0.0, - 1.0, 0.0, - 1.0, 1.0, - 0.0, 1.0, - ]; - glBufferData(GL_ARRAY_BUFFER, sizeof typeof font_interp_data, ~~^font_interp_data, GL_STATIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, false, 2 * sizeof f32, ~~0); - - font_index_buffer: GLint; - glGenBuffers(1, ^font_index_buffer); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_index_buffer); - font_index_data := u8.[ 0, 1, 2, 0, 2, 3 ]; - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof typeof font_index_data, ~~^font_index_data, GL_STATIC_DRAW); - - glGenBuffers(1, ^font_vbo); - glBindBuffer(GL_ARRAY_BUFFER, font_vbo); - glBufferData(GL_ARRAY_BUFFER, 1024 * sizeof stbtt_aligned_quad, null, GL_STREAM_DRAW); - - for 1..5 { - glEnableVertexAttribArray(it); - glVertexAttribDivisor(it, 1); - } - glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~ 0); - glVertexAttribPointer(2, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~16); - glVertexAttribPointer(3, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~8); - glVertexAttribPointer(4, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~24); - - glBindBuffer(GL_ARRAY_BUFFER, -1); - glBindVertexArray(-1); - - font_set_color(.{0,0,0}); -} - - -Font :: struct { - texture: GLint; - texture_width, texture_height: i32; - chars: [] stbtt_packedchar; - em: f32; -} - -font_make :: (fd: FontDescriptor) -> Font { - texture_size :: 256; - - char_data := memory.make_slice(stbtt_packedchar, 96); - - ttf_file := os.get_contents(fd.path); - if ttf_file.count == 0 { - println("Bad font"); - return .{}; - } - defer cfree(ttf_file.data); - - pixels := calloc(texture_size * texture_size); - defer cfree(pixels); - - ctx: stbtt_pack_context; - stbtt_PackBegin(^ctx, pixels, texture_size, texture_size, 0, 1); - stbtt_PackFontRange(^ctx, ttf_file.data, 0, ~~fd.size, #char " ", 96, char_data.data); - stbtt_PackEnd(^ctx); - - texture: GLint; - glGenTextures(1, ^texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture_size, texture_size, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glBindTexture(GL_TEXTURE_2D, 0); - - font := Font.{ - texture = texture, - texture_width = texture_size, - texture_height = texture_size, - chars = char_data, - em = ~~fd.size, - }; - - font_registry[fd] = font; - - return font; -} - -font_set_color :: (color: Color) { - font_color = color; -} - -font_print :: (font: Font, x, y: f32, format: str, va: ..any) { - buf: [1024] u8; - msg := conv.format_va(buf, format, va); - font_draw(font, x, y, msg); -} - -font_draw :: (font: Font, x, y: f32, msg: str) { - quads: ^stbtt_aligned_quad = alloc.from_stack(msg.count * sizeof stbtt_aligned_quad); - quad_num := 0; - - x_, y_ := x, y; - - for msg { - if it == #char "\n" { - x_ = x; - y_ += font.em + 2; - } - - stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, - ~~(it - #char " "), ^x_, ^y_, ^quads[quad_num], false); - - quad_num += 1; - } - - font_render(font, quads[0 .. quad_num]); -} - -font_draw_centered :: (font: Font, x, y, max_width: f32, msg: str) { - quads: ^stbtt_aligned_quad = alloc.from_stack(msg.count * sizeof stbtt_aligned_quad); - quad_num := 0; - - width := font_get_width(font, msg); - x_, y_ := x, y; - x_ = (max_width - width) / 2 + x; - - for msg { - if it == #char "\n" { - x_ = (max_width - width) / 2 + x; - y_ += font.em + 2; - } - - stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, - ~~(it - #char " "), ^x_, ^y_, ^quads[quad_num], false); - - quad_num += 1; - } - - font_render(font, quads[0 .. quad_num]); -} - -#local font_render :: (font: Font, quads: [] stbtt_aligned_quad) { - // If this is being used in conjunction with the immediate - // rendering system, make sure the immediate objects are flushed - // before trying to render over them. - #if #defined(immediate_flush) { - immediate_flush(); - } - - glBindBuffer(GL_ARRAY_BUFFER, font_vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, quads.count * sizeof stbtt_aligned_quad, quads.data); - glBindBuffer(GL_ARRAY_BUFFER, -1); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, font.texture); - shader_use(font_shader); - shader_set_uniform(font_shader, #cstr "u_texture", 0); - shader_set_uniform(font_shader, #cstr "u_color", font_color); - - glDisable(GL_DEPTH_TEST); - glBindVertexArray(font_vao); - glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, ~~0, quads.count); - glBindTexture(GL_TEXTURE_2D, -1); - glBindVertexArray(-1); -} - -font_get_width :: (font: Font, msg: str) -> f32 { - x_, y_ := 0.0f, 0.0f; - width := 0.0f; - - quad: stbtt_aligned_quad; - for msg { - if it == #char "\n" { - width = math.max(width, x_); - x_ = 0; - y_ += font.em + 2; - continue; - } - - stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, - ~~(it - #char " "), ^x_, ^y_, ^quad, false); - } - - return math.max(x_, width); -} - -font_get_height :: (font: Font, msg: str) -> f32 { - x_, y_ := 0.0f, 0.0f; - width := 0.0f; - - quad: stbtt_aligned_quad; - for msg { - if it == #char "\n" { - width = math.max(width, x_); - x_ = 0; - y_ += font.em + 2; - continue; - } - - stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, - ~~(it - #char " "), ^x_, ^y_, ^quad, false); - } - - return y_ + font.em + 2; -} - -font_get_char_width :: (font: Font, ch: u8) -> f32 { - x, y := 0.0f, 0.0f; - quad: stbtt_aligned_quad; - - stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, - ~~(ch - #char " "), ^x, ^y, ^quad, false); - - return x; -} - - -FontDescriptor :: struct { - path : str; - size : u32; -} - -#match hash.to_u32 (fd: FontDescriptor) => { - name_hash := hash.to_u32(fd.path); - size_hash := hash.to_u32(fd.size); - return name_hash * 13 + size_hash * 17; -} - -#operator == (f1, f2: FontDescriptor) => f1.path == f2.path && f1.size == f2.size; - -font_lookup :: (fd := FontDescriptor.{ "assets/calibri.ttf", 12 }) -> Font { - if font_registry->has(fd) { - return font_registry[fd]; - } - - font := font_make(fd); - font_registry[fd] = font; - return font; -} diff --git a/src/gfx/immediate.onyx b/src/gfx/immediate.onyx deleted file mode 100644 index 8a88eca..0000000 --- a/src/gfx/immediate.onyx +++ /dev/null @@ -1,205 +0,0 @@ - -use core -use opengles -use glfw3 - -immediate_init :: () { - memory.alloc_slice(^vertex_data, Maximum_Vertex_Count); - vertex_count = 0; - - imgui_shader = shader_make(Shader_Path); - shader_use(imgui_shader); - shader_link_window_matrix_block(imgui_shader); - shader_link_world_matrix_block(imgui_shader); - shader_set_uniform(imgui_shader, #cstr "u_texture_enabled", 0.0f); - shader_set_uniform(imgui_shader, #cstr "u_texture", 0); - - immediate_mesh = mesh_make(vertex_data, .[], GL_DYNAMIC_DRAW); - immediate_color = .{0,0,0}; -} - -immediate_flush :: (set_shader := true) { - if vertex_count == 0 do return; - - if set_shader { - shader_use(imgui_shader); - shader_set_uniform(imgui_shader, #cstr "u_texture_enabled", 1.0f if rendering_type == .Image else 0.0f); - } - - immediate_mesh.vertex_count = vertex_count; - mesh_update_verticies(immediate_mesh, vertex_data); - - mesh_draw(immediate_mesh); - - vertex_count = 0; - rendering_type = .Plain; - immediate_mesh.primitive = GL_TRIANGLES; -} - -immediate_clear :: (color: Color) { - glClearColor(color.r, color.g, color.b, color.a); - glClear(GL_COLOR_BUFFER_BIT); -} - -immediate_set_color :: (color: Color) { - immediate_color = color; -} - -immediate_vertex :: (x, y: f32, t_x := 0.0f, t_y := 0.0f) { - if vertex_count >= Maximum_Vertex_Count do immediate_flush(); - set_rendering_type(.Plain); - - vertex_data[vertex_count] = .{ .{x, y}, .{t_x, t_y}, immediate_color }; -} - -immediate_line :: (x1, y1, x2, y2: f32) { - if vertex_count >= Maximum_Vertex_Count do immediate_flush(); - set_rendering_type(.Line); - immediate_mesh.primitive = GL_LINES; - - vertex_data[vertex_count + 0] = .{ .{x1, y1}, .{0, 0}, immediate_color }; - vertex_data[vertex_count + 1] = .{ .{x2, y2}, .{0, 0}, immediate_color }; - vertex_count += 2; -} - -immediate_triangle :: (x1, x2, x3: Vector2) { - if vertex_count + 3 > Maximum_Vertex_Count do immediate_flush(); - set_rendering_type(.Plain); - - vertex_data[vertex_count + 0] = .{ x1, .{0,0}, immediate_color }; - vertex_data[vertex_count + 1] = .{ x2, .{0,0}, immediate_color }; - vertex_data[vertex_count + 2] = .{ x3, .{0,0}, immediate_color }; - vertex_count += 3; -} - -immediate_rectangle :: (x, y, w, h: f32) { - if vertex_count + 6 > Maximum_Vertex_Count do immediate_flush(); - set_rendering_type(.Plain); - - vertex_data[vertex_count + 0] = .{ .{x, y}, .{0,0}, immediate_color }; - vertex_data[vertex_count + 1] = .{ .{x+w, y}, .{0,0}, immediate_color }; - vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{0,0}, immediate_color }; - vertex_data[vertex_count + 3] = .{ .{x, y}, .{0,0}, immediate_color }; - vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{0,0}, immediate_color }; - vertex_data[vertex_count + 5] = .{ .{x, y+h}, .{0,0}, immediate_color }; - vertex_count += 6; -} - -immediate_image :: (image: ^Texture, x, y, w, h: f32) { - if vertex_count > 0 do immediate_flush(); - - set_rendering_type(.Image); - texture_use(image); - shader_use(imgui_shader); - shader_set_uniform(imgui_shader, #cstr "u_texture", 0); - - vertex_data[vertex_count + 0] = .{ .{x, y}, .{0,0}, immediate_color }; - vertex_data[vertex_count + 1] = .{ .{x+w, y}, .{1,0}, immediate_color }; - vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{1,1}, immediate_color }; - vertex_data[vertex_count + 3] = .{ .{x, y}, .{0,0}, immediate_color }; - vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{1,1}, immediate_color }; - vertex_data[vertex_count + 5] = .{ .{x, y+h}, .{0,1}, immediate_color }; - vertex_count += 6; -} - -immediate_subimage :: (image: ^Texture, x, y, w, h: f32, tx, ty, tw, th: f32) { - if vertex_count > 0 do immediate_flush(); - - set_rendering_type(.Image); - texture_use(image); - shader_use(imgui_shader); - shader_set_uniform(imgui_shader, #cstr "u_texture", 0); - - sx := tx / ~~ image.width; - sy := ty / ~~ image.height; - sw := tw / ~~ image.width; - sh := th / ~~ image.height; - - vertex_data[vertex_count + 0] = .{ .{x, y}, .{sx, sy}, immediate_color }; - vertex_data[vertex_count + 1] = .{ .{x+w, y}, .{sx+sw, sy}, immediate_color }; - vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{sx+sw,sy+sh}, immediate_color }; - vertex_data[vertex_count + 3] = .{ .{x, y}, .{sx, sy}, immediate_color }; - vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{sx+sw,sy+sh}, immediate_color }; - vertex_data[vertex_count + 5] = .{ .{x, y+h}, .{sx, sy+sh}, immediate_color }; - vertex_count += 6; -} - -immediate_ellipse :: () {} - -immediate_push_scissor :: (x, y, w, h: f32) { - if vertex_count > 0 do immediate_flush(); - - // Assuming that x, y, w, and h are in screen (window) coordinates. - x += offset.x; - y += offset.y; - - xi := cast(i32) x; - yi := cast(i32) y; - wi := cast(i32) w; - hi := cast(i32) h; - scissors << .{xi, yi, wi, hi}; - - glEnable(GL_SCISSOR_TEST); - glScissor(xi, window_height - yi - hi, wi, hi); -} - -immediate_pop_scissor :: () { - if scissors.count > 0 { - array.pop(^scissors); - } - - if scissors.count > 0 { - glEnable(GL_SCISSOR_TEST); - s := scissors[scissors.count - 1]; - glScissor(s.x, window_height - s.y - s.h, s.w, s.h); - } else { - glDisable(GL_SCISSOR_TEST); - } -} - -immediate_set_scroll :: (scroll_x, scroll_y: f32) { - offset = .{ scroll_x, scroll_y }; - update_model_matrix(offset); -} - -immediate_get_scroll :: () => offset; - -Immediate_Vertex :: struct { - pos: Vector2; - tex: Vector2; - color: Color; -} - -#local { - Shader_Path :: "./assets/shaders/imgui.glsl" - imgui_shader: Shader; - - Maximum_Vertex_Count :: 1023; - vertex_count: i32; - vertex_data: [] Immediate_Vertex; - - immediate_color: Color; - - immediate_mesh: ^Mesh(Immediate_Vertex); - - Rendering_Type :: enum { - Plain; - Line; - Image; - } - rendering_type := Rendering_Type.Plain; - - Scissor :: struct { - x, y, w, h: i32; - } - scissors: [..] Scissor; - - offset: Vector2; - - set_rendering_type :: (new_type: typeof rendering_type) { - if rendering_type != new_type { - immediate_flush(); - rendering_type = new_type; - } - } -} diff --git a/src/gfx/mesh.onyx b/src/gfx/mesh.onyx deleted file mode 100644 index 981ff11..0000000 --- a/src/gfx/mesh.onyx +++ /dev/null @@ -1,88 +0,0 @@ -use core -use opengles - -Mesh :: struct (Vertex_Type: type_expr) { - handle: GLint; - vertex_handle: GLint; - index_handle: GLint; - - vertex_count: u32; - index_count: u32; - primitive: GLuint = GL_TRIANGLES; -} - -mesh_make :: (verticies: [] $T, indicies: [] u32, gl_hint := GL_STATIC_DRAW) -> ^Mesh(T) { - mesh := new(Mesh(T)); - mesh.vertex_count = verticies.count; - mesh.index_count = indicies.count; - - glGenVertexArrays(1, ^mesh.handle); - glBindVertexArray(mesh.handle); - - glGenBuffers(1, ^mesh.vertex_handle); - glBindBuffer(GL_ARRAY_BUFFER, mesh.vertex_handle); - glBufferData(GL_ARRAY_BUFFER, sizeof T * verticies.count, verticies.data, gl_hint); - - type_info :: runtime.info - vertex_info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(T); - vertex_attr := 0; - for attr: vertex_info.members { - defer vertex_attr += 1; - glEnableVertexAttribArray(vertex_attr); - - switch attr.type { - case Vector2 do glVertexAttribPointer(vertex_attr, 2, GL_FLOAT, false, sizeof T, ~~attr.offset); - case Vector3 do glVertexAttribPointer(vertex_attr, 3, GL_FLOAT, false, sizeof T, ~~attr.offset); - case u32 do glVertexAttribIPointer(vertex_attr, 1, GL_UNSIGNED_INT, sizeof T, ~~attr.offset); - case i32 do glVertexAttribIPointer(vertex_attr, 1, GL_INT, sizeof T, ~~attr.offset); - - // It would be nice to not have to have all the cases here. - // Instead allow for an extensible way of defining them at compile time. - case Color do glVertexAttribPointer(vertex_attr, 4, GL_FLOAT, false, sizeof T, ~~attr.offset); - - case #default { - buf: [256] u8; - msg := conv.str_format(buf, "Unknown type for GL vertex attribute, {}.", attr.type); - assert(false, msg); - } - } - } - - if indicies.count > 0 { - glGenBuffers(1, ^mesh.index_handle); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.index_handle); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof i32 * indicies.count, indicies.data, gl_hint); - } - - glBindVertexArray(-1); - - return mesh; -} - -mesh_update_verticies :: (use mesh: ^Mesh, verticies: [] mesh.Vertex_Type) { - // @TODO // Add bounds checking to arrays here. - - glBindBuffer(GL_ARRAY_BUFFER, vertex_handle); - glBufferSubData(GL_ARRAY_BUFFER, 0, verticies.count * sizeof mesh.Vertex_Type, verticies.data); - glBindBuffer(GL_ARRAY_BUFFER, -1); -} - -mesh_draw :: (use mesh: ^Mesh) { - glBindVertexArray(handle); - if index_count > 0 { - glDrawElements(primitive, index_count, GL_UNSIGNED_INT, ~~0); - } else { - glDrawArrays(primitive, 0, vertex_count); - } - glBindVertexArray(-1); -} - -mesh_free :: (mesh: ^Mesh) { - glDeleteBuffers(1, ^mesh.vertex_handle); - glDeleteBuffers(1, ^mesh.index_handle); - glDeleteVertexArrays(1, ^mesh.handle); - cfree(mesh); -} - - - diff --git a/src/gfx/shader.onyx b/src/gfx/shader.onyx deleted file mode 100644 index 711c6a0..0000000 --- a/src/gfx/shader.onyx +++ /dev/null @@ -1,181 +0,0 @@ - -use core -use opengles - -Shader :: struct { - vs, fs: GLuint; - prog: GLuint; -} - -window_matrix_block_buffer: GLuint; -world_matrix_block_buffer: GLuint; - -shaders_init :: () { - glGenBuffers(1, ^window_matrix_block_buffer); - glGenBuffers(1, ^world_matrix_block_buffer); - - glBindBuffer(GL_UNIFORM_BUFFER, window_matrix_block_buffer); - glBufferData(GL_UNIFORM_BUFFER, sizeof f32 * 16, null, GL_DYNAMIC_DRAW); - - glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer); - glBufferData(GL_UNIFORM_BUFFER, sizeof f32 * (16 + 16), null, GL_DYNAMIC_DRAW); - - glBindBuffer(GL_UNIFORM_BUFFER, -1); - - glBindBufferBase(GL_UNIFORM_BUFFER, WINDOW_MATRIX_BLOCK, window_matrix_block_buffer); - glBindBufferBase(GL_UNIFORM_BUFFER, WORLD_MATRIX_BLOCK, world_matrix_block_buffer); -} - -shader_make :: (shader_path: str) -> Shader { - shader_source := os.get_contents(shader_path); - vs := compile_shader(shader_source, GL_VERTEX_SHADER); - fs := compile_shader(shader_source, GL_FRAGMENT_SHADER); - - prog := link_program(vs, fs); - - return Shader.{vs, fs, prog}; -} - -shader_use :: (shader: Shader) { - glUseProgram(shader.prog); -} - -#local { - WINDOW_MATRIX_BLOCK :: 0; - WORLD_MATRIX_BLOCK :: 1; -} - -shader_link_window_matrix_block :: (use shader: Shader) { - matrix_block_index := glGetUniformBlockIndex(prog, #cstr "u_window_matrix_block"); - glUniformBlockBinding(prog, matrix_block_index, WINDOW_MATRIX_BLOCK); -} - -shader_link_world_matrix_block :: (use shader: Shader) { - matrix_block_index := glGetUniformBlockIndex(prog, #cstr "u_world_matrix_block"); - glUniformBlockBinding(prog, matrix_block_index, WORLD_MATRIX_BLOCK); -} - -shader_set_uniform :: (shader: Shader, uniform: cstr, value: $T) { - glUseProgram(shader.prog); - location := glGetUniformLocation(shader.prog, uniform); - - set_uniform_internal(location, value); - - set_uniform_internal :: #match { - macro (location: GLint, value: u32) do glUniform1i(location, value); , - macro (location: GLint, value: f32) do glUniform1f(location, value); , - macro (location: GLint, value: Vector2) do glUniform2f(location, value.x, value.y); , - macro (location: GLint, value: Vector3) do glUniform3f(location, value.x, value.y, value.z); , - macro (location: GLint, value: Color) do glUniform4f(location, value.r, value.g, value.b, value.a); , - - macro (location: GLint, value: $T) { - buffer: [1024] u8; - assert(false, conv.format(buffer, "Bad shader_set_uniform case: {}", T)); - } - } -} - -update_view_matrix :: (width, height: u32) { - matrix : [16] f32; - top := 0.0f; - left := 0.0f; - right := cast(f32) width; - bottom := cast(f32) height; - far := 10.0f; - near := 0f; - - matrix[0] = 2 / (right - left); - matrix[5] = 2 / (top - bottom); - matrix[10] = -2 / (far - near); - matrix[12] = -(right + left) / (right - left); - matrix[13] = -(top + bottom) / (top - bottom); - matrix[14] = -(far + near) / (far - near); - matrix[15] = 1; - - glBindBuffer(GL_UNIFORM_BUFFER, window_matrix_block_buffer); - glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof typeof matrix, ^matrix); - glBindBuffer(GL_UNIFORM_BUFFER, -1); - - glViewport(0, 0, width, height); -} - -update_world_matrix :: () { - world_mat: [16] f32; - world_mat[0] = 1; - world_mat[5] = 1; - world_mat[10] = 1; - world_mat[15] = 1; - - glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer); - glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof typeof world_mat, ^world_mat); - glBindBuffer(GL_UNIFORM_BUFFER, -1); -} - -update_model_matrix :: (v: Vector2) { - model_mat: [16] f32; - model_mat[0] = 1; - model_mat[5] = 1; - model_mat[10] = 1; - model_mat[12] = v.x; - model_mat[13] = v.y; - model_mat[14] = 0; - model_mat[15] = 1; - - glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer); - glBufferSubData(GL_UNIFORM_BUFFER, 16 * sizeof f32, sizeof typeof model_mat, ^model_mat); - glBindBuffer(GL_UNIFORM_BUFFER, -1); -} - - -#local { - compile_shader :: (source: str, type: GLenum) -> GLint { - shader := glCreateShader(type); - - #persist VERTEX_HEADER := """ -#version 300 es -#define VERTEX_SHADER 1 -#define COMM out - """; - - #persist FRAGMENT_HEADER := """ -#version 300 es -#define FRAGMENT_SHADER 1 -#define COMM in - """; - - header := VERTEX_HEADER if type == GL_VERTEX_SHADER else FRAGMENT_HEADER; - sources : [] cptr(u8) = .[ cptr.make(header.data), cptr.make(source.data) ]; - source_lens : [] i32 = .[ header.count, source.count ]; - - glShaderSource(shader, 2, sources.data, source_lens.data); - glCompileShader(shader); - - success: GLint; - if glGetShaderiv(shader, GL_COMPILE_STATUS, ^success); success == GL_FALSE { - buf_data: [2048] u8; - buf := str.{ ~~buf_data, 0 }; - glGetShaderInfoLog(shader, 2048, ^buf.count, buf.data); - println(buf); - } - - return shader; - } - - link_program :: (vertex_shader, fragment_shader: GLint) -> GLuint { - prog := glCreateProgram(); - glAttachShader(prog, vertex_shader); - glAttachShader(prog, fragment_shader); - glLinkProgram(prog); - - success: GLint; - if glGetProgramiv(prog, GL_LINK_STATUS, ^success); success == GL_FALSE { - buf_data: [1024] u8; - buf := str.{ ~~buf_data, 0 }; - glGetProgramInfoLog(prog, 1024, ^buf.count, buf.data); - println(buf); - } - - return prog; - } -} - diff --git a/src/gfx/texture.onyx b/src/gfx/texture.onyx deleted file mode 100644 index 5a205d4..0000000 --- a/src/gfx/texture.onyx +++ /dev/null @@ -1,87 +0,0 @@ - -use core -use opengles -use stb_image - -#local texture_cache: Map(str, Texture); - -Texture :: struct { - texture: GLint; - width, height, channels: i32; - filename: str; -} - -texture_lookup :: #match {} -#match texture_lookup (filename: str) -> (Texture, bool) { - if texture_cache->has(filename) { - return texture_cache[filename], true; - } - - buffer: [512] u8; - memory.copy(~~ buffer, filename.data, math.min(filename.count, 511)); - return texture_lookup(cast(cstr) buffer); -} - -#match texture_lookup (path: cstr) -> (Texture, bool) { - filename := string.from_cstr(path); - if texture_cache->has(filename) { - return texture_cache[filename], true; - } - - tex: Texture; - tex.filename = filename; - pixels := stbi_load(path, ^tex.width, ^tex.height, ^tex.channels, 4); - if pixels == null { - debug_log(.Warning, "Failed to load texture: {}", filename); - return .{}, false; - } - defer stbi_image_free(pixels); - - glGenTextures(1, ^tex.texture); - glBindTexture(GL_TEXTURE_2D, tex.texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - // Are these sensible defaults? - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glBindTexture(GL_TEXTURE_2D, 0); - - // This assumes that the filename data is going to be persistant forever. - // Not a great assumption to make but is it really worth copying it? - texture_cache[filename] = tex; - debug_log(.Info, "Loaded texture: {}", filename); - return tex, true; -} - -texture_free :: (use tex: ^Texture) { - glDeleteTextures(1, ^texture); -} - -texture_use :: (use tex: ^Texture, texture_binding := 0) { - glActiveTexture(GL_TEXTURE0 + texture_binding); - glBindTexture(GL_TEXTURE_2D, texture); -} - - -Texture_Wrap :: enum { - Clamp :: GL_CLAMP_TO_EDGE | 0; - Repeat :: GL_REPEAT | 0; - Mirrored_Repeat :: GL_MIRRORED_REPEAT | 0; -} - -texture_wrap :: (use tex: ^Texture, wrap: Texture_Wrap) { - glBindTexture(GL_TEXTURE_2D, texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ~~ wrap); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ~~ wrap); -} - -texture_cache_clear :: () { - for^ texture_cache.entries { - glDeleteTextures(1, ^it.value.texture); - } - - map.clear(^texture_cache); -} diff --git a/src/gfx/ui.onyx b/src/gfx/ui.onyx deleted file mode 100644 index ae31eaa..0000000 --- a/src/gfx/ui.onyx +++ /dev/null @@ -1,728 +0,0 @@ -// -// Very simple immediate mode UI -// - -use core -use opengles -use glfw3 - -UI_Id :: u32 - -ui_end_frame :: () { - hot_item_depth_needed = hot_item_depth; - if !hot_item_was_set do set_hot_item(0); - hot_item_depth = 0; - hot_item_was_set = false; - - for^ animation_states.entries { - if !it.value.accessed_this_frame || (it.value.click_time == 0 && it.value.hover_time == 0) { - map.delete(^animation_states, it.key); - } - } - - for^ animation_states.entries { - it.value.accessed_this_frame = false; - } - - if active_countdown > 0 { - active_countdown -= 1; - if active_countdown == 0 { - set_active_item(0); - input_release_keys(); - } - } -} - -// -// Buttons -// -Button_Theme :: struct { - use text_theme := Text_Theme.{}; - use animation_theme := Animation_Theme.{}; - - background_color := Color.{ 0.1, 0.1, 0.1 }; - hover_color := Color.{ 0.3, 0.3, 0.3 }; - click_color := Color.{ 0.5, 0.5, 0.7 }; - - border_color := Color.{0.2, 0.2, 0.2}; - border_width := 2.0f; - - active := false; -} - -#local default_button_theme := Button_Theme.{}; - -draw_button :: (use r: Rect, text: str, theme := ^default_button_theme, site := #callsite, increment := 0) -> bool { - result := false; - - hash := get_site_hash(site, increment); - animation_state := get_animation(hash); - mx, my := mouse_get_position(); - - contains := Rect.contains(r, .{~~mx, ~~my}); - - if is_active_item(hash) { - renew_active_item(hash); - if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) { - if is_hot_item(hash) && contains { - result = true; - animation_state.click_time = 1.0f; - } - - set_active_item(0); - } - } elseif is_hot_item(hash) { - if is_button_down(GLFW_MOUSE_BUTTON_LEFT) { - set_active_item(hash); - } - } - - if contains { - set_hot_item(hash); - } - - if is_hot_item(hash) || theme.active { - move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed); - } else { - move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed); - } - - border_width := theme.border_width; - - immediate_set_color(theme.border_color); - immediate_rectangle(x, y, w, h); - - surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color); - surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); - immediate_set_color(surface_color); - immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2); - - font := font_lookup(.{theme.font_name, theme.font_size}); - font_height := font_get_height(font, text); - font_set_color(theme.text_color); - font_draw_centered(font, x, y + (h - font_height) / 2 + font.em - 2, w, text); - - move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed); - - return result; -} - - -// -// Sliders -// -Slider_Theme :: struct { - use text_theme := Text_Theme.{}; - use animation_theme := Animation_Theme.{}; - - box_color := Color.{ 0.1, 0.1, 0.1 }; - box_border_color := Color.{ 0.2, 0.2, 0.2 }; - box_border_width := 4.0f; // @InPixels - - bar_color := Color.{ 0.4, 0.4, 0.4 }; - bar_hover_color := Color.{ 0, 0, 1 }; - bar_hover_negative_color := Color.{ 1, 0, 0 }; // The color when value is less than 0 -} - -#local default_slider_theme := Slider_Theme.{}; - -draw_slider :: (use r: Rect, value: ^$T, min_value: T, max_value: T, theme := ^default_slider_theme, site := #callsite, increment := 0) -> bool { - result := false; - - hash := get_site_hash(site, increment); - state := get_animation(hash); - mx, my := mouse_get_position(); - - if is_hot_item(hash) { - if is_button_down(GLFW_MOUSE_BUTTON_LEFT) { - set_active_item(hash); - result = true; - - // Animate this? - sx := ~~mx - x; - - if T == i32 || T == i64 || T == u32 || T == u64 { - step_width := w / ~~math.abs(max_value - min_value); - percent := (sx + step_width / 2) / w; - *value = math.lerp(percent, min_value, max_value); - *value = math.clamp(*value, min_value, max_value); - - } else { - percent := sx / w; - *value = math.lerp(percent, min_value, max_value); - *value = math.clamp(*value, min_value, max_value); - } - } else { - set_active_item(0); - } - } - - if Rect.contains(r, .{ ~~mx, ~~my }) { - set_hot_item(hash); - } - - if is_hot_item(hash) { - move_towards(^state.hover_time, 1.0f, theme.hover_speed); - } else { - move_towards(^state.hover_time, 0.0f, theme.hover_speed); - } - - box_border_width := theme.box_border_width; - - bar_color := theme.bar_color; - if *value < 0 do bar_color = color_lerp(state.hover_time, bar_color, theme.bar_hover_negative_color); - else do bar_color = color_lerp(state.hover_time, bar_color, theme.bar_hover_color); - - immediate_set_color(theme.box_border_color); - immediate_rectangle(x, y, w, h); - - immediate_set_color(theme.box_border_color); - immediate_rectangle(x + box_border_width, y + box_border_width, w - box_border_width * 2, h - box_border_width * 2); - - box_width := cast(f32) (*value - min_value) / ~~(max_value - min_value); - box_width *= w - box_border_width * 2; - box_width = math.clamp(box_width, 0, w - box_border_width * 2); - immediate_set_color(bar_color); - immediate_rectangle(x + box_border_width, y + box_border_width, box_width, h - box_border_width * 2); - - return result; -} - -// -// Textbox -// -Textbox_Theme :: struct { - use text_theme := Text_Theme.{ - text_color = .{ 0, 0, 0 } - }; - - use animation_theme := Animation_Theme.{}; - - background_color := Color.{ 0.8, 0.8, 0.8 }; - hover_color := Color.{ 1.0, 1.0, 1.0 }; - click_color := Color.{ 0.5, 0.5, 0.7 }; - - border_color := Color.{ 0.2, 0.2, 0.2 }; - border_width := 6.0f; // @InPixels - - cursor_color := Color.{ 0.5, 0.5, 0.5 }; - cursor_width := 4.0f; // @InPixels - cursor_blink_speed := 0.04f; // Bigger is faster - - placeholder_text_color := Color.{ 0.5, 0.5, 0.5 }; -} - -#local { - default_textbox_theme := Textbox_Theme.{}; - - Textbox_Editing_State :: struct { - hash: UI_Id = 0; - - cursor_position: i32 = 0; - cursor_animation := 0.0f; - cursor_animation_speed := 0.02f; - - // @HACK // Otherwise the action keys are evaluated every frame. - action_key_timeout := 0.0f; - } - - textbox_editing_state := Textbox_Editing_State.{}; -} - -draw_textbox :: (use r: Rect, text_buffer: ^[..] u8, placeholder := null_str, theme := ^default_textbox_theme, site := #callsite, increment := 0) -> bool { - result := false; - - hash := get_site_hash(site, increment); - animation_state := get_animation(hash); - mx, my := mouse_get_position(); - - border_width := theme.border_width; - text_color := theme.text_color; - text := str.{text_buffer.data, text_buffer.count}; - if text.count == 0 && placeholder.count > 0 { - text = placeholder; - text_color = theme.placeholder_text_color; - } - - font := font_lookup(.{theme.font_name, theme.font_size}); - text_width := font_get_width(font, text); - text_height := font_get_height(font, text); - - text_x := x + border_width; - text_y := y + font.em + (h - text_height) / 2; - - contains := Rect.contains(r, .{~~mx, ~~my}); - - if is_hot_item(hash) && !is_active_item(hash) { - if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains { - set_active_item(hash); - input_capture_keys(); - textbox_editing_state.hash = hash; - textbox_editing_state.cursor_animation_speed = theme.cursor_blink_speed; - } - } - - if is_active_item(hash) { - renew_active_item(hash); - if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) && !contains { - set_active_item(0); - input_release_keys(); - textbox_editing_state.hash = 0; - textbox_editing_state.cursor_position = 0; - } - } - - if contains { - set_hot_item(hash); - } - - if textbox_editing_state.hash == hash { - move_towards(^textbox_editing_state.cursor_animation, 0.0f, textbox_editing_state.cursor_animation_speed); - if textbox_editing_state.cursor_animation <= 0.0f do textbox_editing_state.cursor_animation = 1.0f; - - if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains { - textbox_editing_state.cursor_animation = 1.0f; - textbox_editing_state.cursor_position = screen_to_cursor(^font, text_x, text_y, text, ~~mx, ~~my); - } - - for key: input_get_keys_this_frame() { - switch key.key { - case GLFW_KEY_ESCAPE { - set_active_item(0); - input_release_keys(); - } - - case GLFW_KEY_LEFT do textbox_editing_state.cursor_position -= 1; - case GLFW_KEY_RIGHT do textbox_editing_state.cursor_position += 1; - case GLFW_KEY_END do textbox_editing_state.cursor_position = text_buffer.count; - case GLFW_KEY_HOME do textbox_editing_state.cursor_position = 0; - - case GLFW_KEY_BACKSPACE { - if textbox_editing_state.cursor_position > 0 { - array.delete(text_buffer, textbox_editing_state.cursor_position - 1); - } - textbox_editing_state.cursor_position = math.max(~~0, textbox_editing_state.cursor_position - 1); - } - - case GLFW_KEY_DELETE { - array.delete(text_buffer, textbox_editing_state.cursor_position); - } - } - } - - for ch: input_get_chars_this_frame() { - array.insert(text_buffer, textbox_editing_state.cursor_position, ~~ch); - textbox_editing_state.cursor_position += 1; - result = true; - } - - textbox_editing_state.cursor_position = math.clamp(textbox_editing_state.cursor_position, 0, text_buffer.count); - textbox_editing_state.cursor_animation = 1.0f; - - text = str.{text_buffer.data, text_buffer.count}; - } - - if is_hot_item(hash) { - move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed); - } else { - move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed); - } - - immediate_push_scissor(x, y, w, h); - immediate_set_color(theme.border_color); - immediate_rectangle(x, y, w, h); - - surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color); - surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); - immediate_set_color(surface_color); - immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2); - - // Draw the cursor on textboxes - if is_active_item(hash) { - cursor := cursor_to_screen(^font, x + border_width, y + border_width, text, textbox_editing_state.cursor_position); - right_edge := x + w - border_width * 2; - if cursor.x > right_edge { - text_x -= cursor.x - right_edge; - cursor.x = right_edge - 2; - } - immediate_set_color(.{0,0,0}); - immediate_rectangle(cursor.x, cursor.y + 2, 2, h - border_width * 2 - 4); - } - - font_set_color(theme.text_color); - font_draw(font, text_x, text_y, text); // This is technically a frame late for updating the text? - - move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed); - - immediate_pop_scissor(); - return result; - - cursor_to_screen :: (font: ^Font, x, y: f32, text: str, cur_pos: i32) -> Vector2 { - cx := x; - cy := y; - - for text[0 .. cur_pos] { - if it == #char "\n" { - cx = 0; - cy += font.em + 2; - } else { - cx += font_get_char_width(*font, it); - } - } - - return .{ cx, cy }; - } - - screen_to_cursor :: (font: ^Font, x, y: f32, text: str, mouse_x, mouse_y: f32) -> i32 { - mx := mouse_x - x; - my := mouse_y - y; - - cx := 0.0f; - cy := 0.0f; - for pos: text.count { - if text[pos] == #char "\n" { - cx = 0; - cy += font.em + 2; - } else { - cx += font_get_char_width(*font, text[pos]); - } - - if cy >= my && cx >= mx { - return pos; - } - } - - return text.count; - } -} - - - - -// -// Checkboxes -// -Checkbox_Theme :: struct { - use text_theme := Text_Theme.{}; - use animation_theme := Animation_Theme.{}; - - box_color := Color.{ 0.2, 0.2, 0.2 }; - box_border_width := 4.0f; // @InPixels - box_size := 20.0f; // @InPixels - - checked_color := Color.{ 1, 0, 0 }; - checked_hover_color := Color.{ 1, 0.6, 0.6 }; - - background_color := Color.{ 0.05, 0.05, 0.05 }; // Background of the checkbox button. - hover_color := Color.{ 0.3, 0.3, 0.3 }; - click_color := Color.{ 0.5, 0.5, 0.7 }; -} - -#local default_checkbox_theme := Checkbox_Theme.{}; - -draw_checkbox :: (use r: Rect, value: ^bool, text: str, theme := ^default_checkbox_theme, site := #callsite, increment := 0) -> bool { - result := false; - - hash := get_site_hash(site, increment); - animation_state := get_animation(hash); - mx, my := mouse_get_position(); - - contains := Rect.contains(r, .{~~mx, ~~my}); - - if is_active_item(hash) { - renew_active_item(hash); - if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) { - if is_hot_item(hash) && contains { - result = true; - *value = !*value; - animation_state.click_time = 1.0f; - } - - set_active_item(0); - } - } elseif is_hot_item(hash) { - if is_button_down(GLFW_MOUSE_BUTTON_LEFT) { - set_active_item(hash); - } - } - - if contains { - set_hot_item(hash); - } - - if is_hot_item(hash) { - move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed); - } else { - move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed); - } - - box_border_width := theme.box_border_width; - box_size := theme.box_size; - - immediate_set_color(theme.box_color); - immediate_rectangle(x + 4, y + (h - box_size) / 2, box_size, box_size); - - surface_color : Color; - if *value { - surface_color = theme.checked_color; - surface_color = color_lerp(animation_state.hover_time, surface_color, theme.checked_hover_color); - - } else { - surface_color = theme.background_color; - surface_color = color_lerp(animation_state.hover_time, surface_color, theme.hover_color); - } - - surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); - - immediate_set_color(surface_color); - immediate_rectangle(x + 4 + box_border_width, y + (h - box_size) / 2 + box_border_width, box_size - box_border_width * 2, box_size - box_border_width * 2); - - font := font_lookup(.{theme.font_name, theme.font_size}); - text_width := font_get_width(font, text); - text_height := font_get_height(font, text); - - font_set_color(theme.text_color); - font_draw(font, x + box_size + 4 * 2, y + font.em + (h - text_height) / 2, text); - - move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed); - - return result; -} - - -// -// Radio buttons -// -Radio_Theme :: struct { - use text_theme := Text_Theme.{}; - use animation_theme := Animation_Theme.{}; - - box_color := Color.{ 0.2, 0.2, 0.2 }; - box_border_width := 4.0f; // @InPixels - box_size := 20.0f; // @InPixels - - checked_color := Color.{ 0, 0, 1 }; - checked_hover_color := Color.{ 0.6, 0.6, 1 }; - - background_color := Color.{ 0.05, 0.05, 0.05 }; // Background of the checkbox button. - hover_color := Color.{ 0.3, 0.3, 0.3 }; - click_color := Color.{ 0.5, 0.5, 0.7 }; -} - -#local default_radio_theme := Radio_Theme.{}; - -draw_radio :: (use r: Rect, value: ^$T, set_to: T, text: str, theme := ^default_radio_theme, site := #callsite, increment := 0) -> bool { - result := false; - - hash := get_site_hash(site, increment); - animation_state := get_animation(hash); - mx, my := mouse_get_position(); - - contains := Rect.contains(r, .{~~mx, ~~my}); - - if is_active_item(hash) { - renew_active_item(hash); - if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) { - if is_hot_item(hash) && contains { - result = true; - *value = set_to; - animation_state.click_time = 1.0f; - } - - set_active_item(0); - } - } elseif is_hot_item(hash) { - if is_button_down(GLFW_MOUSE_BUTTON_LEFT) { - set_active_item(hash); - } - } - - if contains { - set_hot_item(hash); - } - - if is_hot_item(hash) { - move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed); - } else { - move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed); - } - - box_border_width := theme.box_border_width; - box_size := theme.box_size; - - immediate_set_color(theme.box_color); - immediate_rectangle(x + 4, y + (h - box_size) / 2, box_size, box_size); - - surface_color : Color; - if *value == set_to { - surface_color = theme.checked_color; - surface_color = color_lerp(animation_state.hover_time, surface_color, theme.checked_hover_color); - - } else { - surface_color = theme.background_color; - surface_color = color_lerp(animation_state.hover_time, surface_color, theme.hover_color); - } - - surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); - - immediate_set_color(surface_color); - immediate_rectangle(x + 4 + box_border_width, y + (h - box_size) / 2 + box_border_width, box_size - box_border_width * 2, box_size - box_border_width * 2); - - font := font_lookup(.{theme.font_name, theme.font_size}); - text_width := font_get_width(font, text); - text_height := font_get_height(font, text); - - font_set_color(theme.text_color); - font_draw(font, x + box_size + 4 * 2, y + font.em + (h - text_height) / 2, text); - - move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed); - - return result; -} - - - - -scrolling_region_start :: (r: Rect, max_y_scroll := 10000.0f, site := #callsite, increment := 0) { - hash := get_site_hash(site, increment); - mx, my := mouse_get_position(); - state := map.get_ptr(^scroll_states, hash); - if state == null { - animation_states[hash] = .{}; - state = ^scroll_states[hash]; - } - - contains := Rect.contains(r, .{~~mx, ~~my}); - if contains { - scroll_delta := mouse_get_scroll_vector(); - state.xscroll -= scroll_delta.x * 20; - state.yscroll -= scroll_delta.y * 20; - - state.yscroll = math.clamp(state.yscroll, 0, max_y_scroll); - } - - immediate_flush(); - immediate_push_scissor(r.x, r.y, r.w, r.h); - immediate_set_scroll(-state.xscroll, -state.yscroll); -} - -scrolling_region_stop :: () { - immediate_flush(); - immediate_pop_scissor(); - immediate_set_scroll(0, 0); -} - - - -#local { - hot_item : UI_Id = 0 - hot_item_was_set := false - - hot_item_depth := 0; - hot_item_depth_needed := 0; - - active_item : UI_Id = 0 - active_countdown := 0 - - set_active_item :: (id: UI_Id) -> bool { - active_item = id; - active_countdown = 2; - return true; - } - - renew_active_item :: (id: UI_Id) { - if active_item == id { - active_countdown = 2; - } - } - - set_hot_item :: (id: UI_Id, force := false) -> bool { - if active_item != 0 do return false; - - if force { - hot_item_was_set = true; - hot_item = id; - return true; - } - - hot_item_depth += 1; - if hot_item_depth >= hot_item_depth_needed { - hot_item_was_set = true; - hot_item = id; - return true; - } - - return false; - } - - is_active_item :: (id: UI_Id) -> bool { - return active_item == id; - } - - is_hot_item :: (id: UI_Id) -> bool { - return hot_item == id; - } - - Text_Theme :: struct { - text_color := Color.{1, 1, 1}; - font_name := "./assets/fonts/calibri.ttf"; - font_size := 18; - } - - Animation_Theme :: struct { - hover_speed := 0.1f; - click_decay_speed := 0.08f; - } - - Animation_State :: struct { - hover_time := 0.0f; - click_time := 0.0f; - - accessed_this_frame := false; - } - - animation_states : Map(UI_Id, Animation_State); - - get_animation :: (id: UI_Id) -> ^Animation_State { - retval := map.get_ptr(^animation_states, id); - if retval == null { - animation_states[id] = .{}; - retval = ^animation_states[id]; - } - - retval.accessed_this_frame = true; - return retval; - } - - has_active_animation :: () -> bool { - for^ animation_states.entries { - if it.value.hover_time != 0.0f || it.value.hover_time != 0.0f do return true; - if it.value.click_time != 0.0f || it.value.click_time != 0.0f do return true; - } - - return false; - } - - - Scroll_State :: struct { - xscroll: f32; - yscroll: f32; - } - - scroll_states : Map(UI_Id, Scroll_State); - - get_site_hash :: macro (site: CallSite, increment := 0) -> UI_Id { - file_hash := core.hash.to_u32(site.file); - line_hash := core.hash.to_u32(site.line); - column_hash := core.hash.to_u32(site.column); - - return ~~ (file_hash * 0x472839 + line_hash * 0x6849210 + column_hash * 0x1248382 + increment); - } - - color_lerp :: macro (t: f32, c1, c2: Color) => Color.{ - r = c1.r * (1 - t) + c2.r * t, - g = c1.g * (1 - t) + c2.g * t, - b = c1.b * (1 - t) + c2.b * t, - a = c1.a * (1 - t) + c2.a * t, - }; -} diff --git a/src/main.onyx b/src/main.onyx index d538d19..05c80aa 100644 --- a/src/main.onyx +++ b/src/main.onyx @@ -2,29 +2,17 @@ use core use opengles use glfw3 +use ogre DEBUG :: #defined(runtime.vars.DEBUG) -// @GlobalVariable -window: GLFWwindow_p - -// @GlobalVariable -window_width: u32 -window_height: u32 +window: Window #if DEBUG { debug_font: Font; } init :: () { - window = create_window(); - input_bind_glfw_events(window); - glInit(glfwGetLoadProcAddress()); - - glEnable(GL_TEXTURE); - glEnable(GL_CULL_FACE); - glFrontFace(GL_CW); - glCullFace(GL_BACK); - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + window = window_create(1200, 900, #cstr "Bar Simulator"); + window_use(^window); shaders_init(); fonts_init(); @@ -32,9 +20,6 @@ init :: () { editor_init(); game_init(); - glfwGetWindowSize(window, ^window_width, ^window_height); - update_view_matrix(window_width, window_height); - #if DEBUG { debug_font = font_lookup(.{"./assets/fonts/calibri.ttf", 16}); } } @@ -46,7 +31,7 @@ update :: (dt: f32) { input_update(); if is_key_just_up(GLFW_KEY_ESCAPE) { - glfwSetWindowShouldClose(window, true); + glfwSetWindowShouldClose(window.glfw_window, true); return; } @@ -59,7 +44,7 @@ update :: (dt: f32) { draw :: () { immediate_clear(.{0.15, 0.15, 0.2}); - defer ui_end_frame(); + defer ui.ui_end_frame(); defer input_post_update(); defer { immediate_flush(); @@ -73,10 +58,10 @@ draw :: () { version_buf : [32] u8; version_str := conv.format(version_buf, "Version: {}.{}", runtime.vars.MAJOR_VERSION, runtime.vars.MINOR_VERSION); - font_print(debug_font, ~~window_width - font_get_width(debug_font, version_str), 16, version_str); + font_print(debug_font, ~~window.width - font_get_width(debug_font, version_str), 16, version_str); } - glfwSwapBuffers(window); + glfwSwapBuffers(window.glfw_window); } game_draw(); @@ -100,7 +85,7 @@ run :: () { seconds := 0.0; frame_count := 0; - while !glfwWindowShouldClose(window) { + while !glfwWindowShouldClose(window.glfw_window) { glfwPollEvents(); now = glfwGetTime(); @@ -121,29 +106,6 @@ run :: () { } } -create_window :: () => { - #if runtime.compiler_os == .Linux { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); - } else { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - } - window := glfwCreateWindow(1200, 900, #cstr "Bar simulator"); - - glfwMakeContextCurrent(window); - glfwSwapInterval(1); - glfwSetWindowSizeCallback(window, "on_resize"); - return window; -} - -#export "on_resize" (window: GLFWwindow_p, width, height: u32) { - window_width = width; - window_height = height; - update_view_matrix(width, height); -} - main :: () { random.set_seed(os.time()); diff --git a/src/ogre/canvas.onyx b/src/ogre/canvas.onyx new file mode 100644 index 0000000..7e85764 --- /dev/null +++ b/src/ogre/canvas.onyx @@ -0,0 +1,62 @@ +package ogre + +use core +use opengles + + +Canvas :: struct { + width, height: i32; + + framebuffer: GLint; + depth_stencil_buffer: GLint; + color_texture: GLint; +} + +canvas_make :: (width, height: i32) -> Canvas { + canvas: Canvas; + canvas.width = width; + canvas.height = height; + + glGenFramebuffers(1, ^canvas.framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, canvas.framebuffer); + + glGenTextures(1, ^canvas.color_texture); + glBindTexture(GL_TEXTURE_2D, canvas.color_texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, null); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas.color_texture, 0); + + glGenRenderbuffers(1, ^canvas.depth_stencil_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, canvas.depth_stencil_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, canvas.depth_stencil_buffer); + + if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE { + printf("[ERROR] Framebuffer is not complete!\n"); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + return canvas; +} + +canvas_free :: (use canvas: ^Canvas) { + glDeleteFramebuffers(1, ^canvas.framebuffer); + glDeleteTextures(1, ^canvas.color_texture); + glDeleteRenderbuffers(1, ^canvas.depth_stencil_buffer); +} + +canvas_use :: (use canvas: ^Canvas) { + if canvas == null { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + update_view_matrix(global_window.width, global_window.height); + } else { + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + update_view_matrix(width, height); + } +} + +canvas_to_texture :: (canvas: ^Canvas) => Texture.{ canvas.color_texture, canvas.width, canvas.height, 3, "" }; + diff --git a/src/ogre/colors.onyx b/src/ogre/colors.onyx new file mode 100644 index 0000000..9a7cceb --- /dev/null +++ b/src/ogre/colors.onyx @@ -0,0 +1,56 @@ +package ogre + +use core + +Color :: struct { + r, g, b : f32; + a := 1.0f; +} + +color_to_hsl :: (c: Color) -> (h: f32, s: f32, l: f32) { + r := c.r / 255; + g := c.g / 255; + b := c.b / 255; + cmax := math.max(math.max(r, g), b); + cmin := math.min(math.min(r, g), b); + delta := cmax - cmin; + + h := 0.0f; + if cmax == r { + m := (g - b) / delta; + while m >= 6 do m -= 6; + while m < 0 do m += 6; + h = 60 * m; + } + if cmax == g do h = 60 * (b - r) / delta + 2; + if cmax == b do h = 60 * (r - g) / delta + 4; + + l := (cmax + cmin) / 2; + s := delta / (1 - math.abs(2 * l - 1)); + + return h, s, l; +} + +hsl_to_color :: (h, s, l: f32) -> Color { + c := (1 - math.abs(2 * l - 1)) * s; + + h_term := h / 60; + while h_term < 0 do h_term += 2; + while h_term >= 0 do h_term -= 2; + + x := c * (1 - math.abs(h_term - 1)); + m := l - c / 2; + + r, g, b: f32; + if 0 <= h && h < 60 { r = c; g = x; b = 0; } + if 60 <= h && h < 120 { r = x; g = c; b = 0; } + if 120 <= h && h < 180 { r = 0; g = c; b = x; } + if 180 <= h && h < 240 { r = 0; g = x; b = c; } + if 240 <= h && h < 300 { r = x; g = 0; b = c; } + if 300 <= h && h < 360 { r = c; g = 0; b = x; } + + r = (r + m) * 255; + g = (g + m) * 255; + b = (b + m) * 255; + return .{ r, g, b, 1 }; +} diff --git a/src/ogre/font.onyx b/src/ogre/font.onyx new file mode 100644 index 0000000..9a9f090 --- /dev/null +++ b/src/ogre/font.onyx @@ -0,0 +1,267 @@ +package ogre + +use core +use stb_truetype +use opengles + +#local { + font_registry: Map(FontDescriptor, Font); + font_vbo: GLint; + font_vao: GLint; + + font_shader: Shader; + font_color: Color; +} + +fonts_init :: () { + map.init(^font_registry); + + font_shader = shader_make("./assets/shaders/font.glsl"); + shader_use(font_shader); + shader_link_window_matrix_block(font_shader); + shader_link_world_matrix_block(font_shader); + + glGenVertexArrays(1, ^font_vao); + glBindVertexArray(font_vao); + + font_interp_buffer: GLint; + glGenBuffers(1, ^font_interp_buffer); + glBindBuffer(GL_ARRAY_BUFFER, font_interp_buffer); + font_interp_data := f32.[ + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + ]; + glBufferData(GL_ARRAY_BUFFER, sizeof typeof font_interp_data, ~~^font_interp_data, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, false, 2 * sizeof f32, ~~0); + + font_index_buffer: GLint; + glGenBuffers(1, ^font_index_buffer); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_index_buffer); + font_index_data := u8.[ 0, 1, 2, 0, 2, 3 ]; + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof typeof font_index_data, ~~^font_index_data, GL_STATIC_DRAW); + + glGenBuffers(1, ^font_vbo); + glBindBuffer(GL_ARRAY_BUFFER, font_vbo); + glBufferData(GL_ARRAY_BUFFER, 1024 * sizeof stbtt_aligned_quad, null, GL_STREAM_DRAW); + + for 1..5 { + glEnableVertexAttribArray(it); + glVertexAttribDivisor(it, 1); + } + glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~ 0); + glVertexAttribPointer(2, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~16); + glVertexAttribPointer(3, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~8); + glVertexAttribPointer(4, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~24); + + glBindBuffer(GL_ARRAY_BUFFER, -1); + glBindVertexArray(-1); + + font_set_color(.{0,0,0}); +} + + +Font :: struct { + texture: GLint; + texture_width, texture_height: i32; + chars: [] stbtt_packedchar; + em: f32; +} + +font_make :: (fd: FontDescriptor) -> Font { + texture_size :: 256; + + char_data := memory.make_slice(stbtt_packedchar, 96); + + ttf_file := os.get_contents(fd.path); + if ttf_file.count == 0 { + println("Bad font"); + return .{}; + } + defer cfree(ttf_file.data); + + pixels := calloc(texture_size * texture_size); + defer cfree(pixels); + + ctx: stbtt_pack_context; + stbtt_PackBegin(^ctx, pixels, texture_size, texture_size, 0, 1); + stbtt_PackFontRange(^ctx, ttf_file.data, 0, ~~fd.size, #char " ", 96, char_data.data); + stbtt_PackEnd(^ctx); + + texture: GLint; + glGenTextures(1, ^texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture_size, texture_size, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + font := Font.{ + texture = texture, + texture_width = texture_size, + texture_height = texture_size, + chars = char_data, + em = ~~fd.size, + }; + + font_registry[fd] = font; + + return font; +} + +font_set_color :: (color: Color) { + font_color = color; +} + +font_print :: (font: Font, x, y: f32, format: str, va: ..any) { + buf: [1024] u8; + msg := conv.format_va(buf, format, va); + font_draw(font, x, y, msg); +} + +font_draw :: (font: Font, x, y: f32, msg: str) { + quads: ^stbtt_aligned_quad = alloc.from_stack(msg.count * sizeof stbtt_aligned_quad); + quad_num := 0; + + x_, y_ := x, y; + + for msg { + if it == #char "\n" { + x_ = x; + y_ += font.em + 2; + } + + stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, + ~~(it - #char " "), ^x_, ^y_, ^quads[quad_num], false); + + quad_num += 1; + } + + font_render(font, quads[0 .. quad_num]); +} + +font_draw_centered :: (font: Font, x, y, max_width: f32, msg: str) { + quads: ^stbtt_aligned_quad = alloc.from_stack(msg.count * sizeof stbtt_aligned_quad); + quad_num := 0; + + width := font_get_width(font, msg); + x_, y_ := x, y; + x_ = (max_width - width) / 2 + x; + + for msg { + if it == #char "\n" { + x_ = (max_width - width) / 2 + x; + y_ += font.em + 2; + } + + stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, + ~~(it - #char " "), ^x_, ^y_, ^quads[quad_num], false); + + quad_num += 1; + } + + font_render(font, quads[0 .. quad_num]); +} + +#local font_render :: (font: Font, quads: [] stbtt_aligned_quad) { + // If this is being used in conjunction with the immediate + // rendering system, make sure the immediate objects are flushed + // before trying to render over them. + #if #defined(immediate_flush) { + immediate_flush(); + } + + glBindBuffer(GL_ARRAY_BUFFER, font_vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, quads.count * sizeof stbtt_aligned_quad, quads.data); + glBindBuffer(GL_ARRAY_BUFFER, -1); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, font.texture); + shader_use(font_shader); + shader_set_uniform(font_shader, #cstr "u_texture", 0); + shader_set_uniform(font_shader, #cstr "u_color", font_color); + + glDisable(GL_DEPTH_TEST); + glBindVertexArray(font_vao); + glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, ~~0, quads.count); + glBindTexture(GL_TEXTURE_2D, -1); + glBindVertexArray(-1); +} + +font_get_width :: (font: Font, msg: str) -> f32 { + x_, y_ := 0.0f, 0.0f; + width := 0.0f; + + quad: stbtt_aligned_quad; + for msg { + if it == #char "\n" { + width = math.max(width, x_); + x_ = 0; + y_ += font.em + 2; + continue; + } + + stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, + ~~(it - #char " "), ^x_, ^y_, ^quad, false); + } + + return math.max(x_, width); +} + +font_get_height :: (font: Font, msg: str) -> f32 { + x_, y_ := 0.0f, 0.0f; + width := 0.0f; + + quad: stbtt_aligned_quad; + for msg { + if it == #char "\n" { + width = math.max(width, x_); + x_ = 0; + y_ += font.em + 2; + continue; + } + + stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, + ~~(it - #char " "), ^x_, ^y_, ^quad, false); + } + + return y_ + font.em + 2; +} + +font_get_char_width :: (font: Font, ch: u8) -> f32 { + x, y := 0.0f, 0.0f; + quad: stbtt_aligned_quad; + + stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height, + ~~(ch - #char " "), ^x, ^y, ^quad, false); + + return x; +} + + +FontDescriptor :: struct { + path : str; + size : u32; +} + +#match hash.to_u32 (fd: FontDescriptor) => { + name_hash := hash.to_u32(fd.path); + size_hash := hash.to_u32(fd.size); + return name_hash * 13 + size_hash * 17; +} + +#operator == (f1, f2: FontDescriptor) => f1.path == f2.path && f1.size == f2.size; + +font_lookup :: (fd := FontDescriptor.{ "assets/calibri.ttf", 12 }) -> Font { + if font_registry->has(fd) { + return font_registry[fd]; + } + + font := font_make(fd); + font_registry[fd] = font; + return font; +} diff --git a/src/ogre/immediate.onyx b/src/ogre/immediate.onyx new file mode 100644 index 0000000..0cd0505 --- /dev/null +++ b/src/ogre/immediate.onyx @@ -0,0 +1,219 @@ +package ogre + +use core +use opengles +use glfw3 + +immediate_init :: () { + memory.alloc_slice(^vertex_data, Maximum_Vertex_Count); + vertex_count = 0; + + imgui_shader = shader_make(Shader_Path); + shader_use(imgui_shader); + shader_link_window_matrix_block(imgui_shader); + shader_link_world_matrix_block(imgui_shader); + shader_set_uniform(imgui_shader, #cstr "u_texture_enabled", 0.0f); + shader_set_uniform(imgui_shader, #cstr "u_texture", 0); + + immediate_mesh = mesh_make(vertex_data, .[], GL_DYNAMIC_DRAW); + immediate_color = .{0,0,0}; +} + +immediate_flush :: (set_shader := true) { + if vertex_count == 0 do return; + + if set_shader { + shader_use(imgui_shader); + shader_set_uniform(imgui_shader, #cstr "u_texture_enabled", 1.0f if rendering_type == .Image else 0.0f); + } + + immediate_mesh.vertex_count = vertex_count; + mesh_update_verticies(immediate_mesh, vertex_data); + + mesh_draw(immediate_mesh); + + vertex_count = 0; + rendering_type = .Plain; + immediate_mesh.primitive = GL_TRIANGLES; +} + +immediate_clear :: (color: Color) { + glClearColor(color.r, color.g, color.b, color.a); + glClear(GL_COLOR_BUFFER_BIT); +} + +immediate_set_color :: (color: Color) { + immediate_color = color; +} + +immediate_vertex :: (x, y: f32, t_x := 0.0f, t_y := 0.0f) { + if vertex_count >= Maximum_Vertex_Count do immediate_flush(); + set_rendering_type(.Plain); + + vertex_data[vertex_count] = .{ .{x, y}, .{t_x, t_y}, immediate_color }; +} + +immediate_line :: (x1, y1, x2, y2: f32) { + if vertex_count >= Maximum_Vertex_Count do immediate_flush(); + set_rendering_type(.Line); + immediate_mesh.primitive = GL_LINES; + + vertex_data[vertex_count + 0] = .{ .{x1, y1}, .{0, 0}, immediate_color }; + vertex_data[vertex_count + 1] = .{ .{x2, y2}, .{0, 0}, immediate_color }; + vertex_count += 2; +} + +immediate_triangle :: (x1, x2, x3: Vector2) { + if vertex_count + 3 > Maximum_Vertex_Count do immediate_flush(); + set_rendering_type(.Plain); + + vertex_data[vertex_count + 0] = .{ x1, .{0,0}, immediate_color }; + vertex_data[vertex_count + 1] = .{ x2, .{0,0}, immediate_color }; + vertex_data[vertex_count + 2] = .{ x3, .{0,0}, immediate_color }; + vertex_count += 3; +} + +immediate_rectangle :: (x, y, w, h: f32) { + if vertex_count + 6 > Maximum_Vertex_Count do immediate_flush(); + set_rendering_type(.Plain); + + vertex_data[vertex_count + 0] = .{ .{x, y}, .{0,0}, immediate_color }; + vertex_data[vertex_count + 1] = .{ .{x+w, y}, .{0,0}, immediate_color }; + vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{0,0}, immediate_color }; + vertex_data[vertex_count + 3] = .{ .{x, y}, .{0,0}, immediate_color }; + vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{0,0}, immediate_color }; + vertex_data[vertex_count + 5] = .{ .{x, y+h}, .{0,0}, immediate_color }; + vertex_count += 6; +} + +immediate_image :: (image: ^Texture, x, y, w, h: f32) { + if vertex_count > 0 do immediate_flush(); + + set_rendering_type(.Image); + texture_use(image); + shader_use(imgui_shader); + shader_set_uniform(imgui_shader, #cstr "u_texture", 0); + + vertex_data[vertex_count + 0] = .{ .{x, y}, .{0,0}, immediate_color }; + vertex_data[vertex_count + 1] = .{ .{x+w, y}, .{1,0}, immediate_color }; + vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{1,1}, immediate_color }; + vertex_data[vertex_count + 3] = .{ .{x, y}, .{0,0}, immediate_color }; + vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{1,1}, immediate_color }; + vertex_data[vertex_count + 5] = .{ .{x, y+h}, .{0,1}, immediate_color }; + vertex_count += 6; +} + +immediate_subimage :: (image: ^Texture, x, y, w, h: f32, tx, ty, tw, th: f32) { + if vertex_count > 0 do immediate_flush(); + + set_rendering_type(.Image); + texture_use(image); + shader_use(imgui_shader); + shader_set_uniform(imgui_shader, #cstr "u_texture", 0); + + sx := tx / ~~ image.width; + sy := ty / ~~ image.height; + sw := tw / ~~ image.width; + sh := th / ~~ image.height; + + vertex_data[vertex_count + 0] = .{ .{x, y}, .{sx, sy}, immediate_color }; + vertex_data[vertex_count + 1] = .{ .{x+w, y}, .{sx+sw, sy}, immediate_color }; + vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{sx+sw,sy+sh}, immediate_color }; + vertex_data[vertex_count + 3] = .{ .{x, y}, .{sx, sy}, immediate_color }; + vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{sx+sw,sy+sh}, immediate_color }; + vertex_data[vertex_count + 5] = .{ .{x, y+h}, .{sx, sy+sh}, immediate_color }; + vertex_count += 6; +} + +immediate_ellipse :: () {} + +immediate_push_scissor :: (x, y, w, h: f32) { + if vertex_count > 0 do immediate_flush(); + + // Assuming that x, y, w, and h are in screen (window) coordinates. + x += offset.x; + y += offset.y; + + xi := cast(i32) x; + yi := cast(i32) y; + wi := cast(i32) w; + hi := cast(i32) h; + scissors << .{xi, yi, wi, hi}; + + glEnable(GL_SCISSOR_TEST); + glScissor(xi, global_window.height - yi - hi, wi, hi); +} + +immediate_pop_scissor :: () { + if scissors.count > 0 { + array.pop(^scissors); + } + + if scissors.count > 0 { + glEnable(GL_SCISSOR_TEST); + s := scissors[scissors.count - 1]; + glScissor(s.x, global_window.height - s.y - s.h, s.w, s.h); + } else { + glDisable(GL_SCISSOR_TEST); + } +} + +immediate_set_scroll :: (scroll_x, scroll_y: f32) { + offset = .{ scroll_x, scroll_y }; + update_model_matrix(offset); +} + +immediate_get_scroll :: () => offset; + +Immediate_Vertex :: struct { + pos: Vector2; + tex: Vector2; + color: Color; +} + +#local { + Shader_Path :: "./assets/shaders/imgui.glsl" + imgui_shader: Shader; + + Maximum_Vertex_Count :: 1023; + vertex_count: i32; + vertex_data: [] Immediate_Vertex; + + immediate_color: Color; + + immediate_mesh: ^Mesh(Immediate_Vertex); + + Rendering_Type :: enum { + Plain; + Line; + Image; + } + rendering_type := Rendering_Type.Plain; + + Scissor :: struct { + x, y, w, h: i32; + } + scissors: [..] Scissor; + + offset: Vector2; + + set_rendering_type :: (new_type: typeof rendering_type) { + if rendering_type != new_type { + immediate_flush(); + rendering_type = new_type; + } + } +} + + + +// @Relocate +move_towards :: (v: ^$T, target: T, diff: T) { + if math.abs(target - *v) <= diff { + *v = target; + return; + } + + if *v < target do *v += diff; + if *v > target do *v -= diff; +} diff --git a/src/ogre/input.onyx b/src/ogre/input.onyx new file mode 100644 index 0000000..38d656e --- /dev/null +++ b/src/ogre/input.onyx @@ -0,0 +1,186 @@ +package ogre + +use package core +use package glfw3 + +// If you are offseting the mouse coordinate for world space +// or UI scrolling etc., set this function to be the function +// to ask for the current offset. Currently scaling the mouse +// coordinate is not supported, only linear translations. +Mouse_Offset_Function :: immediate_get_scroll + + +#local { + keys_this_frame : Set(Key_Descriptor) // Keys currently being pressed this frame + keys_last_frame : Set(Key_Descriptor) // Keys being pressed in the last frame + + key_codepoints : [..] u32 + + buttons_this_frame: [8] bool // Mouse buttons being pressed this frame + buttons_last_frame: [8] bool // Mouse buttons being pressed last frame + + scroll_x: f64 + scroll_y: f64 + + character_mode := false; +} + +#init () { + set.init(^keys_this_frame); + set.init(^keys_last_frame); +} + +Key_Descriptor :: struct { + key: u32; + scancode: u32 = 0; + mod: u32 = 0; +} +#match hash.to_u32 (use x: Key_Descriptor) => hash.to_u32(key); +#operator == (x, y: Key_Descriptor) => x.key == y.key; + +input_update :: () { + glfwGetCursorPos(global_window.glfw_window, ^mouse_x, ^mouse_y); +} + +input_post_update :: () { + set.clear(^keys_last_frame); + for keys_this_frame.entries do keys_last_frame << it.value; + + for 8 do buttons_last_frame[it] = buttons_this_frame[it]; + + last_mouse_x = mouse_x; + last_mouse_y = mouse_y; + scroll_x = 0; + scroll_y = 0; + + array.clear(^key_codepoints); +} + +input_get_chars_this_frame :: () -> [] u32 { + return key_codepoints; +} + +input_get_keys_this_frame :: () -> Iterator(Key_Descriptor) { + #persist index := 0; + + next :: (_: rawptr) -> (Key_Descriptor, bool) { + if index >= keys_this_frame.entries.count do return .{0}, false; + + while keys_last_frame->has(keys_this_frame.entries[index].value) { + index += 1; + if index >= keys_this_frame.entries.count do return .{0}, false; + } + + defer index += 1; + return keys_this_frame.entries[index].value, true; + } + + index = 0; + return .{ null, next }; +} + +input_capture_keys :: () { + character_mode = true; +} + +input_release_keys :: () { + character_mode = false; +} + +is_key_down :: (key) => !character_mode && set.has(^keys_this_frame, .{key}); +is_key_just_down :: (key) => !character_mode && set.has(^keys_this_frame, .{key}) && !set.has(^keys_last_frame, .{key}); +is_key_just_up :: (key) => !character_mode && !set.has(^keys_this_frame, .{key}) && set.has(^keys_last_frame, .{key}); + +is_button_down :: (button) => buttons_this_frame[button]; +is_button_just_down :: (button) => buttons_this_frame[button] && !buttons_last_frame[button]; +is_button_just_up :: (button) => !buttons_this_frame[button] && buttons_last_frame[button]; + +#local { + last_mouse_x: f64; + last_mouse_y: f64; + + mouse_x: f64; + mouse_y: f64; +} + +mouse_get_delta :: () -> (f64, f64) { + return mouse_x - last_mouse_x, mouse_y - last_mouse_y; +} + +mouse_get_delta_vector :: () -> Vector2 { + dmx, dmy := mouse_get_delta(); + return .{ ~~dmx, ~~dmy }; +} + +mouse_get_position :: () -> (f64, f64) { + mx, my := mouse_x, mouse_y; + #if #defined(Mouse_Offset_Function) { + scroll := Mouse_Offset_Function(); + mx -= ~~ scroll.x; + my -= ~~ scroll.y; + } + return mx, my; +} + +mouse_get_position_vector :: () -> Vector2 { + mx, my := mouse_x, mouse_y; + #if #defined(Mouse_Offset_Function) { + scroll := Mouse_Offset_Function(); + mx -= ~~ scroll.x; + my -= ~~ scroll.y; + } + return .{ ~~mx, ~~my }; +} + +mouse_get_scroll :: () -> (f64, f64) { + return scroll_x, scroll_y; +} + +mouse_get_scroll_vector :: () -> Vector2 { + return .{ ~~scroll_x, ~~scroll_y }; +} + +input_bind_glfw_events :: (window: GLFWwindow_p) { + glfwSetKeyCallback(window, #export_name input_key_event); + glfwSetCharCallback(window, #export_name input_char_event); + glfwSetMouseButtonCallback(window, #export_name input_button_event); + glfwSetScrollCallback(window, #export_name input_scroll_event); +} + +#local +input_button_event :: (window: GLFWwindow_p, button, action, mod: u32) { + if action == GLFW_PRESS { + buttons_this_frame[button] = true; + } + + if action == GLFW_RELEASE { + buttons_this_frame[button] = false; + } +} + +#local +input_key_event :: (window: GLFWwindow_p, key, scancode, action, mod: u32) { + if action == GLFW_PRESS { + keys_this_frame << .{ key, scancode, mod }; + } + + if action == GLFW_REPEAT { + set.remove(^keys_last_frame, .{key}); + } + + if action == GLFW_RELEASE { + set.remove(^keys_this_frame, .{ key }); + } +} + +#local +input_char_event :: (window: GLFWwindow_p, codepoint: u32) { + if !character_mode do return; + key_codepoints << codepoint; +} + +#local +input_scroll_event :: (window: GLFWwindow_p, xoff, yoff: f64) { + scroll_x = xoff; + scroll_y = yoff; +} diff --git a/src/ogre/mesh.onyx b/src/ogre/mesh.onyx new file mode 100644 index 0000000..ba926aa --- /dev/null +++ b/src/ogre/mesh.onyx @@ -0,0 +1,90 @@ +package ogre + +use core +use opengles + +Mesh :: struct (Vertex_Type: type_expr) { + handle: GLint; + vertex_handle: GLint; + index_handle: GLint; + + vertex_count: u32; + index_count: u32; + primitive: GLuint = GL_TRIANGLES; +} + +mesh_make :: (verticies: [] $T, indicies: [] u32, gl_hint := GL_STATIC_DRAW) -> ^Mesh(T) { + mesh := new(Mesh(T)); + mesh.vertex_count = verticies.count; + mesh.index_count = indicies.count; + + glGenVertexArrays(1, ^mesh.handle); + glBindVertexArray(mesh.handle); + + glGenBuffers(1, ^mesh.vertex_handle); + glBindBuffer(GL_ARRAY_BUFFER, mesh.vertex_handle); + glBufferData(GL_ARRAY_BUFFER, sizeof T * verticies.count, verticies.data, gl_hint); + + type_info :: runtime.info + vertex_info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(T); + vertex_attr := 0; + for attr: vertex_info.members { + defer vertex_attr += 1; + glEnableVertexAttribArray(vertex_attr); + + switch attr.type { + case Vector2 do glVertexAttribPointer(vertex_attr, 2, GL_FLOAT, false, sizeof T, ~~attr.offset); + case Vector3 do glVertexAttribPointer(vertex_attr, 3, GL_FLOAT, false, sizeof T, ~~attr.offset); + case u32 do glVertexAttribIPointer(vertex_attr, 1, GL_UNSIGNED_INT, sizeof T, ~~attr.offset); + case i32 do glVertexAttribIPointer(vertex_attr, 1, GL_INT, sizeof T, ~~attr.offset); + + // It would be nice to not have to have all the cases here. + // Instead allow for an extensible way of defining them at compile time. + case Color do glVertexAttribPointer(vertex_attr, 4, GL_FLOAT, false, sizeof T, ~~attr.offset); + + case #default { + buf: [256] u8; + msg := conv.str_format(buf, "Unknown type for GL vertex attribute, {}.", attr.type); + assert(false, msg); + } + } + } + + if indicies.count > 0 { + glGenBuffers(1, ^mesh.index_handle); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.index_handle); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof i32 * indicies.count, indicies.data, gl_hint); + } + + glBindVertexArray(-1); + + return mesh; +} + +mesh_update_verticies :: (use mesh: ^Mesh, verticies: [] mesh.Vertex_Type) { + // @TODO // Add bounds checking to arrays here. + + glBindBuffer(GL_ARRAY_BUFFER, vertex_handle); + glBufferSubData(GL_ARRAY_BUFFER, 0, verticies.count * sizeof mesh.Vertex_Type, verticies.data); + glBindBuffer(GL_ARRAY_BUFFER, -1); +} + +mesh_draw :: (use mesh: ^Mesh) { + glBindVertexArray(handle); + if index_count > 0 { + glDrawElements(primitive, index_count, GL_UNSIGNED_INT, ~~0); + } else { + glDrawArrays(primitive, 0, vertex_count); + } + glBindVertexArray(-1); +} + +mesh_free :: (mesh: ^Mesh) { + glDeleteBuffers(1, ^mesh.vertex_handle); + glDeleteBuffers(1, ^mesh.index_handle); + glDeleteVertexArrays(1, ^mesh.handle); + cfree(mesh); +} + + + diff --git a/src/ogre/shader.onyx b/src/ogre/shader.onyx new file mode 100644 index 0000000..671e583 --- /dev/null +++ b/src/ogre/shader.onyx @@ -0,0 +1,182 @@ +package ogre + +use core +use opengles + +Shader :: struct { + vs, fs: GLuint; + prog: GLuint; +} + +window_matrix_block_buffer: GLuint; +world_matrix_block_buffer: GLuint; + +shaders_init :: () { + glGenBuffers(1, ^window_matrix_block_buffer); + glGenBuffers(1, ^world_matrix_block_buffer); + + glBindBuffer(GL_UNIFORM_BUFFER, window_matrix_block_buffer); + glBufferData(GL_UNIFORM_BUFFER, sizeof f32 * 16, null, GL_DYNAMIC_DRAW); + + glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer); + glBufferData(GL_UNIFORM_BUFFER, sizeof f32 * (16 + 16), null, GL_DYNAMIC_DRAW); + + glBindBuffer(GL_UNIFORM_BUFFER, -1); + + glBindBufferBase(GL_UNIFORM_BUFFER, WINDOW_MATRIX_BLOCK, window_matrix_block_buffer); + glBindBufferBase(GL_UNIFORM_BUFFER, WORLD_MATRIX_BLOCK, world_matrix_block_buffer); +} + +shader_make :: (shader_path: str) -> Shader { + shader_source := os.get_contents(shader_path); + vs := compile_shader(shader_source, GL_VERTEX_SHADER); + fs := compile_shader(shader_source, GL_FRAGMENT_SHADER); + + prog := link_program(vs, fs); + + return Shader.{vs, fs, prog}; +} + +shader_use :: (shader: Shader) { + glUseProgram(shader.prog); +} + +#local { + WINDOW_MATRIX_BLOCK :: 0; + WORLD_MATRIX_BLOCK :: 1; +} + +shader_link_window_matrix_block :: (use shader: Shader) { + matrix_block_index := glGetUniformBlockIndex(prog, #cstr "u_window_matrix_block"); + glUniformBlockBinding(prog, matrix_block_index, WINDOW_MATRIX_BLOCK); +} + +shader_link_world_matrix_block :: (use shader: Shader) { + matrix_block_index := glGetUniformBlockIndex(prog, #cstr "u_world_matrix_block"); + glUniformBlockBinding(prog, matrix_block_index, WORLD_MATRIX_BLOCK); +} + +shader_set_uniform :: (shader: Shader, uniform: cstr, value: $T) { + glUseProgram(shader.prog); + location := glGetUniformLocation(shader.prog, uniform); + + set_uniform_internal(location, value); + + set_uniform_internal :: #match { + macro (location: GLint, value: u32) do glUniform1i(location, value); , + macro (location: GLint, value: f32) do glUniform1f(location, value); , + macro (location: GLint, value: Vector2) do glUniform2f(location, value.x, value.y); , + macro (location: GLint, value: Vector3) do glUniform3f(location, value.x, value.y, value.z); , + macro (location: GLint, value: Color) do glUniform4f(location, value.r, value.g, value.b, value.a); , + + macro (location: GLint, value: $T) { + buffer: [1024] u8; + assert(false, conv.format(buffer, "Bad shader_set_uniform case: {}", T)); + } + } +} + +update_view_matrix :: (width, height: u32) { + matrix : [16] f32; + top := 0.0f; + left := 0.0f; + right := cast(f32) width; + bottom := cast(f32) height; + far := 10.0f; + near := 0f; + + matrix[0] = 2 / (right - left); + matrix[5] = 2 / (top - bottom); + matrix[10] = -2 / (far - near); + matrix[12] = -(right + left) / (right - left); + matrix[13] = -(top + bottom) / (top - bottom); + matrix[14] = -(far + near) / (far - near); + matrix[15] = 1; + + glBindBuffer(GL_UNIFORM_BUFFER, window_matrix_block_buffer); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof typeof matrix, ^matrix); + glBindBuffer(GL_UNIFORM_BUFFER, -1); + + glViewport(0, 0, width, height); +} + +update_world_matrix :: () { + world_mat: [16] f32; + world_mat[0] = 1; + world_mat[5] = 1; + world_mat[10] = 1; + world_mat[15] = 1; + + glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer); + glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof typeof world_mat, ^world_mat); + glBindBuffer(GL_UNIFORM_BUFFER, -1); +} + +update_model_matrix :: (v: Vector2) { + model_mat: [16] f32; + model_mat[0] = 1; + model_mat[5] = 1; + model_mat[10] = 1; + model_mat[12] = v.x; + model_mat[13] = v.y; + model_mat[14] = 0; + model_mat[15] = 1; + + glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer); + glBufferSubData(GL_UNIFORM_BUFFER, 16 * sizeof f32, sizeof typeof model_mat, ^model_mat); + glBindBuffer(GL_UNIFORM_BUFFER, -1); +} + + +#local { + compile_shader :: (source: str, type: GLenum) -> GLint { + shader := glCreateShader(type); + + #persist VERTEX_HEADER := """ +#version 300 es +#define VERTEX_SHADER 1 +#define COMM out + """; + + #persist FRAGMENT_HEADER := """ +#version 300 es +#define FRAGMENT_SHADER 1 +#define COMM in + """; + + header := VERTEX_HEADER if type == GL_VERTEX_SHADER else FRAGMENT_HEADER; + sources : [] cptr(u8) = .[ cptr.make(header.data), cptr.make(source.data) ]; + source_lens : [] i32 = .[ header.count, source.count ]; + + glShaderSource(shader, 2, sources.data, source_lens.data); + glCompileShader(shader); + + success: GLint; + if glGetShaderiv(shader, GL_COMPILE_STATUS, ^success); success == GL_FALSE { + buf_data: [2048] u8; + buf := str.{ ~~buf_data, 0 }; + glGetShaderInfoLog(shader, 2048, ^buf.count, buf.data); + println(buf); + } + + return shader; + } + + link_program :: (vertex_shader, fragment_shader: GLint) -> GLuint { + prog := glCreateProgram(); + glAttachShader(prog, vertex_shader); + glAttachShader(prog, fragment_shader); + glLinkProgram(prog); + + success: GLint; + if glGetProgramiv(prog, GL_LINK_STATUS, ^success); success == GL_FALSE { + buf_data: [1024] u8; + buf := str.{ ~~buf_data, 0 }; + glGetProgramInfoLog(prog, 1024, ^buf.count, buf.data); + println(buf); + } + + return prog; + } +} + diff --git a/src/ogre/texture.onyx b/src/ogre/texture.onyx new file mode 100644 index 0000000..64773cc --- /dev/null +++ b/src/ogre/texture.onyx @@ -0,0 +1,88 @@ +package ogre + +use core +use opengles +use stb_image + +#local texture_cache: Map(str, Texture); + +Texture :: struct { + texture: GLint; + width, height, channels: i32; + filename: str; +} + +texture_lookup :: #match {} +#match texture_lookup (filename: str) -> (Texture, bool) { + if texture_cache->has(filename) { + return texture_cache[filename], true; + } + + buffer: [512] u8; + memory.copy(~~ buffer, filename.data, math.min(filename.count, 511)); + return texture_lookup(cast(cstr) buffer); +} + +#match texture_lookup (path: cstr) -> (Texture, bool) { + filename := string.from_cstr(path); + if texture_cache->has(filename) { + return texture_cache[filename], true; + } + + tex: Texture; + tex.filename = filename; + pixels := stbi_load(path, ^tex.width, ^tex.height, ^tex.channels, 4); + if pixels == null { + printf("[WARN ] Failed to load texture: {}\n", filename); + return .{}, false; + } + defer stbi_image_free(pixels); + + glGenTextures(1, ^tex.texture); + glBindTexture(GL_TEXTURE_2D, tex.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Are these sensible defaults? + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + // This assumes that the filename data is going to be persistant forever. + // Not a great assumption to make but is it really worth copying it? + texture_cache[filename] = tex; + printf("[INFO ] Loaded texture: {}\n", filename); + return tex, true; +} + +texture_free :: (use tex: ^Texture) { + glDeleteTextures(1, ^texture); +} + +texture_use :: (use tex: ^Texture, texture_binding := 0) { + glActiveTexture(GL_TEXTURE0 + texture_binding); + glBindTexture(GL_TEXTURE_2D, texture); +} + + +Texture_Wrap :: enum { + Clamp :: GL_CLAMP_TO_EDGE | 0; + Repeat :: GL_REPEAT | 0; + Mirrored_Repeat :: GL_MIRRORED_REPEAT | 0; +} + +texture_wrap :: (use tex: ^Texture, wrap: Texture_Wrap) { + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ~~ wrap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ~~ wrap); +} + +texture_cache_clear :: () { + for^ texture_cache.entries { + glDeleteTextures(1, ^it.value.texture); + } + + map.clear(^texture_cache); +} diff --git a/src/ogre/ui.onyx b/src/ogre/ui.onyx new file mode 100644 index 0000000..1242f11 --- /dev/null +++ b/src/ogre/ui.onyx @@ -0,0 +1,731 @@ +package ogre.ui + +// +// Very simple immediate mode UI +// + +use core +use opengles +use glfw3 +use ogre + +UI_Id :: u32 + +ui_end_frame :: () { + hot_item_depth_needed = hot_item_depth; + if !hot_item_was_set do set_hot_item(0); + hot_item_depth = 0; + hot_item_was_set = false; + + for^ animation_states.entries { + if !it.value.accessed_this_frame || (it.value.click_time == 0 && it.value.hover_time == 0) { + map.delete(^animation_states, it.key); + } + } + + for^ animation_states.entries { + it.value.accessed_this_frame = false; + } + + if active_countdown > 0 { + active_countdown -= 1; + if active_countdown == 0 { + set_active_item(0); + input_release_keys(); + } + } +} + +// +// Buttons +// +Button_Theme :: struct { + use text_theme := Text_Theme.{}; + use animation_theme := Animation_Theme.{}; + + background_color := Color.{ 0.1, 0.1, 0.1 }; + hover_color := Color.{ 0.3, 0.3, 0.3 }; + click_color := Color.{ 0.5, 0.5, 0.7 }; + + border_color := Color.{0.2, 0.2, 0.2}; + border_width := 2.0f; + + active := false; +} + +#local default_button_theme := Button_Theme.{}; + +draw_button :: (use r: Rect, text: str, theme := ^default_button_theme, site := #callsite, increment := 0) -> bool { + result := false; + + hash := get_site_hash(site, increment); + animation_state := get_animation(hash); + mx, my := mouse_get_position(); + + contains := Rect.contains(r, .{~~mx, ~~my}); + + if is_active_item(hash) { + renew_active_item(hash); + if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) { + if is_hot_item(hash) && contains { + result = true; + animation_state.click_time = 1.0f; + } + + set_active_item(0); + } + } elseif is_hot_item(hash) { + if is_button_down(GLFW_MOUSE_BUTTON_LEFT) { + set_active_item(hash); + } + } + + if contains { + set_hot_item(hash); + } + + if is_hot_item(hash) || theme.active { + move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed); + } else { + move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed); + } + + border_width := theme.border_width; + + immediate_set_color(theme.border_color); + immediate_rectangle(x, y, w, h); + + surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color); + surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); + immediate_set_color(surface_color); + immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2); + + font := font_lookup(.{theme.font_name, theme.font_size}); + font_height := font_get_height(font, text); + font_set_color(theme.text_color); + font_draw_centered(font, x, y + (h - font_height) / 2 + font.em - 2, w, text); + + move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed); + + return result; +} + + +// +// Sliders +// +Slider_Theme :: struct { + use text_theme := Text_Theme.{}; + use animation_theme := Animation_Theme.{}; + + box_color := Color.{ 0.1, 0.1, 0.1 }; + box_border_color := Color.{ 0.2, 0.2, 0.2 }; + box_border_width := 4.0f; // @InPixels + + bar_color := Color.{ 0.4, 0.4, 0.4 }; + bar_hover_color := Color.{ 0, 0, 1 }; + bar_hover_negative_color := Color.{ 1, 0, 0 }; // The color when value is less than 0 +} + +#local default_slider_theme := Slider_Theme.{}; + +draw_slider :: (use r: Rect, value: ^$T, min_value: T, max_value: T, theme := ^default_slider_theme, site := #callsite, increment := 0) -> bool { + result := false; + + hash := get_site_hash(site, increment); + state := get_animation(hash); + mx, my := mouse_get_position(); + + if is_hot_item(hash) { + if is_button_down(GLFW_MOUSE_BUTTON_LEFT) { + set_active_item(hash); + result = true; + + // Animate this? + sx := ~~mx - x; + + if T == i32 || T == i64 || T == u32 || T == u64 { + step_width := w / ~~math.abs(max_value - min_value); + percent := (sx + step_width / 2) / w; + *value = math.lerp(percent, min_value, max_value); + *value = math.clamp(*value, min_value, max_value); + + } else { + percent := sx / w; + *value = math.lerp(percent, min_value, max_value); + *value = math.clamp(*value, min_value, max_value); + } + } else { + set_active_item(0); + } + } + + if Rect.contains(r, .{ ~~mx, ~~my }) { + set_hot_item(hash); + } + + if is_hot_item(hash) { + move_towards(^state.hover_time, 1.0f, theme.hover_speed); + } else { + move_towards(^state.hover_time, 0.0f, theme.hover_speed); + } + + box_border_width := theme.box_border_width; + + bar_color := theme.bar_color; + if *value < 0 do bar_color = color_lerp(state.hover_time, bar_color, theme.bar_hover_negative_color); + else do bar_color = color_lerp(state.hover_time, bar_color, theme.bar_hover_color); + + immediate_set_color(theme.box_border_color); + immediate_rectangle(x, y, w, h); + + immediate_set_color(theme.box_border_color); + immediate_rectangle(x + box_border_width, y + box_border_width, w - box_border_width * 2, h - box_border_width * 2); + + box_width := cast(f32) (*value - min_value) / ~~(max_value - min_value); + box_width *= w - box_border_width * 2; + box_width = math.clamp(box_width, 0, w - box_border_width * 2); + immediate_set_color(bar_color); + immediate_rectangle(x + box_border_width, y + box_border_width, box_width, h - box_border_width * 2); + + return result; +} + +// +// Textbox +// +Textbox_Theme :: struct { + use text_theme := Text_Theme.{ + text_color = .{ 0, 0, 0 } + }; + + use animation_theme := Animation_Theme.{}; + + background_color := Color.{ 0.8, 0.8, 0.8 }; + hover_color := Color.{ 1.0, 1.0, 1.0 }; + click_color := Color.{ 0.5, 0.5, 0.7 }; + + border_color := Color.{ 0.2, 0.2, 0.2 }; + border_width := 6.0f; // @InPixels + + cursor_color := Color.{ 0.5, 0.5, 0.5 }; + cursor_width := 4.0f; // @InPixels + cursor_blink_speed := 0.04f; // Bigger is faster + + placeholder_text_color := Color.{ 0.5, 0.5, 0.5 }; +} + +#local { + default_textbox_theme := Textbox_Theme.{}; + + Textbox_Editing_State :: struct { + hash: UI_Id = 0; + + cursor_position: i32 = 0; + cursor_animation := 0.0f; + cursor_animation_speed := 0.02f; + + // @HACK // Otherwise the action keys are evaluated every frame. + action_key_timeout := 0.0f; + } + + textbox_editing_state := Textbox_Editing_State.{}; +} + +draw_textbox :: (use r: Rect, text_buffer: ^[..] u8, placeholder := null_str, theme := ^default_textbox_theme, site := #callsite, increment := 0) -> bool { + result := false; + + hash := get_site_hash(site, increment); + animation_state := get_animation(hash); + mx, my := mouse_get_position(); + + border_width := theme.border_width; + text_color := theme.text_color; + text := str.{text_buffer.data, text_buffer.count}; + if text.count == 0 && placeholder.count > 0 { + text = placeholder; + text_color = theme.placeholder_text_color; + } + + font := font_lookup(.{theme.font_name, theme.font_size}); + text_width := font_get_width(font, text); + text_height := font_get_height(font, text); + + text_x := x + border_width; + text_y := y + font.em + (h - text_height) / 2; + + contains := Rect.contains(r, .{~~mx, ~~my}); + + if is_hot_item(hash) && !is_active_item(hash) { + if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains { + set_active_item(hash); + input_capture_keys(); + textbox_editing_state.hash = hash; + textbox_editing_state.cursor_animation_speed = theme.cursor_blink_speed; + } + } + + if is_active_item(hash) { + renew_active_item(hash); + if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) && !contains { + set_active_item(0); + input_release_keys(); + textbox_editing_state.hash = 0; + textbox_editing_state.cursor_position = 0; + } + } + + if contains { + set_hot_item(hash); + } + + if textbox_editing_state.hash == hash { + move_towards(^textbox_editing_state.cursor_animation, 0.0f, textbox_editing_state.cursor_animation_speed); + if textbox_editing_state.cursor_animation <= 0.0f do textbox_editing_state.cursor_animation = 1.0f; + + if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains { + textbox_editing_state.cursor_animation = 1.0f; + textbox_editing_state.cursor_position = screen_to_cursor(^font, text_x, text_y, text, ~~mx, ~~my); + } + + for key: input_get_keys_this_frame() { + switch key.key { + case GLFW_KEY_ESCAPE { + set_active_item(0); + input_release_keys(); + } + + case GLFW_KEY_LEFT do textbox_editing_state.cursor_position -= 1; + case GLFW_KEY_RIGHT do textbox_editing_state.cursor_position += 1; + case GLFW_KEY_END do textbox_editing_state.cursor_position = text_buffer.count; + case GLFW_KEY_HOME do textbox_editing_state.cursor_position = 0; + + case GLFW_KEY_BACKSPACE { + if textbox_editing_state.cursor_position > 0 { + array.delete(text_buffer, textbox_editing_state.cursor_position - 1); + } + textbox_editing_state.cursor_position = math.max(~~0, textbox_editing_state.cursor_position - 1); + } + + case GLFW_KEY_DELETE { + array.delete(text_buffer, textbox_editing_state.cursor_position); + } + } + } + + for ch: input_get_chars_this_frame() { + array.insert(text_buffer, textbox_editing_state.cursor_position, ~~ch); + textbox_editing_state.cursor_position += 1; + result = true; + } + + textbox_editing_state.cursor_position = math.clamp(textbox_editing_state.cursor_position, 0, text_buffer.count); + textbox_editing_state.cursor_animation = 1.0f; + + text = str.{text_buffer.data, text_buffer.count}; + } + + if is_hot_item(hash) { + move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed); + } else { + move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed); + } + + immediate_push_scissor(x, y, w, h); + immediate_set_color(theme.border_color); + immediate_rectangle(x, y, w, h); + + surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color); + surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); + immediate_set_color(surface_color); + immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2); + + // Draw the cursor on textboxes + if is_active_item(hash) { + cursor := cursor_to_screen(^font, x + border_width, y + border_width, text, textbox_editing_state.cursor_position); + right_edge := x + w - border_width * 2; + if cursor.x > right_edge { + text_x -= cursor.x - right_edge; + cursor.x = right_edge - 2; + } + immediate_set_color(.{0,0,0}); + immediate_rectangle(cursor.x, cursor.y + 2, 2, h - border_width * 2 - 4); + } + + font_set_color(theme.text_color); + font_draw(font, text_x, text_y, text); // This is technically a frame late for updating the text? + + move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed); + + immediate_pop_scissor(); + return result; + + cursor_to_screen :: (font: ^Font, x, y: f32, text: str, cur_pos: i32) -> Vector2 { + cx := x; + cy := y; + + for text[0 .. cur_pos] { + if it == #char "\n" { + cx = 0; + cy += font.em + 2; + } else { + cx += font_get_char_width(*font, it); + } + } + + return .{ cx, cy }; + } + + screen_to_cursor :: (font: ^Font, x, y: f32, text: str, mouse_x, mouse_y: f32) -> i32 { + mx := mouse_x - x; + my := mouse_y - y; + + cx := 0.0f; + cy := 0.0f; + for pos: text.count { + if text[pos] == #char "\n" { + cx = 0; + cy += font.em + 2; + } else { + cx += font_get_char_width(*font, text[pos]); + } + + if cy >= my && cx >= mx { + return pos; + } + } + + return text.count; + } +} + + + + +// +// Checkboxes +// +Checkbox_Theme :: struct { + use text_theme := Text_Theme.{}; + use animation_theme := Animation_Theme.{}; + + box_color := Color.{ 0.2, 0.2, 0.2 }; + box_border_width := 4.0f; // @InPixels + box_size := 20.0f; // @InPixels + + checked_color := Color.{ 1, 0, 0 }; + checked_hover_color := Color.{ 1, 0.6, 0.6 }; + + background_color := Color.{ 0.05, 0.05, 0.05 }; // Background of the checkbox button. + hover_color := Color.{ 0.3, 0.3, 0.3 }; + click_color := Color.{ 0.5, 0.5, 0.7 }; +} + +#local default_checkbox_theme := Checkbox_Theme.{}; + +draw_checkbox :: (use r: Rect, value: ^bool, text: str, theme := ^default_checkbox_theme, site := #callsite, increment := 0) -> bool { + result := false; + + hash := get_site_hash(site, increment); + animation_state := get_animation(hash); + mx, my := mouse_get_position(); + + contains := Rect.contains(r, .{~~mx, ~~my}); + + if is_active_item(hash) { + renew_active_item(hash); + if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) { + if is_hot_item(hash) && contains { + result = true; + *value = !*value; + animation_state.click_time = 1.0f; + } + + set_active_item(0); + } + } elseif is_hot_item(hash) { + if is_button_down(GLFW_MOUSE_BUTTON_LEFT) { + set_active_item(hash); + } + } + + if contains { + set_hot_item(hash); + } + + if is_hot_item(hash) { + move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed); + } else { + move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed); + } + + box_border_width := theme.box_border_width; + box_size := theme.box_size; + + immediate_set_color(theme.box_color); + immediate_rectangle(x + 4, y + (h - box_size) / 2, box_size, box_size); + + surface_color : Color; + if *value { + surface_color = theme.checked_color; + surface_color = color_lerp(animation_state.hover_time, surface_color, theme.checked_hover_color); + + } else { + surface_color = theme.background_color; + surface_color = color_lerp(animation_state.hover_time, surface_color, theme.hover_color); + } + + surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); + + immediate_set_color(surface_color); + immediate_rectangle(x + 4 + box_border_width, y + (h - box_size) / 2 + box_border_width, box_size - box_border_width * 2, box_size - box_border_width * 2); + + font := font_lookup(.{theme.font_name, theme.font_size}); + text_width := font_get_width(font, text); + text_height := font_get_height(font, text); + + font_set_color(theme.text_color); + font_draw(font, x + box_size + 4 * 2, y + font.em + (h - text_height) / 2, text); + + move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed); + + return result; +} + + +// +// Radio buttons +// +Radio_Theme :: struct { + use text_theme := Text_Theme.{}; + use animation_theme := Animation_Theme.{}; + + box_color := Color.{ 0.2, 0.2, 0.2 }; + box_border_width := 4.0f; // @InPixels + box_size := 20.0f; // @InPixels + + checked_color := Color.{ 0, 0, 1 }; + checked_hover_color := Color.{ 0.6, 0.6, 1 }; + + background_color := Color.{ 0.05, 0.05, 0.05 }; // Background of the checkbox button. + hover_color := Color.{ 0.3, 0.3, 0.3 }; + click_color := Color.{ 0.5, 0.5, 0.7 }; +} + +#local default_radio_theme := Radio_Theme.{}; + +draw_radio :: (use r: Rect, value: ^$T, set_to: T, text: str, theme := ^default_radio_theme, site := #callsite, increment := 0) -> bool { + result := false; + + hash := get_site_hash(site, increment); + animation_state := get_animation(hash); + mx, my := mouse_get_position(); + + contains := Rect.contains(r, .{~~mx, ~~my}); + + if is_active_item(hash) { + renew_active_item(hash); + if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) { + if is_hot_item(hash) && contains { + result = true; + *value = set_to; + animation_state.click_time = 1.0f; + } + + set_active_item(0); + } + } elseif is_hot_item(hash) { + if is_button_down(GLFW_MOUSE_BUTTON_LEFT) { + set_active_item(hash); + } + } + + if contains { + set_hot_item(hash); + } + + if is_hot_item(hash) { + move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed); + } else { + move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed); + } + + box_border_width := theme.box_border_width; + box_size := theme.box_size; + + immediate_set_color(theme.box_color); + immediate_rectangle(x + 4, y + (h - box_size) / 2, box_size, box_size); + + surface_color : Color; + if *value == set_to { + surface_color = theme.checked_color; + surface_color = color_lerp(animation_state.hover_time, surface_color, theme.checked_hover_color); + + } else { + surface_color = theme.background_color; + surface_color = color_lerp(animation_state.hover_time, surface_color, theme.hover_color); + } + + surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color); + + immediate_set_color(surface_color); + immediate_rectangle(x + 4 + box_border_width, y + (h - box_size) / 2 + box_border_width, box_size - box_border_width * 2, box_size - box_border_width * 2); + + font := font_lookup(.{theme.font_name, theme.font_size}); + text_width := font_get_width(font, text); + text_height := font_get_height(font, text); + + font_set_color(theme.text_color); + font_draw(font, x + box_size + 4 * 2, y + font.em + (h - text_height) / 2, text); + + move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed); + + return result; +} + + + + +scrolling_region_start :: (r: Rect, max_y_scroll := 10000.0f, site := #callsite, increment := 0) { + hash := get_site_hash(site, increment); + mx, my := mouse_get_position(); + state := ^scroll_states[hash]; + if state == null { + animation_states[hash] = .{}; + state = ^scroll_states[hash]; + } + + contains := Rect.contains(r, .{~~mx, ~~my}); + if contains { + scroll_delta := mouse_get_scroll_vector(); + state.xscroll -= scroll_delta.x * 20; + state.yscroll -= scroll_delta.y * 20; + + state.yscroll = math.clamp(state.yscroll, 0, max_y_scroll); + } + + immediate_flush(); + immediate_push_scissor(r.x, r.y, r.w, r.h); + immediate_set_scroll(-state.xscroll, -state.yscroll); +} + +scrolling_region_stop :: () { + immediate_flush(); + immediate_pop_scissor(); + immediate_set_scroll(0, 0); +} + + + +#local { + hot_item : UI_Id = 0 + hot_item_was_set := false + + hot_item_depth := 0; + hot_item_depth_needed := 0; + + active_item : UI_Id = 0 + active_countdown := 0 + + set_active_item :: (id: UI_Id) -> bool { + active_item = id; + active_countdown = 2; + return true; + } + + renew_active_item :: (id: UI_Id) { + if active_item == id { + active_countdown = 2; + } + } + + set_hot_item :: (id: UI_Id, force := false) -> bool { + if active_item != 0 do return false; + + if force { + hot_item_was_set = true; + hot_item = id; + return true; + } + + hot_item_depth += 1; + if hot_item_depth >= hot_item_depth_needed { + hot_item_was_set = true; + hot_item = id; + return true; + } + + return false; + } + + is_active_item :: (id: UI_Id) -> bool { + return active_item == id; + } + + is_hot_item :: (id: UI_Id) -> bool { + return hot_item == id; + } + + Text_Theme :: struct { + text_color := Color.{1, 1, 1}; + font_name := "./assets/fonts/calibri.ttf"; + font_size := 18; + } + + Animation_Theme :: struct { + hover_speed := 0.1f; + click_decay_speed := 0.08f; + } + + Animation_State :: struct { + hover_time := 0.0f; + click_time := 0.0f; + + accessed_this_frame := false; + } + + animation_states : Map(UI_Id, Animation_State); + + get_animation :: (id: UI_Id) -> ^Animation_State { + retval := ^animation_states[id]; + if retval == null { + animation_states[id] = .{}; + retval = ^animation_states[id]; + } + + retval.accessed_this_frame = true; + return retval; + } + + has_active_animation :: () -> bool { + for^ animation_states.entries { + if it.value.hover_time != 0.0f || it.value.hover_time != 0.0f do return true; + if it.value.click_time != 0.0f || it.value.click_time != 0.0f do return true; + } + + return false; + } + + + Scroll_State :: struct { + xscroll: f32; + yscroll: f32; + } + + scroll_states : Map(UI_Id, Scroll_State); + + get_site_hash :: (site: CallSite, increment := 0) -> UI_Id { + file_hash := core.hash.to_u32(site.file); + line_hash := core.hash.to_u32(site.line); + column_hash := core.hash.to_u32(site.column); + + return ~~ (file_hash * 0x472839 + line_hash * 0x6849210 + column_hash * 0x1248382 + increment); + } + + color_lerp :: macro (t: f32, c1, c2: Color) => Color.{ + r = c1.r * (1 - t) + c2.r * t, + g = c1.g * (1 - t) + c2.g * t, + b = c1.b * (1 - t) + c2.b * t, + a = c1.a * (1 - t) + c2.a * t, + }; +} diff --git a/src/ogre/vecmath.onyx b/src/ogre/vecmath.onyx new file mode 100644 index 0000000..583dd2c --- /dev/null +++ b/src/ogre/vecmath.onyx @@ -0,0 +1,161 @@ +package ogre + +use core {hash, math, conv, string} + +@conv.Custom_Format.{format_vector2i} +@conv.Custom_Parse.{parse_vector2i} +Vector2i :: struct { + x, y: i32; + +} + +@conv.Custom_Format.{format_vector2} +@conv.Custom_Parse.{parse_vector2} +Vector2 :: struct { + x, y: f32; + + mag :: (v: Vector2) => math.sqrt(v.x * v.x + v.y * v.y); + + square_mag :: (v: Vector2) => v.x * v.x + v.y * v.y; + + norm :: (v: Vector2) -> Vector2 { + l := math.sqrt(v.x * v.x + v.y * v.y); + return .{ v.x / l, v.y / l }; + } + + + Zero :: Vector2.{0, 0} +} + +@conv.Custom_Format.{format_vector3i} +Vector3i :: struct { + x, y, z: i32; +} + +@conv.Custom_Format.{format_vector3} +Vector3 :: struct { + x, y, z: f32; + + mag :: (v: Vector3) => math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + + neg :: (v: Vector3) => Vector3.{ -v.x, -v.y, -v.z }; + + dot :: (v1, v2: Vector3) -> f32 { + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; + } + + norm :: (v: Vector3) -> Vector3 { + l := math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); + return .{ v.x / l, v.y / l, v.z / l }; + } + + cross :: (v1, v2: Vector3) -> Vector3 { + return .{ + v1.y * v2.z - v1.z * v2.y, + v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x, + }; + } + + clamp :: (v: Vector3, min: Vector3, max: Vector3) -> Vector3 { + return .{ + math.clamp(v.x, min.x, max.x), + math.clamp(v.y, min.y, max.y), + math.clamp(v.z, min.z, max.z), + }; + } +} + +#operator + (v1, v2: Vector2i) => Vector2i.{ v1.x + v2.x, v1.y + v2.y }; +#operator - (v1, v2: Vector2i) => Vector2i.{ v1.x - v2.x, v1.y - v2.y }; +#operator * (v: Vector2i, s: i32) => Vector2i.{ v.x * s, v.y * s }; +#operator * (v1, v2: Vector2i) => Vector2i.{ v1.x * v2.x, v1.y * v2.y }; +#operator == (v1, v2: Vector2i) => v1.x == v2.x && v1.y == v2.y; +#match hash.to_u32 (v: Vector2i) => 13 * v.x + 17 * v.y; + +#operator + (v1, v2: Vector2) => Vector2.{ v1.x + v2.x, v1.y + v2.y }; +#operator - (v1, v2: Vector2) => Vector2.{ v1.x - v2.x, v1.y - v2.y }; +#operator * (v: Vector2, s: f32) => Vector2.{ v.x * s, v.y * s }; +#operator * (v1, v2: Vector2) => Vector2.{ v1.x * v2.x, v1.y * v2.y }; +#operator == (v1, v2: Vector2) => v1.x == v2.x && v1.y == v2.y; + +#operator + (v1, v2: Vector3) => Vector3.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; +#operator - (v1, v2: Vector3) => Vector3.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; +#operator * (v: Vector3, s: f32) => Vector3.{ v.x * s, v.y * s, v.z * s }; +#operator * (v1, v2: Vector3) => Vector3.{ v1.x * v2.x, v1.y * v2.y, v1.z * v2.z }; +#operator == (v1, v2: Vector3) => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; + +#operator + (v1, v2: Vector3i) => Vector3i.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; +#operator - (v1, v2: Vector3i) => Vector3i.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; +#operator * (v: Vector3i, s: i32) => Vector3i.{ v.x * s, v.y * s, v.z * s }; +#operator * (v1, v2: Vector3i) => Vector3i.{ v1.x * v2.x, v1.y * v2.y, v1.z * v2.z }; +#operator == (v1, v2: Vector3i) => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; + +#local { + format_vector2i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2i) { + conv.format(output, "({}, {})", v.x, v.y); + } + + format_vector2 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2) { + conv.format(output, "({}, {})", v.x, v.y); + } + + format_vector3 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3) { + conv.format(output, "({}, {}, {})", v.x, v.y, v.z); + } + + format_vector3i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3i) { + conv.format(output, "({}, {}, {})", v.x, v.y, v.z); + } + + parse_vector2i :: (output: ^Vector2i, line_: str, string_allocator: Allocator) -> bool { + line := line_; + xs := string.read_until(^line, #char " "); + string.advance(^line, 1); + ys := string.read_until(^line, #char " "); + + if xs == "" || ys == "" do return false; + + output.x = ~~ conv.str_to_i64(xs); + output.y = ~~ conv.str_to_i64(ys); + return true; + } + + parse_vector2 :: (output: ^Vector2, line_: str, string_allocator: Allocator) -> bool { + line := line_; + xs := string.read_until(^line, #char " "); + string.advance(^line, 1); + ys := string.read_until(^line, #char " "); + + if xs == "" || ys == "" do return false; + + output.x = ~~ conv.str_to_f64(xs); + output.y = ~~ conv.str_to_f64(ys); + return true; + } +} + + + +Rect :: struct { + x, y, w, h: f32; + + intersects :: (r1, r2: Rect) -> bool { + return r1.x < r2.x + r2.w + && r1.x + r1.w > r2.x + && r1.y < r2.y + r2.h + && r1.y + r1.h > r2.y; + } + + contains :: (r: Rect, p: Vector2) -> bool { + return r.x <= p.x && r.x + r.w >= p.x + && r.y <= p.y && r.y + r.h >= p.y; + } + + center :: (use r: Rect) => Vector2.{ r.x+r.w/2, r.y+r.h/2 }; + + top_left :: (use r: Rect) => Vector2.{ r.x, r.y }; + top_right :: (use r: Rect) => Vector2.{ r.x+r.w, r.y }; + bottom_left :: (use r: Rect) => Vector2.{ r.x, r.y+r.h }; + bottom_right :: (use r: Rect) => Vector2.{ r.x+r.w, r.y+r.h }; +} diff --git a/src/ogre/window.onyx b/src/ogre/window.onyx new file mode 100644 index 0000000..486cf37 --- /dev/null +++ b/src/ogre/window.onyx @@ -0,0 +1,67 @@ +package ogre + +use glfw3 +use opengles + +Window :: struct { + glfw_window: GLFWwindow_p; + width, height: i32; +} + +window_create :: (width, height: i32, name: cstr) -> Window { + #if runtime.compiler_os == .Linux { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + } else { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + } + + w := glfwCreateWindow(width, height, name); + + return .{ w, width, height }; +} + +window_use :: (w: ^Window) { + glfwMakeContextCurrent(w.glfw_window); + glfwSwapInterval(1); + + input_bind_glfw_events(w.glfw_window); + + // Provide sensible defaults for OpenGL + glInit(glfwGetLoadProcAddress()); + glEnable(GL_TEXTURE); + glEnable(GL_CULL_FACE); + glFrontFace(GL_CW); + glCullFace(GL_BACK); + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + glfwGetWindowSize(w.glfw_window, ^w.width, ^w.height); + update_view_matrix(w.width, w.height); + + glfwSetWindowSizeCallback(w.glfw_window, #export_name (w: GLFWwindow_p, width, height: u32) { + global_window.width = width; + global_window.height = height; + update_view_matrix(width, height); + }); + + global_window = w; +} + +// +// These have to be macros because '#export_name' is used on the argument, +// which requires that they are compile time known functions. +// +// window_size_callback :: macro (w: ^Window, resize: (w: GLFWwindow_p, width, height: u32) -> void) { +// glfw3.glfwSetWindowSizeCallback(w.glfw_window, #export_name resize); +// } + + + + + +#package +global_window: ^Window; + diff --git a/src/sfx/audio_manager.onyx b/src/sfx/audio_manager.onyx index 970ec85..9398137 100644 --- a/src/sfx/audio_manager.onyx +++ b/src/sfx/audio_manager.onyx @@ -55,7 +55,7 @@ Audio_Manager :: struct { } alBufferData(buffer, al_format, wav_file.data.data, wav_file.data.count, wav_file.sample_rate); - memory.free_slice(^wav_file.loaded_file_data); + delete(^wav_file.loaded_file_data); loaded_sounds[path] = .{ buffer }; return .{ buffer }; @@ -78,15 +78,13 @@ Audio_Manager :: struct { } tick :: () { - while i := 0; i < playing_sounds.count { - defer i += 1; - + for it: iter.as_iterator(^playing_sounds) { state: i32; - alGetSourcei(playing_sounds[i], AL_SOURCE_STATE, ^state); + alGetSourcei(it, AL_SOURCE_STATE, ^state); + if state != AL_PLAYING { - alDeleteSources(1, ^playing_sounds[i]); - array.fast_delete(^playing_sounds, i); - i -= 1; + alDeleteSources(1, ^it); + #remove; } } } diff --git a/src/utils/asset_loader.onyx b/src/utils/asset_loader.onyx index 72abc95..ec5f756 100644 --- a/src/utils/asset_loader.onyx +++ b/src/utils/asset_loader.onyx @@ -1,5 +1,6 @@ -use package core +use core +use ogre queue_assets :: (store: ^$T, callsite := #callsite) { assets_to_load << .{ T, store, callsite }; diff --git a/src/utils/colors.onyx b/src/utils/colors.onyx deleted file mode 100644 index ab6231d..0000000 --- a/src/utils/colors.onyx +++ /dev/null @@ -1,55 +0,0 @@ - -use package core - -Color :: struct { - r, g, b : f32; - a := 1.0f; -} - -color_to_hsl :: (c: Color) -> (h: f32, s: f32, l: f32) { - r := c.r / 255; - g := c.g / 255; - b := c.b / 255; - cmax := math.max(math.max(r, g), b); - cmin := math.min(math.min(r, g), b); - delta := cmax - cmin; - - h := 0.0f; - if cmax == r { - m := (g - b) / delta; - while m >= 6 do m -= 6; - while m < 0 do m += 6; - h = 60 * m; - } - if cmax == g do h = 60 * (b - r) / delta + 2; - if cmax == b do h = 60 * (r - g) / delta + 4; - - l := (cmax + cmin) / 2; - s := delta / (1 - math.abs(2 * l - 1)); - - return h, s, l; -} - -hsl_to_color :: (h, s, l: f32) -> Color { - c := (1 - math.abs(2 * l - 1)) * s; - - h_term := h / 60; - while h_term < 0 do h_term += 2; - while h_term >= 0 do h_term -= 2; - - x := c * (1 - math.abs(h_term - 1)); - m := l - c / 2; - - r, g, b: f32; - if 0 <= h && h < 60 { r = c; g = x; b = 0; } - if 60 <= h && h < 120 { r = x; g = c; b = 0; } - if 120 <= h && h < 180 { r = 0; g = c; b = x; } - if 180 <= h && h < 240 { r = 0; g = x; b = c; } - if 240 <= h && h < 300 { r = x; g = 0; b = c; } - if 300 <= h && h < 360 { r = c; g = 0; b = x; } - - r = (r + m) * 255; - g = (g + m) * 255; - b = (b + m) * 255; - return .{ r, g, b, 1 }; -} diff --git a/src/utils/input.onyx b/src/utils/input.onyx deleted file mode 100644 index 31a9022..0000000 --- a/src/utils/input.onyx +++ /dev/null @@ -1,187 +0,0 @@ -use package core -use package glfw3 - -// If you are offseting the mouse coordinate for world space -// or UI scrolling etc., set this function to be the function -// to ask for the current offset. Currently scaling the mouse -// coordinate is not supported, only linear translations. -Mouse_Offset_Function :: immediate_get_scroll - - -#local { - keys_this_frame : Set(Key_Descriptor) // Keys currently being pressed this frame - keys_last_frame : Set(Key_Descriptor) // Keys being pressed in the last frame - - key_codepoints : [..] u32 - - buttons_this_frame: [8] bool // Mouse buttons being pressed this frame - buttons_last_frame: [8] bool // Mouse buttons being pressed last frame - - scroll_x: f64 - scroll_y: f64 - - character_mode := false; -} - -#init () { - set.init(^keys_this_frame); - set.init(^keys_last_frame); -} - -Key_Descriptor :: struct { - key: u32; - scancode: u32 = 0; - mod: u32 = 0; -} -#match hash.to_u32 (use x: Key_Descriptor) => hash.to_u32(key); -#operator == (x, y: Key_Descriptor) => x.key == y.key; - -input_update :: () { - glfwGetCursorPos(window, ^mouse_x, ^mouse_y); -} - -input_post_update :: () { - set.clear(^keys_last_frame); - for keys_this_frame.entries do keys_last_frame << it.value; - - for 8 do buttons_last_frame[it] = buttons_this_frame[it]; - - last_mouse_x = mouse_x; - last_mouse_y = mouse_y; - scroll_x = 0; - scroll_y = 0; - - array.clear(^key_codepoints); -} - -input_get_chars_this_frame :: () -> [] u32 { - return key_codepoints; -} - -input_get_keys_this_frame :: () -> Iterator(Key_Descriptor) { - #persist index := 0; - - next :: (_: rawptr) -> (Key_Descriptor, bool) { - if index >= keys_this_frame.entries.count do return .{0}, false; - - while keys_last_frame->has(keys_this_frame.entries[index].value) { - index += 1; - if index >= keys_this_frame.entries.count do return .{0}, false; - } - - defer index += 1; - return keys_this_frame.entries[index].value, true; - } - - index = 0; - return .{ null, next }; -} - -input_capture_keys :: () { - character_mode = true; -} - -input_release_keys :: () { - character_mode = false; -} - -is_key_down :: (key) => !character_mode && set.has(^keys_this_frame, .{key}); -is_key_just_down :: (key) => !character_mode && set.has(^keys_this_frame, .{key}) && !set.has(^keys_last_frame, .{key}); -is_key_just_up :: (key) => !character_mode && !set.has(^keys_this_frame, .{key}) && set.has(^keys_last_frame, .{key}); - -is_button_down :: (button) => buttons_this_frame[button]; -is_button_just_down :: (button) => buttons_this_frame[button] && !buttons_last_frame[button]; -is_button_just_up :: (button) => !buttons_this_frame[button] && buttons_last_frame[button]; - -#local { - last_mouse_x: f64; - last_mouse_y: f64; - - mouse_x: f64; - mouse_y: f64; -} - -mouse_get_delta :: () -> (f64, f64) { - return mouse_x - last_mouse_x, mouse_y - last_mouse_y; -} - -mouse_get_delta_vector :: () -> Vector2 { - dmx, dmy := mouse_get_delta(); - return .{ ~~dmx, ~~dmy }; -} - -mouse_get_position :: () -> (f64, f64) { - mx, my := mouse_x, mouse_y; - #if #defined(Mouse_Offset_Function) { - scroll := Mouse_Offset_Function(); - mx -= ~~ scroll.x; - my -= ~~ scroll.y; - } - return mx, my; -} - -mouse_get_position_vector :: () -> Vector2 { - mx, my := mouse_x, mouse_y; - #if #defined(Mouse_Offset_Function) { - scroll := Mouse_Offset_Function(); - mx -= ~~ scroll.x; - my -= ~~ scroll.y; - } - return .{ ~~mx, ~~my }; -} - -mouse_get_scroll :: () -> (f64, f64) { - return scroll_x, scroll_y; -} - -mouse_get_scroll_vector :: () -> Vector2 { - return .{ ~~scroll_x, ~~scroll_y }; -} - -input_bind_glfw_events :: (window: GLFWwindow_p) { - glfwSetKeyCallback(window, INPUT_KEY_EVENT); - glfwSetCharCallback(window, INPUT_CHAR_EVENT); - glfwSetMouseButtonCallback(window, INPUT_BUTTON_EVENT); - glfwSetScrollCallback(window, INPUT_SCROLL_EVENT); -} - -#local { - INPUT_BUTTON_EVENT :: "__input_button_event" - INPUT_KEY_EVENT :: "__input_key_event" - INPUT_SCROLL_EVENT :: "__input_scroll_event" - INPUT_CHAR_EVENT :: "__input_char_event" -} - -#export INPUT_BUTTON_EVENT (window: GLFWwindow_p, button, action, mod: u32) { - if action == GLFW_PRESS { - buttons_this_frame[button] = true; - } - - if action == GLFW_RELEASE { - buttons_this_frame[button] = false; - } -} - -#export INPUT_KEY_EVENT (window: GLFWwindow_p, key, scancode, action, mod: u32) { - if action == GLFW_PRESS { - keys_this_frame << .{ key, scancode, mod }; - } - - if action == GLFW_REPEAT { - set.remove(^keys_last_frame, .{key}); - } - - if action == GLFW_RELEASE { - set.remove(^keys_this_frame, .{ key }); - } -} - -#export INPUT_CHAR_EVENT (window: GLFWwindow_p, codepoint: u32) { - if !character_mode do return; - key_codepoints << codepoint; -} - -#export INPUT_SCROLL_EVENT (window: GLFWwindow_p, xoff, yoff: f64) { - scroll_x = xoff; - scroll_y = yoff; -} diff --git a/src/utils/vecmath.onyx b/src/utils/vecmath.onyx deleted file mode 100644 index 83e177f..0000000 --- a/src/utils/vecmath.onyx +++ /dev/null @@ -1,166 +0,0 @@ - -use core {hash, math} - -#tag conv.Custom_Format.{format_vector2i} -#tag conv.Custom_Parse.{parse_vector2i} -Vector2i :: struct { - x, y: i32; - -} - -#tag conv.Custom_Format.{format_vector2} -#tag conv.Custom_Parse.{parse_vector2} -Vector2 :: struct { - x, y: f32; - - mag :: (v: Vector2) => math.sqrt(v.x * v.x + v.y * v.y); - - square_mag :: (v: Vector2) => v.x * v.x + v.y * v.y; - - norm :: (v: Vector2) -> Vector2 { - l := math.sqrt(v.x * v.x + v.y * v.y); - return .{ v.x / l, v.y / l }; - } - - - Zero :: Vector2.{0, 0} -} - -#tag conv.Custom_Format.{format_vector3i} -Vector3i :: struct { - x, y, z: i32; -} - -#tag conv.Custom_Format.{format_vector3} -Vector3 :: struct { - x, y, z: f32; - - mag :: (v: Vector3) => math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); - - neg :: (v: Vector3) => Vector3.{ -v.x, -v.y, -v.z }; - - dot :: (v1, v2: Vector3) -> f32 { - return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; - } - - norm :: (v: Vector3) -> Vector3 { - l := math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); - return .{ v.x / l, v.y / l, v.z / l }; - } - - cross :: (v1, v2: Vector3) -> Vector3 { - return .{ - v1.y * v2.z - v1.z * v2.y, - v1.z * v2.x - v1.x * v2.z, - v1.x * v2.y - v1.y * v2.x, - }; - } - - clamp :: (v: Vector3, min: Vector3, max: Vector3) -> Vector3 { - return .{ - math.clamp(v.x, min.x, max.x), - math.clamp(v.y, min.y, max.y), - math.clamp(v.z, min.z, max.z), - }; - } -} - -#operator + (v1, v2: Vector2i) => Vector2i.{ v1.x + v2.x, v1.y + v2.y }; -#operator - (v1, v2: Vector2i) => Vector2i.{ v1.x - v2.x, v1.y - v2.y }; -#operator * (v: Vector2i, s: i32) => Vector2i.{ v.x * s, v.y * s }; -#operator * (v1, v2: Vector2i) => Vector2i.{ v1.x * v2.x, v1.y * v2.y }; -#operator == (v1, v2: Vector2i) => v1.x == v2.x && v1.y == v2.y; -#match hash.to_u32 (v: Vector2i) => 13 * v.x + 17 * v.y; - -#operator + (v1, v2: Vector2) => (typeof v1).{ v1.x + v2.x, v1.y + v2.y }; -#operator - (v1, v2: Vector2) => (typeof v1).{ v1.x - v2.x, v1.y - v2.y }; -#operator * (v: Vector2, s: f32) => (typeof v ).{ v.x * s, v.y * s }; -#operator * (v1, v2: Vector2) => (typeof v1).{ v1.x * v2.x, v1.y * v2.y }; -#operator == (v1, v2: Vector2) => v1.x == v2.x && v1.y == v2.y; - -#operator + (v1, v2: Vector3) => Vector3.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; -#operator - (v1, v2: Vector3) => Vector3.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; -#operator * (v: Vector3, s: f32) => Vector3.{ v.x * s, v.y * s, v.z * s }; -#operator * (v1, v2: Vector3) => (typeof v1).{ v1.x * v2.x, v1.y * v2.y, v1.z * v2.z }; -#operator == (v1, v2: Vector3) => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; - -#operator + (v1, v2: Vector3i) => Vector3i.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; -#operator - (v1, v2: Vector3i) => Vector3i.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; -#operator * (v: Vector3i, s: i32) => Vector3i.{ v.x * s, v.y * s, v.z * s }; -#operator * (v1, v2: Vector3i) => (typeof v1).{ v1.x * v2.x, v1.y * v2.y, v1.z * v2.z }; -#operator == (v1, v2: Vector3i) => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z; - -#local { - conv :: package core.conv - - format_vector2i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2i) { - conv.format(output, "({}, {})", v.x, v.y); - } - - format_vector2 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2) { - conv.format(output, "({}, {})", v.x, v.y); - } - - format_vector3 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3) { - conv.format(output, "({}, {}, {})", v.x, v.y, v.z); - } - - format_vector3i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3i) { - conv.format(output, "({}, {}, {})", v.x, v.y, v.z); - } - - parse_vector2i :: (output: ^Vector2i, line_: str, string_allocator: Allocator) -> bool { - string :: package core.string - - line := line_; - xs := string.read_until(^line, #char " "); - string.advance(^line, 1); - ys := string.read_until(^line, #char " "); - - if xs == "" || ys == "" do return false; - - output.x = ~~ conv.str_to_i64(xs); - output.y = ~~ conv.str_to_i64(ys); - return true; - } - - parse_vector2 :: (output: ^Vector2, line_: str, string_allocator: Allocator) -> bool { - string :: package core.string - - line := line_; - xs := string.read_until(^line, #char " "); - string.advance(^line, 1); - ys := string.read_until(^line, #char " "); - - if xs == "" || ys == "" do return false; - - output.x = ~~ conv.str_to_f64(xs); - output.y = ~~ conv.str_to_f64(ys); - return true; - } -} - - - -Rect :: struct { - x, y, w, h: f32; - - intersects :: (r1, r2: Rect) -> bool { - return r1.x < r2.x + r2.w - && r1.x + r1.w > r2.x - && r1.y < r2.y + r2.h - && r1.y + r1.h > r2.y; - } - - contains :: (r: Rect, p: Vector2) -> bool { - return r.x <= p.x && r.x + r.w >= p.x - && r.y <= p.y && r.y + r.h >= p.y; - } - - center :: (use r: Rect) => Vector2.{ r.x+r.w/2, r.y+r.h/2 }; - - top_left :: (use r: Rect) => Vector2.{ r.x, r.y }; - top_right :: (use r: Rect) => Vector2.{ r.x+r.w, r.y }; - bottom_left :: (use r: Rect) => Vector2.{ r.x, r.y+r.h }; - bottom_right :: (use r: Rect) => Vector2.{ r.x+r.w, r.y+r.h }; -}