]> git.tdb.fi Git - libs/game.git/commitdiff
Add virtual reality support
authorMikko Rasa <tdb@tdb.fi>
Sun, 26 Jan 2025 16:26:37 +0000 (18:26 +0200)
committerMikko Rasa <tdb@tdb.fi>
Mon, 27 Jan 2025 11:28:30 +0000 (13:28 +0200)
13 files changed:
Build
source/gameview/application.h
source/gameview/motiontracker.cpp [new file with mode: 0644]
source/gameview/motiontracker.h [new file with mode: 0644]
source/gameview/playerinput.cpp
source/gameview/playerinput.h
source/gameview/presenter.cpp
source/gameview/presenter.h
source/gameview/renderer.cpp
source/gameview/renderer.h
source/gameview/resources.h
source/gameview/trackedelement.cpp [new file with mode: 0644]
source/gameview/trackedelement.h [new file with mode: 0644]

diff --git a/Build b/Build
index e2a9e8cfee68a3d45480bda96a638f8c556d8a45..f32bc01fe935deec920561d844a7f1a90b748b55 100644 (file)
--- a/Build
+++ b/Build
@@ -29,6 +29,7 @@ package "mspgame"
                use "mspgame";
                require "mspgui";
                require "mspgl";
+               require "mspvr";
                install true;
                install_map
                {
index 0a2aea2923f3d033f9f602eb209629255bece0cb..6f995c41b2ae75080883900e62e9dd2f6036f191 100644 (file)
@@ -7,6 +7,7 @@
 #include <msp/gl/windowview.h>
 #include <msp/graphics/display.h>
 #include <msp/graphics/window.h>
+#include <msp/vr/system.h>
 #include "playerinput.h"
 #include "presenter.h"
 
@@ -25,6 +26,7 @@ protected:
        ResourcesType resources;
        Game::Director director;
        GL::WindowView gl_view;
+       std::unique_ptr<VR::System> vr_system;
        Presenter presenter;
        PlayerInput player_input;
 
@@ -34,6 +36,8 @@ public:
        int main() override;
 protected:
        void tick() override;
+
+       bool try_enable_vr();
 };
 
 template<typename T, typename R>
@@ -63,6 +67,18 @@ void Application<T, R>::tick()
        director.tick();
 }
 
+template<typename T, typename R>
+bool Application<T, R>::try_enable_vr()
+{
+       vr_system = VR::System::create_autodetect();
+       if(vr_system)
+       {
+               player_input.enable_vr(*vr_system);
+               presenter.enable_vr(*vr_system);
+       }
+       return static_cast<bool>(vr_system);
+}
+
 } // namespace Msp::GameView
 
 #endif
diff --git a/source/gameview/motiontracker.cpp b/source/gameview/motiontracker.cpp
new file mode 100644 (file)
index 0000000..b10633e
--- /dev/null
@@ -0,0 +1,64 @@
+#include "motiontracker.h"
+#include <msp/game/root.h>
+#include <msp/game/transform.h>
+
+using namespace std;
+
+namespace Msp::GameView {
+
+MotionTracker::MotionTracker(Game::Stage &s):
+       System(s)
+{
+       declare_dependency<Game::Transform>(WRITE);
+}
+
+void MotionTracker::track(const VR::HeadTrackingCamera &cam)
+{
+       Part &part = get_or_create_part(TrackedElement::HEAD);
+       part.head = &cam;
+}
+
+void MotionTracker::track(const VR::MotionController &ctrl)
+{
+       TrackedElement::Role role;
+       switch(ctrl.get_role())
+       {
+       case VR::LEFT_HAND: role = TrackedElement::LEFT_HAND; break;
+       case VR::RIGHT_HAND: role = TrackedElement::RIGHT_HAND; break;
+       default: throw invalid_argument("MotionTracker::track");
+       }
+
+       Part &part = get_or_create_part(role);
+       part.controller = &ctrl;
+}
+
+auto MotionTracker::get_or_create_part(TrackedElement::Role role) -> Part &
+{
+       auto i = find_if(parts, [role](const Part &p){ return p.element->get_role()==role; });
+       if(i!=parts.end())
+               return *i;
+
+       Part &part = parts.emplace_back();
+       part.entity = Game::Owned<Game::Entity>(stage.get_root(), Game::TransformValues());
+       part.element = Game::Owned<TrackedElement>(part.entity, role);
+
+       return part;
+}
+
+void MotionTracker::tick(Time::TimeDelta)
+{
+       for(const Part &p: parts)
+       {
+               GL::Matrix matrix;
+               if(p.head)
+                       matrix = p.head->get_object_matrix();
+               else if(p.controller)
+                       matrix = p.controller->get_matrix();
+               else
+                       continue;
+
+               p.entity->get_transform()->set_values(Game::TransformValues::from_matrix(matrix));
+       }
+}
+
+} // namespace Msp::GameView
diff --git a/source/gameview/motiontracker.h b/source/gameview/motiontracker.h
new file mode 100644 (file)
index 0000000..36a66b4
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef MSP_GAMEVIEW_MOTIONTRACKER_H_
+#define MSP_GAMEVIEW_MOTIONTRACKER_H_
+
+#include <msp/game/owned.h>
+#include <msp/game/system.h>
+#include <msp/vr/headtrackingcamera.h>
+#include <msp/vr/motioncontroller.h>
+#include "trackedelement.h"
+
+namespace Msp::GameView {
+
+class MSPGAMEVIEW_API MotionTracker: public Game::System
+{
+private:
+       struct Part
+       {
+               const VR::HeadTrackingCamera *head = nullptr;
+               const VR::MotionController *controller = nullptr;
+               Game::Owned<Game::Entity> entity;
+               Game::Owned<TrackedElement> element;
+       };
+
+       std::vector<Part> parts;
+
+public:
+       MotionTracker(Game::Stage &);
+
+       void track(const VR::HeadTrackingCamera &);
+       void track(const VR::MotionController &);
+private:
+       Part &get_or_create_part(TrackedElement::Role);
+
+public:
+       void tick(Time::TimeDelta) override;
+};
+
+} // namespace Msp::GameView
+
+#endif
index e93f473c9d01d6153179fa1acbbd44e33190711a..487134af22a6561a6881c8bb3446d0faa2ed6b1f 100644 (file)
@@ -16,9 +16,20 @@ PlayerInput::PlayerInput(Game::Director &director, Graphics::Window &wnd):
        bindings = director.get_resources().get_all<Input::Bindings>();
 }
 
+void PlayerInput::enable_vr(VR::System &vr_sys)
+{
+       vr_controllers.push_back(vr_sys.create_controller(VR::LEFT_HAND));
+       vr_controllers.push_back(vr_sys.create_controller(VR::RIGHT_HAND));
+       for(const auto &c: vr_controllers)
+               vr_hub.attach(*c);
+}
+
 void PlayerInput::init_players()
 {
-       add_player(kbm_hub);
+       if(!vr_controllers.empty())
+               add_player(vr_hub);
+       else
+               add_player(kbm_hub);
 }
 
 void PlayerInput::add_player(Input::Device &dev)
index c12d1b9217d341e8641057a9fb163ea8f64f415f..1334a600b00a33222034ee6e6eea28dffcc61680 100644 (file)
@@ -8,6 +8,8 @@
 #include <msp/input/keyboard.h>
 #include <msp/input/mouse.h>
 #include <msp/game/director.h>
+#include <msp/vr/motioncontroller.h>
+#include <msp/vr/system.h>
 #include "events.h"
 #include "mspgameview_api.h"
 
@@ -37,6 +39,8 @@ private:
        Input::Keyboard keyboard;
        Input::Mouse mouse;
        Input::Hub kbm_hub;
+       std::vector<std::unique_ptr<VR::MotionController>> vr_controllers;
+       Input::Hub vr_hub;
        std::vector<Input::Bindings *> bindings;
        SchemeFactoryFunc *scheme_factory = nullptr;
        std::vector<Player> players;
@@ -45,6 +49,8 @@ private:
 public:
        PlayerInput(Game::Director &, Graphics::Window &);
 
+       void enable_vr(VR::System &);
+
        template<typename T>
                requires std::is_base_of_v<Input::ControlScheme, T>
        void set_control_scheme_type(Mode = ONE_LOCAL_PLAYER);
index 095d3e0b98ce075651db876b8b2b21cbe459c619..2d8ffc9437106447ce0f80510e8411f65ae6cb56 100644 (file)
@@ -3,6 +3,7 @@
 #include <msp/game/events.h>
 #include <msp/game/landscape.h>
 #include <msp/game/stage.h>
+#include "motiontracker.h"
 #include "renderer.h"
 #include "terrainmeshcreator.h"
 
@@ -16,6 +17,7 @@ Presenter::Presenter(Game::Director &d, GL::View &v):
        resources(director.get_resources()),
        event_observer(director.get_event_bus())
 {
+       event_observer.observe<Events::LocalPlayerArrived>([this](auto &e){ player_arrived(e); });
        event_observer.observe<Game::Events::StageActivated>([this](auto &e){ stage_activated(e); });
 
        if(Game::Stage *active_stage = director.get_active_stage())
@@ -28,6 +30,27 @@ Presenter::~Presenter()
                s->get_stage().remove_system(*s);
 }
 
+void Presenter::enable_vr(VR::System &vr_sys)
+{
+       vr_system = &vr_sys;
+       if(active_renderer)
+               stage_activated({ active_renderer->get_stage() });
+}
+
+void Presenter::player_arrived(const Events::LocalPlayerArrived &event)
+{
+       for(unsigned i=0;; ++i)
+       {
+               Input::Device *dev = event.device.find_subdevice(VR::VR_CONTROLLER, i);
+               if(!dev)
+                       break;
+
+               VR::MotionController *vr_ctrl = dynamic_cast<VR::MotionController *>(dev);
+               if(vr_ctrl)
+                       vr_controllers.push_back(vr_ctrl);
+       }
+}
+
 void Presenter::stage_activated(const Game::Events::StageActivated &event)
 {
        if(active_renderer)
@@ -36,11 +59,32 @@ void Presenter::stage_activated(const Game::Events::StageActivated &event)
        active_renderer = event.stage.get_system<Renderer>();
        if(!active_renderer)
                systems.push_back(active_renderer = &event.stage.add_system<Renderer>());
+       MotionTracker *tracker = nullptr;
+       if(vr_system)
+       {
+               tracker = event.stage.get_system<MotionTracker>();
+               if(!tracker)
+               {
+                       systems.push_back(tracker = &event.stage.add_system<MotionTracker>());
+                       for(VR::MotionController *c: vr_controllers)
+                               tracker->track(*c);
+               }
+       }
        if(event.stage.get_system<Game::Landscape>() && !event.stage.get_system<TerrainMeshCreator>())
                systems.push_back(&event.stage.add_system<TerrainMeshCreator>());
 
        if(active_renderer)
-               active_renderer->output_to_view(gl_view);
+       {
+               if(vr_system)
+               {
+                       VR::StereoView *view = active_renderer->output_to_vr(*vr_system, gl_view);
+                       vr_system->set_tracking_view(view);
+                       if(const VR::HeadTrackingCamera *head = view->get_head_camera())
+                               tracker->track(*head);
+               }
+               else
+                       active_renderer->output_to_view(gl_view);
+       }
 }
 
 } // namespace Msp::GameView
index bbf3da516fe0038b67f407e9ef2ebccffdedb026..c64b8cf1a519030b81c73357450978325e452158 100644 (file)
@@ -6,6 +6,8 @@
 #include <msp/game/eventobserver.h>
 #include <msp/game/system.h>
 #include <msp/gl/view.h>
+#include <msp/vr/system.h>
+#include "events.h"
 #include "mspgameview_api.h"
 
 namespace Msp::GameView {
@@ -17,16 +19,21 @@ class MSPGAMEVIEW_API Presenter
 private:
        Game::Director &director;
        GL::View &gl_view;
+       VR::System *vr_system = nullptr;
        DataFile::Collection &resources;
        Game::EventObserver event_observer;
        std::vector<Game::System *> systems;
        Renderer *active_renderer = nullptr;
+       std::vector<VR::MotionController *> vr_controllers;
 
 public:
        Presenter(Game::Director &, GL::View &);
        ~Presenter();
 
+       void enable_vr(VR::System &);
+
 private:
+       void player_arrived(const Events::LocalPlayerArrived &);
        void stage_activated(const Game::Events::StageActivated &);
 };
 
index 9bea942ae6113e63d1f13cbaececc2c512a04a7a..b521ccb76df91af06c5e15f3b0083112272a6243 100644 (file)
@@ -48,6 +48,15 @@ void Renderer::output_to_view(GL::View &v)
                create_sequence();
 }
 
+VR::StereoView *Renderer::output_to_vr(VR::System &vr_sys, GL::View &view)
+{
+       cancel_output();
+       vr_view = vr_sys.create_view(view, gl_camera);
+       if(active_camera)
+               create_sequence();
+       return vr_view.get();
+}
+
 void Renderer::cancel_output()
 {
        if(gl_view)
@@ -55,13 +64,19 @@ void Renderer::cancel_output()
                gl_view->set_content(nullptr);
                gl_view->set_camera(nullptr);
        }
+       vr_view = nullptr;
        sequence = nullptr;
 }
 
 void Renderer::create_sequence()
 {
        unsigned w, h;
-       if(gl_view)
+       if(vr_view)
+       {
+               w = vr_view->get_render_width();
+               h = vr_view->get_render_height();
+       }
+       else if(gl_view)
        {
                w = gl_view->get_width();
                h = gl_view->get_height();
@@ -83,7 +98,10 @@ void Renderer::create_sequence()
        sequence->add_postprocessor(*colorcurve);
        sequence->add_owned(colorcurve.release());
 
-       gl_view->set_content(sequence.get());
+       if(vr_view)
+               vr_view->set_content(sequence.get());
+       else
+               gl_view->set_content(sequence.get());
 }
 
 void Renderer::component_created(const Game::Events::ComponentCreated &event)
@@ -145,7 +163,7 @@ void Renderer::component_destroyed(const Game::Events::ComponentDestroyed &event
 void Renderer::camera_changed(const Game::Events::CameraChanged &event)
 {
        active_camera = event.camera;
-       if(active_camera && gl_view)
+       if(active_camera && (gl_view || vr_view))
                create_sequence();
 }
 
@@ -228,7 +246,9 @@ void Renderer::tick(Time::TimeDelta)
        stage.iterate_objects<MeshRenderer>([](MeshRenderer &m){ m.update_matrix(); });
        stage.iterate_objects<LightEmitter>([](LightEmitter &e){ e.update_matrix(); });
 
-       if(gl_view)
+       if(vr_view)
+               vr_view->render();
+       else if(gl_view)
                gl_view->render();
 }
 
index 7b0d4cea448971b40350b0f5f5810e0c87bed97b..04d24391dbd875b7ccbac9602203bb6d33a0bee5 100644 (file)
@@ -14,6 +14,8 @@
 #include <msp/gl/simplescene.h>
 #include <msp/gl/slot.h>
 #include <msp/gl/view.h>
+#include <msp/vr/stereoview.h>
+#include <msp/vr/system.h>
 #include "mspgameview_api.h"
 
 namespace Msp::GameView {
@@ -54,12 +56,14 @@ private:
        unsigned shadow_base_size = 4096;
        bool shadows_changed = false;
        GL::View *gl_view = nullptr;
+       std::unique_ptr<VR::StereoView> vr_view;
 
 public:
        Renderer(Game::Stage &);
        ~Renderer();
 
        void output_to_view(GL::View &);
+       VR::StereoView *output_to_vr(VR::System &, GL::View &);
        void cancel_output();
 private:
        void create_sequence();
index 316a550637d29e5cd24aca183d5d9e96227f3915..91f5927b06c4e56c4f0b82450a6ce048ffa24aac 100644 (file)
@@ -1,12 +1,12 @@
 #ifndef MSP_GAMEVIEW_RESOURCES_H_
 #define MSP_GAMEVIEW_RESOURCES_H_
 
-#include <msp/gl/resources.h>
+#include <msp/vr/resources.h>
 #include "mspgameview_api.h"
 
 namespace Msp::GameView {
 
-class MSPGAMEVIEW_API Resources: public GL::Resources
+class MSPGAMEVIEW_API Resources: public VR::Resources
 {
 public:
        Resources();
diff --git a/source/gameview/trackedelement.cpp b/source/gameview/trackedelement.cpp
new file mode 100644 (file)
index 0000000..2b94c23
--- /dev/null
@@ -0,0 +1,10 @@
+#include "trackedelement.h"
+
+namespace Msp::GameView {
+
+TrackedElement::TrackedElement(Game::Handle<Game::Entity> p, Role r):
+       Component(p),
+       role(r)
+{ }
+
+} // namespace Msp::GameView
diff --git a/source/gameview/trackedelement.h b/source/gameview/trackedelement.h
new file mode 100644 (file)
index 0000000..6158d92
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef MSP_GAMEVIEW_TRACKEDELEMENT_H_
+#define MSP_GAMEVIEW_TRACKEDELEMENT_H_
+
+#include <msp/game/component.h>
+#include "mspgameview_api.h"
+
+namespace Msp::GameView {
+
+class MSPGAMEVIEW_API TrackedElement: public Game::Component
+{
+public:
+       enum Role
+       {
+               HEAD,
+               LEFT_HAND,
+               RIGHT_HAND
+       };
+
+private:
+       Role role;
+
+public:
+       TrackedElement(Game::Handle<Game::Entity>, Role);
+
+       Role get_role() const { return role; }
+};
+
+} // namespace Msp::GameView
+
+#endif