From 0e269f0e832bac147ab6fbf910db987ee81e8795 Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 5 Jan 2026 07:08:35 -0600 Subject: [PATCH] rudimentary raycasting --- src/pp/pp_sim/pp_sim_core.c | 290 ++++++++++++++++++++++++------- src/pp/pp_sim/pp_sim_core.h | 22 ++- src/pp/pp_sim/pp_sim_transcode.c | 2 +- src/pp/pp_vis/pp_vis_core.c | 22 ++- src/pp/pp_vis/pp_vis_core.h | 31 ++-- 5 files changed, 273 insertions(+), 94 deletions(-) diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index 9974202c..92441099 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -1,7 +1,9 @@ S_Ctx S = Zi; Readonly S_Ent S_NilEnt = { + .last_xf = CompXformIdentity, .xf = CompXformIdentity, + .look = { 0, -1 }, }; //////////////////////////////////////////////////////////// @@ -305,22 +307,19 @@ Vec2 S_ClipPointToLine(Vec2 a, Vec2 b, Vec2 p, Vec2 normal) f32 vab_w = WedgeVec2(vab, normal); f32 vap_w = WedgeVec2(vap, normal); - f32 t; - { - f32 w = 1 / vab_w; - t = ClampF32(vap_w * w, 0, 1); - } + f32 w = 1 / vab_w; + f32 t = ClampF32(vap_w * w, 0, 1); Vec2 result = AddVec2(a, MulVec2(vab, t)); return result; } -S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1) +S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1, Vec2 sweep) { S_CollisionData result = Zi; TempArena scratch = BeginScratchNoConflict(); - f32 tolerance = 0.005f; // How close can non-overlapping shapes be before collision is considered + f32 tolerance = 0.00005f; // How close can non-overlapping shapes be before collision is considered f32 min_unique_pt_dist_sq = (0.001f * 0.001f); // NOTE: Should always be less than tolerance, since colliding = 1 if origin is within this distance. u32 max_iterations = 64; // To prevent extremely large prototypes when origin is in exact center of rounded feature @@ -493,12 +492,14 @@ S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1) //- EPA Vec2 normal = Zi; + Vec2 sweep_dir = NormVec2(sweep); + b32 is_sweeping = !IsVec2Zero(sweep_dir); S_MenkowskiSimplex closest_feature = Zi; { S_MenkowskiPoint *proto = 0; - u32 proto_count = 0; if (is_overlapping) { + u32 proto_count = 0; proto = ArenaNext(scratch.arena, S_MenkowskiPoint); { Assert(simplex.count == 3); @@ -518,30 +519,60 @@ S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1) // Find dir from origin to closest edge // FIXME: Winding order of ps & pe index - f32 closest_len_sq = Inf; S_MenkowskiPoint closest_a = Zi; S_MenkowskiPoint closest_b = Zi; u32 closest_b_index = 0; - for (u32 i = 0; i < proto_count; ++i) { - u32 a_index = i; - u32 b_index = (i < proto_count - 1) ? (i + 1) : 0; - S_MenkowskiPoint a = proto[a_index]; - S_MenkowskiPoint b = proto[b_index]; - - Vec2 vab = SubVec2(b.p, a.p); - Vec2 vao = NegVec2(a.p); - - f32 proj_ratio = ClampF32(DotVec2(vao, vab) / Vec2LenSq(vab), 0, 1); - Vec2 proj = AddVec2(a.p, MulVec2(vab, proj_ratio)); - - f32 proj_len_sq = Vec2LenSq(proj); - if (proj_len_sq < closest_len_sq - min_unique_pt_dist_sq) + if (is_sweeping) { - closest_a = a; - closest_b = b; - closest_b_index = b_index; - closest_len_sq = proj_len_sq; + // Find edge segment on prototype furthest along the sweep direction + // FIXME: This is not very stable at the moment, needs refining + for (u32 i = 0; i < proto_count; ++i) + { + u32 a_index = i; + u32 b_index = (i < proto_count - 1) ? (i + 1) : 0; + S_MenkowskiPoint a = proto[a_index]; + S_MenkowskiPoint b = proto[b_index]; + Vec2 vab = SubVec2(b.p, a.p); + if (WedgeVec2(vab, sweep_dir) * winding > 0) + { + f32 wedge_a = WedgeVec2(sweep_dir, a.p); + f32 wedge_b = WedgeVec2(sweep_dir, b.p); + i32 wedge_sign_a = (wedge_a >= 0) - (wedge_a < 0); + i32 wedge_sign_b = (wedge_b >= 0) - (wedge_b < 0); + if (wedge_sign_a != wedge_sign_b) + { + closest_a = a; + closest_b = b; + closest_b_index = b_index; + break; + } + } + } + } + else + { + // Find edge segment on prototype closest to the origin + f32 closest_len_sq = Inf; + for (u32 i = 0; i < proto_count; ++i) + { + u32 a_index = i; + u32 b_index = (i < proto_count - 1) ? (i + 1) : 0; + S_MenkowskiPoint a = proto[a_index]; + S_MenkowskiPoint b = proto[b_index]; + Vec2 vab = SubVec2(b.p, a.p); + Vec2 vao = NegVec2(a.p); + f32 proj_ratio = ClampF32(DotVec2(vao, vab) / Vec2LenSq(vab), 0, 1); + Vec2 proj = AddVec2(a.p, MulVec2(vab, proj_ratio)); + f32 proj_len_sq = Vec2LenSq(proj); + if (proj_len_sq < closest_len_sq - min_unique_pt_dist_sq) + { + closest_a = a; + closest_b = b; + closest_b_index = b_index; + closest_len_sq = proj_len_sq; + } + } } } Vec2 vab = SubVec2(closest_b.p, closest_a.p); @@ -550,21 +581,11 @@ S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1) Vec2 dir = MulVec2(PerpVec2(vab), winding); S_MenkowskiPoint m = S_MenkowskiPointFromShapes(shape0, shape1, dir); - // if (debug) - // { - // // If debug step count is reached, we still want to inspect the normal at the step - // normal = NormVec2(dir); - // closest_feature.a = closest_a; - // closest_feature.b = closest_b; - // closest_feature.count = 2; - // } - // Check validity of new point { b32 valid = 1; - { - // NOTE: Changing this value affects how stable normals are for circular colliders + // NOTE: Changing this value affects how stable normals are for rounded colliders //const f32 validity_epsilon = min_unique_pt_dist_sq; // Arbitrary //const f32 validity_epsilon = 0.00000000001f; // Arbitrary const f32 validity_epsilon = min_unique_pt_dist_sq; // Arbitrary @@ -611,6 +632,30 @@ S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1) // Insert new point into prototype proto[closest_b_index] = m; } + + // Debug draw + // { + // S_DebugDrawPoint(simplex.a.p, VEC4(1, 0, 0, 0.5)); + // S_DebugDrawPoint(simplex.b.p, VEC4(0, 1, 0, 0.5)); + // S_DebugDrawPoint(simplex.c.p, VEC4(0, 0, 1, 0.5)); + // S_DebugDrawLine(simplex.a.p, simplex.b.p, Color_Yellow); + // S_DebugDrawLine(simplex.b.p, simplex.c.p, Color_Yellow); + // S_DebugDrawLine(simplex.c.p, simplex.a.p, Color_Yellow); + // if (proto_count > 0) + // { + // for (i64 i = 0; i < proto_count; ++i) + // { + // i64 p1_idx = i + 1; + // if (p1_idx == proto_count) + // { + // p1_idx = 0; + // } + // Vec2 p0 = proto[i].p; + // Vec2 p1 = proto[p1_idx].p; + // S_DebugDrawLine(p0, p1, VEC4(0, 1, 0, 0.5)); + // } + // } + // } } else { @@ -872,13 +917,32 @@ S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1) CopyStructs(result.collision_points, collision_points, countof(collision_points)); result.collision_points_count = collision_points_count; + result.collision_normal = normal; result.closest_p0 = closest_p0; result.closest_p1 = closest_p1; + EndScratch(scratch); return result; } +S_RaycastData S_RaycastShape(S_Shape shape, Vec2 ray_start, Vec2 ray_dir) +{ + S_RaycastData result = Zi; + + // HACK: Create line shape as large as the world and perform a collision test + Vec2 ray_shape_p0 = ray_start; + Vec2 ray_shape_p1 = AddVec2(ray_start, MulVec2(NormVec2(ray_dir), S_WorldPitch * 1.414213562)); + S_Shape ray_shape = S_ShapeFromDesc(.count = 2, .points = { ray_shape_p0, ray_shape_p1 }); + + S_CollisionData cls = S_CollisionDataFromShapes(ray_shape, shape, ray_dir); + result.is_intersecting = cls.collision_points_count > 0; + result.normal = NegVec2(cls.collision_normal); + result.p = cls.closest_p1; + + return result; +} + //////////////////////////////////////////////////////////// //~ Lookup helpers @@ -1122,10 +1186,30 @@ void S_TickForever(WaveLaneCtx *lane) { target->move = ClampVec2Len(cmd.move, 1); target->look = cmd.look; + target->fire_held = cmd.fire_held; } } } + ////////////////////////////// + //- Spawn entities + + // { + // ////////////////////////////// + // //- Push bullets + + // for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) + // { + // if (ent->fire_held) + // { + // if (ent->has_weapon) + // { + + // } + // } + // } + // } + ////////////////////////////// //- Integrate control forces @@ -1177,39 +1261,40 @@ void S_TickForever(WaveLaneCtx *lane) for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) { - Xform last_xf = ent->last_xf; - S_Shape last_world_shape = S_MulXformShape(last_xf, ent->local_shape); - - Xform xf = ent->xf; - S_Shape world_shape = S_MulXformShape(xf, ent->local_shape); - - Rng2 bb0 = S_BoundingBoxFromShape(last_world_shape); - Rng2 bb1 = S_BoundingBoxFromShape(world_shape); - - if (constraints_count < max_constraints) + if (ent->is_player) { - S_Constraint *constraint = &constraints[constraints_count]; + Xform last_xf = ent->last_xf; + S_Shape last_world_shape = S_MulXformShape(last_xf, ent->local_shape); - // TODO: Real constraint data + Xform xf = ent->xf; + S_Shape world_shape = S_MulXformShape(xf, ent->local_shape); - constraint->ent0 = ent->key; - constraint->shape0 = world_shape; + Rng2 bb0 = S_BoundingBoxFromShape(last_world_shape); + Rng2 bb1 = S_BoundingBoxFromShape(world_shape); + if (constraints_count < max_constraints) + { + S_Constraint *constraint = &constraints[constraints_count]; - Rng2 test_rect = Zi; - test_rect.p0 = VEC2(-1, -1); - test_rect.p1 = VEC2(1, 1); + // TODO: Real constraint data - constraint->shape1 = S_ShapeFromDesc( - .radius = 0.5, - .count = 4, - .points[0] = VEC2(test_rect.p0.x, test_rect.p0.y), - .points[1] = VEC2(test_rect.p1.x, test_rect.p0.y), - .points[2] = VEC2(test_rect.p1.x, test_rect.p1.y), - .points[3] = VEC2(test_rect.p0.x, test_rect.p1.y), - ); + constraint->ent0 = ent->key; + constraint->shape0 = world_shape; - constraints_count += 1; + Rng2 test_rect = Zi; + test_rect.p0 = VEC2(-1, -1); + test_rect.p1 = VEC2(1, 1); + constraint->shape1 = S_ShapeFromDesc( + .radius = 0.5, + .count = 4, + .points[0] = VEC2(test_rect.p0.x, test_rect.p0.y), + .points[1] = VEC2(test_rect.p1.x, test_rect.p0.y), + .points[2] = VEC2(test_rect.p1.x, test_rect.p1.y), + .points[3] = VEC2(test_rect.p0.x, test_rect.p1.y), + ); + + constraints_count += 1; + } } } @@ -1263,7 +1348,7 @@ void S_TickForever(WaveLaneCtx *lane) // Vec2 shape0_pt = S_SupportPointFromShape(shape0, shape_dir); // Vec2 shape1_pt = S_SupportPointFromShape(shape1, neg_shape_dir); - S_CollisionData collision_data = S_CollisionDataFromShapes(shape0, shape1); + S_CollisionData collision_data = S_CollisionDataFromShapes(shape0, shape1, VEC2(0, 0)); Vec2 shape0_pt = collision_data.closest_p0; Vec2 shape1_pt = collision_data.closest_p1; @@ -1313,6 +1398,79 @@ void S_TickForever(WaveLaneCtx *lane) ent->xf = xf; } + + + + + ////////////////////////////// + //- Check for bullet collisions + + + + // TODO: Not like this + + + { + Struct(S_Bullet) + { + Vec2 start; + Vec2 dir; + + f32 speed; + }; + + PERSIST i64 bullets_count = 1; + PERSIST S_Bullet *bullets = 0; + if (!bullets) + { + bullets = PushStruct(PermArena(), S_Bullet); + S_Bullet *bullet = &bullets[0]; + bullet->start = VEC2(1, 0); + bullet->dir = NormVec2(VEC2(1, -1)); + bullet->speed = 1; + } + + for (i64 bullet_idx = 0; bullet_idx < bullets_count; ++bullet_idx) + { + S_Bullet *bullet = &bullets[bullet_idx]; + + // Raycast + for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) + { + Xform xf = ent->xf; + S_Shape world_shape = S_MulXformShape(xf, ent->local_shape); + + if (ent == S_FirstEnt(world)) + { + bullet->start = AddVec2(world_shape.centroid, Vec2WithLen(ent->look, world_shape.radius)); + bullet->dir = NormVec2(ent->look); + } + else + { + S_RaycastData raycast = S_RaycastShape(world_shape, bullet->start, bullet->dir); + Vec2 isect = raycast.p; + + if (raycast.is_intersecting) + { + S_DebugDrawPoint(isect, Color_Green); + S_DebugDrawLine(isect, AddVec2(isect, MulVec2(raycast.normal, 0.5)), Color_White); + } + else + { + S_DebugDrawPoint(isect, Color_Purple); + } + } + } + + S_DebugDrawLine(bullet->start, AddVec2(bullet->start, Vec2WithLen(bullet->dir, 0.5)), Color_Red); + } + } + + + + + + ////////////////////////////// //- Debug draw entities @@ -1425,7 +1583,7 @@ void S_TickForever(WaveLaneCtx *lane) S_Ent **ents_to_prune = PushStructsNoZero(frame_arena, S_Ent *, world->ents_count); for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) { - if (ent->prune >= 1) + if (ent->exists <= 0) { ents_to_prune[ents_to_prune_count] = ent; ents_to_prune_count += 1; diff --git a/src/pp/pp_sim/pp_sim_core.h b/src/pp/pp_sim/pp_sim_core.h index 234b12e3..7f5290fd 100644 --- a/src/pp/pp_sim/pp_sim_core.h +++ b/src/pp/pp_sim/pp_sim_core.h @@ -46,17 +46,19 @@ Struct(S_Ent) S_Ent *next_in_bin; S_Ent *prev_in_bin; - b32 valid; - ////////////////////////////// //- Persistent data S_Key key; + b32 valid; ////////////////////////////// //- Build data - f32 prune; + f32 exists; + + b32 is_player; + f32 health; Xform last_xf; Xform xf; @@ -66,6 +68,7 @@ Struct(S_Ent) f32 move_speed; Vec2 move; Vec2 look; + f32 fire_held; b32 has_weapon; @@ -122,12 +125,20 @@ Struct(S_CollisionData) // Contact manifold i32 collision_points_count; S_CollisionPoint collision_points[2]; + Vec2 collision_normal; // Closest points Vec2 closest_p0; Vec2 closest_p1; }; +Struct(S_RaycastData) +{ + Vec2 p; + Vec2 normal; + b32 is_intersecting; +}; + //////////////////////////////////////////////////////////// //~ Constraint types @@ -219,6 +230,7 @@ Struct(S_Cmd) S_Key target; Vec2 move; Vec2 look; + b32 fire_held; }; Struct(S_CmdNode) @@ -353,7 +365,9 @@ S_SupportPoint S_SupportPointFromShape(S_Shape shape, Vec2 dir); S_MenkowskiPoint S_MenkowskiPointFromShapes(S_Shape shape0, S_Shape shape1, Vec2 dir); S_ClippedLine S_ClipLineToLine(Vec2 a0, Vec2 b0, Vec2 a1, Vec2 b1, Vec2 normal); Vec2 S_ClipPointToLine(Vec2 a, Vec2 b, Vec2 p, Vec2 normal); -S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1); + +S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1, Vec2 sweep); +S_RaycastData S_RaycastShape(S_Shape shape, Vec2 ray_start, Vec2 ray_dir); //////////////////////////////////////////////////////////// //~ Lookup helpers diff --git a/src/pp/pp_sim/pp_sim_transcode.c b/src/pp/pp_sim/pp_sim_transcode.c index 8ddf7dd8..69f4ff91 100644 --- a/src/pp/pp_sim/pp_sim_transcode.c +++ b/src/pp/pp_sim/pp_sim_transcode.c @@ -103,7 +103,7 @@ S_TranscodeResult S_TranscodeWorld(Arena *arena, S_World *src_world, String src_ { for (i64 i = 0; i < ents_count; ++i) { - S_Ent *raw_ent = (S_Ent *)BB_ReadBytesRaw(&br, ents_count * sizeof(S_Ent)); + S_Ent *raw_ent = (S_Ent *)BB_ReadBytesRaw(&br, sizeof(S_Ent)); if (raw_ent) { S_Ent *ent = PushStructNoZero(arena, S_Ent); diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index bca2d267..000ccbc1 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -574,7 +574,7 @@ void V_TickForever(WaveLaneCtx *lane) S_Ent **ents_to_prune = PushStructsNoZero(frame->arena, S_Ent *, world->ents_count); for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) { - if (ent->prune >= 1) + if (ent->exists <= 0) { ents_to_prune[ents_to_prune_count] = ent; ents_to_prune_count += 1; @@ -847,7 +847,7 @@ void V_TickForever(WaveLaneCtx *lane) { b32 m1_held = frame->held_buttons[Button_M1]; b32 m2_held = frame->held_buttons[Button_M2]; - frame->selection_mode = V_SelectionMode_Tile; + // frame->selection_mode = V_SelectionMode_Tile; if (m1_held) { frame->is_selecting = 1; @@ -908,7 +908,7 @@ void V_TickForever(WaveLaneCtx *lane) for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent)) { S_Shape ent_shape = S_MulXformShape(ent->xf, ent->local_shape); - b32 is_hovered = S_CollisionDataFromShapes(ent_shape, cursor_shape).collision_points_count > 0; + b32 is_hovered = S_CollisionDataFromShapes(ent_shape, cursor_shape, VEC2(0, 0)).collision_points_count > 0; if (is_hovered) { hovered_ent = ent; @@ -1021,7 +1021,7 @@ void V_TickForever(WaveLaneCtx *lane) ////////////////////////////// //- Build panels - if (frame->is_editing) + if (TweakBool("Show editor UI", 0) && frame->is_editing) { Struct(PanelDfsNode) { PanelDfsNode *next; b32 visited; V_Panel *panel; UI_Checkpoint cp; }; PanelDfsNode *first_panel_dfs = PushStruct(frame->arena, PanelDfsNode); @@ -2117,6 +2117,7 @@ void V_TickForever(WaveLaneCtx *lane) UI_BuildLabelF("Cursor world pos: %F", FmtFloat2(frame->world_cursor)); UI_BuildLabelF("Cursor tile pos: %F", FmtSint2(tile_pos)); UI_BuildLabelF("Cursor tile idx: %F", FmtSint(tile_idx)); + UI_BuildLabelF("Hovered ent: %F", S_FmtKey(hovered_ent->key)); } UI_BuildSpacer(UI_PIX(padding, 1), Axis_Y); { @@ -2388,15 +2389,18 @@ void V_TickForever(WaveLaneCtx *lane) S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta); cmd->delta.kind = S_DeltaKind_RawEnt; S_Ent *ent = &cmd->delta.ent; + *ent = S_NilEnt; ent->key = V.player_key; ent->xf = XformFromPos(frame->world_cursor); - ent->move_speed = 0.05; + ent->move_speed = 0.075; + ent->is_player = 1; ent->local_shape = S_ShapeFromDesc( .mass = 10, .count = 1, .radius = 0.3, ); ent->has_weapon = 1; + ent->exists = 1; } break; case V_CmdKind_spawn_dummy: @@ -2404,27 +2408,29 @@ void V_TickForever(WaveLaneCtx *lane) S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta); cmd->delta.kind = S_DeltaKind_RawEnt; S_Ent *ent = &cmd->delta.ent; + *ent = S_NilEnt; ent->key = S_RandKey(); ent->xf = XformFromPos(frame->world_cursor); - ent->move_speed = 0.05; + ent->move_speed = 0.075; + ent->is_player = 1; ent->local_shape = S_ShapeFromDesc( .mass = 10, .count = 1, .radius = 0.3, ); ent->has_weapon = 1; + ent->exists = 1; } break; case V_CmdKind_delete: { if (hovered_ent->valid) { - LogDebugF("Sending delete command for ent %F", S_FmtKey(hovered_ent->key)); S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta); cmd->delta.kind = S_DeltaKind_RawEnt; S_Ent *ent = &cmd->delta.ent; ent->key = hovered_ent->key; - ent->prune = 1; + ent->exists = 0; } } break; } diff --git a/src/pp/pp_vis/pp_vis_core.h b/src/pp/pp_vis/pp_vis_core.h index 626be30b..8a01421f 100644 --- a/src/pp/pp_vis/pp_vis_core.h +++ b/src/pp/pp_vis/pp_vis_core.h @@ -1,21 +1,21 @@ //////////////////////////////////////////////////////////// //~ Command table -#define V_CmdsTableXMacro(X) \ - X(nop, NOP, V_CmdDescFlag_HideFromPalette, V_HOTKEY(0), ) \ - X(exit_program, Exit Program, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_Escape ) ) \ - X(toggle_palette, Toggle Command Palette, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_P, .ctrl = 1, .shift = 1 ), ) \ - X(zoom_in, Zoom In, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelUp ), ) \ - X(zoom_out, Zoom Out, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelDown ), ) \ - X(toggle_editor, Toggle Editor, V_CmdDescFlag_None, V_HOTKEY( Button_F1 ), ) \ - X(toggle_ui_debug, Toggle UI Debug, V_CmdDescFlag_None, V_HOTKEY( Button_F5 ), ) \ - X(toggle_console, Toggle Developer Console, V_CmdDescFlag_None, V_HOTKEY( Button_GraveAccent ), ) \ - X(toggle_fullscreen, Toggle Fullscreen Mode, V_CmdDescFlag_None, V_HOTKEY( Button_Enter, .alt = 1 ) ) \ - X(toggle_window_topmost, Toggle Window Topmost, V_CmdDescFlag_None, V_HOTKEY( Button_F4 ), ) \ - X(spawn, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \ - X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_R ), ) \ - X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \ -/* --------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ +#define V_CmdsTableXMacro(X) \ + X(nop, NOP, V_CmdDescFlag_HideFromPalette, V_HOTKEY(0), ) \ + X(exit_program, Exit Program, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_Escape ) ) \ + X(toggle_palette, Toggle Command Palette, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_E, .ctrl = 1 ), V_HOTKEY( Button_P, .ctrl = 1, .shift = 1 ), ) \ + X(zoom_in, Zoom In, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelUp ), ) \ + X(zoom_out, Zoom Out, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelDown ), ) \ + X(toggle_editor, Toggle Editor, V_CmdDescFlag_None, V_HOTKEY( Button_F1 ), ) \ + X(toggle_ui_debug, Toggle UI Debug, V_CmdDescFlag_None, V_HOTKEY( Button_F5 ), ) \ + X(toggle_console, Toggle Developer Console, V_CmdDescFlag_None, V_HOTKEY( Button_GraveAccent ), ) \ + X(toggle_fullscreen, Toggle Fullscreen Mode, V_CmdDescFlag_None, V_HOTKEY( Button_Enter, .alt = 1 ) ) \ + X(toggle_window_topmost, Toggle Window Topmost, V_CmdDescFlag_None, V_HOTKEY( Button_F4 ), ) \ + X(spawn, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \ + X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_R ), ) \ + X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \ +/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ //////////////////////////////////////////////////////////// //~ Theme types @@ -197,6 +197,7 @@ Enum(V_DrawFlag) Enum(V_EditMode) { + V_EditMode_None, V_EditMode_Tile, };