Changing Material by Laser Pointer
Another way to extend functionality is to add a new action for the object.
We have a laser pointer that we can grab, throw, and use (turn on a laser beam). Now, let's add an alternative use action - change the pointed object's material. This action will be executed when we press and hold the Grip side button on one controller while using the laser pointer gripped by the opposite controller.
The ObjLaserPointer component implements the base functionality of the laser pointer.
The base component for all interactive objects is VRInteractable (source/Framework/Components/VRInteractable.h). It defines all available states of the interactive objects (grab, hold, use, throw) via the corresponding virtual methods. Components inherited from VRInteractable must implement overrides of these methods. For example, the logic for grabbing the object must be implemented in the grabIt method of the inherited component.
So, we need to add a new action and map it to a new state (the Grip side button is pressed on one controller while the laser pointer is gripped (Grip) by the opposite controller is used):
-
Add a new useItAlt action (alternative use) to the VRInteractable base component:
#pragma once #include <UnigineNode.h> #include <UniginePhysics.h> #include <UnigineComponentSystem.h> #include "Players/VRPlayer.h" class VRPlayer; class VRInteractable : public Unigine::ComponentBase { public: COMPONENT(VRInteractable, Unigine::ComponentBase); // interact methods virtual void grabIt(VRPlayer* player, int hand_num) {} virtual void holdIt(VRPlayer* player, int hand_num) {} virtual void useIt(VRPlayer* player, int hand_num) {} virtual void throwIt(VRPlayer* player, int hand_num) {} ${#HL}$virtual void useItAlt(VRPlayer* player, int hand_num) {} ${HL#}$ };
-
Add an override of the useItAlt() method to the ObjLaserPointer component (as we are going to add a new action for the laser pointer). In the update() method, implement the logic of the useItAlt action that is executed when the corresponding flags are set.
Don't forget to change the value in the PROP_AUTOSAVE macro to 1 to enable updating the property created by the Component System as we're going to make changes to the component.#pragma once #include <UnigineWorld.h> #include "../../Framework/Components/VRInteractable.h" class ObjLaserPointer : public VRInteractable { public: COMPONENT(ObjLaserPointer, VRInteractable); COMPONENT_INIT(init); COMPONENT_UPDATE(update, 1); // property PROP_NAME("laser_pointer"); ${#HL}$PROP_AUTOSAVE(1); ${HL#}$ // parameters PROP_PARAM(Node, laser); PROP_PARAM(Node, laser_ray); PROP_PARAM(Node, laser_hit); ${#HL}$// array of materials to be used for switching PROP_ARRAY(Material, materials); ${HL#}$ // interact methods void useIt(VRPlayer* player, int hand_num) override; void grabIt(VRPlayer* player, int hand_num) override; void throwIt(VRPlayer* player, int hand_num) override; ${#HL}$// override implementing the alternative action void useItAlt(VRPlayer* player, int hand_num) override; ${HL#}$ protected: void init(); void update(); private: Unigine::ObjectTextPtr obj_text; Unigine::Math::Mat4 laser_ray_mat; int grabbed; ${#HL}$// flag indicating that the alternative action is currently performed bool alt_use = false; // index of the current material to be applied int current_material = 0; ${HL#}$ Unigine::WorldIntersectionPtr intersection = Unigine::WorldIntersection::create(); };
#include "ObjLaserPointer.h" #include "../../Framework/Utils.h" #include "../Global/ObjectLabeling.h" // we don't want to get intersection af the ray with itself, so we use offset #define RAY_OFFSET 0.05f REGISTER_COMPONENT(ObjLaserPointer); using namespace Unigine; using namespace Math; void ObjLaserPointer::init() { laser_ray_mat = laser_ray->getTransform(); grabbed = 0; laser->setEnabled(false); } void ObjLaserPointer::update() { if (laser->isEnabled()) { // calculate laser hit position laser_ray->setTransform(laser_ray_mat); Vec3 dir = Vec3(laser_ray->getWorldDirection(Math::AXIS_Y)); Vec3 p0 = laser_ray->getWorldPosition() + dir * RAY_OFFSET; Vec3 p1 = p0 + dir * 1000; ObjectPtr hit_obj = World::getIntersection(p0, p1, 1, intersection); if (hit_obj) { laser_ray->setScale(vec3(laser_ray->getScale().x, (float)length(intersection->getPoint() - p0) + RAY_OFFSET, laser_ray->getScale().z)); laser_hit->setWorldPosition(intersection->getPoint()); laser_hit->setEnabled(1); ${#HL}$ // implement logic of the 'alternative use' action (cyclic change of materials on the object according to the list) if (alt_use) { // if the list of materials is empty/not defined, do nothing if (materials.nullCheck() || materials.size() < 0) return; current_material++; if (current_material >= materials.size()) current_material = 0; hit_obj->setMaterial(materials[current_material], 0); alt_use = false; } ${HL#}$ } else laser_hit->setEnabled(0); // rotate ray plane to player's head (Y fixed, Z floats) setWorldDirectionY(laser_ray, laser_ray->getWorldDirection(Math::AXIS_Y), vec3(VRPlayer::get()->getHead()->getWorldPosition() - laser_ray->getWorldPosition())); // show text ObjectLabeling *labeling = ObjectLabeling::get(); if (labeling) { if (hit_obj && hit_obj->getProperty() && grabbed) labeling->setTarget(hit_obj, laser_hit->getWorldPosition(), true); } } } void ObjLaserPointer::useIt(VRPlayer* player, int hand_num) { // laser pointer switch on/off (any laser pointer object) laser->setEnabled(!laser->isEnabled()); } void ObjLaserPointer::grabIt(VRPlayer* player, int hand_num) { grabbed = 1; } void ObjLaserPointer::throwIt(VRPlayer* player, int hand_num) { grabbed = 0; } ${#HL}$// implementation of the action setting the corresponding flag void ObjLaserPointer::useItAlt(VRPlayer* player, int hand_num) { alt_use = true; } ${HL#}$
-
Add a new AltUse signal with a callback method and a button to the base VRPlayer class (Framework\Components\Players\VRPlayer.h). All input bindings for VR and PC modes are made in the corresponding classes VRPlayerVR and VRPlayerPC respectively (Framework\Components\Players\).
#pragma once #include <UnigineNode.h> #include <UnigineNodes.h> #include <UnigineWorld.h> #include <UniginePlayers.h> #include <UniginePhysics.h> #include <UnigineVector.h> #include <UnigineGui.h> #include <UnigineMap.h> #include <UnigineGame.h> #include <UnigineSignal.h> #include <UnigineComponentSystem.h> #include "../VRInteractable.h" class VRInteractable; class VRPlayer : public Unigine::ComponentBase { public: COMPONENT(VRPlayer, Unigine::ComponentBase); COMPONENT_INIT(init); COMPONENT_POST_UPDATE(postUpdate); COMPONENT_SHUTDOWN(shutdown); /////////////////////////////////////// // enums /////////////////////////////////////// enum { HAND_FREE, HAND_GRAB, HAND_HOLD, HAND_THROW }; enum GRAB_MODE { BOUNDBOX, INTERSECTIONS }; enum BUTTONS { TELEPORT = 0, USE, GRAB, MENU, ${#HL}$ ALT_USE ${HL#}$ }; /////////////////////////////////////// // static /////////////////////////////////////// // is -extern_plugin "OpenVR/Oculus" added to command line and successfully loaded? static int isVRLoaded(); // return pointer to last created PlayerVR static VRPlayer* get(); /////////////////////////////////////// // methods /////////////////////////////////////// // sets intersection mask for teleportation (where player can be) virtual void setRayIntersectionMask(int mask) {} virtual void setTeleportationMask(int mask) {} // player virtual Unigine::PlayerPtr getPlayer() { return Unigine::Game::getPlayer(); } virtual void setLock(int lock) {} virtual void setPlayerPosition(const Unigine::Math::Vec3 &pos); // move player to position (and then land him to ground) with some direction virtual void landPlayerTo(const Unigine::Math::Vec3 &position, const Unigine::Math::vec3 &direction); UNIGINE_INLINE virtual void landPlayerTo(const Unigine::Math::Mat4 &transform) { landPlayerTo(transform.getColumn3(3), normalize(Unigine::Math::vec3(-transform.getColumn3(2)))); } // head UNIGINE_INLINE virtual Unigine::NodePtr getHead() { return head; } void setController(bool value) { is_controller = value; } bool isController() { return is_controller; } // hands virtual void setGrabMode(GRAB_MODE mode) { grab_mode = mode; } // grab via BoundBox-BoundBox or Line-Surface intersection? virtual int getNumHands() { return 0; } // get count of hands virtual Unigine::NodePtr getHandNode(int num) = 0; // get hand's node virtual int getHandDegreesOfFreedom(int num) = 0; // get hand's degrees of freedom (0-5 for PC, 6 for OpenVR) virtual Unigine::Math::vec3 getHandLinearVelocity(int num) = 0; // get speed of hand virtual Unigine::Math::vec3 getHandAngularVelocity(int num) = 0; // get angular speed of hand virtual int getHandState(int num) = 0; // get hand state (grab, hold, throw) virtual const Unigine::NodePtr &getGrabNode(int num) const = 0; // get object, grabbed by hand virtual const Unigine::Vector<VRInteractable*> &getGrabComponents(int num) const = 0; UNIGINE_INLINE virtual Unigine::Math::vec3 getHandyPos() { return Unigine::Math::vec3(0, -0.024f, 0.067f); } UNIGINE_INLINE virtual Unigine::Math::quat getHandyRot() { return Unigine::Math::quat(10.0f, 0, 0); } virtual void visualizeController(int num, bool value) {}; // hand's controllers virtual int getControllerButtonPressed(int controller_num, VRPlayer::BUTTONS button) = 0; virtual int getControllerButtonDown(int controller_num, VRPlayer::BUTTONS button) = 0; virtual int getControllerButtonUp(int controller_num, VRPlayer::BUTTONS button) = 0; virtual float getControllerAxis(int controller_num, Unigine::InputVRController::AXIS_TYPE axis) = 0; virtual void vibrateController(int controller_num) = 0; // eye tracking UNIGINE_INLINE virtual bool isEyetrackingValid() const { return false; } UNIGINE_INLINE virtual bool isEyetrackingAvailable() const { return false; } UNIGINE_INLINE virtual Unigine::Math::Vec3 getFocusWorldPosition() const { return Unigine::Math::Vec3_zero; } // callbacks UNIGINE_CALLBACK_METODS(Use, use_signal); UNIGINE_CALLBACK_METODS(Grab, grab_signal); UNIGINE_CALLBACK_METODS(Hold, hold_signal); UNIGINE_CALLBACK_METODS(Throw, throw_signal); ${#HL}$ UNIGINE_CALLBACK_METODS(UseAlt, use_alt_signal); ${HL#}$ protected: void init(); void postUpdate(); void shutdown(); // find specific components on the node int can_i_grab_it(const Unigine::NodePtr &node); int is_it_switcher(const Unigine::NodePtr &node); int is_it_handle(const Unigine::NodePtr &node); Unigine::NodeDummyPtr head; GRAB_MODE grab_mode = GRAB_MODE::BOUNDBOX; bool is_zero_pose_reseted = false; bool have_grip_button = false; int grip_axis = -1; private: // singleton static VRPlayer* instance; // signals Unigine::Signal use_signal; Unigine::Signal grab_signal; Unigine::Signal hold_signal; Unigine::Signal throw_signal; ${#HL}$ Unigine::Signal use_alt_signal; ${HL#}$ bool is_controller = false; };
#include "VRPlayer.h" #include <UnigineGame.h> #include <UnigineEditor.h> #include <UnigineEngine.h> #include <UnigineWindowManager.h> #include <UnigineVR.h> #include "../Objects/ObjSwitch.h" #include "../Objects/ObjHandle.h" using namespace Unigine; using namespace Math; VRPlayer* VRPlayer::instance; int VRPlayer::isVRLoaded() { return VR::isInitialized() && (VR::getApiType() != VR::API_NULL); } VRPlayer* VRPlayer::get() { return instance; } void VRPlayer::init() { // run application even when unfocused Engine::get()->setBackgroundUpdate(Engine::BACKGROUND_UPDATE_RENDER_ALWAYS); if(!is_controller) instance = this; is_zero_pose_reseted = VR::resetZeroPose(); } void VRPlayer::postUpdate() { if(!is_zero_pose_reseted) is_zero_pose_reseted = VR::resetZeroPose(); for (int i = 0; i < getNumHands(); i++) { int hand_state = getHandState(i); if (hand_state != HAND_FREE) { auto &components = getGrabComponents(i); switch (hand_state) { case HAND_GRAB: for (int j = 0; j < components.size(); j++) components[j]->grabIt(this, i); grab_signal.invoke(getGrabNode(i)); break; case HAND_HOLD: for (int j = 0; j < components.size(); j++) components[j]->holdIt(this, i); hold_signal.invoke(getGrabNode(i)); break; case HAND_THROW: for (int j = 0; j < components.size(); j++) components[j]->throwIt(this, i); throw_signal.invoke(getGrabNode(i)); break; default: break; } // use grabbed object if (getControllerButtonDown(i, USE)) { for (int j = 0; j < components.size(); j++) components[j]->useIt(this, i); use_signal.invoke(getGrabNode(i)); } ${#HL}$ // alternative use for grabbed object if (getControllerButtonDown(i, ALT_USE)) { for (int j = 0; j < components.size(); j++) components[j]->useItAlt(this, i); use_alt_signal.invoke(getGrabNode(i)); } ${HL#}$ } } } void VRPlayer::shutdown() { use_signal.clear(); grab_signal.clear(); hold_signal.clear(); throw_signal.clear(); ${#HL}$ use_alt_signal.clear(); ${HL#}$ instance = nullptr; } void VRPlayer::setPlayerPosition(const Vec3 &pos) { getPlayer()->setPosition(pos); } void VRPlayer::landPlayerTo(const Vec3 &position, const vec3 &direction) { Vec3 pos1 = position; Vec3 pos2 = position + Vec3(0, 0, -1) * getPlayer()->getZFar(); WorldIntersectionPtr intersection = WorldIntersection::create(); ObjectPtr hitObj = World::getIntersection(pos1, pos2, 1, intersection); if (hitObj) getPlayer()->setWorldTransform(setTo(intersection->getPoint(), intersection->getPoint() + Vec3(direction), vec3_up)); } int VRPlayer::can_i_grab_it(const NodePtr &node) { return ComponentSystem::get()->getComponent<VRInteractable>(node) != nullptr; } int VRPlayer::is_it_switcher(const NodePtr &node) { return ComponentSystem::get()->getComponent<ObjSwitch>(node) != nullptr; } int VRPlayer::is_it_handle(const NodePtr &node) { return ComponentSystem::get()->getComponent<ObjHandle>(node) != nullptr; }
-
All input bindings for VR and PC modes are made in the corresponding classes VRPlayerVR and VRPlayerPC respectively (Framework\Components\Players\). So we should make some changes to the VRPlayerVR::getControllerButtonDown()method in the VRPlayerVR.cpp file:
// ... int VRPlayerVR::getControllerButtonDown(int controller_num, VRPlayer::BUTTONS button) { if (!controller_valid[controller_num]) return 0; InputVRControllerPtr controller = (controller_num == 0 ? left_controller_device : right_controller_device); ${#HL}$ // getting the opposite controller InputVRControllerPtr other_controller = (controller_num == 0 ? right_controller_device : left_controller_device); ${HL#}$ if (!controller) return 0; switch(button) { case VRPlayer::TELEPORT: { int axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRACKPAD_X); if(axis_index == -1) axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_JOYSTICK_X); if(axis_index != -1) return controller->isButtonDown(Input::VR_BUTTON(Input::VR_BUTTON_AXIS_0 + axis_index)); } case VRPlayer::USE: { int axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRIGGER_VALUE); if(axis_index == -1) axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRIGGER_FORCE); if(axis_index != -1) return controller->getAxis(axis_index) > 0.5f; } case VRPlayer::GRAB: return controller->isButtonDown(Input::VR_BUTTON_GRIP); case VRPlayer::MENU: return controller->isButtonDown(Input::VR_BUTTON_APPLICATION) || controller->isButtonDown(Input::VR_BUTTON_Y); ${#HL}$ case VRPlayer::ALT_USE: { if (!other_controller) return 0; int axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRIGGER_VALUE); if (axis_index == -1) axis_index = controller->findAxisByType(InputVRController::AXIS_TYPE_TRIGGER_FORCE); if (axis_index != -1) return (controller->isButtonPressed(Input::VR_BUTTON_GRIP) && other_controller->isButtonDown(Input::VR_BUTTON_GRIP)); } ${HL#}$ } return 0; } // ...
- Save all the files that we modified and then build and run the application by hitting Ctrl + F5 to make the Component System update properties used to assign the components to nodes. Close the application after running it and switch to UnigineEditor.
-
The only thing left to do is to specify the list of materials that will be applied to the object. Select the laser_pointer node and click Edit in the Parameters window. Then, find the laser_pointer property, specify the number of elements for the materials array (for example, 3), and drag the required materials from the Materials window to fill the array.
- Save changes (Ctrl + S) and run the application via SDK Browser.