clone
[Comp371-Clone.git] / src / main.cpp
bloba14dc670cf6c34cd02b070f91fb0712de6b6bc24
1 //example skeleton code
2 //modified from http://learnopengl.com/
4 #ifdef __APPLE__
5 #include <OpenGL/gl3.h>
6 #include <OpenGL/gl3ext.h>
7 #else
8 #include <GL/glew.h> // include GL Extension Wrangler
9 #endif
11 #define GLFW_INCLUDE_NONE // don't include deprecated gl headers on macOS
12 #include <GLFW/glfw3.h> // include GLFW helper library
14 #include <iostream>
15 #include <algorithm>
16 #include <limits>
17 #include <cstdlib>
18 #include <ctime>
19 #include <chrono>
20 #include <glm/glm.hpp>
21 #include <glm/gtc/matrix_transform.hpp>
22 #include <glm/gtx/rotate_vector.hpp>
24 #include "glsetup.hpp" // include gl context setup function
25 #include "shaderprogram.hpp" // include the shader program compiler
26 #include "src/entities/Entity.hpp"
27 #include "src/entities/World.hpp"
28 #include "src/entities/Player.hpp"
29 #include "constants.hpp"
30 #include "TreeDistributor.hpp"
31 #include "src/entities/Skybox.hpp"
32 #include "utils.hpp"
34 /////
35 bool move_state;
36 //////
39 World* world;
41 // Camera constants
42 const glm::vec3 up = glm::normalize(glm::vec3(0.0f, 1.0f, 0.0f));
43 // pitch and yaw stored in degrees for clarity, but must be converted
44 // to radians to work well with glm
45 const float initial_pitch = -65.0f;
46 const float initial_yaw = -90.0f;
47 const float max_pitch = 89.0f;
48 const float min_pitch = -89.0f;
49 const float first_person_pitch = 20.0f;
50 const float max_follow_distance = 300.0f;
51 const float first_person_follow_distance = 0.01f;
53 //menu display
54 bool menu = true;
56 // Camera variables
57 float pitch = initial_pitch;
58 float yaw = initial_yaw;
60 // Projection constants
61 float min_fovy = 45.0f; // degrees
62 // up to 180 is "ok" but it looks really distorted and we can easily see tiles loading.
63 // 120 is a pretty safe maximum that allows a good field of view but preserves the
64 // "endless world" illusion reasonably well.
65 float max_fovy = 120.0f;
67 // Projection variables, to be set by framebufferSizeCallback
68 int framebuffer_width = 0;
69 int framebuffer_height = 0;
70 // Perspective field of view y angle to be set in scrollCallback (stored here in degrees)
71 float fovy = 60.0f;
73 float getPlayerScaleCoefficient()
75 glm::vec3 scale_vec = world->getPlayer()->getScale();
76 return (scale_vec.x + scale_vec.y + scale_vec.z) / 3.0f;
79 glm::vec3 getViewDirection() {
80 return glm::vec3(
81 (float)(cos(glm::radians(yaw)) * cos(glm::radians(pitch))),
82 (float)sin(glm::radians(pitch)),
83 (float)(sin(glm::radians(yaw)) * cos(glm::radians(pitch)))
87 glm::vec3 getFollowVector() {
88 return glm::normalize(getViewDirection()) *
89 max_follow_distance *
90 // scale follow distance according to player size so player always
91 // takes up same proportion of screen for a given viewing angle
92 getPlayerScaleCoefficient() *
93 // The lower the viewing angle, the shorter the follow distance -
94 // to accommodate for less space near terrain. At a specified high pitch,
95 // the third-person camera becomes first-person.
96 std::max(
97 first_person_follow_distance,
98 (1 - (pitch - min_pitch) / (first_person_pitch - min_pitch))
102 bool isKeyPressed(GLFWwindow* const& window, const int& key) {
103 return glfwGetKey(window, key) == GLFW_PRESS;
106 // controls that should be polled at every frame and read
107 // continuously / in combination
108 void pollContinuousControls(GLFWwindow* window) {
109 // delta time solution thanks to: https://gamedev.stackexchange.com/a/112094/109632
110 static auto last_time = std::chrono::steady_clock::now();
112 auto current_time = std::chrono::steady_clock::now();
113 float delta_time = std::chrono::duration<float>(current_time - last_time).count();
114 last_time = current_time;
116 bool up_press = isKeyPressed(window, GLFW_KEY_W) || isKeyPressed(window, GLFW_KEY_UP);
117 bool down_press = isKeyPressed(window, GLFW_KEY_S) || isKeyPressed(window, GLFW_KEY_DOWN);
118 bool left_press = isKeyPressed(window, GLFW_KEY_A) || isKeyPressed(window, GLFW_KEY_LEFT);
119 bool right_press = isKeyPressed(window, GLFW_KEY_D) || isKeyPressed(window, GLFW_KEY_RIGHT);
121 // ignore action canceling button presses
122 if (up_press && down_press) {
123 up_press = down_press = false;
125 if (left_press && right_press) {
126 left_press = right_press = false;
128 if(!move_state) {
129 float move_unit = PLAYER_MOVEMENT_SPEED * delta_time;
130 // first check compound then single movement button actions
131 if (up_press && left_press) {
132 world->movePlayerForwardLeft(getViewDirection(), up, move_unit);
133 } else if (up_press && right_press) {
134 world->movePlayerForwardRight(getViewDirection(), up, move_unit);
135 } else if (down_press && left_press) {
136 world->movePlayerBackLeft(getViewDirection(), up, move_unit);
137 } else if (down_press && right_press) {
138 world->movePlayerBackRight(getViewDirection(), up, move_unit);
139 } else if (up_press) {
140 world->movePlayerForward(getViewDirection(), up, move_unit);
141 } else if (down_press) {
142 world->movePlayerBack(getViewDirection(), up, move_unit);
143 } else if (left_press) {
144 world->movePlayerLeft(getViewDirection(), up, move_unit);
145 } else if (right_press) {
146 world->movePlayerRight(getViewDirection(), up, move_unit);
151 // Is called whenever a key is pressed/released via GLFW
152 void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
154 // ignore key release actions for now
155 if (action == GLFW_PRESS || action == GLFW_REPEAT) {
156 switch (key) {
157 case GLFW_KEY_GRAVE_ACCENT:
158 world->toggleAxes();
159 break;
160 case GLFW_KEY_0: {
161 // Print world seed based on player position
162 glm::vec3 player_position = world->getPlayer()->getPosition();
163 std::cout << "Seed for current world location: ";
164 std::cout << player_position.x << ':' << player_position.z << std::endl;
165 break;
167 case GLFW_KEY_M:
168 //toggle menu
169 if(menu){
170 menu = false;
171 }else{
172 menu = true;
174 break;
175 case GLFW_KEY_ESCAPE:
176 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
177 break;
178 default:
179 break;
184 void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
186 if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
187 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
191 void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
192 // Thanks to https://learnopengl.com/#!Getting-started/Camera for helping
193 // me think about camera movement!
195 static bool was_mouse_captured = glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
197 static double last_xpos = xpos;
198 static double last_ypos = ypos;
200 static float sensitivity = 0.2f;
202 bool is_mouse_captured = glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
203 if (!is_mouse_captured) {
204 was_mouse_captured = false;
205 // we don't want to handle camera movement if the mouse isn't captured
206 return;
208 if (!was_mouse_captured) {
209 // don't jerk the camera around if we're recapturing the mouse
210 last_xpos = xpos;
211 last_ypos = ypos;
213 was_mouse_captured = true;
215 auto x_diff = sensitivity * (float)(xpos - last_xpos);
216 auto y_diff = sensitivity * (float)(last_ypos - ypos);
217 last_xpos = xpos;
218 last_ypos = ypos;
220 yaw += x_diff;
221 pitch += y_diff;
223 // set some vertical limits to avoid weird behavior
224 if (pitch > max_pitch) {
225 pitch = max_pitch;
226 } else if (pitch < min_pitch) {
227 pitch = min_pitch;
231 void scrollCallback(GLFWwindow* window, double xoffset, double yoffset)
233 fovy = glm::clamp(float(fovy + yoffset * 5.0f), min_fovy, max_fovy);
236 void framebufferSizeCallback(GLFWwindow *window, int width, int height)
238 // Update the viewport dimensions
239 glViewport(0, 0, width, height);
241 // update projection matrix variables to maintain aspect ratio
242 framebuffer_width = width;
243 framebuffer_height = height;
246 void getWorldSeedFromUser(float* const& seed_x, float* const& seed_z)
248 // seed random number generator
249 srand((unsigned int)time(nullptr));
250 bool valid_seed = false;
251 while (!valid_seed) {
252 std::cout << "Enter a world seed (or press ENTER for a random seed):" << std::endl;
253 std::string input;
254 std::getline(std::cin, input);
255 if (input.empty()) {
256 // since the user didn't input anything we'll select a random starting
257 // position within a reasonably safe range
258 *seed_x = utils::randomFloat(-100.0f, 100.0f);
259 *seed_z = utils::randomFloat(-100.0f, 100.0f);
260 valid_seed = true;
261 continue;
263 std::size_t colon_pos = input.find(':');
264 if (colon_pos == std::string::npos || colon_pos + 1 == input.length()) {
265 std::cout << "invalid seed (no colon separator)" << std::endl;
266 continue;
268 try {
269 *seed_x = std::stof(input.substr(0, colon_pos));
270 *seed_z = std::stof(input.substr(colon_pos + 1));
271 // if parsing doesn't throw an exception we have a valid
272 // starting position!
273 valid_seed = true;
274 continue;
275 } catch (const std::exception& e) {
276 // invalid seed (invalid floats)
277 continue;
282 // The MAIN function, from here we start the application and run the game loop
283 int main()
285 float seed_x;
286 float seed_z;
287 getWorldSeedFromUser(&seed_x, &seed_z);
289 std::cout << "Generating a new world at x = " << seed_x << ", z = " << seed_z << "\n";
291 GLFWwindow* window = nullptr;
292 setupGlContext(WIDTH, HEIGHT, APP_NAME, &window);
294 // Set the required callback functions
295 glfwSetKeyCallback(window, keyCallback);
296 glfwSetMouseButtonCallback(window, mouseButtonCallback);
297 glfwSetCursorPosCallback(window, cursorPosCallback);
298 glfwSetScrollCallback(window, scrollCallback);
299 glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
301 // Set up viewport and projection matrix, which will be updated whenever the framebuffer resizes.
302 int width, height;
303 glfwGetFramebufferSize(window, &width, &height);
304 framebufferSizeCallback(window, width, height);
306 bool shader_program_ok;
308 GLuint shader_program = prepareShaderProgram(
309 "../shaders/vertex.glsl",
310 "../shaders/fragment.glsl",
311 &shader_program_ok
313 if (!shader_program_ok) {
314 return -1;
317 world = new World(shader_program, seed_x, seed_z);
319 //create light starting at 9am
320 Light light(glm::normalize(glm::vec3(-1, -1, 0)));
321 //create skybox
322 Skybox skybox(shader_program);
324 // Game loop
325 while (!glfwWindowShouldClose(window))
327 static glm::vec3 x_axis(1.0f, 0.0f, 0.0f);
328 static glm::vec3 y_axis(0.0f, 1.0f, 0.0f);
330 // Check if any events have been activated (key pressed, mouse moved etc.) and call corresponding response functions
331 /////////////
332 move_state = world->pollWorld(getViewDirection(), up, PLAYER_MOVEMENT_SPEED);
333 ////////////////
334 glfwPollEvents();
336 pollContinuousControls(window);
338 glm::vec3 follow_vector = getFollowVector();
340 world->setPlayerOpacity(
341 (glm::length(follow_vector) - getPlayerScaleCoefficient() * 30.0f) /
342 (getPlayerScaleCoefficient() * 50.0f)
345 // rotate the sun
346 light.daytime = glm::rotateZ(light.daytime, 0.0005f);
348 glm::vec3 player_position = world->getPlayer()->getPosition();
349 light.light_position = player_position;
350 skybox.setPosition(player_position);
352 light.setDaytime();
354 float daytime = -light.light_direction.y;
356 world->getMenu()->setPosition(glm::vec3(player_position.x -0.035, -0.93, player_position.z-0.05));
357 if(!menu){
358 world->getMenu()->hide();
359 }else{
360 world->getMenu()->unhide();
363 // Render
364 // Clear the colorbuffer
365 glClearColor(light.fog_color.r, light.fog_color.g, light.fog_color.b , 1.0f);
366 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
368 glm::mat4 view_matrix = glm::lookAt(player_position - follow_vector, player_position, up);
371 float player_scale = getPlayerScaleCoefficient();
372 glm::mat4 projection_matrix = glm::perspective(
373 glm::radians(fovy),
374 (GLfloat)framebuffer_width / (GLfloat)framebuffer_height,
375 15.0f * player_scale,
376 1500.0f * player_scale
379 glm::mat4 sky_projection_matrix = glm::perspective(
380 glm::radians(fovy),
381 (GLfloat)framebuffer_width / (GLfloat)framebuffer_height,
382 0.1f * player_scale,
383 1000000.0f * player_scale
385 skybox.draw(view_matrix, sky_projection_matrix, light);
387 world->draw(view_matrix, projection_matrix, light);
388 // Swap the screen buffers
389 glfwSwapBuffers(window);
392 delete world;
394 // Terminate GLFW, clearing any resources allocated by GLFW.
395 glfwTerminate();
396 return 0;