WPF: rotate a Model3D about the Viewport3D center

Claude Gosselin 1 Reputation point
2021-01-05T14:29:58.757+00:00

I have a WPF app where different Model3Ds are arranged in a Model3DGroup and displayed in a Viewport3D. I may pan, zoom in / out the displayed models. Everything is handled programmatically in the .Net app. The only Xaml code is to define a UserControl containing a Grid, Viewport and Canvas, which is then hosted in the main Winforms app.

I want to be able to rotate the Model3DGroup about an arbitrary point - for example the center of the Viewport3D - but NOT about the center of Model3DGroup. The center of rotation (for example the red cross in the image below) is not necessarily at the Viewport3D origin (0, 0, 0).

VisualTreeHelper HitTest works if and only if there is something behind the desired center of rotation in the Viewport3D: then I get a Point3D about which to rotate the display.

The problem is when there is nothing behind the center of rotation of the Viewport3D as can happen with some geometry combinations See image below where the screen center is marked by a red cross: there is nothing in there to perform a VisualTreeHelper HitTest, and the red cross is NOT at the center of the Model3DGroup and it is NOT at the origin of the Viewport3D.

53652-wpfviewportcenter.jpg

I have been hunting around for ideas, including 3DTools - but this requires a Visual as input whereas my app uses a Viewport3D for 3D components and a Canvas for 2D components within a Grid.

Anyone would have a suggestion ?

Thanks in advance - just in case ;-))

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,685 questions
XAML
XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
768 questions
{count} votes

3 answers

Sort by: Most helpful
  1. Claude Gosselin 1 Reputation point
    2021-01-06T11:54:47.68+00:00

    Good day DaisyTian - thank you for the reply.

    Here is the picture. Somehow it showed when I wrote the post yesterday but did not make it. I hope this one does show.

    What I want to do is be able to rotate the Viewport3D content about a Point3D such as to keep the same part of the content visible even though it has been panned and zoomed; I use this Point3D as a pivot.

    Using the bounds of the Model3DGroup is not working if it has been panned and zoomed. And the Viewport3D is not set ClipToBounds.

    Typically the Point3D would be at the center of the Viewport3D; if I use VisualTreeHelper.HitTest to get a pivot Point3D from, say, a mouse click on the display, or simply the display center, if there is nothing behind then I am unable to get a pivot Point3D for the rotation.

    53984-wpfviewportcenter.jpg

    0 comments No comments

  2. DaisyTian-1203 11,616 Reputation points
    2021-01-07T03:01:01.937+00:00

    I will show you a demo to rotate or zoom the 3D element in wherever the mouse is located.
    Part 1: The xaml code is:

      <Grid>  
            <local:ZoomBorder>  
                <local:TrackballDecorator>  
                    <!--replace the below with your  Viewport3D-->  
                    <Viewport3D ClipToBounds="True" Width="150" Height="150" Canvas.Left="0" Canvas.Top="10">  
      
                        <Viewport3D.Camera>  
                            <PerspectiveCamera Position="0,0,2" LookDirection="0,0,-1" FieldOfView="60" />  
                        </Viewport3D.Camera>  
      
                        <Viewport3D.Children>  
      
                            <ModelVisual3D>  
                                <ModelVisual3D.Content>  
                                    <DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />  
                                </ModelVisual3D.Content>  
                            </ModelVisual3D>  
                            <ModelVisual3D>  
                                <ModelVisual3D.Content>  
                                    <GeometryModel3D>  
      
                                        <GeometryModel3D.Geometry>  
                                            <MeshGeometry3D  
                         TriangleIndices="0,1,2 3,4,5 "  
                         Normals="0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 "  
                         TextureCoordinates="0,0 1,0 1,1 1,1 0,1 0,0 "  
                         Positions="-0.5,-0.5,0.5 0.5,-0.5,0.5 0.5,0.5,0.5 0.5,0.5,0.5 -0.5,0.5,0.5 -0.5,-0.5,0.5 " />  
                                        </GeometryModel3D.Geometry>  
                                        <GeometryModel3D.Material>  
                                            <MaterialGroup>  
                                                <DiffuseMaterial>  
                                                    <DiffuseMaterial.Brush>  
                                                        <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">  
                                                            <LinearGradientBrush.GradientStops>  
                                                                <GradientStop Color="Yellow" Offset="0" />  
                                                                <GradientStop Color="Red" Offset="0.25" />  
                                                                <GradientStop Color="Blue" Offset="0.75" />  
                                                                <GradientStop Color="LimeGreen" Offset="1" />  
                                                            </LinearGradientBrush.GradientStops>  
                                                        </LinearGradientBrush>  
                                                    </DiffuseMaterial.Brush>  
                                                </DiffuseMaterial>  
                                            </MaterialGroup>  
                                        </GeometryModel3D.Material>  
      
                                        <GeometryModel3D.Transform>  
                                            <RotateTransform3D>  
                                                <RotateTransform3D.Rotation>  
                                                    <AxisAngleRotation3D Axis="0,3,0" Angle="40" />  
                                                </RotateTransform3D.Rotation>  
                                            </RotateTransform3D>  
                                        </GeometryModel3D.Transform>  
                                    </GeometryModel3D>  
                                </ModelVisual3D.Content>  
                            </ModelVisual3D>  
                        </Viewport3D.Children>  
      
                    </Viewport3D>  
                </local:TrackballDecorator>  
            </local:ZoomBorder>  
        </Grid>  
    

    Part 2: The ZoomBorder.cs code is:

        using System.Linq;  
        using System.Windows;  
        using System.Windows.Controls;  
        using System.Windows.Input;  
        using System.Windows.Media;  
        public class ZoomBorder : Border  
        {  
            private UIElement child = null;  
            private TranslateTransform GetTranslateTransform(UIElement element)  
            {  
                return (TranslateTransform)((TransformGroup)element.RenderTransform)  
                  .Children.First(tr => tr is TranslateTransform);  
            }  
      
            private ScaleTransform GetScaleTransform(UIElement element)  
            {  
                return (ScaleTransform)((TransformGroup)element.RenderTransform)  
                  .Children.First(tr => tr is ScaleTransform);  
            }  
      
            public override UIElement Child  
            {  
                get { return base.Child; }  
                set  
                {  
                    if (value != null && value != this.Child)  
                        this.Initialize(value);  
                    base.Child = value;  
                }  
            }  
      
            public void Initialize(UIElement element)  
            {  
                this.child = element;  
                if (child != null)  
                {  
                    TransformGroup group = new TransformGroup();  
                    ScaleTransform st = new ScaleTransform();  
                    group.Children.Add(st);  
                    TranslateTransform tt = new TranslateTransform();  
                    group.Children.Add(tt);  
                    child.RenderTransform = group;  
                    child.RenderTransformOrigin = new Point(0.0, 0.0);  
                    this.MouseWheel += child_MouseWheel;  
                }  
            }  
            private void child_MouseWheel(object sender, MouseWheelEventArgs e)  
            {  
                if (child != null)  
                {  
                    var st = GetScaleTransform(child);  
                    var tt = GetTranslateTransform(child);  
      
                    double zoom = e.Delta > 0 ? .2 : -.2;  
                    if (!(e.Delta > 0) && (st.ScaleX < .4 || st.ScaleY < .4))  
                        return;  
      
                    Point relative = e.GetPosition(child);  
                    double absoluteX;  
                    double absoluteY;  
      
                    absoluteX = relative.X * st.ScaleX + tt.X;  
                    absoluteY = relative.Y * st.ScaleY + tt.Y;  
      
                    st.ScaleX += zoom;  
                    st.ScaleY += zoom;  
      
                    tt.X = absoluteX - relative.X * st.ScaleX;  
                    tt.Y = absoluteY - relative.Y * st.ScaleY;  
                }  
            }  
        }  
    

    Part 3: You can down load the TrackballDecorator.cs and Viewport3DDecorator.cs from Microsoft 3DTools. They are in 3DTools\sourceCode\sourceCode\3DTools\3DTools


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

  3. Claude Gosselin 1 Reputation point
    2021-01-10T13:43:16.477+00:00

    Good day @DaisyTian-MSFT

    Thank you for your reply. I have been trying out your code but cannot find why it is not working. What I did is:

    • Create a New C# Wpf project
    • Add a reference to 3DTools
    • Paste your code from <Grid> to </Grid> to MainWindow.xaml that is created with the project
    • Paste your C# Code in MainWindow.xaml.cs

    The errors I get are that ZoomBorder and TrackballDecorator do not exist and cannot be found.

    What am I missing here ? Below is the code generated and added before the <Grid> statement.

    <Window x:Class="Test.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:local="clr-namespace:Test" xmlns:local1="clr-namespace:_3DTools;assembly=3DTools"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">