diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index f3e4756e4cabce8894b39bd629b6aae7b5b2c7d3..e2efa8dad686c63c6d8ba985a602d44589df2970 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -1,5 +1,8 @@ # Executable -add_executable(FgglDemo main.cpp) +add_executable(FgglDemo + main.cpp + GameScene.cpp + ) target_link_libraries(FgglDemo fggl) #target_include_directories(FgglDemo PUBLIC ${PROJECT_BINARY_DIR}) diff --git a/demo/GameScene.cpp b/demo/GameScene.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e2b176e72d902e796005b403baf94dfc41f87375 --- /dev/null +++ b/demo/GameScene.cpp @@ -0,0 +1,203 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// +// Created by webpigeon on 22/04/22. +// + +#include "GameScene.h" + +camera_type cam_mode = cam_free; + +static void placeObject(fggl::ecs3::World& world, fggl::ecs::entity_t parent, fggl::ecs::entity_t prototype, glm::vec3 targetPos) { + auto obj = world.copy(prototype); + auto result = world.get<fggl::math::Transform>(obj); + + int xPos = (int)targetPos.x; + int zPos = (int)targetPos.z * -1; + + // figure out the floor height + auto heightMap = world.get<fggl::data::HeightMap>(parent); + targetPos.y = heightMap->getValue(xPos, zPos); // TODO should really be the gradient at the required point + + result->origin( targetPos ); +} + +static void process_camera(fggl::ecs3::World& ecs, const std::shared_ptr<fggl::input::Input>& input) { + auto cameras = ecs.findMatching<fggl::gfx::Camera>(); + fggl::ecs3::entity_t cam = cameras[0]; + + auto camTransform = ecs.get<fggl::math::Transform>(cam); + auto camComp = ecs.get<fggl::gfx::Camera>(cam); + + const glm::vec3 dir = ( camTransform->origin() - camComp->target ); + const glm::vec3 forward = glm::normalize( dir ); + + // scroll wheel + glm::vec3 motion(0.0f); + float delta = input->mouse.axis( fggl::input::MouseAxis::SCROLL_Y ); + if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) ) + motion -= (forward * delta); + camTransform->origin( camTransform->origin() + motion ); + + if ( cam_mode == cam_arcball || input->mouse.down( fggl::input::MouseButton::MIDDLE ) ) { + fggl::input::process_arcball(ecs, *input, cam); + } else if ( cam_mode == cam_free ) { + fggl::input::process_freecam(ecs, *input, cam); + } + fggl::input::process_edgescroll( ecs, *input, cam ); +} + +void GameScene::setup() { + m_canvas.size( fggl::math::vec2(0,0), fggl::math::vec2(100, 100)); + + auto types = m_world->types(); + + // create camera using strings + { + auto prototype = m_world->create(false); + m_world->add(prototype, types.find(fggl::math::Transform::name)); + m_world->add(prototype, types.find(fggl::gfx::Camera::name)); + m_world->add(prototype, types.find(fggl::input::FreeCamKeys::name)); + + auto camTf = m_world->get<fggl::math::Transform>(prototype); + if ( camTf != nullptr) { + camTf->origin(glm::vec3(10.0f, 3.0f, 10.0f)); + } + + auto cameraKeys = m_world->get<fggl::input::FreeCamKeys>(prototype); + if ( cameraKeys != nullptr ) { + cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W); + cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S); + cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A); + cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D); + cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q); + cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E); + } + } + + fggl::ecs3::entity_t terrain; + { + terrain = m_world->create(false); + m_world->add(terrain, types.find(fggl::math::Transform::name)); + + auto camTf = m_world->get<fggl::math::Transform>(terrain); + camTf->origin( glm::vec3(0.0f, 0.0f, 0.0f) ); + + //auto terrainData = m_world.get<fggl::data::HeightMap>(terrain); + fggl::data::HeightMap terrainData{}; + terrainData.clear(); + + const siv::PerlinNoise::seed_type seed = 123456u; + const siv::PerlinNoise perlin{ seed }; + + for (int y = 0; y < 255; ++y) { + for (int x = 0; x < 255; ++x) { + const double noise = perlin.octave2D_11( (x * 0.01), (y * 0.01) , 4) * 10.f; + terrainData.heightValues[x * 255 +y] = (float)noise; + } + } + m_world->set<fggl::data::HeightMap>(terrain, &terrainData); + } + + // create foundation object + fggl::ecs3::entity_t foundation; + { + foundation = m_world->create(true); + m_world->add(foundation, types.find(fggl::math::Transform::name)); + + // plot rendering + fggl::data::Mesh mesh; + fggl::data::make_cube(mesh); + mesh.removeDups(); + + // add mesh as a component + constexpr char shader[] = "phong"; + fggl::data::StaticMesh staticMesh{mesh, shader}; + m_world->set<fggl::data::StaticMesh>(foundation, &staticMesh); + } + + // create building prototype + fggl::ecs3::entity_t bunker; + { + bunker = m_world->create(true); + m_world->add(bunker, types.find(fggl::math::Transform::name)); + + // mesh + int nSections = 2; + constexpr float HALF_PI = M_PI / 2.0f; + constexpr char shader[] = "phong"; + + fggl::data::Mesh mesh; + for (int j=-(nSections/2); j<=nSections/2; j++) { + const auto shapeOffset = glm::vec3( 0.0f, 0.5f, (float)j * 1.0f ); + + const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset ); + const auto leftSlope = fggl::math::modelMatrix( + glm::vec3(-1.0f, 0.0f, 0.0f) + shapeOffset, + glm::vec3( 0.0f, -HALF_PI, 0.0f) ); + const auto rightSlope = fggl::math::modelMatrix( + glm::vec3( 1.0f, 0.0f, 0.0f) + shapeOffset, + glm::vec3( 0.0f, HALF_PI, 0.0f) ); + + fggl::data::make_cube( mesh, cubeMat ); + fggl::data::make_slope( mesh, leftSlope ); + fggl::data::make_slope( mesh, rightSlope ); + } + mesh.removeDups(); + + fggl::data::StaticMesh staticMesh{mesh, shader}; + m_world->set<fggl::data::StaticMesh>(bunker, &staticMesh); + } + + for (int i=0; i<3; ++i) { + glm::vec3 location(i * 6.5f + 1.0f, 0.0f, -7.0f); + placeObject(*m_world, terrain, foundation, location); + } + + int nCubes = 3; + for ( int i=0; i<nCubes; i++ ) { + glm::vec3 location; + location.x = i * 6.f + 1.0f; + location.z = -5.0f + 1.0f; + placeObject(*m_world, terrain, bunker, location); + } +} + +void GameScene::update() { +process_camera(*m_world, m_inputs); +} + +void GameScene::render(fggl::gfx::Graphics &gfx) { + + // render the 3D scene + if ( m_world != nullptr ) { + gfx.drawScene( *m_world ); + } + + const fggl::math::vec2 panelSize { 250.0F, 250.0F }; + const auto canvasY = gfx.canvasBounds().bottom - panelSize.y; + m_canvas.size( {0.0F, canvasY}, panelSize); + + // now the 2D scene + fggl::gfx::Paint paint; + m_canvas.render(paint); + gfx.draw2D(paint); +} \ No newline at end of file diff --git a/demo/GameScene.h b/demo/GameScene.h new file mode 100644 index 0000000000000000000000000000000000000000..90bf636c8727cf7cfe768b69e11f44a7e5920c98 --- /dev/null +++ b/demo/GameScene.h @@ -0,0 +1,77 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// +// Created by webpigeon on 22/04/22. +// + +#ifndef FGGL_DEMO_GAMESCENE_H +#define FGGL_DEMO_GAMESCENE_H + +#include <fggl/app.hpp> +#include <fggl/gui/gui.hpp> + +#include <fggl/data/heightmap.h> +#include <fggl/data/procedural.hpp> + +#include <fggl/gfx/camera.hpp> +#include <fggl/input/input.hpp> +#include <fggl/input/camera_input.h> + +#include <fggl/util/service.h> +#include <fggl/util/chrono.hpp> + +#include <PerlinNoise.hpp> + +enum camera_type { cam_free, cam_arcball }; + +class GameScene : public fggl::AppState { + public: + explicit GameScene(fggl::App& app) : fggl::AppState(app), m_world(nullptr), m_sceneTime(nullptr), m_inputs(nullptr) { }; + //~GameScene() override = default; + + void activate() override { + auto& locator = fggl::util::ServiceLocator::instance(); + m_inputs = locator.providePtr<fggl::input::Input>(); + + // setup the world using the global type registry + // FIXME: type registry probably doesn't need to be application-global - will make savegame/mod support complicated + m_world = std::make_unique<fggl::ecs3::World>( *m_owner.registry() ); + + m_sceneTime = std::make_unique<fggl::util::Timer>(); + m_sceneTime->frequency( glfwGetTimerFrequency() ); + m_sceneTime->setup( glfwGetTimerValue() ); + + setup(); + } + + void setup(); + void update() override; + void render(fggl::gfx::Graphics& gfx) override; + + private: + std::unique_ptr<fggl::ecs3::World> m_world; + std::unique_ptr<fggl::util::Timer> m_sceneTime; + std::shared_ptr<fggl::input::Input> m_inputs; + fggl::gui::Panel m_canvas; +}; + + +#endif //FGGL_DEMO_GAMESCENE_H diff --git a/demo/main.cpp b/demo/main.cpp index 9bc3aaee0d188f0b4c9375e2aac4928edcc4e270..589f45e8d124f5dca1b47f66af2ce348df3e5440 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -1,44 +1,27 @@ #include <filesystem> #include <iostream> #include <memory> -#include <utility> #include <fggl/app.hpp> -#include <fggl/scenes/menu.hpp> #include <fggl/gfx/atlas.hpp> #include <fggl/gfx/window.hpp> -#include <fggl/gfx/camera.hpp> - -#include "fggl/gfx/ogl/renderer.hpp" -#include "fggl/gfx/ogl/compat.hpp" #include "fggl/gfx/compat.hpp" -#include "fggl/gui/containers.hpp" +#include "fggl/gfx/ogl/compat.hpp" -#include <fggl/data/heightmap.h> -#include <fggl/data/procedural.hpp> #include <fggl/data/storage.hpp> -#include <fggl/util/chrono.hpp> #include <fggl/util/service.h> #include <fggl/ecs3/types.hpp> -#include <fggl/ecs3/ecs.hpp> - -#include <fggl/input/camera_input.h> - -#include <fggl/debug/debug.h> -// FIXME: imgui and perlinNoise shouldn't form part of our public API >.< -#include <imgui.h> -#include <PerlinNoise.hpp> - -constexpr bool showNormals = false; +#include "fggl/scenes/menu.hpp" +#include "GameScene.h" // prototype of resource discovery void discover(const std::filesystem::path& base) { std::vector< std::filesystem::path > contentPacks; - for ( auto& item : std::filesystem::directory_iterator(base) ) { + for ( const auto& item : std::filesystem::directory_iterator(base) ) { // content pack detection if ( std::filesystem::is_directory( item ) ) { @@ -58,287 +41,10 @@ void discover(const std::filesystem::path& base) { } -enum camera_type { cam_free, cam_arcball }; -camera_type cam_mode = cam_free; - -//TODO proper input system -using InputManager = std::shared_ptr<fggl::input::Input>; - -void process_camera(fggl::ecs3::World& ecs, const InputManager& input) { - auto cameras = ecs.findMatching<fggl::gfx::Camera>(); - fggl::ecs3::entity_t cam = cameras[0]; - - auto camTransform = ecs.get<fggl::math::Transform>(cam); - auto camComp = ecs.get<fggl::gfx::Camera>(cam); - - const glm::vec3 dir = ( camTransform->origin() - camComp->target ); - const glm::vec3 forward = glm::normalize( dir ); - - // scroll wheel - glm::vec3 motion(0.0f); - float delta = input->mouse.axis( fggl::input::MouseAxis::SCROLL_Y ); - if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) ) - motion -= (forward * delta); - camTransform->origin( camTransform->origin() + motion ); - - if ( cam_mode == cam_arcball || input->mouse.down( fggl::input::MouseButton::MIDDLE ) ) { - fggl::input::process_arcball(ecs, *input, cam); - } else if ( cam_mode == cam_free ) { - fggl::input::process_freecam(ecs, *input, cam); - } - fggl::input::process_edgescroll( ecs, *input, cam ); -} - -void placeObject(fggl::ecs3::World& world, fggl::ecs::entity_t parent, fggl::ecs::entity_t prototype, glm::vec3 targetPos) { - auto obj = world.copy(prototype); - auto result = world.get<fggl::math::Transform>(obj); - - int xPos = (int)targetPos.x; - int zPos = (int)targetPos.z * -1; - - // figure out the floor height - auto heightMap = world.get<fggl::data::HeightMap>(parent); - targetPos.y = heightMap->getValue(xPos, zPos); // TODO should really be the gradient at the required point - - result->origin( targetPos ); -} - - -class GameScene : public fggl::AppState { -public: - explicit GameScene(fggl::App& app) : fggl::AppState(app), m_world(nullptr), m_sceneTime(nullptr), m_inputs(nullptr) { }; - //~GameScene() override = default; - - void activate() override { - auto& locator = fggl::util::ServiceLocator::instance(); - m_inputs = locator.providePtr<fggl::input::Input>(); - - // setup the world using the global type registry - // FIXME: type registry probably doesn't need to be application-global - will make savegame/mod support complicated - m_world = std::make_unique<fggl::ecs3::World>( *m_owner.registry() ); - - m_sceneTime = std::make_unique<fggl::util::Timer>(); - m_sceneTime->frequency( glfwGetTimerFrequency() ); - m_sceneTime->setup( glfwGetTimerValue() ); - - setup(); - } - - void setup() { - m_canvas.size( fggl::math::vec2(0,0), fggl::math::vec2(100, 100)); - - auto types = m_world->types(); - - // create camera using strings - { - auto prototype = m_world->create(false); - m_world->add(prototype, types.find(fggl::math::Transform::name)); - m_world->add(prototype, types.find(fggl::gfx::Camera::name)); - m_world->add(prototype, types.find(fggl::input::FreeCamKeys::name)); - - auto camTf = m_world->get<fggl::math::Transform>(prototype); - if ( camTf != nullptr) { - camTf->origin(glm::vec3(10.0f, 3.0f, 10.0f)); - } - - auto cameraKeys = m_world->get<fggl::input::FreeCamKeys>(prototype); - if ( cameraKeys != nullptr ) { - cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W); - cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S); - cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A); - cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D); - cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q); - cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E); - } - } - - fggl::ecs3::entity_t terrain; - { - terrain = m_world->create(false); - m_world->add(terrain, types.find(fggl::math::Transform::name)); - - auto camTf = m_world->get<fggl::math::Transform>(terrain); - camTf->origin( glm::vec3(0.0f, 0.0f, 0.0f) ); - - //auto terrainData = m_world.get<fggl::data::HeightMap>(terrain); - fggl::data::HeightMap terrainData{}; - terrainData.clear(); - - const siv::PerlinNoise::seed_type seed = 123456u; - const siv::PerlinNoise perlin{ seed }; - - for (int y = 0; y < 255; ++y) { - for (int x = 0; x < 255; ++x) { - const double noise = perlin.octave2D_11( (x * 0.01), (y * 0.01) , 4) * 10.f; - terrainData.heightValues[x * 255 +y] = (float)noise; - } - } - m_world->set<fggl::data::HeightMap>(terrain, &terrainData); - } - - // create foundation object - fggl::ecs3::entity_t foundation; - { - foundation = m_world->create(true); - m_world->add(foundation, types.find(fggl::math::Transform::name)); - - // plot rendering - fggl::data::Mesh mesh; - fggl::data::make_cube(mesh); - mesh.removeDups(); - - // add mesh as a component - constexpr char shader[] = "phong"; - fggl::data::StaticMesh staticMesh{mesh, shader}; - m_world->set<fggl::data::StaticMesh>(foundation, &staticMesh); - } - - // create building prototype - fggl::ecs3::entity_t bunker; - { - bunker = m_world->create(true); - m_world->add(bunker, types.find(fggl::math::Transform::name)); - - // mesh - int nSections = 2; - constexpr float HALF_PI = M_PI / 2.0f; - constexpr char shader[] = "phong"; - - fggl::data::Mesh mesh; - for (int j=-(nSections/2); j<=nSections/2; j++) { - const auto shapeOffset = glm::vec3( 0.0f, 0.5f, (float)j * 1.0f ); - - const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset ); - const auto leftSlope = fggl::math::modelMatrix( - glm::vec3(-1.0f, 0.0f, 0.0f) + shapeOffset, - glm::vec3( 0.0f, -HALF_PI, 0.0f) ); - const auto rightSlope = fggl::math::modelMatrix( - glm::vec3( 1.0f, 0.0f, 0.0f) + shapeOffset, - glm::vec3( 0.0f, HALF_PI, 0.0f) ); - - fggl::data::make_cube( mesh, cubeMat ); - fggl::data::make_slope( mesh, leftSlope ); - fggl::data::make_slope( mesh, rightSlope ); - } - mesh.removeDups(); - - fggl::data::StaticMesh staticMesh{mesh, shader}; - m_world->set<fggl::data::StaticMesh>(bunker, &staticMesh); - } - - for (int i=0; i<3; ++i) { - glm::vec3 location(i * 6.5f + 1.0f, 0.0f, -7.0f); - placeObject(*m_world, terrain, foundation, location); - } - - int nCubes = 3; - for ( int i=0; i<nCubes; i++ ) { - glm::vec3 location; - location.x = i * 6.f + 1.0f; - location.z = -5.0f + 1.0f; - placeObject(*m_world, terrain, bunker, location); - } - } - - void deactivate() override { - } - - void update() override { - process_camera(*m_world, m_inputs); - - // JWR - this doesn't really seem like it belongs in the game scene... - if ( m_inputs->keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_F10)) ) { - auto dbgui = fggl::util::ServiceLocator::instance().providePtr<fggl::debug::DebugUI>(); - dbgui->visible( !dbgui->visible() ); - } - } - - void render(fggl::gfx::Graphics& gfx) override { - // render the 3D scene - if ( m_world != nullptr ) { - gfx.drawScene( *m_world ); - } - - const fggl::math::vec2 panelSize { 250.0F, 250.0F }; - const auto canvasY = gfx.canvasBounds().bottom - panelSize.y; - m_canvas.size( {0.0F, canvasY}, panelSize); - - // now the 2D scene - fggl::gfx::Paint paint; - m_canvas.render(paint); - gfx.draw2D(paint); - } - - // entity inspector - void debugInspector() { - auto types = m_world->types(); - - ImGui::Begin("Entities"); - auto entityItr = m_world->all(); - for (auto& entity : entityItr) { - std::string label = "entity-" + std::to_string(entity); - if ( ImGui::TreeNode(label.c_str()) ){ - auto entComp = m_world->getComponents(entity); - for ( auto comp : entComp ){ - auto meta = types.meta(comp); - ImGui::Text("%s (%d) - %lu bytes", meta->name(), comp, meta->size()); - } - ImGui::TreePop(); - } - } - ImGui::End(); - }; - -private: - std::unique_ptr<fggl::ecs3::World> m_world; - std::unique_ptr<fggl::util::Timer> m_sceneTime; - InputManager m_inputs; - fggl::gui::Panel m_canvas; -}; - -void gamepadDebug(bool* visible) { - auto inputs = fggl::util::ServiceLocator::instance().providePtr<fggl::input::Input>(); - auto &gamepads = inputs->gamepads; - ImGui::Begin("GamePad", visible); - for (int i = 0; i < 16; i++) { - std::string title = gamepads.name(i); - - bool present = gamepads.present(i); - if (ImGui::TreeNode(title.c_str())) { - ImGui::Text("present: %s", present ? "yes" : "no"); - - if (present) { - - if (ImGui::TreeNode("buttons##2")) { - for (auto &btn: fggl::input::GamepadButtonsMicrosoft) { - ImGui::Text("%s: %i %i %i", btn.name, - gamepads.button(i, btn.id), - gamepads.buttonPressed(i, btn.id), - gamepads.buttonReleased(i, btn.id) - ); - } - ImGui::TreePop(); - } - - if (ImGui::TreeNode("axes##2")) { - for (auto &axis: fggl::input::GamepadAxes) { - ImGui::Text("%s: %f %f", axis.name, - gamepads.axis(i, axis.id), - gamepads.axisDelta(i, axis.id) - ); - - } - ImGui::TreePop(); - } - - } - - ImGui::TreePop(); - ImGui::Separator(); - } - - } - ImGui::End(); +static void test_atlas_api() { + // atlas testing + std::vector< fggl::gfx::ImageAtlas<char>::SubImage > images; + auto *atlas = fggl::gfx::ImageAtlas<char>::pack(images); } int main(int argc, const char* argv[]) { @@ -365,18 +71,15 @@ int main(int argc, const char* argv[]) { //app.use<fggl::ecs3::ecsTypes>(); app.use<fggl::gfx::SceneUtils>(); - // atlas testing - std::vector< fggl::gfx::ImageAtlas<char>::SubImage > images; - auto *atlas = fggl::gfx::ImageAtlas<char>::pack(images); + test_atlas_api(); - // and now our states + // Add a basic main menu auto *menu = app.add_state<fggl::scenes::BasicMenu>("menu"); - menu->add("start", [&app]() { app.change_state("game"); }); menu->add("options", [&app]() { app.change_state("game"); }); menu->add("quit", [&app]() { app.running(false); }); - // game state + // the game state itself app.add_state<GameScene>("game"); return app.run(argc, argv); diff --git a/include/fggl/data/procedural.hpp b/include/fggl/data/procedural.hpp index d8431a95fe1dd89901201c87b299282a5df2a68a..f05a859fbb7f693717a6f114b4ec793c5bb0717e 100644 --- a/include/fggl/data/procedural.hpp +++ b/include/fggl/data/procedural.hpp @@ -1,3 +1,5 @@ +#ifndef FGGL_DATA_PROCEDURAL_HPP +#define FGGL_DATA_PROCEDURAL_HPP #include "model.hpp" @@ -37,3 +39,5 @@ namespace fggl::data { return make_point(mesh, OFFSET_NONE); } } + +#endif \ No newline at end of file