├── resources ├── Materials │ ├── Text.mat │ ├── GUI │ │ ├── Label.mat │ │ └── Label.mat.meta │ └── Text.mat.meta └── Shaders │ ├── VertexColor.shader │ ├── FlatColor.shader │ ├── Diffuse.shader │ ├── Skybox.shader │ ├── Font.shader │ ├── Lambert.shader │ └── Sprite.shader ├── docs ├── Logo.png ├── AssetManager.png ├── Text.md ├── Entity.md └── AssetManager.md ├── tools ├── AssetManager │ ├── Resources │ │ ├── Delete.png │ │ ├── Error.png │ │ ├── Expand.png │ │ ├── Folder.png │ │ ├── Search.png │ │ ├── Building.ico │ │ ├── Collapse.png │ │ ├── Correct.png │ │ ├── JewelIcon.ico │ │ ├── Settings.ico │ │ └── Settings.png │ ├── App.config │ ├── Properties │ │ ├── Settings.settings │ │ ├── Settings.Designer.cs │ │ └── AssemblyInfo.cs │ ├── workspace_templates │ │ ├── open_asset_manager.cmd │ │ ├── update_metadata.cmd │ │ ├── pack_assets.cmd │ │ └── config.workspace │ ├── Program.cs │ ├── WorkspaceConfig.cs │ ├── Settings.cs │ ├── RichTextBoxStreamWriter.cs │ └── FileCache.cs ├── CMakeLists.txt ├── FontEncoder │ ├── main.cpp │ ├── CMakeLists.txt │ └── FontEncoder.h ├── MeshEncoder │ ├── main.cpp │ ├── CMakeLists.txt │ └── MeshEncoder.h ├── SoundEncoder │ ├── main.cpp │ ├── CMakeLists.txt │ └── SoundEncoder.h ├── TextureEncoder │ ├── main.cpp │ ├── CMakeLists.txt │ └── TextureEncoder.h └── MaterialEncoder │ ├── main.cpp │ ├── CMakeLists.txt │ └── MaterialEncoder.h ├── .gitignore ├── gemcutter ├── Resource │ ├── Resource.cpp │ ├── Material.h │ ├── Model.h │ ├── ParticleBuffer.h │ ├── Shareable.h │ ├── ParticleFunctor.cpp │ ├── ConfigTable.h │ ├── Font.h │ ├── ParticleFunctor.h │ ├── Sound.h │ ├── Material.cpp │ ├── Model.cpp │ ├── Resource.h │ └── UniformBuffer.inl ├── Rendering │ ├── Viewport.h │ ├── Viewport.cpp │ ├── Mesh.h │ ├── Text.h │ ├── Renderable.h │ ├── Mesh.cpp │ ├── Light.h │ ├── Sprite.h │ ├── Sprite.cpp │ ├── Renderable.cpp │ ├── Primitives.h │ ├── RenderPass.h │ ├── Light.cpp │ ├── Text.cpp │ └── ParticleEmitter.h ├── GUI │ ├── Rectangle.h │ ├── Widget.inl │ ├── Rectangle.cpp │ ├── Screen.h │ ├── Image.h │ ├── Screen.cpp │ ├── Label.h │ ├── Button.h │ ├── Widget.h │ ├── Image.cpp │ ├── Label.cpp │ └── Widget.cpp ├── Entity │ ├── Hierarchy.inl │ ├── Name.h │ ├── Name.cpp │ └── Hierarchy.h ├── Math │ ├── Math.cpp │ ├── Transform.h │ ├── Quaternion.h │ └── Transform.cpp ├── Application │ ├── HierarchicalEvent.cpp │ ├── Event.cpp │ ├── Delegate.cpp │ ├── Event.inl │ ├── CmdArgs.h │ ├── Timer.h │ ├── Reflection.inl │ ├── Reflection.cpp │ ├── Reflection.h │ ├── HierarchicalEvent.h │ ├── Logging.h │ ├── CmdArgs.cpp │ ├── Timer.cpp │ └── Event.h ├── Sound │ ├── SoundSystem.h │ ├── SoundListener.h │ ├── SoundSource.h │ ├── SoundListener.cpp │ ├── SoundSource.cpp │ └── SoundSystem.cpp ├── Utilities │ ├── Random.h │ ├── Identifier.h │ ├── EnumFlags.h │ ├── StdExt.h │ ├── String.h │ ├── Random.cpp │ └── ScopeGuard.h ├── AI │ └── ProbabilityMatrix.h └── Input │ ├── XboxGamePad.h │ └── Input.h ├── tests ├── WeakPtr.cpp ├── CMakeLists.txt ├── main.cpp ├── FileSystem.cpp ├── Math.cpp ├── String.cpp ├── EnumFlags.cpp └── Meta.cpp ├── .vscode ├── launch.json ├── cmake-variants.json └── c_cpp_properties.json ├── .editorconfig ├── .gitmodules ├── LICENSE ├── CMakeLists.txt ├── external └── CMakeLists.txt └── README.md /resources/Materials/Text.mat: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/Materials/GUI/Label.mat: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/docs/Logo.png -------------------------------------------------------------------------------- /docs/AssetManager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/docs/AssetManager.png -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Delete.png -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Error.png -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Expand.png -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Folder.png -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Search.png -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Building.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Building.ico -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Collapse.png -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Correct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Correct.png -------------------------------------------------------------------------------- /tools/AssetManager/Resources/JewelIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/JewelIcon.ico -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Settings.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Settings.ico -------------------------------------------------------------------------------- /tools/AssetManager/Resources/Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmilianC/Gemcutter/HEAD/tools/AssetManager/Resources/Settings.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Log_Output.txt 2 | *.opendb 3 | *.TMP 4 | *.VC.db 5 | *.pdb 6 | *.user 7 | .vscode/ 8 | 9 | # Likely CMake build locations 10 | build/ 11 | gen/ 12 | generated/ 13 | -------------------------------------------------------------------------------- /resources/Materials/GUI/Label.mat.meta: -------------------------------------------------------------------------------- 1 | blend_mode=Linear 2 | cull_mode=None 3 | depth_mode=None 4 | shader=Engine/Shaders/Font.shader 5 | texture_bind_points= 6 | textures= 7 | version=2 8 | -------------------------------------------------------------------------------- /resources/Materials/Text.mat.meta: -------------------------------------------------------------------------------- 1 | blend_mode=Linear 2 | cull_mode=Clockwise 3 | depth_mode=Normal 4 | shader=Engine/Shaders/font.shader 5 | texture_bind_points= 6 | textures= 7 | version=2 8 | -------------------------------------------------------------------------------- /gemcutter/Resource/Resource.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Emilian Cioca 2 | #include "Resource.h" 3 | 4 | REFLECT(gem::ResourceBase) 5 | MEMBERS { 6 | REF_PRIVATE_MEMBER(path, readonly()) 7 | } 8 | REF_END; 9 | -------------------------------------------------------------------------------- /tools/AssetManager/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_FOLDER "${CMAKE_FOLDER}/tools") 2 | add_subdirectory(FontEncoder) 3 | add_subdirectory(MaterialEncoder) 4 | add_subdirectory(MeshEncoder) 5 | add_subdirectory(SoundEncoder) 6 | add_subdirectory(TextureEncoder) 7 | 8 | add_subdirectory(AssetManager) 9 | -------------------------------------------------------------------------------- /tools/AssetManager/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/Text.md: -------------------------------------------------------------------------------- 1 | # Text Resources 2 | Before rendering Text, you must generate and load a .font file containing the pre-rendered characters. 3 | By default, any .ttf can be converted to a .font using the AssetManager. 4 | 5 | ```cpp 6 | auto font = Load("Fonts/Arial.font"); 7 | entity->Add("Hello World!", font); 8 | ``` -------------------------------------------------------------------------------- /gemcutter/Rendering/Viewport.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | 4 | namespace gem 5 | { 6 | struct Viewport 7 | { 8 | void bind() const; 9 | float GetAspectRatio() const; 10 | 11 | unsigned x = 0; 12 | unsigned y = 0; 13 | unsigned width = 1; 14 | unsigned height = 1; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /tools/FontEncoder/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "FontEncoder.h" 3 | 4 | #include 5 | 6 | int main() 7 | { 8 | gem::InitializeReflectionTables(); 9 | 10 | auto encoder = FontEncoder(); 11 | 12 | if (!gem::Encoder::RunEncoder(encoder)) 13 | { 14 | return EXIT_FAILURE; 15 | } 16 | 17 | return EXIT_SUCCESS; 18 | } 19 | -------------------------------------------------------------------------------- /tools/MeshEncoder/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "MeshEncoder.h" 3 | 4 | #include 5 | 6 | int main() 7 | { 8 | gem::InitializeReflectionTables(); 9 | 10 | auto encoder = MeshEncoder(); 11 | 12 | if (!gem::Encoder::RunEncoder(encoder)) 13 | { 14 | return EXIT_FAILURE; 15 | } 16 | 17 | return EXIT_SUCCESS; 18 | } 19 | -------------------------------------------------------------------------------- /tools/SoundEncoder/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #include "SoundEncoder.h" 3 | 4 | #include 5 | 6 | int main() 7 | { 8 | gem::InitializeReflectionTables(); 9 | 10 | auto encoder = SoundEncoder(); 11 | 12 | if (!gem::Encoder::RunEncoder(encoder)) 13 | { 14 | return EXIT_FAILURE; 15 | } 16 | 17 | return EXIT_SUCCESS; 18 | } 19 | -------------------------------------------------------------------------------- /tools/TextureEncoder/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #include "TextureEncoder.h" 3 | 4 | #include 5 | 6 | int main() 7 | { 8 | gem::InitializeReflectionTables(); 9 | 10 | auto encoder = TextureEncoder(); 11 | 12 | if (!gem::Encoder::RunEncoder(encoder)) 13 | { 14 | return EXIT_FAILURE; 15 | } 16 | 17 | return EXIT_SUCCESS; 18 | } 19 | -------------------------------------------------------------------------------- /resources/Shaders/VertexColor.shader: -------------------------------------------------------------------------------- 1 | Attributes 2 | { 3 | vec4 a_vert : 0; 4 | vec4 a_color : 1; 5 | } 6 | 7 | Vertex 8 | { 9 | out vec4 color; 10 | 11 | void main() 12 | { 13 | color = a_color; 14 | gl_Position = Gem_MVP * a_vert; 15 | } 16 | } 17 | 18 | Fragment 19 | { 20 | in vec4 color; 21 | out vec4 outColor; 22 | 23 | void main() 24 | { 25 | outColor = color; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tools/MaterialEncoder/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #include "MaterialEncoder.h" 3 | 4 | #include 5 | 6 | int main() 7 | { 8 | gem::InitializeReflectionTables(); 9 | 10 | auto encoder = MaterialEncoder(); 11 | 12 | if (!gem::Encoder::RunEncoder(encoder)) 13 | { 14 | return EXIT_FAILURE; 15 | } 16 | 17 | return EXIT_SUCCESS; 18 | } 19 | -------------------------------------------------------------------------------- /gemcutter/GUI/Rectangle.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #pragma once 3 | 4 | namespace gem 5 | { 6 | struct vec2; 7 | 8 | struct Rectangle 9 | { 10 | float GetAspectRatio() const; 11 | bool Contains(float x, float y) const; 12 | bool Contains(const vec2& pos) const; 13 | 14 | float x = 0.0f; 15 | float y = 0.0f; 16 | float width = 1.0f; 17 | float height = 1.0f; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /gemcutter/Entity/Hierarchy.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | namespace gem 3 | { 4 | template 5 | void Hierarchy::ForEachChild(this Self& self, bool recursive, Functor&& Func) 6 | { 7 | for (auto& entity : self.children) 8 | { 9 | Func(*entity); 10 | 11 | if (recursive) 12 | { 13 | entity->Get().ForEachChild(true, Func); 14 | } 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /gemcutter/GUI/Widget.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | namespace gem 3 | { 4 | template 5 | T& Widget::CreateChild(Args&&... constructorParams) 6 | { 7 | static_assert(std::is_base_of_v, "Template argument must be a Widget."); 8 | 9 | auto entity = owner.Get().CreateChild(); 10 | return entity->Add(std::forward(constructorParams)...); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /resources/Shaders/FlatColor.shader: -------------------------------------------------------------------------------- 1 | Attributes 2 | { 3 | vec4 a_vert : 0; 4 | } 5 | 6 | Uniforms 7 | { 8 | instance Material : 0 9 | { 10 | vec4 Color = (0.0, 0.0, 0.0, 1.0); 11 | } 12 | } 13 | 14 | Vertex 15 | { 16 | void main() 17 | { 18 | gl_Position = Gem_MVP * a_vert; 19 | } 20 | } 21 | 22 | Fragment 23 | { 24 | out vec4 outColor; 25 | 26 | void main() 27 | { 28 | outColor = Material.Color; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tools/MeshEncoder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND mesh_encoder_files 2 | "main.cpp" 3 | "MeshEncoder.h" 4 | "MeshEncoder.cpp" 5 | ) 6 | 7 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${mesh_encoder_files}) 8 | 9 | add_executable(mesh_encoder ${mesh_encoder_files}) 10 | sf_target_compile_warnings(mesh_encoder) 11 | sf_target_compile_warnings_as_errors(mesh_encoder OPTIONAL) 12 | 13 | target_link_libraries(mesh_encoder PRIVATE gemcutter) 14 | -------------------------------------------------------------------------------- /gemcutter/Math/Math.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Math.h" 3 | 4 | namespace gem 5 | { 6 | float SnapToGrid(float value, float step) 7 | { 8 | return std::floorf((value + (step / 2.0f)) / step) * step; 9 | } 10 | 11 | float EaseIn(float percent) 12 | { 13 | return 1.0f - cosf(percent * (M_PI / 2.0f)); 14 | } 15 | 16 | float EaseOut(float percent) 17 | { 18 | return cosf((percent - 1.0f) * (M_PI / 2.0f)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tools/SoundEncoder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND sound_encoder_files 2 | "main.cpp" 3 | "SoundEncoder.h" 4 | "SoundEncoder.cpp" 5 | ) 6 | 7 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${sound_encoder_files}) 8 | 9 | add_executable(sound_encoder ${sound_encoder_files}) 10 | sf_target_compile_warnings(sound_encoder) 11 | sf_target_compile_warnings_as_errors(sound_encoder OPTIONAL) 12 | 13 | target_link_libraries(sound_encoder 14 | PRIVATE 15 | gemcutter 16 | ) 17 | -------------------------------------------------------------------------------- /tools/FontEncoder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND font_encoder_files 2 | "main.cpp" 3 | "FontEncoder.h" 4 | "FontEncoder.cpp" 5 | ) 6 | 7 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${font_encoder_files}) 8 | 9 | add_executable(font_encoder ${font_encoder_files}) 10 | sf_target_compile_warnings(font_encoder) 11 | sf_target_compile_warnings_as_errors(font_encoder OPTIONAL) 12 | 13 | target_link_libraries(font_encoder 14 | PRIVATE 15 | gemcutter 16 | freetype 17 | ) 18 | -------------------------------------------------------------------------------- /resources/Shaders/Diffuse.shader: -------------------------------------------------------------------------------- 1 | Attributes 2 | { 3 | vec4 a_vert : 0; 4 | vec2 a_uv : 1; 5 | } 6 | 7 | Vertex 8 | { 9 | out vec2 texcoord; 10 | 11 | void main() 12 | { 13 | gl_Position = Gem_MVP * a_vert; 14 | texcoord = a_uv; 15 | } 16 | } 17 | 18 | Samplers 19 | { 20 | sampler2D sTex : 0; 21 | } 22 | 23 | Fragment 24 | { 25 | in vec2 texcoord; 26 | out vec3 outColor; 27 | 28 | void main() 29 | { 30 | outColor = texture(sTex, texcoord).rgb; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tools/MaterialEncoder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND material_encoder_files 2 | "main.cpp" 3 | "MaterialEncoder.h" 4 | "MaterialEncoder.cpp" 5 | ) 6 | 7 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${material_encoder_files}) 8 | 9 | add_executable(material_encoder ${material_encoder_files}) 10 | sf_target_compile_warnings(material_encoder) 11 | sf_target_compile_warnings_as_errors(material_encoder OPTIONAL) 12 | 13 | target_link_libraries(material_encoder PRIVATE gemcutter) 14 | -------------------------------------------------------------------------------- /tools/TextureEncoder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND texture_encoder_files 2 | "main.cpp" 3 | "TextureEncoder.h" 4 | "TextureEncoder.cpp" 5 | ) 6 | 7 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${texture_encoder_files}) 8 | 9 | add_executable(texture_encoder ${texture_encoder_files}) 10 | sf_target_compile_warnings(texture_encoder) 11 | sf_target_compile_warnings_as_errors(texture_encoder OPTIONAL) 12 | 13 | target_link_libraries(texture_encoder 14 | PRIVATE 15 | gemcutter 16 | soil2 17 | ) 18 | -------------------------------------------------------------------------------- /tests/WeakPtr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace gem; 5 | 6 | TEST_CASE("WeakPtr") 7 | { 8 | std::shared_ptr shared = std::make_shared(1); 9 | std::weak_ptr weak = shared; 10 | std::weak_ptr weakNull; 11 | std::weak_ptr weakExpired = std::make_shared(1); 12 | 13 | SECTION("Null Checks") 14 | { 15 | CHECK(!IsPtrNull(weak)); 16 | CHECK(IsPtrNull(weakNull)); 17 | CHECK(!IsPtrNull(weakExpired)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | list(APPEND unit_test_files 2 | "Delegate.cpp" 3 | "EntityComponentSystem.cpp" 4 | "EnumFlags.cpp" 5 | "FileSystem.cpp" 6 | "Hierarchy.cpp" 7 | "main.cpp" 8 | "Math.cpp" 9 | "Meta.cpp" 10 | "String.cpp" 11 | "WeakPtr.cpp" 12 | ) 13 | 14 | source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${unit_test_files}) 15 | 16 | add_executable(unit_tests ${unit_test_files}) 17 | sf_target_compile_warnings(unit_tests) 18 | 19 | target_link_libraries(unit_tests 20 | PRIVATE 21 | gemcutter 22 | catch 23 | ) 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch", 7 | "request": "launch", 8 | "type": "cppvsdbg", 9 | "program": "${command:cmake.launchTargetPath}", 10 | "args": [], 11 | "cwd": "${workspaceFolder}\\samples", 12 | "visualizerFile": "${workspaceFolder}\\gemcutter\\Gemcutter.natvis", 13 | "environment": [] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /resources/Shaders/Skybox.shader: -------------------------------------------------------------------------------- 1 | Attributes 2 | { 3 | vec3 a_vert : 0; 4 | } 5 | 6 | Vertex 7 | { 8 | out vec3 texcoord; 9 | 10 | void main() 11 | { 12 | texcoord = a_vert; 13 | // After the perspective divide, Z will be very close to 1.0 14 | gl_Position = (Gem_ViewProj * vec4(a_vert, 0.0)).xyww; 15 | gl_Position.w *= 1.0001; 16 | } 17 | } 18 | 19 | Samplers 20 | { 21 | samplerCube sTex : 0; 22 | } 23 | 24 | Fragment 25 | { 26 | in vec3 texcoord; 27 | out vec4 outColor; 28 | 29 | void main() 30 | { 31 | outColor = texture(sTex, texcoord); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_RUNNER 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | int main(int argc, char** argv) 10 | { 11 | gem::InitializeReflectionTables(); 12 | 13 | const int result = Catch::Session().run(argc, argv); 14 | if (result != 0) 15 | { 16 | std::println("Press any key to continue..."); 17 | std::cin.get(); 18 | } 19 | else 20 | { 21 | std::this_thread::sleep_for(std::chrono::seconds(1)); 22 | } 23 | 24 | return result; 25 | } 26 | -------------------------------------------------------------------------------- /tools/AssetManager/workspace_templates/open_asset_manager.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: Prefer the Release version if it exists. 4 | if exist "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/asset_manager.exe" ( 5 | start "" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/asset_manager.exe" 6 | goto :EOF 7 | ) 8 | 9 | if exist "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/RelWithDebInfo/asset_manager.exe" ( 10 | start "" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/RelWithDebInfo/asset_manager.exe" 11 | goto :EOF 12 | ) 13 | 14 | start "" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/asset_manager.exe" 15 | -------------------------------------------------------------------------------- /.vscode/cmake-variants.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildType": { 3 | "default": "debug", 4 | "description": "The build type.", 5 | "choices": { 6 | "debug": { 7 | "short": "Debug", 8 | "long": "Optimizations disabled.", 9 | "buildType": "Debug" 10 | }, 11 | "development": { 12 | "short": "Development", 13 | "long": "Optimizations enabled with development tools.", 14 | "buildType": "RelWithDebInfo" 15 | }, 16 | "production": { 17 | "short": "Production", 18 | "long": "Optimizations enabled without development tools.", 19 | "buildType": "Release" 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /tools/FontEncoder/FontEncoder.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | class FontEncoder final : public gem::Encoder 6 | { 7 | public: 8 | FontEncoder(); 9 | 10 | gem::ConfigTable GetDefault() const override; 11 | 12 | bool Validate(const gem::ConfigTable& metadata, unsigned loadedVersion) const override; 13 | 14 | private: 15 | bool Convert(std::string_view source, std::string_view destination, const gem::ConfigTable& metadata) const override; 16 | 17 | bool Upgrade(gem::ConfigTable& metadata, unsigned loadedVersion) const override; 18 | }; 19 | -------------------------------------------------------------------------------- /tools/MeshEncoder/MeshEncoder.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | class MeshEncoder final : public gem::Encoder 6 | { 7 | public: 8 | MeshEncoder(); 9 | 10 | gem::ConfigTable GetDefault() const override; 11 | 12 | bool Validate(const gem::ConfigTable& metadata, unsigned loadedVersion) const override; 13 | 14 | private: 15 | bool Convert(std::string_view source, std::string_view destination, const gem::ConfigTable& metadata) const override; 16 | 17 | bool Upgrade(gem::ConfigTable& metadata, unsigned loadedVersion) const override; 18 | }; 19 | -------------------------------------------------------------------------------- /tools/SoundEncoder/SoundEncoder.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | class SoundEncoder final : public gem::Encoder 6 | { 7 | public: 8 | SoundEncoder(); 9 | 10 | gem::ConfigTable GetDefault() const override; 11 | 12 | bool Validate(const gem::ConfigTable& metadata, unsigned loadedVersion) const override; 13 | 14 | private: 15 | bool Convert(std::string_view source, std::string_view destination, const gem::ConfigTable& metadata) const override; 16 | 17 | bool Upgrade(gem::ConfigTable& metadata, unsigned loadedVersion) const override; 18 | }; 19 | -------------------------------------------------------------------------------- /tools/MaterialEncoder/MaterialEncoder.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | class MaterialEncoder final : public gem::Encoder 6 | { 7 | public: 8 | MaterialEncoder(); 9 | 10 | gem::ConfigTable GetDefault() const override; 11 | 12 | bool Validate(const gem::ConfigTable& metadata, unsigned loadedVersion) const override; 13 | 14 | private: 15 | bool Convert(std::string_view source, std::string_view destination, const gem::ConfigTable& metadata) const override; 16 | 17 | bool Upgrade(gem::ConfigTable& metadata, unsigned loadedVersion) const override; 18 | }; 19 | -------------------------------------------------------------------------------- /tools/TextureEncoder/TextureEncoder.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | class TextureEncoder final : public gem::Encoder 6 | { 7 | public: 8 | TextureEncoder(); 9 | 10 | gem::ConfigTable GetDefault() const override; 11 | 12 | bool Validate(const gem::ConfigTable& metadata, unsigned loadedVersion) const override; 13 | 14 | private: 15 | bool Convert(std::string_view source, std::string_view destination, const gem::ConfigTable& metadata) const override; 16 | 17 | bool Upgrade(gem::ConfigTable& metadata, unsigned loadedVersion) const override; 18 | }; 19 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | root = true 3 | 4 | # Global 5 | [*] 6 | charset = utf-8 7 | indent_style = tab 8 | indent_size = 4 9 | tab_width = 4 10 | 11 | # c++, c# 12 | [*.{c,cs,cpp,h,inl}] 13 | curly_bracket_next_line=true 14 | indent_brace_style=Allman 15 | insert_final_newline=true 16 | trim_trailing_whitespace=true 17 | 18 | # MSBuild 19 | [*.{csproj,vcxproj}] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | # Config files 24 | [*.{config,resx,xml,cache,workspace,natvis}] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | # Batch files 29 | [*.cmd] 30 | indent_style = space 31 | indent_size = 4 32 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Viewport.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Viewport.h" 3 | #include "gemcutter/Rendering/Rendering.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace gem 9 | { 10 | void Viewport::bind() const 11 | { 12 | SetViewport(x, y, width, height); 13 | } 14 | 15 | float Viewport::GetAspectRatio() const 16 | { 17 | return static_cast(width) / static_cast(height); 18 | } 19 | } 20 | 21 | REFLECT(gem::Viewport) 22 | MEMBERS { 23 | REF_MEMBER(x) 24 | REF_MEMBER(y) 25 | REF_MEMBER(width, min(0u)) 26 | REF_MEMBER(height, min(0u)) 27 | } 28 | REF_END; 29 | -------------------------------------------------------------------------------- /gemcutter/GUI/Rectangle.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #include "Rectangle.h" 3 | 4 | #include 5 | 6 | namespace gem 7 | { 8 | float Rectangle::GetAspectRatio() const 9 | { 10 | return width / height; 11 | } 12 | 13 | bool Rectangle::Contains(float _x, float _y) const 14 | { 15 | return _x >= x && 16 | _y >= y && 17 | _x <= x + width && 18 | _y <= y + height; 19 | } 20 | 21 | bool Rectangle::Contains(const vec2& pos) const 22 | { 23 | return Contains(pos.x, pos.y); 24 | } 25 | } 26 | 27 | REFLECT(gem::Rectangle) 28 | MEMBERS { 29 | REF_MEMBER(x) 30 | REF_MEMBER(y) 31 | REF_MEMBER(width) 32 | REF_MEMBER(height) 33 | } 34 | REF_END; 35 | -------------------------------------------------------------------------------- /resources/Shaders/Font.shader: -------------------------------------------------------------------------------- 1 | Attributes 2 | { 3 | vec4 a_vert : 0; 4 | vec2 a_uv : 1; 5 | } 6 | 7 | Uniforms 8 | { 9 | instance Material : 0 10 | { 11 | vec3 Color = (1.0, 1.0, 1.0); 12 | } 13 | } 14 | 15 | Vertex 16 | { 17 | out vec2 texcoord; 18 | 19 | void main() 20 | { 21 | gl_Position = Gem_MVP * a_vert; 22 | texcoord = a_uv; 23 | } 24 | } 25 | 26 | Samplers 27 | { 28 | sampler2D sTex : 0; 29 | } 30 | 31 | Fragment 32 | { 33 | in vec2 texcoord; 34 | 35 | out vec4 outColor; 36 | 37 | void main() 38 | { 39 | outColor = vec4(Material.Color, texture(sTex, texcoord).r); 40 | 41 | #if defined(GEM_CUTOUT) 42 | if (outColor.a < 1.0) 43 | { 44 | discard; 45 | } 46 | #endif 47 | } 48 | } -------------------------------------------------------------------------------- /gemcutter/Entity/Name.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace gem 9 | { 10 | // Searches the given entity's sub-tree for the first child with the specified name. 11 | Entity* FindChild(const Entity& root, std::string_view name); 12 | 13 | // Searches all Entities with a name component and returns the first one found with the specified name. 14 | Entity* FindEntity(std::string_view name); 15 | 16 | // Associates an entity with a name. 17 | class Name : public Component 18 | { 19 | public: 20 | Name(Entity& owner); 21 | Name(Entity& owner, std::string name); 22 | 23 | std::string name; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /gemcutter/GUI/Screen.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Application/Application.h" 4 | #include "gemcutter/Application/Event.h" 5 | #include "gemcutter/GUI/Widget.h" 6 | 7 | namespace gem 8 | { 9 | // A root UI widget which automatically resizes to the size of the application's screen. 10 | // This is intended to be the root widget for a standard UI setup. 11 | class Screen : public Widget 12 | { 13 | public: 14 | Screen(Entity& owner); 15 | 16 | private: 17 | // These inherited functions are hidden because they are not appropriate for a screen root. 18 | void SetTransform(vec2 position, vec2 size) = delete; 19 | void FitToParent() = delete; 20 | 21 | Listener onResize; 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /gemcutter/Math/Transform.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Math/Quaternion.h" 4 | #include "gemcutter/Math/Vector.h" 5 | 6 | namespace gem 7 | { 8 | // A pose in space - Position / Rotation / Scale. 9 | struct Transform 10 | { 11 | Transform() = default; 12 | Transform(const vec3& position); 13 | Transform(const vec3& position, const quat& rotation); 14 | Transform(const vec3& position, const quat& rotation, const vec3& scale); 15 | 16 | void LookAt(const vec3& pos, const vec3& target, const vec3& up = vec3::Up); 17 | void Rotate(const vec3& axis, float degrees); 18 | void RotateX(float degrees); 19 | void RotateY(float degrees); 20 | void RotateZ(float degrees); 21 | 22 | vec3 position; 23 | quat rotation; 24 | vec3 scale = vec3::One; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /gemcutter/GUI/Image.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/GUI/Widget.h" 4 | #include "gemcutter/Resource/Material.h" 5 | #include "gemcutter/Resource/Texture.h" 6 | 7 | namespace gem 8 | { 9 | class Sprite; 10 | 11 | // A widget containing a sprite scaled to fit within it. 12 | class Image : public Widget 13 | { 14 | public: 15 | Image(Entity& owner); 16 | Image(Entity& owner, Texture::Ptr texture); 17 | Image(Entity& owner, Material::Ptr material); 18 | 19 | Sprite& GetSprite(); 20 | const Sprite& GetSprite() const; 21 | 22 | void SetTexture(Texture::Ptr texture); 23 | Texture::Ptr GetTexture() const; 24 | 25 | void SetMaterial(Material::Ptr material); 26 | Material& GetMaterial() const; 27 | 28 | private: 29 | void UpdateContent() override; 30 | 31 | Sprite* sprite = nullptr; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Mesh.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Rendering/Renderable.h" 4 | #include "gemcutter/Resource/Model.h" 5 | 6 | namespace gem 7 | { 8 | // Causes an entity to render as a 3D mesh. 9 | // Can use loaded *.models as well as custom VertexArrays. 10 | class Mesh : public Renderable 11 | { 12 | public: 13 | Mesh(Entity& owner); 14 | Mesh(Entity& owner, Model::Ptr model); 15 | Mesh(Entity& owner, Model::Ptr model, Material::Ptr material); 16 | Mesh(Entity& owner, Material::Ptr material); 17 | Mesh(Entity& owner, VertexArray::Ptr array); 18 | Mesh(Entity& owner, VertexArray::Ptr array, Material::Ptr material); 19 | 20 | void SetModel(Model::Ptr model); 21 | Model* GetModel() const; 22 | 23 | private: 24 | Model::Ptr model; 25 | 26 | public: 27 | PRIVATE_MEMBER(Mesh, model); 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Text.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Rendering/Renderable.h" 4 | #include "gemcutter/Resource/Font.h" 5 | 6 | #include 7 | 8 | namespace gem 9 | { 10 | // Causes text to render at the entity's position. 11 | class Text : public Renderable 12 | { 13 | public: 14 | Text(Entity& owner); 15 | Text(Entity& owner, Font::Ptr font); 16 | Text(Entity& owner, std::string string); 17 | Text(Entity& owner, Material::Ptr material); 18 | Text(Entity& owner, Font::Ptr font, std::string string, Material::Ptr material); 19 | 20 | unsigned GetNumLines() const; 21 | float GetLineWidth(unsigned line) const; 22 | 23 | Font::Ptr font; 24 | std::string string; 25 | bool centeredX = false; 26 | bool centeredY = false; 27 | // Extra spacing between letters. 28 | float kerning = 0.0f; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /gemcutter/Application/HierarchicalEvent.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Emilian Cioca 2 | #include "HierarchicalEvent.h" 3 | #include "gemcutter/Application/Application.h" 4 | #include "gemcutter/Input/Input.h" 5 | 6 | #include 7 | 8 | REFLECT_SIMPLE(gem::HierarchicalListener); 9 | REFLECT_SIMPLE(gem::HierarchicalListener); 10 | REFLECT_SIMPLE(gem::HierarchicalListener); 11 | REFLECT_SIMPLE(gem::HierarchicalListener); 12 | REFLECT_SIMPLE(gem::HierarchicalListener); 13 | 14 | REFLECT_SIMPLE(gem::HierarchicalDispatcher); 15 | REFLECT_SIMPLE(gem::HierarchicalDispatcher); 16 | REFLECT_SIMPLE(gem::HierarchicalDispatcher); 17 | REFLECT_SIMPLE(gem::HierarchicalDispatcher); 18 | REFLECT_SIMPLE(gem::HierarchicalDispatcher); 19 | -------------------------------------------------------------------------------- /gemcutter/Sound/SoundSystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | 4 | namespace gem 5 | { 6 | // Provides global control of audio playback. 7 | extern class SoundSystemSingleton SoundSystem; 8 | class SoundSystemSingleton 9 | { 10 | friend class ApplicationSingleton; // For Update(). 11 | public: 12 | bool Init(unsigned bufferSize = 1024); 13 | bool IsLoaded() const; 14 | void Unload(); 15 | 16 | // Sets the global volume level. Should be [0, ...]. Default is 1.0f. 17 | void SetGlobalVolume(float volume); 18 | float GetGlobalVolume() const; 19 | 20 | void Mute(); 21 | bool IsMuted() const; 22 | void Unmute(); 23 | 24 | private: 25 | // Called every frame in order to ensure that sounds play at the correct locations. 26 | void Update(); 27 | 28 | bool muted = true; 29 | bool initialized = false; 30 | float globalVolume = 1.0f; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /tools/AssetManager/workspace_templates/update_metadata.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: Prefer the Release version if it exists. 4 | if exist "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/asset_manager.exe" ( 5 | title Updating Metadata [Release] 6 | call "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/asset_manager.exe" --update 7 | goto :end 8 | ) 9 | 10 | if exist "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/RelWithDebInfo/asset_manager.exe" ( 11 | title Updating Metadata [RelWithDebInfo] 12 | call "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/RelWithDebInfo/asset_manager.exe" --update 13 | goto :end 14 | ) 15 | 16 | title Updating Metadata [Debug] 17 | call "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/asset_manager.exe" --update 18 | 19 | :end 20 | if errorlevel 1 ( 21 | title Update failed! 22 | pause 23 | goto :eof 24 | ) 25 | 26 | title Updating complete! 27 | echo Updating complete! 28 | goto :eof 29 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/scaffold"] 2 | path = external/scaffold 3 | url = https://github.com/EmilianC/scaffold.git 4 | branch = compile-options 5 | [submodule "samples"] 6 | path = samples 7 | url = https://github.com/EmilianC/Gemcutter-Samples.git 8 | [submodule "external/imgui"] 9 | path = external/imgui 10 | url = https://github.com/ocornut/imgui.git 11 | [submodule "external/soloud"] 12 | path = external/soloud 13 | url = https://github.com/EmilianC/soloud.git 14 | [submodule "external/loupe"] 15 | path = external/loupe 16 | url = https://github.com/EmilianC/Loupe.git 17 | [submodule "external/glew"] 18 | path = external/glew 19 | url = https://github.com/Perlmint/glew-cmake.git 20 | [submodule "external/freetype"] 21 | path = external/freetype 22 | url = https://github.com/EmilianC/freetype.git 23 | [submodule "external/soil2"] 24 | path = external/soil2 25 | url = https://github.com/SpartanJ/SOIL2.git 26 | -------------------------------------------------------------------------------- /gemcutter/Sound/SoundListener.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | 5 | namespace gem 6 | { 7 | // The Entity with this component marks the location at which sounds are heard from. 8 | // Only one SoundListener can be enabled at a given point in time. 9 | // Creating or enabling a SoundListener will disable the currently active one. 10 | // Disabling this component mutes all audio until a SoundListener becomes enabled again. 11 | class SoundListener : public Component 12 | { 13 | public: 14 | SoundListener(Entity& owner); 15 | 16 | // Gets the currently active SoundListener. There can only ever be one. 17 | static Entity::Ptr GetListener(); 18 | 19 | private: 20 | void OnDisable() final override; 21 | void OnEnable() final override; 22 | 23 | // A pointer to the active listener. 24 | static Entity::WeakPtr listener; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /tests/FileSystem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace gem; 5 | 6 | TEST_CASE("FileSystem") 7 | { 8 | SECTION("Directories and Filenames") 9 | { 10 | CHECK(DirectoryExists("C:/")); 11 | 12 | CHECK(IsPathAbsolute("C:/")); 13 | 14 | CHECK_FALSE(IsPathRelative("C:/")); 15 | 16 | CHECK(IsPathRelative("./Folder1/Folder2/")); 17 | 18 | CHECK(IsPathRelative("/Folder1/Folder2/")); 19 | 20 | CHECK(ExtractDriveLetter("C:/user/test.txt") == 'C'); 21 | 22 | CHECK(ExtractPath("C:/user/test.txt") == "C:/user/"); 23 | 24 | CHECK(ExtractPath("./user/test.txt") == "./user/"); 25 | 26 | CHECK(ExtractFile("C:/user/test2.txt") == "test2.txt"); 27 | 28 | CHECK(ExtractFilename("C:/user/test3.txt") == "test3"); 29 | 30 | CHECK(ExtractFileExtension("C:/user/test4.txt") == ".txt"); 31 | 32 | CHECK(ExtractFileExtension("C:/user/test5.txt.zip") == ".zip"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gemcutter/Application/Event.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Event.h" 3 | 4 | #include "gemcutter/Application/Logging.h" 5 | 6 | namespace gem 7 | { 8 | EventQueueSingleton EventQueue; 9 | 10 | void EventQueueSingleton::Push(std::unique_ptr e) 11 | { 12 | eventQueue.push(std::move(e)); 13 | } 14 | 15 | void EventQueueSingleton::Dispatch(const EventBase& e) const 16 | { 17 | e.Raise(); 18 | } 19 | 20 | void EventQueueSingleton::Dispatch() 21 | { 22 | ASSERT(!dispatching, 23 | "Call to Dispatch() cannot be executed because the event queue is already being dispatched higher in the call stack.\n" 24 | "Consider using Dispatch(const EventBase&) if an event must be processed here immediately."); 25 | 26 | dispatching = true; 27 | while (!eventQueue.empty()) 28 | { 29 | eventQueue.front()->Raise(); 30 | eventQueue.pop(); 31 | } 32 | dispatching = false; 33 | } 34 | 35 | bool EventQueueSingleton::IsDispatching() const 36 | { 37 | return dispatching; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gemcutter/Resource/Material.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Rendering/Rendering.h" 4 | #include "gemcutter/Resource/Resource.h" 5 | #include "gemcutter/Resource/Shader.h" 6 | #include "gemcutter/Resource/Texture.h" 7 | 8 | namespace gem 9 | { 10 | // When rendering an Entity, the Material specifies: 11 | // - Textures 12 | // - Blend / Cull / Depth modes 13 | // - The shader 14 | // - Static uniform buffers 15 | class Material : public Resource, public Shareable 16 | { 17 | public: 18 | static constexpr std::string_view Extension = ".material"; 19 | 20 | Material() = default; 21 | 22 | bool Load(std::string_view filePath); 23 | 24 | BlendFunc blendMode = BlendFunc::None; 25 | DepthFunc depthMode = DepthFunc::Normal; 26 | CullFunc cullMode = CullFunc::Clockwise; 27 | 28 | // The shader used to render the entity. 29 | Shader::Ptr shader; 30 | // These textures will be bound whenever the material is used in rendering. 31 | TextureList textures; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /tools/AssetManager/workspace_templates/pack_assets.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | if "%1" == "" goto :default 4 | 5 | title Packing Assets [%1] 6 | call "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/%1/asset_manager.exe" --pack 7 | goto :end 8 | 9 | :default 10 | :: Prefer the Release version if it exists. 11 | if exist "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/asset_manager.exe" ( 12 | title Packing Assets [Release] 13 | call "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/asset_manager.exe" --pack 14 | goto :end 15 | ) 16 | 17 | if exist "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/RelWithDebInfo/asset_manager.exe" ( 18 | title Packing Assets [RelWithDebInfo] 19 | call "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/RelWithDebInfo/asset_manager.exe" --pack 20 | goto :end 21 | ) 22 | 23 | title Packing Assets [Debug] 24 | call "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/asset_manager.exe" --pack 25 | 26 | :end 27 | if errorlevel 1 ( 28 | title Packing failed! 29 | pause 30 | goto :eof 31 | ) 32 | 33 | title Packing completed! 34 | echo Packing completed! 35 | goto :eof 36 | -------------------------------------------------------------------------------- /gemcutter/GUI/Screen.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #include "Screen.h" 3 | #include "gemcutter/Application/HierarchicalEvent.h" 4 | #include "gemcutter/Entity/Hierarchy.h" 5 | #include "gemcutter/Input/Input.h" 6 | 7 | namespace gem 8 | { 9 | Screen::Screen(Entity& _owner) 10 | : Widget(_owner) 11 | { 12 | owner.Require(); 13 | ASSERT(owner.Get().IsRoot(), "A Screen widget must be a root."); 14 | 15 | owner.Require>(); 16 | owner.Require>(); 17 | owner.Require>(); 18 | owner.Require>(); 19 | 20 | top.offset = static_cast(Application.GetScreenHeight()); 21 | right.offset = static_cast(Application.GetScreenWidth()); 22 | 23 | onResize = [this](const Resize& e) { 24 | top.offset = static_cast(e.height); 25 | right.offset = static_cast(e.width); 26 | }; 27 | } 28 | } 29 | 30 | REFLECT_COMPONENT(gem::Screen, gem::Widget) REF_END; 31 | -------------------------------------------------------------------------------- /gemcutter/GUI/Label.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/GUI/Widget.h" 4 | #include "gemcutter/Resource/Font.h" 5 | #include "gemcutter/Resource/Material.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace gem 11 | { 12 | class Text; 13 | 14 | // A widget containing text scaled to fit within it. 15 | class Label : public Widget 16 | { 17 | public: 18 | Label(Entity& owner); 19 | Label(Entity& owner, Font::Ptr font, std::string_view string); 20 | Label(Entity& owner, Font::Ptr font, std::string_view string, Material::Ptr material); 21 | 22 | Text& GetText(); 23 | const Text& GetText() const; 24 | 25 | void SetString(std::string_view string); 26 | const std::string& GetString() const; 27 | 28 | void SetFont(Font::Ptr font); 29 | Font::Ptr GetFont() const; 30 | 31 | void SetMaterial(Material::Ptr material); 32 | Material& GetMaterial() const; 33 | 34 | float textScale = 1.0f; 35 | 36 | private: 37 | void UpdateContent() override; 38 | 39 | float textWidth = 0.0f; 40 | Text* text = nullptr; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /gemcutter/Utilities/Random.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | 4 | namespace gem 5 | { 6 | struct vec3; 7 | 8 | void SeedRandomNumberGenerator(); 9 | void SeedRandomNumberGenerator(unsigned seed); 10 | 11 | // Returns a random float in the range [min, max]. 12 | float RandomRange(float min, float max); 13 | // Returns a random int in the range [min, max]. 14 | int RandomRange(int min, int max); 15 | 16 | // Returns a random unit-length vector. 17 | vec3 RandomDirection(); 18 | // Returns a random color with [0, 1] RGB values. 19 | vec3 RandomColor(); 20 | 21 | // Returns true randomly given the [0, 1] probability. 22 | bool RandomBool(float probability); 23 | 24 | struct Range 25 | { 26 | Range() = default; 27 | Range(float min, float max); 28 | 29 | // Potential range is the 'value' +/- half the 'deviation'. 30 | [[nodiscard]] static Range Deviation(float value, float deviation); 31 | 32 | float Random() const; 33 | void Set(float min, float max); 34 | 35 | bool Contains(float value) const; 36 | 37 | float min = 0.0f; 38 | float max = 1.0f; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /gemcutter/Application/Delegate.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #include "Delegate.h" 3 | 4 | namespace gem 5 | { 6 | namespace detail 7 | { 8 | DelegateBase::DelegateBase() 9 | : controlBlock(std::make_shared(this)) 10 | { 11 | } 12 | 13 | DelegateBase::DelegateBase(DelegateBase&& other) noexcept 14 | : controlBlock(std::move(other.controlBlock)) 15 | { 16 | controlBlock->delegate = this; 17 | } 18 | 19 | DelegateBase& DelegateBase::operator=(DelegateBase&& other) noexcept 20 | { 21 | controlBlock = std::move(other.controlBlock); 22 | controlBlock->delegate = this; 23 | 24 | return *this; 25 | } 26 | } 27 | 28 | DelegateHandle::DelegateHandle(std::weak_ptr blockPtr, detail::DelegateId handleId) 29 | : controlBlock(std::move(blockPtr)) 30 | , id(handleId) 31 | { 32 | } 33 | 34 | DelegateHandle::~DelegateHandle() 35 | { 36 | Expire(); 37 | } 38 | 39 | void DelegateHandle::Expire() 40 | { 41 | if (auto lock = controlBlock.lock()) 42 | { 43 | lock->delegate->Unbind(*this); 44 | controlBlock.reset(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /resources/Shaders/Lambert.shader: -------------------------------------------------------------------------------- 1 | Attributes 2 | { 3 | vec4 a_vert : 0; 4 | vec2 a_uv : 1; 5 | vec3 a_normal : 2; 6 | } 7 | 8 | Samplers 9 | { 10 | sampler2D sTex : 0; 11 | } 12 | 13 | Uniforms 14 | { 15 | Light : 0 16 | { 17 | vec3 Color; 18 | vec3 Position; 19 | vec3 Direction; 20 | float AttenuationLinear; 21 | float AttenuationQuadratic; 22 | float Angle; 23 | uint Type; 24 | } 25 | 26 | static Ambient : 1 27 | { 28 | vec3 Color = (0.1, 0.1, 0.1); 29 | } 30 | } 31 | 32 | Vertex 33 | { 34 | out vec2 texcoord; 35 | out vec3 norm; 36 | out vec3 pos; 37 | 38 | void main() 39 | { 40 | pos = (Gem_Model * a_vert).xyz; 41 | gl_Position = Gem_ViewProj * vec4(pos, 1.0); 42 | 43 | texcoord = a_uv; 44 | norm = Gem_NormalToWorld * a_normal; 45 | } 46 | } 47 | 48 | Fragment 49 | { 50 | in vec2 texcoord; 51 | in vec3 norm; 52 | in vec3 pos; 53 | 54 | out vec3 outColor; 55 | 56 | void main() 57 | { 58 | vec3 normal = normalize(norm); 59 | 60 | vec3 lighting = Ambient.Color; 61 | lighting += compute_light(Light, normal, pos); 62 | 63 | outColor = texture(sTex, texcoord).rgb * lighting; 64 | } 65 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Emilian Cioca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /tools/AssetManager/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace AssetManager.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gemcutter/Sound/SoundSource.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | #include "gemcutter/Resource/Sound.h" 5 | 6 | namespace gem 7 | { 8 | // Plays 2D or 3D audio. 9 | class SoundSource : public Component 10 | { 11 | friend class SoundSystemSingleton; // For hSound. 12 | public: 13 | SoundSource(Entity& owner); 14 | SoundSource(Entity& owner, Sound::Ptr sound); 15 | ~SoundSource(); 16 | 17 | void SetData(Sound::Ptr sound); 18 | Sound* GetData() const; 19 | 20 | void Play(); 21 | void Stop(); 22 | void Pause(); 23 | void Resume(); 24 | bool IsPlaying() const; 25 | bool IsPaused() const; 26 | 27 | // Sets the volume multiplier of this SoundSource instance. 28 | void SetSourceVolume(float volume); 29 | float GetSourceVolume() const; 30 | 31 | private: 32 | // Stops any playing sound when disabled. 33 | void OnDisable() final override; 34 | 35 | Sound::Ptr data; 36 | 37 | // A handle for the sound instance. 38 | unsigned hSound = 0; 39 | float volume = 1.0f; 40 | 41 | public: 42 | PRIVATE_MEMBER(SoundSource, data); 43 | PRIVATE_MEMBER(SoundSource, volume); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /gemcutter/Resource/Model.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Math/Vector.h" 4 | #include "gemcutter/Resource/Resource.h" 5 | #include "gemcutter/Resource/VertexArray.h" 6 | 7 | namespace gem 8 | { 9 | // A 3D model resource. Can be attached to an Entity's Mesh component. 10 | // 11 | // Provides the following attributes and bindings: 12 | // Vertex : 0 13 | // UVs : 1 14 | // Normal : 2 15 | // Tangent : 3 16 | class Model : public Resource, public Shareable 17 | { 18 | public: 19 | static constexpr std::string_view Extension = ".model"; 20 | 21 | // Loads pre-packed *.model resources. 22 | bool Load(std::string_view filePath); 23 | 24 | VertexArray::Ptr GetArray() const; 25 | 26 | // Returns the extents of each axis in local-space. 27 | const vec3& GetMinBounds() const; 28 | const vec3& GetMaxBounds() const; 29 | 30 | bool HasUVs() const; 31 | bool HasNormals() const; 32 | bool HasTangents() const; 33 | 34 | private: 35 | VertexArray::Ptr array; 36 | 37 | vec3 minBounds; 38 | vec3 maxBounds; 39 | 40 | bool hasUvs = false; 41 | bool hasNormals = false; 42 | bool hasTangents = false; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Renderable.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | #include "gemcutter/Resource/Material.h" 5 | #include "gemcutter/Resource/VertexArray.h" 6 | 7 | namespace gem 8 | { 9 | // Base class for all renderable components. 10 | class Renderable : public Component 11 | { 12 | public: 13 | Renderable(Entity& owner); 14 | Renderable(Entity& owner, Material::Ptr material); 15 | Renderable(Entity& owner, VertexArray::Ptr array); 16 | Renderable(Entity& owner, VertexArray::Ptr array, Material::Ptr material); 17 | 18 | void SetMaterial(Material::Ptr newMaterial); 19 | const Material& GetMaterial() const; 20 | Material& GetMaterial(); 21 | 22 | // The set of definitions used to permute the shader. 23 | ShaderVariantControl variants; 24 | 25 | // Per-instance uniform data for the Material's shader. 26 | BufferList buffers; 27 | 28 | // Per-instance texture overrides for the Material. 29 | TextureList textures; 30 | 31 | // The main geometry data to be rendered. 32 | VertexArray::Ptr array; 33 | 34 | private: 35 | Material::Ptr material; 36 | 37 | public: 38 | PRIVATE_MEMBER(Renderable, material); 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${workspaceFolder}/external/catch/**", 8 | "${workspaceFolder}/external/dirent/**", 9 | "${workspaceFolder}/external/freetype/**", 10 | "${workspaceFolder}/external/glew/**", 11 | "${workspaceFolder}/external/imgui/**", 12 | "${workspaceFolder}/external/soloud/include/**", 13 | "${workspaceFolder}/external/soil/**" 14 | ], 15 | "defines": [ 16 | "_DEBUG", 17 | "GEM_DEBUG", 18 | "GEM_DEV", 19 | "GEM_PRODUCTION", 20 | "WIN32_LEAN_AND_MEAN", 21 | "GLEW_STATIC", 22 | "NOMINMAX", 23 | "_CRT_SECURE_NO_WARNINGS", 24 | "IMGUI_ENABLED" 25 | ], 26 | "cStandard": "c11", 27 | "cppStandard": "c++23", 28 | "intelliSenseMode": "msvc-x64", 29 | "configurationProvider": "ms-vscode.cmake-tools" 30 | } 31 | ], 32 | "version": 4 33 | } -------------------------------------------------------------------------------- /gemcutter/Rendering/Mesh.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Mesh.h" 3 | 4 | namespace gem 5 | { 6 | Mesh::Mesh(Entity& _owner) 7 | : Renderable(_owner) 8 | { 9 | } 10 | 11 | Mesh::Mesh(Entity& _owner, Model::Ptr _model) 12 | : Renderable(_owner) 13 | { 14 | SetModel(std::move(_model)); 15 | } 16 | 17 | Mesh::Mesh(Entity& _owner, Model::Ptr _model, Material::Ptr material) 18 | : Renderable(_owner, std::move(material)) 19 | { 20 | SetModel(std::move(_model)); 21 | } 22 | 23 | Mesh::Mesh(Entity& _owner, Material::Ptr material) 24 | : Renderable(_owner, std::move(material)) 25 | { 26 | } 27 | 28 | Mesh::Mesh(Entity& _owner, VertexArray::Ptr _array) 29 | : Renderable(_owner, std::move(_array)) 30 | { 31 | } 32 | 33 | Mesh::Mesh(Entity& _owner, VertexArray::Ptr _array, Material::Ptr material) 34 | : Renderable(_owner, std::move(_array), std::move(material)) 35 | { 36 | } 37 | 38 | void Mesh::SetModel(Model::Ptr _model) 39 | { 40 | model = std::move(_model); 41 | array = model->GetArray(); 42 | } 43 | 44 | Model* Mesh::GetModel() const 45 | { 46 | return model.get(); 47 | } 48 | } 49 | 50 | REFLECT_COMPONENT(gem::Mesh, gem::Renderable) 51 | MEMBERS { 52 | REF_PRIVATE_MEMBER_EX(model, nullptr, &gem::Mesh::SetModel) 53 | } 54 | REF_END; 55 | -------------------------------------------------------------------------------- /gemcutter/Sound/SoundListener.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "SoundListener.h" 3 | #include "gemcutter/Sound/SoundSystem.h" 4 | 5 | namespace gem 6 | { 7 | Entity::WeakPtr SoundListener::listener; 8 | 9 | SoundListener::SoundListener(Entity& _owner) 10 | : Component(_owner) 11 | { 12 | if (!IsEnabled()) 13 | { 14 | // Do not change any existing state. 15 | return; 16 | } 17 | 18 | OnEnable(); 19 | } 20 | 21 | Entity::Ptr SoundListener::GetListener() 22 | { 23 | return listener.lock(); 24 | } 25 | 26 | void SoundListener::OnDisable() 27 | { 28 | Entity::Ptr listenerEntity = GetListener(); 29 | 30 | // If we are in fact the currently active listener, we can be disabled. 31 | if (listenerEntity == owner) 32 | { 33 | listener.reset(); 34 | SoundSystem.Mute(); 35 | } 36 | } 37 | 38 | void SoundListener::OnEnable() 39 | { 40 | // If there is already an active listener, we must disable it 41 | // because we only support one active listener at a time. 42 | if (Entity::Ptr listenerEntity = GetListener()) 43 | { 44 | listenerEntity->Disable(); 45 | } 46 | 47 | listener = owner.GetWeakPtr(); 48 | SoundSystem.Unmute(); 49 | } 50 | } 51 | 52 | REFLECT_COMPONENT(gem::SoundListener, gem::ComponentBase) REF_END; 53 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Light.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | #include "gemcutter/Math/Vector.h" 5 | #include "gemcutter/Resource/UniformBuffer.h" 6 | 7 | namespace gem 8 | { 9 | // Defaults to a point light. 10 | class Light : public Component 11 | { 12 | public: 13 | enum class Type : uint16_t 14 | { 15 | Point, 16 | Directional, 17 | Spot 18 | }; 19 | 20 | Light(Entity& owner); 21 | Light(Entity& owner, const vec3& color); 22 | Light(Entity& owner, const vec3& color, Type type); 23 | 24 | Type type = Type::Point; 25 | // Defaults to a white light. 26 | UniformHandle color; 27 | // Defaults to zero. 28 | UniformHandle attenuationLinear; 29 | // Defaults to one. 30 | UniformHandle attenuationQuadratic; 31 | // Cone angle for spotlights, in degrees. 32 | float angle = 25.0f; 33 | 34 | // Keeps the internal buffer up to date with the light's transform. 35 | void Update(); 36 | 37 | UniformBuffer::Ptr& GetBuffer(); 38 | 39 | private: 40 | void CreateUniformBuffer(); 41 | 42 | UniformHandle position; 43 | UniformHandle direction; 44 | UniformHandle cosAngle; 45 | UniformHandle typeHandle; 46 | UniformBuffer::Ptr lightBuffer; 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /docs/Entity.md: -------------------------------------------------------------------------------- 1 | # Dynamic Queries 2 | A query will only return active Components and Entities. Any Component or Tag can be part of a query. 3 | 4 | Queries are implemented in a LINQ-style fashion. This means that they are very efficient (zero dynamic allocations), 5 | but also that Components/Tags of the queried types should not be created or deleted during the loop. 6 | 7 | # Examples 8 | ```cpp 9 | class Player : public Component { /**/ }; 10 | class Network : public Component { /**/ }; 11 | class Friendly : public Tag {}; 12 | class Enemy : public Tag {}; 13 | class SquadLeader : public Tag {}; 14 | 15 | // Process all Entities with a Player Component. 16 | for (Entity& e : With()) 17 | { 18 | //... 19 | } 20 | 21 | // Using All<>() processes the Component directly. 22 | // This avoids the cost of calling Entity.Get<>(). 23 | for (Player& p : All()) 24 | { 25 | //... 26 | } 27 | 28 | // Process all enemy players. 29 | for (Entity& e : With()) 30 | { 31 | //... 32 | } 33 | 34 | // Process all friendly players. 35 | for (Entity& e : With()) 36 | { 37 | //... 38 | } 39 | 40 | // Process all friendly SquadLeaders on the network. 41 | for (Entity& e : With()) 42 | { 43 | //... 44 | } 45 | ``` -------------------------------------------------------------------------------- /gemcutter/Entity/Name.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Name.h" 3 | #include "gemcutter/Entity/Hierarchy.h" 4 | 5 | namespace gem 6 | { 7 | Entity* FindChild(const Entity& root, std::string_view name) 8 | { 9 | if (name.empty()) 10 | { 11 | return nullptr; 12 | } 13 | 14 | auto& hierarchy = root.Get(); 15 | 16 | for (auto& child : hierarchy.GetChildren()) 17 | { 18 | auto* nameComp = child->Try(); 19 | if (nameComp && nameComp->name == name) 20 | { 21 | return child.get(); 22 | } 23 | } 24 | 25 | for (auto& child : hierarchy.GetChildren()) 26 | { 27 | if (auto* result = FindChild(*child, name)) 28 | { 29 | return result; 30 | } 31 | } 32 | 33 | return nullptr; 34 | } 35 | 36 | Entity* FindEntity(std::string_view name) 37 | { 38 | if (name.empty()) 39 | { 40 | return nullptr; 41 | } 42 | 43 | for (auto& comp : All()) 44 | { 45 | if (comp.name == name) 46 | { 47 | return &comp.owner; 48 | } 49 | } 50 | 51 | return nullptr; 52 | } 53 | 54 | Name::Name(Entity& _owner) 55 | : Component(_owner) 56 | { 57 | } 58 | 59 | Name::Name(Entity& _owner, std::string _name) 60 | : Component(_owner) 61 | , name(std::move(_name)) 62 | { 63 | } 64 | } 65 | 66 | REFLECT_COMPONENT(gem::Name, gem::ComponentBase) 67 | MEMBERS { 68 | REF_MEMBER(name) 69 | } 70 | REF_END; 71 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Sprite.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Rendering/Renderable.h" 4 | 5 | namespace gem 6 | { 7 | enum class Alignment : uint16_t 8 | { 9 | BottomLeft, 10 | BottomCenter, 11 | LeftCenter, 12 | Center 13 | }; 14 | 15 | // Causes an entity to render as a 2D textured sprite. 16 | // The desired textures should be set on the Material component. 17 | // Alignment and BillBoard options are supported by the default sprite shader. 18 | class Sprite : public Renderable 19 | { 20 | public: 21 | Sprite(Entity& owner); 22 | Sprite(Entity& owner, Material::Ptr material); 23 | Sprite(Entity& owner, Alignment pivot); 24 | Sprite(Entity& owner, Alignment pivot, bool billBoard, Material::Ptr material); 25 | 26 | // The sprite will render with the specified point on the entity's position. 27 | // Defines "GEM_SPRITE_CENTERED_X" and "GEM_SPRITE_CENTERED_Y" on the Material Component if needed. 28 | void SetAlignment(Alignment pivot); 29 | Alignment GetAlignment() const; 30 | 31 | // Causes the Sprite to face the camera. 32 | // Defines "GEM_SPRITE_BILLBOARD" on the Material Component if needed. 33 | void SetBillBoarded(bool state); 34 | bool GetBillBoarded() const; 35 | 36 | private: 37 | Alignment alignment = Alignment::BottomLeft; 38 | bool billBoarded = false; 39 | 40 | public: 41 | PRIVATE_MEMBER(Sprite, alignment); 42 | PRIVATE_MEMBER(Sprite, billBoarded); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /gemcutter/Math/Quaternion.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | 4 | namespace gem 5 | { 6 | struct vec3; 7 | struct mat3; 8 | 9 | struct quat 10 | { 11 | quat() = default; 12 | quat(float X, float Y, float Z, float W); 13 | quat(const vec3& right, const vec3& up, const vec3& forward); 14 | explicit quat(const mat3& rotation); 15 | 16 | bool operator==(const quat&) const; 17 | bool operator!=(const quat&) const; 18 | 19 | quat operator*(const quat&) const; 20 | vec3 operator*(const vec3&) const; 21 | quat& operator*=(const quat&); 22 | 23 | float operator[](unsigned index) const; 24 | float& operator[](unsigned index); 25 | 26 | void SetIdentity(); 27 | 28 | void FromEuler(const vec3& degrees); 29 | vec3 GetEuler() const; 30 | 31 | void Conjugate(); 32 | quat GetConjugate() const; 33 | 34 | void Normalize(); 35 | quat GetNormalized() const; 36 | 37 | vec3 GetRight() const; 38 | vec3 GetUp() const; 39 | vec3 GetForward() const; 40 | 41 | void Rotate(const vec3& axis, float degrees); 42 | void Rotate(float X, float Y, float Z, float degrees); 43 | void RotateX(float degrees); 44 | void RotateY(float degrees); 45 | void RotateZ(float degrees); 46 | 47 | static const quat Identity; 48 | 49 | float x = 0.0f; 50 | float y = 0.0f; 51 | float z = 0.0f; 52 | float w = 1.0f; 53 | }; 54 | 55 | [[nodiscard]] float Dot(const quat& p0, const quat& p1); 56 | [[nodiscard]] quat Slerp(const quat& p0, const quat& p1, float percent); 57 | } 58 | -------------------------------------------------------------------------------- /gemcutter/AI/ProbabilityMatrix.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | 4 | namespace gem 5 | { 6 | // Stores and manages a probability matrix of States vs Actions. 7 | class ProbabilityMatrix 8 | { 9 | public: 10 | ProbabilityMatrix(unsigned numStates, unsigned numActions); 11 | ProbabilityMatrix(const ProbabilityMatrix&); 12 | ProbabilityMatrix(ProbabilityMatrix&&) noexcept; 13 | ~ProbabilityMatrix(); 14 | 15 | ProbabilityMatrix& operator=(const ProbabilityMatrix&); 16 | ProbabilityMatrix& operator=(ProbabilityMatrix&&) noexcept; 17 | 18 | // Re-initialize the matrix with uniform probabilities. 19 | void Reset(); 20 | 21 | // Based on the given state, returns a random action with respect to the probabilities. 22 | int QueryAction(unsigned state) const; 23 | 24 | // Reinforces (positively or negatively) an action by the scalar, percentage. 25 | void ReinforceScale(unsigned state, unsigned action, float percentage); 26 | 27 | // Reinforces (positively or negatively) an action by the additive, value. 28 | void ReinforceLinear(unsigned state, unsigned action, float value); 29 | 30 | // Returns the probability of taking an action from a given state. 31 | float GetValue(unsigned state, unsigned action) const; 32 | int GetNumStates() const; 33 | int GetNumActions() const; 34 | 35 | private: 36 | unsigned numStates = 0; 37 | unsigned numActions = 0; 38 | float* data = nullptr; 39 | 40 | void Normalize(); 41 | void SetValue(unsigned state, unsigned action, float value); 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /tools/AssetManager/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AssetManager")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AssetManager")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6099e7ad-99a7-4eb4-b3c7-d35ee1184ba5")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /tools/AssetManager/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | using System; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Windows.Forms; 6 | 7 | namespace AssetManager 8 | { 9 | static class Program 10 | { 11 | [DllImport("kernel32")] 12 | private static extern bool AttachConsole(int dwProcessId); 13 | const int ATTACH_PARENT_PROCESS = -1; 14 | 15 | [STAThread] 16 | static void Main() 17 | { 18 | Application.EnableVisualStyles(); 19 | Application.SetCompatibleTextRenderingDefault(false); 20 | var builder = new Manager(); 21 | 22 | // If we have arguments, process them then exit. 23 | if (Environment.GetCommandLineArgs().Length > 1) 24 | { 25 | if (!AttachConsole(ATTACH_PARENT_PROCESS)) 26 | throw new SystemException("Could not link output to the calling console window."); 27 | 28 | if (Environment.GetCommandLineArgs().Contains("--update", StringComparer.InvariantCultureIgnoreCase)) 29 | { 30 | if (!builder.UpdateWorkspace()) 31 | { 32 | Environment.ExitCode = 1; 33 | return; 34 | } 35 | } 36 | 37 | if (Environment.GetCommandLineArgs().Contains("--pack", StringComparer.InvariantCultureIgnoreCase)) 38 | { 39 | if (!builder.PackWorkspace()) 40 | Environment.ExitCode = 1; 41 | 42 | builder.SaveFileCache(); 43 | } 44 | } 45 | else 46 | { 47 | // Otherwise we can start the UI normally and route output to the visual textBox. 48 | Console.SetOut(new RichTextBoxStreamWriter(builder.GetOutput())); 49 | 50 | Application.Run(builder); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gemcutter/Resource/ParticleBuffer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Resource/VertexArray.h" 4 | #include "gemcutter/Utilities/EnumFlags.h" 5 | 6 | namespace gem 7 | { 8 | struct vec2; 9 | struct vec3; 10 | 11 | enum class ParticleAttributes : uint16_t 12 | { 13 | None = 0, 14 | Size = 1, // vec2 15 | Color = 2, // vec3 16 | Alpha = 4, // float 17 | Rotation = 8, // float 18 | AgeRatio = 16 // float (age / lifetime) 19 | }; 20 | 21 | class ParticleBuffer 22 | { 23 | public: 24 | ParticleBuffer(unsigned maxParticles); 25 | ~ParticleBuffer(); 26 | 27 | void Unload(); 28 | 29 | void SetAttributes(EnumFlags attributes); 30 | // Uploads data to the GPU buffers. 31 | void Update(unsigned activeParticles); 32 | 33 | void Kill(unsigned index, unsigned last); 34 | bool IsAlive(unsigned index) const; 35 | 36 | EnumFlags GetAttributes() const; 37 | VertexArray::Ptr GetArray() const; 38 | unsigned GetMaxParticles() const; 39 | 40 | vec3* positions = nullptr; 41 | vec3* velocities = nullptr; 42 | float* ages = nullptr; 43 | float* lifetimes = nullptr; 44 | vec2* sizes = nullptr; 45 | vec3* colors = nullptr; 46 | float* alphas = nullptr; 47 | float* rotations = nullptr; 48 | float* ageRatios = nullptr; 49 | 50 | private: 51 | unsigned TotalBufferSize() const; 52 | 53 | EnumFlags attributes = ParticleAttributes::None; 54 | unsigned maxParticles = 0; 55 | 56 | VertexArray::Ptr array; 57 | VertexBuffer::Ptr buffer; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /gemcutter/Utilities/Identifier.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #pragma once 3 | #include "Gemcutter/Application/Logging.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace gem 9 | { 10 | // A base class for strongly-typed identifiers. 11 | // The intended usage is to derive from this structure: 12 | // 13 | // struct CustomId : public Identifier {}; 14 | // CustomId id = GenerateUniqueId(); 15 | // 16 | // Please note that you will need to specialize std::hash<> 17 | // if you wish to use your identifiers as keys in containers. 18 | template 19 | struct Identifier 20 | { 21 | static_assert(std::is_integral_v); 22 | using ValueType = T; 23 | 24 | ValueType GetValue() const { return value; } 25 | void Reset() { value = invalid; } 26 | 27 | [[nodiscard]] bool IsValid() const { return value != invalid; } 28 | [[nodiscard]] bool operator==(const Identifier&) const = default; 29 | [[nodiscard]] bool operator!=(const Identifier&) const = default; 30 | 31 | static constexpr ValueType invalid = std::numeric_limits::min(); 32 | 33 | private: 34 | template friend IdType GenerateUniqueId(); 35 | ValueType value = invalid; 36 | }; 37 | 38 | template 39 | IdType GenerateUniqueId() 40 | { 41 | constinit static IdType::ValueType counter = IdType::invalid; 42 | 43 | ASSERT(counter != std::numeric_limits::max(), 44 | "The underlying identifier type ran out of unique values to generate."); 45 | 46 | IdType Id; 47 | Id.value = ++counter; 48 | return Id; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /gemcutter/GUI/Button.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Application/Delegate.h" 4 | #include "gemcutter/GUI/Widget.h" 5 | #include "gemcutter/Resource/Sound.h" 6 | #include "gemcutter/Resource/Texture.h" 7 | #include "gemcutter/Resource/Font.h" 8 | 9 | #include 10 | 11 | namespace gem 12 | { 13 | class Image; 14 | class Label; 15 | 16 | // An interactable button with a label and automatically updating background image. 17 | class Button : public Widget 18 | { 19 | public: 20 | Button(Entity& owner); 21 | Button(Entity& owner, Font::Ptr font, std::string_view text); 22 | Button(Entity& owner, Texture::Ptr idle, Texture::Ptr hover, Texture::Ptr pressed); 23 | Button(Entity& owner, Font::Ptr font, std::string_view text, Texture::Ptr idle, Texture::Ptr hover, Texture::Ptr pressed); 24 | 25 | enum class State : uint16_t 26 | { 27 | Idle, 28 | Hover, 29 | Pressed 30 | }; 31 | 32 | void Update(); 33 | 34 | Image& GetImage(); 35 | Label& GetLabel(); 36 | const Image& GetImage() const; 37 | const Label& GetLabel() const; 38 | State GetState() const; 39 | 40 | Sound::Ptr pressedSound; 41 | Texture::Ptr idleTexture; 42 | Texture::Ptr hoverTexture; 43 | Texture::Ptr pressedTexture; 44 | 45 | Dispatcher onClick; 46 | 47 | private: 48 | void SetState(State); 49 | void SetTexture(State); 50 | 51 | void OnDisable() override; 52 | void OnEnable() override; 53 | 54 | State state = State::Idle; 55 | Image* image = nullptr; 56 | Label* label = nullptr; 57 | 58 | public: 59 | PRIVATE_MEMBER(Button, state); 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /gemcutter/Resource/Shareable.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | namespace gem 6 | { 7 | template 8 | class Shareable : public std::enable_shared_from_this 9 | { 10 | // Hide these functions from user code. 11 | using std::enable_shared_from_this::shared_from_this; 12 | using std::enable_shared_from_this::weak_from_this; 13 | 14 | protected: 15 | // A helper allowing private constructors to participate in the Shareable pattern. 16 | struct ShareableAlloc : public Derived 17 | { 18 | template 19 | ShareableAlloc(Args&&... args) : Derived(std::forward(args)...) {} 20 | }; 21 | 22 | public: 23 | using Ptr = std::shared_ptr; 24 | using ConstPtr = std::shared_ptr; 25 | using WeakPtr = std::weak_ptr; 26 | using ConstWeakPtr = std::weak_ptr; 27 | 28 | Ptr GetPtr() 29 | { 30 | return shared_from_this(); 31 | } 32 | 33 | ConstPtr GetPtr() const 34 | { 35 | return shared_from_this(); 36 | } 37 | 38 | WeakPtr GetWeakPtr() 39 | { 40 | return weak_from_this(); 41 | } 42 | 43 | ConstWeakPtr GetWeakPtr() const 44 | { 45 | return weak_from_this(); 46 | } 47 | 48 | template 49 | static Ptr MakeNew(Args&&... params) 50 | { 51 | // If you have a compile error because the Derived class has an inaccessible private constructor, 52 | // declare "friend ShareableAlloc;" and MakeNew() will still be able to instantiate it. 53 | return std::make_shared(std::forward(params)...); 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /gemcutter/Math/Transform.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Transform.h" 3 | #include "gemcutter/Application/Logging.h" 4 | #include "gemcutter/Math/Math.h" 5 | 6 | #include 7 | 8 | namespace gem 9 | { 10 | Transform::Transform(const vec3& _position) 11 | : position(_position) 12 | { 13 | } 14 | 15 | Transform::Transform(const vec3& _position, const quat& _rotation) 16 | : position(_position) 17 | , rotation(_rotation) 18 | { 19 | } 20 | 21 | Transform::Transform(const vec3& _position, const quat& _rotation, const vec3& _scale) 22 | : position(_position) 23 | , rotation(_rotation) 24 | , scale(_scale) 25 | { 26 | } 27 | 28 | void Transform::LookAt(const vec3& pos, const vec3& target, const vec3& up) 29 | { 30 | ASSERT(Equals(Length(up), 1.0f), "'up' must be a normalized vector."); 31 | ASSERT(pos != target, "Transform cannot look at itself."); 32 | 33 | vec3 forward = Normalize(target - pos); 34 | vec3 right = Normalize(Cross(forward, up)); 35 | 36 | rotation = quat(right, Cross(right, forward), -forward); 37 | position = pos; 38 | } 39 | 40 | void Transform::Rotate(const vec3& axis, float degrees) 41 | { 42 | rotation.Rotate(axis, degrees); 43 | } 44 | 45 | void Transform::RotateX(float degrees) 46 | { 47 | rotation.RotateX(degrees); 48 | } 49 | 50 | void Transform::RotateY(float degrees) 51 | { 52 | rotation.RotateY(degrees); 53 | } 54 | 55 | void Transform::RotateZ(float degrees) 56 | { 57 | rotation.RotateZ(degrees); 58 | } 59 | } 60 | 61 | REFLECT(gem::Transform) 62 | MEMBERS { 63 | REF_MEMBER(position) 64 | REF_MEMBER(rotation) 65 | REF_MEMBER(scale) 66 | } 67 | REF_END; 68 | -------------------------------------------------------------------------------- /gemcutter/Application/Event.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | namespace gem 3 | { 4 | template std::vector*> Event::listeners; 5 | 6 | template 7 | Listener::Listener() 8 | { 9 | EventObj::Subscribe(*this); 10 | } 11 | 12 | template 13 | Listener::Listener(std::function callback) 14 | : func(std::move(callback)) 15 | { 16 | EventObj::Subscribe(*this); 17 | } 18 | 19 | template 20 | Listener::~Listener() 21 | { 22 | EventObj::Unsubscribe(*this); 23 | } 24 | 25 | template 26 | Listener& Listener::operator=(std::function callback) 27 | { 28 | func = std::move(callback); 29 | return *this; 30 | } 31 | 32 | template 33 | bool Event::HasListeners() const 34 | { 35 | return HasListenersStatic(); 36 | } 37 | 38 | template 39 | bool Event::HasListenersStatic() 40 | { 41 | return !listeners.empty(); 42 | } 43 | 44 | template 45 | void Event::Raise() const 46 | { 47 | for (unsigned i = 0; i < listeners.size(); ++i) 48 | { 49 | if (listeners[i]->func) 50 | { 51 | listeners[i]->func(*static_cast(this)); 52 | } 53 | } 54 | } 55 | 56 | template 57 | void Event::Subscribe(Listener& listener) 58 | { 59 | listeners.push_back(&listener); 60 | } 61 | 62 | template 63 | void Event::Unsubscribe(Listener& listener) 64 | { 65 | listeners.erase(std::find(listeners.begin(), listeners.end(), &listener)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /gemcutter/GUI/Widget.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | #include "gemcutter/Entity/Hierarchy.h" 5 | #include "gemcutter/GUI/Rectangle.h" 6 | 7 | namespace gem 8 | { 9 | struct Edge 10 | { 11 | // Percentage of total distance from a parent edge. 12 | float anchor = 0.0f; 13 | // Flat offset from the anchor. 14 | float offset = 0.0f; 15 | }; 16 | 17 | // A generic UI element sized with respect to its parent widget. 18 | class Widget : public Component 19 | { 20 | public: 21 | Widget(Entity& owner); 22 | 23 | // Creates a new child widget of the given type. 24 | template 25 | T& CreateChild(Args&&... constructorParams); 26 | 27 | // Helper function which sets anchors to maintain the target size and position. 28 | // The given position should be relative to the bottom-left corner of the parent widget. 29 | // The parent widget's layout must be up to date. 30 | void SetTransform(vec2 position, vec2 size); 31 | 32 | // Sets the anchors to the full space available in the parent widget. 33 | // This is the default behaviour. 34 | void FitToParent(); 35 | 36 | // Returns the current world-space dimensions of the widget. 37 | const Rectangle& GetAbsoluteBounds() const; 38 | 39 | // Called when the bounds of the widget are updated. 40 | // This is where specific widgets can adjust their content. 41 | virtual void UpdateContent() {} 42 | 43 | static void UpdateAll(); 44 | 45 | Edge top; 46 | Edge bottom; 47 | Edge left; 48 | Edge right; 49 | 50 | private: 51 | void UpdateBounds(const Widget& parent); 52 | 53 | Rectangle absoluteBounds; 54 | }; 55 | } 56 | 57 | #include "Widget.inl" 58 | -------------------------------------------------------------------------------- /resources/Shaders/Sprite.shader: -------------------------------------------------------------------------------- 1 | Attributes 2 | { 3 | vec4 a_vert : 0; 4 | vec2 a_uv : 1; 5 | } 6 | 7 | Uniforms 8 | { 9 | instance Material : 0 10 | { 11 | float AlphaCutOff = 0.5; 12 | } 13 | } 14 | 15 | Vertex 16 | { 17 | out vec2 texcoord; 18 | 19 | void main() 20 | { 21 | texcoord = a_uv; 22 | 23 | #if defined(GEM_SPRITE_BILLBOARD) 24 | // Get the position of the sprite. 25 | vec4 vertex = Gem_Model[3].xyzw; 26 | 27 | // Calculate vertex offset from the center. 28 | vec2 offset = texcoord; 29 | #if defined(GEM_SPRITE_CENTERED_X) 30 | offset.x -= 0.5; 31 | #endif 32 | #if defined(GEM_SPRITE_CENTERED_Y) 33 | offset.y -= 0.5; 34 | #endif 35 | 36 | // Extract the scale from the basis vectors. 37 | vec2 scale = vec2(length(Gem_Model[0].xyz), length(Gem_Model[1].xyz)); 38 | 39 | // Offset in view space to create perfect billboard. 40 | vertex = Gem_View * vertex; 41 | vertex.xy += offset * scale; 42 | 43 | // Project to screen. 44 | gl_Position = Gem_Proj * vertex; 45 | #else 46 | vec4 vertex = a_vert; 47 | 48 | // Offset in local-space. 49 | #if defined(GEM_SPRITE_CENTERED_X) 50 | vertex.x -= 0.5; 51 | #endif 52 | #if defined(GEM_SPRITE_CENTERED_Y) 53 | vertex.y -= 0.5; 54 | #endif 55 | 56 | // Transform and project to screen. 57 | gl_Position = Gem_MVP * vertex; 58 | #endif 59 | } 60 | } 61 | 62 | Samplers 63 | { 64 | sampler2D sTex : 0; 65 | } 66 | 67 | Fragment 68 | { 69 | in vec2 texcoord; 70 | 71 | out vec4 outColor; 72 | 73 | void main() 74 | { 75 | outColor = texture(sTex, texcoord).rgba; 76 | 77 | #if defined(GEM_CUTOUT) 78 | if (outColor.a <= Material.AlphaCutOff) 79 | { 80 | discard; 81 | } 82 | #endif 83 | } 84 | } -------------------------------------------------------------------------------- /tools/AssetManager/WorkspaceConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Xml.Serialization; 6 | 7 | namespace AssetManager 8 | { 9 | public class EncoderLink 10 | { 11 | public string extension; 12 | public string encoder; 13 | } 14 | 15 | // Manages the input and output of the workspace settings. 16 | public class WorkspaceConfig 17 | { 18 | public string outputDirectory = "../Assets"; 19 | public string excludedExtensions = ""; 20 | 21 | [System.NonSerialized()] 22 | private string[] _excludedExtensions = new string[0]; 23 | 24 | public List encoders = new List(); 25 | 26 | public bool IsExtensionExcluded(string extension) 27 | { 28 | return _excludedExtensions.Contains(extension); 29 | } 30 | 31 | public static WorkspaceConfig Load() 32 | { 33 | WorkspaceConfig config; 34 | 35 | try 36 | { 37 | using (var myFileStream = File.OpenRead("config.workspace")) 38 | { 39 | var mySerializer = new XmlSerializer(typeof(WorkspaceConfig)); 40 | config = (WorkspaceConfig)mySerializer.Deserialize(myFileStream); 41 | } 42 | 43 | config._excludedExtensions = config.excludedExtensions.Split(';'); 44 | } 45 | catch (System.Exception) 46 | { 47 | config = new WorkspaceConfig(); 48 | } 49 | 50 | return config; 51 | } 52 | 53 | public void Save() 54 | { 55 | if (File.Exists("config.workspace")) 56 | File.Delete("config.workspace"); 57 | 58 | var serializer = new XmlSerializer(typeof(WorkspaceConfig)); 59 | using (var stream = File.OpenWrite("config.workspace")) 60 | { 61 | serializer.Serialize(stream, this); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gemcutter/Application/CmdArgs.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | /* 6 | Assists in reading command line arguments, especially in the format: 7 | - 8 | 9 | For example, if the command line arguments are: 10 | -width 10 -name MyGame -enableDebug 1 -speed 10.2 11 | 12 | Then we could retrieve these values with: 13 | GetCommandLineArg("-width", &int) 14 | GetCommandLineArg("-name", &string) 15 | GetCommandLineArg("-enableDebug", &bool) 16 | GetCommandLineArg("-speed", &float) 17 | */ 18 | 19 | namespace gem 20 | { 21 | int GetArgc(); 22 | char** GetArgv(); 23 | 24 | // Returns true if the command line argument with the given name exists. 25 | bool HasCommandLineArg(const char* name); 26 | // Returns the index of an argument or -1 if the argument is not found. 27 | int FindCommandLineArg(const char* name); 28 | // Returns the directory in which the program's executable was run. 29 | const char* GetExecutablePath(); 30 | 31 | // Gets the argument at the specified index. 32 | bool GetCommandLineArg(int index, bool& value); 33 | bool GetCommandLineArg(int index, double& value); 34 | bool GetCommandLineArg(int index, float& value); 35 | bool GetCommandLineArg(int index, int& value); 36 | bool GetCommandLineArg(int index, unsigned& value); 37 | bool GetCommandLineArg(int index, char& value); 38 | bool GetCommandLineArg(int index, const char*& value); 39 | bool GetCommandLineArg(int index, std::string& value); 40 | 41 | // Gets the argument value after the specified name. 42 | template 43 | bool GetCommandLineArg(const char* name, T& value) 44 | { 45 | int index = FindCommandLineArg(name); 46 | if (index == -1) 47 | { 48 | return false; 49 | } 50 | 51 | return GetCommandLineArg(index + 1, value); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gemcutter/GUI/Image.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #include "Image.h" 3 | #include "gemcutter/Rendering/Sprite.h" 4 | 5 | namespace gem 6 | { 7 | Image::Image(Entity& _owner) 8 | : Widget(_owner) 9 | { 10 | auto material = Material::MakeNew(); 11 | material->depthMode = DepthFunc::None; 12 | material->shader = Load("Engine/Shaders/Sprite"); 13 | 14 | sprite = &owner.Require(); 15 | sprite->SetAlignment(Alignment::Center); 16 | sprite->SetMaterial(std::move(material)); 17 | } 18 | 19 | Image::Image(Entity& _owner, Texture::Ptr texture) 20 | : Image(_owner) 21 | { 22 | sprite->GetMaterial().textures.Add(std::move(texture)); 23 | } 24 | 25 | Image::Image(Entity& _owner, Material::Ptr material) 26 | : Widget(_owner) 27 | { 28 | sprite = &owner.Require(); 29 | sprite->SetAlignment(Alignment::Center); 30 | sprite->SetMaterial(std::move(material)); 31 | } 32 | 33 | Sprite& Image::GetSprite() 34 | { 35 | return *sprite; 36 | } 37 | 38 | const Sprite& Image::GetSprite() const 39 | { 40 | return *sprite; 41 | } 42 | 43 | void Image::SetTexture(Texture::Ptr texture) 44 | { 45 | sprite->GetMaterial().textures.Add(std::move(texture)); 46 | } 47 | 48 | Texture::Ptr Image::GetTexture() const 49 | { 50 | std::span textureSlots = sprite->GetMaterial().textures.GetAll(); 51 | 52 | return textureSlots.empty() ? nullptr : textureSlots[0].tex; 53 | } 54 | 55 | void Image::SetMaterial(Material::Ptr material) 56 | { 57 | sprite->SetMaterial(std::move(material)); 58 | } 59 | 60 | Material& Image::GetMaterial() const 61 | { 62 | return sprite->GetMaterial(); 63 | } 64 | 65 | void Image::UpdateContent() 66 | { 67 | const Rectangle& bounds = GetAbsoluteBounds(); 68 | 69 | owner.scale.x = bounds.width; 70 | owner.scale.y = bounds.height; 71 | } 72 | } 73 | 74 | REFLECT_COMPONENT(gem::Image, gem::Widget) REF_END; 75 | -------------------------------------------------------------------------------- /gemcutter/Resource/ParticleFunctor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "ParticleFunctor.h" 3 | #include "gemcutter/Application/Logging.h" 4 | #include "gemcutter/Rendering/ParticleEmitter.h" 5 | 6 | namespace gem 7 | { 8 | void ParticleFunctor::Init(ParticleBuffer&, ParticleEmitter&, unsigned, unsigned) 9 | { 10 | // This function is defined here in order to avoid unused-variable warnings. 11 | } 12 | 13 | FunctorList::FunctorList(const FunctorList& other) 14 | : functors(other.functors) 15 | { 16 | } 17 | 18 | FunctorList& FunctorList::operator=(const FunctorList& other) 19 | { 20 | functors = other.functors; 21 | dirty = true; 22 | 23 | return *this; 24 | } 25 | 26 | void FunctorList::Add(std::shared_ptr functor) 27 | { 28 | functors.push_back(std::move(functor)); 29 | dirty = true; 30 | } 31 | 32 | void FunctorList::Clear() 33 | { 34 | functors.clear(); 35 | } 36 | 37 | RotationFunc::RotationFunc(float _rotationSpeed, Range _initialRotation) 38 | : rotationSpeed(_rotationSpeed) 39 | , initialRotation(_initialRotation) 40 | { 41 | } 42 | 43 | void RotationFunc::Init(ParticleBuffer& particles, ParticleEmitter&, unsigned startIndex, unsigned count) 44 | { 45 | ASSERT(startIndex + count <= particles.GetMaxParticles(), "Indices out of range."); 46 | 47 | for (unsigned i = startIndex, end = startIndex + count; i < end; ++i) 48 | { 49 | particles.rotations[i] = initialRotation.Random(); 50 | } 51 | } 52 | 53 | void RotationFunc::Update(ParticleBuffer& particles, ParticleEmitter& emitter, float deltaTime) 54 | { 55 | float rotation = rotationSpeed * deltaTime; 56 | for (unsigned i = 0; i < emitter.GetNumAliveParticles(); ++i) 57 | { 58 | particles.rotations[i] += rotation; 59 | } 60 | } 61 | 62 | ParticleAttributes RotationFunc::GetRequirements() const 63 | { 64 | return ParticleAttributes::Rotation; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gemcutter/Utilities/EnumFlags.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | namespace gem 6 | { 7 | // Allows you to easily use a strongly-typed enum as if it was a bit-field. 8 | template 9 | class EnumFlags 10 | { 11 | public: 12 | using PrimitiveType = std::underlying_type_t; 13 | 14 | EnumFlags() : value(PrimitiveType{}) {} 15 | EnumFlags(Enumeration val) : value(static_cast(val)) {} 16 | EnumFlags(PrimitiveType val) : value(val) {} 17 | 18 | void Clear() 19 | { 20 | value = PrimitiveType{}; 21 | } 22 | 23 | [[nodiscard]] 24 | PrimitiveType Value() const 25 | { 26 | return value; 27 | } 28 | 29 | [[nodiscard]] 30 | bool Has(EnumFlags mask) const 31 | { 32 | return (value & mask.value) != PrimitiveType{}; 33 | } 34 | 35 | [[nodiscard]] 36 | bool operator==(EnumFlags other) const 37 | { 38 | return value == other.value; 39 | } 40 | 41 | [[nodiscard]] 42 | bool operator!=(EnumFlags other) const 43 | { 44 | return value != other.value; 45 | } 46 | 47 | EnumFlags& operator|=(EnumFlags other) 48 | { 49 | value = value | other.value; 50 | return *this; 51 | } 52 | 53 | EnumFlags& operator&=(EnumFlags other) 54 | { 55 | value = value & other.value; 56 | return *this; 57 | } 58 | 59 | [[nodiscard]] 60 | EnumFlags operator|(EnumFlags other) const 61 | { 62 | return value | other.value; 63 | } 64 | 65 | [[nodiscard]] 66 | EnumFlags operator&(EnumFlags other) const 67 | { 68 | return value & other.value; 69 | } 70 | 71 | [[nodiscard]] 72 | explicit operator Enumeration() const 73 | { 74 | return static_cast(value); 75 | } 76 | 77 | [[nodiscard]] 78 | explicit operator PrimitiveType() const 79 | { 80 | return value; 81 | } 82 | 83 | private: 84 | PrimitiveType value; 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /gemcutter/Resource/ConfigTable.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gem 9 | { 10 | class ConfigTable 11 | { 12 | public: 13 | bool Load(std::string_view file); 14 | bool Save(std::string_view file) const; 15 | 16 | bool operator==(const ConfigTable&) const; 17 | bool operator!=(const ConfigTable&) const; 18 | 19 | // Returns true if the setting has a value in the table. 20 | bool HasSetting(std::string_view setting) const; 21 | // Returns the number of entries in the table. 22 | std::size_t GetSize() const; 23 | 24 | std::string GetString(std::string_view setting) const; 25 | float GetFloat(std::string_view setting) const; 26 | int GetInt(std::string_view setting) const; 27 | bool GetBool(std::string_view setting) const; 28 | 29 | std::vector GetStringArray(std::string_view setting) const; 30 | std::vector GetFloatArray(std::string_view setting) const; 31 | std::vector GetIntArray(std::string_view setting) const; 32 | std::vector GetBoolArray(std::string_view setting) const; 33 | 34 | // Creates or overwrites an existing setting. 35 | void SetString(const std::string& setting, std::string_view value); 36 | void SetFloat(const std::string& setting, float value); 37 | void SetInt(const std::string& setting, int value); 38 | void SetBool(const std::string& setting, bool value); 39 | 40 | // Does not overwrite an existing setting, but creates it if it doesn't exist. 41 | void SetDefaultString(const std::string& setting, std::string_view value); 42 | void SetDefaultFloat(const std::string& setting, float value); 43 | void SetDefaultInt(const std::string& setting, int value); 44 | void SetDefaultBool(const std::string& setting, bool value); 45 | 46 | private: 47 | std::map> settings; 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /gemcutter/Utilities/StdExt.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gem 9 | { 10 | // Enables a standard container to compare lightweight strings 11 | // directly to internal keys of type std::string. 12 | struct string_hash 13 | { 14 | using is_transparent = void; 15 | using transparent_key_equal = std::equal_to<>; 16 | using hash_type = std::hash; 17 | 18 | size_t operator()(std::string_view text) const { return hash_type{}(text); } 19 | size_t operator()(const std::string& text) const { return hash_type{}(text); } 20 | size_t operator()(const char* text) const { return hash_type{}(text); } 21 | }; 22 | 23 | // Returns true if the pointer is explicitly null or default-initialized. 24 | template [[nodiscard]] 25 | bool IsPtrNull(const std::weak_ptr& ptr) 26 | { 27 | std::weak_ptr empty; 28 | return !ptr.owner_before(empty) && !empty.owner_before(ptr); 29 | } 30 | 31 | // Helper for visiting all the alternatives in a variant with lambdas. 32 | template 33 | auto Visit(Variant&& variant, Visitors&&... visitors) 34 | { 35 | struct Overload : public Visitors... 36 | { 37 | using Visitors::operator()...; 38 | }; 39 | 40 | return std::visit(Overload { std::forward(visitors)... }, std::forward(variant)); 41 | } 42 | 43 | template [[nodiscard]] 44 | bool Contains(const Container& container, const Item& item) 45 | { 46 | if constexpr ( requires(Container& C, Item& I) { { C.contains(I) } -> std::same_as; } ) 47 | { 48 | return container.contains(item); 49 | } 50 | else 51 | { 52 | auto&& begin = std::begin(container); 53 | auto&& end = std::end(container); 54 | return std::find(begin, end, item) != end; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | set(CMAKE_SKIP_INSTALL_RULES TRUE) 4 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 5 | 6 | project(Gemcutter LANGUAGES C CXX) 7 | 8 | find_package(Git REQUIRED) 9 | find_package(OpenGL REQUIRED) 10 | 11 | # Ensures that a git submodule is initialized and updated. 12 | function(update_submodule submodule_dir) 13 | if (NOT EXISTS "${submodule_dir}/.git") 14 | execute_process( 15 | COMMAND "${GIT_EXECUTABLE}" submodule update --init --recursive "${submodule_dir}/" 16 | WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" 17 | RESULT_VARIABLE error_code_submodule 18 | ) 19 | if (error_code_submodule) 20 | message(FATAL_ERROR "Failed to update submodule: ${submodule_dir}") 21 | else() 22 | message(STATUS "Submodule updated: ${submodule_dir}") 23 | endif() 24 | endif() 25 | endfunction() 26 | 27 | if (MSVC) 28 | option(MSVC_MULTIPROCESSOR_COMPILATION "Whether compilation can use multiple CPU cores (/MP)." ON) 29 | if (MSVC_MULTIPROCESSOR_COMPILATION) 30 | add_compile_options(/MP) 31 | endif() 32 | endif() 33 | 34 | option(ENABLE_IMGUI_DEV_SUPPORT "Build and use ImGui in development configurations." ON) 35 | option(ENABLE_IMGUI_SUPPORT "Build and use ImGui in all configurations." OFF) 36 | 37 | # Load Scaffold CMake utilities. 38 | update_submodule("${CMAKE_CURRENT_LIST_DIR}/external/scaffold") 39 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/external/scaffold") 40 | include(scaffold) 41 | sf_is_project_top_level(IS_TOP_LEVEL) 42 | 43 | add_subdirectory(external) 44 | add_subdirectory(gemcutter) 45 | add_subdirectory(tools) 46 | 47 | option(ENABLE_TESTS "Build unit tests." ${IS_TOP_LEVEL}) 48 | if (ENABLE_TESTS) 49 | add_subdirectory(tests) 50 | endif() 51 | 52 | option(ENABLE_SAMPLES "Download and build sample projects." ${IS_TOP_LEVEL}) 53 | if (ENABLE_SAMPLES) 54 | update_submodule("${CMAKE_CURRENT_LIST_DIR}/samples") 55 | add_subdirectory(samples) 56 | endif() 57 | -------------------------------------------------------------------------------- /gemcutter/Application/Timer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace gem 8 | { 9 | class Timer 10 | { 11 | public: 12 | // Calls Reset(), immediately starting the internal clock. 13 | Timer(); 14 | 15 | // Returns true if performance counters are available on the system architecture. 16 | static bool IsSupported(); 17 | 18 | // Returns the resolution of the timer in ticks-per-millisecond. 19 | static int64_t GetTicksPerMS(); 20 | // Returns the resolution of the timer in ticks-per-second. 21 | static int64_t GetTicksPerSecond(); 22 | 23 | // Returns the current global tick count. Can be subtracted from a previous call 24 | // in order to calculate the amount of time that has passed. 25 | static int64_t GetCurrentTick(); 26 | 27 | // Sets the reference time for any other functions called in the future. 28 | void Reset(); 29 | 30 | // Returns the amount of time passed since the last Reset() in milliseconds. 31 | double GetElapsedMS() const; 32 | // Returns the amount of time passed since the last Reset() in seconds. 33 | double GetElapsedSeconds() const; 34 | 35 | // Returns true if the specified amount of milliseconds have elapsed since the last Reset(). 36 | bool IsElapsedMS(double ms) const; 37 | // Returns true if the specified amount of seconds have elapsed since the last Reset(). 38 | bool IsElapsedSeconds(double seconds) const; 39 | 40 | // Removes milliseconds from the running internal clock. 41 | void SubtractTimeMS(double ms); 42 | // Removes seconds from the running internal clock. 43 | void SubtractTimeSeconds(double seconds); 44 | 45 | // Adds milliseconds to the running internal clock. 46 | void AddTimeMS(double ms); 47 | // Adds seconds to the running internal clock. 48 | void AddTimeSeconds(double seconds); 49 | 50 | private: 51 | int64_t startTime; 52 | 53 | public: 54 | PRIVATE_MEMBER(Timer, startTime); 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Sprite.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Sprite.h" 3 | #include "gemcutter/Rendering/Primitives.h" 4 | 5 | namespace gem 6 | { 7 | Sprite::Sprite(Entity& _owner) 8 | : Renderable(_owner, Primitives.GetQuadArray()) 9 | { 10 | } 11 | 12 | Sprite::Sprite(Entity& _owner, Material::Ptr material) 13 | : Renderable(_owner, Primitives.GetQuadArray(), std::move(material)) 14 | { 15 | } 16 | 17 | Sprite::Sprite(Entity& _owner, Alignment pivot) 18 | : Renderable(_owner, Primitives.GetQuadArray()) 19 | { 20 | SetAlignment(pivot); 21 | } 22 | 23 | Sprite::Sprite(Entity& _owner, Alignment pivot, bool billBoard, Material::Ptr material) 24 | : Renderable(_owner, Primitives.GetQuadArray(), std::move(material)) 25 | { 26 | SetAlignment(pivot); 27 | SetBillBoarded(billBoard); 28 | } 29 | 30 | void Sprite::SetAlignment(Alignment pivot) 31 | { 32 | if (alignment == pivot) 33 | return; 34 | 35 | variants.Switch("GEM_SPRITE_CENTERED_X", pivot == Alignment::Center || pivot == Alignment::BottomCenter); 36 | variants.Switch("GEM_SPRITE_CENTERED_Y", pivot == Alignment::Center || pivot == Alignment::LeftCenter); 37 | 38 | alignment = pivot; 39 | } 40 | 41 | Alignment Sprite::GetAlignment() const 42 | { 43 | return alignment; 44 | } 45 | 46 | void Sprite::SetBillBoarded(bool state) 47 | { 48 | if (billBoarded == state) 49 | return; 50 | 51 | variants.Switch("GEM_SPRITE_BILLBOARD", state); 52 | 53 | billBoarded = state; 54 | } 55 | 56 | bool Sprite::GetBillBoarded() const 57 | { 58 | return billBoarded; 59 | } 60 | } 61 | 62 | REFLECT(gem::Alignment) 63 | ENUM_VALUES { 64 | REF_VALUE(BottomLeft) 65 | REF_VALUE(BottomCenter) 66 | REF_VALUE(LeftCenter) 67 | REF_VALUE(Center) 68 | } 69 | REF_END; 70 | 71 | REFLECT_COMPONENT(gem::Sprite, gem::Renderable) 72 | MEMBERS { 73 | REF_PRIVATE_MEMBER_EX(alignment, nullptr, &gem::Sprite::SetAlignment); 74 | REF_PRIVATE_MEMBER_EX(billBoarded, nullptr, &gem::Sprite::SetBillBoarded); 75 | } 76 | REF_END; 77 | -------------------------------------------------------------------------------- /gemcutter/Utilities/String.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | 7 | namespace gem 8 | { 9 | // Removes all whitespace characters from the start of the string. 10 | void TrimStart(std::string& str); 11 | 12 | // Removes all whitespace characters from the end of the string. 13 | void TrimEnd(std::string& str); 14 | 15 | // Replaces the first occurrence of "from" in the source string with "to". Case sensitive. 16 | void ReplaceFirst(std::string& str, std::string_view from, std::string_view to); 17 | 18 | // Replaces the last occurrence of "from" in the source string with "to". Case sensitive. 19 | void ReplaceLast(std::string& str, std::string_view from, std::string_view to); 20 | 21 | // Replaces all occurrences of "from" in the source string with "to". Case sensitive. 22 | void ReplaceAll(std::string& str, std::string_view from, std::string_view to); 23 | 24 | // Remove all instances of whitespace from the string. 25 | void RemoveWhitespace(std::string& str); 26 | 27 | // Removes duplicate or unnecessary whitespace from the string based on standard programming syntax rules. 28 | // For Example "int myVar, temp, *ptr ;" would become "int myVar, temp, *ptr;". 29 | void RemoveRedundantWhitespace(std::string& str); 30 | 31 | // Removes all instances of comments, "// ..." and "/* ... */", from the string. 32 | void RemoveComments(std::string& str); 33 | 34 | // Performs standard vsnprintf formatting on the argument list, up to 1024 characters. 35 | std::string FormatString(const char* format, ...); 36 | 37 | // Performs standard vsnprintf formatting on the argument list, up to 1024 characters. 38 | std::string FormatString(const char* format, va_list args); 39 | 40 | // Compares two strings for equality while ignoring capitalization. 41 | bool CompareLowercase(std::string_view, std::string_view); 42 | 43 | // Converts all uppercase letters in the string to lowercase. 44 | void ToLowercase(std::string& str); 45 | } 46 | -------------------------------------------------------------------------------- /tools/AssetManager/Settings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Windows.Forms; 5 | 6 | namespace AssetManager 7 | { 8 | public partial class Settings : Form 9 | { 10 | readonly List entries = new List(); 11 | 12 | readonly WorkspaceConfig config; 13 | 14 | public Settings(WorkspaceConfig config) 15 | { 16 | InitializeComponent(); 17 | panel.AutoScroll = true; 18 | 19 | this.config = config; 20 | TextBoxOutputDirectory.Text = config.outputDirectory; 21 | TextBoxExcludedExtensions.Text = config.excludedExtensions; 22 | foreach (var link in config.encoders) 23 | AddItem(link.extension, link.encoder); 24 | 25 | FormClosing += (_, e) => { 26 | config.encoders.Clear(); 27 | 28 | foreach (EncoderEntry entry in entries) 29 | config.encoders.Add(new EncoderLink { extension=entry.extension, encoder=entry.encoder }); 30 | 31 | config.Save(); 32 | }; 33 | } 34 | 35 | // Reorganizes the spacing of the list. 36 | private void RepositionList() 37 | { 38 | for (int i = 0; i < entries.Count; ++i) 39 | { 40 | entries[i].SetPosition(EncoderEntry.ItemHeight * i); 41 | } 42 | } 43 | 44 | private void AddItem(string ext = "", string app = "") 45 | { 46 | var entry = new EncoderEntry(panel, EncoderEntry.ItemHeight * entries.Count, ext, app); 47 | entries.Add(entry); 48 | 49 | entry.onDelete += (sender, e) => { 50 | entries.Remove(sender as EncoderEntry); 51 | RepositionList(); 52 | }; 53 | } 54 | 55 | private void buttonAdd_Click(object sender, EventArgs e) 56 | { 57 | AddItem(); 58 | } 59 | 60 | private void TextBoxOutputDirectory_TextChanged(object sender, EventArgs e) 61 | { 62 | config.outputDirectory = TextBoxOutputDirectory.Text; 63 | } 64 | 65 | private void TextBoxExcludedExtensions_TextChanged(object sender, EventArgs e) 66 | { 67 | config.excludedExtensions = TextBoxExcludedExtensions.Text; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /gemcutter/Application/Reflection.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Emilian Cioca 2 | namespace gem 3 | { 4 | template [[nodiscard]] 5 | const loupe::type& ReflectType() 6 | { 7 | if constexpr (std::is_same_v, class Entity>) 8 | { 9 | return *EntityTypeId; 10 | } 11 | else if constexpr (std::is_same_v, class ComponentBase>) 12 | { 13 | return *BaseComponentTypeId; 14 | } 15 | else if constexpr (std::is_same_v, class ResourceBase>) 16 | { 17 | return *BaseResourceTypeId; 18 | } 19 | else 20 | { 21 | static const loupe::type* descriptor = reflection_tables.find(); 22 | ASSERT(descriptor, "The type was not reflected."); 23 | 24 | return *descriptor; 25 | } 26 | } 27 | 28 | template [[nodiscard]] 29 | std::optional StringToEnum(std::string_view valueName) 30 | { 31 | static_assert(std::is_enum_v); 32 | 33 | static const loupe::type* descriptor = reflection_tables.find(); 34 | ASSERT(descriptor, "The enum type was not reflected."); 35 | 36 | const auto& enumeration = std::get(descriptor->data); 37 | const uint16_t* result = enumeration.find_value(valueName); 38 | 39 | return result ? std::optional{static_cast(*result)} : std::nullopt; 40 | } 41 | 42 | template [[nodiscard]] 43 | std::string_view EnumToString(Enum value) 44 | { 45 | static_assert(std::is_enum_v); 46 | 47 | static const loupe::type* descriptor = reflection_tables.find(); 48 | ASSERT(descriptor, "The enum type was not reflected."); 49 | 50 | const auto& enumeration = std::get(descriptor->data); 51 | const std::string_view* result = enumeration.find_name(static_cast(value)); 52 | ASSERT(result, "The enum value was not reflected."); 53 | 54 | return *result; 55 | } 56 | 57 | template [[nodiscard]] 58 | consteval std::string_view GetTypeName() 59 | { 60 | return loupe::get_type_name(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gemcutter/Resource/Font.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Resource/Resource.h" 4 | #include "gemcutter/Resource/Shareable.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace gem 11 | { 12 | struct CharData 13 | { 14 | int x; 15 | int y; 16 | }; 17 | 18 | // A typeface that can be used to render text. 19 | // To be rendered, this must be set on an Entity's Text component. 20 | class Font : public Resource, public Shareable 21 | { 22 | public: 23 | static constexpr std::string_view Extension = ".font"; 24 | 25 | ~Font(); 26 | 27 | // Loads pre-packed *.font resources. 28 | bool Load(std::string_view filePath); 29 | void Unload(); 30 | 31 | // Returns the real world unit width of the string. 32 | // If the string is multi-line, the length of the longest line is returned. 33 | int GetStringWidth(std::string_view text) const; 34 | int GetStringHeight() const; 35 | // Returns the width required to advance forward by a space. 36 | int GetSpaceWidth() const; 37 | 38 | std::span GetTextures() const; 39 | std::span GetDimensions() const; 40 | std::span GetPositions() const; 41 | std::span GetAdvances() const; 42 | std::span GetMasks() const; 43 | unsigned GetFontWidth() const; 44 | unsigned GetFontHeight() const; 45 | 46 | static unsigned GetVAO(); 47 | static unsigned GetVBO(); 48 | 49 | private: 50 | std::array textures; 51 | // Each character's dimensions. 52 | std::array dimensions; 53 | // The position of each character. 54 | std::array positions; 55 | // The distance from one character to the next. 56 | std::array advances; 57 | // Whether the character is included in the font. 58 | std::array masks; 59 | 60 | unsigned width = 0; 61 | unsigned height = 0; 62 | 63 | static unsigned VBO; 64 | static unsigned VAO; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /tools/AssetManager/RichTextBoxStreamWriter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.IO; 6 | using System.Text; 7 | using System.Windows.Forms; 8 | 9 | namespace AssetManager 10 | { 11 | // Redirects Console output to a textBox control while managing colors. 12 | public class RichTextBoxStreamWriter : TextWriter 13 | { 14 | public static ConsoleColor ForegroundColor = ConsoleColor.Gray; 15 | 16 | static readonly Dictionary colorMap = new Dictionary 17 | { 18 | { ConsoleColor.Black, Color.Black }, 19 | { ConsoleColor.DarkBlue, Color.DarkBlue }, 20 | { ConsoleColor.DarkGreen, Color.DarkGreen }, 21 | { ConsoleColor.DarkCyan, Color.DarkCyan }, 22 | { ConsoleColor.DarkRed, Color.DarkRed }, 23 | { ConsoleColor.DarkMagenta, Color.DarkMagenta }, 24 | { ConsoleColor.DarkYellow, Color.DarkGoldenrod }, 25 | { ConsoleColor.Gray, Color.Azure }, 26 | { ConsoleColor.DarkGray, Color.DarkGray }, 27 | { ConsoleColor.Blue, Color.CornflowerBlue }, 28 | { ConsoleColor.Green, Color.LightGreen }, 29 | { ConsoleColor.Cyan, Color.Cyan }, 30 | { ConsoleColor.Red, Color.Red }, 31 | { ConsoleColor.Magenta, Color.Magenta }, 32 | { ConsoleColor.Yellow, Color.Yellow }, 33 | { ConsoleColor.White, Color.White } 34 | }; 35 | 36 | private readonly RichTextBox output; 37 | 38 | public RichTextBoxStreamWriter(RichTextBox _output) 39 | { 40 | output = _output; 41 | } 42 | 43 | public override void Write(string value) 44 | { 45 | Color color; 46 | colorMap.TryGetValue(ForegroundColor, out color); 47 | 48 | output.SelectionColor = color; 49 | 50 | output.SelectionStart = output.TextLength; 51 | output.SelectionLength = 0; 52 | 53 | output.AppendText(value); 54 | output.SelectionColor = output.ForeColor; 55 | 56 | output.ScrollToCaret(); 57 | output.Refresh(); 58 | } 59 | 60 | public override Encoding Encoding 61 | { 62 | get { return Encoding.UTF8; } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gemcutter/Application/Reflection.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #include "Reflection.h" 3 | 4 | namespace 5 | { 6 | std::vector componentTypes; 7 | } 8 | 9 | namespace gem 10 | { 11 | loupe::reflection_blob reflection_tables; 12 | 13 | const loupe::type* EntityTypeId = nullptr; 14 | const loupe::type* BaseComponentTypeId = nullptr; 15 | const loupe::type* BaseResourceTypeId = nullptr; 16 | 17 | void InitializeReflectionTables() 18 | { 19 | #ifdef GEM_DEBUG 20 | constinit static bool calledOnce = false; 21 | ASSERT(!calledOnce, "InitializeReflectionTables() should not be called multiple times."); 22 | calledOnce = true; 23 | #endif 24 | reflection_tables = loupe::reflect(1); 25 | loupe::clear_reflect_tasks(); 26 | 27 | EntityTypeId = reflection_tables.find(); 28 | BaseComponentTypeId = reflection_tables.find(); 29 | BaseResourceTypeId = reflection_tables.find(); 30 | 31 | // Cache all component types. They will already be sorted alphabetically by loupe. 32 | componentTypes.reserve(64); 33 | for (const loupe::type& type : reflection_tables.get_types()) 34 | { 35 | // The base component itself is not required. 36 | if (&type == BaseComponentTypeId) [[unlikely]] 37 | continue; 38 | 39 | if (type.is_a(*BaseComponentTypeId)) 40 | { 41 | componentTypes.push_back(&type); 42 | } 43 | } 44 | componentTypes.shrink_to_fit(); 45 | } 46 | 47 | std::string_view GetDisplayName(const loupe::member& member) 48 | { 49 | if (auto* displayName = member.metadata.find()) 50 | { 51 | return displayName->name; 52 | } 53 | 54 | return member.name; 55 | } 56 | 57 | std::string_view GetDisplayName(const loupe::enum_entry& entry) 58 | { 59 | if (auto* displayName = entry.metadata.find()) 60 | { 61 | return displayName->name; 62 | } 63 | 64 | return entry.name; 65 | } 66 | 67 | std::span GetAllComponentTypes() 68 | { 69 | return componentTypes; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gemcutter/Input/XboxGamePad.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Math/Vector.h" 4 | 5 | struct _XINPUT_STATE; typedef _XINPUT_STATE XINPUT_STATE; 6 | 7 | namespace gem 8 | { 9 | class XboxGamePad 10 | { 11 | public: 12 | enum class Buttons : uint16_t 13 | { 14 | DPadUp = 1, 15 | DPadDown = 2, 16 | DPadLeft = 4, 17 | DPadRight = 8, 18 | Start = 16, 19 | Back = 32, 20 | LeftThumb = 64, 21 | RightThumb = 128, 22 | LeftShoulder = 256, 23 | RightShoulder = 512, 24 | A = 4096, 25 | B = 8192, 26 | X = 16384, 27 | Y = 32768 28 | }; 29 | 30 | XboxGamePad() = delete; 31 | // Initialize with a game-pad ID to read input from. [0,1,2,3] 32 | XboxGamePad(unsigned gamepadID = 0); 33 | ~XboxGamePad(); 34 | 35 | // Returns true if the controller is connected and ready to use. Implicitly updates. 36 | bool IsConnected(); 37 | 38 | // Updates the internal states by polling the device for input. 39 | void Update(); 40 | 41 | // Set the level of vibration. [0,1] 42 | void SetVibration(float left, float right); 43 | 44 | // Returns the state of a given button. 45 | bool GetButton(Buttons button) const; 46 | 47 | // Returns the state of the left trigger. [0,1] 48 | float GetLeftTrigger() const; 49 | // Returns the state of the right trigger. [0,1] 50 | float GetRightTrigger() const; 51 | 52 | // Returns x,y state of the left thumb stick. [-1,1] 53 | vec2 GetLeftThumbStick() const; 54 | // Returns x,y state of the right thumb stick. [-1,1] 55 | vec2 GetRightThumbStick() const; 56 | 57 | // Returns the distance of the left thumb stick from the origin. [0,1] 58 | float GetLeftThumbStickMagnitude() const; 59 | // Returns the distance of the right thumb stick from the origin. [0,1] 60 | float GetRightThumbStickMagnitude() const; 61 | 62 | private: 63 | vec2 leftThumbStick; 64 | vec2 rightThumbStick; 65 | float leftThumbStickMagnitude = 0.0f; 66 | float rightThumbStickMagnitude = 0.0f; 67 | 68 | XINPUT_STATE* controllerState; 69 | unsigned gamepadID = 0; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /tests/Math.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace gem; 5 | 6 | TEST_CASE("Math") 7 | { 8 | SECTION("Utility Functions") 9 | { 10 | CHECK(Abs(0) == 0); 11 | CHECK(Abs(1) == 1); 12 | CHECK(Abs(-2.5f) == 2.5f); 13 | 14 | CHECK(Equals(0.0f, FLT_EPSILON)); 15 | CHECK(Equals(0.0, DBL_EPSILON)); 16 | CHECK(Equals(0.0f, 0.0f)); 17 | CHECK(Equals(1.0f, 1.0f)); 18 | CHECK(Equals(-1.0f, -1.0f)); 19 | CHECK(Equals(1.0f, 1.0f + FLT_EPSILON)); 20 | CHECK(Equals(1.0, 1.0 + DBL_EPSILON)); 21 | CHECK_FALSE(Equals(0.0f - FLT_EPSILON, 0.0f + FLT_EPSILON)); 22 | CHECK_FALSE(Equals(0.0 - DBL_EPSILON, 0.0 + DBL_EPSILON)); 23 | CHECK_FALSE(Equals(-1.0f, 1.0f)); 24 | CHECK_FALSE(Equals(-1.0, 1.0)); 25 | CHECK_FALSE(Equals(FLT_EPSILON, -FLT_EPSILON)); 26 | CHECK_FALSE(Equals(DBL_EPSILON, -DBL_EPSILON)); 27 | 28 | CHECK(Min(0.0f, 0.0f) == 0.0f); 29 | CHECK(Min(2.5f, 2.0f) == 2.0f); 30 | CHECK(Min(1, 2, 3, 4, 5, 6) == 1); 31 | 32 | CHECK(Max(0.0f, 0.0f) == 0.0f); 33 | CHECK(Max(2.5f, 2.0f) == 2.5f); 34 | CHECK(Max(1, 3, 5, 6, 2, 4) == 6); 35 | 36 | CHECK(Clamp(0, 0, 0) == 0); 37 | CHECK(Clamp(10, 5, 15) == 10); 38 | CHECK(Clamp(0, 5, 15) == 5); 39 | CHECK(Clamp(20, 5, 15) == 15); 40 | 41 | CHECK(Equals(PercentInRange(5.0f, 0.0f, 10.0f), 0.5f)); 42 | CHECK(Equals(PercentInRange(-1.0f, -2.0f, 2.0f), 0.25f)); 43 | CHECK(Equals(PercentInRange(3.0f, 0.0f, 1.0f), 3.0f)); 44 | CHECK(Equals(PercentInRange(0.0f, 1.0f, 1.0f), 0.0f)); 45 | 46 | CHECK(PowerOfTwoCeil(0u) == 1u); 47 | CHECK(PowerOfTwoCeil(1u) == 1u); 48 | CHECK(PowerOfTwoCeil(2u) == 2u); 49 | CHECK(PowerOfTwoCeil(3u) == 4u); 50 | CHECK(PowerOfTwoCeil(10u) == 16u); 51 | CHECK(PowerOfTwoCeil(1025u) == 2048u); 52 | CHECK(PowerOfTwoCeil(2048u) == 2048u); 53 | 54 | CHECK(PowerOfTwoFloor(0u) == 0u); 55 | CHECK(PowerOfTwoFloor(1u) == 1u); 56 | CHECK(PowerOfTwoFloor(2u) == 2u); 57 | CHECK(PowerOfTwoFloor(3u) == 2u); 58 | CHECK(PowerOfTwoFloor(10u) == 8u); 59 | CHECK(PowerOfTwoFloor(1025u) == 1024u); 60 | CHECK(PowerOfTwoFloor(2048u) == 2048u); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /gemcutter/Application/Reflection.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Application/Logging.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace gem 10 | { 11 | // The core reflection data for the whole program. 12 | extern loupe::reflection_blob reflection_tables; 13 | 14 | // Common typeIds cached for use in quick comparisons. Will never be null. 15 | extern const loupe::type* EntityTypeId; 16 | extern const loupe::type* BaseComponentTypeId; 17 | extern const loupe::type* BaseResourceTypeId; 18 | 19 | // Populates the global reflection tables for the program. 20 | // Should only be called once at the top of main(). 21 | void InitializeReflectionTables(); 22 | 23 | // Returns the reflection information for the given type. 24 | template [[nodiscard]] 25 | const loupe::type& ReflectType(); 26 | 27 | // Uses reflection information to convert the name into the corresponding enum value. 28 | template [[nodiscard]] 29 | std::optional StringToEnum(std::string_view valueName); 30 | 31 | // Uses reflection information to convert the enum value into the corresponding string name. 32 | // Asserts if the value is not properly reflected. 33 | template [[nodiscard]] 34 | std::string_view EnumToString(Enum value); 35 | 36 | // Returns a string representation of the given type's name. 37 | // Results will vary by compiler and are best used for debug purposes only. 38 | template [[nodiscard]] 39 | consteval std::string_view GetTypeName(); 40 | 41 | // Returns all component type descriptors (except ComponentBase). 42 | std::span GetAllComponentTypes(); 43 | 44 | [[nodiscard]] std::string_view GetDisplayName(const loupe::member&); 45 | [[nodiscard]] std::string_view GetDisplayName(const loupe::enum_entry&); 46 | } 47 | 48 | #define REFLECT_RESOURCE(resource) REFLECT(resource) BASES { REF_BASE(gem::ResourceBase) } 49 | #define REFLECT_COMPONENT(component, base) REFLECT(component) BASES { REF_BASE(base) } USER_CONSTRUCTOR(gem::Entity&) 50 | 51 | #include "Reflection.inl" 52 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Renderable.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Emilian Cioca 2 | #include "Renderable.h" 3 | 4 | namespace gem 5 | { 6 | Renderable::Renderable(Entity& _owner) 7 | : Component(_owner) 8 | { 9 | material = Material::MakeNew(); 10 | material->shader = Shader::MakeNewPassThrough(); 11 | } 12 | 13 | Renderable::Renderable(Entity& _owner, Material::Ptr _material) 14 | : Component(_owner) 15 | { 16 | SetMaterial(std::move(_material)); 17 | } 18 | 19 | Renderable::Renderable(Entity& _owner, VertexArray::Ptr _array) 20 | : Component(_owner) 21 | , array(std::move(_array)) 22 | { 23 | material = Material::MakeNew(); 24 | material->shader = Shader::MakeNewPassThrough(); 25 | } 26 | 27 | Renderable::Renderable(Entity& _owner, VertexArray::Ptr _array, Material::Ptr _material) 28 | : Component(_owner) 29 | , array(std::move(_array)) 30 | { 31 | SetMaterial(std::move(_material)); 32 | } 33 | 34 | void Renderable::SetMaterial(Material::Ptr newMaterial) 35 | { 36 | ASSERT(newMaterial, "'newMaterial' cannot be null."); 37 | 38 | // Remove any instanced buffer bindings from the previous shader. 39 | if (material && material->shader) 40 | { 41 | for (const BufferBinding& binding : material->shader->GetBufferBindings()) 42 | { 43 | if (binding.templateBuff) 44 | { 45 | buffers.Remove(binding.unit); 46 | } 47 | } 48 | } 49 | 50 | // Create any instanced buffer copies needed per-entity. 51 | for (const BufferBinding& binding : newMaterial->shader->GetBufferBindings()) 52 | { 53 | if (binding.templateBuff) 54 | { 55 | auto newBuffer = UniformBuffer::MakeNew(); 56 | newBuffer->Copy(*binding.templateBuff); 57 | 58 | buffers.Add(std::move(newBuffer), binding.unit); 59 | } 60 | } 61 | 62 | material = std::move(newMaterial); 63 | } 64 | 65 | const Material& Renderable::GetMaterial() const 66 | { 67 | return *material; 68 | } 69 | 70 | Material& Renderable::GetMaterial() 71 | { 72 | return *material; 73 | } 74 | } 75 | 76 | REFLECT_COMPONENT(gem::Renderable, gem::ComponentBase) 77 | MEMBERS { 78 | REF_PRIVATE_MEMBER_EX(material, nullptr, &gem::Renderable::SetMaterial) 79 | } 80 | REF_END; 81 | -------------------------------------------------------------------------------- /tools/AssetManager/workspace_templates/config.workspace: -------------------------------------------------------------------------------- 1 | 2 | 3 | ../assets 4 | 5 | 6 | 7 | ttf 8 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\font_encoder.exe 9 | 10 | 11 | obj 12 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\mesh_encoder.exe 13 | 14 | 15 | png 16 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\texture_encoder.exe 17 | 18 | 19 | jpg 20 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\texture_encoder.exe 21 | 22 | 23 | tga 24 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\texture_encoder.exe 25 | 26 | 27 | bmp 28 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\texture_encoder.exe 29 | 30 | 31 | mat 32 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\material_encoder.exe 33 | 34 | 35 | wav 36 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\sound_encoder.exe 37 | 38 | 39 | flac 40 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\sound_encoder.exe 41 | 42 | 43 | ogg 44 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\sound_encoder.exe 45 | 46 | 47 | mp3 48 | ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}\\[[Config]]\\sound_encoder.exe 49 | 50 | 51 | -------------------------------------------------------------------------------- /gemcutter/Utilities/Random.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Random.h" 3 | #include "gemcutter/Application/Logging.h" 4 | #include "gemcutter/Math/Math.h" 5 | #include "gemcutter/Math/Vector.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace 12 | { 13 | std::mt19937 engine; 14 | } 15 | 16 | namespace gem 17 | { 18 | void SeedRandomNumberGenerator() 19 | { 20 | std::random_device rd; 21 | 22 | engine.seed(rd()); 23 | srand(static_cast(time(nullptr))); 24 | } 25 | 26 | void SeedRandomNumberGenerator(unsigned seed) 27 | { 28 | engine.seed(seed); 29 | srand(seed); 30 | } 31 | 32 | float RandomRange(float min, float max) 33 | { 34 | std::uniform_real_distribution dist(min, std::nextafter(max, FLT_MAX)); 35 | return dist(engine); 36 | } 37 | 38 | int RandomRange(int min, int max) 39 | { 40 | std::uniform_int_distribution dist(min, max); 41 | return dist(engine); 42 | } 43 | 44 | vec3 RandomDirection() 45 | { 46 | return Normalize(vec3( 47 | RandomRange(-1.0f, 1.0f), 48 | RandomRange(-1.0f, 1.0f), 49 | RandomRange(-1.0f, 1.0f))); 50 | } 51 | 52 | vec3 RandomColor() 53 | { 54 | return vec3( 55 | RandomRange(0.0f, 1.0f), 56 | RandomRange(0.0f, 1.0f), 57 | RandomRange(0.0f, 1.0f)); 58 | } 59 | 60 | bool RandomBool(float probability) 61 | { 62 | ASSERT(probability >= 0.0f && probability <= 1.0f, "Probability must be within [0, 1]."); 63 | 64 | std::bernoulli_distribution dist(probability); 65 | return dist(engine); 66 | } 67 | 68 | Range::Range(float _min, float _max) 69 | { 70 | Set(_min, _max); 71 | } 72 | 73 | Range Range::Deviation(float value, float deviation) 74 | { 75 | float halfRange = Abs(deviation) * 0.5f; 76 | return { value - halfRange, value + halfRange }; 77 | } 78 | 79 | float Range::Random() const 80 | { 81 | return RandomRange(min, max); 82 | } 83 | 84 | void Range::Set(float _min, float _max) 85 | { 86 | ASSERT(_min <= _max, "Invalid range."); 87 | min = _min; 88 | max = _max; 89 | } 90 | 91 | bool Range::Contains(float value) const 92 | { 93 | return value >= min && value <= max; 94 | } 95 | } 96 | 97 | REFLECT(gem::Range) 98 | MEMBERS { 99 | REF_MEMBER(min) 100 | REF_MEMBER(max) 101 | } 102 | REF_END; 103 | -------------------------------------------------------------------------------- /docs/AssetManager.md: -------------------------------------------------------------------------------- 1 | # AssetManager 2 | ![AssetManager](AssetManager.png) 3 | 4 | This tool allows you to easily prepare your assets for run-time use by the framework. 5 | 6 | # Workspace 7 | The workspace folder holds all the raw assets that will eventually be used by the game. 8 | 9 | # Encoders 10 | An Encoder is an executable that implements the interface provided by `gemcutter\Resource\Encoder.h`. 11 | In the AssetManager, each Encoder is associated with a specific file extension. Assets of that type will be processed using the functions in the Encoder. 12 | Custom Encoders can be created for game-specific assets (levels, items, dialog-trees etc.), allowing you to easily pack game data. 13 | Paths to encoders can use "\[\[Config\]\]" to help select the correct binaries for the current configuration. 14 | 15 | # Metadata 16 | Every asset with an associated encoder has metaData created for it. MetaData is saved adjacent to the asset (`assetName.extension.meta`) as plain text. 17 | The content of the metaData file depends on the encoder. These are the properties used when packing the asset into a binary format. 18 | For instance, .ttf.meta files for fonts specify the output resolution of the generated textures. 19 | 20 | MetaData files are automatically added for newly created assets while the AssetManager is running. 21 | 22 | # Building Assets 23 | The AssetManager provides two functions, Updating and Packing. Updating your workspace will ensure that all 24 | assets have up-to-date metaData associated with them. If an asset doesn't have metaData, Updating the workspace 25 | will use the Encoder associated with that asset to generate a default metaData file. If an asset already has 26 | metaData, the Encoder will be used to validate the integrity of the metaData and update it to the latest version. 27 | 28 | Packing the workspace will prepare all assets for use by the game. By default, an output folder called `Assets` will be created adjacent to the Workspace. 29 | All assets with an associated Encoder will be converted and saved to the Assets folder. Any remaining assets are copied 1:1 to the folder. 30 | The output folder is populated with the same folder structure as the workspace. 31 | 32 | In the default `Manual` packing mode, all assets will be processed when a packing operation is started. 33 | When the mode is set to `Auto`, individual assets will automatically be packed whenever a change is detected on the disk. -------------------------------------------------------------------------------- /gemcutter/GUI/Label.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #include "Label.h" 3 | #include "gemcutter/Rendering/Text.h" 4 | 5 | namespace gem 6 | { 7 | Label::Label(Entity& _owner) 8 | : Widget(_owner) 9 | { 10 | auto material = Material::MakeNew(); 11 | material->shader = Load("Engine/Shaders/Font"); 12 | material->blendMode = BlendFunc::Linear; 13 | 14 | text = &owner.Require(); 15 | text->centeredX = true; 16 | text->centeredY = true; 17 | text->SetMaterial(std::move(material)); 18 | } 19 | 20 | Label::Label(Entity& _owner, Font::Ptr font, std::string_view string) 21 | : Label(_owner) 22 | { 23 | SetFont(std::move(font)); 24 | SetString(string); 25 | } 26 | 27 | Label::Label(Entity& _owner, Font::Ptr font, std::string_view string, Material::Ptr material) 28 | : Widget(_owner) 29 | { 30 | text = &owner.Require(); 31 | text->centeredX = true; 32 | text->centeredY = true; 33 | 34 | SetFont(std::move(font)); 35 | SetString(string); 36 | SetMaterial(std::move(material)); 37 | } 38 | 39 | Text& Label::GetText() 40 | { 41 | return *text; 42 | } 43 | 44 | const Text& Label::GetText() const 45 | { 46 | return *text; 47 | } 48 | 49 | void Label::SetString(std::string_view string) 50 | { 51 | text->string = string; 52 | 53 | textWidth = text->GetLineWidth(1); 54 | } 55 | 56 | const std::string& Label::GetString() const 57 | { 58 | return text->string; 59 | } 60 | 61 | void Label::SetFont(Font::Ptr font) 62 | { 63 | text->font = std::move(font); 64 | } 65 | 66 | Font::Ptr Label::GetFont() const 67 | { 68 | return text->font; 69 | } 70 | 71 | void Label::SetMaterial(Material::Ptr material) 72 | { 73 | text->SetMaterial(std::move(material)); 74 | } 75 | 76 | Material& Label::GetMaterial() const 77 | { 78 | return text->GetMaterial(); 79 | } 80 | 81 | void Label::UpdateContent() 82 | { 83 | const float widgetWidth = GetAbsoluteBounds().width; 84 | 85 | if (textWidth > widgetWidth) 86 | { 87 | const float scalar = (widgetWidth / textWidth) * textScale; 88 | 89 | owner.scale = vec3(scalar); 90 | } 91 | else 92 | { 93 | owner.scale = vec3(textScale); 94 | } 95 | } 96 | } 97 | 98 | REFLECT_COMPONENT(gem::Label, gem::Widget) 99 | MEMBERS { 100 | REF_MEMBER(textScale) 101 | } 102 | REF_END; 103 | -------------------------------------------------------------------------------- /gemcutter/Resource/ParticleFunctor.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Resource/ParticleBuffer.h" 4 | #include "gemcutter/Resource/Shareable.h" 5 | #include "gemcutter/Utilities/Random.h" 6 | 7 | #include 8 | 9 | namespace gem 10 | { 11 | class ParticleEmitter; 12 | 13 | // Base functor class for changing particle behaviour or spawning particles. 14 | class ParticleFunctor 15 | { 16 | public: 17 | virtual ~ParticleFunctor() = default; 18 | // Called to initialize newly spawned particles. 19 | // New particles will be in the specified index range. 20 | virtual void Init(ParticleBuffer& particles, ParticleEmitter& emitter, unsigned startIndex, unsigned count); 21 | // Main update call for the functor. 22 | virtual void Update(ParticleBuffer& particles, ParticleEmitter& emitter, float deltaTime) = 0; 23 | // Specifies the required data attributes for this functor to update. 24 | virtual ParticleAttributes GetRequirements() const { return ParticleAttributes::None; } 25 | // Specifies if the functor should be executed even when the emitter is paused. 26 | virtual bool UpdateWhenPaused() const { return false; } 27 | }; 28 | 29 | // A list of ParticleFunctors acting on a ParticleEmitter. 30 | class FunctorList 31 | { 32 | friend ParticleEmitter; 33 | public: 34 | FunctorList() = default; 35 | FunctorList(const FunctorList&); 36 | FunctorList& operator=(const FunctorList&); 37 | 38 | void Add(std::shared_ptr functor); 39 | 40 | // Removes all functors. 41 | void Clear(); 42 | 43 | const auto& GetAll() const { return functors; } 44 | 45 | private: 46 | std::vector> functors; 47 | 48 | bool dirty = true; 49 | }; 50 | 51 | /* Built-in Particle Functors */ 52 | // Rotates particles. 53 | class RotationFunc : public ParticleFunctor, public Shareable 54 | { 55 | friend ShareableAlloc; 56 | RotationFunc() = default; 57 | RotationFunc(float rotationSpeed, Range initialRotation = Range(0.0f, 360.0f)); 58 | 59 | public: 60 | void Init(ParticleBuffer& particles, ParticleEmitter& emitter, unsigned startIndex, unsigned count) override; 61 | void Update(ParticleBuffer& particles, ParticleEmitter& emitter, float deltaTime) override; 62 | 63 | ParticleAttributes GetRequirements() const final override; 64 | 65 | float rotationSpeed = 5.0f; 66 | Range initialRotation{ 0.0f, 360.0f }; 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /gemcutter/Utilities/ScopeGuard.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | 4 | /* 5 | ScopeGuard assists in preventing memory leaks and making code more clear. 6 | Following RAII principles, this can allow you to create safer functions. 7 | 8 | A function that allocates memory but has many exit points can easily leak 9 | unless you remember to duplicate your cleanup code in every return case. For example: 10 | 11 | void Load() { 12 | int* memory = new int[256]; 13 | 14 | if (!okay) return; // Leaks memory! 15 | 16 | // Many more exit points... 17 | 18 | delete[] int; // Only here will the memory be safely released. 19 | } 20 | 21 | With the scope guard we improve readability, safety, and maintainability. 22 | Write the cleanup code once, and it will run however the scope exits. 23 | 24 | void Load() { 25 | int* memory = new int[256]; 26 | defer { delete[] memory }; 27 | 28 | if (!okay) return; // defer block is called! 29 | 30 | // Many more (safe) exit points... 31 | 32 | // defer block is called. No need to manually delete. 33 | } 34 | 35 | Based on the implementation by Andrei Alexandrescu. 36 | */ 37 | 38 | namespace gem 39 | { 40 | template 41 | class ScopeGuard 42 | { 43 | public: 44 | ScopeGuard() = delete; 45 | ScopeGuard(const ScopeGuard&) = delete; 46 | ScopeGuard& operator=(ScopeGuard&&) = delete; 47 | ScopeGuard& operator=(const ScopeGuard&) = delete; 48 | 49 | ScopeGuard(ScopeGuard&& rhs) noexcept 50 | : active(rhs.active) 51 | , functor(std::move(rhs.functor)) 52 | { 53 | rhs.Dismiss(); 54 | } 55 | 56 | ScopeGuard(Functor func) 57 | : functor(std::move(func)) 58 | { 59 | } 60 | 61 | ~ScopeGuard() 62 | { 63 | if (active) functor(); 64 | } 65 | 66 | // Executes the function if it hasn't already. 67 | // It will not be called again when the scope is terminated. 68 | void Execute() 69 | { 70 | if (active) functor(); 71 | Dismiss(); 72 | } 73 | 74 | // Stops the guard from activating when the scope is terminated. 75 | void Dismiss() 76 | { 77 | active = false; 78 | } 79 | 80 | private: 81 | bool active = true; 82 | [[no_unique_address]] Functor functor; 83 | }; 84 | } 85 | 86 | #define CONCATENATE(s1, s2) s1##s2 87 | #define CONCATENATE_INDIRECT(s1, s2) CONCATENATE(s1, s2) 88 | #define ANONYMOUS_VARIABLE(str) CONCATENATE_INDIRECT(str, __COUNTER__) 89 | #define defer gem::ScopeGuard ANONYMOUS_VARIABLE(SCOPE_GUARD_) = [&]() 90 | -------------------------------------------------------------------------------- /gemcutter/Application/HierarchicalEvent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Application/Delegate.h" 4 | #include "gemcutter/Application/Event.h" 5 | #include "gemcutter/Entity/Entity.h" 6 | #include "gemcutter/Entity/Hierarchy.h" 7 | 8 | namespace gem 9 | { 10 | // Responds to events given from an EventDispatcher component higher in the hierarchy. 11 | template 12 | class HierarchicalListener : public Component> 13 | { 14 | static_assert(std::is_base_of_v, "Template argument must inherit from Event."); 15 | public: 16 | HierarchicalListener(Entity& _owner) 17 | : Component>(_owner) 18 | { 19 | } 20 | 21 | // Your function should return 'true' if the event is handled. This will 22 | // consume it and stop it from propagating further down the hierarchy. 23 | Delegate callback; 24 | }; 25 | 26 | // Propagates an event through a hierarchy of Entities. 27 | template 28 | class HierarchicalDispatcher : public Component> 29 | { 30 | static_assert(std::is_base_of_v, "Template argument must inherit from Event."); 31 | public: 32 | HierarchicalDispatcher(Entity& _owner) 33 | : Component>(_owner) 34 | { 35 | this->owner.Require(); 36 | 37 | listener = [this](auto& e) { 38 | if (this->IsEnabled()) 39 | { 40 | Distribute(this->owner, e); 41 | } 42 | }; 43 | } 44 | 45 | private: 46 | // Propagates the event through the hierarchy while notifying HierarchicalListeners. 47 | // Once a listener has handled the event, propagation stops. 48 | static bool Distribute(Entity& ent, const EventObj& e) 49 | { 50 | auto& children = ent.Get().GetChildren(); 51 | 52 | // Manual loop to avoid iterator invalidation from callback side-effects. 53 | for (unsigned i = 0; i < children.size(); ++i) 54 | { 55 | if (auto* comp = children[i]->Try>()) 56 | { 57 | if (comp->callback(e).value_or(false)) 58 | { 59 | return true; 60 | } 61 | } 62 | } 63 | 64 | for (unsigned i = 0; i < children.size(); ++i) 65 | { 66 | if (Distribute(*children[i], e)) 67 | { 68 | return true; 69 | } 70 | } 71 | 72 | return false; 73 | } 74 | 75 | Listener listener; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /gemcutter/Sound/SoundSource.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "SoundSource.h" 3 | #include "gemcutter/Application/Logging.h" 4 | 5 | #include 6 | 7 | namespace gem 8 | { 9 | extern SoLoud::Soloud audioEngine; 10 | 11 | SoundSource::SoundSource(Entity& _owner) 12 | : Component(_owner) 13 | { 14 | } 15 | 16 | SoundSource::SoundSource(Entity& _owner, Sound::Ptr sound) 17 | : Component(_owner) 18 | { 19 | SetData(std::move(sound)); 20 | } 21 | 22 | SoundSource::~SoundSource() 23 | { 24 | Stop(); 25 | } 26 | 27 | void SoundSource::SetData(Sound::Ptr sound) 28 | { 29 | Stop(); 30 | 31 | data = std::move(sound); 32 | } 33 | 34 | Sound* SoundSource::GetData() const 35 | { 36 | return data.get(); 37 | } 38 | 39 | void SoundSource::Play() 40 | { 41 | ASSERT(data && data->source, "The SoundSource does not have a valid sound to play."); 42 | 43 | if (data->Is3D()) 44 | { 45 | const vec3 pos = owner.GetWorldPosition(); 46 | hSound = audioEngine.play3d(*data->source, pos.x, pos.y, pos.z, 0.0f, 0.0f, 0.0f, data->GetVolume() * volume); 47 | } 48 | else 49 | { 50 | hSound = audioEngine.play(*data->source, data->GetVolume() * volume); 51 | } 52 | } 53 | 54 | void SoundSource::Stop() 55 | { 56 | audioEngine.stop(hSound); 57 | } 58 | 59 | void SoundSource::Pause() 60 | { 61 | audioEngine.setPause(hSound, true); 62 | } 63 | 64 | void SoundSource::Resume() 65 | { 66 | audioEngine.setPause(hSound, false); 67 | } 68 | 69 | bool SoundSource::IsPlaying() const 70 | { 71 | return audioEngine.isValidVoiceHandle(hSound); 72 | } 73 | 74 | bool SoundSource::IsPaused() const 75 | { 76 | return audioEngine.getPause(hSound); 77 | } 78 | 79 | void SoundSource::SetSourceVolume(float _volume) 80 | { 81 | ASSERT(_volume >= 0.0f, "'volume' must be non negative."); 82 | 83 | volume = _volume; 84 | if (IsPlaying()) 85 | { 86 | audioEngine.setVolume(hSound, data->GetVolume() * volume); 87 | } 88 | } 89 | 90 | float SoundSource::GetSourceVolume() const 91 | { 92 | return volume; 93 | } 94 | 95 | void SoundSource::OnDisable() 96 | { 97 | Stop(); 98 | } 99 | } 100 | 101 | REFLECT_COMPONENT(gem::SoundSource, gem::ComponentBase) 102 | MEMBERS { 103 | REF_PRIVATE_MEMBER_EX(data, nullptr, &gem::SoundSource::SetData) 104 | REF_PRIVATE_MEMBER_EX(volume, nullptr, &gem::SoundSource::SetSourceVolume, min(0.0f)) 105 | } 106 | REF_END; 107 | -------------------------------------------------------------------------------- /gemcutter/Resource/Sound.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Resource/Resource.h" 4 | #include "gemcutter/Resource/Shareable.h" 5 | 6 | namespace SoLoud 7 | { 8 | class AudioSource; 9 | } 10 | 11 | namespace gem 12 | { 13 | enum class AttenuationFunc : uint16_t 14 | { 15 | None = 0, 16 | InverseDistance = 1, 17 | Linear = 2, 18 | Exponential = 3 19 | }; 20 | 21 | // An audio clip which can be attached to an Entity's SoundSource component. 22 | // A SoundListener must also be present in the scene for 3d audio effects. 23 | class Sound : public Resource, public Shareable 24 | { 25 | friend class SoundSource; // For source. 26 | public: 27 | static constexpr std::string_view Extension = ".sound"; 28 | 29 | ~Sound(); 30 | 31 | // Loads pre-packed *.sound resources. 32 | bool Load(std::string_view filePath); 33 | void Unload(); 34 | 35 | void SetIs3D(bool is3D); 36 | bool Is3D() const; 37 | 38 | // Causes the sound to loop to the beginning when done. 39 | void SetLooping(bool loop); 40 | bool IsLooping() const; 41 | 42 | // Playing a unique sound will stop any other currently playing instance of the sound. 43 | void SetIsUnique(bool unique); 44 | bool IsUnique() const; 45 | 46 | // Sets the behaviour of volume roll-off over distance. 47 | // Roll-off should be [0, ...], or [0, 1] for linear attenuation. A larger value speeds up roll-off. 48 | void SetAttenuation(AttenuationFunc func, float rolloff); 49 | AttenuationFunc GetAttenuationFunc() const; 50 | float GetAttenuationRolloff() const; 51 | 52 | // Sets the min/max range for attenuation. Should be (0, ...] for both. 53 | void SetDistances(float min, float max); 54 | float GetMinDistance() const; 55 | float GetMaxDistance() const; 56 | 57 | // Volume should be [0, ...]. 58 | void SetVolume(float volume); 59 | float GetVolume() const; 60 | 61 | private: 62 | SoLoud::AudioSource* source = nullptr; 63 | std::vector buffer; 64 | 65 | bool is3D = false; 66 | bool loop = false; 67 | bool unique = false; 68 | AttenuationFunc attenuation = AttenuationFunc::None; 69 | float rolloff = 1.0f; 70 | float volume = 1.0f; 71 | float minDistance = 0.0f; 72 | float maxDistance = 1.0f; 73 | 74 | public: 75 | PRIVATE_MEMBER(Sound, is3D); 76 | PRIVATE_MEMBER(Sound, loop); 77 | PRIVATE_MEMBER(Sound, unique); 78 | PRIVATE_MEMBER(Sound, attenuation); 79 | PRIVATE_MEMBER(Sound, rolloff); 80 | PRIVATE_MEMBER(Sound, volume); 81 | PRIVATE_MEMBER(Sound, minDistance); 82 | PRIVATE_MEMBER(Sound, maxDistance); 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /gemcutter/Application/Logging.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | 5 | /* 6 | This file defines various functions and macros for logging. 7 | 8 | "Log_Output.txt" is appended with every message if it has been previously opened with OpenOutputLog(). 9 | Console output can be toggled as well with CreateConsoleWindow() and DestroyConsoleWindow(). 10 | DEBUG_* and ASSERT() macros allow you to log in debug builds. 11 | */ 12 | 13 | namespace gem 14 | { 15 | enum class ConsoleColor : uint16_t 16 | { 17 | DarkBlue = 1, 18 | DarkGreen, 19 | DarkTeal, 20 | DarkRed, 21 | DarkPink, 22 | DarkYellow, 23 | Gray, 24 | DarkGray, 25 | Blue, 26 | Green, 27 | Teal, 28 | Red, 29 | Pink, 30 | Yellow, 31 | White 32 | }; 33 | 34 | void OpenOutputLog(); 35 | void CloseOutputLog(); 36 | 37 | void CreateConsoleWindow(); 38 | void DestroyConsoleWindow(); 39 | void FocusConsoleWindow(); 40 | void SetConsoleColor(ConsoleColor color); 41 | void ResetConsoleColor(); 42 | 43 | void Log(const char* format, ...); 44 | void Log(std::string_view message); 45 | void Error(const char* format, ...); 46 | void Error(std::string_view message); 47 | void Warning(const char* format, ...); 48 | void Warning(std::string_view message); 49 | void ErrorBox(const char* format, ...); 50 | void ErrorBox(std::string_view message); 51 | void WarningBox(const char* format, ...); 52 | void WarningBox(std::string_view message); 53 | 54 | void Assert(const char* exp, const char* format, ...); 55 | } 56 | 57 | #ifdef GEM_DEBUG 58 | #define DEBUG_LOG(format, ...) gem::Log((format), ##__VA_ARGS__) 59 | #define DEBUG_ERROR(format, ...) gem::Error((format), ##__VA_ARGS__) 60 | #define DEBUG_WARNING(format, ...) gem::Warning((format), ##__VA_ARGS__) 61 | #define DEBUG_ERROR_BOX(format, ...) gem::ErrorBox((format), ##__VA_ARGS__) 62 | #define DEBUG_WARNING_BOX(format, ...) gem::WarningBox((format), ##__VA_ARGS__) 63 | #define ASSERT(exp, format, ...) \ 64 | do { \ 65 | if (!(exp)) [[unlikely]] { \ 66 | gem::Assert(#exp, (format), ##__VA_ARGS__); \ 67 | __debugbreak(); \ 68 | } \ 69 | __pragma(warning(suppress:4127)) \ 70 | } while (false) 71 | #else 72 | #define DEBUG_LOG(format, ...) 73 | #define DEBUG_ERROR(format, ...) 74 | #define DEBUG_WARNING(format, ...) 75 | #define DEBUG_ERROR_BOX(format, ...) 76 | #define DEBUG_WARNING_BOX(format, ...) 77 | #define ASSERT(exp, format, ...) __assume(exp) 78 | #endif 79 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Primitives.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Resource/Shader.h" 4 | #include "gemcutter/Resource/VertexArray.h" 5 | 6 | namespace gem 7 | { 8 | struct vec3; 9 | struct vec4; 10 | 11 | // Provides simple, intimidate, geometry rendering. 12 | // Line, Triangle, Rectangle, and Grid functions are intended for debugging; they are not particularly efficient. 13 | extern class PrimitivesSingleton Primitives; 14 | class PrimitivesSingleton 15 | { 16 | public: 17 | bool Load(); 18 | bool IsLoaded() const; 19 | void Unload(); 20 | 21 | void DrawLine(const vec3& p1, const vec3& p2, const vec4& color); 22 | void DrawLine(const vec3& p1, const vec3& p2, const vec4& color1, const vec4& color2); 23 | void DrawLine(const vec3& p1, const vec3& p2, Texture& tex); 24 | 25 | void DrawTriangle(const vec3& p1, const vec3& p2, const vec3& p3, const vec4& color); 26 | void DrawTriangle(const vec3& p1, const vec3& p2, const vec3& p3, const vec4& color1, const vec4& color2, const vec4& color3); 27 | void DrawTriangle(const vec3& p1, const vec3& p2, const vec3& p3, Texture& tex); 28 | 29 | void DrawRectangle(const vec3& p1, const vec3& p2, const vec3& p3, const vec3& p4, const vec4& color); 30 | void DrawRectangle(const vec3& p1, const vec3& p2, const vec3& p3, const vec3& p4, const vec4& color1, const vec4& color2, const vec4& color3, const vec4& color4); 31 | void DrawRectangle(const vec3& p1, const vec3& p2, const vec3& p3, const vec3& p4, Texture& tex); 32 | 33 | void DrawGrid(const vec3& p1, const vec3& p2, const vec3& p3, const vec3& p4, const vec4& color, unsigned numDivisions); 34 | 35 | void DrawFullScreenQuad(Shader& program); 36 | void DrawFullScreenQuad(Texture& tex); 37 | 38 | // Returns a rectangle mesh spanning from 0 to 1 on the x and y axes. 39 | VertexArray::Ptr GetQuadArray() const; 40 | // Returns a rectangle mesh spanning from -1 to 1 on the x and y axes. 41 | VertexArray::Ptr GetUnitQuadArray() const; 42 | // Returns a cube mesh spanning from -1 to 1 on all axes. Can be used for skyboxes. 43 | VertexArray::Ptr GetUnitCubeArray() const; 44 | 45 | private: 46 | bool isLoaded = false; 47 | 48 | Shader lineProgram; 49 | Shader triangleProgram; 50 | Shader rectangleProgram; 51 | Shader texturedLineProgram; 52 | Shader texturedTriangleProgram; 53 | Shader texturedRectangleProgram; 54 | Shader texturedFullScreenQuadProgram; 55 | 56 | VertexArray::Ptr quadArray; 57 | VertexArray::Ptr unitQuadArray; 58 | VertexArray::Ptr unitCubeArray; 59 | 60 | // Allows the rendering of primitives without vertex attributes. 61 | unsigned dummyVAO = 0; 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /gemcutter/Application/CmdArgs.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "CmdArgs.h" 3 | 4 | #include 5 | 6 | namespace gem 7 | { 8 | int GetArgc() 9 | { 10 | return __argc; 11 | } 12 | 13 | char** GetArgv() 14 | { 15 | return __argv; 16 | } 17 | 18 | bool HasCommandLineArg(const char* name) 19 | { 20 | return FindCommandLineArg(name) != -1; 21 | } 22 | 23 | int FindCommandLineArg(const char* name) 24 | { 25 | for (int i = 1; i < __argc; ++i) 26 | { 27 | if (strcmp(__argv[i], name) == 0) 28 | { 29 | return i; 30 | } 31 | } 32 | 33 | return -1; 34 | } 35 | 36 | const char* GetExecutablePath() 37 | { 38 | return __argv[0]; 39 | } 40 | 41 | bool GetCommandLineArg(int index, bool& value) 42 | { 43 | if (index > 0 && index < __argc) 44 | { 45 | value = atoi(__argv[index]) == 1; 46 | return true; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | bool GetCommandLineArg(int index, double& value) 53 | { 54 | if (index > 0 && index < __argc) 55 | { 56 | value = atof(__argv[index]); 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | bool GetCommandLineArg(int index, float& value) 64 | { 65 | if (index > 0 && index < __argc) 66 | { 67 | value = static_cast(atof(__argv[index])); 68 | return true; 69 | } 70 | 71 | return false; 72 | } 73 | 74 | bool GetCommandLineArg(int index, int& value) 75 | { 76 | if (index > 0 && index < __argc) 77 | { 78 | value = atoi(__argv[index]); 79 | return true; 80 | } 81 | 82 | return false; 83 | } 84 | 85 | bool GetCommandLineArg(int index, unsigned& value) 86 | { 87 | if (index > 0 && index < __argc) 88 | { 89 | value = atoi(__argv[index]); 90 | return true; 91 | } 92 | 93 | return false; 94 | } 95 | 96 | bool GetCommandLineArg(int index, char& value) 97 | { 98 | if (index > 0 && index < __argc) 99 | { 100 | value = __argv[index][0]; 101 | return true; 102 | } 103 | 104 | return false; 105 | } 106 | 107 | bool GetCommandLineArg(int index, const char*& value) 108 | { 109 | // Index 0 is valueid here. The first argument is always a string. 110 | if (index >= 0 && index < __argc) 111 | { 112 | value = __argv[index]; 113 | return true; 114 | } 115 | 116 | return false; 117 | } 118 | 119 | bool GetCommandLineArg(int index, std::string& value) 120 | { 121 | // Index 0 is valueid here. The first argument is always a string. 122 | if (index >= 0 && index < __argc) 123 | { 124 | value = __argv[index]; 125 | return true; 126 | } 127 | 128 | return false; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /gemcutter/Rendering/RenderPass.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | #include "gemcutter/Rendering/RenderTarget.h" 5 | #include "gemcutter/Rendering/Viewport.h" 6 | #include "gemcutter/Resource/Shader.h" 7 | #include "gemcutter/Resource/Texture.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace gem 13 | { 14 | // Ties together the three requirements for rendering: geometry, shaders, and a render target. 15 | class RenderPass 16 | { 17 | public: 18 | RenderPass(); 19 | RenderPass(const RenderPass&); 20 | 21 | RenderPass& operator=(const RenderPass&); 22 | 23 | // The provided Entity must have a camera component if it is not null. 24 | void SetCamera(Entity::WeakPtr camera); 25 | // Provides a shader to be used with PostProcess(), or to override Entity materials. 26 | void SetShader(Shader::Ptr shader); 27 | // If no explicit target is set, the back-buffer will be targeted. 28 | void SetTarget(RenderTarget::Ptr target); 29 | // If no explicit viewport is set, the viewport will match the render target or back-buffer. 30 | void SetViewport(std::optional vp); 31 | 32 | auto GetCamera() const { return camera.lock(); } 33 | const auto& GetShader() const { return shader; } 34 | const auto& GetTarget() const { return target; } 35 | const auto& GetViewport() const { return viewport; } 36 | 37 | // Prepares the pass for rendering and captures the current state of the camera. 38 | void Bind(); 39 | void UnBind(); 40 | 41 | // Renders a fullscreen quad. 42 | void PostProcess(); 43 | // Renders only the given Entity. 44 | void Render(const Entity&); 45 | // Renders all Entities in the list in order. 46 | void Render(std::span entities); 47 | // Renders the root Entity along with all renderable descendants (depth first traversal). 48 | void RenderRoot(const Entity& root); 49 | 50 | // These textures will be bound along with the RenderPass. 51 | TextureList textures; 52 | // These buffers will be bound along with the RenderPass. 53 | BufferList buffers; 54 | 55 | private: 56 | void CreateUniformBuffer(); 57 | 58 | std::optional viewport; 59 | Entity::WeakPtr camera; 60 | RenderTarget::Ptr target; 61 | Shader::Ptr shader; 62 | 63 | // Holds the world transformation matrices for an Entity while rendering. 64 | UniformBuffer transformBuffer; 65 | 66 | mat4 viewMatrix; 67 | mat4 viewProjMatrix; 68 | 69 | UniformHandle MVP; 70 | UniformHandle modelView; 71 | UniformHandle model; 72 | UniformHandle invModel; 73 | UniformHandle normalMatrix; 74 | 75 | static inline RenderPass* boundPass = nullptr; 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /gemcutter/Resource/Material.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Material.h" 3 | #include "gemcutter/Application/Logging.h" 4 | #include "gemcutter/Utilities/ScopeGuard.h" 5 | 6 | #include 7 | 8 | namespace gem 9 | { 10 | bool Material::Load(std::string_view filePath) 11 | { 12 | // Load binary file. 13 | FILE* binaryFile = fopen(filePath.data(), "rb"); 14 | if (binaryFile == nullptr) 15 | { 16 | Error("Material: ( %s )\nUnable to open file.", filePath.data()); 17 | return false; 18 | } 19 | defer{ fclose(binaryFile); }; 20 | 21 | char pathBuffer[MAX_PATH + 1]; 22 | 23 | size_t shaderLen = 0; 24 | fread(&shaderLen, sizeof(shaderLen), 1, binaryFile); 25 | if (shaderLen == 0) 26 | { 27 | shader = Shader::MakeNewPassThrough(); 28 | } 29 | else 30 | { 31 | if (shaderLen > MAX_PATH) 32 | { 33 | Error("Material: ( %s )\nInvalid Shader file path length.", filePath.data()); 34 | return false; 35 | } 36 | fread(pathBuffer, sizeof(char), shaderLen, binaryFile); 37 | pathBuffer[shaderLen] = '\0'; 38 | 39 | shader = gem::Load(pathBuffer); 40 | if (!shader) 41 | { 42 | Error("Material: ( %s )\nFailed to load Shader ( %s ).", filePath.data(), pathBuffer); 43 | return false; 44 | } 45 | } 46 | 47 | size_t textureCount = 0; 48 | fread(&textureCount, sizeof(textureCount), 1, binaryFile); 49 | if (textureCount > GPUInfo.GetMaxTextureSlots()) 50 | { 51 | Error("Material: ( %s )\nMaterial contains more texture units than is supported ( %d ).", filePath.data(), GPUInfo.GetMaxTextureSlots()); 52 | return false; 53 | } 54 | 55 | for (size_t i = 0; i < textureCount; ++i) 56 | { 57 | int unit = 0; 58 | fread(&unit, sizeof(unit), 1, binaryFile); 59 | 60 | size_t textureLen = 0; 61 | fread(&textureLen, sizeof(textureLen), 1, binaryFile); 62 | if (textureLen == 0 || textureLen > MAX_PATH) 63 | { 64 | Error("Material: ( %s )\nInvalid Texture file path length.", filePath.data()); 65 | return false; 66 | } 67 | 68 | fread(pathBuffer, sizeof(char), textureLen, binaryFile); 69 | pathBuffer[textureLen] = '\0'; 70 | 71 | auto texture = gem::Load(pathBuffer); 72 | if (!texture) 73 | { 74 | Error("Material: ( %s )\nFailed to load Texture ( %s ).", filePath.data(), pathBuffer); 75 | return false; 76 | } 77 | 78 | textures.Add(std::move(texture), unit); 79 | } 80 | 81 | fread(&blendMode, sizeof(blendMode), 1, binaryFile); 82 | fread(&depthMode, sizeof(depthMode), 1, binaryFile); 83 | fread(&cullMode, sizeof(cullMode), 1, binaryFile); 84 | 85 | return true; 86 | } 87 | } 88 | 89 | REFLECT(gem::Material) BASES { REF_BASE(gem::ResourceBase) } 90 | MEMBERS { 91 | REF_MEMBER(blendMode) 92 | REF_MEMBER(depthMode) 93 | REF_MEMBER(cullMode) 94 | REF_MEMBER(shader) 95 | REF_MEMBER(textures) 96 | } 97 | REF_END; 98 | -------------------------------------------------------------------------------- /gemcutter/Application/Timer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Timer.h" 3 | #include "gemcutter/Application/Logging.h" 4 | 5 | #include 6 | #include 7 | 8 | namespace 9 | { 10 | int64_t GetTimerResolution() 11 | { 12 | LARGE_INTEGER temp; 13 | if (QueryPerformanceFrequency(&temp)) 14 | { 15 | return temp.QuadPart; 16 | } 17 | 18 | gem::Error("Timer: High performance timer is not supported by your CPU."); 19 | return 0; 20 | } 21 | 22 | const int64_t ticksPerSecond = GetTimerResolution(); 23 | const int64_t ticksPerMS = ticksPerSecond / 1000; 24 | 25 | // Casted ahead of time for a performance boost in GetElapsedMS() and GetElapsedSeconds(). 26 | const double d_ticksPerSecond = static_cast(ticksPerSecond); 27 | const double d_ticksPerMS = static_cast(ticksPerMS); 28 | } 29 | 30 | namespace gem 31 | { 32 | Timer::Timer() 33 | { 34 | Reset(); 35 | } 36 | 37 | bool Timer::IsSupported() 38 | { 39 | return ticksPerSecond > 0; 40 | } 41 | 42 | int64_t Timer::GetTicksPerMS() 43 | { 44 | return ticksPerMS; 45 | } 46 | 47 | int64_t Timer::GetTicksPerSecond() 48 | { 49 | return ticksPerSecond; 50 | } 51 | 52 | int64_t Timer::GetCurrentTick() 53 | { 54 | LARGE_INTEGER currentTime; 55 | QueryPerformanceCounter(¤tTime); 56 | 57 | return currentTime.QuadPart; 58 | } 59 | 60 | void Timer::Reset() 61 | { 62 | startTime = GetCurrentTick(); 63 | } 64 | 65 | double Timer::GetElapsedMS() const 66 | { 67 | return (GetCurrentTick() - startTime) / d_ticksPerMS; 68 | } 69 | 70 | double Timer::GetElapsedSeconds() const 71 | { 72 | return (GetCurrentTick() - startTime) / d_ticksPerSecond; 73 | } 74 | 75 | bool Timer::IsElapsedMS(double ms) const 76 | { 77 | return ms >= GetElapsedMS(); 78 | } 79 | 80 | bool Timer::IsElapsedSeconds(double seconds) const 81 | { 82 | return seconds >= GetElapsedSeconds(); 83 | } 84 | 85 | void Timer::SubtractTimeMS(double ms) 86 | { 87 | // Moving the start time forward effectively removes time. 88 | startTime += static_cast(ms) / ticksPerMS; 89 | } 90 | 91 | void Timer::SubtractTimeSeconds(double seconds) 92 | { 93 | // Moving the start time forward effectively removes time. 94 | startTime += static_cast(seconds) / ticksPerSecond; 95 | } 96 | 97 | void Timer::AddTimeMS(double ms) 98 | { 99 | // Moving the start time back effective adds more time. 100 | startTime -= static_cast(ms) / ticksPerMS; 101 | } 102 | 103 | void Timer::AddTimeSeconds(double seconds) 104 | { 105 | // Moving the start time back effective adds more time. 106 | startTime -= static_cast(seconds) / ticksPerSecond; 107 | } 108 | } 109 | 110 | REFLECT(gem::Timer) 111 | MEMBERS { 112 | REF_PRIVATE_MEMBER(startTime) 113 | } 114 | REF_END; 115 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Light.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Light.h" 3 | #include "gemcutter/Math/Math.h" 4 | 5 | namespace gem 6 | { 7 | Light::Light(Entity& _owner) 8 | : Component(_owner) 9 | { 10 | CreateUniformBuffer(); 11 | color.Set(vec3(1.0f)); 12 | } 13 | 14 | Light::Light(Entity& _owner, const vec3& _color) 15 | : Component(_owner) 16 | { 17 | CreateUniformBuffer(); 18 | color.Set(_color); 19 | } 20 | 21 | Light::Light(Entity& _owner, const vec3& _color, Type _type) 22 | : Component(_owner) 23 | , type(_type) 24 | { 25 | CreateUniformBuffer(); 26 | color.Set(_color); 27 | } 28 | 29 | void Light::Update() 30 | { 31 | // The type must be a full uint to match up with GLSL. 32 | typeHandle.Set(static_cast(type)); 33 | 34 | switch (type) 35 | { 36 | case Type::Spot: 37 | { 38 | ASSERT(angle >= 0.0f && angle <= 360.0f, "Invalid SpotLight cone angle ( %f )", angle); 39 | 40 | // We don't use GetWorldTransform() here because it includes scaling. 41 | quat rot = owner.GetWorldRotation(); 42 | vec3 pos = owner.GetWorldPosition(); 43 | direction.Set(rot.GetForward()); 44 | position.Set(pos); 45 | cosAngle.Set(cos(ToRadian(angle * 0.5f))); 46 | break; 47 | } 48 | case Type::Point: 49 | { 50 | vec3 pos = owner.GetWorldPosition(); 51 | direction.Set(vec3(0.0f)); 52 | position.Set(pos); 53 | break; 54 | } 55 | case Type::Directional: 56 | { 57 | quat rot = owner.GetWorldRotation(); 58 | direction.Set(rot.GetForward()); 59 | position.Set(vec3(0.0f)); 60 | break; 61 | } 62 | } 63 | } 64 | 65 | UniformBuffer::Ptr& Light::GetBuffer() 66 | { 67 | return lightBuffer; 68 | } 69 | 70 | void Light::CreateUniformBuffer() 71 | { 72 | lightBuffer = UniformBuffer::MakeNew(); 73 | color = lightBuffer->AddUniform("Color"); 74 | position = lightBuffer->AddUniform("Position"); 75 | direction = lightBuffer->AddUniform("Direction"); 76 | attenuationLinear = lightBuffer->AddUniform("AttenuationLinear"); 77 | attenuationQuadratic = lightBuffer->AddUniform("AttenuationQuadratic"); 78 | cosAngle = lightBuffer->AddUniform("Angle"); 79 | typeHandle = lightBuffer->AddUniform("Type"); 80 | lightBuffer->InitBuffer(BufferUsage::Dynamic); 81 | 82 | attenuationLinear.Set(0.0f); 83 | attenuationQuadratic.Set(1.0f); 84 | } 85 | } 86 | 87 | REFLECT(gem::Light::Type) 88 | ENUM_VALUES { 89 | REF_VALUE(Point) 90 | REF_VALUE(Directional) 91 | REF_VALUE(Spot) 92 | } 93 | REF_END; 94 | 95 | REFLECT_COMPONENT(gem::Light, gem::ComponentBase) 96 | MEMBERS { 97 | REF_MEMBER(type) 98 | REF_MEMBER(color) 99 | REF_MEMBER(attenuationLinear) 100 | REF_MEMBER(attenuationQuadratic) 101 | REF_MEMBER(angle, range(0.0f, 360.0f)) 102 | } 103 | REF_END; 104 | -------------------------------------------------------------------------------- /tests/String.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace gem; 5 | 6 | TEST_CASE("String") 7 | { 8 | std::string input = 9 | "// Comment1\n" 10 | "int temp =\t\t 10 ;\n" 11 | "/* Comment 2 */\n"; 12 | 13 | SECTION("Remove Comments") 14 | { 15 | RemoveComments(input); 16 | 17 | CHECK(input == "int temp =\t\t 10 ;\n\n"); 18 | } 19 | 20 | SECTION("Remove Whitespace") 21 | { 22 | RemoveWhitespace(input); 23 | 24 | CHECK(input == "//Comment1" 25 | "inttemp=10;" 26 | "/*Comment2*/"); 27 | 28 | std::string str = " \t\n\n TEMP string value... \t\n "; 29 | 30 | TrimStart(str); 31 | CHECK(str == "TEMP string value... \t\n "); 32 | 33 | TrimEnd(str); 34 | CHECK(str == "TEMP string value..."); 35 | 36 | TrimStart(str); 37 | CHECK(str == "TEMP string value..."); 38 | 39 | TrimEnd(str); 40 | CHECK(str == "TEMP string value..."); 41 | 42 | str = {}; 43 | TrimStart(str); 44 | CHECK(str == ""); 45 | 46 | str = {}; 47 | TrimEnd(str); 48 | CHECK(str == ""); 49 | } 50 | 51 | SECTION("Remove Redundant Whitespace") 52 | { 53 | RemoveRedundantWhitespace(input); 54 | 55 | CHECK(input == 56 | "// Comment1\n" 57 | "int temp = 10 ;\n" 58 | "/* Comment 2 */\n"); 59 | } 60 | 61 | SECTION("Sanitize Code") 62 | { 63 | RemoveComments(input); 64 | RemoveRedundantWhitespace(input); 65 | 66 | CHECK(input == "int temp = 10 ;\n"); 67 | } 68 | 69 | SECTION("ToLowercase") 70 | { 71 | std::string str = "TEMP !@#$%^&*()-= temp"; 72 | 73 | ToLowercase(str); 74 | CHECK(str == "temp !@#$%^&*()-= temp"); 75 | } 76 | 77 | SECTION("Replace") 78 | { 79 | std::string str = "TEMP string is here!"; 80 | 81 | SECTION("First") 82 | { 83 | ReplaceFirst(str, "", "test"); 84 | CHECK(str == "TEMP string is here!"); 85 | 86 | ReplaceFirst(str, "TEMP", "temp"); 87 | CHECK(str == "temp string is here!"); 88 | 89 | ReplaceFirst(str, "e", "o"); 90 | CHECK(str == "tomp string is here!"); 91 | 92 | ReplaceFirst(str, "tomp ", ""); 93 | CHECK(str == "string is here!"); 94 | } 95 | 96 | SECTION("Last") 97 | { 98 | ReplaceLast(str, "", "test"); 99 | CHECK(str == "TEMP string is here!"); 100 | 101 | ReplaceLast(str, "here", "ring"); 102 | CHECK(str == "TEMP string is ring!"); 103 | 104 | ReplaceLast(str, "ing", "oot"); 105 | CHECK(str == "TEMP string is root!"); 106 | 107 | ReplaceLast(str, "root", ""); 108 | CHECK(str == "TEMP string is !"); 109 | } 110 | 111 | SECTION("All") 112 | { 113 | ReplaceAll(str, "", "test"); 114 | CHECK(str == "TEMP string is here!"); 115 | 116 | ReplaceAll(str, " ", "_"); 117 | CHECK(str == "TEMP_string_is_here!"); 118 | 119 | ReplaceAll(str, "EMP", ""); 120 | CHECK(str == "T_string_is_here!"); 121 | 122 | ReplaceAll(str, "e", "ee"); 123 | CHECK(str == "T_string_is_heeree!"); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /gemcutter/Rendering/Text.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Text.h" 3 | #include "gemcutter/Application/Logging.h" 4 | #include "gemcutter/Resource/Font.h" 5 | 6 | namespace gem 7 | { 8 | Text::Text(Entity& _owner) 9 | : Renderable(_owner) 10 | { 11 | } 12 | 13 | Text::Text(Entity& _owner, Font::Ptr _font) 14 | : Renderable(_owner) 15 | , font(std::move(_font)) 16 | { 17 | } 18 | 19 | Text::Text(Entity& _owner, std::string _string) 20 | : Renderable(_owner) 21 | , string(std::move(_string)) 22 | { 23 | } 24 | 25 | Text::Text(Entity& _owner, Material::Ptr material) 26 | : Renderable(_owner, std::move(material)) 27 | { 28 | } 29 | 30 | Text::Text(Entity& _owner, Font::Ptr _font, std::string _string, Material::Ptr material) 31 | : Renderable(_owner, std::move(material)) 32 | , font(std::move(_font)) 33 | , string(std::move(_string)) 34 | { 35 | } 36 | 37 | unsigned Text::GetNumLines() const 38 | { 39 | if (string.empty()) 40 | { 41 | return 0; 42 | } 43 | 44 | unsigned count = 1; 45 | const char* ptr = string.data(); 46 | while (true) 47 | { 48 | switch (*ptr) 49 | { 50 | case '\n': 51 | ++count; 52 | break; 53 | 54 | case '\0': 55 | return count; 56 | } 57 | 58 | ++ptr; 59 | } 60 | } 61 | 62 | float Text::GetLineWidth(unsigned line) const 63 | { 64 | ASSERT(line != 0, "'line' must be greater than 0."); 65 | ASSERT(font != nullptr, "Must have a Font attached to query width."); 66 | 67 | if (line > GetNumLines()) 68 | { 69 | return 0.0f; 70 | } 71 | else if (line == 1) 72 | { 73 | size_t loc = string.find('\n'); 74 | if (loc == std::string::npos) 75 | { 76 | return static_cast(font->GetStringWidth(string)); 77 | } 78 | else 79 | { 80 | return static_cast(font->GetStringWidth({ string.begin(), string.begin() + loc })); 81 | } 82 | } 83 | else 84 | { 85 | size_t start = std::string::npos; 86 | unsigned count = 1; 87 | for (unsigned i = 0; i < string.size(); ++i) 88 | { 89 | if (string[i] == '\n') 90 | { 91 | count++; 92 | if (count == line) 93 | { 94 | start = i; 95 | break; 96 | } 97 | } 98 | } 99 | 100 | size_t end = string.size(); 101 | for (size_t i = start + 1; i < string.size(); ++i) 102 | { 103 | if (string[i] == '\n') 104 | { 105 | end = i; 106 | break; 107 | } 108 | } 109 | 110 | if (start == std::string::npos) 111 | { 112 | return 0.0f; 113 | } 114 | 115 | return static_cast(font->GetStringWidth({ string.begin() + start, string.begin() + end })); 116 | } 117 | } 118 | } 119 | 120 | REFLECT_COMPONENT(gem::Text, gem::Renderable) 121 | MEMBERS { 122 | REF_MEMBER(font) 123 | REF_MEMBER(string) 124 | REF_MEMBER(centeredX) 125 | REF_MEMBER(centeredY) 126 | REF_MEMBER(kerning, description("Extra spacing between letters.")) 127 | } 128 | REF_END; 129 | -------------------------------------------------------------------------------- /gemcutter/Rendering/ParticleEmitter.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | #include "gemcutter/Resource/ParticleBuffer.h" 5 | #include "gemcutter/Rendering/Renderable.h" 6 | #include "gemcutter/Resource/ParticleFunctor.h" 7 | #include "gemcutter/Resource/UniformBuffer.h" 8 | #include "gemcutter/Utilities/Random.h" 9 | 10 | namespace gem 11 | { 12 | struct vec2; 13 | struct vec3; 14 | 15 | // Spawns particles and updates their positions. 16 | // Can be extended with ParticleFunctors to add custom functionality. 17 | class ParticleEmitter : public Renderable 18 | { 19 | public: 20 | ParticleEmitter(Entity& owner, unsigned maxParticles = 100); 21 | ParticleEmitter(Entity& owner, Material::Ptr material, unsigned maxParticles = 100); 22 | 23 | enum class Type : uint16_t 24 | { 25 | Omni, 26 | Box 27 | }; 28 | 29 | void Warmup(float time, float step = 0.25f); 30 | void Update(); 31 | 32 | unsigned GetNumAliveParticles() const; 33 | unsigned GetNumMaxParticles() const; 34 | 35 | // Sets the Size behaviour of particles if no functors manipulate size. 36 | void SetSizeStartEnd(const vec2& start, const vec2& end); 37 | void SetSizeStartEnd(const vec2& constant); 38 | // Sets the color behaviour of particles if no functors manipulate color. 39 | void SetColorStartEnd(const vec3& start, const vec3& end); 40 | void SetColorStartEnd(const vec3& constant); 41 | // Sets the alpha behaviour of particles if no functors manipulate alpha. 42 | void SetAlphaStartEnd(float start, float end); 43 | void SetAlphaStartEnd(float constant); 44 | 45 | // Sets the simulation space of the particle system. 46 | // When local, the particles will translate and move with the entity. 47 | // Particles will be transformed into the new space to avoid a visual pop. 48 | void SetLocalSpace(bool isLocal); 49 | bool IsLocalSpace() const; 50 | 51 | const UniformBuffer& GetBuffer() const; 52 | UniformBuffer& GetBuffer(); 53 | 54 | // Custom Particle Functors can be added her to customize the behaviour of the emitter. 55 | FunctorList functors; 56 | 57 | Range velocity{ 0.5f, 1.0f }; 58 | Range lifetime{ 5.0f, 10.0f }; 59 | Type spawnType = Type::Omni; 60 | float spawnPerSecond = 10.0f; 61 | 62 | // Used for box spawning. 63 | Range axisX{ -1.0f, 1.0f }; 64 | Range axisY{ -1.0f, 1.0f }; 65 | Range axisZ{ -1.0f, 1.0f }; 66 | 67 | // Used for Omni-directional spawning. 68 | Range radius{ 0.0f, 1.0f }; 69 | 70 | // When true, the emitter will cease to update, but will still be rendered. 71 | bool isPaused = false; 72 | 73 | private: 74 | void UpdateInternal(float deltaTime); 75 | void InitUniformBuffer(); 76 | 77 | ParticleBuffer data; 78 | 79 | float numToSpawn = 0.0f; 80 | bool requiresAgeRatio = false; 81 | bool localSpace = false; 82 | unsigned maxParticles = 0; 83 | unsigned numCurrentParticles = 0; 84 | 85 | UniformBuffer::Ptr particleParameters; 86 | 87 | public: 88 | PRIVATE_MEMBER(ParticleEmitter, localSpace); 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /gemcutter/Resource/Model.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "Model.h" 3 | #include "gemcutter/Application/Logging.h" 4 | #include "gemcutter/Utilities/ScopeGuard.h" 5 | 6 | namespace gem 7 | { 8 | bool Model::Load(std::string_view filePath) 9 | { 10 | // Load binary file. 11 | FILE* binaryFile = fopen(filePath.data(), "rb"); 12 | if (binaryFile == nullptr) 13 | { 14 | Error("Model: ( %s )\nUnable to open file.", filePath.data()); 15 | return false; 16 | } 17 | defer { fclose(binaryFile); }; 18 | 19 | unsigned numVertices; 20 | fread(&minBounds, sizeof(minBounds), 1, binaryFile); 21 | fread(&maxBounds, sizeof(maxBounds), 1, binaryFile); 22 | fread(&hasUvs, sizeof(hasUvs), 1, binaryFile); 23 | fread(&hasNormals, sizeof(hasNormals), 1, binaryFile); 24 | fread(&hasTangents, sizeof(hasTangents), 1, binaryFile); 25 | fread(&numVertices, sizeof(numVertices), 1, binaryFile); 26 | 27 | // Determine mesh properties. 28 | unsigned bufferSize = numVertices * 3; 29 | int stride = sizeof(float) * 3; 30 | if (hasUvs) 31 | { 32 | bufferSize += numVertices * 2; 33 | stride += sizeof(float) * 2; 34 | } 35 | if (hasNormals) 36 | { 37 | bufferSize += numVertices * 3; 38 | stride += sizeof(float) * 3; 39 | } 40 | if (hasTangents) 41 | { 42 | bufferSize += numVertices * 4; 43 | stride += sizeof(float) * 4; 44 | } 45 | 46 | array = VertexArray::MakeNew(); 47 | auto buffer = VertexBuffer::MakeNew(static_cast(sizeof(float) * bufferSize), BufferUsage::Static, VertexBufferType::Data); 48 | 49 | { // Read the data buffer from the file. 50 | auto mapping = buffer->MapBuffer(VertexAccess::WriteOnly); 51 | fread(mapping.GetPtr(), sizeof(float), bufferSize, binaryFile); 52 | } 53 | 54 | // Enable vertex attribute streams. 55 | VertexStream stream { 56 | .buffer = std::move(buffer), 57 | .bindingUnit = 0, 58 | .format = VertexFormat::Vec3, 59 | .normalized = false, 60 | .startOffset = 0, 61 | .stride = static_cast(stride) 62 | }; 63 | 64 | array->AddStream(stream); 65 | stream.startOffset += sizeof(float) * 3; 66 | 67 | if (hasUvs) 68 | { 69 | stream.bindingUnit = 1; 70 | stream.format = VertexFormat::Vec2; 71 | 72 | array->AddStream(stream); 73 | stream.startOffset += sizeof(float) * 2; 74 | } 75 | if (hasNormals) 76 | { 77 | stream.bindingUnit = 2; 78 | stream.format = VertexFormat::Vec3; 79 | 80 | array->AddStream(stream); 81 | stream.startOffset += sizeof(float) * 3; 82 | } 83 | if (hasTangents) 84 | { 85 | stream.bindingUnit = 3; 86 | stream.format = VertexFormat::Vec4; 87 | 88 | array->AddStream(std::move(stream)); 89 | } 90 | 91 | array->SetVertexCount(numVertices); 92 | 93 | return true; 94 | } 95 | 96 | VertexArray::Ptr Model::GetArray() const 97 | { 98 | return array; 99 | } 100 | 101 | const vec3& Model::GetMinBounds() const 102 | { 103 | return minBounds; 104 | } 105 | 106 | const vec3& Model::GetMaxBounds() const 107 | { 108 | return maxBounds; 109 | } 110 | 111 | bool Model::HasUVs() const 112 | { 113 | return hasUvs; 114 | } 115 | 116 | bool Model::HasNormals() const 117 | { 118 | return hasNormals; 119 | } 120 | 121 | bool Model::HasTangents() const 122 | { 123 | return hasTangents; 124 | } 125 | } 126 | 127 | REFLECT_RESOURCE(gem::Model) REF_END; 128 | -------------------------------------------------------------------------------- /tests/EnumFlags.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace gem; 5 | 6 | enum class Values : uint16_t 7 | { 8 | One = 0b0001, 9 | Two = 0b0010, 10 | Three = 0b0100, 11 | Four = 0b1000 12 | }; 13 | 14 | static_assert(std::is_trivially_copyable_v>); 15 | static_assert(std::is_same_v::PrimitiveType, uint16_t>); 16 | 17 | TEST_CASE("EnumFlags") 18 | { 19 | auto flags = EnumFlags(); 20 | 21 | CHECK(!flags.Has(Values::One)); 22 | CHECK(!flags.Has(Values::Two)); 23 | CHECK(!flags.Has(Values::Three)); 24 | CHECK(!flags.Has(Values::Four)); 25 | CHECK(flags.Value() == 0); 26 | CHECK(flags == static_cast(0b0000)); 27 | 28 | SECTION("OR-ing Flags") 29 | { 30 | flags |= Values::One; 31 | CHECK(flags.Has(Values::One)); 32 | CHECK(!flags.Has(Values::Two)); 33 | CHECK(!flags.Has(Values::Three)); 34 | CHECK(!flags.Has(Values::Four)); 35 | CHECK(flags.Value() == 0b0001); 36 | CHECK(flags == static_cast(0b0001)); 37 | 38 | flags |= EnumFlags{Values::Two}; 39 | CHECK(flags.Has(Values::One)); 40 | CHECK(flags.Has(Values::Two)); 41 | CHECK(!flags.Has(Values::Three)); 42 | CHECK(!flags.Has(Values::Four)); 43 | CHECK(flags.Value() == 0b0011); 44 | CHECK(flags == static_cast(0b0011)); 45 | 46 | flags |= 0b0100; 47 | CHECK(flags.Has(Values::One)); 48 | CHECK(flags.Has(Values::Two)); 49 | CHECK(flags.Has(Values::Three)); 50 | CHECK(!flags.Has(Values::Four)); 51 | CHECK(flags.Value() == 0b0111); 52 | CHECK(flags == static_cast(0b0111)); 53 | 54 | flags |= Values::Four; 55 | CHECK(flags.Has(0b0001)); 56 | CHECK(flags.Has(0b0010)); 57 | CHECK(flags.Has(0b0100)); 58 | CHECK(flags.Has(0b1000)); 59 | CHECK(flags.Value() == 0b1111); 60 | CHECK(flags == static_cast(0b1111)); 61 | } 62 | 63 | SECTION("AND-ing Flags") 64 | { 65 | flags = 0b1100; 66 | CHECK(!flags.Has(Values::One)); 67 | CHECK(!flags.Has(Values::Two)); 68 | CHECK(flags.Has(Values::Three)); 69 | CHECK(flags.Has(Values::Four)); 70 | CHECK(flags.Value() == 0b1100); 71 | CHECK(flags == static_cast(0b1100)); 72 | 73 | SECTION("Test 1") 74 | { 75 | flags &= Values::Three; 76 | CHECK(!flags.Has(Values::One)); 77 | CHECK(!flags.Has(Values::Two)); 78 | CHECK(flags.Has(Values::Three)); 79 | CHECK(!flags.Has(Values::Four)); 80 | CHECK(flags.Value() == 0b0100); 81 | CHECK(flags == static_cast(0b0100)); 82 | } 83 | 84 | SECTION("Test 2") 85 | { 86 | flags &= 0b1111; 87 | CHECK(!flags.Has(Values::One)); 88 | CHECK(!flags.Has(Values::Two)); 89 | CHECK(flags.Has(Values::Three)); 90 | CHECK(flags.Has(Values::Four)); 91 | CHECK(flags.Value() == 0b1100); 92 | CHECK(flags == static_cast(0b1100)); 93 | } 94 | 95 | SECTION("Test 3") 96 | { 97 | flags = flags & 0b1011; 98 | CHECK(!flags.Has(Values::One)); 99 | CHECK(!flags.Has(Values::Two)); 100 | CHECK(!flags.Has(Values::Three)); 101 | CHECK(flags.Has(Values::Four)); 102 | CHECK(flags.Value() == 0b1000); 103 | CHECK(flags == static_cast(0b1000)); 104 | } 105 | } 106 | 107 | flags.Clear(); 108 | CHECK(!flags.Has(Values::One)); 109 | CHECK(!flags.Has(Values::Two)); 110 | CHECK(!flags.Has(Values::Three)); 111 | CHECK(!flags.Has(Values::Four)); 112 | CHECK(flags.Value() == 0b0000); 113 | CHECK(flags == static_cast(0b0000)); 114 | } 115 | -------------------------------------------------------------------------------- /gemcutter/Resource/Resource.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Application/FileSystem.h" 4 | #include "gemcutter/Utilities/StdExt.h" 5 | #include "gemcutter/Utilities/String.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace gem 14 | { 15 | // Base class for all resources loadable from a path on disk. 16 | class ResourceBase 17 | { 18 | public: 19 | const std::string& GetPath() const { return path; } 20 | 21 | protected: 22 | ResourceBase() = default; 23 | 24 | std::string path; 25 | 26 | public: 27 | PRIVATE_MEMBER(ResourceBase, path); 28 | }; 29 | 30 | // Provides an interface for cached loading of the specified asset. 31 | template 32 | class Resource : public ResourceBase 33 | { 34 | protected: 35 | Resource() = default; 36 | 37 | public: 38 | Resource(const Resource&) = delete; 39 | Resource& operator=(const Resource&) = delete; 40 | 41 | // Loads an asset from the specified file, or retrieves it from the cache. 42 | // Calls the derived class's Load() with the normalized file path. 43 | static std::shared_ptr Load(std::string filePath) 44 | { 45 | NormalizeFilePath(filePath); 46 | 47 | // Search for the cached asset. 48 | if (std::shared_ptr ptr = FindNormalized(filePath)) 49 | { 50 | return ptr; 51 | } 52 | 53 | // Create the new asset. 54 | auto resourcePtr = std::make_shared(); 55 | resourcePtr->path = filePath; 56 | if (!resourcePtr->Load(filePath)) 57 | { 58 | return nullptr; 59 | } 60 | 61 | // Add new asset to cache. 62 | resourceCache.insert(std::make_pair(std::move(filePath), resourcePtr)); 63 | 64 | return resourcePtr; 65 | } 66 | 67 | // Searches for a loaded asset previously loaded from the specified file path. 68 | static std::shared_ptr Find(std::string filePath) 69 | { 70 | NormalizeFilePath(filePath); 71 | return FindNormalized(filePath); 72 | } 73 | 74 | // Clears the asset cache. An asset will need to load from file again after this call. 75 | static void UnloadAll() 76 | { 77 | resourceCache.clear(); 78 | } 79 | 80 | private: 81 | // Converts the path to a standard format for cache lookups. 82 | static void NormalizeFilePath(std::string& filePath) 83 | { 84 | ToLowercase(filePath); 85 | 86 | if (!HasExtension(filePath)) 87 | { 88 | filePath += Asset::Extension; 89 | } 90 | } 91 | 92 | static std::shared_ptr FindNormalized(std::string_view filePath) 93 | { 94 | auto itr = resourceCache.find(filePath); 95 | if (itr == resourceCache.end()) 96 | { 97 | return nullptr; 98 | } 99 | else 100 | { 101 | return itr->second; 102 | } 103 | } 104 | 105 | static inline std::unordered_map, string_hash, std::equal_to<>> resourceCache; 106 | }; 107 | 108 | // Helper function to load an asset. 109 | template 110 | std::shared_ptr Load(std::string filePath) 111 | { 112 | static_assert(std::is_base_of_v, Asset>); 113 | return Resource::Load(std::move(filePath)); 114 | } 115 | 116 | // Helper function to unload all managed instances of an asset. 117 | template 118 | void UnloadAll() 119 | { 120 | static_assert(std::is_base_of_v, Asset>); 121 | Resource::UnloadAll(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /gemcutter/Application/Event.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gem 9 | { 10 | // The base class for event objects. 11 | class EventBase 12 | { 13 | friend class EventQueueSingleton; 14 | public: 15 | virtual ~EventBase() = default; 16 | // Returns true if at least one listener responds to this event. 17 | virtual bool HasListeners() const = 0; 18 | 19 | private: 20 | // Notifies all listeners of the derived class event by invoking their callback functions. 21 | virtual void Raise() const = 0; 22 | }; 23 | 24 | // Invokes a callback function when an instance of the respective event is dispatched. 25 | // The template parameter must be the event object that the listener will subscribe to. 26 | // For example: Listener OnPlayerDeath(&myFunc); 27 | template 28 | class Listener 29 | { 30 | template friend class Event; 31 | static_assert(std::is_base_of_v, "Template argument must inherit from Event."); 32 | public: 33 | using EventFunc = void(const EventObj&); 34 | 35 | // Subscribes to the event. 36 | Listener(); 37 | Listener(std::function callback); 38 | 39 | // Unsubscribes from the event. 40 | ~Listener(); 41 | 42 | Listener& operator=(std::function callback); 43 | 44 | private: 45 | std::function func; 46 | }; 47 | 48 | // You can inherit from this class to create your own custom events. 49 | // The template parameter must be the derived class. For example: 50 | // class PlayerDeath : public Event {}; 51 | template 52 | class Event : public EventBase 53 | { 54 | friend Listener; 55 | public: 56 | ~Event() override = default; 57 | 58 | // Returns true if at least one listener responds to this event. 59 | bool HasListeners() const final override; 60 | static bool HasListenersStatic(); 61 | 62 | // Returns a vector of all objects currently listening for this type of event. 63 | static const auto& GetListenersStatic() { return listeners; } 64 | 65 | private: 66 | void Raise() const final override; 67 | 68 | // Subscribes a listener to be notified from this type of event. 69 | static void Subscribe(Listener& listener); 70 | // Stops a listener from being notified callbacks from this type of event. 71 | static void Unsubscribe(Listener& listener); 72 | 73 | // All Listeners of the derived class event. 74 | static std::vector*> listeners; 75 | }; 76 | 77 | // This singleton class handles queuing and distribution of events. 78 | extern class EventQueueSingleton EventQueue; 79 | class EventQueueSingleton 80 | { 81 | public: 82 | // Add a new event to the queue. 83 | // The event will be distributed to all listeners of its type when Dispatch() is called. 84 | void Push(std::unique_ptr e); 85 | 86 | // Immediately distributes an event across listeners. It is not added to the queue. 87 | void Dispatch(const EventBase& e) const; 88 | 89 | // Distributes the queue of events across listeners. Nested calls are not allowed. 90 | void Dispatch(); 91 | 92 | // Returns true if the event queue is currently being processed and distributed. 93 | bool IsDispatching() const; 94 | 95 | private: 96 | std::queue> eventQueue; 97 | 98 | bool dispatching = false; 99 | }; 100 | } 101 | 102 | #include "Event.inl" 103 | -------------------------------------------------------------------------------- /gemcutter/Sound/SoundSystem.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #include "SoundSystem.h" 3 | #include "gemcutter/Application/Logging.h" 4 | #include "gemcutter/Entity/Entity.h" 5 | #include "gemcutter/Math/Matrix.h" 6 | #include "gemcutter/Math/Vector.h" 7 | #include "gemcutter/Resource/Sound.h" 8 | #include "gemcutter/Sound/SoundListener.h" 9 | #include "gemcutter/Sound/SoundSource.h" 10 | 11 | #include 12 | 13 | namespace gem 14 | { 15 | SoundSystemSingleton SoundSystem; 16 | 17 | SoLoud::Soloud audioEngine; 18 | 19 | bool SoundSystemSingleton::Init(unsigned bufferSize) 20 | { 21 | ASSERT(bufferSize > 0, "'bufferSize' must be greater than zero."); 22 | 23 | using SoLoud::Soloud; 24 | SoLoud::result result = audioEngine.init(Soloud::CLIP_ROUNDOFF, Soloud::AUTO, Soloud::AUTO, bufferSize); 25 | if (result != SoLoud::SOLOUD_ERRORS::SO_NO_ERROR) 26 | { 27 | Error("SoundSystem: %s", audioEngine.getErrorString(result)); 28 | return false; 29 | } 30 | 31 | initialized = true; 32 | return true; 33 | } 34 | 35 | bool SoundSystemSingleton::IsLoaded() const 36 | { 37 | return initialized; 38 | } 39 | 40 | void SoundSystemSingleton::Unload() 41 | { 42 | // Delete all resources. 43 | UnloadAll(); 44 | 45 | audioEngine.deinit(); 46 | 47 | muted = true; 48 | initialized = false; 49 | globalVolume = 1.0f; 50 | } 51 | 52 | void SoundSystemSingleton::SetGlobalVolume(float volume) 53 | { 54 | ASSERT(IsLoaded(), "SoundSystem must be initialized to call this function."); 55 | ASSERT(volume >= 0.0f, "'volume' must be non negative."); 56 | 57 | if (!muted) 58 | { 59 | audioEngine.setGlobalVolume(volume); 60 | } 61 | 62 | globalVolume = volume; 63 | } 64 | 65 | float SoundSystemSingleton::GetGlobalVolume() const 66 | { 67 | return globalVolume; 68 | } 69 | 70 | void SoundSystemSingleton::Mute() 71 | { 72 | ASSERT(IsLoaded(), "SoundSystem must be initialized to call this function."); 73 | 74 | audioEngine.setGlobalVolume(0.0f); 75 | muted = true; 76 | } 77 | 78 | bool SoundSystemSingleton::IsMuted() const 79 | { 80 | return muted; 81 | } 82 | 83 | void SoundSystemSingleton::Unmute() 84 | { 85 | ASSERT(IsLoaded(), "SoundSystem must be initialized to call this function."); 86 | 87 | audioEngine.setGlobalVolume(globalVolume); 88 | muted = false; 89 | } 90 | 91 | void SoundSystemSingleton::Update() 92 | { 93 | ASSERT(IsLoaded(), "SoundSystem must be initialized to call this function."); 94 | 95 | if (muted) 96 | { 97 | return; 98 | } 99 | 100 | Entity::Ptr listener = SoundListener::GetListener(); 101 | if (!listener) 102 | { 103 | return; 104 | } 105 | 106 | /* Update listener */ 107 | const mat4 pose = listener->GetWorldTransform(); 108 | audioEngine.set3dListenerParameters( 109 | pose.data[mat4::TransX], 110 | pose.data[mat4::TransY], 111 | pose.data[mat4::TransZ], 112 | -pose.data[mat4::ForwardX], 113 | -pose.data[mat4::ForwardY], 114 | -pose.data[mat4::ForwardZ], 115 | pose.data[mat4::UpX], 116 | pose.data[mat4::UpY], 117 | pose.data[mat4::UpZ], 118 | 0.0f, 119 | 0.0f, 120 | 0.0f 121 | ); 122 | 123 | /* Update all sounds */ 124 | for (auto& source : All()) 125 | { 126 | if (!source.IsPlaying()) 127 | continue; 128 | 129 | vec3 position = source.owner.GetWorldPosition(); 130 | audioEngine.set3dSourceParameters( 131 | source.hSound, 132 | position.x, 133 | position.y, 134 | position.z 135 | ); 136 | } 137 | 138 | audioEngine.update3dAudio(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /tests/Meta.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace gem; 6 | 7 | namespace types 8 | { 9 | class MyClass {}; 10 | struct MyStruct {}; 11 | enum class StrongEnum {}; 12 | enum WeakEnum {}; 13 | } 14 | 15 | TEST_CASE("Metaprogramming") 16 | { 17 | SECTION("all_of") 18 | { 19 | CHECK(meta::all_of_v<>); 20 | 21 | CHECK(meta::all_of_v); 22 | CHECK(meta::all_of_v); 23 | CHECK(meta::all_of_v); 24 | 25 | CHECK(!meta::all_of_v); 26 | CHECK(!meta::all_of_v); 27 | CHECK(!meta::all_of_v); 28 | 29 | CHECK(!meta::all_of_v); 30 | CHECK(!meta::all_of_v); 31 | CHECK(!meta::all_of_v); 32 | CHECK(!meta::all_of_v); 33 | } 34 | 35 | SECTION("any_of") 36 | { 37 | CHECK(!meta::any_of_v<>); 38 | 39 | CHECK(meta::any_of_v); 40 | CHECK(meta::any_of_v); 41 | CHECK(meta::any_of_v); 42 | 43 | CHECK(!meta::any_of_v); 44 | CHECK(!meta::any_of_v); 45 | CHECK(!meta::any_of_v); 46 | 47 | CHECK(!meta::any_of_v); 48 | CHECK(meta::any_of_v); 49 | CHECK(meta::any_of_v); 50 | CHECK(meta::any_of_v); 51 | } 52 | 53 | SECTION("none_of") 54 | { 55 | CHECK(meta::none_of_v<>); 56 | 57 | CHECK(!meta::none_of_v); 58 | CHECK(!meta::none_of_v); 59 | CHECK(!meta::none_of_v); 60 | 61 | CHECK(meta::none_of_v); 62 | CHECK(meta::none_of_v); 63 | CHECK(meta::none_of_v); 64 | 65 | CHECK(meta::none_of_v); 66 | CHECK(!meta::none_of_v); 67 | CHECK(!meta::none_of_v); 68 | CHECK(!meta::none_of_v); 69 | } 70 | 71 | SECTION("is_any_of") 72 | { 73 | CHECK(!meta::is_any_of_v); 74 | 75 | CHECK(meta::is_any_of_v); 76 | CHECK(meta::is_any_of_v); 77 | 78 | CHECK(!meta::is_any_of_v); 79 | CHECK(!meta::is_any_of_v); 80 | CHECK(!meta::is_any_of_v); 81 | } 82 | 83 | SECTION("is_none_of") 84 | { 85 | CHECK(meta::is_none_of_v); 86 | 87 | CHECK(meta::is_none_of_v); 88 | CHECK(meta::is_none_of_v); 89 | CHECK(meta::is_none_of_v); 90 | CHECK(meta::is_none_of_v); 91 | 92 | CHECK(!meta::is_none_of_v); 93 | CHECK(!meta::is_none_of_v); 94 | } 95 | 96 | SECTION("GetTypeName") 97 | { 98 | CHECK(GetTypeName() == "void"); 99 | CHECK(GetTypeName() == "void*"); 100 | CHECK(GetTypeName() == "int"); 101 | CHECK(GetTypeName() == "int"); 102 | CHECK(GetTypeName>() == "std::optional"); 103 | 104 | CHECK(GetTypeName() == "types::MyClass"); 105 | CHECK(GetTypeName() == "types::MyStruct"); 106 | CHECK(GetTypeName() == "types::StrongEnum"); 107 | CHECK(GetTypeName() == "types::WeakEnum"); 108 | 109 | CHECK(GetTypeName() == "types::MyClass"); 110 | CHECK(GetTypeName() == "types::MyStruct"); 111 | CHECK(GetTypeName() == "types::StrongEnum"); 112 | CHECK(GetTypeName() == "types::WeakEnum"); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Append to any IDE folders already set be calling scripts. 2 | set(PARENT_FOLDER ${CMAKE_FOLDER}) 3 | 4 | ## Catch ## 5 | set(CMAKE_FOLDER "${PARENT_FOLDER}/external/catch") 6 | add_library(catch INTERFACE) 7 | target_include_directories(catch INTERFACE catch/) 8 | target_compile_definitions(catch 9 | INTERFACE 10 | CATCH_CONFIG_FAST_COMPILE 11 | CATCH_CONFIG_DISABLE_EXCEPTIONS 12 | ) 13 | 14 | ## Dirent ## 15 | set(CMAKE_FOLDER "${PARENT_FOLDER}/external/dirent") 16 | add_library(dirent INTERFACE) 17 | target_include_directories(dirent INTERFACE dirent/) 18 | 19 | ## Freetype ## 20 | update_submodule("${CMAKE_CURRENT_LIST_DIR}/freetype") 21 | set(CMAKE_FOLDER "${PARENT_FOLDER}/external/freetype") 22 | set(FT_DISABLE_ZLIB ON CACHE BOOL "") 23 | set(FT_DISABLE_BZIP2 ON CACHE BOOL "") 24 | set(FT_DISABLE_PNG ON CACHE BOOL "") 25 | set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "") 26 | set(FT_DISABLE_BROTLI ON CACHE BOOL "") 27 | set(SKIP_INSTALL_ALL ON CACHE BOOL "") 28 | set(SKIP_PACKAGING_ALL ON CACHE BOOL "") 29 | cmake_policy(PUSH) 30 | set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Allow IPO 31 | add_subdirectory(freetype) 32 | cmake_policy(POP) 33 | 34 | ## GLEW ## 35 | update_submodule("${CMAKE_CURRENT_LIST_DIR}/glew") 36 | set(CMAKE_FOLDER "${PARENT_FOLDER}/external/glew") 37 | set(glew-cmake_BUILD_SHARED OFF CACHE BOOL "") 38 | set(glew-cmake_BUILD_STATIC ON CACHE BOOL "") 39 | set(ONLY_LIBS ON CACHE BOOL "") 40 | cmake_policy(PUSH) 41 | set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Allow IPO 42 | add_subdirectory(glew) 43 | cmake_policy(POP) 44 | 45 | ## Imgui ## 46 | if (ENABLE_IMGUI_SUPPORT OR ENABLE_IMGUI_DEV_SUPPORT) 47 | update_submodule("${CMAKE_CURRENT_LIST_DIR}/imgui") 48 | set(CMAKE_FOLDER "${PARENT_FOLDER}/external/imgui") 49 | add_library(imgui STATIC 50 | "imgui/backends/imgui_impl_win32.cpp" 51 | "imgui/backends/imgui_impl_opengl3.cpp" 52 | "imgui/misc/cpp/imgui_stdlib.cpp" 53 | "imgui/imgui.cpp" 54 | "imgui/imgui_demo.cpp" 55 | "imgui/imgui_draw.cpp" 56 | "imgui/imgui_tables.cpp" 57 | "imgui/imgui_widgets.cpp" 58 | ) 59 | target_include_directories(imgui PUBLIC imgui/) 60 | target_compile_definitions(imgui PUBLIC IMGUI_ENABLED) 61 | sf_target_compile_disable_exceptions(imgui) 62 | endif() 63 | 64 | ## Loupe ## 65 | update_submodule("${CMAKE_CURRENT_LIST_DIR}/loupe") 66 | set(CMAKE_FOLDER "${PARENT_FOLDER}/external/loupe") 67 | add_subdirectory(loupe) 68 | 69 | ## SOIL2 ## 70 | update_submodule("${CMAKE_CURRENT_LIST_DIR}/soil2") 71 | set(CMAKE_FOLDER "${PARENT_FOLDER}/external/soil2") 72 | set(SOIL2_BUILD_TESTS OFF CACHE BOOL "") 73 | add_subdirectory(soil2) 74 | 75 | ## SoLoud ## 76 | update_submodule("${CMAKE_CURRENT_LIST_DIR}/soloud") 77 | set(CMAKE_FOLDER "${PARENT_FOLDER}/external/soloud") 78 | set(SOLOUD_BACKEND_ALSA OFF CACHE BOOL "") 79 | set(SOLOUD_BACKEND_COREAUDIO OFF CACHE BOOL "") 80 | set(SOLOUD_BACKEND_NULL OFF CACHE BOOL "") 81 | set(SOLOUD_BACKEND_OPENSLES OFF CACHE BOOL "") 82 | set(SOLOUD_BACKEND_SDL2 OFF CACHE BOOL "") 83 | set(SOLOUD_BACKEND_WASAPI OFF CACHE BOOL "") 84 | set(SOLOUD_BACKEND_WINMM OFF CACHE BOOL "") 85 | set(SOLOUD_BACKEND_XAUDIO2 ON CACHE BOOL "") 86 | set(SOLOUD_BUILD_DEMOS OFF CACHE BOOL "") 87 | set(SOLOUD_C_API OFF CACHE BOOL "") 88 | set(SOLOUD_DYNAMIC OFF CACHE BOOL "") 89 | set(SOLOUD_GENERATE_GLUE OFF CACHE BOOL "") 90 | set(SOLOUD_INSTALL OFF CACHE BOOL "") 91 | set(SOLOUD_STATIC ON CACHE BOOL "") 92 | cmake_policy(PUSH) 93 | set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) # Allow IPO 94 | add_subdirectory(soloud/contrib) 95 | cmake_policy(POP) 96 | target_include_directories(soloud INTERFACE soloud/include/) 97 | -------------------------------------------------------------------------------- /tools/AssetManager/FileCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | using System.Collections.Generic; 3 | using System; 4 | using System.IO; 5 | using System.Xml.Serialization; 6 | using System.Xml.Schema; 7 | using System.Xml; 8 | 9 | namespace AssetManager 10 | { 11 | public struct TimeStamps 12 | { 13 | // Last-write time in ticks for the asset. 14 | public long fileTime; 15 | // Last-write time in ticks for the metadata file. 16 | public long metaTime; 17 | }; 18 | 19 | [XmlRoot("Cache")] 20 | public class FileCacheDictionary : Dictionary, IXmlSerializable 21 | { 22 | public XmlSchema GetSchema() { return null; } 23 | 24 | public void ReadXml(XmlReader reader) 25 | { 26 | if (reader.IsEmptyElement) 27 | return; 28 | 29 | reader.Read(); 30 | while (reader.NodeType != XmlNodeType.EndElement) 31 | { 32 | string key = reader.GetAttribute("File"); 33 | string fileTime = reader.GetAttribute("LastFileWriteTime"); 34 | string metaTime = reader.GetAttribute("LastMetaWriteTime"); 35 | 36 | TimeStamps times; 37 | if (long.TryParse(fileTime, out times.fileTime) && 38 | long.TryParse(metaTime, out times.metaTime)) 39 | { 40 | Add(key, times); 41 | } 42 | 43 | reader.Read(); 44 | } 45 | } 46 | 47 | public void WriteXml(XmlWriter writer) 48 | { 49 | foreach (var key in Keys) 50 | { 51 | writer.WriteStartElement("Cache"); 52 | writer.WriteAttributeString("File", key.ToString()); 53 | writer.WriteAttributeString("LastFileWriteTime", this[key].fileTime.ToString()); 54 | writer.WriteAttributeString("LastMetaWriteTime", this[key].metaTime.ToString()); 55 | writer.WriteEndElement(); 56 | } 57 | } 58 | } 59 | 60 | // Keeps track of the last time that files were updated. 61 | public class FileCache 62 | { 63 | public FileCacheDictionary cache = new FileCacheDictionary(); 64 | 65 | public bool ShouldPack(string file) 66 | { 67 | if (!cache.ContainsKey(file)) 68 | return true; 69 | 70 | TimeStamps item; 71 | item.fileTime = GetLastWriteTime(file); 72 | item.metaTime = GetLastWriteTime(file + ".meta"); 73 | 74 | var cached = cache[file]; 75 | return item.fileTime != cached.fileTime || item.metaTime != cached.metaTime; 76 | } 77 | 78 | public void Add(string file) 79 | { 80 | TimeStamps item; 81 | item.fileTime = GetLastWriteTime(file); 82 | item.metaTime = GetLastWriteTime(file + ".meta"); 83 | 84 | if (cache.ContainsKey(file)) 85 | { 86 | cache[file] = item; 87 | } 88 | else 89 | { 90 | cache.Add(file, item); 91 | } 92 | } 93 | 94 | public static FileCache Load() 95 | { 96 | FileCache config; 97 | try 98 | { 99 | using (var myFileStream = File.OpenRead("Files.cache")) 100 | { 101 | var mySerializer = new XmlSerializer(typeof(FileCache)); 102 | config = (FileCache)mySerializer.Deserialize(myFileStream); 103 | } 104 | } 105 | catch (Exception) 106 | { 107 | config = new FileCache(); 108 | } 109 | 110 | return config; 111 | } 112 | 113 | public void Save() 114 | { 115 | if (File.Exists("Files.cache")) 116 | File.Delete("Files.cache"); 117 | 118 | var serializer = new XmlSerializer(typeof(FileCache)); 119 | using (var stream = File.OpenWrite("Files.cache")) 120 | { 121 | serializer.Serialize(stream, this); 122 | } 123 | } 124 | 125 | public void Clear() 126 | { 127 | cache.Clear(); 128 | } 129 | 130 | private long GetLastWriteTime(string file) 131 | { 132 | if (!File.Exists(file)) 133 | return 0; 134 | 135 | var fileInfo = new FileInfo(file); 136 | 137 | return fileInfo.LastWriteTime.Ticks; 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /gemcutter/Entity/Hierarchy.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Entity/Entity.h" 4 | #include "gemcutter/Math/Matrix.h" 5 | #include "gemcutter/Math/Quaternion.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace gem 11 | { 12 | // A Tag identifying an Entity with no parents. 13 | struct HierarchyRoot : public Tag {}; 14 | 15 | // Allows Entities to be organized in a tree structure. 16 | // Can also propagate transformations from parent to child. 17 | class Hierarchy : public Component 18 | { 19 | public: 20 | Hierarchy(Entity& owner); 21 | ~Hierarchy(); 22 | 23 | // Attaches a child. 24 | void AddChild(Entity::Ptr entity); 25 | 26 | // Detaches the given child. 27 | void RemoveChild(Entity& entity); 28 | 29 | // Returns true if the given Entity is a direct child of this one. 30 | bool IsChild(const Entity& child) const; 31 | 32 | // Returns true if this Entity is a direct child of the given Entity. 33 | bool IsChildOf(const Entity& parent) const; 34 | 35 | // Returns true if the given Entity is a descendant of this one (at any depth). 36 | bool IsDescendant(const Entity& descendant) const; 37 | 38 | // Returns true if this Entity is a descendant of the given Entity (at any depth). 39 | bool IsDescendantOf(const Entity& ancestor) const; 40 | 41 | // Detaches all children. 42 | void ClearChildren(); 43 | 44 | // Detaches from the parent, if we have one. 45 | void DetachFromParent(); 46 | 47 | // Finds the root of the hierarchy, which could be this Entity. 48 | const Entity& GetRoot() const; 49 | 50 | // Finds the root of the hierarchy, which could be this Entity. 51 | Entity& GetRoot(); 52 | 53 | // Returns the parent we are attached to, if we have one. 54 | Entity::Ptr GetParent() const; 55 | 56 | // Returns the parent's hierarchy component, if there is one. 57 | Hierarchy* GetParentHierarchy() const; 58 | 59 | // Returns the number of children currently held by this Entity. 60 | std::size_t GetNumChildren() const; 61 | 62 | // Gets the list of children held by this Entity. 63 | const auto& GetChildren() const { return children; } 64 | auto& GetChildren() { return children; } 65 | 66 | // Iterates over all direct children, and optionally all decedents. 67 | // Children should not be added or removed in during this function. 68 | template 69 | void ForEachChild(this Self& self, bool recursive, Functor&& Func); 70 | 71 | // Gets the depth of this Entity in the hierarchy. The root is always depth 0. 72 | // Direct children of the root are at depth 1, and so on. 73 | unsigned GetDepth() const; 74 | 75 | // Returns true if this Entity doesn't have a parent. 76 | bool IsRoot() const; 77 | 78 | // Returns true if this Entity doesn't have any children. 79 | bool IsLeaf() const; 80 | 81 | // Creates and returns a new child Entity. 82 | Entity::Ptr CreateChild(); 83 | Entity::Ptr CreateChild(std::string name); 84 | 85 | // Returns the world-space transformation of the Entity, accumulated from the root of the hierarchy. 86 | mat4 GetWorldTransform() const; 87 | 88 | // Returns the world-space rotation of the Entity, accumulated from the root of the hierarchy. 89 | quat GetWorldRotation() const; 90 | 91 | // Whether or not this Entity propagates its transformations downwards through the hierarchy. 92 | bool propagateTransform = true; 93 | 94 | private: 95 | Hierarchy* parentHierarchy = nullptr; 96 | Entity::WeakPtr parent; 97 | std::vector children; 98 | 99 | public: 100 | PRIVATE_MEMBER(Hierarchy, parentHierarchy); 101 | PRIVATE_MEMBER(Hierarchy, parent); 102 | PRIVATE_MEMBER(Hierarchy, children); 103 | }; 104 | } 105 | 106 | #include "Hierarchy.inl" 107 | -------------------------------------------------------------------------------- /gemcutter/Input/Input.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | #pragma once 3 | #include "gemcutter/Application/Event.h" 4 | #include "gemcutter/Math/Vector.h" 5 | 6 | struct tagMSG; 7 | typedef tagMSG MSG; 8 | 9 | namespace gem 10 | { 11 | enum class Key : uint16_t 12 | { 13 | Space = 0x20, 14 | PageUp, 15 | PageDown, 16 | End, 17 | Home, 18 | 19 | Left = 0x25, 20 | Up, 21 | Right, 22 | Down, 23 | 24 | LeftShift = 0xA0, 25 | RightShift, 26 | LeftControl, 27 | RightControl, 28 | LeftAlt, 29 | RightAlt, 30 | 31 | Insert = 0x2D, 32 | Delete = 0x2E, 33 | Backspace = 0x08, 34 | CapsLock = 0x14, 35 | Enter = 0x0D, 36 | Escape = 0x1B, 37 | Tab = 0x09, 38 | 39 | Semicolon = 0xBA, 40 | Plus, 41 | Comma, 42 | Minus, 43 | Period, 44 | ForwardSlash, 45 | Backtick, 46 | 47 | BracketLeft = 0xDB, 48 | BackSlash, 49 | BracketRight, 50 | Quote, 51 | 52 | Mute = 0xAD, 53 | VolumeDown, 54 | VolumeUp, 55 | NextTrack, 56 | PreviousTrack, 57 | MediaStop, 58 | MediaPlayPause, 59 | 60 | KeyBoard0 = 0x30, KeyBoard1, KeyBoard2, KeyBoard3, KeyBoard4, KeyBoard5, KeyBoard6, KeyBoard7, KeyBoard8, KeyBoard9, 61 | Numpad0 = 0x60, Numpad1, Numpad2, Numpad3, Numpad4, Numpad5, Numpad6, Numpad7, Numpad8, Numpad9, 62 | NumpadMultiply, 63 | NumpadPlus, 64 | NumpadSeparator, 65 | NumpadMinus, 66 | NumpadDecimal, 67 | NumpadDivide, 68 | 69 | A = 0x41, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 70 | F1 = 0x70, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, 71 | 72 | LeftWindows, 73 | RightWindows, 74 | 75 | MouseLeft = 0x101, 76 | MouseRight, 77 | MouseMiddle, 78 | 79 | NUM_KEYS 80 | }; 81 | 82 | // Singleton class for managing keyboard and mouse input. 83 | extern class InputSingleton Input; 84 | class InputSingleton 85 | { 86 | friend class ApplicationSingleton; 87 | public: 88 | bool IsDown(Key key) const; 89 | bool IsUp(Key key) const; 90 | 91 | int GetMouseX() const; 92 | int GetMouseY() const; 93 | vec2 GetMousePos() const; 94 | // Moves the system cursor to the given window position. 95 | // Can optionally emit a MouseMoved event. 96 | void SetMousePos(int posX, int posY, bool emitEvent); 97 | 98 | // Locks the cursor to the center of the window so that the player's 99 | // input will not cause the mouse to leave the game's focus. 100 | void SetCursorLock(bool lock); 101 | bool IsCursorLocked() const; 102 | 103 | private: 104 | bool Update(const MSG& msg); 105 | 106 | bool cursorLocked = false; 107 | bool keys[static_cast(Key::NUM_KEYS)] = { false }; 108 | int x = 0; 109 | int y = 0; 110 | }; 111 | 112 | // An event distributed by the engine when the mouse position has changed from the previous frame. 113 | struct MouseMoved : public Event 114 | { 115 | MouseMoved(const vec2& pos, const vec2& delta); 116 | 117 | // The new mouse position. 118 | const vec2 pos; 119 | // The different between this position and the last. 120 | const vec2 delta; 121 | }; 122 | 123 | // An event distributed by the engine when the mouse wheel is moved. 124 | struct MouseScrolled : public Event 125 | { 126 | MouseScrolled(int scroll); 127 | 128 | // Each positive integer indicates a single roll away from the user, and vice-versa. 129 | const int scroll; 130 | }; 131 | 132 | // An event distributed by the engine when a key is first pressed. 133 | struct KeyPressed : public Event 134 | { 135 | KeyPressed(Key key); 136 | 137 | const Key key; 138 | }; 139 | 140 | // An event distributed by the engine when a key is released after previously being down. 141 | struct KeyReleased : public Event 142 | { 143 | KeyReleased(Key key); 144 | 145 | const Key key; 146 | }; 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Gemcutter Logo](docs/Logo.png) 2 | 3 | # What is Gemcutter? 4 | Gemcutter is a free C++ game development framework for Windows. It features a powerful Entity-Component-System 5 | and fine-grain control of both 2D and 3D rendering. 6 | 7 | # Why use Gemcutter? 8 | Gemcutter provides a programmer-centric modern C++ workflow that is expressive yet succinct. 9 | 10 | The API is kept slim with a philosophy of minimizing boilerplate and unifying code paths. 11 | For example: `Widgets`, `Sprites`, `Meshes`, and `Particles` all render the same way - a Renderable component with a Material and VertexArray. 12 | 13 | Highlight features include: 14 | * [A rich shader workflow](docs/Shader.md) 15 | * [Queryable Entity-Component-System](docs/Entity.md) 16 | * [Extendable asset management](docs/AssetManager.md) 17 | * Run-time reflection 18 | * A modular & extendable particle system 19 | * Audio powered by [SoLoud](https://github.com/EmilianC/soloud) 20 | * Mesh and Texture loading 21 | * Dynamic GUI widgets 22 | * [FreeType text rendering](docs/Text.md) 23 | * Event handling & lifetime-safe delegates 24 | * A robust fixed-timestep gameloop 25 | * Keyboard/Mouse/Xbox-Controller support 26 | * [ImGui](https://github.com/ocornut/imgui) integration 27 | * A complete math library 28 | * Local networking 29 | 30 | # Quick Start 31 | If you're looking to start a new project using Gemcutter, then you can use [this repository template](https://github.com/EmilianC/Gemcutter-Project-Template) to get started quickly. 32 | 33 | If you just want to download the framework, then simply clone the repo and configure the project with CMake from the root folder: 34 | ``` 35 | > mkdir build 36 | > cd build 37 | > cmake .. # Default configuration, or 38 | > cmake .. -DENABLE_SAMPLES=OFF # Default configuration without downloading samples 39 | ``` 40 | And that's it! Any required git submodules are pulled automatically. You'll find your generated projects under the `build/` folder. Code samples can be found under `samples/` if they were enabled. 41 | 42 | The following build configurations will be available: 43 | | Config | defines | optimizations | asserts | tools | 44 | |----------------|--------------------|---------------|---------|-------| 45 | | Debug | GEM_DEBUG, GEM_DEV | off | on | on | 46 | | RelWithDebInfo | GEM_DEV | on | off | on | 47 | | Release | GEM_PRODUCTION | on | off | off | 48 | 49 | # Development Roadmap 50 | See the [Trello board](https://trello.com/b/Oc2GFT2A/gemcutter) to follow development in more detail. 51 | 52 | ### v1.0 53 | - [ ] A fully featured 3D world editor 54 | - [ ] Input remapping utilities 55 | - [ ] Serialization & deserialization 56 | - [ ] Single draw-call text rendering 57 | ### v1.x 58 | - CLI asset manager in Rust 59 | - Native Linux support 60 | - Memory management utilities 61 | - Physics library integration 62 | - UI screen designer 63 | 64 | # Dependencies 65 | All Dependencies used under their respective licenses. Copyright is held by their respective owners. 66 | * [GLEW 2.2.0+](https://github.com/Perlmint/glew-cmake) 67 | * [FreeType 2.13.3](https://github.com/EmilianC/freetype) 68 | * [Catch 2.13.8](https://github.com/catchorg/Catch2/tree/v2.x) 69 | * [SoLoud](https://github.com/EmilianC/soloud) 70 | * [Scaffold](https://github.com/EmilianC/scaffold) 71 | * [SOIL2 1.31](https://github.com/SpartanJ/SOIL2) 72 | * [dirent 1.23.2](https://github.com/tronkko/dirent) 73 | * [XInput 1.4](https://msdn.microsoft.com/en-us/library/windows/desktop/ee417001(v=vs.85).aspx) 74 | * [Imgui 1.85](https://github.com/ocornut/imgui) 75 | * [Loupe](https://github.com/EmilianC/Loupe) 76 | 77 | # Notes 78 | * Gemcutter is built and tested on Windows with the latest version of Visual Studio. 79 | * Gemcutter requires at least OpenGL 3.3. 80 | * Gemcutter is provided under the MIT License. 81 | -------------------------------------------------------------------------------- /gemcutter/Resource/UniformBuffer.inl: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Emilian Cioca 2 | namespace gem 3 | { 4 | template 5 | UniformHandle UniformBuffer::AddUniform(std::string_view name, unsigned count) 6 | { 7 | unsigned alignment = sizeof(T); 8 | unsigned size = sizeof(T); 9 | 10 | // Due to alignment rules, some types are adjusted. 11 | if constexpr (std::is_same_v || std::is_same_v) 12 | { 13 | alignment = sizeof(vec4); 14 | } 15 | else if constexpr (std::is_same_v) 16 | { 17 | alignment = sizeof(vec4); 18 | size = sizeof(vec4) * 2; 19 | } 20 | else if constexpr (std::is_same_v) 21 | { 22 | alignment = sizeof(vec4); 23 | size = sizeof(vec4) * 3; 24 | } 25 | 26 | AddUniform(std::string(name), size, alignment, count); 27 | 28 | return MakeHandle(name); 29 | } 30 | 31 | template 32 | void UniformBuffer::SetUniform(std::string_view name, const T& data) 33 | { 34 | T* dest = static_cast(GetBufferLoc(name)); 35 | ASSERT(dest, "Could not find uniform parameter ( %s ).", name.data()); 36 | ASSERT(reinterpret_cast(dest) + sizeof(T) <= static_cast(buffer) + bufferSize, 37 | "Setting uniform ( %s ) out of bounds of the buffer.", name.data()); 38 | 39 | *dest = data; 40 | dirty = true; 41 | } 42 | 43 | template 44 | void UniformBuffer::SetUniformArray(std::string_view name, unsigned numElements, T* data) 45 | { 46 | ASSERT(data != nullptr, "Source data cannot be null."); 47 | 48 | void* dest = GetBufferLoc(name); 49 | ASSERT(dest, "Could not find uniform parameter ( %s ).", name.data()); 50 | ASSERT(static_cast(dest) + sizeof(T) * numElements <= static_cast(buffer) + bufferSize, 51 | "Setting uniform ( %s ) out of bounds of the buffer.", name.data()); 52 | 53 | memcpy(dest, data, sizeof(T) * numElements); 54 | dirty = true; 55 | } 56 | 57 | template 58 | UniformHandle UniformBuffer::MakeHandle(std::string_view name) 59 | { 60 | auto itr = table.find(name); 61 | ASSERT(itr != table.end(), "\"%s\" does not match the name of a Uniform.", name.data()); 62 | 63 | return UniformHandle(*this, itr->second); 64 | } 65 | 66 | extern template void UniformBuffer::SetUniform(std::string_view name, const mat2& data); 67 | extern template void UniformBuffer::SetUniform(std::string_view name, const mat3& data); 68 | 69 | template 70 | UniformHandle::UniformHandle(UniformBuffer& buff, unsigned _offset) 71 | : uniformBuffer(&buff), offset(_offset) 72 | { 73 | } 74 | 75 | template 76 | UniformHandle& UniformHandle::operator=(const T& value) 77 | { 78 | Set(value); 79 | return *this; 80 | } 81 | 82 | template 83 | void UniformHandle::Set(const T& value) 84 | { 85 | ASSERT(uniformBuffer, "Uniform handle is not associated with a UniformBuffer."); 86 | ASSERT(uniformBuffer->buffer, "The associated UniformBuffer has not been initialized yet."); 87 | 88 | T* ptr = reinterpret_cast(static_cast(uniformBuffer->buffer) + offset); 89 | *ptr = value; 90 | 91 | uniformBuffer->dirty = true; 92 | } 93 | 94 | template 95 | T UniformHandle::Get() const 96 | { 97 | ASSERT(uniformBuffer, "Uniform handle is not associated with a UniformBuffer."); 98 | ASSERT(uniformBuffer->buffer, "The associated UniformBuffer has not been initialized yet."); 99 | 100 | return *reinterpret_cast(static_cast(uniformBuffer->buffer) + offset); 101 | } 102 | 103 | extern template void UniformHandle::Set(const mat2& value); 104 | extern template void UniformHandle::Set(const mat3& value); 105 | extern template mat2 UniformHandle::Get() const; 106 | extern template mat3 UniformHandle::Get() const; 107 | } 108 | -------------------------------------------------------------------------------- /gemcutter/GUI/Widget.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Emilian Cioca 2 | #include "Widget.h" 3 | #include "gemcutter/Entity/Hierarchy.h" 4 | 5 | namespace gem 6 | { 7 | Widget::Widget(Entity& _owner) 8 | : Component(_owner) 9 | { 10 | owner.Require().propagateTransform = false; 11 | } 12 | 13 | void Widget::SetTransform(vec2 position, vec2 size) 14 | { 15 | const float halfWidth = size.x * -0.5f; 16 | const float halfHeight = size.y * -0.5f; 17 | 18 | top.offset = halfHeight; 19 | bottom.offset = halfHeight; 20 | left.offset = halfWidth; 21 | right.offset = halfWidth; 22 | 23 | if (Entity::Ptr parent = owner.Get().GetParent()) 24 | { 25 | const Rectangle& parentBounds = parent->Get().GetAbsoluteBounds(); 26 | 27 | const float percentageX = position.x / parentBounds.width; 28 | const float percentageY = position.y / parentBounds.height; 29 | 30 | top.anchor = (1.0f - percentageY); 31 | bottom.anchor = percentageY; 32 | left.anchor = percentageX; 33 | right.anchor = (1.0f - percentageX); 34 | } 35 | else 36 | { 37 | absoluteBounds.x = position.x; 38 | absoluteBounds.y = position.y; 39 | } 40 | } 41 | 42 | void Widget::FitToParent() 43 | { 44 | top.anchor = 0.0f; 45 | top.offset = 0.0f; 46 | 47 | bottom.anchor = 0.0f; 48 | bottom.offset = 0.0f; 49 | 50 | left.anchor = 0.0f; 51 | left.offset = 0.0f; 52 | 53 | right.anchor = 0.0f; 54 | right.offset = 0.0f; 55 | } 56 | 57 | const Rectangle& Widget::GetAbsoluteBounds() const 58 | { 59 | return absoluteBounds; 60 | } 61 | 62 | void Widget::UpdateAll() 63 | { 64 | for (Entity& entity : With()) 65 | { 66 | auto& widget = entity.Get(); 67 | widget.absoluteBounds.width = widget.right.offset - widget.left.offset; 68 | widget.absoluteBounds.height = widget.top.offset - widget.bottom.offset; 69 | 70 | for (Entity::Ptr& child : entity.Get().GetChildren()) 71 | { 72 | if (auto* childWidget = child->Try()) 73 | { 74 | childWidget->UpdateBounds(widget); 75 | } 76 | } 77 | } 78 | } 79 | 80 | void Widget::UpdateBounds(const Widget& parent) 81 | { 82 | const float offsetFromTop = parent.absoluteBounds.height * top.anchor + top.offset; 83 | const float offsetFromBottom = parent.absoluteBounds.height * bottom.anchor + bottom.offset; 84 | const float offsetFromLeft = parent.absoluteBounds.width * left.anchor + left.offset; 85 | const float offsetFromRight = parent.absoluteBounds.width * right.anchor + right.offset; 86 | 87 | // These values are in world-space. 88 | const float topEdge = (parent.absoluteBounds.y + parent.absoluteBounds.height) - offsetFromTop; 89 | const float bottomEdge = parent.absoluteBounds.y + offsetFromBottom; 90 | const float leftEdge = parent.absoluteBounds.x + offsetFromLeft; 91 | const float rightEdge = (parent.absoluteBounds.x + parent.absoluteBounds.width) - offsetFromRight; 92 | 93 | absoluteBounds.x = leftEdge; 94 | absoluteBounds.y = bottomEdge; 95 | absoluteBounds.width = rightEdge - leftEdge; 96 | absoluteBounds.height = topEdge - bottomEdge; 97 | 98 | // The actual position of the entity is the pivot (center of the widget). 99 | owner.position.x = absoluteBounds.x + absoluteBounds.width * 0.5f; 100 | owner.position.y = absoluteBounds.y + absoluteBounds.height * 0.5f; 101 | 102 | UpdateContent(); 103 | 104 | for (Entity::Ptr& child : owner.Get().GetChildren()) 105 | { 106 | if (auto* childWidget = child->Try()) 107 | { 108 | childWidget->UpdateBounds(*this); 109 | } 110 | } 111 | } 112 | } 113 | 114 | REFLECT(gem::Edge) 115 | MEMBERS { 116 | REF_MEMBER(anchor, description("Percentage of total distance from a parent edge.")) 117 | REF_MEMBER(offset, description("Flat offset from the anchor.")) 118 | } 119 | REF_END; 120 | 121 | REFLECT_COMPONENT(gem::Widget, gem::ComponentBase) 122 | MEMBERS { 123 | REF_MEMBER(top) 124 | REF_MEMBER(bottom) 125 | REF_MEMBER(left) 126 | REF_MEMBER(right) 127 | } 128 | REF_END; 129 | --------------------------------------------------------------------------------