WPF3Dことはじめ

WPFの3Dクラスがややこしい。

wpf3d

左から。WPFのWindowやGridに3D表示されるUIElementはViewPort3D。このViewPort3DにはCameraプロパティがあって3Dモデルを映している形にります。その3DモデルはModelVisual3Dオブジェクトですが、通常は複数のオブジェクトを保有するのでModel3DGroupでグループ化してやる必要があります。

Model3DGroupの中にはLight(AmbientLightやDirectionalLight)と目的の3Dモデル(Geometry3Dオブジェクト)を保有する形になる。3Dモデルはまず3頂点で三角形をつくる、これがMeshGeometry3D、それにMaterial(テクスチャ)を貼り付けてGeometry3Dを作る。

 

1.3DオブジェクトをXAMLで作る

3DオブジェクトをXAMLコードでカキカキするのはほぼ不可能、モデラーを使います。Blenderがメジャーぽいけど使いにくかったのでメタセコイアで。初心者向けの書籍もAmazon中古で格安になっていたりするのでお勧め。

metasequoia

モデルを作成後ファイル保存(.mqo)し、Identityで開きXAMLで保存します。アニメーションも作りたい場合はIdentityで作ります。

Identity_

できたXAMLを張り付ければ3Dオブジェクトは完成。

Test.xaml

<Viewbox x:Name="Viewbox_test" 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/interactivedesigner/2006" xmlns:c="http://schemas.openxmlformats.org/markup-compatibility/2006" c:Ignorable="d">
	<Viewport3D x:Name="Viewport3D_test" Width="400" Height="300">
		<Viewport3D.Resources>
			<ResourceDictionary>
				<MaterialGroup x:Key="Material_test_NoMaterial" >
					<DiffuseMaterial>
						<DiffuseMaterial.Brush>
							<SolidColorBrush Color="#cccccc" Opacity="1.000000"/>
						</DiffuseMaterial.Brush>
					</DiffuseMaterial>
				</MaterialGroup>
				<Transform3DGroup x:Key="Transform_Character_test_Character_0" >
					<TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/>
					<ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/>
					<RotateTransform3D CenterX="0" CenterY="0" CenterZ="0">
						<RotateTransform3D.Rotation>
							<AxisAngleRotation3D Angle="0" Axis="0 1 0"/>
						</RotateTransform3D.Rotation>
					</RotateTransform3D>
					<TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/>
				</Transform3DGroup>
				<Transform3DGroup x:Key="Transform_Model_test_obj1" >
					<TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/>
					<ScaleTransform3D CenterX="0.000000" CenterY="0.000000" CenterZ="0.000000" ScaleX="1" ScaleY="1" ScaleZ="1"/>
					<RotateTransform3D CenterX="0.000000" CenterY="0.000000" CenterZ="0.000000">
						<RotateTransform3D.Rotation>
							<AxisAngleRotation3D Angle="0" Axis="0 1 0"/>
						</RotateTransform3D.Rotation>
					</RotateTransform3D>
					<TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/>
				</Transform3DGroup>
				<MeshGeometry3D x:Key="Geometry_test_obj1_0"
					TriangleIndices="2,1,0 3,2,0 5,4,1 2,5,1 7,6,4 5,7,4 3,0,6 7,3,6 1,4,6 0,1,6 5,2,3 7,5,3 "
					Positions="-160.219193,132.025406,117.890900 161.003204,132.025406,117.890900 202.421402,-139.093903,117.890800 -201.637405,-139.093903,117.890800 161.003204,132.025299,-117.890999 202.421402,-139.093994,-117.891098 -160.219193,132.025299,-117.890999 -201.637405,-139.093994,-117.891098 "
				/>
			</ResourceDictionary>
		</Viewport3D.Resources>

		<Viewport3D.Camera>
			<PerspectiveCamera x:Name="Camera_test" Position="955.806458,986.842163,1417.041260" LookDirection="-0.484275,-0.500000,-0.717968" UpDirection="0.000000,1.000000,0.000000" FieldOfView="20.000000" NearPlaneDistance="1" FarPlaneDistance="10000" />
		</Viewport3D.Camera>

		<ModelVisual3D>
			<ModelVisual3D.Content>
				<Model3DGroup>
					<AmbientLight Color="#646464" />
					<DirectionalLight x:Name="Light_test" Color="#FFFFFF" Direction="-0.484275,-0.500000,-0.717968" />
				</Model3DGroup>
			</ModelVisual3D.Content>
		</ModelVisual3D>
		<ModelVisual3D x:Name="Character_test_Character_0" Transform="{StaticResource Transform_Character_test_Character_0}">
			<ModelVisual3D.Content>
				<Model3DGroup>
					<Model3DGroup x:Name="Model_test_obj1" Transform="{StaticResource Transform_Model_test_obj1}">
						<GeometryModel3D x:Name="Geometry_Model_test_obj1_0" Geometry="{StaticResource Geometry_test_obj1_0}" Material="{StaticResource Material_test_NoMaterial}"/>
					</Model3DGroup>
				</Model3DGroup>
			</ModelVisual3D.Content>
		</ModelVisual3D>
	</Viewport3D>

	<Viewbox.Resources>
		<Storyboard Duration="Forever" FillBehavior="HoldEnd" BeginTime="0:0:0" x:Key="Animation_test_Animation_0">
		</Storyboard>
	</Viewbox.Resources>

	<Viewbox.Triggers>
		<EventTrigger RoutedEvent="Viewport3D.Loaded">
			<EventTrigger.Actions>
				<BeginStoryboard Storyboard="{StaticResource Animation_test_Animation_0}" />
			</EventTrigger.Actions>
		</EventTrigger>
	</Viewbox.Triggers>
</Viewbox>

ここまでコード0行。

2.3Dオブジェクトの操作

3Dオブジェクトではほぼ必須と言える回転・拡大縮小処理をビヘイビアとして用意。もっと完成度の高いビヘイビアが標準であるかと思ったのですが、見つかりませんでした。

 Wpf3DModelOperateBehavior.cs

namespace Wpf3DTestApp
{
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media.Media3D;
    using System.Windows.Interactivity;

    public class Wpf3DModelOperateBehavior : Behavior<Viewport3D>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.MouseWheel += AssociatedObject_MouseWheel;
            this.AssociatedObject.MouseMove += AssociatedObject_MouseMove;
            this.AssociatedObject.MouseDown += AssociatedObject_MouseDown;
            this.AssociatedObject.MouseUp += AssociatedObject_MouseUp;
        }

        protected override void OnDetaching()
        {
            this.AssociatedObject.MouseWheel += AssociatedObject_MouseWheel;
            this.AssociatedObject.MouseMove -= AssociatedObject_MouseMove;
            base.OnDetaching();
        }

        void AssociatedObject_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
        {
            var viewport = sender as Viewport3D;
            if (viewport == null){
                return;
            }
            var camera = viewport.Camera as PerspectiveCamera;
            if (camera == null){
                return;
            }
            camera.FieldOfView += e.Delta / 100;
        }

        bool dragging = false;
        Point startPoint;
        double startangle;
        void AssociatedObject_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            dragging = false;
        }

        void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            dragging = true;
            startPoint = e.GetPosition(sender as Viewport3D);
            var axisRot = GetRot(sender);
            if (axisRot == null)
            {
                return;
            }
            startangle = axisRot.Angle;
        }

        void AssociatedObject_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (!dragging){
                return;
            }
            var axisRot = GetRot(sender);
            if (axisRot == null){
                return;
            }
            axisRot.Angle = startangle + (e.GetPosition(sender as Viewport3D).X - startPoint.X);
        }

        private AxisAngleRotation3D GetRot(object sender)
        {
            var viewport = sender as Viewport3D;
            if (viewport == null)
            {
                return null;
            }
            var model = viewport.Children.First();
            if (model == null || !(model is ModelVisual3D))
            {
                return null;
            }
            var transform = model.Transform;
            if (transform == null)
            {
                return null;
            }
            RotateTransform3D rotate = null;
            if (transform is RotateTransform3D)
            {
                rotate = transform as RotateTransform3D;
            }
            if (transform is Transform3DGroup)
            {
                var transgroup = transform as Transform3DGroup;
                rotate = transgroup.Children.First(trans => trans is RotateTransform3D) as RotateTransform3D;
            }
            if (rotate == null)
            {
                return null;
            }
            return rotate.Rotation as AxisAngleRotation3D;
        }

    }
}

まずビヘイビアとは何か?はこちらを参照くださいませ。作る過程でわかったこととしては3Dの回転やスケーリングを汎用的なビヘイビアとして作るのは非常に難しそうということ。Viewport3Dから操作対象のオブジェクトネストが深いので、実際の変換を行える保証がない。3Dモデルの作り方も影響しそう。今回のビヘイビアを適用するならこんな感じ。

MainWindow.xaml(抜粋)

<Window
        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:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:me="clr-namespace:Wpf3DTestApp"
        mc:Ignorable="d" x:Class="Wpf3DTestApp.MainWindow"
        Title="MainWindow" Height="350" Width="525">
	<Grid MouseWheel="Grid_MouseWheel" MouseUp="Grid_MouseUp" MouseDown="Grid_MouseDown" MouseMove="Grid_MouseMove">
		<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:c="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Name="Viewbox_test" c:Ignorable="d" d:IsLocked="True">
            <Viewport3D x:Name="Viewport3D_test" Width="400" Height="300">
                <i:Interaction.Behaviors>
                    <me:Wpf3DModelOperateBehavior/>
                </i:Interaction.Behaviors>
            </Viewport3D>
		</Viewbox>
	</Grid>
</Window>

簡単な操作はこれでできるようになりました。

img

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中