package edit2d import "core:fmt" 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 "core:reflect" import mu "vendor:microui" 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, time : f32, tex_line : u32, drop_callback : glfw.DropProc, input : input_state, mu_ctx : mu.Context, mu_tex : u32, } MAX_KEYS :: 512 input_state :: struct { mouse_wheel : [2]f64, 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)) } /* 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) //gl.DebugMessageControl(gl.DONT_CARE, gl.DONT_CARE, gl.DONT_CARE, 0, nil, gl.TRUE); assert(state.window != nil) glfw.MakeContextCurrent(state.window) glfw.SwapInterval(1) gl.load_up_to(4, 5, glfw.gl_set_proc_address) im.CHECKVERSION() im.CreateContext() io := im.GetIO() io.ConfigFlags += { .NavEnableKeyboard, .NavEnableGamepad, .DpiEnableScaleFonts, } im.StyleColorsClassic() style := im.GetStyle() style.ChildBorderSize = 0. style.ChildRounding = 6 style.TabRounding = 6 style.FrameRounding = 6 style.GrabRounding = 6 style.WindowRounding = 6 style.PopupRounding = 6 imgui_impl_glfw.InitForOpenGL(state.window, true) imgui_impl_opengl3.Init("#version 150") 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) //Glyph and microui { //Create index 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) { state.input.mouse_wheel = {} glfw.PollEvents() keyboard_update(state) x, y:= glfw.GetCursorPos(state.window) mu.input_mouse_move(&state.mu_ctx, i32(x), i32(y)) { 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) imgui_impl_opengl3.NewFrame() imgui_impl_glfw.NewFrame() im.NewFrame() } end_frame :: proc(state: ^engine_state) { im.Render() imgui_impl_opengl3.RenderDrawData(im.GetDrawData()) //Microui { 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 //hex_color := e2_draw.make_hex_color({color.r, color.g, color.b, color.a}) 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)}, }) } } } } glfw.SwapBuffers(state.window) } 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(d: ^e2_draw.Draw) { e2_draw.draw_flush(d) }