Трансформации и вращения трехмерной графики

67

Как и в случае двухмерного содержимого, наиболее впечатляющим и гибким способом изменения аспектов трехмерной сцены являются трансформации. Это особенно верно в случае трехмерной графики, поскольку приходится работать с классами относительно низкого уровня. Например, для масштабирования сферы придется сконструировать соответствующую геометрию и использовать ScaleTransform3D для ее анимации.

При наличии трехмерного примитива сферы это может быть не обязательно, поскольку можно выполнить анимацию такого высокоуровневого свойства, как Radius. Трансформации — очевидный способ создания динамических эффектов. Однако прежде чем можно будет использовать трансформации, необходимо решить, как именно они должны применяться. Существует несколько возможных подходов:

Трансформации настолько удобны при разработке трехмерной графики, что стоит взять за привычку применять Transform3DGroup всякий раз, когда требуется трансформация. Это позволит добавлять дополнительные трансформации позднее, без изменения кода анимации. Программа трехмерного моделирования ZAM 3D всегда добавляет набор из четырех заготовок трансформаций к каждой группе Model3DGroup, так что объектом, представленным группой, можно манипулировать разными способами:

<Model3DGroup.Transform>
   <Transform3DGroup>
      <TranslateTransform3D 0ffsetX="0" OffsetY="0" OffsetZ="0"/>
      <ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/>
      <RotateTransform3D>
        <RotateTransform3D.Rotation>
           <AxisAngleRotation3D Angle="0" Axis="0 1 0"/>
        </RotateTransform3D.Rotation>
      </RotateTransform3D>
      <TranslateTransform3D 0ffsetX="0" OffsetY="0" OffsetZ="0"/>
   </Transform3DGroup>
</Model3DGroup.Transform>

Обратите внимание, что показанный набор трансформаций включает два объекта TranslateTransform3D. Это связано с тем, что трансляция объекта перед его вращением дает другой результат, чем его трансляция после вращения, и могут понадобиться оба эффекта.

Другой полезный прием — именовать объекты трансформации в XAML с применением атрибута x:Name. Даже если объекты трансформации не имеют свойства имени, это создаст приватную переменную-член, которую можно будет использовать для облегчения доступа без необходимости длительного путешествия по иерархии объектов. Это особенно важно, поскольку, как указывалось выше, сложные трехмерные сцены часто имеют несколько уровней объектов Model3DGroup. Проход по этому дереву элементов вниз от ModelVisual3D верхнего уровня утомителен и чреват ошибками.

Вращения

Чтобы прочувствовать способы использования трансформаций, рассмотрим следующий код разметки. В нем применяется трансформация RotateTransform3D, которая позволяет вращать трехмерный объект вокруг указанных осей. В данном случае ось вращения направлена по оси Y координатной системы.

Используя именованное вращение, можно создать привязанный к данным ползунок (Slider), который позволит пользователям поворачивать куб вокруг его осей. Это вращение так же легко может использоваться в анимации. Рассмотрим пример анимации, вращающей тор (трехмерное кольцо) одновременно вокруг двух разных осей. Все начинается после щелчка на кнопке:

<Grid>
    <Grid.RowDefinitions>      
      <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
        
    <Viewport3D x:Name="viewport">
      <Viewport3D.Camera>
        <PerspectiveCamera x:Name="camera" FarPlaneDistance="10" LookDirection="0,0,-1" UpDirection="0,1,0" NearPlaneDistance="0.2" Position="0,0,2" FieldOfView="50" />
      </Viewport3D.Camera>

	  <ModelUIElement3D x:Name="ringVisual" MouseDown="ringVisual_MouseDown">
		<ModelUIElement3D.Model>
      
          <Model3DGroup x:Name="Scene">         
            
            <AmbientLight Color="#333333" />
            <DirectionalLight Color="#FFFFFF" Direction="-0.612372,-0.5,-0.612372" />
            <DirectionalLight Color="#FFFFFF" Direction="0.612372,-0.5,-0.612372" />
            
            <Model3DGroup>            
              <Model3DGroup.Transform>
                <Transform3DGroup>                  
                  <RotateTransform3D>
                    <RotateTransform3D.Rotation>
                      <AxisAngleRotation3D Angle="0" Axis="0.9901928156 0 0.1397075084"/>
                    </RotateTransform3D.Rotation>
                  </RotateTransform3D>
                  <RotateTransform3D>
                    <RotateTransform3D.Rotation>
                      <AxisAngleRotation3D Angle="0" Axis="0 0.9901928156 0.1397075084"/>
                    </RotateTransform3D.Rotation>
                  </RotateTransform3D>
                </Transform3DGroup>
              </Model3DGroup.Transform>
              <GeometryModel3D x:Name="ringModel">
                <GeometryModel3D.Material>
                  <MaterialGroup>
                    <DiffuseMaterial>
                      <DiffuseMaterial.Brush>
                        <SolidColorBrush Color="DarkBlue" />
                      </DiffuseMaterial.Brush>
                    </DiffuseMaterial>

                    <SpecularMaterial SpecularPower="24">
                      <SpecularMaterial.Brush>
                        <SolidColorBrush Color="LightBlue" Opacity="1.000000"/>
                      </SpecularMaterial.Brush>
                    </SpecularMaterial>

                  </MaterialGroup>
                </GeometryModel3D.Material>
                <GeometryModel3D.BackMaterial>
                  <MaterialGroup>
                    <DiffuseMaterial>
                      <DiffuseMaterial.Brush>
                        <SolidColorBrush Color="#7F7F92" Opacity="1.000000"/>
                      </DiffuseMaterial.Brush>
                    </DiffuseMaterial>
                    <SpecularMaterial SpecularPower="64">
                      <SpecularMaterial.Brush>
                        <SolidColorBrush Color="#DBDBDB" Opacity="1.000000"/>
                      </SpecularMaterial.Brush>
                    </SpecularMaterial>
                  </MaterialGroup>
                </GeometryModel3D.BackMaterial>
                <GeometryModel3D.Geometry>
                  <MeshGeometry3D x:Name="ringMesh"
                    ...
							/>
                </GeometryModel3D.Geometry>
                <GeometryModel3D.Transform>                                      
                      <RotateTransform3D>
                        <RotateTransform3D.Rotation>
                          <AxisAngleRotation3D x:Name="axisRotation" Angle="0" Axis="0 1 0.14"/>
                        </RotateTransform3D.Rotation>
                      </RotateTransform3D>                                      
                </GeometryModel3D.Transform>
              </GeometryModel3D>
            </Model3DGroup>
          </Model3DGroup>
        

	    </ModelUIElement3D.Model>
	  </ModelUIElement3D>

		</Viewport3D>
  </Grid>
public partial class HitTesting : System.Windows.Window
    {

        public HitTesting()
        {
            InitializeComponent();
        }

        private void ringVisual_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Point location = e.GetPosition(viewport);

            RayMeshGeometry3DHitTestResult meshHitResult = (RayMeshGeometry3DHitTestResult)VisualTreeHelper.HitTest(viewport, location);

            axisRotation.Axis = new Vector3D(
                     -meshHitResult.PointHit.Y, meshHitResult.PointHit.X, 0);

            DoubleAnimation animation = new DoubleAnimation();
            animation.To = 40;
            animation.DecelerationRatio = 1;
            animation.Duration = TimeSpan.FromSeconds(0.15);
            animation.AutoReverse = true;
            axisRotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, animation);
        }
        
        // Alternative implementation using the Viewport.MouseDown event.
        private void viewport_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Point location = e.GetPosition(viewport);
            HitTestResult hitResult = VisualTreeHelper.HitTest(viewport, location);
            
            if (hitResult != null && hitResult.VisualHit == ringVisual)
            {
                // Hit the ring.
            }
            
            RayMeshGeometry3DHitTestResult meshHitResult = hitResult as RayMeshGeometry3DHitTestResult;
            if (meshHitResult != null && meshHitResult.ModelHit == ringModel)
            {
                // Hit the ring.
            }
            if (meshHitResult != null && meshHitResult.MeshHit == ringMesh)
            { 
                axisRotation.Axis = new Vector3D(
                    -meshHitResult.PointHit.Y, meshHitResult.PointHit.X, 0);

                DoubleAnimation animation = new DoubleAnimation();                
                animation.To = 40;
                animation.DecelerationRatio = 1;
                animation.Duration = TimeSpan.FromSeconds(0.15);
                animation.AutoReverse = true;
                axisRotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, animation);               
            }
        }
    }
Вращающаяся трехмерная фигура
Пройди тесты
Лучший чат для C# программистов