framework2
518 строк · 15.3 Кб
1#include "ofEasyCam.h"
2#include "ofMath.h"
3#include "ofUtils.h"
4#include "ofGraphicsBaseTypes.h"
5
6#define GLM_FORCE_CTOR_INIT
7#include "glm/gtx/vector_angle.hpp"
8#include <limits>
9
10// when an ofEasyCam is moving due to momentum, this keeps it
11// from moving forever by assuming small values are zero.
12static const float minDifference = 0.1e-5f;
13
14// this is the default on windows os
15static const unsigned long doubleclickTime = 200;
16
17//----------------------------------------
18ofEasyCam::ofEasyCam(){
19reset();
20sensitivityTranslate = {1,1,1};
21sensitivityRot = {1,1,1};
22
23addInteraction(TRANSFORM_TRANSLATE_XY, OF_MOUSE_BUTTON_LEFT,doTranslationKey);
24addInteraction(TRANSFORM_ROTATE, OF_MOUSE_BUTTON_LEFT);
25addInteraction(TRANSFORM_TRANSLATE_Z, OF_MOUSE_BUTTON_RIGHT);
26addInteraction(TRANSFORM_TRANSLATE_XY, OF_MOUSE_BUTTON_MIDDLE);
27
28}
29//----------------------------------------
30void ofEasyCam::update(ofEventArgs & args){
31if(this->viewport.isZero()){
32viewport = getViewport();
33}
34if(!bDistanceSet && bAutoDistance){
35setDistance(getImagePlaneDistance(viewport), true);
36}
37if(bMouseInputEnabled && events){
38if(events->getMousePressed()) {
39updateMouse(glm::vec2(events->getMouseX(),events->getMouseY()));
40}
41if (currentTransformType == TRANSFORM_ROTATE) {
42updateRotation();
43}else if (currentTransformType == TRANSFORM_TRANSLATE_XY ||
44currentTransformType == TRANSFORM_TRANSLATE_Z ||
45currentTransformType == TRANSFORM_SCALE) {
46updateTranslation();
47}
48}
49}
50
51//----------------------------------------
52void ofEasyCam::begin(const ofRectangle & _viewport){
53if(!bEventsSet){
54setEvents(ofEvents());
55}
56viewport = _viewport;
57ofCamera::begin(viewport);
58}
59
60//----------------------------------------
61void ofEasyCam::reset(){
62target.resetTransform();
63
64target.setPosition(0, 0, 0);
65lookAt(target);
66
67resetTransform();
68setPosition(0, 0, lastDistance);
69
70rot = {0,0,0};
71translate = {0,0,0};
72
73if(bAutoDistance){
74bDistanceSet = false;
75}
76bApplyInertia = false;
77currentTransformType = TRANSFORM_NONE;
78}
79
80//----------------------------------------
81void ofEasyCam::setTarget(const glm::vec3& targetPoint){
82target.setPosition(targetPoint);
83lookAt(target);
84}
85
86//----------------------------------------
87void ofEasyCam::setTarget(ofNode& targetNode){
88target = targetNode;
89lookAt(target);
90}
91
92//----------------------------------------
93const ofNode& ofEasyCam::getTarget() const{
94return target;
95}
96
97//----------------------------------------
98void ofEasyCam::setDistance(float distance){
99setDistance(distance, true);
100}
101
102//----------------------------------------
103void ofEasyCam::setDistance(float distance, bool save){//should this be the distance from the camera to the target?
104if(distance > 0.0f){
105if(save){
106this->lastDistance = distance;
107}
108setPosition(target.getPosition() + (distance * getZAxis()));
109bDistanceSet = true;
110}
111}
112
113//----------------------------------------
114float ofEasyCam::getDistance() const{
115return glm::distance(target.getPosition(), getPosition());
116}
117
118//----------------------------------------
119void ofEasyCam::setAutoDistance(bool bAutoDistance){
120this->bAutoDistance = bAutoDistance;
121if(bAutoDistance){
122bDistanceSet = false;
123}
124}
125
126//----------------------------------------
127void ofEasyCam::setDrag(float drag){
128this->drag = drag;
129}
130
131//----------------------------------------
132float ofEasyCam::getDrag() const{
133return drag;
134}
135
136//----------------------------------------
137void ofEasyCam::setTranslationKey(char key){
138doTranslationKey = key;
139}
140
141//----------------------------------------
142char ofEasyCam::getTranslationKey() const{
143return doTranslationKey;
144}
145
146//----------------------------------------
147void ofEasyCam::enableMouseInput(){
148if(!bMouseInputEnabled && events){
149listeners.push(events->update.newListener(this, &ofEasyCam::update));
150listeners.push(events->mousePressed.newListener(this, &ofEasyCam::mousePressed));
151listeners.push(events->mouseReleased.newListener(this, &ofEasyCam::mouseReleased));
152listeners.push(events->mouseScrolled.newListener(this, &ofEasyCam::mouseScrolled));
153}
154// if enableMouseInput was called within ofApp::setup()
155// `events` will still carry a null pointer, and bad things
156// will happen. Therefore we only update the flag.
157bMouseInputEnabled = true;
158// setEvents() is called upon first load, and will make sure
159// to enable the mouse input once the camera is fully loaded.
160}
161
162//----------------------------------------
163void ofEasyCam::disableMouseInput(){
164if(bMouseInputEnabled && events){
165listeners.unsubscribeAll();
166}
167// if disableMouseInput was called within ofApp::setup()
168// `events` will still carry a null pointer, and bad things
169// will happen. Therefore we only update the flag.
170bMouseInputEnabled = false;
171// setEvents() is called upon first load, and will make sure
172// to enable the mouse input once the camera is fully loaded.
173}
174//----------------------------------------
175bool ofEasyCam::getMouseInputEnabled() const{
176return bMouseInputEnabled;
177}
178
179//----------------------------------------
180void ofEasyCam::setEvents(ofCoreEvents & _events){
181// If en/disableMouseInput were called within ofApp::setup(),
182// bMouseInputEnabled will tell us about whether the camera
183// mouse input needs to be initialised as enabled or disabled.
184// we will still set `events`, so that subsequent enabling
185// and disabling can work.
186
187// we need a temporary copy of bMouseInputEnabled, since it will
188// get changed by disableMouseInput as a side-effect.
189bool wasMouseInputEnabled = bMouseInputEnabled;// || !events;
190disableMouseInput();
191events = &_events;
192if (wasMouseInputEnabled) {
193// note: this will set bMouseInputEnabled to true as a side-effect.
194enableMouseInput();
195}
196bEventsSet = true;
197}
198
199//----------------------------------------
200void ofEasyCam::setRotationSensitivity(const glm::vec3& sensitivity){
201sensitivityRot = sensitivity;
202}
203//----------------------------------------
204void ofEasyCam::setRotationSensitivity(float x, float y, float z){
205setRotationSensitivity({x,y,z});
206}
207//----------------------------------------
208void ofEasyCam::setTranslationSensitivity(const glm::vec3& sensitivity){
209sensitivityTranslate = sensitivity;
210}
211//----------------------------------------
212void ofEasyCam::setTranslationSensitivity(float x, float y, float z){
213sensitivityTranslate = {x,y,z};
214}
215
216//----------------------------------------
217void ofEasyCam::enableMouseMiddleButton(){
218bEnableMouseMiddleButton = true;
219}
220
221//----------------------------------------
222void ofEasyCam::disableMouseMiddleButton(){
223bEnableMouseMiddleButton = false;
224}
225
226//----------------------------------------
227bool ofEasyCam::getMouseMiddleButtonEnabled() const{
228return bEnableMouseMiddleButton;
229}
230
231//----------------------------------------
232glm::vec3 ofEasyCam::up() const{
233if(bRelativeYAxis){
234if(bApplyInertia){
235return getYAxis();
236}else{
237return lastPressAxisY;
238}
239}else{
240return upAxis;
241}
242}
243
244//----------------------------------------
245void ofEasyCam::setRelativeYAxis(bool relative){
246bRelativeYAxis = relative;
247}
248
249//----------------------------------------
250bool ofEasyCam::getRelativeYAxis() const{
251return bRelativeYAxis;
252}
253
254//----------------------------------------
255void ofEasyCam::setUpAxis(const glm::vec3 & _up){
256upAxis = _up;
257}
258
259//----------------------------------------
260const glm::vec3 & ofEasyCam::getUpAxis() const{
261return upAxis;
262}
263
264//----------------------------------------
265void ofEasyCam::enableInertia(){
266doInertia = true;
267}
268
269//----------------------------------------
270void ofEasyCam::disableInertia(){
271doInertia = false;
272}
273
274//----------------------------------------
275bool ofEasyCam::getInertiaEnabled() const{
276return doInertia;
277}
278
279//----------------------------------------
280void ofEasyCam::updateTranslation(){
281if(bApplyInertia){
282translate *= drag;
283if(std::abs(translate.x) <= minDifference && std::abs(translate.y) <= minDifference && std::abs(translate.z) <= minDifference){
284translate = {0,0,0};
285bApplyInertia = false;
286currentTransformType = TRANSFORM_NONE;
287
288bIsScrolling = false;
289return;
290}
291move((getXAxis() * translate.x) + (getYAxis() * translate.y) + (getZAxis() * translate.z));
292}
293if(currentTransformType == TRANSFORM_TRANSLATE_XY ||
294currentTransformType == TRANSFORM_TRANSLATE_Z ||
295currentTransformType == TRANSFORM_SCALE) {
296if(getOrtho()){
297//In ortho mode moving along the z axis has no effect besides clipping.
298// Instead, scale is applied to achieve the effect of getting near or far from the target.
299glm::vec3 mousePre ;
300bool bDoScale = (currentTransformType == TRANSFORM_SCALE || currentTransformType == TRANSFORM_TRANSLATE_Z);
301if (bDoScale) {
302mousePre = screenToWorld(glm::vec3((bIsScrolling?mouseAtScroll:lastPressMouse),0));
303}
304move(glm::vec3(lastPressAxisX * translate.x) + (lastPressAxisY * translate.y));
305if (bDoScale) {
306setScale(getScale() + translate.z);
307// this move call is to keep the scaling centered below the mouse.
308move(mousePre - screenToWorld(glm::vec3((bIsScrolling?mouseAtScroll:lastPressMouse),0)));
309}
310}else{
311move(glm::vec3(lastPressAxisX * translate.x) + (lastPressAxisY * translate.y) + (lastPressAxisZ * translate.z));
312}
313}
314if (bIsScrolling) {
315//this it to avoid the transformation to keep on after scrolling ended.
316currentTransformType = TRANSFORM_NONE;
317bIsScrolling = false;
318}
319}
320
321//----------------------------------------
322void ofEasyCam::updateRotation(){
323if(bApplyInertia){
324rot *=drag;
325if(std::abs(rot.x) <= minDifference && std::abs(rot.y) <= minDifference && std::abs(rot.z) <= minDifference){
326rot = {0,0,0};
327bApplyInertia = false;
328currentTransformType = TRANSFORM_NONE;
329return;
330}
331
332}
333if (bApplyInertia) {
334curRot = glm::angleAxis(rot.z, getZAxis()) * glm::angleAxis(rot.y, up()) * glm::angleAxis(rot.x, getXAxis());
335rotateAround(curRot, target.getGlobalPosition());
336rotate(curRot);
337}else{
338curRot = glm::angleAxis(rot.z, lastPressAxisZ) * glm::angleAxis(rot.y, up()) * glm::angleAxis(rot.x, lastPressAxisX);
339setOrientation(curRot * lastPressOrientation);
340setPosition(curRot * (lastPressPosition-target.getGlobalPosition()) + target.getGlobalPosition());
341}
342}
343
344//----------------------------------------
345void ofEasyCam::setControlArea(const ofRectangle & _controlArea) {
346controlArea = _controlArea;
347}
348
349//----------------------------------------
350void ofEasyCam::clearControlArea() {
351controlArea = ofRectangle();
352}
353
354//----------------------------------------
355ofRectangle ofEasyCam::getControlArea() const {
356if (controlArea.isZero()) {
357if (viewport.isZero()) {
358return getRenderer()->getCurrentViewport();
359}
360return viewport;
361}
362return controlArea;
363}
364
365//----------------------------------------
366void ofEasyCam::mousePressed(ofMouseEventArgs & mouse){
367ofRectangle area = getControlArea();
368if(area.inside(mouse.x, mouse.y)){
369lastPressMouse = mouse;
370prevMouse = mouse;
371lastPressAxisX = getXAxis();
372lastPressAxisY = getYAxis();
373lastPressAxisZ = getZAxis();
374lastPressPosition = ofCamera::getGlobalPosition();
375lastPressOrientation = ofCamera::getGlobalOrientation();
376
377currentTransformType = TRANSFORM_NONE;
378if (events) {
379for (const auto& i: interactions) {
380if (i.mouseButton == mouse.button && ((i.key == -1) || events->getKeyPressed(i.key)) ) {
381currentTransformType = i.transformType;
382break;
383}
384}
385}
386if(currentTransformType == TRANSFORM_ROTATE){
387bInsideArcball = glm::length(mouse - glm::vec2(area.getCenter())) < std::min(area.width/2, area.height/2);
388}
389bApplyInertia = false;
390}
391}
392
393//----------------------------------------
394void ofEasyCam::mouseReleased(ofMouseEventArgs & mouse){
395ofRectangle area = getControlArea();
396
397if(area.inside(mouse)){
398// Check if it's double click
399unsigned long curTap = ofGetElapsedTimeMillis();
400if(lastTap != 0 && curTap - lastTap < doubleclickTime){
401reset();
402return;
403}
404lastTap = curTap;
405}
406
407if(doInertia){
408bApplyInertia = true;
409}else{
410currentTransformType = TRANSFORM_NONE;
411rot = {0,0,0};
412translate = {0,0,0};
413}
414}
415//----------------------------------------
416void ofEasyCam::mouseScrolled(ofMouseEventArgs & mouse){
417ofRectangle area = getControlArea();
418if(area.inside(mouse)){
419mouseVel = mouse - prevMouse;
420prevMouse = mouse;
421if (doInertia) {
422bApplyInertia = true;
423}
424lastPressPosition = ofCamera::getGlobalPosition();
425lastPressAxisZ = getZAxis();
426if (getOrtho()) {
427translate.z = sensitivityScroll * mouse.scrollY / viewport.height;
428mouseAtScroll = mouse;
429}else{
430translate.z = mouse.scrollY * 30 * sensitivityTranslate.z * (getDistance() + std::numeric_limits<float>::epsilon())/ area.height;
431}
432currentTransformType = TRANSFORM_SCALE;
433bIsScrolling = true;
434}
435}
436
437//----------------------------------------
438void ofEasyCam::updateMouse(const glm::vec2 & mouse){
439ofRectangle area = getControlArea();
440int vFlip =(isVFlipped()?-1:1);
441
442rot = {0,0,0};
443translate = {0,0,0};
444switch (currentTransformType) {
445case TRANSFORM_ROTATE:
446mouseVel = mouse - lastPressMouse;
447if(bInsideArcball){
448rot.x = vFlip * -mouseVel.y * sensitivityRot.x * glm::pi<float>() / std::min(area.width, area.height);
449rot.y = -mouseVel.x * sensitivityRot.y * glm::pi<float>() / std::min(area.width, area.height);
450}else{
451glm::vec2 center(area.getCenter());
452rot.z = sensitivityRot.z * -vFlip * glm::orientedAngle(glm::normalize(mouse - center),
453glm::normalize(lastPressMouse - center));
454}
455break;
456case TRANSFORM_TRANSLATE_XY:
457mouseVel = mouse - prevMouse;
458if (getOrtho()) {
459translate.x = -mouseVel.x * getScale().z;
460translate.y = vFlip * mouseVel.y * getScale().z;
461}else{
462translate.x = -mouseVel.x * sensitivityTranslate.x * 0.5f * (getDistance() + std::numeric_limits<float>::epsilon())/ area.width;
463translate.y = vFlip * mouseVel.y * sensitivityTranslate.y* 0.5f * (getDistance() + std::numeric_limits<float>::epsilon())/ area.height;
464}
465break;
466case TRANSFORM_TRANSLATE_Z:
467mouseVel = mouse - prevMouse;
468if (getOrtho()) {
469translate.z = mouseVel.y * sensitivityScroll / area.height;
470}else{
471translate.z = mouseVel.y * (sensitivityTranslate.z * 0.7f) * (getDistance() + std::numeric_limits<float>::epsilon())/ area.height;
472}
473break;
474default:
475break;
476}
477prevMouse = mouse;
478}
479//----------------------------------------
480void ofEasyCam::addInteraction(TransformType type, int mouseButton, int key){
481if(!hasInteraction(mouseButton, key)){
482interactions.push_back(interaction(type, mouseButton, key));
483}else{
484ofLogNotice("ofEasyCam") << "Can not add interaction. It already exists";
485}
486}
487//----------------------------------------
488void ofEasyCam::removeInteraction(TransformType type, int mouseButton, int key){
489ofRemove(interactions, [&](interaction & i){ return i.transformType == type && i.mouseButton == mouseButton && i.key ==key;});
490}
491//----------------------------------------
492bool ofEasyCam:: hasInteraction(int mouseButton, int key){
493for(const auto& i : interactions){
494if(i.mouseButton == mouseButton && i.key == key){
495return true;
496}
497}
498return false;
499}
500//----------------------------------------
501bool ofEasyCam:: hasInteraction(TransformType type, int mouseButton, int key){
502for(const auto& i : interactions){
503if(i.transformType == type && i.mouseButton == mouseButton && i.key == key){
504return true;
505}
506}
507return false;
508}
509//----------------------------------------
510void ofEasyCam::removeAllInteractions(){
511interactions.clear();
512}
513//----------------------------------------
514void ofEasyCam::onPositionChanged(){
515if(!bDistanceSet && bAutoDistance){
516bDistanceSet = true;
517}
518}
519