Huge refactor, Adding more code to engine from the game
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
package ion
|
package edit2d
|
||||||
|
|
||||||
import "core:strings"
|
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:math"
|
import "core:math"
|
||||||
|
|||||||
+59
-2
@@ -1,12 +1,16 @@
|
|||||||
package ion
|
package edit2d
|
||||||
|
|
||||||
|
import "base:intrinsics"
|
||||||
|
|
||||||
import "core:encoding/cbor"
|
import "core:encoding/cbor"
|
||||||
|
import "base:runtime"
|
||||||
import im "shared:odin-imgui"
|
import im "shared:odin-imgui"
|
||||||
import "shared:odin-imgui/imgui_impl_glfw"
|
import "shared:odin-imgui/imgui_impl_glfw"
|
||||||
import "shared:odin-imgui/imgui_impl_opengl3"
|
import "shared:odin-imgui/imgui_impl_opengl3"
|
||||||
import gl "vendor:OpenGL"
|
import gl "vendor:OpenGL"
|
||||||
|
import "core:fmt"
|
||||||
import "vendor:glfw"
|
import "vendor:glfw"
|
||||||
|
import "core:reflect"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -25,6 +29,7 @@ engine_state :: struct
|
|||||||
drop_callback : glfw.DropProc,
|
drop_callback : glfw.DropProc,
|
||||||
|
|
||||||
input : input_state,
|
input : input_state,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_KEYS :: 512
|
MAX_KEYS :: 512
|
||||||
@@ -38,13 +43,64 @@ input_state :: struct
|
|||||||
curr, prev : [MAX_KEYS]bool,
|
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
|
This will only be called once to initilize the engine
|
||||||
initilize graphics library, glfw, callbacks
|
initilize graphics library, glfw, callbacks
|
||||||
*/
|
*/
|
||||||
engine_init :: proc(state: ^engine_state)
|
engine_init :: proc($GameType : typeid, state: ^engine_state)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
engine_check_types(GameType)
|
||||||
|
|
||||||
assert(glfw.Init() == true)
|
assert(glfw.Init() == true)
|
||||||
|
|
||||||
glfw.WindowHint(glfw.SCALE_TO_MONITOR, 1)
|
glfw.WindowHint(glfw.SCALE_TO_MONITOR, 1)
|
||||||
@@ -105,6 +161,7 @@ engine_init :: proc(state: ^engine_state)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
update_frame :: proc(state: ^engine_state)
|
update_frame :: proc(state: ^engine_state)
|
||||||
{
|
{
|
||||||
state.input.mouse_wheel = {}
|
state.input.mouse_wheel = {}
|
||||||
+52
-21
@@ -1,4 +1,4 @@
|
|||||||
package ion
|
package edit2d
|
||||||
|
|
||||||
import b2 "vendor:box2d"
|
import b2 "vendor:box2d"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
@@ -12,30 +12,13 @@ static_index_global :: struct
|
|||||||
offset : b2.Vec2,
|
offset : b2.Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//DropProc :: #type proc "c" (window: WindowHandle, count: c.int, paths: [^]cstring)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This file contains code to handle box2d stuffs of the game code
|
This file contains code to handle box2d stuffs of the game code
|
||||||
Don't put game's logic here
|
Don't put game's logic here
|
||||||
*/
|
*/
|
||||||
|
|
||||||
engine_world :: struct
|
|
||||||
{
|
|
||||||
world_id : b2.WorldId,
|
|
||||||
|
|
||||||
//This in engine code?
|
|
||||||
static_indexes : map[static_index]int `cbor:"-"`,
|
|
||||||
relations : map[^static_index][dynamic]static_index_global `cbor:"-"`,
|
|
||||||
relations_serializeable : map[ static_index][dynamic]static_index_global,
|
|
||||||
|
|
||||||
/*
|
|
||||||
Seems okay to put the joint defs in engine rather than in game because
|
|
||||||
we don't add more attributes in joints in the game
|
|
||||||
|
|
||||||
Can be changed later without requireing refactor
|
|
||||||
*/
|
|
||||||
revolute_joint_defs : [dynamic]revolt_joint_def,
|
|
||||||
distant_joint_defs : [dynamic]distance_joint_def,
|
|
||||||
joints : [dynamic]b2.JointId `cbor:"-"`,
|
|
||||||
}
|
|
||||||
|
|
||||||
engine_entity_flags_enum :: enum u64 {
|
engine_entity_flags_enum :: enum u64 {
|
||||||
POLYGON_IS_BOX,
|
POLYGON_IS_BOX,
|
||||||
@@ -75,6 +58,32 @@ engine_entity_def :: struct {
|
|||||||
link_length_array : [dynamic]b2.Vec2,
|
link_length_array : [dynamic]b2.Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default_engine_entity_def :: proc() -> engine_entity_def
|
||||||
|
{
|
||||||
|
ret : engine_entity_def
|
||||||
|
ret.body_def = b2.DefaultBodyDef()
|
||||||
|
ret.shape_def = b2.DefaultShapeDef()
|
||||||
|
ret.shape_type = .polygonShape
|
||||||
|
ret.scale = 1
|
||||||
|
ret.centers = {{-10, 0}, {10, 0}}
|
||||||
|
ret.size = {2, 2}
|
||||||
|
ret.radius = 10
|
||||||
|
ret.body_count = 10
|
||||||
|
ret.rev_joint = b2.DefaultRevoluteJointDef()
|
||||||
|
|
||||||
|
//for dynamic polygon
|
||||||
|
vs : [4]b2.Vec2 = {
|
||||||
|
{-1.0, -1.0},
|
||||||
|
{-1.0, 1.0},
|
||||||
|
{1.0, 1.0},
|
||||||
|
{1.0, -1.0},
|
||||||
|
}
|
||||||
|
ret.is_loop = true
|
||||||
|
|
||||||
|
for v in vs do append(&ret.vertices, v)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
compare_engine_entity_def :: proc(a, b : engine_entity_def) -> bool
|
compare_engine_entity_def :: proc(a, b : engine_entity_def) -> bool
|
||||||
{
|
{
|
||||||
@@ -118,6 +127,7 @@ engine_entity :: struct {
|
|||||||
joints : [dynamic]b2.JointId,
|
joints : [dynamic]b2.JointId,
|
||||||
entity_flags : engine_entity_flags,
|
entity_flags : engine_entity_flags,
|
||||||
index : ^static_index,
|
index : ^static_index,
|
||||||
|
joint_id : b2.JointId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -259,7 +269,11 @@ engine_entity_single_body :: proc(def : ^engine_entity_def, world_id: b2.WorldId
|
|||||||
return new_entity
|
return new_entity
|
||||||
}
|
}
|
||||||
|
|
||||||
engine_create_chain_shape :: proc(def : ^engine_entity_def, world_id: b2.WorldId, index : i32) -> engine_entity
|
engine_create_chain_shape :: proc(
|
||||||
|
def : ^engine_entity_def,
|
||||||
|
world_id : b2.WorldId,
|
||||||
|
index : i32
|
||||||
|
) -> engine_entity
|
||||||
{
|
{
|
||||||
joint_def := def.rev_joint
|
joint_def := def.rev_joint
|
||||||
orig_pos := def.body_def.position
|
orig_pos := def.body_def.position
|
||||||
@@ -296,6 +310,23 @@ engine_create_chain_shape :: proc(def : ^engine_entity_def, world_id: b2.WorldId
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
engine_create_entity :: proc(
|
||||||
|
def : ^engine_entity_def,
|
||||||
|
world_id : b2.WorldId,
|
||||||
|
index : i32
|
||||||
|
) -> engine_entity
|
||||||
|
{
|
||||||
|
|
||||||
|
if .CHAIN not_in def.entity_flags
|
||||||
|
{
|
||||||
|
return engine_entity_single_body(def, world_id, index)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return engine_create_chain_shape(def, world_id, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package edit2d
|
||||||
|
|
||||||
|
|
||||||
|
game_mode :: enum
|
||||||
|
{
|
||||||
|
PLAY,
|
||||||
|
EDIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
engine_game :: struct {
|
||||||
|
curr_level : string,
|
||||||
|
interface : interface_state,
|
||||||
|
mode : game_mode,
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
package edit2d
|
||||||
|
|
||||||
|
import b2 "vendor:box2d"
|
||||||
|
import im "shared:odin-imgui"
|
||||||
|
import "vendor:glfw"
|
||||||
|
import "base:runtime"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handling input
|
||||||
|
|
||||||
|
Key Bindings
|
||||||
|
|
||||||
|
Meta keys
|
||||||
|
E -> Cycle thru interface edit mode
|
||||||
|
CTRL + S -> Save level
|
||||||
|
|
||||||
|
CTRL + C -> Copy entity
|
||||||
|
CTRL + V -> Paste entity
|
||||||
|
ALT + D -> Delete entity
|
||||||
|
|
||||||
|
CTRL + Mouse left -> Move according to edit mode
|
||||||
|
Mouse wheel -> Scale according to edit mode
|
||||||
|
Mouse Left -> select entity
|
||||||
|
Mouse Right -> create new entity
|
||||||
|
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
Command mode?
|
||||||
|
|
||||||
|
|
||||||
|
Different handling of keys for different modes
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handle all keys when the editor is in entity mode
|
||||||
|
|
||||||
|
Select
|
||||||
|
Add
|
||||||
|
Move
|
||||||
|
Delete
|
||||||
|
Copy paste
|
||||||
|
Resize
|
||||||
|
*/
|
||||||
|
handle_entity_mode :: proc(
|
||||||
|
$E : typeid,
|
||||||
|
state : ^engine_state,
|
||||||
|
game_data : ^$G,
|
||||||
|
) -> bool
|
||||||
|
{
|
||||||
|
ret := false
|
||||||
|
|
||||||
|
click_query_filter :: proc "c" (
|
||||||
|
shape_id : b2.ShapeId,
|
||||||
|
ctx : rawptr
|
||||||
|
) -> bool
|
||||||
|
{
|
||||||
|
context = runtime.default_context()
|
||||||
|
game := cast(^engine_game)ctx
|
||||||
|
index := i32(uintptr(b2.Shape_GetUserData(shape_id)))
|
||||||
|
|
||||||
|
game.interface.selected_entity = index
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
game: ^engine_game = game_data
|
||||||
|
interface := &game.interface
|
||||||
|
level := &game_data.levels[game.curr_level]
|
||||||
|
input := &state.input
|
||||||
|
mpos := camera_convert_screen_to_world(&state.draw.cam, input.mouse)
|
||||||
|
|
||||||
|
//Setlect entity
|
||||||
|
if is_key_pressed(state, glfw.MOUSE_BUTTON_LEFT)
|
||||||
|
{
|
||||||
|
aabb : b2.AABB = {mpos, mpos + 1}
|
||||||
|
|
||||||
|
r := b2.World_OverlapAABB(
|
||||||
|
level.engine.world_id,
|
||||||
|
aabb,
|
||||||
|
b2.DefaultQueryFilter(),
|
||||||
|
click_query_filter,
|
||||||
|
game,
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if is_key_pressed(state, glfw.MOUSE_BUTTON_RIGHT)
|
||||||
|
{
|
||||||
|
new_def := E {
|
||||||
|
engine = default_engine_entity_def(),
|
||||||
|
type = .NPC
|
||||||
|
}
|
||||||
|
|
||||||
|
interface.selected_entity = i32(len(level.entity_defs))
|
||||||
|
append(&level.entity_defs, new_def)
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
|
||||||
|
//Can only do these actions if a entity is selected
|
||||||
|
if interface.selected_entity != -1 && len(level.entity_defs) > 0
|
||||||
|
{
|
||||||
|
|
||||||
|
def := &level.entity_defs[interface.selected_entity]
|
||||||
|
|
||||||
|
//Copy paste
|
||||||
|
if is_key_down( state, glfw.KEY_LEFT_CONTROL)\\
|
||||||
|
&& is_key_pressed( state, glfw.KEY_C)
|
||||||
|
{
|
||||||
|
game_data.copied_def = def^
|
||||||
|
}
|
||||||
|
else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\
|
||||||
|
&& is_key_pressed( state, glfw.KEY_V)
|
||||||
|
{
|
||||||
|
|
||||||
|
//Paste the copied def on the mouse position
|
||||||
|
interface.selected_entity = i32(len(level.entity_defs))
|
||||||
|
game_data.copied_def.body_def.position = mpos
|
||||||
|
append(&level.entity_defs, game_data.copied_def)
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\
|
||||||
|
&& is_key_pressed( state, glfw.KEY_D)
|
||||||
|
{
|
||||||
|
//Remove the def
|
||||||
|
unordered_remove(&level.entity_defs, interface.selected_entity)
|
||||||
|
interface.selected_entity = -1
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
else if is_key_down( state, glfw.KEY_LEFT_CONTROL) \\
|
||||||
|
&& is_key_down( state, glfw.MOUSE_BUTTON_LEFT)
|
||||||
|
{
|
||||||
|
//move def
|
||||||
|
def.body_def.position = mpos
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
else if state.input.mouse_wheel.y != 0
|
||||||
|
{
|
||||||
|
def.scale += f32(state.input.mouse_prev.y / 5)
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
handle_input :: proc(
|
||||||
|
$E : typeid,
|
||||||
|
state : ^engine_state,
|
||||||
|
game_data : ^$G,
|
||||||
|
) -> bool
|
||||||
|
{
|
||||||
|
|
||||||
|
game : ^engine_game = &game_data.engine
|
||||||
|
|
||||||
|
/*
|
||||||
|
Applies for all mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
level := &game_data.levels[game.curr_level]
|
||||||
|
|
||||||
|
if is_key_pressed(state, glfw.KEY_E)
|
||||||
|
{
|
||||||
|
interface := &game.interface
|
||||||
|
if interface.edit_mode == max(EditMode)
|
||||||
|
{
|
||||||
|
interface.edit_mode = min(EditMode)
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
new_val := int(interface.edit_mode) + 1
|
||||||
|
interface.edit_mode = EditMode(new_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_key_pressed(state, glfw.KEY_S) && \\
|
||||||
|
is_key_down(state, glfw.KEY_LEFT_CONTROL)
|
||||||
|
{
|
||||||
|
level_save_to_file(level, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
if im.GetIO().WantCaptureMouse do return false
|
||||||
|
if im.GetIO().WantCaptureKeyboard do return false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#partial switch game.interface.edit_mode
|
||||||
|
{
|
||||||
|
case .ENTITY : handle_entity_mode(E, state, game_data)
|
||||||
|
// case .VERTICES : interface_handle_vertices_mode(state, game_data)
|
||||||
|
// case .OVERVIEW : interface_handle_overview_mode(state, game_data)
|
||||||
|
// case .CHAIN : interface_handle_chain_mode(state, game_data)
|
||||||
|
// case .JOINT : interface_handle_joint_mode(state, game_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+29
-5
@@ -1,7 +1,6 @@
|
|||||||
package ion
|
package edit2d
|
||||||
import "core:slice"
|
|
||||||
import "core:fmt"
|
|
||||||
import im "shared:odin-imgui"
|
import im "shared:odin-imgui"
|
||||||
|
import "core:fmt"
|
||||||
import b2 "vendor:box2d"
|
import b2 "vendor:box2d"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -27,7 +26,7 @@ interface_state :: struct
|
|||||||
{
|
{
|
||||||
entity_defs : [dynamic]^engine_entity_def,
|
entity_defs : [dynamic]^engine_entity_def,
|
||||||
entities : [dynamic]^engine_entity,
|
entities : [dynamic]^engine_entity,
|
||||||
selected_entity : ^i32,
|
selected_entity : i32,
|
||||||
world : ^engine_world,
|
world : ^engine_world,
|
||||||
state : ^engine_state,
|
state : ^engine_state,
|
||||||
|
|
||||||
@@ -42,6 +41,15 @@ interface_state :: struct
|
|||||||
curr_static_index : static_index_global,
|
curr_static_index : static_index_global,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface_get_default :: proc(interface: ^interface_state)
|
||||||
|
{
|
||||||
|
interface.selected_entity = 0
|
||||||
|
interface.vertex_index = new(i32)
|
||||||
|
interface.vertex_index^ = 0
|
||||||
|
interface.chain_index = new(i32)
|
||||||
|
interface.chain_index^ = 0
|
||||||
|
}
|
||||||
|
|
||||||
interface_draw_options :: proc(state: ^engine_state) {
|
interface_draw_options :: proc(state: ^engine_state) {
|
||||||
debug_draw := &state.draw.debug_draw
|
debug_draw := &state.draw.debug_draw
|
||||||
|
|
||||||
@@ -66,6 +74,23 @@ interface_draw_options :: proc(state: ^engine_state) {
|
|||||||
interface_all :: proc(interface: ^interface_state) -> bool
|
interface_all :: proc(interface: ^interface_state) -> bool
|
||||||
{
|
{
|
||||||
ret := false
|
ret := false
|
||||||
|
if interface.selected_entity <0
|
||||||
|
{
|
||||||
|
interface.selected_entity = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if im.Begin("Edit Mode", nil)
|
||||||
|
{
|
||||||
|
for type in EditMode
|
||||||
|
{
|
||||||
|
if im.RadioButton(fmt.ctprint(type), interface.edit_mode == type)
|
||||||
|
{
|
||||||
|
interface.edit_mode = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
im.End()
|
||||||
|
|
||||||
if im.Begin("Box2d interface")
|
if im.Begin("Box2d interface")
|
||||||
{
|
{
|
||||||
if im.BeginTabBar("Tabs")
|
if im.BeginTabBar("Tabs")
|
||||||
@@ -77,7 +102,6 @@ interface_all :: proc(interface: ^interface_state) -> bool
|
|||||||
im.EndTabItem()
|
im.EndTabItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if im.BeginTabItem("Joints", nil, {})
|
if im.BeginTabItem("Joints", nil, {})
|
||||||
{
|
{
|
||||||
if interface_joints(interface) do ret = true
|
if interface_joints(interface) do ret = true
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package ion
|
package edit2d
|
||||||
|
|
||||||
import b2 "vendor:box2d"
|
import b2 "vendor:box2d"
|
||||||
import im "shared:odin-imgui"
|
import im "shared:odin-imgui"
|
||||||
@@ -62,7 +62,7 @@ interface_edit_static_index :: proc(interface:^interface_state, def: ^engine_ent
|
|||||||
{
|
{
|
||||||
|
|
||||||
curr_index := &interface.curr_static_index
|
curr_index := &interface.curr_static_index
|
||||||
entity := interface.entities[interface.selected_entity^]
|
entity := interface.entities[interface.selected_entity]
|
||||||
|
|
||||||
level := interface.world
|
level := interface.world
|
||||||
|
|
||||||
@@ -216,11 +216,11 @@ interface_shape_def_editor :: proc(def: ^engine_entity_def) -> bool
|
|||||||
|
|
||||||
interface_entity :: proc(interface: ^interface_state) -> bool
|
interface_entity :: proc(interface: ^interface_state) -> bool
|
||||||
{
|
{
|
||||||
entity_selected := (interface.selected_entity^ != -1) && len(interface.entity_defs) > 0
|
entity_selected := (interface.selected_entity != -1) && len(interface.entity_defs) > 0
|
||||||
|
|
||||||
if entity_selected
|
if entity_selected
|
||||||
{
|
{
|
||||||
def := interface.entity_defs[interface.selected_entity^]
|
def := interface.entity_defs[interface.selected_entity]
|
||||||
def_old := def^
|
def_old := def^
|
||||||
|
|
||||||
ret := false
|
ret := false
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package ion
|
package edit2d
|
||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import b2 "vendor:box2d"
|
import b2 "vendor:box2d"
|
||||||
|
|||||||
+414
@@ -0,0 +1,414 @@
|
|||||||
|
package edit2d
|
||||||
|
|
||||||
|
import "core:strings"
|
||||||
|
import "core:os"
|
||||||
|
import "core:bytes"
|
||||||
|
import "core:path/filepath"
|
||||||
|
import "core:crypto/hash"
|
||||||
|
import "core:time"
|
||||||
|
import b2 "vendor:box2d"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:encoding/cbor"
|
||||||
|
|
||||||
|
/*
|
||||||
|
The engine can provide helper functions so that it'll be easier to write
|
||||||
|
the level code in the game
|
||||||
|
|
||||||
|
A Typical level code
|
||||||
|
*/
|
||||||
|
|
||||||
|
engine_world :: struct {
|
||||||
|
world_id : b2.WorldId,
|
||||||
|
|
||||||
|
//This in engine code?
|
||||||
|
static_indexes : map[static_index]int `cbor:"-"`,
|
||||||
|
relations : map[^static_index][dynamic]static_index_global `cbor:"-"`,
|
||||||
|
relations_serializeable : map[ static_index][dynamic]static_index_global,
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Seems okay to put the joint defs in engine rather than in game because
|
||||||
|
we don't add more attributes in joints in the game
|
||||||
|
|
||||||
|
Can be changed later without requireing refactor
|
||||||
|
*/
|
||||||
|
revolute_joint_defs : [dynamic]revolt_joint_def,
|
||||||
|
distant_joint_defs : [dynamic]distance_joint_def,
|
||||||
|
joints : [dynamic]b2.JointId `cbor:"-"`,
|
||||||
|
cam : Camera,
|
||||||
|
name : string,
|
||||||
|
}
|
||||||
|
|
||||||
|
@(require_results)
|
||||||
|
time_to_string_hms :: proc(t: time.Time, buf: []u8) -> (res: string) #no_bounds_check {
|
||||||
|
assert(len(buf) >= time.MIN_HMS_LEN)
|
||||||
|
h, m, s := time.clock(t)
|
||||||
|
|
||||||
|
buf[7] = '0' + u8(s % 10); s /= 10
|
||||||
|
buf[6] = '0' + u8(s)
|
||||||
|
buf[5] = '-'
|
||||||
|
buf[4] = '0' + u8(m % 10); m /= 10
|
||||||
|
buf[3] = '0' + u8(m)
|
||||||
|
buf[2] = '-'
|
||||||
|
buf[1] = '0' + u8(h % 10); h /= 10
|
||||||
|
buf[0] = '0' + u8(h)
|
||||||
|
|
||||||
|
return string(buf[:time.MIN_HMS_LEN])
|
||||||
|
}
|
||||||
|
|
||||||
|
cbor_flags : cbor.Encoder_Flags :
|
||||||
|
{
|
||||||
|
.Self_Described_CBOR,
|
||||||
|
.Deterministic_Int_Size,
|
||||||
|
.Deterministic_Float_Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
level_load_from_files :: proc(
|
||||||
|
$G, $L, $E: typeid,
|
||||||
|
game: ^G,
|
||||||
|
state: ^engine_state
|
||||||
|
)
|
||||||
|
{
|
||||||
|
files, err := os.read_all_directory_by_path("levels", context.allocator)
|
||||||
|
|
||||||
|
for file in files
|
||||||
|
{
|
||||||
|
if !os.exists(file.fullpath) do continue
|
||||||
|
if os.is_dir(file.fullpath) do continue
|
||||||
|
|
||||||
|
level_name := strings.clone(filepath.stem(file.fullpath))
|
||||||
|
game.levels[level_name] = {}
|
||||||
|
game.curr_level = strings.clone(level_name)
|
||||||
|
level_init_from_path(L, E, &game.levels[level_name], file.fullpath, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Returns engine_world by reading from the file
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
In the user code
|
||||||
|
|
||||||
|
level_name = strings.clone(filepath.stem(path))
|
||||||
|
game.levels[level_name] = {}
|
||||||
|
|
||||||
|
level_init_from_path(&game.levels[level_name], path)
|
||||||
|
|
||||||
|
*/
|
||||||
|
//We don't want the user to configure and use cbor everytime so we want to handle it
|
||||||
|
//
|
||||||
|
level_init_from_path :: proc(
|
||||||
|
$T, $E: typeid,
|
||||||
|
level_data : ^T,
|
||||||
|
path : string,
|
||||||
|
state: ^engine_state,
|
||||||
|
) -> (ret : bool)
|
||||||
|
{
|
||||||
|
|
||||||
|
level := cast(^engine_world)level_data
|
||||||
|
|
||||||
|
if os.is_file(path)
|
||||||
|
{
|
||||||
|
|
||||||
|
level_name := strings.clone(filepath.stem(path))
|
||||||
|
data, err := os.read_entire_file_from_path(path, context.allocator)
|
||||||
|
level.name = level_name
|
||||||
|
|
||||||
|
//?
|
||||||
|
level.relations = {}
|
||||||
|
level.relations = make(map[^static_index][dynamic]static_index_global)
|
||||||
|
|
||||||
|
if len(data) > 0
|
||||||
|
{
|
||||||
|
|
||||||
|
unmarshall_err := cbor.unmarshal(data, level_data)
|
||||||
|
delete(data)
|
||||||
|
|
||||||
|
if unmarshall_err == nil
|
||||||
|
{
|
||||||
|
|
||||||
|
level_reload(T, E, level_data)
|
||||||
|
state.draw.cam = level.cam
|
||||||
|
|
||||||
|
for key, val in level.relations_serializeable
|
||||||
|
{
|
||||||
|
index := level.static_indexes[key]
|
||||||
|
entity := level_data.entities[index]
|
||||||
|
|
||||||
|
level_data.relations[entity.index] = {}
|
||||||
|
for v in val do append(&level.relations[entity.index], v)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = true
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
ret = false
|
||||||
|
fmt.eprintf("Faield to unmarshall file %s \n", unmarshall_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
level_reload(T, E, level_data)
|
||||||
|
state.draw.cam = level.cam
|
||||||
|
|
||||||
|
for key, val in level.relations_serializeable
|
||||||
|
{
|
||||||
|
index := level.static_indexes[key]
|
||||||
|
entity := level_data.entities[index]
|
||||||
|
|
||||||
|
level_data.relations[entity.index] = {}
|
||||||
|
for v in val do append(&level.relations[entity.index], v)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle no data
|
||||||
|
fmt.println("Empty level")
|
||||||
|
ret = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
fmt.eprintln("File not exist or is a directory")
|
||||||
|
ret = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We can't create the entities
|
||||||
|
That should be done by the user
|
||||||
|
*/
|
||||||
|
level_reload :: proc($T: typeid, $E: typeid, level: ^T)
|
||||||
|
{
|
||||||
|
clear(&level.entities)
|
||||||
|
clear(&level.static_indexes)
|
||||||
|
|
||||||
|
if level.world_id != b2.nullWorldId do b2.DestroyWorld(level.world_id)
|
||||||
|
|
||||||
|
//initilize world
|
||||||
|
{
|
||||||
|
world_def := b2.DefaultWorldDef()
|
||||||
|
b2.SetLengthUnitsPerMeter(100)
|
||||||
|
|
||||||
|
//Get the gravity from config
|
||||||
|
world_def.gravity = {0, -9.8 * 100}
|
||||||
|
level.world_id = b2.CreateWorld(world_def)
|
||||||
|
}
|
||||||
|
|
||||||
|
for &def, i in &level.entity_defs
|
||||||
|
{
|
||||||
|
//new_entity := engine_create_entity(&def, level.world_id, i32(i))
|
||||||
|
new_entity : E
|
||||||
|
new_entity.type = def.type
|
||||||
|
new_entity.engine = engine_create_entity(&def, level.world_id, i32(i))
|
||||||
|
|
||||||
|
//new_entity := entity_create_by_type(&def, level.world_id, i32(i))
|
||||||
|
|
||||||
|
append(&level.entities, new_entity)
|
||||||
|
|
||||||
|
if new_entity.index != nil do level.static_indexes[new_entity.index^] = i
|
||||||
|
|
||||||
|
//if new_entity.type == .PLAYER do level.player_index = i32(i)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
for &def in &level.distant_joint_defs
|
||||||
|
{
|
||||||
|
//Get entity_a and entity_b
|
||||||
|
|
||||||
|
if def.entity_a <= 0 do continue
|
||||||
|
if def.entity_b <= 0 do continue
|
||||||
|
|
||||||
|
entity_a := &level.entities[level.static_indexes[def.entity_a]]
|
||||||
|
def.bodyIdA = entity_a.body_id
|
||||||
|
|
||||||
|
entity_b := &level.entities[level.static_indexes[def.entity_b]]
|
||||||
|
def.bodyIdB = entity_b.body_id
|
||||||
|
|
||||||
|
append(&level.joints, b2.CreateDistanceJoint(level.world_id, def.def))
|
||||||
|
}
|
||||||
|
|
||||||
|
for &def in &level.revolute_joint_defs
|
||||||
|
{
|
||||||
|
//Get entity_a and entity_b
|
||||||
|
|
||||||
|
if def.entity_a <= 0 do continue
|
||||||
|
if def.entity_b <= 0 do continue
|
||||||
|
|
||||||
|
entity_a := &level.entities[level.static_indexes[def.entity_a]]
|
||||||
|
def.bodyIdA = entity_a.body_id
|
||||||
|
|
||||||
|
entity_b := &level.entities[level.static_indexes[def.entity_b]]
|
||||||
|
def.bodyIdB = entity_b.body_id
|
||||||
|
|
||||||
|
append(&level.joints, b2.CreateRevoluteJoint(level.world_id, def.def))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//level.player.e = &level.entities[level.player_index]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Saves the level to the levels directory using level's name as filename
|
||||||
|
It uses core library's hash and cbor to searialize the level into bytes
|
||||||
|
|
||||||
|
It assumes that it uses engine_world struct as it's first member
|
||||||
|
|
||||||
|
Checks hash of current level state and file's hash
|
||||||
|
If both hash is same the exits
|
||||||
|
*/
|
||||||
|
|
||||||
|
level_save_to_file :: proc(level_data: ^$T, state: ^engine_state)
|
||||||
|
{
|
||||||
|
|
||||||
|
level := cast(^engine_world)level_data
|
||||||
|
level_path := fmt.tprintf("levels/%s.cbor", level.name)
|
||||||
|
|
||||||
|
level.cam = state.draw.cam
|
||||||
|
fmt.println(level_path)
|
||||||
|
|
||||||
|
//Relation serializeable
|
||||||
|
{
|
||||||
|
clear(&level.relations_serializeable)
|
||||||
|
for key, val in &level.relations
|
||||||
|
{
|
||||||
|
if key != nil
|
||||||
|
{
|
||||||
|
level.relations_serializeable[key^] = {}
|
||||||
|
for v in val do append(&level.relations_serializeable[key^], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binary, cbor_err := cbor.marshal_into_bytes(level_data^, cbor_flags)
|
||||||
|
|
||||||
|
if cbor_err == nil
|
||||||
|
{
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
fmt.eprintf("Failed to marshall level %s \n", cbor_err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Get hash of current loaded game and current saved game
|
||||||
|
binary_hash := hash.hash_bytes(.SHA3_512, binary)
|
||||||
|
fmt.println(binary_hash)
|
||||||
|
file_hash, hash_err := hash.hash_file_by_name(.SHA3_512, level_path)
|
||||||
|
|
||||||
|
//If the hash is same that means no changes has been made
|
||||||
|
|
||||||
|
|
||||||
|
if bytes.compare(binary_hash, file_hash) != 0
|
||||||
|
{
|
||||||
|
|
||||||
|
if !os.exists("levels")
|
||||||
|
{
|
||||||
|
err := os.mkdir("levels")
|
||||||
|
if err != nil
|
||||||
|
{
|
||||||
|
fmt.println("Failed to create levels directory")
|
||||||
|
fmt.println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create backeup before saving to file
|
||||||
|
|
||||||
|
We create backup by copying the current level file to the backup directory
|
||||||
|
Backup directory will contain the date and time of the backup
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
if os.exists(level_path)
|
||||||
|
{
|
||||||
|
//Get time and date to create file and folder name for backup
|
||||||
|
curr_time := time.now()
|
||||||
|
|
||||||
|
buf : [time.MIN_YYYY_DATE_LEN]u8
|
||||||
|
time_buf : [time.MIN_HMS_LEN]u8
|
||||||
|
date_str := time.to_string_yyyy_mm_dd(curr_time, buf[:])
|
||||||
|
time_str := time_to_string_hms(curr_time, time_buf[:])
|
||||||
|
|
||||||
|
backup_dir_path := fmt.tprintf("levels/backups/%s",date_str)
|
||||||
|
backup_file_path := fmt.tprintf("levels/backups/%s/%s-%s", date_str, level.name, time_str)
|
||||||
|
|
||||||
|
bk_err := os.make_directory_all(backup_dir_path)
|
||||||
|
|
||||||
|
if bk_err != nil
|
||||||
|
{
|
||||||
|
fmt.eprintf("Failed to create backup directory %s \n", bk_err)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
bk_err := os.copy_file(backup_file_path, level_path)
|
||||||
|
|
||||||
|
if bk_err != nil
|
||||||
|
{
|
||||||
|
fmt.eprintf("Failed to copy file %s\n", bk_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Save the current binary to file
|
||||||
|
save_err := os.write_entire_file_from_bytes(level_path, binary)
|
||||||
|
|
||||||
|
if save_err != nil
|
||||||
|
{
|
||||||
|
fmt.eprintf("Failed to save level %s\n", save_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}else{
|
||||||
|
fmt.println("No changes detected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
The engine assumes that it has attribute entities
|
||||||
|
*/
|
||||||
|
game_to_engine_entities :: proc($Game: typeid, game_data: ^Game, state: ^engine_state)
|
||||||
|
{
|
||||||
|
level := &game_data.levels[game_data.curr_level]
|
||||||
|
|
||||||
|
interface : ^interface_state = &game_data.interface
|
||||||
|
|
||||||
|
clear(&interface.entities)
|
||||||
|
clear(&interface.entity_defs)
|
||||||
|
|
||||||
|
for &entity in &level.entities
|
||||||
|
{
|
||||||
|
append(&interface.entities, &entity.engine)
|
||||||
|
}
|
||||||
|
for &def in &level.entity_defs
|
||||||
|
{
|
||||||
|
append(&interface.entity_defs, &def.engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface.state = state
|
||||||
|
interface.world = &level.engine
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user