#include "scene.h" #include "video.h" #include "terrain.h" #include #include #include #include #include "gl.h" #include using models::ModelManager; Scene::Scene() { running = true; grid = false; normals = false; render_terrain = true; gravity = true; flying = true; underwater = false; last_node = NULL; do_select = false; show_selection = false; dialog = false; tool = NULL; /* setup shader programs */ GLFragmentShader fog_fragment("shaders/fog_fragment.glsl"); GLVertexShader terrain_vertex("shaders/terrain_vertex.glsl"); GLFragmentShader terrain_fragment("shaders/terrain_fragment.glsl"); terrain_program.attach(terrain_vertex); terrain_program.attach(terrain_fragment); terrain_program.attach(fog_fragment); terrain_program.link(); GLFragmentShader water_fragment("shaders/water_fragment.glsl"); water_program.attach(terrain_vertex); water_program.attach(water_fragment); water_program.link(); GLVertexShader tree_vertex("shaders/tree_vertex.glsl"); GLFragmentShader tree_fragment("shaders/tree_fragment.glsl"); tree_program.attach(tree_vertex); tree_program.attach(tree_fragment); tree_program.attach(fog_fragment); tree_program.link(); terrain_program.use(); GLint tex = glGetUniformLocation(terrain_program.get_program(), "tex"); GLint texv[] = {0, 1, 2}; glUniform1iv(tex, 3, texv); GLint markloc = glGetUniformLocation(terrain_program.get_program(), "marktex"); glUniform1i(markloc, 3); tree_program.use(); tex = glGetUniformLocation(tree_program.get_program(), "tex"); glUniform1iv(tex, 1, texv); glUseProgram(0); /* load textures */ grass_texture = load_texture("textures/zooboing-366-grass.jpg"); rock_texture = load_texture("textures/zooboing-825-stone-modified.jpg"); soil_texture = load_texture("textures/zooboing-469-sand-modified.jpg"); water_texture = load_texture("textures/zooboing-688-water.jpg"); marker_texture = load_texture("textures/cross.png"); placeholder_texture = load_texture("textures/placeholder.png"); Assimp::Importer ai_importer; const aiScene *tree_scene = ai_importer.ReadFile("models/trees.blend", aiProcess_Triangulate); for(unsigned int i = 0; i < tree_scene->mNumTextures; i++) { aiTexture *texture = tree_scene->mTextures[i]; GLuint tx = load_texture(texture); std::string name = (boost::format("*%d") % i).str(); scene_textures.insert(std::pair(name, tx)); } Model::p tree = Model::p(new models::Tree(tree_scene, scene_textures)); ModelManager::get_instance().add_model("tree", tree); /* init terrain */ terrain = new Terrain(); /* load font */ font = new FTTextureFont("fonts/VeraMono.ttf"); font->FaceSize(10); lua = new Lua(); GUI::init(); console = new ConsoleWindow(lua); console->hide(); chat = new ChatWindow(); void (ConsoleWindow::*add_line)(const char*) = &ConsoleWindow::add_line; lua->set_log_func(boost::bind(add_line, console, _1)); } Scene::~Scene() { delete lua; if(tool) delete tool; if(terrain) delete terrain; delete font; delete console; delete chat; } void Scene::lookat() { const float cam_height = 1.7; /* calculate cartesian coordinates for the center vector where yaw is Φ and pitch is θ * x = cos Φ sin θ * y = cos θ * z = sin Φ sin θ */ Vector3 center(sinf(pitch) * cosf(yaw), cosf(pitch), sinf(pitch) * sinf(yaw)); center.y += cam_height; Vector3 up(-cosf(pitch) * cosf(yaw), sinf(pitch), -cosf(pitch) * sinf(yaw)); gluLookAt(0, cam_height, 0, center.x, center.y, center.z, up.x, up.y, up.z); } void Scene::move(float forward, float right, int steps) { Vector2 dir; dir.x += forward*cosf(yaw); dir.y += forward*sinf(yaw); dir.x += right*cosf(yaw+M_PI_2); dir.y += right*sinf(yaw+M_PI_2); // ensure that the vector length is 1.0 dir /= dir.length(); dir *= 0.005; dir *= steps; pos.x += dir.x; pos.z += dir.y; } bool Scene::select(int x, int y, float& px, float& py, float& pz) { GLint view[4] = {0}; GLdouble mvmatrix[16] = {0}, projmatrix[16] = {0}; glGetDoublev(GL_PROJECTION_MATRIX, projmatrix); glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix); glGetIntegerv(GL_VIEWPORT, view); y = view[3] - y - 1; float z; glReadBuffer(GL_BACK); glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z); GLdouble _px, _py, _pz; if(gluUnProject(x, y, z, mvmatrix, projmatrix, view, &_px, &_py, &_pz) == GLU_TRUE) { px = _px; py = _py; pz = _pz; return true; } return false; } void Scene::update() { terrain->update(pos.x, pos.z); // sort players once a second if(SDL_GetTicks() - last_sort > 1000) { sort_players(); last_sort = SDL_GetTicks(); } } void Scene::events() { SDL_Event event; while(SDL_PollEvent(&event)) { if(tool && !dialog && tool->handle_event(event, selected)) continue; switch(event.type) { case SDL_QUIT: running = false; break; case SDL_KEYDOWN: // TODO: FIX! #ifdef WIN32 if(console->showing() && event.key.keysym.sym != SDLK_BACKQUOTE) { #else if(console->showing() && event.key.keysym.sym != 124) { #endif CEGUI::System::getSingleton().injectKeyDown(event.key.keysym.scancode); if(CEGUI::System::getSingleton().injectChar(event.key.keysym.unicode)) break; } switch(event.key.keysym.sym) { case SDLK_ESCAPE: if(tool) { delete tool; tool = NULL; } else if(console->showing()) { console->hide(); dialog = false; } else running = false; break; case SDLK_g: grid = !grid; break; case SDLK_n: normals = !normals; break; case SDLK_t: render_terrain = !render_terrain; break; case SDLK_SPACE: if(!flying || underwater) { yvel = underwater ? .005 : .02; flying = true; } break; case SDLK_h: gravity = !gravity; break; case SDLK_u: update(); break; case SDLK_TAB: dialog = !dialog && tool; if(tool) { if(dialog) tool->gui_show(); else tool->gui_hide(); } break; case SDLK_1: if(tool) delete tool; tool = new RaiseTool(terrain); break; // TODO: change/make configurable #ifdef WIN32 case SDLK_BACKQUOTE: #else case 124: #endif if(console->showing()) { console->hide(); dialog = false; } else { console->show(); dialog = true; } break; default: break; } break; case SDL_KEYUP: if(console->showing() && event.key.keysym.sym != 124) { CEGUI::System::getSingleton().injectKeyUp(event.key.keysym.scancode); } break; case SDL_MOUSEBUTTONDOWN: switch(event.button.button) { case SDL_BUTTON_LEFT: if(dialog) CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::LeftButton); break; case SDL_BUTTON_RIGHT: if(dialog) CEGUI::System::getSingleton().injectMouseButtonDown(CEGUI::RightButton); break; } break; case SDL_MOUSEBUTTONUP: switch(event.button.button) { case SDL_BUTTON_LEFT: if(dialog) CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::LeftButton); else { sx = event.button.x; sy = event.button.y; do_select = true; } break; case SDL_BUTTON_RIGHT: if(dialog) CEGUI::System::getSingleton().injectMouseButtonUp(CEGUI::RightButton); else show_selection = false; break; case SDL_BUTTON_WHEELUP: if(dialog) CEGUI::System::getSingleton().injectMouseWheelChange(1); break; case SDL_BUTTON_WHEELDOWN: if(dialog) CEGUI::System::getSingleton().injectMouseWheelChange(-1); break; } break; case SDL_MOUSEMOTION: if(dialog) { CEGUI::System::getSingleton().injectMousePosition(event.motion.x, event.motion.y); break; } if(event.motion.x == video::width/2 && event.motion.y == video::height/2) break; yaw += (float)event.motion.xrel / 500; pitch += (float)event.motion.yrel / 500; if(yaw > 2*M_PI) yaw -= 2*M_PI; else if(yaw < 0) yaw += 2*M_PI; if(pitch > M_PI) pitch = M_PI; else if(pitch < 0) pitch = 0; SDL_WarpMouse(video::width/2, video::height/2); break; case SDL_VIDEORESIZE: CEGUI::System::getSingleton().notifyDisplaySizeChanged(CEGUI::Size(event.resize.w, event.resize.h)); break; } } } void Scene::render() { unsigned int time = SDL_GetTicks(); unsigned int steps = time - last_time + 1; last_time = time; CEGUI::System::getSingleton().injectTimePulse(0.001*(float)steps); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); video::persp(); unsigned char *keystate = SDL_GetKeyState(NULL); float forward = 0; float right = 0; bool moved = false; if(!dialog) { if(keystate[SDLK_w]) { moved = true; forward++; } if(keystate[SDLK_s]) { moved = true; forward--; } if(keystate[SDLK_a]) { moved = true; right--; } if(keystate[SDLK_d]) { moved = true; right++; } if(keystate[SDLK_q]) pos.y -= 0.002*steps*(keystate[SDLK_LSHIFT]?10:1); if(keystate[SDLK_e]) pos.y += 0.002*steps*(keystate[SDLK_LSHIFT]?10:1); if(moved && (forward || right)) { move(forward, right, steps*(keystate[SDLK_LSHIFT]?10:1)); } } // handle vertical movement std::string move_str; Terrain::Node *node = terrain->find(pos.x, pos.z); if(node) { if(gravity) { float y = node->get_height(pos.x, pos.z); underwater = pos.y < 30 - 1.7; if(pos.y < 30 - 1.5 || (pos.y < 30 - 1 && y < 30 - 1)) yvel -= 9.81 / 2000000. * steps; else if(pos.y > y && !keystate[SDLK_e]) yvel -= 9.81 / 200000. * steps; if(yvel < -.1) yvel = -.1; pos.y += yvel * steps; if(pos.y < y) { pos.y = y; yvel = 0; flying = false; } } move_str = (boost::format("%s n: %.2f,%.2f c: %d,%d %dx%d") % pos.str() % node->x % node->y % node->chunk->x % node->chunk->y % node->chunk->width % node->chunk->height).str(); if(last_node != node) { last_node = node; update(); } } lookat(); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); Terrain::Chunk::ObjectList trees; // render terrain if(render_terrain) { const float fog_color[4] = {1, 1, 1, 0}; glFogfv(GL_FOG_COLOR, fog_color); terrain_program.use(); GLint show_sel = glGetUniformLocation(terrain_program.get_program(), "show_sel"); glUniform1i(show_sel, show_selection ? 1 : 0); if(show_selection) { GLint selpos = glGetUniformLocation(terrain_program.get_program(), "selpos"); glUniform3f(selpos, selected.x, selected.y, selected.z); } GLint chunk_pos = glGetUniformLocation(terrain_program.get_program(), "chunk_pos"); GLint player_pos = glGetUniformLocation(terrain_program.get_program(), "player_pos"); glUniform3f(player_pos, pos.x, pos.y, pos.z); glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, grass_texture); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, rock_texture); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, soil_texture); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, marker_texture); // enable arrays, disabled after water rendering glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); // draw chunk VBOs for(std::list::iterator it = terrain->chunks.begin(); it != terrain->chunks.end(); it++) { Terrain::Chunk *chunk = *it; trees.insert(trees.end(), chunk->objects.begin(), chunk->objects.end()); glPushMatrix(); glTranslatef(-pos.x + chunk->x, -pos.y, -pos.z + chunk->y); glUniform2f(chunk_pos, chunk->x, chunk->y); glBindBuffer(GL_ARRAY_BUFFER, chunk->vbo_object); glVertexPointer(3, GL_FLOAT, 0, NULL); glNormalPointer(GL_FLOAT, 0, (GLvoid*)(chunk->vertices*3*sizeof(float))); glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)(chunk->vertices*3*sizeof(float)*2)); glDrawArrays(GL_TRIANGLES, 0, chunk->vertices); glPopMatrix(); } glBindBuffer(GL_ARRAY_BUFFER, 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, water_texture); glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); water_program.use(); chunk_pos = glGetUniformLocation(water_program.get_program(), "chunk_pos"); player_pos = glGetUniformLocation(water_program.get_program(), "player_pos"); glUniform3f(player_pos, pos.x, pos.y, pos.z); GLint t_pos = glGetUniformLocation(water_program.get_program(), "t"); static float t = 0.0; t += 0.00002 * steps; glUniform1f(t_pos, t); // draw chunk water for(std::list::iterator it = terrain->chunks.begin(); it != terrain->chunks.end(); it++) { Terrain::Chunk *chunk = *it; glPushMatrix(); glTranslatef(-pos.x + chunk->x, -pos.y, -pos.z + chunk->y); glUniform2f(chunk_pos, chunk->x, chunk->y); float v[4*3]; v[0] = v[2] = v[3] = v[11] = 0; v[5] = v[6] = v[8] = v[9] = terrain->chunk_size; v[1] = v[4] = v[7] = v[10] = 30; float n[4*3]; n[0] = n[2] = n[3] = n[5] = n[6] = n[8] = n[9] = n[11] = 0; n[1] = n[4] = n[7] = n[10] = 1; float tc[4*2]; tc[0] = tc[1] = tc[2] = tc[7] = 0; tc[3] = tc[4] = tc[5] = tc[6] = terrain->chunk_size / 2; glVertexPointer(3, GL_FLOAT, 0, v); glNormalPointer(GL_FLOAT, 0, n); glTexCoordPointer(2, GL_FLOAT, 0, tc); glDrawArrays(GL_QUADS, 0, 4); glPopMatrix(); } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisable(GL_BLEND); glUseProgram(0); } if(grid) { if(terrain) glColor3f(0, 0, 0); else glColor3f(1, 1, 1); for(std::list::iterator it = terrain->chunks.begin(); it != terrain->chunks.end(); it++) { Terrain::Chunk *chunk = *it; glPushMatrix(); glTranslatef(-pos.x, -pos.y, -pos.z); for(unsigned int i = 0; i < chunk->node_count; i++) chunk->nodes[i]->draw_grid(); glPopMatrix(); } } if(normals) { for(std::list::iterator it = terrain->chunks.begin(); it != terrain->chunks.end(); it++) { Terrain::Chunk *chunk = *it; glPushMatrix(); glTranslatef(-pos.x, -pos.y, -pos.z); for(unsigned int i = 0; i < chunk->node_count; i++) chunk->nodes[i]->draw_normal(); glPopMatrix(); } } float px, py, pz; if(do_select && select(sx, sy, px, py, pz)) { do_select = false; selected = Vector3(pos.x + px, pos.y + py, pos.z + pz); show_selection = true; } glDisable(GL_TEXTURE_2D); // draw sky glPushMatrix(); glTranslatef(700*cosf(yaw), 0, 700*sinf(yaw)); glRotatef(-yaw*180/M_PI+180, 0, 1, 0); glBegin(GL_QUADS); glColor3f(1, 1, 1); glVertex3f(0, 0, -700); glVertex3f(0, 0, 700); glColor3f(.5, .5, 1); glVertex3f(0, 550, 700); glVertex3f(0, 550, -700); glVertex3f(0, 550, -700); glVertex3f(0, 550, 700); glColor3f(0, 0, .3); glVertex3f(1000, 550, 700); glVertex3f(1000, 550, -700); glEnd(); glPopMatrix(); // player rendering glPushMatrix(); glTranslatef(-pos.x, -pos.y, -pos.z); glColor3f(1, 1, 1); font->FaceSize(30); for(PlayerList::iterator it = players.begin(); it != players.end(); it++) { (*it)->render(font, steps, placeholder_texture); } // don't pop matrix; use translated player position // rotate to point upwards along the y-aksis glRotatef(-90, 1, 0, 0); glEnable(GL_TEXTURE_2D); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); tree_program.use(); GLint player_pos = glGetUniformLocation(tree_program.get_program(), "player_pos"); glUniform3f(player_pos, pos.x, pos.y, pos.z); GLint tree_pos = glGetUniformLocation(tree_program.get_program(), "tree_pos"); //tree->render(); for(Terrain::Chunk::ObjectList::iterator it = trees.begin(); it != trees.end(); it++) { glPushMatrix(); Vector3 pos(it->second); glUniform3f(tree_pos, pos.x, pos.y, pos.z); // rotated around x-axis; swap y and z, do magic to fix positions glTranslatef(pos.x, -pos.z, pos.y+.5); it->first->render(); glPopMatrix(); } glPopMatrix(); glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); glUseProgram(0); // active texture # must be reset before rendering UI stuff glActiveTexture(GL_TEXTURE0); // HUD video::ortho(); font->FaceSize(10); float height = font->LineHeight(); glColor3f(1, 1, 1); glTranslatef(0, video::height-height, 0); font->Render((boost::format("chunks: %d cache: %d gravity: %d flying: %d water: %d steps: %d trees: %d") % terrain->chunks.size() % terrain->tc->get_size() % gravity % flying % underwater % steps % trees.size()).str().c_str()); glTranslatef(0, -height, 0); font->Render(move_str.c_str()); glTranslatef(0, -height, 0); font->Render(selected.str().c_str()); if(tool) { glTranslatef(0, -height, 0); font->Render((boost::format("Tool: %s") % tool->get_name()).str().c_str()); } GUI::pre_render(); if(tool && dialog) { tool->gui_update(); } if(console->showing()) { console->update(); } GUI::render(); SDL_GL_SwapBuffers(); } GLuint Scene::load_texture(const char *filename) { GLuint texture; SDL_Surface *surface = IMG_Load(filename); SDL_Surface *image = SDL_CreateRGBSurface(SDL_SWSURFACE, surface->w, surface->h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); SDL_Rect area; area.x = area.y = 0; area.w = surface->w; area.h = surface->h; SDL_BlitSurface(surface, &area, image, &area); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if(image->format->Amask) { gluBuild2DMipmaps(GL_TEXTURE_2D, 4, image->w, image->h, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels); } else { gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image->w, image->h, GL_RGB, GL_UNSIGNED_BYTE, image->pixels); } SDL_FreeSurface(surface); SDL_FreeSurface(image); return texture; } GLuint Scene::load_texture(aiTexture *texture) { GLuint tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); if(texture->mHeight == 0) { SDL_RWops *rw = SDL_RWFromConstMem(texture->pcData, texture->mWidth); SDL_Surface *image = IMG_Load_RW(rw, 1); if(image->format->Amask) { gluBuild2DMipmaps(GL_TEXTURE_2D, 4, image->w, image->h, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels); } else { gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image->w, image->h, GL_RGB, GL_UNSIGNED_BYTE, image->pixels); } SDL_FreeSurface(image); } else { gluBuild2DMipmaps(GL_TEXTURE_2D, 4, texture->mWidth, texture->mHeight, GL_BGRA, GL_UNSIGNED_BYTE, texture->pcData); } return tex; } static bool playerlist_sort(Vector3 pos, Player::p a, Player::p b) { return (a->get_pos() - pos).length() > (b->get_pos() - pos).length(); } void Scene::sort_players() { players.sort(boost::bind(&playerlist_sort, pos, _1, _2)); }