diff --git a/.vscode/launch.json b/.vscode/launch.json index e44b60d..f969458 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,7 +3,6 @@ "configurations": [ { "name": "Debug (lldb)", - "preLaunchTask": "CMake: build", "type": "cppdbg", "request": "launch", "program": "${command:cmake.launchTargetPath}", @@ -19,6 +18,5 @@ "console": "externalTerminal", "MIMode": "lldb" }, - ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 1418bba..cf84160 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -82,8 +82,6 @@ "__availability": "cpp" }, "cSpell.words": [ - "Cooldown", - "cpptrace", "emscripten", "Emscripten", "EMSDK", @@ -95,9 +93,7 @@ "SMPTE", "substeps" ], - "cmake.configureOnOpen": true, - "cmake.buildBeforeRun": true, - "cmake.parseBuildDiagnostics": true, + "cmake.configureOnOpen": false, "C_Cpp.intelliSenseEngine": "disabled", "clangd.path": "/usr/bin/clangd", "clangd.arguments": [ diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 3266555..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "CMake: launch current target", - "type": "shell", - "command": "${command:cmake.launchTargetPath}", - "args": [], - "group": "build", - "dependsOn": "CMake: build", - }, - { - "type": "cmake", - "label": "CMake: build", - "command": "build", - "targets": [ - "asteroids" - ], - "preset": "${command:cmake.activeBuildPresetName}", - "group": "build", - "problemMatcher": [], - "detail": "CMake template build task" - } - ] -} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9de58f9..e052cc5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,34 +59,14 @@ function(define_project_variables) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) endif() - if("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) - endif() - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O3") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CXX_STANDARD 23) - # Set colors for ninja - if(CMAKE_GENERATOR STREQUAL "Ninja") - if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always") - elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") - endif() - - if("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always") - elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics") - endif() - endif() - # For clang use -std=c++23 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++23") @@ -158,35 +138,6 @@ add_subdirectory(lib) add_executable(${PROJECT_NAME} ${SOURCES}) define_target_properties(${PROJECT_NAME}) -# region dSYM -if(APPLE) - add_custom_command( - TARGET ${PROJECT_NAME} - POST_BUILD - COMMAND dsymutil $ - ) -endif() -# endregion - -# # region Run assets.sh after build -# add_custom_command( -# TARGET ${PROJECT_NAME} -# PRE_BUILD -# COMMAND sh ${CMAKE_SOURCE_DIR}/scripts/assets.sh -# WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} -# ) -# # endregion - -# region Format -file(GLOB_RECURSE FORMAT_SOURCES "src/core/*.cpp" "src/core/*.hpp") -add_custom_target(${PROJECT_NAME}_format - COMMAND clang-format -i ${FORMAT_SOURCES} - COMMENT "Running clang-format" - DEPENDS ${FORMAT_SOURCES} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} -) -# endregion - # region Linter file(GLOB_RECURSE LINT_SOURCES "src/core/*.cpp" "src/core/*.hpp") add_custom_target(${PROJECT_NAME}_lint @@ -194,10 +145,8 @@ add_custom_target(${PROJECT_NAME}_lint COMMENT "Running clang-tidy" DEPENDS ${LINT_SOURCES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} -) -# endregion - -# endregion# region Clean +)# endregion +# region Clean add_custom_target(${PROJECT_NAME}_clean COMMAND sh ${CMAKE_SOURCE_DIR}/scripts/clean.sh COMMENT "Running clean.sh" diff --git a/assets/audio/explosion.wav b/assets/audio/explosion.wav deleted file mode 100644 index ffe6a67..0000000 Binary files a/assets/audio/explosion.wav and /dev/null differ diff --git a/assets/audio/hit.wav b/assets/audio/hit.wav deleted file mode 100644 index 40506af..0000000 Binary files a/assets/audio/hit.wav and /dev/null differ diff --git a/assets/audio/original/explosion.rfx b/assets/audio/original/explosion.rfx deleted file mode 100644 index 97f4f33..0000000 Binary files a/assets/audio/original/explosion.rfx and /dev/null differ diff --git a/assets/audio/original/hit.sfxr b/assets/audio/original/hit.sfxr deleted file mode 100644 index 8009c5a..0000000 --- a/assets/audio/original/hit.sfxr +++ /dev/null @@ -1,29 +0,0 @@ -{ - "oldParams": true, - "wave_type": 3, - "p_env_attack": 0, - "p_env_sustain": 0.3400512095246295, - "p_env_punch": 0.48519889379528036, - "p_env_decay": 0.3972516406875707, - "p_base_freq": 0.05054721600947243, - "p_freq_limit": 0, - "p_freq_ramp": -0.008087208760721859, - "p_freq_dramp": 0, - "p_vib_strength": 0, - "p_vib_speed": 0, - "p_arp_mod": 0, - "p_arp_speed": 0, - "p_duty": 0, - "p_duty_ramp": 0, - "p_repeat_speed": 0.5865777415692228, - "p_pha_offset": 0, - "p_pha_ramp": 0, - "p_lpf_freq": 1, - "p_lpf_ramp": 0, - "p_lpf_resonance": 0, - "p_hpf_freq": 0, - "p_hpf_ramp": 0, - "sound_vol": 0.25, - "sample_rate": 44100, - "sample_size": 16 -} diff --git a/assets/audio/original/shoot.sfxr b/assets/audio/original/shoot.sfxr deleted file mode 100644 index 8153717..0000000 --- a/assets/audio/original/shoot.sfxr +++ /dev/null @@ -1,29 +0,0 @@ -{ - "oldParams": true, - "wave_type": 0, - "p_env_attack": 0, - "p_env_sustain": 0.186, - "p_env_punch": 0.14416162357904438, - "p_env_decay": 0.359, - "p_base_freq": 0.515, - "p_freq_limit": 0.134, - "p_freq_ramp": -0.2619783169500224, - "p_freq_dramp": 0, - "p_vib_strength": 0.283, - "p_vib_speed": 1, - "p_arp_mod": -0.053, - "p_arp_speed": 0, - "p_duty": 0.718, - "p_duty_ramp": -0.158, - "p_repeat_speed": 0, - "p_pha_offset": 0.189, - "p_pha_ramp": -0.234, - "p_lpf_freq": 1, - "p_lpf_ramp": 0, - "p_lpf_resonance": 0, - "p_hpf_freq": 0.126, - "p_hpf_ramp": 0, - "sound_vol": 0.25, - "sample_rate": 44100, - "sample_size": 16 -} \ No newline at end of file diff --git a/assets/audio/original/shoot2.sfxr b/assets/audio/original/shoot2.sfxr deleted file mode 100644 index 45a1b40..0000000 --- a/assets/audio/original/shoot2.sfxr +++ /dev/null @@ -1,29 +0,0 @@ -{ - "oldParams": true, - "wave_type": 1, - "p_env_attack": 0, - "p_env_sustain": 0.103, - "p_env_punch": 0.254, - "p_env_decay": 0.76, - "p_base_freq": 0.605, - "p_freq_limit": 0.185, - "p_freq_ramp": -0.285, - "p_freq_dramp": 0.06, - "p_vib_strength": 0.361, - "p_vib_speed": 0, - "p_arp_mod": -0.712, - "p_arp_speed": 0, - "p_duty": 0.26, - "p_duty_ramp": 0.647, - "p_repeat_speed": 0, - "p_pha_offset": -0.007, - "p_pha_ramp": -0.024, - "p_lpf_freq": 1, - "p_lpf_ramp": 0, - "p_lpf_resonance": 0, - "p_hpf_freq": 0.021623842742273, - "p_hpf_ramp": 0, - "sound_vol": 0.25, - "sample_rate": 44100, - "sample_size": 16 -} \ No newline at end of file diff --git a/assets/audio/original/spawn.rfx b/assets/audio/original/spawn.rfx deleted file mode 100644 index 702a07f..0000000 Binary files a/assets/audio/original/spawn.rfx and /dev/null differ diff --git a/assets/audio/shoot.wav b/assets/audio/shoot.wav deleted file mode 100644 index dc10966..0000000 Binary files a/assets/audio/shoot.wav and /dev/null differ diff --git a/assets/audio/spawn.wav b/assets/audio/spawn.wav deleted file mode 100644 index 61ccd45..0000000 Binary files a/assets/audio/spawn.wav and /dev/null differ diff --git a/assets/audio/thrust.wav b/assets/audio/thrust.wav deleted file mode 100644 index 779371b..0000000 Binary files a/assets/audio/thrust.wav and /dev/null differ diff --git a/assets/audio/thrust2.wav b/assets/audio/thrust2.wav deleted file mode 100644 index e71dd34..0000000 Binary files a/assets/audio/thrust2.wav and /dev/null differ diff --git a/assets/shaders/all.fs b/assets/shaders/all.fs deleted file mode 100644 index 2f9c850..0000000 --- a/assets/shaders/all.fs +++ /dev/null @@ -1,114 +0,0 @@ -#version 330 - -precision mediump float; - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -uniform float aberration; - -#define USE_CHROMATIC_ABERRATION -#define USE_BLOOM -#define USE_GAMMA_CORRECTION -#define USE_SATURATION -#define USE_CONTRAST -#define USE_BRIGHTNESS -#define USE_PIXELATION - -void applyPixelation(inout vec2 normalizedFragTexCoord) { -#ifdef USE_PIXELATION - float pixelation = 0.005; - float pixelationIntensity = pow(aberration, 1.0 / 2.0) * 0.5; - - vec2 pixelatedFragTexCoord = floor(normalizedFragTexCoord / pixelation) * pixelation; - normalizedFragTexCoord = mix(normalizedFragTexCoord, pixelatedFragTexCoord, pixelationIntensity); -#endif -} - -void applyChromaticAberration(inout vec4 texelColor, in vec2 normalizedFragTexCoord, in vec2 texelSize) { -#ifdef USE_CHROMATIC_ABERRATION - float edgeFactor = max(length(normalizedFragTexCoord - 0.5) * 2.0, 0.35); - float aberrationIntensity = edgeFactor * aberration * 1.5; - vec2 aberrationOffset = vec2(aberrationIntensity, -aberrationIntensity); - float red = texture(texture0, normalizedFragTexCoord + aberrationOffset * texelSize).r; - float green = texture(texture0, normalizedFragTexCoord).g; - float blue = texture(texture0, normalizedFragTexCoord - aberrationOffset * texelSize).b; - texelColor.rgb = vec3(red, green, blue); -#endif -} - -void applyGammaCorrection(inout vec4 texelColor) { -#ifdef USE_GAMMA_CORRECTION - float gammmaCorrectionIntensity = 0.25; - texelColor.rgb = mix(texelColor.rgb, pow(texelColor.rgb, vec3(1.0 / 2.2)), gammmaCorrectionIntensity); -#endif -} - -void applyBloom(inout vec4 texelColor, in vec2 normalizedFragTexCoord, in vec2 texelSize) { -#ifdef USE_BLOOM - float bloomIntensity = 0.95; - float bloomThreshold = 0.60; - int bloomRadius = 4; - vec4 bloomColor = vec4(0.0); - for (int i = -bloomRadius; i <= bloomRadius; i++) { - for (int j = -bloomRadius; j <= bloomRadius; j++) { - vec2 offset = vec2(i, j) * texelSize; - vec4 bloomTexelColor = texture(texture0, normalizedFragTexCoord + offset); - if (length(bloomTexelColor.rgb) > bloomThreshold) { - bloomColor += bloomTexelColor; - } - } - } - bloomColor /= float((bloomRadius * 2 + 1) * (bloomRadius * 2 + 1)); - // Tint the bloom color - vec4 tint = vec4(1.0, 0.9, 0.7, 1.0); - bloomColor *= tint; - - texelColor.rgb += bloomColor.rgb * bloomIntensity; -#endif -} - -void applySaturation(inout vec4 texelColor) { -#ifdef USE_SATURATION - float saturation = 1.25; - float luminance = dot(texelColor.rgb, vec3(0.2126, 0.7152, 0.0722)); - texelColor.rgb = mix(vec3(luminance), texelColor.rgb, saturation); -#endif -} - -void applyContrast(inout vec4 texelColor) { -#ifdef USE_CONTRAST - float contrast = 0.98; - texelColor.rgb = (texelColor.rgb - 0.5) * pow(contrast, 2.0) + 0.5; -#endif -} - -void applyBrightness(inout vec4 texelColor) { -#ifdef USE_BRIGHTNESS - float brightness = 1.05; - texelColor.rgb *= pow(brightness, 2.0); -#endif -} - -void main() { - vec2 texelSize = 1.0 / textureSize(texture0, 0); - // Flip vertically to match OpenGL's texture coordinate system - vec2 normalizedFragTexCoord = vec2(fragTexCoord.x, 1.0 - fragTexCoord.y); - - applyPixelation(normalizedFragTexCoord); - - vec4 texelColor = texture(texture0, normalizedFragTexCoord); - - applyChromaticAberration(texelColor, normalizedFragTexCoord, texelSize); - applyGammaCorrection(texelColor); - applyBloom(texelColor, normalizedFragTexCoord, texelSize); - - applySaturation(texelColor); - applyContrast(texelColor); - applyBrightness(texelColor); - - finalColor = texelColor * colDiffuse * fragColor; -} \ No newline at end of file diff --git a/assets/shaders/background.fs b/assets/shaders/background.fs deleted file mode 100644 index c632515..0000000 --- a/assets/shaders/background.fs +++ /dev/null @@ -1,35 +0,0 @@ -#version 330 - -precision mediump float; - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -uniform vec2 resolution; - -vec3 applyVignetteLinear(vec3 color) { - // Apply vignette - float radius = 0.95; - float softness = 0.9999; - float intensity = 0.6; - vec2 position = gl_FragCoord.xy / resolution.xy; - - float vignette = smoothstep(radius, radius - softness, length(position - 0.5)); - return mix(color, color * vignette, intensity); -} - -void main() { - // DEBUG: Render gl_FragCoord gradient - // finalColor = vec4(gl_FragCoord.x / resolution.x, gl_FragCoord.y / resolution.y, 0.0, 1.0); return; - - finalColor = vec4(0.0, 0.06, 0.1, 1.0); - - // DEBUG: Apply gamma correction - // finalColor.rgb = pow(finalColor.rgb, vec3(1.0 / 2.2)); - - // Apply vignette - finalColor.rgb = applyVignetteLinear(finalColor.rgb); -} \ No newline at end of file diff --git a/assets/shaders/particles.fs b/assets/shaders/particles.fs deleted file mode 100644 index 2fc53cb..0000000 --- a/assets/shaders/particles.fs +++ /dev/null @@ -1,22 +0,0 @@ -#version 330 - -precision mediump float; - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -void main() { - // Flip vertically to match OpenGL's texture coordinate system - vec4 texelColor = fragColor; - - // Apply color correction - texelColor = pow(texelColor, vec4(2.45)); - texelColor.r *= 0.95; - texelColor.g *= 0.95; - texelColor.b *= 1.05; - - finalColor = texelColor * colDiffuse; -} \ No newline at end of file diff --git a/assets/shaders/stars.fs b/assets/shaders/stars.fs deleted file mode 100644 index 3683916..0000000 --- a/assets/shaders/stars.fs +++ /dev/null @@ -1,55 +0,0 @@ -#version 330 - -precision mediump float; - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -// Bloom configuration constants -const int radius = 2; -const float threshold = 0.35; -const float intensity = 0.95; -const float size = 1.25; -const float PI = 3.14159; - -float calculateLuminance(vec4 color) { - return dot(color.rgb, vec3(0.2126, 0.7152, 0.0722)); -} - -void main() { - // Texel size - vec2 texelSize = 1.0 / textureSize(texture0, 0); - vec2 normalizedFragTexCoord = vec2(fragTexCoord.x, 1.0 - fragTexCoord.y); - - // Flip vertically to match OpenGL's texture coordinate system - vec4 texelColor = texture(texture0, normalizedFragTexCoord); - - // Apply gamma correction - texelColor = pow(texelColor, vec4(2.2)); - - // Calculate luminance - float luminance = calculateLuminance(texelColor); - - // Apply bloom - vec4 bloom = vec4(0.0); - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { - vec2 offset = vec2(i, j) * texelSize * size; - vec4 sample = texture(texture0, normalizedFragTexCoord + offset); - float sampleLuminance = calculateLuminance(sample); - if (sampleLuminance > threshold) { - bloom += sample * intensity; - } - } - } - bloom /= (radius * radius * 4.0 + 2.0); - texelColor += bloom * intensity; - texelColor.a *= 0.75; - - // texelColor = vec4(1.0, 1.0, 0.0, 1.0); - - finalColor = texelColor * colDiffuse * fragColor; -} \ No newline at end of file diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 3ca1852..256bad4 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -76,13 +76,13 @@ function(include_library name version source) # Include if(EXISTS ${INSTALL_PATH}/${name}-${version}/CMakeLists.txt) - message(STATUS "Including library's CMakeLists.txt ${name} ${version}") + message(STATUS "Including library's directory ${name} ${version}") add_subdirectory(${INSTALL_PATH}/${name}-${version} ${CMAKE_CURRENT_BINARY_DIR}/${name}-${version}) elseif(EXISTS ${INSTALL_PATH}/${name}-${version}/projects/CMake/CMakeLists.txt) - message(STATUS "Including library's projects/CMake/CMakeLists.txt ${name} ${version}") + message(STATUS "Including library's directory ${name} ${version}") add_subdirectory(${INSTALL_PATH}/${name}-${version}/projects/CMake ${CMAKE_CURRENT_BINARY_DIR}/${name}-${version}) elseif(EXISTS ${INSTALL_PATH}/${name}-${version}/include) - message(STATUS "Including library 'include' folder ${name} ${version}") + message(STATUS "Including library ${name} ${version}") add_library(${name} INTERFACE) else() # Check if on the root there is .h, .hpp, .c or .cpp files diff --git a/scripts/assets.sh b/scripts/assets.sh deleted file mode 100644 index 269ad3b..0000000 --- a/scripts/assets.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -set -e - -# Get current directory -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -SOURCE_DIR=$DIR/assets -TARGET_DIR=$DIR/build/assets - -# Check if there is already a symlink to the assets directory -if [ -L $TARGET_DIR ]; then - echo "Symlink already exists" -else - echo "Creating symlink" - ln -s $SOURCE_DIR $TARGET_DIR - - if [ $? -eq 0 ]; then - echo "Symlink created" - else - echo "Symlink creation failed" - fi -fi \ No newline at end of file diff --git a/src/core/data/dummy-list.cpp b/src/core/data/dummy-list.cpp index f7afc2a..284ceae 100644 --- a/src/core/data/dummy-list.cpp +++ b/src/core/data/dummy-list.cpp @@ -37,6 +37,6 @@ std::vector> DummyList::retrieve(raylib::Vector2 posit return asteroids; } -std::vector> DummyList::getAll() { +std::vector> DummyList::all() { return asteroids; } \ No newline at end of file diff --git a/src/core/data/dummy-list.hpp b/src/core/data/dummy-list.hpp index 8ead2ec..b98a307 100644 --- a/src/core/data/dummy-list.hpp +++ b/src/core/data/dummy-list.hpp @@ -22,5 +22,5 @@ class DummyList : public IContainer { void insert(std::shared_ptr asteroid) override; void remove(std::shared_ptr asteroid) override; std::vector> retrieve(raylib::Vector2 position, uint16_t radius) override; - std::vector> getAll() override; + std::vector> all() override; }; \ No newline at end of file diff --git a/src/core/data/icontainer.hpp b/src/core/data/icontainer.hpp index 516753d..b5fbd0d 100644 --- a/src/core/data/icontainer.hpp +++ b/src/core/data/icontainer.hpp @@ -9,12 +9,11 @@ class IContainer { virtual void clear() = 0; virtual bool isEmpty() = 0; virtual uint32_t size() = 0; - virtual bool isInitialized() = 0; virtual uint16_t getCellIndex(raylib::Vector2 position) = 0; virtual raylib::Vector2 getCellPosition(uint16_t index) = 0; virtual void resize(uint16_t rows, uint16_t cols) = 0; virtual void insert(std::shared_ptr asteroid) = 0; virtual void remove(std::shared_ptr asteroid) = 0; virtual std::vector> retrieve(raylib::Vector2 position, uint16_t radius) = 0; - virtual std::vector> getAll() = 0; + virtual std::vector> all() = 0; }; \ No newline at end of file diff --git a/src/core/data/spatial-hash-grid.cpp b/src/core/data/spatial-hash-grid.cpp index 78b972f..27f7c8a 100644 --- a/src/core/data/spatial-hash-grid.cpp +++ b/src/core/data/spatial-hash-grid.cpp @@ -1,39 +1,27 @@ #include "spatial-hash-grid.hpp" #include "../settings.hpp" #include "../models/asteroid.hpp" -#include "Color.hpp" -#include "Rectangle.hpp" -#include "icontainer.hpp" -#include "raylib.h" -#include void SpatialHashGrid::clear() { for (auto &cell : cells) { cell.asteroids.resize(0); } - cache.clear(); + count = 0; } bool SpatialHashGrid::isEmpty() { - return cells.empty() || cache.empty(); -} - -bool SpatialHashGrid::isInitialized() { - return !cells.empty(); + return cells.empty() || count <= 0; } uint32_t SpatialHashGrid::size() { - return cache.size(); + return count; } uint16_t SpatialHashGrid::getCellIndex(raylib::Vector2 position) { - uint16_t row = position.y / cellSize; - uint16_t col = position.x / cellSize; - if (row < 0) row = 0; - else if (row >= rows) row = rows - 1; - if (col < 0) col = 0; - else if (col >= cols) col = cols - 1; - return row * cols + col; + uint16_t index = (uint16_t)(position.y / cellSize) * cols + (uint16_t)(position.x / cellSize); + if (index < 0) index = 0; + if (index >= cells.size()) index = cells.size() - 1; + return index; } raylib::Vector2 SpatialHashGrid::getCellPosition(uint16_t index) { @@ -41,11 +29,9 @@ raylib::Vector2 SpatialHashGrid::getCellPosition(uint16_t index) { } void SpatialHashGrid::resize(uint16_t rows, uint16_t cols) { - std::lock_guard lock(mutex); - - const auto backup = cache; - cache.clear(); + const auto backup = all(); cells.clear(); + count = 0; cells.resize(rows * cols); this->rows = rows; @@ -53,50 +39,36 @@ void SpatialHashGrid::resize(uint16_t rows, uint16_t cols) { cellSize = std::min(ceilf(HEIGHT / (float)rows), ceilf(WIDTH / (float)cols)); - for (auto &asteroid : backup) insert(asteroid); - - // Assign color palette - for (uint16_t row = 0; row < rows; row++) { - for (uint16_t col = 0; col < cols; col++) { - const float index = (row * cols + col) / (float)(rows * cols) * 240.0f; - cells[index].color = raylib::Color::FromHSV(index, 0.5f, 0.5f); - } + for (auto &asteroid : backup) { + insert(asteroid); } } void SpatialHashGrid::insert(std::shared_ptr asteroid) { - std::lock_guard lock(mutex); - auto index = getCellIndex(asteroid->position); - asteroid->gridCellIndex = index; + asteroid->index = index; asteroid->color = cells[index].color; cells[index].asteroids.push_back(asteroid); - cache.push_back(asteroid); + count++; } void SpatialHashGrid::remove(std::shared_ptr asteroid) { - std::lock_guard lock(mutex); - auto index = getCellIndex(asteroid->position); - asteroid->gridCellIndex = -1; + asteroid->index = -1; cells[index].asteroids.remove(asteroid); - cache.erase(std::remove(cache.begin(), cache.end(), asteroid), cache.end()); + count--; } void SpatialHashGrid::update() { - std::lock_guard lock(mutex); - for (auto &cell : cells) { auto it = cell.asteroids.begin(); while (it != cell.asteroids.end()) { - const auto &asteroid = *it; - const uint16_t currentIndex = asteroid->gridCellIndex; - const uint16_t newIndex = getCellIndex(asteroid->position); - if (currentIndex != newIndex) { - asteroid->gridCellIndex = newIndex; - asteroid->color = cells[newIndex].color; - cells[newIndex].asteroids.push_back(asteroid); - it = cell.asteroids.erase(it); + uint16_t index = getCellIndex(it->get()->position); + if (index != it->get()->index) { + it->get()->index = index; + it->get()->color = cell.color; + cells[index].asteroids.push_back(*it); + it = cells[it->get()->index].asteroids.erase(it); } else { it++; } @@ -105,8 +77,6 @@ void SpatialHashGrid::update() { } std::vector> SpatialHashGrid::retrieve(raylib::Vector2 position, uint16_t radius) { - std::lock_guard lock(mutex); - // Get the adjacent cells based on the radius uint16_t startRow = std::max(0, (int)(position.y - radius) / cellSize); uint16_t endRow = std::min(rows - 1, (int)(position.y + radius) / cellSize); @@ -117,10 +87,8 @@ std::vector> SpatialHashGrid::retrieve(raylib::Vector2 for (uint16_t row = startRow; row <= endRow; row++) { for (uint16_t col = startCol; col <= endCol; col++) { uint16_t index = row * cols + col; - if (index >= cells.size() || index < 0) continue; - for (auto &asteroid : cells[index].asteroids) { - if (asteroid && position.Distance(asteroid->position) <= radius) { + if (position.Distance(asteroid->position) <= radius) { result.push_back(asteroid); } } @@ -130,19 +98,19 @@ std::vector> SpatialHashGrid::retrieve(raylib::Vector2 return result; } -std::vector> SpatialHashGrid::getAll() { - return cache; +std::vector> SpatialHashGrid::all() { + std::vector> result; + for (auto &cell : cells) { + result.insert(result.end(), cell.asteroids.begin(), cell.asteroids.end()); + } + return result; } void SpatialHashGrid::render() { for (uint16_t row = 0; row < rows; row++) { for (uint16_t col = 0; col < cols; col++) { - const uint16_t index = row * cols + col; - const raylib::Rectangle rect(col * cellSize, row * cellSize, cellSize, cellSize); - DrawRectangleLinesEx(rect, 1.0f, cells[index].color); - cells[index].color.SetA(50); - DrawRectangleRec(rect, cells[index].color); - cells[index].color.SetA(255); + uint16_t index = row * cols + col; + DrawRectangleLines(col * cellSize, row * cellSize, cellSize, cellSize, cells[index].color); DrawText(TextFormat("%d", index), col * cellSize + 5, row * cellSize + 5, 10, WHITE); } } diff --git a/src/core/data/spatial-hash-grid.hpp b/src/core/data/spatial-hash-grid.hpp index 6afdc37..ef5e69d 100644 --- a/src/core/data/spatial-hash-grid.hpp +++ b/src/core/data/spatial-hash-grid.hpp @@ -3,7 +3,6 @@ #include "../precomp.hpp" #include "icontainer.hpp" #include "../utils.hpp" -#include class SpatialHashGrid : public IContainer { private: @@ -11,9 +10,8 @@ class SpatialHashGrid : public IContainer { std::list> asteroids; raylib::Color color = randomColor(); }; - std::vector> cache; std::vector cells; - std::mutex mutex; + uint16_t count; public: uint16_t rows; @@ -25,7 +23,6 @@ class SpatialHashGrid : public IContainer { void clear() override; bool isEmpty() override; - bool isInitialized() override; uint32_t size() override; uint16_t getCellIndex(raylib::Vector2 position) override; raylib::Vector2 getCellPosition(uint16_t index) override; @@ -34,6 +31,6 @@ class SpatialHashGrid : public IContainer { void remove(std::shared_ptr asteroid) override; void update(); std::vector> retrieve(raylib::Vector2 position, uint16_t radius) override; - std::vector> getAll() override; + std::vector> all() override; void render(); }; \ No newline at end of file diff --git a/src/core/main.cpp b/src/core/main.cpp index 2ce78c1..566d66d 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -1,21 +1,54 @@ #include "precomp.hpp" +#include "raylib.h" +#include "settings.hpp" #include "models/app.hpp" -#if defined(PLATFORM_DESKTOP) - #define GLSL_VERSION 330 -#else // PLATFORM_ANDROID, PLATFORM_WEB - #define GLSL_VERSION 100 -#endif +App app; + +void render() { + BeginDrawing(); + ClearBackground(BLACK); + app.renderGUI(); + EndDrawing(); + + app.update(); + app.render(); +} int main() { - /* int* p = NULL; - *p = 0; */ + SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI); + +#ifdef PLATFORM_WEB + InitWindow(WIDTH, HEIGHT, "Asteroids"); +#else + const int monitor = GetCurrentMonitor(); + if (monitor > 0) { + WIDTH = GetMonitorWidth(monitor) / 2.0f; + HEIGHT = GetMonitorHeight(monitor) / 2.0f; + TARGET_FPS = GetMonitorRefreshRate(monitor); + + TraceLog(LOG_INFO, "Monitor: %i", monitor); + TraceLog(LOG_INFO, "Viewport: %i, %i", WIDTH, HEIGHT); + TraceLog(LOG_INFO, "Refresh Rate: %i", TARGET_FPS); + } - char* p = new char[10]; + InitWindow(WIDTH, HEIGHT, "Asteroids"); + SetWindowMinSize(WIDTH, HEIGHT); +#endif + + app.setup(); + +#ifdef PLATFORM_WEB + emscripten_set_main_loop(render, 0, 1); +#else + SetTargetFPS(TARGET_FPS); + + while (!WindowShouldClose()) { + render(); + } +#endif - auto app = std::make_shared(); - app->setup(); - app->run(); + CloseWindow(); return 0; } \ No newline at end of file diff --git a/src/core/models/app.cpp b/src/core/models/app.cpp index bad9ac8..f759728 100644 --- a/src/core/models/app.cpp +++ b/src/core/models/app.cpp @@ -1,131 +1,57 @@ #include "app.hpp" #include "../settings.hpp" -#include "Shader.hpp" -#include "Vector2.hpp" +#include "RenderTexture.hpp" #include "asteroid.hpp" -#include "particle.hpp" +#include "imgui.h" +#include "polygon.hpp" #include "raylib.h" -#include "raymath.h" -#include "soundfx.hpp" -#include "wave.hpp" #include -#include - -App::App(): starsShader("assets/shaders/stars.fs"), particlesShader("assets/shaders/particles.fs"), allShader("assets/shaders/all.fs"), backgroundShader("assets/shaders/background.fs") { - TraceLog(LOG_INFO, "App::App()"); - - // Setup camera - camera.offset = raylib::Vector2::Zero(); - camera.target = raylib::Vector2::Zero(); - camera.zoom = 1.0f; - camera.rotation = 0.0f; -} +#include App::~App() { TraceLog(LOG_INFO, "App::~App()"); rlImGuiShutdown(); - SoundFX::release(); - CloseAudioDevice(); - CloseWindow(); } -void App::setup() { -#ifdef DEBUG - SetTraceLogLevel(LOG_DEBUG); -#endif - TraceLog(LOG_INFO, "App::setup()"); - - setupWindow(); - - // Shaders - backgroundShader.load(); - particlesShader.load(); - starsShader.load(); - allShader.load(); - - // Setup uniforms - uniformAberrationLocation = allShader.getUniformLocation("aberration"); - uniformAberrationValue = 0.0f; - uniformResolutionLocation = backgroundShader.getUniformLocation("resolution"); - uniformResolutionValue = raylib::Vector2((float)WIDTH, (float)HEIGHT); - - // Misc - setupGUI(); - wave.start(shared_from_this()); - SoundFX::setup(); +void App::shoot() { + Bullet bullet { + ship.position, + raylib::Vector2(cosf(ship.angle), sinf(ship.angle)) * BULLET_VELOCITY + }; + bullets.push_back(bullet); } -void App::setupWindow() { - SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI); +void App::setup() { #ifdef DEBUG - SetConfigFlags(FLAG_WINDOW_TOPMOST); -#endif - -#ifdef PLATFORM_WEB - InitWindow(WIDTH, HEIGHT, "Asteroids"); -#else - const int monitor = GetCurrentMonitor(); - const int monitorCount = GetMonitorCount(); - TraceLog(LOG_INFO, "Main::main() - Monitor: %i of %i", monitor, monitorCount); - if (monitor >= 0 && monitorCount > 0) { - WIDTH = GetMonitorWidth(monitor) / 2.0f; - HEIGHT = GetMonitorHeight(monitor) / 2.0f; - TARGET_FPS = GetMonitorRefreshRate(monitor); - - TraceLog(LOG_INFO, "Main::main() - Monitor id: %i", monitor); - TraceLog(LOG_INFO, "Main::main() - Monitor viewport: %ix%i", WIDTH, HEIGHT); - TraceLog(LOG_INFO, "Main::main() - Monitor refresh Rate: %i", TARGET_FPS); - } - - InitWindow(WIDTH, HEIGHT, "Asteroids"); - SetWindowMinSize(WIDTH, HEIGHT); -#endif - - InitAudioDevice(); -} - -void App::run() { -#ifdef PLATFORM_WEB - emscripten_set_main_loop(loop, 0, 1); -#else - SetTargetFPS(TARGET_FPS); - while (!WindowShouldClose()) { - loop(); - } + SetTraceLogLevel(LOG_DEBUG); #endif -} -void App::setupStars() { - const size_t count = 150; + TraceLog(LOG_INFO, "App::setup()"); - stars.clear(); - stars.reserve(count); + rlImGuiSetup(true); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - for (size_t i = 0; i < count; i++) { - stars.emplace_back( - raylib::Vector2( - getRandomValue(0, WIDTH), - getRandomValue(0, HEIGHT) - ), - getRandomValue(0.75f, 4.5f) * 1.25f - ); - } + ship.setup(); } void App::updateAsteroids() { Asteroid::collisionCount = 0; - const int substeps = 1; - const float dt = GetFrameTime() / substeps; + for (auto &asteroid : asteroids.all()) asteroid->update(asteroids); - for (auto &asteroid : grid.getAll()) asteroid->update(grid, dt); - - grid.update(); + asteroids.update(); } void App::updateBullets() { + if (shootTimer > BULLET_SHOOT_INTERVAL) shootTimer = BULLET_SHOOT_INTERVAL; + else shootTimer += GetFrameTime(); + if (shootTimer >= BULLET_SHOOT_INTERVAL && IsKeyDown(KEY_SPACE)) { + shoot(); + shootTimer -= BULLET_SHOOT_INTERVAL; + } + for (auto it = bullets.begin(); it != bullets.end();) { - if (it->update(grid)) { - shake(0.25f); + if (it->update(asteroids)) { it = bullets.erase(it); } else { it++; @@ -133,165 +59,32 @@ void App::updateBullets() { } } -void App::updateStars(raylib::Vector2 &offset) { - if (fabsf(offset.x) < WIDTH / 2.0f && fabsf(offset.y) < HEIGHT / 2.0f) { - for (auto &star : stars) { - star.update(offset); - } - } -} - -void App::updateScreenShake() { - - if (IsKeyDown(KEY_ENTER)) trauma += 0.15f; - if (trauma >= 1.0f) trauma = 1.0f; - - if (trauma <= EPSILON) return; - - const float traumaPower = powf(trauma, 2.0f); - const auto center = raylib::Vector2(WIDTH / 2.0f, HEIGHT / 2.0f); - cameraShakeOffset = raylib::Vector2(GetRandomValue(-1, 1), GetRandomValue(-1, 1)) * traumaPower * 25.5f; - cameraShakeRotation = GetRandomValue(-1, 1) * traumaPower * 15.5f; - - camera.offset = Vector2Lerp(camera.offset, cameraShakeOffset + center, GetFrameTime() * 25.0f); - camera.rotation = Lerp(camera.rotation, cameraShakeRotation, GetFrameTime() * 10.0f); - camera.zoom = Lerp(camera.zoom, /* 1.0f + */ 1.0f - traumaPower * 0.1f, GetFrameTime() * 10.0f); - - uniformAberrationValue = traumaPower * 5.0f; - allShader.setUniform(uniformAberrationLocation, &uniformAberrationValue, ShaderUniformDataType::SHADER_UNIFORM_FLOAT); - - trauma *= 0.92f; - if (trauma <= EPSILON) { - trauma = 0.0f; - camera.offset = center; - camera.rotation = 0.0f; - camera.zoom = 1.0f; - } -} - -void App::shake(float amount) { - trauma += amount; - if (trauma >= 1.0f) trauma = 1.0f; -} - -void App::loop() { - BeginDrawing(); - ClearBackground(BLACK); - renderGUI(); - EndDrawing(); - - update(); - render(); -} - -static long lastShaderUpdateTime = 0; - void App::update() { - profiler.onFrameStart(); - updateScreenShake(); updateAsteroids(); updateBullets(); - ParticleSystem::update(); - - auto offset = ship.position; - ship.update(bullets); - offset = ship.position - offset; - updateStars(offset); - -#ifdef DEBUG - backgroundShader.autoReloadIfNeeded(); - particlesShader.autoReloadIfNeeded(); - starsShader.autoReloadIfNeeded(); - allShader.autoReloadIfNeeded(); - - backgroundShader.setUniform(uniformResolutionLocation, &uniformResolutionValue, ShaderUniformDataType::SHADER_UNIFORM_VEC2); -#endif - - // TODO: Research bout the spaceship that appears sometimes, is it an enemy? - - // TODO: Move to function, and maybe ship - // TODO: Improve particles - // const auto reflectedAngle = ship.angle + PI; - // const auto shipDirection = raylib::Vector2(cosf(reflectedAngle), sinf(reflectedAngle)); - // const auto shipBottom = ship.position + shipDirection * 10.0f; - // const int count = 5; - // for (int i = 0; i < count; i++) { - // const float angle = (180.0f / count * i) * DEG2RAD + ship.angle + PI / 2.0f + getRandomValue(-0.1f, 0.1f); - // const auto direction = raylib::Vector2(cosf(angle), sinf(angle)); - // const auto position = shipBottom; - // const auto velocity = direction * ship.velocity; - // particles.emplace_back( - // position, - // velocity, - // LIGHTGRAY, - // 2.0f, - // 0.25f - // ); - // } + ship.update(); + wave.update(asteroids, bullets, ship); } void App::render() { - if (!frameBuffer.IsReady() || !tempFrameBuffer.IsReady()) { - TraceLog(LOG_WARNING, "App::render() - Frame buffer not ready"); - return; - } - - // First pass is drawing the stars to a temporary frame buffer - tempFrameBuffer.BeginMode(); - ClearBackground(raylib::Color(0, 0, 0, 0)); - - tempFrameBuffer.EndMode(); - - // Second pass is drawing the game to the main frame buffer frameBuffer.BeginMode(); + ClearBackground(BLACK); - // Render background - backgroundShader.beginMode(); - DrawRectangle(0, 0, WIDTH, HEIGHT, WHITE); - backgroundShader.endMode(); - - camera.BeginMode(); - - // Render stars - starsShader.beginMode(); - tempFrameBuffer.GetTexture().Draw(0, 0, WHITE); - starsShader.endMode(); - - for (auto &star : stars) star.render(); - - // DEBUG: Render debug grid overlay - if (RENDER_GRID) grid.render(); - - // Render asteroids - for (auto &asteroid : grid.getAll()) asteroid->render(); + for (auto &asteroid : asteroids.all()) asteroid->render(); - // Render bullets for (auto &bullet : bullets) bullet.render(); - // Render particles - particlesShader.beginMode(); - ParticleSystem::render(); - particlesShader.endMode(); - - camera.EndMode(); - frameBuffer.EndMode(); - - // Third pass is drawing the frame buffer to the screen - tempFrameBuffer.BeginMode(); - allShader.beginMode(); - frameBuffer.GetTexture().Draw(0, 0, WHITE); - allShader.endMode(); - // Render space ship ship.render(); - tempFrameBuffer.EndMode(); - profiler.onFrameEnd(); + // Render grid + asteroids.render(); + + frameBuffer.EndMode(); } void App::onResize(uint32_t width, uint32_t height) { TraceLog(LOG_INFO, "App::onResize(%i, %i)", width, height); frameBuffer = raylib::RenderTexture2D(width, height); - tempFrameBuffer = raylib::RenderTexture2D(width, height); // Update global settings WIDTH = width; @@ -299,21 +92,5 @@ void App::onResize(uint32_t width, uint32_t height) { // Update the grid const auto size = (float)ASTEROID_RADIUS * 4; - grid.resize(ceilf(height / size), ceilf(width / size)); - - // Update the camera - camera.offset = raylib::Vector2(width / 2.0f, height / 2.0f); - camera.target = raylib::Vector2(width / 2.0f, height / 2.0f); - - // Update the uniforms - uniformResolutionValue = raylib::Vector2((float)width, (float)height); - backgroundShader.setUniform(uniformResolutionLocation, &uniformResolutionValue, ShaderUniformDataType::SHADER_UNIFORM_VEC2); - - // Update the ship - if (!isViewportDefined) { - isViewportDefined = true; - ship.setup(); - } - - setupStars(); + asteroids.resize(ceilf(HEIGHT / size), ceilf(WIDTH / size)); } diff --git a/src/core/models/app.hpp b/src/core/models/app.hpp index 8632f17..110459c 100644 --- a/src/core/models/app.hpp +++ b/src/core/models/app.hpp @@ -1,74 +1,34 @@ #pragma once #include "../precomp.hpp" -#include "Vector2.hpp" #include "bullet.hpp" -#include "hotreload-shader.hpp" -#include "particle.hpp" -#include "profiler.hpp" #include "ship.hpp" #include "asteroid.hpp" -#include "star.hpp" #include "wave.hpp" -#include "particle.hpp" #include "../data/spatial-hash-grid.hpp" -class App : public std::enable_shared_from_this { +class App { private: - raylib::Camera2D camera; raylib::RenderTexture2D frameBuffer; - raylib::RenderTexture2D tempFrameBuffer; - // Shaders - HotReloadShader starsShader; - HotReloadShader particlesShader; - HotReloadShader backgroundShader; - HotReloadShader allShader; + SpatialHashGrid asteroids; + std::list bullets; + float shootTimer; + Ship ship; - // Uniforms - int uniformResolutionLocation = -1; - raylib::Vector2 uniformResolutionValue; - int uniformAberrationLocation = -1; - float uniformAberrationValue = 0.0f; - - Profiler profiler; WaveController wave; - float trauma; - raylib::Vector2 cameraShakeOffset; - float cameraShakeRotation; - - bool isGUIBuilt; - bool isViewportDefined; - - void setupWindow(); - void loop(); - + void shoot(); void updateAsteroids(); void updateBullets(); - void setupStars(); - - void setupGUI(); - void buildGUIDockSpace(); - - void updateStars(raylib::Vector2 &offset); - void update(); - void render(); - void renderGUI(); - void onResize(uint32_t width, uint32_t height); - void updateScreenShake(); - public: - SpatialHashGrid grid; - std::list bullets; - std::vector stars; - Ship ship; - - App(); + App() = default; ~App(); void setup(); - void run(); - void shake(float amount); + void update(); + void render(); + void onResize(uint32_t width, uint32_t height); + void renderGUI(); }; \ No newline at end of file diff --git a/src/core/models/asteroid.cpp b/src/core/models/asteroid.cpp index bc0f6b0..cdc535e 100644 --- a/src/core/models/asteroid.cpp +++ b/src/core/models/asteroid.cpp @@ -1,208 +1,211 @@ #include "asteroid.hpp" #include "../utils.hpp" -#include "Vector2.hpp" -#include "collision-handler.hpp" -#include "raylib.h" -#include "raymath.h" -#include -#include - -// Static +#include "polygon.hpp" + size_t Asteroid::idCounter = 0; uint32_t Asteroid::collisionCount = 0; -// Utility -void Asteroid::updatePhysics(float deltaTime) { - position += velocity * deltaTime; - angle += angularVelocity * deltaTime; +#pragma region SAT +struct CollisionInfo { + raylib::Vector2 normal; + float penetration; +}; + +struct Projection { + float min; + float max; +}; + +inline std::vector getGlobalVertices(const Asteroid &a) { + std::vector vertices(a.polygon.vertices.size()); + for (size_t i = 0; i < a.polygon.vertices.size(); i++) { + vertices[i] = rotateAround(a.position + a.polygon.vertices[i], a.position, a.angle); + } + return vertices; } -void Asteroid::wrapAroundScreen() { - const float padding = radius * 3; - - if (position.x - padding > WIDTH) { - position.x = -padding; - } else if (position.x < -padding) { - position.x = WIDTH + padding; - } - - if (position.y - padding > HEIGHT) { - position.y = -padding; - } else if (position.y < -padding) { - position.y = HEIGHT + padding; - } +void getAxes(const std::vector &vertices, std::vector &axes) { + for (size_t i = 0; i < vertices.size(); i++) { + const auto p1 = vertices[i]; + const auto p2 = vertices[(i + 1) % vertices.size()]; + const auto edge = p1 - p2; + axes.emplace_back(-edge.y, edge.x); + } } -void Asteroid::solveCollisions(IContainer &grid) { - if (!ASTEROIDS_SELF_COLLISION) return; - - const auto others = grid.retrieve(position, radius * 2); - for (auto &other : others) { - if (id == other->id) continue; - if (fabsf(radius - other->radius) > 7.5f) continue; - - Asteroid::collisionCount++; - - // Time to bring out the big guns - const auto contact = CollisionHandler::getCollisionInfo(*this, *other.get()); - if (contact.has_value()) { - // Move the asteroids apart - const auto correction = contact->normal * contact->penetration; - position -= correction / 2.0f; - other->position += correction / 2.0f; - - // Calculate the new velocities - const auto relativeVelocity = velocity - other->velocity; - const auto velocityAlongNormal = relativeVelocity.DotProduct(contact->normal); - - // Use asteroid.radius / ASTEROID_RADIUS for mass - const float j = -(1 + ASTEROID_RESTITUTION) * velocityAlongNormal; - const float mass1 = 1.0f + radius / ASTEROID_RADIUS; - const float mass2 = 1.0f + other->radius / ASTEROID_RADIUS; - const float impulse = j / (mass1 + mass2); - velocity += contact->normal * impulse * mass1; - other->velocity -= contact->normal * impulse * mass2; - - // Ensure the asteroids are not stationary - if (velocity.Length() < EPSILON) velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; - if (other->velocity.Length() < EPSILON) other->velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; - - // Ensure the asteroids are not too fast - if (velocity.Length() > ASTEROID_VELOCITY) velocity = velocity.Normalize() * ASTEROID_VELOCITY; - if (other->velocity.Length() > ASTEROID_VELOCITY) other->velocity = other->velocity.Normalize() * ASTEROID_VELOCITY; - - // Calculate the new angular velocities - const float angularVelocityAlongNormal = angularVelocity - other->angularVelocity; - const float torque = angularVelocityAlongNormal * mass1; - const float angularImpulse = torque / (mass1 + mass2); - angularVelocity -= angularImpulse * mass1; - other->angularVelocity += angularImpulse * mass2; - - // Ensure the asteroids are not stationary - if (fabsf(angularVelocity) < 0.1f) angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; - if (fabsf(other->angularVelocity) < 0.1f) other->angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; - - // Ensure the asteroids are not too fast - if (fabsf(angularVelocity) > ASTEROID_ANGULAR_VELOCITY) angularVelocity = ASTEROID_ANGULAR_VELOCITY * (angularVelocity < 0 ? -1 : 1); - if (fabsf(other->angularVelocity) > ASTEROID_ANGULAR_VELOCITY) other->angularVelocity = ASTEROID_ANGULAR_VELOCITY * (other->angularVelocity < 0 ? -1 : 1); - } - } +Projection project(const std::vector &vertices, const raylib::Vector2 &axis) { + float min = axis.DotProduct(vertices[0]); + float max = min; + for (size_t i = 1; i < vertices.size(); i++) { + const float p = axis.DotProduct(vertices[i]); + if (p < min) min = p; + else if (p > max) max = p; + } + return { min, max }; } -void Asteroid::updateAnimations() { - float animationSpeed = 2.0f; // 500ms +std::optional getCollisionInfo(const Asteroid &a, const Asteroid &b) { + auto normal = raylib::Vector2::Zero(); + float overlap = std::numeric_limits::max(); - if (radius < ASTEROID_RADIUS) { - animationSpeed = 4.0f; // 250ms - } + // Get the global world position of the vertices + const auto verticesA = getGlobalVertices(a); + const auto verticesB = getGlobalVertices(b); - // Scale up the asteroid - scale += GetFrameTime() * radius; // 500ms - if (scale > 1.0f) scale = 1.0f; -} + // Get the axes of the polygons + std::vector axes; + getAxes(verticesA, axes); + getAxes(verticesB, axes); -void Asteroid::updateVertices() { - // After updating the position and angle, update the vertices of the asteroid - // Translating and rotating the vertices but keeping the same shape - const float diffAngle = angle - lastAngle; - for (size_t i = 0; i < vertices.size(); i++) { - // Translate the vertex to the new position - vertices[i] -= lastPosition; + // Iterate through the axes + for (auto it = axes.begin(); it != axes.end(); it++) { + const auto axis = *it; + const auto projectionA = project(verticesA, axis); + const auto projectionB = project(verticesB, axis); - // Rotate the vertex around the center of the asteroid - vertices[i] = vertices[i].Rotate(diffAngle); - vertices[i] += position; - } + // Detected a gap, no collision + if (projectionA.max < projectionB.min || projectionB.max < projectionA.min) { + return std::nullopt; + } + // Calculate the overlap + const float depth = std::min(projectionA.max, projectionB.max) - std::max(projectionA.min, projectionB.min); + if (depth < overlap) { + overlap = depth; + normal = axis; + } + } - lastPosition = position; - lastAngle = angle; -} + // Normalize the depth and normal + const auto length = normal.Length(); + normal /= length; + overlap /= length; -void Asteroid::generateVertices(float radius, uint8_t vertexCount) { - this->radius = 0.0f; - innerRadius = 0.0f; + // If the normal is pointing from A to B, invert it + const auto center = b.position - a.position; + if (center.DotProduct(normal) < 0) { + normal = -normal; + } - float scale = radius / ASTEROID_RADIUS; - for (uint8_t i = 0; i < vertexCount; i++) { - float angle = (i / (float)vertexCount) * 2.0f * PI; + return CollisionInfo{ normal, overlap }; +} +#pragma endregion - float r = radius + GetRandomValue(-ASTEROID_JAGGEDNESS * scale, ASTEROID_JAGGEDNESS * scale); +void Asteroid::updatePhysics() { + const auto deltaTime = GetFrameTime(); - // Keep track of the largest radius - if (this->radius == 0.0f || r > this->radius) this->radius = r; + position += velocity * deltaTime; + angle += angularVelocity * deltaTime; +} - // Keep track of the smallest radius - if (innerRadius == 0.0f || r < innerRadius) innerRadius = r; +void Asteroid::wrapAroundScreen() { + if (position.x - polygon.outerRadius > WIDTH) { + position.x = -polygon.outerRadius; + } else if (position.x < -polygon.outerRadius) { + position.x = WIDTH + polygon.outerRadius; + } - vertices.emplace_back(position.x + cosf(angle) * r, position.y + sinf(angle) * r); + if (position.y - polygon.outerRadius > HEIGHT) { + position.y = -polygon.outerRadius; + } else if (position.y < -polygon.outerRadius) { + position.y = HEIGHT + polygon.outerRadius; } } -// Public API -Asteroid::Asteroid(raylib::Vector2 position, float radius) { - id = Asteroid::idCounter++; - this->position = position; - generateVertices(radius, GetRandomValue(ASTEROID_MIN_VERTEX_COUNT, ASTEROID_MAX_VERTEX_COUNT)); - - // Generate random values - velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; - angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; - - // Store the initial position and angle - lastPosition = position; - lastAngle = angle; -} +void Asteroid::checkForCollisions(IContainer &asteroids) { + const auto others = asteroids.retrieve(position, polygon.outerRadius * 2); + for (auto &other : others) { + if (id == other->id) continue; + + // Check if asteroids are close enough to collide + // const float distanceLength = (position - other->position).Length(); + // const float minimumDistance = powf(polygon.outerRadius + other->polygon.outerRadius, 2.0f); + // if (distanceLength > minimumDistance) continue; + + // Time to bring out the big guns + const auto contact = getCollisionInfo(*this, *other.get()); + if (contact.has_value()) { + Asteroid::collisionCount++; + // Move the asteroids apart + const auto correction = contact->normal * contact->penetration; + position -= correction / 2.0f; + other->position += correction / 2.0f; + + // Calculate the new velocities + const auto relativeVelocity = velocity - other->velocity; + const auto velocityAlongNormal = relativeVelocity.DotProduct(contact->normal); + + // Use asteroid.radius / ASTEROID_RADIUS for mass + const float e = 1.0f; // Coefficient of restitution + const float j = -(1 + e) * velocityAlongNormal; + const float mass1 = polygon.outerRadius / ASTEROID_RADIUS; + const float mass2 = other->polygon.outerRadius / ASTEROID_RADIUS; + const float impulse = j / (mass1 + mass2); + velocity += contact->normal * impulse * mass1; + other->velocity -= contact->normal * impulse * mass2; + + // Ensure the asteroids are not stationary + if (velocity.Length() < 0.1f) velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; + if (other->velocity.Length() < 0.1f) other->velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; + + // Ensure the asteroids are not too fast + if (velocity.Length() > ASTEROID_VELOCITY) velocity = velocity.Normalize() * ASTEROID_VELOCITY; + if (other->velocity.Length() > ASTEROID_VELOCITY) other->velocity = other->velocity.Normalize() * ASTEROID_VELOCITY; + + // Calculate the new angular velocities + const float angularVelocityAlongNormal = angularVelocity - other->angularVelocity; + const float torque = angularVelocityAlongNormal * mass1; + const float angularImpulse = torque / (mass1 + mass2); + angularVelocity -= angularImpulse * mass1; + other->angularVelocity += angularImpulse * mass2; + + // Ensure the asteroids are not stationary + if (fabsf(angularVelocity) < 0.1f) angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; + if (fabsf(other->angularVelocity) < 0.1f) other->angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; + + // Ensure the asteroids are not too fast + if (fabsf(angularVelocity) > ASTEROID_ANGULAR_VELOCITY) angularVelocity = ASTEROID_ANGULAR_VELOCITY * (angularVelocity < 0 ? -1 : 1); + if (fabsf(other->angularVelocity) > ASTEROID_ANGULAR_VELOCITY) other->angularVelocity = ASTEROID_ANGULAR_VELOCITY * (other->angularVelocity < 0 ? -1 : 1); -std::vector> Asteroid::split() { - std::vector> newAsteroids; - - // Split the asteroid into two - if (radius >= ASTEROID_MIN_RADIUS_TO_SPLIT) { - const uint8_t fragmentCount = GetRandomValue(2, 4); - float newRadius = radius / fragmentCount; - if (newRadius < ASTEROID_MIN_FRAGMENT_RADIUS) newRadius = ASTEROID_MIN_FRAGMENT_RADIUS; - - for (int i = 0; i < fragmentCount; i++) { - const float angle = 360.0f * (i / (float)fragmentCount) * DEG2RAD; - const auto newPosition = position + raylib::Vector2(cosf(angle), sinf(angle)) * newRadius; - auto newAsteroid = std::make_shared(newPosition, newRadius); - newAsteroid->velocity = raylib::Vector2(cosf(angle), sinf(angle)) * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY * 2.0f; - // newAsteroid->scale = 1.0f; - newAsteroids.push_back(newAsteroid); } } +} - return newAsteroids; +void Asteroid::updateAnimations() { + // Scale up the asteroid + scale += GetFrameTime() * 2.0f; // 500ms + if (scale > 1.0f) scale = 1.0f; } -void Asteroid::update(IContainer &others, float deltaTime) { - updatePhysics(deltaTime); - updateVertices(); +Asteroid::Asteroid() { + id = Asteroid::idCounter++; + polygon.generateVertices(ASTEROID_RADIUS, GetRandomValue(ASTEROID_MIN_VERTEX_COUNT, ASTEROID_MAX_VERTEX_COUNT)); + position = raylib::Vector2(GetRandomValue(0, WIDTH), GetRandomValue(0, HEIGHT)); + velocity = raylib::Vector2::One() * getRandomValue(-1.0f, 1.0f) * ASTEROID_VELOCITY; + angle = getRandomValue(0.0f, 360.0f) * DEG2RAD; + angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; +} - if (scale >= 1.0f) - solveCollisions(others); - else - updateAnimations(); +Asteroid::Asteroid(Polygon &&polygon, raylib::Vector2 position) : polygon(polygon), position(position) { + id = Asteroid::idCounter++; + angle = getRandomValue(0.0f, 360.0f) * DEG2RAD; + angularVelocity = getRandomValue(-1.0f, 1.0f) * ASTEROID_ANGULAR_VELOCITY; + scale = 1.0f; +} - wrapAroundScreen(); +void Asteroid::update(IContainer &others) { + updatePhysics(); + if (scale >= 1.0f) checkForCollisions(others); + else updateAnimations(); + wrapAroundScreen(); } void Asteroid::render() { - // Check if the asteroid is inside the screen - if (position.x + radius < 0 || position.x - radius > WIDTH || position.y + radius < 0 || position.y - radius > HEIGHT) return; - - drawFilledPolygon(position, vertices.data(), vertices.size(), BLACK);// Only to occlude other asteroids - drawPolygonOutline(vertices.data(), vertices.size(), WHITE); - - if (RENDER_GRID) { - auto text = TextFormat("%i", gridCellIndex); - auto size = MeasureTextEx(GetFontDefault(), text, 12, 1); - DrawText(text, position.x - size.x / 2.0f, position.y - size.y / 2.0f, 12, LIGHTGRAY); - } + polygon.render(position, easeInOutBack(scale), angle, color); #ifdef DEBUG - /* DrawCircleLinesV(position, radius, LIGHTGRAY); - DrawCircleLinesV(position, innerRadius, GRAY); */ + auto text = TextFormat("%i", index); + auto size = MeasureTextEx(GetFontDefault(), text, 12, 1); + DrawText(text, position.x - size.x / 2.0f, position.y - size.y / 2.0f, 12, LIGHTGRAY); #endif } diff --git a/src/core/models/asteroid.hpp b/src/core/models/asteroid.hpp index 1164d98..79b4dbd 100644 --- a/src/core/models/asteroid.hpp +++ b/src/core/models/asteroid.hpp @@ -1,46 +1,40 @@ #pragma once -#include "../data/icontainer.hpp" #include "../precomp.hpp" -#include "Vector2.hpp" -#include "collision-handler.hpp" +#include "polygon.hpp" +#include "../data/icontainer.hpp" + +class Asteroid { -class Asteroid : public CollisionHandler::Shape { public: - static size_t idCounter; - static uint32_t collisionCount; + static size_t idCounter; + static uint32_t collisionCount; - // Identifiers - size_t id; - uint16_t gridCellIndex = 0; - raylib::Color color = raylib::Color::White(); + uint32_t index; + size_t id; - private: - float innerRadius; - - // Animation - float scale; - - // Physics - raylib::Vector2 velocity; - raylib::Vector2 lastPosition; - float lastAngle; - float angle; - float angularVelocity; - - // Utility - void updatePhysics(float deltaTime); - void solveCollisions(IContainer &grid); - void wrapAroundScreen(); - void updateAnimations(); - void updateVertices(); - void generateVertices(float radius, uint8_t vertexCount); + Polygon polygon; + raylib::Vector2 position; + raylib::Vector2 velocity; + float angle; + float angularVelocity; + raylib::Color color = raylib::Color::White(); + + // Animation + float scale; + + void updatePhysics(); + void wrapAroundScreen(); public: - Asteroid(raylib::Vector2 position, float radius); - ~Asteroid() = default; + Asteroid(); + Asteroid(Polygon &&polygon, raylib::Vector2 position); + ~Asteroid() = default; - std::vector> split(); - void update(IContainer &others, float deltaTime); - void render(); + void update(IContainer &others); + void render(); + + private: + void checkForCollisions(IContainer &others); + void updateAnimations(); }; \ No newline at end of file diff --git a/src/core/models/bullet.cpp b/src/core/models/bullet.cpp index d374a11..b4eccf0 100644 --- a/src/core/models/bullet.cpp +++ b/src/core/models/bullet.cpp @@ -1,31 +1,39 @@ #include "bullet.hpp" #include "../settings.hpp" #include "asteroid.hpp" -#include "particle.hpp" -#include "soundfx.hpp" -void Bullet::updatePhysics() { +bool Bullet::update(IContainer &asteroids) { + // Update physics position = position + velocity * GetFrameTime(); -} -bool Bullet::solveCollisions(IContainer &grid) { + // Check if outside screen bounds + if (position.x < 0 || position.x > WIDTH || position.y < 0 || position.y > HEIGHT) { + return true; + } + // Check for collisions - const auto list = grid.retrieve(position, ASTEROID_RADIUS * 2); + const auto list = asteroids.retrieve(position, ASTEROID_RADIUS * 2); for (auto &asteroid : list) { - if (position.Distance(asteroid->position) < asteroid->radius) { - if (asteroid->radius >= ASTEROID_MIN_RADIUS_TO_SPLIT) { - const auto newAsteroids = asteroid->split(); - for (auto &newAsteroid : newAsteroids) grid.insert(newAsteroid); + if (position.Distance(asteroid->position) < asteroid->polygon.outerRadius) { + if (asteroid->polygon.outerRadius >= ASTEROID_MIN_RADIUS_TO_SPLIT) { + // Split the asteroid + auto offset = raylib::Vector2::Zero(); + for (auto &poly : asteroid->polygon.split()) { + auto r = raylib::Vector2::One() * poly.outerRadius; + + const auto newAsteroid = std::make_shared(std::move(poly), asteroid->position + offset - r); + asteroids.insert(newAsteroid); + + // Calculate the velocity of the new asteroid + float angle = atan2f(newAsteroid->position.y - position.y, newAsteroid->position.x - position.x); + newAsteroid->velocity = asteroid->velocity + raylib::Vector2(cosf(angle), sinf(angle)) * ASTEROID_VELOCITY; + + offset += r; + } } - // Particles - ParticleSystem::addExplosion(position, 30, 0, 360); - - // Play sound - SoundFX::play(SoundFX::explosion, 0.25f, -1.0f, 1.0f - asteroid->position.x / (float)WIDTH); - // Kill the asteroid - grid.remove(asteroid); + asteroids.remove(asteroid); return true; } @@ -34,37 +42,6 @@ bool Bullet::solveCollisions(IContainer &grid) { return false; } -Bullet::Bullet(raylib::Vector2 position, raylib::Vector2 velocity) : position(position), velocity(velocity) { } - - -bool Bullet::update(IContainer &grid) { - timeAlive += GetFrameTime(); - if (timeAlive > BULLET_TIME_TO_LIVE) return true; - - updatePhysics(); - - // Check if outside screen bounds - if (BULLETS_WRAP_AROUND_SCREEN) { - if (position.x < 0) position.x = WIDTH; - if (position.x > WIDTH) position.x = 0; - if (position.y < 0) position.y = HEIGHT; - if (position.y > HEIGHT) position.y = 0; - } else if (position.x < 0 || position.x > WIDTH || position.y < 0 || position.y > HEIGHT) { - return true; - } - - if (solveCollisions(grid)) { - // TODO: Spawn particles - // TODO: Play sound - return true; - } - - // TODO: Emit particle tail - // TODO: Emit light - - return false; -} - void Bullet::render() { DrawCircleV(position, BULLET_RADIUS, WHITE); } diff --git a/src/core/models/bullet.hpp b/src/core/models/bullet.hpp index 753fcb7..82055d3 100644 --- a/src/core/models/bullet.hpp +++ b/src/core/models/bullet.hpp @@ -1,22 +1,12 @@ #pragma once -#include "../data/spatial-hash-grid.hpp" #include "../precomp.hpp" +#include "../data/spatial-hash-grid.hpp" -class Bullet { - private: - raylib::Vector2 velocity; - float timeAlive; - - void updatePhysics(); - bool solveCollisions(IContainer &grid); - - public: - raylib::Vector2 position; - - Bullet(raylib::Vector2 position, raylib::Vector2 velocity); - ~Bullet() = default; +struct Bullet { + raylib::Vector2 position; + raylib::Vector2 velocity; - bool update(IContainer &asteroids); - void render(); + bool update(IContainer &asteroids); + void render(); }; \ No newline at end of file diff --git a/src/core/models/collision-handler.hpp b/src/core/models/collision-handler.hpp index 6b771c0..d0c855d 100644 --- a/src/core/models/collision-handler.hpp +++ b/src/core/models/collision-handler.hpp @@ -1,25 +1,25 @@ #pragma once -#include "../precomp.hpp" +// #include "../precomp.hpp" -namespace CollisionHandler { - struct CollisionInfo { - raylib::Vector2 normal; - float penetration; - }; +// namespace CollisionHandler { +// struct CollisionInfo { +// raylib::Vector2 normal; +// float penetration; +// }; - struct Shape { - std::vector vertices; - raylib::Vector2 position; - float radius; - }; +// struct Shape { +// std::vector vertices; +// raylib::Vector2 position; +// float radius; +// }; - /** - * @brief Implements the Separating Axis Theorem (SAT) to detect collisions between two polygons - * - * @param a The first polygon - * @param b The second polygon - * @return std::optional The collision info if a collision is detected, otherwise std::nullopt - */ - std::optional getCollisionInfo(const Shape &a, const Shape &b); -} \ No newline at end of file +// /** +// * @brief Implements the Separating Axis Theorem (SAT) to detect collisions between two polygons +// * +// * @param a The first polygon +// * @param b The second polygon +// * @return std::optional The collision info if a collision is detected, otherwise std::nullopt +// */ +// std::optional getCollisionInfo(const Shape &a, const Shape &b); +// } \ No newline at end of file diff --git a/src/core/models/collision-hanlder.cpp b/src/core/models/collision-hanlder.cpp index 5e85ac4..f0c48c1 100644 --- a/src/core/models/collision-hanlder.cpp +++ b/src/core/models/collision-hanlder.cpp @@ -1,79 +1,73 @@ -#include "collision-handler.hpp" +// #include "collision-handler.hpp" -using namespace CollisionHandler; +// using namespace CollisionHandler; -struct Projection { - float min; - float max; -}; +// struct Projection { +// float min; +// float max; +// }; -void getAxes(const std::vector &vertices, std::vector &axes) { - for (size_t i = 0; i < vertices.size(); i++) { - const auto p1 = vertices[i]; - const auto p2 = vertices[(i + 1) % vertices.size()]; - const auto edge = p1 - p2; - axes.emplace_back(-edge.y, edge.x); // Perpendicular vector - } -} +// void getAxes(const std::vector &vertices, std::vector &axes) { +// for (size_t i = 0; i < vertices.size(); i++) { +// const auto p1 = vertices[i]; +// const auto p2 = vertices[(i + 1) % vertices.size()]; +// const auto edge = p1 - p2; +// axes.emplace_back(-edge.y, edge.x); // Perpendicular vector +// } +// } -Projection project(const std::vector &vertices, const raylib::Vector2 &axis) { - float min = axis.DotProduct(vertices[0]); - float max = min; - for (size_t i = 1; i < vertices.size(); i++) { - const float p = axis.DotProduct(vertices[i]); - if (p < min) - min = p; - else if (p > max) - max = p; - } +// Projection project(const std::vector &vertices, const raylib::Vector2 &axis) { +// float min = axis.DotProduct(vertices[0]); +// float max = min; +// for (size_t i = 1; i < vertices.size(); i++) { +// const float p = axis.DotProduct(vertices[i]); +// if (p < min) +// min = p; +// else if (p > max) +// max = p; +// } - return { min, max }; -} +// return { min, max }; +// } -std::optional CollisionHandler::getCollisionInfo(const Shape &a, const Shape &b) { - // Ignore if AABBs are not colliding - if (a.position.x + a.radius < b.position.x - b.radius || a.position.x - a.radius > b.position.x + b.radius || - a.position.y + a.radius < b.position.y - b.radius || a.position.y - a.radius > b.position.y + b.radius) { - return std::nullopt; - } +// std::optional CollisionHandler::getCollisionInfo(const Shape &a, const Shape &b) { +// auto normal = raylib::Vector2::Zero(); +// float overlap = std::numeric_limits::max(); - auto normal = raylib::Vector2::Zero(); - float overlap = std::numeric_limits::max(); +// // Get the axes of the polygons +// std::vector axes; +// axes.reserve(a.vertices.size() + b.vertices.size()); +// getAxes(a.vertices, axes); +// getAxes(b.vertices, axes); - // Get the axes of the polygons - std::vector axes; - axes.reserve(a.vertices.size() + b.vertices.size()); - getAxes(a.vertices, axes); - getAxes(b.vertices, axes); +// // Iterate through the axes +// for (auto &axis : axes) { +// const auto projectionA = project(a.vertices, axis); +// const auto projectionB = project(b.vertices, axis); - // Iterate through the axes - for (auto &axis : axes) { - const auto projectionA = project(a.vertices, axis); - const auto projectionB = project(b.vertices, axis); +// // Detected a gap, no collision +// if (projectionA.max < projectionB.min || projectionB.max < projectionA.min) { +// return std::nullopt; +// } - // Detected a gap, no collision - if (projectionA.max < projectionB.min || projectionB.max < projectionA.min) { - return std::nullopt; - } +// // Calculate the overlap +// const float depth = std::min(projectionA.max, projectionB.max) - std::max(projectionA.min, projectionB.min); +// if (depth < overlap) { +// overlap = depth; +// normal = axis; +// } +// } - // Calculate the overlap - const float depth = std::min(projectionA.max, projectionB.max) - std::max(projectionA.min, projectionB.min); - if (depth < overlap) { - overlap = depth; - normal = axis; - } - } +// // Normalize the depth and normal +// const auto length = normal.Length(); +// normal /= length; +// overlap /= length; - // Normalize the depth and normal - const auto length = normal.Length(); - normal /= length; - overlap /= length; +// // If the normal is pointing from A to B, invert it +// const auto center = b.position - a.position; +// if (center.DotProduct(normal) < 0) { +// normal = -normal; +// } - // If the normal is pointing from A to B, invert it - const auto center = b.position - a.position; - if (center.DotProduct(normal) < 0) { - normal = -normal; - } - - return CollisionInfo{ normal, overlap }; -} \ No newline at end of file +// return CollisionInfo{ normal, overlap }; +// } \ No newline at end of file diff --git a/src/core/models/gui.cpp b/src/core/models/gui.cpp index cdf4e3b..adf6633 100644 --- a/src/core/models/gui.cpp +++ b/src/core/models/gui.cpp @@ -1,104 +1,29 @@ #include "app.hpp" +#include "asteroid.hpp" #include "imgui.h" -#include "imgui_internal.h" -#include "../utils.hpp" #include "raylib.h" -#include "rlImGui.h" - -void App::setupGUI() { - TraceLog(LOG_INFO, "App::setupGUI()"); - - rlImGuiSetup(true); - auto &io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - // Ensure the ini file is in the same directory as the executable - std::string cwd = GetApplicationDirectory(); - cwd += "imgui.ini"; - io.IniFilename = cwd.c_str(); -#ifdef DEBUG - if (raylib::FileExists(cwd)) { - raylib::SaveFileText(cwd, ""); - } -#endif -} - -void App::buildGUIDockSpace() { - const auto workPosition = ImGui::GetMainViewport()->WorkPos; - const auto workSize = ImGui::GetMainViewport()->WorkSize; - const auto nodeSize = ImVec2 { (float)WIDTH, (float)HEIGHT }; - const auto center = ImVec2 { workPosition.x + workSize.x / 2.0f, workPosition.y + workSize.y / 2.0f }; - const auto nodePos = ImVec2 { center.x - nodeSize.x / 2.0f, center.y - nodeSize.y / 2.0f }; - - auto id = ImGui::GetID("MainDockSpace"); - ImGui::DockBuilderRemoveNode(id); - ImGui::DockBuilderAddNode(id); - - ImGui::DockBuilderSetNodeSize(id, nodeSize); - ImGui::DockBuilderSetNodePos(id, nodePos); - - auto dockTop = ImGui::DockBuilderSplitNode(id, ImGuiDir_Up, 0.20f, nullptr, &id); - const auto dockBottom = ImGui::DockBuilderSplitNode(id, ImGuiDir_Down, 0.80f, nullptr, &id); - - ImGui::DockBuilderDockWindow("Viewport", dockBottom); - - auto dockLeft = ImGui::DockBuilderSplitNode(dockTop, ImGuiDir_Left, 0.5f, nullptr, &dockTop); - ImGui::DockBuilderDockWindow("Stats", dockLeft); - - dockLeft = ImGui::DockBuilderSplitNode(dockLeft, ImGuiDir_Right, 0.5f, nullptr, &dockLeft); - ImGui::DockBuilderDockWindow("Ship", dockLeft); - - ImGui::DockBuilderDockWindow("Wave", dockTop); - - auto dockRight = ImGui::DockBuilderSplitNode(dockTop, ImGuiDir_Right, 0.5f, nullptr, &dockTop); - ImGui::DockBuilderDockWindow("Bullets", dockRight); - ImGui::DockBuilderDockWindow("Asteroids", dockRight); - - ImGui::DockBuilderFinish(id); - - isGUIBuilt = true; -} void App::renderGUI() { rlImGuiBegin(); - if (!isGUIBuilt) buildGUIDockSpace(); + ImGui::DockSpaceOverViewport(NULL, ImGuiDockNodeFlags_PassthruCentralNode); - ImGui::Begin("Viewport", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); + ImGui::Begin("Viewport"); + if (frameBuffer.IsReady()) rlImGuiImageRenderTexture(&frameBuffer); auto min = ImGui::GetWindowContentRegionMin(); auto max = ImGui::GetWindowContentRegionMax(); auto size = ImVec2(max.x - min.x, max.y - min.y); - if ((frameBuffer.texture.id <= 0 || size.x != frameBuffer.texture.width || size.y != frameBuffer.texture.height) && size.x > 0 && size.y > 0) { + if (frameBuffer.texture.id <= 0 || size.x != frameBuffer.texture.width || size.y != frameBuffer.texture.height) { onResize(size.x, size.y); - } else if (tempFrameBuffer.IsReady()) rlImGuiImageRenderTexture(&tempFrameBuffer); + } ImGui::End(); ImGui::Begin("Stats"); ImGui::Text("FPS: %i", GetFPS()); ImGui::Text("Frame Time: %.2f ms", GetFrameTime() * 1000); - - ImGui::SliderFloat("Tension:", &trauma, 0.0f, 1.0f); - - /* int targetFps = TARGET_FPS; - if (ImGui::SliderInt("Target FPS", &targetFps, 0, 100)) { - TARGET_FPS = targetFps; - SetTargetFPS(targetFps); - } */ - ImGui::Checkbox("Render grid overlay", &RENDER_GRID); - ImGui::Checkbox("Render star halo", &RENDER_STAR_HALO); - - profiler.renderGUI(); - ImGui::End(); - // TODO: Move this to Ship::renderGUI() ImGui::Begin("Ship"); - /* ImGui::SliderFloat("SHIP_ANGULAR_ACCELERATION", &SHIP_ANGULAR_ACCELERATION, 0.0f, PI * 2.0f); - ImGui::SliderFloat("SHIP_MAX_ANGULAR_VELOCITY", &SHIP_MAX_ANGULAR_VELOCITY, 0.0f, PI * 2.0f); - ImGui::SliderFloat("SHIP_ACCELERATION", &SHIP_ACCELERATION, 10.0f, 300.0f); - ImGui::SliderFloat("SHIP_DAMPING", &SHIP_DAMPING, 0.0f, 1.0f); - ImGui::SliderFloat("SHIP_ANGULAR_DAMPING", &SHIP_ANGULAR_DAMPING, 0.0f, 1.0f); */ - ImGui::Text("Position: { %.2f, %.2f }", ship.position.x, ship.position.y); ImGui::Text("Velocity: { %.2f, %.2f }", ship.velocity.x, ship.velocity.y); ImGui::Text("Acceleration: { %.2f, %.2f }", ship.acceleration.x, ship.acceleration.y); @@ -107,47 +32,49 @@ void App::renderGUI() { ImGui::End(); ImGui::Begin("Asteroids"); - ImGui::Text("Alive: %i", grid.size()); + ImGui::Text("Alive: %i", asteroids.size()); ImGui::Text("Collisions: %i", Asteroid::collisionCount); - if (ImGui::Button("Kill all")) grid.clear(); - - // int i = 0; - // for (auto &asteroid : asteroids.all()) { - // TODO: Move this to Asteroid::renderGUI() - // if (ImGui::TreeNode((void *)(intptr_t)i, "Asteroid %i", i)) { - // ImGui::Text("Position: { %.2f, %.2f }", asteroid->position.x, asteroid->position.y); - // ImGui::Text("Velocity: { %.2f, %.2f }", asteroid->velocity.x, asteroid->velocity.y); - // ImGui::Text("Angle: %.2f", asteroid->angle * RAD2DEG); - // ImGui::Text("Angular Velocity: %.2f", asteroid->angularVelocity * RAD2DEG); - // ImGui::Text("Radius: %.2f", asteroid->polygon.outerRadius); - // ImGui::Text("Vertices: %zu", asteroid->polygon.vertices.size()); - // for (size_t j = 0; j < asteroid->polygon.vertices.size(); j++) { - // ImGui::Text("Vertex %zu: { %.2f, %.2f }", j, asteroid->polygon.vertices[j].x, asteroid->polygon.vertices[j].y); - // } - // ImGui::TreePop(); - // } - - // i++; - // } + if (ImGui::Button("Kill all")) { + asteroids.clear(); + } + int i = 0; + for (auto &asteroid : asteroids.all()) { + if (ImGui::TreeNode((void *)(intptr_t)i, "Asteroid %i", i)) { + ImGui::Text("Position: { %.2f, %.2f }", asteroid->position.x, asteroid->position.y); + ImGui::Text("Velocity: { %.2f, %.2f }", asteroid->velocity.x, asteroid->velocity.y); + ImGui::Text("Angle: %.2f", asteroid->angle * RAD2DEG); + ImGui::Text("Angular Velocity: %.2f", asteroid->angularVelocity * RAD2DEG); + ImGui::Text("Radius: %.2f", asteroid->polygon.outerRadius); + ImGui::Text("Vertices: %zu", asteroid->polygon.vertices.size()); + for (size_t j = 0; j < asteroid->polygon.vertices.size(); j++) { + ImGui::Text("Vertex %zu: { %.2f, %.2f }", j, asteroid->polygon.vertices[j].x, asteroid->polygon.vertices[j].y); + } + ImGui::TreePop(); + } + + i++; + } ImGui::End(); ImGui::Begin("Bullets"); ImGui::Text("Alive: %zu", bullets.size()); - // i = 0; - // for (auto &bullet : bullets) { - // TODO: Move this to Bullet::renderGUI() - // if (ImGui::TreeNode((void *)(intptr_t)i, "Bullet %i", i)) { - // ImGui::Text("Position: { %.2f, %.2f }", bullet.position.x, bullet.position.y); - // ImGui::Text("Velocity: { %.2f, %.2f }", bullet.velocity.x, bullet.velocity.y); - // ImGui::TreePop(); - // } - - // i++; - // } + i = 0; + for (auto &bullet : bullets) { + if (ImGui::TreeNode((void *)(intptr_t)i, "Bullet %i", i)) { + ImGui::Text("Position: { %.2f, %.2f }", bullet.position.x, bullet.position.y); + ImGui::Text("Velocity: { %.2f, %.2f }", bullet.velocity.x, bullet.velocity.y); + ImGui::TreePop(); + } + + i++; + } ImGui::End(); ImGui::Begin("Wave"); - wave.renderGUI(); + ImGui::Text("Current: %i", wave.currentWave); + ImGui::Text("Time Since Last Wave: %.2f", wave.timeSinceLastWave); + ImGui::Text("Time Since Last Spawn: %.2f", wave.timeSinceLastSpawn); + ImGui::Text("Asteroids Spawned: %i", wave.asteroidsSpawned); ImGui::End(); rlImGuiEnd(); diff --git a/src/core/models/hotreload-shader.cpp b/src/core/models/hotreload-shader.cpp deleted file mode 100644 index d0160bc..0000000 --- a/src/core/models/hotreload-shader.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "hotreload-shader.hpp" -#include "raylib.h" - -bool HotReloadShader::load() { - auto shader = std::make_unique(nullptr, path.c_str()); - if (shader->IsReady()) { - this->shader = std::move(shader); -#ifdef DEBUG - lastModified = raylib::GetFileModTime(path); -#endif - return true; - } - - return false; -} - -#ifdef DEBUG -void HotReloadShader::autoReloadIfNeeded() { - timer += GetFrameTime(); - if (timer >= 1.0) timer -= 1.0; - else return; - - const unsigned long modifiedTime = raylib::GetFileModTime(path); - if (modifiedTime > lastModified) { - TraceLog(LOG_INFO, "HotReloadShader::autoReloadIfNeeded() - Reloading shader: %s", path.c_str()); - load(); - } -} -#endif - -void HotReloadShader::beginMode() { - if (shader != nullptr && shader->IsReady()) { - shader->BeginMode(); - } -} - -void HotReloadShader::endMode() { - if (shader != nullptr && shader->IsReady()) { - shader->EndMode(); - } -} - -const int HotReloadShader::getUniformLocation(std::string name) { - if (shader != nullptr && shader->IsReady()) { - return shader->GetLocation(name); - } - - return -1; -} - -void HotReloadShader::setUniform(const int location, const void *value, ShaderUniformDataType uniformType) { - if (shader != nullptr && shader->IsReady()) { - shader->SetValue(location, value, uniformType); - } -} diff --git a/src/core/models/hotreload-shader.hpp b/src/core/models/hotreload-shader.hpp deleted file mode 100644 index e7a62d0..0000000 --- a/src/core/models/hotreload-shader.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "../precomp.hpp" - -class HotReloadShader { - private: - std::unique_ptr shader = nullptr; - std::string path; - -#ifdef DEBUG - unsigned long lastModified = 0L; - float timer = 0; -#endif - - public: - HotReloadShader(std::string_view path) : path(path) { } - - bool load(); - -#ifdef DEBUG - void autoReloadIfNeeded(); -#endif - - void beginMode(); - - void endMode(); - - const int getUniformLocation(std::string name); - - void setUniform(const int location, const void *value, ShaderUniformDataType uniformType); -}; diff --git a/src/core/models/particle.cpp b/src/core/models/particle.cpp deleted file mode 100644 index dc786ac..0000000 --- a/src/core/models/particle.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "particle.hpp" -#include "../utils.hpp" -#include "Color.hpp" - -bool Particle::update() { - position = position + velocity * GetFrameTime(); - - // Deviation - if (GetRandomValue(0, 100) < 2) { - velocity = raylib::Vector2(GetRandomValue(-1.0f, 1.0f), GetRandomValue(-1.0f, 1.0f)).Normalize() * velocity.Length(); - } - - timeAlive += GetFrameTime(); - - if (timeAlive >= timeToLive) { - timeAlive = timeToLive; - return true; - } - - return false; -} - -void Particle::render() { - const float progress = powf(1.0f - timeAlive / timeToLive, 2.0f); - const float alpha = this->color.a * progress; - const float radius = this->radius * progress; - raylib::Color currentColor; - if (changeColorBasedOnTime) currentColor = convertKelvinToColor(1000.0f + progress * 9000.0f); - else raylib::Color(color.r, color.g, color.b, alpha); - - DrawCircleV(position, radius, currentColor); -} diff --git a/src/core/models/particle.hpp b/src/core/models/particle.hpp deleted file mode 100644 index d6ded4a..0000000 --- a/src/core/models/particle.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "../precomp.hpp" -#include "../utils.hpp" -#include "Color.hpp" -#include "Vector2.hpp" - -struct Particle { - raylib::Vector2 position; - raylib::Vector2 velocity; - raylib::Color color; - float radius; - float timeToLive = 1.0f; - float timeAlive = 0.0f; - bool changeColorBasedOnTime = false; - - Particle(raylib::Vector2 position, raylib::Vector2 velocity, raylib::Color color, float radius, float timeToLive, bool changeColorBasedOnTime = false) : position(position), velocity(velocity), color(color), radius(radius), timeToLive(timeToLive), changeColorBasedOnTime(changeColorBasedOnTime) { } - - bool update(); - void render(); - -}; - -namespace ParticleSystem { - inline std::list particles; - - static void update() { - for (auto it = particles.begin(); it != particles.end();) { - if (it->update()) { - it = particles.erase(it); - } else { - it++; - } - } - } - - static void render() { - for (auto &particle : particles) { - particle.render(); - } - } - - static void addExplosion(raylib::Vector2 position, unsigned int count, float startAngle = -PI, float endAngle = PI, raylib::Vector2 baseVelocity = raylib::Vector2::Zero(), float minVelocity = 50.0f, float maxVelocity = 100.0f, float radius = 2.0f, float timeToLive = 1.0f, raylib::Color color = LIGHTGRAY) { - for (int i = 0; i < count; i++) { - const float angle = i / (float)count * (endAngle - startAngle) + startAngle; - const auto velocity = raylib::Vector2(cosf(angle), sinf(angle)) * getRandomValue(minVelocity, maxVelocity) + baseVelocity; - particles.emplace_back(position, velocity, color, radius, timeToLive, true); - } - } - - static void addTrail(raylib::Vector2 position, unsigned int count, float baseAngle, raylib::Vector2 velocity, raylib::Color color = RED, float radius = 2.0f, float timeToLive = 0.250f) { - for (int i = 0; i < count; i++) { - const float angle = getRandomValue(-PI, PI) + baseAngle; - const auto vel = raylib::Vector2(cosf(angle), sinf(angle)) * getRandomValue(-8.5f, 40.0f) + velocity; - particles.emplace_back(position + vel / SHIP_SIZE, vel, color, radius, timeToLive, true); - } - } -} \ No newline at end of file diff --git a/src/core/models/polygon.cpp b/src/core/models/polygon.cpp new file mode 100644 index 0000000..08c081a --- /dev/null +++ b/src/core/models/polygon.cpp @@ -0,0 +1,57 @@ +#include "polygon.hpp" +#include "../settings.hpp" +#include "../utils.hpp" +#include "Vector2.hpp" +#include "raylib.h" +#include + +void Polygon::generateVertices(float radius, uint8_t vertexCount) { + outerRadius = 0.0f; + innerRadius = 0.0f; + + float scale = radius / ASTEROID_RADIUS; + + for (int i = 0; i < vertexCount; i++) { + float angle = (i / (float)vertexCount) * 2.0f * PI; + + float r = radius + GetRandomValue(-ASTEROID_JAGGEDNESS * scale, ASTEROID_JAGGEDNESS * scale); + + // Keep track of the largest radius + if (outerRadius == 0.0f || r > outerRadius) outerRadius = r; + + // Keep track of the smallest radius + if (innerRadius == 0.0f || r < innerRadius) innerRadius = r; + + vertices.emplace_back(cosf(angle) * r, sinf(angle) * r); + } +} + +void Polygon::render(raylib::Vector2 center, float scale, float angle, raylib::Color color) { + for (size_t i = 0; i < vertices.size(); i++) { + size_t j = (i + 1) % vertices.size(); + DrawLineEx( + rotateAround(center + vertices[i] * scale, center, angle), + rotateAround(center + vertices[j] * scale, center, angle), + 1.5f, + color + ); + } + +// #ifdef DEBUG +// DrawCircleLinesV(center, outerRadius, LIGHTGRAY); +// DrawCircleLinesV(center, innerRadius, GRAY); +// #endif +} + +std::vector Polygon::split() { + std::vector buffer; + + float radius = ceilf(outerRadius / (float)(ASTEROID_FRAGMENTS_COUNT - 1)); + uint8_t vertexCount = GetRandomValue(ASTEROID_MIN_VERTEX_COUNT, ASTEROID_MAX_VERTEX_COUNT); + for (size_t i = 0; i < ASTEROID_FRAGMENTS_COUNT; i++) { + buffer.emplace_back(); + buffer.back().generateVertices(radius, vertexCount); + } + + return buffer; +} \ No newline at end of file diff --git a/src/core/models/polygon.hpp b/src/core/models/polygon.hpp new file mode 100644 index 0000000..5177c1c --- /dev/null +++ b/src/core/models/polygon.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "../precomp.hpp" +#include "Vector2.hpp" +#include +#include + +class Polygon { + public: + std::vector vertices; + float innerRadius; + float outerRadius; + + void generateVertices(float radius, uint8_t vertexCount); + void render(raylib::Vector2 center, float scale, float angle, raylib::Color color); + std::vector split(); +}; \ No newline at end of file diff --git a/src/core/models/profiler.hpp b/src/core/models/profiler.hpp deleted file mode 100644 index 49951d2..0000000 --- a/src/core/models/profiler.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "../precomp.hpp" - -class Profiler { - private: - std::array frameTimeBuffer; - size_t frameTimeIndex = 0; - - uint32_t frameCount = 0; - double frameTimeSum = 0.0; - - std::chrono::time_point frameStart; - - public: - Profiler() = default; - ~Profiler() = default; - - void onFrameStart() { - frameStart = std::chrono::high_resolution_clock::now(); - } - - void onFrameEnd() { - const auto frameEnd = std::chrono::high_resolution_clock::now(); - const double frameTime = std::chrono::duration(frameEnd - frameStart).count(); - - if (frameTimeIndex >= frameTimeBuffer.size()) frameTimeIndex = 0; - frameTimeBuffer[frameTimeIndex++] = frameTime; - frameTimeSum += frameTime; - frameCount++; - } - - void renderGUI() { - const float averageFrameTime = frameTimeSum / frameCount; - - // ImGui::Begin("Profiler"); - ImGui::Text("Average frame time: %.3f ms", averageFrameTime * 1000.0f); - ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); - ImGui::PlotLines( - "Frame Time", - frameTimeBuffer.data(), - frameTimeBuffer.size(), - 0, - NULL, - 0.0f, - 0.033f, - ImVec2(0, 20) - ); - ImGui::PopStyleColor(); - // ImGui::End(); - } -}; \ No newline at end of file diff --git a/src/core/models/ship.cpp b/src/core/models/ship.cpp index ce1c006..7024fc7 100644 --- a/src/core/models/ship.cpp +++ b/src/core/models/ship.cpp @@ -1,55 +1,27 @@ #include "ship.hpp" #include "../utils.hpp" -#include "Vector2.hpp" -#include "particle.hpp" -#include "raylib.h" -#include "soundfx.hpp" - -void Ship::shoot(std::list &bullets) { - const auto shipDirection = raylib::Vector2(cosf(angle), sinf(angle)); - const auto spawnPosition = position + shipDirection * SHIP_SIZE * 1.5f; - - // Spawn particles - ParticleSystem::addExplosion(spawnPosition, 5, angle - PI / 4.0f, angle + PI / 4.0f); - - // Spawn bullet - bullets.emplace_back( - spawnPosition, - shipDirection * BULLET_VELOCITY + velocity - ); - - // Play sound - SoundFX::play( - SoundFX::shoot, - 0.05f, - -1.0f, - 1.0f - position.x / (float)WIDTH - ); -} void Ship::updatePhysics() { const auto deltaTime = GetFrameTime(); - const float maxSpeed = BULLET_VELOCITY * 0.75f; - // Movement - velocity += acceleration * deltaTime * 0.5f; - if (velocity.Length() >= maxSpeed) velocity = velocity.Normalize() * maxSpeed; + velocity += acceleration * deltaTime; position += velocity * deltaTime; - velocity += acceleration * deltaTime * 0.5f; - velocity *= powf(SHIP_DAMPING, deltaTime * TARGET_FPS); + acceleration = raylib::Vector2::Zero(); // Reset acceleration + velocity *= SHIP_DAMPING; // Angle angle += angularVelocity * deltaTime; - if (fabsf(angularVelocity) > SHIP_MAX_ANGULAR_VELOCITY) angularVelocity = SHIP_MAX_ANGULAR_VELOCITY * (angularVelocity > 0 ? 1 : -1); - angularVelocity *= powf(SHIP_ANGULAR_DAMPING, deltaTime * TARGET_FPS); + angularVelocity *= SHIP_ANGULAR_DAMPING; if (angle > 2 * PI) angle -= 2 * PI; if (angle < 0) angle += 2 * PI; - if (fabs(angularVelocity) < 0.1f) angularVelocity = 0;// Prevents the ship from rotating forever + + // Reset angular velocity if it's too small + if (fabs(angularVelocity) < 0.1f) angularVelocity = 0; } -void Ship::updateInput(std::list &bullets) { +void Ship::updateInput() { // Rotation if (IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_D)) angularVelocity += SHIP_ANGULAR_ACCELERATION; if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_A)) angularVelocity -= SHIP_ANGULAR_ACCELERATION; @@ -58,18 +30,11 @@ void Ship::updateInput(std::list &bullets) { if (IsKeyDown(KEY_UP) || IsKeyDown(KEY_W)) { acceleration.x += cosf(angle) * SHIP_ACCELERATION; acceleration.y += sinf(angle) * SHIP_ACCELERATION; - thrust += 0.1f; } - /* if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) { + if (IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_S)) { acceleration.x -= cosf(angle) * SHIP_ACCELERATION; acceleration.y -= sinf(angle) * SHIP_ACCELERATION; - } */ - - // Shooting - if (shotCooldown >= BULLET_SHOOT_INTERVAL && IsKeyDown(KEY_SPACE)) { - shoot(bullets); - shotCooldown = 0; - } else shotCooldown += GetFrameTime(); + } } void Ship::wrapAroundScreen() { @@ -93,63 +58,27 @@ void Ship::setup() { angularVelocity = 0.0f; } -void Ship::updateVertices() { - vertices = { - rotateAround(position + raylib::Vector2(SHIP_SIZE, 0), position, angle), - rotateAround(position + raylib::Vector2(-SHIP_SIZE, -SHIP_SIZE), position, angle), - rotateAround(position + raylib::Vector2(-SHIP_SIZE, SHIP_SIZE), position, angle) - }; -} - -void Ship::updateTrail() { - const auto reflectedAngle = angle + PI; - const auto shipDirection = raylib::Vector2(cosf(reflectedAngle), sinf(reflectedAngle)); - const auto shipBottom = position + shipDirection * SHIP_SIZE; - - auto baseParticleVelocity = raylib::Vector2::Zero(); - - // Small bursts of particles, when the ship is not thrusting - if (thrust <= 0.1f && getRandomValue(0, 100) < 10) baseParticleVelocity += shipDirection * 50.0f; - - ParticleSystem::addTrail(shipBottom, 10, angle, baseParticleVelocity); -} - -void Ship::updateThrustSound() { - if (!SoundFX::thrust.IsReady()) return; - - // Postion sound - SoundFX::thrust.SetPan(1.0f - position.x / (float)WIDTH); - - // Decay thrust force - thrust *= 0.95f; - - // Set volume based on thrust force - float volume = std::max(thrust, 1.0f) * 0.075f; - if (thrust <= 0.1) volume = 0.0f; - SoundFX::thrust.SetVolume(volume); - - // Play sound - if (SoundFX::thrust.IsPlaying()) { - if (volume <= 0.1f) SoundFX::thrust.Pause(); - } else { - if (volume >= 0.1f) SoundFX::thrust.Play(); - else SoundFX::thrust.Pause(); - } - -} - -void Ship::update(std::list &bullets) { - updateInput(bullets); +void Ship::update() { updatePhysics(); + updateInput(); wrapAroundScreen(); - updateVertices(); - updateTrail(); - updateThrustSound(); - - // TODO: Check for collisions with asteroids } void Ship::render() { - drawFilledPolygon(position, vertices.data(), vertices.size(), WHITE); - // drawPolygonOutline(vertices.data(), vertices.size(), WHITE); + // Render triangle with the pointy end facing the direction of the ship + const float size = 10.0f; + raylib::Vector2 p1 = position + raylib::Vector2(size, 0); + raylib::Vector2 p2 = position + raylib::Vector2(-size, -size); + raylib::Vector2 p3 = position + raylib::Vector2(-size, size); + + /* DrawCircleV(position, size, WHITE); + DrawLineV(position, position + raylib::Vector2(cosf(angle) * size, sinf(angle) * size), RED); */ + + DrawTriangleLines( + rotateAround(p1, position, angle), + rotateAround(p2, position, angle), + rotateAround(p3, position, angle), + WHITE + ); + } diff --git a/src/core/models/ship.hpp b/src/core/models/ship.hpp index bde51cc..14b6d6e 100644 --- a/src/core/models/ship.hpp +++ b/src/core/models/ship.hpp @@ -1,11 +1,10 @@ #pragma once #include "../precomp.hpp" -#include "bullet.hpp" +#include "Vector2.hpp" class Ship { public: - std::array vertices; raylib::Vector2 position; raylib::Vector2 velocity; raylib::Vector2 acceleration; @@ -13,25 +12,15 @@ class Ship { float angularVelocity; private: - float thrust; - float shotCooldown; - - void updateVertices(); void updatePhysics(); - void updateInput(std::list &bullets); + void updateInput(); void wrapAroundScreen(); - void updateShootingLogic(std::list &bullets); - void shoot(std::list &bullets); - - void updateTrail(); - - void updateThrustSound(); public: Ship() = default; ~Ship() = default; void setup(); - void update(std::list &bullets); + void update(); void render(); }; \ No newline at end of file diff --git a/src/core/models/soundfx.hpp b/src/core/models/soundfx.hpp deleted file mode 100644 index 12d43d1..0000000 --- a/src/core/models/soundfx.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "../precomp.hpp" -#include "../utils.hpp" -#include "raylib.h" -#include "utils.h" - -namespace SoundFX { - inline raylib::Sound shoot; - inline raylib::Sound thrust; - inline raylib::Sound explosion; - inline raylib::Sound hit; - inline raylib::Sound spawn; - - static void setup() { - TraceLog(LOG_INFO, "SoundFX::setup()"); - - shoot.Load("assets/audio/shoot.wav"); - if (!shoot.IsReady()) TraceLog(LOG_ERROR, "Failed to load shoot sound"); - - thrust.Load("assets/audio/thrust.wav"); - if (!thrust.IsReady()) TraceLog(LOG_ERROR, "Failed to load thrust sound"); - - explosion.Load("assets/audio/explosion.wav"); - if (!explosion.IsReady()) TraceLog(LOG_ERROR, "Failed to load explosion sound"); - - hit.Load("assets/audio/hit.wav"); - if (!hit.IsReady()) TraceLog(LOG_ERROR, "Failed to load hit sound"); - - spawn.Load("assets/audio/spawn.wav"); - if (!spawn.IsReady()) TraceLog(LOG_ERROR, "Failed to load spawn sound"); - } - - static void release() { - TraceLog(LOG_INFO, "SoundFX::release()"); - - shoot.Unload(); - explosion.Unload(); - hit.Unload(); - spawn.Unload(); - } - - static void play(raylib::Sound &sound, float volume = -1.0f, float pitch = -1.0f, float pan = -1.0f) { - if (!sound.IsReady()) { - TraceLog(LOG_ERROR, "SoundFX::play() - Sound is not ready"); - return; - } - - if (volume <= 0.0f) volume = 0.5f; - if (pitch <= 0.0f) pitch = getRandomValue(0.65f, 1.35f); - if (pan <= 0.0f) pan = getRandomValue(0.25f, 0.75f); - - sound.SetVolume(volume); - sound.SetPitch(pitch); - sound.SetPan(pan); - sound.Play(); - } -}; \ No newline at end of file diff --git a/src/core/models/star.hpp b/src/core/models/star.hpp deleted file mode 100644 index 3731d73..0000000 --- a/src/core/models/star.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -#include "../precomp.hpp" -#include "../settings.hpp" -#include "Color.hpp" -#include "Vector2.hpp" -#include "raylib.h" -#include "ship.hpp" -#include "../utils.hpp" -#include - -struct Star { - raylib::Vector2 position; - raylib::Color color; - float angle; - float radius; - - Star(raylib::Vector2 position, float radius) : position(position), radius(radius) { - angle = getRandomValue(0, PI * 2.0f); - - color = convertKelvinToColor(randomKelvin()); - } - - void update(raylib::Vector2 offset) { - const float parallax = powf(radius / 5.0f, 2.0f) / 25.0f; - position -= offset * parallax; - - if (position.x + radius > WIDTH) position.x -= WIDTH + radius; - if (position.x - radius < 0) position.x += WIDTH + radius; - if (position.y + radius > HEIGHT) position.y -= HEIGHT + radius; - if (position.y - radius < 0) position.y += HEIGHT + radius; - } - - void render() { - angle += 0.1f * GetFrameTime(); - - float alpha = (radius / 7.0f) + 0.25f; - alpha *= sinf((PI * 2.0f * GetTime() + radius * 10.0f) / 10.0f) * 0.15f + 0.85f; - - const float angleIncrement = 360.0f / 5.0f * DEG2RAD; - float currentAngle = -90 * DEG2RAD + angle; - float innerRadius = radius * 0.5f; - std::array vertices; - for (int i = 0; i < 5; i++) { - // Calculate the position of the outer vertex - float x1 = position.x + cosf(currentAngle) * radius; - float y1 = position.y + sinf(currentAngle) * radius; - vertices[i * 2] = raylib::Vector2(x1, y1); - currentAngle += angleIncrement; - - // Calculate the position of the inner vertex - float x2 = position.x + cosf(currentAngle + angleIncrement / 2.0f) * innerRadius; - float y2 = position.y + sinf(currentAngle + angleIncrement / 2.0f) * innerRadius; - vertices[i * 2 + 1] = raylib::Vector2(x2, y2); - } - - drawFilledPolygon(position, vertices.data(), vertices.size(), Fade(color, alpha)); - - // Draw halo around it - if (RENDER_STAR_HALO) { - DrawCircleV(position, radius * 1.5f, Fade(color, alpha * 0.15f)); - } - - /* // Extra effect, draw lines from the center to radius * 2 - // Draw angled line 1 - auto offset = raylib::Vector2(cosf(angle), sinf(angle)) * radius * 1.5f; - DrawLineV(position - offset, position + offset, Fade(color, alpha * 0.25f)); - - // Draw angled line 2 - offset = raylib::Vector2(cosf(angle + PI / 4.0f), sinf(angle + PI / 4.0f)) * radius * 1.5f; - DrawLineV(position - offset, position + offset, Fade(color, alpha * 0.25f)); */ - } -}; \ No newline at end of file diff --git a/src/core/models/wave.cpp b/src/core/models/wave.cpp index df8e8a1..a45557c 100644 --- a/src/core/models/wave.cpp +++ b/src/core/models/wave.cpp @@ -1,46 +1,62 @@ #include "wave.hpp" #include "asteroid.hpp" -#include "app.hpp" -#include "raylib.h" -#include "soundfx.hpp" -WaveController::WaveController() { -} +void WaveController::update(IContainer &asteroids, std::list &bullets, Ship &ship) { + timeSinceLastWave += GetFrameTime(); + timeSinceLastSpawn += GetFrameTime(); -WaveController::~WaveController() { - TraceLog(LOG_INFO, "WaveController::~WaveController()"); - if (thread.joinable()) thread.join(); -} + // if (asteroids.isEmpty()) { + // for (int i = 0; i < 50; i++) { + // spawn(asteroids, bullets, ship); + // } + // } + + if (timeSinceLastWave >= waveInterval || (asteroids.isEmpty() && currentWave > 0)) { + TraceLog(LOG_DEBUG, "Entering wave %i", currentWave); + + // Start new wave + timeSinceLastWave -= waveInterval; + currentWave++; + + asteroidsPerWave += LEVEL_ASTEROIDS_PER_WAVE; -void WaveController::renderGUI() { - ImGui::Text("Current: %i", currentWave); - ImGui::Text("Time Since Last Wave: %.2f", timeSinceLastWave); - ImGui::Text("Time Since Last Spawn: %.2f", timeSinceLastSpawn); - ImGui::Text("Asteroids Spawned: %i", asteroidsSpawned); + waveInterval *= LEVEL_WAVE_INTERVAL_DECREMENT; + if (waveInterval < LEVEL_WAVE_INTERVAL_MIN) waveInterval = LEVEL_WAVE_INTERVAL_MIN; + + spawnInterval *= LEVEL_ASTEROID_SPAWN_INTERVAL_DECREMENT_PER_WAVE; + if (spawnInterval < LEVEL_ASTEROID_SPAWN_INTERVAL_MIN) spawnInterval = LEVEL_ASTEROID_SPAWN_INTERVAL_MIN; + } + + if (timeSinceLastSpawn >= spawnInterval && asteroidsSpawned < asteroidsPerWave) { + // Spawn new asteroid + timeSinceLastSpawn -= spawnInterval; + asteroidsSpawned++; + spawn(asteroids, bullets, ship); + } + + if (IsKeyReleased(KEY_ENTER)) { + spawn(asteroids, bullets, ship); + } } -void WaveController::spawn(std::shared_ptr app) { - const int maxTries = 4; - const float radius = getRandomValue(0.7f, 1.0f) * ASTEROID_RADIUS; - auto position = raylib::Vector2( - GetRandomValue(0, WIDTH), - GetRandomValue(0, HEIGHT) - ); +void WaveController::spawn(IContainer &asteroids, std::list &bullets, Ship &ship) { + const auto asteroid = std::make_shared(); bool hit = false; uint8_t tries = 0; do { // Ensures it did not spawn on top of the ship - if (position.Distance(app->ship.position) < radius) { - TraceLog(LOG_DEBUG, "Ship hit on %.2f, %.2f with distance %.3f - %.2f", position.x, position.y, app->ship.position.Distance(position), radius); + if (asteroid->position.Distance(ship.position) < asteroid->polygon.outerRadius) { + TraceLog(LOG_DEBUG, "Ship hit on %.2f, %.2f with distance %.3f - %.2f", asteroid->position.x, asteroid->position.y, ship.position.Distance(asteroid->position), asteroid->polygon.outerRadius); hit = true; } // Ensures it did not spawn on top of another asteroid - if (!hit && ASTEROIDS_SELF_COLLISION) { - for (auto &other : app->grid.getAll()) { - if (other->position.Distance(position) < other->radius + radius) { - TraceLog(LOG_DEBUG, "Asteroid hit on %.2f, %.2f", position.x, position.y); + if (!hit) { + for (auto &other : asteroids.all()) { + if (&other == &asteroid) continue; + if (other->position.Distance(asteroid->position) < other->polygon.outerRadius + asteroid->polygon.outerRadius) { + TraceLog(LOG_DEBUG, "Asteroid hit on %.2f, %.2f", asteroid->position.x, asteroid->position.y); hit = true; break; } @@ -49,9 +65,9 @@ void WaveController::spawn(std::shared_ptr app) { // Ensures it did not spawn on top of bullets if (!hit) { - for (auto &bullet : app->bullets) { - if (bullet.position.Distance(position) < radius + BULLET_RADIUS) { - TraceLog(LOG_DEBUG, "Bullet hit on %.2f, %.2f", position.x, position.y); + for (auto &bullet : bullets) { + if (bullet.position.Distance(asteroid->position) < asteroid->polygon.outerRadius + BULLET_RADIUS) { + TraceLog(LOG_DEBUG, "Bullet hit on %.2f, %.2f", asteroid->position.x, asteroid->position.y); hit = true; break; } @@ -60,78 +76,17 @@ void WaveController::spawn(std::shared_ptr app) { // Assign new position if it hit something if (hit) { - position = raylib::Vector2( + asteroid->position = raylib::Vector2( GetRandomValue(0, WIDTH), GetRandomValue(0, HEIGHT) ); tries++; } - } while(hit && tries < maxTries); + } while(hit && tries < 10); - if (tries >= maxTries) { - TraceLog(LOG_INFO, "Failed to spawn asteroid after %i tries", maxTries); + if (tries >= 10) { + TraceLog(LOG_INFO, "Failed to spawn asteroid after 10 tries"); } else { - app->grid.insert(std::make_shared(position, radius)); - SoundFX::play(SoundFX::spawn, 0.15f, -1.0f, 1.0f - position.x / (float)WIDTH); + asteroids.insert(asteroid); } -} - -void WaveController::threadCallback() { - bool once = true; - auto lastTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - - while (!app.expired()) { - if (auto app = this->app.lock()) { - // Ignore if grid is not initialized - if (app->grid.rows <= 0 || app->grid.cols <= 0 || !app->grid.isInitialized()) continue; - - auto currentTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - auto deltaTime = (currentTime - lastTime) / 1000.0f; - lastTime = currentTime; - - // Update timers - timeSinceLastWave += deltaTime; - timeSinceLastSpawn += deltaTime; - - // Update wave - if (timeSinceLastWave >= waveInterval || (app->grid.isEmpty() && currentWave > 0)) { - TraceLog(LOG_DEBUG, "Entering wave %i", currentWave); - - // Start new wave - timeSinceLastWave -= waveInterval; - currentWave++; - - asteroidsPerWave += LEVEL_ASTEROIDS_PER_WAVE; - - waveInterval -= waveInterval * LEVEL_WAVE_INTERVAL_DECREMENT; - if (waveInterval < LEVEL_WAVE_INTERVAL_MIN) waveInterval = LEVEL_WAVE_INTERVAL_MIN; - - spawnInterval -= spawnInterval * LEVEL_ASTEROID_SPAWN_INTERVAL_DECREMENT_PER_WAVE; - if (spawnInterval < LEVEL_ASTEROID_SPAWN_INTERVAL_MIN) spawnInterval = LEVEL_ASTEROID_SPAWN_INTERVAL_MIN; - } - - // Spawn new asteroid - if (timeSinceLastSpawn >= spawnInterval && asteroidsSpawned < asteroidsPerWave) { - timeSinceLastSpawn -= spawnInterval; - asteroidsSpawned++; - spawn(app); - } - - if (SPAWN_AT_START && once && app->grid.isEmpty()) { - once = false; - TraceLog(LOG_DEBUG, "Spawning initial asteroids"); - for (int i = 0; i < 50; i++) spawn(app); - } - } - - std::this_thread::sleep_for(std::chrono::milliseconds((long)(spawnInterval * 1000L))); - } - - TraceLog(LOG_INFO, "WaveController::threadCallback() exited"); -} - -void WaveController::start(std::shared_ptr app) { - this->app = app; - thread = std::thread(&WaveController::threadCallback, this); - thread.detach(); } \ No newline at end of file diff --git a/src/core/models/wave.hpp b/src/core/models/wave.hpp index 7beac64..52544b4 100644 --- a/src/core/models/wave.hpp +++ b/src/core/models/wave.hpp @@ -6,12 +6,8 @@ #include "ship.hpp" #include "../data/spatial-hash-grid.hpp" -class App; - class WaveController { - private: - std::weak_ptr app; - + public: float waveInterval = LEVEL_WAVE_INTERVAL; float spawnInterval = LEVEL_ASTEROID_SPAWN_INTERVAL; uint8_t asteroidsPerWave = LEVEL_ASTEROIDS_PER_WAVE; @@ -22,16 +18,12 @@ class WaveController { float timeSinceLastSpawn = spawnInterval / 2.0f; uint16_t asteroidsSpawned = 0; - // Threading - std::thread thread; - - void spawn(std::shared_ptr app); - void threadCallback(); - public: - WaveController(); - ~WaveController(); + WaveController() = default; + ~WaveController() = default; + + void update(IContainer &asteroids, std::list &bullets, Ship &ship); - void renderGUI(); - void start(std::shared_ptr app); +private: + void spawn(IContainer &asteroids, std::list &bullets, Ship &ship); }; \ No newline at end of file diff --git a/src/core/precomp.hpp b/src/core/precomp.hpp index 1f29d1a..ae4f7d5 100644 --- a/src/core/precomp.hpp +++ b/src/core/precomp.hpp @@ -23,6 +23,5 @@ #include #include #include -#include -#include -#include +#include +#include \ No newline at end of file diff --git a/src/core/settings.cpp b/src/core/settings.cpp new file mode 100644 index 0000000..0408d49 --- /dev/null +++ b/src/core/settings.cpp @@ -0,0 +1,5 @@ +#include "settings.hpp" + +unsigned int WIDTH = 850; +unsigned int HEIGHT = 750; +unsigned int TARGET_FPS = 60; \ No newline at end of file diff --git a/src/core/settings.hpp b/src/core/settings.hpp index 9aa4822..7c75af1 100644 --- a/src/core/settings.hpp +++ b/src/core/settings.hpp @@ -1,55 +1,37 @@ #pragma once // Constants - General -inline unsigned int WIDTH = 850; -inline unsigned int HEIGHT = 750; -inline unsigned int TARGET_FPS = 60; +extern unsigned int WIDTH; +extern unsigned int HEIGHT; +extern unsigned int TARGET_FPS; // Constants - Ship -const float SHIP_SIZE = 10.0f; -const float SHIP_ANGULAR_ACCELERATION = 0.265f; -const float SHIP_MAX_ANGULAR_VELOCITY = 3.5f; -const float SHIP_ACCELERATION = 500.0f; -const float SHIP_DAMPING = 0.988f; -const float SHIP_ANGULAR_DAMPING = 0.963f; +const float SHIP_ANGULAR_ACCELERATION = 30.0f * DEG2RAD; +const float SHIP_ACCELERATION = 150.0f; +const float SHIP_DAMPING = 0.98f; +const float SHIP_ANGULAR_DAMPING = 0.90f; // Constants - Asteroid const float ASTEROID_RADIUS = 25.0f; const float ASTEROID_JAGGEDNESS = 5.0f; const float ASTEROID_ANGULAR_VELOCITY = 100.0f * DEG2RAD; const float ASTEROID_VELOCITY = 100.0f; -const float ASTEROID_RESTITUTION = 0.98f; const int ASTEROID_MIN_VERTEX_COUNT = 8; const int ASTEROID_MAX_VERTEX_COUNT = 32; const int ASTEROID_FRAGMENTS_COUNT = 3; const float ASTEROID_RADIUS_DIFFERENCE = 10.0f; const float ASTEROID_MIN_RADIUS_TO_SPLIT = 20.0f; -const float ASTEROID_MIN_FRAGMENT_RADIUS = 7.5f; // Constants - Bullet const float BULLET_VELOCITY = 500.0f; const float BULLET_RADIUS = 2.0f; const float BULLET_SHOOT_INTERVAL = 0.250f; -const float BULLET_TIME_TO_LIVE = 1.0f; // Constants - Level const uint8_t LEVEL_ASTEROIDS_PER_WAVE = 10; const float LEVEL_ASTEROID_SPAWN_INTERVAL = 5.0f; -const float LEVEL_ASTEROID_SPAWN_INTERVAL_DECREMENT_PER_WAVE = 0.15f; -const float LEVEL_ASTEROID_SPAWN_INTERVAL_MIN = 0.5f; +const float LEVEL_ASTEROID_SPAWN_INTERVAL_DECREMENT_PER_WAVE = 0.5f; +const float LEVEL_ASTEROID_SPAWN_INTERVAL_MIN = 0.05f; const float LEVEL_WAVE_INTERVAL = (LEVEL_ASTEROIDS_PER_WAVE + 1) * LEVEL_ASTEROID_SPAWN_INTERVAL; -const float LEVEL_WAVE_INTERVAL_DECREMENT = 0.25f; -const float LEVEL_WAVE_INTERVAL_MIN = 2.0f; - -// Flags - Grid -inline bool RENDER_GRID = false; - -// Flags - Bullets -const bool BULLETS_WRAP_AROUND_SCREEN = true; - -// Flags - Stars -inline bool RENDER_STAR_HALO = true; - -// Flags - Asteroids -const bool SPAWN_AT_START = true; -const bool ASTEROIDS_SELF_COLLISION = true; \ No newline at end of file +const float LEVEL_WAVE_INTERVAL_DECREMENT = 0.5f; +const float LEVEL_WAVE_INTERVAL_MIN = 2.0f; \ No newline at end of file diff --git a/src/core/utils.hpp b/src/core/utils.hpp index 12b60b8..316378c 100644 --- a/src/core/utils.hpp +++ b/src/core/utils.hpp @@ -1,14 +1,10 @@ #pragma once #include "Color.hpp" -#include "Image.hpp" -#include "Vector2.hpp" #include "precomp.hpp" #include "raylib.h" -#include "rlgl.h" #include "settings.hpp" #include -#include inline raylib::Vector2 rotateAround(raylib::Vector2 point, raylib::Vector2 center, float angle) { return { @@ -22,7 +18,7 @@ inline float getRandomValue(float min, float max) { } inline float smoothstep(float x) { - return x < 0.5f ? 2 * x * x : 1 - powf(-2 * x + 2, 2) / 2; + return x * x * (3 - 2 * x); } inline float easeInOutBack(float x) { @@ -41,127 +37,4 @@ inline raylib::Color randomColor() { GetRandomValue(128, 255), 255 ); -} - -/** - * @brief Given a center and a list of vertices, draw a filled polygon - * Raylib does provide a few functions for drawing polygons, but I don't really like the way they work and in some scenarios the results are incomplete polygons - * - * @param center - * @param vertices - * @param color - */ -static void drawFilledPolygon(raylib::Vector2 center, raylib::Vector2 *vertices, size_t size, raylib::Color color) { - if (size < 3) return; - if (size == 3) { - DrawTriangle(vertices[0], vertices[1], vertices[2], color); - } - -#if defined(SUPPORT_QUADS_DRAW_MODE) - constexpr Texture2D texShapes = { 1, 1, 1, 1, 7 }; - constexpr Rectangle texShapesRec = { 0.0f, 0.0f, 1.0f, 1.0f }; - rlSetTexture(texShapes.id); - - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - for (int i = 0; i < size; i++) - { - int j = (i + 1) % size; - - rlTexCoord2f(texShapesRec.x / texShapes.width, texShapesRec.y / texShapes.height); - rlVertex2f(vertices[i].x, vertices[i].y); - - rlTexCoord2f(texShapesRec.x / texShapes.width, (texShapesRec.y + texShapesRec.height) / texShapes.height); - rlVertex2f(center.x, center.y); - - rlTexCoord2f((texShapesRec.x + texShapesRec.width) / texShapes.width, (texShapesRec.y + texShapesRec.height) / texShapes.height); - rlVertex2f(center.x, center.y); - - rlTexCoord2f((texShapesRec.x + texShapesRec.width) / texShapes.width, texShapesRec.y / texShapes.height); - rlVertex2f(vertices[j].x, vertices[j].y); - } - - rlEnd(); - - rlSetTexture(0); - #else - rlBegin(RL_TRIANGLES); - rlColor4ub(color.r, color.g, color.b, color.a); - for (int i = 0; i < size; i++) - { - int j = i + 1; - if (j == size) j = 0; - - rlVertex2f(vertices[i].x, vertices[i].y); - rlVertex2f(center.x, center.y); - rlVertex2f(vertices[j].x, vertices[j].y); - } - - rlEnd(); -#endif -} - -static void drawPolygonOutline(raylib::Vector2 *vertices, size_t size, raylib::Color color) { - if (size < 3) return; - - rlBegin(RL_LINES); - rlColor4ub(color.r, color.g, color.b, color.a); - - for (int i = 0; i < size - 1; i++) { - rlVertex2f(vertices[i].x, vertices[i].y); - rlVertex2f(vertices[i + 1].x, vertices[i + 1].y); - } - - rlVertex2f(vertices[size - 1].x, vertices[size - 1].y); - rlVertex2f(vertices[0].x, vertices[0].y); - - rlEnd(); -} - -static float noise(raylib::Vector2 position) { - static std::optional image; - - if (!image.has_value()) { - image = GenImagePerlinNoise(200, 200, 0, 0, 0.2f); - } - - const int x = (int)position.x % 200; - const int y = (int)position.y % 200; - - const auto color = image->GetColor(x, y); - return color.r / 255.0f; -} - -static float randomKelvin() { - return powf(getRandomValue(0.0f, 1.0f), 1.0f / 2.0f) * 9000.0f + 1000.0f; -} - -static raylib::Color convertKelvinToColor(float temperature) { - // Using Tanner Helland's formula and Neil Bartlett's implementation - - float red, green, blue; - - if (temperature <= 6600.0f) { - red = 255.0f; - green = temperature / 100.0f - 2.0f; - green = 99.4708025861f * logf(green) - 161.1195681661f; - if (temperature <= 1900.0f) { - blue = 0.0f; - } else { - blue = temperature / 100.0f - 10.0f; - blue = 138.5177312231f * logf(blue) - 305.0447927307f; - } - } else { - red = temperature / 100.0f - 55.0f; - red = 329.698727446f * powf(red, -0.1332047592f); - green = temperature / 100.0f - 50.0f; - green = 288.1221695283f * powf(green, -0.0755148492f); - blue = 255.0f; - } - - return raylib::Color((unsigned char)red, (unsigned char)green, (unsigned char)blue, 255); -} - -inline float oscillate(float perSecond, float amplitude, float offset = 0.0f) { - return sinf(PI * 2.0f * GetTime() * perSecond + offset) * amplitude; } \ No newline at end of file