Смена материала лазерной указкой
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.Предположим, мы хотим расширить функциональность лазерной указки в нашем шаблоне, которую на данный момент мы можем захватывать, удерживать, бросать и использовать (включать луч), добавив дополнительное действие – циклическая смена материалов по заданному списку на объекте, на который указывает луч (мы уже делали подобное в самом первом ArchViz-проекте). Это действие будет выполняться при использовании (Grip) указки захваченной одним контроллером с зажатой боковой кнопкой использования (Grip) на противоположном контроллере.
The ObjLaserPointer component implements the base functionality of the laser pointer.Базовый функционал лазерной указки реализован в компоненте ObjLaserPointer.
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.Основным компонентом, определяющим базовый функционал всех интерактивных объектов, является VRInteractable (source/Framework/Components/VRInteractable.h). Именно здесь определяются возможные состояния интерактивных объектов (grab, hold, use, throw) через соответствующие виртуальные методы. Компоненты отнаследованные от VRInteractable должны иметь перегрузки этих методов. Например, логику захвата объекта (grab) нужно реализовать в методе grabIt отнаследованного компонента.
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):Итак, добавим новое действие и свяжем его с новым состоянием (на одном контроллере нажата боковая кнопка Grip, в то время как используется лазерная указка удерживаемая (Grip) вторым контроллером):
-
Add a new useItAlt action (alternative use) to the VRInteractable base component:Добавим новое действие useItAlt (альтернативное использование) в базовый компонент VRInteractable:
#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.Добавим перегрузку метода useItAlt() в компонент ObjLaserPointer (поскольку мы хотим добавить новое действие для лазерной указки). Внутри метода update() реализуем логику действия useItAlt, которое будет выполняться когда включены соответствующие флаги.
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.Не забудьте изменить значение в макросе PROP_AUTOSAVE на 1 чтобы при перекомпиляции после внесения изменений в код компонента соответствующее свойство (property) обновилось.#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\).Добавим новый сигнал AltUse с обработчиком и кнопкой в базовый класс VRPlayer (Framework\Components\Players\VRPlayer.h).
#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:Все настройки ввода для режимов VR и PC находятся в классах VRPlayerVR и VRPlayerPC соответственно (Framework\Components\Players\). Для VR-режима добавим немного кода в метод VRPlayerVR::getControllerButtonDown() в файле VRPlayerVR.cpp:
// ... 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.Сохраните все файлы, которые мы изменили, а затем пересоберите и запустите приложения, нажав Ctrl + F5, чтобы Компонентная система обновила свойства (property), которые используются для связи компонент с объектами. После запуска закройте приложение и переключитесь в 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.Единственное, что осталось сделать - это указать список материалов, которые будут назначаться объектам, на которые указывает указка. Выделите ноду laser_pointer и щелкните Edit в окне Parameters. Затем, найдите свойство laser_pointer, задайте число элементов в массиве materials (например, 3), и перетащите в соответствующие поля массива материалы, которые хотите использовать, прямо из окна Materials.
- Save changes (Ctrl + S) and run the application via SDK Browser.Сохраните внесенные изменения (Ctrl + S) и запустите проект через SDK Browser.