This page has been translated automatically.
Unigine Basics
1. Introduction
2. Managing Virtual Worlds
3. Preparing 3D Models
4. Materials
5. Cameras and Lighting
7. Making Cutscenes and Recording Videos
8. Preparing Your Project for Release
9. Physics
10. Optimization Basics
11. PROJECT2: First-Person Shooter
12. PROJECT3: Third-Person Cross-Country Arcade Racing Game
13. PROJECT4: VR Application With Simple Interaction

Processing the Input (keyboard, mouse). Intersections

Now we have the first interactive objects, but no interaction yet.

The good old way of interacting with the user is processing input from the user received from different devices (mouse, keyboard, joystick, etc.). The main class in charge of all this in UNIGINE is Input class.

The following code illustrates how to use the Input class to get the cursor coordinates when the right mouse button is pressed, and to close the application when the "q" key on the keyboard is pressed (ignoring this key if the Console is open):

Source code (C#)
private void Update()
{
	// if the right mouse button is pressed
	if(Input.IsMouseButtonDown(Input.MOUSE_BUTTON.RIGHT))
	{
		ivec2 mouse = Input.MousePosition;
		// write the current cursor coordinates to the Console
		Log.Message("Right mouse button was clicked at {0}\n", mouse);
	}

	// close the application when the 'q' key is pressed on the keyboard, or ignore it if the Console is open
	if (Input.IsKeyDown(Input.KEY.Q) && !Unigine.Console.Active)
	{
		Engine.Quit();
	}
}

Intersections are widely used in 3D applications for a wide range of tasks, such as selecting objects with the mouse, simplified modeling of car wheels, identifying objects and players caught in the blast zone and much more. There are three main types of intersections in UNIGINE:

  • World Intersection — intersection with objects and nodes.
  • Physics Intersection — intersection with shapes and collision objects.
  • Game Intersection — intersection with pathfinding nodes such as obstacles.

However, there are conditions that should be fulfilled to ensure surface intersection detection:

  • The surface must be enabled.
  • The surface must have a material assigned.
  • The Intersection flag must be enabled for each surface. This can be done via the API using the Object.SetIntersection() method.

The code below shows several usage examples for intersections:

  • Finding all nodes intersected by a bounding box.
  • Finding all nodes intersected by a bounding sphere.
  • Finding all nodes intersected by a bounding frustum.
  • Finding the first object intersected with a ray (raycast).
Source code (C#)
void listNodes(List<Node> nodes, string intersection_with)
{
	Log.Message("total number of nodes intersecting a {0} is: {1} \n", intersection_with, nodes.Count);
	foreach (Node node in nodes)
	{
		Log.Message("Intersected node: {0} \n", node.Name);
	}

	// clearing the list of nodes
	nodes.Clear();
}

private void Update()
{

	// take a reference to the current camera (Player)
	Player player = Game.Player;

	// create a list for storing detected nodes
	List<Node> nodes = new <Node>();

	//-------------------------- FINDING INTERSECTIONS WITH BOUNDING BOX -------------------------

	// initializing a bounding box with a size of 3 units located at the World origin
	WorldBoundBox boundBox = new WorldBoundBox(new Vec3(0.0f), new Vec3(3.0f));

	// searching for nodes intersected the bound and, if found, adding them into the list
	if (World.GetIntersection(boundBox, nodes))
		listNodes(nodes, "bounding box");

	//------------------------- FINDING INTERSECTIONS WITH BOUNDING SPHERE ------------------------

	// initializing a bounding sphere with a radius of 3 units at the coordinate origin
	WorldBoundSphere boundSphere = new WorldBoundSphere(new Vec3(0.0f), 3.0f);

	// searching for nodes intersected the bound and, if found, adding them into the list
	if (World.GetIntersection(boundSphere, nodes))
		listNodes(nodes, "bounding sphere");

	//-------------------------FINDING INTERSECTIONS WITH BOUNDING FRUSTUM -----------------------

	// initializing a bounding frustum with a frustum of the player's camera
	WorldBoundFrustum boundFrustum = new WorldBoundFrustum(player.Camera.Projection, player.Camera.Modelview);

	// finding ObjectMeshStaticNodes intersecting a bounding frustum and listing them if any
	if (World.GetIntersection(boundFrustum, Node.TYPE.OBJECT_MESH_STATIC, nodes))
		listNodes(nodes, "bounding frustum");

	//---------------- FINDING THE FIRST OBJECT INTERSECTED BY A RAY CAST FROM P0 to P1 --------------

	// initializing points of the ray from player's position (p0) in the direction pointed by the mouse cursor (p1)
	ivec2 mouse = Input.MousePosition;
	Vec3 p0 = player.WorldPosition;
	Vec3 p1 = p0 + new Vec3(player.GetDirectionFromMainWindow(mouse.x, mouse.y)) * 100;

	// creating a WorldIntersection object to store the information about the intersection
	WorldIntersection intersection = new WorldIntersection();

	// casting a ray from p0 to p1 to find the first intersected object
	Unigine.Object obj = World.GetIntersection(p0, p1, 1, intersection);

	// print the name of the first intersected object and coordinates of intersection point, if any, to the Console
	if (obj)
	{
		Vec3 p = intersection.Point;
		Log.Message("The first object intersected by the ray at point ({0}) is: {1} \n ", p, obj.Name);
	}
}

Interacting with objects in the scene using the mouse usually involves detecting the object under the cursor. The last example (raycast) is used for this purpose. We cast a ray from the position of the observer (Player) in the direction of the mouse cursor coordinates and look for the first object intersected by our ray.

Practice#

To test out interactive objects in our application, we desperately need the component implementing this functionality, processing the mouse click, and sending the Action signal (user action) on the selected object to the Interactable component. In addition, the component will handle the keystrokes:

  • Q – Close the application.
  • TAB – Send an additional Action (1) signal to the Interactable component on the selected object (for those components that have it implemented).

Let's create a new component, name it InputProcessor, and write the following code in it:

InputProcessor.cs
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
#if UNIGINE_DOUBLE
using Vec3 = Unigine.dvec3;
#else
	using Vec3 = Unigine.vec3;
#endif 
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class InputProcessor : Component
{
	private Player player = Game.Player;
	private Unigine.Object SelectedObject = null;	// last selected object

	public WorldIntersection intersection = null;	// intersection point with the last object under the mouse cursor
	
	private void Init()
	{
		// create an instance of the WorldIntersection class to store information about intersection
		intersection = new WorldIntersection();
	}

	private void Update()
	{
		// if the console is open, do nothing
		if(Unigine.Console.Active)
		return;

		// set the beginning of the segment (p0) at the camera position and the end (p1) at the point the mouse cursor is pointing to
		ivec2 mouse = Input.MousePosition;
		Vec3 p0 = player.WorldPosition;
		Vec3 p1 = p0 + new Vec3(player.GetDirectionFromMainWindow(mouse.x, mouse.y)) * 100;

		// cast a ray from point p0 to point p1 to find the first intersected object
		Unigine.Object obj = World.GetIntersection(p0, p1, 1, intersection);

		// if the object is interactable (has the Interactable component assigned), display information about it on the screen
		Interactable interactable = obj.GetComponent<Interactable>();
		if (interactable)
			interactable.DisplayInfo();
		
		// check the state of the right mouse button
		if(Input.IsMouseButtonDown(Input.MOUSE_BUTTON.RIGHT))
		{
			// if the object is interactable (has the Interactable component assigned)
			if (obj && interactable)
			{
				// send the 'Perform action 0' signal to the object
				interactable.Action(0);
				// register it as the last selected
				SelectedObject = obj;
			}
		}

		// check if any object is selected and the 'TAB' key is pressed 
		if(SelectedObject && Input.IsKeyDown(Input.KEY.TAB))
		{
			// send 'Perform action 1' signal to the object
			SelectedObject.GetComponent<Interactable>().Action(1);
		}

		// check if the 'Q' key is pressed and the Console is not open, then close the application
		if(Input.IsKeyDown(Input.KEY.Q) && !Unigine.Console.Active)
		{
			Engine.Quit();
		}
	}
}

Next, let's create an empty NodeDummy, name it input_processor, and assign our component to it (this is a common practice for components with general purpose functionality).

Now let's launch our application by clicking on the Play button and try turning the switches on and off.

After launching the application, don't forget to enable Visualizer to see the prompts on the screen — just open the console and type: show_visualizer 2

Last update: 2024-11-06
Build: ()