#include "ofEasyCam.h"
#include "ofMath.h"
#include "ofUtils.h"
#include "ofGraphicsBaseTypes.h"
#include <limits>
#include "glm/gtx/vector_angle.hpp"
using namespace std;
static const float minDifference = 0.1e-5f;
static const unsigned long doubleclickTime = 200;
ofEasyCam::ofEasyCam(){
reset();
sensitivityTranslate = {1,1,1};
sensitivityRot = {1,1,1};
addInteraction(TRANSFORM_TRANSLATE_XY, OF_MOUSE_BUTTON_LEFT,doTranslationKey);
addInteraction(TRANSFORM_ROTATE, OF_MOUSE_BUTTON_LEFT);
addInteraction(TRANSFORM_TRANSLATE_Z, OF_MOUSE_BUTTON_RIGHT);
addInteraction(TRANSFORM_TRANSLATE_XY, OF_MOUSE_BUTTON_MIDDLE);
}
void ofEasyCam::update(ofEventArgs & args){
if(this->viewport.isZero()){
viewport = getViewport();
}
if(!bDistanceSet && bAutoDistance){
setDistance(getImagePlaneDistance(viewport), true);
}
if(bMouseInputEnabled && events){
if(events->getMousePressed()) {
updateMouse(glm::vec2(events->getMouseX(),events->getMouseY()));
}
if (currentTransformType == TRANSFORM_ROTATE) {
updateRotation();
}else if (currentTransformType == TRANSFORM_TRANSLATE_XY ||
currentTransformType == TRANSFORM_TRANSLATE_Z ||
currentTransformType == TRANSFORM_SCALE) {
updateTranslation();
}
}
}
void ofEasyCam::begin(const ofRectangle & _viewport){
if(!bEventsSet){
setEvents(ofEvents());
}
viewport = _viewport;
ofCamera::begin(viewport);
}
void ofEasyCam::reset(){
target.resetTransform();
target.setPosition(0, 0, 0);
lookAt(target);
resetTransform();
setPosition(0, 0, lastDistance);
rot = {0,0,0};
translate = {0,0,0};
if(bAutoDistance){
bDistanceSet = false;
}
bApplyInertia = false;
currentTransformType = TRANSFORM_NONE;
}
void ofEasyCam::setTarget(const glm::vec3& targetPoint){
target.setPosition(targetPoint);
lookAt(target);
}
void ofEasyCam::setTarget(ofNode& targetNode){
target = targetNode;
lookAt(target);
}
const ofNode& ofEasyCam::getTarget() const{
return target;
}
void ofEasyCam::setDistance(float distance){
setDistance(distance, true);
}
void ofEasyCam::setDistance(float distance, bool save){
if(distance > 0.0f){
if(save){
this->lastDistance = distance;
}
setPosition(target.getPosition() + (distance * getZAxis()));
bDistanceSet = true;
}
}
float ofEasyCam::getDistance() const{
return glm::distance(target.getPosition(), getPosition());
}
void ofEasyCam::setAutoDistance(bool bAutoDistance){
this->bAutoDistance = bAutoDistance;
if(bAutoDistance){
bDistanceSet = false;
}
}
void ofEasyCam::setDrag(float drag){
this->drag = drag;
}
float ofEasyCam::getDrag() const{
return drag;
}
void ofEasyCam::setTranslationKey(char key){
doTranslationKey = key;
}
char ofEasyCam::getTranslationKey() const{
return doTranslationKey;
}
void ofEasyCam::enableMouseInput(){
if(!bMouseInputEnabled && events){
listeners.push(events->update.newListener(this, &ofEasyCam::update));
listeners.push(events->mousePressed.newListener(this, &ofEasyCam::mousePressed));
listeners.push(events->mouseReleased.newListener(this, &ofEasyCam::mouseReleased));
listeners.push(events->mouseScrolled.newListener(this, &ofEasyCam::mouseScrolled));
}
bMouseInputEnabled = true;
}
void ofEasyCam::disableMouseInput(){
if(bMouseInputEnabled && events){
listeners.unsubscribeAll();
}
bMouseInputEnabled = false;
}
bool ofEasyCam::getMouseInputEnabled() const{
return bMouseInputEnabled;
}
void ofEasyCam::setEvents(ofCoreEvents & _events){
bool wasMouseInputEnabled = bMouseInputEnabled;
disableMouseInput();
events = &_events;
if (wasMouseInputEnabled) {
enableMouseInput();
}
bEventsSet = true;
}
void ofEasyCam::setRotationSensitivity(const glm::vec3& sensitivity){
sensitivityRot = sensitivity;
}
void ofEasyCam::setRotationSensitivity(float x, float y, float z){
setRotationSensitivity({x,y,z});
}
void ofEasyCam::setTranslationSensitivity(const glm::vec3& sensitivity){
sensitivityTranslate = sensitivity;
}
void ofEasyCam::setTranslationSensitivity(float x, float y, float z){
sensitivityTranslate = {x,y,z};
}
void ofEasyCam::enableMouseMiddleButton(){
bEnableMouseMiddleButton = true;
}
void ofEasyCam::disableMouseMiddleButton(){
bEnableMouseMiddleButton = false;
}
bool ofEasyCam::getMouseMiddleButtonEnabled() const{
return bEnableMouseMiddleButton;
}
glm::vec3 ofEasyCam::up() const{
if(bRelativeYAxis){
if(bApplyInertia){
return getYAxis();
}else{
return lastPressAxisY;
}
}else{
return upAxis;
}
}
void ofEasyCam::setRelativeYAxis(bool relative){
bRelativeYAxis = relative;
}
bool ofEasyCam::getRelativeYAxis() const{
return bRelativeYAxis;
}
void ofEasyCam::setUpAxis(const glm::vec3 & _up){
upAxis = _up;
}
const glm::vec3 & ofEasyCam::getUpAxis() const{
return upAxis;
}
void ofEasyCam::enableInertia(){
doInertia = true;
}
void ofEasyCam::disableInertia(){
doInertia = false;
}
bool ofEasyCam::getInertiaEnabled() const{
return doInertia;
}
void ofEasyCam::updateTranslation(){
if(bApplyInertia){
translate *= drag;
if(std::abs(translate.x) <= minDifference && std::abs(translate.y) <= minDifference && std::abs(translate.z) <= minDifference){
translate = {0,0,0};
bApplyInertia = false;
currentTransformType = TRANSFORM_NONE;
bIsScrolling = false;
return;
}
move((getXAxis() * translate.x) + (getYAxis() * translate.y) + (getZAxis() * translate.z));
}
if(currentTransformType == TRANSFORM_TRANSLATE_XY ||
currentTransformType == TRANSFORM_TRANSLATE_Z ||
currentTransformType == TRANSFORM_SCALE) {
if(getOrtho()){
glm::vec3 mousePre ;
bool bDoScale = (currentTransformType == TRANSFORM_SCALE || currentTransformType == TRANSFORM_TRANSLATE_Z);
if (bDoScale) {
mousePre = screenToWorld(glm::vec3((bIsScrolling?mouseAtScroll:lastPressMouse),0));
}
move(glm::vec3(lastPressAxisX * translate.x) + (lastPressAxisY * translate.y));
if (bDoScale) {
setScale(getScale() + translate.z);
move(mousePre - screenToWorld(glm::vec3((bIsScrolling?mouseAtScroll:lastPressMouse),0)));
}
}else{
move(glm::vec3(lastPressAxisX * translate.x) + (lastPressAxisY * translate.y) + (lastPressAxisZ * translate.z));
}
}
if (bIsScrolling) {
currentTransformType = TRANSFORM_NONE;
bIsScrolling = false;
}
}
void ofEasyCam::updateRotation(){
if(bApplyInertia){
rot *=drag;
if(std::abs(rot.x) <= minDifference && std::abs(rot.y) <= minDifference && std::abs(rot.z) <= minDifference){
rot = {0,0,0};
bApplyInertia = false;
currentTransformType = TRANSFORM_NONE;
return;
}
}
if (bApplyInertia) {
curRot = glm::angleAxis(rot.z, getZAxis()) * glm::angleAxis(rot.y, up()) * glm::angleAxis(rot.x, getXAxis());
rotateAround(curRot, target.getGlobalPosition());
rotate(curRot);
}else{
curRot = glm::angleAxis(rot.z, lastPressAxisZ) * glm::angleAxis(rot.y, up()) * glm::angleAxis(rot.x, lastPressAxisX);
setOrientation(curRot * lastPressOrientation);
setPosition(curRot * (lastPressPosition-target.getGlobalPosition()) + target.getGlobalPosition());
}
}
void ofEasyCam::setControlArea(const ofRectangle & _controlArea) {
controlArea = _controlArea;
}
void ofEasyCam::clearControlArea() {
controlArea = ofRectangle();
}
ofRectangle ofEasyCam::getControlArea() const {
if (controlArea.isZero()) {
if (viewport.isZero()) {
return getRenderer()->getCurrentViewport();
}
return viewport;
}
return controlArea;
}
void ofEasyCam::mousePressed(ofMouseEventArgs & mouse){
ofRectangle area = getControlArea();
if(area.inside(mouse.x, mouse.y)){
lastPressMouse = mouse;
prevMouse = mouse;
lastPressAxisX = getXAxis();
lastPressAxisY = getYAxis();
lastPressAxisZ = getZAxis();
lastPressPosition = ofCamera::getGlobalPosition();
lastPressOrientation = ofCamera::getGlobalOrientation();
currentTransformType = TRANSFORM_NONE;
if (events) {
for (const auto& i: interactions) {
if (i.mouseButton == mouse.button && ((i.key == -1) || events->getKeyPressed(i.key)) ) {
currentTransformType = i.transformType;
break;
}
}
}
if(currentTransformType == TRANSFORM_ROTATE){
bInsideArcball = glm::length(mouse - glm::vec2(area.getCenter())) < std::min(area.width/2, area.height/2);
}
bApplyInertia = false;
}
}
void ofEasyCam::mouseReleased(ofMouseEventArgs & mouse){
ofRectangle area = getControlArea();
if(area.inside(mouse)){
unsigned long curTap = ofGetElapsedTimeMillis();
if(lastTap != 0 && curTap - lastTap < doubleclickTime){
reset();
return;
}
lastTap = curTap;
}
if(doInertia){
bApplyInertia = true;
}else{
currentTransformType = TRANSFORM_NONE;
rot = {0,0,0};
translate = {0,0,0};
}
}
void ofEasyCam::mouseScrolled(ofMouseEventArgs & mouse){
ofRectangle area = getControlArea();
if(area.inside(mouse)){
mouseVel = mouse - prevMouse;
prevMouse = mouse;
if (doInertia) {
bApplyInertia = true;
}
lastPressPosition = ofCamera::getGlobalPosition();
lastPressAxisZ = getZAxis();
if (getOrtho()) {
translate.z = sensitivityScroll * mouse.scrollY / viewport.height;
mouseAtScroll = mouse;
}else{
translate.z = mouse.scrollY * 30 * sensitivityTranslate.z * (getDistance() + std::numeric_limits<float>::epsilon())/ area.height;
}
currentTransformType = TRANSFORM_SCALE;
bIsScrolling = true;
}
}
void ofEasyCam::updateMouse(const glm::vec2 & mouse){
ofRectangle area = getControlArea();
int vFlip =(isVFlipped()?-1:1);
rot = {0,0,0};
translate = {0,0,0};
switch (currentTransformType) {
case TRANSFORM_ROTATE:
mouseVel = mouse - lastPressMouse;
if(bInsideArcball){
rot.x = vFlip * -mouseVel.y * sensitivityRot.x * glm::pi<float>() / std::min(area.width, area.height);
rot.y = -mouseVel.x * sensitivityRot.y * glm::pi<float>() / std::min(area.width, area.height);
}else{
glm::vec2 center(area.getCenter());
rot.z = sensitivityRot.z * -vFlip * glm::orientedAngle(glm::normalize(mouse - center),
glm::normalize(lastPressMouse - center));
}
break;
case TRANSFORM_TRANSLATE_XY:
mouseVel = mouse - prevMouse;
if (getOrtho()) {
translate.x = -mouseVel.x * getScale().z;
translate.y = vFlip * mouseVel.y * getScale().z;
}else{
translate.x = -mouseVel.x * sensitivityTranslate.x * 0.5f * (getDistance() + std::numeric_limits<float>::epsilon())/ area.width;
translate.y = vFlip * mouseVel.y * sensitivityTranslate.y* 0.5f * (getDistance() + std::numeric_limits<float>::epsilon())/ area.height;
}
break;
case TRANSFORM_TRANSLATE_Z:
mouseVel = mouse - prevMouse;
if (getOrtho()) {
translate.z = mouseVel.y * sensitivityScroll / area.height;
}else{
translate.z = mouseVel.y * (sensitivityTranslate.z * 0.7f) * (getDistance() + std::numeric_limits<float>::epsilon())/ area.height;
}
break;
default:
break;
}
prevMouse = mouse;
}
void ofEasyCam::addInteraction(TransformType type, int mouseButton, int key){
if(!hasInteraction(mouseButton, key)){
interactions.push_back(interaction(type, mouseButton, key));
}else{
ofLogNotice("ofEasyCam") << "Can not add interaction. It already exists";
}
}
void ofEasyCam::removeInteraction(TransformType type, int mouseButton, int key){
ofRemove(interactions, [&](interaction & i){ return i.transformType == type && i.mouseButton == mouseButton && i.key ==key;});
}
bool ofEasyCam:: hasInteraction(int mouseButton, int key){
for(const auto& i : interactions){
if(i.mouseButton == mouseButton && i.key == key){
return true;
}
}
return false;
}
bool ofEasyCam:: hasInteraction(TransformType type, int mouseButton, int key){
for(const auto& i : interactions){
if(i.transformType == type && i.mouseButton == mouseButton && i.key == key){
return true;
}
}
return false;
}
void ofEasyCam::removeAllInteractions(){
interactions.clear();
}
void ofEasyCam::onPositionChanged(){
if(!bDistanceSet && bAutoDistance){
bDistanceSet = true;
}
}
Comments