LumixEngine/src/editor/gizmo.cpp
2018-02-08 18:02:41 +01:00

1234 lines
35 KiB
C++

#include "editor/gizmo.h"
#include "engine/math_utils.h"
#include "engine/matrix.h"
#include "engine/quat.h"
#include "engine/universe/universe.h"
#include "render_interface.h"
#include <cfloat>
#include <cmath>
namespace Lumix
{
static const float INFLUENCE_DISTANCE = 0.3f;
static const u32 X_COLOR = 0xff6363cf;
static const u32 Y_COLOR = 0xff63cf63;
static const u32 Z_COLOR = 0xffcf6363;
static const u32 SELECTED_COLOR = 0xff63cfcf;
enum class Axis : u32
{
NONE,
X,
Y,
Z,
XY,
XZ,
YZ
};
enum class Pivot
{
CENTER,
OBJECT_PIVOT
};
enum class Mode : u32
{
ROTATE,
TRANSLATE,
SCALE,
COUNT
};
enum class CoordSystem
{
LOCAL,
WORLD
};
struct GizmoImpl LUMIX_FINAL : public Gizmo
{
static const int MAX_GIZMOS = 16;
static const int MAX_IMMEDIATE = 16;
explicit GizmoImpl(WorldEditor& editor)
: m_editor(editor)
, m_mode(Mode::TRANSLATE)
, m_pivot(Pivot::CENTER)
, m_coord_system(CoordSystem::LOCAL)
, m_is_autosnap_down(false)
, m_count(0)
, m_transform_axis(Axis::X)
, m_active(-1)
, m_is_step(false)
, m_rel_accum(0, 0)
, m_is_dragging(false)
, m_mouse_pos(0, 0)
, m_offset(0, 0, 0)
, m_immediate_count(0)
{
m_steps[int(Mode::TRANSLATE)] = 10;
m_steps[int(Mode::ROTATE)] = 45;
m_steps[int(Mode::SCALE)] = 1;
editor.universeDestroyed().bind<GizmoImpl, &GizmoImpl::onUniverseDestroyed>(this);
}
~GizmoImpl()
{
m_editor.universeDestroyed().unbind<GizmoImpl, &GizmoImpl::onUniverseDestroyed>(this);
}
void onUniverseDestroyed()
{
m_count = 0;
}
Matrix getMatrix(Entity entity) const
{
Matrix mtx;
auto* universe = m_editor.getUniverse();
if (m_pivot == Pivot::OBJECT_PIVOT)
{
mtx = universe->getPositionAndRotation(entity);
mtx.translate(mtx.getRotation().rotate(m_offset));
}
else if (m_pivot == Pivot::CENTER)
{
mtx = universe->getPositionAndRotation(entity);
float scale = universe->getScale(entity);
Matrix scale_mtx = Matrix::IDENTITY;
scale_mtx.m11 = scale_mtx.m22 = scale_mtx.m33 = scale;
Vec3 center = m_editor.getRenderInterface()->getModelCenter(entity);
mtx.setTranslation((mtx * scale_mtx).transformPoint(center));
}
else
{
ASSERT(false);
}
if (m_coord_system == CoordSystem::WORLD)
{
Vec3 pos = mtx.getTranslation();
mtx = Matrix::IDENTITY;
mtx.setTranslation(pos);
}
return mtx;
}
static float getScale(const Vec3& camera_pos, float fov, const Vec3& pos, float entity_scale, bool is_ortho)
{
if (is_ortho) return 2;
float scale = tanf(fov * 0.5f) * (pos - camera_pos).length() * 2;
return scale / (10 / entity_scale);
}
void renderTranslateGizmo(const Matrix& gizmo_mtx,
bool is_active,
const Vec3& camera_pos,
const Vec3& camera_dir,
float fov,
bool is_ortho) const
{
Axis transform_axis = is_active ? m_transform_axis : Axis::NONE;
Matrix scale_mtx = Matrix::IDENTITY;
auto entity_pos = gizmo_mtx.getTranslation();
float scale = getScale(camera_pos, fov, entity_pos, gizmo_mtx.getXVector().length(), is_ortho);
scale_mtx.m11 = scale_mtx.m22 = scale_mtx.m33 = scale;
Vec3 to_entity_dir = is_ortho ? camera_dir : camera_pos - entity_pos;
Matrix mtx = gizmo_mtx * scale_mtx;
RenderInterface::Vertex vertices[9];
u16 indices[9];
vertices[0].position = Vec3(0, 0, 0);
vertices[0].color = transform_axis == Axis::X ? SELECTED_COLOR : X_COLOR;
indices[0] = 0;
vertices[1].position = Vec3(1, 0, 0);
vertices[1].color = transform_axis == Axis::X ? SELECTED_COLOR : X_COLOR;
indices[1] = 1;
vertices[2].position = Vec3(0, 0, 0);
vertices[2].color = transform_axis == Axis::Y ? SELECTED_COLOR : Y_COLOR;
indices[2] = 2;
vertices[3].position = Vec3(0, 1, 0);
vertices[3].color = transform_axis == Axis::Y ? SELECTED_COLOR : Y_COLOR;
indices[3] = 3;
vertices[4].position = Vec3(0, 0, 0);
vertices[4].color = transform_axis == Axis::Z ? SELECTED_COLOR : Z_COLOR;
indices[4] = 4;
vertices[5].position = Vec3(0, 0, 1);
vertices[5].color = transform_axis == Axis::Z ? SELECTED_COLOR : Z_COLOR;
indices[5] = 5;
m_editor.getRenderInterface()->render(mtx, indices, 6, vertices, 6, true);
if (dotProduct(gizmo_mtx.getXVector(), to_entity_dir) < 0) mtx.setXVector(-mtx.getXVector());
if (dotProduct(gizmo_mtx.getYVector(), to_entity_dir) < 0) mtx.setYVector(-mtx.getYVector());
if (dotProduct(gizmo_mtx.getZVector(), to_entity_dir) < 0) mtx.setZVector(-mtx.getZVector());
vertices[0].position = Vec3(0, 0, 0);
vertices[0].color = transform_axis == Axis::XY ? SELECTED_COLOR : Z_COLOR;
indices[0] = 0;
vertices[1].position = Vec3(0.5f, 0, 0);
vertices[1].color = transform_axis == Axis::XY ? SELECTED_COLOR : Z_COLOR;
indices[1] = 1;
vertices[2].position = Vec3(0, 0.5f, 0);
vertices[2].color = transform_axis == Axis::XY ? SELECTED_COLOR : Z_COLOR;
indices[2] = 2;
vertices[3].position = Vec3(0, 0, 0);
vertices[3].color = transform_axis == Axis::YZ ? SELECTED_COLOR : X_COLOR;
indices[3] = 3;
vertices[4].position = Vec3(0, 0.5f, 0);
vertices[4].color = transform_axis == Axis::YZ ? SELECTED_COLOR : X_COLOR;
indices[4] = 4;
vertices[5].position = Vec3(0, 0, 0.5f);
vertices[5].color = transform_axis == Axis::YZ ? SELECTED_COLOR : X_COLOR;
indices[5] = 5;
vertices[6].position = Vec3(0, 0, 0);
vertices[6].color = transform_axis == Axis::XZ ? SELECTED_COLOR : Y_COLOR;
indices[6] = 6;
vertices[7].position = Vec3(0.5f, 0, 0);
vertices[7].color = transform_axis == Axis::XZ ? SELECTED_COLOR : Y_COLOR;
indices[7] = 7;
vertices[8].position = Vec3(0, 0, 0.5f);
vertices[8].color = transform_axis == Axis::XZ ? SELECTED_COLOR : Y_COLOR;
indices[8] = 8;
RenderInterface* ri = m_editor.getRenderInterface();
ri->render(mtx, indices, 9, vertices, 9, false);
if (m_is_dragging)
{
Vec3 intersection = getMousePlaneIntersection(m_editor.getMousePos(), gizmo_mtx, m_transform_axis);
Vec3 delta_vec = intersection - m_start_axis_point;
Vec2 p = ri->worldToScreenPixels(entity_pos);
StaticString<128> tmp("", delta_vec.x, "; ", delta_vec.y, "; ", delta_vec.z);
ri->addText2D(p.x + 31, p.y + 31, 16, 0xff000000, tmp);
ri->addText2D(p.x + 30, p.y + 30, 16, 0xffffFFFF, tmp);
}
}
void renderScaleGizmo(const Matrix& gizmo_mtx,
bool is_active,
const Vec3& camera_pos,
const Vec3& camera_dir,
float fov,
bool is_ortho)
{
Axis transform_axis = is_active ? m_transform_axis : Axis::NONE;
Matrix scale_mtx = Matrix::IDENTITY;
auto entity_pos = gizmo_mtx.getTranslation();
float scale = getScale(camera_pos, fov, entity_pos, gizmo_mtx.getXVector().length(), is_ortho);
scale_mtx.m11 = scale_mtx.m22 = scale_mtx.m33 = scale;
Matrix mtx = gizmo_mtx * scale_mtx;
RenderInterface::Vertex vertices[9];
u16 indices[12];
vertices[0].position = Vec3(0, 0, 0);
vertices[0].color = transform_axis == Axis::X ? SELECTED_COLOR : X_COLOR;
indices[0] = 0;
vertices[1].position = Vec3(1, 0, 0);
vertices[1].color = transform_axis == Axis::X ? SELECTED_COLOR : X_COLOR;
indices[1] = 1;
vertices[2].position = Vec3(0, 0, 0);
vertices[2].color = transform_axis == Axis::Y ? SELECTED_COLOR : Y_COLOR;
indices[2] = 2;
vertices[3].position = Vec3(0, 1, 0);
vertices[3].color = transform_axis == Axis::Y ? SELECTED_COLOR : Y_COLOR;
indices[3] = 3;
vertices[4].position = Vec3(0, 0, 0);
vertices[4].color = transform_axis == Axis::Z ? SELECTED_COLOR : Z_COLOR;
indices[4] = 4;
vertices[5].position = Vec3(0, 0, 1);
vertices[5].color = transform_axis == Axis::Z ? SELECTED_COLOR : Z_COLOR;
indices[5] = 5;
m_editor.getRenderInterface()->render(mtx, indices, 6, vertices, 6, true);
auto renderCube = [this](u32 color, const Matrix& mtx, const Vec3& pos)
{
RenderInterface::Vertex vertices[8];
for (int i = 0; i < 8; ++i) vertices[i].color = color;
vertices[0].position = pos + Vec3(-0.1f, -0.1f, -0.1f);
vertices[1].position = pos + Vec3(0.1f, -0.1f, -0.1f);
vertices[2].position = pos + Vec3(0.1f, -0.1f, 0.1f);
vertices[3].position = pos + Vec3(-0.1f, -0.1f, 0.1f);
vertices[4].position = pos + Vec3(-0.1f, 0.1f, -0.1f);
vertices[5].position = pos + Vec3(0.1f, 0.1f, -0.1f);
vertices[6].position = pos + Vec3(0.1f, 0.1f, 0.1f);
vertices[7].position = pos + Vec3(-0.1f, 0.1f, 0.1f);
u16 indices[36] =
{
0, 1, 2,
0, 2, 3,
4, 6, 5,
4, 7, 6,
0, 4, 5,
0, 5, 1,
2, 6, 7,
2, 7, 3,
0, 3, 7,
0, 7, 4,
1, 2, 6,
1, 6, 5
};
m_editor.getRenderInterface()->render(mtx, indices, 36, vertices, 8, false);
};
renderCube(transform_axis == Axis::X ? SELECTED_COLOR : X_COLOR, mtx, Vec3(1, 0, 0));
renderCube(transform_axis == Axis::Y ? SELECTED_COLOR : Y_COLOR, mtx, Vec3(0, 1, 0));
renderCube(transform_axis == Axis::Z ? SELECTED_COLOR : Z_COLOR, mtx, Vec3(0, 0, 1));
}
void renderQuarterRing(const Matrix& mtx, const Vec3& a, const Vec3& b, u32 color) const
{
RenderInterface::Vertex vertices[1200];
u16 indices[1200];
const float ANGLE_STEP = Math::degreesToRadians(1.0f / 100.0f * 360.0f);
Vec3 n = crossProduct(a, b) * 0.05f;
int offset = -1;
for (int i = 0; i < 25; ++i)
{
float angle = i * ANGLE_STEP;
float s = sinf(angle);
float c = cosf(angle);
float sn = sinf(angle + ANGLE_STEP);
float cn = cosf(angle + ANGLE_STEP);
Vec3 p0 = a * s + b * c - n * 0.5f;
Vec3 p1 = a * sn + b * cn - n * 0.5f;
++offset;
vertices[offset].position = p0;
vertices[offset].color = color;
indices[offset] = offset;
++offset;
vertices[offset].position = p1;
vertices[offset].color = color;
indices[offset] = offset;
++offset;
vertices[offset].position = p0 + n;
vertices[offset].color = color;
indices[offset] = offset;
++offset;
vertices[offset].position = p1;
vertices[offset].color = color;
indices[offset] = offset;
++offset;
vertices[offset].position = p1 + n;
vertices[offset].color = color;
indices[offset] = offset;
++offset;
vertices[offset].position = p0 + n;
vertices[offset].color = color;
indices[offset] = offset;
}
m_editor.getRenderInterface()->render(mtx, indices, offset, vertices, offset, false);
const int GRID_SIZE = 5;
offset = -1;
for (int i = 0; i <= GRID_SIZE; ++i)
{
float t = 1.0f / GRID_SIZE * i;
float ratio = sinf(acosf(t));
++offset;
vertices[offset].position = a * t;
vertices[offset].color = color;
indices[offset] = offset;
++offset;
vertices[offset].position = a * t + b * ratio;
vertices[offset].color = color;
indices[offset] = offset;
++offset;
vertices[offset].position = b * t + a * ratio;
vertices[offset].color = color;
indices[offset] = offset;
++offset;
vertices[offset].position = b * t;
vertices[offset].color = color;
indices[offset] = offset;
}
m_editor.getRenderInterface()->render(mtx, indices, offset, vertices, offset, true);
}
void renderArc(const Vec3& pos, const Vec3& n, const Vec3& origin, float angle, u32 color) const
{
u16 indices[51 * 3];
RenderInterface::Vertex vertices[52];
int count = Math::clamp(int(fabs(angle) / 0.1f), 1, 50);
Vec3 side = crossProduct(n.normalized(), origin);
vertices[0] = { pos, color, 0, 0 };
for (int i = 0; i <= count; ++i)
{
float a = angle / count * i;
Vec3 p = pos + origin * cosf(a) + side * sinf(a);
vertices[i + 1] = { p, color, 0, 0 };
}
for (int i = 0; i < count; ++i)
{
indices[i * 3 + 0] = 0;
indices[i * 3 + 1] = i+1;
indices[i * 3 + 2] = i + 2;
}
RenderInterface* ri = m_editor.getRenderInterface();
ri->render(Matrix::IDENTITY, indices, count * 3, vertices, count + 2, false);
}
void renderRotateGizmo(const Matrix& gizmo_mtx,
bool is_active,
const Vec3& camera_pos,
const Vec3& camera_dir,
float fov,
bool is_ortho)
{
Axis transform_axis = is_active ? m_transform_axis : Axis::NONE;
Matrix scale_mtx = Matrix::IDENTITY;
Vec3 entity_pos = gizmo_mtx.getTranslation();
float scale = getScale(camera_pos, fov, entity_pos, gizmo_mtx.getXVector().length(), is_ortho);
scale_mtx.m11 = scale_mtx.m22 = scale_mtx.m33 = scale;
Vec3 to_entity_dir = is_ortho ? camera_dir : camera_pos - entity_pos;
Matrix mtx = gizmo_mtx * scale_mtx;
Vec3 right(1, 0, 0);
Vec3 up(0, 1, 0);
Vec3 dir(0, 0, 1);
if (dotProduct(gizmo_mtx.getXVector(), to_entity_dir) < 0) right = -right;
if (dotProduct(gizmo_mtx.getYVector(), to_entity_dir) < 0) up = -up;
if (dotProduct(gizmo_mtx.getZVector(), to_entity_dir) < 0) dir = -dir;
if (!m_is_dragging || !is_active)
{
renderQuarterRing(mtx, right, up, transform_axis == Axis::Z ? SELECTED_COLOR : Z_COLOR);
renderQuarterRing(mtx, up, dir, transform_axis == Axis::X ? SELECTED_COLOR : X_COLOR);
renderQuarterRing(mtx, right, dir, transform_axis == Axis::Y ? SELECTED_COLOR : Y_COLOR);
}
else
{
Vec3 n;
Vec3 axis1, axis2;
switch (transform_axis)
{
case Axis::X:
n = gizmo_mtx.getXVector();
axis1 = up;
axis2 = dir;
break;
case Axis::Y:
n = gizmo_mtx.getYVector();
axis1 = right;
axis2 = dir;
break;
case Axis::Z:
n = gizmo_mtx.getZVector();
axis1 = right;
axis2 = up;
break;
default: ASSERT(false); break;
}
renderQuarterRing(mtx, axis1, axis2, SELECTED_COLOR);
renderQuarterRing(mtx, -axis1, axis2, SELECTED_COLOR);
renderQuarterRing(mtx, -axis1, -axis2, SELECTED_COLOR);
renderQuarterRing(mtx, axis1, -axis2, SELECTED_COLOR);
RenderInterface* ri = m_editor.getRenderInterface();
Vec3 origin = (m_start_plane_point - entity_pos).normalized();
renderArc(entity_pos, n * scale, origin * scale, m_angle_accum, 0x8800a5ff);
float angle_degrees = Math::radiansToDegrees(m_angle_accum);
Vec2 p = ri->worldToScreenPixels(entity_pos);
StaticString<128> tmp("", angle_degrees, " deg");
Vec2 screen_delta = (m_start_mouse_pos - p).normalized();
Vec2 text_pos = m_start_mouse_pos + screen_delta * 15;
ri->addText2D(text_pos.x + 1, text_pos.y + 1, 16, 0xff000000, tmp);
ri->addText2D(text_pos.x, text_pos.y, 16, 0xffffFFFF, tmp);
}
}
void clearEntities() override
{
m_active = -1;
m_pivot = Pivot::CENTER;
m_offset.set(0, 0, 0);
m_count = 0;
}
bool isActive() const override
{
return m_transform_axis != Axis::NONE;
}
Axis collideTranslate(const Matrix& gizmo_mtx,
const Vec3& camera_pos,
const Vec3& camera_dir,
float fov,
bool is_ortho,
const Vec3& origin,
const Vec3& dir) const
{
Matrix scale_mtx = Matrix::IDENTITY;
Vec3 entity_pos = gizmo_mtx.getTranslation();
float scale = getScale(camera_pos, fov, entity_pos, gizmo_mtx.getXVector().length(), is_ortho);
scale_mtx.m11 = scale_mtx.m22 = scale_mtx.m33 = scale;
Vec3 to_entity_dir = is_ortho ? camera_dir : camera_pos - entity_pos;
Matrix mtx = gizmo_mtx * scale_mtx;
Vec3 pos = mtx.getTranslation();
Vec3 x = mtx.getXVector() * 0.5f;
Vec3 y = mtx.getYVector() * 0.5f;
Vec3 z = mtx.getZVector() * 0.5f;
if (dotProduct(gizmo_mtx.getXVector(), to_entity_dir) < 0) x = -x;
if (dotProduct(gizmo_mtx.getYVector(), to_entity_dir) < 0) y = -y;
if (dotProduct(gizmo_mtx.getZVector(), to_entity_dir) < 0) z = -z;
float t, tmin = FLT_MAX;
bool hit = Math::getRayTriangleIntersection(origin, dir, pos, pos + x, pos + y, &t);
Axis transform_axis = Axis::NONE;
if (hit)
{
tmin = t;
transform_axis = Axis::XY;
}
hit = Math::getRayTriangleIntersection(origin, dir, pos, pos + y, pos + z, &t);
if (hit && t < tmin)
{
tmin = t;
transform_axis = Axis::YZ;
}
hit = Math::getRayTriangleIntersection(origin, dir, pos, pos + x, pos + z, &t);
if (hit && t < tmin)
{
transform_axis = Axis::XZ;
}
if (transform_axis != Axis::NONE)
{
return transform_axis;
}
float x_dist = Math::getLineSegmentDistance(origin, dir, pos, pos + mtx.getXVector());
float y_dist = Math::getLineSegmentDistance(origin, dir, pos, pos + mtx.getYVector());
float z_dist = Math::getLineSegmentDistance(origin, dir, pos, pos + mtx.getZVector());
float influenced_dist = scale * INFLUENCE_DISTANCE;
if (x_dist > influenced_dist && y_dist > influenced_dist && z_dist > influenced_dist)
{
return Axis::NONE;
}
if (x_dist < y_dist && x_dist < z_dist)
return Axis::X;
else if (y_dist < z_dist)
return Axis::Y;
return Axis::Z;
}
static Axis collideScale(const Matrix& gizmo_mtx,
const Vec3& camera_pos,
const Vec3& camera_dir,
float fov,
bool is_ortho,
const Vec3& origin,
const Vec3& dir)
{
Matrix scale_mtx = Matrix::IDENTITY;
Vec3 entity_pos = gizmo_mtx.getTranslation();
float scale = getScale(camera_pos, fov, entity_pos, gizmo_mtx.getXVector().length(), is_ortho);
scale_mtx.m11 = scale_mtx.m22 = scale_mtx.m33 = scale;
Vec3 to_entity_dir = is_ortho ? camera_dir : camera_pos - entity_pos;
Matrix mtx = gizmo_mtx * scale_mtx;
Vec3 pos = mtx.getTranslation();
float x_dist = Math::getLineSegmentDistance(origin, dir, pos, pos + mtx.getXVector());
float y_dist = Math::getLineSegmentDistance(origin, dir, pos, pos + mtx.getYVector());
float z_dist = Math::getLineSegmentDistance(origin, dir, pos, pos + mtx.getZVector());
float influenced_dist = scale * INFLUENCE_DISTANCE;
if (x_dist > influenced_dist && y_dist > influenced_dist && z_dist > influenced_dist)
{
return Axis::NONE;
}
if (x_dist < y_dist && x_dist < z_dist)
return Axis::X;
else if (y_dist < z_dist)
return Axis::Y;
return Axis::Z;
}
Axis collideRotate(const Matrix& gizmo_mtx,
const Vec3& camera_pos,
const Vec3& camera_dir,
float fov,
bool is_ortho,
const Vec3& origin,
const Vec3& dir) const
{
Vec3 pos = gizmo_mtx.getTranslation();
float scale = getScale(camera_pos, fov, pos, gizmo_mtx.getXVector().length(), is_ortho);
Vec3 hit;
if (Math::getRaySphereIntersection(origin, dir, pos, scale, hit))
{
Vec3 x = gizmo_mtx.getXVector();
float x_dist = fabs(dotProduct(hit, x) - dotProduct(x, pos));
Vec3 y = gizmo_mtx.getYVector();
float y_dist = fabs(dotProduct(hit, y) - dotProduct(y, pos));
Vec3 z = gizmo_mtx.getZVector();
float z_dist = fabs(dotProduct(hit, z) - dotProduct(z, pos));
float influence_dist = scale * 0.15f;
if (x_dist > influence_dist && y_dist > influence_dist && z_dist > influence_dist)
{
return Axis::NONE;
}
if (x_dist < y_dist && x_dist < z_dist)
return Axis::X;
else if (y_dist < z_dist)
return Axis::Y;
else
return Axis::Z;
}
return Axis::NONE;
}
bool immediate(Transform& frame) override
{
ASSERT(m_immediate_count < MAX_IMMEDIATE);
Matrix mtx = frame.toMatrix();
collide(mtx);
bool ret = transform(frame);
m_immediate_frames[m_immediate_count] = frame;
++m_immediate_count;
return ret;
}
void collide(const Matrix& gizmo_mtx)
{
if (m_is_dragging) return;
auto edit_camera = m_editor.getEditCamera();
auto* render_interface = m_editor.getRenderInterface();
bool is_ortho = render_interface->isCameraOrtho(edit_camera.entity);
auto camera_pos = m_editor.getUniverse()->getPosition(edit_camera.entity);
auto camera_dir = m_editor.getUniverse()->getRotation(edit_camera.entity).rotate(Vec3(0, 0, -1));
float fov = render_interface->getCameraFOV(edit_camera.entity);
Vec3 origin, cursor_dir;
Vec2 mouse_pos = m_editor.getMousePos();
m_editor.getRenderInterface()->getRay(edit_camera.entity, mouse_pos, origin, cursor_dir);
Axis axis = Axis::NONE;
switch(m_mode)
{
case Mode::TRANSLATE:
axis = collideTranslate(gizmo_mtx, camera_pos, camera_dir, fov, is_ortho, origin, cursor_dir);
break;
case Mode::ROTATE:
axis = collideRotate(gizmo_mtx, camera_pos, camera_dir, fov, is_ortho, origin, cursor_dir);
break;
case Mode::SCALE:
axis = collideScale(gizmo_mtx, camera_pos, camera_dir, fov, is_ortho, origin, cursor_dir);
break;
default:
ASSERT(false);
break;
}
if (axis != Axis::NONE)
{
m_transform_axis = axis;
m_active = -1;
}
}
void collide(const Vec3& camera_pos, const Vec3& camera_dir, float fov, bool is_ortho)
{
if (m_is_dragging) return;
auto edit_camera = m_editor.getEditCamera();
Vec3 origin, cursor_dir;
Vec2 mouse_pos = m_editor.getMousePos();
m_editor.getRenderInterface()->getRay(edit_camera.entity, mouse_pos, origin, cursor_dir);
m_transform_axis = Axis::NONE;
m_active = -1;
for (int i = 0; i < m_count; ++i)
{
Matrix gizmo_mtx = getMatrix(m_entities[i]);
Axis axis = Axis::NONE;
switch (m_mode)
{
case Mode::TRANSLATE:
axis = collideTranslate(gizmo_mtx, camera_pos, camera_dir, fov, is_ortho, origin, cursor_dir);
break;
case Mode::ROTATE:
axis = collideRotate(gizmo_mtx, camera_pos, camera_dir, fov, is_ortho, origin, cursor_dir);
break;
case Mode::SCALE:
axis = collideScale(gizmo_mtx, camera_pos, camera_dir, fov, is_ortho, origin, cursor_dir);
break;
default:
ASSERT(false);
break;
}
if (axis != Axis::NONE)
{
m_transform_axis = axis;
m_active = i;
return;
}
}
}
Vec3 getMousePlaneIntersection(const Vec2& mouse_pos, const Matrix& gizmo_mtx, Axis transform_axis) const
{
auto camera = m_editor.getEditCamera();
Vec3 origin, dir;
m_editor.getRenderInterface()->getRay(camera.entity, mouse_pos, origin, dir);
dir.normalize();
bool is_two_axed = transform_axis == Axis::XZ || transform_axis == Axis::XY || transform_axis == Axis::YZ;
if (is_two_axed)
{
Vec3 plane_normal;
switch (transform_axis)
{
case Axis::XZ: plane_normal = gizmo_mtx.getYVector(); break;
case Axis::XY: plane_normal = gizmo_mtx.getZVector(); break;
case Axis::YZ: plane_normal = gizmo_mtx.getXVector(); break;
default: ASSERT(false); break;
}
float t;
if (Math::getRayPlaneIntersecion(origin, dir, gizmo_mtx.getTranslation(), plane_normal, t))
{
return origin + dir * t;
}
return origin;
}
Vec3 axis;
switch (transform_axis)
{
case Axis::X: axis = gizmo_mtx.getXVector(); break;
case Axis::Y: axis = gizmo_mtx.getYVector(); break;
case Axis::Z: axis = gizmo_mtx.getZVector(); break;
default: ASSERT(false); return Vec3::ZERO;
}
Vec3 pos = gizmo_mtx.getTranslation();
Vec3 normal = crossProduct(crossProduct(dir, axis), dir);
float d = dotProduct(origin - pos, normal) / dotProduct(axis, normal);
return axis * d + pos;
}
float computeRotateAngle(int relx, int rely)
{
if (relx == 0 && rely == 0) return 0;
Matrix mtx = getMatrix(m_entities[m_active]);
Axis plane;
Vec3 axis;
switch (m_transform_axis)
{
case Axis::X: plane = Axis::YZ; axis = mtx.getXVector(); break;
case Axis::Y: plane = Axis::XZ; axis = mtx.getYVector(); break;
case Axis::Z: plane = Axis::XY; axis = mtx.getZVector(); break;
default: ASSERT(false); return 0;
}
Vec3 pos = getMousePlaneIntersection(m_editor.getMousePos(), mtx, plane);
Vec3 start_pos = getMousePlaneIntersection(m_mouse_pos, mtx, plane);
Vec3 delta = (pos - mtx.getTranslation()).normalized();
Vec3 start_delta = (start_pos - mtx.getTranslation()).normalized();
Vec3 side = crossProduct(axis, start_delta);
float y = Math::clamp(dotProduct(delta, start_delta), -1.0f, 1.0f);
float x = Math::clamp(dotProduct(delta, side), -1.0f, 1.0f);
return atan2(x, y);
/*
if (m_is_step)
{
m_rel_accum.x += relx;
m_rel_accum.y += rely;
if (m_rel_accum.x + m_rel_accum.y > 50)
{
m_rel_accum.x = m_rel_accum.y = 0;
return Math::degreesToRadians(float(getStep()));
}
else if (m_rel_accum.x + m_rel_accum.y < -50)
{
m_rel_accum.x = m_rel_accum.y = 0;
return -Math::degreesToRadians(float(getStep()));
}
else
{
return 0;
}
}
return (relx + rely) / 100.0f;*/
}
static Axis getPlane(Axis axis)
{
switch (axis)
{
case Axis::X: return Axis::YZ;
case Axis::Y: return Axis::XZ;
case Axis::Z: return Axis::XY;
default: return axis;
}
}
bool transform(Transform& frame)
{
if (m_active >= 0) return false;
if (m_transform_axis == Axis::NONE) return false;
if (m_editor.isMouseClick(MouseButton::LEFT))
{
m_is_dragging = true;
m_transform_point = getMousePlaneIntersection(m_editor.getMousePos(), frame.toMatrix(), m_transform_axis);
m_start_axis_point = m_transform_point;
m_start_plane_point = getMousePlaneIntersection(m_editor.getMousePos(), frame.toMatrix(), getPlane(m_transform_axis));
m_start_mouse_pos = m_editor.getMousePos();
m_angle_accum = 0;
m_active = -1;
}
if (!m_is_dragging) return false;
if (m_mode == Mode::ROTATE) return rotate(frame.rot);
if (m_mode == Mode::TRANSLATE) return translate(frame);
if (m_mode == Mode::SCALE) return scale(frame);
return false;
}
bool scale(Transform& frame) const
{
Vec2 mouse_pos = m_editor.getMousePos();
Matrix frame_mtx = frame.toMatrix();
Vec3 intersection = getMousePlaneIntersection(mouse_pos, frame_mtx, m_transform_axis);
Vec2 old_mouse_pos = { mouse_pos.x - m_editor.getMouseRelX(),mouse_pos.y - m_editor.getMouseRelY() };
Vec3 old_intersection = getMousePlaneIntersection(old_mouse_pos, frame_mtx, m_transform_axis);
Vec3 delta = intersection - old_intersection;
if (!m_is_step || delta.length() > float(getStep()))
{
if (m_is_step) delta = delta.normalized() * float(getStep());
frame.scale += delta.length();
return true;
}
return false;
}
bool translate(Transform& frame) const
{
Vec2 mouse_pos = m_editor.getMousePos();
Matrix frame_mtx = frame.toMatrix();
Vec3 intersection = getMousePlaneIntersection(mouse_pos, frame_mtx, m_transform_axis);
Vec2 old_mouse_pos = {mouse_pos.x - m_editor.getMouseRelX(),mouse_pos.y - m_editor.getMouseRelY()};
Vec3 old_intersection = getMousePlaneIntersection(old_mouse_pos, frame_mtx, m_transform_axis);
Vec3 delta = intersection - old_intersection;
if (!m_is_step || delta.length() > float(getStep()))
{
if (m_is_step) delta = delta.normalized() * float(getStep());
frame.pos += delta;
return true;
}
return false;
}
bool rotate(Quat& rot)
{
float relx = m_editor.getMouseRelX();
float rely = m_editor.getMouseRelY();
if (relx == 0 && rely == 0) return false;
auto mtx = rot.toMatrix();
Vec3 axis;
switch (m_transform_axis)
{
case Axis::X: axis = mtx.getXVector(); break;
case Axis::Y: axis = mtx.getYVector(); break;
case Axis::Z: axis = mtx.getZVector(); break;
default: ASSERT(false); break;
}
float angle = computeRotateAngle((int)relx, (int)rely);
m_angle_accum += angle;
m_angle_accum = Math::angleDiff(m_angle_accum, 0);
rot = Quat(axis, angle) * rot;
return true;
}
void rotate()
{
if (m_active < 0) return;
float relx = m_editor.getMousePos().x - m_mouse_pos.x;
float rely = m_editor.getMousePos().y - m_mouse_pos.y;
Universe* universe = m_editor.getUniverse();
Array<Vec3> new_positions(m_editor.getAllocator());
Array<Quat> new_rotations(m_editor.getAllocator());
auto mtx = getMatrix(m_entities[m_active]);
Vec3 axis;
switch (m_transform_axis)
{
case Axis::X: axis = mtx.getXVector(); break;
case Axis::Y: axis = mtx.getYVector(); break;
case Axis::Z: axis = mtx.getZVector(); break;
default: ASSERT(false); break;
}
float angle = computeRotateAngle((int)relx, (int)rely);
m_angle_accum += angle;
m_angle_accum = Math::angleDiff(m_angle_accum, 0);
if (m_editor.getSelectedEntities()[0] == m_entities[m_active])
{
for (int i = 0, c = m_editor.getSelectedEntities().size(); i < c; ++i)
{
Vec3 pos = universe->getPosition(m_editor.getSelectedEntities()[i]);
Quat old_rot = universe->getRotation(m_editor.getSelectedEntities()[i]);
Quat new_rot = Quat(axis, angle) * old_rot;
new_rot.normalize();
new_rotations.push(new_rot);
Vec3 pdif = mtx.getTranslation() - pos;
old_rot.conjugate();
pos = -pdif;
pos = new_rot.rotate(old_rot.rotate(pos));
pos += mtx.getTranslation();
new_positions.push(pos);
}
m_editor.setEntitiesPositionsAndRotations(&m_editor.getSelectedEntities()[0],
&new_positions[0],
&new_rotations[0],
new_positions.size());
}
else
{
Quat old_rot = universe->getRotation(m_entities[m_active]);
Quat new_rot = Quat(axis, angle) * old_rot;
new_rot.normalize();
new_rotations.push(new_rot);
m_editor.setEntitiesRotations(&m_entities[m_active], &new_rotations[0], 1);
}
}
void scale()
{
Matrix gizmo_mtx = getMatrix(m_entities[m_active]);
Vec3 intersection = getMousePlaneIntersection(m_editor.getMousePos(), gizmo_mtx, m_transform_axis);
Vec3 delta_vec = intersection - m_transform_point;
float delta = delta_vec.length();
Vec3 entity_to_intersection = intersection - gizmo_mtx.getTranslation();
if (dotProduct(delta_vec, entity_to_intersection) < 0) delta = -delta;
if (!m_is_step || delta > float(getStep()))
{
if (m_is_step) delta = float(getStep());
Array<float> new_scales(m_editor.getAllocator());
if (m_entities[m_active] == m_editor.getSelectedEntities()[0])
{
for (int i = 0, ci = m_editor.getSelectedEntities().size(); i < ci; ++i)
{
float scale = m_editor.getUniverse()->getScale(m_editor.getSelectedEntities()[i]);
scale += delta;
new_scales.push(scale);
}
m_editor.setEntitiesScales(&m_editor.getSelectedEntities()[0], &new_scales[0], new_scales.size());
}
else
{
float scale = m_editor.getUniverse()->getScale(m_entities[m_active]);
scale += delta;
new_scales.push(scale);
m_editor.setEntitiesScales(&m_entities[m_active], &new_scales[0], 1);
}
m_transform_point = intersection;
}
}
void translate()
{
Matrix mtx = getMatrix(m_entities[m_active]);
Vec3 intersection = getMousePlaneIntersection(m_editor.getMousePos(), mtx, m_transform_axis);
Vec3 delta = intersection - m_transform_point;
if (!m_is_step || delta.length() > float(getStep()))
{
if (m_is_step) delta = delta.normalized() * float(getStep());
Array<Vec3> new_positions(m_editor.getAllocator());
if (m_entities[m_active] == m_editor.getSelectedEntities()[0])
{
for (int i = 0, ci = m_editor.getSelectedEntities().size(); i < ci; ++i)
{
Vec3 pos = m_editor.getUniverse()->getPosition(m_editor.getSelectedEntities()[i]);
pos += delta;
new_positions.push(pos);
}
m_editor.setEntitiesPositions(
&m_editor.getSelectedEntities()[0], &new_positions[0], new_positions.size());
if (m_is_autosnap_down) m_editor.snapDown();
}
else
{
Vec3 pos = m_editor.getUniverse()->getPosition(m_entities[m_active]);
pos += delta;
new_positions.push(pos);
m_editor.setEntitiesPositions(&m_entities[m_active], &new_positions[0], 1);
}
m_transform_point = intersection;
}
}
void transform()
{
if (m_active >= 0 && m_editor.isMouseClick(MouseButton::LEFT))
{
Matrix gizmo_mtx = getMatrix(m_entities[m_active]);
m_transform_point = getMousePlaneIntersection(m_editor.getMousePos(), gizmo_mtx, m_transform_axis);
m_start_axis_point = m_transform_point;
m_start_plane_point = getMousePlaneIntersection(m_editor.getMousePos(), gizmo_mtx, getPlane(m_transform_axis));
m_start_mouse_pos = m_editor.getMousePos();
m_angle_accum = 0;
m_is_dragging = true;
}
else if (!m_editor.isMouseDown(MouseButton::LEFT))
{
m_is_dragging = false;
}
if (!m_is_dragging || m_active < 0) return;
switch (m_mode)
{
case Mode::ROTATE: rotate(); break;
case Mode::TRANSLATE: translate(); break;
case Mode::SCALE: scale(); break;
default: ASSERT(false); break;
}
}
void render(const Matrix& gizmo_mtx, bool is_active)
{
auto edit_camera = m_editor.getEditCamera();
auto* render_interface = m_editor.getRenderInterface();
bool is_ortho = render_interface->isCameraOrtho(edit_camera.entity);
auto camera_pos = m_editor.getUniverse()->getPosition(edit_camera.entity);
auto camera_dir = m_editor.getUniverse()->getRotation(edit_camera.entity).rotate(Vec3(0, 0, -1));
float fov = render_interface->getCameraFOV(edit_camera.entity);
switch (m_mode)
{
case Mode::TRANSLATE:
renderTranslateGizmo(gizmo_mtx, is_active, camera_pos, camera_dir, fov, is_ortho);
break;
case Mode::ROTATE:
renderRotateGizmo(gizmo_mtx, is_active, camera_pos, camera_dir, fov, is_ortho);
break;
case Mode::SCALE:
renderScaleGizmo(gizmo_mtx, is_active, camera_pos, camera_dir, fov, is_ortho);
break;
default:
ASSERT(false);
break;
}
}
void render() override
{
auto edit_camera = m_editor.getEditCamera();
auto* render_interface = m_editor.getRenderInterface();
bool is_ortho = render_interface->isCameraOrtho(edit_camera.entity);
auto camera_pos = m_editor.getUniverse()->getPosition(edit_camera.entity);
auto camera_dir = m_editor.getUniverse()->getRotation(edit_camera.entity).rotate(Vec3(0, 0, -1));
float fov = render_interface->getCameraFOV(edit_camera.entity);
collide(camera_pos, camera_dir, fov, is_ortho);
transform();
for (int i = 0; i < m_count; ++i)
{
Matrix gizmo_mtx = getMatrix(m_entities[i]);
render(gizmo_mtx, m_active == i);
}
for (int i = 0; i < m_immediate_count; ++i)
{
render(m_immediate_frames[i].toMatrix(), m_active < 0);
}
m_immediate_count = 0;
m_mouse_pos.x = m_editor.getMousePos().x;
m_mouse_pos.y = m_editor.getMousePos().y;
m_count = 0;
}
Vec3 getOffset() const override
{
return m_offset;
}
void setOffset(const Vec3& offset) override
{
m_pivot = Pivot::OBJECT_PIVOT;
m_offset = offset;
}
void add(Entity entity) override
{
if (m_count >= MAX_GIZMOS) return;
m_entities[m_count] = entity;
++m_count;
}
void setTranslateMode() override
{
m_mode = Mode::TRANSLATE;
}
void setRotateMode() override
{
m_mode = Mode::ROTATE;
}
void setScaleMode() override
{
m_mode = Mode::SCALE;
}
void setPivotCenter() override { m_pivot = Pivot::CENTER; m_offset.set(0, 0, 0); }
void setPivotOrigin() override { m_pivot = Pivot::OBJECT_PIVOT; }
bool isPivotCenter() const override { return m_pivot == Pivot::CENTER; }
bool isPivotOrigin() const override { return m_pivot == Pivot::OBJECT_PIVOT; }
void setGlobalCoordSystem() override { m_coord_system = CoordSystem::WORLD; }
void setLocalCoordSystem() override { m_coord_system = CoordSystem::LOCAL; }
bool isLocalCoordSystem() const override { return m_coord_system == CoordSystem::LOCAL; }
bool isGlobalCoordSystem() const override { return m_coord_system == CoordSystem::WORLD; }
int getStep() const override { return m_steps[(int)m_mode]; }
void enableStep(bool enable) override { m_is_step = enable; }
void setStep(int step) override { m_steps[(int)m_mode] = step; }
bool isAutosnapDown() const override { return m_is_autosnap_down; }
void setAutosnapDown(bool snap) override { m_is_autosnap_down = snap; }
bool isTranslateMode() const override { return m_mode == Mode::TRANSLATE; }
bool isScaleMode() const override { return m_mode == Mode::SCALE; }
bool isRotateMode() const override { return m_mode == Mode::ROTATE; }
Pivot m_pivot;
CoordSystem m_coord_system;
int m_steps[(int)Mode::COUNT];
Mode m_mode;
Axis m_transform_axis;
bool m_is_autosnap_down;
WorldEditor& m_editor;
Vec3 m_transform_point;
Vec3 m_start_axis_point;
Vec3 m_start_plane_point;
Vec2 m_start_mouse_pos;
float m_angle_accum;
bool m_is_dragging;
int m_active;
Vec2 m_mouse_pos;
Vec2 m_rel_accum;
bool m_is_step;
int m_count;
Entity m_entities[MAX_GIZMOS];
Transform m_immediate_frames[MAX_IMMEDIATE];
int m_immediate_count;
Vec3 m_offset;
};
Gizmo* Gizmo::create(WorldEditor& editor)
{
return LUMIX_NEW(editor.getAllocator(), GizmoImpl)(editor);
}
void Gizmo::destroy(Gizmo& gizmo)
{
LUMIX_DELETE(static_cast<GizmoImpl&>(gizmo).m_editor.getAllocator(), &gizmo);
}
} // !namespace Lumix