diff --git a/CMakeLists.txt b/CMakeLists.txt index f066720..bb6e502 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.8) # options +option(ENGINE_BUILD_TEST "Compile the test program" ON) +option(ENGINE_BUILD_VULKAN "Use Vulkan 1.3 for graphics" ON) +option(ENGINE_BUILD_OPENGL "Use OpenGL 4.5 for graphics" OFF) project(engine LANGUAGES CXX VERSION "0.1.0" @@ -106,7 +109,20 @@ target_include_directories(${PROJECT_NAME} PRIVATE src) configure_file(config.h.in config.h) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_compile_definitions(${PROJECT_NAME} PRIVATE "ENGINE_BUILD_VULKAN") +# figure out what graphics api to use +if (ENGINE_BUILD_VULKAN) + target_compile_definitions(${PROJECT_NAME} PRIVATE "ENGINE_BUILD_VULKAN") +elseif(ENGINE_BUILD_OPENGL) + target_compile_definitions(${PROJECT_NAME} PRIVATE "ENGINE_BUILD_OPENGL") +else() + target_compile_definitions(${PROJECT_NAME} PRIVATE "ENGINE_BUILD_NULL") +endif() + +# Build the test +if (ENGINE_BUILD_TEST) + add_subdirectory(test) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $) +endif() # external libraries: @@ -126,7 +142,6 @@ endif() set(GLAD_SPEC "gl" CACHE INTERNAL "" FORCE) set(BUILD_SHARED_LIBS OFF) add_subdirectory(dependencies/glad) -set_property(TARGET glad PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(${PROJECT_NAME} PUBLIC glad) target_include_directories(${PROJECT_NAME} PUBLIC dependencies/glad/include) diff --git a/include/gfx_device.hpp b/include/gfx_device.hpp index cc8396c..61237f3 100644 --- a/include/gfx_device.hpp +++ b/include/gfx_device.hpp @@ -32,7 +32,7 @@ namespace engine { gfx::Pipeline* createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize); void destroyPipeline(const gfx::Pipeline* pipeline); - void updateUniformBuffer(const gfx::Pipeline* pipeline, void* data); + void updateUniformBuffer(const gfx::Pipeline* pipeline, void* data, size_t size, uint32_t offset); gfx::Buffer* createBuffer(gfx::BufferType type, uint64_t size, const void* data); void destroyBuffer(const gfx::Buffer* buffer); diff --git a/include/resources/shader.hpp b/include/resources/shader.hpp index e547c82..61ae537 100644 --- a/include/resources/shader.hpp +++ b/include/resources/shader.hpp @@ -22,8 +22,8 @@ public: ~Shader() override; struct UniformBuffer { - glm::mat4 v; glm::mat4 p; + glm::vec4 color; }; gfx::Pipeline* getPipeline() diff --git a/src/components/camera.cpp b/src/components/camera.cpp index d384ee2..fcea4f5 100644 --- a/src/components/camera.cpp +++ b/src/components/camera.cpp @@ -38,13 +38,9 @@ void Camera::updateCam(glm::mat4 transform, glm::mat4* viewMatOut) glm::mat4 viewMatrix = glm::inverse(transform); - struct { - glm::mat4 view; - glm::mat4 proj; - } uniformData{}; + resources::Shader::UniformBuffer uniformData{}; - uniformData.view = viewMatrix; - uniformData.proj = m_projMatrix; + uniformData.p = m_projMatrix; using namespace resources; @@ -54,7 +50,7 @@ void Camera::updateCam(glm::mat4 transform, glm::mat4* viewMatOut) auto lockedPtr = resPtr.lock(); auto shader = dynamic_cast(lockedPtr.get()); // SET VIEW TRANSFORM HERE - gfxdev->updateUniformBuffer(shader->getPipeline(), &uniformData); + gfxdev->updateUniformBuffer(shader->getPipeline(), &uniformData.p, sizeof(uniformData.p), offsetof(resources::Shader::UniformBuffer, p)); } *viewMatOut = viewMatrix; diff --git a/src/components/mesh_renderer.cpp b/src/components/mesh_renderer.cpp index 9ef6358..6f7a7b9 100644 --- a/src/components/mesh_renderer.cpp +++ b/src/components/mesh_renderer.cpp @@ -1,5 +1,7 @@ #include "components/mesh_renderer.hpp" +#include "resources/shader.hpp" + #include "object.hpp" #include "resource_manager.hpp" @@ -25,6 +27,10 @@ Renderer::~Renderer() void Renderer::render(glm::mat4 transform, glm::mat4 view) { + resources::Shader::UniformBuffer uniformData{}; + uniformData.color = glm::vec4{ m_color.r, m_color.g, m_color.b, 1.0 }; + gfxdev->updateUniformBuffer(m_shader->getPipeline(), &uniformData.color, sizeof(uniformData.color), offsetof(resources::Shader::UniformBuffer, color)); + glm::mat4 pushConsts[] = { transform, view }; gfxdev->draw(m_shader->getPipeline(), m_mesh->vb, m_mesh->ib, m_mesh->m_vertices.size(), pushConsts, sizeof(glm::mat4) * 2); } diff --git a/src/gfx_device_opengl45.cpp b/src/gfx_device_opengl45.cpp index dfd89c2..381401e 100644 --- a/src/gfx_device_opengl45.cpp +++ b/src/gfx_device_opengl45.cpp @@ -1,29 +1,70 @@ // The implementation of the graphics layer using OpenGL 4.5 // This uses SDL specific code -//#undef ENGINE_BUILD_OPENGL #ifdef ENGINE_BUILD_OPENGL #include "gfx_device.hpp" -#include "util.hpp" -#include "config.h" + #include "log.hpp" #include -#include +#include -#include -#include -#include #include -#include -#include namespace engine { + // EXTERNED GLOBAL VARIABLE + GFXDevice* gfxdev = nullptr; + // structures and enums + + + // handles + + struct gfx::Buffer { + gfx::BufferType type; + // TODO + }; + + struct gfx::Pipeline { + // TODO + }; + + + + // enum converters + + namespace vkinternal { + /* + static GLenum getVertexAttribFormat(gfx::VertexAttribFormat fmt) + { + switch (fmt) { + case gfx::VertexAttribFormat::VEC2: + return VK_FORMAT_R32G32_SFLOAT; + case gfx::VertexAttribFormat::VEC3: + return VK_FORMAT_R32G32B32_SFLOAT; + } + throw std::runtime_error("Unknown vertex attribute format"); + } + + static VkBufferUsageFlagBits getBufferUsageFlag(gfx::BufferType type) + { + switch (type) { + case gfx::BufferType::VERTEX: + return VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + case gfx::BufferType::INDEX: + return VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + } + throw std::runtime_error("Unknown buffer type"); + }*/ + + } + + // functions + static std::vector readFile(const std::string& filename) { std::ifstream file(filename, std::ios::ate | std::ios::binary); @@ -37,21 +78,37 @@ namespace engine { return buffer; } - - // class definitions struct GFXDevice::Impl { - SDL_GLContext context = nullptr; - + SDL_Window* window = nullptr; + + uint64_t FRAMECOUNT = 0; + + SDL_GLContext context; + }; GFXDevice::GFXDevice(const char* appName, const char* appVersion, SDL_Window* window) { + if (gfxdev != nullptr) { + throw std::runtime_error("There can only be one graphics device"); + } + gfxdev = this; + pimpl = std::make_unique(); + pimpl->window = window; + pimpl->context = SDL_GL_CreateContext(window); + if (pimpl->context == NULL) { + throw std::runtime_error("Unable to create OpenGL context: " + std::string(SDL_GetError())); + } + + if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { + throw std::runtime_error("Unable to initialise GLAD"); + } } @@ -62,42 +119,66 @@ namespace engine { SDL_GL_DeleteContext(pimpl->context); } - void GFXDevice::drawBuffer(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, uint32_t count) + void GFXDevice::getViewportSize(uint32_t* w, uint32_t* h) { + int width, height; + SDL_GL_GetDrawableSize(pimpl->window, &width, &height); + *w = (uint32_t)width; + *h = (uint32_t)height; } - void GFXDevice::drawIndexed(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t indexCount) + void GFXDevice::draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize) { + assert(vertexBuffer->type == gfx::BufferType::VERTEX); + assert(vertexBuffer != nullptr); + assert(indexBuffer == nullptr || indexBuffer->type == gfx::BufferType::INDEX); + } void GFXDevice::renderFrame() { + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + SDL_GL_SwapWindow(pimpl->window); + + pimpl->FRAMECOUNT++; } - - gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat) + + gfx::Pipeline* GFXDevice::createPipeline(const char* vertShaderPath, const char* fragShaderPath, const gfx::VertexFormat& vertexFormat, uint64_t uniformBufferSize) { - return nullptr; + gfx::Pipeline* pipeline = new gfx::Pipeline{}; + + return pipeline; } void GFXDevice::destroyPipeline(const gfx::Pipeline* pipeline) + { + delete pipeline; + } + + void GFXDevice::updateUniformBuffer(const gfx::Pipeline* pipeline, void* data) { } gfx::Buffer* GFXDevice::createBuffer(gfx::BufferType type, uint64_t size, const void* data) { - return nullptr; + auto out = new gfx::Buffer{}; + + out->type = type; + + return out; } void GFXDevice::destroyBuffer(const gfx::Buffer* buffer) { - + delete buffer; } void GFXDevice::waitIdle() { - glFinish(); + //glFinish(); } } diff --git a/src/gfx_device_vulkan.cpp b/src/gfx_device_vulkan.cpp index 093d489..38c2d3d 100644 --- a/src/gfx_device_vulkan.cpp +++ b/src/gfx_device_vulkan.cpp @@ -1175,8 +1175,10 @@ namespace engine { void GFXDevice::getViewportSize(uint32_t *w, uint32_t *h) { - *w = pimpl->swapchain.extent.width; - *h = pimpl->swapchain.extent.height; + int width, height; + SDL_Vulkan_GetDrawableSize(pimpl->window, &width, &height); + *w = (uint32_t)width; + *h = (uint32_t)height; } void GFXDevice::draw(const gfx::Pipeline* pipeline, const gfx::Buffer* vertexBuffer, const gfx::Buffer* indexBuffer, uint32_t count, const void* pushConstantData, size_t pushConstantSize) @@ -1600,15 +1602,17 @@ namespace engine { delete pipeline; } - void GFXDevice::updateUniformBuffer(const gfx::Pipeline* pipeline, void* data) + void GFXDevice::updateUniformBuffer(const gfx::Pipeline* pipeline, void* data, size_t size, uint32_t offset) { + assert(size <= pipeline->uniformBuffers[0]->size); + VkResult res; - void* uniformDest; for (gfx::Buffer* buffer : pipeline->uniformBuffers) { + void* uniformDest; res = vmaMapMemory(pimpl->allocator, buffer->allocation, &uniformDest); assert(res == VK_SUCCESS); - memcpy(uniformDest, data, buffer->size); + memcpy((uint8_t*)uniformDest + offset, data, size); vmaUnmapMemory(pimpl->allocator, buffer->allocation); } diff --git a/src/window.cpp b/src/window.cpp index 0e694be..f2d6ab0 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -31,6 +31,9 @@ namespace engine { #ifdef ENGINE_BUILD_VULKAN windowFlags |= SDL_WINDOW_VULKAN; #endif +#ifdef ENGINE_BUILD_OPENGL + windowFlags |= SDL_WINDOW_OPENGL; +#endif if (m_resizable) { windowFlags |= SDL_WINDOW_RESIZABLE; @@ -58,21 +61,6 @@ namespace engine { const int WINDOWED_MIN_HEIGHT = 480; SDL_SetWindowMinimumSize(m_handle, WINDOWED_MIN_WIDTH, WINDOWED_MIN_HEIGHT); - /* - m_glContext = SDL_GL_CreateContext(m_handle); - if (m_glContext == NULL) { - SDL_DestroyWindow(m_handle); - SDL_Quit(); - throw std::runtime_error("Unable to create OpenGL context: " + std::string(SDL_GetError())); - } - - if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { - SDL_DestroyWindow(m_handle); - SDL_Quit(); - throw std::runtime_error("Unable to initialise GLAD"); - } - */ - // onResize(m_winSize.x, m_winSize.y); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..133cbdb --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 3.12) + +# options + +project(enginetest LANGUAGES CXX + VERSION "0.1.0" +) + +set(GAME_SOURCES + + src/main.cpp + + src/game.cpp + src/game.hpp + src/meshgen.cpp + src/meshgen.hpp + src/terrain.cpp + src/terrain.hpp + src/camera_controller.cpp + src/camera_controller.hpp + +) + +if (WIN32) + add_executable(${PROJECT_NAME} WIN32 ${GAME_SOURCES} "game.rc") +else() + add_executable(${PROJECT_NAME} ${GAME_SOURCES}) +endif() + +# compiling options: + + +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) +set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) + +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W3) + target_compile_options(${PROJECT_NAME} PRIVATE /MP) +endif() + +target_include_directories(${PROJECT_NAME} PRIVATE src) + +# Pass some project information into the source code +configure_file(config.h.in config.h) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +target_link_libraries(${PROJECT_NAME} PRIVATE engine) +target_include_directories(${PROJECT_NAME} PRIVATE ../include) + +add_custom_command( + TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E create_symlink + ${PROJECT_SOURCE_DIR}/res $/res) \ No newline at end of file diff --git a/test/README b/test/README new file mode 100644 index 0000000..8dda317 --- /dev/null +++ b/test/README @@ -0,0 +1 @@ +A random game made using my game engine diff --git a/test/config.h.in b/test/config.h.in new file mode 100644 index 0000000..4d8d5b4 --- /dev/null +++ b/test/config.h.in @@ -0,0 +1,4 @@ +#pragma once + +#define PROJECT_VERSION "@PROJECT_VERSION@" +#define PROJECT_NAME "@PROJECT_NAME@" \ No newline at end of file diff --git a/test/game.rc b/test/game.rc new file mode 100644 index 0000000..5f437be --- /dev/null +++ b/test/game.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "icon1.ico" \ No newline at end of file diff --git a/test/icon1.ico b/test/icon1.ico new file mode 100644 index 0000000..5d06b9f Binary files /dev/null and b/test/icon1.ico differ diff --git a/test/res/fonts/LiberationMono-Regular.ttf b/test/res/fonts/LiberationMono-Regular.ttf new file mode 100644 index 0000000..e774859 Binary files /dev/null and b/test/res/fonts/LiberationMono-Regular.ttf differ diff --git a/test/res/meshes/cube.mesh b/test/res/meshes/cube.mesh new file mode 100644 index 0000000..1bb8f2d Binary files /dev/null and b/test/res/meshes/cube.mesh differ diff --git a/test/res/meshes/donut.mesh b/test/res/meshes/donut.mesh new file mode 100644 index 0000000..035fb2b Binary files /dev/null and b/test/res/meshes/donut.mesh differ diff --git a/test/res/meshes/gun.mesh b/test/res/meshes/gun.mesh new file mode 100644 index 0000000..b92b13b Binary files /dev/null and b/test/res/meshes/gun.mesh differ diff --git a/test/res/meshes/monke.mesh b/test/res/meshes/monke.mesh new file mode 100644 index 0000000..0329dff Binary files /dev/null and b/test/res/meshes/monke.mesh differ diff --git a/test/res/shader.frag b/test/res/shader.frag new file mode 100644 index 0000000..e21bd30 --- /dev/null +++ b/test/res/shader.frag @@ -0,0 +1,36 @@ +#version 450 + +layout(location = 0) in vec3 fragPos; +layout(location = 1) in vec3 fragNorm; +layout(location = 2) in vec2 fragUV; +layout(location = 3) in vec3 fragLightPos; +layout(location = 4) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + + // constants + vec3 lightColor = vec3(1.0, 1.0, 1.0); + vec3 ambientColor = vec3(1.0, 1.0, 1.0); + float ambientStrength = 0.1; + vec3 baseColor = fragColor; + vec3 emission = vec3(0.0, 0.0, 0.0); + + // code + + vec3 norm = normalize(fragNorm); + vec3 lightDir = normalize(fragLightPos - fragPos); + + vec3 diffuse = max(dot(norm, lightDir), 0.0) * lightColor; + vec3 ambient = ambientColor * ambientStrength; + + vec3 viewDir = normalize(-fragPos); + vec3 reflectDir = reflect(-lightDir, norm); + + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); + vec3 specular = 0.5 * spec * lightColor; + + vec3 lighting = min(diffuse + ambient + specular, 1.0); + outColor = min( ( vec4(baseColor, 1.0) ) * vec4(lighting + emission, 1.0), vec4(1.0)); +} \ No newline at end of file diff --git a/test/res/shader.frag.spv b/test/res/shader.frag.spv new file mode 100644 index 0000000..e936d60 Binary files /dev/null and b/test/res/shader.frag.spv differ diff --git a/test/res/shader.vert b/test/res/shader.vert new file mode 100644 index 0000000..cde310c --- /dev/null +++ b/test/res/shader.vert @@ -0,0 +1,31 @@ +#version 450 + +layout(binding = 0) uniform UBO { + mat4 proj; + vec4 color; +} ubo; + +layout( push_constant ) uniform Constants { + mat4 model; + mat4 view; +} constants; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNorm; +layout(location = 2) in vec2 inUV; + +layout(location = 0) out vec3 fragPos; +layout(location = 1) out vec3 fragNorm; +layout(location = 2) out vec2 fragUV; +layout(location = 3) out vec3 fragLightPos; +layout(location = 4) out vec3 fragColor; + +void main() { + gl_Position = ubo.proj * constants.view * constants.model * vec4(inPosition, 1.0); + fragPos = vec3(constants.view * constants.model * vec4(inPosition, 1.0)); + fragNorm = mat3(transpose(inverse(constants.view * constants.model))) * inNorm; + fragUV = inUV; + vec3 lightPos = vec3(-5.0, 20.0, 5.0); + fragLightPos = vec3(constants.view * vec4(lightPos, 1.0)); + fragColor = ubo.color.rgb; +} diff --git a/test/res/shader.vert.spv b/test/res/shader.vert.spv new file mode 100644 index 0000000..172d636 Binary files /dev/null and b/test/res/shader.vert.spv differ diff --git a/test/res/shaders/basic.frag b/test/res/shaders/basic.frag new file mode 100644 index 0000000..a659b9c --- /dev/null +++ b/test/res/shaders/basic.frag @@ -0,0 +1,37 @@ +#version 330 + +uniform float ambientStrength; +uniform vec3 ambientColor; + +uniform vec3 lightColor; +uniform vec3 emission; + +uniform vec3 baseColor; +uniform sampler2D tex; + +in vec3 f_Pos; +in vec3 f_Norm; +in vec2 f_UV; + +in vec3 f_lightPos; + +out vec4 FragColor; + +void main() { + + vec3 norm = normalize(f_Norm); + vec3 lightDir = normalize(f_lightPos - f_Pos); + + vec3 diffuse = max(dot(norm, lightDir), 0.0) * lightColor; + vec3 ambient = ambientColor * ambientStrength; + + vec3 viewDir = normalize(-f_Pos); + vec3 reflectDir = reflect(-lightDir, norm); + + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); + vec3 specular = 0.5 * spec * lightColor; + + vec3 lighting = min(diffuse + ambient + specular, 1.0); + FragColor = min( ( vec4(baseColor, 1.0) * texture(tex, f_UV) ) * vec4(lighting + emission, 1.0), vec4(1.0)); + +} diff --git a/test/res/shaders/basic.vert b/test/res/shaders/basic.vert new file mode 100644 index 0000000..3a62de5 --- /dev/null +++ b/test/res/shaders/basic.vert @@ -0,0 +1,26 @@ +#version 330 + +layout (location = 0) in vec3 v_Position; +layout (location = 1) in vec3 v_Norm; +layout (location = 2) in vec2 v_UV; + +uniform mat4 modelMat; +uniform mat4 viewMat; +uniform mat4 projMat; + +uniform vec3 lightPos; + +out vec3 f_Pos; +out vec3 f_Norm; +out vec2 f_UV; + +out vec3 f_lightPos; + +void main() +{ + gl_Position = projMat * viewMat * modelMat * vec4(v_Position, 1.0); + f_Pos = vec3(viewMat * modelMat * vec4(v_Position, 1.0)); + f_Norm = mat3(transpose(inverse(viewMat * modelMat))) * v_Norm; + f_UV = v_UV; + f_lightPos = vec3(viewMat * vec4(lightPos, 1.0)); +} diff --git a/test/res/shaders/font.frag b/test/res/shaders/font.frag new file mode 100644 index 0000000..9f4a7b2 --- /dev/null +++ b/test/res/shaders/font.frag @@ -0,0 +1,14 @@ +#version 330 + +uniform vec3 textColor; + +uniform sampler2D tex; + +in vec2 f_UV; + +out vec4 FragColor; + +void main() +{ + FragColor = vec4(textColor, texture(tex, f_UV).r); +} diff --git a/test/res/shaders/font.vert b/test/res/shaders/font.vert new file mode 100644 index 0000000..5c8b291 --- /dev/null +++ b/test/res/shaders/font.vert @@ -0,0 +1,25 @@ +#version 330 + +uniform vec2 windowSize; +uniform bool textScaling; // true means keep text aspect ratio + +layout (location = 0) in vec3 v_Position; +layout (location = 2) in vec2 v_UV; + +uniform mat4 modelMat; + +out vec2 f_UV; + +void main() +{ + float aspect = windowSize.y / windowSize.x; + + vec3 pos = v_Position; + + if (textScaling) { + pos.x *= aspect; + } + + gl_Position = modelMat * vec4(pos, 1.0); + f_UV = v_UV; +} diff --git a/test/res/textures/MonkeTexture.png b/test/res/textures/MonkeTexture.png new file mode 100644 index 0000000..162d903 Binary files /dev/null and b/test/res/textures/MonkeTexture.png differ diff --git a/test/res/textures/cobble_stone.png b/test/res/textures/cobble_stone.png new file mode 100644 index 0000000..c8f2958 Binary files /dev/null and b/test/res/textures/cobble_stone.png differ diff --git a/test/res/textures/grass_block_top.png b/test/res/textures/grass_block_top.png new file mode 100644 index 0000000..3f140af Binary files /dev/null and b/test/res/textures/grass_block_top.png differ diff --git a/test/res/textures/gun.png b/test/res/textures/gun.png new file mode 100644 index 0000000..e54ce61 Binary files /dev/null and b/test/res/textures/gun.png differ diff --git a/test/res/textures/missing.png b/test/res/textures/missing.png new file mode 100644 index 0000000..ec7e3b4 Binary files /dev/null and b/test/res/textures/missing.png differ diff --git a/test/res/textures/stone_bricks.png b/test/res/textures/stone_bricks.png new file mode 100644 index 0000000..acc7b66 Binary files /dev/null and b/test/res/textures/stone_bricks.png differ diff --git a/test/res/textures/ten_feet.png b/test/res/textures/ten_feet.png new file mode 100644 index 0000000..590adf3 Binary files /dev/null and b/test/res/textures/ten_feet.png differ diff --git a/test/res/textures/white.png b/test/res/textures/white.png new file mode 100644 index 0000000..b7b6ab4 Binary files /dev/null and b/test/res/textures/white.png differ diff --git a/test/src/camera_controller.cpp b/test/src/camera_controller.cpp new file mode 100644 index 0000000..02165e4 --- /dev/null +++ b/test/src/camera_controller.cpp @@ -0,0 +1,118 @@ +#include "camera_controller.hpp" + +#include "object.hpp" + +#include "window.hpp" +#include "input.hpp" + +#include +#include +#include +#include + +#include + +CameraController::CameraController(engine::Object* parent) : + CustomComponent(parent) +{ + standingHeight = parent->transform.position.y; + m_yaw = glm::half_pi(); +} + +void CameraController::onUpdate(glm::mat4 t) +{ + + // calculate new position + + // use one unit per meter + + const float dt = win.dt(); + + // jumping + constexpr float G = 9.8f; + constexpr float JUMPHEIGHT = 16.0f * 25.4f / 1000.0f; // 16 inches + constexpr float JUMPVEL = (float)2.82231110971133017648; //std::sqrt(2 * G * JUMPHEIGHT); + //constexpr float JUMPDURATION = 0.5f; + //constexpr float JUMPVEL = G * JUMPDURATION / 2.0f; + + if (inp.getButton("jump") && isJumping == false) { + isJumping = true; + dy = JUMPVEL; + //standingHeight = tcomp->position.y; + + } + + if (isJumping) { + dy -= G * dt; + parent.transform.position.y += dy * dt; + if (parent.transform.position.y < standingHeight) { + isJumping = false; + dy = 0.0f; + parent.transform.position.y = standingHeight; + } + } + + if (win.getButton(engine::inputs::MouseButton::M_LEFT)) { + //standingHeight = tcomp->position.y; + dy += dt * thrust; + isJumping = true; + } + + // in metres per second + //constexpr float SPEED = 1.5f; + float SPEED = walk_speed; + if (win.getKey(engine::inputs::Key::LSHIFT)) SPEED *= 10.0f; + + const float dx = inp.getAxis("movex") * SPEED; + const float dz = (-inp.getAxis("movey")) * SPEED; + + // calculate new pitch and yaw + + constexpr float MAX_PITCH = glm::half_pi(); + constexpr float MIN_PITCH = -MAX_PITCH; + + float dPitch = inp.getAxis("looky") * -1.0f * m_cameraSensitivity; + m_pitch += dPitch; + if (m_pitch <= MIN_PITCH || m_pitch >= MAX_PITCH) { + m_pitch -= dPitch; + } + m_yaw += inp.getAxis("lookx") * -1.0f * m_cameraSensitivity; + + // update position relative to camera direction in xz plane + const glm::vec3 d2xRotated = glm::rotateY(glm::vec3{ dx, 0.0f, 0.0f }, m_yaw); + const glm::vec3 d2zRotated = glm::rotateY(glm::vec3{ 0.0f, 0.0f, dz }, m_yaw); + parent.transform.position += (d2xRotated + d2zRotated) * dt; + parent.transform.position.y += dy * dt; + + // pitch quaternion + const float halfPitch = m_pitch / 2.0f; + glm::quat pitchQuat{}; + pitchQuat.x = glm::sin(halfPitch); + pitchQuat.y = 0.0f; + pitchQuat.z = 0.0f; + pitchQuat.w = glm::cos(halfPitch); + + // yaw quaternion + const float halfYaw = m_yaw / 2.0f; + glm::quat yawQuat{}; + yawQuat.x = 0.0f; + yawQuat.y = glm::sin(halfYaw); + yawQuat.z = 0.0f; + yawQuat.w = glm::cos(halfYaw); + + // update rotation + parent.transform.rotation = yawQuat * pitchQuat; + + if (win.getKeyPress(engine::inputs::Key::P)) { + std::string pos_string{ + "x: " + std::to_string(parent.transform.position.x) + + " y: " + std::to_string(parent.transform.position.y) + + " z: " + std::to_string(parent.transform.position.z) + }; +#ifdef NDEBUG + win.infoBox("POSITION", pos_string); +#endif + INFO("position: " + pos_string); + } + +} diff --git a/test/src/camera_controller.hpp b/test/src/camera_controller.hpp new file mode 100644 index 0000000..ebe2be4 --- /dev/null +++ b/test/src/camera_controller.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "components/custom.hpp" + +class CameraController : public engine::components::CustomComponent { + +public: + CameraController(engine::Object* parent); + void onUpdate(glm::mat4 t) override; + + float m_cameraSensitivity = 0.007f; + +private: + float m_yaw = 0.0f; + float m_pitch = 0.0f; + + float walk_speed = 4.0f; + + bool isJumping = false; + float dy = 0.0f; + float standingHeight = 0.0f; + float thrust = 25.0f; + +}; diff --git a/test/src/game.cpp b/test/src/game.cpp new file mode 100644 index 0000000..7ace58f --- /dev/null +++ b/test/src/game.cpp @@ -0,0 +1,122 @@ +#include "config.h" + +#include "engine.hpp" + +#include "window.hpp" +#include "input.hpp" +#include "sceneroot.hpp" + +#include "components/camera.hpp" +#include "components/mesh_renderer.hpp" + +#include "camera_controller.hpp" +#include "meshgen.hpp" + +#include + +#include + +void playGame() +{ + engine::Application app(PROJECT_NAME, PROJECT_VERSION); + + // configure window + app.window()->setRelativeMouseMode(true); + + + + // input config + + // game buttons + app.input()->addInputButton("fire", engine::inputs::MouseButton::M_LEFT); + app.input()->addInputButton("aim", engine::inputs::MouseButton::M_RIGHT); + app.input()->addInputButton("jump", engine::inputs::Key::SPACE); + app.input()->addInputButton("sneak", engine::inputs::Key::LSHIFT); + // game movement + app.input()->addInputButtonAsAxis("movex", engine::inputs::Key::D, engine::inputs::Key::A); + app.input()->addInputButtonAsAxis("movey", engine::inputs::Key::W, engine::inputs::Key::S); + // looking around + app.input()->addInputAxis("lookx", engine::inputs::MouseAxis::X); + app.input()->addInputAxis("looky", engine::inputs::MouseAxis::Y); + + + + // create the scene + + auto cam = app.scene()->createChild("cam"); + constexpr float HEIGHT_INCHES = 6.0f * 12.0f; + // eye level is about 4 1/2 inches below height + constexpr float EYE_LEVEL = (HEIGHT_INCHES - 4.5f) * 25.4f / 1000.0f; + cam->transform.position = { 0.0f, EYE_LEVEL, 0.0f }; + auto camCamera = cam->createComponent(); + camCamera->usePerspective(70.0f); + cam->createComponent(); + cam->createComponent()->m_mesh = genSphereMesh(0.2f, 20); + cam->getComponent()->setTexture("textures/cobble_stone.png"); + + auto gun = cam->createChild("gun"); + gun->transform.position = glm::vec3{ 0.2f, -0.1f, -0.15f }; + gun->transform.rotation = glm::angleAxis(glm::pi(), glm::vec3{ 0.0f, 1.0f, 0.0f }); + float GUN_SCALE = 9.0f / 560.0f; + GUN_SCALE *= 1.0f; + gun->transform.scale *= GUN_SCALE; + auto gunRenderer = gun->createComponent(); + gunRenderer->setMesh("meshes/gun.mesh"); + gunRenderer->setTexture("textures/gun.png"); + gunRenderer->m_color = { 0.2f, 0.3f, 0.2f }; + + // FLOOR + + constexpr float GRASS_DENSITY = 128.0f * 20.0f; + auto floor = app.scene()->createChild("floor"); + auto floorRenderer = floor->createComponent(); + floor->transform.position = glm::vec3{ 0.0f, 0.0f, 0.0f }; + floorRenderer->setTexture("textures/stone_bricks.png"); + floorRenderer->m_mesh = std::make_unique(std::vector{ + { { -16.0f, 0.0f, 16.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, GRASS_DENSITY } }, + { { 16.0f, 0.0f, -16.0f }, { 0.0f, 1.0f, 0.0f }, { GRASS_DENSITY, 0.0f } }, + { { -16.0f, 0.0f, -16.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f } }, + { { 16.0f, 0.0f, 16.0f }, { 0.0f, 1.0f, 0.0f }, { GRASS_DENSITY, GRASS_DENSITY } }, + { { 16.0f, 0.0f, -16.0f }, { 0.0f, 1.0f, 0.0f }, { GRASS_DENSITY, 0.0f } }, + { { -16.0f, 0.0f, 16.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, GRASS_DENSITY } } + }); + floor->transform.scale = { 100.0f, 1.0f, 100.0f }; + floorRenderer->m_color = { 0.1f, 0.9f, 0.1f }; + + auto cube = app.scene()->createChild("cube"); + auto cubeRen = cube->createComponent(); + cubeRen->setMesh("meshes/cube.mesh"); + cubeRen->m_color = { 0.8f, 0.2f, 0.05f }; + + cube->transform.position = glm::vec3{ -5.0f, 1.0f, 0.0f }; + class Spin : public engine::components::CustomComponent { + + public: + Spin(engine::Object* parent) : CustomComponent(parent) + { + + } + void onUpdate(glm::mat4 t) override + { + m_yaw += win.dt(); + m_yaw = glm::mod(m_yaw, glm::two_pi()); + const float halfYaw = m_yaw / 2.0f; + glm::quat yawQuat{}; + yawQuat.x = 0.0f; + yawQuat.y = glm::sin(halfYaw); + yawQuat.z = 0.0f; + yawQuat.w = glm::cos(halfYaw); + parent.transform.rotation = yawQuat; + } + + private: + float m_yaw = 0.0f; + + }; + app.scene()->getChild("cube")->createComponent(); + + + app.scene()->printTree(); + + app.gameLoop(); +} diff --git a/test/src/game.hpp b/test/src/game.hpp new file mode 100644 index 0000000..faaa60d --- /dev/null +++ b/test/src/game.hpp @@ -0,0 +1,3 @@ +#pragma once + +void playGame(); \ No newline at end of file diff --git a/test/src/main.cpp b/test/src/main.cpp new file mode 100644 index 0000000..1a82ba9 --- /dev/null +++ b/test/src/main.cpp @@ -0,0 +1,34 @@ +#include "config.h" +#include "game.hpp" + +#include "logger.hpp" +#include "window.hpp" + +#include + +int main(int argc, char *argv[]) +{ + + engine::setupLog(PROJECT_NAME); + + INFO("{} v{}", PROJECT_NAME, PROJECT_VERSION); + + try { + playGame(); + } + catch (const std::exception& e) { + + CRITICAL("{}", e.what()); + +#ifdef NDEBUG + engine::Window::errorBox(e.what()); +#else + fputs(e.what(), stderr); + fputc('\n', stderr); +#endif + + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/test/src/meshgen.cpp b/test/src/meshgen.cpp new file mode 100644 index 0000000..a655f1c --- /dev/null +++ b/test/src/meshgen.cpp @@ -0,0 +1,135 @@ +#include "meshgen.hpp" + +#include +#include +#include + +#include + +#include + +std::unique_ptr genSphereMesh(float r, int detail, bool windInside) +{ + using namespace glm; + + std::vector vertices{}; + + float angleStep = two_pi() / (float)detail; + + for (int i = 0; i < detail; i++) { + // theta goes north-to-south + float theta = i * angleStep; + float theta2 = theta + angleStep; + for (int j = 0; j < detail/2; j++) { + // phi goes west-to-east + float phi = j * angleStep; + float phi2 = phi + angleStep; + + vec3 top_left{ r * sin(phi) * cos(theta), + r * cos(phi), + r * sin(phi) * sin(theta) }; + vec3 bottom_left{ r * sin(phi) * cos(theta2), + r * cos(phi), + r * sin(phi) * sin(theta2) }; + vec3 top_right{ r * sin(phi2) * cos(theta), + r * cos(phi2), + r * sin(phi2) * sin(theta) }; + vec3 bottom_right{ r * sin(phi2) * cos(theta2), + r * cos(phi2), + r * sin(phi2) * sin(theta2) }; + + if (windInside == false) { + // tris are visible from outside the sphere + + // triangle 1 + vertices.push_back({ top_left, {}, {0.0f, 0.0f} }); + vertices.push_back({ bottom_left, {}, {0.0f, 1.0f} }); + vertices.push_back({ bottom_right, {}, {1.0f, 1.0f} }); + // triangle 2 + vertices.push_back({ top_right, {}, {1.0f, 0.0f} }); + vertices.push_back({ top_left, {}, {0.0f, 0.0f} }); + vertices.push_back({ bottom_right, {}, {1.0f, 1.0f} }); + + } + else { + // tris are visible from inside the sphere + + // triangle 1 + vertices.push_back({ bottom_right, {}, {1.0f, 1.0f} }); + vertices.push_back({ bottom_left, {}, {0.0f, 1.0f} }); + vertices.push_back({ top_left, {}, {0.0f, 0.0f} }); + + // triangle 2 + vertices.push_back({ bottom_right, {}, {1.0f, 1.0f} }); + vertices.push_back({ top_left, {}, {0.0f, 0.0f} }); + vertices.push_back({ top_right, {}, {1.0f, 0.0f} }); + + } + + glm::vec3 vector1 = (vertices.end() - 1)->pos - (vertices.end() - 2)->pos; + glm::vec3 vector2 = (vertices.end() - 2)->pos - (vertices.end() - 3)->pos; + glm::vec3 norm = glm::normalize(glm::cross(vector1, vector2)); + + + // TODO: FIX NORMALS + if (!windInside) + norm = -norm; + + for (auto it = vertices.end() - 6; it != vertices.end(); it++) { + it->norm = norm; + } + + } + } + + return std::make_unique(vertices); +} + +std::unique_ptr genCuboidMesh(float x, float y, float z) +{ + + // x goes -> + // y goes ^ + // z goes into the screen + + using glm::vec3; + + std::vector v{}; + + // 0 top_left_front + v.push_back({{ 0.0f, y, 0.0f }, {}, {}}); + // 1 bottom_left_front + v.push_back({{ 0.0f, 0.0f, 0.0f }, {}, {}}); + // 2 top_right_front + v.push_back({{ x, y, 0.0f }, {}, {}}); + // 3 bottom_right_front + v.push_back({{ x, 0.0f, 0.0f }, {}, {}}); + + // 4 top_left_back + v.push_back({{ 0.0f, y, z }, {}, {}}); + // 5 bottom_left_back + v.push_back({{ 0.0f, 0.0f, z }, {}, {}}); + // 6 top_right_back + v.push_back({{ x, y, z }, {}, {}}); + // 7 bottom_right_back + v.push_back({{ x, 0.0f, z }, {}, {}}); + + // front quad + std::vector indices{ + // front + 0, 1, 3, 2, 0, 3, + // back + 4, 5, 7, 6, 4, 7, + // bottom + 5, 1, 3, 7, 5, 3, + // top + 4, 0, 2, 6, 4, 2, + // left + 4, 5, 1, 0, 4, 1, + // right + 2, 3, 7, 6, 2, 7 + }; + + return std::make_unique(v, indices); + +} diff --git a/test/src/meshgen.hpp b/test/src/meshgen.hpp new file mode 100644 index 0000000..d20f15d --- /dev/null +++ b/test/src/meshgen.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include "resources/mesh.hpp" + +// generates a UV sphere +std::unique_ptr genSphereMesh(float r, int detail, bool windInside = false); +std::unique_ptr genCuboidMesh(float x, float y, float z); diff --git a/test/src/terrain.cpp b/test/src/terrain.cpp new file mode 100644 index 0000000..b33312b --- /dev/null +++ b/test/src/terrain.cpp @@ -0,0 +1,21 @@ +#if 0 + +#include "terrain.hpp" + +#include "resources/mesh.hpp" + +std::unique_ptr getChunkMesh(int x, int y) +{ + (void)x; + (void)y; + + std::vector vertices{ + {{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, + {{0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f}} + }; + + return std::make_unique(vertices); +} + +#endif diff --git a/test/src/terrain.hpp b/test/src/terrain.hpp new file mode 100644 index 0000000..5ed5c19 --- /dev/null +++ b/test/src/terrain.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace engine::resources { + class Mesh; +} + +std::unique_ptr getChunkMesh(int x, int y);