226 lines
6.7 KiB
C++
226 lines
6.7 KiB
C++
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// Licensed under the MIT License.
|
||
|
|
||
|
// Associated header
|
||
|
//
|
||
|
#include "k4apointcloudviewcontrol.h"
|
||
|
|
||
|
// System headers
|
||
|
//
|
||
|
#include <algorithm>
|
||
|
|
||
|
// Library headers
|
||
|
//
|
||
|
|
||
|
// Project headers
|
||
|
//
|
||
|
|
||
|
using namespace k4aviewer;
|
||
|
using namespace linmath;
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
inline float Radians(const float angle)
|
||
|
{
|
||
|
constexpr float pi = 3.14159265358979323846f;
|
||
|
return angle / 180.f * pi;
|
||
|
}
|
||
|
|
||
|
// Default camera values
|
||
|
//
|
||
|
constexpr float MinZoom = 1.0f;
|
||
|
constexpr float MaxZoom = 120.0f;
|
||
|
constexpr float DefaultZoom = 65.0f;
|
||
|
constexpr float ZoomSensitivity = 3.f;
|
||
|
constexpr float TranslationSensitivity = 0.01f;
|
||
|
|
||
|
// Default point cloud position, chosen such that the entire point cloud
|
||
|
// should be in the field of view
|
||
|
//
|
||
|
const vec3 DefaultPointCloudPosition{ 0.f, 0.f, -8.0f };
|
||
|
|
||
|
// Approximate midpoint of a point cloud. We need to translate
|
||
|
// the point cloud by this much before we start applying rotations to the
|
||
|
// point cloud in order to to get the rotation to be around the point
|
||
|
// cloud's midpoint instead of its origin.
|
||
|
//
|
||
|
const vec3 PointCloudMidpoint{ 0.f, 1.f, -3.0f };
|
||
|
|
||
|
// Version of mat4x4_mul that doesn't modify its non-result inputs (i.e. a, b)
|
||
|
//
|
||
|
void MatrixMultiply(mat4x4 out, mat4x4 a, mat4x4 b)
|
||
|
{
|
||
|
mat4x4 atmp;
|
||
|
mat4x4 btmp;
|
||
|
mat4x4_dup(atmp, a);
|
||
|
mat4x4_dup(btmp, b);
|
||
|
mat4x4_mul(out, a, b);
|
||
|
}
|
||
|
|
||
|
// Map XY coordinates to a virtual sphere, which we use for rotation calculations
|
||
|
//
|
||
|
void MapToArcball(vec3 out, const vec2 displayDimensions, const vec2 mousePos)
|
||
|
{
|
||
|
// Scale coords to (-1, 1) to simplify some of the math
|
||
|
//
|
||
|
vec2 scaledMousePos;
|
||
|
for (int i = 0; i < 2; ++i)
|
||
|
{
|
||
|
scaledMousePos[i] = mousePos[i] * (1.0f / ((displayDimensions[i] - 1.0f) * 0.5f)) - 1.0f;
|
||
|
}
|
||
|
|
||
|
float lenSquared = scaledMousePos[0] * scaledMousePos[0] + scaledMousePos[1] * scaledMousePos[1];
|
||
|
|
||
|
// If the point is 'outside' our virtual sphere, we need to normalize to the sphere
|
||
|
// This works because our sphere is of radius 1
|
||
|
//
|
||
|
if (lenSquared > 1.f)
|
||
|
{
|
||
|
const float normalizationFactor = 1.f / std::sqrt(lenSquared);
|
||
|
|
||
|
// Return a point on the edge of the sphere
|
||
|
//
|
||
|
out[0] = scaledMousePos[0] * normalizationFactor;
|
||
|
out[1] = scaledMousePos[1] * normalizationFactor;
|
||
|
out[2] = 0.f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Return a point inside the sphere
|
||
|
//
|
||
|
out[0] = scaledMousePos[0];
|
||
|
out[1] = scaledMousePos[1];
|
||
|
out[2] = std::sqrt(1.f - lenSquared);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Imagine a virtual sphere of displayDimensions size were drawn on the screen.
|
||
|
// Returns a quaternion representing the rotation that sphere would undergo if you took the point
|
||
|
// on that sphere at startPos and rotated it to endPos (i.e. an "arcball" camera).
|
||
|
//
|
||
|
void GetArcballRotation(quat rotation, const vec2 displayDimensions, const vec2 startPos, const vec2 endPos)
|
||
|
{
|
||
|
vec3 startVector;
|
||
|
MapToArcball(startVector, displayDimensions, startPos);
|
||
|
|
||
|
vec3 endVector;
|
||
|
MapToArcball(endVector, displayDimensions, endPos);
|
||
|
|
||
|
vec3 cross;
|
||
|
vec3_mul_cross(cross, startVector, endVector);
|
||
|
|
||
|
constexpr float epsilon = 0.001f;
|
||
|
if (vec3_len(cross) < epsilon)
|
||
|
{
|
||
|
// Smooth out floating point error if the user didn't move the mouse
|
||
|
// enough that it should register
|
||
|
//
|
||
|
quat_identity(rotation);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// The first 3 elements of the quaternion are the unit vector perpendicular
|
||
|
// to the rotation (i.e. the cross product); the last element is the magnitude
|
||
|
// of the rotation
|
||
|
//
|
||
|
vec3_copy(rotation, cross);
|
||
|
vec3_norm(cross, cross);
|
||
|
rotation[3] = vec3_mul_inner(startVector, endVector);
|
||
|
}
|
||
|
}
|
||
|
} // namespace
|
||
|
|
||
|
ViewControl::ViewControl() : m_zoom(DefaultZoom)
|
||
|
{
|
||
|
ResetPosition();
|
||
|
}
|
||
|
|
||
|
void ViewControl::GetViewMatrix(mat4x4 viewMatrix)
|
||
|
{
|
||
|
mat4x4_identity(viewMatrix);
|
||
|
|
||
|
// Move the center of the point cloud to (0, 0, 0) so we can rotate it
|
||
|
//
|
||
|
mat4x4 pointCloudMidpointTranslation;
|
||
|
mat4x4_translate(pointCloudMidpointTranslation,
|
||
|
PointCloudMidpoint[0],
|
||
|
PointCloudMidpoint[1],
|
||
|
PointCloudMidpoint[2]);
|
||
|
|
||
|
// Move the point cloud to a point in front of the field of view
|
||
|
//
|
||
|
mat4x4 pointCloudFinalTranslation;
|
||
|
mat4x4_translate(pointCloudFinalTranslation,
|
||
|
m_pointCloudPosition[0],
|
||
|
m_pointCloudPosition[1],
|
||
|
m_pointCloudPosition[2]);
|
||
|
|
||
|
// Rotate 180 degrees about the Y axis so the scene starts out facing toward the user
|
||
|
//
|
||
|
quat rotateQuat;
|
||
|
vec3 rotateAxis{ 0.f, 1.f, 0.f };
|
||
|
mat4x4 rotateMatrix;
|
||
|
quat_rotate(rotateQuat, Radians(180), rotateAxis);
|
||
|
mat4x4_from_quat(rotateMatrix, rotateQuat);
|
||
|
|
||
|
// Multiplication order is reversed because we're moving the scene, not the camera
|
||
|
//
|
||
|
|
||
|
// Move the point cloud into the field of view
|
||
|
//
|
||
|
MatrixMultiply(viewMatrix, viewMatrix, pointCloudFinalTranslation);
|
||
|
|
||
|
// Set up the point cloud
|
||
|
//
|
||
|
MatrixMultiply(viewMatrix, viewMatrix, m_userRotations);
|
||
|
MatrixMultiply(viewMatrix, viewMatrix, rotateMatrix);
|
||
|
MatrixMultiply(viewMatrix, viewMatrix, pointCloudMidpointTranslation);
|
||
|
}
|
||
|
|
||
|
void ViewControl::GetPerspectiveMatrix(mat4x4 perspectiveMatrix, const vec2 renderDimensions) const
|
||
|
{
|
||
|
mat4x4_perspective(perspectiveMatrix, Radians(m_zoom), renderDimensions[0] / renderDimensions[1], 0.1f, 100.f);
|
||
|
}
|
||
|
|
||
|
void ViewControl::ProcessMouseMovement(const vec2 displayDimensions,
|
||
|
const vec2 mousePos,
|
||
|
const vec2 mouseDelta,
|
||
|
MouseMovementType movementType)
|
||
|
{
|
||
|
if (movementType == MouseMovementType::Rotation)
|
||
|
{
|
||
|
vec2 lastMousePos;
|
||
|
vec2_copy(lastMousePos, mousePos);
|
||
|
vec2_sub(lastMousePos, mousePos, mouseDelta);
|
||
|
|
||
|
quat newRotationQuat;
|
||
|
GetArcballRotation(newRotationQuat, displayDimensions, lastMousePos, mousePos);
|
||
|
|
||
|
mat4x4 newRotationMtx;
|
||
|
mat4x4_from_quat(newRotationMtx, newRotationQuat);
|
||
|
|
||
|
MatrixMultiply(m_userRotations, newRotationMtx, m_userRotations);
|
||
|
}
|
||
|
else if (movementType == MouseMovementType::Translation)
|
||
|
{
|
||
|
m_pointCloudPosition[0] += mouseDelta[0] * TranslationSensitivity;
|
||
|
m_pointCloudPosition[1] += mouseDelta[1] * TranslationSensitivity;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ViewControl::ProcessMouseScroll(const float yoffset)
|
||
|
{
|
||
|
if (m_zoom >= MinZoom && m_zoom <= MaxZoom)
|
||
|
{
|
||
|
m_zoom -= yoffset * ZoomSensitivity;
|
||
|
m_zoom = std::min(MaxZoom, std::max(MinZoom, m_zoom));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ViewControl::ResetPosition()
|
||
|
{
|
||
|
vec3_copy(m_pointCloudPosition, DefaultPointCloudPosition);
|
||
|
m_zoom = DefaultZoom;
|
||
|
mat4x4_identity(m_userRotations);
|
||
|
}
|