diff options
Diffstat (limited to 'dev/MinGfx/src/unicam.cc')
-rw-r--r-- | dev/MinGfx/src/unicam.cc | 317 |
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 |