/* This file is part of Minitrem. Minitrem is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Minitrem is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Minitrem. If not, see . */ #include "common.hpp" #include namespace world { sector_index_t sector_index_at(v2f_t x) { return sector_index_t((x / SECTOR_SIZE).floor()); } tile_index_t tile_index_at(v2f_t x) { return sector_index_t(x.floor()); } void world_t::seed_procgen(uint32_t seed) { prng.seed(seed); perlin.generate(&prng, 32); } // FIXME: rename static cflags_t tiles[256] = {0}; void register_tile(uint8_t type, cflags_t cflags) { tiles[type] = cflags; } void world_t::generate(sector_t *sector, sector_index_t index, sector_level_t level) { bool gen_tiles = false, gen_decos = false; sector->index = index; sector->bounds.v[0] = (v2f_t)index * SECTOR_SIZE; sector->bounds.v[1] = sector->bounds.v[0] + v2f_t(SECTOR_SIZE, SECTOR_SIZE); if (sector->level == SECTOR_EMPTY) { if (level == SECTOR_PARTIAL) gen_tiles = true; else if (level == SECTOR_FULL) gen_tiles = gen_decos = true; } else if (sector->level == SECTOR_PARTIAL) gen_decos = true; sector->level = level; generator(this, index, sector, gen_tiles, gen_decos, generator_data); stats.sectors++; stats.tiles += SECTOR_SIZE * SECTOR_SIZE; if (sector->level < SECTOR_FULL) return; for (coord_t ty = 0; ty < SECTOR_SIZE; ty++) for (coord_t tx = 0; tx < SECTOR_SIZE; tx++) { tile_t *tile; tile_index_t local(tx, ty); tile = sector->tiles + ty * SECTOR_SIZE + tx; tile->neighbors = 0; for (size_t i = 0; i < 8; i++) { tile_index_t neighbor_index; tile_t *neighbor; neighbor_index = index * SECTOR_SIZE + local + neighbor_offsets[i]; neighbor = get_tile(neighbor_index, SECTOR_PARTIAL); if (neighbor->type == tile->type) tile->neighbors |= (1 << i); } } } bool world_t::find_path(v2f_t src, v2f_t dst, cmodel_t *cmodel, entity_t *ignore, std::list *path) { path_finder_t finder; rectf_t bounds; v2f_t cmodel_dims; finder.setup_nodes(src, dst, cmodel->cflags); cmodel_dims = cmodel->bounds.dims(); for (size_t y = 0; y < finder.height; y++) for (size_t x = 0; x < finder.width; x++) { tile_index_t index; rectf_t combined; index = finder.base + tile_index_t(x, y); if (!(tiles[get_tile(index, SECTOR_FULL)->type] & cmodel->cflags)) continue; combined[0] = v2f_t(index) - cmodel_dims / 2; combined[1] = v2f_t(index) + v2f_t(1.0f, 1.0f) + cmodel_dims / 2; finder.eliminate_nodes(combined); } bounds = finder.bounds; bounds[0] -= cmodel_dims / 2; bounds[1] += cmodel_dims / 2; for (entity_t *ent : get_entities(bounds, cmodel->cflags)) { v2f_t center; v2f_t combined_dims; rectf_t combined; if (ent == ignore) continue; if (ent->cmodel.ignore) continue; if (!(ent->cmodel.cflags & cmodel->cflags)) continue; center = ent->cmodel.bounds.center(); combined_dims = ent->cmodel.bounds.dims() + cmodel_dims; combined[0] = center - combined_dims / 2; combined[1] = center + combined_dims / 2; finder.eliminate_nodes(combined); } if (!finder.find()) return false; finder.export_path(path); return true; } sector_t *world_t::get_sector(sector_index_t index, sector_level_t level) { sector_t *sector; sector = §ors[index]; if (sector->level < level) generate(sector, index, level); return sector; } tile_t *world_t::get_tile(tile_index_t index, sector_level_t level) { sector_index_t sector_index; sector_t *sector; int64_t tx, ty; sector_index[0] = divide_rmi(index[0], (int64_t)SECTOR_SIZE, &tx); sector_index[1] = divide_rmi(index[1], (int64_t)SECTOR_SIZE, &ty); sector = get_sector(sector_index, level); return sector->tiles + ty * SECTOR_SIZE + tx; } std::list world_t::get_sectors(rectf_t rect, sector_level_t level) { sector_index_t base, upper; std::list list; base = sector_index_at(rect.v[0]); upper = sector_index_at(rect.v[1]); for (int64_t y = base[1]; y <= upper[1]; y++) for (int64_t x = base[0]; x <= upper[0]; x++) { sector_index_t index(x, y); list.push_back(get_sector(index, level)); } return list; } static size_t cookie = 0; std::list world_t::get_entities(rectf_t rect, cflags_t cflags) { std::list list; cookie++; fc_get_entities.tick(); for (sector_t *sector : get_sectors(rect, SECTOR_FULL)) for (entity_t *ent : sector->ents) { if (ent->cookie == cookie) continue; ent->cookie = cookie; fc_get_entities_ents.tick(); if (ent->cmodel.ignore) continue; if (!(ent->cmodel.cflags & cflags)) continue; if (!(rect && ent->cmodel.bounds)) continue; list.push_back(ent); } return list; } std::list world_t::get_render_entities(rectf_t rect) { std::list list; cookie++; for (sector_t *sector : get_sectors(rect, SECTOR_FULL)) for (entity_t *ent : sector->ents) { if (ent->cookie == cookie) continue; if (!(rect && ent->render_bounds)) continue; ent->cookie = cookie; list.push_back(ent); } return list; } bool world_t::test_rect(const cmodel_t *cmodel, const entity_t *ignore) { cookie++; for (sector_t *sector : get_sectors(cmodel->bounds, SECTOR_FULL)) { rect_t bounds; tile_index_t index; bounds[0] = (cmodel->bounds[0] - sector->bounds[0]).floor(); bounds[1] = (cmodel->bounds[1] - sector->bounds[0]).floor(); if (bounds[0][0] < 0) bounds[0][0] = 0; if (bounds[0][1] < 0) bounds[0][1] = 0; if (bounds[1][0] >= (coord_t)SECTOR_SIZE) bounds[1][0] = SECTOR_SIZE - 1; if (bounds[1][1] >= (coord_t)SECTOR_SIZE) bounds[1][1] = SECTOR_SIZE - 1; for (index[1] = bounds[0][1]; index[1] <= bounds[1][1]; index[1]++) for (index[0] = bounds[0][0]; index[0] <= bounds[1][0]; index[0]++) { tile_t *tile; tile = sector->tiles + index[1] * SECTOR_SIZE + index[0]; if (tiles[tile->type] & cmodel->cflags) return true; } for (entity_t *ent : sector->ents) { if (ent == ignore) continue; if (ent->cookie == cookie) continue; ent->cookie = cookie; if (ent->cmodel.ignore) continue; if (!(ent->cmodel.cflags & cmodel->cflags)) continue; if (!(cmodel->bounds && ent->cmodel.bounds)) continue; return true; } } return false; } static const v2f_t transforms[4] = { {+1, +1}, {-1, +1}, {-1, -1}, {+1, -1} }; static const tile_index_t transforms_index[4] = { {+1, +1}, {-1, +1}, {-1, -1}, {+1, -1} }; static const tile_index_t offsets_index[4] = { {0, 0}, {-1, 0}, {-1, -1}, {0, -1} }; static inline size_t quadrant(v2f_t dx) { if (dx[0] == 0.0f && dx[1] == 0.0f) return 4; if (dx[1] >= 0.0f) { if (dx[0] >= 0.0f) return 0; else return 1; } else { if (dx[0] >= 0.0f) return 3; else return 2; } } trace_t ray_v_rect(v2f_t start, v2f_t end, rectf_t rect) { v2f_t dx, tdelta, dims; size_t quad; trace_t res; if (rect.contains(start)) { res.hit = true; res.end = start; res.frac = 0.0f; return res; } dx = end - start; quad = quadrant(dx); if (quad == 4) goto out_no_hit; // If necessary, transform (with simple X/Y reflections) the coordinate // system, so that dx lies in the first quadrant. start ^= transforms[quad]; dx ^= transforms[quad]; end ^= transforms[quad]; dims = rect.dims(); rect.v[0] ^= transforms[quad]; rect.v[1] ^= transforms[quad]; // Find the closest point on the rectangle and transform the coordinates // so that it lies at (0, 0). tdelta[0] = std::min(rect.v[0][0], rect.v[1][0]); tdelta[1] = std::min(rect.v[0][1], rect.v[1][1]); start -= tdelta; // Project onto x = 0. res.frac = -start[0] / dx[0]; if (res.frac >= 0.0f && res.frac <= 1.0f) { float y; y = start[1] + res.frac * dx[1]; if (y >= 0 && y <= dims[1]) { res.hit = true; res.end = (v2f_t(0, y) + tdelta) ^ transforms[quad]; return res; } } // Project onto y = 0. res.frac = -start[1] / dx[1]; if (res.frac >= 0.0f && res.frac <= 1.0f) { float x; x = start[0] + res.frac * dx[0]; if (x >= 0 && x <= dims[0]) { res.hit = true; res.end = (v2f_t(x, 0) + tdelta) ^ transforms[quad]; return res; } } out_no_hit: res.hit = false; res.end = end ^ transforms[quad]; res.frac = 1.0f; return res; } trace_t world_t::ray_v_ents(v2f_t start, v2f_t end, cflags_t cflags, const entity_t *ignore) { trace_t best; rectf_t rect; best.hit = false; best.frac = 2.0f; rect = rectf_t(start, end).norm(); for (entity_t *ent : get_entities(rect, cflags)) { trace_t trace; fc_trace_ents.tick(); if (ent == ignore) continue; if (ent->cmodel.ignore) continue; trace = ray_v_rect(start, end, ent->cmodel.bounds); if (!trace.hit) continue; if (trace.frac < best.frac) { best = trace; best.tile = nullptr; best.ent = ent; } } return best; } trace_t world_t::point_v_tiles(v2f_t at, cflags_t cflags) { tile_index_t index; tile_t *tile; trace_t trace; index = tile_index_t(at.floor()); tile = get_tile(index, SECTOR_FULL); if (tiles[tile->type] & cflags) { trace.hit = true; trace.frac = 0.0f; trace.tile = tile; } else { trace.hit = false; trace.frac = 1.0f; } trace.end = at; return trace; } trace_t world_t::ray_v_tiles(v2f_t start, v2f_t end, cflags_t cflags) { v2f_t x, dx; size_t quad; trace_t res; dx = end - start; quad = quadrant(dx); if (quad == 4) return point_v_tiles(start, cflags); start ^= transforms[quad]; dx ^= transforms[quad]; end ^= transforms[quad]; x = start; while (1) { v2f_t mod, P; tile_index_t index; tile_t *tile; if (x[0] >= end[0] && x[1] >= end[1]) { res.hit = false; res.end = end ^ transforms[quad]; res.frac = 1.0f; return res; } index = (tile_index_t(x.floor()) ^ transforms_index[quad]) + offsets_index[quad]; tile = get_tile(index, SECTOR_FULL); fc_trace_tiles.tick(); if (tiles[tile->type] & cflags) { res.hit = true; res.end = x ^ transforms[quad]; res.frac = (x - start).len() / (end - start).len(); res.ent = nullptr; res.tile = tile; return res; } mod = x - x.floor(); // Project onto x = const. P[0] = 1.0f - mod[0]; P[1] = P[0] / dx[0] * dx[1]; if (mod[1] + P[1] >= 0.0f && mod[1] + P[1] <= 1.0f) { x += P; x[0] = std::round(x[0]); continue; } // Project onto y = const. P[1] = 1.0f - mod[1]; P[0] = P[1] / dx[1] * dx[0]; x += P; x[1] = std::round(x[1]); } } trace_t world_t::ray_v_all(v2f_t start, v2f_t end, cflags_t cflags, const entity_t *ignore) { trace_t v_tiles, v_ents; fc_traces.tick(); v_tiles = ray_v_tiles(start, end, cflags); v_ents = ray_v_ents(start, end, cflags, ignore); if (v_tiles.frac < v_ents.frac) return v_tiles; else return v_ents; } trace_t world_t::ray_v_all_p3d(v2f_t start, v2f_t end, cflags_t cflags, cflags_t end_cflags, const entity_t *ignore) { trace_t trace; trace = ray_v_all(start, end, cflags, ignore); if (!trace.hit) trace = ray_v_all(end, end, end_cflags, ignore); return trace; } entity_t::entity_t(int type_) { type = type_; } entity_t::~entity_t(void) { } void entity_t::link_to_sector(sector_t *sector) { parents.push_back(sector); sector->ents.insert(this); } void entity_t::link(world_t *world_) { rectf_t total_bounds; float fx, fy; sector_index_t base; float xlip, ylip; size_t xsecs, ysecs; if (world) unlink(); world = world_; total_bounds = cmodel.bounds | render_bounds; fx = floor(total_bounds[0][0]); fy = floor(total_bounds[0][1]); base = sector_index_at(v2f_t(fx, fy)); xlip = total_bounds[1][0] - (base[0] + 1) * SECTOR_SIZE; ylip = total_bounds[1][1] - (base[1] + 1) * SECTOR_SIZE; if (xlip > 0.0f) xsecs = ceil(xlip / SECTOR_SIZE) + 1; else xsecs = 1; if (ylip > 0.0f) ysecs = ceil(ylip / SECTOR_SIZE) + 1; else ysecs = 1; for (int64_t y = 0; y < (int64_t)ysecs; y++) for (int64_t x = 0; x < (int64_t)xsecs; x++) { sector_index_t index = base + sector_index_t(x, y); sector_t *sector; sector = world->get_sector(index, SECTOR_FULL); link_to_sector(sector); } world->stats.entities++; } void entity_t::unlink(void) { for (sector_t *sector : parents) { if (sector->ents.find(this) == sector->ents.end()) { printf("entity_t::unlink: %p should belong to %p (%" PRIi64", %" PRIi64") but it doesn't\n", this, sector, sector->index[0], sector->index[1]); continue; } sector->ents.erase(sector->ents.find(this)); } parents.clear(); world->stats.entities--; world = 0; } } // namespace world