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 }