framework2
278 строк · 8.3 Кб
1#include "ofCamera.h"
2#include "ofGraphics.h"
3
4#define GLM_FORCE_CTOR_INIT
5#include "glm/gtx/transform.hpp"
6#include "glm/gtc/quaternion.hpp"
7#include "of3dGraphics.h"
8
9using std::shared_ptr;
10
11//----------------------------------------
12ofCamera::ofCamera() :
13isOrtho(false),
14fov(60),
15nearClip(0),
16farClip(0),
17lensOffset(0.0f, 0.0f),
18forceAspectRatio(false),
19aspectRatio(4./3.),
20vFlip(false)
21{
22}
23
24//----------------------------------------
25ofCamera::~ofCamera() {}
26
27//----------------------------------------
28void ofCamera::setFov(float f) {
29fov = f;
30}
31
32//----------------------------------------
33void ofCamera::setNearClip(float f) {
34nearClip = f;
35}
36
37//----------------------------------------
38void ofCamera::setFarClip(float f) {
39farClip = f;
40}
41
42//----------------------------------------
43void ofCamera::setLensOffset(const glm::vec2 & lensOffset){
44this->lensOffset = lensOffset;
45}
46
47//----------------------------------------
48void ofCamera::setAspectRatio(float aspectRatio){
49this->aspectRatio = aspectRatio;
50setForceAspectRatio(true);
51}
52
53//----------------------------------------
54void ofCamera::setForceAspectRatio(bool forceAspectRatio){
55this->forceAspectRatio = forceAspectRatio;
56}
57
58//----------------------------------------
59void ofCamera::setupPerspective(bool _vFlip, float fov, float nearDist, float farDist, const glm::vec2 & lensOffset){
60ofRectangle orientedViewport = getRenderer()->getNativeViewport();
61float eyeX = orientedViewport.width / 2;
62float eyeY = orientedViewport.height / 2;
63float halfFov = glm::pi<float>() * fov / 360.0f;
64float theTan = tanf(halfFov);
65float dist = eyeY / theTan;
66
67if(nearDist == 0) nearDist = dist / 10.0f;
68if(farDist == 0) farDist = dist * 10.0f;
69
70setFov(fov);
71setNearClip(nearDist);
72setFarClip(farDist);
73setLensOffset(lensOffset);
74setForceAspectRatio(false);
75
76setPosition(eyeX,eyeY,dist);
77lookAt({eyeX,eyeY,0.f}, {0.f,1.f,0.f});
78vFlip = _vFlip;
79}
80
81//----------------------------------------
82void ofCamera::setupOffAxisViewPortal(const glm::vec3 & topLeft, const glm::vec3 & bottomLeft, const glm::vec3 & bottomRight){
83auto bottomEdge = bottomRight - bottomLeft; // plane x axis
84auto leftEdge = topLeft - bottomLeft; // plane y axis
85auto bottomEdgeNorm = glm::normalize(bottomEdge);
86auto leftEdgeNorm = glm::normalize(leftEdge);
87auto bottomLeftToCam = this->getPosition() - bottomLeft;
88
89auto cameraLookVector = glm::cross(leftEdgeNorm, bottomEdgeNorm);
90auto cameraUpVector = glm::cross(bottomEdgeNorm, cameraLookVector);
91
92lookAt(cameraLookVector + this->getPosition(), cameraUpVector);
93
94//lensoffset
95glm::vec2 lensOffset;
96lensOffset.x = -glm::dot(bottomLeftToCam, bottomEdgeNorm) * 2.0f / glm::length(bottomEdge) + 1.0f;
97lensOffset.y = -glm::dot(bottomLeftToCam, leftEdgeNorm) * 2.0f / glm::length(leftEdge) + 1.0f;
98setLensOffset(lensOffset);
99setAspectRatio( glm::length(bottomEdge) / glm::length(leftEdge) );
100auto distanceAlongOpticalAxis = fabs(glm::dot(bottomLeftToCam, cameraLookVector));
101setFov(2.0f * glm::degrees( atan( (glm::length(leftEdge) / 2.0f) / distanceAlongOpticalAxis) ) );
102}
103
104
105//----------------------------------------
106void ofCamera::setVFlip(bool vflip){
107vFlip = vflip;
108}
109
110//----------------------------------------
111bool ofCamera::isVFlipped() const{
112return vFlip;
113}
114
115//----------------------------------------
116void ofCamera::enableOrtho() {
117isOrtho = true;
118}
119
120//----------------------------------------
121void ofCamera::disableOrtho() {
122isOrtho = false;
123}
124
125//----------------------------------------
126bool ofCamera::getOrtho() const {
127return isOrtho;
128}
129
130//----------------------------------------
131float ofCamera::getImagePlaneDistance(const ofRectangle & viewport) const {
132return viewport.height / (2.0f * tanf(glm::pi<float>() * fov / 360.0f));
133}
134
135//----------------------------------------
136void ofCamera::begin(const ofRectangle & viewport) {
137ofGetCurrentRenderer()->bind(*this,viewport);
138}
139
140// if begin(); pushes first, then we need an end to pop
141//----------------------------------------
142void ofCamera::end() {
143ofGetCurrentRenderer()->unbind(*this);
144}
145
146//----------------------------------------
147glm::mat4 ofCamera::getProjectionMatrix(const ofRectangle & viewport) const {
148// autocalculate near/far clip planes if not set by user
149const_cast<ofCamera*>(this)->calcClipPlanes(viewport);
150
151if(isOrtho) {
152return glm::translate(glm::mat4(1.0), {-lensOffset.x, -lensOffset.y, 0.f}) * glm::ortho(
153- viewport.width/2,
154+ viewport.width/2,
155- viewport.height/2,
156+ viewport.height/2,
157nearClip,
158farClip
159);
160}else{
161float aspect = forceAspectRatio ? aspectRatio : viewport.width/viewport.height;
162auto projection = glm::perspective(glm::radians(fov), aspect, nearClip, farClip);
163projection = glm::translate(glm::mat4(1.0), {-lensOffset.x, -lensOffset.y, 0.f}) * projection;
164return projection;
165}
166}
167
168//----------------------------------------
169glm::mat4 ofCamera::getModelViewMatrix() const {
170return glm::inverse(getGlobalTransformMatrix());
171}
172
173//----------------------------------------
174glm::mat4 ofCamera::getModelViewProjectionMatrix(const ofRectangle & viewport) const {
175return getProjectionMatrix(viewport) * getModelViewMatrix();
176}
177
178//----------------------------------------
179glm::vec3 ofCamera::worldToScreen(glm::vec3 WorldXYZ, const ofRectangle & viewport) const {
180auto CameraXYZ = worldToCamera(WorldXYZ, viewport);
181
182glm::vec3 ScreenXYZ;
183ScreenXYZ.x = (CameraXYZ.x + 1.0f) / 2.0f * viewport.width + viewport.x;
184ScreenXYZ.y = (1.0f - CameraXYZ.y) / 2.0f * viewport.height + viewport.y;
185ScreenXYZ.z = CameraXYZ.z;
186
187return ScreenXYZ;
188}
189
190//----------------------------------------
191glm::vec3 ofCamera::screenToWorld(glm::vec3 ScreenXYZ, const ofRectangle & viewport) const {
192//convert from screen to camera
193glm::vec3 CameraXYZ;
194CameraXYZ.x = 2.0f * (ScreenXYZ.x - viewport.x) / viewport.width - 1.0f;
195CameraXYZ.y = 1.0f - 2.0f *(ScreenXYZ.y - viewport.y) / viewport.height;
196CameraXYZ.z = ScreenXYZ.z;
197
198return cameraToWorld(CameraXYZ, viewport);
199}
200
201//----------------------------------------
202glm::vec3 ofCamera::worldToCamera(glm::vec3 WorldXYZ, const ofRectangle & viewport) const {
203auto MVPmatrix = getModelViewProjectionMatrix(viewport);
204if(vFlip){
205MVPmatrix = glm::scale(glm::mat4(1.0), glm::vec3(1.f,-1.f,1.f)) * MVPmatrix;
206}
207auto camera = MVPmatrix * glm::vec4(WorldXYZ, 1.0);
208return glm::vec3(camera) / camera.w;
209
210}
211
212//----------------------------------------
213glm::vec3 ofCamera::cameraToWorld(glm::vec3 CameraXYZ, const ofRectangle & viewport) const {
214auto MVPmatrix = getModelViewProjectionMatrix(viewport);
215if(vFlip){
216MVPmatrix = glm::scale(glm::mat4(1.0), glm::vec3(1.f,-1.f,1.f)) * MVPmatrix;
217}
218auto world = glm::inverse(MVPmatrix) * glm::vec4(CameraXYZ, 1.0);
219return glm::vec3(world) / world.w;
220}
221
222//----------------------------------------
223void ofCamera::calcClipPlanes(const ofRectangle & viewport) {
224// autocalculate near/far clip planes if not set by user
225if(nearClip == 0 || farClip == 0) {
226float dist = getImagePlaneDistance(viewport);
227nearClip = (nearClip == 0) ? dist / 100.0f : nearClip;
228farClip = (farClip == 0) ? dist * 10.0f : farClip;
229}
230}
231
232ofRectangle ofCamera::getViewport() const{
233return getRenderer()->getCurrentViewport();
234}
235
236shared_ptr<ofBaseRenderer> ofCamera::getRenderer() const{
237if(!renderer){
238return ofGetCurrentRenderer();
239}else{
240return renderer;
241}
242}
243
244void ofCamera::setRenderer(shared_ptr<ofBaseRenderer> _renderer){
245renderer = _renderer;
246}
247
248void ofCamera::drawFrustum(const ofRectangle & viewport) const {
249ofPushMatrix();
250
251// In World Space, the camera frustum is the "capped pyramid" which
252// contains everything which can be seen from a camera.
253//
254// In Clip Space, the frustum is defined as a box with dimensions: -1, -1, -1 to +1, +1, +1.
255//
256// Much simpler.
257//
258// We therefore want to draw our frustum as if in Clip Space.
259// For this, we must find a matrix which will transform Clip Space
260// back to World Space.
261
262
263// Note: globalTransformMatrix == inverse(modelViewMatrix)
264glm::mat4 clipSpaceToWorld = getGlobalTransformMatrix() * glm::inverse(getProjectionMatrix(viewport));
265
266// Move into Clip Space - this matrix will transform anything we
267// draw from Clip Space to World Space
268ofMultMatrix(clipSpaceToWorld);
269
270// Anything we draw now is transformed from Clip Space back to World Space
271
272ofPushStyle();
273ofNoFill();
274ofDrawBox(0, 0, 0, 2.f, 2.f, 2.f); // In Clip Space, the frustum is standardised to be a box of dimensions -1,1 in each axis
275ofPopStyle();
276
277ofPopMatrix();
278}
279