Commit 328a4b02 authored by Philip Trettner's avatar Philip Trettner

Initial commit with readme and ported cameras

parents
cmake_minimum_required(VERSION 3.0)
project(glow-extras)
add_library(glow-extras ${GLOW_LINK_TYPE} glow-extras.cc)
# Camera
add_subdirectory(camera)
target_link_libraries(glow-extras PUBLIC glow-extras-camera)
# Glow Extras
Companion library for glow with convenience and helper functions.
## Usage
Add git submodule
```
git submodule add https://www.graphics.rwth-aachen.de:9000/Glow/glow-extras.git
```
Add to cmake (be sure to do that _after_ glow)
```
add_subdirectory(path/to/glow-extras)
```
Choose which libraries you need
```
# all
target_link_libraries(YourLib PUBLIC glow-extras)
# .. or single ones
target_link_libraries(YourLib PUBLIC glow-extras-camera)
target_link_libraries(YourLib PUBLIC glow-extras-mesh)
target_link_libraries(YourLib PUBLIC glow-extras-postprocess)
```
## Sub-libs
Currently supported sub-libs with their intent.
### glow-extras-camera
Camera classes:
* BaseCamera
* StaticCamera
* GenericCamera
cmake_minimum_required(VERSION 3.0)
file(GLOB_RECURSE SOURCE_FILES "*.cc")
file(GLOB_RECURSE HEADER_FILES "*.hh")
add_library(glow-extras-camera ${GLOW_LINK_TYPE} ${SOURCE_FILES} ${HEADER_FILES})
target_include_directories(glow-extras-camera PUBLIC ./)
target_compile_options(glow-extras-camera PRIVATE -Wall -Werror -std=c++11)
target_link_libraries(glow-extras-camera PUBLIC glow)
#include "CameraBase.hh"
using namespace glow::extras;
CameraBase::CameraBase()
{
}
CameraBase::~CameraBase()
{
}
#pragma once
#include <glm/fwd.hpp>
namespace glow
{
namespace extras
{
/**
* @brief Common interface for cameras
*
* This interface only contains getter on purpose.
* All logic that wants to modify a camera should know the actual structure of the camera and therefore use the
* specific subclass.
*/
class CameraBase
{
protected:
CameraBase();
public:
virtual ~CameraBase();
/**
* @brief gets the Position of the camera
* @return a 3-dimensional position in the global coordinate system
*/
virtual glm::vec3 getPosition() const = 0;
/**
* @brief gets the ViewMatrix of the camera
* @return a 4x4 matrix containing projection independent camera transforms
*/
virtual glm::mat4 getViewMatrix() const = 0;
/**
* @brief gets the ProjectionMatrix of the camera
* @return a 4x4 matrix containing the projection into normalized device coordinates
*/
virtual glm::mat4 getProjectionMatrix() const = 0;
/**
* @brief gets the ViewportSize of the current viewport of this camera
* @return the 2-dimensional size of the viewport
*/
virtual glm::uvec2 getViewportSize() const = 0;
/**
* @brief Gets the near clipping plane as a distance from the camera.
* @return the near clipping plane
*/
virtual float getNearClippingPlane() const = 0;
/**
* @brief Gets the far clipping plane as a distance from the camera. Note that it could be inf!
* Not all projection matrices have a real far plane but some are unlimited!
* @return the near clipping plane
*/
virtual float getFarClippingPlane() const = 0;
};
}
}
#include "FixedCamera.hh"
#include <glm/glm.hpp>
using namespace glow::extras;
FixedCamera::FixedCamera()
{
}
FixedCamera::FixedCamera(const glm::vec3 &_pos, const glm::mat4 &_view, const glm::mat4 &_proj, const glm::uvec2 &_viewport)
: mPosition(_pos), mViewMatrix(_view), mViewportSize(_viewport)
{
setProjectionMatrix(_proj);
}
void FixedCamera::setProjectionMatrix(glm::mat4 const &_val)
{
mProjectionMatrix = _val;
//
// Calculate near and far plane from the matrix
// As the matrix does not have to be a standard OpenGL
// projection matrix, we can't derrive the values from
// the matrix entries directly but we have to reproject
// points from the NDC cube... ...but as some matrices
// project to -1..1 and others to 0..1 we have to figure
// this out first... ...keeping in mind, that some also
// invert the range inside of the Z buffer, so Z=1 might
// be the near plane while Z=0 is the far plane...
// ...oh, and the far plane might be at infinity...
//
//
// First step, figure out which values project to
// -1, 0 and 1:
//
glm::mat4 invProj = glm::inverse(mProjectionMatrix);
glm::vec4 tmp = invProj * glm::vec4(0.0f, 0.0f, -1.0f, 1.0f);
float reproj_minusOne = tmp.z / tmp.w;
tmp = invProj * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
float reproj_Zero = tmp.z / tmp.w;
tmp = invProj * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f);
float reproj_One = tmp.z / tmp.w;
//
// Next step, figure out which value is the near plane and which is the far plane.
//
// right handed coordinate system -> the camera was looking along the negative Z axis
// if it was an OpenGL matrix, the projections will result in -near, -something, -far
// if it was an OpenGL matrix with infinit far plane, the projections will result in -near, -something, -inf
// if it was a reverse DX style matrix it will result in +near, -inf, -near
if (reproj_minusOne > 0.0f)
{
// near,far mapped to 1,0 or 0,1 as -1 was mapped to a positive value which is behind the camera
// if we assume we are looking along the negative Z axis!
if (-reproj_Zero > -reproj_One)
{
// the far plane is mapped to 0 -> inverse DX style
mFarPlane = -reproj_Zero;
mNearPlane = -reproj_One;
}
else
{
// the near plane is mapped to zero -> DX style
mFarPlane = -reproj_One;
mNearPlane = -reproj_Zero;
}
}
else
{
// OpenGL style, mapping to -1..1 or 1..-1
if (-reproj_minusOne > -reproj_One)
{
// the far plane is mapped to -1 -> inverse GL style
mFarPlane = -reproj_minusOne;
mNearPlane = -reproj_One;
}
else
{
// the near plane is mapped to -1 -> GL style
mFarPlane = -reproj_One;
mNearPlane = -reproj_minusOne;
}
}
}
#pragma once
#include "CameraBase.hh"
#include <glm/vec3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
namespace glow
{
namespace extras
{
/**
* @brief A fixed camera where all attributes are set explicitly except for the near/far plane
* which are derrived from the projection matrix.
*/
class FixedCamera : public CameraBase
{
private:
glm::vec3 mPosition;
glm::mat4 mViewMatrix;
glm::mat4 mProjectionMatrix;
glm::uvec2 mViewportSize;
// will get calculated based on the projection matrix
// so there are no explicit setters for this!
float mNearPlane;
float mFarPlane;
public:
/// CAUTION: default ctor with zero values
FixedCamera();
FixedCamera(const glm::vec3& _pos, const glm::mat4& _view, const glm::mat4& _proj, const glm::uvec2& _viewport);
// Getter, Setter for Camera Position
virtual glm::vec3 getPosition() const override { return mPosition; }
virtual void setPosition(glm::vec3 const& _val) { mPosition = _val; }
// Getter, Setter for Camera ViewMatrix
virtual glm::mat4 getViewMatrix() const override { return mViewMatrix; }
virtual void setViewMatrix(glm::mat4 const& _val) { mViewMatrix = _val; }
// Getter, Setter for Camera ProjectionMatrix
virtual glm::mat4 getProjectionMatrix() const override { return mProjectionMatrix; }
virtual void setProjectionMatrix(glm::mat4 const& _val);
// Getter, Setter for Camera ViewportSize
virtual glm::uvec2 getViewportSize() const override { return mViewportSize; }
virtual void setViewportSize(glm::uvec2 const& _val) { mViewportSize = _val; }
// getters for near/far plane (far can be inf!)
virtual float getNearClippingPlane() const override { return mNearPlane; }
virtual float getFarClippingPlane() const override { return mFarPlane; }
};
}
}
#include "GenericCamera.hh"
#include <glow/common/log.hh>
#include <cassert>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <glm/glm.hpp>
#include <glm/gtx/string_cast.hpp>
namespace
{
bool isApproxEqual(const glm::mat4 &_v1, const glm::mat4 &_v2, float _eps = .01)
{
glm::mat4 diff = _v1 - _v2;
float d = 0;
d += glm::abs(diff[0][0]);
d += glm::abs(diff[0][1]);
d += glm::abs(diff[0][2]);
d += glm::abs(diff[0][3]);
d += glm::abs(diff[1][0]);
d += glm::abs(diff[1][1]);
d += glm::abs(diff[1][2]);
d += glm::abs(diff[1][3]);
d += glm::abs(diff[2][0]);
d += glm::abs(diff[2][1]);
d += glm::abs(diff[2][2]);
d += glm::abs(diff[2][3]);
d += glm::abs(diff[3][0]);
d += glm::abs(diff[3][1]);
d += glm::abs(diff[3][2]);
d += glm::abs(diff[3][3]);
return d < _eps;
}
bool isApproxEqual(const glm::mat3 &_v1, const glm::mat3 &_v2, float _eps = .01)
{
glm::mat3 diff = _v1 - _v2;
float d = 0;
d += glm::abs(diff[0][0]);
d += glm::abs(diff[0][1]);
d += glm::abs(diff[0][2]);
d += glm::abs(diff[1][0]);
d += glm::abs(diff[1][1]);
d += glm::abs(diff[1][2]);
d += glm::abs(diff[2][0]);
d += glm::abs(diff[2][1]);
d += glm::abs(diff[2][2]);
return d < _eps;
}
bool isOrthonormalMatrix(const glm::mat3 &_matrix)
{
return isApproxEqual(glm::inverse(_matrix), glm::transpose(_matrix));
}
}
using namespace glow::extras;
using namespace std;
GenericCamera::GenericCamera()
{
setRotationMatrix(glm::mat3(1.0f));
}
GenericCamera::GenericCamera(const std::string &_state)
{
setStateFromString(_state);
}
void GenericCamera::FPSstyleLookAround(float _deltaX, float _deltaY)
{
float yaw = 0.0f;
float pitch = 0.0f;
glm::mat3 R = getRotationMatrix3();
// get roll / pitch / yaw from the current rotation matrix:
float yaw1 = asin(-R[2][0]);
float yaw2 = M_PI - asin(-R[2][0]);
float pitch1 = (cos(yaw1) > 0) ? atan2(R[2][1], R[2][2]) : atan2(-R[2][1], -R[2][2]);
float pitch2 = (cos(yaw2) > 0) ? atan2(R[2][1], R[2][2]) : atan2(-R[2][1], -R[2][2]);
float roll1 = (cos(yaw1) > 0) ? atan2(R[1][0], R[0][0]) : atan2(-R[1][0], -R[0][0]);
float roll2 = (cos(yaw2) > 0) ? atan2(R[1][0], R[0][0]) : atan2(-R[1][0], -R[0][0]);
// we assume no roll at all, in that case one of the roll variants will be 0.0
// if not, use the smaller one -> minimize the camera "jump" as this will destroy
// information
if (std::abs(roll1) <= std::abs(roll2))
{
yaw = -yaw1;
pitch = -pitch1;
}
else
{
yaw = -yaw2;
pitch = -pitch2;
}
// add rotation diffs given:
yaw = yaw + _deltaX;
pitch = glm::clamp(pitch + _deltaY, -0.5f * (float)M_PI, 0.5f * (float)M_PI);
// create rotation matices, seperated so we have full control over the order:
glm::mat4 newRotY = glm::yawPitchRoll(yaw, 0.0f, 0.0f);
glm::mat4 newRotX = glm::yawPitchRoll(0.0f, pitch, 0.0f);
// multiplication order is important to prevent roll:
setRotationMatrix(newRotX * newRotY);
}
void GenericCamera::rotateAroundTaget_GlobalAxes(float _x, float _y, float _z)
{
// move camera so, that the target is the center, then rotate around the
// global coordinate system
glm::mat3 t = glm::mat3(glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
rotateAroundTaget_helper(_x, _y, _z, t);
}
void GenericCamera::rotateAroundTaget_LocalAxes(float _x, float _y, float _z)
{
glm::mat3 R = getRotationMatrix3();
R = glm::transpose(R);
rotateAroundTaget_helper(_x, _y, _z, R);
}
void GenericCamera::rotateAroundTaget_helper(float _x, float _y, float _z, const glm::mat3 &_rotationAxes)
{
glm::vec4 T = glm::vec4(getTarget(), 1.0f);
glm::vec4 P = glm::vec4(getPosition(), 1.0f);
glm::vec4 tempPos = P - T;
glm::mat4 newRotation = glm::rotate(glm::mat4(), _x, _rotationAxes[0]);
newRotation = glm::rotate(newRotation, _y, _rotationAxes[1]);
newRotation = glm::rotate(newRotation, _z, _rotationAxes[2]);
tempPos = newRotation * tempPos;
P = tempPos + T; // new position
glm::vec4 N = glm::vec4(getUpDirection(), 1.0f);
N = newRotation * N;
setLookAtMatrix(glm::vec3(P), glm::vec3(T), glm::vec3(N));
}
void GenericCamera::setHorizontalFieldOfView(float _fovh)
{
assert(_fovh < 180.0f);
assert(_fovh > 0.0f);
mHorizontalFieldOfView = _fovh;
}
void GenericCamera::setVerticalFieldOfView(float _fovv)
{
assert(_fovv < 180.0f);
assert(_fovv > 0.0f);
// we only save the aspectRatio and the horizontal FoV
// so if we change the vertical FoV, we change the aspectRatio
// mAspectRatio = tan( glm::radians(0.5f * mHorizontalFieldOfView) ) / tan( glm::radians(0.5f * _fovv) );
float x = tan(glm::radians(0.5f * _fovv)) * mAspectRatio;
mHorizontalFieldOfView = glm::degrees(2.0f * atan(x));
}
float GenericCamera::getVerticalFieldOfView() const
{
return glm::degrees(atan(tan(glm::radians(0.5f * mHorizontalFieldOfView)) / mAspectRatio) * 2.0f);
}
void GenericCamera::setNearClippingPlane(float _plane)
{
assert(_plane > 0.0f);
mNearClippingPlane = _plane;
}
void GenericCamera::setFarClippingPlane(float _plane)
{
assert(_plane > 0.0f);
mFarClippingPlane = _plane;
}
glm::mat4 GenericCamera::getViewMatrix() const
{
if (mStereoMode == Mono)
{
return getMonoViewMatrix();
}
else
{
// all kinds of stereo
bool eyeIsLeftEye = (getEye() == EyeLeft);
return getStereoViewMatrix(eyeIsLeftEye, mStereoMode);
}
}
glm::mat4 GenericCamera::getStereoViewMatrix(bool _leftEye, StereoMode _stereoMode) const
{
// The view matrix is independent of the projection mode (isometric or perspective)
// so only the stereo mode has to be checked.
assert(_stereoMode != Mono && "mono is not a stereo mode!");
float cameraPositionShiftValue = (mInterpupillaryDistance * 0.5f); // shift to the right
if (_leftEye)
cameraPositionShiftValue *= -1.0f; // if left eye shift to the left
if ((_stereoMode == ParallelShift) || (_stereoMode == OffAxis))
{
//
// parallel shift and off-axis have the same view matrices:
// just shift the camera position to the left/right by half the eye-distance
//
// ACGL::Utils::debug() << "WARNING: getStereoViewMatrix() is not tested yet" << std::endl; // remove after
// testing
glm::mat3 inverseRotation = getInverseRotationMatrix3();
glm::vec3 eyePosition = mPosition + (inverseRotation * glm::vec3(cameraPositionShiftValue, 0.0f, 0.0f));
glm::mat4 m(mRotationMatrix);
m[3][0] = -(m[0][0] * eyePosition.x + m[1][0] * eyePosition.y + m[2][0] * eyePosition.z);
m[3][1] = -(m[0][1] * eyePosition.x + m[1][1] * eyePosition.y + m[2][1] * eyePosition.z);
m[3][2] = -(m[0][2] * eyePosition.x + m[1][2] * eyePosition.y + m[2][2] * eyePosition.z);
return m;
}
// else it has to be toe-in:
assert(_stereoMode == ToeIn && "unsupported stereo mode!");
//
// Toe-in: shift the camera position to the left/right by half the eye-distance and
// rotate a bit inwards so that the two cameras focus the same point
// at the look-at distance (focal point)
assert(0 && "getStereoViewMatrix() for TOE_IN is not implemented yet!");
return glm::mat4();
}
glm::mat4 GenericCamera::getInverseViewMatrix() const
{
if (mStereoMode == Mono)
{
return getMonoInverseViewMatrix();
}
glm::mat4 viewMatrix = getViewMatrix();
return glm::inverse(viewMatrix);
}
glm::mat4 GenericCamera::getProjectionMatrix() const
{
if (mStereoMode == Mono)
{
return getMonoProjectionMatrix();
}
else
{
// all kinds of stereo
bool eyeIsLeftEye = (getEye() == EyeLeft);
return getStereoProjectionMatrix(eyeIsLeftEye, mStereoMode);
}
}
glm::mat4 GenericCamera::getMonoProjectionMatrix() const
{
glm::mat4 projectionMatrix; // identity matrix
if (getProjectionMode() == IsometricProjection)
{
// we don't set the left/right/top/bottom values explicitly, so we want that
// all object at our focal distance appear the same in perspective and isometric view
float right = tan(glm::radians(getHorizontalFieldOfView() * 0.5f)) * mLookAtDistance;
float left = -right;
float top = tan(glm::radians(getVerticalFieldOfView() * 0.5f)) * mLookAtDistance;
float bottom = -top;
// we do the same here as a glOrtho call would do.
projectionMatrix[0][0] = 2.0f / (right - left);
projectionMatrix[1][1] = 2.0f / (top - bottom);
projectionMatrix[2][2] = -2.0f / (mFarClippingPlane - mNearClippingPlane);
projectionMatrix[0][3] = -(right + left) / (right - left);
projectionMatrix[1][3] = -(top + bottom) / (top - bottom);
projectionMatrix[2][3] = -(mFarClippingPlane + mNearClippingPlane) / (mFarClippingPlane - mNearClippingPlane);
projectionMatrix[3][3] = 1.0;
}
else if (mProjectionMode == PerspectiveProjectionOpenGL)
{
if (std::isinf(mFarClippingPlane))
{
float e = 1.0f / tan(glm::radians(getVerticalFieldOfView() * 0.5f));
const float a = getAspectRatio();
// infinite Perspective matrix reversed mapping to 1..-1
projectionMatrix = {e / a, 0.0f, 0.0f, 0.0f, 0.0f,
e, 0.0f, 0.0f, 0.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 0.0f, -2.0 * mNearClippingPlane,
0.0f};
}
else
{
projectionMatrix = glm::perspective(glm::radians((float)getHorizontalFieldOfView()), (float)getAspectRatio(),
(float)mNearClippingPlane, (float)mFarClippingPlane);
}
}
else if (mProjectionMode == PerspectiveProjectionDXReverse)
{
if (std::isinf(mFarClippingPlane))
{
float e = 1.0f / tan(glm::radians(getVerticalFieldOfView() * 0.5f));
const float a = getAspectRatio();
// infinite Perspective matrix reversed mapping to 1..0
projectionMatrix = {
e / a, 0.0f, 0.0f, 0.0f, 0.0f, e, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, mNearClippingPlane,
0.0f};
}
else
{
assert(0 && "unsupported projection mode");
}
}
else
assert(0 && "unsupported projection mode");
return projectionMatrix;
}
glm::mat4 GenericCamera::getMonoViewMatrix() const
{
glm::mat4 m(mRotationMatrix);
m[3][0] = -(m[0][0] * mPosition.x + m[1][0] * mPosition.y + m[2][0] * mPosition.z);
m[3][1] = -(m[0][1] * mPosition.x + m[1][1] * mPosition.y + m[2][1] * mPosition.z);
m[3][2] = -(m[0][2] * mPosition.x + m[1][2] * mPosition.y + m[2][2] * mPosition.z);
assert(isApproxEqual(getRotationMatrix4() * getTranslationMatrix4(), m));
return m;
}
glm::mat4 GenericCamera::getMonoInverseViewMatrix() const
{
glm::mat4 m(glm::transpose(mRotationMatrix));
m[3][0] = mPosition.x;
m[3][1] = mPosition.y;
m[3][2] = mPosition.z;
assert(isApproxEqual(glm::inverse(getViewMatrix()), m));
return m;
}
glm::mat4 GenericCamera::getStereoProjectionMatrix(bool _leftEye, StereoMode _stereoMode) const
{
assert(_stereoMode != Mono && "mono is not a stereo mode!");
if (getProjectionMode() == IsometricProjection)
{
// very unusual, prepare for headaches!
return getMonoProjectionMatrix();
}
if ((_stereoMode == ParallelShift) || (_stereoMode == ToeIn))
{
// the view matrix changes but the projection matrix stays the same
return getMonoProjectionMatrix();
}
// so off-axis it is!
assert(_stereoMode == OffAxis && "unknown projection mode!");
//
// In this mode the camera positions (view matrix) is shifted to the left/right still looking
// straight ahead. The projection is also looking ahead but the projection center is
// off (hence off-axis).
// There is one plane in front of the cameras where the view-frusta match.
// This should be the distance to the physical screen from the users position.
assert(0 && "getStereoViewMatrix() is not implemented for OFF_AXIS yet!");
return glm::mat4();
}
/// Writes all internal state to one string
/// Elements are seperated by pipes ('|'), spaces can get ignored.
std::string GenericCamera::storeStateToString() const
{
error() << "Not implemented";
return "";