1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/Context.h"
26 #include "../Graphics/Camera.h"
27 #include "../Graphics/DebugRenderer.h"
28 #include "../Graphics/Drawable.h"
29 #include "../Scene/Node.h"
30 
31 #include "../DebugNew.h"
32 
33 namespace Urho3D
34 {
35 
36 extern const char* SCENE_CATEGORY;
37 
38 static const char* fillModeNames[] =
39 {
40     "Solid",
41     "Wireframe",
42     "Point",
43     0
44 };
45 
46 static const Matrix4 flipMatrix(
47     1.0f, 0.0f, 0.0f, 0.0f,
48     0.0f, -1.0f, 0.0f, 0.0f,
49     0.0f, 0.0f, 1.0f, 0.0f,
50     0.0f, 0.0f, 0.0f, 1.0f
51 );
52 
Camera(Context * context)53 Camera::Camera(Context* context) :
54     Component(context),
55     viewDirty_(true),
56     projectionDirty_(true),
57     frustumDirty_(true),
58     orthographic_(false),
59     nearClip_(DEFAULT_NEARCLIP),
60     farClip_(DEFAULT_FARCLIP),
61     fov_(DEFAULT_CAMERA_FOV),
62     orthoSize_(DEFAULT_ORTHOSIZE),
63     aspectRatio_(1.0f),
64     zoom_(1.0f),
65     lodBias_(1.0f),
66     viewMask_(DEFAULT_VIEWMASK),
67     viewOverrideFlags_(VO_NONE),
68     fillMode_(FILL_SOLID),
69     projectionOffset_(Vector2::ZERO),
70     reflectionPlane_(Plane::UP),
71     clipPlane_(Plane::UP),
72     autoAspectRatio_(true),
73     flipVertical_(false),
74     useReflection_(false),
75     useClipping_(false),
76     customProjection_(false)
77 {
78     reflectionMatrix_ = reflectionPlane_.ReflectionMatrix();
79 }
80 
~Camera()81 Camera::~Camera()
82 {
83 }
84 
RegisterObject(Context * context)85 void Camera::RegisterObject(Context* context)
86 {
87     context->RegisterFactory<Camera>(SCENE_CATEGORY);
88 
89     URHO3D_ACCESSOR_ATTRIBUTE("Is Enabled", IsEnabled, SetEnabled, bool, true, AM_DEFAULT);
90     URHO3D_ACCESSOR_ATTRIBUTE("Near Clip", GetNearClip, SetNearClip, float, DEFAULT_NEARCLIP, AM_DEFAULT);
91     URHO3D_ACCESSOR_ATTRIBUTE("Far Clip", GetFarClip, SetFarClip, float, DEFAULT_FARCLIP, AM_DEFAULT);
92     URHO3D_ACCESSOR_ATTRIBUTE("FOV", GetFov, SetFov, float, DEFAULT_CAMERA_FOV, AM_DEFAULT);
93     URHO3D_ACCESSOR_ATTRIBUTE("Aspect Ratio", GetAspectRatio, SetAspectRatioInternal, float, 1.0f, AM_DEFAULT);
94     URHO3D_ENUM_ATTRIBUTE("Fill Mode", fillMode_, fillModeNames, FILL_SOLID, AM_DEFAULT);
95     URHO3D_ATTRIBUTE("Auto Aspect Ratio", bool, autoAspectRatio_, true, AM_DEFAULT);
96     URHO3D_ACCESSOR_ATTRIBUTE("Orthographic", IsOrthographic, SetOrthographic, bool, false, AM_DEFAULT);
97     URHO3D_ACCESSOR_ATTRIBUTE("Orthographic Size", GetOrthoSize, SetOrthoSizeAttr, float, DEFAULT_ORTHOSIZE, AM_DEFAULT);
98     URHO3D_ACCESSOR_ATTRIBUTE("Zoom", GetZoom, SetZoom, float, 1.0f, AM_DEFAULT);
99     URHO3D_ACCESSOR_ATTRIBUTE("LOD Bias", GetLodBias, SetLodBias, float, 1.0f, AM_DEFAULT);
100     URHO3D_ATTRIBUTE("View Mask", int, viewMask_, DEFAULT_VIEWMASK, AM_DEFAULT);
101     URHO3D_ATTRIBUTE("View Override Flags", int, viewOverrideFlags_, VO_NONE, AM_DEFAULT);
102     URHO3D_ACCESSOR_ATTRIBUTE("Projection Offset", GetProjectionOffset, SetProjectionOffset, Vector2, Vector2::ZERO, AM_DEFAULT);
103     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Reflection Plane", GetReflectionPlaneAttr, SetReflectionPlaneAttr, Vector4,
104         Vector4(0.0f, 1.0f, 0.0f, 0.0f), AM_DEFAULT);
105     URHO3D_MIXED_ACCESSOR_ATTRIBUTE("Clip Plane", GetClipPlaneAttr, SetClipPlaneAttr, Vector4, Vector4(0.0f, 1.0f, 0.0f, 0.0f),
106         AM_DEFAULT);
107     URHO3D_ACCESSOR_ATTRIBUTE("Use Reflection", GetUseReflection, SetUseReflection, bool, false, AM_DEFAULT);
108     URHO3D_ACCESSOR_ATTRIBUTE("Use Clipping", GetUseClipping, SetUseClipping, bool, false, AM_DEFAULT);
109 }
110 
DrawDebugGeometry(DebugRenderer * debug,bool depthTest)111 void Camera::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
112 {
113     debug->AddFrustum(GetFrustum(), Color::WHITE, depthTest);
114 }
115 
SetNearClip(float nearClip)116 void Camera::SetNearClip(float nearClip)
117 {
118     nearClip_ = Max(nearClip, M_MIN_NEARCLIP);
119     frustumDirty_ = true;
120     projectionDirty_ = true;
121     MarkNetworkUpdate();
122 }
123 
SetFarClip(float farClip)124 void Camera::SetFarClip(float farClip)
125 {
126     farClip_ = Max(farClip, M_MIN_NEARCLIP);
127     frustumDirty_ = true;
128     projectionDirty_ = true;
129     MarkNetworkUpdate();
130 }
131 
SetFov(float fov)132 void Camera::SetFov(float fov)
133 {
134     fov_ = Clamp(fov, 0.0f, M_MAX_FOV);
135     frustumDirty_ = true;
136     projectionDirty_ = true;
137     MarkNetworkUpdate();
138 }
139 
SetOrthoSize(float orthoSize)140 void Camera::SetOrthoSize(float orthoSize)
141 {
142     orthoSize_ = orthoSize;
143     aspectRatio_ = 1.0f;
144     frustumDirty_ = true;
145     projectionDirty_ = true;
146     MarkNetworkUpdate();
147 }
148 
SetOrthoSize(const Vector2 & orthoSize)149 void Camera::SetOrthoSize(const Vector2& orthoSize)
150 {
151     autoAspectRatio_ = false;
152     orthoSize_ = orthoSize.y_;
153     aspectRatio_ = orthoSize.x_ / orthoSize.y_;
154     frustumDirty_ = true;
155     projectionDirty_ = true;
156     MarkNetworkUpdate();
157 }
158 
SetAspectRatio(float aspectRatio)159 void Camera::SetAspectRatio(float aspectRatio)
160 {
161     autoAspectRatio_ = false;
162     SetAspectRatioInternal(aspectRatio);
163 }
164 
SetZoom(float zoom)165 void Camera::SetZoom(float zoom)
166 {
167     zoom_ = Max(zoom, M_EPSILON);
168     frustumDirty_ = true;
169     projectionDirty_ = true;
170     MarkNetworkUpdate();
171 }
172 
SetLodBias(float bias)173 void Camera::SetLodBias(float bias)
174 {
175     lodBias_ = Max(bias, M_EPSILON);
176     MarkNetworkUpdate();
177 }
178 
SetViewMask(unsigned mask)179 void Camera::SetViewMask(unsigned mask)
180 {
181     viewMask_ = mask;
182     MarkNetworkUpdate();
183 }
184 
SetViewOverrideFlags(unsigned flags)185 void Camera::SetViewOverrideFlags(unsigned flags)
186 {
187     viewOverrideFlags_ = flags;
188     MarkNetworkUpdate();
189 }
190 
SetFillMode(FillMode mode)191 void Camera::SetFillMode(FillMode mode)
192 {
193     fillMode_ = mode;
194     MarkNetworkUpdate();
195 }
196 
SetOrthographic(bool enable)197 void Camera::SetOrthographic(bool enable)
198 {
199     orthographic_ = enable;
200     frustumDirty_ = true;
201     projectionDirty_ = true;
202     MarkNetworkUpdate();
203 }
204 
SetAutoAspectRatio(bool enable)205 void Camera::SetAutoAspectRatio(bool enable)
206 {
207     autoAspectRatio_ = enable;
208     MarkNetworkUpdate();
209 }
210 
SetProjectionOffset(const Vector2 & offset)211 void Camera::SetProjectionOffset(const Vector2& offset)
212 {
213     projectionOffset_ = offset;
214     projectionDirty_ = true;
215     MarkNetworkUpdate();
216 }
217 
SetUseReflection(bool enable)218 void Camera::SetUseReflection(bool enable)
219 {
220     useReflection_ = enable;
221     viewDirty_ = true;
222     frustumDirty_ = true;
223     MarkNetworkUpdate();
224 }
225 
SetReflectionPlane(const Plane & plane)226 void Camera::SetReflectionPlane(const Plane& plane)
227 {
228     reflectionPlane_ = plane;
229     reflectionMatrix_ = reflectionPlane_.ReflectionMatrix();
230     viewDirty_ = true;
231     frustumDirty_ = true;
232     MarkNetworkUpdate();
233 }
234 
SetUseClipping(bool enable)235 void Camera::SetUseClipping(bool enable)
236 {
237     useClipping_ = enable;
238     projectionDirty_ = true;
239     MarkNetworkUpdate();
240 }
241 
SetClipPlane(const Plane & plane)242 void Camera::SetClipPlane(const Plane& plane)
243 {
244     clipPlane_ = plane;
245     MarkNetworkUpdate();
246 }
247 
SetFlipVertical(bool enable)248 void Camera::SetFlipVertical(bool enable)
249 {
250     flipVertical_ = enable;
251     MarkNetworkUpdate();
252 }
253 
SetProjection(const Matrix4 & projection)254 void Camera::SetProjection(const Matrix4& projection)
255 {
256     projection_ = projection;
257     Matrix4 projInverse = projection_.Inverse();
258 
259     // Calculate the actual near & far clip from the custom matrix
260     projNearClip_ = (projInverse * Vector3(0.0f, 0.0f, 0.0f)).z_;
261     projFarClip_ = (projInverse * Vector3(0.0f, 0.0f, 1.0f)).z_;
262     projectionDirty_ = false;
263     autoAspectRatio_ = false;
264     frustumDirty_ = true;
265     customProjection_ = true;
266     // Called due to autoAspectRatio changing state, the projection itself is not serialized
267     MarkNetworkUpdate();
268 }
269 
GetNearClip() const270 float Camera::GetNearClip() const
271 {
272     if (projectionDirty_)
273         UpdateProjection();
274 
275     return projNearClip_;
276 }
277 
GetFarClip() const278 float Camera::GetFarClip() const
279 {
280     if (projectionDirty_)
281         UpdateProjection();
282 
283     return projFarClip_;
284 }
285 
GetFrustum() const286 const Frustum& Camera::GetFrustum() const
287 {
288     // Use projection_ instead of GetProjection() so that Y-flip has no effect. Update first if necessary
289     if (projectionDirty_)
290         UpdateProjection();
291 
292     if (frustumDirty_)
293     {
294         if (customProjection_)
295             frustum_.Define(projection_ * GetView());
296         else
297         {
298             // If not using a custom projection, prefer calculating frustum from projection parameters instead of matrix
299             // for better accuracy
300             if (!orthographic_)
301                 frustum_.Define(fov_, aspectRatio_, zoom_, GetNearClip(), GetFarClip(), GetEffectiveWorldTransform());
302             else
303                 frustum_.DefineOrtho(orthoSize_, aspectRatio_, zoom_, GetNearClip(), GetFarClip(), GetEffectiveWorldTransform());
304         }
305 
306         frustumDirty_ = false;
307     }
308 
309     return frustum_;
310 }
311 
GetSplitFrustum(float nearClip,float farClip) const312 Frustum Camera::GetSplitFrustum(float nearClip, float farClip) const
313 {
314     if (projectionDirty_)
315         UpdateProjection();
316 
317     nearClip = Max(nearClip, projNearClip_);
318     farClip = Min(farClip, projFarClip_);
319     if (farClip < nearClip)
320         farClip = nearClip;
321 
322     Frustum ret;
323 
324     if (customProjection_)
325     {
326         // DefineSplit() needs to project the near & far distances, so can not use a combined view-projection matrix.
327         // Transform to world space afterward instead
328         ret.DefineSplit(projection_, nearClip, farClip);
329         ret.Transform(GetEffectiveWorldTransform());
330     }
331     else
332     {
333         if (!orthographic_)
334             ret.Define(fov_, aspectRatio_, zoom_, nearClip, farClip, GetEffectiveWorldTransform());
335         else
336             ret.DefineOrtho(orthoSize_, aspectRatio_, zoom_, nearClip, farClip, GetEffectiveWorldTransform());
337     }
338 
339     return ret;
340 }
341 
GetViewSpaceFrustum() const342 Frustum Camera::GetViewSpaceFrustum() const
343 {
344     if (projectionDirty_)
345         UpdateProjection();
346 
347     Frustum ret;
348 
349     if (customProjection_)
350         ret.Define(projection_);
351     else
352     {
353         if (!orthographic_)
354             ret.Define(fov_, aspectRatio_, zoom_, GetNearClip(), GetFarClip());
355         else
356             ret.DefineOrtho(orthoSize_, aspectRatio_, zoom_, GetNearClip(), GetFarClip());
357     }
358 
359     return ret;
360 }
361 
GetViewSpaceSplitFrustum(float nearClip,float farClip) const362 Frustum Camera::GetViewSpaceSplitFrustum(float nearClip, float farClip) const
363 {
364     if (projectionDirty_)
365         UpdateProjection();
366 
367     nearClip = Max(nearClip, projNearClip_);
368     farClip = Min(farClip, projFarClip_);
369     if (farClip < nearClip)
370         farClip = nearClip;
371 
372     Frustum ret;
373 
374     if (customProjection_)
375         ret.DefineSplit(projection_, nearClip, farClip);
376     else
377     {
378         if (!orthographic_)
379             ret.Define(fov_, aspectRatio_, zoom_, nearClip, farClip);
380         else
381             ret.DefineOrtho(orthoSize_, aspectRatio_, zoom_, nearClip, farClip);
382     }
383 
384     return ret;
385 }
386 
GetScreenRay(float x,float y) const387 Ray Camera::GetScreenRay(float x, float y) const
388 {
389     Ray ret;
390 
391     // If projection is invalid, just return a ray pointing forward
392     if (!IsProjectionValid())
393     {
394         ret.origin_ = node_ ? node_->GetWorldPosition() : Vector3::ZERO;
395         ret.direction_ = node_ ? node_->GetWorldDirection() : Vector3::FORWARD;
396         return ret;
397     }
398 
399     Matrix4 viewProjInverse = (GetProjection() * GetView()).Inverse();
400 
401     // The parameters range from 0.0 to 1.0. Expand to normalized device coordinates (-1.0 to 1.0) & flip Y axis
402     x = 2.0f * x - 1.0f;
403     y = 1.0f - 2.0f * y;
404     Vector3 near(x, y, 0.0f);
405     Vector3 far(x, y, 1.0f);
406 
407     ret.origin_ = viewProjInverse * near;
408     ret.direction_ = ((viewProjInverse * far) - ret.origin_).Normalized();
409     return ret;
410 }
411 
WorldToScreenPoint(const Vector3 & worldPos) const412 Vector2 Camera::WorldToScreenPoint(const Vector3& worldPos) const
413 {
414     Vector3 eyeSpacePos = GetView() * worldPos;
415     Vector2 ret;
416 
417     if (eyeSpacePos.z_ > 0.0f)
418     {
419         Vector3 screenSpacePos = GetProjection() * eyeSpacePos;
420         ret.x_ = screenSpacePos.x_;
421         ret.y_ = screenSpacePos.y_;
422     }
423     else
424     {
425         ret.x_ = (-eyeSpacePos.x_ > 0.0f) ? -1.0f : 1.0f;
426         ret.y_ = (-eyeSpacePos.y_ > 0.0f) ? -1.0f : 1.0f;
427     }
428 
429     ret.x_ = (ret.x_ / 2.0f) + 0.5f;
430     ret.y_ = 1.0f - ((ret.y_ / 2.0f) + 0.5f);
431     return ret;
432 }
433 
ScreenToWorldPoint(const Vector3 & screenPos) const434 Vector3 Camera::ScreenToWorldPoint(const Vector3& screenPos) const
435 {
436     Ray ray = GetScreenRay(screenPos.x_, screenPos.y_);
437     Vector3 viewSpaceDir = (GetView() * Vector4(ray.direction_, 0.0f));
438     float rayDistance = (Max(screenPos.z_ - GetNearClip(), 0.0f) / viewSpaceDir.z_);
439     return ray.origin_ + ray.direction_ * rayDistance;
440 }
441 
GetProjection() const442 Matrix4 Camera::GetProjection() const
443 {
444     if (projectionDirty_)
445         UpdateProjection();
446 
447     return flipVertical_ ? flipMatrix * projection_ : projection_;
448 }
449 
GetGPUProjection() const450 Matrix4 Camera::GetGPUProjection() const
451 {
452 #ifndef URHO3D_OPENGL
453     return GetProjection(); // Already matches API-specific format
454 #else
455     // See formulation for depth range conversion at http://www.ogre3d.org/forums/viewtopic.php?f=4&t=13357
456     Matrix4 ret = GetProjection();
457 
458     ret.m20_ = 2.0f * ret.m20_ - ret.m30_;
459     ret.m21_ = 2.0f * ret.m21_ - ret.m31_;
460     ret.m22_ = 2.0f * ret.m22_ - ret.m32_;
461     ret.m23_ = 2.0f * ret.m23_ - ret.m33_;
462 
463     return ret;
464 #endif
465 }
466 
GetFrustumSize(Vector3 & near,Vector3 & far) const467 void Camera::GetFrustumSize(Vector3& near, Vector3& far) const
468 {
469     Frustum viewSpaceFrustum = GetViewSpaceFrustum();
470     near = viewSpaceFrustum.vertices_[0];
471     far = viewSpaceFrustum.vertices_[4];
472 
473     /// \todo Necessary? Explain this
474     if (flipVertical_)
475     {
476         near.y_ = -near.y_;
477         far.y_ = -far.y_;
478     }
479 }
480 
GetHalfViewSize() const481 float Camera::GetHalfViewSize() const
482 {
483     if (!orthographic_)
484         return tanf(fov_ * M_DEGTORAD * 0.5f) / zoom_;
485     else
486         return orthoSize_ * 0.5f / zoom_;
487 }
488 
GetDistance(const Vector3 & worldPos) const489 float Camera::GetDistance(const Vector3& worldPos) const
490 {
491     if (!orthographic_)
492     {
493         const Vector3& cameraPos = node_ ? node_->GetWorldPosition() : Vector3::ZERO;
494         return (worldPos - cameraPos).Length();
495     }
496     else
497         return Abs((GetView() * worldPos).z_);
498 }
499 
GetDistanceSquared(const Vector3 & worldPos) const500 float Camera::GetDistanceSquared(const Vector3& worldPos) const
501 {
502     if (!orthographic_)
503     {
504         const Vector3& cameraPos = node_ ? node_->GetWorldPosition() : Vector3::ZERO;
505         return (worldPos - cameraPos).LengthSquared();
506     }
507     else
508     {
509         float distance = (GetView() * worldPos).z_;
510         return distance * distance;
511     }
512 }
513 
GetLodDistance(float distance,float scale,float bias) const514 float Camera::GetLodDistance(float distance, float scale, float bias) const
515 {
516     float d = Max(lodBias_ * bias * scale * zoom_, M_EPSILON);
517     if (!orthographic_)
518         return distance / d;
519     else
520         return orthoSize_ / d;
521 }
522 
GetFaceCameraRotation(const Vector3 & position,const Quaternion & rotation,FaceCameraMode mode,float minAngle)523 Quaternion Camera::GetFaceCameraRotation(const Vector3& position, const Quaternion& rotation, FaceCameraMode mode, float minAngle)
524 {
525     if (!node_)
526         return rotation;
527 
528     switch (mode)
529     {
530     case FC_ROTATE_XYZ:
531         return node_->GetWorldRotation();
532 
533     case FC_ROTATE_Y:
534         {
535             Vector3 euler = rotation.EulerAngles();
536             euler.y_ = node_->GetWorldRotation().EulerAngles().y_;
537             return Quaternion(euler.x_, euler.y_, euler.z_);
538         }
539 
540     case FC_LOOKAT_XYZ:
541         {
542             Quaternion lookAt;
543             lookAt.FromLookRotation(position - node_->GetWorldPosition());
544             return lookAt;
545         }
546 
547     case FC_LOOKAT_Y:
548     case FC_LOOKAT_MIXED:
549         {
550             // Mixed mode needs true look-at vector
551             const Vector3 lookAtVec(position - node_->GetWorldPosition());
552             // While Y-only lookat happens on an XZ plane to make sure there are no unwanted transitions or singularities
553             const Vector3 lookAtVecXZ(lookAtVec.x_, 0.0f, lookAtVec.z_);
554 
555             Quaternion lookAt;
556             lookAt.FromLookRotation(lookAtVecXZ);
557 
558             Vector3 euler = rotation.EulerAngles();
559             if (mode == FC_LOOKAT_MIXED)
560             {
561                 const float angle = lookAtVec.Angle(rotation * Vector3::UP);
562                 if (angle > 180 - minAngle)
563                     euler.x_ += minAngle - (180 - angle);
564                 else if (angle < minAngle)
565                     euler.x_ -= minAngle - angle;
566             }
567             euler.y_ = lookAt.EulerAngles().y_;
568             return Quaternion(euler.x_, euler.y_, euler.z_);
569         }
570 
571     default:
572         return rotation;
573     }
574 }
575 
GetEffectiveWorldTransform() const576 Matrix3x4 Camera::GetEffectiveWorldTransform() const
577 {
578     Matrix3x4 worldTransform = node_ ? Matrix3x4(node_->GetWorldPosition(), node_->GetWorldRotation(), 1.0f) : Matrix3x4::IDENTITY;
579     return useReflection_ ? reflectionMatrix_ * worldTransform : worldTransform;
580 }
581 
IsProjectionValid() const582 bool Camera::IsProjectionValid() const
583 {
584     return GetFarClip() > GetNearClip();
585 }
586 
GetView() const587 const Matrix3x4& Camera::GetView() const
588 {
589     if (viewDirty_)
590     {
591         // Note: view matrix is unaffected by node or parent scale
592         view_ = GetEffectiveWorldTransform().Inverse();
593         viewDirty_ = false;
594     }
595 
596     return view_;
597 }
598 
SetAspectRatioInternal(float aspectRatio)599 void Camera::SetAspectRatioInternal(float aspectRatio)
600 {
601     if (aspectRatio != aspectRatio_)
602     {
603         aspectRatio_ = aspectRatio;
604         frustumDirty_ = true;
605         projectionDirty_ = true;
606     }
607     MarkNetworkUpdate();
608 }
609 
SetOrthoSizeAttr(float orthoSize)610 void Camera::SetOrthoSizeAttr(float orthoSize)
611 {
612     orthoSize_ = orthoSize;
613     frustumDirty_ = true;
614     projectionDirty_ = true;
615     MarkNetworkUpdate();
616 }
617 
SetReflectionPlaneAttr(const Vector4 & value)618 void Camera::SetReflectionPlaneAttr(const Vector4& value)
619 {
620     SetReflectionPlane(Plane(value));
621 }
622 
SetClipPlaneAttr(const Vector4 & value)623 void Camera::SetClipPlaneAttr(const Vector4& value)
624 {
625     SetClipPlane(Plane(value));
626 }
627 
GetReflectionPlaneAttr() const628 Vector4 Camera::GetReflectionPlaneAttr() const
629 {
630     return reflectionPlane_.ToVector4();
631 }
632 
GetClipPlaneAttr() const633 Vector4 Camera::GetClipPlaneAttr() const
634 {
635     return clipPlane_.ToVector4();
636 }
637 
OnNodeSet(Node * node)638 void Camera::OnNodeSet(Node* node)
639 {
640     if (node)
641         node->AddListener(this);
642 }
643 
OnMarkedDirty(Node * node)644 void Camera::OnMarkedDirty(Node* node)
645 {
646     frustumDirty_ = true;
647     viewDirty_ = true;
648 }
649 
UpdateProjection() const650 void Camera::UpdateProjection() const
651 {
652     // Start from a zero matrix in case it was custom previously
653     projection_ = Matrix4::ZERO;
654 
655     if (!orthographic_)
656     {
657         float h = (1.0f / tanf(fov_ * M_DEGTORAD * 0.5f)) * zoom_;
658         float w = h / aspectRatio_;
659         float q = farClip_ / (farClip_ - nearClip_);
660         float r = -q * nearClip_;
661 
662         projection_.m00_ = w;
663         projection_.m02_ = projectionOffset_.x_ * 2.0f;
664         projection_.m11_ = h;
665         projection_.m12_ = projectionOffset_.y_ * 2.0f;
666         projection_.m22_ = q;
667         projection_.m23_ = r;
668         projection_.m32_ = 1.0f;
669         projNearClip_ = nearClip_;
670         projFarClip_ = farClip_;
671     }
672     else
673     {
674         float h = (1.0f / (orthoSize_ * 0.5f)) * zoom_;
675         float w = h / aspectRatio_;
676         float q = 1.0f / farClip_;
677         float r = 0.0f;
678 
679         projection_.m00_ = w;
680         projection_.m03_ = projectionOffset_.x_ * 2.0f;
681         projection_.m11_ = h;
682         projection_.m13_ = projectionOffset_.y_ * 2.0f;
683         projection_.m22_ = q;
684         projection_.m23_ = r;
685         projection_.m33_ = 1.0f;
686         // Near clip does not affect depth accuracy in ortho projection, so let it stay 0 to avoid problems with shader depth parameters
687         projNearClip_ = 0.0f;
688         projFarClip_ = farClip_;
689     }
690 
691     projectionDirty_ = false;
692     customProjection_ = false;
693 }
694 
695 }
696