summaryrefslogtreecommitdiffstats
path: root/dev/MinGfx/src/unicam.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dev/MinGfx/src/unicam.cc317
1 files changed, 317 insertions, 0 deletions
diff --git a/dev/MinGfx/src/unicam.cc b/dev/MinGfx/src/unicam.cc
new file mode 100644
index 0000000..0acb9c9
--- /dev/null
+++ b/dev/MinGfx/src/unicam.cc
@@ -0,0 +1,317 @@
+/*
+ Copyright (c) 2017,2018 Regents of the University of Minnesota.
+ All Rights Reserved.
+ See corresponding header file for details.
+ */
+
+#include "unicam.h"
+
+#include "gfxmath.h"
+
+namespace mingfx {
+
+
+UniCam::UniCam() : state_(UniCamState::START), defaultDepth_(4.0), boundingSphereRad_(1.0),
+ dollyFactor_(1.0), dollyInitialized_(false), elapsedTime_(0.0), hitGeometry_(false),
+ rotAngularVel_(0.0), rotInitialized_(false), rotLastTime_(0.0), showIcon_(false)
+{
+}
+
+UniCam::UniCam(const Matrix4 &initialViewMatrix) :
+ state_(UniCamState::START), defaultDepth_(4.0), V_(initialViewMatrix), boundingSphereRad_(1.0),
+ dollyFactor_(1.0), dollyInitialized_(false), elapsedTime_(0.0), hitGeometry_(false),
+ rotAngularVel_(0.0), rotInitialized_(false), rotLastTime_(0.0), showIcon_(false)
+{
+}
+
+UniCam::~UniCam()
+{
+}
+
+
+void UniCam::recalc_angular_vel() {
+ // update angular velocity
+ float cutoff = (float)elapsedTime_ - 0.2f; // look just at the last 0.2 secs
+ while ((rotAngularVelBuffer_.size()) && (rotAngularVelBuffer_[0].first < cutoff)) {
+ rotAngularVelBuffer_.erase(rotAngularVelBuffer_.begin());
+ }
+ rotAngularVel_ = 0.0;
+ if (rotAngularVelBuffer_.size()) {
+ for (int i=0; i<rotAngularVelBuffer_.size(); i++) {
+ rotAngularVel_ += rotAngularVelBuffer_[i].second;
+ }
+ rotAngularVel_ /= rotAngularVelBuffer_.size();
+ }
+ //std::cout << rotAngularVelBuffer_.size() << " " << rotAngularVel_ << std::endl;
+}
+
+
+void UniCam::OnButtonDown(const Point2 &mousePos, float mouseZ) {
+ if (state_ == UniCamState::START) {
+ initialClickPos_ = mousePos;
+ mouseLast_ = mousePos;
+ elapsedTime_ = 0.0;
+ rotInitialized_ = false;
+ dollyInitialized_ = false;
+
+ hitGeometry_ = (mouseZ < 1.0);
+ if (hitGeometry_) {
+ hitPoint_ = GfxMath::ScreenToWorld(V_, Pdraw_, mousePos, mouseZ);
+ }
+ else {
+ hitPoint_ = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0,0), defaultDepth_);
+ }
+ showIcon_ = true;
+ state_ = UniCamState::PAN_DOLLY_ROT_DECISION;
+ }
+ else if (state_ == UniCamState::ROT_WAIT_FOR_SECOND_CLICK) {
+ // we have the second click now, and we will start the trackball rotate interaction
+ state_ = UniCamState::ROT;
+ }
+ else if (state_ == UniCamState::SPINNING) {
+ // this click is to "catch" the model, stopping it from spinning.
+ state_ = UniCamState::START;
+ }
+ else {
+ std::cerr << "UniCam::OnButtonDown() unexpected state." << std::endl;
+ }
+}
+
+void UniCam::OnDrag(const Point2 &mousePos) {
+ if (state_ == UniCamState::PAN_DOLLY_ROT_DECISION) {
+ const double panMovementThreshold = 0.01;
+ const double dollyMovementThreshold = 0.01;
+ if (fabs(mousePos[0] - initialClickPos_[0]) > panMovementThreshold) {
+ // already lots of horizontal movement, we can go right to pan
+ state_ = UniCamState::PAN;
+ showIcon_ = false;
+ }
+ else if (fabs(mousePos[1] - initialClickPos_[1]) > dollyMovementThreshold) {
+ // already lots of vertical movement, we can go right to dolly
+ state_ = UniCamState::DOLLY;
+ showIcon_ = false;
+ }
+ else if (elapsedTime_ > 1.0) {
+ // timeout, this was not a quick click to set a center of rotation,
+ // so there is no intent to rotate. instead we will be doing either
+ // pan or dolly.
+ state_ = UniCamState::PAN_DOLLY_DECISION;
+ showIcon_ = false;
+ }
+ }
+ else if (state_ == UniCamState::PAN_DOLLY_DECISION) {
+ const double panMovementThreshold = 0.01;
+ const double dollyMovementThreshold = 0.01;
+ if (fabs(mousePos[0] - initialClickPos_[0]) > panMovementThreshold) {
+ // lots of horizontal movement, go to pan
+ state_ = UniCamState::PAN;
+ }
+ else if (fabs(mousePos[1] - initialClickPos_[1]) > dollyMovementThreshold) {
+ // lots of vertical movement, go to dolly
+ state_ = UniCamState::DOLLY;
+ }
+ }
+ else if (state_ == UniCamState::PAN) {
+ Matrix4 camMat = V_.Inverse();
+ Point3 eye = camMat.ColumnToPoint3(3);
+ Vector3 look = -camMat.ColumnToVector3(2);
+ float depth = (hitPoint_ - eye).Dot(look);
+ Point3 pWorld1 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, mouseLast_, depth);
+ Point3 pWorld2 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, mousePos, depth);
+ V_ = V_ * Matrix4::Translation(pWorld2 - pWorld1);
+ }
+ else if (state_ == UniCamState::DOLLY) {
+ if (!dollyInitialized_) {
+ // Setup dollyFactor so that if you move the mouse to the bottom of the screen, the point
+ // you clicked on will be right on top of the camera.
+ Matrix4 camMat = V_.Inverse();
+ Point3 eye = camMat.ColumnToPoint3(3);
+ Vector3 look = -camMat.ColumnToVector3(2);
+ float depth = (hitPoint_ - eye).Dot(look);
+ float deltaYToBottom = initialClickPos_[1] + 1;
+ dollyFactor_ = depth / deltaYToBottom;
+ dollyInitialized_ = true;
+ }
+ Vector3 d(0, 0, -dollyFactor_ * (mousePos[1] - mouseLast_[1]));
+ V_ = Matrix4::Translation(d) * V_ ;
+ }
+ else if (state_ == UniCamState::ROT) {
+ if (!rotInitialized_) {
+ float depth = 0.0;
+ if (hitGeometry_) {
+ // if we hit some geometry, then make that the center of rotation
+ boundingSphereCtr_ = hitPoint_;
+ Matrix4 camMat = V_.Inverse();
+ Point3 eye = camMat.ColumnToPoint3(3);
+ Vector3 look = -camMat.ColumnToVector3(2);
+ depth = (hitPoint_ - eye).Dot(look);
+ }
+ else {
+ // if we did not hit any geometry, then center the bounding sphere in front of
+ // the camera at a distance that can be configured by the user.
+ boundingSphereCtr_ = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0,0), defaultDepth_);
+ depth = defaultDepth_;
+ }
+
+ // determine the size of the bounding sphere by projecting a screen-space
+ // distance of 0.75 units to the depth of the sphere center
+ Point3 pWorld1 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0,0), depth);
+ Point3 pWorld2 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0.75,0), depth);
+ boundingSphereRad_ = (pWorld2-pWorld1).Length();
+
+ rotLastTime_ = elapsedTime_;
+ rotAngularVelBuffer_.clear();
+ rotInitialized_ = true;
+ }
+ else {
+ // Do a trackball rotation based on the mouse movement and the bounding sphere
+ // setup earlier.
+
+ Matrix4 camMat = V_.Inverse();
+ Point3 eye = camMat.ColumnToPoint3(3);
+
+ // last mouse pos
+ bool hit1 = false;
+ Point3 mouse3D1 = GfxMath::ScreenToNearPlane(V_, Pdraw_, mouseLast_);
+ Ray ray1(eye, mouse3D1 - eye);
+ float t1;
+ Point3 iPoint1;
+ if (ray1.IntersectSphere(boundingSphereCtr_, boundingSphereRad_, &t1, &iPoint1)) {
+ hit1 = true;
+ }
+
+ // current mouse pos
+ bool hit2 = false;
+ Point3 mouse3D2 = GfxMath::ScreenToNearPlane(V_, Pdraw_, mousePos);
+ Ray ray2(eye, mouse3D2 - eye);
+ float t2;
+ Point3 iPoint2;
+ if (ray2.IntersectSphere(boundingSphereCtr_, boundingSphereRad_, &t2, &iPoint2)) {
+ hit2 = true;
+ }
+ rotLastIPoint_ = iPoint2;
+
+ if (hit1 && hit2) {
+ Vector3 v1 = (iPoint1 - boundingSphereCtr_).ToUnit();
+ Vector3 v2 = (iPoint2 - boundingSphereCtr_).ToUnit();
+
+ rotAxis_ = v1.Cross(v2).ToUnit();
+ float angle = std::acos(v1.Dot(v2));
+
+ if (std::isfinite(angle)) {
+ Matrix4 R = Matrix4::Rotation(boundingSphereCtr_, rotAxis_, angle);
+ R = R.Orthonormal();
+ V_ = V_ * R;
+ //V_ = V_.orthonormal();
+
+ // add a sample to the angular vel vector
+ double dt = elapsedTime_ - rotLastTime_;
+ double avel = angle / dt;
+ if (std::isfinite(avel)) {
+ rotAngularVelBuffer_.push_back(std::make_pair(elapsedTime_, avel));
+ }
+ rotLastTime_ = elapsedTime_;
+ }
+ }
+
+ recalc_angular_vel();
+ }
+ }
+ else if (state_ == UniCamState::START) {
+ // picked up a little mouse movement after "catching" a spinning model
+ // nothing to do, just wait for the button up.
+ }
+ else {
+ std::cerr << "UniCam::OnDrag() unexpected state." << std::endl;
+ }
+ mouseLast_ = mousePos;
+}
+
+void UniCam::OnButtonUp(const Point2 &mousePos) {
+ if (state_ == UniCamState::PAN_DOLLY_ROT_DECISION) {
+ // here, we got a quick click of the mouse to indicate a center of rotation
+ // so we now go into a mode of waiting for a second click to start rotating
+ // around that point.
+ state_ = UniCamState::ROT_WAIT_FOR_SECOND_CLICK;
+ }
+ else if (state_ == UniCamState::ROT) {
+ showIcon_ = false;
+ // if we are leaving the rotation state and the angular velocity is
+ // greater than some thresold, then the user has "thrown" the model
+ // keep rotating the same way by entering the spinning state.
+
+ recalc_angular_vel();
+ //std::cout << "check for spin: " << n-start << " " << rotAngularVel_ << " " << avel2 << std::endl;
+
+ const float threshold = 0.2f;
+ if (std::fabs(rotAngularVel_) > threshold) {
+ state_ = UniCamState::SPINNING;
+ }
+ else {
+ state_ = UniCamState::START;
+ }
+ }
+ else {
+ showIcon_ = false;
+ // all other cases go back to the start state
+ state_ = UniCamState::START;
+ }
+}
+
+void UniCam::AdvanceAnimation(double dt) {
+ elapsedTime_ += dt;
+
+ if (state_ == UniCamState::SPINNING) {
+ double deltaT = elapsedTime_ - rotLastTime_;
+ rotLastTime_ = elapsedTime_;
+ double angle = (double)rotAngularVel_ * deltaT;
+ Matrix4 R = Matrix4::Rotation(boundingSphereCtr_, rotAxis_, (float)angle);
+ //R = R.orthonormal();
+ V_ = V_ * R;
+ }
+}
+
+
+void UniCam::Draw(const Matrix4 &projectionMatrix) {
+ Pdraw_ = projectionMatrix;
+
+ if (showIcon_) {
+ Matrix4 camMat = V_.Inverse();
+ Point3 eye = camMat.ColumnToPoint3(3);
+ Vector3 look = -camMat.ColumnToVector3(2);
+ float depth = (hitPoint_ - eye).Dot(look);
+ Point3 pWorld1 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0.f,0.f), depth);
+ Point3 pWorld2 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0.015f,0.f), depth);
+ float rad = (pWorld2 - pWorld1).Length();
+ Matrix4 M = Matrix4::Translation(hitPoint_ - Point3::Origin()) * Matrix4::Scale(Vector3(rad, rad, rad));
+ quickShapes_.DrawSphere(M, V_, Pdraw_, Color(0,0,0));
+ }
+}
+
+
+Matrix4 UniCam::view_matrix() {
+ return V_;
+}
+
+void UniCam::set_view_matrix(Matrix4 viewMatrix) {
+ V_ = viewMatrix;
+}
+
+void UniCam::set_default_depth(float d) {
+ defaultDepth_ = d;
+}
+
+Point3 UniCam::eye() {
+ Matrix4 camMat = V_.Inverse();
+ return camMat.ColumnToPoint3(3);
+}
+
+Vector3 UniCam::look() {
+ Matrix4 camMat = V_.Inverse();
+ return -camMat.ColumnToVector3(2);
+}
+
+
+
+
+} // end namespace