AB4D Forum

Full Version: Choose camera Distance to fit scene into WPF window
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I have a WPF app that shows a simple scene, just a PlaneVisual3D and I want this shape to almost fill the entire WPF window.  The height/width of the WPF window  is 600/1000, the height/width of the  PlaneVisual3D is 400/800 and the camera distance is 1500.   The executed app appears as shown in the attachment Test.gif.  

I would like to to know how to choose the camera Distance so that the width of the PlaneVisual3D takes up the width of the WPF window with a 100 pixel horizontal margin on each side (the vertical margin is not important).

In general, what I am trying to do is to figure out how to programmatically choose the camera Distance so that the scene I create fits within the current size of a WPF window.

Here is the code:

Code:
<Window x:Class="ChooseCameraDistance.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
       xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
       xmlns:cameras="clr-namespace:Ab3d.Cameras;assembly=Ab3d.PowerToys"
       xmlns:ab3d="clr-namespace:Ab3d.Controls;assembly=Ab3d.PowerToys"  
       xmlns:visuals="clr-namespace:Ab3d.Visuals;assembly=Ab3d.PowerToys"
       xmlns:local="clr-namespace:ChooseCameraDistance"
       mc:Ignorable="d"
       Title="MainWindow" Height="600" Width="1000">
   <Grid Name="MainGrid">
       <Border Name="ViewportBorder">
           <Border.Background>
               <LinearGradientBrush StartPoint="0 0" EndPoint="0 1">
                   <GradientStop Offset="0" Color="#033C62"/>
                   <GradientStop Offset="1" Color="#01131F"/>
               </LinearGradientBrush>
           </Border.Background>

           <Viewport3D Name="MainViewport"  >
               <visuals:PlaneVisual3D CenterPosition="0,0,0" Size="800 400" HeightDirection="0 1 0"  
                                      Normal="0 0 1" Material="Emissive:Yellow" />
           </Viewport3D>
       </Border>

       <cameras:TargetPositionCamera Name="Camera1"
                                     TargetPosition="0 0 0"
                                     Heading="0" Attitude="0" Bank="0"
                                     Distance="1500"
                                     ShowCameraLight="Always"
                                     TargetViewport3D="{Binding ElementName=MainViewport}" />
   </Grid>
</Window>
There are multiple ways on how this can be done.

1)
The easiest is to call Camera1.FitIntoView method.
But in case the view area is wider than the object (view's aspect ratio = width / height is bigger than aspect ratio of the object), then this method will also fit the object vertically.

Also note that when you resize the window, then the rendered 3D objects will be also scaled and no additional space will be shown (as in usual 2D application when you increase the window size you see more and not scale the content).

2)
When using a perspective camera (by default CameraType is set to PerspectiveCamera), then the required distance depends on the width of the object and the FieldOfView - the angle at which the camera views the scene. With some trigonometry, you can calculate the required distance - here is the method that can do that:

Code:
private double GetCameraDistance(double width, double fieldOfView)
{
   double radian = fieldOfView * Math.PI / 180.0;
   return (width * 0.5) / Math.Tan(radian * 0.5);
}


3)
Instead of a perspective camera, you can also use an orthographic camera (there the objects do not scale down when they are farther away from the camera) - you can use that by setting CameraType to CameraType OrthographicCamera). In this case, the FieldOfView property does not have any effect. Instead, CameraWidth is used - this specifies the width of the visible area. So in your case, you could simply set CameraWidth to 800.

This mode is also recommended for showing 2D content. In this case you set the CameraWidth to the width of the visible area (Viewport3D or DXViewportView width). This way you get 1 : 1 ratio from the 3D to 2D space. When the view area is realized, then also the CameraWidth would be changed. This way you can show more area when increasing the size of the window. You can also use zooming - in this case you would divide the visible area width by the zoom factor.

In the following days I will prepare a sample that will show how to render many 2D lines with Ab3d.DXEngine  -it will use the OrthographicCamera and support zooming and panning. I will attach the sample to this post.
Thanks for your prompt support.  This works perfectly.   I used a TargetPositionCamera and simply call Camera1.FitIntoView() from the Loaded event handler.

There are two things in FitIntoView() that are not obvious from your API documentation that you might want to clarify:

(1) If you define the scene in XAML, then you cannot call FitIntoView() in the code-behind constructor.  You must call it in the Loaded event handler.

(2) If you define the scene programmatically, you must call FitIntoView() after the scene has been created, i.e. don't call FitIntoView() and then add children to the Viewport.

After receiving your reply, I happened to notice your "FitIntoView" sample.  I ran the sample but the "Fit Into View" button does not seem to do anything.  What I would have expected to see is a large scene that I can move around by holding the left mouse button and the Ctrl button.  Then when I click FitIntoView I would see the whole scene compressed in the view and not need to move it around.

Thanks again for responding so quickly.