Fixing culling for magnifier use case 68/272368/6
authorDavid Steele <david.steele@samsung.com>
Mon, 14 Mar 2022 17:17:52 +0000 (17:17 +0000)
committerDavid Steele <david.steele@samsung.com>
Mon, 4 Apr 2022 14:51:04 +0000 (15:51 +0100)
Should use the camera's canvas size, not the viewport.

Added canvas size to left/right/top/bottom clipping distance
in camera for perspective projection

Fixed bug that would always force using perspective projection
Fixed bug with modifying scene object directly in event thread.

Top and Bottom clipping plane values for perspective have been
made consistent with values for Ortho projection; however, this
currently forces top to be +ve and bottom to be -ve when set
using just size. Test cases are inconsistent in this regard; when
set via l/r/t/b/n/f API, top and bottom are -ve and +ve respectively.
  Have used fabsf when re-calculating bounding box dimensions to
allow for this inconsistency.

Change-Id: I95e80e4c09f17a3240cccf16df23f09e8d9ad9cf

automated-tests/src/dali/utc-Dali-CameraActor.cpp
dali/internal/event/actors/camera-actor-impl.cpp
dali/internal/render/common/render-item.cpp
dali/internal/render/common/render-item.h
dali/internal/update/manager/render-instruction-processor.cpp
dali/internal/update/render-tasks/scene-graph-camera.cpp
dali/internal/update/render-tasks/scene-graph-camera.h

index 3352ee9..0ec0ccb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -186,7 +186,7 @@ int UtcDaliCameraActorNewDefaultPerspectiveProjection(void)
 
   // 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);
@@ -2118,3 +2118,118 @@ int UtcDaliCameraActorGetTypeNegative(void)
   }
   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;
+}
index 97affa3..d69462a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -197,19 +197,26 @@ void CameraActor::OnInitialize()
 
 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);
   }
 }
 
@@ -386,6 +393,7 @@ bool CameraActor::GetInvertYAxis() const
 
 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))
@@ -425,34 +433,56 @@ void CameraActor::SetPerspectiveProjection(const Size& size)
   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,
index 5fb838d..4a4733f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -55,6 +55,57 @@ RenderItem::RenderItem()
 
 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:
index d89ab22..66d7158 100644 (file)
@@ -2,7 +2,7 @@
 #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.
@@ -52,6 +52,15 @@ struct RenderItem
   ~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.
@@ -60,6 +69,8 @@ struct RenderItem
    *
    * 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
index 1ff28a8..0365d5b 100644 (file)
@@ -208,19 +208,25 @@ inline void AddRendererToRenderList(BufferIndex         updateBufferIndex,
       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)
index f7b2b1c..c6e9973 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -144,7 +144,7 @@ const Dali::Camera::Type           Camera::DEFAULT_TYPE(Dali::Camera::FREE_LOOK)
 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);
index 4b37ce4..23e1c54 100644 (file)
@@ -2,7 +2,7 @@
 #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.
@@ -461,6 +461,17 @@ inline void SetFarClippingPlaneMessage(EventThreadServices& eventThreadServices,
   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>;