373 lines
9.5 KiB
Odin
373 lines
9.5 KiB
Odin
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..<mu.DEFAULT_ATLAS_HEIGHT
|
|
{
|
|
for x in 0..<mu.DEFAULT_ATLAS_WIDTH
|
|
{
|
|
texture_data[idx + 0] = 255
|
|
texture_data[idx + 1] = 255
|
|
texture_data[idx + 2] = 255
|
|
texture_data[idx + 3] = mu.default_atlas_alpha[idx/4]
|
|
idx += 4
|
|
}
|
|
}
|
|
state.mu_tex = e2_draw.create_texture(texture_data, mu.DEFAULT_ATLAS_WIDTH, mu.DEFAULT_ATLAS_HEIGHT)
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
normalize_rect :: proc(rect : mu.Rect, size : i32) -> 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..<glfw.KEY_SPACE
|
|
{
|
|
state.input.curr[key] = glfw.GetMouseButton(state.window, i32(key)) == glfw.PRESS
|
|
}
|
|
}
|
|
|
|
is_key_down :: #force_inline proc(state: ^engine_state, key : i32) -> 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)
|
|
}
|
|
|