7. Performing Basic Transformations (Move, Rotate, Scale)
<< RETURN TO THE PREVIOUS SECTION
Every node has a transformation matrix, which encodes position, rotation, and scale of the node in the world. If a node is added as a child of another node, it has a transformation matrix that is related to its parent node. That is why the Node class has different functions: getTransform(), setTransform() and getWorldTransform(), setWorldTransform() that operate with local and world transformation matrices respectively. The following code illustrates how to perform basic node transformations:
// move the node by X, Y, Z units along the corresponding axes
node.WorldPosition = node.WorldPosition + new vec3(X, Y, Z);
// move the node by one unit along the Y axis
node.WorldTranslate(0.0f, 1.0f, 0.0f);
// rotate the node around the axis (X, Y, Z) by the Alpha angle
node.SetWorldRotation(node.GetWorldRotation() * new quat(new vec3(X, Y, Z), Alpha));
// rotate the node around X, Y, and Z axes by the corresponding angle (angle_X, angle_Y, angle_Z)
node.SetWorldRotation(node.GetWorldRotation() * new quat(angle_X, angle_Y, angle_Z));
// rotate the node by 45 degrees along the Z axis
node.WorldRotate(0.0f, 0.0f, 45.0f);
// orient the node using a direction vector and a vector pointing upwards
node.SetWorldDirection(new vec3(0.5f, 0.5f, 0.0f), vec3.UP, MathLib.AXIS.Y);
// setting node scale to Scale_X, Scale_Y, Scale_Z along the corresponding axes
node.WorldScale = new vec3(Scale_X, Scale_Y, Scale_Z);
// setting new transformation matrix to scale the node 2 times along all axes, rotate it by 45 degrees around the Z-axis and move it by 1 unit along all axes
dmat4 transform = new dmat4(MathLib.Translate(1.0f, 1.0f, 1.0f) * MathLib.Rotate(new quat(0.0f, 0.0f, 1.0f, 45.0f)) * MathLib.Scale(new vec3(2.0f)));
// setting node transformation matrix relative to its parent
node.Transform = transform;
// setting node transformation matrix relative to the world origin
node.WorldTransform = transform;
Additional information:
- For more information on matrix transformations, see the Matrix Transformations article.
Project Progress#
In our project we are going to apply some transformations to our initial set of objects. So, let us write an auxiliary method called transformNode that will move, rotate and scale a given node. We are also going to use some multiplier (let's call it ifps) as a parameter for this function to scale the transformations. We'll explain it in the next section.
In the AppWorldLogic.h file, we define constants to be used in our transformNode method (speed of objects movement, delta angle of objects rotation), add a variable to store current scaling vector for our objects and declare our transformNode method.
// AppWorldLogic.h
/* .. */
// constants
const float MOVING_SPEED = 3.0f; // speed of objects movement
const float DELTA_ANGLE = 60.0f; // delta angle of objects rotation
/* .. */
class AppWorldLogic : public Unigine::WorldLogic {
public:
/* .. */
private:
/* .. */
int transformNode(Unigine::NodePtr node, float ifps);
/* .. */
// current scaling vector for objects
Unigine::Math::vec3 current_objects_scale = Unigine::Math::vec3(1.0f);
// current forward direction vector for objects
Unigine::Math::Vec3 forward_direction = Unigine::Math::Vec3(0.0f, -1.0f, 0.0f);
/* .. */
};
In the AppWorldLogic.cpp file let us implement our transformNode method.
// AppWorldLogic.cpp
/* .. */
/// 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;
}
/* .. */
Just to check if everything works fine, you can temporarily add one line to the AppWorldLogic::update() method:
// AppWorldLogic.cpp
/* .. */
int AppWorldLogic::update()
{
// apply transformation to scene objects
transformNode(World::getNodeByName("my_meshdynamic_0"), 0.0005);
transformNode(World::getNodeByName("my_meshdynamic_1"), 0.0005);
transformNode(World::getNodeByName("my_meshdynamic_2"), 0.0005);
transformNode(World::getNodeByName("my_meshdynamic_3"), 0.0005);
return 1;
}
/* .. */
Source Files
You can copy the code below and paste it to the corresponding source files of your project:
AppWorldLogic.h
#ifndef __APP_WORLD_LOGIC_H__
#define __APP_WORLD_LOGIC_H__
#include <UnigineLogic.h>
#include <UnigineStreams.h>
#include <UnigineObjects.h>
#include <UnigineGame.h>
#include <UnigineLights.h>
#include <UnigineMaterials.h>
#include <UnigineWorld.h>
using namespace Unigine;
// auxiliary constants
const float DELTA_ANGLE = 60.0f; // delta angle of objects rotation
const float MOVING_SPEED = 3.0f; // speed of objects movement
class AppWorldLogic : public WorldLogic {
public:
AppWorldLogic();
virtual ~AppWorldLogic();
virtual int init();
virtual int update();
virtual int postUpdate();
virtual int updatePhysics();
virtual int shutdown();
virtual int destroyRenderResources();
virtual int save(const StreamPtr &stream);
virtual int restore(const StreamPtr &stream);
private:
PlayerSpectatorPtr player;
// pointers to light sources
LightWorldPtr thesun;
LightOmniPtr light_omni;
LightProjPtr projector;
// auxiliary functions
int addMeshToScene(const char *file_name, const char *mesh_name, const char *material_name, Math::Vec3 position);
int removeMeshFromScene(const char *node_name);
int transformNode(NodePtr node, float ifps);
// initialization functions
int initObjects();
int initPlayer();
int initLights();
int initMaterials();
// shutdown functions
int clearMaterials();
int removeObjects();
// scene objects vector
Vector <ObjectMeshDynamicPtr> Objects;
// current scaling vector for objects
Math::vec3 current_objects_scale = Math::vec3(1.0f);
// current forward direction vector for objects
Math::Vec3 forward_direction = Math::Vec3(0.0f, -1.0f, 0.0f);
};
#endif // __APP_WORLD_LOGIC_H__
AppWorldLogic.cpp
#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.
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- 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 (file_name){ // loading a mesh from a specified file
if (!mesh->load(file_name))
{
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);
// 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 = checked_ptr_cast<ObjectMeshDynamic>(World::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
object->deleteLater();
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;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- 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), String::format("my_mesh_base%d", index), 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 as a default one via the Game singleton instance
Game::setPlayer(player);
//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);
// 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(86.0f, 30.0f, 300.0f));
// 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);
// 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()
{
// creating a new child material of the mesh_base and setting its color
MaterialPtr mesh_base = Materials::findMaterial("mesh_base");
MaterialPtr my_mesh_base = mesh_base->inherit("my_mesh_base0");
my_mesh_base->setParameterFloat4("albedo_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
my_mesh_base = mesh_base->inherit("my_mesh_base1");
my_mesh_base->setParameterFloat4("albedo_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
my_mesh_base = mesh_base->inherit("my_mesh_base2");
my_mesh_base->setParameterFloat4("albedo_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
my_mesh_base = mesh_base->inherit("my_mesh_base3");
my_mesh_base->setParameterFloat4("albedo_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");
my_mesh_base.clear();
return 1;
}
//-----------------------------------------------------------------------------------------------------------------------------
//---------------------------------------------- SHUTDOWN METHODS -------------------------------------------------------
//-----------------------------------------------------------------------------------------------------------------------------
/// method removing all created materials
int AppWorldLogic::clearMaterials()
{
Materials::removeMaterial(Materials::findMaterial("my_mesh_base0")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base1")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base2")->getGUID());
Materials::removeMaterial(Materials::findMaterial("my_mesh_base3")->getGUID());
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.
// 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.
// apply transformation to scene objects
transformNode(World::getNodeByName("my_meshdynamic_0"), 0.0005);
transformNode(World::getNodeByName("my_meshdynamic_1"), 0.0005);
transformNode(World::getNodeByName("my_meshdynamic_2"), 0.0005);
transformNode(World::getNodeByName("my_meshdynamic_3"), 0.0005);
return 1;
}
int AppWorldLogic::postUpdate() {
// 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::updatePhysics() {
// Write here code to be called before updating each physics frame: control physics in your application and put non-rendering calculations.
// The engine calls updatePhysics() 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->deleteLater();
// clearing light sources
thesun->deleteLater();
light_omni->deleteLater();
projector->deleteLater();
// clearing all created materials
clearMaterials();
return 1;
}
int AppWorldLogic::destroyRenderResources() {
// 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 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 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;
}