需求
需要将UI元素布置到3D模型上,还能实现对UI元素的操作。
环境
实现
UI元素模板
控件模型UserControlTemplate.xaml
<UserControl x:Class="Melphi.UserControlTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Melphi"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="200">
<Viewbox>
<Grid Width="200" Height="200">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Grid>
<Rectangle Width="1" Margin="10 0">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="#FFF10707" Offset="0"/>
<GradientStop Color="Gainsboro" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Click="PlayClicked" Content="A" VerticalAlignment="Bottom"/>
</Grid>
<Grid>
<Rectangle Width="1" Margin="10 0">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="#FFF17C07" Offset="0"/>
<GradientStop Color="Gainsboro" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Click="PlayClicked" Content="B" VerticalAlignment="Bottom"/>
</Grid>
<Grid>
<Rectangle Width="1" Margin="10 0">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="#FFF1F107" Offset="0"/>
<GradientStop Color="Gainsboro" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Click="PlayClicked" Content="C" VerticalAlignment="Bottom"/>
</Grid>
<Grid>
<Rectangle Width="1" Margin="10 0">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="#FF27F107" Offset="0"/>
<GradientStop Color="Gainsboro" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Click="PlayClicked" Content="D" VerticalAlignment="Bottom"/>
</Grid>
<Grid>
<Rectangle Width="1" Margin="10 0">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="#FF07F1E6" Offset="0"/>
<GradientStop Color="Gainsboro" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Click="PlayClicked" Content="E" VerticalAlignment="Bottom"/>
</Grid>
<Grid>
<Rectangle Width="1" Margin="10 0">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="#FF073CF1" Offset="0"/>
<GradientStop Color="Gainsboro" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Click="PlayClicked" Content="F" VerticalAlignment="Bottom"/>
</Grid>
<Grid>
<Rectangle Width="1" Margin="10 0">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="#FFD107F1" Offset="0"/>
<GradientStop Color="Gainsboro" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Button Click="PlayClicked" Content="G" VerticalAlignment="Bottom"/>
</Grid>
</StackPanel>
<TextBlock x:Name="txtblkSing" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFF55A02" Offset="0"/>
<GradientStop Color="#FFC90BF3" Offset="1"/>
</LinearGradientBrush>
</TextBlock.Background>
</TextBlock>
</Grid>
</Viewbox>
</UserControl>
场景控制球
鼠标操作 Trackball.cs
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Media3D;
namespace Melphi
{
/// <summary>
/// 鼠标跟踪球
/// </summary>
/// <typeparam name="TChild">Viewport3D 的首个变换模型的结构类型</typeparam>
public class Trackball<TChild> where TChild : Visual3D
{
private Vector3D _center;
private bool _centered;
// The state of the trackball
private bool _enabled;
private Point _point; // Initial point of drag
private bool _rotating;
private Quaternion _rotation;
private Quaternion _rotationDelta; // Change to rotation because of this drag
private double _scale;
private double _scaleDelta; // Change to scale because of this drag
// The state of the current drag
private bool _scaling;
private List<Viewport3D> _slaves;
private Vector3D _translate;
private Vector3D _translateDelta;
public Trackball()
{
Reset();
}
public List<Viewport3D> Slaves
{
get { return _slaves ?? (_slaves = new List<Viewport3D>()); }
set { _slaves = value; }
}
public bool Enabled
{
get { return _enabled && (_slaves != null) && (_slaves.Count > 0); }
set { _enabled = value; }
}
public void Attach(FrameworkElement element)
{
element.MouseMove += MouseMoveHandler;
element.MouseRightButtonDown += MouseDownHandler;
element.MouseRightButtonUp += MouseUpHandler;
element.MouseWheel += OnMouseWheel;
}
public void Detach(FrameworkElement element)
{
element.MouseMove -= MouseMoveHandler;
element.MouseRightButtonDown -= MouseDownHandler;
element.MouseRightButtonUp -= MouseUpHandler;
element.MouseWheel -= OnMouseWheel;
}
// Updates the matrices of the slaves using the rotation quaternion.
private void UpdateSlaves(Quaternion q, double s, Vector3D t)
{
if (_slaves != null)
{
foreach (var i in _slaves)
{
var mv = i.Children[0] as TChild;
var t3Dg = mv.Transform as Transform3DGroup;
var groupScaleTransform = t3Dg.Children[0] as ScaleTransform3D;
var groupRotateTransform = t3Dg.Children[1] as RotateTransform3D;
var groupTranslateTransform = t3Dg.Children[2] as TranslateTransform3D;
groupScaleTransform.ScaleX = s;
groupScaleTransform.ScaleY = s;
groupScaleTransform.ScaleZ = s;
groupRotateTransform.Rotation = new AxisAngleRotation3D(q.Axis, q.Angle);
groupTranslateTransform.OffsetX = t.X;
groupTranslateTransform.OffsetY = t.Y;
groupTranslateTransform.OffsetZ = t.Z;
}
}
}
private void MouseMoveHandler(object sender, MouseEventArgs e)
{
if (!Enabled) return;
e.Handled = true;
var el = (UIElement)sender;
if (el.IsMouseCaptured)
{
var delta = _point - e.MouseDevice.GetPosition(el);
delta /= 2;
var q = _rotation;
if (_rotating)
{
// We can redefine this 2D mouse delta as a 3D mouse delta
// where "into the screen" is Z
var mouse = new Vector3D(delta.X, -delta.Y, 0);
var axis = Vector3D.CrossProduct(mouse, new Vector3D(0, 0, 1));
var len = axis.Length;
if (len < 0.00001 || _scaling)
_rotationDelta = new Quaternion(new Vector3D(0, 0, 1), 0);
else
_rotationDelta = new Quaternion(axis, len);
q = _rotationDelta * _rotation;
}
else
{
delta /= 20;
_translateDelta.X = delta.X * -1;
_translateDelta.Y = delta.Y;
}
var t = _translate + _translateDelta;
UpdateSlaves(q, _scale * _scaleDelta, t);
}
}
private void MouseDownHandler(object sender, MouseButtonEventArgs e)
{
if (!Enabled) return;
e.Handled = true;
if (Keyboard.IsKeyDown(Key.F1))
{
Reset();
return;
}
var el = (UIElement)sender;
_point = e.MouseDevice.GetPosition(el);
// Initialize the center of rotation to the lookatpoint
if (!_centered)
{
var camera = (ProjectionCamera)_slaves[0].Camera;
_center = camera.LookDirection;
_centered = true;
}
_scaling = (e.MiddleButton == MouseButtonState.Pressed);
_rotating = Keyboard.IsKeyDown(Key.Space) == false;
el.CaptureMouse();
}
private void MouseUpHandler(object sender, MouseButtonEventArgs e)
{
if (!_enabled) return;
e.Handled = true;
// Stuff the current initial + delta into initial so when we next move we
// start at the right place.
if (_rotating)
_rotation = _rotationDelta * _rotation;
else
{
_translate += _translateDelta;
_translateDelta.X = 0;
_translateDelta.Y = 0;
}
//_scale = _scaleDelta*_scale;
var el = (UIElement)sender;
el.ReleaseMouseCapture();
}
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
_scaleDelta += e.Delta / (double)1000;
var q = _rotation;
UpdateSlaves(q, _scale * _scaleDelta, _translate);
}
private void MouseDoubleClickHandler(object sender, MouseButtonEventArgs e)
{
Reset();
}
private void Reset()
{
_rotation = new Quaternion(0, 0, 0, 1);
_scale = 1;
_translate.X = 0;
_translate.Y = 0;
_translate.Z = 0;
_translateDelta.X = 0;
_translateDelta.Y = 0;
_translateDelta.Z = 0;
// Clear delta too, because if reset is called because of a double click then the mouse
// up handler will also be called and this way it won't do anything.
_rotationDelta = Quaternion.Identity;
_scaleDelta = 1;
UpdateSlaves(_rotation, _scale, _translate);
}
}
}
Viewport2DVisual3D
3D模型UserControlViewport2DVisual3D.xaml
<UserControl x:Class="Melphi.UserControlViewport2DVisual3D"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Melphi"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Viewport3D x:Name="viewport3DScan" ClipToBounds="True">
<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,3.0" LookDirection="0,0,-1" UpDirection="0,1,0" NearPlaneDistance="0.25" FarPlaneDistance="20" FieldOfView="60"/>
</Viewport3D.Camera>
<Viewport3D.Children>
<Viewport2DVisual3D>
<!-- 模型图形 -->
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="-1 -1 0 1 -1 0 -1 1 0 1 1 0"
Normals="0 0 1 0 0 1 0 0 1 0 0 1"
TextureCoordinates="0 1 1 1 0 0 1 0 "
TriangleIndices="0 1 2 1 3 2" />
</Viewport2DVisual3D.Geometry>
<!-- 变换 -->
<Viewport2DVisual3D.Transform>
<Transform3DGroup>
<Transform3DGroup.Children>
<Transform3DCollection>
<ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1"/>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0 1 0" Angle="0"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0"/>
</Transform3DCollection>
</Transform3DGroup.Children>
</Transform3DGroup>
</Viewport2DVisual3D.Transform>
<!-- 材质 -->
<Viewport2DVisual3D.Material>
<DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True" Brush="Red"/>
</Viewport2DVisual3D.Material>
<!-- 附着元素 -->
<local:UserControlTemplate/>
</Viewport2DVisual3D>
<ModelVisual3D>
<ModelVisual3D.Content>
<!-- 灯光 -->
<DirectionalLight/>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
</Viewport3D>
</Grid>
</UserControl>
3D模型UserControlViewport2DVisual3D.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Media3D;
namespace Melphi
{
/// <summary>
/// UserControlViewport2DVisual3D.xaml 的交互逻辑
/// </summary>
public partial class UserControlViewport2DVisual3D : UserControl
{
Trackball<Viewport2DVisual3D> mTrackball;
public UserControlViewport2DVisual3D()
{
InitializeComponent();
Loaded += UserControlViewport2DVisual3D_Loaded;
}
private void UserControlViewport2DVisual3D_Loaded(object sender, RoutedEventArgs e)
{
mTrackball = new Trackball<Viewport2DVisual3D>();
mTrackball.Attach(this);
mTrackball.Slaves.Add(viewport3DScan);
mTrackball.Enabled = true;
}
}
}
主窗口 MianWindow.xaml
<Window x:Class="Melphi.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:Melphi"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<Grid>
<TabControl>
<TabItem Header="Viewport2DVisual3D">
<local:UserControlViewport2DVisual3D/>
</TabItem>
</TabControl>
</Grid>
</Window>
效果:
ModelVisual3D
3D模型UserControlModelVisual3D.xaml
<UserControl x:Class="Melphi.UserControlModelVisual3D"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Melphi"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="Black">
<Viewport3D x:Name="viewport3DScan" ClipToBounds="True">
<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,3.0" LookDirection="0,0,-1" UpDirection="0,1,0" NearPlaneDistance="0.25" FarPlaneDistance="20" FieldOfView="60"/>
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Transform>
<Transform3DGroup>
<Transform3DGroup.Children>
<Transform3DCollection>
<ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1" />
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0 1 0" Angle="0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0" />
</Transform3DCollection>
</Transform3DGroup.Children>
</Transform3DGroup>
</ModelVisual3D.Transform>
<ModelVisual3D.Content>
<Model3DGroup>
<Model3DGroup.Transform>
<Transform3DGroup>
<Transform3DGroup.Children>
<Transform3DCollection>
<ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1" />
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="0 1 0" Angle="0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0" />
</Transform3DCollection>
</Transform3DGroup.Children>
</Transform3DGroup>
</Model3DGroup.Transform>
<Model3DGroup.Children>
<Model3DGroup>
<Model3DGroup.Transform>
<Transform3DGroup>
<Transform3DGroup.Children>
<Transform3DCollection>
<ScaleTransform3D ScaleX="1" ScaleY="1" ScaleZ="1" />
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D Axis="1 0 0" Angle="0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<TranslateTransform3D OffsetX="0" OffsetY="0" OffsetZ="0" />
</Transform3DCollection>
</Transform3DGroup.Children>
</Transform3DGroup>
</Model3DGroup.Transform>
<Model3DGroup.Children>
<GeometryModel3D >
<GeometryModel3D.Geometry>
<MeshGeometry3D Positions="-1 -1 0 1 -1 0 -1 1 0 1 1 0"
Normals="0 0 1 0 0 1 0 0 1 0 0 1"
TextureCoordinates="0 1 1 1 0 0 1 0 "
TriangleIndices="0 1 2 1 3 2" />
</GeometryModel3D.Geometry>
<GeometryModel3D.Material>
<MaterialGroup>
<MaterialGroup.Children>
<EmissiveMaterial>
<EmissiveMaterial.Brush>
<VisualBrush>
<VisualBrush.Visual>
<local:UserControlTemplate/>
</VisualBrush.Visual>
</VisualBrush>
</EmissiveMaterial.Brush>
</EmissiveMaterial>
</MaterialGroup.Children>
</MaterialGroup>
</GeometryModel3D.Material>
<GeometryModel3D.BackMaterial>
<MaterialGroup>
<MaterialGroup.Children>
<EmissiveMaterial>
<EmissiveMaterial.Brush>
<SolidColorBrush Color="Red"/>
</EmissiveMaterial.Brush>
</EmissiveMaterial>
</MaterialGroup.Children>
</MaterialGroup>
</GeometryModel3D.BackMaterial>
</GeometryModel3D>
</Model3DGroup.Children>
</Model3DGroup>
</Model3DGroup.Children>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
<!--<ModelVisual3D>
<ModelVisual3D.Content>
--><!--灯光--><!--
<DirectionalLight Color="White"/>
</ModelVisual3D.Content>
</ModelVisual3D>-->
</Viewport3D.Children>
</Viewport3D>
</Grid>
</UserControl>
3D模型 UserControlModelVisual3D.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Media3D;
namespace Melphi
{
/// <summary>
/// UserControlGeometryModel3D.xaml 的交互逻辑
/// </summary>
public partial class UserControlModelVisual3D : UserControl
{
Trackball<ModelVisual3D> mTrackball;
public UserControlModelVisual3D()
{
InitializeComponent();
Loaded += UserControlModelVisual3D_Loaded; ;
}
private void UserControlModelVisual3D_Loaded(object sender, RoutedEventArgs e)
{
mTrackball = new Trackball<ModelVisual3D>();
mTrackball.Attach(this);
mTrackball.Slaves.Add(viewport3DScan);
mTrackball.Enabled = true;
}
}
}
主窗口 MianWindow.xaml
<Window x:Class="Melphi.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:Melphi"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800">
<Grid>
<TabControl>
<TabItem Header="Viewport2DVisual3D">
<local:UserControlViewport2DVisual3D/>
</TabItem>
<TabItem Header="ModelVisual3D">
<local:UserControlModelVisual3D/>
</TabItem>
</TabControl>
</Grid>
</Window>
效果
总结
Viewport2DVisual3D元素能将UI元素附着到3D模型上,模型上的元素能正常操作与控制。但是ModelVisual3D上的材质元素不能正常操作与控制。
Over
每次记录一小步…点点滴滴人生路…