/*
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);
tile->top->setSmooth(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->side->setSmooth(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;
}
frames[i].setSmooth(true);
}
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