overte-JulianGro/libraries/shared/src/Transform.h
2018-01-04 16:15:44 -08:00

568 lines
16 KiB
C++

//
// Transform.h
// shared/src/gpu
//
// Created by Sam Gateau on 11/4/2014.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_gpu_Transform_h
#define hifi_gpu_Transform_h
#include <assert.h>
#include "GLMHelpers.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/quaternion.hpp>
#include <bitset>
#include <memory>
class QJsonObject;
class QJsonValue;
inline bool isValidScale(glm::vec3 scale) {
bool result = scale.x != 0.0f && scale.y != 0.0f && scale.z != 0.0f;
assert(result);
return result;
}
inline bool isValidScale(float scale) {
bool result = scale != 0.0f && !glm::isnan(scale) && !glm::isinf(scale);
assert(result);
return result;
}
class Transform {
public:
friend QDebug& operator<<(QDebug& debug, const Transform& transform);
using Pointer = std::shared_ptr<Transform>;
typedef glm::mat4 Mat4;
typedef glm::mat3 Mat3;
typedef glm::vec4 Vec4;
typedef glm::vec3 Vec3;
typedef glm::vec2 Vec2;
typedef glm::quat Quat;
Transform() :
_rotation(1.0f, 0.0f, 0.0f, 0.0f),
_scale(1.0f),
_translation(0.0f),
_flags(FLAG_CACHE_INVALID_BITSET) // invalid cache
{
}
Transform(Quat rotation, Vec3 scale, Vec3 translation) :
_rotation(rotation),
_scale(scale),
_translation(translation),
_flags(0xf) // FLAG_TRANSLATION | FLAG_ROTATION | FLAG_SCALING | FLAG_NON_UNIFORM
{
if (!isValidScale(_scale)) {
_scale = Vec3(1.0f);
}
}
Transform(const Transform& transform) :
_rotation(transform._rotation),
_scale(transform._scale),
_translation(transform._translation),
_flags(transform._flags)
{
invalidCache();
}
Transform(const Mat4& raw) {
evalFromRawMatrix(raw);
}
~Transform() {}
Transform& operator=(const Transform& transform) {
_rotation = transform._rotation;
_scale = transform._scale;
_translation = transform._translation;
_flags = transform._flags;
invalidCache();
return (*this);
}
bool operator==(const Transform& other) const {
return _rotation == other._rotation && _scale == other._scale && _translation == other._translation;
}
bool operator!=(const Transform& other) const {
return _rotation != other._rotation || _scale != other._scale || _translation != other._translation;
}
Transform& setIdentity();
const Vec3& getTranslation() const;
Transform& setTranslation(const Vec3& translation); // [new this] = [translation] * [this.rotation] * [this.scale]
Transform& preTranslate(const Vec3& translation); // [new this] = [translation] * [this]
Transform& postTranslate(const Vec3& translation); // [new this] = [this] * [translation] equivalent to:glTranslate
const Quat& getRotation() const;
Transform& setRotation(const Quat& rotation); // [new this] = [this.translation] * [rotation] * [this.scale]
Transform& preRotate(const Quat& rotation); // [new this] = [rotation] * [this]
Transform& postRotate(const Quat& rotation); // [new this] = [this] * [rotation] equivalent to:glRotate
const Vec3& getScale() const;
Transform& setScale(float scale);
Transform& setScale(const Vec3& scale); // [new this] = [this.translation] * [this.rotation] * [scale]
Transform& postScale(float scale); // [new this] = [this] * [scale] equivalent to:glScale
Transform& postScale(const Vec3& scale); // [new this] = [this] * [scale] equivalent to:glScale
bool isIdentity() const { return (_flags & ~Flags(FLAG_CACHE_INVALID_BITSET)).none(); }
bool isTranslating() const { return _flags[FLAG_TRANSLATION]; }
bool isRotating() const { return _flags[FLAG_ROTATION]; }
bool isScaling() const { return _flags[FLAG_SCALING]; }
bool isUniform() const { return !isNonUniform(); }
bool isNonUniform() const { return _flags[FLAG_NON_UNIFORM]; }
Transform& evalFromRawMatrix(const Mat4& matrix);
Transform& evalFromRawMatrix(const Mat3& rotationScalematrix);
Mat4 getMatrix() const;
Mat4 getInverseMatrix() const;
Mat4& getMatrix(Mat4& result) const;
Mat4& getInverseMatrix(Mat4& result) const;
Mat4& getInverseTransposeMatrix(Mat4& result) const;
Mat4& getRotationScaleMatrix(Mat4& result) const;
Mat4& getRotationScaleMatrixInverse(Mat4& result) const;
Transform& evalInverse(Transform& result) const;
Transform relativeTransform(const Transform& world) const;
Transform worldTransform(const Transform& relative) const;
static void evalRotationScale(Quat& rotation, Vec3& scale, const Mat3& rotationScaleMatrix);
static Transform& mult(Transform& result, const Transform& left, const Transform& right);
// Left will be inversed before the multiplication
static Transform& inverseMult(Transform& result, const Transform& left, const Transform& right);
static Transform fromJson(const QJsonValue& json);
static QJsonObject toJson(const Transform& transform);
Vec4 transform(const Vec4& pos) const;
Vec3 transform(const Vec3& pos) const;
Vec3 transformDirection(const Vec3& dir) const;
bool containsNaN() const { return isNaN(_rotation) || isNaN(glm::dot(_scale, _translation)); }
protected:
enum Flag {
FLAG_CACHE_INVALID = 0,
FLAG_TRANSLATION,
FLAG_ROTATION,
FLAG_SCALING,
FLAG_NON_UNIFORM,
FLAG_ZERO_SCALE,
FLAG_PROJECTION,
NUM_FLAGS,
FLAG_CACHE_INVALID_BITSET = 1,
};
typedef std::bitset<NUM_FLAGS> Flags;
// TRS
Quat _rotation;
Vec3 _scale;
Vec3 _translation;
mutable Flags _flags;
// Cached transform
mutable std::unique_ptr<Mat4> _matrix;
bool isCacheInvalid() const { return _flags[FLAG_CACHE_INVALID]; }
void validCache() const { _flags.set(FLAG_CACHE_INVALID, false); }
void invalidCache() const { _flags.set(FLAG_CACHE_INVALID, true); }
void flagTranslation() { _flags.set(FLAG_TRANSLATION, true); }
void unflagTranslation() { _flags.set(FLAG_TRANSLATION, false); }
void flagRotation() { _flags.set(FLAG_ROTATION, true); }
void unflagRotation() { _flags.set(FLAG_ROTATION, false); }
void flagScaling() { _flags.set(FLAG_SCALING, true); }
void unflagScaling() { _flags.set(FLAG_SCALING, false); }
void flagUniform() { _flags.set(FLAG_NON_UNIFORM, false); }
void flagNonUniform() { _flags.set(FLAG_NON_UNIFORM, true); }
void updateCache() const;
Mat4& getCachedMatrix(Mat4& result) const;
};
QDebug& operator<<(QDebug& debug, const Transform& transform);
inline Transform& Transform::setIdentity() {
_translation = Vec3(0.0f);
_rotation = Quat(1.0f, 0.0f, 0.0f, 0.0f);
_scale = Vec3(1.0f);
_flags = Flags(FLAG_CACHE_INVALID_BITSET);
return *this;
}
inline const Transform::Vec3& Transform::getTranslation() const {
return _translation;
}
inline Transform& Transform::setTranslation(const Vec3& translation) {
invalidCache();
if (translation == Vec3()) {
unflagTranslation();
} else {
flagTranslation();
}
_translation = translation;
return *this;
}
inline Transform& Transform::preTranslate(const Vec3& translation) {
if (translation == Vec3()) {
return *this;
}
invalidCache();
flagTranslation();
_translation += translation;
return *this;
}
inline Transform& Transform::postTranslate(const Vec3& translation) {
if (translation == Vec3()) {
return *this;
}
invalidCache();
flagTranslation();
Vec3 scaledT = translation;
if (isScaling()) {
scaledT *= _scale;
}
if (isRotating()) {
_translation += glm::rotate(_rotation, scaledT);
} else {
_translation += scaledT;
}
return *this;
}
inline const Transform::Quat& Transform::getRotation() const {
return _rotation;
}
inline Transform& Transform::setRotation(const Quat& rotation) {
invalidCache();
if (rotation == Quat()) {
unflagRotation();
} else {
flagRotation();
}
_rotation = rotation;
return *this;
}
inline Transform& Transform::preRotate(const Quat& rotation) {
if (rotation == Quat()) {
return *this;
}
invalidCache();
if (isRotating()) {
_rotation = rotation * _rotation;
} else {
_rotation = rotation;
}
flagRotation();
_translation = glm::rotate(rotation, _translation);
return *this;
}
inline Transform& Transform::postRotate(const Quat& rotation) {
if (rotation == Quat()) {
return *this;
}
invalidCache();
if (isNonUniform()) {
Quat newRot;
Vec3 newScale;
Mat3 scaleRot(glm::mat3_cast(rotation));
scaleRot[0] *= _scale;
scaleRot[1] *= _scale;
scaleRot[2] *= _scale;
evalRotationScale(newRot, newScale, scaleRot);
if (isRotating()) {
_rotation *= newRot;
} else {
_rotation = newRot;
}
setScale(newScale);
} else {
if (isRotating()) {
_rotation *= rotation;
} else {
_rotation = rotation;
}
}
flagRotation();
return *this;
}
inline const Transform::Vec3& Transform::getScale() const {
return _scale;
}
inline Transform& Transform::setScale(float scale) {
if (!isValidScale(scale)) {
return *this;
}
invalidCache();
flagUniform();
if (scale == 1.0f) {
unflagScaling();
} else {
flagScaling();
}
_scale = Vec3(scale);
return *this;
}
inline Transform& Transform::setScale(const Vec3& scale) {
if (!isValidScale(scale)) {
return *this;
}
if ((scale.x == scale.y) && (scale.x == scale.z)) {
return setScale(scale.x);
}
invalidCache();
flagScaling();
flagNonUniform();
_scale = scale;
return *this;
}
inline Transform& Transform::postScale(float scale) {
if (!isValidScale(scale) || scale == 1.0f) {
return *this;
}
if (!isScaling()) {
return setScale(scale);
}
// if already scaling, just invalid cache and apply uniform scale
invalidCache();
_scale *= scale;
return *this;
}
inline Transform& Transform::postScale(const Vec3& scale) {
if (!isValidScale(scale)) {
return *this;
}
invalidCache();
if ((scale.x != scale.y) || (scale.x != scale.z)) {
flagNonUniform();
}
if (isScaling()) {
_scale *= scale;
} else {
_scale = scale;
}
flagScaling();
return *this;
}
inline Transform::Mat4 Transform::getMatrix() const {
Transform::Mat4 result;
getMatrix(result);
return result;
}
inline Transform::Mat4 Transform::getInverseMatrix() const {
Transform::Mat4 result;
getInverseMatrix(result);
return result;
}
inline Transform::Mat4& Transform::getMatrix(Transform::Mat4& result) const {
if (isRotating()) {
Mat3 rot = glm::mat3_cast(_rotation);
if (isScaling()) {
rot[0] *= _scale.x;
rot[1] *= _scale.y;
rot[2] *= _scale.z;
}
result[0] = Vec4(rot[0], 0.0f);
result[1] = Vec4(rot[1], 0.0f);
result[2] = Vec4(rot[2], 0.0f);
} else {
result[0] = Vec4(_scale.x, 0.0f, 0.0f, 0.0f);
result[1] = Vec4(0.0f, _scale.y, 0.0f, 0.0f);
result[2] = Vec4(0.0f, 0.0f, _scale.z, 0.0f);
}
result[3] = Vec4(_translation, 1.0f);
return result;
}
inline Transform::Mat4& Transform::getInverseMatrix(Transform::Mat4& result) const {
Transform inverse;
evalInverse(inverse);
return inverse.getMatrix(result);
}
inline Transform::Mat4& Transform::getInverseTransposeMatrix(Transform::Mat4& result) const {
getInverseMatrix(result);
result = glm::transpose(result);
return result;
}
inline Transform::Mat4& Transform::getRotationScaleMatrix(Mat4& result) const {
getMatrix(result);
result[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f);
return result;
}
inline Transform::Mat4& Transform::getRotationScaleMatrixInverse(Mat4& result) const {
getInverseMatrix(result);
result[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f);
return result;
}
inline Transform& Transform::evalFromRawMatrix(const Mat4& matrix) {
// for now works only in the case of TRS transformation
if ((matrix[0][3] == 0.0f) && (matrix[1][3] == 0.0f) && (matrix[2][3] == 0.0f) && (matrix[3][3] == 1.0f)) {
setTranslation(extractTranslation(matrix));
evalFromRawMatrix(Mat3(matrix));
}
return *this;
}
inline Transform& Transform::evalFromRawMatrix(const Mat3& rotationScaleMatrix) {
Quat rotation;
Vec3 scale;
evalRotationScale(rotation, scale, rotationScaleMatrix);
setRotation(rotation);
setScale(scale);
return *this;
}
inline Transform& Transform::evalInverse(Transform& inverse) const {
inverse.setIdentity();
if (isScaling()) {
if (isNonUniform()) {
inverse.setScale(Vec3(1.0f) / _scale);
} else {
inverse.setScale(1.0f / _scale.x);
}
}
if (isRotating()) {
inverse.postRotate(glm::conjugate(_rotation));
}
if (isTranslating()) {
inverse.postTranslate(-_translation);
}
return inverse;
}
inline Transform& Transform::mult( Transform& result, const Transform& left, const Transform& right) {
result = left;
if (right.isTranslating()) {
result.postTranslate(right.getTranslation());
}
if (right.isRotating()) {
result.postRotate(right.getRotation());
}
if (right.isScaling()) {
result.postScale(right.getScale());
}
// HACK: In case of an issue in the Transform multiplication results, to make sure this code is
// working properly uncomment the next 2 lines and compare the results, they should be the same...
// Transform::Mat4 mv = left.getMatrix() * right.getMatrix();
// Transform::Mat4 mv2 = result.getMatrix();
return result;
}
inline Transform& Transform::inverseMult( Transform& result, const Transform& left, const Transform& right) {
result.setIdentity();
if (left.isScaling()) {
result.setScale(Vec3(1.0f) / left.getScale());
}
if (left.isRotating()) {
result.postRotate(glm::conjugate(left.getRotation()));
}
if (left.isTranslating() || right.isTranslating()) {
result.postTranslate(right.getTranslation() - left.getTranslation());
}
if (right.isRotating()) {
result.postRotate(right.getRotation());
}
if (right.isScaling()) {
result.postScale(right.getScale());
}
// HACK: In case of an issue in the Transform multiplication results, to make sure this code is
// working properly uncomment the next 2 lines and compare the results, they should be the same...
// Transform::Mat4 mv = left.getMatrix() * right.getMatrix();
// Transform::Mat4 mv2 = result.getMatrix();
return result;
}
inline Transform::Vec4 Transform::transform(const Vec4& pos) const {
Mat4 m;
getMatrix(m);
return m * pos;
}
inline Transform::Vec3 Transform::transform(const Vec3& pos) const {
Mat4 m;
getMatrix(m);
Vec4 result = m * Vec4(pos, 1.0f);
return Vec3(result.x / result.w, result.y / result.w, result.z / result.w);
}
inline Transform::Vec3 Transform::transformDirection(const Vec3& dir) const {
Mat4 m;
getMatrix(m);
Vec4 result = m * Vec4(dir, 0.0f);
return Vec3(result.x, result.y, result.z);
}
inline Transform::Mat4& Transform::getCachedMatrix(Transform::Mat4& result) const {
updateCache();
result = (*_matrix);
return result;
}
inline void Transform::updateCache() const {
if (isCacheInvalid()) {
if (!_matrix.get()) {
_matrix.reset(new Mat4());
}
getMatrix((*_matrix));
validCache();
}
}
#endif