Programming
Fundamentals
Setting Up Development Environment
Usage Examples
UnigineScript
High-Level Systems
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
Rendering-Related Classes

13. Managing Intersections

<< RETURN TO THE PREVIOUS SECTION

Intersections are widely used in 3D applications. In UNIGINE there are three main types of intersections:

But there are some conditions to detect intersections with the surface:

  1. The surface is enabled (by default) and its intersection flag is enabled.
    Notice
    You can set intersection flag of the object's surface by using the Object.setIntersection() method.
  2. The surface has an assigned property with intersection option of the property is enabled.
    Notice
    You can enable property intersection option by using the Property.setIntersection() method.
  3. The surface has an assigned material.
The code below illustrates several ways of using world intersections:
  • to find all nodes intersected by a bounding box
  • to find all nodes intersected by a bounding sphere
  • to find all nodes intersected by a bounding frustum
  • to find the first object intersected by a ray
Source code (C++)
// AppWorldLogic.cpp

// injecting Unigine namespace to the global namespace
using namespace Unigine;

/* ... */

int listNodes(Vector<Ptr<Node>> &nodes, const char *intersection_with)
{
	Log::message("Total number of nodes intersecting a %s is: %i \n", intersection_with, nodes.size());

	for (int i = 0; i < nodes.size(); i++)
	{
		Log::message("Intersected node: %s \n", nodes.get(i)->getName());
	}

	// clearing the list of nodes 
	nodes.clear();

	return 1;
}

int AppWorldLogic::update() 
{
	// getting a player pointer
	PlayerPtr player = Game::get()->getPlayer();

	// creating a vector to store intersected nodes
	Vector<Ptr<Node>> nodes;

	//-------------------------- FINDING INTERSECTIONS WITH A BOUNDING BOX -------------------------
	
	// initializing a bounding box with a size of 3 units, located at the World's origin 
	WorldBoundBox boundBox(Math::Vec3(0.0f), Math::Vec3(3.0f));

	// finding nodes intersecting a bounding box and listing them if any
	if (World::get()->getIntersection(boundBox, nodes)) 
		listNodes(nodes, "bounding box");

	//------------------------- FINDING INTERSECTIONS WITH A BOUNDING SPHERE ------------------------
		
	// initializing a bounding sphere with a radius of 3 units, located at the World's origin 
	WorldBoundSphere boundSphere(Math::Vec3(0.0f), 3.0f);

	// finding nodes intersecting a bounding sphere and listing them if any
	if (World::get()->getIntersection(boundSphere, nodes)) 
		listNodes(nodes, "bounding sphere");

	//------------------------- FINDING INTERSECTIONS WITH A BOUNDING FRUSTUM -----------------------
		
	// initializing a bounding frustum with a  frustum of the player's camera
	WorldBoundFrustum boundFrustum(player->getCamera()->getProjection(), player->getCamera()->getModelview());

	// finding ObjectMeshStaticNodes intersecting a bounding frustum and listing them if any
	if (World::get()->getIntersection(boundFrustum, Node::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 in the direction pointed by the mouse cursor 
	Math::Vec3 p0 = player->getNodeWorldPosition();
	Math::Vec3 p1 = p0 + Math::Vec3(player->getDirectionFromScreen(App::get()->getMouseX(), App::get()->getMouseY())) * 100;

	//creating a WorldIntersection object to store the information about the intersection
	WorldIntersectionPtr intersection = WorldIntersection::create();

	// casting a ray from p0 to p1 to find the first intersected object
	ObjectPtr obj = World::get()->getIntersection(p0, p1, 1, intersection);

	// print the name of the first intersected object and coordinates of intersection point if any
	if (obj)
	{
		Math::Vec3 p = intersection->getPoint();
		Log::message("The first object intersected by the ray at point (%f, %f, %f) is: %s \n ", p.x, p.y, p.z, obj->getName());
	}
	
	return 1;
}
/* ... */

Additional information:

  • For more information on intersections, see Intersections page.
  • For more information on managing World intersections via API, see World class page.
  • For more information on managing Game intersections via API, see Game class page.
  • For more information on managing Physics intersections via API, see Physics class page.

Project Progress

In our project we are going to use world intersections to find an object under the mouse cursor. Actually, the task is to find the first object intersected by the line traced from the center of the player's screen in the direction pointed by the mouse cursor. Let us write an auxiliary function called getObjectUnderMouseCursor. In order to enable intersection detection for our objects we're also going to add a couple of lines to the addMeshToScene() method right after setting up object's material.

In the AppWorldLogic.cpp file let us implement our getObjectUnderMouseCursor function.

Source code (C++)
// AppWorldLogic.cpp

/* .. */

int AppWorldLogic::addMeshToScene(const char *filename, const char *mesh_name, const char *material_name, Math::Vec3 position)
{
/* .. */

	/* setting up object's material */

	// assigning the surface_base property to object's surfaces
	omd->setProperty("surface_base", "*");

	// enabling intersection detection for the first surface (0)
	omd->setIntersection(1, 0);

/* .. */
}

/* .. */

/// function getting an object under the mouse cursor
ObjectPtr getObjectUnderMouseCursor(const PlayerPtr &player, int mouse_x, int mouse_y, float max_dist)
{
	// setting start point (p0) at the center of player's screen
	Math::Vec3 p0 = player->getWorldPosition();
	
	// setting end point (p1) according to the position of the mouse cursor
	Math::Vec3 p1 = p0 + Math::Vec3(player->getDirectionFromScreen(mouse_x, mouse_y)) * max_dist;
	
	WorldIntersectionPtr intersection = WorldIntersection::create();

	// returning the pointer to the first intersected object if any or NULL
	return World::get()->getIntersection(p0, p1, 1, intersection);
}

/* .. */

Source Files

You can copy the code below and paste it to the corresponding source files of your project:

AppWorldLogic.h

Source code (C++)
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__

#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineEditor.h>
#include <UnigineGame.h>
#include <UnigineLights.h>
#include <UnigineMaterials.h>

// auxiliary constants
const float DELTA_ANGLE = 60.0f;		// delta angle of objects rotation
const float MOVING_SPEED = 3.0f;		// speed of objects movement
const float CHANGE_INTERVAL = 1.0f;		// the interval between changes of objects' parameters, in seconds
const float SUN_ROTATION_RATE = 10.0f;	// rotation rate of the sun

class AppWorldLogic : public Unigine::WorldLogic {
	
public:
	AppWorldLogic();
	virtual ~AppWorldLogic();
	
	virtual int init();
	
	virtual int update();
	virtual int render();
	virtual int flush();
	
	virtual int shutdown();
	virtual int destroy();
	
	virtual int save(const Unigine::StreamPtr &stream);
	virtual int restore(const Unigine::StreamPtr &stream);
private:
	Unigine::Editor *editor;
	Unigine::Materials *materials;
	Unigine::PlayerSpectatorPtr player;
	
	// pointers to light sources
	Unigine::LightWorldPtr thesun;
	Unigine::LightOmniPtr light_omni;
	Unigine::LightProjPtr projector;

	// auxiliary functions
	int addMeshToScene(const char *file_name, const char *mesh_name, const char *material_name, Unigine::Math::Vec3 position);
	int removeMeshFromScene(const char *node_name);
	int transformNode(Unigine::NodePtr node, float ifps);

	// initialization functions
	int initObjects();
	int initPlayer();
	int initLights();
	int initMaterials();

	// update functions
	int updateLights();
	int updateObjects();

	// shutdown functions
	int clearMaterials();
	int removeObjects();

	// scene objects vector
	Unigine::Vector <Unigine::ObjectMeshDynamicPtr> Objects;
	
	Unigine::Math::vec3 current_objects_scale = Unigine::Math::vec3(1.0f);			// current scaling vector for objects
	Unigine::Math::Vec3 forward_direction = Unigine::Math::Vec3(0.0f, -1.0f, 0.0f);	// current forward direction vector for objects
	float elapsed_time = CHANGE_INTERVAL;							// current time left to change current scale and forward direction of our objects
	float sun_angle = 0.0f;											// current sun position
};

#endif // __APP_WORLD_LOGIC_H__

AppWorldLogic.cpp

Source code (C++)
#include "AppWorldLogic.h"
// World logic, it takes effect only when the world is loaded.
// These methods are called right after corresponding world script's (UnigineScript) methods.
using namespace Unigine;
// auxiliary variable to store the time of rendering the last frame
float ifps;

//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- AUXILIARY FUNCTIONS AND METHODS ----------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------

/// method adding a Dynamic Mesh Object to the scene. If an empty filename is passed the method creates a default box; otherwise, loads a mesh-file with a given name.
int AppWorldLogic::addMeshToScene(const char *file_name, const char *mesh_name, const char *material_name, Math::Vec3 position)
{
	MeshPtr mesh = Mesh::create();
	ObjectMeshDynamicPtr omd;
	if (filename){				// loading a mesh from a specified file
		if (!mesh->load(filename))
		{
			Log::error("\nError opening .mesh file!\n");
			mesh.clear();
			
			return 0;
		}
		else omd = ObjectMeshDynamic::create(mesh);
	}
	else																// creating a default box
	{
		mesh->addBoxSurface("box_surface", Math::vec3(0.5f));

		omd = ObjectMeshDynamic::create(mesh);
	}

	// setting node material, name and position
	omd->setMaterial(material_name, "*");
	omd->setName(mesh_name);
	omd->setWorldPosition(position);

	// assigning the surface_base property to object's surfaces
	omd->setProperty("surface_base", "*");

	// enabling intersection detection for the first surface (0)
	omd->setIntersection(1, 0);
	
	// passing node ownership to the editor as a runtime node
	omd->release();
	editor->addNode(omd->getNode());

	// updating the list of scene objects
	Objects.append(omd);

	// reporting progress to the console
	Log::message("-> Object %s added to the scene.\n", mesh_name);

	// clearing the mesh
	mesh->clear();

	return 1;

}
//-----------------------------------------------------------------------------------------------------------------------------
/// method deleting a Dynamic Mesh Object with a given name from the scene
int AppWorldLogic::removeMeshFromScene(const char *node_name)
{
	// getting a pointer to the node with a given name and downcasting it to ObjectMeshDynamicPtr
	ObjectMeshDynamicPtr object = ObjectMeshDynamic::cast(editor->getNodeByName(node_name));

	if (object)
	{
		// reporting node deletion to the console
		Log::message("Removing %s node named %s from the scene.\n", object->getTypeName(), node_name);

		// removing the node with a given name from the list of scene objects

		for (int i = 0; i < Objects.size(); i++)
		{
			if (strcmp(Objects[i]->getName(), node_name) == 0) {
				Objects.remove(i);
				break;
			}
		}

		// removing the node from the scene using upcasting
		editor->removeNode(object->getNode());

		return 1;
	}

	return 0;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing node transformations 
int AppWorldLogic::transformNode(NodePtr node, float ifps)
{
	// getting current node transformation matrix
	Math::Mat4 transform = node->getTransform();

	// calculating delta rotation around an arbitrary axis
	Math::quat delta_rotation = Math::quat(rand() % 2, rand() % 2, rand() % 2, DELTA_ANGLE * ifps);

	// setting node's scale, rotation and position
	node->setWorldScale(current_objects_scale);
	node->setWorldRotation(node->getWorldRotation() * delta_rotation);
	node->setWorldPosition(node->getWorldPosition() + forward_direction * MOVING_SPEED *ifps);

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// function getting an object under the mouse cursor
ObjectPtr getObjectUnderMouseCursor(const PlayerPtr &player, int mouse_x, int mouse_y, float max_dist)
{
	// setting start point (p0) at the center of player's screen
	Math::Vec3 p0 = player->getWorldPosition();

	// setting end point (p1) according to the position of the mouse cursor
	Math::Vec3 p1 = p0 + Math::Vec3(player->getDirectionFromScreen(mouse_x, mouse_y)) * max_dist;

	WorldIntersectionPtr intersection = WorldIntersection::create();

	// returning the pointer to the first intersected object if any or NULL
	return World::get()->getIntersection(p0, p1, 1, intersection);
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- INITIALIZATION METHODS -------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of the set of 4 boxes
int AppWorldLogic::initObjects()
{
	int index = 0;

	for (int x = 0; x < 2; x++)
	{
		for (int y = 0; y < 2; y++)
		{
			addMeshToScene(NULL, String::format("my_meshdynamic_%d", index).get(), String::format("my_mesh_base%d", index).get(), Math::Vec3(x, y, 1.0f));
			index++;
		}
	}

	// reporting progress to the console
	Log::warning("Objects generation OK!\n\n");

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of the player
int AppWorldLogic::initPlayer()
{
	// creating a new PlayerSpectator instance
	player = PlayerSpectator::create();

	// setting player's FOV, ZNear, ZFar
	player->setFov(90.0f);
	player->setZNear(0.1f);
	player->setZFar(10000.0f);

	// setting player's view direction vector and position
	player->setPosition(Math::Vec3(3.0f));
	player->setDirection(Math::vec3(-1.0f), Math::vec3(0.0f, 0.0f, -1.0f));

	// setting the player to the Game singleton instance
	Game::get()->setPlayer(player->getPlayer());
	player->release();

	//reporting progress to the console
	Log::warning("\nPlayer initialization OK!\n\n");

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of lights
int AppWorldLogic::initLights()
{
	// creating an omni light and setting up its parameters
	light_omni = LightOmni::create(Math::vec4(1.0f, 1.0f, 1.0f, 1.0f), 10.0f, "");
	light_omni->setWorldPosition(Math::Vec3(0.0f, 0.0f, 5.0f));
	light_omni->setIntensity(0.1f);

	// passing node ownership to the editor
	light_omni->release();
	editor->addNode(light_omni->getNode());

	// reporting progress to the console
	Log::message("-> Created a %s light source.\n", light_omni->getName());

	// creating a world light and setting up its parameters
	thesun = LightWorld::create(Math::vec4(1.0f, 1.0f, 1.0f, 1.0f));
	thesun->setName("Sun");
	thesun->setDisableAngle(90.0f);
	thesun->setIntensity(1.0f);
	thesun->setScattering(LightWorld::SCATTERING_SUN);
	thesun->setWorldRotation(Math::quat(0.0f, 1.0f, 0.0f, 170.0f));

	// passing node ownership to the editor
	thesun->release();
	editor->addNode(thesun->getNode());

	// reporting progress to the console
	Log::message("-> Created a %s light source.\n", thesun->getName());

	// creating a proj light and setting up its parameters
	projector = LightProj::create(Math::vec4(1.0f, 1.0f, 0.5f, 1.0f), 10.0f, 60.0f, "");
	projector->setWorldPosition(Math::Vec3(2.5f, 2.5f, 3.0f));
	projector->setName("projector");
	projector->setRotation(Math::quat(-45.0f, 45.0f, 0.0f));
	projector->setPenumbra(0.425f);
	projector->setIntensity(1.0f);

	// passing node ownership to the editor
	projector->release();
	editor->addNode(projector->getNode());

	// reporting progress to the console
	Log::message("-> Created a %s light source.\n", projector->getName());
	Log::warning("Lights initialization OK!\n");

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method performing initialization of materials
int AppWorldLogic::initMaterials()
{
	// getting a pointer to materials interface
	materials = Materials::get();

	// creating a new material library
	materials->create("my_material_lib");

	// creating a new child material of the mesh_base and setting its color
	materials->inheritMaterial("mesh_base", "my_material_lib", "my_mesh_base0");
	MaterialPtr  my_mesh_base = materials->findMaterial("my_mesh_base0");
	my_mesh_base->setParameter(Material::PARAMETER_COLOR, Math::vec4(255, 0, 0, 255));

	// reporting progress to the console
	Log::message("\n-> Generated %s material.\n", my_mesh_base->getName());

	// creating a new child material of the mesh_base and setting its color
	materials->inheritMaterial("mesh_base", "my_material_lib", "my_mesh_base1");
	my_mesh_base = materials->findMaterial("my_mesh_base1");
	my_mesh_base->setParameter(Material::PARAMETER_COLOR, Math::vec4(0, 255, 0, 255));

	// reporting progress to the console
	Log::message("-> Generated %s material.\n", my_mesh_base->getName());

	//creating a new child material of the mesh_base and setting its color
	materials->inheritMaterial("mesh_base", "my_material_lib", "my_mesh_base2");
	my_mesh_base = materials->findMaterial("my_mesh_base2");
	my_mesh_base->setParameter(Material::PARAMETER_COLOR, Math::vec4(0, 0, 255, 255));

	// reporting progress to the console
	Log::message("-> Generated %s material.\n", my_mesh_base->getName());

	//creating a new child material of the mesh_base and setting its color
	materials->inheritMaterial("mesh_base", "my_material_lib", "my_mesh_base3");
	my_mesh_base = materials->findMaterial("my_mesh_base3");
	my_mesh_base->setParameter(Material::PARAMETER_COLOR, Math::vec4(255, 255, 0, 255));

	// reporting progress to the console
	Log::message("-> Generated %s material.\n", my_mesh_base->getName());
	Log::warning("Material generation OK!\n\n");

	// clearing material pointer
	my_mesh_base.clear();

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------- UPDATE METHODS ---------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method updating the position of the sun
int AppWorldLogic::updateLights()
{
	// updating the Sun rotation angle
	sun_angle += SUN_ROTATION_RATE * ifps;
	if (sun_angle > 360.0f) sun_angle = 0.0f;

	// changing the Sun position using the new angle
	LightWorld::cast(editor->getNodeByName("Sun"))->setWorldRotation(Math::quat(Math::vec3(0.0f, 1.0f, 0.0f), 180.0f - sun_angle));

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method updating the initial set of scene objects
int AppWorldLogic::updateObjects()
{
	ObjectMeshDynamicPtr object;

	// changing transformation for all scene objects named "my_meshdynamic_*" (initial set)
	for (auto it = Objects.begin(); it != Objects.end(); it++)
	{
		object = it.get();
		if (strstr(object->getName(), "my_meshdynamic_"))
		{
			// transform the node
			transformNode(object->getNode(), ifps);
		}
	}

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------- SHUTDOWN METHODS -------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method removing all created materials
int AppWorldLogic::clearMaterials()
{
	materials->removeMaterial("my_mesh_base0");
	materials->removeMaterial("my_mesh_base1");
	materials->removeMaterial("my_mesh_base2");
	materials->removeMaterial("my_mesh_base3");

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
/// method removing all  created objects
int AppWorldLogic::removeObjects()
{
	while (Objects.size() > 0)
	{
		removeMeshFromScene(Objects.begin()->get()->getName());
	}

	return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
AppWorldLogic::AppWorldLogic() {
	
}

AppWorldLogic::~AppWorldLogic() {
	
}

int AppWorldLogic::init() {
	// Write here code to be called on world initialization: initialize resources for your world scene during the world start.

	// initializing pseudo-random number generator
	Game::get()->setSeed(time(NULL));

	// getting a pointer to the Editor
	editor = Editor::get();

	// creating materials
	initMaterials();

	// creating objects
	initObjects();

	// creating a player
	initPlayer();

	// creating lights
	initLights();
	
	return 1;
}

// start of the main loop
int AppWorldLogic::update() {
	// Write here code to be called before updating each render frame: specify all graphics-related functions you want to be called every frame while your application executes.

	// getting an inverse FPS value (the time in seconds it took to complete the last frame)
	ifps = Game::get()->getIFps();

	// checking if it's time to change current scale and forward vector direction of our objects
	if (elapsed_time < 0.0f)
	{
		// change current scaling vector for objects
		current_objects_scale = Math::vec3(Game::get()->getRandomFloat(0.8f, 1.2f));

		// change forward direction for objects
		forward_direction = forward_direction * Math::rotateZ(60.0f);

		// resetting elapsed time counter
		elapsed_time = CHANGE_INTERVAL;
	}
	
	// decreasing the time counter to the next change of current scale and forward vector direction
	elapsed_time -= ifps;

	// updating objects
	updateObjects();

	// updating lights
	updateLights();

	return 1;
}

int AppWorldLogic::render() {
	// The engine calls this function before rendering each render frame: correct behavior after the state of the node has been updated.
	
	return 1;
}

int AppWorldLogic::flush() {
	// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
	// The engine calls flush() with the fixed rate (60 times per second by default) regardless of the FPS value.
	// WARNING: do not create, delete or change transformations of nodes here, because rendering is already in progress.
	
	return 1;
}
// end of the main loop

int AppWorldLogic::shutdown() {
// Write here code to be called on world shutdown: delete resources that were created during world script execution to avoid memory leaks.
		
	// deleting all created nodes
	removeObjects();

	// clearing the player pointer
	player.clear();

	// clearing light sources
	thesun.clear();
	light_omni.clear();
	projector.clear();

	// clearing all created materials
	clearMaterials();

	return 1;
}

int AppWorldLogic::destroy() {
	// Write here code to be called when the video mode is changed or the application is restarted (i.e. video_restart is called). It is used to reinitialize the graphics context.
	
	return 1;
}

int AppWorldLogic::save(const Unigine::StreamPtr &stream) {
	// Write here code to be called when the world is saving its state (i.e. state_save is called): save custom user data to a file.
	
	UNIGINE_UNUSED(stream);
	return 1;
}

int AppWorldLogic::restore(const Unigine::StreamPtr &stream) {
	// Write here code to be called when the world is restoring its state (i.e. state_restore is called): restore custom user data to a file here.
	
	UNIGINE_UNUSED(stream);
	return 1;
}

PROCEED TO THE NEXT SECTION >>

Last update: 2017-07-03