2 * Copyright (c) 2020 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/event/actors/camera-actor-impl.h>
23 #include <cstring> // for strcmp
26 #include <dali/public-api/object/type-registry.h>
27 #include <dali/devel-api/actors/camera-actor-devel.h>
28 #include <dali/internal/event/common/property-helper.h>
29 #include <dali/internal/event/common/projection.h>
30 #include <dali/internal/event/common/thread-local-storage.h>
44 * We want to discourage the use of property strings (minimize string comparisons),
45 * particularly for the default properties.
46 * Name Type writable animatable constraint-input enum for index-checking
48 DALI_PROPERTY_TABLE_BEGIN
49 DALI_PROPERTY( "type", STRING, true, false, true, Dali::CameraActor::Property::TYPE )
50 DALI_PROPERTY( "projectionMode", STRING, true, false, true, Dali::CameraActor::Property::PROJECTION_MODE )
51 DALI_PROPERTY( "fieldOfView", FLOAT, true, false, true, Dali::CameraActor::Property::FIELD_OF_VIEW )
52 DALI_PROPERTY( "aspectRatio", FLOAT, true, false, true, Dali::CameraActor::Property::ASPECT_RATIO )
53 DALI_PROPERTY( "nearPlaneDistance", FLOAT, true, false, true, Dali::CameraActor::Property::NEAR_PLANE_DISTANCE )
54 DALI_PROPERTY( "farPlaneDistance", FLOAT, true, false, true, Dali::CameraActor::Property::FAR_PLANE_DISTANCE )
55 DALI_PROPERTY( "leftPlaneDistance", FLOAT, true, false, true, Dali::CameraActor::Property::LEFT_PLANE_DISTANCE )
56 DALI_PROPERTY( "rightPlaneDistance", FLOAT, true, false, true, Dali::CameraActor::Property::RIGHT_PLANE_DISTANCE )
57 DALI_PROPERTY( "topPlaneDistance", FLOAT, true, false, true, Dali::CameraActor::Property::TOP_PLANE_DISTANCE )
58 DALI_PROPERTY( "bottomPlaneDistance", FLOAT, true, false, true, Dali::CameraActor::Property::BOTTOM_PLANE_DISTANCE )
59 DALI_PROPERTY( "targetPosition", VECTOR3, true, false, true, Dali::CameraActor::Property::TARGET_POSITION )
60 DALI_PROPERTY( "projectionMatrix", MATRIX, false, false, true, Dali::CameraActor::Property::PROJECTION_MATRIX )
61 DALI_PROPERTY( "viewMatrix", MATRIX, false, false, true, Dali::CameraActor::Property::VIEW_MATRIX )
62 DALI_PROPERTY( "invertYAxis", BOOLEAN, true, false, true, Dali::CameraActor::Property::INVERT_Y_AXIS )
63 DALI_PROPERTY_TABLE_END( DEFAULT_DERIVED_ACTOR_PROPERTY_START_INDEX, CameraDefaultProperties )
65 // calculate the far plane distance for a 16bit depth buffer with 4 bits per unit precision
66 void CalculateClippingAndZ( float width, float height, float& nearClippingPlane, float& farClippingPlane, float& cameraZ )
68 nearClippingPlane = std::max( width, height );
69 farClippingPlane = nearClippingPlane + static_cast<float>( 0xFFFF >> 4 );
70 cameraZ = 2.0f * nearClippingPlane;
75 return Dali::CameraActor::New();
78 TypeRegistration mType( typeid( Dali::CameraActor ), typeid( Dali::Actor ), Create, CameraDefaultProperties );
81 * Builds the picking ray in the world reference system from an orthographic camera
82 * The ray origin is the screen coordinate in the near plane translated to a parallel
83 * plane at the camera origin. The ray direction is the direction the camera is facing
84 * (i.e. Z=-1 in view space).
86 void BuildOrthoPickingRay( const Matrix& viewMatrix,
87 const Matrix& projectionMatrix,
88 const Viewport& viewport,
93 float nearPlaneDistance )
95 // inv( modelMatrix ) inv( viewMatrix ) inv( projectionMatrix ) normalize
96 // <----------------- <----------------- <-------------- <-------------
97 // Local World Camera Normalized Screen
98 // reference reference reference clip coordinates
99 // system system system coordinates
100 // -----------------> -----------------> --------------> ------------->
101 // modelMatrix viewMatrix projectionMatrix viewport
103 // Transforms the touch point from the screen reference system to the world reference system.
104 Matrix invViewProjection( false ); // Don't initialize.
105 Matrix::Multiply( invViewProjection, viewMatrix, projectionMatrix );
106 if( !invViewProjection.Invert() )
108 DALI_ASSERT_DEBUG( false );
111 Vector4 near( screenX - static_cast<float>( viewport.x ),
112 static_cast<float>( viewport.height ) - (screenY - static_cast<float>( viewport.y ) ),
114 if( !Unproject( near, invViewProjection, static_cast<float>( viewport.width ), static_cast<float>( viewport.height ), rayOrigin ) )
116 DALI_ASSERT_DEBUG( false );
119 Matrix invView = viewMatrix;
120 if( !invView.Invert() )
122 DALI_ASSERT_DEBUG( false );
125 Vector4 cameraOrigin = invView * Vector4( 0.f, 0.f, 0.f, 1.f );
126 Vector4 nearPlaneOrigin = invView * Vector4( 0.0f, 0.0f, -nearPlaneDistance, 1.0f);
128 // Vector pointing from the camera to the near plane
129 rayDir = cameraOrigin - nearPlaneOrigin;
137 CameraActorPtr CameraActor::New( const Size& size )
139 CameraActorPtr actor( new CameraActor( *CreateNode() ) );
141 // Second-phase construction
144 actor->SetName( "DefaultCamera" );
145 actor->SetPerspectiveProjection( size );
147 // By default Actors face in the positive Z direction in world space
148 // CameraActors should face in the negative Z direction, towards the other actors
149 actor->SetOrientation( Quaternion( Dali::ANGLE_180, Vector3::YAXIS ) );
154 CameraActor::CameraActor( const SceneGraph::Node& node )
155 : Actor( Actor::BASIC, node ),
156 mSceneObject( nullptr ),
157 mTarget( SceneGraph::Camera::DEFAULT_TARGET_POSITION ),
158 mType( SceneGraph::Camera::DEFAULT_TYPE ),
159 mProjectionMode( SceneGraph::Camera::DEFAULT_MODE ),
160 mFieldOfView( SceneGraph::Camera::DEFAULT_FIELD_OF_VIEW ),
161 mAspectRatio( SceneGraph::Camera::DEFAULT_ASPECT_RATIO ),
162 mNearClippingPlane( SceneGraph::Camera::DEFAULT_NEAR_CLIPPING_PLANE ),
163 mFarClippingPlane( SceneGraph::Camera::DEFAULT_FAR_CLIPPING_PLANE ),
164 mLeftClippingPlane( SceneGraph::Camera::DEFAULT_LEFT_CLIPPING_PLANE ),
165 mRightClippingPlane( SceneGraph::Camera::DEFAULT_RIGHT_CLIPPING_PLANE ),
166 mTopClippingPlane( SceneGraph::Camera::DEFAULT_TOP_CLIPPING_PLANE ),
167 mBottomClippingPlane( SceneGraph::Camera::DEFAULT_BOTTOM_CLIPPING_PLANE ),
168 mInvertYAxis( SceneGraph::Camera::DEFAULT_INVERT_Y_AXIS )
172 CameraActor::~CameraActor()
174 if( EventThreadServices::IsCoreRunning() )
176 // Create scene-object and transfer ownership through message
177 RemoveCameraMessage( GetEventThreadServices().GetUpdateManager(), mSceneObject );
181 void CameraActor::OnInitialize()
183 // Create scene-object and keep raw pointer for message passing.
184 SceneGraph::Camera* sceneGraphCamera = SceneGraph::Camera::New();
186 // Store a pointer to this camera node inside the scene-graph camera.
187 sceneGraphCamera->SetNode( &GetNode() );
189 mSceneObject = sceneGraphCamera;
190 OwnerPointer< SceneGraph::Camera > sceneGraphCameraOwner( sceneGraphCamera );
192 // Send message to inform update of this camera (and move ownership).
193 AddCameraMessage( GetEventThreadServices().GetUpdateManager(), sceneGraphCameraOwner );
196 void CameraActor::OnSceneConnectionInternal()
198 // If the canvas size has not been set, then use the size of the scene we've been added to to set up the perspective projection
199 if( ( mCanvasSize.width < Math::MACHINE_EPSILON_1000 ) || ( mCanvasSize.height < Math::MACHINE_EPSILON_1000 ) )
201 SetPerspectiveProjection( GetScene().GetSize() );
205 void CameraActor::SetReflectByPlane(const Vector4& plane) {
206 SceneGraph::Camera* cam = const_cast<SceneGraph::Camera*>(GetCamera());
209 cam->SetReflectByPlane(plane);
213 void CameraActor::SetTarget( const Vector3& target )
215 if( target != mTarget ) // using range epsilon
219 SetTargetPositionMessage( GetEventThreadServices(), *mSceneObject, mTarget );
223 Vector3 CameraActor::GetTarget() const
228 void CameraActor::SetType( Dali::Camera::Type type )
234 // sceneObject is being used in a separate thread; queue a message to set
235 SetTypeMessage( GetEventThreadServices(), *mSceneObject, mType );
239 Dali::Camera::Type CameraActor::GetType() const
244 void CameraActor::SetProjectionMode( Dali::Camera::ProjectionMode mode )
246 if( mode != mProjectionMode )
248 mProjectionMode = mode;
250 // sceneObject is being used in a separate thread; queue a message to set
251 SetProjectionModeMessage( GetEventThreadServices(), *mSceneObject, mProjectionMode );
255 Dali::Camera::ProjectionMode CameraActor::GetProjectionMode() const
257 return mProjectionMode;
260 void CameraActor::SetFieldOfView( float fieldOfView )
262 if( ! Equals( fieldOfView, mFieldOfView ) )
264 mFieldOfView = fieldOfView;
266 // sceneObject is being used in a separate thread; queue a message to set
267 SetFieldOfViewMessage( GetEventThreadServices(), *mSceneObject, mFieldOfView );
271 float CameraActor::GetFieldOfView() const
276 void CameraActor::SetAspectRatio( float aspectRatio )
278 if( ! Equals( aspectRatio, mAspectRatio ) )
280 mAspectRatio = aspectRatio;
282 // sceneObject is being used in a separate thread; queue a message to set
283 SetAspectRatioMessage( GetEventThreadServices(), *mSceneObject, mAspectRatio );
287 float CameraActor::GetAspectRatio() const
292 void CameraActor::SetNearClippingPlane( float nearClippingPlane )
294 if( ! Equals( nearClippingPlane, mNearClippingPlane ) )
296 mNearClippingPlane = nearClippingPlane;
298 // sceneObject is being used in a separate thread; queue a message to set
299 SetNearClippingPlaneMessage( GetEventThreadServices(), *mSceneObject, mNearClippingPlane );
303 float CameraActor::GetNearClippingPlane() const
305 return mNearClippingPlane;
308 void CameraActor::SetFarClippingPlane( float farClippingPlane )
310 if( ! Equals( farClippingPlane, mFarClippingPlane ) )
312 mFarClippingPlane = farClippingPlane;
314 // sceneObject is being used in a separate thread; queue a message to set
315 SetFarClippingPlaneMessage( GetEventThreadServices(), *mSceneObject, mFarClippingPlane );
319 float CameraActor::GetFarClippingPlane() const
321 return mFarClippingPlane;
324 void CameraActor::SetLeftClippingPlane( float leftClippingPlane )
326 if( ! Equals( leftClippingPlane, mLeftClippingPlane ) )
328 mLeftClippingPlane = leftClippingPlane;
330 // sceneObject is being used in a separate thread; queue a message to set
331 SetLeftClippingPlaneMessage( GetEventThreadServices(), *mSceneObject, mLeftClippingPlane );
335 void CameraActor::SetRightClippingPlane( float rightClippingPlane )
337 if( ! Equals( rightClippingPlane, mRightClippingPlane ) )
339 mRightClippingPlane = rightClippingPlane;
341 // sceneObject is being used in a separate thread; queue a message to set
342 SetRightClippingPlaneMessage( GetEventThreadServices(), *mSceneObject, mRightClippingPlane );
346 void CameraActor::SetTopClippingPlane( float topClippingPlane )
348 if( ! Equals( topClippingPlane, mTopClippingPlane ) )
350 mTopClippingPlane = topClippingPlane;
352 // sceneObject is being used in a separate thread; queue a message to set
353 SetTopClippingPlaneMessage( GetEventThreadServices(), *mSceneObject, mTopClippingPlane );
357 void CameraActor::SetBottomClippingPlane( float bottomClippingPlane )
359 if( ! Equals( bottomClippingPlane, mBottomClippingPlane ) )
361 mBottomClippingPlane = bottomClippingPlane;
363 // sceneObject is being used in a separate thread; queue a message to set
364 SetBottomClippingPlaneMessage( GetEventThreadServices(), *mSceneObject, mBottomClippingPlane );
368 void CameraActor::SetInvertYAxis(bool invertYAxis)
370 if( invertYAxis != mInvertYAxis )
372 mInvertYAxis = invertYAxis;
374 // sceneObject is being used in a separate thread; queue a message to set
375 SetInvertYAxisMessage( GetEventThreadServices(), *mSceneObject, mInvertYAxis );
379 bool CameraActor::GetInvertYAxis() const
384 void CameraActor::SetPerspectiveProjection( const Size& size )
388 if( ( size.width < Math::MACHINE_EPSILON_1000 ) || ( size.height < Math::MACHINE_EPSILON_1000 ) )
390 // If the size given is invalid, i.e. ZERO, then check if we've been added to a scene
393 // We've been added to a scene already, set the canvas size to the scene's size
394 mCanvasSize = GetScene().GetSize();
398 // We've not been added to a scene yet, so just return.
399 // We'll set the canvas size when we get added to a scene later
404 float width = mCanvasSize.width;
405 float height = mCanvasSize.height;
407 float nearClippingPlane;
408 float farClippingPlane;
410 CalculateClippingAndZ( width, height, nearClippingPlane, farClippingPlane, cameraZ );
412 // calculate the position of the camera to have the desired aspect ratio
413 const float fieldOfView = 2.0f * std::atan( height * 0.5f / cameraZ );
415 // unless it is too small, we want at least as much space to the back as we have torwards the front
416 const float minClippingFarPlane = 2.f * nearClippingPlane;
417 if ( farClippingPlane < minClippingFarPlane )
419 farClippingPlane = minClippingFarPlane;
422 const float aspectRatio = width / height;
424 // sceneObject is being used in a separate thread; queue a message to set
425 SetProjectionMode(Dali::Camera::PERSPECTIVE_PROJECTION);
426 SetFieldOfView( fieldOfView );
427 SetNearClippingPlane( nearClippingPlane );
428 SetFarClippingPlane( farClippingPlane );
429 SetAspectRatio( aspectRatio );
434 void CameraActor::SetOrthographicProjection( const Vector2& size )
436 // Choose near, far and Z parameters to match the SetPerspectiveProjection above.
437 float nearClippingPlane;
438 float farClippingPlane;
440 CalculateClippingAndZ( size.width, size.height, nearClippingPlane, farClippingPlane, cameraZ );
441 SetOrthographicProjection( -size.x*0.5f, size.x*0.5f, size.y*0.5f, -size.y*0.5f,
442 nearClippingPlane, farClippingPlane );
446 void CameraActor::SetOrthographicProjection( float left, float right, float top, float bottom, float near, float far )
448 SetLeftClippingPlane( left );
449 SetRightClippingPlane( right );
450 SetTopClippingPlane( top );
451 SetBottomClippingPlane( bottom );
452 SetNearClippingPlane( near );
453 SetFarClippingPlane( far );
454 SetProjectionMode( Dali::Camera::ORTHOGRAPHIC_PROJECTION );
457 bool CameraActor::BuildPickingRay( const Vector2& screenCoordinates,
458 const Viewport& viewport,
460 Vector4& rayDirection )
463 if( mProjectionMode == Dali::Camera::PERSPECTIVE_PROJECTION )
465 // Build a picking ray in the world reference system.
466 // ray starts from the camera world position
467 rayOrigin = GetNode().GetWorldMatrix(0).GetTranslation();
470 // Transform the touch point from the screen coordinate system to the world coordinates system.
471 Vector4 near( screenCoordinates.x - static_cast<float>(viewport.x),
472 static_cast<float>( viewport.height ) - (screenCoordinates.y - static_cast<float>( viewport.y ) ),
474 const Matrix& inverseViewProjection = mSceneObject->GetInverseViewProjectionMatrix( GetEventThreadServices().GetEventBufferIndex() );
475 success = Unproject( near, inverseViewProjection, static_cast<float>( viewport.width ), static_cast<float>( viewport.height ), near );
477 // Compute the ray's director vector.
478 rayDirection.x = near.x - rayOrigin.x;
479 rayDirection.y = near.y - rayOrigin.y;
480 rayDirection.z = near.z - rayOrigin.z;
481 rayDirection.Normalize();
482 rayDirection.w = 1.f;
486 float nearPlaneDistance = GetNearClippingPlane();
487 BuildOrthoPickingRay( GetViewMatrix(),
488 GetProjectionMatrix(),
489 viewport, screenCoordinates.x,
499 const Matrix& CameraActor::GetViewMatrix() const
503 return mSceneObject->GetViewMatrix( GetEventThreadServices().GetEventBufferIndex() );
507 return Matrix::IDENTITY;
511 const Matrix& CameraActor::GetProjectionMatrix() const
515 return mSceneObject->GetProjectionMatrix( GetEventThreadServices().GetEventBufferIndex() );
519 return Matrix::IDENTITY;
522 const SceneGraph::Camera* CameraActor::GetCamera() const
527 void CameraActor::RotateProjection( int rotationAngle )
529 // sceneObject is being used in a separate thread; queue a message to set
530 RotateProjectionMessage( GetEventThreadServices(), *mSceneObject, rotationAngle );
533 void CameraActor::SetDefaultProperty( Property::Index index, const Property::Value& propertyValue )
535 if(index < DEFAULT_ACTOR_PROPERTY_MAX_COUNT)
537 Actor::SetDefaultProperty(index, propertyValue);
543 case Dali::CameraActor::Property::TYPE:
545 std::string s( propertyValue.Get<std::string>() );
546 if(s == "LOOK_AT_TARGET")
548 SetType( Dali::Camera::LOOK_AT_TARGET );
550 else if(s == "FREE_LOOK")
552 SetType( Dali::Camera::FREE_LOOK );
556 case Dali::CameraActor::Property::PROJECTION_MODE:
558 std::string s( propertyValue.Get<std::string>() );
559 if( s == "PERSPECTIVE_PROJECTION" )
561 SetProjectionMode( Dali::Camera::PERSPECTIVE_PROJECTION );
563 else if( s == "ORTHOGRAPHIC_PROJECTION" )
565 SetProjectionMode( Dali::Camera::ORTHOGRAPHIC_PROJECTION );
569 case Dali::CameraActor::Property::FIELD_OF_VIEW:
571 SetFieldOfView( propertyValue.Get<float>() ); // set to 0 in case property is not float
574 case Dali::CameraActor::Property::ASPECT_RATIO:
576 SetAspectRatio( propertyValue.Get<float>() ); // set to 0 in case property is not float
579 case Dali::CameraActor::Property::NEAR_PLANE_DISTANCE:
581 SetNearClippingPlane( propertyValue.Get<float>() ); // set to 0 in case property is not float
584 case Dali::CameraActor::Property::FAR_PLANE_DISTANCE:
586 SetFarClippingPlane( propertyValue.Get<float>() ); // set to 0 in case property is not float
589 case Dali::CameraActor::Property::LEFT_PLANE_DISTANCE:
591 SetLeftClippingPlane( propertyValue.Get<float>() ); // set to 0 in case property is not float
594 case Dali::CameraActor::Property::RIGHT_PLANE_DISTANCE:
596 SetRightClippingPlane( propertyValue.Get<float>() ); // set to 0 in case property is not float
599 case Dali::CameraActor::Property::TOP_PLANE_DISTANCE:
601 SetTopClippingPlane( propertyValue.Get<float>() ); // set to 0 in case property is not float
604 case Dali::CameraActor::Property::BOTTOM_PLANE_DISTANCE:
606 SetBottomClippingPlane( propertyValue.Get<float>() ); // set to 0 in case property is not float
609 case Dali::CameraActor::Property::TARGET_POSITION:
611 SetTarget( propertyValue.Get<Vector3>() ); // set to 0 in case property is not Vector3
614 case Dali::CameraActor::Property::PROJECTION_MATRIX:
616 DALI_LOG_WARNING( "projection-matrix is read-only\n" );
619 case Dali::CameraActor::Property::VIEW_MATRIX:
621 DALI_LOG_WARNING( "view-matrix is read-only\n" );
624 case Dali::CameraActor::Property::INVERT_Y_AXIS:
626 SetInvertYAxis( propertyValue.Get<bool>() ); // set to false in case property is not bool
629 case Dali::DevelCameraActor::Property::REFLECTION_PLANE:
631 SetReflectByPlane( propertyValue.Get<Vector4>() );
637 DALI_LOG_WARNING( "Unknown property (%d)\n", index );
645 Property::Value CameraActor::GetDefaultProperty( Property::Index index ) const
648 if( index < DEFAULT_ACTOR_PROPERTY_MAX_COUNT )
650 ret = Actor::GetDefaultProperty(index);
656 case Dali::CameraActor::Property::TYPE:
658 if( Dali::Camera::LOOK_AT_TARGET == mType )
660 ret = "LOOK_AT_TARGET";
662 else if( Dali::Camera::FREE_LOOK == mType )
668 case Dali::CameraActor::Property::PROJECTION_MODE:
670 if( Dali::Camera::PERSPECTIVE_PROJECTION == mProjectionMode )
672 ret = "PERSPECTIVE_PROJECTION";
674 else if( Dali::Camera::ORTHOGRAPHIC_PROJECTION == mProjectionMode )
676 ret = "ORTHOGRAPHIC_PROJECTION";
680 case Dali::CameraActor::Property::FIELD_OF_VIEW:
685 case Dali::CameraActor::Property::ASPECT_RATIO:
690 case Dali::CameraActor::Property::NEAR_PLANE_DISTANCE:
692 ret = mNearClippingPlane;
695 case Dali::CameraActor::Property::FAR_PLANE_DISTANCE:
697 ret = mFarClippingPlane;
700 case Dali::CameraActor::Property::LEFT_PLANE_DISTANCE:
702 ret = mLeftClippingPlane;
705 case Dali::CameraActor::Property::RIGHT_PLANE_DISTANCE:
707 ret = mRightClippingPlane;
710 case Dali::CameraActor::Property::TOP_PLANE_DISTANCE:
712 ret = mTopClippingPlane;
715 case Dali::CameraActor::Property::BOTTOM_PLANE_DISTANCE:
717 ret = mBottomClippingPlane;
720 case Dali::CameraActor::Property::TARGET_POSITION:
725 case Dali::CameraActor::Property::PROJECTION_MATRIX:
727 ret = GetProjectionMatrix(); // Only on scene-graph
730 case Dali::CameraActor::Property::VIEW_MATRIX:
732 ret = GetViewMatrix(); // Only on scene-graph
735 case Dali::CameraActor::Property::INVERT_Y_AXIS:
746 Property::Value CameraActor::GetDefaultPropertyCurrentValue( Property::Index index ) const
749 if( index < DEFAULT_ACTOR_PROPERTY_MAX_COUNT )
751 ret = Actor::GetDefaultPropertyCurrentValue(index);
755 ret = GetDefaultProperty( index ); // Most are event-side properties, the scene-graph properties are only on the scene-graph
761 const PropertyInputImpl* CameraActor::GetSceneObjectInputProperty( Property::Index index ) const
763 const PropertyInputImpl* property( nullptr );
767 case Dali::CameraActor::Property::PROJECTION_MATRIX:
769 property = mSceneObject->GetProjectionMatrix();
772 case Dali::CameraActor::Property::VIEW_MATRIX:
774 property = mSceneObject->GetViewMatrix();
777 // no default on purpose as we chain method up to actor
781 property = Actor::GetSceneObjectInputProperty( index );
787 } // namespace Internal