Search code examples
wpfdata-bindinghelix-3d-toolkit

Why does two Transform do not work with a Helix Toolkit Object


I have put together some code where I am manipulating two Helix Toolkit Torus objects by using WPF slider controls. This code is a prototype for some other more complex problem.

When I apply only Translation or Rotation Transformation, Torus objects move or rotate. However, when I have both applied only the last one listed in the ode works (Rotation in this example). If I comment out Rotation Transform, translations work.

I tried TransformGroup approach, then nothings works except the dependency objects defined in the Helix Tool kit (e.g. TorusDiameter).

As you can see from the code only one transform is meant to work at a time as one slider can be moved at a time.

I have two questions:

(1) Why does multiple application (lines) of Transform not work? How can I make it work?

(2) I put a Message Box inside the event handler MyTorusChanged. It seems to be fired only in the beginning. I a new to WPF. How does Transform work without going through the event handler?

The viewmodel:

// April 08, 2019, Dr. I. Konuk

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using HelixToolkit.Wpf;

namespace AnimatedTorusDemo
{
    class MyTorusViewModel : ModelVisual3D
    {
        // origin of torus object
        public Point3D TorusOrigin { get; set; }
        public double translationX { get; set; }
        public double translationY { get; set; }
        public double translationZ { get; set; }

        // translation transformation parameter properties

        public static readonly DependencyProperty TranslationXProperty =
                    DependencyProperty.Register("TranslationX", typeof(double), typeof(MyTorusViewModel),
                            new UIPropertyMetadata(0.0));
        public static readonly DependencyProperty TranslationYProperty =
                    DependencyProperty.Register("TranslationY", typeof(double), typeof(MyTorusViewModel),
                            new UIPropertyMetadata(0.0));
        public static readonly DependencyProperty TranslationZProperty =
                    DependencyProperty.Register("TranslationZ", typeof(double), typeof(MyTorusViewModel),
                            new UIPropertyMetadata(0.0));

        public double TranslationX
        {
            get { return (double)GetValue(TranslationXProperty); }
            set { SetValue(TranslationXProperty, value); translationX = value; }
        }
        public double TranslationY
        {
            get { return (double)GetValue(TranslationYProperty); }
            set { SetValue(TranslationYProperty, value); translationY = value; }
        }
        public double TranslationZ
        {
            get { return (double)GetValue(TranslationZProperty); }
            set { SetValue(TranslationZProperty, value); translationZ = value; }
        }

        // MyTorusProperty
        // notice the callback function definition via Metadata

        public static DependencyProperty MyTorusProperty =
                    DependencyProperty.Register("MyTorus", typeof(TorusVisual3D), typeof(MyTorusViewModel),
                    new UIPropertyMetadata(null, MyTorusChanged));
        public TorusVisual3D MyTorus
        {
            get => (TorusVisual3D)GetValue(MyTorusProperty);
            set { SetValue(MyTorusProperty, value); }
        }

        // rotation angle property
        // Gets or sets the rotation angle (angle of torus).

        public static readonly DependencyProperty RotationAngleProperty =
                    DependencyProperty.Register("RotationAngle", typeof(double), typeof(MyTorusViewModel),
                            new UIPropertyMetadata(45.0));
        public double RotationAngle
        {
            get { return (double)GetValue(RotationAngleProperty); }
            set { SetValue(RotationAngleProperty, value); }
        }

        public static AxisAngleRotation3D axisAngleRotation = new AxisAngleRotation3D(new Vector3D(1, 0, 0), 0);

        public static readonly DependencyProperty TorusAxisAngleProperty =
                    DependencyProperty.Register("TorusAxisAngle", typeof(AxisAngleRotation3D), typeof(MyTorusViewModel),
                    new UIPropertyMetadata(axisAngleRotation));
        public AxisAngleRotation3D TorusAxisAngle
        {
            get { return (AxisAngleRotation3D)GetValue(TorusAxisAngleProperty);  }
            set { SetValue(TorusAxisAngleProperty, value);  }
        }

        // callback function used updating visuals (torus obejects)
        protected static void MyTorusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ((MyTorusViewModel)obj).UpdateVisuals();
        }

        // member function to update visuals
        private void UpdateVisuals()
        {
            MessageBox.Show("UpdateVisuals");

            Children.Clear();

            // each instance must have a torus object assigned for each MyTorusViewModel before

            if (MyTorus == null) return;

            MyTorus.ThetaDiv = 36;
            MyTorus.PhiDiv = 24;

            // translation

            var torusTranslate = new TranslateTransform3D(new Vector3D(translationX, translationY, translationZ));

            MyTorus.Transform = torusTranslate;

            // rotation

            var torusRotate = new RotateTransform3D();
            TorusAxisAngle = new AxisAngleRotation3D();

            TorusAxisAngle.Axis= new Vector3D(0, 1, 0);
            TorusAxisAngle.Angle = RotationAngle;

            torusRotate.Rotation = TorusAxisAngle;

            MyTorus.Transform = torusRotate;


            // transform group

            var torusTransformGroup = new Transform3DGroup();
            torusTransformGroup.Children.Add(torusTranslate);

            // MyTorus.Transform = torusTransformGroup;

            Children.Add(MyTorus);
        }

    }
}

xaml:

<Window x:Class="AnimatedTorusDemo.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:h="clr-namespace:HelixToolkit.Wpf;assembly=HelixToolkit.Wpf"
        xmlns:local="clr-namespace:AnimatedTorusDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="750" Width="800">

    <Grid ShowGridLines="True">

        <Grid.RowDefinitions>
            <RowDefinition Height="4*" />
            <RowDefinition Height="0.75*" />
            <RowDefinition Height="0.75*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>

        <h:HelixViewport3D x:Name="TorusView" ZoomExtentsWhenLoaded="True"  ShowCoordinateSystem="True" PanGesture="LeftClick" >
            <h:DefaultLights/>
        </h:HelixViewport3D>

        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Bottom" Opacity="0.9" >

            <Slider x:Name="translationX1Slider" Value="0" Minimum="-5.0" Maximum="5.0" Width="150" Margin="10"
                    />
            <Slider x:Name="translationY1Slider" Value="0" Minimum="-5.0" Maximum="5.0" Width="150" Margin="10"
                    />
            <Slider x:Name="translationZ1Slider" Value="1" Minimum="-3" Maximum="3" Width="150" Margin="10"
                    />
            <Slider x:Name="rotation1Slider" Value="45" Minimum="0" Maximum="360" Width="150" Margin="10"
                    />

        </StackPanel>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Bottom" Opacity="0.9" >

            <Slider x:Name="translationX2Slider" Value="0" Minimum="-5.0" Maximum="5.0" Width="150" Margin="10"
                    />
            <Slider x:Name="translationY2Slider" Value="0" Minimum="-5" Maximum="5" Width="150" Margin="10"
                    />
            <Slider x:Name="translationZ2Slider" Value="-1" Minimum="-3" Maximum="3" Width="150" Margin="10"
                    />
            <Slider x:Name="rotation2Slider" Value="45" Minimum="0" Maximum="360" Width="150" Margin="10"
                    />

        </StackPanel>

        <StackPanel  Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Bottom" Opacity="0.9" >

            <Slider x:Name="torusDiameterSlider" Minimum="1" Maximum="10" Width="150" Margin="10"
                    />
            <Slider x:Name="tubeDiameterSlider" Value="1" Minimum="0.5" Maximum="5" Width="150" Margin="10"
                    />
            <Slider x:Name="thetaDivSlider" Value="36" Minimum="3" Maximum="256" Width="150" Margin="10"
                    />
            <Slider x:Name="phiDivSlider" Value="24" Minimum="3" Maximum="256" Width="150" Margin="10"/>

        </StackPanel>

    </Grid>
</Window>

main window:

// April 08, 2019, Dr. I. Konuk

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.Diagnostics;
using System.Windows.Media.Media3D;

using HelixToolkit.Wpf;

namespace AnimatedTorusDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ModelVisual3D model;
        private MyTorusViewModel visual1;
        private MyTorusViewModel visual2;
        public MainWindow()
        {
            InitializeComponent();

            model = new ModelVisual3D();

            visual1 = new MyTorusViewModel();

            var mytorus1 = new TorusVisual3D();
            mytorus1.Fill = Brushes.Red;
            visual1.MyTorus = mytorus1;

            visual2 = new MyTorusViewModel();

            var mytorus2 = new TorusVisual3D();
            mytorus2.Fill = Brushes.Blue;
            visual2.MyTorus = mytorus2;
            mytorus2.TorusDiameter = 5;

            // bindings with sliders in code

            // Helix Toolkit dependencyproperties:

            // a) Geometry:
            BindingOperations.SetBinding(mytorus1, HelixToolkit.Wpf.TorusVisual3D.TorusDiameterProperty, 
                                        new Binding("Value") { Source = torusDiameterSlider });
            BindingOperations.SetBinding(mytorus1, HelixToolkit.Wpf.TorusVisual3D.TubeDiameterProperty, 
                                        new Binding("Value") { Source = tubeDiameterSlider });

            // b) Translation:
            // Dependency properties defined in MyTorusView Model
            BindingOperations.SetBinding(mytorus1.Transform, TranslateTransform3D.OffsetXProperty, 
                                        new Binding("Value") { Source = translationX1Slider });
            BindingOperations.SetBinding(mytorus1.Transform, TranslateTransform3D.OffsetYProperty, 
                                        new Binding("Value") { Source = translationY1Slider });
            BindingOperations.SetBinding(mytorus1.Transform, TranslateTransform3D.OffsetZProperty,
                            new Binding("Value") { Source = translationZ1Slider });

            BindingOperations.SetBinding(mytorus2.Transform, TranslateTransform3D.OffsetXProperty,
                                        new Binding("Value") { Source = translationX2Slider });
            BindingOperations.SetBinding(mytorus2.Transform, TranslateTransform3D.OffsetYProperty,
                            new Binding("Value") { Source = translationY2Slider });
            BindingOperations.SetBinding(mytorus2.Transform, TranslateTransform3D.OffsetZProperty,
                new Binding("Value") { Source = translationZ2Slider });

            // b) Rotation
            BindingOperations.SetBinding(visual1.TorusAxisAngle, AxisAngleRotation3D.AngleProperty,
                             new Binding("Value") { Source = rotation1Slider });
            BindingOperations.SetBinding(visual2.TorusAxisAngle, AxisAngleRotation3D.AngleProperty,
                             new Binding("Value") { Source = rotation2Slider });


            model.Children.Add(visual1);
            model.Children.Add(visual2);
            TorusView.Children.Add(model);

            this.DataContext = this;
        }
    }
}

Solution

  • When you apply a rotation and set it to the .transform property, it replaces the prior transformation that included the translation with the new one that includes only the rotation. Instead, what you have to do is get the latest transformation matrix (that included the translation) and apply rotation on to that, and then reset the .transform property. My answer to this question answers the first part of your question: How do you rotate a 3D model using HelixToolkit? Please ask your other question as a separate one (One question per question). Refer to this link for reasons: https://meta.stackexchange.com/questions/222735/can-i-ask-only-one-question-per-post