/*
- * Copyright (c) 2020 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
// All the properties should still be the default values
// Defaults taken from scene-graph-camera.cpp
- DALI_TEST_EQUALS(4.0f / 3.0f, actor.GetProperty(CameraActor::Property::ASPECT_RATIO).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
+ DALI_TEST_EQUALS(800.0f / 480.0f, actor.GetProperty(CameraActor::Property::ASPECT_RATIO).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
DALI_TEST_EQUALS(45.0f * (Math::PI / 180.0f), actor.GetProperty(CameraActor::Property::FIELD_OF_VIEW).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
DALI_TEST_EQUALS(800.0f, actor.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
DALI_TEST_EQUALS(3.0f * 800.0f, actor.GetProperty(CameraActor::Property::FAR_PLANE_DISTANCE).Get<float>(), FLOAT_EPSILON, TEST_LOCATION);
}
END_TEST;
}
+
+int UtcDaliCameraActorNewDefaultOrthogonalProjection01(void)
+{
+ TestApplication application;
+ tet_infoline("Test that changing to orthogonal projection and then adding to scene calculates the right defaults");
+
+ CameraActor actor = CameraActor::New();
+ DALI_TEST_CHECK(actor);
+
+ actor.SetProjectionMode(Camera::ORTHOGRAPHIC_PROJECTION);
+ application.GetScene().Add(actor);
+
+ // Test application screen size is 480x800
+ // Check that the properties match to that screen size
+ float value;
+ actor.GetProperty(CameraActor::Property::ASPECT_RATIO).Get(value);
+ DALI_TEST_EQUALS(800.0f / 480.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ actor.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(800.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ actor.GetProperty(CameraActor::Property::FAR_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(4895.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ actor.GetProperty(CameraActor::Property::LEFT_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(-240.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+ actor.GetProperty(CameraActor::Property::RIGHT_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(240.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ actor.GetProperty(CameraActor::Property::TOP_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(400.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+ actor.GetProperty(CameraActor::Property::BOTTOM_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(-400.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ DALI_TEST_EQUALS(1600.0f, actor.GetProperty(Actor::Property::POSITION_Z).Get<float>(), TEST_LOCATION);
+
+ DALI_TEST_EQUALS(actor.GetProjectionMode(), Dali::Camera::ORTHOGRAPHIC_PROJECTION, TEST_LOCATION);
+ END_TEST;
+}
+
+int UtcDaliCameraActorNewDefaultOrthogonalProjection02(void)
+{
+ TestApplication application;
+ tet_infoline("Test that changing to orthogonal projection and then adding to scene calculates the right defaults");
+
+ CameraActor actor = CameraActor::New();
+ DALI_TEST_CHECK(actor);
+
+ actor.SetOrthographicProjection(Vector2::ZERO);
+ DALI_TEST_EQUALS(actor.GetProjectionMode(), Dali::Camera::ORTHOGRAPHIC_PROJECTION, TEST_LOCATION);
+ application.GetScene().Add(actor);
+
+ // Test application screen size is 480x800
+ // Check that the properties match to that screen size
+ float value;
+ actor.GetProperty(CameraActor::Property::ASPECT_RATIO).Get(value);
+ DALI_TEST_EQUALS(800.0f / 480.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ actor.GetProperty(CameraActor::Property::NEAR_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(800.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ actor.GetProperty(CameraActor::Property::FAR_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(4895.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ actor.GetProperty(CameraActor::Property::LEFT_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(-240.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+ actor.GetProperty(CameraActor::Property::RIGHT_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(240.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ actor.GetProperty(CameraActor::Property::TOP_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(400.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+ actor.GetProperty(CameraActor::Property::BOTTOM_PLANE_DISTANCE).Get(value);
+ DALI_TEST_EQUALS(-400.0f, value, FLOAT_EPSILON, TEST_LOCATION);
+
+ DALI_TEST_EQUALS(1600.0f, actor.GetProperty(Actor::Property::POSITION_Z).Get<float>(), TEST_LOCATION);
+
+ DALI_TEST_EQUALS(actor.GetProjectionMode(), Dali::Camera::ORTHOGRAPHIC_PROJECTION, TEST_LOCATION);
+ END_TEST;
+}
+
+// Add tests for culling:
+// add large actor just outside canvas, & rotate it 45% - should still be rendered
+// Rotate back to 0, should be culled.
+
+int UtcDaliCameraActorCulling01(void)
+{
+ TestApplication application;
+ auto& gfx = application.GetGraphicsController();
+
+ tet_infoline("Create a renderable actor and position it slightly to the left of the scene");
+ tet_infoline("The actor should not be rendered");
+
+ Actor a = CreateRenderableActor(CreateTexture(TextureType::TEXTURE_2D, Pixel::Format::RGBA8888, 200, 200));
+
+ a[Actor::Property::PARENT_ORIGIN] = ParentOrigin::CENTER_LEFT;
+ a[Actor::Property::ANCHOR_POINT] = ParentOrigin::CENTER_RIGHT;
+ a[Actor::Property::POSITION] = Vector3(-10.0f, 0.0f, 0.0f);
+
+ application.GetScene().Add(a);
+
+ application.SendNotification();
+ application.Render();
+
+ auto& cmdStack = gfx.mCommandBufferCallStack;
+ DALI_TEST_CHECK(!cmdStack.FindMethod("Draw") && !cmdStack.FindMethod("DrawIndexed"));
+
+ tet_infoline("Rotate the actor 45 degrees, the actor should now be rendered");
+ a[Actor::Property::ORIENTATION] = Quaternion(Dali::ANGLE_45, Vector3::ZAXIS);
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_CHECK(cmdStack.FindMethod("Draw") || cmdStack.FindMethod("DrawIndexed"));
+
+ END_TEST;
+}
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
void CameraActor::OnSceneConnectionInternal()
{
- // 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
+ // If the canvas size has not been set, then use the size of the scene to which we've been added
+ // in order to set up the current projection
if((mCanvasSize.width < Math::MACHINE_EPSILON_1000) || (mCanvasSize.height < Math::MACHINE_EPSILON_1000))
{
- SetPerspectiveProjection(GetScene().GetSize());
+ if(mProjectionMode == Dali::Camera::ORTHOGRAPHIC_PROJECTION)
+ {
+ SetOrthographicProjection(GetScene().GetSize());
+ }
+ else //if(mProjectionMode == Dali::Camera::PERSPECTIVE_PROJECTION)
+ {
+ SetPerspectiveProjection(GetScene().GetSize());
+ }
}
}
void CameraActor::SetReflectByPlane(const Vector4& plane)
{
- SceneGraph::Camera* cam = const_cast<SceneGraph::Camera*>(GetCamera());
- if(cam)
+ if(mSceneObject)
{
- cam->SetReflectByPlane(plane);
+ SetReflectByPlaneMessage(GetEventThreadServices(), *mSceneObject, plane);
}
}
void CameraActor::SetPerspectiveProjection(const Size& size)
{
+ SetProjectionMode(Dali::Camera::PERSPECTIVE_PROJECTION);
mCanvasSize = size;
if((size.width < Math::MACHINE_EPSILON_1000) || (size.height < Math::MACHINE_EPSILON_1000))
const float aspectRatio = width / height;
// sceneObject is being used in a separate thread; queue a message to set
- SetProjectionMode(Dali::Camera::PERSPECTIVE_PROJECTION);
SetFieldOfView(fieldOfView);
SetNearClippingPlane(nearClippingPlane);
SetFarClippingPlane(farClippingPlane);
+ SetLeftClippingPlane(width * -0.5f);
+ SetRightClippingPlane(width * 0.5f);
+ SetTopClippingPlane(height * 0.5f); // Top is +ve to keep consistency with orthographic values
+ SetBottomClippingPlane(height * -0.5f); // Bottom is -ve to keep consistency with orthographic values
SetAspectRatio(aspectRatio);
SetZ(cameraZ);
}
void CameraActor::SetOrthographicProjection(const Vector2& size)
{
+ SetProjectionMode(Dali::Camera::ORTHOGRAPHIC_PROJECTION);
+ mCanvasSize = size;
+
+ if((size.width < Math::MACHINE_EPSILON_1000) || (size.height < Math::MACHINE_EPSILON_1000))
+ {
+ // If the size given is invalid, i.e. ZERO, then check if we've been added to a scene
+ if(OnScene())
+ {
+ // We've been added to a scene already, set the canvas size to the scene's size
+ mCanvasSize = GetScene().GetSize();
+ }
+ else
+ {
+ // We've not been added to a scene yet, so just return.
+ // We'll set the canvas size when we get added to a scene later
+ return;
+ }
+ }
+
// Choose near, far and Z parameters to match the SetPerspectiveProjection above.
float nearClippingPlane;
float farClippingPlane;
float cameraZ;
CalculateClippingAndZ(size.width, size.height, nearClippingPlane, farClippingPlane, cameraZ);
- SetOrthographicProjection(-size.x * 0.5f, size.x * 0.5f, size.y * 0.5f, -size.y * 0.5f, nearClippingPlane, farClippingPlane);
+ SetOrthographicProjection(-size.x * 0.5f, size.x * 0.5f, size.y * 0.5f, size.y * -0.5f, nearClippingPlane, farClippingPlane);
SetZ(cameraZ);
}
void CameraActor::SetOrthographicProjection(float left, float right, float top, float bottom, float near, float far)
{
+ SetProjectionMode(Dali::Camera::ORTHOGRAPHIC_PROJECTION);
SetLeftClippingPlane(left);
SetRightClippingPlane(right);
SetTopClippingPlane(top);
SetBottomClippingPlane(bottom);
SetNearClippingPlane(near);
SetFarClippingPlane(far);
- SetProjectionMode(Dali::Camera::ORTHOGRAPHIC_PROJECTION);
}
bool CameraActor::BuildPickingRay(const Vector2& screenCoordinates,
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
RenderItem::~RenderItem() = default;
+ClippingBox RenderItem::CalculateTransformSpaceAABB(const Matrix& transformMatrix, const Vector3& size)
+{
+ // Calculate extent vector of the AABB:
+ const float halfActorX = size.x * 0.5f;
+ const float halfActorY = size.y * 0.5f;
+
+ // To transform the actor bounds to the transformed space, We do a fast, 2D version of a matrix multiply optimized for 2D quads.
+ // This reduces float multiplications from 64 (16 * 4) to 12 (4 * 3).
+ // We create an array of 4 corners and directly initialize the first 3 with the matrix multiplication result of the respective corner.
+ // This causes the construction of the vector arrays contents in-place for optimization.
+ // We place the coords into the array in clockwise order, so we know opposite corners are always i + 2 from corner i.
+ // We skip the 4th corner here as we can calculate that from the other 3, bypassing matrix multiplication.
+ // Note: The below transform methods use a fast (2D) matrix multiply (only 4 multiplications are done).
+ Vector2 corners[4]{Transform2D(transformMatrix, -halfActorX, -halfActorY),
+ Transform2D(transformMatrix, halfActorX, -halfActorY),
+ Transform2D(transformMatrix, halfActorX, halfActorY)};
+
+ // As we are dealing with a rectangle, we can do a fast calculation to get the 4th corner from knowing the other 3 (even if rotated).
+ corners[3] = Vector2(corners[0] + (corners[2] - corners[1]));
+
+ // Calculate the AABB:
+ // We use knowledge that opposite corners will be the max/min of each other. Doing this reduces the normal 12 branching comparisons to 3.
+ // The standard equivalent min/max code of the below would be:
+ // Vector2 AABBmax( std::max( corners[0].x, std::max( corners[1].x, std::max( corners[3].x, corners[2].x ) ) ),
+ // std::max( corners[0].y, std::max( corners[1].y, std::max( corners[3].y, corners[2].y ) ) ) );
+ // Vector2 AABBmin( std::min( corners[0].x, std::min( corners[1].x, std::min( corners[3].x, corners[2].x ) ) ),
+ // std::min( corners[0].y, std::min( corners[1].y, std::min( corners[3].y, corners[2].y ) ) ) );
+ unsigned int smallestX = 0u;
+ // Loop 3 times to find the index of the smallest X value.
+ // Note: We deliberately do NOT unroll the code here as this hampers the compilers output.
+ for(unsigned int i = 1u; i < 4u; ++i)
+ {
+ if(corners[i].x < corners[smallestX].x)
+ {
+ smallestX = i;
+ }
+ }
+
+ // As we are dealing with a rectangle, we can assume opposite corners are the largest.
+ // So without doing min/max branching, we can fetch the min/max values of all the remaining X/Y coords from this one index.
+ Vector4 aabb(corners[smallestX].x, corners[(smallestX + 3u) % 4].y, corners[(smallestX + 2u) % 4].x, corners[(smallestX + 1u) % 4].y);
+
+ // Round outwards from center
+ int x = static_cast<int>(floor(aabb.x));
+ int y = static_cast<int>(floor(aabb.y));
+ int z = static_cast<int>(ceilf(aabb.z));
+ int w = static_cast<int>(ceilf(aabb.w));
+
+ return ClippingBox(x, y, z - x, fabsf(w - y));
+}
+
ClippingBox RenderItem::CalculateViewportSpaceAABB(const Matrix& modelViewMatrix, const Vector3& size, const int viewportWidth, const int viewportHeight)
{
// Calculate extent vector of the AABB:
#define DALI_INTERNAL_SCENE_GRAPH_RENDER_ITEM_H
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
~RenderItem();
/**
+ * Produce a 2D AABB in transformed space
+ * See below for caveats.
+ *
+ * @param[in] transformMatrix The matrix for converting to a different space
+ * @param[in] size The size of the render item
+ */
+ static ClippingBox CalculateTransformSpaceAABB(const Matrix& transformMatrix, const Vector3& size);
+
+ /**
* @brief This method is an optimized calculation of a viewport-space AABB (Axis-Aligned-Bounding-Box).
*
* We use the model-view-matrix, but we do not use projection. Therefore we assume Z = 0.
*
* Note: We pass in the viewport dimensions rather than allow the caller to modify the raw AABB in order to optimally generate the final result.
*
+ * Note: ASSUMES THAT THE VIEWPORT COVERS THE SCREEN AND THAT THE CANVAS SIZE AND VIEWPORT SIZE ARE THE SAME!!!!! (Not the case for magnifier)
+ *
* @param[in] modelViewMatrix The model view matrix
* @param[in] size The size of the render item
* @param[in] viewportWidth The width of the viewport to calculate for
SetNodeUpdateSize(node, isLayer3d, nodeWorldMatrix, nodeSize, nodeUpdateSize);
nodeUpdateSizeSet = true;
- const Vector3& scale = node->GetWorldScale(updateBufferIndex);
- const Vector3& halfSize = nodeUpdateSize * scale * 0.5f;
- float radius(halfSize.Length());
+ const Vector3& scale = node->GetWorldScale(updateBufferIndex);
+ const Vector3& size = nodeUpdateSize * scale;
- if(radius > Math::MACHINE_EPSILON_1000)
+ if(size.LengthSquared() > Math::MACHINE_EPSILON_1000)
{
Matrix::Multiply(nodeModelViewMatrix, nodeWorldMatrix, viewMatrix);
nodeModelViewMatrixSet = true;
- ClippingBox clippingBox = RenderItem::CalculateViewportSpaceAABB(nodeModelViewMatrix, nodeUpdateSize, viewport.width, viewport.height);
- inside = clippingBox.Intersects(viewport);
+ // Assume actors are at z=0, compute AABB in view space & test rect intersection
+ // against z=0 plane boundaries for frustum. (NOT viewport). This should take into account
+ // magnification due to FOV etc.
+ ClippingBox boundingBox = RenderItem::CalculateTransformSpaceAABB(nodeModelViewMatrix, nodeUpdateSize);
+ ClippingBox clippingBox(camera.mLeftClippingPlane, camera.mBottomClippingPlane, camera.mRightClippingPlane - camera.mLeftClippingPlane, fabsf(camera.mBottomClippingPlane - camera.mTopClippingPlane));
+ inside = clippingBox.Intersects(boundingBox);
}
}
+ /*
+ * Note, the API Camera::CheckAABBInFrustum() can be used to test if a bounding box is (partially/fully) inside the view frustum.
+ */
}
if(inside)
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
const Dali::Camera::ProjectionMode Camera::DEFAULT_MODE(Dali::Camera::PERSPECTIVE_PROJECTION);
const bool Camera::DEFAULT_INVERT_Y_AXIS(false);
const float Camera::DEFAULT_FIELD_OF_VIEW(45.0f * (Math::PI / 180.0f));
-const float Camera::DEFAULT_ASPECT_RATIO(4.0f / 3.0f);
+const float Camera::DEFAULT_ASPECT_RATIO(800.0f / 480.0f);
const float Camera::DEFAULT_LEFT_CLIPPING_PLANE(-240.0f);
const float Camera::DEFAULT_RIGHT_CLIPPING_PLANE(240.0f);
const float Camera::DEFAULT_TOP_CLIPPING_PLANE(-400.0f);
#define DALI_INTERNAL_SCENE_GRAPH_CAMERA_H
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
new(slot) LocalType(&camera, &Camera::SetFarClippingPlane, parameter);
}
+inline void SetReflectByPlaneMessage(EventThreadServices& eventThreadServices, const Camera& camera, const Vector4& plane)
+{
+ using LocalType = MessageValue1<Camera, Vector4>;
+
+ // Reserve some memory inside the message queue
+ uint32_t* slot = eventThreadServices.ReserveMessageSlot(sizeof(LocalType));
+
+ // Construct message in the message queue memory; note that delete should not be called on the return value
+ new(slot) LocalType(&camera, &Camera::SetReflectByPlane, plane);
+}
+
inline void SetTargetPositionMessage(EventThreadServices& eventThreadServices, const Camera& camera, const Vector3& parameter)
{
using LocalType = MessageValue1<Camera, Vector3>;