a-game

2D platformer written from scratch.
git clone git://git.amin.space/a-game.git
Log | Files | Refs | README | LICENSE

world.c (9899B)


      1 internal struct AbsolutePos world_pos_recanonicalize(struct AbsolutePos p)
      2 {
      3     struct AbsolutePos p_final = p;
      4     v2i room_index_delta = {0};
      5 
      6     if (p.local.x < 0.0f)
      7     {
      8         room_index_delta.x -= 1;
      9         p_final.local.x = math_wrap(p.local.x, 0.0f, ROOM_DIM_X);
     10     }
     11     else if (p.local.x > ROOM_DIM_X)
     12     {
     13         room_index_delta.x += 1;
     14         p_final.local.x = math_wrap(p.local.x, 0.0f, ROOM_DIM_X);
     15     }
     16 
     17     if (p.local.y < 0.0f)
     18     {
     19         room_index_delta.y -= 1;
     20         p_final.local.y = math_wrap(p.local.y, 0.0f, ROOM_DIM_Y);
     21     }
     22     else if (p.local.y > ROOM_DIM_Y)
     23     {
     24         room_index_delta.y += 1;
     25         p_final.local.y = math_wrap(p.local.y, 0.0f, ROOM_DIM_Y);
     26     }
     27 
     28     p_final.room = math_v2i_a(p.room, room_index_delta);
     29     return p_final;
     30 }
     31 
     32 internal v2 world_tile_get_sw_corner(v2 tile_center_pos)
     33 {
     34     v2 tile_sw_corner = {
     35         .x = tile_center_pos.x - (TILE_SIZE * 0.5f),
     36         .y = tile_center_pos.y - (TILE_SIZE * 0.5f),
     37     };
     38     return tile_sw_corner;
     39 }
     40 
     41 internal v2 world_tile_get_center(v2 tile_sw_corner_pos)
     42 {
     43     v2 tile_center = {
     44         .x = tile_sw_corner_pos.x + (TILE_SIZE * 0.5f),
     45         .y = tile_sw_corner_pos.y + (TILE_SIZE * 0.5f),
     46     };
     47     return tile_center;
     48 }
     49 
     50 internal struct AbsolutePos world_tile_pos_recanonicalize(struct AbsolutePos tile_p)
     51 {
     52     v2 tile_center = world_tile_get_center(tile_p.local);
     53     struct AbsolutePos tile_center_canonical_pos = world_pos_recanonicalize(
     54         (struct AbsolutePos) {
     55             .room = tile_p.room,
     56             .local = tile_center,
     57         });
     58 
     59     struct AbsolutePos tile_canonical_pos = {
     60         .room = tile_center_canonical_pos.room,
     61         .local = world_tile_get_sw_corner(tile_center_canonical_pos.local),
     62     };
     63     return tile_canonical_pos;
     64 }
     65 
     66 internal v2i world_room_get_chunk_index(v2i room_i)
     67 {
     68     // NOTE(amin): truncated integer division here is intentional. We want a
     69     // square of CHUNK_NUM_ROOMS rooms to all have the same chunk index.
     70 
     71     v2i chunk_i = {
     72         (room_i.x - (intr_mod(room_i.x, CHUNK_ROOMS_PER_SIDE))) / CHUNK_ROOMS_PER_SIDE,
     73         (room_i.y - (intr_mod(room_i.y, CHUNK_ROOMS_PER_SIDE))) / CHUNK_ROOMS_PER_SIDE,
     74     };
     75     return chunk_i;
     76 }
     77 
     78 internal u32 world_index_hash(v2i index)
     79 {
     80     // http://graphics.stanford.edu/~seander/bithacks.html#InterleaveTableObvious
     81     u16 low_x = index.x & 0xFFFF;
     82     u16 low_y = index.y & 0xFFFF;
     83     u32 morton_code = 0;
     84 
     85     // TODO: profile this
     86     for (u32 i = 0; i < sizeof(u32); i++)
     87     {
     88         morton_code |= (low_x & 1u << i) << i | (low_y & 1u << i) << (i + 1);
     89     }
     90     // NOTE(amin): We're not really after any of the specific cool things about
     91     // morton codes. I just picked a hash function that seemed sensible given
     92     // the requirements. It makes intuitive sense to mix, rather than
     93     // concatenate the bits of the two numbers, since information from both
     94     // will remain even if the upper bits of the hash value are discarded (for
     95     // example, when the hash value is modulo'd against the number of hash
     96     // table buckets).
     97     return morton_code;
     98 }
     99 
    100 internal struct WorldChunk* world_chunk_get(struct World *world, v2i chunk_i)
    101 {
    102     struct WorldChunk *result = NULL;
    103 
    104     u32 hash_value = world_index_hash(chunk_i);
    105     size_t hash_table_bucket = hash_value % NUM_CHUNK_BUCKETS;
    106     assert(hash_table_bucket < NUM_CHUNK_BUCKETS);
    107 
    108     struct WorldChunk *chunk = world->chunk_ht[hash_table_bucket];
    109     while (chunk)
    110     {
    111         if (math_v2i_eq(chunk->index, chunk_i))
    112         {
    113             result = chunk;
    114             break;
    115         }
    116         chunk = chunk->next;
    117     }
    118 
    119     return result;
    120 }
    121 
    122 internal struct WorldChunk* world_chunk_get_or_create(struct World *world, v2i chunk_i, struct StackAllocator *allocator)
    123 {
    124     struct WorldChunk *result = NULL;
    125 
    126     u32 hash_value = world_index_hash(chunk_i);
    127     size_t hash_table_bucket = hash_value % NUM_CHUNK_BUCKETS;
    128     assert(hash_table_bucket < NUM_CHUNK_BUCKETS);
    129 
    130     struct WorldChunk *chunk = world->chunk_ht[hash_table_bucket];
    131     while (chunk)
    132     {
    133         if (math_v2i_eq(chunk->index, chunk_i))
    134         {
    135             result = chunk;
    136             break;
    137         }
    138         chunk = chunk->next;
    139     }
    140 
    141     if (!result)
    142     {
    143         struct WorldChunk *prev_head_chunk = world->chunk_ht[hash_table_bucket];
    144         struct WorldChunk *new_chunk = mem_st_alloc_struct(allocator, struct WorldChunk);
    145         new_chunk->index = chunk_i;
    146         for (size_t i = 0; i < CHUNK_NUM_ROOMS; i++)
    147         {
    148             new_chunk->rooms[i].index.y = ROOM_UNINITIALIZED_Y_VALUE;
    149         }
    150         new_chunk->next = prev_head_chunk;
    151         world->chunk_ht[hash_table_bucket] = new_chunk;
    152         result = new_chunk;
    153     }
    154 
    155     return result;
    156 }
    157 
    158 internal size_t world_room_get_index_within_chunk(v2i room_i)
    159 {
    160     // TODO: assert that no chunk ever spans the [-1, 0] room index boundary.
    161 
    162     // NOTE(amin): Because we are using only the lower bits here, we will get
    163     // index collisions if both positive and negative numbers appear in the
    164     // same chunk. For example with two's complement, the first 7 bits of 127
    165     // and -1 are identical.
    166     v2u room_i_lower_bits = {
    167         room_i.x & CHUNK_MASK_FOR_INTERNAL_ROOM_INDEX,
    168         room_i.y & CHUNK_MASK_FOR_INTERNAL_ROOM_INDEX,
    169     };
    170     assert(room_i_lower_bits.x < CHUNK_ROOMS_PER_SIDE);
    171     assert(room_i_lower_bits.y < CHUNK_ROOMS_PER_SIDE);
    172 
    173     size_t room_i_within_chunk = room_i_lower_bits.x + (room_i_lower_bits.y * CHUNK_ROOMS_PER_SIDE);
    174     assert(room_i_within_chunk < CHUNK_NUM_ROOMS);
    175     return room_i_within_chunk;
    176 }
    177 
    178 internal bool world_room_is_initialized(struct Room *room)
    179 {
    180     bool is_initialized = false;
    181     if (room->index.y != ROOM_UNINITIALIZED_Y_VALUE)
    182     {
    183         is_initialized = true;
    184     }
    185     return is_initialized;
    186 }
    187 
    188 internal struct Room* world_room_get(struct World *world, v2i room_i)
    189 {
    190     struct Room *result = NULL;
    191     v2i chunk_i = world_room_get_chunk_index(room_i);
    192 
    193     struct WorldChunk *chunk = world_chunk_get(world, chunk_i);
    194     if (chunk)
    195     {
    196         size_t room_i_in_chunk = world_room_get_index_within_chunk(room_i);
    197         struct Room *room = &chunk->rooms[room_i_in_chunk];
    198         if (world_room_is_initialized(room))
    199         {
    200             result = room;
    201             room = NULL;
    202         }
    203     }
    204 
    205     return result;
    206 }
    207 
    208 internal struct Room *world_room_set(struct World *world, struct Room room, struct StackAllocator *allocator)
    209 {
    210     struct Room *result = NULL;
    211     v2i chunk_i = world_room_get_chunk_index(room.index);
    212 
    213     struct WorldChunk *chunk = world_chunk_get_or_create(world, chunk_i, allocator);
    214 
    215     if (chunk)
    216     {
    217         size_t room_i_in_chunk = world_room_get_index_within_chunk(room.index);
    218         assert(!world_room_is_initialized(&chunk->rooms[room_i_in_chunk]));
    219         chunk->rooms[room_i_in_chunk] = room;
    220         result = &chunk->rooms[room_i_in_chunk];
    221         world->num_rooms++;
    222     }
    223 
    224     return result;
    225 }
    226 
    227 internal u8 get_tile_value(u8 *tiles, v2 tile_pos)
    228 {
    229     assert(tiles);
    230 
    231     v2u tile_i_2d = {
    232         tile_pos.x,
    233         ROOM_DIM_Y - 1.0f - tile_pos.y,
    234     };
    235 
    236     assert(math_v2_lt(
    237         (v2) {tile_i_2d.x, tile_i_2d.y},
    238         (v2) {ROOM_DIM_X, ROOM_DIM_Y}));
    239 
    240     assert(math_v2_ge(
    241         (v2) {tile_i_2d.x, tile_i_2d.y},
    242         (v2) {0}));
    243 
    244     u32 tile_i_linearized = (ROOM_DIM_X * tile_i_2d.y) + tile_i_2d.x;
    245     assert(tile_i_linearized < ROOM_TILE_COUNT);
    246 
    247     u8 tile_value = tiles[tile_i_linearized];
    248     return tile_value;
    249 }
    250 
    251 internal bool world_tile_in_room_is_solid(u8 *tiles, v2 tile_pos)
    252 {
    253     bool is_solid = false;
    254     u8 tile_value = get_tile_value(tiles, tile_pos);
    255     is_solid = tile_value > 0;
    256     return is_solid;
    257 }
    258 
    259 internal bool world_tile_is_solid(struct World *world, struct AbsolutePos abs_tile_p)
    260 {
    261     bool is_solid = true;
    262     assert(world);
    263     struct AbsolutePos p = world_tile_pos_recanonicalize(abs_tile_p);
    264     struct Room *r = world_room_get(world, p.room);
    265     // NOTE(amin): If we don't get a room back here, we will just treat the
    266     // 'tile' as solid so we can collide with it rather than entering the
    267     // nonexistent room.
    268     if (r)
    269     {
    270         is_solid = world_tile_in_room_is_solid(r->tiles, p.local);
    271     }
    272     return is_solid;
    273 }
    274 
    275 internal bool wall_is_screen_edge(segment wall)
    276 {
    277     bool is_screen_edge = false;
    278     bool wall_is_horizontal = wall.min.y == wall.max.y;
    279     bool wall_is_vertical = wall.min.x == wall.max.x;
    280 
    281     assert(wall_is_vertical || wall_is_horizontal);
    282     assert(math_v2_ge(wall.min, (v2) {0}));
    283     assert(math_v2_le(wall.max, (v2) {ROOM_DIM_X, ROOM_DIM_Y}));
    284 
    285     if (wall_is_vertical)
    286     {
    287         is_screen_edge = wall.min.x == 0.0f || wall.min.x == ROOM_DIM_X;
    288     }
    289     else
    290     {
    291         is_screen_edge = wall.min.y == 0.0f || wall.min.y == ROOM_DIM_Y;
    292     }
    293 
    294     return is_screen_edge;
    295 }
    296 
    297 internal bool wall_is_internal(struct World *world, v2i room_i, segment wall)
    298 {
    299     assert(world);
    300 
    301     bool is_internal = false;
    302     bool wall_is_horizontal = wall.min.y == wall.max.y;
    303     bool wall_is_vertical = wall.min.x == wall.max.x;
    304 
    305     assert(wall_is_vertical || wall_is_horizontal);
    306     assert(
    307         math_f32_is_i32(wall.min.x)
    308         && math_f32_is_i32(wall.min.y)
    309         && math_f32_is_i32(wall.max.x)
    310         && math_f32_is_i32(wall.max.y));
    311 
    312     struct AbsolutePos lesser_neighbor = {
    313         .room = room_i,
    314         .local = {0},
    315     };
    316     struct AbsolutePos greater_neighbor = {
    317         .room = room_i,
    318         .local = {0},
    319     };
    320 
    321     if (wall_is_vertical)
    322     {
    323         lesser_neighbor.local = (v2) {wall.min.x - TILE_SIZE, wall.min.y};
    324     }
    325     else
    326     {
    327         lesser_neighbor.local = (v2) {wall.min.x, wall.min.y - TILE_SIZE};
    328     }
    329     greater_neighbor.local = wall.min;
    330 
    331     is_internal = world_tile_is_solid(world, lesser_neighbor) && world_tile_is_solid(world, greater_neighbor);
    332 
    333     return is_internal;
    334 }