Using Manipulators to Transform Objects
After adding an object to the scene in Unigine Editor, you can control object transformations with control devices. But sometimes transformations are supposed to be made at application runtime. For example, when you create your own game with a world editor, where you can place objects in the world and move them around.
This usage example will teach you how to:
- Select an object on the screen with the mouse using Intersections.
- Use manipulator widgets to modify the object transform matrix.
- Switch between different manipulator widgets using the keyboard.
See Also#
- Article on Matrix Transformations
- Article on Intersections
Creating Manipulators to Control Object Transformations#
There are 3 types of manipulator widgets used for object transforming:
- Widget Manipulator Translator creates a mover manipulator used to translate objects along three axes with arrows at the end.
- Widget Manipulator Rotator creates a rotation manipulator used to rotate objects around three axes in the form of a sphere.
- Widget Manipulator Scaler creates a scaling manipulator used to scale objects along three axes in the form of a triangle.
All these manipulators work the same way, each of them visually represents a part of the transformation matrix that you can change by dragging the control elements of the widget. Use the CHANGED callback of the widget to make the selected object follow manipulators transformations.
public class Manipulator : Component
{
// declare object and translator widget manipulator variables
Unigine.Node obj;
WidgetManipulator ObjManTranslator;
private void Init()
{
// write here code to be called on component initialization
// create a manipulator class instance and add it to the UI
gui = Gui.GetCurrent();
ObjManTranslator = new WidgetManipulatorTranslator(gui);
gui.AddChild(ObjManTranslator);
// setting the ApplyTransform method as a callback on changing the widget state
ObjManTranslator.AddCallback(Gui.CALLBACK_INDEX.CHANGED, ApplyTransform);
}
// a callback method used to transform an object
private void ApplyTransform()
{
if(obj != null)
obj.WorldTransform = ObjManTranslator.Transform;
}
}
Selecting Objects Using Mouse Cursor#
To select an object under the mouse cursor we should cast a ray from the cursor location in the view direction of the camera using the World.GetIntersection() method, that returns an intersected object (if any).
// find an object under the cursor
private Unigine.Node GetNodeUnderCursor()
{
ivec2 mouse = Input.MousePosition;
// find a point 100 units away in front of the camera
Vec3 p0 = Game.Player.WorldPosition;
EngineWindow main_window = WindowManager.MainWindow;
if (main_window)
{
ivec2 main_pos = main_window.Position;
mouse.x += main_pos.x;
mouse.y += main_pos.y;
}
Vec3 p1 = p0 + new vec3(Game.Player.GetDirectionFromMainWindow(mouse.x, mouse.y)) * 100;
// cast a ray, that will detect an object within 100 units in front of the camera
return World.GetIntersection(p0, p1, 1, intersection);
}
Putting it All Together#
Now let's put it all together and add a keyboard handler to switch the current manipulator. For example, let's use Z, X, C keys to select Translator, Rotator, Scaler Manipulators accordingly. The selected widget will be displayed on the screen where the object is located (i.e. it will have the same transformation).
- Create a new C# component — Manipulator. Double-click it in the Asset Browser to edit code in your IDE.
- Copy the code below and paste it to your component.
- Assign Manipulator to any enabled node in the world and click Play.
#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 = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class Manipulator : Component
{
// WorldIntersection object to store the information about the intersection
WorldIntersection intersection = new WorldIntersection();
// Unigine.Object object to gain access to a selected object's transform in Unigine
Unigine.Node obj;
// create manipulators for each type of transform(Translating, Scaling, Rotating)
WidgetManipulator ObjManTranslator;
WidgetManipulator ObjManScaler;
WidgetManipulator ObjManRotator;
// create widget manipulator used to store transformation matrix of an active widget
WidgetManipulator CurrentObjectManipulator;
Gui gui;
[Method(Order=2)] private void Init()
{
// write here code to be called on component initialization
gui = Gui.GetCurrent();
// create widget for each transformation type
ObjManTranslator = new WidgetManipulatorTranslator(gui);
ObjManScaler = new WidgetManipulatorScaler(gui);
ObjManRotator = new WidgetManipulatorRotator(gui);
// set projection for the widgets
ObjManTranslator.Projection= Game.Player.Camera.Projection;
ObjManScaler.Projection = Game.Player.Camera.Projection;
ObjManRotator.Projection = Game.Player.Camera.Projection;
// add widgets to UI
gui.AddChild(ObjManTranslator);
gui.AddChild(ObjManScaler);
gui.AddChild(ObjManRotator);
// add a callback for each widget
ObjManTranslator.AddCallback(Gui.CALLBACK_INDEX.CHANGED, ApplyTransform);
ObjManRotator.AddCallback(Gui.CALLBACK_INDEX.CHANGED, ApplyTransform);
ObjManScaler.AddCallback(Gui.CALLBACK_INDEX.CHANGED, ApplyTransform);
// hide created widgets
ObjManScaler.Hidden = true;
ObjManRotator.Hidden = true;
ObjManTranslator.Hidden = true;
}
private void Update()
{
// write here code to be called before updating each render frame
if (!Game.Player)
return;
// set modelview for each widget manipulator every frame
ObjManTranslator.Modelview = Game.Player.Camera.Modelview;
ObjManScaler.Modelview = Game.Player.Camera.Modelview;
ObjManRotator.Modelview = Game.Player.Camera.Modelview;
// select an object with right-click
if (Input.IsMouseButtonDown(Input.MOUSE_BUTTON.RIGHT))
{
obj = GetNodeUnderCursor();
CurrentObjectManipulator.Transform = obj.WorldTransform;
}
// choose manipulator with Z,X,C keys
if(obj)
{
if(Input.IsKeyDown(Input.KEY.Z))
SwitchManipulator(ObjManTranslator);
if(Input.IsKeyDown(Input.KEY.X))
SwitchManipulator(ObjManRotator);
if(Input.IsKeyDown(Input.KEY.C))
SwitchManipulator(ObjManScaler);
if (!Input.IsMouseButtonPressed(Input.MOUSE_BUTTON.LEFT))
CurrentObjectManipulator.Transform = obj.WorldTransform;
}
}
// transform an object
private void ApplyTransform()
{
if(obj != null && Input.IsMouseButtonPressed(Input.MOUSE_BUTTON.LEFT))
obj.WorldTransform = CurrentObjectManipulator.Transform;
}
// find an object under the cursor
private Unigine.Node GetNodeUnderCursor()
{
ivec2 mouse = Input.MousePosition;
// find a point 100 units away in front of the camera
Vec3 p0 = Game.Player.WorldPosition;
EngineWindow main_window = WindowManager.MainWindow;
if (main_window)
{
ivec2 main_pos = main_window.Position;
mouse.x += main_pos.x;
mouse.y += main_pos.y;
}
Vec3 p1 = p0 + new vec3(Game.Player.GetDirectionFromMainWindow(mouse.x, mouse.y)) * 100;
// cast a ray, that will detect an object within 100 units in front of the camera
return World.GetIntersection(p0, p1, 1, intersection);
}
// relocate chosen manipulator to the position of selected object and make it visible
void SwitchManipulator(WidgetManipulator CurrentManipulator)
{
// relocate a widget and making it visible
CurrentManipulator.Transform = obj.WorldTransform;
CurrentManipulator.Hidden = false;
// make other widgets hidden
CurrentObjectManipulator = CurrentManipulator;
if(ObjManTranslator != CurrentManipulator)
ObjManTranslator.Hidden = true;
if(ObjManRotator != CurrentManipulator)
ObjManRotator.Hidden = true;
if(ObjManScaler != CurrentManipulator)
ObjManScaler.Hidden = true;
}
}