diff --git a/CMakeLists.txt b/CMakeLists.txt index 28787168..6d5400af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,12 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS True) # Tell CMake to ignore warnings from stuff in our dependencies. Surely GLM # knows what it is doing... https://7tv.app/emotes/61e8fae862858c6406126ced +IF (NOT WIN32) + set_property( + DIRECTORY build + PROPERTY COMPILE_OPTIONS "-w" + ) +ENDIF() # Add google test to CMake add_subdirectory(dependencies/google-test) @@ -66,4 +72,9 @@ add_custom_target(lint -i${CMAKE_SOURCE_DIR}/src/client/tests -i${CMAKE_SOURCE_DIR}/src/server/tests -i${CMAKE_SOURCE_DIR}/src/shared/tests -) \ No newline at end of file +) + +add_custom_target(pull_models + COMMAND + gdown 1N7a5cDgMcXbPO0RtgznnEo-1XUfdMScM -O ${CMAKE_SOURCE_DIR}/assets/graphics --folder +) diff --git a/README.md b/README.md index 77ac15af..ebda9bb7 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,10 @@ Depending on where you need to link the library (client, server, shared), you wi - [C++ Intellisense](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) - [General Productivity](https://marketplace.visualstudio.com/items?itemName=jirkavrba.subway-surfers) +## Models + +You can download models from our Google Drive folder [here](https://drive.google.com/drive/folders/1N7a5cDgMcXbPO0RtgznnEo-1XUfdMScM?usp=sharing) and place them in `src/client/models`. Alternatively, you can install [gdown](https://github.com/wkentaro/gdown) and run `make pull_models` to automatically pull them. + ## Documentation View deployed documentation [here](https://cse125.ucsd.edu/2024/cse125g3/site/docs/html/) diff --git a/assets/graphics/.gitignore b/assets/graphics/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/assets/graphics/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/dependencies/assimp/CMakeLists.txt b/dependencies/assimp/CMakeLists.txt new file mode 100644 index 00000000..13eb3a9c --- /dev/null +++ b/dependencies/assimp/CMakeLists.txt @@ -0,0 +1,15 @@ +include(FetchContent) + +FetchContent_Declare(assimp + GIT_REPOSITORY https://github.com/assimp/assimp + GIT_TAG v5.3.1 + GIT_PROGRESS TRUE +) + +FetchContent_MakeAvailable(assimp) + +set(ASSIMP_INCLUDE_DIRS + ${CMAKE_BINARY_DIR}/_deps/assimp-src/include/ + ${CMAKE_BINARY_DIR}/_deps/assimp-build/include/ + PARENT_SCOPE +) diff --git a/dependencies/glew/CMakeLists.txt b/dependencies/glew/CMakeLists.txt index 8a750c9b..3cf51284 100644 --- a/dependencies/glew/CMakeLists.txt +++ b/dependencies/glew/CMakeLists.txt @@ -8,6 +8,6 @@ FetchContent_Declare(glew FetchContent_MakeAvailable(glew) -# find_package(GLEW 2.0 REQUIRED) -# target_link_libraries(Starting GLEW::GLEW) - +set(GLEW_INCLUDE_DIRS + ${CMAKE_BINARY_DIR}/_deps/glew-src/include + PARENT_SCOPE) diff --git a/dependencies/stb/CMakeLists.txt b/dependencies/stb/CMakeLists.txt new file mode 100644 index 00000000..84bd7aba --- /dev/null +++ b/dependencies/stb/CMakeLists.txt @@ -0,0 +1,11 @@ +include(FetchContent) + +FetchContent_Declare(stb + GIT_REPOSITORY https://github.com/nothings/stb + GIT_PROGRESS TRUE +) +FetchContent_MakeAvailable(stb) + +set(STB_INCLUDE_DIRS + ${CMAKE_BINARY_DIR}/_deps/stb-src/ + PARENT_SCOPE) diff --git a/include/client/camera.hpp b/include/client/camera.hpp index 86f5340d..512e2ceb 100644 --- a/include/client/camera.hpp +++ b/include/client/camera.hpp @@ -30,6 +30,8 @@ class Camera { glm::mat4 getViewProj(); void updatePos(glm::vec3 pos); + glm::vec3 getPos(); + private: // Perspective controls float FOV; // Field of View Angle (degrees) diff --git a/include/client/client.hpp b/include/client/client.hpp index 3e2ecd54..306329d4 100644 --- a/include/client/client.hpp +++ b/include/client/client.hpp @@ -1,5 +1,3 @@ -#pragma once - #include "client/core.hpp" #include @@ -12,6 +10,9 @@ #include #include "client/cube.hpp" +#include "client/lightsource.hpp" +#include "client/shader.hpp" +#include "client/model.hpp" #include "client/util.hpp" #include "client/lobbyfinder.hpp" #include "client/camera.hpp" @@ -57,10 +58,17 @@ class Client { SharedGameState gameState; - float cubeMovementDelta = 0.05f; + std::shared_ptr cube_shader; + std::shared_ptr model_shader; + std::shared_ptr light_source_shader; + + std::unique_ptr player_model; + std::unique_ptr bear_model; + std::unique_ptr light_source; + + float playerMovementDelta = 0.05f; GLFWwindow *window; - GLuint cubeShaderProgram; Camera *cam; diff --git a/include/client/cube.hpp b/include/client/cube.hpp index 20ea8d55..58cf1574 100644 --- a/include/client/cube.hpp +++ b/include/client/cube.hpp @@ -1,6 +1,7 @@ #pragma once #include "client/core.hpp" +#include "client/renderable.hpp" #define GLM_ENABLE_EXPERIMENTAL #include @@ -11,23 +12,22 @@ #include #include -class Cube { +class Cube : public Renderable { public: - Cube(glm::vec3 newColor, glm::vec3 scale); + explicit Cube(glm::vec3 newColor); ~Cube(); - void draw(glm::mat4 viewProjMat, GLuint shader, bool fill); - void update(glm::vec3 new_pos); - void update_delta(glm::vec3 delta); - + void draw(std::shared_ptr shader, + glm::mat4 viewProj, + glm::vec3 camPos, + glm::vec3 lightPos, + bool fill) override; private: GLuint VAO; GLuint VBO_positions, VBO_normals, EBO; - glm::mat4 model; glm::vec3 color; - // Cube Information std::vector positions; std::vector normals; diff --git a/include/client/lightsource.hpp b/include/client/lightsource.hpp new file mode 100644 index 00000000..a7256107 --- /dev/null +++ b/include/client/lightsource.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "client/core.hpp" +#include "client/shader.hpp" + +#define GLM_ENABLE_EXPERIMENTAL +#include +#include +#include + +#include +#include +#include +#include + +class LightSource { +public: + LightSource(); + void draw(std::shared_ptr shader, glm::mat4 viewProj); + void TranslateTo(const glm::vec3& new_pos); + + glm::vec3 lightPos; +private: + GLuint VAO, VBO; + + glm::mat4 model; + glm::vec3 color; + + // Cube Information + std::vector positions; + std::vector normals; + std::vector indices; + +}; diff --git a/include/client/model.hpp b/include/client/model.hpp new file mode 100644 index 00000000..27dcb9a5 --- /dev/null +++ b/include/client/model.hpp @@ -0,0 +1,172 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "assimp/material.h" +#include "client/renderable.hpp" +#include "client/shader.hpp" + +/** + * Stores position, normal vector, and coordinates + * in texture map for every vertex in a mesh. + */ +struct Vertex { + glm::vec3 position; + glm::vec3 normal; + glm::vec2 textureCoords; +}; + +class Texture { + public: + /** + * Load a texture from a filepath. + * + * @param filepath is the file path to the texture + * @param type specifies the type of texture to load. + * Currently only aiTextureType_SPECULAR and aiTextureType_DIFFUSE + * are implemented. + */ + Texture(const std::string& filepath, const aiTextureType& type); + + /** + * @return the texture's ID to be passed into OpenGL functions + */ + GLuint getID() const; + + /* + * Get the type of texture. Either "texture_diffuse" or "texture_specular". + */ + std::string getType() const; + private: + GLuint ID; + std::string type; +}; + +struct Material { + glm::vec3 ambient; + glm::vec3 diffuse; + glm::vec3 specular; + float shininess; +}; + +/** + * Mesh holds the data needed to render a mesh (collection of triangles). + * + * A Mesh differs from a Model since a Model is typically made up of multiple, + * smaller meshes. This is useful for animating parts of model individual (ex: legs, + * arms, head) + */ +class Mesh : public Renderable { + public: + /** + * Creates a new mesh from a collection of vertices, indices and textures + */ + Mesh( + const std::vector& vertices, + const std::vector& indices, + const std::vector& textures, + const Material& material + ); + + /** + * Render the Mesh to the viewport using the provided shader program. + * + * @param shader to use when rendering the program. Determines position of + * vertices and their color/texture. + * @param modelView determines the scaling/rotation/translation of the + * mesh + */ + void draw(std::shared_ptr shader, + glm::mat4 viewProj, + glm::vec3 camPos, + glm::vec3 lightPos, + bool fill) override; + private: + std::vector vertices; + std::vector indices; + std::vector textures; + Material material; + + // render data opengl needs + GLuint VAO, VBO, EBO; +}; + + +class Model : public Renderable { + public: + /** + * Loads Model from a given filename. Can be of format + * .obj, .blend or any of the formats that assimp supports + * @see https://assimp-docs.readthedocs.io/en/latest/about/introduction.html?highlight=obj#introduction + * + * @param Filepath to model file. + */ + explicit Model(const std::string& filepath); + + /** + * Draws all the meshes of a given model + * + * @param Shader to use while drawing all the + * meshes of the model + */ + void draw(std::shared_ptr shader, + glm::mat4 viewProj, + glm::vec3 camPos, + glm::vec3 lightPos, + bool fill) override; + + /** + * Sets the position of the Model to the given x,y,z + * values + * + * @param vector of x, y, z of the model's new position + */ + void translateAbsolute(const glm::vec3& new_pos) override; + + /** + * Updates the position of the Model relative to it's + * previous position + * + * @param vector of x, y, z of the change in the Model's + * position + */ + void translateRelative(const glm::vec3& delta) override; + + /** + * Scale the Model across all axes (x,y,z) + * by a factor + * + * @param new_factor describes how much to scale the model by. + * Ex: setting it to 0.5 will cut the model's rendered size + * in half. + */ + void scale(const float& new_factor) override; + + /** + * Scale the model across all axes (x,y,z) + * by the scale factor in each axis. + * + * @param the scale vector describes how much to independently scale + * the model in each axis (x, y, z) + */ + void scale(const glm::vec3& scale) override; + private: + std::vector meshes; + + void processNode(aiNode* node, const aiScene* scene); + Mesh processMesh(aiMesh* mesh, const aiScene* scene); + std::vector loadMaterialTextures(aiMaterial* mat, const aiTextureType& type); + + // store the directory of the model file so that textures can be + // loaded relative to the model file + std::string directory; +}; diff --git a/include/client/renderable.hpp b/include/client/renderable.hpp new file mode 100644 index 00000000..a06efa05 --- /dev/null +++ b/include/client/renderable.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include + +#include "client/shader.hpp" + +class Renderable { + public: + Renderable(); + /** + * Draws the renderable item + * + * @param Shader to use while drawing all the + * meshes of the model + * @param + */ + virtual void draw(std::shared_ptr shader, + glm::mat4 viewProj, + glm::vec3 camPos, + glm::vec3 lightPos, + bool fill) = 0; + + /** + * Sets the position of the item to the given x,y,z + * values + * + * @param vector of x, y, z of the 's new position + */ + virtual void translateAbsolute(const glm::vec3& new_pos); + + /** + * Updates the position of the item relative to it's + * previous position + * + * @param vector of x, y, z of the change in the item's + * position + */ + virtual void translateRelative(const glm::vec3& delta); + + /** + * Scale the Model across all axes (x,y,z) + * by a factor + * + * @param new_factor describes how much to scale the model by. + * Ex: setting it to 0.5 will cut the model's rendered size + * in half. + */ + virtual void scale(const float& new_factor); + + /** + * Scale the item across all axes (x,y,z) + * by the scale factor in each axis. + * + * @param the scale vector describes how much to independently scale + * the item in each axis (x, y, z) + */ + virtual void scale(const glm::vec3& scale); + + /** + * Gets the model matrix given all the transformations + * applied to it + * + * @return updated model matrix + */ + glm::mat4 getModelMat(); + private: + glm::mat4 model; +}; diff --git a/include/client/shader.hpp b/include/client/shader.hpp index 2c5d1d46..17284347 100644 --- a/include/client/shader.hpp +++ b/include/client/shader.hpp @@ -1,5 +1,65 @@ #pragma once +#include +#include +#include +#include +#include +#include + -GLuint loadCubeShaders(); +class Shader { + public: + /** + * Create a shader program from filepaths to a vertex and fragment shader. + * Use getID() to access the shader program's ID. + */ + Shader(const std::string& vertexPath, const std::string& fragmentPath); + ~Shader(); + + /* + * @return the shader program's ID which can be passed into OpenGL functions + */ + GLuint getID(); + + /* + * Activate the shader program. Must be called before drawing. + * Calls glUseProgram under the hood. + */ + void use(); + + /* + * Sets a boolean unform variable of the shader program + * with the specified value + * @param name is the name of the uniform variable as written + * in the shader program + * @param value is the boolean value to write to that variable + */ + void setBool(const std::string &name, bool value) const; + + /* + * Sets an integer unform variable of the shader program + * with the specified value + * @param name is the name of the uniform variable as written + * in the shader program + * @param value is the integer value to write to that variable + */ + void setInt(const std::string &name, int value) const; + + /* + * Sets a float unform variable of the shader program + * with the specified value + * @param name is the name of the uniform variable as written + * in the shader program + * @param value is the float value to write to that variable + */ + void setFloat(const std::string &name, float value) const; + void setMat4(const std::string &name, glm::mat4& value); + void setVec3(const std::string &name, glm::vec3& value); + + glm::vec3 getVec3(const std::string &name); + private: + // the shader program ID + unsigned int ID; +}; diff --git a/include/client/util.hpp b/include/client/util.hpp index e9c4ffaa..1206e4fc 100644 --- a/include/client/util.hpp +++ b/include/client/util.hpp @@ -1,19 +1,7 @@ #pragma once -#include +#include "assimp/types.h" +#include -#include -#include -#include -#include -#include +glm::vec3 aiColorToGLM(const aiColor3D& color); -// #include - -// #include -// #include - -#include "client/core.hpp" - - -GLuint LoadShaders(const std::string& vertex_file_path, const std::string& fragment_file_path); diff --git a/include/server/game/servergamestate.hpp b/include/server/game/servergamestate.hpp index 0a7a8bdb..168019ff 100644 --- a/include/server/game/servergamestate.hpp +++ b/include/server/game/servergamestate.hpp @@ -48,7 +48,7 @@ class ServerGameState { */ explicit ServerGameState(GamePhase start_phase); - ServerGameState(GamePhase start_phase, GameConfig config); + ServerGameState(GamePhase start_phase, const GameConfig& config); /** * @brief This is the ONLY constructor that initializes the maze from a diff --git a/include/shared/game/sharedgamestate.hpp b/include/shared/game/sharedgamestate.hpp index d59f972a..60cb5156 100644 --- a/include/shared/game/sharedgamestate.hpp +++ b/include/shared/game/sharedgamestate.hpp @@ -64,7 +64,7 @@ struct SharedGameState { this->lobby.max_players = MAX_PLAYERS; } - SharedGameState(GamePhase start_phase, GameConfig config): + SharedGameState(GamePhase start_phase, const GameConfig& config): objects(std::vector>()) { this->phase = start_phase; diff --git a/shell.nix b/shell.nix index 3d6f66bb..be17b5a5 100644 --- a/shell.nix +++ b/shell.nix @@ -9,6 +9,7 @@ mkShell { gnumake gcc13 gdb + zlib wayland wayland-scanner @@ -34,6 +35,8 @@ mkShell { doxygen clang-tools_14 cppcheck + + python310Packages.gdown ]; nativeBuildInputs = with pkgs; [ pkg-config diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt index 5ba10bae..6fe0794a 100644 --- a/src/client/CMakeLists.txt +++ b/src/client/CMakeLists.txt @@ -8,7 +8,10 @@ set(FILES cube.cpp util.cpp lobbyfinder.cpp - shaders.cpp + shader.cpp + model.cpp + lightsource.cpp + renderable.cpp ${imgui-source} ) @@ -22,11 +25,23 @@ add_subdirectory(../../dependencies/glfw ${CMAKE_BINARY_DIR}/glfw) add_subdirectory(../../dependencies/glm ${CMAKE_BINARY_DIR}/glm) add_subdirectory(../../dependencies/imgui ${CMAKE_BINARY_DIR}/imgui) add_subdirectory(../../dependencies/glew ${CMAKE_BINARY_DIR}/glew) +add_subdirectory(../../dependencies/assimp ${CMAKE_BINARY_DIR}/assimp) +add_subdirectory(../../dependencies/stb ${CMAKE_BINARY_DIR}/stb) add_subdirectory(../../dependencies/sfml ${CMAKE_BINARY_DIR}/sfml) add_library(${LIB_NAME} STATIC ${FILES}) target_include_directories(${LIB_NAME} PRIVATE ${INCLUDE_DIRECTORY}) -target_include_directories(${LIB_NAME} PRIVATE ${BOOST_LIBRARY_INCLUDES}) +target_include_directories(${LIB_NAME} + PRIVATE + ${BOOST_LIBRARY_INCLUDES} + ${OPENGL_INCLUDE_DIRS} + glfw + glm + ${imgui-directory} + ${GLEW_INCLUDE_DIRS} + ${ASSIMP_INCLUDE_DIRS} + ${STB_INCLUDE_DIRS} +) target_link_libraries(${LIB_NAME} PRIVATE game_shared_lib @@ -36,26 +51,38 @@ target_link_libraries(${LIB_NAME} Boost::program_options Boost::serialization nlohmann_json::nlohmann_json + glm + glfw + libglew_static + assimp ) -target_include_directories(${LIB_NAME} PRIVATE ${OPENGL_INCLUDE_DIRS} glfw glm ${imgui-directory} "${CMAKE_BINARY_DIR}/_deps/glew-src/include") -target_link_libraries(${LIB_NAME} PRIVATE glm glfw libglew_static) add_executable(${TARGET_NAME} main.cpp) - target_include_directories(${TARGET_NAME} PRIVATE ${INCLUDE_DIRECTORY}) -target_include_directories(${TARGET_NAME} PRIVATE ${OPENGL_INCLUDE_DIRS} glfw glm ${imgui-directory} "${CMAKE_BINARY_DIR}/_deps/glew-src/include") +target_include_directories(${TARGET_NAME} + PRIVATE + ${BOOST_LIBRARY_INCLUDES} + ${OPENGL_INCLUDE_DIRS} + glfw + glm + ${imgui-directory} + ${GLEW_INCLUDE_DIRS} + ${ASSIMP_INCLUDE_DIRS} + ${STB_INCLUDE_DIRS} +) target_link_libraries(${TARGET_NAME} PRIVATE game_shared_lib ${LIB_NAME}) -target_link_libraries(${TARGET_NAME} PRIVATE glm glfw libglew_static) - -target_include_directories(${TARGET_NAME} PRIVATE ${BOOST_LIBRARY_INCLUDES}) -target_link_libraries(${TARGET_NAME} - PRIVATE +target_link_libraries(${TARGET_NAME} + PRIVATE Boost::asio Boost::filesystem Boost::thread Boost::program_options Boost::serialization nlohmann_json::nlohmann_json + glm + glfw + libglew_static + assimp ) target_link_libraries(${TARGET_NAME} PRIVATE sfml-audio) diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 7416ea13..30f6bc26 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -33,6 +33,10 @@ glm::mat4 Camera::getViewProj() { return viewProjMat; } +glm::vec3 Camera::getPos() { + return cameraPos; +} + void Camera::update(float xpos, float ypos) { if (firstMouse) diff --git a/src/client/client.cpp b/src/client/client.cpp index 0dbbba62..7984e8c5 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1,5 +1,4 @@ #include "client/client.hpp" - #include #include @@ -9,12 +8,18 @@ #include #include +#include "client/lightsource.hpp" #include "client/shader.hpp" +#include "client/model.hpp" +#include "glm/fwd.hpp" +#include "server/game/solidsurface.hpp" #include "shared/game/event.hpp" +#include "shared/game/sharedobject.hpp" #include "shared/network/constants.hpp" #include "shared/network/packet.hpp" #include "shared/utilities/config.hpp" + using namespace boost::asio::ip; using namespace std::chrono_literals; @@ -75,14 +80,19 @@ Client::~Client() { // TODO: error flags / output for broken init bool Client::init() { /* Initialize glfw library */ - if (!glfwInit()) + if (!glfwInit()) { + const char* glfwErrorDesc = NULL; + glfwGetError(&glfwErrorDesc); + std::cout << "glfw init fails" << glfwErrorDesc << std::endl; return false; + } /* Create a windowed mode window and its OpenGL context */ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL); + window = glfwCreateWindow(640, 480, "Arcana", NULL, NULL); if (!window) { + std::cout << "could not create window" << std::endl; glfwTerminate(); return false; } @@ -102,17 +112,35 @@ bool Client::init() { std::cout << "shader version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl; std::cout << "shader version: " << glGetString(GL_VERSION) << std::endl; - this->cubeShaderProgram = loadCubeShaders(); - if (!this->cubeShaderProgram) { - std::cout << "Failed to load cube shader files" << std::endl; - return false; - } + boost::filesystem::path shaders_dir = this->root_path / "src/client/shaders"; + boost::filesystem::path graphics_assets_dir = this->root_path / "assets/graphics"; + + boost::filesystem::path cube_vert_path = shaders_dir / "cube.vert"; + boost::filesystem::path cube_frag_path = shaders_dir / "cube.frag"; + this->cube_shader = std::make_shared(cube_vert_path.string(), cube_frag_path.string()); + + boost::filesystem::path model_vert_path = shaders_dir / "model.vert"; + boost::filesystem::path model_frag_path = shaders_dir / "model.frag"; + this->model_shader = std::make_shared(model_vert_path.string(), model_frag_path.string()); + + boost::filesystem::path bear_model_path = graphics_assets_dir / "bear-sp22.obj"; + this->bear_model = std::make_unique(bear_model_path.string()); + this->bear_model->scale(0.25); + + boost::filesystem::path player_model_path = graphics_assets_dir / "Fire-testing.obj"; + this->player_model = std::make_unique(player_model_path.string()); + this->player_model->scale(0.25); + + this->light_source = std::make_unique(); + + boost::filesystem::path lightVertFilepath = this->root_path / "src/client/shaders/lightsource.vert"; + boost::filesystem::path lightFragFilepath = this->root_path / "src/client/shaders/lightsource.frag"; + this->light_source_shader = std::make_shared(lightVertFilepath.string(), lightFragFilepath.string()); return true; } bool Client::cleanup() { - glDeleteProgram(this->cubeShaderProgram); return true; } @@ -221,33 +249,76 @@ void Client::draw() { for (int i = 0; i < this->gameState.objects.size(); i++) { std::shared_ptr sharedObject = this->gameState.objects.at(i); - if (sharedObject == nullptr) + if (sharedObject == nullptr) { continue; + } std::cout << "shared object " << i << ": position: " << glm::to_string(sharedObject->physics.position) << std::endl; // Get camera position from server, update position and don't render player object (or special handling) if (this->session->getInfo().client_eid.has_value() && sharedObject->globalID == this->session->getInfo().client_eid.value()) { - cam->updatePos(sharedObject->physics.position); - continue; } - // If solidsurface, scale cube to given dimensions - if(sharedObject->solidSurface.has_value()){ - Cube* cube = new Cube(glm::vec3(0.4f,0.5f,0.7f), sharedObject->solidSurface->dimensions); - cube->update(sharedObject->physics.position); - cube->draw(this->cam->getViewProj(), this->cubeShaderProgram, true); - continue; + switch (sharedObject->type) { + case ObjectType::Player: { + // don't render yourself + if (this->session->getInfo().client_eid.has_value() && sharedObject->globalID == this->session->getInfo().client_eid.value()) { + cam->updatePos(sharedObject->physics.position); + break; + } + auto lightPos = glm::vec3(-5.0f, 0.0f, 0.0f); + // subtracting 1 from y position to render players "standing" on ground + auto player_pos = glm::vec3(sharedObject->physics.position.x, sharedObject->physics.position.y - 1.0f, sharedObject->physics.position.z); + + this->player_model->translateAbsolute(player_pos); + this->player_model->draw( + this->model_shader, + this->cam->getViewProj(), + this->cam->getPos(), + lightPos, + true); + break; + } + case ObjectType::Enemy: { + // warren bear is an enemy because why not + // auto pos = glm::vec3(0.0f, 0.0f, 0.0f); + auto lightPos = glm::vec3(-5.0f, 0.0f, 0.0f); + this->bear_model->translateAbsolute(sharedObject->physics.position); + this->bear_model->draw( + this->model_shader, + this->cam->getViewProj(), + this->cam->getPos(), + lightPos, + true); + + /* this->light_source->TranslateTo(lightPos); + this->light_source->draw( + this->light_source_shader, + this->cam->getViewProj());*/ + + // Cube* cube = new Cube(glm::vec3(0.4f,0.5f,0.7f)); + // cube->translateAbsolute(lightPos); + // cube->draw(this->cube_shader, + // this->cam->getViewProj(), + // this->cam->getPos(), + // glm::vec3(), + // false); + break; + } + case ObjectType::SolidSurface: { + Cube* cube = new Cube(glm::vec3(0.4f,0.5f,0.7f)); + cube->scale( sharedObject->solidSurface->dimensions); + cube->translateAbsolute(sharedObject->physics.position); + cube->draw(this->cube_shader, + this->cam->getViewProj(), + this->cam->getPos(), + glm::vec3(), + true); + break; + } + default: + break; } - - // tmp: all objects are cubes - Cube* cube = new Cube(glm::vec3(0.0f,1.0f,1.0f), glm::vec3(1.0f)); - cube->update(glm::vec3(0.0f)); - cube->draw(this->cam->getViewProj(), this->cubeShaderProgram, false); - - Cube* origin = new Cube(glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.05f, 0.05f, 0.05f)); - origin->update(glm::vec3(0.0f)); - origin->draw(this->cam->getViewProj(), this->cubeShaderProgram, true); } } diff --git a/src/client/cube.cpp b/src/client/cube.cpp index 4a0438d8..163ef89e 100644 --- a/src/client/cube.cpp +++ b/src/client/cube.cpp @@ -1,16 +1,11 @@ #include "client/cube.hpp" -Cube::Cube(glm::vec3 newColor, glm::vec3 scale) { +Cube::Cube(glm::vec3 newColor) { // create a vertex buffer for positions and normals // insert the data into these buffers // initialize model matrix - // Model matrix. - glm::vec3 cubeMin = glm::vec3(-0.5f, -0.5f, -0.5f); - glm::vec3 cubeMax = glm::vec3(0.5f, 0.5f, 0.5f); - model = glm::mat4(1.0f); - - //scale the cube to with given vector - model = glm::scale(model, scale); + glm::vec3 cubeMin = glm::vec3(-1.0f, -1.0f, -1.0f); + glm::vec3 cubeMax = glm::vec3(1.0f, 1.0f, 1.0f); // The color of the cube. Try setting it to something else! color = newColor; @@ -139,15 +134,20 @@ Cube::~Cube() { glDeleteVertexArrays(1, &VAO); } -void Cube::draw(glm::mat4 viewProjMat, GLuint shader, bool fill) { - // actiavte the shader program - glUseProgram(shader); +void Cube::draw(std::shared_ptr shader, + glm::mat4 viewProj, + glm::vec3 camPos, + glm::vec3 lightPos, + bool fill) { + // actiavte the shader program + shader->use(); // get the locations and send the uniforms to the shader - glUniformMatrix4fv(glGetUniformLocation(shader, "viewProj"), 1, false, reinterpret_cast(&viewProjMat)); - glUniformMatrix4fv(glGetUniformLocation(shader, "model"), 1, GL_FALSE, reinterpret_cast(&model)); - glUniform3fv(glGetUniformLocation(shader, "DiffuseColor"), 1, &color[0]); + shader->setMat4("viewProj", viewProj); + auto model = this->getModelMat(); + shader->setMat4("model", model); + shader->setVec3("DiffuseColor", color); // Bind the VAO glBindVertexArray(VAO); @@ -166,11 +166,3 @@ void Cube::draw(glm::mat4 viewProjMat, GLuint shader, bool fill) { glBindVertexArray(0); glUseProgram(0); } - -void Cube::update(glm::vec3 new_pos) { - model[3] = glm::vec4(new_pos, 1.0f); -} - -void Cube::update_delta(glm::vec3 delta) { - model = glm::translate(model, delta); -} \ No newline at end of file diff --git a/src/client/lightsource.cpp b/src/client/lightsource.cpp new file mode 100644 index 00000000..363959d8 --- /dev/null +++ b/src/client/lightsource.cpp @@ -0,0 +1,37 @@ +#include "client/lightsource.hpp" + +#include + +#include + +#include "client/shader.hpp" + +LightSource::LightSource() : model(1.0f) { + glGenVertexArrays(1, &VAO); + glBindVertexArray(VAO); + // we only need to bind to the VBO, the container's VBO's data already contains the data. + glBindBuffer(GL_ARRAY_BUFFER, VBO); + // set the vertex attribute + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); +} + +void LightSource::draw(std::shared_ptr shader, + glm::mat4 viewProj) { + shader->use(); + + // get the locations and send the uniforms to the shader + shader->setMat4("viewProj", viewProj); + shader->setMat4("model", model); + + // draw the light cube object + glBindVertexArray(VAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + + glBindVertexArray(0); + glUseProgram(0); +} + +void LightSource::TranslateTo(const glm::vec3 &new_pos) { + model[3] = glm::vec4(new_pos, 1.0f); +} diff --git a/src/client/main.cpp b/src/client/main.cpp index e495f0ac..9b9dc9f1 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -67,6 +67,7 @@ int main(int argc, char* argv[]) } if (!client.init()) { + std::cout << "client init failed" << std::endl; exit(EXIT_FAILURE); } diff --git a/src/client/model.cpp b/src/client/model.cpp new file mode 100644 index 00000000..38f7200c --- /dev/null +++ b/src/client/model.cpp @@ -0,0 +1,343 @@ +#include "client/model.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "assimp/material.h" +#include "assimp/types.h" +#include "client/util.hpp" +#include "glm/ext/matrix_transform.hpp" +#include +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#include + +#define GLM_ENABLE_EXPERIMENTAL +#include "glm/ext/matrix_clip_space.hpp" +#include "glm/fwd.hpp" +#include +#include +#include +#include + + +Mesh::Mesh( + const std::vector& vertices, + const std::vector& indices, + const std::vector& textures, + const Material& material) : + vertices(vertices), indices(indices), textures(textures), material(material) { + + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &EBO); + + glBindVertexArray(VAO); + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW); + + // vertex positions + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(0)); + + // vertex normals + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, normal))); + + // vertex texture coords + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, textureCoords))); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + std::cout << "Loaded mesh with " << vertices.size() << " vertices, and " << textures.size() << " textures" << std::endl; + std::cout << "\t diffuse " << glm::to_string(this->material.diffuse) << std::endl; + std::cout << "\t ambient " << glm::to_string(this->material.diffuse) << std::endl; + std::cout << "\t specular " << glm::to_string(this->material.specular) << std::endl; + std::cout << "\t shininess" << this->material.shininess << std::endl; +} + +void Mesh::draw( + std::shared_ptr shader, + glm::mat4 viewProj, + glm::vec3 camPos, + glm::vec3 lightPos, + bool fill) { + // actiavte the shader program + shader->use(); + + // vertex shader uniforms + shader->setMat4("viewProj", viewProj); + auto model = this->getModelMat(); + shader->setMat4("model", model); + + // fragment shader uniforms + shader->setVec3("material.diffuse", this->material.diffuse); + shader->setVec3("material.ambient", this->material.ambient); + shader->setVec3("material.specular", this->material.specular); + shader->setFloat("material.shininess", this->material.shininess); + shader->setVec3("viewPos", camPos); + auto lightColor = glm::vec3(1.0f, 1.0f, 1.0f); + shader->setVec3("lightColor", lightColor); + shader->setVec3("lightPos", lightPos); + + if (textures.size() == 0) { + } else { + unsigned int diffuseNr = 1; + unsigned int specularNr = 1; + for(unsigned int i = 0; i < textures.size(); i++) { + glActiveTexture(GL_TEXTURE0 + i); // activate proper texture unit before binding + // retrieve texture number (the N in diffuse_textureN) + std::string number; + std::string name = textures[i].getType(); + if(name == "texture_diffuse") + number = std::to_string(diffuseNr++); + else if(name == "texture_specular") + number = std::to_string(specularNr++); + + std::string shaderTextureName = "material." + name + number; + shader->setInt(shaderTextureName, i); + glBindTexture(GL_TEXTURE_2D, textures[i].getID()); + } + glActiveTexture(GL_TEXTURE0); + } + + // draw mesh + glBindVertexArray(VAO); + if(fill){ + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } else { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0); + glBindVertexArray(0); + glUseProgram(0); +} + +Model::Model(const std::string& filepath) { + this->directory = std::filesystem::path(filepath).parent_path().string(); + + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(filepath, aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_SplitLargeMeshes | aiProcess_OptimizeMeshes); + if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { + throw std::invalid_argument(std::string("ERROR::ASSIMP::") + importer.GetErrorString()); + } + + processNode(scene->mRootNode, scene); +} + +void Model::draw(std::shared_ptr shader, + glm::mat4 viewProj, + glm::vec3 camPos, + glm::vec3 lightPos, + bool fill) { + + for(Mesh& mesh : this->meshes) { + mesh.draw(shader, viewProj, camPos, lightPos, fill); + } +} + +void Model::translateAbsolute(const glm::vec3& new_pos) { + for(Mesh& mesh : this->meshes) { + mesh.translateAbsolute(new_pos); + } +} + +void Model::translateRelative(const glm::vec3& delta) { + for(Mesh& mesh : this->meshes) { + mesh.translateAbsolute(delta); + } +} + +void Model::scale(const float& new_factor) { + for(Mesh& mesh : this->meshes) { + mesh.scale(new_factor); + } +} + +void Model::scale(const glm::vec3& scale) { + for(Mesh& mesh : this->meshes) { + mesh.scale(scale); + } +} + +void Model::processNode(aiNode *node, const aiScene *scene) { + // process all the node's meshes (if any) + for(unsigned int i = 0; i < node->mNumMeshes; i++) { + aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; + meshes.push_back(processMesh(mesh, scene)); + } + // then do the same for each of its children + for(unsigned int i = 0; i < node->mNumChildren; i++) { + processNode(node->mChildren[i], scene); + } +} + +Mesh Model::processMesh(aiMesh *mesh, const aiScene *scene) { + std::vector vertices; + std::vector indices; + std::vector textures; + + // process vertex positions, normals and texture coordinates + for(unsigned int i = 0; i < mesh->mNumVertices; i++) { + glm::vec3 position( + mesh->mVertices[i].x, + mesh->mVertices[i].y, + mesh->mVertices[i].z); + glm::vec3 normal( + mesh->mNormals[i].x, + mesh->mNormals[i].y, + mesh->mNormals[i].z); + + // check if the mesh contain texture coordinates + glm::vec2 texture(0.0f, 0.0f); + if(mesh->mTextureCoords[0]) { + texture.x = mesh->mTextureCoords[0][i].x; + texture.y = mesh->mTextureCoords[0][i].y; + } + + vertices.push_back(Vertex{ + position, + normal, + texture + }); + } + // process indices + for(unsigned int i = 0; i < mesh->mNumFaces; i++) { + aiFace face = mesh->mFaces[i]; + for(unsigned int j = 0; j < face.mNumIndices; j++) + indices.push_back(face.mIndices[j]); + } + + // process material + aiColor3D diffuse_color; + aiColor3D ambient_color; + aiColor3D specular_color; + float shininess = 0.0f; + + if(mesh->mMaterialIndex >= 0) { + std::cout << "processing material of id: " << mesh->mMaterialIndex << std::endl; + aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex]; + + std::vector diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE); + textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end()); + + std::vector specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR); + textures.insert(textures.end(), specularMaps.begin(), specularMaps.end()); + + if(AI_SUCCESS != material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse_color)) { + std::cout << "couldn't get diffuse color" << std::endl; + } + + if(AI_SUCCESS != material->Get(AI_MATKEY_COLOR_AMBIENT, ambient_color)) { + std::cout << "couldn't get ambient color" << std::endl; + } + + if(AI_SUCCESS != material->Get(AI_MATKEY_COLOR_SPECULAR, specular_color)) { + std::cout << "couldn't get specular color" << std::endl; + } + + if(AI_SUCCESS != material->Get(AI_MATKEY_SHININESS, shininess)) { + std::cout << "couldn't get shininess factor" << std::endl; + } + } + + return Mesh( + vertices, + indices, + textures, + Material { + aiColorToGLM(diffuse_color), + aiColorToGLM(ambient_color), + aiColorToGLM(specular_color), + shininess + } + ); +} + + +std::vector Model::loadMaterialTextures(aiMaterial* mat, const aiTextureType& type) { + std::vector textures; + std::cout << "material has " << mat->GetTextureCount(type) << " textures of type " << aiTextureTypeToString(type) << std::endl; + for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) { + aiString str; + mat->GetTexture(type, i, &str); + std::filesystem::path textureFilepath = std::filesystem::path(this->directory) / std::string(str.C_Str()); + Texture texture(textureFilepath.string(), type); + textures.push_back(texture); + } + return textures; +} + + +Texture::Texture(const std::string& filepath, const aiTextureType& type) { + switch (type) { + case aiTextureType_DIFFUSE: + this->type = "texture_diffuse"; + break; + case aiTextureType_SPECULAR: + this->type = "texture_specular"; + break; + default: + throw std::invalid_argument(std::string("Unimplemented texture type ") + aiTextureTypeToString(type)); + } + + unsigned int textureID; + glGenTextures(1, &textureID); + + int width, height, nrComponents; + std::cout << "Attempting to load texture at " << filepath << std::endl; + unsigned char *data = stbi_load(filepath.c_str(), &width, &height, &nrComponents, 0); + if (!data) { + std::cout << "Texture failed to load at path: " << filepath << std::endl; + stbi_image_free(data); + throw std::exception(); + } + std::cout << "Succesfully loaded texture at " << filepath << std::endl; + GLenum format = GL_RED; + if (nrComponents == 1) + format = GL_RED; + else if (nrComponents == 3) + format = GL_RGB; + else if (nrComponents == 4) + format = GL_RGBA; + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + stbi_image_free(data); + + this->ID = textureID; +} + +unsigned int Texture::getID() const { + return ID; +} + +std::string Texture::getType() const { + return type; +} + + diff --git a/src/client/renderable.cpp b/src/client/renderable.cpp new file mode 100644 index 00000000..5094337c --- /dev/null +++ b/src/client/renderable.cpp @@ -0,0 +1,28 @@ +#include "client/renderable.hpp" + +#include +#define GLM_ENABLE_EXPERIMENTAL +#include + +Renderable::Renderable() : model(1.0f) { } + +void Renderable::translateAbsolute(const glm::vec3 &new_pos) { + this->model[3] = glm::vec4(new_pos, 1.0f); +} + +void Renderable::translateRelative(const glm::vec3& delta) { + this->model = glm::translate(this->model, delta); +} + +void Renderable::scale(const float& new_factor) { + glm::vec3 scaleVector(new_factor, new_factor, new_factor); + this->model = glm::scale(this->model, scaleVector); +} + +void Renderable::scale(const glm::vec3& scale) { + this->model = glm::scale(this->model, scale); +} + +glm::mat4 Renderable::getModelMat() { + return this->model; +} diff --git a/src/client/shader.cpp b/src/client/shader.cpp new file mode 100644 index 00000000..437c115c --- /dev/null +++ b/src/client/shader.cpp @@ -0,0 +1,127 @@ +#include + +#include +#include +#include + +#include +#include + +#include "client/shader.hpp" + +Shader::Shader(const std::string& vertexPath, const std::string& fragmentPath) { + std::string vertexCode; + std::string fragmentCode; + std::ifstream vShaderFile; + std::ifstream fShaderFile; + + // ensure ifstream objects can throw exceptions + vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); + fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit); + try { + vShaderFile.open(vertexPath); + fShaderFile.open(fragmentPath); + + std::stringstream vShaderStream, fShaderStream; + + vShaderStream << vShaderFile.rdbuf(); + fShaderStream << fShaderFile.rdbuf(); + + vShaderFile.close(); + fShaderFile.close(); + + vertexCode = vShaderStream.str(); + fragmentCode = fShaderStream.str(); + } catch(std::ifstream::failure& e) { + throw std::invalid_argument("Error: could not read shader file " + vertexPath + " and " + fragmentPath); + } + + const char* vShaderCode = vertexCode.c_str(); + const char* fShaderCode = fragmentCode.c_str(); + + GLuint vertex, fragment; + int success; + char infoLog[512]; + + // vertex Shader + vertex = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex, 1, &vShaderCode, NULL); + glCompileShader(vertex); + // print compile errors if any + glGetShaderiv(vertex, GL_COMPILE_STATUS, &success); + if(!success) { + glGetShaderInfoLog(vertex, 512, NULL, infoLog); + std::cout << "ERROR: Vertex shader compilation failed\n" << infoLog << std::endl; + }; + + // fragment shader + fragment = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment, 1, &fShaderCode, NULL); + glCompileShader(fragment); + // print compile errors if any + glGetShaderiv(fragment, GL_COMPILE_STATUS, &success); + if(!success) { + glGetShaderInfoLog(vertex, 512, NULL, infoLog); + std::cout << "ERROR: Fragment shader compilation failed\n" << infoLog << std::endl; + }; + + if (vertex == 0 && fragment == 0) { + throw new std::invalid_argument("both shaders failed to init"); + } + + // shader Program + ID = glCreateProgram(); + glAttachShader(ID, vertex); + glAttachShader(ID, fragment); + glLinkProgram(ID); + // print linking errors if any + glGetProgramiv(ID, GL_LINK_STATUS, &success); + if(!success) { + glGetProgramInfoLog(ID, 512, NULL, infoLog); + std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; + } + + // delete the shaders as they're linked into our program now and no longer necessary + glDetachShader(ID, vertex); + glDetachShader(ID, fragment); + glDeleteShader(vertex); + glDeleteShader(fragment); +} + +Shader::~Shader() { + glDeleteProgram(this->ID); +} + +void Shader::use() { + glUseProgram(ID); +} + +void Shader::setBool(const std::string &name, bool value) const { + glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value); +} + +void Shader::setInt(const std::string &name, int value) const { + glUniform1i(glGetUniformLocation(ID, name.c_str()), value); +} + +void Shader::setFloat(const std::string &name, float value) const { + glUniform1f(glGetUniformLocation(ID, name.c_str()), value); +} + +void Shader::setMat4(const std::string &name, glm::mat4& value) { + glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, reinterpret_cast(&value)); +} + +void Shader::setVec3(const std::string &name, glm::vec3& value) { + glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, reinterpret_cast(&value)); +} + +glm::vec3 Shader::getVec3(const std::string &name) { + glm::vec3 vec; + glGetUniformfv(ID, glGetUniformLocation(ID, name.c_str()), reinterpret_cast(&vec)); + return vec; +} + +GLuint Shader::getID() { + return ID; +} diff --git a/src/client/shaders.cpp b/src/client/shaders.cpp deleted file mode 100644 index 2a7ea5f0..00000000 --- a/src/client/shaders.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include - -#include - -#include "client/util.hpp" - -GLuint loadCubeShaders() { - boost::filesystem::path root_path = boost::dll::program_location().parent_path().parent_path().parent_path(); - boost::filesystem::path vertFilepath = root_path / "src/client/shaders/shader.vert"; - boost::filesystem::path fragFilepath = root_path / "src/client/shaders/shader.frag"; - - GLuint shaderProgram = LoadShaders(vertFilepath.string(), fragFilepath.string()); - // Check the shader program exists and is non-zero - if (!shaderProgram) { - return 0; - } - return shaderProgram; -} diff --git a/src/client/shaders/shader.frag b/src/client/shaders/cube.frag similarity index 62% rename from src/client/shaders/shader.frag rename to src/client/shaders/cube.frag index 9b84c61c..9c30ceae 100644 --- a/src/client/shaders/shader.frag +++ b/src/client/shaders/cube.frag @@ -1,22 +1,18 @@ #version 330 core -// Inputs to the fragment shader are the outputs of the same name from the vertex shader. -// Note that you do not have access to the vertex shader's default output, gl_Position. +// Fragment shader of solid color, untextured cubes in vec3 fragNormal; +in vec3 fragPos; -// uniforms used for lighting uniform vec3 AmbientColor = vec3(0.2); uniform vec3 LightDirection = normalize(vec3(2, 4, 3)); uniform vec3 LightColor = vec3(1.0, 1.0, 1.0); uniform vec3 DiffuseColor = vec3(1.0, 1.0, 1.0); -// You can output many things. The first vec4 type output determines the color of the fragment out vec4 fragColor; -void main() -{ - +void main() { // Compute irradiance (sum of ambient & direct lighting) vec3 irradiance = AmbientColor + LightColor * max(0, dot(LightDirection, fragNormal)); diff --git a/src/client/shaders/shader.vert b/src/client/shaders/cube.vert similarity index 79% rename from src/client/shaders/shader.vert rename to src/client/shaders/cube.vert index 56567efe..2ae4fdda 100644 --- a/src/client/shaders/shader.vert +++ b/src/client/shaders/cube.vert @@ -1,5 +1,6 @@ #version 330 core -// NOTE: Do NOT use any version older than 330! Bad things will happen! +# +// Fragment shader of solid color, untextured cubes layout (location = 0) in vec3 position; layout (location = 1) in vec3 normal; @@ -11,7 +12,7 @@ uniform mat4 model; // Outputs of the vertex shader are the inputs of the same name of the fragment shader. // The default output, gl_Position, should be assigned something. out vec3 fragNormal; - +out vec3 fragPos; void main() { @@ -20,4 +21,6 @@ void main() // for shading fragNormal = vec3(model * vec4(normal, 0)); + //fragNormal = normal; + fragPos = vec3(model * vec4(position, 1.0)); } \ No newline at end of file diff --git a/src/client/shaders/lightsource.frag b/src/client/shaders/lightsource.frag new file mode 100644 index 00000000..6b1b3513 --- /dev/null +++ b/src/client/shaders/lightsource.frag @@ -0,0 +1,7 @@ +#version 330 core +out vec4 FragColor; + +void main() +{ + FragColor = vec4(1.0); // set all 4 vector values to 1.0 +} diff --git a/src/client/shaders/lightsource.vert b/src/client/shaders/lightsource.vert new file mode 100644 index 00000000..820e2b27 --- /dev/null +++ b/src/client/shaders/lightsource.vert @@ -0,0 +1,14 @@ +#version 330 core +// NOTE: Do NOT use any version older than 330! Bad things will happen! + +layout (location = 0) in vec3 position; + +// Uniform variables +uniform mat4 viewProj; +uniform mat4 model; + +void main() +{ + // OpenGL maintains the D matrix so you only need to multiply by P, V (aka C inverse), and M + gl_Position = viewProj * model * vec4(position, 1.0); +} diff --git a/src/client/shaders/model.frag b/src/client/shaders/model.frag new file mode 100644 index 00000000..e08d85d3 --- /dev/null +++ b/src/client/shaders/model.frag @@ -0,0 +1,47 @@ +#version 330 core + +// Fragment shader for loaded models. +// This shader currently expects textured models. Untextured +// models will show up as black. + +in vec3 fragNormal; +in vec3 fragPos; +in vec2 TexCoords; + +struct Material { + vec3 ambient; + vec3 diffuse; + vec3 specular; + float shininess; + sampler2D texture_diffuse1; +}; + +uniform Material material; +uniform vec3 viewPos; + +uniform vec3 lightPos; +uniform vec3 lightColor; + +out vec4 fragColor; + +void main() { + // ambient + vec3 ambient = lightColor * vec3(texture(material.texture_diffuse1, TexCoords)); + + // diffuse + vec3 norm = normalize(fragNormal); + vec3 lightDir = normalize(lightPos - fragPos); + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = lightColor * (diff * vec3(texture(material.texture_diffuse1, TexCoords))); + + // specular + vec3 viewDir = normalize(viewPos - fragPos); + vec3 reflectDir = reflect(-lightDir, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + vec3 specular = lightColor * (spec * material.specular); + + vec3 result = ambient + diffuse + specular; + fragColor = vec4(result, 1.0); + // * vec4(texture(material.texture_diffuse1, TexCoords)); + // fragColor = vec4(0.0, 1.0, 1.0, 1.0); +} diff --git a/src/client/shaders/model.vert b/src/client/shaders/model.vert new file mode 100644 index 00000000..7d681852 --- /dev/null +++ b/src/client/shaders/model.vert @@ -0,0 +1,29 @@ +#version 330 core +# +// Vertex shader for loaded models. +// Also forwards texture coordinates to fragment shader. + +layout (location = 0) in vec3 position; +layout (location = 1) in vec3 normal; +layout (location = 2) in vec2 uvs; + +// Uniform variables +uniform mat4 viewProj; +uniform mat4 model; + +// Outputs of the vertex shader are the inputs of the same name of the fragment shader. +// The default output, gl_Position, should be assigned something. +out vec3 fragNormal; +out vec3 fragPos; +out vec2 TexCoords; + +void main() { + // OpenGL maintains the D matrix so you only need to multiply by P, V (aka C inverse), and M + gl_Position = viewProj * model * vec4(position, 1.0); + + // for shading + fragNormal = vec3(model * vec4(normal, 0)); + //fragNormal = normal; + fragPos = vec3(model * vec4(position, 1.0)); + TexCoords = uvs; +} diff --git a/src/client/tests/CMakeLists.txt b/src/client/tests/CMakeLists.txt index fbe4eb69..88ba2017 100644 --- a/src/client/tests/CMakeLists.txt +++ b/src/client/tests/CMakeLists.txt @@ -12,6 +12,17 @@ target_include_directories(${TARGET_NAME} PRIVATE ${INCLUDE_DIRECTORY}) target_link_libraries(${TARGET_NAME} PUBLIC gtest_main) add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME}) target_include_directories(${TARGET_NAME} PRIVATE ${BOOST_LIBRARY_INCLUDES}) +target_include_directories(${TARGET_NAME} + PRIVATE + ${OPENGL_INCLUDE_DIRS} + glfw + glm + ${imgui-directory} + ${GLEW_INCLUDE_DIRS} + ${ASSIMP_INCLUDE_DIRS} + ${STB_INCLUDE_DIRS} +) + target_link_libraries(${TARGET_NAME} PRIVATE Boost::asio @@ -20,9 +31,10 @@ target_link_libraries(${TARGET_NAME} Boost::program_options Boost::serialization nlohmann_json::nlohmann_json + glm + glfw + libglew_static ) -target_include_directories(${TARGET_NAME} PRIVATE ${OPENGL_INCLUDE_DIRS} glfw glm ${imgui-directory} "${CMAKE_BINARY_DIR}/_deps/glew-src/include") -target_link_libraries(${TARGET_NAME} PRIVATE glm glfw libglew_static) # setup make target set(RUN_TESTS_TARGET "run_${TARGET_NAME}") diff --git a/src/client/util.cpp b/src/client/util.cpp index b5357dd3..39a0ffac 100644 --- a/src/client/util.cpp +++ b/src/client/util.cpp @@ -1,79 +1,6 @@ #include "client/util.hpp" -enum ShaderType { - vertex, - fragment -}; - -GLuint LoadSingleShader(const std::string& shaderFilePath, ShaderType type) { - // Create a shader id. - GLuint shaderID = 0; - - if (type == vertex) - shaderID = glCreateShader(GL_VERTEX_SHADER); - else if (type == fragment) - shaderID = glCreateShader(GL_FRAGMENT_SHADER); - - // Try to read shader codes from the shader file. - std::string shaderCode; - std::ifstream shaderStream(shaderFilePath, std::ios::in); - if (shaderStream.is_open()) { - std::string Line = ""; - while (getline(shaderStream, Line)) - shaderCode += "\n" + Line; - shaderStream.close(); - } else { - std::cerr << "Impossible to open " << shaderFilePath << ". " - << "Check to make sure the file exists and you passed in the " - << "right filepath!" - << std::endl; - return 0; - } - - GLint Result = GL_FALSE; - - // Compile Shader. - std::cerr << "Compiling shader: " << shaderFilePath << std::endl; - char const* sourcePointer = shaderCode.c_str(); - glShaderSource(shaderID, 1, &sourcePointer, NULL); - glCompileShader(shaderID); - - // Check Shader. - glGetShaderiv(shaderID, GL_COMPILE_STATUS, &Result); - if (type == vertex) - printf("Successfully compiled vertex shader!\n"); - else if (type == fragment) - printf("Successfully compiled fragment shader!\n"); - - return shaderID; +glm::vec3 aiColorToGLM(const aiColor3D& color) { + return glm::vec3(color.r, color.g, color.b); } -GLuint LoadShaders(const std::string& vertexFilePath, const std::string& fragmentFilePath) { - // Create the vertex shader and fragment shader. - GLuint vertexShaderID = LoadSingleShader(vertexFilePath, vertex); - GLuint fragmentShaderID = LoadSingleShader(fragmentFilePath, fragment); - - // Check both shaders. - if (vertexShaderID == 0 || fragmentShaderID == 0) return 0; - - GLint Result = GL_FALSE; - - // Link the program. - printf("Linking program\n"); - GLuint programID = glCreateProgram(); - glAttachShader(programID, vertexShaderID); - glAttachShader(programID, fragmentShaderID); - glLinkProgram(programID); - - // Check the program. - glGetProgramiv(programID, GL_LINK_STATUS, &Result); - printf("Successfully linked program!\n"); - - // Detach and delete the shaders as they are no longer needed. - glDetachShader(programID, vertexShaderID); - glDetachShader(programID, fragmentShaderID); - glDeleteShader(vertexShaderID); - glDeleteShader(fragmentShaderID); - - return programID; -} diff --git a/src/server/game/enemy.cpp b/src/server/game/enemy.cpp index d1c331a6..96befe58 100644 --- a/src/server/game/enemy.cpp +++ b/src/server/game/enemy.cpp @@ -6,6 +6,7 @@ SharedObject Enemy::toShared() { return so; } -Enemy::Enemy() : Creature(ObjectType::Enemy) {} +Enemy::Enemy() : Creature(ObjectType::Enemy) { +} Enemy::~Enemy() {} \ No newline at end of file diff --git a/src/server/game/item.cpp b/src/server/game/item.cpp index dbb256bf..e655869d 100644 --- a/src/server/game/item.cpp +++ b/src/server/game/item.cpp @@ -2,7 +2,7 @@ #include "shared/game/sharedobject.hpp" /* Constructors and Destructors */ -Item::Item() : Object(ObjectType::Item) { +Item::Item() : Object(ObjectType::Item) { // cppcheck-suppress uninitMemberVar } diff --git a/src/server/game/objectmanager.cpp b/src/server/game/objectmanager.cpp index 49e4ceae..330d0bcb 100644 --- a/src/server/game/objectmanager.cpp +++ b/src/server/game/objectmanager.cpp @@ -1,4 +1,5 @@ #include "server/game/objectmanager.hpp" +#include "server/game/enemy.hpp" #include @@ -70,8 +71,22 @@ SpecificID ObjectManager::createObject(ObjectType type) { player->globalID = globalID; break; } - case ObjectType::Object: - default: { + case ObjectType::Enemy: { + // Create a new object of type Enemy + Enemy* enemy = new Enemy(); + + // Push to type-specific enemies vector + typeID = (SpecificID)this->enemies.push(enemy); + + // Push to global objects vector + globalID = (EntityID)this->objects.push(enemy); + + // Set object's type and global IDs + enemy->typeID = typeID; + enemy->globalID = globalID; + break; + } + case ObjectType::Object: { // Create a new object of type Object Object* object = new Object(ObjectType::Object); @@ -84,11 +99,29 @@ SpecificID ObjectManager::createObject(ObjectType type) { // Push to global objects vector globalID = (EntityID)this->objects.push(object); - // Set object's type and global IDs - object->typeID = typeID; - object->globalID = globalID; - break; - } + // Set object's type and global IDs + object->typeID = typeID; + object->globalID = globalID; + break; + } + default: { + // Create a new object of type Object + Object* object = new Object(ObjectType::Object); + + // TODO: Maybe change SmartVector's index return value? size_t is + // larger than uint32 (which is what SpecificID and EntityID are + // defined as) + // Push to type-specific base_objects vector + typeID = (SpecificID)this->base_objects.push(object); + + // Push to global objects vector + globalID = (EntityID)this->objects.push(object); + + // Set object's type and global IDs + object->typeID = typeID; + object->globalID = globalID; + break; + } } // Return new object's specificID diff --git a/src/server/game/player.cpp b/src/server/game/player.cpp index 95473d44..2a963e7a 100644 --- a/src/server/game/player.cpp +++ b/src/server/game/player.cpp @@ -8,7 +8,6 @@ SharedObject Player::toShared() { } Player::Player() : Creature(ObjectType::Player) { - } Player::~Player() { diff --git a/src/server/game/servergamestate.cpp b/src/server/game/servergamestate.cpp index a9d1023f..78b0d3f7 100644 --- a/src/server/game/servergamestate.cpp +++ b/src/server/game/servergamestate.cpp @@ -28,7 +28,7 @@ ServerGameState::ServerGameState(GamePhase start_phase) this->phase = start_phase; } -ServerGameState::ServerGameState(GamePhase start_phase, GameConfig config) // cppcheck-suppress passedByValue +ServerGameState::ServerGameState(GamePhase start_phase, const GameConfig& config) : ServerGameState(config) { this->phase = start_phase; } @@ -207,7 +207,7 @@ void ServerGameState::useItem() { SmartVector items = this->objects.getItems(); for (int i = 0; i < items.size(); i++) { - Item* item = items.get(i); + const Item* item = items.get(i); if (item == nullptr) continue; diff --git a/src/server/game/solidsurface.cpp b/src/server/game/solidsurface.cpp index 04d2c6db..8f3171e4 100644 --- a/src/server/game/solidsurface.cpp +++ b/src/server/game/solidsurface.cpp @@ -9,9 +9,9 @@ SolidSurface::~SolidSurface() {} /* SharedGameState generation */ SharedObject SolidSurface::toShared() { - SharedObject shared = Object::toShared(); + SharedObject sharedSolidSurface = Object::toShared(); - shared.solidSurface = this->shared; + sharedSolidSurface.solidSurface = this->shared; - return shared; + return sharedSolidSurface; } \ No newline at end of file diff --git a/src/server/server.cpp b/src/server/server.cpp index fcb374bf..0954c16f 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -14,9 +14,12 @@ #include #include "boost/variant/get.hpp" +#include "server/game/enemy.hpp" +#include "server/game/player.hpp" #include "shared/game/event.hpp" #include "server/game/servergamestate.hpp" #include "server/game/object.hpp" +#include "shared/game/sharedobject.hpp" #include "shared/network/session.hpp" #include "shared/network/packet.hpp" #include "shared/network/constants.hpp" @@ -36,6 +39,9 @@ Server::Server(boost::asio::io_context& io_context, GameConfig config) Object* cube = state.objects.getBaseObject(cubeID); cube->physics.movable = false; + EntityID bearID = state.objects.createObject(ObjectType::Enemy); + Enemy* bear = reinterpret_cast(state.objects.getObject(bearID)); + bear->physics.shared.position = glm::vec3(0.0f, 50.0f, 0.0f); // Create a room /* diff --git a/src/shared/game/sharedobject.cpp b/src/shared/game/sharedobject.cpp index 692498a6..d1521876 100644 --- a/src/shared/game/sharedobject.cpp +++ b/src/shared/game/sharedobject.cpp @@ -4,10 +4,14 @@ std::string objectTypeString(ObjectType type) { switch (type) { case ObjectType::Object: return "Object"; - case ObjectType::Item: - return "Item"; - case ObjectType::SolidSurface: - return "SolidSurface"; + case ObjectType::Item: + return "Item"; + case ObjectType::SolidSurface: + return "SolidSurface"; + case ObjectType::Player: + return "Player"; + case ObjectType::Enemy: + return "Enemy"; default: return "Unknown"; }