package edit2d import "base:intrinsics" import "core:encoding/cbor" import b2 "vendor:box2d" import "base:runtime" /* import im "shared:odin-imgui" import "shared:odin-imgui/imgui_impl_glfw" import "shared:odin-imgui/imgui_impl_opengl3" **/ import gl "vendor:OpenGL" import "vendor:glfw" import e2_draw "shared:Edit2D/draw" import e2_glyph "shared:Edit2D/glyph" import "core:reflect" import mu "vendor:microui" //Only to be used inside the libarary __e2d_interal : struct { viewport_changed : bool, mouse_scroll : [2]f64, } /* This engine is meant to be used by the game All the stuffs internal should be stored in __e2d_internal */ Engine_State :: struct { window : glfw.WindowHandle, debug_draw : b2.DebugDraw, draw : e2_draw.Draw, restart, pause : bool, substep_count : u32, //Must be set before calling ion_init width, height : i32, title : cstring, drop_callback : glfw.DropProc, input : Input_State, mu_ctx : mu.Context, /* The opengl texture id created using mu.default_atlas_alpha */ mu_tex : u32, } MAX_KEYS :: 512 Input_State :: struct { mouse : [2]f64, mouse_prev : [2]f64, curr, prev : [MAX_KEYS]bool, } //This function defines if the game's structs are defines as requirements //Doing the checking in the beggining makes writing helper code more reliable engine_check_types :: proc($Game: typeid) { assert(intrinsics.type_has_field(Game, "levels"), "game should contan levels") assert(intrinsics.type_has_field(Game, "engine"), "game should contan engine") level_type := reflect.struct_field_by_name(Game, "levels") engine_type := reflect.struct_field_by_name(Game, "engine") assert(engine_type.is_using, "You should be using the engine") assert(engine_type.type.id == typeid_of(engine_game), "Engine Should be engine_game") level_variant := level_type.type.variant assert(reflect.is_dynamic_map(level_type.type), "Level should be map of names and level files") level_map_type := level_type.type.variant.(runtime.Type_Info_Map) assert(level_map_type.key.id == typeid_of(string), "Level key should be name") //Level should also have engine level_engine_type := reflect.struct_field_by_name(level_map_type.value.id, "engine") assert(level_engine_type.is_using, "Should be using engine") assert(level_engine_type.type.id == typeid_of(Engine_World), "Should be using engine") entities_type := reflect.struct_field_by_name(level_map_type.value.id, "entities") entity_defs_type := reflect.struct_field_by_name(level_map_type.value.id, "entity_defs") assert(reflect.is_dynamic_array(entities_type.type)) assert(reflect.is_dynamic_array(entity_defs_type.type)) entities_arr := entities_type.type.variant.(runtime.Type_Info_Dynamic_Array) entity_defs_arr := entity_defs_type.type.variant.(runtime.Type_Info_Dynamic_Array) entity_engine := reflect.struct_field_by_name(entities_arr.elem.id, "engine") defs_engine := reflect.struct_field_by_name(entity_defs_arr.elem.id, "engine") assert(entity_engine.is_using, "Should be using") assert(defs_engine.is_using, "Should be using") assert(entity_engine.type.id == typeid_of(Engine_Entity)) assert(defs_engine.type.id == typeid_of(Engine_Entity_Def)) } size_callback :: proc "c" (window: glfw.WindowHandle, width, height : i32) { __e2d_interal.viewport_changed = true } scroll_callback :: proc "c" (window : glfw.WindowHandle, x, y : f64) { __e2d_interal.mouse_scroll = {x, y} } /* This will only be called once to initilize the engine initilize graphics library, glfw, callbacks */ engine_init :: proc($GameType : typeid, state: ^Engine_State, font_path : string="") { engine_check_types(GameType) assert(glfw.Init() == true) glfw.WindowHint(glfw.SCALE_TO_MONITOR, 1) glfw.WindowHint(glfw.CONTEXT_VERSION_MAJOR, 4) glfw.WindowHint(glfw.OPENGL_DEBUG_CONTEXT, true) glfw.WindowHint(glfw.CONTEXT_VERSION_MINOR, 5) glfw.WindowHint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) state.window = glfw.CreateWindow(state.width, state.height, state.title, nil, nil) assert(state.window != nil) glfw.MakeContextCurrent(state.window) glfw.SwapInterval(1) gl.load_up_to(4, 5, glfw.gl_set_proc_address) glfw.SetWindowSizeCallback(state.window, size_callback) glfw.SetScrollCallback( state.window, scroll_callback) state.draw.cam = e2_draw.camera_init() display_w, display_h := glfw.GetFramebufferSize(state.window) state.draw.cam.width = display_w state.draw.cam.height = display_h state.draw.cam.zoom = 15 state.draw.show_ui = true e2_draw.draw_create(&state.draw, &state.draw.cam, font_path) draw_configure_box2d(state) cbor.tag_register_type({ marshal = proc( _: ^cbor.Tag_Implementation, e: cbor.Encoder, v: any) -> cbor.Marshal_Error { cbor._encode_u8(e.writer, 201, .Tag) or_return return nil; }, unmarshal = proc( _: ^cbor.Tag_Implementation, d: cbor.Decoder, _: cbor.Tag_Number, v: any) -> (cbor.Unmarshal_Error) { return nil }, }, 201, rawptr) mu.init(&state.mu_ctx) state.mu_ctx.style.font = cast(mu.Font)&state.draw.glyph state.mu_ctx.text_width = mui_text_width state.mu_ctx.text_height = mui_text_height texture_data := make([]u8, mu.DEFAULT_ATLAS_WIDTH* mu.DEFAULT_ATLAS_HEIGHT * 4) idx :i32 = 0 for y in 0.. e2_draw.Rect { points : e2_draw.Rect points.x = f32(rect.x) / f32(size) points.y = f32(rect.y) / f32(size) points.w = f32(rect.w) / f32(size) points.h = f32(rect.h) / f32(size) return points } update_frame :: proc(state: ^Engine_State) { __e2d_interal.mouse_scroll = {} glfw.PollEvents() keyboard_update(state) x, y:= glfw.GetCursorPos(state.window) mu.input_mouse_move(&state.mu_ctx, i32(x), i32(y)) wheel := __e2d_interal.mouse_scroll mu.input_scroll(&state.mu_ctx, i32(wheel.x) * 10, i32(wheel.y) * -20) { for key in key_map { if is_key_pressed(state, key) do mu.input_key_down(&state.mu_ctx, key_map[key]) if is_key_released(state, key) do mu.input_key_up(&state.mu_ctx, key_map[key]) } for key in mouse_map { if is_key_pressed(state, key) do mu.input_mouse_down(&state.mu_ctx, i32(x), i32(y), mouse_map[key]) if is_key_released(state, key) do mu.input_mouse_up( &state.mu_ctx,i32(x), i32(y), mouse_map[key]) } } gl.ClearColor(0.4, 0.5, 0.6, 1.0) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) cam := &state.draw.cam cam.width, cam.height = glfw.GetWindowSize(state.window) state.width , state.height = glfw.GetFramebufferSize(state.window) gl.Viewport(0, 0, state.width, state.height) } end_frame :: proc(state: ^Engine_State) { //Microui fb_x, fb_y := glfw.GetFramebufferSize(state.window) state.draw.glyph.atlas_width = fb_x state.draw.glyph.atlas_height = fb_y cmd: ^mu.Command draw := &state.draw for mu.next_command(&state.mu_ctx, &cmd) { #partial switch c in cmd.variant{ case ^mu.Command_Text: { color := c.color append(&draw.texts, e2_draw.TextItem{c.str, {f32(c.pos.x), f32(c.pos.y)}, {c.color.r, color.g, color.b, color.a}}) } case ^mu.Command_Rect: { rect := c.rect pos :[2]f32= {f32(rect.x), f32(rect.y)} wh :[2]f32= {f32(rect.w), f32(rect.h)} w, h := wh.x, wh.y points : [4][2]f32 = { {pos.x + w, pos.y}, {pos.x, pos.y}, {pos.x, pos.y + h}, {pos.x + w, pos.y + h}, } for &p, i in &points do points[i] = e2_draw.camera_convert_screen_to_world(&draw.cam, p) color := c.color e2_draw.solid_polygon_add(&draw.polygons, {q = e2_draw.Rot{c = 1}}, &points[0], 4, 0.01, transmute([4]u8)color) } case ^mu.Command_Clip: { rect := c.rect gl.Scissor(rect.x, state.height - (rect.y + rect.h), rect.w, rect.h) } case ^mu.Command_Icon:{ rect := mu.default_atlas[c.id] append(&draw.textures.textures, e2_draw.TextureData{ texture_id = state.mu_tex, src_rect = normalize_rect(rect, mu.DEFAULT_ATLAS_WIDTH), dst_rect = e2_draw.Rect{f32(c.rect.x), f32(c.rect.y), f32(rect.w), f32(rect.h)}, }) } } } } cleanup :: proc(state: ^Engine_State) { //imgui_impl_opengl3.Shutdown() //imgui_impl_glfw.Shutdown() } engine_should_close :: proc(state : ^Engine_State) -> b32 { return glfw.WindowShouldClose(state.window) } keyboard_update :: proc(state: ^Engine_State) { state.input.mouse_prev = state.input.mouse state.input.mouse.x, state.input.mouse.y = glfw.GetCursorPos(state.window) state.input.prev = state.input.curr //Update current states for key in glfw.KEY_SPACE ..< MAX_KEYS { state.input.curr[key] = glfw.GetKey(state.window, i32(key)) == glfw.PRESS } for key in 0.. bool{ return state.input.curr[key] } is_key_pressed :: #force_inline proc(state: ^Engine_State, key : i32) -> bool{ return state.input.curr[key] && !state.input.prev[key] } is_key_released :: #force_inline proc(state: ^Engine_State, key : i32) -> bool{ return !state.input.curr[key] && state.input.prev[key] } draw_flush :: proc(state: ^Engine_State) { d := &state.draw if __e2d_interal.viewport_changed { d.glyph.atlas_width = d.cam.width d.glyph.atlas_height = d.cam.height d.glyph.font_size_pt = 12 d.glyph.width = d.cam.width d.glyph.height = d.cam.height width, height := glfw.GetFramebufferSize(state.window) state.width = width state.height = height e2_glyph.glyph_init(&d.glyph, d.font_path) } e2_draw.draw_flush(d, __e2d_interal.viewport_changed) glfw.SwapBuffers(state.window) __e2d_interal.viewport_changed = false }