]> git.tdb.fi Git - libs/game.git/commitdiff
Implement a VR mode for the playground example
authorMikko Rasa <tdb@tdb.fi>
Tue, 22 Apr 2025 21:40:07 +0000 (00:40 +0300)
committerMikko Rasa <tdb@tdb.fi>
Tue, 22 Apr 2025 21:47:31 +0000 (00:47 +0300)
Currently grabbing toys only works with left hand because Input::Bindings
does not support multiple devices of the same type.

15 files changed:
examples/playground/data/default.player.setup [new file with mode: 0644]
examples/playground/data/left.hand.setup [new file with mode: 0644]
examples/playground/data/right.hand.setup [new file with mode: 0644]
examples/playground/data/vr.binds [new file with mode: 0644]
examples/playground/source/controls.cpp
examples/playground/source/controls.h
examples/playground/source/hand.cpp [new file with mode: 0644]
examples/playground/source/hand.h [new file with mode: 0644]
examples/playground/source/handpicker.cpp [new file with mode: 0644]
examples/playground/source/handpicker.h [new file with mode: 0644]
examples/playground/source/player.cpp [new file with mode: 0644]
examples/playground/source/player.h [new file with mode: 0644]
examples/playground/source/playground.cpp
examples/playground/source/playground.h
examples/playground/source/setups.mgs

diff --git a/examples/playground/data/default.player.setup b/examples/playground/data/default.player.setup
new file mode 100644 (file)
index 0000000..a35899b
--- /dev/null
@@ -0,0 +1,10 @@
+head_elevation 1.65;
+camera
+{
+       near_clip 0.05;
+       far_clip 20.0;
+};
+shadow
+{
+       radius 15.0;
+};
diff --git a/examples/playground/data/left.hand.setup b/examples/playground/data/left.hand.setup
new file mode 100644 (file)
index 0000000..9992d72
--- /dev/null
@@ -0,0 +1,22 @@
+palm_shape
+{
+       box
+       {
+               extent 0.04 0.1 0.14;
+       };
+       technique_name "checker.tech";
+};
+
+trigger_shape
+{
+       box
+       {
+               extent 0.05 0.12 0.16;
+       };
+};
+trigger_body
+{
+       report_collisions true;
+       corporeal false;
+};
+trigger_offset 0.045 0.0 0.0;
diff --git a/examples/playground/data/right.hand.setup b/examples/playground/data/right.hand.setup
new file mode 100644 (file)
index 0000000..177e633
--- /dev/null
@@ -0,0 +1,22 @@
+palm_shape
+{
+       box
+       {
+               extent 0.04 0.1 0.14;
+       };
+       technique_name "checker.tech";
+};
+
+trigger_shape
+{
+       box
+       {
+               extent 0.05 0.12 0.16;
+       };
+};
+trigger_body
+{
+       report_collisions true;
+       corporeal false;
+};
+trigger_offset -0.045 0.0 0.0;
diff --git a/examples/playground/data/vr.binds b/examples/playground/data/vr.binds
new file mode 100644 (file)
index 0000000..bb3773d
--- /dev/null
@@ -0,0 +1,5 @@
+device VR_CONTROLLER;
+binding "grab_left"
+{
+       button { button 33; };
+};
index 2a2c02343f55cd82575cd0d8f2cc237e43a56554..149931884f36b5740e6294a2ca801e62c3c584dd 100644 (file)
@@ -5,3 +5,10 @@ Controls::Controls()
        add("pointer", pointer);
        add("pick", pick);
 }
+
+
+VRControls::VRControls()
+{
+       add("grab_left", grab_left);
+       add("grab_right", grab_right);
+}
index 2f3db15ca36c8bc0cf4a41e72fce75d6610444b6..e849f8dd59a86e45e6d2bed205a24e6f499c3d24 100644 (file)
@@ -12,4 +12,13 @@ struct Controls: public Msp::Input::ControlScheme
        Controls();
 };
 
+
+struct VRControls: public Msp::Input::ControlScheme
+{
+       Msp::Input::BinaryControl grab_left;
+       Msp::Input::BinaryControl grab_right;
+
+       VRControls();
+};
+
 #endif
diff --git a/examples/playground/source/hand.cpp b/examples/playground/source/hand.cpp
new file mode 100644 (file)
index 0000000..4694512
--- /dev/null
@@ -0,0 +1,18 @@
+#include "hand.h"
+#include <msp/game/transform.h>
+#include "toy.h"
+
+using namespace Msp;
+
+Hand::Hand(Game::Handle<Game::Entity> e, const Setup &s):
+       Entity(e, Game::TransformValues()),
+       setup(s),
+       palm_shape(this, setup.palm_shape),
+       palm_body(this, setup.palm_body),
+       palm_motion(this),
+       trigger_entity(this, Game::TransformValues(s.trigger_offset)),
+       trigger_shape(trigger_entity, setup.trigger_shape),
+       trigger_body(trigger_entity, setup.trigger_body)
+{
+       palm_body->set_kinematic(true);
+}
diff --git a/examples/playground/source/hand.h b/examples/playground/source/hand.h
new file mode 100644 (file)
index 0000000..1bd04aa
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef HAND_H_
+#define HAND_H_
+
+#include <msp/game/entity.h>
+#include <msp/game/motion.h>
+#include <msp/game/rigidbody.h>
+#include <msp/game/shape.h>
+#include <msp/game/weld.h>
+#include "playground/setups.h"
+
+class Toy;
+
+class Hand: public Msp::Game::Entity
+{
+public:
+       using Setup = HandSetup;
+
+private:
+       const Setup &setup;
+
+       Msp::Game::Owned<Msp::Game::Shape> palm_shape;
+       Msp::Game::Owned<Msp::Game::RigidBody> palm_body;
+       Msp::Game::Owned<Msp::Game::Motion> palm_motion;
+
+       Msp::Game::Owned<Msp::Game::Entity> trigger_entity;
+       Msp::Game::Owned<Msp::Game::Shape> trigger_shape;
+       Msp::Game::Owned<Msp::Game::RigidBody> trigger_body;
+
+public:
+       Hand(Msp::Game::Handle<Msp::Game::Entity>, const Setup &);
+
+       Msp::Game::Handle<Msp::Game::Shape> get_trigger_shape() const { return trigger_shape; }
+};
+
+#endif
diff --git a/examples/playground/source/handpicker.cpp b/examples/playground/source/handpicker.cpp
new file mode 100644 (file)
index 0000000..9b9cae3
--- /dev/null
@@ -0,0 +1,97 @@
+#include "handpicker.h"
+#include <msp/gameview/trackedelement.h>
+#include "controls.h"
+#include "hand.h"
+#include "toy.h"
+
+using namespace Msp;
+
+HandPicker::HandPicker(Game::Stage &s):
+       System(s),
+       event_observer(stage.get_event_bus()),
+       left_setup(stage.get_resources().get<HandSetup>("left.hand.setup")),
+       right_setup(stage.get_resources().get<HandSetup>("right.hand.setup"))
+{
+       event_observer.observe<Game::Events::ComponentCreated>([this](auto &e){ component_created(e); });
+       event_observer.observe<Game::Events::EntityDestroyed>([this](auto &e){ entity_destroyed(e); });
+       event_observer.observe<Game::Events::Collision>([this](auto &e){ collision(e); });
+}
+
+HandPicker::~HandPicker() = default;
+
+void HandPicker::set_controls(VRControls *c)
+{
+       controls = c;
+}
+
+void HandPicker::component_created(const Game::Events::ComponentCreated &event)
+{
+       if(auto tracked = dynamic_handle_cast<GameView::TrackedElement>(event.component))
+       {
+               if(tracked->get_role()==GameView::TrackedElement::LEFT_HAND)
+                       left_hand.hand = Game::Owned<Hand>(tracked->get_entity(), left_setup);
+               else if(tracked->get_role()==GameView::TrackedElement::RIGHT_HAND)
+                       right_hand.hand = Game::Owned<Hand>(tracked->get_entity(), right_setup);
+       }
+}
+
+void HandPicker::entity_destroyed(const Game::Events::EntityDestroyed &event)
+{
+       if(left_hand.hand && event.entity==left_hand.hand->get_parent())
+               left_hand.hand = nullptr;
+       else if(right_hand.hand && event.entity==right_hand.hand->get_parent())
+               right_hand.hand = nullptr;
+}
+
+void HandPicker::collision(const Game::Events::Collision &event)
+{
+       if(event.shape1==left_hand.hand->get_trigger_shape())
+               maybe_grab(left_hand, event.shape2->get_entity());
+       else if(event.shape1==right_hand.hand->get_trigger_shape())
+               maybe_grab(right_hand, event.shape2->get_entity());
+       else if(event.shape2==left_hand.hand->get_trigger_shape())
+               maybe_grab(left_hand, event.shape1->get_entity());
+       else if(event.shape2==right_hand.hand->get_trigger_shape())
+               maybe_grab(right_hand, event.shape1->get_entity());
+}
+
+void HandPicker::maybe_grab(TrackedHand &hand, Game::Handle<Game::Entity> target)
+{
+       if(hand.held_toy || !hand.grab_activated)
+               return;
+
+       auto toy = dynamic_handle_cast<Toy>(target);
+       if(!toy)
+               return;
+
+       defer([&hand, toy](){
+               hand.held_toy = toy;
+               hand.joint = Game::Owned<Game::Weld>(hand.hand, toy->get_rigid_body());
+       });
+}
+
+void HandPicker::release(TrackedHand &hand)
+{
+       defer([&hand](){
+               hand.joint = nullptr;
+               hand.held_toy = nullptr;
+       });
+}
+
+void HandPicker::tick(Time::TimeDelta)
+{
+       if(!controls)
+               return;
+
+       if(controls->grab_left.was_released())
+               release(left_hand);
+       else
+               left_hand.grab_activated = controls->grab_left.was_activated();
+
+       if(controls->grab_right.was_released())
+               release(right_hand);
+       else
+               right_hand.grab_activated = controls->grab_right.was_activated();
+
+       controls->reset_edges();
+}
diff --git a/examples/playground/source/handpicker.h b/examples/playground/source/handpicker.h
new file mode 100644 (file)
index 0000000..17c282d
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef HANDPICKER_H_
+#define HANDPICKER_H_
+
+#include <msp/game/joint.h>
+#include <msp/game/owned.h>
+#include <msp/game/system.h>
+#include "playground/setups.h"
+
+class Hand;
+class Toy;
+struct VRControls;
+
+class HandPicker: public Msp::Game::System
+{
+private:
+       struct TrackedHand
+       {
+               Msp::Game::Owned<Hand> hand;
+               Msp::Game::Handle<Toy> held_toy;
+               Msp::Game::Owned<Msp::Game::Joint> joint;
+               bool grab_activated = false;
+       };
+
+       Msp::Game::EventObserver event_observer;
+       const HandSetup &left_setup;
+       const HandSetup &right_setup;
+       VRControls *controls = nullptr;
+       TrackedHand left_hand;
+       TrackedHand right_hand;
+
+public:
+       HandPicker(Msp::Game::Stage &);
+       ~HandPicker();
+
+       void set_controls(VRControls *);
+
+private:
+       void component_created(const Msp::Game::Events::ComponentCreated &);
+       void entity_destroyed(const Msp::Game::Events::EntityDestroyed &);
+       void collision(const Msp::Game::Events::Collision &);
+       void maybe_grab(TrackedHand &, Msp::Game::Handle<Msp::Game::Entity>);
+       void release(TrackedHand &);
+
+public:
+       void tick(Msp::Time::TimeDelta) override;
+};
+
+#endif
diff --git a/examples/playground/source/player.cpp b/examples/playground/source/player.cpp
new file mode 100644 (file)
index 0000000..f0d608a
--- /dev/null
@@ -0,0 +1,14 @@
+#include "player.h"
+
+using namespace Msp;
+
+Player::Player(Game::Handle<Game::Entity> p, const Setup &s, const Game::TransformValues &tv):
+       Entity(p, tv),
+       setup(s),
+       anchor(this),
+       shadow(this, setup.shadow),
+       head(this, Game::TransformValues({ 0.0f, 0.0f, setup.head_elevation },
+               Geometry::make_quat(40.0f*Geometry::degrees, { 0.0f, -1.0f, 0.0f })*
+               Geometry::make_quat(-90.0f*Geometry::degrees, { 0.0f, 0.0f, 1.0f }))),
+       camera(head, setup.camera)
+{ }
diff --git a/examples/playground/source/player.h b/examples/playground/source/player.h
new file mode 100644 (file)
index 0000000..3eedbd0
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef PLAYER_H_
+#define PLAYER_H_
+
+#include <msp/game/camera.h>
+#include <msp/game/entity.h>
+#include <msp/game/shadowtarget.h>
+#include <msp/game/trackinganchor.h>
+#include <msp/game/transform.h>
+#include "playground/setups.h"
+
+class Player: public Msp::Game::Entity
+{
+public:
+       using Setup = PlayerSetup;
+
+private:
+       const Setup &setup;
+       Msp::Game::Owned<Msp::Game::TrackingAnchor> anchor;
+       Msp::Game::Owned<Msp::Game::ShadowTarget> shadow;
+       Msp::Game::Owned<Msp::Game::Entity> head;
+       Msp::Game::Owned<Msp::Game::Camera> camera;
+
+public:
+       Player(Msp::Game::Handle<Msp::Game::Entity>, const Setup &, const Msp::Game::TransformValues & = {});
+};
+
+#endif
index 53551292ca473debc08a211f7d5c4818fcfcbb31..13324a00bc6bb68929a72ae94c9f95a4ebc99d83 100644 (file)
@@ -1,24 +1,34 @@
 #include "playground.h"
 #include <random>
+#include <msp/core/getopt.h>
 #include <msp/game/physicssystem.h>
 #include <msp/game/root.h>
 #include <msp/game/stageplan.h>
 #include "controls.h"
 #include "fixture.h"
+#include "handpicker.h"
 #include "mousepicker.h"
+#include "player.h"
 #include "playground/setups.h"
 #include "toy.h"
 
 using namespace std;
 using namespace Msp;
 
-Playground::Playground(int, char **):
+PlaygroundOptions::PlaygroundOptions(int argc, char **argv)
+{
+       GetOpt getopt;
+       getopt.add_option("vr", options.vr, GetOpt::NO_ARG).set_help("Enable virtual reality");
+       getopt(argc, argv);
+}
+
+Playground::Playground(int argc, char **argv):
+       PlaygroundOptions(argc, argv),
+       Application(options.vr ? VIRTUAL_REALITY : NO_FEATURES),
        event_observer(director.get_event_bus()),
        stage(director.create_stage("playground")),
-       cam_entity(stage.get_root(), Game::TransformValues({ 0.0f, -1.5f, 1.5f }, Geometry::make_quat(45.0f*Geometry::degrees, { 1.0f, 0.0f, 0.0f }))),
-       camera(cam_entity, resources.get<Game::CameraSetup>("playground.camera.setup")),
-       shadow_setup{ 15.0f },
-       shadow_target(cam_entity, shadow_setup),
+       player(stage.get_root(), resources.get<PlayerSetup>("default.player.setup"),
+               Game::TransformValues(get_player_position(options.vr), Geometry::make_quat(90.0f*Geometry::degrees, { 0.0f, 0.0f, 1.0f }))),
        sun_entity(stage.get_root(), Game::TransformValues({}, Geometry::make_quat(30.0f*Geometry::degrees, { 1.0f, -0.5f, 0.0f }))),
        sun(sun_entity, resources.get<Game::LightSetup>("sun.light.setup"))
 {
@@ -27,7 +37,10 @@ Playground::Playground(int, char **):
        event_observer.observe<GameView::Events::LocalPlayerArrived>([this](auto &e){ player_arrived(e); });
 
        Game::PhysicsSystem &physics = stage.add_system<Game::PhysicsSystem>();
-       mouse_picker = &stage.add_system<MousePicker>(physics);
+       if(options.vr)
+               hand_picker = &stage.add_system<HandPicker>();
+       else
+               mouse_picker = &stage.add_system<MousePicker>(physics);
        stage.apply_plan(resources.get<Game::StagePlan>("playground.stage"));
 
        vector<ToySetup *> toy_setups = resources.get_all<ToySetup>();
@@ -42,19 +55,35 @@ Playground::Playground(int, char **):
                toys.emplace_back(stage.get_root(), setup, Game::TransformValues(pos, Geometry::Quaternion<float>::one()));
        }
 
-       player_input.set_control_scheme_type<Controls>();
+       if(options.vr)
+               player_input.set_control_scheme_type<VRControls>();
+       else
+               player_input.set_control_scheme_type<Controls>();
 
        director.activate_stage(stage);
 }
 
+LinAl::Vector<float, 3> Playground::get_player_position(bool vr)
+{
+       if(vr)
+               return { 0.0f, 0.0f, 0.0f };
+       else
+               return { 0.0f, -1.5f, 0.0f };
+}
+
 void Playground::player_arrived(const GameView::Events::LocalPlayerArrived &e)
 {
-       mouse_picker->set_controls(dynamic_cast<Controls *>(&e.controls));
+       if(mouse_picker)
+               mouse_picker->set_controls(dynamic_cast<Controls *>(&e.controls));
+       if(hand_picker)
+               hand_picker->set_controls(dynamic_cast<VRControls *>(&e.controls));
 }
 
 
 PlaygroundResources::PlaygroundResources()
 {
        add_type<FixtureSetup>().suffix(".fixt.setup");
+       add_type<HandSetup>().suffix(".hand.setup");
+       add_type<PlayerSetup>().suffix(".player.setup");
        add_type<ToySetup>().suffix(".toy.setup");
 }
index b07622b32661bb88a62190c958b949765f6d63c3..295bfb4875c1b5590858b6a4a0b49eda04877635 100644 (file)
@@ -1,16 +1,16 @@
 #ifndef PLAYGROUND_H_
 #define PLAYGROUND_H_
 
-#include <msp/game/camera.h>
 #include <msp/game/light.h>
 #include <msp/game/owned.h>
 #include <msp/game/resources.h>
-#include <msp/game/shadowtarget.h>
 #include <msp/game/stage.h>
 #include <msp/gameview/application.h>
 #include <msp/gameview/resources.h>
 
+class HandPicker;
 class MousePicker;
+class Player;
 class Toy;
 
 class PlaygroundResources: public Msp::Game::ApplicationResources, public Msp::GameView::Resources
@@ -19,16 +19,26 @@ public:
        PlaygroundResources();
 };
 
-class Playground: public Msp::GameView::Application<Playground, PlaygroundResources>
+struct PlaygroundOptions
+{
+       struct Options
+       {
+               bool vr = false;
+       };
+
+       Options options;
+
+       PlaygroundOptions(int, char **);
+};
+
+class Playground: public PlaygroundOptions, public Msp::GameView::Application<Playground, PlaygroundResources>
 {
 private:
        Msp::Game::EventObserver event_observer;
        Msp::Game::Stage &stage;
        MousePicker *mouse_picker = nullptr;
-       Msp::Game::Owned<Msp::Game::Entity> cam_entity;
-       Msp::Game::Owned<Msp::Game::Camera> camera;
-       Msp::Game::ShadowTargetSetup shadow_setup;
-       Msp::Game::Owned<Msp::Game::ShadowTarget> shadow_target;
+       HandPicker *hand_picker = nullptr;
+       Msp::Game::Owned<Player> player;
        Msp::Game::Owned<Msp::Game::Entity> sun_entity;
        Msp::Game::Owned<Msp::Game::Light> sun;
        std::vector<Msp::Game::Owned<Toy>> toys;
@@ -37,6 +47,8 @@ public:
        Playground(int, char **);
 
 private:
+       static Msp::LinAl::Vector<float, 3> get_player_position(bool);
+
        void player_arrived(const Msp::GameView::Events::LocalPlayerArrived &);
 };
 
index 61a997c328e925935753b24791a8b49007282af8..f2e84ebb273fb02645f4d7df2d02ce67c1ff2311 100644 (file)
@@ -11,3 +11,19 @@ entity Toy
        field shape Shape;
        field body RigidBody;
 };
+
+entity Hand
+{
+       field palm_shape Shape;
+       field palm_body RigidBody;
+       field trigger_shape Shape;
+       field trigger_body RigidBody;
+       field trigger_offset vector3;
+};
+
+entity Player
+{
+       field camera Camera;
+       field head_elevation float;
+       field shadow ShadowTarget;
+};