#define STB_IMAGE_IMPLEMENTATION #include "glew.h" #include "freeglut.h" #include "glm.hpp" #include "ext.hpp" #include #include #include #include #include "Shader_Loader.h" #include "Render_Utils.h" #include "Texture.h" #include "Camera.h" #include "SOIL/stb_image_aug.h" GLuint skyboxProgram, skyboxBuffer; GLuint bubbleProgram; GLuint programColor; GLuint programTexture; GLuint textureSubmarine; GLuint textureBubble; GLuint textureFish; unsigned int cubemapTexture, skyboxVAO; unsigned int cubeVAO, cubeVBO; float skyboxVerticeParameter = 50.0f; float skyboxBoundary = 48.0f; std::vector bubbleArray[300]; float old_x, old_y = -1; glm::vec3 cursorDiff; glm::vec3 lightDir = glm::normalize(glm::vec3(0.0f, skyboxVerticeParameter, 0.0f)); glm::vec3 cameraPos = glm::vec3(0, 0, 0); glm::vec3 oldCameraPos = glm::vec3(0, 0, 5); glm::vec3 cameraDir; // Wektor "do przodu" kamery glm::vec3 cameraSide; // Wektor "w bok" kamery float cameraAngle = 0; glm::quat rotation = glm::quat(1, 0, 0, 0); glm::mat4 cameraMatrix, perspectiveMatrix; Core::Shader_Loader shaderLoader; Core::RenderContext submarineContext; Core::RenderContext fishContext; Core::RenderContext bubbleContext; std::vector fishKeyPoints({ glm::vec3(-18.0f, -10.0f, -10.0f), glm::vec3(-10.0f, -5.0f, -12.0f), glm::vec3(8.0f, -3.0f, -3.0f), glm::vec3(5.0f, 0.0f, 3.0f), glm::vec3(3.0f, 2.0f, 4.0f), glm::vec3(8.0f, 5.0f, 9.0f), glm::vec3(14.0f, 6.0f, 15.0f), glm::vec3(15.0f, 12.0f, 12.0f), glm::vec3(10.0f, 17.0f, 15.0f), glm::vec3(5.0f, 10.0f, 7.0f), glm::vec3(-1.0f, 4.0f, 8.0f), glm::vec3(-8.0f, 0.0f, 3.0f), glm::vec3(-12.0f, -6.0f, -3.0f), glm::vec3(-15.0f, -8.0f, -6.0f), glm::vec3(-18.0f, -10.0f, -10.0f), }); std::vector keyRotation; std::vector fish; std::string skyboxTextures[6] = { "models/skybox/right.jpg", "models/skybox/left.jpg", "models/skybox/top.jpg", "models/skybox/bottom.jpg", "models/skybox/front.jpg", "models/skybox/back.jpg" }; float cubeVertices[] = { // positions // normals -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f }; float skyboxVertices[] = { -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter }; bool isInBoundaries(glm::vec3 nextPosition) { return nextPosition.z > -skyboxBoundary && nextPosition.z < skyboxBoundary&& nextPosition.y > -skyboxBoundary && nextPosition.y < skyboxBoundary&& nextPosition.x < skyboxBoundary&& nextPosition.x > -skyboxBoundary; } std::random_device rd; // obtain a random number from hardware std::mt19937 gen(rd()); // seed the generator std::uniform_int_distribution<> distr(-skyboxVerticeParameter, skyboxVerticeParameter); // define the range std::vector genBubbleKeyPoints() { float random1 = distr(gen); float random2 = distr(gen); std::vector bubbleKeyPoints({ glm::vec3(random1 , -skyboxVerticeParameter, random2), glm::vec3(random1 , skyboxVerticeParameter, random2) } ); return bubbleKeyPoints; }; void generateBubbleArray() { for (int i = 0; i < 300; i++) { bubbleArray[i] = genBubbleKeyPoints(); } } void keyboard(unsigned char key, int x, int y) { float angleSpeed = 10.f; float moveSpeed = 1.0f; glm::vec3 nextPosition; switch (key) { case 'z': cursorDiff.z -= angleSpeed; break; case 'x': cursorDiff.z += angleSpeed; break; case 'w': nextPosition = cameraPos + (cameraDir * moveSpeed); if (isInBoundaries(nextPosition)) { cameraPos = nextPosition; } break; case 's': nextPosition = cameraPos - (cameraDir * moveSpeed); if (isInBoundaries(nextPosition)) { cameraPos = nextPosition; } break; case 'd': nextPosition = cameraPos + (cameraSide * moveSpeed); if (isInBoundaries(nextPosition)) { cameraPos = nextPosition; } break; case 'a': nextPosition = cameraPos - (cameraSide * moveSpeed); if (isInBoundaries(nextPosition)) { cameraPos = nextPosition; } break; } } void mouse(int x, int y) { if (old_x >= 0) { cursorDiff.x = x - old_x; cursorDiff.y = y - old_y; } old_x = x; old_y = y; if (x < 100 || x > 800 - 100) { //you can use values other than 100 for the screen edges if you like, kind of seems to depend on your mouse sensitivity for what ends up working best old_x = 800 / 2; //centers the last known position, this way there isn't an odd jump with your cam as it resets old_y = 800 / 2; glutWarpPointer(800 / 2, 800 / 2); //centers the cursor } else if (y < 100 || y > 800 - 100) { old_x = 800 / 2; old_y = 800 / 2; glutWarpPointer(800 / 2, 800 / 2); } } glm::mat4 createCameraMatrix() { glm::quat rotation_x = glm::angleAxis(cursorDiff.y * 0.03f, glm::vec3(1, 0, 0)); cursorDiff.y = 0; glm::quat rotation_y = glm::angleAxis(cursorDiff.x * 0.03f, glm::vec3(0, 1, 0)); cursorDiff.x = 0; glm::quat rotation_z = glm::angleAxis(cursorDiff.z * 0.03f, glm::vec3(0, 0, 1)); cursorDiff.z = 0; glm::quat rotationChange = rotation_x * rotation_y * rotation_z; rotation = glm::normalize(rotationChange * rotation); cameraDir = glm::inverse(rotation) * glm::vec3(0, 0, -1); cameraSide = glm::inverse(rotation) * glm::vec3(1, 0, 0); return Core::createViewMatrixQuat(cameraPos, rotation); } std::vector changeKeyPoints(std::vector keyPoints, glm::vec3 toChange) { std::vector result; int size = keyPoints.size(); glm::vec3 change; for (int i = 0; i < size; i++) { change.x = keyPoints[i].x + toChange.x; change.y = keyPoints[i].y + toChange.y; change.z = keyPoints[i].z + toChange.z; result.push_back(change); } return result; } glm::mat4 animationMatrix(float time, glm::vec3 change, std::vector keyPoints, glm::vec3 scaleValue, float speed) { time = time * speed; std::vector distances; std::vector newKeyPoints = changeKeyPoints(keyPoints, change); float timeStep = 0; for (int i = 0; i < keyPoints.size() - 1; i++) { timeStep += (keyPoints[i] - keyPoints[i + 1]).length(); distances.push_back((keyPoints[i] - keyPoints[i + 1]).length()); } time = fmod(time, timeStep); //index of first keyPoint int index = 0; while (distances[index] <= time) { time = time - distances[index]; index += 1; } float t = time / distances[index]; int size = keyPoints.size(); int rotationSize = keyRotation.size(); glm::vec3 pos = glm::catmullRom(newKeyPoints[std::max(0, (index - 1) % size)], newKeyPoints[(index) % size], newKeyPoints[(index + 1) % size], newKeyPoints[(index + 2) % size], t); glm::quat divideByFour = glm::quat(0.25f, 0.25f, 0.25f, 0.25f); auto a1 = keyRotation[index % rotationSize] * glm::exp(-(glm::log(glm::inverse(keyRotation[index % rotationSize]) * keyRotation[std::max(0, (index - 1) % rotationSize)]) + glm::log(glm::inverse(keyRotation[index % rotationSize]) * keyRotation[(index + 1) % rotationSize])) * divideByFour); auto a2 = keyRotation[(index + 1) % rotationSize] * glm::exp(-(glm::log(glm::inverse(keyRotation[(index + 1) % rotationSize]) * keyRotation[index % rotationSize]) + glm::log(glm::inverse(keyRotation[(index + 1) % rotationSize]) * keyRotation[(index + 2) % rotationSize])) * divideByFour); auto animationRotation = glm::squad(keyRotation[index % rotationSize], keyRotation[(index + 1) % rotationSize], a1, a2, t); glm::mat4 result = glm::translate(pos) * glm::scale(glm::vec3(scaleValue)) * glm::mat4_cast(animationRotation); return result; } void drawObjectTexture(Core::RenderContext context, glm::mat4 modelMatrix, GLuint textureId, GLuint program) { glUseProgram(program); glUniform3f(glGetUniformLocation(program, "lightDir"), lightDir.x, lightDir.y, lightDir.z); Core::SetActiveTexture(textureId, "textureSampler", program, 0); glm::mat4 transformation = perspectiveMatrix * cameraMatrix * modelMatrix; glUniformMatrix4fv(glGetUniformLocation(program, "modelViewProjectionMatrix"), 1, GL_FALSE, (float*)&transformation); glUniformMatrix4fv(glGetUniformLocation(program, "modelMatrix"), 1, GL_FALSE, (float*)&modelMatrix); Core::DrawContext(context); glUseProgram(0); } void renderScene() { cameraMatrix = createCameraMatrix(); perspectiveMatrix = Core::createPerspectiveMatrix(); glClearColor(0.219f, 0.407f, 0.658f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); float time = glutGet(GLUT_ELAPSED_TIME) / 1000.f; glUseProgram(skyboxProgram); glUniform1i(glGetUniformLocation(skyboxProgram, "skybox"), 0); glm::mat4 transformation = perspectiveMatrix * cameraMatrix; glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "projectionViewMatrix"), 1, GL_FALSE, (float*)&transformation); glBindVertexArray(skyboxVAO); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); glDrawArrays(GL_TRIANGLES, 0, 36); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glm::mat4 submarineInitialTransformation = glm::translate(glm::vec3(0, -0.5, -0.4)) * glm::rotate(glm::radians(180.0f), glm::vec3(0, 1, 0)) * glm::scale(glm::vec3(0.25f)); glm::mat4 submarineModelMatrix = glm::translate(cameraPos + cameraDir) * glm::mat4_cast(glm::inverse(rotation)) * submarineInitialTransformation; glm::mat4 bubbleInitialTransformation = glm::translate(glm::vec3(0, -0.5, -0.4)) * glm::rotate(glm::radians(180.0f), glm::vec3(0, 1, 0)) * glm::scale(glm::vec3(0.5f)); glm::vec3 change1 = glm::vec3(0, 3, 0); glm::vec3 change2 = glm::vec3(0, 0, 0); glm::vec3 change3 = glm::vec3(3, 0, 0); glm::vec3 change4 = glm::vec3(0, 2, 1); glm::vec3 change0 = glm::vec3(0, 0, 0); for (int j = 0; j < 100; j++) { drawObjectTexture(bubbleContext, animationMatrix(time + j, change0, bubbleArray[j], glm::vec3(0.04f), 0.2f), cubemapTexture, bubbleProgram); } for (int i = 0; i < 5; i++) { if (time > -10) { drawObjectTexture(fishContext, animationMatrix(time + 15, change1, fishKeyPoints, glm::vec3(0.25f), 1.f), textureFish, programTexture); drawObjectTexture(fishContext, animationMatrix(time + 15, change2, fishKeyPoints, glm::vec3(0.25f), 1.f), textureFish, programTexture); drawObjectTexture(fishContext, animationMatrix(time + 15, change3, fishKeyPoints, glm::vec3(0.25f), 1.f), textureFish, programTexture); drawObjectTexture(fishContext, animationMatrix(time + 15, change4, fishKeyPoints, glm::vec3(0.25f), 1.f), textureFish, programTexture); time -= 6; } } //drawObjectTexture(bubbleContext, submarineInitialTransformation, cubemapTexture, bubbleProgram); drawObjectTexture(submarineContext, submarineModelMatrix, textureSubmarine, programTexture); glutSwapBuffers(); } void loadModelToContext(std::string path, Core::RenderContext& context) { Assimp::Importer import; const aiScene* scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_CalcTangentSpace); if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { std::cout << "ERROR::ASSIMP::" << import.GetErrorString() << std::endl; return; } context.initFromAssimpMesh(scene->mMeshes[0]); } unsigned int loadCubemap() { unsigned int textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); int width, height, nrChannels; for (unsigned int i = 0; i < 6; i++) { unsigned char* data = stbi_load(skyboxTextures[i].c_str(), &width, &height, &nrChannels, STBI_rgb_alpha); if (data) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); stbi_image_free(data); } else { std::cout << stbi_failure_reason() << std::endl; std::cout << "Cubemap tex failed to load at path: " << skyboxTextures[i] << std::endl; stbi_image_free(data); } } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); return textureID; } void initSkybox() { glGenVertexArrays(1, &skyboxVAO); glBindVertexArray(skyboxVAO); glGenBuffers(1, &skyboxBuffer); glBindBuffer(GL_ARRAY_BUFFER, skyboxBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW); GLuint vPosition = glGetAttribLocation(skyboxProgram, "aPos"); glEnableVertexAttribArray(vPosition); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(skyboxVertices), skyboxVertices); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); } void initKeyRotation() { glm::vec3 oldDirection = glm::vec3(0, 0, 1); glm::quat oldRotationCamera = glm::quat(1, 0, 0, 0); glm::vec3 direction; glm::quat rotation; for (int i = 0; i < fishKeyPoints.size() - 1; i++) { //3.1 direction = glm::normalize(fishKeyPoints[i + 1] - fishKeyPoints[i]); //3.2 rotation = glm::normalize(glm::rotationCamera(oldDirection, direction) * oldRotationCamera); //3.3 keyRotation.push_back(rotation); //3.4 oldDirection = direction; oldRotationCamera = rotation; } keyRotation.push_back(glm::quat(1, 0, 0, 0)); } void initCube() { glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &cubeVBO); glBindVertexArray(cubeVAO); glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); } void init() { glEnable(GL_DEPTH_TEST); programColor = shaderLoader.CreateProgram((char*)"shaders/shader_color.vert", (char*)"shaders/shader_color.frag"); programTexture = shaderLoader.CreateProgram((char*)"shaders/shader_tex.vert", (char*)"shaders/shader_tex.frag"); skyboxProgram = shaderLoader.CreateProgram((char*)"shaders/skybox.vert", (char*)"shaders/skybox.frag"); bubbleProgram = shaderLoader.CreateProgram((char*)"shaders/bubble.vert", (char*)"shaders/bubble.frag"); cubemapTexture = loadCubemap(); loadModelToContext("models/submarine.obj", submarineContext); textureSubmarine = Core::LoadTexture("textures/submarine.png"); loadModelToContext("models/fish.obj", fishContext); textureFish = Core::LoadTexture("textures/fish.png"); initKeyRotation(); loadModelToContext("models/submarine.obj", submarineContext); textureSubmarine = Core::LoadTexture("textures/submarine.png"); loadModelToContext("models/sphere.obj", bubbleContext); textureBubble = Core::LoadTexture("textures/bubble.png"); generateBubbleArray(); initCube(); initSkybox(); } void shutdown() { shaderLoader.DeleteProgram(programColor); shaderLoader.DeleteProgram(programTexture); shaderLoader.DeleteProgram(skyboxProgram); shaderLoader.DeleteProgram(bubbleProgram); } void idle() { glutPostRedisplay(); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); glutInitWindowPosition(200, 200); glutInitWindowSize(800, 800); glutCreateWindow("Water and shit"); glewInit(); init(); glutKeyboardFunc(keyboard); glutPassiveMotionFunc(mouse); glutDisplayFunc(renderScene); glutIdleFunc(idle); glutSetCursor(GLUT_CURSOR_NONE); glutMainLoop(); shutdown(); return 0; }