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 VRLaserPointer.cs component implements the base functionality of the laser pointer.
The base component for all interactive objects is VRBaseInteractable (vr_template/components/base/VRInteractable.cs). It defines all available states of the interactive objects (NOT_INTERACT, HOVERED, GRABBED, USED) and methods executed on certain actions performed on the objects (OnHoverBegin, OnHoverEnd, OnGrabBegin, OnGrabEnd, OnUseBegin, OnUseEnd). Components inherited from VRBaseInteractable must implement overrides of these methods. For example, the logic for grabbing the object must be implemented in the OnGrabBegin 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 gripped (Grip) by the opposite controller is used):
-
Add a new OnAltUse action (alternative use) and a new ALT_USED state to the VRBaseInteractable base component:
using System; using System.Collections; using System.Collections.Generic; using Unigine; [Component(PropertyGuid = "AUTOGEN_GUID")] // <-- an identifier is generated automatically for the component public class VRBaseInteractable : Component { static public event Action onInit; public enum INTERACTABLE_STATE { NOT_INTERACT, HOVERED, GRABBED, USED${#HL}$, ALT_USED // add a new state ${HL#}$ } public INTERACTABLE_STATE CurrentState { get; set; } protected void Init() { onInit?.Invoke(this); } public virtual void OnHoverBegin(VRBaseInteraction interaction, VRBaseController controller) { } public virtual void OnHoverEnd(VRBaseInteraction interaction, VRBaseController controller) { } public virtual void OnGrabBegin(VRBaseInteraction interaction, VRBaseController controller) { } public virtual void OnGrabEnd(VRBaseInteraction interaction, VRBaseController controller) { } public virtual void OnUseBegin(VRBaseInteraction interaction, VRBaseController controller) { } public virtual void OnUseEnd(VRBaseInteraction interaction, VRBaseController controller) { } ${#HL}$// add methods for a new "alternative use" action public virtual void OnAltUse(VRBaseInteraction interaction, VRBaseController controller) { } public virtual void OnAltUseEnd(VRBaseInteraction interaction, VRBaseController controller) { } ${HL#}$ }
-
Add overrides of the OnAltUse() and OnAltUseEnd() methods to the VRLaserPointer component (as we are going to add a new action for the laser pointer). In the Update() method, implement the logic of the AltUse action that is executed when the corresponding flags are set.
#region Math Variables #if UNIGINE_DOUBLE using Scalar = System.Double; using Vec2 = Unigine.dvec2; using Vec3 = Unigine.dvec3; using Vec4 = Unigine.dvec4; using Mat4 = Unigine.dmat4; #else using Scalar = System.Single; using Vec2 = Unigine.vec2; using Vec3 = Unigine.vec3; using Vec4 = Unigine.vec4; using Mat4 = Unigine.mat4; using WorldBoundBox = Unigine.BoundBox; using WorldBoundSphere = Unigine.BoundSphere; using WorldBoundFrustum = Unigine.BoundFrustum; #endif #endregion using System; using System.Collections; using System.Collections.Generic; using Unigine; [Component(PropertyGuid = "AUTOGEN_GUID")] // <-- an identifier is generated automatically for the component public class VRLaserPointer : VRBaseInteractable { [ShowInEditor] [Parameter(Title = "Laser", Group = "VR Laser Pointer")] private Node laser = null; [ShowInEditor] [Parameter(Title = "Laser Ray", Group = "VR Laser Pointer")] private Node laserRay = null; [ShowInEditor] [Parameter(Title = "Laser Hit", Group = "VR Laser Pointer")] private Node laserHit = null; [ShowInEditor] [Parameter(Title = "Object Text", Group = "VR Laser Pointer")] private ObjectText objText = null; private Mat4 laserRayMat; private WorldIntersection intersection = new WorldIntersection(); private float rayOffset = 0.05f; private bool grabbed = false; ${#HL}$private bool altuse = false; public List<Material> materials = null; private int current_material = 0; ${HL#}$ protected override void OnReady() { laserRayMat = new Mat4(laserRay.Transform); laser.Enabled = false; ${#HL}$// if the list of textures is empty/not defined, report an error if(materials == null || materials.Count < 0) { Log.Error($"{nameof(VRLaserPointer)} error: materials list is empty.\n"); Enabled = false; return; } ${HL#}$ } private void Update() { if(laser.Enabled && grabbed) { laserRay.Transform = laserRayMat; vec3 dir = laserRay.GetWorldDirection(MathLib.AXIS.Y); Vec3 p0 = laserRay.WorldPosition + dir * rayOffset; Vec3 p1 = p0 + dir * 1000; Unigine.Object hitObj = World.GetIntersection(p0, p1, 1, intersection); if(hitObj != null) { laserRay.Scale = new vec3(laserRay.Scale.x, MathLib.Length(intersection.Point - p0) + rayOffset, laserRay.Scale.z); laserHit.WorldPosition = intersection.Point; laserHit.Enabled = true; } else { laserHit.Enabled = false; } if (hitObj != null) { objText.Enabled = true; objText.Text = hitObj.Name; float radius = objText.BoundSphere.Radius; vec3 shift = vec3.UP * radius; objText.WorldTransform = MathLib.SetTo(laserHit.WorldPosition + shift, VRPlayer.LastPlayer.HeadController.WorldPosition, vec3.UP, MathLib.AXIS.Z); ${#HL}$// implement logic of the "alternative use" action (cyclic change of materials on the object according to the list) if(altuse) { current_material++; if (current_material >= materials.Capacity) current_material = 0; hitObj.SetMaterial(materials[current_material], 0); Log.Message("ALTUSE HIT\n"); altuse = false; } ${HL#}$ } else objText.Enabled = false; } } public override void OnGrabBegin(VRBaseInteraction interaction, VRBaseController controller) { grabbed = true; } public override void OnGrabEnd(VRBaseInteraction interaction, VRBaseController controller) { grabbed = false; laser.Enabled = false; objText.Enabled = false; } public override void OnUseBegin(VRBaseInteraction interaction, VRBaseController controller) { if(grabbed) laser.Enabled = true; } public override void OnUseEnd(VRBaseInteraction interaction, VRBaseController controller) { laser.Enabled = false; objText.Enabled = false; } ${#HL}$ // override the method that is called when the "alternative use" action is performed on the object public override void OnAltUse(VRBaseInteraction interaction, VRBaseController controller) { altuse = true; CurrentState = VRBaseInteractable.INTERACTABLE_STATE.ALT_USED; } // override the method that is called when the "alternative use" action is done public override void OnAltUseEnd(VRBaseInteraction interaction, VRBaseController controller) { altuse = false; CurrentState = VRBaseInteractable.INTERACTABLE_STATE.NOT_INTERACT; } ${HL#}$ }
-
Add an execution condition for the OnAltUse action. All available types of interactions are implemented in the VRHandShapeInteraction component (vr_template/components/interactions/interactions/VRHandShapeInteraction.cs). So, add the following code to the Interact() method of this component:
public override void Interact(VRInteractionManager.InteractablesState interactablesState, float ifps) { // ... // update current input bool grabDown = false; bool grabUp = false; bool useDown = false; bool useUp = false; ${#HL}$// reset flags before checking the current input bool altUse = false; bool altUseUp = false; ${HL#}$ switch (controller.Device) { case VRInput.VRDevice.LEFT_CONTROLLER: grabDown = VRInput.IsLeftButtonDown(VRInput.ControllerButtons.GRAB_BUTTON); grabUp = VRInput.IsLeftButtonUp(VRInput.ControllerButtons.GRAB_BUTTON); useDown = VRInput.IsLeftButtonDown(VRInput.ControllerButtons.USE_BUTTON); useUp = VRInput.IsLeftButtonUp(VRInput.ControllerButtons.USE_BUTTON); ${#HL}$// for the left controller, the Use button must be hold on the right controller altUse = VRInput.IsRightButtonPress(VRInput.ControllerButtons.USE_BUTTON); altUseUp = VRInput.IsRightButtonUp(VRInput.ControllerButtons.USE_BUTTON); ${HL#}$ break; case VRInput.VRDevice.RIGHT_CONTROLLER: grabDown = VRInput.IsRightButtonDown(VRInput.ControllerButtons.GRAB_BUTTON); grabUp = VRInput.IsRightButtonUp(VRInput.ControllerButtons.GRAB_BUTTON); useDown = VRInput.IsRightButtonDown(VRInput.ControllerButtons.USE_BUTTON); useUp = VRInput.IsRightButtonUp(VRInput.ControllerButtons.USE_BUTTON); ${#HL}$// for the right controller, the Use button must be hold on the left controller altUse = VRInput.IsLeftButtonPress(VRInput.ControllerButtons.USE_BUTTON); altUseUp = VRInput.IsLeftButtonUp(VRInput.ControllerButtons.USE_BUTTON); ${HL#}$ break; case VRInput.VRDevice.PC_HAND: grabDown = VRInput.IsGeneralButtonDown(VRInput.GeneralButtons.FIRE_1); grabUp = VRInput.IsGeneralButtonUp(VRInput.GeneralButtons.FIRE_1); useDown = VRInput.IsGeneralButtonDown(VRInput.GeneralButtons.FIRE_2); useUp = VRInput.IsGeneralButtonUp(VRInput.GeneralButtons.FIRE_2); ${#HL}$// for the keyboard, the JUMP button must be held altUse = VRInput.IsGeneralButtonDown(VRInput.GeneralButtons.JUMP); altUseUp = VRInput.IsGeneralButtonUp(VRInput.GeneralButtons.JUMP); ${HL#}$ break; default: break; } ${#HL}$// stop the "alternative use" action - call OnAltUseEnd // for all components of the hovered object that have an implementation of this method if (altUseUp) { foreach (var hoveredObjectComponent in hoveredObjectComponents) hoveredObjectComponent.OnAltUseEnd(this, controller); } ${HL#}$ // can grab and use hovered object if (hoveredObject != null) { if (grabDown && grabbedObject == null) { grabbedObject = hoveredObject; grabbedObjectComponents.Clear(); grabbedObjectComponents.AddRange(hoveredObjectComponents); foreach (var grabbedObjectComponent in grabbedObjectComponents) { if (VRInteractionManager.IsGrabbed(grabbedObjectComponent)) { VRBaseInteraction inter = VRInteractionManager.GetGrabInteraction(grabbedObjectComponent); VRInteractionManager.StopGrab(inter); } grabbedObjectComponent.OnGrabBegin(this, controller); interactablesState.SetGrabbed(grabbedObjectComponent, true, this); } } if (useDown) { usedObject = hoveredObject; usedObjectComponents.Clear(); usedObjectComponents.AddRange(hoveredObjectComponents); foreach (var usedObjectComponent in usedObjectComponents) { usedObjectComponent.OnUseBegin(this, controller); interactablesState.SetUsed(usedObjectComponent, true, this); } } ${#HL}$// call OnAltUse for all components of the hovered object that have an implementation of this method if (altUse) { foreach (var hoveredObjectComponent in hoveredObjectComponents) if (hoveredObjectComponent.CurrentState != VRBaseInteractable.INTERACTABLE_STATE.ALT_USED) hoveredObjectComponent.OnAltUse(this, controller); altUse = false; } ${HL#}$ } // ... }
-
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 VRLaserPointer component, 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 press the Play button to run the application.