virtualGizmo3D is an 3D GIZMO manipulator: like a trackball it provides a way to rotate, move and scale a model, with mouse, also with dolly and pan features You can also define a way to rotate the model around any single axis. It use mouse movement on screen, mouse buttons and (eventually) key modifiers, like Shift/Ctrl/Alt/Super, that you define
It uses quaternions algebra, internally, to manage rotations, but you can also only pass your model matrix and gat back a transormation matrix with rotation, translazion and scale, inside.
virtualGizmo3D is an header only tool and is not bound to frameworks or render engines: it uses simply glm mathematics library (0.9.9 or higher), also it an header only tool. In this way you can use it with any engine, like: OpenGL, DirectX, Vulkan, Metal, etc. and/or with any framework, like: GLFW, SDL, GLUT, Native O.S. calls, etc.
You can run/test an emscripten WebGL 2 example of virtualGismo3D from following link:
It works only on browsers with WebGl 2 and webassembly support (FireFox/Opera/Chrome and Chromium based).
Test if your browser supports WebGL 2, here: WebGL2 Report
To use virtualGizmo3D need to include virtualGizmo.h
file in your code and declare an object of type vfGizmo3DClass, global or as member of your class
#include "virtualGizmo.h"
// Global or member class declaration
vfGizmo3DClass gizmo;
vfGizmo3DClass &getGizmo() { return gizmo; } //optional helper
In your 3D engine initalization declare (overriding default ones) your preferred controls:
GLFW buttons/keys initialization
void onInit()
{
//If you use a different framework simply associate internal ID with your preferences
//For main manipulator/rotation
getGizmo().setGizmoRotControl( (vgButtons) GLFW_MOUSE_BUTTON_LEFT, (vgModifiers) 0 /* evNoModifier */ );
//for pan and zoom/dolly... you can use also wheel to zoom
getGizmo().setDollyControl((vgButtons) GLFW_MOUSE_BUTTON_RIGHT, (vgModifiers) GLFW_MOD_CONTROL|GLFW_MOD_SHIFT);
getGizmo().setPanControl( (vgButtons) GLFW_MOUSE_BUTTON_RIGHT, (vgModifiers) 0);
// Now call viewportSize with the dimension of window/screen
// It is need to set mouse sensitivity for rotation
// You need to call it also in your "reshape" function: when resize the window (look below)
getGizmo().viewportSize(GetWidth(), GetHeight());
// more...
// if you need to rotate around a single axis have to select your preferences: uncomment below...
//
// getGizmo().setGizmoRotXControl((vgButtons) GLFW_MOUSE_BUTTON_LEFT, (vgModifiers) GLFW_MOD_SHIFT); // around X pressing SHIFT+LButton
// getGizmo().setGizmoRotYControl((vgButtons) GLFW_MOUSE_BUTTON_LEFT, (vgModifiers) GLFW_MOD_CONTROL); // around Y pressing CTRL+LButton
// getGizmo().setGizmoRotZControl((vgButtons) GLFW_MOUSE_BUTTON_LEFT, (vgModifiers) GLFW_MOD_ALT | GLFW_MOD_SUPER); // around Z pressing ALT|SUPER+LButton
}
SDL buttons/keys Initialization
void onInit()
{
//If you use a different framework simply associate internal ID with your preferences
//For main manipulator/rotation
getGizmo().setGizmoRotControl( (vgButtons) SDL_BUTTON_LEFT, (vgModifiers) 0 /* evNoModifier */ );
//for pan and zoom/dolly... you can use also wheel to zoom
getGizmo().setDollyControl((vgButtons) SDL_BUTTON_RIGHT, (vgModifiers) 0);
getGizmo().setPanControl( (vgButtons) SDL_BUTTON_RIGHT, (vgModifiers) KMOD_CTRL|KMOD_SHIFT);
// Now call viewportSize with the dimension of window/screen
// It is need to set mouse sensitivity for rotation
// You need to call it also in your "reshape" function: when resize the window (look below)
getGizmo().viewportSize(GetWidth(), GetHeight());
// more...
// if you need to rotate around a single axis have to select your preferences: uncomment below...
//
// getGizmo().setGizmoRotXControl((vgButtons) SDL_BUTTON_LEFT, (vgModifiers) KMOD_SHIFT); // around X pressing SHIFT+LButton
// getGizmo().setGizmoRotYControl((vgButtons) SDL_BUTTON_LEFT, (vgModifiers) KMOD_CTRL); // around Y pressing CTRL+LButton
// getGizmo().setGizmoRotZControl((vgButtons) SDL_BUTTON_LEFT, (vgModifiers) KMOD_ALT); // around Z pressing ALT+LButton
}
Now you need to add some event funtions:
In your Mouse-Button Event function need to call:
void onMouseButton(int button, int upOrDown, int x, int y)
{
// Call in 'mouse button event' the gizmo.mouse() func with:
// button: your mouse button
// mod: your modifier key -> CTRL, SHIFT, ALT, SUPER
// pressed: if button is pressed (TRUE) or released (FALSE)
// x, y: mouse coordinates
bool isPressed = upOrDown==GLFW_PRESS; // or upOrDown==SDL_MOUSEBUTTONDOWN for SDL
getGizmo().mouse((vgButtons) (button), (vgModifiers) theApp->getModifier(), isPressed, x, y);
}
In your Mouse-Motion Event function need to call:
void onMotion(int x, int y)
{
// Call on motion event to communicate the position
getGizmo().motion(x, y);
}
And in your Resize-Window Event function :
void onReshape(GLint w, GLint h)
{
// call it on resize window to re-align mouse sensitivity
getGizmo().viewportSize(w, h);
}
And finally, in your render function (or where you prefer) you can get the transformations
void onRender() //or when you prefer
{
glm::mat4 model(1.0f); // Identity matrix
// virtualGizmo transformations
getGizmo().applyTransform(model); // apply transform to matrix model
// Now the matrix 'model' has inside all the transformations:
// rotation, pan and dolly translations,
// and you can build MV and MVP matrix
}
If you need to more feeling with the mouse use:
getGizmo().setGizmoFeeling(1.0); // 1.0 default, > 1.0 more sensible, < 1.0 less
Same thing for Dolly and Pan:
getGizmo().setDollyScale(1.0f);
getGizmo().setPanScale(1.0f);
You probably will need to set center of rotation (default: origin), Dolly position (default: 1.0), and Pan position (default: vec2(0.0, 0.0)
getGizmo().setDollyPosition(1.0f);
getGizmo().setPanPosition(vec3(0.0f);
getGizmo().setRotationCenter(vec3(0.0));
If you want a continuous rotation, that you can stop with a click, like in example, need to add the below call in your Idle function, or inside of the main render loop
void onIdle()
{
// call it every rendering if want an continue rotation until you do not click on screen
// look at glApp.cpp : "mainLoop" ("newFrame") functions
getGizmo().idle();
}
Class declaration
The include file virtualGizmo.h
contains two classes:
virtualGizmoClass<T>
simple rotation manipulator, used mainly for imGuIZMO.quat (a GIZMO widget developed for ImGui, Graphic User Intefrace)virtualGizmo3DClass<T>
manipulator (like above) with dolly/zoom and pan/shift
Helper typedef
are also defined:
typedef virtualGizmoClass<float> vfGizmoClass;
typedef virtualGizmo3DClass<float> vfGizmo3DClass;
typedef virtualGizmoClass<double> vdGizmoClass;
typedef virtualGizmo3DClass<double> vdGizmo3DClass;
The source code example shown in the animated gif screenshot, is provided.
In example I use GLFW or SDL2 (via #define GLAPP_USE_SDL
) with OpenGL, but it is simple to change if you use Vulkan/DirectX/etc, other frameworks (like GLUT) or native OS access.
To use SDL framework instead of GLFW, uncomment #define GLAPP_USE_SDL
in glApp.h
file, or pass -DGLAPP_USE_SDL
directly to compiler.
To build it you can use CMake (3.10 or higher) or the Visual Studio solution project (for VS 2017) in Windows. You need to have GLFW (or SDL) in your compiler search path (LIB/INCLUDE). Instead copy of glm is attached and included in the example.
The CMake file is able to build also an EMSCRIPTEN version, obviously you need to have installed EMSCRIPTEN SDK on your computer (1.38.10 or higher): look at or use the helper batch/script files, in main example folder, to pass appropriate defines/patameters to CMake command line.
To build the EMSCRIPTEN version, in Windows, with CMake, need to have mingw32-make.exe in your computer and search PATH (only the make utility is enough): it is a condition of EMSDK tool to build with CMake.
For windows users that use vs2017 project solution:
- If you have GLFW and/or SDL headers/library directory paths added to
INCLUDE
andLIB
environment vars, the compiler find them. - The current VisualStudio project solution refers to my environment variable RAMDISK (
R:
), and subsequent VS intrinsic variables to generate binary output:$(RAMDISK)\$(MSBuildProjectDirectoryNoRoot)\$(DefaultPlatformToolset)\$(Platform)\$(Configuration)\
, so without a RAMDISK variable, executable and binary files are outputted in base to the values of these VS variables, starting from root of current drive. (you find built binary here... or change it)