Compare commits

...

72 Commits

Author SHA1 Message Date
sebastian
ba47255ee0 UPD: Change Lobby Background 2026-08-07 18:59:55 +02:00
sebastian
8690efe184 FIX: Cyclic Includes of Scene and duplicate std::variant 2026-08-07 18:40:28 +02:00
sebastian
d6445092c2 UPD: Added LobbyScene 2026-08-06 21:34:26 +02:00
sebastian
452bf92e20 UPD: Added LobbyScene 2026-08-06 21:34:22 +02:00
sebastian
6c2f580134 UPD: Send Connection Success only when auth acc was received 2026-08-06 20:41:11 +02:00
sebastian
d58b84d4a5 Multiplayer Connection Start 2026-08-06 20:35:13 +02:00
sebastian
19a3e55e5f Multiplayer Connect Modal 2026-08-06 17:47:41 +02:00
sebastian
fe9ecb1ecc ADD: Quit when Quit button is pressed 2026-08-06 17:31:14 +02:00
sebastian
de7cbdc73c ADD: Main Menu 2026-08-06 16:59:38 +02:00
sebastian
82b499e65c ADD: Render Basic UI Textures 2026-08-06 16:18:23 +02:00
sebastian
4ab4ea5262 UPD: Compute MoveResult before mutating state of piece 2026-08-05 18:59:36 +02:00
sebastian
f86e1b872a UPD: Move getMoveablePieceIndices to LudoRules 2026-08-05 18:46:38 +02:00
sebastian
72c7db8439 UPD: Move wouldCapture to LudoRules 2026-08-05 18:44:14 +02:00
sebastian
1c8a6840e6 UPD: Move isBlockedByOwnPiece to LudoRules 2026-08-05 18:42:01 +02:00
sebastian
7426374b00 UPD: Move computeDestination to LudoRules 2026-08-05 18:38:31 +02:00
sebastian
a3bb54f041 UPD: Move computePath to PiecePathBuilder 2026-08-05 18:34:02 +02:00
sebastian
bb75a3400a FIX: SIGSEGV 2026-08-05 17:26:57 +02:00
sebastian
5e580af16f WIP: GameController 2026-08-05 16:00:32 +02:00
sebastian
c755d39677 FIX: Correct Handling of Captured Case 2026-08-05 14:21:32 +02:00
sebastian
cdc6a1f92c ADD: Synchronize Hud with Animations 2026-08-05 13:41:25 +02:00
sebastian
64671363dc ADD: PlaySound when piece walks to its destination 2026-08-05 12:25:37 +02:00
sebastian
a5643d794c ADD: PlaySound when rolling dice 2026-08-05 10:40:01 +02:00
sebastian
89435a66a4 FIX: Animation for Capturing 2026-08-05 09:37:09 +02:00
sebastian
97f8e71e18 ADD: Enable AnimationSystem 2026-08-05 09:01:18 +02:00
sebastian
12c617587f ADD: Tests for LudoGameMode::computePath 2026-08-05 07:47:24 +02:00
sebastian
a278ce14c5 UPD: Remove thinking... spam 2026-08-05 07:19:09 +02:00
sebastian
ecc3f595bc UPD: Enhance output logs 2026-08-05 07:11:06 +02:00
sebastian
d4dbcc85fb UPD: Enhance test and output logs 2026-08-05 07:10:27 +02:00
sebastian
befd8b3cd9 ADD: AnimationSystem + Tests 2026-08-05 06:41:08 +02:00
sebastian
dedc37f6ac ADD: AnimationSystem 2026-08-04 18:56:02 +02:00
sebastian
a1936d666c ADD: Async Sound Loading + AudioLayer 2026-08-04 18:00:33 +02:00
sebastian
a5a97d141e ADD: Basic Sounds 2026-08-04 17:01:36 +02:00
sebastian
01e74fe40e CLEANUP 2026-08-04 17:01:25 +02:00
sebastian
1923da2339 ADD: HighlightSystem 2026-08-04 16:12:32 +02:00
sebastian
f6096e2889 ADD: PickingSystem with AABB 2026-08-04 15:50:07 +02:00
sebastian
b879f2a636 ADD: AABB 2026-08-04 15:36:33 +02:00
sebastian
023016c6a2 ADD: SphereFactory 2026-08-04 14:42:57 +02:00
sebastian
6ee58d4901 ADD: Visualize BoundingSpheres 2026-08-04 14:42:42 +02:00
sebastian
bb29a1d3f1 ADD: Basic AI 2026-08-02 21:52:16 +02:00
sebastian
c6d1fd8910 ADD: Basic UI 2026-08-02 21:04:34 +02:00
sebastian
7d9b31fe12 ADD: PickingSystem 2026-08-02 20:35:06 +02:00
sebastian
4ce6791343 ADD: Pawn materials 2026-08-02 20:11:16 +02:00
sebastian
9bce759259 ADD: Spawn Pawns 2026-08-02 20:11:05 +02:00
sebastian
c82fc49aac ADD: Boardlayout + Fix Section Name 2026-08-02 17:38:07 +02:00
sebastian
5d1da184e0 ADD: Centroid in meshSection 2026-08-02 17:07:58 +02:00
sebastian
1a2b6ae364 UPD: Rename background texture of gameboard 2026-08-02 17:05:44 +02:00
sebastian
5262143af8 ADD: Render Gameboard 2026-08-02 17:04:43 +02:00
sebastian
a1c6ffa0b7 ADD: Ludo Game 2026-08-02 15:56:57 +02:00
sebastian
7d1774e222 ADD: Generic GameMode and GameState 2026-08-02 14:45:39 +02:00
sebastian
92e3092d3b ADD: Move Camera 2026-08-02 13:51:24 +02:00
sebastian
ef72cb56c6 ADD: InputHandling 2026-08-02 13:35:31 +02:00
sebastian
6e4863961d UPD: Activate Backface Culling 2026-08-02 11:05:00 +02:00
sebastian
6b011860fc ADD: Phong Lighting model 2026-08-02 10:17:38 +02:00
sebastian
86e15013d0 ADD: Load Material to shader 2026-08-02 09:26:55 +02:00
sebastian
0b2f7ba60f UPD: Use texture for coloring 2026-08-02 08:59:30 +02:00
sebastian
cfe51648bd FIX: Correctly assign material name to model 2026-08-02 08:56:41 +02:00
sebastian
96d2e89d48 ADD: AssetManager 2026-07-30 18:33:15 +02:00
sebastian
eeb7c9c00f ADD: Asset Loading 2026-07-30 14:23:19 +02:00
sebastian
6e37fda356 ADD: Introduce Scenes 2026-07-30 07:42:56 +02:00
sebastian
c1732e01fa ADD: Introduce GameLayer 2026-07-30 06:49:17 +02:00
sebastian
484fe4aa3e ADD: Render with ECS 2026-07-29 21:55:01 +02:00
sebastian
eedee9dd9f ADD: ECS-System 2026-07-29 19:03:48 +02:00
sebastian
474874d21b ADD: Projection, Transformation and ViewMatrix 2026-07-29 16:33:14 +02:00
sebastian
30c9683780 ADD: Rendering cubes 2026-07-29 15:06:44 +02:00
sebastian
2088dde6ca ADD: Render first triangle 2026-07-29 14:44:07 +02:00
sebastian
081a091f37 ADD: VAO and VBO classes for OpenGL 2026-07-29 13:59:52 +02:00
sebastian
8ae128e0ee ADD: Basic Shader and Attribute Infrastructure for OpenGL 2026-07-29 13:37:59 +02:00
sebastian
4ada346252 ADD: OpenGLWrapper 2026-07-29 12:18:47 +02:00
sebastian
7c7ac67fc8 ADD: Basic Window 2026-07-29 11:14:15 +02:00
sebastian
a7c2e586eb ADD: Include dependencies 2026-07-29 10:39:14 +02:00
sebastian
d45bd62623 UPD: .gitignore 2026-07-29 10:23:30 +02:00
760b619f9e Merge pull request 'master' (#1) from master into main
Reviewed-on: #1
2026-07-29 08:08:32 +00:00
240 changed files with 14812 additions and 28 deletions

8
.gitignore vendored
View File

@ -1,7 +1,13 @@
# ---> C++ # ---> C++
# Prerequisites # Prerequisites
*.d *.d
cmake-build-*/
.idea/
*.o
*.obj
*.exe
*.out
build/
# Compiled Object files # Compiled Object files
*.slo *.slo
*.lo *.lo

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "external/NetworkCore"]
path = external/NetworkCore
url = git@git.fawkes100.de:sebastian/JsonTCPServer.git

View File

@ -1,8 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4"> <module classpath="CIDR" type="CPP_MODULE" version="4" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,6 +1,358 @@
cmake_minimum_required(VERSION 4.2) cmake_minimum_required(VERSION 4.2)
project(ColorRace) project(ColorRace)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(ColorRace main.cpp) # --- CPM.cmake Bootstrap ---
set(CPM_DOWNLOAD_VERSION 0.40.2)
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
if(NOT EXISTS ${CPM_DOWNLOAD_LOCATION})
file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION}
)
endif()
include(${CPM_DOWNLOAD_LOCATION})
# --- OpenGL (systemseitig, kein CPM nötig) ---
find_package(OpenGL REQUIRED)
# --- GLFW ---
CPMAddPackage(
NAME glfw
GITHUB_REPOSITORY glfw/glfw
GIT_TAG 3.4
OPTIONS
"GLFW_BUILD_EXAMPLES OFF"
"GLFW_BUILD_TESTS OFF"
"GLFW_BUILD_DOCS OFF"
"GLFW_INSTALL OFF"
)
# --- GLM (header-only) ---
CPMAddPackage(
NAME glm
GITHUB_REPOSITORY g-truc/glm
GIT_TAG 1.0.1
)
# --- tinyobjloader ---
CPMAddPackage(
NAME tinyobjloader
GITHUB_REPOSITORY tinyobjloader/tinyobjloader
GIT_TAG v2.0.0rc13
)
# --- stb_image (kein CMake-Projekt, nur Header) ---
CPMAddPackage(
NAME stb
GITHUB_REPOSITORY nothings/stb
GIT_TAG master
DOWNLOAD_ONLY YES
)
add_library(stb_image STATIC third_party/stb_image_impl.cpp)
target_include_directories(stb_image PUBLIC ${stb_SOURCE_DIR})
# --- Dear ImGui (Core + GLFW/OpenGL3 Backend) ---
CPMAddPackage(
NAME imgui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.91.5
DOWNLOAD_ONLY YES
)
CPMAddPackage(
NAME spdlog
GITHUB_REPOSITORY gabime/spdlog
GIT_TAG v1.15.3
)
# --- GoogleTest ---
CPMAddPackage(
NAME googletest
GITHUB_REPOSITORY google/googletest
GIT_TAG v1.15.2
OPTIONS
"INSTALL_GTEST OFF"
"gtest_force_shared_crt ON"
)
enable_testing()
add_library(imgui STATIC
${imgui_SOURCE_DIR}/imgui.cpp
${imgui_SOURCE_DIR}/imgui_draw.cpp
${imgui_SOURCE_DIR}/imgui_tables.cpp
${imgui_SOURCE_DIR}/imgui_widgets.cpp
${imgui_SOURCE_DIR}/imgui_demo.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_glfw.cpp
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
)
target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR} ${imgui_SOURCE_DIR}/backends)
target_link_libraries(imgui PUBLIC glfw glad)
# --- GLAD (manuell generiert, siehe README/Hinweis unten) ---
add_library(glad STATIC third_party/glad/src/glad.c)
target_include_directories(glad PUBLIC third_party/glad/include)
# --- OpenAL-Soft ---
CPMAddPackage(
NAME openal-soft
GITHUB_REPOSITORY kcat/openal-soft
GIT_TAG 1.24.3
OPTIONS
"LIBTYPE STATIC"
"ALSOFT_UTILS OFF"
"ALSOFT_EXAMPLES OFF"
"ALSOFT_TESTS OFF"
"ALSOFT_INSTALL OFF"
)
# --- dr_libs (dr_wav.h, dr_mp3.h header-only) ---
CPMAddPackage(
NAME dr_libs
GITHUB_REPOSITORY mackron/dr_libs
GIT_TAG master
DOWNLOAD_ONLY YES
)
message(STATUS "dr_libs_SOURCE_DIR = ${dr_libs_SOURCE_DIR}")
add_subdirectory(external/NetworkCore)
# Engine als statische Lib
add_library(Engine STATIC
engine/src/core/Window.h
engine/src/core/Window.cpp
engine/src/core/Application.cpp
engine/src/core/Application.h
engine/src/core/EngineConfig.h
engine/src/utils/openglWrapper/GLStateCache.cpp
engine/src/utils/openglWrapper/GLStateCache.h
engine/src/utils/openglWrapper/GLRenderState.cpp
engine/src/utils/openglWrapper/GLRenderState.h
engine/src/utils/openglWrapper/openglObjects/Attribute.cpp
engine/src/utils/openglWrapper/openglObjects/Attribute.h
engine/src/utils/openglWrapper/openglObjects/Vbo.cpp
engine/src/utils/openglWrapper/openglObjects/Vbo.h
engine/src/utils/openglWrapper/openglObjects/Vao.cpp
engine/src/utils/openglWrapper/openglObjects/Vao.h
engine/src/utils/openglWrapper/shader/ShaderProgram.cpp
engine/src/utils/openglWrapper/shader/ShaderProgram.h
engine/src/renderer/primitives/SphereFactory.cpp
engine/src/renderer/primitives/SphereFactory.h
engine/src/utils/openglWrapper/shader/Uniform.cpp
engine/src/utils/openglWrapper/shader/Uniform.h
engine/src/utils/openglWrapper/shader/UniformValue.cpp
engine/src/utils/openglWrapper/shader/UniformValue.h
engine/src/layer/Scene.cpp
engine/src/layer/Scene.h
engine/src/loader/AssetLoader.cpp
engine/src/loader/AssetLoader.h
engine/src/loader/assets/AssetTypes.h
engine/src/loader/assets/AssetRequests.h
engine/src/loader/assets/AssetRequests.cpp
engine/src/loader/assets/RawAssetData.h
engine/src/loader/assets/AssetFormatters.h
engine/src/loader/textures/TextureUploader.cpp
engine/src/loader/textures/TextureUploader.h
engine/src/loader/textures/TextureImporter.cpp
engine/src/loader/textures/TextureImporter.h
engine/src/loader/textures/Texture2D.cpp
engine/src/loader/textures/Texture2D.h
engine/src/loader/models/ModelImporter.cpp
engine/src/loader/models/ModelImporter.h
engine/src/loader/models/ModelUploader.cpp
engine/src/loader/models/ModelUploader.h
engine/src/loader/models/Material.cpp
engine/src/loader/models/Material.h
engine/src/loader/assets/LoadedAssets.h
engine/src/loader/models/Model.cpp
engine/src/loader/models/Model.h
engine/src/loader/models/MeshSection.cpp
engine/src/loader/models/MeshSection.h
engine/src/loader/assets/AssetManager.cpp
engine/src/loader/assets/AssetManager.h
engine/src/renderer/entities/PointLight.cpp
engine/src/renderer/entities/PointLight.h
engine/src/core/inputsOutputs/inputs/MouseButton.h
engine/src/core/inputsOutputs/inputs/Mouse.cpp
engine/src/core/inputsOutputs/inputs/Mouse.h
engine/src/core/inputsOutputs/inputs/Keyboard.cpp
engine/src/core/inputsOutputs/inputs/Keyboard.h
engine/src/core/inputsOutputs/context/InputContextStack.cpp
engine/src/core/inputsOutputs/context/InputContextStack.h
engine/src/core/inputsOutputs/controller/CameraController.cpp
engine/src/core/inputsOutputs/controller/CameraController.h
engine/src/game/GameState.cpp
engine/src/game/GameState.h
engine/src/game/GameMode.cpp
engine/src/game/GameMode.h
engine/src/game/TurnBasedGameMode.cpp
engine/src/game/TurnBasedGameMode.h
engine/src/ecs/standardComponents/PickableComponent.h
engine/src/ecs/systems/PickingSystem.cpp
engine/src/ecs/systems/PickingSystem.h
engine/src/ui/ImGuiContext.cpp
engine/src/ui/ImGuiContext.h
engine/src/renderer/shader/BoundingDebugShader.cpp
engine/src/renderer/shader/BoundingDebugShader.h
engine/src/renderer/DebugRenderer.cpp
engine/src/renderer/DebugRenderer.h
engine/src/layer/DebugLayer.cpp
engine/src/layer/DebugLayer.h
engine/src/loader/models/AABB.cpp
engine/src/loader/models/AABB.h
engine/src/loader/models/Ray.h
engine/src/ecs/standardComponents/HighlightComponent.h
third_party/audio_impl.cpp
engine/src/core/audio/AudioDevice.cpp
engine/src/core/audio/AudioDevice.h
engine/src/core/audio/SoundBuffer.cpp
engine/src/core/audio/SoundBuffer.h
engine/src/core/audio/SoundSource.cpp
engine/src/core/audio/SoundSource.h
engine/src/loader/sounds/SoundUploader.cpp
engine/src/loader/sounds/SoundUploader.h
engine/src/core/animation/components/TransformAnimationComponent.h
engine/src/core/animation/AnimationSystem.cpp
engine/src/core/animation/AnimationSystem.h
engine/src/renderer/shader/StaticShader.cpp
engine/src/renderer/shader/StaticShader.h
engine/src/renderer/entities/Camera.cpp
engine/src/renderer/entities/Camera.h
engine/src/ecs/EntityRegistry.cpp
engine/src/ecs/EntityRegistry.h
engine/src/ecs/Entity.cpp
engine/src/ecs/Entity.h
engine/src/ecs/ComponentPool.cpp
engine/src/ecs/ComponentPool.h
engine/src/ecs/standardComponents/TransformComponent.h
engine/src/ecs/standardComponents/MeshComponent.h
engine/src/ecs/EntitySystem.cpp
engine/src/ecs/EntitySystem.h
engine/src/renderer/RenderSystem.cpp
engine/src/renderer/RenderSystem.h
engine/src/renderer/RenderQueue.cpp
engine/src/renderer/RenderQueue.h
engine/src/renderer/MeshRenderSystem.cpp
engine/src/renderer/MeshRenderSystem.h
engine/src/renderer/MeshRenderer.cpp
engine/src/renderer/MeshRenderer.h
engine/src/layer/Layer.cpp
engine/src/layer/Layer.h
engine/src/core/inputsOutputs/context/InputContext.cpp
engine/src/core/inputsOutputs/context/InputContext.h
engine/src/layer/SceneManager.cpp
engine/src/layer/SceneManager.h
engine/src/core/events/EventBus.cpp
engine/src/core/events/EventBus.h
engine/src/core/audio/ecs/PlaySoundComponent.h
engine/src/core/animation/events/AnimationMarkerEvent.h
engine/src/core/animation/components/BlockingAnimationComponent.h
engine/src/core/audio/ecs/AudioSystem.cpp
engine/src/core/audio/ecs/AudioSystem.h
engine/src/core/audio/ecs/BlockingSoundComponent.h
engine/src/renderer/shader/GuiShader.cpp
engine/src/renderer/shader/GuiShader.h
engine/src/renderer/primitives/QuadFactory.cpp
engine/src/renderer/primitives/QuadFactory.h
engine/src/loader/models/SimpleTexturedMesh.cpp
engine/src/loader/models/SimpleTexturedMesh.h
engine/src/loader/models/Mesh.cpp
engine/src/loader/models/Mesh.h
engine/src/renderer/GuiRenderer.cpp
engine/src/renderer/GuiRenderer.h
engine/src/renderer/ui/GUITexture.cpp
engine/src/renderer/ui/GUITexture.h
engine/src/core/events/ApplicationEvents.h
engine/src/core/events/EventChannels.cpp
engine/src/core/events/EventChannels.h
engine/src/core/events/ApplicationEvents.cpp
)
target_include_directories(Engine PUBLIC engine/src ${dr_libs_SOURCE_DIR})
target_link_libraries(Engine PUBLIC OpenGL::GL glfw glad glm::glm imgui spdlog::spdlog tinyobjloader stb_image OpenAL::OpenAL NetworkCore)
# --- GameLib: Spiel-Logik, unabhängig von main.cpp testbar ---
add_library(GameLib STATIC
game/src/ColorRaceApp.cpp
game/src/ColorRaceApp.h
game/src/gameLayer/GameLayer.cpp
game/src/gameLayer/GameLayer.h
game/src/GameScene.cpp
game/src/GameScene.h
game/src/ludo/LudoGameState.cpp
game/src/ludo/LudoGameState.h
game/src/ludo/LudoTypes.h
game/src/ludo/LudoGameMode.cpp
game/src/ludo/LudoGameMode.h
game/src/ludo/BoardLayout.cpp
game/src/ludo/BoardLayout.h
game/src/ludo/PiecePresenter.cpp
game/src/ludo/PiecePresenter.h
game/src/ludo/LudoAiController.cpp
game/src/ludo/LudoAiController.h
game/src/ludo/ecs/highlight/HighlightSystem.cpp
game/src/ludo/ecs/highlight/HighlightSystem.h
game/src/AudioLayer.cpp
game/src/AudioLayer.h
game/src/ludo/DiceSource.cpp
game/src/ludo/DiceSource.h
game/src/ludo/PresentationGameState.cpp
game/src/ludo/PresentationGameState.h
game/src/gameLayer/GameRenderer.cpp
game/src/gameLayer/GameRenderer.h
game/src/gameLayer/gameController/LudoGameController.cpp
game/src/gameLayer/gameController/LudoGameController.h
game/src/gameLayer/LudoHud.cpp
game/src/gameLayer/LudoHud.h
game/src/gameLayer/GameInput.cpp
game/src/gameLayer/GameInput.h
game/src/gameLayer/gameController/LudoGameControllerContext.h
game/src/ludo/gameMode/LudoRules.cpp
game/src/ludo/gameMode/LudoRules.h
game/src/ludo/gameMode/PiecePathBuilder.cpp
game/src/ludo/gameMode/PiecePathBuilder.h
game/src/ludo/gameMode/PathNode.h
game/src/mainMenu/MainMenuScene.cpp
game/src/mainMenu/MainMenuScene.h
)
target_include_directories(GameLib PUBLIC game/src)
target_link_libraries(GameLib PUBLIC Engine)
add_subdirectory(server)
# --- Executable ---
add_executable(ColorRace
game/src/main.cpp
game/src/mainMenu/layer/MainMenuUiLayer.cpp
game/src/mainMenu/layer/MainMenuUiLayer.h
game/src/multiplayer/LudoGameClient.cpp
game/src/multiplayer/LudoGameClient.h
game/src/multiplayer/LudoNetworkEvents.h
game/src/multiplayer/lobby/LobbyScene.cpp
game/src/multiplayer/lobby/LobbyScene.h
game/src/multiplayer/lobby/LobbyUiLayer.cpp
game/src/multiplayer/lobby/LobbyUiLayer.h
)
target_link_libraries(ColorRace PRIVATE
Engine
GameLib
)
# --- Tests ---
add_executable(ColorRaceTests
tests/LoduGameMode.cpp
tests/helpers/SequenceDiceSource.cpp
tests/helpers/SequenceDiceSource.h
)
target_link_libraries(ColorRaceTests PRIVATE
GameLib
GTest::gtest
GTest::gtest_main
)
include(GoogleTest)
gtest_discover_tests(ColorRaceTests)

View File

@ -0,0 +1,62 @@
# Blender 5.2.0 LTS MTL File: 'GameBoard.blend'
# www.blender.org
newmtl Background
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd gameboard_brackground.png
newmtl Blue
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd chess_blue.png
newmtl Green
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd chess_green.png
newmtl Material
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd track.jpg
newmtl Red
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd chess_red.png
newmtl Yellow
Ns 250.000000
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 2
map_Kd chess_yellow.png

12
assets/models/chess.mtl Normal file
View File

@ -0,0 +1,12 @@
# Blender 5.2.0 LTS MTL File: 'None'
# www.blender.org
newmtl Material.002
Ns 735.817688
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd chess_blue.png

View File

@ -0,0 +1,12 @@
# Blender 5.2.0 LTS MTL File: 'None'
# www.blender.org
newmtl Material.002
Ns 735.817688
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd chess_blue.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

View File

@ -0,0 +1,12 @@
# Blender 5.2.0 LTS MTL File: 'None'
# www.blender.org
newmtl Material.002
Ns 735.817688
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd chess_green.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 B

View File

@ -0,0 +1,12 @@
# Blender 5.2.0 LTS MTL File: 'None'
# www.blender.org
newmtl Material.002
Ns 735.817688
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd chess_red.png

BIN
assets/models/chess_red.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

View File

@ -0,0 +1,12 @@
# Blender 5.2.0 LTS MTL File: 'None'
# www.blender.org
newmtl Material.002
Ns 735.817688
Ka 1.000000 1.000000 1.000000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd chess_yellow.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

BIN
assets/models/track.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

41
assets/shaders/basic.frag Normal file
View File

@ -0,0 +1,41 @@
#version 460 core
in vec2 pass_textureCoords;
in vec3 surfaceNormal;
in vec3 worldPos;
uniform sampler2D diffuseSampler;
uniform float opacity;
uniform float shininess;
uniform vec3 lightPosition;
uniform vec3 lightColor;
uniform vec3 cameraPosition;
uniform vec3 highlightColor;
uniform float highlightIntensity;
out vec4 fragColor;
void main() {
vec3 normal = normalize(surfaceNormal);
vec3 lightDir = normalize(lightPosition - worldPos);
vec3 camDir = normalize(cameraPosition - worldPos);
vec3 reflectDir = reflect(-lightDir, normal);
vec3 ambient = 0.1 * lightColor;
float diff = max(dot(normal, lightDir) , 0.0f);
vec3 diffuse = diff * lightColor;
float spec = pow(max(dot(camDir, reflectDir), 0.0), max(shininess, 1.0f));
vec3 specular = 0.5 * spec * lightColor;
vec4 texColor = texture(diffuseSampler, pass_textureCoords);
fragColor = vec4((ambient + diffuse + specular) * texColor.rgb, texColor.a * opacity);
float intensity = clamp(highlightIntensity, 0.0, 1.0);
fragColor.rgb += highlightColor * intensity;
}

22
assets/shaders/basic.vert Normal file
View File

@ -0,0 +1,22 @@
#version 460 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 textureCoords;
uniform mat4 transformationMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
out vec2 pass_textureCoords;
out vec3 surfaceNormal;
out vec3 worldPos;
void main() {
vec4 worldPosition = transformationMatrix * vec4(position, 1.0f);
gl_Position = projectionMatrix * viewMatrix * worldPosition;
pass_textureCoords = textureCoords;
surfaceNormal = mat3(transpose(inverse(transformationMatrix))) * normal;
worldPos = worldPosition.xyz;
}

View File

@ -0,0 +1,8 @@
#version 460 core
uniform vec3 color;
out vec4 fragColor;
void main() {
fragColor = vec4(color, 1.0f);
}

View File

@ -0,0 +1,14 @@
#version 460 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 textureCoords;
uniform mat4 transformationMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
void main() {
vec4 worldPosition = transformationMatrix * vec4(position, 1.0f);
gl_Position = projectionMatrix * viewMatrix * worldPosition;
}

11
assets/shaders/gui.frag Normal file
View File

@ -0,0 +1,11 @@
#version 460 core
in vec2 textureCoords;
out vec4 color;
uniform sampler2D guiTexture;
void main() {
color = texture(guiTexture, textureCoords);
}

12
assets/shaders/gui.vert Normal file
View File

@ -0,0 +1,12 @@
#version 460 core
layout(location = 0) in vec2 position;
out vec2 textureCoords;
uniform mat4 transformationMatrix;
void main() {
gl_Position = transformationMatrix * vec4(position, 0.0, 1.0);
textureCoords = vec2((position.x + 1.0) / 2.0, (position.y + 1.0) / 2.0);
}

Binary file not shown.

BIN
assets/sounds/pawn_step.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -0,0 +1,60 @@
//
// Created by sebastian on 29.07.26.
//
#include "Application.h"
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "utils/openglWrapper/GLStateCache.h"
namespace engine {
Application::Application(const EngineConfig& config)
: m_config(config),
m_window(std::make_unique<Window>(config)),
m_inputContextStack(),
m_keyboard(*m_window),
m_mouse(*m_window),
m_sceneManager(m_inputContextStack),
m_applicationEventBus(std::make_shared<EventBus<ApplicationEvents>>()) {
m_applicationEventBus->subscribe([this](ApplicationEvents& event) {
std::visit([this](auto&& e) {
using T = std::decay_t<decltype(e)>;
if constexpr (std::is_same_v<T, QuitRequested>) {
m_window->requestWindowClose();
} else if constexpr (std::is_same_v<T, SceneSwitchRequested>) {
m_sceneManager.switchTo(std::move(e.nextScene), std::move(e.onSceneLoaded));
}
}, event);
});
}
Application::~Application() = default;
void Application::run() {
auto lastTime = static_cast<float>(glfwGetTime());
while (!m_window->shouldClose()) {
const auto currentTime = static_cast<float>(glfwGetTime());
const float deltaTime = currentTime - lastTime;
lastTime = currentTime;
m_window->pollEvents();
m_imguiContext.beginFrame();
onUpdate(deltaTime);
GLStateCache::clearFrameBuffer(m_config.clearColor);
onRender();
m_imguiContext.endFrame();
m_window->swapBuffers();
m_keyboard.update();
m_mouse.update();
}
}
}

View File

@ -0,0 +1,57 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_APPLICATION_H
#define COLORRACE_APPLICATION_H
#include <memory>
#include <string>
#include "EngineConfig.h"
#include "Window.h"
#include "audio/AudioDevice.h"
#include "events/ApplicationEvents.h"
#include "inputsOutputs/context/InputContextStack.h"
#include "inputsOutputs/inputs/Keyboard.h"
#include "inputsOutputs/inputs/Mouse.h"
#include "layer/SceneManager.h"
#include "ui/ImGuiContext.h"
namespace engine {
class Application {
public:
Application(const EngineConfig& config);
virtual ~Application();
void run();
void requestClose();
protected:
virtual void onUpdate(float deltaTime) {}
virtual void onRender() {}
InputContextStack& getInputContextStack() { return m_inputContextStack; }
SceneManager& getSceneManager() { return m_sceneManager; }
Keyboard& getKeyboard() { return m_keyboard; }
Mouse& getMouse() { return m_mouse; }
std::shared_ptr<EventBus<ApplicationEvents>> m_applicationEventBus;
private:
EngineConfig m_config;
std::unique_ptr<Window> m_window;
InputContextStack m_inputContextStack;
Keyboard m_keyboard{*m_window}; // braucht Window& im Konstruktor
Mouse m_mouse{*m_window};
SceneManager m_sceneManager{m_inputContextStack};
ImGuiContext m_imguiContext{*m_window}; // neu
engine::audio::AudioDevice m_audioDevice;
};
}
#endif //COLORRACE_APPLICATION_H

View File

@ -0,0 +1,28 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_ENGINECONFIG_H
#define COLORRACE_ENGINECONFIG_H
#include <string>
#include "glm/vec4.hpp"
namespace engine {
struct EngineConfig {
int windowWidth = 1280;
int windowHeight = 720;
std::string windowTitle = "Engine";
bool fullscreen = false;
bool vsync = true;
bool msaa = true;
int msaaSamples = 4;
glm::vec4 clearColor = {0.1f, 0.1f, 0.15f, 1.0f};
bool debugContext = true; // GL_DEBUG_OUTPUT an/aus
};
}
#endif //COLORRACE_ENGINECONFIG_H

View File

@ -0,0 +1,91 @@
//
// Created by sebastian on 29.07.26.
//
#include "Window.h"
#include <iostream>
#include "EngineConfig.h"
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include "utils/openglWrapper/GLStateCache.h"
namespace engine {
static void GLAPIENTRY DebugCallback(GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length,
const GLchar* message, const void* userParam) {
if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return; // Spam raus
std::cerr << "[GL DEBUG] " << message << "\n";
}
Window::Window(const EngineConfig& config) {
if (!glfwInit()) throw std::runtime_error("GLFW init failed");
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, config.debugContext ? GLFW_TRUE : GLFW_FALSE);
if (config.msaa) {
glfwWindowHint(GLFW_SAMPLES, config.msaaSamples);
}
GLFWmonitor* monitor = config.fullscreen ? glfwGetPrimaryMonitor() : nullptr;
m_handle = glfwCreateWindow(config.windowWidth, config.windowHeight, config.windowTitle.c_str(), monitor, nullptr);
if (!m_handle) {
glfwTerminate();
throw std::runtime_error("Window creation failed");
}
glfwMakeContextCurrent(m_handle);
glfwSwapInterval(config.vsync ? 1 : 0); // VSync
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
throw std::runtime_error("GLAD init failed");
}
GLStateCache::init();
if (config.debugContext) {
std::cout << "Debug Context should be enabled" << std::endl;
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(DebugCallback, nullptr);
}
if (config.msaa) {
glEnable(GL_MULTISAMPLE);
}
glViewport(0, 0, config.windowWidth, config.windowHeight);
glfwSetWindowUserPointer(m_handle, &m_callbackTarget);
}
Window::~Window() {
glfwDestroyWindow(m_handle);
glfwTerminate();
}
bool Window::shouldClose() const { return glfwWindowShouldClose(m_handle); }
void Window::swapBuffers() { glfwSwapBuffers(m_handle); }
void Window::pollEvents() { glfwPollEvents(); }
int Window::getWidth() const {
int width, height;
glfwGetFramebufferSize(m_handle, &width, &height);
return width;
}
int Window::getHeight() const {
int width, height;
glfwGetFramebufferSize(m_handle, &width, &height);
return height;
}
void Window::requestWindowClose() {
glfwSetWindowShouldClose(m_handle, true);
}
}

51
engine/src/core/Window.h Normal file
View File

@ -0,0 +1,51 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_WINDOW_H
#define COLORRACE_WINDOW_H
#pragma once
#include <string>
#include "EngineConfig.h"
struct GLFWwindow;
namespace engine {
class Keyboard;
class Mouse;
class Window {
public:
struct CallbackTarget {
Mouse* mouse = nullptr;
Keyboard* keyboard = nullptr;
};
Window(const EngineConfig& config);
~Window();
bool shouldClose() const;
void swapBuffers();
void pollEvents();
[[nodiscard]] GLFWwindow* getHandle() const { return m_handle; }
[[nodiscard]] int getWidth() const;
[[nodiscard]] int getHeight() const;
void setMouse(Mouse* mouse) { m_callbackTarget.mouse = mouse; }
void setKeyboard(Keyboard* keyboard) { m_callbackTarget.keyboard = keyboard; }
void requestWindowClose();
private:
GLFWwindow* m_handle;
CallbackTarget m_callbackTarget;
};
}
#endif //COLORRACE_WINDOW_H

View File

@ -0,0 +1,53 @@
//
// Created by sebastian on 04.08.26.
//
#include "AnimationSystem.h"
#include <map>
#include "components/TransformAnimationComponent.h"
#include "ecs/standardComponents/TransformComponent.h"
namespace engine::animation {
void animation::AnimationSystem::update(float deltaTime) {
std::vector<engine::Entity> toRemove;
for (auto& [entity, animation] : m_registry.getPool<TransformAnimationComponent>()) {
animation.time += deltaTime;
while (animation.nextMarkerIndex < animation.markers.size() && animation.markers[animation.nextMarkerIndex].time <= animation.time) {
animation.markers[animation.nextMarkerIndex].callback();
animation.nextMarkerIndex++;
}
auto& transform = m_registry.getComponent<TransformComponent>(entity);
if (!animation.position.keys.empty()) {
transform.m_position = evaluate(animation.position, animation.time);
}
if (!animation.rotation.keys.empty()) {
transform.m_rotation = evaluate(animation.rotation, animation.time);
}
if (!animation.scale.keys.empty()) {
transform.m_scale = evaluate(animation.scale, animation.time);
}
if (animation.time >= animation.duration) {
if (animation.loop) {
animation.time = 0.f;
animation.nextMarkerIndex = 0;
} else {
if (animation.onFinished) {
animation.onFinished();
}
animation.finished = true;
toRemove.push_back(entity);
}
}
}
for (auto entity : toRemove) {
m_registry.removeComponent<TransformAnimationComponent>(entity);
}
}
} // engine

View File

@ -0,0 +1,60 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_ANIMATIONSYSTEM_H
#define COLORRACE_ANIMATIONSYSTEM_H
#include "components/TransformAnimationComponent.h"
#include "ecs/EntityRegistry.h"
#include "glm/glm.hpp"
namespace engine::animation {
class AnimationSystem {
public:
explicit AnimationSystem(EntityRegistry& registry) : m_registry(registry) {}
void update(float deltaTime);
private:
EntityRegistry& m_registry;
template<typename T>
T evaluate(const AnimationTrack<T>& track, float time) {
Keyframe<T> a;
Keyframe<T> b;
findSurroundingKeys(track, time, a, b);
if (a.time == b.time) return a.value;
float localT = (time - a.time) / (b.time - a.time);
float easedT = a.easingFunction(localT);
return glm::mix(a.value, b.value, easedT);
}
template<typename T>
void findSurroundingKeys(const AnimationTrack<T>& track, float time, Keyframe<T>& a, Keyframe<T>& b) {
if (track.keys.size() == 1) {
a = b = track.keys.front();
return;
}
if (time <= track.keys.front().time) {
a = b = track.keys.front();
return;
}
if (time >= track.keys.back().time) {
a = b = track.keys.back();
return;
}
for (size_t i = 1; i < track.keys.size(); ++i) {
if (track.keys[i - 1].time <= time && track.keys[i].time >= time) {
a = track.keys[i - 1];
b = track.keys[i];
return;
}
}
}
};
} // engine
#endif //COLORRACE_ANIMATIONSYSTEM_H

View File

@ -0,0 +1,11 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_BLOCKINGANIMATIONCOMPONENT_H
#define COLORRACE_BLOCKINGANIMATIONCOMPONENT_H
namespace engine::animation {
struct BlockingAnimationComponent {
};
}
#endif //COLORRACE_BLOCKINGANIMATIONCOMPONENT_H

View File

@ -0,0 +1,51 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_TRANSFORMANIMATIONCOMPONENT_H
#define COLORRACE_TRANSFORMANIMATIONCOMPONENT_H
#include <functional>
#include "glm/glm.hpp"
namespace engine::animation {
using EasingFunction = std::function<float(float)>;
template<typename T>
struct Keyframe {
float time;
T value;
EasingFunction easingFunction = [](float t) {
return t;
};
};
struct AnimationMarker {
float time;
std::function<void()> callback;
};
template<typename T>
struct AnimationTrack {
std::vector<Keyframe<T>> keys;
};
struct TransformAnimationComponent {
float time = 0.0f;
float duration = 0.0f;
AnimationTrack<glm::vec3> position;
AnimationTrack<glm::vec3> rotation;
AnimationTrack<glm::vec3> scale;
std::vector<AnimationMarker> markers;
size_t nextMarkerIndex = 0;
std::function<void()> onFinished;
bool loop = false;
bool finished = false;
};
}
#endif //COLORRACE_TRANSFORMANIMATIONCOMPONENT_H

View File

@ -0,0 +1,18 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_ANIMATIONMARKEREVENT_H
#define COLORRACE_ANIMATIONMARKEREVENT_H
#include <string>
#include "ecs/Entity.h"
namespace engine::animation {
struct AnimationMarkerEvent {
engine::Entity entity;
std::string tag;
};
}
#endif //COLORRACE_ANIMATIONMARKEREVENT_H

View File

@ -0,0 +1,24 @@
//
// Created by sebastian on 04.08.26.
//
#include "AudioDevice.h"
engine::audio::AudioDevice::AudioDevice() {
m_device = alcOpenDevice(nullptr);
if (!m_device) {
throw std::runtime_error("Kein Audio-Device gefunden");
}
m_context = alcCreateContext(m_device, nullptr);
if (!m_context) {
throw std::runtime_error("Kein Audio-Context gefunden");
}
alcMakeContextCurrent(m_context);
}
engine::audio::AudioDevice::~AudioDevice() {
alcMakeContextCurrent(nullptr);
if (m_context) alcDestroyContext(m_context);
if (m_device) alcCloseDevice(m_device);
}

View File

@ -0,0 +1,37 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_AUDIODEVICE_H
#define COLORRACE_AUDIODEVICE_H
#include <iostream>
#include <ostream>
#include "al.h"
#include "alc.h"
namespace engine::audio {
class AudioDevice {
public:
AudioDevice();
~AudioDevice();
// Kein Kopieren/Verschieben nötig für den Anfang
AudioDevice(const AudioDevice&) = delete;
AudioDevice& operator=(const AudioDevice&) = delete;
private:
ALCdevice* m_device = nullptr;
ALCcontext* m_context = nullptr;
};
inline void checkAlError(const char* location) {
ALenum err = alGetError();
if (err != AL_NO_ERROR) {
std::cerr << "OpenAL Error: " << alGetString(err) << " at " << location << std::endl;
}
}
}
#endif //COLORRACE_AUDIODEVICE_H

View File

@ -0,0 +1,31 @@
//
// Created by sebastian on 04.08.26.
//
#include "SoundBuffer.h"
#include <dr_wav.h>
#include <stdexcept>
#include "AudioDevice.h"
namespace engine::audio {
SoundBuffer::~SoundBuffer() {
if (m_bufferID) alDeleteBuffers(1, &m_bufferID);
}
SoundBuffer::SoundBuffer(SoundBuffer &&other) noexcept : m_bufferID(other.m_bufferID) {
other.m_bufferID = 0;
}
SoundBuffer & SoundBuffer::operator=(SoundBuffer &&other) noexcept {
if (this != &other) {
release();
m_bufferID = other.m_bufferID;
other.m_bufferID = 0;
}
return *this;
}
} // engine

View File

@ -0,0 +1,39 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_SOUNDBUFFER_H
#define COLORRACE_SOUNDBUFFER_H
#include <string>
#include "al.h"
namespace engine::audio {
class SoundBuffer {
public:
explicit SoundBuffer(ALuint bufferID) : m_bufferID(bufferID) {}
~SoundBuffer();
SoundBuffer(const SoundBuffer&) = delete;
SoundBuffer& operator=(const SoundBuffer&) = delete;
SoundBuffer(SoundBuffer&& other) noexcept;
SoundBuffer& operator=(SoundBuffer&& other) noexcept;
ALuint id() const {
return m_bufferID;
}
private:
ALuint m_bufferID = 0;
void release() {
if (m_bufferID != 0) {
alDeleteBuffers(1, &m_bufferID);
m_bufferID = 0;
}
}
};
} // engine
#endif //COLORRACE_SOUNDBUFFER_H

View File

@ -0,0 +1,8 @@
//
// Created by sebastian on 04.08.26.
//
#include "SoundSource.h"
namespace engine {
} // engine

View File

@ -0,0 +1,90 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_SOUNDSOURCE_H
#define COLORRACE_SOUNDSOURCE_H
#include "al.h"
#include "AudioDevice.h"
#include "SoundBuffer.h"
namespace engine::audio {
class SoundSource {
public:
SoundSource() {
alGenSources(1, &m_source);
}
~SoundSource() {
if (m_source) alDeleteSources(1, &m_source);
}
SoundSource(const SoundSource&) = delete;
SoundSource& operator=(const SoundSource&) = delete;
SoundSource(SoundSource&& other) noexcept
: m_source(other.m_source)
{
other.m_source = 0;
}
SoundSource& operator=(SoundSource&& other) noexcept
{
if (this != &other) {
release();
m_source = other.m_source;
other.m_source = 0;
}
return *this;
}
void setBuffer(const SoundBuffer& buffer) {
alSourcei(m_source, AL_BUFFER, static_cast<ALint>(buffer.id()));
checkAlError("alSourcei(AL_BUFFER)");
}
void setPosition(float x, float y, float z) {
alSource3f(m_source, AL_POSITION, x, y, z);
}
void setGain(float gain) {
alSourcef(m_source, AL_GAIN, gain);
}
void setLooping(bool loop) {
alSourcei(m_source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
}
void play() { alSourcePlay(m_source); }
void stop() { alSourceStop(m_source); }
bool isPlaying() const {
ALint state;
alGetSourcei(m_source, AL_SOURCE_STATE, &state);
return state == AL_PLAYING;
}
private:
ALuint m_source = 0;
void release()
{
if (m_source != 0) {
alDeleteSources(1, &m_source);
m_source = 0;
}
}
};
inline void updateListener(float px, float py, float pz,
float fx, float fy, float fz,
float ux, float uy, float uz) {
alListener3f(AL_POSITION, px, py, pz);
ALfloat orientation[] = { fx, fy, fz, ux, uy, uz };
alListenerfv(AL_ORIENTATION, orientation);
}
} // engine
#endif //COLORRACE_SOUNDSOURCE_H

View File

@ -0,0 +1,34 @@
//
// Created by sebastian on 05.08.26.
//
#include "AudioSystem.h"
#include "BlockingSoundComponent.h"
#include "PlaySoundComponent.h"
#include "core/audio/SoundSource.h"
void engine::audio::AudioSystem::update(float deltaTime) const {
std::vector<Entity> toRemove;
for (auto& [entity, playSound] : m_registry.getPool<PlaySoundComponent>()) {
auto& source = m_registry.getComponent<SoundSource>(entity);
if (!playSound.started) {
source.setGain(playSound.gain);
source.play();
playSound.started = true;
} else if (!source.isPlaying()) {
if (playSound.onFinished) {
playSound.onFinished();
}
toRemove.push_back(entity);
}
}
for (auto entity : toRemove) {
m_registry.removeComponent<PlaySoundComponent>(entity);
if (m_registry.hasComponent<BlockingSoundComponent>(entity)) {
m_registry.removeComponent<BlockingSoundComponent>(entity);
}
}
}

View File

@ -0,0 +1,20 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_AUDIOSYSTEM_H
#define COLORRACE_AUDIOSYSTEM_H
#include "ecs/EntityRegistry.h"
namespace engine::audio {
class AudioSystem {
public:
explicit AudioSystem(EntityRegistry& registry) : m_registry(registry) {}
void update(float deltaTime) const;
private:
EntityRegistry& m_registry;
};
}
#endif //COLORRACE_AUDIOSYSTEM_H

View File

@ -0,0 +1,10 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_BLOCKINGSOUNDCOMPONENT_H
#define COLORRACE_BLOCKINGSOUNDCOMPONENT_H
namespace engine::audio {
struct BlockingSoundComponent {};
}
#endif //COLORRACE_BLOCKINGSOUNDCOMPONENT_H

View File

@ -0,0 +1,21 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_PLAYSOUNDCOMPONENT_H
#define COLORRACE_PLAYSOUNDCOMPONENT_H
#include <functional>
#include <memory>
#include "../SoundBuffer.h"
namespace engine::audio {
struct PlaySoundComponent {
float gain = 1.f;
bool started = false;
bool blocking = false;
std::function<void()> onFinished;
};
}
#endif //COLORRACE_PLAYSOUNDCOMPONENT_H

View File

@ -0,0 +1,14 @@
//
// Created by sebastian on 07.08.26.
//
#include "ApplicationEvents.h"
#include "layer/Scene.h" // hier ist Scene vollständig bekannt
SceneSwitchRequested::SceneSwitchRequested() = default;
SceneSwitchRequested::SceneSwitchRequested(std::unique_ptr<engine::Scene> scene, std::function<void()> callback) : nextScene(std::move(scene)), onSceneLoaded(std::move(callback)){
}
SceneSwitchRequested::~SceneSwitchRequested() = default;
SceneSwitchRequested::SceneSwitchRequested(SceneSwitchRequested&&) noexcept = default;
SceneSwitchRequested& SceneSwitchRequested::operator=(SceneSwitchRequested&&) noexcept = default;

View File

@ -0,0 +1,31 @@
//
// Created by sebastian on 06.08.26.
//
#ifndef COLORRACE_APPLICATIONEVENTS_H
#define COLORRACE_APPLICATIONEVENTS_H
#include <functional>
#include <memory>
#include <variant>
namespace engine {
class Scene;
}
struct QuitRequested {};
struct SceneSwitchRequested {
std::unique_ptr<engine::Scene> nextScene;
std::function<void()> onSceneLoaded;
SceneSwitchRequested();
SceneSwitchRequested(std::unique_ptr<engine::Scene> scene, std::function<void()> callback);
~SceneSwitchRequested();
SceneSwitchRequested(SceneSwitchRequested&&) noexcept;
SceneSwitchRequested& operator=(SceneSwitchRequested&&) noexcept;
};
using ApplicationEvents = std::variant<QuitRequested, SceneSwitchRequested>;
#endif //COLORRACE_APPLICATIONEVENTS_H

View File

@ -0,0 +1,8 @@
//
// Created by sebastian on 05.08.26.
//
#include "EventBus.h"
namespace engine {
} // engine

View File

@ -0,0 +1,42 @@
//
// Created by sebastian on 05.08.26.
//
#ifndef COLORRACE_EVENTBUS_H
#define COLORRACE_EVENTBUS_H
#include <functional>
#include <mutex>
namespace engine {
class IEventBus {
public:
virtual ~IEventBus() = default;
};
template<typename Event>
class EventBus {
public:
using Handler = std::function<void(Event&)>;
void subscribe(Handler handler) {
std::lock_guard<std::mutex> lock(m_mutex);
m_handlers.push_back(handler);
}
void publish(Event& event) const {
std::vector<Handler> handlersCopy;
{
std::lock_guard<std::mutex> lock(m_mutex);
handlersCopy = m_handlers;
}
for (const auto& handler : handlersCopy) {
handler(event);
}
}
private:
mutable std::mutex m_mutex;
std::vector<Handler> m_handlers;
};
} // engine
#endif //COLORRACE_EVENTBUS_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 06.08.26.
//
#include "EventChannels.h"

View File

@ -0,0 +1,43 @@
//
// Created by sebastian on 06.08.26.
//
#ifndef COLORRACE_EVENTCHANNELS_H
#define COLORRACE_EVENTCHANNELS_H
#include <memory>
#include <typeindex>
#include "EventBus.h"
namespace engine {
class EventChannels {
public:
template<typename Event>
void subscribe(typename EventBus<Event>::Handler handler) {
getBus<Event>().subscribe(std::move(handler));
}
template<typename Event>
void publish(Event& event) {
getBus<Event>().publish(event);
}
template<typename Event>
EventBus<Event>& getBus() {
auto key = std::type_index(typeid(Event));
auto it = m_buses.find(key);
if (it == m_buses.end()) {
auto bus = std::make_unique<EventBus<Event>>();
auto* ptr = bus.get();
m_buses.emplace(key, std::move(bus));
return *ptr;
}
return static_cast<EventBus<Event>&>(*it->second);
}
private:
std::unordered_map<std::type_index, std::unique_ptr<IEventBus>> m_buses;
};
}
#endif //COLORRACE_EVENTCHANNELS_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 02.08.26.
//
#include "InputContext.h"

View File

@ -0,0 +1,19 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_INPUTCONTEXT_H
#define COLORRACE_INPUTCONTEXT_H
namespace engine {
class InputContext {
public:
virtual ~InputContext() = default;
virtual void onActivate() {};
virtual void onDeactivate() {};
};
}
#endif //COLORRACE_INPUTCONTEXT_H

View File

@ -0,0 +1,24 @@
//
// Created by sebastian on 02.08.26.
//
#include "InputContextStack.h"
void engine::InputContextStack::push(std::shared_ptr<InputContext> context) {
if (!m_stack.empty()) m_stack.back()->onDeactivate();
context->onActivate();
m_stack.push_back(std::move(context));
}
void engine::InputContextStack::pop() {
if (m_stack.empty()) return;
m_stack.back()->onDeactivate();
m_stack.pop_back();
if (!m_stack.empty()) m_stack.back()->onActivate();
}
bool engine::InputContextStack::isActive(const InputContext &context) const {
return !m_stack.empty() && m_stack.back().get() == &context;
}

View File

@ -0,0 +1,25 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_INPUTCONTEXTSTACK_H
#define COLORRACE_INPUTCONTEXTSTACK_H
#include <memory>
#include <vector>
#include "InputContext.h"
namespace engine {
class InputContextStack {
public:
void push(std::shared_ptr<InputContext> context);
void pop();
[[nodiscard]] bool isActive(const InputContext& context) const;
private:
std::vector<std::shared_ptr<InputContext>> m_stack;
};
}
#endif //COLORRACE_INPUTCONTEXTSTACK_H

View File

@ -0,0 +1,23 @@
//
// Created by sebastian on 02.08.26.
//
#include "CameraController.h"
#include <iostream>
#include <ostream>
#include "GLFW/glfw3.h"
#include "glm/vec3.hpp"
#include "renderer/entities/Camera.h"
void engine::CameraController::update(const Keyboard &keyboard, Camera &camera, float deltaTime) {
if (!m_stack.isActive(*m_gameContext.get())) return;
if (keyboard.isKeyDown(GLFW_KEY_W)) camera.moveRelative(glm::vec3(0.0f, 0.0f, 1.0f), 10.0f, deltaTime);
if (keyboard.isKeyDown(GLFW_KEY_S)) camera.moveRelative(glm::vec3(0.0f, 0.0f, -1.0f), 10.0f, deltaTime);
if (keyboard.isKeyDown(GLFW_KEY_A)) camera.moveRelative(glm::vec3(-1.0f, 0.0f, 0.0f), 10.0f, deltaTime);
if (keyboard.isKeyDown(GLFW_KEY_D)) camera.moveRelative(glm::vec3(1.0f, 0.0f, 0.0f), 10.0f, deltaTime);
if (keyboard.isKeyDown(GLFW_KEY_SPACE)) camera.moveRelative(glm::vec3(0.0f, 1.0f, 0.0f), 10.0f, deltaTime);
}

View File

@ -0,0 +1,25 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_CAMERACONTROLLER_H
#define COLORRACE_CAMERACONTROLLER_H
#include "core/inputsOutputs/context/InputContextStack.h"
#include "core/inputsOutputs/inputs/Keyboard.h"
namespace engine {
class Camera;
class CameraController {
public:
CameraController(InputContextStack& stack, std::shared_ptr<InputContext> gameContext) : m_stack(stack), m_gameContext(std::move(gameContext)) {}
void update(const Keyboard& keyboard, Camera& camera, float deltaTime);
private:
InputContextStack& m_stack;
std::shared_ptr<InputContext> m_gameContext;
};
}
#endif //COLORRACE_CAMERACONTROLLER_H

View File

@ -0,0 +1,82 @@
//
// Created by sebastian on 02.08.26.
//
#include "Keyboard.h"
#include "imgui_impl_glfw.h"
#include "core/Window.h"
#include "GLFW/glfw3.h"
engine::Keyboard::Keyboard(engine::Window &window) {
window.setKeyboard(this);
addKeyListener(window);
addTextListener(window);
}
void engine::Keyboard::update() {
keysPressedThisFrame.clear();
keysRepeatedThisFrame.clear();
charsThisFrame.clear();
keysReleasedThisFrame.clear();
}
bool engine::Keyboard::isKeyDown(const int key) const {
return keysDown.contains(key);
}
const std::vector<int> & engine::Keyboard::getCharsThisFrame() {
return charsThisFrame;
}
auto engine::Keyboard::keyPressEvent(const int key) const -> bool {
return keysPressedThisFrame.contains(key);
}
auto engine::Keyboard::keyPressEvent(const int key, bool checkRepeats) const -> bool {
return keysPressedThisFrame.contains(key) || (checkRepeats && keysRepeatedThisFrame.contains(key));
}
bool engine::Keyboard::keyReleaseEvent(const int key) const {
return keysReleasedThisFrame.contains(key);
}
void engine::Keyboard::addTextListener(Window &window) {
glfwSetCharCallback(window.getHandle(), [](GLFWwindow* handle, unsigned int key) {
ImGui_ImplGlfw_CharCallback(handle, key);
auto* target = static_cast<Window::CallbackTarget*>(glfwGetWindowUserPointer(handle));
if (!target || !target->keyboard) return;
target->keyboard->reportChar(key);
});
}
void engine::Keyboard::addKeyListener(Window &window) {
glfwSetKeyCallback(window.getHandle(), [](GLFWwindow* handle, int key, int scancode, int action, int mods) {
ImGui_ImplGlfw_KeyCallback(handle, key, scancode, action, mods);
auto* target = static_cast<Window::CallbackTarget*>(glfwGetWindowUserPointer(handle));
if (!target || !target->keyboard) return;
if (action == GLFW_PRESS) target->keyboard->reportKeyPress(key);
else if (action == GLFW_RELEASE) target->keyboard->reportKeyRelease(key);
else if (action == GLFW_REPEAT) target->keyboard->reportKeyRepeat(key);
});
}
void engine::Keyboard::reportKeyPress(int key) {
keysDown.insert(key);
keysPressedThisFrame.insert(key);
}
void engine::Keyboard::reportKeyRelease(int key) {
keysDown.erase(key);
keysReleasedThisFrame.insert(key);
}
void engine::Keyboard::reportKeyRepeat(int key) {
keysRepeatedThisFrame.insert(key);
}
void engine::Keyboard::reportChar(int key) {
charsThisFrame.push_back(key);
}

View File

@ -0,0 +1,46 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_KEYBOARD_H
#define COLORRACE_KEYBOARD_H
#include <unordered_set>
#include <vector>
namespace engine {
class Window;
}
namespace engine {
class Keyboard {
public:
explicit Keyboard(engine::Window& window);
void update();
bool isKeyDown(int key) const;
const std::vector<int> &getCharsThisFrame();
bool keyPressEvent(int key) const;
bool keyPressEvent(int key, bool checkRepeats) const;
bool keyReleaseEvent(int key) const;
private:
std::unordered_set<int> keysPressedThisFrame;
std::unordered_set<int> keysRepeatedThisFrame;
std::unordered_set<int> keysReleasedThisFrame;
std::unordered_set<int> keysDown;
std::vector<int> charsThisFrame;
void addTextListener(Window& window);
void addKeyListener(Window& window);
void reportKeyPress(int key);
void reportKeyRelease(int key);
void reportKeyRepeat(int key);
void reportChar(int key);
};
}
#endif //COLORRACE_KEYBOARD_H

View File

@ -0,0 +1,124 @@
//
// Created by sebastian on 02.08.26.
//
#include "Mouse.h"
#include "imgui_impl_glfw.h"
#include "core/Window.h"
#include "GLFW/glfw3.h"
#include "spdlog/spdlog.h"
engine::Mouse::Mouse(Window &window) {
window.setMouse(this);
addClickListener(window);
addMoveListener(window);
addScrollListener(window);
}
bool engine::Mouse::isButtonDown(const MouseButton button) const {
return buttonsDown.contains(MouseButtonToInt(button));
}
bool engine::Mouse::isClickEvent(const MouseButton button) const {
return buttonsClickedThisFrame.contains(MouseButtonToInt(button));
}
bool engine::Mouse::isReleaseEvent(const MouseButton button) const {
return buttonsReleasedThisFrame.contains(MouseButtonToInt(button));
}
float engine::Mouse::getX(const bool ndc) const {
return ndc ? 2 * x - 1 : x;
}
float engine::Mouse::getY(const bool ndc) const {
return ndc ? 1 - 2 * y : y;
}
float engine::Mouse::getDX() const {
return dx;
}
float engine::Mouse::getDY() const {
return dy;
}
float engine::Mouse::getScroll() const {
return scroll;
}
void engine::Mouse::update() {
updateDetlas();
scroll = 0;
buttonsClickedThisFrame.clear();
buttonsReleasedThisFrame.clear();
}
glm::vec2 engine::Mouse::getXY(bool ndc) const {
return {getX(ndc), getY(ndc)};
}
void engine::Mouse::reportButtonClick(int button) {
spdlog::debug("Button {} clicked", button);
buttonsDown.insert(button);
buttonsClickedThisFrame.insert(button);
}
void engine::Mouse::reportButtonRelease(int button) {
buttonsDown.erase(button);
buttonsReleasedThisFrame.insert(button);
}
void engine::Mouse::updateDetlas() {
dx = x - lastX;
dy = y - lastY;
lastX = x;
lastY = y;
}
void engine::Mouse::addMoveListener(Window &window) {
glfwSetCursorPosCallback(window.getHandle(), [](GLFWwindow* handle, double xPos, double yPos) {
ImGui_ImplGlfw_CursorPosCallback(handle, xPos, yPos);
auto* target = static_cast<Window::CallbackTarget*>(glfwGetWindowUserPointer(handle));
if (!target || !target->mouse) return;
int width, height;
glfwGetFramebufferSize(handle, &width, &height);
target->mouse->x = static_cast<float>(xPos / width);
target->mouse->y = static_cast<float>(yPos / height);
});
}
void engine::Mouse::addScrollListener(Window &window) {
glfwSetScrollCallback(window.getHandle(), [](GLFWwindow* handle, double xoff, double yoff) {
ImGui_ImplGlfw_ScrollCallback(handle, xoff, yoff);
auto* target = static_cast<Window::CallbackTarget*>(glfwGetWindowUserPointer(handle));
if (target && target->mouse) target->mouse->scroll = static_cast<float>(yoff);
});
}
void engine::Mouse::addClickListener(Window &window) {
glfwSetMouseButtonCallback(window.getHandle(), [](GLFWwindow* handle, int button, int action, int mods) {
ImGui_ImplGlfw_MouseButtonCallback(handle, button, action, mods);
auto* target = static_cast<Window::CallbackTarget*>(glfwGetWindowUserPointer(handle));
if (!target || !target->mouse) return;
if (action == GLFW_PRESS) target->mouse->reportButtonClick(button);
else if (action == GLFW_RELEASE) target->mouse->reportButtonRelease(button);
});
}
int engine::Mouse::MouseButtonToInt(MouseButton button) {
int glfwButton = 0;
switch (button) {
case MouseButton::LEFT: glfwButton = GLFW_MOUSE_BUTTON_LEFT; break;
case MouseButton::RIGHT: glfwButton = GLFW_MOUSE_BUTTON_RIGHT; break;
case MouseButton::MIDDLE: glfwButton = GLFW_MOUSE_BUTTON_MIDDLE; break;
}
return glfwButton;
}

View File

@ -0,0 +1,52 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_MOUSE_H
#define COLORRACE_MOUSE_H
#include <unordered_set>
#include "MouseButton.h"
#include "glm/vec2.hpp"
namespace engine {
class Window;
class Mouse {
public:
explicit Mouse(Window& window);
bool isButtonDown(MouseButton button) const;
bool isClickEvent(MouseButton button) const;
bool isReleaseEvent(MouseButton button) const;
float getX(bool ndc) const;
float getY(bool ndc) const;
float getDX() const;
float getDY() const;
float getScroll() const;
void update();
glm::vec2 getXY(bool ndc) const;
private:
std::unordered_set<int> buttonsDown;
std::unordered_set<int> buttonsClickedThisFrame;
std::unordered_set<int> buttonsReleasedThisFrame;
float x = 0.0f, y = 0.0f;
float dx = 0.0f, dy = 0.0f;
float scroll = 0.0f;
float lastX = 0.0f, lastY = 0.0f;
void reportButtonClick(int button);
void reportButtonRelease(int button);
void updateDetlas();
static void addMoveListener(Window& window);
static void addScrollListener(Window& window);
static void addClickListener(Window& window);
static int MouseButtonToInt(MouseButton button);
};
}
#endif //COLORRACE_MOUSE_H

View File

@ -0,0 +1,14 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_MOUSEBUTTON_H
#define COLORRACE_MOUSEBUTTON_H
namespace engine {
enum class MouseButton {
LEFT,
RIGHT,
MIDDLE
};
}
#endif //COLORRACE_MOUSEBUTTON_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 29.07.26.
//
#include "ComponentPool.h"

View File

@ -0,0 +1,50 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_COMPONENTPOOL_H
#define COLORRACE_COMPONENTPOOL_H
#include "Entity.h"
namespace engine {
class IComponentPool {
public:
virtual ~IComponentPool() = default;
virtual void remove(Entity entity) = 0;
virtual size_t size() = 0;
};
template<typename T>
class ComponentPool : public IComponentPool {
public:
void remove(Entity entity) override {
m_components.erase(entity);
}
void insert(Entity entity, T component) {
m_components[entity] = std::move(component);
}
[[nodiscard]] bool has(Entity entity) const {
return m_components.contains(entity);
}
T& get(Entity entity) {
return m_components.at(entity);
}
size_t size() override {
return m_components.size();
}
// Für Iteration in Systemen
auto begin() { return m_components.begin(); }
auto end() { return m_components.end(); }
private:
std::unordered_map<Entity, T> m_components;
};
}
#endif //COLORRACE_COMPONENTPOOL_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 29.07.26.
//
#include "Entity.h"

39
engine/src/ecs/Entity.h Normal file
View File

@ -0,0 +1,39 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_ENTITY_H
#define COLORRACE_ENTITY_H
#include <cstdint>
#include <limits>
#include <functional> // <- das hier fehlt vermutlich
namespace engine {
class Entity {
public:
constexpr Entity() noexcept = default;
constexpr explicit Entity(std::uint32_t id) noexcept : m_id(id) {}
[[nodiscard]] constexpr std::uint32_t value() const noexcept { return m_id; }
constexpr bool operator==(const Entity&) const = default;
static constexpr Entity null() noexcept {
return Entity{std::numeric_limits<std::uint32_t>::max()};
}
private:
std::uint32_t m_id = std::numeric_limits<std::uint32_t>::max();
};
}
// Damit Entity in unordered_map als Key funktioniert
template<>
struct std::hash<engine::Entity> {
std::size_t operator()(const engine::Entity& e) const noexcept {
return std::hash<std::uint32_t>{}(e.value());
}
};
#endif //COLORRACE_ENTITY_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 29.07.26.
//
#include "EntityRegistry.h"

View File

@ -0,0 +1,70 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_ENTITYREGISTRY_H
#define COLORRACE_ENTITYREGISTRY_H
#include <cstdint>
#include <memory>
#include <typeindex>
#include <unordered_map>
#include "ComponentPool.h"
namespace engine {
class EntityRegistry {
public:
Entity createEntity() {
return Entity{m_nextId++};
}
void destroyEntity(Entity entity) {
for (auto& [type, pool] : m_pools) {
pool->remove(entity);
}
}
template<typename T>
void addComponent(Entity entity, T component) {
getPool<T>().insert(entity, std::move(component));
}
template<typename T>
void removeComponent(Entity entity) {
getPool<T>().remove(entity);
}
template<typename T>
[[nodiscard]] bool hasComponent(Entity entity) {
auto key = std::type_index(typeid(T));
auto it = m_pools.find(key);
if (it == m_pools.end()) return false;
return static_cast<ComponentPool<T>&>(*it->second).has(entity);
}
template<typename T>
T& getComponent(Entity entity) {
return getPool<T>().get(entity);
}
template<typename T>
ComponentPool<T>& getPool() {
auto key = std::type_index(typeid(T));
auto it = m_pools.find(key);
if (it == m_pools.end()) {
auto pool = std::make_unique<ComponentPool<T>>();
auto* ptr = pool.get();
m_pools.emplace(key, std::move(pool));
return *ptr;
}
return static_cast<ComponentPool<T>&>(*it->second);
}
private:
std::uint32_t m_nextId = 0;
std::pmr::unordered_map<std::type_index, std::unique_ptr<IComponentPool>> m_pools;
};
}
#endif //COLORRACE_ENTITYREGISTRY_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 29.07.26.
//
#include "EntitySystem.h"

View File

@ -0,0 +1,18 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_ENTITYSYSTEM_H
#define COLORRACE_ENTITYSYSTEM_H
#include "EntityRegistry.h"
class EntitySystem {
public:
virtual ~EntitySystem() = default;
virtual void update(engine::EntityRegistry& registry, float deltaTime) = 0;
};
#endif //COLORRACE_ENTITYSYSTEM_H

View File

@ -0,0 +1,16 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_HIGHLIGHTCOMPONENT_H
#define COLORRACE_HIGHLIGHTCOMPONENT_H
#include "glm/glm.hpp"
namespace engine {
struct HighlightComponent {
glm::vec3 highlightColor;
float intensity;
};
}
#endif //COLORRACE_HIGHLIGHTCOMPONENT_H

View File

@ -0,0 +1,16 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_MESHCOMPONENT_H
#define COLORRACE_MESHCOMPONENT_H
#include <memory>
#include "loader/models/Model.h"
namespace engine {
struct MeshComponent {
std::shared_ptr<Model> model;
};
}
#endif //COLORRACE_MESHCOMPONENT_H

View File

@ -0,0 +1,15 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_PICKABLECOMPONENT_H
#define COLORRACE_PICKABLECOMPONENT_H
namespace engine {
struct PickableComponent {
};
}
#endif //COLORRACE_PICKABLECOMPONENT_H

View File

@ -0,0 +1,28 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_TRANSFORMCOMPONENT_H
#define COLORRACE_TRANSFORMCOMPONENT_H
#include "glm/glm.hpp"
#include <glm/gtc/matrix_transform.hpp>
namespace engine {
struct TransformComponent {
glm::vec3 m_position;
glm::vec3 m_rotation;
glm::vec3 m_scale;
[[nodiscard]] glm::mat4 computeModelMatrix() const {
auto modelMatrix = glm::mat4(1.0f);
modelMatrix = glm::translate(modelMatrix, m_position);
modelMatrix = glm::rotate(modelMatrix, glm::radians(m_rotation.x), {1.0f, 0.0f, 0.0f});
modelMatrix = glm::rotate(modelMatrix, glm::radians(m_rotation.y), {0.0f, 1.0f, 0.0f});
modelMatrix = glm::rotate(modelMatrix, glm::radians(m_rotation.z), {0.0f, 0.0f, 1.0f});
modelMatrix = glm::scale(modelMatrix, m_scale);
return modelMatrix;
}
};
}
#endif //COLORRACE_TRANSFORMCOMPONENT_H

View File

@ -0,0 +1,27 @@
//
// Created by sebastian on 02.08.26.
//
#include "PickingSystem.h"
std::pair<glm::vec3, glm::vec3> engine::PickingSystem::screenToRay(const glm::vec2 &ndc, const Camera &camera) {
glm::mat4 invVP = glm::inverse(camera.getProjectionMatrix() * camera.getViewMatrix());
glm::vec4 nearPoint = invVP * glm::vec4(ndc.x, ndc.y, -1.0f, 1.0f); // Y invertiert (Screen vs. NDC)
glm::vec4 farPoint = invVP * glm::vec4(ndc.x, ndc.y, 1.0f, 1.0f);
nearPoint /= nearPoint.w;
farPoint /= farPoint.w;
auto origin = glm::vec3(nearPoint);
glm::vec3 dir = glm::normalize(glm::vec3(farPoint) - glm::vec3(nearPoint));
return {origin, dir};
}
bool engine::PickingSystem::raySphereIntersect(const glm::vec3 &origin, const glm::vec3 &dir, const glm::vec3 &center,
float radius, float &t) {
glm::vec3 oc = origin - center;
float b = glm::dot(oc, dir);
float c = glm::dot(oc, oc) - radius * radius;
float discriminant = b * b - c;
if (discriminant < 0.0f) return false;
t = -b - std::sqrt(discriminant);
return t >= 0.0f;
}

View File

@ -0,0 +1,66 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_PICKINGSYSTEM_H
#define COLORRACE_PICKINGSYSTEM_H
#include <optional>
#include "imgui.h"
#include "core/inputsOutputs/inputs/Mouse.h"
#include "core/inputsOutputs/inputs/MouseButton.h"
#include "ecs/EntityRegistry.h"
#include "ecs/standardComponents/PickableComponent.h"
#include "ecs/standardComponents/TransformComponent.h"
#define GLM_ENABLE_EXPERIMENTAL
#include <iostream>
#include "ecs/standardComponents/MeshComponent.h"
#include "renderer/entities/Camera.h"
#include "spdlog/spdlog.h"
namespace engine {
class Mouse;
class PickingSystem {
public:
explicit PickingSystem(Camera& camera) : m_camera(camera) {}
std::optional<Entity> update(const Mouse& mouse, EntityRegistry& registry) {
//if (ImGui::GetIO().WantCaptureMouse) return std::nullopt;
if (!mouse.isClickEvent(MouseButton::LEFT)) return std::nullopt;
auto [origin, dir] = screenToRay(mouse.getXY(/*ndc=*/true), m_camera);
float closestT = std::numeric_limits<float>::max();
std::optional<Entity> hit;
for (const auto& [entity, pickable] : registry.getPool<PickableComponent>()) {
const auto transform = registry.getComponent<TransformComponent>(entity);
const auto mesh = registry.getComponent<MeshComponent>(entity);
AABB transformedAABB = mesh.model->getAABB().transformed(transform.computeModelMatrix());
Ray ray = Ray(origin, dir);
std::optional<RayHit> ray_hit = transformedAABB.intersects(ray, closestT);
if (ray_hit.has_value()) {
spdlog::info(std::format("Hit: {} {}", entity.value(), std::to_string(ray_hit.value().distance)));
closestT = ray_hit.value().distance;
hit = entity;
}
}
return hit;
}
static std::pair<glm::vec3, glm::vec3> screenToRay(const glm::vec2& screenPos, const Camera& camera);
static bool raySphereIntersect(const glm::vec3& origin, const glm::vec3& dir, const glm::vec3& center, float radius, float& t);
private:
Camera& m_camera;
};
}
#endif //COLORRACE_PICKINGSYSTEM_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 02.08.26.
//
#include "GameMode.h"

View File

@ -0,0 +1,28 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_GAMEMODE_H
#define COLORRACE_GAMEMODE_H
#include <type_traits>
#include <vector>
#include "GameState.h"
namespace engine {
template <typename Command, typename Event, typename State>
class GameMode {
public:
static_assert(std::is_base_of_v<GameState, State>, "State must be derived from GameState");
virtual ~GameMode() = default;
virtual void sendCommand(const Command& command) = 0;
virtual std::vector<Event> pollEvents() = 0;
[[nodiscard]] virtual const State& getState() const = 0;
};
}
#endif //COLORRACE_GAMEMODE_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 02.08.26.
//
#include "GameState.h"

View File

@ -0,0 +1,17 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_GAMESTATE_H
#define COLORRACE_GAMESTATE_H
namespace engine {
class GameState {
public:
virtual ~GameState() = default;
};
}
#endif //COLORRACE_GAMESTATE_H

View File

@ -0,0 +1,5 @@
//
// Created by sebastian on 02.08.26.
//
#include "TurnBasedGameMode.h"

View File

@ -0,0 +1,33 @@
//
// Created by sebastian on 02.08.26.
//
#ifndef COLORRACE_TURNBASEDGAMEMODE_H
#define COLORRACE_TURNBASEDGAMEMODE_H
#include <cstdint>
#include "GameMode.h"
namespace engine {
using PlayerID = uint32_t;
template<typename Command, typename Event, typename State>
class TurnBasedGameMode : public GameMode<Command, Event, State> {
public:
explicit TurnBasedGameMode(std::vector<PlayerID> playerOrder) : m_playerOrder(std::move(playerOrder)) {}
[[nodiscard]] PlayerID getCurrentPlayer() const { return m_playerOrder[m_currentPlayerIndex]; }
[[nodiscard]] bool isCurrentPlayer(PlayerID playerID) const { return getCurrentPlayer() == playerID; }
[[nodiscard]] std::vector<PlayerID> getPlayerOrder() const { return m_playerOrder; }
protected:
void endTurn(bool extraTurn = false) {
if (!extraTurn) m_currentPlayerIndex = (m_currentPlayerIndex + 1) % m_playerOrder.size();
}
private:
std::vector<PlayerID> m_playerOrder;
size_t m_currentPlayerIndex = 0;
};
}
#endif //COLORRACE_TURNBASEDGAMEMODE_H

View File

@ -0,0 +1,53 @@
//
// Created by sebastian on 04.08.26.
//
#include "DebugLayer.h"
#include "ecs/standardComponents/MeshComponent.h"
#include "ecs/standardComponents/PickableComponent.h"
#include "ecs/standardComponents/TransformComponent.h"
#include "GLFW/glfw3.h"
#include "renderer/RenderQueue.h"
#include "renderer/primitives/SphereFactory.h"
#include "spdlog/spdlog.h"
void engine::DebugLayer::onUpdate(float deltaTime) {
Layer::onUpdate(deltaTime);
if (getKeyboard().isKeyDown(GLFW_KEY_H)) {
m_showDebugInfo = !m_showDebugInfo;
}
}
void engine::DebugLayer::onRender() {
Layer::onRender();
if (m_showDebugInfo) {
auto pickablePool = m_entityRegistry.getPool<PickableComponent>();
auto transforms = m_entityRegistry.getPool<engine::TransformComponent>();
for (auto& [entity, pickableComponent] : pickablePool) {
if (transforms.has(entity)) {
auto meshComponent = m_entityRegistry.getComponent<engine::MeshComponent>(entity);
glm::vec3 halfExtent = meshComponent.model.get()->getAABB().getHalfExtent();
//spdlog::info(std::to_string(halfExtent.x) + " " + std::to_string(halfExtent.y) + " " + std::to_string(halfExtent.z));
AABB transformedAABB = meshComponent.model->getAABB().transformed(transforms.get(entity).computeModelMatrix());
const auto sphereTransformComp = TransformComponent(transforms.get(entity).m_position, {0,0,0}, transformedAABB.getHalfExtent());
glm::mat4 modelMatrix = sphereTransformComp.computeModelMatrix();
RenderCommand renderCommand(m_sphereModel, modelMatrix);
m_renderQueue.submit(renderCommand);
}
}
m_debugRenderer.render(m_renderQueue, m_camera);
}
}
void engine::DebugLayer::onAttachImpl() {
Layer::onAttachImpl();
}
void engine::DebugLayer::onDetachImpl() {
Layer::onDetachImpl();
}

View File

@ -0,0 +1,40 @@
//
// Created by sebastian on 04.08.26.
//
#ifndef COLORRACE_DEBUGLAYER_H
#define COLORRACE_DEBUGLAYER_H
#include "Layer.h"
#include "renderer/DebugRenderer.h"
#include "renderer/RenderQueue.h"
namespace engine {
class Model;
class DebugLayer: public Layer {
public:
DebugLayer(engine::EventBus<animation::AnimationMarkerEvent>& animationEventbus, Camera& camera, engine::EntityRegistry& registry, Keyboard &keyboard, Mouse &mouse, std::shared_ptr<Model> sphereModel)
: Layer(keyboard, mouse, animationEventbus), m_entityRegistry(registry), m_sphereModel(std::move(sphereModel)), m_camera(camera) {
}
void onUpdate(float deltaTime) override;
void onRender() override;
protected:
void onAttachImpl() override;
void onDetachImpl() override;
private:
engine::EntityRegistry& m_entityRegistry;
bool m_showDebugInfo = true;
std::shared_ptr<engine::Model> m_sphereModel;
RenderQueue m_renderQueue;
DebugRenderer m_debugRenderer;
Camera& m_camera;
};
}
#endif //COLORRACE_DEBUGLAYER_H

View File

@ -0,0 +1,17 @@
//
// Created by sebastian on 29.07.26.
//
#include "Layer.h"
void engine::Layer::onAttach(InputContextStack &inputStack) {
m_inputStack = &inputStack;
m_context = std::make_shared<InputContext>();
inputStack.push(m_context);
onAttachImpl();
}
void engine::Layer::onDetach(InputContextStack &inputStack) {
inputStack.pop();
onDetachImpl();
}

48
engine/src/layer/Layer.h Normal file
View File

@ -0,0 +1,48 @@
//
// Created by sebastian on 29.07.26.
//
#ifndef COLORRACE_LAYER_H
#define COLORRACE_LAYER_H
#include "core/animation/events/AnimationMarkerEvent.h"
#include "core/events/ApplicationEvents.h"
#include "core/events/EventBus.h"
#include "core/inputsOutputs/context/InputContextStack.h"
#include "core/inputsOutputs/inputs/Keyboard.h"
#include "core/inputsOutputs/inputs/Mouse.h"
#include "ecs/EntityRegistry.h"
namespace engine {
class Layer {
public:
Layer(Keyboard& keyboard, Mouse& mouse, EventBus<animation::AnimationMarkerEvent>& animationEventBus) : m_keyboard(keyboard), m_mouse(mouse), m_animationEventBus(animationEventBus){};
Layer(Keyboard& keyboard, Mouse& mouse, EventBus<animation::AnimationMarkerEvent>& animationEventBus, std::shared_ptr<EventBus<ApplicationEvents>> applicationEventBus) : m_keyboard(keyboard), m_mouse(mouse), m_animationEventBus(animationEventBus), m_applicationEventBus(std::move(applicationEventBus)){};
virtual ~Layer() = default;
void onAttach(InputContextStack& inputStack);
void onDetach(InputContextStack& inputStack);
virtual void onUpdate(float deltaTime) {}
virtual void onRender() {}
protected:
virtual void onAttachImpl() {}
virtual void onDetachImpl() {}
std::shared_ptr<InputContext>& getInputContext() { return m_context; }
InputContextStack& getInputStack() { return *m_inputStack; }
Keyboard& getKeyboard() { return m_keyboard; }
Mouse& getMouse() { return m_mouse; }
EventBus<animation::AnimationMarkerEvent>& getAnimationEventBus() { return m_animationEventBus; }
std::shared_ptr<EventBus<ApplicationEvents>> m_applicationEventBus;
private:
std::shared_ptr<InputContext> m_context;
InputContextStack* m_inputStack = nullptr;
engine::Keyboard& m_keyboard;
engine::Mouse& m_mouse;
engine::EventBus<animation::AnimationMarkerEvent>& m_animationEventBus;
};
}
#endif //COLORRACE_LAYER_H

View File

@ -0,0 +1,34 @@
//
// Created by sebastian on 30.07.26.
//
#include "Scene.h"
#include <iostream>
#include <ostream>
#include <utility>
#include "core/inputsOutputs/context/InputContextStack.h"
void engine::Scene::onUpdate(float deltaTime) {
for (const auto& layer : m_layers) {
layer->onUpdate(deltaTime);
}
}
void engine::Scene::onRender() {
for (const auto& layer : m_layers) {
layer->onRender();
}
}
void engine::Scene::onRegisterLoadedAsset(LoadedAsset loadedAsset) {
std::cout << "Loaded asset: " << std::endl;
assetManager->insertAsset(std::move(loadedAsset));
}
void engine::Scene::addLayer(std::unique_ptr<Layer> layer) {
layer->onAttach(m_inputStack);
m_layers.push_back(std::move(layer));
}

63
engine/src/layer/Scene.h Normal file
View File

@ -0,0 +1,63 @@
//
// Created by sebastian on 30.07.26.
//
#ifndef COLORRACE_SCENE_H
#define COLORRACE_SCENE_H
#include <memory>
#include <vector>
#include "Layer.h"
#include "core/animation/events/AnimationMarkerEvent.h"
#include "core/events/ApplicationEvents.h"
#include "core/events/EventBus.h"
#include "loader/assets/AssetManager.h"
#include "loader/assets/AssetRequests.h"
#include "loader/assets/LoadedAssets.h"
#include "renderer/entities/Camera.h"
namespace engine {
class Keyboard;
class Mouse;
class InputContextStack;
class Scene {
public:
explicit Scene(std::shared_ptr<engine::EventBus<ApplicationEvents>> applicationEventbus, engine::InputContextStack& inputStack, engine::Keyboard& keyboard, engine::Mouse& mouse):
assetManager(std::make_unique<AssetManager>()), m_inputStack(inputStack), m_keyboard(keyboard), m_mouse(mouse), m_applicationEventBus(std::move(applicationEventbus)) {}
virtual ~Scene() = default;
virtual void onEnter() {}
virtual void onExit() {
for (const auto& layer : m_layers) {
layer->onDetach(m_inputStack);
}
}
void onUpdate(float deltaTime);
void onRender();
void onRegisterLoadedAsset(LoadedAsset loadedAsset);
virtual std::vector<AssetRequest> getRequiredAssets() const { return {}; }
protected:
std::vector<std::unique_ptr<Layer>> m_layers;
EventBus<animation::AnimationMarkerEvent>& getAnimationEventBus() { return m_animationMarkerEventBus; }
void addLayer(std::unique_ptr<Layer> layer);
engine::EntityRegistry entityRegistry;
std::unique_ptr<AssetManager> assetManager;
InputContextStack& m_inputStack;
engine::Keyboard& m_keyboard;
engine::Mouse& m_mouse;
std::shared_ptr<engine::EventBus<ApplicationEvents>> m_applicationEventBus;
private:
EventBus<animation::AnimationMarkerEvent> m_animationMarkerEventBus;
};
}
#endif //COLORRACE_SCENE_H

View File

@ -0,0 +1,64 @@
//
// Created by sebastian on 30.07.26.
//
#include "SceneManager.h"
#include <iostream>
#include "spdlog/spdlog.h"
void engine::SceneManager::switchTo(std::unique_ptr<Scene> scene, std::function<void()> onSceneLoaded) {
onSceneLoaded = std::move(onSceneLoaded);
nextScene = std::move(scene);
std::vector<AssetRequest> requestedAssets = nextScene->getRequiredAssets();
spdlog::debug("Requested Assets: {}", requestedAssets.size());
for (AssetRequest & request : requestedAssets) {
assetLoader.scheduleAsset(request);
}
assetLoader.start();
}
void engine::SceneManager::update(float deltaTime) {
if (nextScene) {
auto uploadedAssets = assetLoader.processUploadQueue(2);
std::cout << "Uploaded Assets: " << uploadedAssets.size() << std::endl;
for (const auto& asset : uploadedAssets) {
nextScene->onRegisterLoadedAsset(asset);
}
if (assetLoader.getProgress().isDone()) {
if (currentScene) {
currentScene->onExit();
}
currentScene = std::move(nextScene);
nextScene = nullptr;
currentScene->onEnter();
assetLoader.stop();
if (onSceneLoaded) {
onSceneLoaded();
onSceneLoaded = nullptr;
}
}
}
if (currentScene) {
currentScene->onUpdate(deltaTime);
}
}
void engine::SceneManager::render() const {
if (currentScene) {
currentScene->onRender();
}
}
float engine::SceneManager::getTransitionProgress() const {
return assetLoader.getProgress().fraction();
}

View File

@ -0,0 +1,36 @@
//
// Created by sebastian on 30.07.26.
//
#ifndef COLORRACE_SCENEMANAGER_H
#define COLORRACE_SCENEMANAGER_H
#include <memory>
#include "Scene.h"
#include "core/inputsOutputs/context/InputContextStack.h"
#include "loader/AssetLoader.h"
namespace engine {
class SceneManager {
public:
explicit SceneManager(InputContextStack& inputStack) :m_inputStack(inputStack) {}
void switchTo(std::unique_ptr<Scene> scene, std::function<void()> onSceneLoaded = nullptr);
void update(float deltaTime);
void render() const;
~SceneManager() = default;
[[nodiscard]] float getTransitionProgress() const;
private:
std::unique_ptr<Scene> currentScene;
std::unique_ptr<Scene> nextScene;
bool isTransitioning = false;
std::function<void()> onSceneLoaded;
AssetLoader assetLoader;
InputContextStack& m_inputStack;
};
}
#endif //COLORRACE_SCENEMANAGER_H

View File

@ -0,0 +1,180 @@
//
// Created by sebastian on 30.07.26.
//
#include "AssetLoader.h"
#include <dr_wav.h>
#include <iostream>
#include "models/ModelImporter.h"
#include "models/ModelUploader.h"
#include "sounds/SoundUploader.h"
#include "spdlog/spdlog.h"
#include "textures/TextureImporter.h"
#include "textures/TextureUploader.h"
void engine::AssetLoader::scheduleAsset(const AssetRequest &request) {
{
std::lock_guard<std::mutex> lock(pendingMutex);
pendingQueue.push(request);
++total;
}
pendingCondition.notify_one();
std::visit([]<typename T>(const T& req) -> void {
using Type = std::decay_t<T>;
if constexpr (std::is_same_v<Type, TextureRequest>) {
spdlog::debug("Scheduled Texture Request: {} -> {}", req.name, req.path);
} else if constexpr (std::is_same_v<Type, ModelRequest>) {
spdlog::debug("Scheduled Model Request: {} -> {}", req.name, req.path);
} else if constexpr (std::is_same_v<Type, SoundRequest>) {
spdlog::debug("Scheduled Sound Request: {} -> {}", req.name, req.path);
}
}, request);
}
void engine::AssetLoader::start() {
if (!running) {
running = true;
loadingThread = std::thread(&AssetLoader::loadingThreadFunc, this);
}
}
void engine::AssetLoader::stop() {
running = false;
pendingCondition.notify_all();
if (loadingThread.joinable()) {
loadingThread.join();
}
}
std::vector<engine::LoadedAsset> engine::AssetLoader::processUploadQueue(int maxPerFrame) {
std::vector<LoadedAsset> result;
auto uploadQueue = determineUploadQueue(maxPerFrame);
std::cout << "Upload Queue: " << uploadQueue.size() << std::endl;
for (const auto& asset : uploadQueue) {
std::visit([&result]<typename T0>(T0& rawData) -> void {
using T = std::decay_t<T0>;
if constexpr (std::is_same_v<T, RawTextureData>) {
Texture2D texture = TextureUploader::upload(rawData);
result.emplace_back(LoadedTexture{rawData.name, std::make_shared<Texture2D>(std::move(texture))});
} else if constexpr (std::is_same_v<T, RawModelBundle>) {
std::cout << "Uploading model " << rawData.model.name << std::endl;
auto modelAssets = ModelUploader::upload(rawData, rawData.model.splitPolicy);
std::cout << "Moddel Assets" << modelAssets.size() << std::endl;
result.insert(result.end(), modelAssets.begin(), modelAssets.end());
std::cout << "Result size" << result.size() << std::endl;
} else if constexpr (std::is_same_v<T, RawSoundData>) {
auto soundAsset = SoundUploader::upload(rawData);
result.emplace_back(LoadedSound{rawData.name, std::make_shared<audio::SoundBuffer>(std::move(soundAsset))});
} else {
static_assert(
always_false<T>,
"Unhandled asset type in AssetLoader::processUploadQueue"
);
}
}, asset);
++loaded;
}
std::cout << "Loaded Assets: " << result.size() << std::endl;
return result;
}
engine::LoadingProgress engine::AssetLoader::getProgress() const {
return {total.load(), loaded.load()};
}
void engine::AssetLoader::loadingThreadFunc() {
while (running) {
AssetRequest request;
{
std::unique_lock lock(pendingMutex);
pendingCondition.wait(lock, [this] {
return !pendingQueue.empty() || !running;
});
if (!running && pendingQueue.empty()) {
return;
}
request = pendingQueue.front();
pendingQueue.pop();
}
IntermediateAsset result = std::visit([]<typename T0>(T0& req) -> IntermediateAsset {
using T = std::decay_t<T0>;
if constexpr (std::is_same_v<T, TextureRequest>) {
return processTextureRequest(req);
} else if constexpr (std::is_same_v<T, ModelRequest>) {
return processModelRequest(req);
} else if constexpr (std::is_same_v<T, SoundRequest>) {
return processSoundRequest(req);
} else {
static_assert(always_false<T>, "Unhandled asset type in AssetLoader::loadingThreadFunc");
}
}, request);
{
std::lock_guard lock(readyMutex);
readyQueue.push(std::move(result));
}
}
}
engine::RawTextureData engine::AssetLoader::processTextureRequest(const TextureRequest &request) {
spdlog::debug("Processing Texture Request: {} -> {}", request.name, request.path);
RawTextureData textureData = TextureImporter::import(request.path, request.flipY);
textureData.name = request.name;
return textureData;
}
engine::RawModelBundle engine::AssetLoader::processModelRequest(const ModelRequest &request) {
spdlog::debug("Processing Model Request: {} -> {}", request.name, request.path);
RawModelBundle modelData = ModelImporter::importFromFile(request.path, request.splitPolicy);
modelData.model.name = request.name;
return modelData;
}
engine::RawSoundData engine::AssetLoader::processSoundRequest(const SoundRequest &request) {
RawSoundData soundData;
unsigned int channels, sampleRate;
drwav_uint64 frameCount;
drwav_int16* pcmData = drwav_open_file_and_read_pcm_frames_s16(
request.path.c_str(), &channels, &sampleRate, &frameCount, nullptr
);
if (!pcmData) {
throw std::runtime_error("Failed to load sound file: " + request.path);
}
soundData.name = request.name;
soundData.channels = channels;
soundData.sampleRate = sampleRate;
soundData.bitsPerSample = 16;
size_t dataSize = frameCount * channels * sizeof(drwav_int16);
soundData.pcmData.resize(dataSize);
std::memcpy(
soundData.pcmData.data(),
pcmData,
dataSize
);
drwav_free(pcmData, nullptr);
return soundData;
}
std::vector<engine::IntermediateAsset> engine::AssetLoader::determineUploadQueue(int maxPerFrame) {
std::vector<IntermediateAsset> result;
std::lock_guard lock(readyMutex);
for (int i = 0; i < maxPerFrame && !readyQueue.empty(); ++i) {
result.push_back(std::move(readyQueue.front()));
readyQueue.pop();
}
return result;
}

View File

@ -0,0 +1,68 @@
//
// Created by sebastian on 30.07.26.
//
#ifndef COLORRACE_ASSETLOADER_H
#define COLORRACE_ASSETLOADER_H
#include <condition_variable>
#include <format>
#include <mutex>
#include <queue>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
#include "assets/AssetRequests.h"
#include "assets/LoadedAssets.h"
#include "assets/RawAssetData.h"
namespace engine {
struct LoadingProgress {
int total;
int loaded;
float fraction() const {
return total > 0 ? static_cast<float>(loaded) / total : 1.f;
}
bool isDone() const {
return loaded >= total;
}
};
template<typename>
inline constexpr bool always_false = false;
class AssetLoader {
public:
void scheduleAsset(const AssetRequest& request);
void start();
void stop();
std::vector<LoadedAsset> processUploadQueue(int maxPerFrame);
LoadingProgress getProgress() const;
private:
void loadingThreadFunc();
static RawTextureData processTextureRequest(const TextureRequest& request);
static RawModelBundle processModelRequest(const ModelRequest& request);
static RawSoundData processSoundRequest(const SoundRequest& request);
std::vector<IntermediateAsset> determineUploadQueue(int maxPerFrame);
std::queue<AssetRequest> pendingQueue;
std::mutex pendingMutex;
std::condition_variable pendingCondition;
std::queue<IntermediateAsset> readyQueue;
std::mutex readyMutex;
std::atomic<int> total{0};
std::atomic<int> loaded{0};
std::thread loadingThread;
std::atomic<bool> running{false};
};
}
#endif //COLORRACE_ASSETLOADER_H

View File

@ -0,0 +1,59 @@
//
// Created by sebastian on 30.07.26.
//
#ifndef COLORRACE_ASSETFORMATTERS_H
#define COLORRACE_ASSETFORMATTERS_H
#pragma once
#include "AssetRequests.h"
#include "AssetTypes.h"
#include <format>
template<>
struct std::formatter<engine::AssetRequest>
{
constexpr auto parse(
std::format_parse_context& ctx)
{
return ctx.begin();
}
auto format(
const engine::AssetRequest& request,
std::format_context& ctx) const
{
return std::format_to(
ctx.out(),
"{}",
engine::assetRequestToString(request)
);
}
};
template<>
struct std::formatter<engine::TextureType>
{
constexpr auto parse(
std::format_parse_context& ctx)
{
return ctx.begin();
}
auto format(
engine::TextureType type,
std::format_context& ctx) const
{
return std::format_to(
ctx.out(),
"{}",
engine::toString(type)
);
}
};
#endif //COLORRACE_ASSETFORMATTERS_H

View File

@ -0,0 +1,86 @@
//
// Created by sebastian on 30.07.26.
//
#include "AssetManager.h"
#include <iostream>
#include <ostream>
std::shared_ptr<engine::Model> engine::AssetManager::getModel(const std::string &name) const {
auto it = m_models.find(name);
if (it == m_models.end()) {
throw std::runtime_error("Model not found: " + name);
}
return it->second;
}
bool engine::AssetManager::hasModel(const std::string &name) const {
return m_models.contains(name);
}
void engine::AssetManager::insertAsset(LoadedAsset loadedAsset) {
std::cout << "Inserting asset: " << std::endl;
std::visit([this](const auto& loaded) {
using T = std::decay_t<decltype(loaded)>;
if constexpr (std::is_same_v<T, LoadedModel>) {
insertModel(loaded.name, loaded.model);
} else if constexpr (std::is_same_v<T, LoadedTexture>) {
insertTexture(loaded.name, loaded.texture);
} else if constexpr (std::is_same_v<T, LoadedSound>) {
insertSound(loaded.name, loaded.sound);
}
}, loadedAsset);
}
void engine::AssetManager::insertModel(const std::string &name, std::shared_ptr<Model> model) {
std::cout << "Inserting model " << name << std::endl;
m_models[name] = std::move(model);
}
void engine::AssetManager::unloadModel(const std::string &name) {
m_models.erase(name);
}
std::shared_ptr<engine::Texture2D> engine::AssetManager::getTexture(const std::string &name) const {
auto it = m_textures.find(name);
if (it == m_textures.end()) {
throw std::runtime_error("Texture not found: " + name);
}
return it->second;
}
bool engine::AssetManager::hasTexture(const std::string &name) const {
return m_textures.contains(name);
}
void engine::AssetManager::insertTexture(const std::string &name, std::shared_ptr<Texture2D> texture) {
std::cout << "Inserting texture " << name << std::endl;
m_textures[name] = std::move(texture);
}
void engine::AssetManager::unloadTexture(const std::string &name) {
m_textures.erase(name);
}
std::shared_ptr<engine::audio::SoundBuffer> engine::AssetManager::getSound(const std::string &name) const {
auto it = m_loadedAssets.find(name);
if (it == m_loadedAssets.end()) {
throw std::runtime_error("Sound not found: " + name);
}
return it->second;
}
bool engine::AssetManager::hasSound(const std::string &name) const {
return m_loadedAssets.contains(name);
}
void engine::AssetManager::insertSound(const std::string &name, std::shared_ptr<audio::SoundBuffer> sound) {
std::cout << "Inserting sound " << name << std::endl;
m_loadedAssets[name] = std::move(sound);
}
void engine::AssetManager::unloadSound(const std::string &name) {
m_loadedAssets.erase(name);
}

View File

@ -0,0 +1,45 @@
//
// Created by sebastian on 30.07.26.
//
#ifndef COLORRACE_ASSETMANAGER_H
#define COLORRACE_ASSETMANAGER_H
#include <string>
#include <unordered_map>
#include "LoadedAssets.h"
#include "loader/models/Model.h"
#include "loader/textures/Texture2D.h"
namespace engine {
class AssetManager {
public:
AssetManager() = default;
~AssetManager() = default;
std::shared_ptr<Model> getModel(const std::string& name) const;
bool hasModel(const std::string& name) const;
void insertAsset(LoadedAsset loadedAsset);
void insertModel(const std::string& name, std::shared_ptr<Model> model);
void unloadModel(const std::string& name);
std::shared_ptr<Texture2D> getTexture(const std::string& name) const;
bool hasTexture(const std::string& name) const;
void insertTexture(const std::string& name, std::shared_ptr<Texture2D> texture);
void unloadTexture(const std::string& name);
std::shared_ptr<audio::SoundBuffer> getSound(const std::string& name) const;
bool hasSound(const std::string& name) const;
void insertSound(const std::string& name, std::shared_ptr<audio::SoundBuffer> sound);
void unloadSound(const std::string& name);
private:
std::unordered_map<std::string, std::shared_ptr<Texture2D>> m_textures;
std::unordered_map<std::string, std::shared_ptr<Model>> m_models;
std::unordered_map<std::string, std::shared_ptr<audio::SoundBuffer>> m_loadedAssets;
};
}
#endif //COLORRACE_ASSETMANAGER_H

View File

@ -0,0 +1,50 @@
//
// Created by sebastian on 30.07.26.
//
// AssetRequests.cpp
#include "AssetRequests.h"
#include <format>
#include <type_traits>
#include "RawAssetData.h"
#include "loader/AssetLoader.h"
namespace engine {
std::string assetRequestToString(
const AssetRequest& request)
{
return std::visit(
[](auto const& req)
{
using Type = std::decay_t<decltype(req)>;
if constexpr(std::is_same_v<Type, TextureRequest>)
{
return std::format(
"TextureRequest{{name='{}', path='{}'}}",
req.name,
req.path
);
} else if constexpr(std::is_same_v<Type, ModelRequest>)
{
return std::format(
"ModelRequest{{name='{}', path='{}'}}",
req.name,
req.path
);
} else if constexpr(std::is_same_v<Type, SoundRequest>){
return std::format(
"SoundRequest{{name='{}', path='{}'}}",
req.name,
req.path);
}
},
request);
}
}

Some files were not shown because too many files have changed in this diff Show More