#include "mesh.hpp"
#include "shader.hpp"
#include "buffer.hpp"
#include "txt.hpp"
#include "gameworld.hpp"
#include "path.hpp"
#include "transform.hpp"
#include "tile.hpp"
#include "input.hpp"
#include "ogl.hpp"
#include "common.hpp"
static bool process_inputs_and_move(Worlds& worlds, const Mesh& mesh, Input& input, const LookAt& lookat, int origin_x, int origin_y) {
static ShortestPathFac* curr_path = NULL;
bool moved = false;
int poskey_x, poskey_y;
double click_x, click_y;
float click_z;
if (input.getPosKey(poskey_x, poskey_y)) { // arrow keys have highest precedence, aborting paths and clearing all other pending clicks
safe_delete(curr_path);
while (input.getClick(click_x, click_y, click_z)) {}
moved = worlds.world->player_step(poskey_x, poskey_y) ?: worlds.world->player_push(poskey_x, poskey_y);
} else if (input.getClick(click_x, click_y, click_z) && worlds.world->world_get()->autoroute()) {
GLfloat iso_x, iso_y;
lookat.unproject(click_x, click_y, click_z, iso_x, iso_y);
unsigned to_x = (unsigned)MAX(0, MIN((int)worlds.world->w-1, (int)mesh.iso2tile_x(iso_x) + origin_x));
unsigned to_y = (unsigned)MAX(0, MIN((int)worlds.world->h-1, (int)mesh.iso2tile_y(iso_y) + origin_y));
safe_delete(curr_path);
curr_path = new ShortestPathFac(worlds.world->world_get(), to_x, to_y, worlds.world->x, worlds.world->y);
Logger::log("%u-%u -> %u-%u", worlds.world->x, worlds.world->y, to_x, to_y);
}
if (curr_path) {
unsigned to_x, to_y;
if (!curr_path->get()) {
// not yet calculated - noop
} else if (curr_path->get()->step(worlds.world->x, worlds.world->y, to_x, to_y) && worlds.world->player_step(to_x-worlds.world->x, to_y-worlds.world->y)) {
moved = true;
} else {
safe_delete(curr_path);
}
}
if (moved) {
const tile_t& tile = worlds.world->world_get()->at(worlds.world->x, worlds.world->y);
switch (tile.action) {
case tile_t::TILE_ACTION_GOTO:
if (worlds.world->player_goto(tile.action_arg2[0], tile.action_arg2[1])) {
safe_delete(curr_path);
}
break;
case tile_t::TILE_ACTION_FINISH:
Logger::log("Great Success!!1!");
input.shouldClose();
safe_delete(curr_path);
break;
case tile_t::TILE_ACTION_SWITCH:
if (worlds.all_done()) {
Logger::log("All Done: Great Success!!1!");
input.shouldClose();
}
if (worlds.set(tile.action_arg1)) {
safe_delete(curr_path);
}
break;
default:
break;
}
}
return moved;
}
static void draw_scene(const World* world, const Mesh& mesh, const TileMap& tilemap, GLint uniTileSpec, GLint uniModelType, unsigned ticks, int& origin_x, int& origin_y) {
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); // depth buffer needed, color buffer only for logger. TODO: does it hurt?
unsigned player_x=world->x, player_y=world->y;
origin_x = player_x - ((mesh.w-1)/2); // viewing radius
origin_y = player_y - ((mesh.h-1)/2);
for (unsigned y=0; y<mesh.h; ++y) {
int world_y = (int)y + origin_y;
if (world_y < 0) continue;
if ((unsigned)world_y >= world->h) break;
for (unsigned x=0; x<mesh.w;) {
int world_x = (int)x + origin_x;
if (world_x < 0) { x++; continue; }
if ((unsigned)world_x >= world->w) break;
bool is_player = (unsigned)world_x==player_x && (unsigned)world_y==player_y;
unsigned l, b;
const tile_t& tile = is_player? tile_defaults[tile_t::TILE_PLAYER]: world->at(world_x, world_y, l, b);
l = MIN(l, mesh.w-x);
if (is_player) {
l = 1;
} else if (player_y == (unsigned)world_y && player_x > (unsigned)world_x) {
l = MIN(l, player_x-world_x);
}
glUniform4fv(uniTileSpec, 1,
tilemap.tilespec(tile.tile, ticks/2) // TODO: could do this in shader, too
);
glUniform2ui(uniModelType, tile.model, is_player? 0: b);
mesh.draw(y, x, l);
x += l;
}
}
}
int main(int argc, char** argv) {
// Args
srand(time(NULL));
uint32_t seed = rand();
bool fullscreen = true;
unsigned arg_w=0, arg_h=0;
for (int i=1; i<argc; ++i) {
if (strcmp(argv[i], "--window") == 0) {
fullscreen = false;
} else if (sscanf(argv[i], "--window=%ux%u", &arg_w, &arg_h) == 2) {
fullscreen = false;
} else if (sscanf(argv[i], "--seed=%"SCNu32, &seed) == 1) {
} else {
LOG("Cannot parse arg '%s'", argv[i]);
return 1;
}
}
// initialize libs and create main window
Window* window = Window::getInst(arg_w, arg_h, fullscreen, argv[0]);
if (!window || glerr()) return 1;
LOG("created %ux%u window", window->w, window->h);
// implicitly create/bind logging/font stuff now
Logger* logger = Logger::getInst();
assert(logger->blocked); // do not mess with our main initialization
LOG("loaded overlay shaders");
// create program and attach shaders
Program* program = Program::getInst(FILE_PREFIX "/shaders");
if (!program) {
return 1;
}
glLinkProgram(*program);
glUseProgram(*program);
LOG("loaded main shaders");
// Generate our world
Mesh mesh(window, 75);
LOG("created %u (%ux%u) mesh (%u vertices, %zuB)", mesh.w*mesh.h, mesh.w, mesh.h, mesh.vertices_num, mesh.vertices_len);
Worlds worlds;
std::vector<Layout*> gworlds;
GameWorld::gen(seed, 100, 100, 200, 200, gworlds);
assert(!gworlds.empty());
for (std::vector<Layout*>::iterator it=gworlds.begin(); it!=gworlds.end(); ++it) {
worlds.push((*it)->id, new World(*it));
}
worlds.set(gworlds.front()->id);
gworlds.clear();
LOG("generated %ux%u world (seed %u)", worlds.world->w, worlds.world->h, seed);
// Create a Vertex Buffer & Array Object first and copy the vertex data to it
ArrayBuffer vbo(4);
vbo.bind(mesh.vertices, mesh.vertices_num, GL_STATIC_DRAW);
vbo.setAttrib(program, "posVI", 0, 2); // set input format acc. to GL_ARRAY_BUFFER layout
vbo.setAttrib(program, "texPosVI", 2, 2);
// Create an element array
Buffer ebo(GL_ELEMENT_ARRAY_BUFFER);
ebo.bind(mesh.elements, mesh.elements_len, GL_STATIC_DRAW);
LOG("created buffers");
// check for texture dimensions
const unsigned tile_px_x = window->iso2px_x(mesh.tile2iso_x(1)) - window->iso2px_x(mesh.tile2iso_x(0));
const unsigned tile_px_y = window->iso2px_y(mesh.tile2iso_y(1)) - window->iso2px_y(mesh.tile2iso_y(0));
LOG("flat tiles @ ~%ux%upx", tile_px_x, tile_px_y);
// load tilemap
TileMap tilemap(FILE_PREFIX "/textures/tilemap.ppm", 20, 1, 1);
GLint texloc = glGetUniformLocation(*program, "tex_tilemap");
if (texloc != -1) {
glUniform1i(texloc, tilemap.index());
}
LOG("loaded tilemap");
// find some uniforms needed lateron
GLint uniPlayerPos = glGetUniformLocation(*program, "playerPos");
GLint uniTileSpec = glGetUniformLocation(*program, "tilespec");
GLint uniTime = glGetUniformLocation(*program, "time");
GLint uniModelType = glGetUniformLocation(*program, "modelType");
// setup camera transformation
GLint uniOffset = glGetUniformLocation(*program, "offset");
GLint uniModel = glGetUniformLocation(*program, "model");
GLint uniView = glGetUniformLocation(*program, "view");
GLint uniProj = glGetUniformLocation(*program, "proj");
LookAt lookat(window, uniModel, uniView, uniProj);
// as we have fixed coordinates now, the player is always at 0,0. don't using stencils atm to save cpu time, too.
int cam_angle = lookat.project(45); // keep looking at origin, we redraw all tiles in the mesh atm
if (uniPlayerPos != -1) {
assert(window->w >= window->h);
glUniform3f(
uniPlayerPos,
0.0f, 0.0f,
(float)window->w/(float)window->h // shadow factor for x-radius to avoid ellipsis
);
}
LOG("initialized viewport");
// define framerate timings etc.
const double tick_interval = 1.0f/60.0f; // 100 fps at max (for animations)
const double move_interval = 1.0f/10.0f; // 10 movements/s at max
double last_tick = 0.0;
double last_move = 0.0;
double sleep_total=0.0, sleep_min=1.0, sleep_max=0.0;
const bool draw_ticks = true;
unsigned ticks=0;
// track current animation steps in each direction (one will be 0 atm, though)
const unsigned animation_ticks = floor(move_interval/tick_interval);
const float animation_step_x = (mesh.tile2iso_x(1) - mesh.tile2iso_x(0)) / (float)animation_ticks;
const float animation_step_y = (mesh.tile2iso_y(1) - mesh.tile2iso_y(0)) / (float)animation_ticks;
int animation_x=0, animation_y=0;
LOG("running at %.2f frames (%u animations)", round(1.0/tick_interval), animation_ticks);
// Finish for main loop
logger->blocked = false; // can now write to window, we will re-set active program if needed
Input input(window);
int origin_x=0, origin_y=0;
LOG("starting up...");
// main loop
while (!window->shouldClose()) {
// check frame timimgs
double this_tick = glfwGetTime();
double tick_delay = tick_interval - (this_tick - last_tick);
if (tick_delay > 0.0f) {
sleep_total += tick_delay;
sleep_max = MAX(sleep_max, tick_delay);
sleep_min = MIN(sleep_min, tick_delay);
usleep(floor(tick_delay * 1000000.0f)); // microsecs
this_tick = glfwGetTime();
} else if (ticks != 0){
LOG("losing frames!");
sleep_min = 0.0;
}
if (uniTime != -1) {
glUniform1ui(uniTime, (unsigned)floor(this_tick * 1000.0f)); // millisecs
}
last_tick = this_tick;
ticks++;
// backup current player position
World* last_world = worlds.world;
unsigned last_player_x=worlds.world->x, last_player_y=worlds.world->y;
// Check for existing task or new user input
bool moved = false;
if (!animation_x && !animation_y && last_move <= this_tick - move_interval) {
moved = process_inputs_and_move(worlds, mesh, input, lookat, origin_x, origin_y);
if (moved) {
last_move = this_tick;
}
}
// check for perspective changes more often, after unproject above
int panned = 0;
if (input.getPanKey(panned)) {
cam_angle = lookat.project(cam_angle + panned);
}
// update current position overlay if needed
if (moved || panned) {
Logger::log("%u-%u (%d\xb0)", worlds.world->x, worlds.world->y, cam_angle);
}
// setup animation if we just moved by 1 tile
if (moved && worlds.world == last_world) {
assert(!animation_x && !animation_y);
int dir_x = worlds.world->x - last_player_x;
int dir_y = worlds.world->y - last_player_y;
if (abs(dir_x) + abs(dir_y) == 1) {
animation_x = dir_x * animation_ticks;
animation_y = dir_y * animation_ticks;
}
}
// set camera offset acc. to a running animation
if (animation_x || animation_y) {
glUniform2f(uniOffset, (float)animation_x * animation_step_x, (float)animation_y * animation_step_y);
if (animation_x < 0) { animation_x++; } else if (animation_x > 0) { animation_x--; }
if (animation_y < 0) { animation_y++; } else if (animation_y > 0) { animation_y--; }
} else {
glUniform2f(uniOffset, 0.0f, 0.0f);
}
// draw mesh
bool drawn = draw_ticks || moved || panned || ticks == 1;
if (drawn) {
draw_scene(worlds.world, mesh, tilemap, uniTileSpec, uniModelType, ticks, origin_x, origin_y);
}
// log new messages
if (logger->logall(window, drawn)) { // redraw if we did sth before
glUseProgram(*program);
vbo.bind();
ebo.bind();
drawn = true;
}
// draw & collect new input
if (drawn) {
glfwSwapBuffers(window->window);
}
glfwPollEvents();
}
LOG("exiting after %u loops/draws", ticks);
LOG("slept %f avg (%f min, %f max) of %f", sleep_total/(double)ticks, sleep_min, sleep_max, tick_interval);
sleep(1);
// TODO: cleanup
delete program;
delete window;
return 0;
}