2 * Copyright (c) 2018 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/internal/update/render-tasks/scene-graph-camera.h>
25 #include <dali/integration-api/debug.h>
26 #include <dali/public-api/common/dali-common.h>
27 #include <dali/public-api/math/math-utils.h>
28 #include <dali/internal/update/nodes/node.h>
30 namespace // unnamed namespace
32 const uint32_t UPDATE_COUNT = 2u; // Update projection or view matrix this many frames after a change
33 const uint32_t COPY_PREVIOUS_MATRIX = 1u; // Copy view or projection matrix from previous frame
35 //For reflection and clipping plane
36 const float REFLECTION_NORMALIZED_DEVICE_COORDINATE_PARAMETER_A = 2.0f;
37 const float REFLECTION_NORMALIZED_DEVICE_COORDINATE_PARAMETER_D = 1.0f;
52 template< typename T >
55 return T( T(0) < value ) - T( value < T(0) );
58 void LookAt(Matrix& result, const Vector3& eye, const Vector3& target, const Vector3& up)
60 Vector3 vZ = target - eye;
63 Vector3 vX = up.Cross(vZ);
66 Vector3 vY = vZ.Cross(vX);
69 result.SetInverseTransformComponents(vX, vY, vZ, eye);
72 void Frustum(Matrix& result, float left, float right, float bottom, float top, float near, float far, bool invertYAxis)
74 float deltaZ = far - near;
75 if ((near <= 0.0f) || (far <= 0.0f) || Equals(right, left) || Equals(bottom, top) || (deltaZ <= 0.0f))
77 DALI_LOG_ERROR("Invalid parameters passed into Frustum!\n");
78 DALI_ASSERT_DEBUG("Invalid parameters passed into Frustum!");
82 float deltaX = right - left;
83 float deltaY = invertYAxis ? bottom - top : top - bottom;
87 float* m = result.AsFloat();
88 m[0] = -2.0f * near / deltaX;
89 m[1] = m[2] = m[3] = 0.0f;
91 m[5] = -2.0f * near / deltaY;
92 m[4] = m[6] = m[7] = 0.0f;
94 m[8] = (right + left) / deltaX;
95 m[9] = (top + bottom) / deltaY;
96 m[10] = (near + far) / deltaZ;
99 m[14] = -2.0f * near * far / deltaZ;
100 m[12] = m[13] = m[15] = 0.0f;
103 void Perspective(Matrix& result, float fovy, float aspect, float near, float far, bool invertYAxis )
105 float frustumH = tanf( fovy * 0.5f ) * near;
106 float frustumW = frustumH * aspect;
108 Frustum(result, -frustumW, frustumW, -frustumH, frustumH, near, far, invertYAxis);
111 void Orthographic(Matrix& result, float left, float right, float bottom, float top, float near, float far, bool invertYAxis)
113 if ( Equals(right, left) || Equals(top, bottom) || Equals(far, near) )
115 DALI_LOG_ERROR( "Cannot create orthographic projection matrix with a zero dimension.\n" );
116 DALI_ASSERT_DEBUG( "Cannot create orthographic projection matrix with a zero dimension." );
120 float deltaX = right - left;
121 float deltaY = invertYAxis ? bottom - top : top - bottom;
122 float deltaZ = far - near;
124 float *m = result.AsFloat();
125 m[0] = -2.0f / deltaX;
131 m[5] = -2.0f / deltaY;
137 m[10] = 2.0f / deltaZ;
139 m[12] = -(right + left) / deltaX;
140 m[13] = -(top + bottom) / deltaY;
141 m[14] = -(near + far) / deltaZ;
145 } // unnamed namespace
147 const Dali::Camera::Type Camera::DEFAULT_TYPE( Dali::Camera::FREE_LOOK );
148 const Dali::Camera::ProjectionMode Camera::DEFAULT_MODE( Dali::Camera::PERSPECTIVE_PROJECTION );
149 const bool Camera::DEFAULT_INVERT_Y_AXIS( false );
150 const float Camera::DEFAULT_FIELD_OF_VIEW( 45.0f*(Math::PI/180.0f) );
151 const float Camera::DEFAULT_ASPECT_RATIO( 4.0f/3.0f );
152 const float Camera::DEFAULT_LEFT_CLIPPING_PLANE(-240.0f);
153 const float Camera::DEFAULT_RIGHT_CLIPPING_PLANE(240.0f);
154 const float Camera::DEFAULT_TOP_CLIPPING_PLANE(-400.0f);
155 const float Camera::DEFAULT_BOTTOM_CLIPPING_PLANE(400.0f);
156 const float Camera::DEFAULT_NEAR_CLIPPING_PLANE( 800.0f ); // default height of the screen
157 const float Camera::DEFAULT_FAR_CLIPPING_PLANE( DEFAULT_NEAR_CLIPPING_PLANE + 2.f * DEFAULT_NEAR_CLIPPING_PLANE );
158 const Vector3 Camera::DEFAULT_TARGET_POSITION( 0.0f, 0.0f, 0.0f );
162 : mUpdateViewFlag( UPDATE_COUNT ),
163 mUpdateProjectionFlag( UPDATE_COUNT ),
165 mType( DEFAULT_TYPE ),
166 mProjectionMode( DEFAULT_MODE ),
167 mInvertYAxis( DEFAULT_INVERT_Y_AXIS ),
168 mFieldOfView( DEFAULT_FIELD_OF_VIEW ),
169 mAspectRatio( DEFAULT_ASPECT_RATIO ),
170 mLeftClippingPlane( DEFAULT_LEFT_CLIPPING_PLANE ),
171 mRightClippingPlane( DEFAULT_RIGHT_CLIPPING_PLANE ),
172 mTopClippingPlane( DEFAULT_TOP_CLIPPING_PLANE ),
173 mBottomClippingPlane( DEFAULT_BOTTOM_CLIPPING_PLANE ),
174 mNearClippingPlane( DEFAULT_NEAR_CLIPPING_PLANE ),
175 mFarClippingPlane( DEFAULT_FAR_CLIPPING_PLANE ),
176 mTargetPosition( DEFAULT_TARGET_POSITION ),
179 mInverseViewProjection( Matrix::IDENTITY )
183 Camera* Camera::New()
188 Camera::~Camera() = default;
190 void Camera::SetNode( const Node* node )
195 const Node* Camera::GetNode() const
200 void Camera::SetType( Dali::Camera::Type type )
205 void Camera::SetProjectionMode( Dali::Camera::ProjectionMode mode )
207 mProjectionMode = mode;
208 mUpdateProjectionFlag = UPDATE_COUNT;
211 void Camera::SetInvertYAxis( bool invertYAxis )
213 mInvertYAxis = invertYAxis;
214 mUpdateProjectionFlag = UPDATE_COUNT;
217 void Camera::SetFieldOfView( float fieldOfView )
219 mFieldOfView = fieldOfView;
220 mUpdateProjectionFlag = UPDATE_COUNT;
223 void Camera::SetAspectRatio( float aspectRatio )
225 mAspectRatio = aspectRatio;
226 mUpdateProjectionFlag = UPDATE_COUNT;
229 void Camera::SetLeftClippingPlane( float leftClippingPlane )
231 mLeftClippingPlane = leftClippingPlane;
232 mUpdateProjectionFlag = UPDATE_COUNT;
235 void Camera::SetRightClippingPlane( float rightClippingPlane )
237 mRightClippingPlane = rightClippingPlane;
238 mUpdateProjectionFlag = UPDATE_COUNT;
241 void Camera::SetTopClippingPlane( float topClippingPlane )
243 mTopClippingPlane = topClippingPlane;
244 mUpdateProjectionFlag = UPDATE_COUNT;
247 void Camera::SetBottomClippingPlane( float bottomClippingPlane )
249 mBottomClippingPlane = bottomClippingPlane;
250 mUpdateProjectionFlag = UPDATE_COUNT;
253 void Camera::SetNearClippingPlane( float nearClippingPlane )
255 mNearClippingPlane = nearClippingPlane;
256 mUpdateProjectionFlag = UPDATE_COUNT;
259 void Camera::SetFarClippingPlane( float farClippingPlane )
261 mFarClippingPlane = farClippingPlane;
262 mUpdateProjectionFlag = UPDATE_COUNT;
265 void Camera::SetTargetPosition( const Vector3& targetPosition )
267 mTargetPosition = targetPosition;
268 mUpdateViewFlag = UPDATE_COUNT;
273 void VectorReflectedByPlane(Vector4 &out, Vector4 &in, Vector4 &plane)
275 float d = float(2.0) * plane.Dot(in);
276 out.x = static_cast<float>(in.x - plane.x*d);
277 out.y = static_cast<float>(in.y - plane.y*d);
278 out.z = static_cast<float>(in.z - plane.z*d);
279 out.w = static_cast<float>(in.w - plane.w*d);
282 void Camera::AdjustNearPlaneForPerspective( Matrix& perspective, const Vector4& clipPlane )
285 float* v = perspective.AsFloat();
287 q.x = (Sign(clipPlane.x) + v[8]) / v[0];
288 q.y = (Sign(clipPlane.y) + v[9]) / v[5];
290 q.w = (1.0f + v[10]) / v[14];
292 // Calculate the scaled plane vector
293 Vector4 c = clipPlane * (REFLECTION_NORMALIZED_DEVICE_COORDINATE_PARAMETER_A / q.Dot( clipPlane));
295 // Replace the third row of the projection v
298 v[10] = c.z + REFLECTION_NORMALIZED_DEVICE_COORDINATE_PARAMETER_D;
302 void Camera::SetReflectByPlane( const Vector4& plane )
304 float* v = mReflectionMtx.AsFloat();
305 float _2ab = -2.0f * plane.x * plane.y;
306 float _2ac = -2.0f * plane.x * plane.z;
307 float _2bc = -2.0f * plane.y * plane.z;
309 v[0] = 1.0f - 2.0f * plane.x * plane.x;
315 v[5] = 1.0f - 2.0f * plane.y * plane.y;
321 v[10] = 1.0f - 2.0f * plane.z * plane.z;
324 v[12] = - 2 * plane.x * plane.w;
325 v[13] = - 2 * plane.y * plane.w;
326 v[14] = - 2 * plane.z * plane.w;
329 mUseReflection = true;
330 mReflectionPlane = plane;
331 mUpdateViewFlag = UPDATE_COUNT;
334 const Matrix& Camera::GetProjectionMatrix( BufferIndex bufferIndex ) const
336 return mProjectionMatrix[ bufferIndex ];
339 const Matrix& Camera::GetViewMatrix( BufferIndex bufferIndex ) const
341 return mViewMatrix[ bufferIndex ];
344 const Matrix& Camera::GetInverseViewProjectionMatrix( BufferIndex bufferIndex ) const
346 return mInverseViewProjection[ bufferIndex ];
349 const PropertyInputImpl* Camera::GetProjectionMatrix() const
351 return &mProjectionMatrix;
354 const PropertyInputImpl* Camera::GetViewMatrix() const
359 void Camera::Update( BufferIndex updateBufferIndex )
361 // if owning node has changes in world position we need to update camera for next 2 frames
362 if( mNode->IsLocalMatrixDirty() )
364 mUpdateViewFlag = UPDATE_COUNT;
366 if( mNode->GetDirtyFlags() & NodePropertyFlags::VISIBLE )
368 // If the visibility changes, the projection matrix needs to be re-calculated.
369 // It may happen the first time an actor is rendered it's rendered only once and becomes invisible,
370 // in the following update the node will be skipped leaving the projection matrix (double buffered)
371 // with the Identity.
372 mUpdateProjectionFlag = UPDATE_COUNT;
375 // if either matrix changed, we need to recalculate the inverse matrix for hit testing to work
376 uint32_t viewUpdateCount = UpdateViewMatrix( updateBufferIndex );
377 uint32_t projectionUpdateCount = UpdateProjection( updateBufferIndex );
379 // if model or view matrix changed we need to either recalculate the inverse VP or copy previous
380 if( viewUpdateCount > COPY_PREVIOUS_MATRIX || projectionUpdateCount > COPY_PREVIOUS_MATRIX )
382 // either has actually changed so recalculate
383 Matrix::Multiply( mInverseViewProjection[ updateBufferIndex ], mViewMatrix[ updateBufferIndex ], mProjectionMatrix[ updateBufferIndex ] );
384 UpdateFrustum( updateBufferIndex );
386 // ignore the error, if the view projection is incorrect (non inversible) then you will have tough times anyways
387 static_cast< void >( mInverseViewProjection[ updateBufferIndex ].Invert() );
389 else if( viewUpdateCount == COPY_PREVIOUS_MATRIX || projectionUpdateCount == COPY_PREVIOUS_MATRIX )
391 // neither has actually changed, but we might copied previous frames value so need to
392 // copy the previous inverse and frustum as well
393 mInverseViewProjection[updateBufferIndex] = mInverseViewProjection[updateBufferIndex ? 0 : 1];
394 mFrustum[ updateBufferIndex ] = mFrustum[ updateBufferIndex ? 0 : 1 ];
398 bool Camera::ViewMatrixUpdated()
400 return 0u != mUpdateViewFlag;
403 uint32_t Camera::UpdateViewMatrix( BufferIndex updateBufferIndex )
405 uint32_t retval( mUpdateViewFlag );
406 if( 0u != mUpdateViewFlag )
408 if( COPY_PREVIOUS_MATRIX == mUpdateViewFlag )
410 // The projection matrix was updated in the previous frame; copy it
411 mViewMatrix.CopyPrevious( updateBufferIndex );
413 else // UPDATE_COUNT == mUpdateViewFlag
417 // camera orientation taken from node - i.e. look in abitrary, unconstrained direction
418 case Dali::Camera::FREE_LOOK:
420 Matrix& viewMatrix = mViewMatrix.Get( updateBufferIndex );
421 viewMatrix = mNode->GetWorldMatrix( updateBufferIndex );
425 const Matrix& owningNodeMatrix( mNode->GetWorldMatrix( updateBufferIndex ) );
426 Vector3 position{}, scale{};
427 Quaternion orientation{};
428 owningNodeMatrix.GetTransformComponents( position, orientation, scale );
429 mReflectionEye = position;
430 mUseReflectionClip = true;
432 Matrix& viewMatrix = mViewMatrix.Get( updateBufferIndex );
433 Matrix oldViewMatrix( viewMatrix );
434 Matrix::Multiply(viewMatrix, oldViewMatrix, mReflectionMtx);
438 mViewMatrix.SetDirty( updateBufferIndex );
442 // camera orientation constrained to look at a target
443 case Dali::Camera::LOOK_AT_TARGET:
445 const Matrix& owningNodeMatrix( mNode->GetWorldMatrix( updateBufferIndex ) );
446 Vector3 position, scale;
447 Quaternion orientation;
448 owningNodeMatrix.GetTransformComponents( position, orientation, scale );
449 Matrix& viewMatrix = mViewMatrix.Get( updateBufferIndex );
453 Vector3 up = orientation.Rotate( Vector3::YAXIS );
454 Vector4 position4 = Vector4(position);
455 Vector4 target4 = Vector4(mTargetPosition);
456 Vector4 up4 = Vector4(up);
460 Vector3 positionNew3;
461 Vector3 targetNewVector3;
465 VectorReflectedByPlane(positionNew, position4, mReflectionPlane);
466 VectorReflectedByPlane(targetNew, target4, mReflectionPlane);
467 VectorReflectedByPlane(upNew, up4, mReflectionPlane);
469 positionNew3 = Vector3(positionNew);
470 targetNewVector3 = Vector3(targetNew);
471 upNew3 = Vector3(upNew);
472 LookAt(viewMatrix, positionNew3, targetNewVector3, upNew3 );
474 Matrix oldViewMatrix( viewMatrix );
476 tmp.SetIdentityAndScale(Vector3(-1.0, 1.0,1.0));
477 Matrix::Multiply(viewMatrix, oldViewMatrix, tmp);
479 mReflectionEye = positionNew;
480 mUseReflectionClip = true;
484 LookAt( viewMatrix, position, mTargetPosition, orientation.Rotate( Vector3::YAXIS ) );
486 mViewMatrix.SetDirty( updateBufferIndex );
496 void Camera::UpdateFrustum( BufferIndex updateBufferIndex, bool normalize )
499 // Extract the clip matrix planes
501 Matrix::Multiply( clipMatrix, mViewMatrix[ updateBufferIndex ], mProjectionMatrix[ updateBufferIndex ] );
503 const float* cm = clipMatrix.AsFloat();
504 FrustumPlanes& planes = mFrustum[ updateBufferIndex ];
507 planes.mPlanes[ 0 ].mNormal.x = cm[ 3 ] + cm[ 0 ]; // column 4 + column 1
508 planes.mPlanes[ 0 ].mNormal.y = cm[ 7 ] + cm[ 4 ];
509 planes.mPlanes[ 0 ].mNormal.z = cm[ 11 ] + cm[ 8 ];
510 planes.mPlanes[ 0 ].mDistance = cm[ 15 ] + cm[ 12 ];
513 planes.mPlanes[ 1 ].mNormal.x = cm[ 3 ] - cm[ 0 ]; // column 4 - column 1
514 planes.mPlanes[ 1 ].mNormal.y = cm[ 7 ] - cm[ 4 ];
515 planes.mPlanes[ 1 ].mNormal.z = cm[ 11 ] - cm[ 8 ];
516 planes.mPlanes[ 1 ].mDistance = cm[ 15 ] - cm[ 12 ];
519 planes.mPlanes[ 2 ].mNormal.x = cm[ 3 ] + cm[ 1 ]; // column 4 + column 2
520 planes.mPlanes[ 2 ].mNormal.y = cm[ 7 ] + cm[ 5 ];
521 planes.mPlanes[ 2 ].mNormal.z = cm[ 11 ] + cm[ 9 ];
522 planes.mPlanes[ 2 ].mDistance = cm[ 15 ] + cm[ 13 ];
525 planes.mPlanes[ 3 ].mNormal.x = cm[ 3 ] - cm[ 1 ]; // column 4 - column 2
526 planes.mPlanes[ 3 ].mNormal.y = cm[ 7 ] - cm[ 5 ];
527 planes.mPlanes[ 3 ].mNormal.z = cm[ 11 ] - cm[ 9 ];
528 planes.mPlanes[ 3 ].mDistance = cm[ 15 ] - cm[ 13 ];
531 planes.mPlanes[ 4 ].mNormal.x = cm[ 3 ] + cm[ 2 ]; // column 4 + column 3
532 planes.mPlanes[ 4 ].mNormal.y = cm[ 7 ] + cm[ 6 ];
533 planes.mPlanes[ 4 ].mNormal.z = cm[ 11 ] + cm[ 10 ];
534 planes.mPlanes[ 4 ].mDistance = cm[ 15 ] + cm[ 14 ];
537 planes.mPlanes[ 5 ].mNormal.x = cm[ 3 ] - cm[ 2 ]; // column 4 - column 3
538 planes.mPlanes[ 5 ].mNormal.y = cm[ 7 ] - cm[ 6 ];
539 planes.mPlanes[ 5 ].mNormal.z = cm[ 11 ] - cm[ 10 ];
540 planes.mPlanes[ 5 ].mDistance = cm[ 15 ] - cm[ 14 ];
544 for ( uint32_t i = 0; i < 6; ++i )
546 // Normalize planes to ensure correct bounding distance checking
547 Plane& plane = planes.mPlanes[ i ];
548 float l = 1.0f / plane.mNormal.Length();
550 plane.mDistance *= l;
552 planes.mSign[i] = Vector3( Sign(plane.mNormal.x), Sign(plane.mNormal.y), Sign(plane.mNormal.z) );
557 for ( uint32_t i = 0; i < 6; ++i )
559 planes.mSign[i] = Vector3( Sign(planes.mPlanes[ i ].mNormal.x), Sign(planes.mPlanes[ i ].mNormal.y), Sign(planes.mPlanes[ i ].mNormal.z) );
562 mFrustum[ updateBufferIndex ? 0 : 1 ] = planes;
565 bool Camera::CheckSphereInFrustum( BufferIndex bufferIndex, const Vector3& origin, float radius )
567 const FrustumPlanes& planes = mFrustum[ bufferIndex ];
568 for ( uint32_t i = 0; i < 6; ++i )
570 if ( ( planes.mPlanes[ i ].mDistance + planes.mPlanes[ i ].mNormal.Dot( origin ) ) < -radius )
578 bool Camera::CheckAABBInFrustum( BufferIndex bufferIndex, const Vector3& origin, const Vector3& halfExtents )
580 const FrustumPlanes& planes = mFrustum[ bufferIndex ];
581 for ( uint32_t i = 0; i < 6; ++i )
583 if( planes.mPlanes[ i ].mNormal.Dot( origin + (halfExtents * planes.mSign[i]) ) > -(planes.mPlanes[ i ].mDistance) )
593 uint32_t Camera::UpdateProjection( BufferIndex updateBufferIndex )
595 uint32_t retval( mUpdateProjectionFlag );
596 // Early-exit if no update required
597 if ( 0u != mUpdateProjectionFlag )
599 if ( COPY_PREVIOUS_MATRIX == mUpdateProjectionFlag )
601 // The projection matrix was updated in the previous frame; copy it
602 mProjectionMatrix.CopyPrevious( updateBufferIndex );
604 else // UPDATE_COUNT == mUpdateProjectionFlag
606 switch( mProjectionMode )
608 case Dali::Camera::PERSPECTIVE_PROJECTION:
610 Matrix &projectionMatrix = mProjectionMatrix.Get( updateBufferIndex );
611 Perspective( projectionMatrix,
618 //need to apply custom clipping plane
619 if (mUseReflectionClip)
621 Matrix& viewMatrix = mViewMatrix.Get( updateBufferIndex );
622 Matrix viewInv = viewMatrix;
626 Dali::Vector4 adjReflectPlane = mReflectionPlane;
627 float d = mReflectionPlane.Dot(mReflectionEye);
630 adjReflectPlane.w = -adjReflectPlane.w;
633 Vector4 customClipping = viewInv * adjReflectPlane;
634 AdjustNearPlaneForPerspective(projectionMatrix, customClipping);
639 float* vZ = matZ.AsFloat();
641 Matrix::Multiply(projectionMatrix, projectionMatrix , matZ);
645 case Dali::Camera::ORTHOGRAPHIC_PROJECTION:
647 Matrix &projectionMatrix = mProjectionMatrix.Get( updateBufferIndex );
648 Orthographic( projectionMatrix,
649 mLeftClippingPlane, mRightClippingPlane,
650 mBottomClippingPlane, mTopClippingPlane,
651 mNearClippingPlane, mFarClippingPlane,
657 mProjectionMatrix.SetDirty( updateBufferIndex );
659 --mUpdateProjectionFlag;
664 } // namespace SceneGraph
666 } // namespace Internal