rudimentary raycasting

This commit is contained in:
jacob 2026-01-05 07:08:35 -06:00
parent 1839899027
commit 0e269f0e83
5 changed files with 273 additions and 94 deletions

View File

@ -1,7 +1,9 @@
S_Ctx S = Zi; S_Ctx S = Zi;
Readonly S_Ent S_NilEnt = { Readonly S_Ent S_NilEnt = {
.last_xf = CompXformIdentity,
.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 vab_w = WedgeVec2(vab, normal);
f32 vap_w = WedgeVec2(vap, normal); f32 vap_w = WedgeVec2(vap, normal);
f32 t; f32 w = 1 / vab_w;
{ f32 t = ClampF32(vap_w * w, 0, 1);
f32 w = 1 / vab_w;
t = ClampF32(vap_w * w, 0, 1);
}
Vec2 result = AddVec2(a, MulVec2(vab, t)); Vec2 result = AddVec2(a, MulVec2(vab, t));
return result; 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; S_CollisionData result = Zi;
TempArena scratch = BeginScratchNoConflict(); 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. 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 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 //- EPA
Vec2 normal = Zi; Vec2 normal = Zi;
Vec2 sweep_dir = NormVec2(sweep);
b32 is_sweeping = !IsVec2Zero(sweep_dir);
S_MenkowskiSimplex closest_feature = Zi; S_MenkowskiSimplex closest_feature = Zi;
{ {
S_MenkowskiPoint *proto = 0; S_MenkowskiPoint *proto = 0;
u32 proto_count = 0;
if (is_overlapping) if (is_overlapping)
{ {
u32 proto_count = 0;
proto = ArenaNext(scratch.arena, S_MenkowskiPoint); proto = ArenaNext(scratch.arena, S_MenkowskiPoint);
{ {
Assert(simplex.count == 3); 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 // Find dir from origin to closest edge
// FIXME: Winding order of ps & pe index // FIXME: Winding order of ps & pe index
f32 closest_len_sq = Inf;
S_MenkowskiPoint closest_a = Zi; S_MenkowskiPoint closest_a = Zi;
S_MenkowskiPoint closest_b = Zi; S_MenkowskiPoint closest_b = Zi;
u32 closest_b_index = 0; u32 closest_b_index = 0;
for (u32 i = 0; i < proto_count; ++i)
{ {
u32 a_index = i; if (is_sweeping)
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; // Find edge segment on prototype furthest along the sweep direction
closest_b = b; // FIXME: This is not very stable at the moment, needs refining
closest_b_index = b_index; for (u32 i = 0; i < proto_count; ++i)
closest_len_sq = proj_len_sq; {
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); 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); Vec2 dir = MulVec2(PerpVec2(vab), winding);
S_MenkowskiPoint m = S_MenkowskiPointFromShapes(shape0, shape1, dir); 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 // Check validity of new point
{ {
b32 valid = 1; 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 = min_unique_pt_dist_sq; // Arbitrary
//const f32 validity_epsilon = 0.00000000001f; // Arbitrary //const f32 validity_epsilon = 0.00000000001f; // Arbitrary
const f32 validity_epsilon = min_unique_pt_dist_sq; // 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 // Insert new point into prototype
proto[closest_b_index] = m; 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 else
{ {
@ -872,13 +917,32 @@ S_CollisionData S_CollisionDataFromShapes(S_Shape shape0, S_Shape shape1)
CopyStructs(result.collision_points, collision_points, countof(collision_points)); CopyStructs(result.collision_points, collision_points, countof(collision_points));
result.collision_points_count = collision_points_count; result.collision_points_count = collision_points_count;
result.collision_normal = normal;
result.closest_p0 = closest_p0; result.closest_p0 = closest_p0;
result.closest_p1 = closest_p1; result.closest_p1 = closest_p1;
EndScratch(scratch); EndScratch(scratch);
return result; 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 //~ Lookup helpers
@ -1122,10 +1186,30 @@ void S_TickForever(WaveLaneCtx *lane)
{ {
target->move = ClampVec2Len(cmd.move, 1); target->move = ClampVec2Len(cmd.move, 1);
target->look = cmd.look; 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 //- 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)) for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
{ {
Xform last_xf = ent->last_xf; if (ent->is_player)
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)
{ {
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; Rng2 bb0 = S_BoundingBoxFromShape(last_world_shape);
constraint->shape0 = world_shape; Rng2 bb1 = S_BoundingBoxFromShape(world_shape);
if (constraints_count < max_constraints)
{
S_Constraint *constraint = &constraints[constraints_count];
Rng2 test_rect = Zi; // TODO: Real constraint data
test_rect.p0 = VEC2(-1, -1);
test_rect.p1 = VEC2(1, 1);
constraint->shape1 = S_ShapeFromDesc( constraint->ent0 = ent->key;
.radius = 0.5, constraint->shape0 = world_shape;
.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; 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 shape0_pt = S_SupportPointFromShape(shape0, shape_dir);
// Vec2 shape1_pt = S_SupportPointFromShape(shape1, neg_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 shape0_pt = collision_data.closest_p0;
Vec2 shape1_pt = collision_data.closest_p1; Vec2 shape1_pt = collision_data.closest_p1;
@ -1313,6 +1398,79 @@ void S_TickForever(WaveLaneCtx *lane)
ent->xf = xf; 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 //- 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); 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)) 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[ents_to_prune_count] = ent;
ents_to_prune_count += 1; ents_to_prune_count += 1;

View File

@ -46,17 +46,19 @@ Struct(S_Ent)
S_Ent *next_in_bin; S_Ent *next_in_bin;
S_Ent *prev_in_bin; S_Ent *prev_in_bin;
b32 valid;
////////////////////////////// //////////////////////////////
//- Persistent data //- Persistent data
S_Key key; S_Key key;
b32 valid;
////////////////////////////// //////////////////////////////
//- Build data //- Build data
f32 prune; f32 exists;
b32 is_player;
f32 health;
Xform last_xf; Xform last_xf;
Xform xf; Xform xf;
@ -66,6 +68,7 @@ Struct(S_Ent)
f32 move_speed; f32 move_speed;
Vec2 move; Vec2 move;
Vec2 look; Vec2 look;
f32 fire_held;
b32 has_weapon; b32 has_weapon;
@ -122,12 +125,20 @@ Struct(S_CollisionData)
// Contact manifold // Contact manifold
i32 collision_points_count; i32 collision_points_count;
S_CollisionPoint collision_points[2]; S_CollisionPoint collision_points[2];
Vec2 collision_normal;
// Closest points // Closest points
Vec2 closest_p0; Vec2 closest_p0;
Vec2 closest_p1; Vec2 closest_p1;
}; };
Struct(S_RaycastData)
{
Vec2 p;
Vec2 normal;
b32 is_intersecting;
};
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Constraint types //~ Constraint types
@ -219,6 +230,7 @@ Struct(S_Cmd)
S_Key target; S_Key target;
Vec2 move; Vec2 move;
Vec2 look; Vec2 look;
b32 fire_held;
}; };
Struct(S_CmdNode) 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_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); 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); 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 //~ Lookup helpers

View File

@ -103,7 +103,7 @@ S_TranscodeResult S_TranscodeWorld(Arena *arena, S_World *src_world, String src_
{ {
for (i64 i = 0; i < ents_count; ++i) 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) if (raw_ent)
{ {
S_Ent *ent = PushStructNoZero(arena, S_Ent); S_Ent *ent = PushStructNoZero(arena, S_Ent);

View File

@ -574,7 +574,7 @@ void V_TickForever(WaveLaneCtx *lane)
S_Ent **ents_to_prune = PushStructsNoZero(frame->arena, S_Ent *, world->ents_count); 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)) 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[ents_to_prune_count] = ent;
ents_to_prune_count += 1; ents_to_prune_count += 1;
@ -847,7 +847,7 @@ void V_TickForever(WaveLaneCtx *lane)
{ {
b32 m1_held = frame->held_buttons[Button_M1]; b32 m1_held = frame->held_buttons[Button_M1];
b32 m2_held = frame->held_buttons[Button_M2]; b32 m2_held = frame->held_buttons[Button_M2];
frame->selection_mode = V_SelectionMode_Tile; // frame->selection_mode = V_SelectionMode_Tile;
if (m1_held) if (m1_held)
{ {
frame->is_selecting = 1; 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)) for (S_Ent *ent = S_FirstEnt(world); ent->valid; ent = S_NextEnt(ent))
{ {
S_Shape ent_shape = S_MulXformShape(ent->xf, ent->local_shape); 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) if (is_hovered)
{ {
hovered_ent = ent; hovered_ent = ent;
@ -1021,7 +1021,7 @@ void V_TickForever(WaveLaneCtx *lane)
////////////////////////////// //////////////////////////////
//- Build panels //- 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; }; Struct(PanelDfsNode) { PanelDfsNode *next; b32 visited; V_Panel *panel; UI_Checkpoint cp; };
PanelDfsNode *first_panel_dfs = PushStruct(frame->arena, PanelDfsNode); 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 world pos: %F", FmtFloat2(frame->world_cursor));
UI_BuildLabelF("Cursor tile pos: %F", FmtSint2(tile_pos)); UI_BuildLabelF("Cursor tile pos: %F", FmtSint2(tile_pos));
UI_BuildLabelF("Cursor tile idx: %F", FmtSint(tile_idx)); 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); 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); S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta);
cmd->delta.kind = S_DeltaKind_RawEnt; cmd->delta.kind = S_DeltaKind_RawEnt;
S_Ent *ent = &cmd->delta.ent; S_Ent *ent = &cmd->delta.ent;
*ent = S_NilEnt;
ent->key = V.player_key; ent->key = V.player_key;
ent->xf = XformFromPos(frame->world_cursor); 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( ent->local_shape = S_ShapeFromDesc(
.mass = 10, .mass = 10,
.count = 1, .count = 1,
.radius = 0.3, .radius = 0.3,
); );
ent->has_weapon = 1; ent->has_weapon = 1;
ent->exists = 1;
} break; } break;
case V_CmdKind_spawn_dummy: case V_CmdKind_spawn_dummy:
@ -2404,27 +2408,29 @@ void V_TickForever(WaveLaneCtx *lane)
S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta); S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta);
cmd->delta.kind = S_DeltaKind_RawEnt; cmd->delta.kind = S_DeltaKind_RawEnt;
S_Ent *ent = &cmd->delta.ent; S_Ent *ent = &cmd->delta.ent;
*ent = S_NilEnt;
ent->key = S_RandKey(); ent->key = S_RandKey();
ent->xf = XformFromPos(frame->world_cursor); 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( ent->local_shape = S_ShapeFromDesc(
.mass = 10, .mass = 10,
.count = 1, .count = 1,
.radius = 0.3, .radius = 0.3,
); );
ent->has_weapon = 1; ent->has_weapon = 1;
ent->exists = 1;
} break; } break;
case V_CmdKind_delete: case V_CmdKind_delete:
{ {
if (hovered_ent->valid) if (hovered_ent->valid)
{ {
LogDebugF("Sending delete command for ent %F", S_FmtKey(hovered_ent->key));
S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta); S_Cmd *cmd = V_PushSimCmd(S_CmdKind_Delta);
cmd->delta.kind = S_DeltaKind_RawEnt; cmd->delta.kind = S_DeltaKind_RawEnt;
S_Ent *ent = &cmd->delta.ent; S_Ent *ent = &cmd->delta.ent;
ent->key = hovered_ent->key; ent->key = hovered_ent->key;
ent->prune = 1; ent->exists = 0;
} }
} break; } break;
} }

View File

@ -1,21 +1,21 @@
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Command table //~ Command table
#define V_CmdsTableXMacro(X) \ #define V_CmdsTableXMacro(X) \
X(nop, NOP, V_CmdDescFlag_HideFromPalette, V_HOTKEY(0), ) \ X(nop, NOP, V_CmdDescFlag_HideFromPalette, V_HOTKEY(0), ) \
X(exit_program, Exit Program, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_Escape ) ) \ 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(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_in, Zoom In, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelUp ), ) \
X(zoom_out, Zoom Out, V_CmdDescFlag_HideFromPalette, V_HOTKEY( Button_MWheelDown ), ) \ 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_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_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_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_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(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, Spawn/Teleport Player, V_CmdDescFlag_None, V_HOTKEY( Button_T ), ) \
X(spawn_dummy, Spawn Dummy, V_CmdDescFlag_None, V_HOTKEY( Button_R ), ) \ 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 ), ) \ X(delete, Delete entity at cursor, V_CmdDescFlag_None, V_HOTKEY( Button_M2 ), ) \
/* --------------------------------------------------------------------------------------------------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Theme types //~ Theme types
@ -197,6 +197,7 @@ Enum(V_DrawFlag)
Enum(V_EditMode) Enum(V_EditMode)
{ {
V_EditMode_None,
V_EditMode_Tile, V_EditMode_Tile,
}; };