1 /* This file is part of Dilay
2  * Copyright © 2015-2018 Alexander Bau
3  * Use and redistribute under the terms of the GNU General Public License
4  */
5 #include <glm/gtc/matrix_transform.hpp>
6 #include <glm/gtx/matrix_major_storage.hpp>
7 #include <glm/gtx/quaternion.hpp>
8 #include "camera.hpp"
9 #include "config.hpp"
10 #include "dimension.hpp"
11 #include "intersection.hpp"
12 #include "opengl.hpp"
13 #include "primitive/plane.hpp"
14 #include "primitive/ray.hpp"
15 #include "renderer.hpp"
16 #include "util.hpp"
17 
18 struct Camera::Impl
19 {
20   Camera*     self;
21   Renderer    renderer;
22   glm::vec3   gazePoint;
23   glm::vec3   toEyePoint;
24   glm::vec3   right;
25   glm::mat4x4 projection;
26   glm::mat4x4 view;
27   glm::mat4x4 viewRotation;
28   glm::uvec2  resolution;
29   float       nearClipping;
30   float       farClipping;
31   float       fieldOfView;
32 
ImplCamera::Impl33   Impl (Camera* s, const Config& config)
34     : self (s)
35     , renderer (config)
36     , resolution (config.get<int> ("window/initial-width"),
37                   config.get<int> ("window/initial-height"))
38   {
39     this->set (glm::vec3 (0.0f, 0.0f, 0.0f), glm::vec3 (0.0f, 0.0f, 6.0f));
40     this->runFromConfig (config);
41   }
42 
upCamera::Impl43   const glm::vec3& up () const
44   {
45     static const glm::vec3 up (0.0f, 1.0f, 0.0f);
46     return up;
47   }
48 
realUpCamera::Impl49   glm::vec3 realUp () const { return glm::normalize (glm::cross (this->toEyePoint, this->right)); }
50 
positionCamera::Impl51   glm::vec3 position () const { return this->gazePoint + this->toEyePoint; }
52 
worldCamera::Impl53   glm::mat4x4 world () const
54   {
55     const glm::vec3 x = this->right;
56     const glm::vec3 z = glm::normalize (this->toEyePoint);
57     const glm::vec3 y = glm::cross (z, x);
58     const glm::vec3 p = this->position ();
59 
60     return glm::colMajor4 (glm::vec4 (x, 0.0f), glm::vec4 (y, 0.0f), glm::vec4 (z, 0.0f),
61                            glm::vec4 (p, 1.0f));
62   }
63 
updateResolutionCamera::Impl64   void updateResolution (const glm::uvec2& dimension)
65   {
66     this->resolution = dimension;
67     this->updateProjection ();
68   }
69 
setModelViewProjectionCamera::Impl70   void setModelViewProjection (const glm::mat4x4& model, const glm::mat3x3& modelNormal,
71                                bool onlyRotation)
72   {
73     this->renderer.setModel (&model[0][0], &modelNormal[0][0]);
74     this->renderer.setProjection (&this->projection[0][0]);
75 
76     if (onlyRotation)
77     {
78       this->renderer.setView (&this->viewRotation[0][0]);
79     }
80     else
81     {
82       this->renderer.setView (&this->view[0][0]);
83     }
84   }
85 
setCamera::Impl86   void set (const glm::vec3& g, const glm::vec3& e)
87   {
88     this->gazePoint = g;
89     this->toEyePoint = e;
90 
91     if (Util::colinear (e, this->up ()))
92     {
93       this->right = glm::vec3 (1.0f, 0.0f, 0.0f);
94     }
95     else
96     {
97       this->right = glm::normalize (glm::cross (this->up (), this->toEyePoint));
98     }
99     this->updateView ();
100   }
101 
setGazeCamera::Impl102   void setGaze (const glm::vec3& g)
103   {
104     this->gazePoint = g;
105     this->updateView ();
106   }
107 
stepAlongGazeCamera::Impl108   void stepAlongGaze (float factor)
109   {
110     constexpr float minD = 0.01f;
111     constexpr float maxD = 1000.0f;
112 
113     const float d = glm::length (this->toEyePoint);
114     const float newD = d * factor;
115 
116     if (minD <= newD && newD <= maxD)
117     {
118       this->toEyePoint *= glm::vec3 (factor);
119     }
120     this->updateView ();
121   }
122 
verticalRotationCamera::Impl123   void verticalRotation (float angle)
124   {
125     const glm::quat q = glm::angleAxis (angle, this->up ());
126     this->right = glm::rotate (q, this->right);
127     this->toEyePoint = glm::rotate (q, this->toEyePoint);
128     this->updateView ();
129   }
130 
horizontalRotationCamera::Impl131   void horizontalRotation (float angle)
132   {
133     const glm::quat q = glm::angleAxis (angle, this->right);
134     this->toEyePoint = glm::rotate (q, this->toEyePoint);
135     this->updateView ();
136   }
137 
viewportCamera::Impl138   glm::vec4 viewport () const { return glm::vec4 (0, 0, this->resolution.x, this->resolution.y); }
139 
fromWorldCamera::Impl140   glm::vec2 fromWorld (const glm::vec3& p, const glm::mat4x4& model, bool onlyRotation) const
141   {
142     const glm::mat4x4 mv = onlyRotation ? this->viewRotation * model : this->view * model;
143     const glm::vec3   w = glm::project (p, mv, this->projection, this->viewport ());
144 
145     return glm::vec2 (w.x, float(resolution.y) - w.y);
146   }
147 
toWorldCamera::Impl148   glm::vec3 toWorld (const glm::ivec2& p, float z = 0.0f) const
149   {
150     const float invY = this->resolution.y - float(p.y);
151     const float normZ = z / this->farClipping;
152     return glm::unProject (glm::vec3 (float(p.x), invY, normZ), this->view, this->projection,
153                            this->viewport ());
154   }
155 
toWorldCamera::Impl156   float toWorld (float length, float z)
157   {
158     const float onNearPlane = 2.0f * this->nearClipping * glm::tan (this->fieldOfView * 0.5f) *
159                               length / float(this->resolution.x);
160     return onNearPlane * (this->nearClipping + z) / this->nearClipping;
161   }
162 
rayCamera::Impl163   PrimRay ray (const glm::ivec2& p) const
164   {
165     const glm::vec3 w = this->toWorld (p);
166     const glm::vec3 eye = this->position ();
167     return PrimRay (eye, w - eye);
168   }
169 
updateProjectionCamera::Impl170   void updateProjection ()
171   {
172     OpenGL::glViewport (0, 0, this->resolution.x, this->resolution.y);
173     this->projection =
174       glm::perspective (this->fieldOfView, float(this->resolution.x) / float(this->resolution.y),
175                         this->nearClipping, this->farClipping);
176   }
177 
updateViewCamera::Impl178   void updateView ()
179   {
180     const glm::vec3 up = this->realUp ();
181 
182     this->view = glm::lookAt (this->position (), this->gazePoint, up);
183     this->viewRotation = glm::lookAt (glm::normalize (this->toEyePoint), glm::vec3 (0.0f), up);
184     this->renderer.setEyePoint (this->position ());
185   }
186 
primaryDimensionCamera::Impl187   Dimension primaryDimension () const
188   {
189     const glm::vec3 t = glm::abs (this->toEyePoint);
190     if (t.x > t.y && t.x > t.z)
191     {
192       return Dimension::X;
193     }
194     else if (t.y > t.z)
195     {
196       return Dimension::Y;
197     }
198     return Dimension::Z;
199   }
200 
planeIntersectionCamera::Impl201   glm::vec3 planeIntersection (const glm::ivec2& p, const PrimPlane& plane) const
202   {
203     const PrimRay ray = this->ray (p);
204     float         t;
205 
206     if (IntersectionUtil::intersects (ray, plane, &t))
207     {
208       return ray.pointAt (t);
209     }
210     else
211     {
212       DILAY_WARN ("No view-plane intersection");
213       return plane.point ();
214     }
215   }
216 
viewPlaneIntersectionCamera::Impl217   glm::vec3 viewPlaneIntersection (const glm::ivec2& p) const
218   {
219     const PrimPlane plane (this->gazePoint, this->toEyePoint);
220     return this->planeIntersection (p, plane);
221   }
222 
primaryPlaneIntersectionCamera::Impl223   glm::vec3 primaryPlaneIntersection (const glm::ivec2& p) const
224   {
225     const PrimPlane plane (this->gazePoint, DimensionUtil::vector (this->primaryDimension ()));
226     return this->planeIntersection (p, plane);
227   }
228 
runFromConfigCamera::Impl229   void runFromConfig (const Config& config)
230   {
231     this->renderer.fromConfig (config);
232 
233     this->nearClipping = config.get<float> ("editor/camera/near-clipping");
234     this->farClipping = config.get<float> ("editor/camera/far-clipping");
235     this->fieldOfView = glm::radians (config.get<float> ("editor/camera/field-of-view"));
236 
237     this->updateProjection ();
238   }
239 };
240 
241 DELEGATE1_BIG3_SELF (Camera, const Config&)
242 
243 GETTER_CONST (Renderer&, Camera, renderer)
244 GETTER_CONST (const glm::uvec2&, Camera, resolution)
245 GETTER_CONST (const glm::vec3&, Camera, gazePoint)
246 GETTER_CONST (const glm::vec3&, Camera, toEyePoint)
247 DELEGATE_CONST (glm::vec3, Camera, realUp)
248 GETTER_CONST (const glm::vec3&, Camera, right)
249 GETTER_CONST (const glm::mat4x4&, Camera, view)
250 GETTER_CONST (const glm::mat4x4&, Camera, viewRotation)
251 DELEGATE_CONST (glm::vec3, Camera, position)
252 DELEGATE_CONST (glm::mat4x4, Camera, world)
253 DELEGATE1 (void, Camera, updateResolution, const glm::uvec2&)
254 DELEGATE3 (void, Camera, setModelViewProjection, const glm::mat4x4&, const glm::mat3x3&, bool)
255 DELEGATE2 (void, Camera, set, const glm::vec3&, const glm::vec3&)
256 DELEGATE1 (void, Camera, setGaze, const glm::vec3&)
257 DELEGATE1 (void, Camera, stepAlongGaze, float)
258 DELEGATE1 (void, Camera, verticalRotation, float)
259 DELEGATE1 (void, Camera, horizontalRotation, float)
260 DELEGATE3_CONST (glm::vec2, Camera, fromWorld, const glm::vec3&, const glm::mat4x4&, bool)
261 DELEGATE2_CONST (glm::vec3, Camera, toWorld, const glm::ivec2&, float)
262 DELEGATE2_CONST (float, Camera, toWorld, float, float)
263 DELEGATE1_CONST (PrimRay, Camera, ray, const glm::ivec2&)
264 DELEGATE_CONST (Dimension, Camera, primaryDimension)
265 DELEGATE1_CONST (glm::vec3, Camera, viewPlaneIntersection, const glm::ivec2&)
266 DELEGATE1_CONST (glm::vec3, Camera, primaryPlaneIntersection, const glm::ivec2&)
267 DELEGATE1 (void, Camera, runFromConfig, const Config&)
268