diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index f785a80d42..e20fe86a08 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -77,6 +77,9 @@ set(CUBOS_ENGINE_SOURCE "src/cubos/engine/renderer/pps/copy_pass.cpp" "src/cubos/engine/renderer/pps/manager.cpp" "src/cubos/engine/renderer/pps/pass.cpp" + "src/cubos/engine/renderer/viewport.cpp" + + "src/cubos/engine/splitscreen/plugin.cpp" ) # Create cubos engine diff --git a/engine/include/cubos/engine/renderer/plugin.hpp b/engine/include/cubos/engine/renderer/plugin.hpp index 3969fbeeb6..35ea685859 100644 --- a/engine/include/cubos/engine/renderer/plugin.hpp +++ b/engine/include/cubos/engine/renderer/plugin.hpp @@ -48,6 +48,7 @@ namespace cubos::engine /// ## Components /// - @ref RenderableGrid - a grid to be rendered. /// - @ref Camera - holds camera information. + /// - @ref Viewport - holds viewport information. /// - @ref SpotLight - emits a spot light. /// - @ref DirectionalLight - emits a directional light. /// - @ref PointLight - emits a point light. diff --git a/engine/include/cubos/engine/renderer/viewport.hpp b/engine/include/cubos/engine/renderer/viewport.hpp new file mode 100644 index 0000000000..bda8231ca0 --- /dev/null +++ b/engine/include/cubos/engine/renderer/viewport.hpp @@ -0,0 +1,25 @@ +/// @file +/// @brief Component @ref cubos::engine::Viewport. +/// @ingroup renderer-plugin + +#pragma once + +#include + +#include + +namespace cubos::engine +{ + /// @brief Component which defines parameters of a viewport, the actual screen space + /// that will be used by the camera it is attached to. Useful for having multiple camera views + /// shown on screen. + /// @note Should be used with @ref LocalToWorld. + /// @ingroup renderer-plugin + struct Viewport + { + CUBOS_REFLECT; + + glm::ivec2 position{0.0F, 0.0F}; //< Top left position of the viewport in pixels + glm::ivec2 size{0.0F, 0.0F}; //< Size of the viewport in pixels + }; +} // namespace cubos::engine diff --git a/engine/include/cubos/engine/splitscreen/plugin.hpp b/engine/include/cubos/engine/splitscreen/plugin.hpp new file mode 100644 index 0000000000..5e0b7147f8 --- /dev/null +++ b/engine/include/cubos/engine/splitscreen/plugin.hpp @@ -0,0 +1,25 @@ +/// @dir +/// @brief @ref splitscreen-plugin plugin directory. + +/// @file +/// @brief Plugin entry point. +/// @ingroup splitscreen-plugin + +#pragma once + +#include + +namespace cubos::engine +{ + /// @defgroup splitscreen-plugin Splitscreen + /// @ingroup engine + /// @brief Adds viewport to all active cameras to achieve a splitscreen layout. + /// + /// ## Dependencies + /// - @ref renderer-plugin + + /// @brief Plugin entry function. + /// @param cubos @b CUBOS. main class + /// @ingroup splitscreen-plugin + void splitscreenPlugin(Cubos& cubos); +} // namespace cubos::engine diff --git a/engine/samples/renderer/main.cpp b/engine/samples/renderer/main.cpp index 97fb44240d..b425782f13 100644 --- a/engine/samples/renderer/main.cpp +++ b/engine/samples/renderer/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include using namespace cubos::engine; @@ -66,9 +67,19 @@ static void spawnCamerasSystem(Commands commands, Write camera) .add(Rotation{glm::quatLookAt(glm::normalize(glm::vec3{1.0F, 0.0F, 1.0F}), glm::vec3{0.0F, 1.0F, 0.0F})}) .entity(); - // Add two other viewports using the same camera, which splits the screen in three. - camera->entities[1] = camera->entities[0]; - camera->entities[2] = camera->entities[0]; + camera->entities[1] = + commands.create() + .add(Camera{.fovY = 60.0F, .zNear = 0.1F, .zFar = 100.0F}) + .add(Position{{-3.0, 1.0F, -3.0F}}) + .add(Rotation{glm::quatLookAt(glm::normalize(glm::vec3{1.0F, 0.0F, 1.0F}), glm::vec3{0.0F, 1.0F, 0.0F})}) + .entity(); + + camera->entities[2] = + commands.create() + .add(Camera{.fovY = 60.0F, .zNear = 0.1F, .zFar = 100.0F}) + .add(Position{{-3.0, 1.0F, -3.0F}}) + .add(Rotation{glm::quatLookAt(glm::normalize(glm::vec3{1.0F, 0.0F, 1.0F}), glm::vec3{0.0F, 1.0F, 0.0F})}) + .entity(); } /// [Spawn the cameras] @@ -77,6 +88,7 @@ int main() Cubos cubos{}; /// [Adding the plugin] + cubos.addPlugin(splitscreenPlugin); cubos.addPlugin(rendererPlugin); /// [Adding the plugin] diff --git a/engine/samples/voxels/main.cpp b/engine/samples/voxels/main.cpp index b71c7b1ec9..a887946a1c 100644 --- a/engine/samples/voxels/main.cpp +++ b/engine/samples/voxels/main.cpp @@ -1,77 +1,77 @@ -#include -#include -#include -#include -#include - -using namespace cubos::engine; - -/// [Get handles to assets] -static const Asset CarAsset = AnyAsset("059c16e7-a439-44c7-9bdc-6e069dba0c75"); -static const Asset PaletteAsset = AnyAsset("1aa5e234-28cb-4386-99b4-39386b0fc215"); -/// [Get handles to assets] - -static void settingsSystem(Write settings) -{ - settings->setString("assets.io.path", SAMPLE_ASSETS_FOLDER); -} - -/// [Load and set palette] -static void setPaletteSystem(Read assets, Write renderer) -{ - // Read the palette's data and pass it to the renderer. - auto palette = assets->read(PaletteAsset); - (*renderer)->setPalette(*palette); -} -/// [Load and set palette] - -static void spawnCameraSystem(Commands cmds, Write activeCameras) -{ - // Spawn the camera entity. - activeCameras->entities[0] = - cmds.create() - .add(Camera{.fovY = 60.0F, .zNear = 0.1F, .zFar = 1000.0F}) - .add(Position{{50.0F, 50.0F, 50.0F}}) - .add(Rotation{glm::quatLookAt(glm::normalize(glm::vec3{-1.0F, -1.0F, -1.0F}), glm::vec3{0.0F, 1.0F, 0.0F})}) - .entity(); -} - -static void spawnLightSystem(Commands cmds) -{ - // Spawn the sun. - cmds.create() - .add(DirectionalLight{glm::vec3(1.0F), 1.0F}) - .add(Rotation{glm::quat(glm::vec3(glm::radians(45.0F), glm::radians(45.0F), 0))}); -} - -/// [Spawn car system] -static void spawnCarSystem(Commands cmds, Read assets) -{ - // Calculate the necessary offset to center the model on (0, 0, 0). - auto car = assets->read(CarAsset); - glm::vec3 offset = glm::vec3(car->size().x, 0.0F, car->size().z) / -2.0F; - - // Create the car entity - cmds.create().add(RenderableGrid{CarAsset, offset}).add(LocalToWorld{}); -} -/// [Spawn car system] - -int main(int argc, char** argv) -{ - Cubos cubos{argc, argv}; - - cubos.addPlugin(rendererPlugin); - /// [Adding the plugin] - cubos.addPlugin(voxelsPlugin); - /// [Adding the plugin] - - cubos.startupSystem(settingsSystem).tagged("cubos.settings"); - cubos.startupSystem(spawnCameraSystem); - cubos.startupSystem(spawnLightSystem); - /// [Adding systems] - cubos.startupSystem(setPaletteSystem).after("cubos.renderer.init"); - cubos.startupSystem(spawnCarSystem).tagged("cubos.assets"); - /// [Adding systems] - - cubos.run(); +#include +#include +#include +#include +#include + +using namespace cubos::engine; + +/// [Get handles to assets] +static const Asset CarAsset = AnyAsset("059c16e7-a439-44c7-9bdc-6e069dba0c75"); +static const Asset PaletteAsset = AnyAsset("1aa5e234-28cb-4386-99b4-39386b0fc215"); +/// [Get handles to assets] + +static void settingsSystem(Write settings) +{ + settings->setString("assets.io.path", SAMPLE_ASSETS_FOLDER); +} + +/// [Load and set palette] +static void setPaletteSystem(Read assets, Write renderer) +{ + // Read the palette's data and pass it to the renderer. + auto palette = assets->read(PaletteAsset); + (*renderer)->setPalette(*palette); +} +/// [Load and set palette] + +static void spawnCameraSystem(Commands cmds, Write activeCameras) +{ + // Spawn the camera entity. + activeCameras->entities[0] = + cmds.create() + .add(Camera{.fovY = 60.0F, .zNear = 0.1F, .zFar = 1000.0F}) + .add(Position{{50.0F, 50.0F, 50.0F}}) + .add(Rotation{glm::quatLookAt(glm::normalize(glm::vec3{-1.0F, -1.0F, -1.0F}), glm::vec3{0.0F, 1.0F, 0.0F})}) + .entity(); +} + +static void spawnLightSystem(Commands cmds) +{ + // Spawn the sun. + cmds.create() + .add(DirectionalLight{glm::vec3(1.0F), 1.0F}) + .add(Rotation{glm::quat(glm::vec3(glm::radians(45.0F), glm::radians(45.0F), 0))}); +} + +/// [Spawn car system] +static void spawnCarSystem(Commands cmds, Read assets) +{ + // Calculate the necessary offset to center the model on (0, 0, 0). + auto car = assets->read(CarAsset); + glm::vec3 offset = glm::vec3(car->size().x, 0.0F, car->size().z) / -2.0F; + + // Create the car entity + cmds.create().add(RenderableGrid{CarAsset, offset}).add(LocalToWorld{}); +} +/// [Spawn car system] + +int main(int argc, char** argv) +{ + Cubos cubos{argc, argv}; + + cubos.addPlugin(rendererPlugin); + /// [Adding the plugin] + cubos.addPlugin(voxelsPlugin); + /// [Adding the plugin] + + cubos.startupSystem(settingsSystem).tagged("cubos.settings"); + cubos.startupSystem(spawnCameraSystem); + cubos.startupSystem(spawnLightSystem); + /// [Adding systems] + cubos.startupSystem(setPaletteSystem).after("cubos.renderer.init"); + cubos.startupSystem(spawnCarSystem).tagged("cubos.assets"); + /// [Adding systems] + + cubos.run(); } \ No newline at end of file diff --git a/engine/src/cubos/engine/renderer/plugin.cpp b/engine/src/cubos/engine/renderer/plugin.cpp index afb5572896..ef4dbea354 100644 --- a/engine/src/cubos/engine/renderer/plugin.cpp +++ b/engine/src/cubos/engine/renderer/plugin.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -113,42 +114,8 @@ static void checkPaletteUpdateSystem(Write assets, Write rende } } -/// @brief Splits the viewport recursively for the given cameras. -/// @param position Viewport position. -/// @param size Viewport size. -/// @param count How many cameras need to be fitted in to the given viewport. -/// @param viewport Output array where the viewports will be set. -static void splitViewport(glm::ivec2 position, glm::ivec2 size, int count, BaseRenderer::Viewport* viewports) -{ - if (count == 1) - { - viewports[0].position = position; - viewports[0].size = size; - } - else if (count >= 2) - { - glm::ivec2 splitSize; - glm::ivec2 splitOffset; - - // Split along the largest axis. - if (size.x > size.y) - { - splitSize = {size.x / 2, size.y}; - splitOffset = {size.x / 2, 0}; - } - else - { - splitSize = {size.x, size.y / 2}; - splitOffset = {0, size.y / 2}; - } - - splitViewport(position, splitSize, count / 2, viewports); - splitViewport(position + splitOffset, splitSize, (count + 1) / 2, &viewports[count / 2]); - } -} - static void draw(Write renderer, Read activeCameras, Write frame, - Query, Read> query) + Query, Read, OptRead> query) { Camera cameras[4]{}; glm::mat4 views[4]{}; @@ -165,17 +132,25 @@ static void draw(Write renderer, Read activeCameras, Wr if (auto components = query[activeCameras->entities[i]]) { - auto [localToWorld, camera] = *components; + auto [localToWorld, camera, viewport] = *components; cameras[cameraCount].fovY = camera->fovY; cameras[cameraCount].zNear = camera->zNear; cameras[cameraCount].zFar = camera->zFar; + if (viewport) + { + viewports[i].position = viewport->position; + viewports[i].size = viewport->size; + } + else + { + viewports[i].position = {0, 0}; + viewports[i].size = (*renderer)->size(); + } views[cameraCount] = glm::inverse(localToWorld->mat); cameraCount += 1; } } - splitViewport({0, 0}, (*renderer)->size(), cameraCount, viewports); - if (cameraCount == 0) { CUBOS_WARN("No active camera set - renderer skipping frame"); @@ -206,6 +181,7 @@ void cubos::engine::rendererPlugin(Cubos& cubos) cubos.addComponent(); cubos.addComponent(); cubos.addComponent(); + cubos.addComponent(); cubos.startupTag("cubos.renderer.init").after("cubos.window.init"); cubos.tag("cubos.renderer.frame").after("cubos.transform.update"); diff --git a/engine/src/cubos/engine/renderer/viewport.cpp b/engine/src/cubos/engine/renderer/viewport.cpp new file mode 100644 index 0000000000..3751b7e63d --- /dev/null +++ b/engine/src/cubos/engine/renderer/viewport.cpp @@ -0,0 +1,12 @@ +#include +#include + +#include + +CUBOS_REFLECT_IMPL(cubos::engine::Viewport) +{ + return core::ecs::ComponentTypeBuilder("cubos::engine::Viewport") + .withField("position", &Viewport::position) + .withField("size", &Viewport::size) + .build(); +} diff --git a/engine/src/cubos/engine/splitscreen/plugin.cpp b/engine/src/cubos/engine/splitscreen/plugin.cpp new file mode 100644 index 0000000000..b8fc05963f --- /dev/null +++ b/engine/src/cubos/engine/splitscreen/plugin.cpp @@ -0,0 +1,99 @@ +#include + +#include +#include +#include +#include +#include + +using namespace cubos::engine; + +/// @brief Splits the viewport recursively for the given cameras. +/// @param position Viewport position. +/// @param size Viewport size. +/// @param count How many cameras need to be fitted in to the given viewport. +/// @param activeCameras Output array where the viewports will be set. +static void setViewportCameras(glm::ivec2 position, glm::ivec2 size, int count, glm::ivec2* positions, + glm::ivec2* sizes) +{ + if (count == 1) + { + positions[0] = position; + sizes[0] = size; + } + else if (count >= 2) + { + glm::ivec2 splitSize; + glm::ivec2 splitOffset; + + // Split along the largest axis. + if (size.x > size.y) + { + splitSize = {size.x / 2, size.y}; + splitOffset = {size.x / 2, 0}; + } + else + { + splitSize = {size.x, size.y / 2}; + splitOffset = {0, size.y / 2}; + } + + setViewportCameras(position, splitSize, count / 2, positions, sizes); + setViewportCameras(position + splitOffset, splitSize, (count + 1) / 2, &positions[count / 2], + &sizes[count / 2]); + } +} + +static void splitViewportSystem(Commands commands, Write renderer, Read activeCameras, + Query, Write, OptWrite> query) +{ + glm::ivec2 positions[4]; + glm::ivec2 sizes[4]; + int cameraCount = 0; + + for (int i = 0; i < 4; ++i) // NOLINT(modernize-loop-convert) + { + if (activeCameras->entities[i].isNull()) + { + continue; + } + + if (auto components = query[activeCameras->entities[i]]) + { + cameraCount += 1; + } + } + + setViewportCameras({0, 0}, (*renderer)->size(), cameraCount, positions, sizes); + + for (int i = 0; i < 4; ++i) // NOLINT(modernize-loop-convert) + { + if (activeCameras->entities[i].isNull()) + { + continue; + } + + if (auto components = query[activeCameras->entities[i]]) + { + auto [localToWorld, camera, viewport] = *components; + if (!viewport) + { + commands.add(activeCameras->entities[i], + Viewport{.position = glm::ivec2{positions[i][0], positions[i][1]}, + .size = glm::ivec2{sizes[i][0], sizes[i][1]}}); + } + else + { + viewport->position[0] = positions[i][0]; + viewport->position[1] = positions[i][1]; + viewport->size[0] = sizes[i][0]; + viewport->size[1] = positions[i][1]; + } + } + } +} + +void cubos::engine::splitscreenPlugin(Cubos& cubos) +{ + cubos.system(splitViewportSystem).tagged("cubos.renderer.frame"); +}