1 //example skeleton code
2 //modified from http://learnopengl.com/
5 #include <OpenGL/gl3.h>
6 #include <OpenGL/gl3ext.h>
8 #include <GL/glew.h> // include GL Extension Wrangler
11 #define GLFW_INCLUDE_NONE // don't include deprecated gl headers on macOS
12 #include <GLFW/glfw3.h> // include GLFW helper library
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"
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
;
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)
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() {
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()) *
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.
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;
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
) {
157 case GLFW_KEY_GRAVE_ACCENT
:
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
;
175 case GLFW_KEY_ESCAPE
:
176 glfwSetInputMode(window
, GLFW_CURSOR
, GLFW_CURSOR_NORMAL
);
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
208 if (!was_mouse_captured
) {
209 // don't jerk the camera around if we're recapturing the mouse
213 was_mouse_captured
= true;
215 auto x_diff
= sensitivity
* (float)(xpos
- last_xpos
);
216 auto y_diff
= sensitivity
* (float)(last_ypos
- ypos
);
223 // set some vertical limits to avoid weird behavior
224 if (pitch
> max_pitch
) {
226 } else if (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
;
254 std::getline(std::cin
, input
);
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
);
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
;
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!
275 } catch (const std::exception
& e
) {
276 // invalid seed (invalid floats)
282 // The MAIN function, from here we start the application and run the game loop
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.
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",
313 if (!shader_program_ok
) {
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)));
322 Skybox
skybox(shader_program
);
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
332 move_state
= world
->pollWorld(getViewDirection(), up
, PLAYER_MOVEMENT_SPEED
);
336 pollContinuousControls(window
);
338 glm::vec3 follow_vector
= getFollowVector();
340 world
->setPlayerOpacity(
341 (glm::length(follow_vector
) - getPlayerScaleCoefficient() * 30.0f
) /
342 (getPlayerScaleCoefficient() * 50.0f
)
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
);
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));
358 world
->getMenu()->hide();
360 world
->getMenu()->unhide();
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(
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(
381 (GLfloat
)framebuffer_width
/ (GLfloat
)framebuffer_height
,
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
);
394 // Terminate GLFW, clearing any resources allocated by GLFW.