/* 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 static sf::RectangleShape wot_rect; static sf::Font font; #include namespace render { state_t::state_t(sf::RenderWindow *window_) { window = window_; font.loadFromFile("assets/Roboto-Medium.ttf"); window->clear(); render_text(v2f_t(0, 0), 50, "Loading...", ALIGN_LEFT_TOP, sf::Color::White); window->display(); } typedef struct { sf::Texture *top, *side; float height; layer_t layer; } tiledata_t; static tiledata_t tiledata[256] = {0}; void register_tile(uint8_t type, const char *top, const char *side, float height, layer_t layer) { tiledata_t *tile = tiledata + type; printf("load %s\n", top); tile->top = new sf::Texture; tile->top->loadFromFile(top); tile->top->setRepeated(true); if (height > 0.0f) { tile->height = height; printf("load %s\n", side); tile->side = new sf::Texture; tile->side->loadFromFile(side); tile->side->setRepeated(true); } tile->layer = layer; } void state_t::begin_frame(ntime_t time_, double dt_) { time = time_; dt = dt_; stats.sectors = 0; stats.tiles = 0; stats.entities = 0; } void state_t::end_frame(void) { window->display(); } void state_t::drender_text(rectf_t rect, std::string str) { sf::Text text(str, font, 20); sf::FloatRect text_rect; v2f_t text_size, x; text.setScale(0.006f, 0.006f); text_rect = text.getGlobalBounds(); text_size = v2f_t(text_rect.width, text_rect.height); x = rect.center() - text_size / 2; text.setPosition(x + v2f_t(0.01f, 0.01f)); text.setFillColor(sf::Color::Black); window->draw(text); text.setPosition(x); text.setFillColor(sf::Color::White); window->draw(text); } void state_t::drender_entity(world::entity_t *ent) { std::stringstream ss; render_hlrect(ent->render_bounds, sf::Color::Red); render_hlrect(ent->cmodel.bounds, sf::Color::Yellow); ss << (void*)ent << "\n"; ss << "CF=" << ent->cmodel.cflags; drender_text(ent->render_bounds, ss.str()); } static void generate_tile_verts(sf::Vertex *verts, v2f_t tx, procgen::perlin_noise_t *perlin) { static const v2f_t local_base[8] ={ {1.0f, 0.5f}, {1.0f, 1.0f}, {0.5f, 1.0f}, {0.0f, 1.0f}, {0.0f, 0.5f}, {0.0f, 0.0f}, {0.5f, 0.0f}, {1.0f, 0.0f} }; for (size_t i = 0; i < 8; i++) { v2f_t base, turb; base = local_base[i] + tx; turb[0] = perlin->get(base, 1.0f) * 0.5f; turb[1] = perlin->get(base, 2.0f) * 0.5f; verts[i].position = base + turb; verts[i].texCoords = (base + turb) * 32.0f; verts[i].color = sf::Color::White; } } void state_t::render_tile(world::world_t *world, v2f_t tx, world::tile_t *tile) { static const size_t side_order[6] = {4, 0, 7, 3, 2, 1}; static const size_t side_tests[8] = {1, 4, 4, 16, 16, 64, 64, 1}; sf::Vertex verts[8]; tiledata_t *data; if (tile->type == 0) return; data = tiledata + tile->type; generate_tile_verts(verts, tx, &world->perlin); if (data->height) { sf::RenderStates states(data->side); for (size_t i = 0; i < 6; i++) { size_t j = side_order[i]; size_t k = (j + 1) % 8; sf::Vertex quad[4]; if (tile->neighbors & side_tests[j]) continue; quad[0] = verts[j]; quad[0].texCoords.y = 0; quad[1] = verts[k]; quad[1].texCoords.y = 0; quad[2] = quad[1]; quad[3] = quad[0]; quad[2].position.y -= data->height; quad[2].texCoords.y -= data->height * 32; quad[3].position.y -= data->height; quad[3].texCoords.y -= data->height * 32; window->draw(quad, 4, sf::TrianglesFan, states); } for (size_t i = 0; i < 8; i++) verts[i].position.y -= data->height; } sf::RenderStates states(data->top); window->draw(verts, 8, sf::TrianglesFan, states); stats.tiles++; } rectf_t window_bounds(sf::RenderWindow *window) { const v2f_t margin(1.5f, 1.5f); sf::Vector2u size = window->getSize(); v2f_t A, B, C, D; rectf_t bounds; A = window->mapPixelToCoords(sf::Vector2i(0, 0)); B = window->mapPixelToCoords(sf::Vector2i(size.x, 0)); C = window->mapPixelToCoords(sf::Vector2i(0, size.y)); D = window->mapPixelToCoords(sf::Vector2i(size.x, size.y)); bounds[0][0] = std::min({A[0], B[0], C[0], D[0]}); bounds[0][1] = std::min({A[1], B[1], C[1], D[1]}); bounds[1][0] = std::max({A[0], B[0], C[0], D[0]}); bounds[1][1] = std::max({A[1], B[1], C[1], D[1]}); return bounds; } bool rendering_order(const world::entity_t *x, const world::entity_t *y) { return x->render_bounds[1][1] < y->render_bounds[1][1]; } bool visibility_order(const world::entity_t *x, const world::entity_t *y) { if (x->render_layer < y->render_layer) return false; else if (x->render_layer > y->render_layer) return true; else return x->render_bounds[1][1] > y->render_bounds[1][1]; } void state_t::render_water(world::world_t *world, rectf_t rect, double time) { sf::Vertex quad[4]; sf::RenderStates states; quad[0].position = rect[0]; quad[1].position = v2f_t(rect[1][0], rect[0][1]); quad[2].position = rect[1]; quad[3].position = v2f_t(rect[0][0], rect[1][1]); states.texture = tiledata[0].top; for (size_t i = 0; i < 3; i++) { states.blendMode = (i == 0 ? sf::BlendAlpha : sf::BlendAdd); for (size_t j = 0; j < 4; j++) { float mul; v2f_t delta; mul = powf(2.0f, i + 4); delta[0] = world->perlin.get(v2f_t(time, time), mul); delta[1] = world->perlin.get(v2f_t(time, -time), mul); delta *= 20.0f; quad[j].texCoords = (quad[j].position + delta) * mul; quad[j].texCoords.y *= 2.0f; } window->draw(quad, 4, sf::Quads, states); } } void state_t::render_layer(world::world_t *world, rect_t §ors, std::list &ents, layer_t layer) { std::list::iterator ent; ent = ents.begin(); for (world::coord_t sy = sectors[0][1]; sy <= sectors[1][1]; sy++) for (world::coord_t ty = 0; ty < SECTOR_SIZE; ty++) for (world::coord_t sx = sectors[0][0]; sx <= sectors[1][0]; sx++) { world::sector_index_t sector_index(sx, sy); world::sector_t *sector; sector = world->get_sector(sector_index, world::SECTOR_FULL); while (ent != ents.end() && (*ent)->render_bounds[1][1] < sy * SECTOR_SIZE + ty) { if ((*ent)->render_layer == layer) { (*ent)->render_to(this); stats.entities++; } ent++; } for (world::coord_t tx = 0; tx < SECTOR_SIZE; tx++) { world::tile_index_t index; world::tile_t *tile; index = sector_index * SECTOR_SIZE + world::tile_index_t(tx, ty); tile = sector->tiles + ty * SECTOR_SIZE + tx; if (tiledata[tile->type].layer != layer) continue; render_tile(world, index, tile); } if (layer == LAYER_COUNT - 1) stats.sectors++; } } void state_t::render(world::world_t *world, double time) { const v2f_t margin = {1.5f, 1.5f}; rectf_t bounds; std::list ents; rect_t sectors; bounds = window_bounds(window); bounds[0] -= margin; bounds[1] += margin; sectors[0] = world::sector_index_at(bounds[0]); sectors[1] = world::sector_index_at(bounds[1]); // Draw the water _with_ the margin to avoid seams at the screen's edges. render_water(world, bounds, time); ents = world->get_render_entities(bounds); ents.sort(rendering_order); for (layer_t layer = LAYER_FLAT; layer < LAYER_COUNT; layer++) render_layer(world, sectors, ents, layer); if (debug_draw_tile_coords) { for (world::sector_t *sector : world->get_sectors(bounds, world::SECTOR_FULL)) for (world::coord_t ty = 0; ty < SECTOR_SIZE; ty++) for (world::coord_t tx = 0; tx < SECTOR_SIZE; tx++) { std::stringstream ss; world::tile_index_t local, index; world::tile_t *tile; int neighbors; local = world::tile_index_t(tx, ty); index = sector->index * SECTOR_SIZE + local; tile = sector->tiles + ty * SECTOR_SIZE + tx; ss << "SI: " << sector->index << "\n"; ss << "GI: " << index << "\n"; ss << "LI: " << local << "\n"; ss << "T: " << tile->type << "\n"; ss << "N: "; neighbors = tile->neighbors; for (size_t i = 0; i < 8; i++) { ss << ((neighbors & 1) ? 'P' : 'A'); neighbors >>= 1; } ss << " (" << tile->neighbors << ")"; sf::Text text(ss.str(), font, 20); text.setPosition(index); text.setScale(0.005, 0.005); window->draw(text); } } for (world::entity_t *ent : ents) { ent->render_late_to(this); if (debug_draw_cmodels) drender_entity(ent); } fc_render.tick(); } void state_t::render(double phase, animated_texture_t *anim, rectf_t bounds, sf::Color color, bool mirror){ size_t frame; if (!anim) return; if (!anim->frame_count) return; phase += 0.001; // FIXME frame = floor((phase - floor(phase)) * anim->frame_count); wot_rect.setTexture(anim->frames + frame, true); wot_rect.setFillColor(color); if (!mirror) { wot_rect.setPosition(bounds[0]); wot_rect.setSize(bounds[1] - bounds[0]); } else { float dx = bounds[1][0] - bounds[0][0]; wot_rect.setPosition(bounds[0] + v2f_t(dx, 0)); wot_rect.setSize(bounds[1] - bounds[0]); wot_rect.setScale(v2f_t(-1, 1)); } window->draw(wot_rect); wot_rect.setTexture(NULL); wot_rect.setScale(v2f_t(1, 1)); } void state_t::render(double phase, oriented_sprite_t *sprite, rectf_t bounds, float angle) { size_t index; bool mirror; index = sprite->select_index(angle, &mirror); render(phase, sprite->textures + index, bounds, sf::Color::White, mirror); } void state_t::render_text(v2f_t x, float height, std::string str, text_align_t align, sf::Color color) { sf::Text text(sf::String::fromUtf8(str.begin(), str.end()), font, 40); sf::FloatRect rect; float scale; v2f_t offset; rect = text.getGlobalBounds(); scale = height / 40.0f; switch (align) { case ALIGN_LEFT_TOP: offset = v2f_t(0, 0); break; case ALIGN_RIGHT_TOP: offset[0] = -rect.width; offset[1] = 0.0f; break; case ALIGN_LEFT_BOTTOM: offset[0] = 0.0f; offset[1] = -rect.height; break; case ALIGN_CENTER_BOTTOM: offset[0] = -rect.width / 2; offset[1] = -rect.height; break; case ALIGN_RIGHT_BOTTOM: offset[0] = -rect.width; offset[1] = -rect.height; break; default: abort(); } offset *= scale; text.setScale(scale, scale); text.setPosition(x + offset); text.setFillColor(color); window->draw(text); } void state_t::render_rect(rectf_t rect, sf::Color color) { wot_rect.setSize(rect.dims()); wot_rect.setPosition(rect[0]); wot_rect.setFillColor(color); window->draw(wot_rect); } void state_t::render_hlrect(rectf_t rect, sf::Color color) { sf::Color fill; fill = sf::Color(color.r, color.g, color.b, 50); wot_rect.setSize(rect.dims()); wot_rect.setPosition(rect[0]); wot_rect.setFillColor(fill); wot_rect.setOutlineThickness(0.01); wot_rect.setOutlineColor(color); window->draw(wot_rect); wot_rect.setOutlineColor(sf::Color::Transparent); } void state_t::render_line(v2f_t x0, v2f_t x1, sf::Color color) { sf::Vertex line[2] = { sf::Vertex(x0, color), sf::Vertex(x1, color) }; window->draw(line, 2, sf::Lines); } void state_t::render_circle(v2f_t x, float r, sf::Color color) { sf::CircleShape circle; circle.setRadius(r); circle.setPosition(x - v2f_t(r, r)); circle.setFillColor(color); window->draw(circle); } void state_t::render_ring_sect(v2f_t x, float r0, float r1, float t0, float t1, sf::Color color) { sf::VertexArray array(sf::TriangleStrip); float t; bool done = false; for (t = t0; !done; t += 0.1f) { v2f_t rad; if (t > t1) { t = t1; done = true; } rad = v2f_t::rad(t); array.append(sf::Vertex(x + rad * r0, color)); array.append(sf::Vertex(x + rad * r1, color)); } window->draw(array); } void state_t::debug_path(v2f_t x, std::list *path) { sf::Vertex line[2]; line[0] = sf::Vertex(x, sf::Color::Blue); for (v2f_t &point : *path) { line[1] = line[0]; line[0] = sf::Vertex(point, sf::Color::Blue); window->draw(line, 2, sf::Lines); } } animated_texture_t::~animated_texture_t(void) { delete[] frames; } bool animated_texture_t::load(std::string prefix, size_t frame_count_) { frame_count = frame_count_; frames = new sf::Texture[frame_count]; for (size_t i = 0; i < frame_count; i++) { std::string path; path = prefix + std::to_string(i) + ".png"; std::cout << "load " << path << "\n"; if (!frames[i].loadFromFile(path)) { delete[] frames; frames = NULL; frame_count = 0; return false; } } return true; } oriented_sprite_t::~oriented_sprite_t(void) { delete[] textures; } float normalize_angle(float angle) { float t; t = angle / (2 * M_PI); t -= floor(t); return t * 2 * M_PI; } size_t oriented_sprite_4M_t::select_index(float angle, bool *mirror) { angle = normalize_angle(angle); if (angle < 0.25f * M_PI) { select_x: *mirror = false; return 0; } else if (angle < 0.75f * M_PI) { *mirror = false; return 1; } else if (angle < 1.25f * M_PI) { *mirror = true; return 0; } else if (angle < 1.75f * M_PI) { *mirror = false; return 2; } else goto select_x; } void oriented_sprite_4M_t::load(std::string prefix, size_t xc, size_t yc, size_t nyc) { textures = new animated_texture_t[3]; textures[0].load(prefix + "_x_", xc); textures[1].load(prefix + "_y_", yc); textures[2].load(prefix + "_ny_", nyc); } size_t oriented_sprite_4M2_t::select_index(float angle, bool *mirror) { angle = normalize_angle(angle + 0.01f); if (angle < 0.25f * M_PI) { select_x: *mirror = false; return 0; } else if (angle < 0.75f * M_PI) { *mirror = false; return 1; } else if (angle < 1.25f * M_PI) { *mirror = true; return 0; } else if (angle < 1.75f * M_PI) { *mirror = false; return 1; } else goto select_x; } void oriented_sprite_4M2_t::load(std::string prefix, size_t xc, size_t yc) { textures = new animated_texture_t[2]; textures[0].load(prefix + "_x_", xc); textures[1].load(prefix + "_y_", yc); } } // namespace render