#include #include #include GL_INCLUDE_FILE #include #include #include #include "Client.h" #include "ccfs.h" #include "HexTile.h" using namespace std; #define LEN(arr) (sizeof(arr)/sizeof(arr[0])) #define OPENGL_CONTEXT_MAJOR 3 #define OPENGL_CONTEXT_MINOR 0 #define SKY_DIST 2000 #define NUM_SKY_STEPS 9 #define LAVA_SIZE 100 #define SHOT_RING_WIDTH 10.0f #define NUM_SHOT_RING_STEPS 24 /* points of a horizontal hexagon 1.0 units high */ static const float overlay_hex_attributes[][3] = { {0.0, 0.0}, {HEX_WIDTH_TO_HEIGHT / 2.0, 0.0, 0.0}, {HEX_WIDTH_TO_HEIGHT / 4.0, 0.5, 0.0}, {-HEX_WIDTH_TO_HEIGHT / 4.0, 0.5, 0.0}, {-HEX_WIDTH_TO_HEIGHT / 2.0, 0.0, 0.0}, {-HEX_WIDTH_TO_HEIGHT / 4.0, -0.5, 0.0}, {HEX_WIDTH_TO_HEIGHT / 4.0, -0.5, 0.0} }; static const GLushort overlay_hex_indices[] = { 0, 1, 2, 3, 4, 5, 6, 1 }; static const struct { float pos[3]; float tex_coord[2]; } tex_quad_attributes[] = { {{0.5, 0.5, 0.0}, {1.0, 1.0}}, {{-0.5, 0.5, 0.0}, {0.0, 1.0}}, {{-0.5, -0.5, 0.0}, {0.0, 0.0}}, {{0.5, -0.5, 0.0}, {1.0, 0.0}} }; static const float quad_attributes[][3] = { {0.5, 0.5, 0.0}, {-0.5, 0.5, 0.0}, {-0.5, -0.5, 0.0}, {0.5, -0.5, 0.0} }; static bool load_file(const char *fname, WFObj::Buffer & buff) { unsigned int length; uint8_t *contents = (uint8_t *) CFS.get_file(fname, &length); if (contents != NULL) { buff.data = contents; buff.length = length; return true; } return false; } bool Client::create_window(bool fullscreen, int width, int height) { sf::VideoMode mode = fullscreen ? sf::VideoMode::getDesktopMode() : sf::VideoMode(width, height, 32); long style = fullscreen ? sf::Style::Fullscreen : sf::Style::Resize | sf::Style::Close; sf::ContextSettings cs = sf::ContextSettings(0, 0, 0, OPENGL_CONTEXT_MAJOR, OPENGL_CONTEXT_MINOR); m_window = new sf::Window(mode, "Treacherous Terrain", style, cs); m_window->setMouseCursorVisible(false); grab_mouse(true); if (!initgl()) return false; resize_window(m_window->getSize().x, m_window->getSize().y); return true; } bool Client::initgl() { if (gl3wInit()) { cerr << "Failed to initialize GL3W" << endl; return false; } if (!gl3wIsSupported(3, 0)) { cerr << "OpenGL 3.0 is not supported!" << endl; return false; } glEnable(GL_DEPTH_TEST); if (!m_obj_program.create( CFS.get_file("shaders/obj.v.glsl"), CFS.get_file("shaders/obj.f.glsl"), "pos", 0, "normal", 1, NULL, "ambient", "diffuse", "specular", "shininess", "projection", "modelview", NULL)) return false; if (!m_overlay_program.create( CFS.get_file("shaders/obj.v.glsl"), CFS.get_file("shaders/overlay.f.glsl"), "pos", 0, "normal", 1, NULL, "projection", "modelview", "color", NULL)) return false; if (!m_overlay_hover_program.create( CFS.get_file("shaders/obj.v.glsl"), CFS.get_file("shaders/overlay_hover.f.glsl"), "pos", 0, "normal", 1, NULL, "projection", "modelview", NULL)) return false; if (!m_sky_program.create( CFS.get_file("shaders/sky.v.glsl"), CFS.get_file("shaders/sky.f.glsl"), "pos", 0, "color", 1, NULL, "projection", "modelview", NULL)) return false; if (!m_lava_program.create( CFS.get_file("shaders/lava.v.glsl"), CFS.get_file("shaders/lava.f.glsl"), "pos", 0, "tex_coord", 1, NULL, "projection", "modelview", "tex", "shift", NULL)) return false; if (!m_shot_ring_program.create( CFS.get_file("shaders/shot-ring.v.glsl"), CFS.get_file("shaders/shot-ring.f.glsl"), "pos", 0, NULL, "projection", "modelview", "scale", "width", NULL)) return false; if (!m_tank_obj.load("models/tank.obj", load_file)) { cerr << "Error loading tank model" << endl; return false; } if (!m_tile_obj.load("models/hex-tile.obj", load_file)) { cerr << "Error loading hex-tile model" << endl; return false; } if (!m_tile_damaged_obj.load("models/hex-tile-damaged.obj", load_file)) { cerr << "Error loading hex-tile-damaged model" << endl; return false; } if (!m_overlay_hex_attributes.create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, overlay_hex_attributes, sizeof(overlay_hex_attributes))) { cerr << "Error creating overlay hex attribute buffer" << endl; return false; } if (!m_overlay_hex_indices.create(GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW, overlay_hex_indices, sizeof(overlay_hex_indices))) { cerr << "Error creating overlay hex indices buffer" << endl; return false; } if (!m_tex_quad_attributes.create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, tex_quad_attributes, sizeof(tex_quad_attributes))) { cerr << "Error creating tex quad attributes buffer" << endl; return false; } if (!m_quad_attributes.create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, quad_attributes, sizeof(quad_attributes))) { cerr << "Error creating quad attributes buffer" << endl; return false; } vector sky_attributes((NUM_SKY_STEPS + 1) * 2 * (3 + 3)); for (int i = 0, idx = 0; i <= NUM_SKY_STEPS; i++) { GLfloat x = SKY_DIST * sin(M_PI_4 + i * M_PI_2 / NUM_SKY_STEPS); GLfloat y = SKY_DIST * cos(M_PI - M_PI_4 - i * M_PI_2 / NUM_SKY_STEPS); sky_attributes[idx++] = x; sky_attributes[idx++] = y; sky_attributes[idx++] = -10.0; sky_attributes[idx++] = 0.6; sky_attributes[idx++] = 0.6; sky_attributes[idx++] = 1.0; sky_attributes[idx++] = x; sky_attributes[idx++] = y; sky_attributes[idx++] = 1000.0; sky_attributes[idx++] = 0.1; sky_attributes[idx++] = 0.1; sky_attributes[idx++] = 1.0; } if (!m_sky_attributes.create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, &sky_attributes[0], sizeof(sky_attributes[0]) * sky_attributes.size())) { cerr << "Error creating sky attribute buffer" << endl; return false; } vector shot_ring_attributes((NUM_SHOT_RING_STEPS + 1) * 2 * 4); for (int i = 0, idx = 0; i <= NUM_SHOT_RING_STEPS; i++) { double angle = i * M_PI * 2.0 / NUM_SHOT_RING_STEPS; GLfloat x = sin(angle); GLfloat y = cos(angle); shot_ring_attributes[idx++] = x; shot_ring_attributes[idx++] = y; shot_ring_attributes[idx++] = 0.0f; shot_ring_attributes[idx++] = 0.0f; shot_ring_attributes[idx++] = x; shot_ring_attributes[idx++] = y; shot_ring_attributes[idx++] = 0.0f; shot_ring_attributes[idx++] = 1.0f; } if (!m_shot_ring_attributes.create(GL_ARRAY_BUFFER, GL_STATIC_DRAW, &shot_ring_attributes[0], sizeof(shot_ring_attributes[0]) * shot_ring_attributes.size())) { cerr << "Error creating shot ring attributes buffer" << endl; return false; } unsigned int lava_texture_length; const uint8_t *lava_texture = CFS.get_file("textures/lava.jpg", &lava_texture_length); if (lava_texture == NULL) { cerr << "Error loading lava texture" << endl; return false; } if (!m_lava_texture.loadFromMemory(lava_texture, lava_texture_length)) { cerr << "Error creating lava texture" << endl; return false; } glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); return true; } void Client::resize_window(int width, int height) { m_width = width; m_height = height; recenter_cursor(); glViewport(0, 0, width, height); float aspect = (float)width / (float)height; m_projection.load_identity(); m_projection.perspective(60.0f, aspect, 1.0f, 5000.0f); } void Client::redraw() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (m_players.size() > 0) { m_modelview.load_identity(); m_modelview.look_at( m_players[m_current_player]->x - m_player_dir_x * 25, m_players[m_current_player]->y - m_player_dir_y * 25, 30, m_players[m_current_player]->x, m_players[m_current_player]->y, 20, 0, 0, 1); for(std::map >::iterator piter = m_players.begin(); piter != m_players.end(); piter++) { draw_player(piter->second); } draw_map(); draw_sky(); draw_lava(); draw_shot_ring(); draw_overlay(); } m_window->display(); } void Client::draw_player(refptr player) { m_obj_program.use(); m_modelview.push(); m_modelview.translate(player->x, player->y, 4); m_modelview.rotate(player->direction * 180.0 / M_PI, 0, 0, 1); m_modelview.scale(2, 2, 2); m_tank_obj.bindBuffers(); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); int stride = m_tank_obj.getStride(); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid *) m_tank_obj.getVertexOffset()); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid *) m_tank_obj.getNormalOffset()); m_projection.to_uniform(m_obj_program.uniform("projection")); m_modelview.to_uniform(m_obj_program.uniform("modelview")); for (map::iterator it = m_tank_obj.getMaterials().begin(); it != m_tank_obj.getMaterials().end(); it++) { WFObj::Material & m = it->second; if (m.flags & WFObj::Material::SHININESS_BIT) { glUniform1f(m_obj_program.uniform("shininess"), m.shininess); } if (m.flags & WFObj::Material::AMBIENT_BIT) { glUniform4fv(m_obj_program.uniform("ambient"), 1, &m.ambient[0]); } if (m.flags & WFObj::Material::DIFFUSE_BIT) { glUniform4fv(m_obj_program.uniform("diffuse"), 1, &m.diffuse[0]); } if (m.flags & WFObj::Material::SPECULAR_BIT) { glUniform4fv(m_obj_program.uniform("specular"), 1, &m.specular[0]); } glDrawElements(GL_TRIANGLES, m.num_vertices, GL_UNSIGNED_SHORT, (GLvoid *) (sizeof(GLushort) * m.first_vertex)); } glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); m_modelview.pop(); } void Client::draw_map() { m_obj_program.use(); m_projection.to_uniform(m_obj_program.uniform("projection")); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); const int width = m_map.get_width(); const int height = m_map.get_height(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if ((m_map.tile_present(x, y)) && (m_map.get_tile(x,y)->get_damage_state() < HexTile::DESTROYED)) { refptr tile = m_map.get_tile(x, y); WFObj & obj = (tile->get_damage_state() == HexTile::DAMAGED) ? m_tile_damaged_obj : m_tile_obj; obj.bindBuffers(); int stride = obj.getStride(); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid *) obj.getVertexOffset()); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid *) obj.getNormalOffset()); float cx = tile->get_x(); float cy = tile->get_y(); m_modelview.push(); m_modelview.translate(cx, cy, 0); if (tile->get_damage_state() == HexTile::DAMAGED) m_modelview.translate(0, 0, -1); m_modelview.scale(tile->get_size(), tile->get_size(), tile->get_size()); m_modelview.to_uniform(m_obj_program.uniform("modelview")); for (map::iterator it = obj.getMaterials().begin(); it != obj.getMaterials().end(); it++) { WFObj::Material & m = it->second; if (m.flags & WFObj::Material::SHININESS_BIT) { glUniform1f(m_obj_program.uniform("shininess"), m.shininess); } if (m.flags & WFObj::Material::AMBIENT_BIT) { glUniform4fv(m_obj_program.uniform("ambient"), 1, &m.ambient[0]); } if (m.flags & WFObj::Material::DIFFUSE_BIT) { glUniform4fv(m_obj_program.uniform("diffuse"), 1, &m.diffuse[0]); } if (m.flags & WFObj::Material::SPECULAR_BIT) { glUniform4fv(m_obj_program.uniform("specular"), 1, &m.specular[0]); } glDrawElements(GL_TRIANGLES, m.num_vertices, GL_UNSIGNED_SHORT, (GLvoid *) (sizeof(GLushort) * m.first_vertex)); } m_modelview.pop(); } } } } void Client::draw_overlay() { /* draw overlay map */ int overlay_size = (int)(m_width * 0.15); glViewport(m_width - overlay_size - 50, m_height - overlay_size - 50, overlay_size, overlay_size); glEnable(GL_BLEND); glDisable(GL_DEPTH_TEST); m_overlay_program.use(); GLMatrix proj; const float span = 50 * 8; proj.ortho(-span, span, -span, span, -1, 1); proj.to_uniform(m_overlay_program.uniform("projection")); GLMatrix modelview; modelview.rotate(90 - m_players[m_current_player]->direction * 180 / M_PI, 0, 0, 1); modelview.translate(-m_players[m_current_player]->x, -m_players[m_current_player]->y, 0); m_overlay_hex_attributes.bind(); m_overlay_hex_indices.bind(); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(overlay_hex_attributes[0]), NULL); const int width = m_map.get_width(); const int height = m_map.get_height(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if ((m_map.tile_present(x, y)) && (m_map.get_tile(x,y)->get_damage_state() < HexTile::DESTROYED)) { refptr tile = m_map.get_tile(x, y); float cx = tile->get_x(); float cy = tile->get_y(); modelview.push(); modelview.translate(cx, cy, 0); modelview.scale(tile->get_size(), tile->get_size(), tile->get_size()); modelview.to_uniform(m_overlay_program.uniform("modelview")); glUniform4f(m_overlay_program.uniform("color"), 0.2, 0.8, 0.8, 0.3); glDrawElements(GL_TRIANGLE_FAN, LEN(overlay_hex_indices), GL_UNSIGNED_SHORT, NULL); glUniform4f(m_overlay_program.uniform("color"), 0.4, 0.9, 0.9, 1.0); glDrawElements(GL_LINE_LOOP, LEN(overlay_hex_indices) - 2, GL_UNSIGNED_SHORT, (GLvoid *)(sizeof(GLushort))); modelview.pop(); } } } glDisableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); GLMatrix::Identity.to_uniform(m_overlay_program.uniform("modelview")); glVertexAttrib3f(0, 0, 0, 0); glUniform4f(m_overlay_program.uniform("color"), 1, 0, 0, 1); glPointSize(3); glDrawArrays(GL_POINTS, 0, 1); /* draw hover bar */ glViewport(m_width - 200, 100, 150, 25); m_overlay_hover_program.use(); m_quad_attributes.bind(); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), NULL); GLMatrix::Identity.to_uniform( m_overlay_hover_program.uniform("projection")); modelview.load_identity(); modelview.translate(m_players[m_current_player]->hover - 1, 0, 0); modelview.scale(m_players[m_current_player]->hover * 2, 2.0, 1.0); modelview.to_uniform(m_overlay_hover_program.uniform("modelview")); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); /* draw map border */ glViewport(0, 0, m_width, m_height); m_overlay_program.use(); m_quad_attributes.bind(); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), NULL); GLMatrix::Identity.to_uniform(m_overlay_program.uniform("projection")); modelview.load_identity(); modelview.ortho(0, m_width, 0, m_height, -1, 1); modelview.translate(m_width - overlay_size / 2 - 50, m_height - overlay_size / 2 - 50, 0); modelview.scale(overlay_size + 0.1, overlay_size + 0.1, 1); modelview.to_uniform(m_overlay_program.uniform("modelview")); glUniform4f(m_overlay_program.uniform("color"), 1, 1, 1, 1); glDrawArrays(GL_LINE_LOOP, 0, 4); /* draw hover bar border */ modelview.load_identity(); modelview.ortho(0, m_width, 0, m_height, -1, 1); modelview.translate(m_width - 200 + 150 / 2, 100 + 25 / 2, 0); modelview.scale(150.1, 25.1, 1); modelview.to_uniform(m_overlay_program.uniform("modelview")); glDrawArrays(GL_LINE_LOOP, 0, 4); /* reset GL to normal state */ glDisableVertexAttribArray(0); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); } void Client::draw_sky() { m_sky_program.use(); m_sky_attributes.bind(); m_projection.to_uniform(m_sky_program.uniform("projection")); m_modelview.push(); m_modelview.translate(m_players[m_current_player]->x, m_players[m_current_player]->y, 0); m_modelview.rotate(m_players[m_current_player]->direction * 180.0 / M_PI, 0, 0, 1); m_modelview.to_uniform(m_sky_program.uniform("modelview")); m_modelview.pop(); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), NULL); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid *) (3 * sizeof(GLfloat))); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_STRIP, 0, (NUM_SKY_STEPS + 1) * 2); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); } void Client::draw_lava() { m_lava_program.use(); m_tex_quad_attributes.bind(); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); m_projection.to_uniform(m_lava_program.uniform("projection")); m_lava_texture.bind(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glUniform1i(m_lava_program.uniform("tex"), 0); double shift = m_clock.getElapsedTime().asSeconds() / 40; glUniform1f(m_lava_program.uniform("shift"), shift); const int n_lavas = 2 * SKY_DIST / LAVA_SIZE; glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), NULL); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid *) (3 * sizeof(GLfloat))); for (int i = 0; i < n_lavas; i++) { for (int j = 0; j < n_lavas; j++) { m_modelview.push(); m_modelview.translate((i - n_lavas / 2) * LAVA_SIZE, (j - n_lavas / 2) * LAVA_SIZE, -2); m_modelview.scale(LAVA_SIZE, LAVA_SIZE, 1); m_modelview.to_uniform(m_lava_program.uniform("modelview")); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); m_modelview.pop(); } } glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); } void Client::draw_shot_ring() { if (m_drawing_shot) { m_shot_ring_program.use(); m_shot_ring_attributes.bind(); glEnableVertexAttribArray(0); m_modelview.push(); m_modelview.translate(m_players[m_current_player]->x, m_players[m_current_player]->y, 0.4); m_projection.to_uniform(m_shot_ring_program.uniform("projection")); m_modelview.to_uniform(m_shot_ring_program.uniform("modelview")); glUniform1f(m_shot_ring_program.uniform("scale"), m_drawing_shot_distance); glUniform1f(m_shot_ring_program.uniform("width"), SHOT_RING_WIDTH); draw_shot_ring_instance(); float mid_dist = m_drawing_shot_distance + SHOT_RING_WIDTH / 2.0; m_modelview.translate(mid_dist * m_player_dir_x, mid_dist * m_player_dir_y, 0.01); m_modelview.to_uniform(m_shot_ring_program.uniform("modelview")); glUniform1f(m_shot_ring_program.uniform("scale"), 0.45 * SHOT_RING_WIDTH); glUniform1f(m_shot_ring_program.uniform("width"), 0.05 * SHOT_RING_WIDTH); draw_shot_ring_instance(); m_modelview.pop(); glDisableVertexAttribArray(0); } } void Client::draw_shot_ring_instance() { glEnable(GL_BLEND); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), NULL); glDrawArrays(GL_TRIANGLE_STRIP, 0, (NUM_SHOT_RING_STEPS + 1) * 2); glDisable(GL_BLEND); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), NULL); glDrawArrays(GL_LINE_STRIP, 0, NUM_SHOT_RING_STEPS + 1); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (void *) (4 * sizeof(GLfloat))); glDrawArrays(GL_LINE_STRIP, 0, NUM_SHOT_RING_STEPS + 1); }