diff --git a/game/editor.odin b/game/editor.odin index c3a2765..a5ccb56 100644 --- a/game/editor.odin +++ b/game/editor.odin @@ -1,6 +1,9 @@ package game +import "core:fmt" import lg "core:math/linalg" +import "game:physics/collision" +import "game:ui" import rl "libs:raylib" update_editor :: proc(es: ^Editor_State, dt: f32) { @@ -40,11 +43,21 @@ update_editor :: proc(es: ^Editor_State, dt: f32) { clear(&es.point_selection) } if rl.IsKeyPressed(.G) { - es.track_edit_state = .Move - es.move_axis = .None - es.total_movement_world = {} - world_stack_push(&es.world_stack) - // es.initial_point_pos = g_mem.track.points[es.selected_track_point] + track := &world_stack_current(&es.world_stack).track + selected_count := 0 + es.move_initial_pos = 0 + for k in es.point_selection { + es.move_initial_pos += track.points[k] + selected_count += 1 + } + if selected_count > 0 { + es.move_initial_pos /= f32(selected_count) + es.track_edit_state = .Move + es.move_axis = .None + es.total_movement_world = {} + + world_stack_push(&es.world_stack) + } } } } @@ -89,24 +102,53 @@ update_editor :: proc(es: ^Editor_State, dt: f32) { camera := game_camera_3d() - mouse_delta := rl.GetMouseDelta() * 0.05 - - view_rotation := lg.transpose(rl.GetCameraMatrix(camera)) - view_rotation[3].xyz = 0 - view_proj := view_rotation * rl.MatrixOrtho(-1, 1, 1, -1, -1, 1) + mouse_pos := rl.GetMousePosition() + prev_mouse_pos := es.prev_mouse_pos + prev_ray := rl.GetScreenToWorldRay(prev_mouse_pos, camera) + ray := rl.GetScreenToWorldRay(mouse_pos, camera) axes_buf: [2]rl.Vector3 colors_buf: [2]rl.Color axes, _ := get_movement_axes(es.move_axis, &axes_buf, &colors_buf) movement_world: rl.Vector3 - for axis in axes { - axis_screen := (rl.Vector4{axis.x, axis.y, axis.z, 1} * view_proj).xy - axis_screen = lg.normalize0(axis_screen) + if len(axes) == 2 { + normal := lg.normalize0(lg.cross(axes[0], axes[1])) + plane := collision.plane_from_point_normal(es.move_initial_pos, normal) - movement_screen := lg.dot(axis_screen, mouse_delta) * axis_screen - movement_world += - (rl.Vector4{movement_screen.x, movement_screen.y, 0, 1} * rl.MatrixInvert(view_proj)).xyz + _, pos1 := collision.intersect_ray_plane( + prev_ray.position, + prev_ray.direction, + plane, + ) + _, pos2 := collision.intersect_ray_plane( + ray.position, + ray.direction, + plane, + ) + world_delta := pos2 - pos1 + world_delta -= lg.dot(world_delta, plane.normal) * plane.normal + movement_world += world_delta + } else if len(axes) == 1 { + for axis in axes { + tangent := lg.cross(es.move_initial_pos - camera.position, axis) + normal := lg.normalize0(lg.cross(axis, tangent)) + + plane := collision.plane_from_point_normal(es.move_initial_pos, normal) + _, pos1 := collision.intersect_ray_plane( + prev_ray.position, + prev_ray.direction, + plane, + ) + _, pos2 := collision.intersect_ray_plane( + ray.position, + ray.direction, + plane, + ) + + world_delta := pos2 - pos1 + movement_world += lg.dot(axis, world_delta) * axis + } } for k in es.point_selection { @@ -117,8 +159,26 @@ update_editor :: proc(es: ^Editor_State, dt: f32) { } } } - } - // world := world_stack_current(&es.world_stack) - // update_world(world, dt, false) + + es.prev_mouse_pos = rl.GetMousePosition() + + ui_ctx := &g_mem.ui_context + if ui.window(ui_ctx, "Editor", ui.Rect{x = 500, y = 0, w = 500, h = 600}) { + cnt := ui.get_current_container(ui_ctx) + cnt.rect.w = max(cnt.rect.w, 500) + cnt.rect.h = max(cnt.rect.h, 600) + + if .ACTIVE in ui.header(ui_ctx, "Details", {.EXPANDED}) { + ui.layout_row(ui_ctx, {100, -1}) + + ui.label(ui_ctx, "mov") + ui.label(ui_ctx, fmt.tprintf("%v", es.move_initial_pos)) + } + } + world := world_stack_current(&es.world_stack) + config := World_Update_Config { + commit_physics = false, + } + update_world(world, dt, config) } diff --git a/game/game.odin b/game/game.odin index 6dcd00e..a040fef 100644 --- a/game/game.odin +++ b/game/game.odin @@ -222,13 +222,18 @@ Editor_State :: struct { point_selection: map[int]bool, track_edit_state: Track_Edit_State, move_axis: Move_Axis, + prev_mouse_pos: rl.Vector2, + move_initial_pos: rl.Vector3, total_movement_world: rl.Vector3, - initial_point_pos: rl.Vector3, camera: Free_Camera, } editor_state_init :: proc(es: ^Editor_State, num_snapshots: int) { world_stack_init(&es.world_stack, num_snapshots) + world_stack_current(&es.world_stack).debug_state = { + show_menu = true, + draw_physics_scene = true, + } } editor_state_destroy :: proc(es: ^Editor_State) { @@ -348,7 +353,12 @@ get_movement_axes :: proc( return out_axes[0:0], out_colors[0:0] } -update_world :: proc(world: ^World, dt: f32, single_step_physics: bool) { +World_Update_Config :: struct { + single_step_physics: bool, + commit_physics: bool, +} + +update_world :: proc(world: ^World, dt: f32, config: World_Update_Config) { if !world.pause { car_model := assets.get_model(&g_mem.assetman, "assets/toyota_corolla_ae86_trueno.glb") car_bounds := rl.GetModelBoundingBox(car_model) @@ -608,8 +618,8 @@ update_world :: proc(world: ^World, dt: f32, single_step_physics: bool) { &world.physics_scene, SOLVER_CONFIG, dt, - commit = true, - step_mode = single_step_physics ? physics.Step_Mode.Single : physics.Step_Mode.Accumulated_Time, + commit = config.commit_physics, + step_mode = config.single_step_physics ? physics.Step_Mode.Single : physics.Step_Mode.Accumulated_Time, ) } } @@ -626,7 +636,11 @@ update_runtime_world :: proc(runtime_world: ^Runtime_World, dt: f32) { if !runtime_world.rewind_simulation { next_world := runtime_world_next_world(runtime_world) copy_world(next_world, cur_world) - update_world(next_world, dt, should_single_step) + config := World_Update_Config { + commit_physics = true, + single_step_physics = should_single_step, + } + update_world(next_world, dt, config) if runtime_world.commit_simulation { runtime_world.current_world_index = @@ -676,7 +690,7 @@ collect_camera_input :: proc() -> (delta: rl.Vector2, sense: f32) { mouse_delta := g_mem.mouse_captured ? rl.GetMouseDelta() : 0 - MOUSE_SENSE :: 0.01 + MOUSE_SENSE :: 0.005 GAMEPAD_SENSE :: 1 if linalg.length2(mouse_delta) > linalg.length2(gamepad_delta) { @@ -786,6 +800,14 @@ update :: proc() { if rl.IsKeyPressed(.TAB) { g_mem.editor = !g_mem.editor + + // When switching from editor to game, copy editor world into game world + if !g_mem.editor { + es := &g_mem.es + + editor_world := world_stack_current(&es.world_stack) + copy_world(runtime_world_current_world(&g_mem.runtime_world), editor_world) + } } if rl.IsKeyPressed(.F1) { @@ -921,8 +943,8 @@ draw_world :: proc(world: ^World) { if world.debug_state.show_menu { ui_ctx := &g_mem.ui_context - ui.push_font_size_style(ui_ctx, 20) - defer ui.pop_style(ui_ctx) + //ui.push_font_size_style(ui_ctx, 20) + // defer ui.pop_style(ui_ctx) if ui.window(ui_ctx, "Debug Menu", {x = 0, y = 0, w = 200, h = 300}) { cnt := ui.get_current_container(ui_ctx) @@ -961,7 +983,6 @@ draw_world :: proc(world: ^World) { } } } - } car_matrix := rl.QuaternionToMatrix(car_body.q) @@ -1050,9 +1071,9 @@ draw :: proc() { for v in soa_zip(axis = axes, color = colors) { rlgl.Color4ub(v.color.r, v.color.g, v.color.b, v.color.a) start, end := - es.initial_point_pos - + es.move_initial_pos - v.axis * 100000, - es.initial_point_pos + + es.move_initial_pos + v.axis * 100000 rlgl.Vertex3f(start.x, start.y, start.z) @@ -1251,9 +1272,8 @@ game_init :: proc() { ui.init(&g_mem.ui_context) g_mem.ui_context.default_style.font = ui.Font(&g_mem.default_font) - g_mem.ui_context.default_style.font_size = 32 - g_mem.ui_context.text_width = ui.rl_measure_text_width - g_mem.ui_context.text_height = ui.rl_measure_text_height + g_mem.ui_context.default_style.font_size = 20 + g_mem.ui_context.text_size = ui.rl_measure_text_2d game_hot_reloaded(g_mem) } diff --git a/game/physics/collision/collision.odin b/game/physics/collision/collision.odin index 7c853fe..1a20790 100644 --- a/game/physics/collision/collision.odin +++ b/game/physics/collision/collision.odin @@ -620,6 +620,13 @@ intersect_ray_triangle :: proc( return t, normal, barycentric, true } +intersect_ray_plane :: proc(origin, dir: Vec3, plane: Plane) -> (t: f32, pos: Vec3) { + t = (dot(plane.normal, (plane.normal * plane.dist - origin))) / dot(plane.normal, dir) + pos = origin + dir * t + + return +} + intersect_segment_plane :: proc( segment: [2]Vec3, plane: Plane, diff --git a/game/physics/immediate.odin b/game/physics/immediate.odin index b6708d2..5b2b018 100644 --- a/game/physics/immediate.odin +++ b/game/physics/immediate.odin @@ -10,26 +10,115 @@ Body_Config_Inertia_Mode :: enum { Explicit, } +Immedate_State :: struct($T: typeid) { + handle: T, + // When was this referenced last time (frame number) + last_ref: u32, +} + +Immediate_Container :: struct($T: typeid) { + items: map[u32]Immedate_State(T), + num_items: i32, +} + +// When ok = true, it means we found an existing item +immediate_container_find_or_add :: proc( + c: $T/^Immediate_Container($E), + simulation_frame: u32, + id: u32, +) -> ( + handle: ^E, + ok: bool, +) { + if id in c.items { + item := &c.items[id] + if item.last_ref != simulation_frame { + item.last_ref = simulation_frame + c.num_items += 1 + } + handle = &item.handle + ok = true + } else { + c.num_items += 1 + c.items[id] = { + handle = {}, + last_ref = simulation_frame, + } + item := &c.items[id] + handle = &item.handle + ok = false + } + + return +} + +// Returns removed handles, allocated using allocator +immediate_container_prune :: proc( + c: $T/^Immediate_Container($E), + simulation_frame: u32, + allocator := context.temp_allocator, +) -> ( + removed_handles: []E, +) { + if int(c.num_items) == len(c.items) { + return + } + + num_unreferenced := len(c.items) - int(c.num_items) + assert(num_unreferenced >= 0) + + removed_handles = make([]E, num_unreferenced, allocator) + items_to_remove := make([]u32, num_unreferenced, context.temp_allocator) + + i := 0 + for k, &v in c.items { + if v.last_ref != simulation_frame { + items_to_remove[i] = k + removed_handles[i] = v.handle + i += 1 + } + } + + assert(i == len(items_to_remove)) + + for k in items_to_remove { + delete_key(&c.items, k) + } + + return +} + +copy_map :: proc(dst, src: $T/^map[$K]$V) { + clear(dst) + reserve_map(dst, len(src)) + + for k, v in src { + dst[k] = v + } +} + +immediate_container_copy :: proc(dst, src: $T/^Immediate_Container($E)) { + copy_map(&dst.items, &src.items) + dst.num_items = src.num_items +} + +immediate_container_destroy :: proc(c: $T/^Immediate_Container($E)) { + delete_map(c.items) +} + immediate_body :: proc(scene: ^Scene, id: u32, config: Body_Config) -> (handle: Body_Handle) { state := &scene.solver_state sim_state := get_sim_state(scene) - if id in state.immedate_bodies { - body := &state.immedate_bodies[id] - if body.last_ref != state.simulation_frame { - body.last_ref = state.simulation_frame - state.num_referenced_bodies += 1 - } - handle = body.handle - update_body_from_config(sim_state, get_body(sim_state, handle), config) + h, ok := immediate_container_find_or_add(&state.immediate_bodies, state.simulation_frame, id) + + if ok { + update_body_from_config(sim_state, get_body(sim_state, h^), config) } else { - state.num_referenced_bodies += 1 - handle = add_body(sim_state, config) - state.immedate_bodies[id] = { - handle = handle, - last_ref = state.simulation_frame, - } + h^ = add_body(sim_state, config) } + handle = h^ + return } @@ -41,22 +130,17 @@ immediate_suspension_constraint :: proc( handle: Suspension_Constraint_Handle, ) { state := &scene.solver_state - if id in state.immediate_suspension_constraints { - constraint := &state.immediate_suspension_constraints[id] - if constraint.last_ref != state.simulation_frame { - constraint.last_ref = state.simulation_frame - state.num_referenced_suspension_constraints += 1 - } - handle = constraint.handle - } else { - state.num_referenced_suspension_constraints += 1 - handle = add_suspension_constraint(get_sim_state(scene), {}) - state.immediate_suspension_constraints[id] = { - handle = handle, - last_ref = state.simulation_frame, - } + h, ok := immediate_container_find_or_add( + &state.immediate_suspension_constraints, + state.simulation_frame, + id, + ) + + if !ok { + h^ = add_suspension_constraint(get_sim_state(scene), {}) } + handle = h^ update_suspension_constraint_from_config( get_suspension_constraint(get_sim_state(scene), handle), config, @@ -74,117 +158,36 @@ immediate_engine :: proc( ) { state := &scene.solver_state sim_state := get_sim_state(scene) - if id in state.immediate_engines { - engine := &state.immediate_engines[id] - if engine.last_ref != state.simulation_frame { - engine.last_ref = state.simulation_frame - state.num_referenced_engines += 1 - } - handle = engine.handle - update_engine_from_config(sim_state, get_engine(sim_state, handle), config) + h, ok := immediate_container_find_or_add(&state.immediate_engines, state.simulation_frame, id) + + if ok { + update_engine_from_config(sim_state, get_engine(sim_state, h^), config) } else { - state.num_referenced_engines += 1 - handle = add_engine(sim_state, config) - state.immediate_engines[id] = { - handle = handle, - last_ref = state.simulation_frame, - } + h^ = add_engine(sim_state, config) } + handle = h^ return } prune_immediate :: proc(scene: ^Scene) { tracy.Zone() - prune_immediate_bodies(scene) - prune_immediate_suspension_constraints(scene) - prune_immediate_engines(scene) -} -// TODO: Generic version -prune_immediate_bodies :: proc(scene: ^Scene) { + sim_state := get_sim_state(scene) state := &scene.solver_state - if int(state.num_referenced_bodies) == len(state.immedate_bodies) { - return + removed_bodies := immediate_container_prune(&state.immediate_bodies, state.simulation_frame) + removed_wheels := immediate_container_prune( + &state.immediate_suspension_constraints, + state.simulation_frame, + ) + removed_engines := immediate_container_prune(&state.immediate_engines, state.simulation_frame) + for handle in removed_bodies { + remove_body(sim_state, handle) } - - num_unreferenced_bodies := len(state.immedate_bodies) - int(state.num_referenced_bodies) - assert(num_unreferenced_bodies >= 0) - - bodies_to_remove := make([]u32, num_unreferenced_bodies, context.temp_allocator) - - i := 0 - for k, &v in state.immedate_bodies { - if v.last_ref != state.simulation_frame { - bodies_to_remove[i] = k - i += 1 - } + for handle in removed_wheels { + remove_suspension_constraint(sim_state, handle) } - - assert(i == len(bodies_to_remove)) - - for k in bodies_to_remove { - handle := state.immedate_bodies[k].handle - delete_key(&state.immedate_bodies, k) - remove_body(get_sim_state(scene), handle) - } -} - -prune_immediate_suspension_constraints :: proc(scene: ^Scene) { - state := &scene.solver_state - if int(state.num_referenced_suspension_constraints) == - len(state.immediate_suspension_constraints) { - return - } - - num_unreferenced_constraints := - len(state.immediate_suspension_constraints) - - int(state.num_referenced_suspension_constraints) - assert(num_unreferenced_constraints >= 0) - - constraints_to_remove := make([]u32, num_unreferenced_constraints, context.temp_allocator) - - i := 0 - for k, &v in state.immediate_suspension_constraints { - if v.last_ref != state.simulation_frame { - constraints_to_remove[i] = k - i += 1 - } - } - - assert(i == len(constraints_to_remove)) - - for k in constraints_to_remove { - handle := state.immediate_suspension_constraints[k].handle - delete_key(&state.immediate_suspension_constraints, k) - remove_suspension_constraint(get_sim_state(scene), handle) - } -} - -prune_immediate_engines :: proc(scene: ^Scene) { - state := &scene.solver_state - if int(state.num_referenced_engines) == len(state.immediate_engines) { - return - } - - num_unreferenced_engines := len(state.immediate_engines) - int(state.num_referenced_engines) - assert(num_unreferenced_engines >= 0) - - engines_to_remove := make([]u32, num_unreferenced_engines, context.temp_allocator) - - i := 0 - for k, &v in state.immediate_engines { - if v.last_ref != state.simulation_frame { - engines_to_remove[i] = k - i += 1 - } - } - - assert(i == len(engines_to_remove)) - - for k in engines_to_remove { - handle := state.immediate_engines[k].handle - delete_key(&state.immediate_engines, k) - remove_engine(get_sim_state(scene), handle) + for handle in removed_engines { + remove_engine(sim_state, handle) } } diff --git a/game/physics/simulation.odin b/game/physics/simulation.odin index b5c0c0a..f8fecb1 100644 --- a/game/physics/simulation.odin +++ b/game/physics/simulation.odin @@ -30,51 +30,32 @@ Solver_Config :: struct { } Solver_State :: struct { - accumulated_time: f32, + accumulated_time: f32, // Incremented when simulate is called (not simulate_step) - simulation_frame: u32, + simulation_frame: u32, // Number of immediate bodies referenced this frame - num_referenced_bodies: i32, - num_referenced_suspension_constraints: i32, - num_referenced_engines: i32, - immedate_bodies: map[u32]Immedate_State(Body_Handle), - immediate_suspension_constraints: map[u32]Immedate_State(Suspension_Constraint_Handle), - immediate_engines: map[u32]Immedate_State(Engine_Handle), -} - -copy_map :: proc(dst, src: $T/^map[$K]$V) { - clear(dst) - reserve_map(dst, len(src)) - - for k, v in src { - dst[k] = v - } + immediate_bodies: Immediate_Container(Body_Handle), + immediate_suspension_constraints: Immediate_Container(Suspension_Constraint_Handle), + immediate_engines: Immediate_Container(Engine_Handle), } copy_solver_state :: proc(dst, src: ^Solver_State) { dst.accumulated_time = src.accumulated_time dst.simulation_frame = src.simulation_frame - dst.num_referenced_bodies = src.num_referenced_bodies - dst.num_referenced_suspension_constraints = src.num_referenced_suspension_constraints - dst.num_referenced_engines = src.num_referenced_engines - - copy_map(&dst.immedate_bodies, &src.immedate_bodies) - copy_map(&dst.immediate_suspension_constraints, &src.immediate_suspension_constraints) - copy_map(&dst.immediate_engines, &src.immediate_engines) + immediate_container_copy(&dst.immediate_bodies, &src.immediate_bodies) + immediate_container_copy( + &dst.immediate_suspension_constraints, + &src.immediate_suspension_constraints, + ) + immediate_container_copy(&dst.immediate_engines, &src.immediate_engines) } destroy_solver_state :: proc(state: ^Solver_State) { - delete(state.immedate_bodies) - delete(state.immediate_suspension_constraints) - delete(state.immediate_engines) -} - -Immedate_State :: struct($T: typeid) { - handle: T, - // When was this referenced last time (frame number) - last_ref: u32, + immediate_container_destroy(&state.immediate_bodies) + immediate_container_destroy(&state.immediate_suspension_constraints) + immediate_container_destroy(&state.immediate_engines) } MAX_STEPS :: 10 @@ -253,9 +234,9 @@ simulate :: proc( } state.simulation_frame += 1 - state.num_referenced_bodies = 0 - state.num_referenced_suspension_constraints = 0 - state.num_referenced_engines = 0 + state.immediate_bodies.num_items = 0 + state.immediate_suspension_constraints.num_items = 0 + state.immediate_engines.num_items = 0 } GLOBAL_PLANE :: collision.Plane { diff --git a/game/ui/microui.odin b/game/ui/microui.odin index 36b2a4c..1250583 100644 --- a/game/ui/microui.odin +++ b/game/ui/microui.odin @@ -241,8 +241,7 @@ Style :: struct { Context :: struct { /* callbacks */ - text_width: proc(font: Font, font_size: i32, str: string) -> i32, - text_height: proc(font: Font, font_size: i32) -> i32, + text_size: proc(font: Font, font_size: i32, str: string) -> [2]i32, draw_frame: proc(ctx: ^Context, rect: Rect, colorid: Color_Type), /* core state */ default_style: Style, @@ -304,6 +303,7 @@ unclipped_rect := Rect{0, 0, 0x1000000, 0x1000000} default_style := Style { font = nil, size = {68, 10}, + font_size = 16, padding = 5, spacing = 4, indent = 24, @@ -402,8 +402,7 @@ init :: proc( } begin :: proc(ctx: ^Context) { - assert(ctx.text_width != nil, "ctx.text_width is not set") - assert(ctx.text_height != nil, "ctx.text_height is not set") + assert(ctx.text_size != nil, "ctx.text_size is not set") ctx.command_list.idx = 0 ctx.root_list.idx = 0 ctx.line_segments_num = 0 @@ -792,12 +791,8 @@ draw_text :: proc( pos: Vec2, color: Color, ) { - rect := Rect { - pos.x, - pos.y, - ctx.text_width(font, font_size, str), - ctx.text_height(font, font_size), - } + text_size := ctx.text_size(font, font_size, str) + rect := Rect{pos.x, pos.y, text_size.x, text_size.y} clipped := check_clip(ctx, rect) switch clipped { case .NONE: // okay @@ -1049,13 +1044,13 @@ draw_control_text :: proc( pos: Vec2 font := get_style(ctx).font font_size := get_style(ctx).font_size - tw := ctx.text_width(font, font_size, str) + ts := ctx.text_size(font, font_size, str) push_clip_rect(ctx, rect) - pos.y = rect.y + (rect.h - ctx.text_height(font, font_size)) / 2 + pos.y = rect.y + (rect.h - ts.y) / 2 if .ALIGN_CENTER in opt { - pos.x = rect.x + (rect.w - tw) / 2 + pos.x = rect.x + (rect.w - ts.x) / 2 } else if .ALIGN_RIGHT in opt { - pos.x = rect.x + rect.w - tw - get_style(ctx).padding + pos.x = rect.x + rect.w - ts.x - get_style(ctx).padding } else { pos.x = rect.x + get_style(ctx).padding } @@ -1109,7 +1104,7 @@ text :: proc(ctx: ^Context, text: string) { font_size := style.font_size color := style.colors[.TEXT] layout_begin_column(ctx) - layout_row(ctx, {-1}, ctx.text_height(font, font_size)) + layout_row(ctx, {-1}, ctx.text_size(font, font_size, text).y) for len(text) > 0 { w: i32 start: int @@ -1118,12 +1113,12 @@ text :: proc(ctx: ^Context, text: string) { for ch, i in text { if ch == ' ' || ch == '\n' { word := text[start:i] - w += ctx.text_width(font, font_size, word) + w += ctx.text_size(font, font_size, word).x if w > r.w && start != 0 { end = start break } - w += ctx.text_width(font, font_size, text[i:i + 1]) + w += ctx.text_size(font, font_size, text[i:i + 1]).x if ch == '\n' { end = i + 1 break @@ -1317,7 +1312,7 @@ textbox_raw :: proc( if ctx.mouse_pos.x < r.x + ctx.textbox_offset + - ctx.text_width(font, font_size, string(textbuf[:i])) { + ctx.text_size(font, font_size, string(textbuf[:i])).x { idx = i break } @@ -1336,10 +1331,11 @@ textbox_raw :: proc( if ctx.focus_id == id { text_color := style.colors[.TEXT] sel_color := style.colors[.SELECTION_BG] - textw := ctx.text_width(font, font_size, textstr) - texth := ctx.text_height(font, font_size) - headx := ctx.text_width(font, font_size, textstr[:ctx.textbox_state.selection[0]]) - tailx := ctx.text_width(font, font_size, textstr[:ctx.textbox_state.selection[1]]) + text_size := ctx.text_size(font, font_size, textstr) + textw := text_size.x + texth := text_size.y + headx := ctx.text_size(font, font_size, textstr[:ctx.textbox_state.selection[0]]).x + tailx := ctx.text_size(font, font_size, textstr[:ctx.textbox_state.selection[1]]).x ofmin := max(get_style(ctx).padding - headx, r.w - textw - get_style(ctx).padding) ofmax := min(r.w - headx - get_style(ctx).padding, get_style(ctx).padding) ctx.textbox_offset = clamp(ctx.textbox_offset, ofmin, ofmax) diff --git a/game/ui/raylib.odin b/game/ui/raylib.odin index 6dc7db9..d6e4068 100644 --- a/game/ui/raylib.odin +++ b/game/ui/raylib.odin @@ -3,6 +3,7 @@ package ui import "core:log" +import "core:math" import "core:strings" import rl "libs:raylib" import "libs:raylib/rlgl" @@ -13,23 +14,23 @@ _ :: log default_atlas_texture: rl.Texture2D rl_init :: proc() { - rl.UnloadTexture(default_atlas_texture) - default_atlas_texture = {} + rl.UnloadTexture(default_atlas_texture) + default_atlas_texture = {} - image := rl.Image{ - data = &default_atlas_alpha, - width = DEFAULT_ATLAS_WIDTH, - height = DEFAULT_ATLAS_HEIGHT, - mipmaps = 1, - format = .UNCOMPRESSED_GRAYSCALE, - } + image := rl.Image { + data = &default_atlas_alpha, + width = DEFAULT_ATLAS_WIDTH, + height = DEFAULT_ATLAS_HEIGHT, + mipmaps = 1, + format = .UNCOMPRESSED_GRAYSCALE, + } - default_atlas_texture = rl.LoadTextureFromImage(image) - rl.SetTextureFilter(default_atlas_texture, .POINT) + default_atlas_texture = rl.LoadTextureFromImage(image) + rl.SetTextureFilter(default_atlas_texture, .POINT) - gl.BindTexture(gl.TEXTURE_2D, default_atlas_texture.id) - gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_SWIZZLE_A, gl.RED) - gl.BindTexture(gl.TEXTURE_2D, 0) + gl.BindTexture(gl.TEXTURE_2D, default_atlas_texture.id) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_SWIZZLE_A, gl.RED) + gl.BindTexture(gl.TEXTURE_2D, 0) } to_rl_color :: proc(c: Color) -> rl.Color { @@ -40,7 +41,7 @@ to_rl_rect :: proc(r: Rect) -> rl.Rectangle { return rl.Rectangle{x = f32(r.x), y = f32(r.y), width = f32(r.w), height = f32(r.h)} } -rl_measure_text_2d :: #force_inline proc(font: Font, font_size: i32, text: string) -> rl.Vector2 { +rl_measure_text_2d :: #force_inline proc(font: Font, font_size: i32, text: string) -> [2]i32 { font := (cast(^rl.Font)font) size := rl.MeasureTextEx( font^ if font != nil else rl.GetFontDefault(), @@ -49,15 +50,7 @@ rl_measure_text_2d :: #force_inline proc(font: Font, font_size: i32, text: strin f32(font_size / (font.baseSize if font != nil else 10)), ) - return size -} - -rl_measure_text_width :: proc(font: Font, font_size: i32, text: string) -> i32 { - return i32(rl_measure_text_2d(font, font_size, text).x) -} - -rl_measure_text_height :: proc(font: Font, font_size: i32) -> i32 { - return i32(rl_measure_text_2d(font, font_size, "A").y) + return {i32(math.ceil(size.x)), i32(math.ceil(size.y))} } rl_draw :: proc(ctx: ^Context) { @@ -88,13 +81,13 @@ rl_draw :: proc(ctx: ^Context) { rl.DrawLineStrip(&segments[0], i32(len(segments)), to_rl_color(c.color)) case ^Command_Jump: case ^Command_Icon: - src_rect := default_atlas[int(c.id)] - x := f32(c.rect.x + (c.rect.w - src_rect.w) / 2) - y := f32(c.rect.y + (c.rect.h - src_rect.h) / 2) + src_rect := default_atlas[int(c.id)] + x := f32(c.rect.x + (c.rect.w - src_rect.w) / 2) + y := f32(c.rect.y + (c.rect.h - src_rect.h) / 2) rl.DrawTextureRec( default_atlas_texture, to_rl_rect(src_rect), - {x, y}, + {x, y}, to_rl_color(c.color), ) }