Keeping an object the same size in the view
#1
I am creating a view in which I want to highlight certain 3D points.   The object I use to highlight the point is a cross hair and I need this object to stay in the selected 3D location but scale as the camera is zoomed/panned so that the cross hair is always the same size.  I'm struggling to work out the maths to calculate the object size based on the camera distance and width.  

What I'm doing at present is drawing a small PlaneVisual3D on which I paste a bitmap of the cross hair.  I then rotate this plane so that it always faces the camera by applying the same rotation as the camera - this part works well.

I then want to scale the plane so that it is always the same size on the screen.  This of course depends on where the plane is relative to the camera.

I also need this to work with a perspective and an orthographic camera as I have two separate views that use both camera types.

Here's what I have tried so far.   This neither works in perspective nor orthographic views.  It does work at times but I think I'm going about it wrong.

 var fieldOfViewInRadians = MainCamera.FieldOfView * (Math.PI / 180.0);
            var cam = MainCamera;
            var t = cam.TargetPosition;
            var camQuat = cam.ConvertCameraAnglesToQuaternion();


            var vectorC2T = new Vector3D(0, 0, cam.Distance);
//this part converts cameras rotation into a toration matrix
            var camMatrix = new Transformation(new Vector3D(), camQuat).ToMatrix();
//then calculate the camera to target vector
            vectorC2T = camMatrix.Transform(vectorC2T);
//this is the vector of the target
            var vectorT = new Vector3D(t.X, t.Y, t.Z);

            foreach (var p in PCSPoints)
            {
//this is the world position of the point (i have to swap hand conventions)
                var wp = p.WorldPosition.SwapHandedness();
//the vector of the point
                var vectorP = new Vector3D(wp.X, wp.Y, wp.Z);
//calculate the vector of the camera to the point
                var vectorC2P = vectorC2T + vectorP - vectorT;
//the distance is then length of this vector
                var distance = vectorC2P.Length;

                var width = 2 * distance  * Math.Tan(fieldOfViewInRadians / 2);
                var newVSize = width * (_viewType == ViewType.Perspective ? 0.02 : 0.005);
                Debug.WriteLine($"fov:{CameraAndMouseController.MainCamera.FieldOfView} d:{CameraAndMouseController.MainCamera.Distance} w:{width} newsize:{newVSize}");
                p.Plane.Size = new Size(newVSize, newVSize);

                //the plane is on x y plane so rotate to x z plane
                camQuat *= new Quaternion(new Vector3D(1, 0, 0), -90);
                p.Rotation = camQuat;
            }

Of course if there is an entirely different way of doing this I'm open to suggestions.
#2
One way to mark a location in 3D space is to use 2D overlay Canvas. For this you create a Canvas over the Viewport3D object (or DXViewportView if you are using DXEngine). Then on each camera change or Viewport3D size change, you call Point3DTo2D method on the camera to get a 2D position of a 3D object. Then you can put any WPF Shape, Border or other element to the overlay canvas to the calculated 2D coordinates. There are a few samples in the Ab3d.PowerToys samples project - just do a text search for "OverlayCanvas".

A drawback of this solution is that the elements on OverlayCanvas is that those elements are always on top of 3D scene and cannot be hidden behind other 3D objects.

For such situations you will need to create a real 3D object. I have developed a few helper methods that can be used to get the size of 3D object based on the desired screen size and position in 3D world. Those methods will be part of the next version of Ab3d.PowerToys, but for you convenience I am publishing two of the methods here:

Code:
/// <summary>
/// GetPerspectiveWorldSize calculates a size in 3D world from
/// a screen Size (screenSize in screen coordinates) that is at lookDirectionDistance and shown with perspective camera with fieldOfView and in Viewport3D with viewport3DSize.
/// </summary>
/// <param name="screenSize">Size on screen (in same units as size of Viewport3D.Width - without DPI scale)</param>
/// <param name="lookDirectionDistance">distance from camera in the camera's look direction (see remarks for more info)</param>
/// <param name="fieldOfView">camera's field of view</param>
/// <param name="viewport3DSize">Viewport3D's size</param>
/// <returns>3D world size</returns>
/// <remarks>
/// <para>
/// <b>GetPerspectiveWorldSize</b> calculates a size in 3D world from
/// a screen Size (screenSize in screen coordinates) that is at distanceFromCamera and shown with perspective camera with fieldOfView and in Viewport3D with viewport3DSize.
/// </para>
/// <para>
/// To get the most accurate results the <b>lookDirectionDistance</b> must not be the direct distance from the camera but the distance in the camera's look direction.
/// The lookDirectionDistance can be calculated with the following code:
/// </para>
/// <code>
/// var targetPosition = new Point3D(x,y,z);
/// var cameraPosition = Camera1.GetCameraPosition();
///
/// var distanceVector = targetPosition - cameraPosition;
///
/// var lookDirection = Camera1.LookDirection;
/// lookDirection.Normalize();
///
/// // To get look direction distance we project the distanceVector to the look direction vector
/// var lookDirectionDistance = Vector3D.DotProduct(distanceVector, lookDirection);
///
/// var screenSize = GetPerspectiveScreenSize(worldSize, lookDirectionDistance, Camera1.FieldOfView, viewport3DSize)
/// </code>
/// </remarks>        
public static Size GetPerspectiveWorldSize(Size screenSize, double lookDirectionDistance, double fieldOfView, Size viewport3DSize)
{
   double aspectRatio = viewport3DSize.Width / viewport3DSize.Height;

   //double xScale = 1.0 / Math.Tan(fieldOfView * Math.PI / 360);
   //double yScale = xScale * aspectRatio;

   //Size worldSize = new Size((2 * lookDirectionDistance * screenSize.Width) / (viewport3DSize.Width * xScale),
   //                          (2 * lookDirectionDistance * screenSize.Height) / (viewport3DSize.Height * yScale));


   double xScale = Math.Tan(fieldOfView * Math.PI / 360);

   var worldSize = new Size((2 * lookDirectionDistance * screenSize.Width * xScale) / (viewport3DSize.Width),
                               (2 * lookDirectionDistance * screenSize.Height * xScale) / (viewport3DSize.Height * aspectRatio));

   return worldSize;
}

/// <summary>
/// GetOrthographicWorldSize calculates a size in 3D world from
/// a screen Size (screenSize in screen coordinates) that is at distanceFromCamera and shown with orthographic camera with cameraWidth and in Viewport3D with viewport3DSize.
/// </summary>
/// <param name="screenSize">Size on screen (in same units as size of Viewport3D.Width - without DPI scale)</param>
/// <param name="cameraWidth">camera's width</param>
/// <param name="viewport3DSize">Viewport3D's size</param>
/// <returns>3D world size</returns>
public static Size GetOrthographicWorldSize(Size screenSize, double cameraWidth, Size viewport3DSize)
{
   double aspectRatio = viewport3DSize.Width / viewport3DSize.Height;

   // The equations here are derived from the one in GetOrthographicScreenSize
   Size worldSize = new Size((screenSize.Width * cameraWidth) / viewport3DSize.Width,
                               (screenSize.Height * cameraWidth) / (viewport3DSize.Height * aspectRatio));

   return worldSize;
}

See remarks to see how to calculate the lookDirectionDistance for the GetPerspectiveWorldSize method.
Andrej Benedik
#3
Looks good, I'll give that a go this week.  thanks for the help
  


Forum Jump:


Users browsing this thread:
1 Guest(s)