如何使用 WPF 的 3D 组件或使用伪 3D 效果来创建“碗”效果,其中用户正在俯视碗并可以拖动矩形并更改矩形透视以使其看起来就像他们在碗里上下移动一样?我不追求任何重力效果或任何东西,只是当物品移动时,我需要调整它们的视角......
编辑:我一直在研究 WPF 中可用的实际 3D 效果,它们看起来确实非常强大,所以也许有人可以帮助在我的应用程序上获得一个半球体,然后将一些 3D 网格(矩形)绑定到其表面?
有什么想法吗?
谢谢,马克
如何使用 WPF 的 3D 组件或使用伪 3D 效果来创建“碗”效果,其中用户正在俯视碗并可以拖动矩形并更改矩形透视以使其看起来就像他们在碗里上下移动一样?我不追求任何重力效果或任何东西,只是当物品移动时,我需要调整它们的视角......
编辑:我一直在研究 WPF 中可用的实际 3D 效果,它们看起来确实非常强大,所以也许有人可以帮助在我的应用程序上获得一个半球体,然后将一些 3D 网格(矩形)绑定到其表面?
有什么想法吗?
谢谢,马克
啊,对了,那就给你 - 你现在可以在碗周围拖动红色矩形 - 享受吧!
<Window x:Class="wpfbowl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="500" Width="500"
DataContext="{Binding RelativeSource={RelativeSource Self}}" MouseDown="Window_MouseDown" MouseMove="Window_MouseMove" MouseUp="Window_MouseUp" >
<Window.Resources>
<Transform3DGroup x:Key="WorldTrans">
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="myAngleRotation" Axis="0,0,1" Angle="{Binding RotationLeftRight}" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="myAngleRotation2" Axis="1,0,0" Angle="{Binding RotationUpDown}" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</Window.Resources>
<StackPanel>
<Viewport3D Name="mainViewport" ClipToBounds="True" HorizontalAlignment="Stretch" Margin="0" Height="500" >
<Viewport3D.Camera>
<PerspectiveCamera
LookDirection="0,5,0"
UpDirection="0,0,1"
Position="0,-10,0"
/>
</Viewport3D.Camera>
<ModelVisual3D >
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<PointLight Position="0,-10,0" Range="150" Color="White" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</ModelVisual3D>
<ModelVisual3D Transform="{StaticResource WorldTrans}">
<ModelVisual3D Content="{Binding Models}">
</ModelVisual3D>
</ModelVisual3D>
<ModelVisual3D >
<ModelVisual3D Content="{Binding BowlModel}">
</ModelVisual3D>
</ModelVisual3D>
</Viewport3D>
</StackPanel>
</Window>
和背后的代码......
using System;
using System.ComponentModel;
using System.Timers;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
using System.Windows;
using System.Windows.Input;
namespace wpfbowl
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : INotifyPropertyChanged
{
public Window1()
{
InitModels();
InitializeComponent();
}
private Model3DGroup _cube;
private bool _cubeSelected;
private bool _cubeMoving;
private Point3D _startPoint;
private Point3D _currentPoint;
public void InitModels()
{
const int bowlQuality = 20;
Models = new Model3DGroup();
BowlModel = new Model3DGroup();
_cube = GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(0, 2.6, 0), new Size3D(1.5, 0.2, 2));
Models.Children.Add(_cube);
var bowl = CreateBowl(new Point3D(0, 0, 0), 3, bowlQuality, bowlQuality, GetSurfaceMaterial(Colors.Green));
BowlModel.Children.Add(bowl);
}
private readonly Timer _timer;
public Model3DGroup Models { get; set; }
public Model3DGroup BowlModel { get; set; }
private double _rotationLeftRight;
public double RotationLeftRight
{
get { return _rotationLeftRight; }
set
{
if (_rotationLeftRight == value) return;
_rotationLeftRight = value;
OnPropertyChanged("RotationLeftRight");
}
}
private double _rotationUpDown;
public double RotationUpDown
{
get { return _rotationUpDown; }
set
{
if (_rotationUpDown == value) return;
_rotationUpDown = value;
OnPropertyChanged("RotationUpDown");
}
}
public static Model3DGroup CreateBowl(Point3D center, double radius, int u, int v, MaterialGroup materialGroup)
{
var bowl = new Model3DGroup();
if (u < 2 || v < 2) return null;
var pts = new Point3D[u, v];
for (var i = 0; i < u; i++)
{
for (var j = 0; j < v; j++)
{
pts[i, j] = GetPosition(radius, i * 180 / (u - 1), j * 360 / (v - 1));
pts[i, j] += (Vector3D)center;
}
}
var p = new Point3D[4];
for (var i = 0; i < (u /2) - 1; i++)
{
for (var j = 0; j < v - 1; j++)
{
p[0] = pts[i, j];
p[1] = pts[i + 1, j];
p[2] = pts[i + 1, j + 1];
p[3] = pts[i, j + 1];
bowl.Children.Add(CreateTriangleModel(materialGroup, p[0], p[1], p[2]));
bowl.Children.Add(CreateTriangleModel(materialGroup, p[2], p[1], p[0]));
bowl.Children.Add(CreateTriangleModel(materialGroup, p[2], p[3], p[0]));
bowl.Children.Add(CreateTriangleModel(materialGroup, p[0], p[3], p[2]));
}
}
return bowl;
}
private static Model3DGroup CreateTriangleModel(Material material, Point3D p0, Point3D p1, Point3D p2)
{
var mesh = new MeshGeometry3D();
mesh.Positions.Add(p0);
mesh.Positions.Add(p1);
mesh.Positions.Add(p2);
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(1);
mesh.TriangleIndices.Add(2);
var normal = CalculateNormal(p0, p1, p2);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
var model = new GeometryModel3D(mesh, material);
var group = new Model3DGroup();
group.Children.Add(model);
return group;
}
private static Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2)
{
var v0 = new Vector3D(p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
var v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
return Vector3D.CrossProduct(v0, v1);
}
private static Point3D GetPosition(double radius, double theta, double phi)
{
var pt = new Point3D();
var snt = Math.Sin(theta * Math.PI / 180);
var cnt = Math.Cos(theta * Math.PI / 180);
var snp = Math.Sin(phi * Math.PI / 180);
var cnp = Math.Cos(phi * Math.PI / 180);
pt.X = radius * snt * cnp;
pt.Y = radius * cnt;
pt.Z = -radius * snt * snp;
return pt;
}
public static MaterialGroup GetSurfaceMaterial(Color colour)
{
var materialGroup = new MaterialGroup();
var emmMat = new EmissiveMaterial(new SolidColorBrush(colour));
materialGroup.Children.Add(emmMat);
materialGroup.Children.Add(new DiffuseMaterial(new SolidColorBrush(colour)));
var specMat = new SpecularMaterial(new SolidColorBrush(Colors.White), 30);
materialGroup.Children.Add(specMat);
return materialGroup;
}
public static Model3DGroup GetCube(MaterialGroup materialGroup, Point3D point, Size3D size)
{
var farPoint = new Point3D(point.X - (size.X / 2), point.Y - (size.Y / 2), point.Z - (size.Z / 2));
var nearPoint = new Point3D(point.X + (size.X / 2), point.Y + (size.Y / 2), point.Z + (size.Z / 2));
var cube = new Model3DGroup();
var p0 = new Point3D(farPoint.X, farPoint.Y, farPoint.Z);
var p1 = new Point3D(nearPoint.X, farPoint.Y, farPoint.Z);
var p2 = new Point3D(nearPoint.X, farPoint.Y, nearPoint.Z);
var p3 = new Point3D(farPoint.X, farPoint.Y, nearPoint.Z);
var p4 = new Point3D(farPoint.X, nearPoint.Y, farPoint.Z);
var p5 = new Point3D(nearPoint.X, nearPoint.Y, farPoint.Z);
var p6 = new Point3D(nearPoint.X, nearPoint.Y, nearPoint.Z);
var p7 = new Point3D(farPoint.X, nearPoint.Y, nearPoint.Z);
//front side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p3, p2, p6));
cube.Children.Add(CreateTriangleModel(materialGroup, p3, p6, p7));
//right side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p1, p5));
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p5, p6));
//back side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p1, p0, p4));
cube.Children.Add(CreateTriangleModel(materialGroup, p1, p4, p5));
//left side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p0, p3, p7));
cube.Children.Add(CreateTriangleModel(materialGroup, p0, p7, p4));
//top side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p7, p6, p5));
cube.Children.Add(CreateTriangleModel(materialGroup, p7, p5, p4));
//bottom side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p3, p0));
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p0, p1));
return cube;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
private void Window_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var mousePos = e.GetPosition(mainViewport);
var hitParams = new PointHitTestParameters(mousePos);
VisualTreeHelper.HitTest(mainViewport, null, ResultCallback, hitParams);
}
public HitTestResultBehavior ResultCallback(HitTestResult result)
{
// Did we hit 3D?
var rayResult = result as RayHitTestResult;
if (rayResult != null)
{
// Did we hit a MeshGeometry3D?
var rayMeshResult = rayResult as RayMeshGeometry3DHitTestResult;
if (rayMeshResult != null)
{
if (_cubeSelected)
{
_cubeMoving = true;
_currentPoint = rayMeshResult.PointHit;
RotationLeftRight = (_startPoint.X - _currentPoint.X) * 15;
RotationUpDown = (_currentPoint.Z -_startPoint.Z)*15;
}
else
{
var model = rayMeshResult.ModelHit;
foreach (var c in _cube.Children)
{
if (c.GetType() != typeof(Model3DGroup)) continue;
var model3DGroup = (Model3DGroup)c;
foreach (var sc in model3DGroup.Children)
{
if (model != sc) continue;
_cubeSelected = true;
_startPoint = rayMeshResult.PointHit;
}
}
}
}
}
return HitTestResultBehavior.Continue;
}
private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (!_cubeSelected) return;
var mousePos = e.GetPosition(mainViewport);
var hitParams = new PointHitTestParameters(mousePos);
VisualTreeHelper.HitTest(mainViewport, null, ResultCallback, hitParams);
}
private void Window_MouseUp(object sender, MouseButtonEventArgs e)
{
if (!_cubeSelected) return;
_cubeSelected = false;
_cubeMoving = false;
}
}
}
给你,我不太清楚你说的矩形是什么意思,所以我刚刚在一个绿色碗的开口周围添加了四个红色矩形。
干杯,
安迪
Xaml 首先...
<Window x:Class="wpfbowl.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="500" Width="500"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<Transform3DGroup x:Key="WorldTrans">
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="myAngleRotation" Axis="0,0,1" Angle="{Binding Rotation}" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</Window.Resources>
<StackPanel>
<Viewport3D Name="mainViewport" ClipToBounds="True" HorizontalAlignment="Stretch" Margin="0" Height="500" >
<Viewport3D.Camera>
<PerspectiveCamera
LookDirection="0,5,0"
UpDirection="0,0,1"
Position="0,-10,0"
/>
</Viewport3D.Camera>
<ModelVisual3D >
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<PointLight Position="0,-10,0" Range="150" Color="White" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</ModelVisual3D>
<ModelVisual3D Transform="{StaticResource WorldTrans}">
<ModelVisual3D Content="{Binding Models}">
</ModelVisual3D>
</ModelVisual3D>
</Viewport3D>
</StackPanel>
</Window>
...这是背后的代码...
using System;
using System.ComponentModel;
using System.Timers;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
namespace wpfbowl
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : INotifyPropertyChanged
{
public Window1()
{
InitModels();
InitializeComponent();
_timer = new Timer(100);
_timer.Elapsed += TimerElapsed;
_timer.Enabled = true;
}
void TimerElapsed(object sender, ElapsedEventArgs e)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action<double>(Transform), 2);
}
private void Transform(double value)
{
Rotation += value;
}
public void InitModels()
{
const int bowlQuality = 20;
Models = new Model3DGroup();
var sphere = CreateBowl(new Point3D(0, 0, 0), 3, bowlQuality, bowlQuality, GetSurfaceMaterial(Colors.Green));
Models.Children.Add(GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(3, 0, 0), new Size3D(1.5, 0.2, 2)));
Models.Children.Add(GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(-3, 0, 0), new Size3D(1.5, 0.2, 2)));
Models.Children.Add(GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(0, 0, 3), new Size3D(1.5, 0.2, 2)));
Models.Children.Add(GetCube(GetSurfaceMaterial(Colors.Red), new Point3D(0, 0, -3), new Size3D(1.5, 0.2, 2)));
Models.Children.Add(sphere);
}
private readonly Timer _timer;
public Model3DGroup Models { get; set; }
private double _rotation;
public double Rotation
{
get { return _rotation; }
set
{
if (_rotation == value) return;
_rotation = value;
OnPropertyChanged("Rotation");
}
}
public static Model3DGroup CreateBowl(Point3D center, double radius, int u, int v, MaterialGroup materialGroup)
{
var bowl = new Model3DGroup();
if (u < 2 || v < 2) return null;
var pts = new Point3D[u, v];
for (var i = 0; i < u; i++)
{
for (var j = 0; j < v; j++)
{
pts[i, j] = GetPosition(radius, i * 180 / (u - 1), j * 360 / (v - 1));
pts[i, j] += (Vector3D)center;
}
}
var p = new Point3D[4];
for (var i = 0; i < (u /2) - 1; i++)
{
for (var j = 0; j < v - 1; j++)
{
p[0] = pts[i, j];
p[1] = pts[i + 1, j];
p[2] = pts[i + 1, j + 1];
p[3] = pts[i, j + 1];
bowl.Children.Add(CreateTriangleModel(materialGroup, p[0], p[1], p[2]));
bowl.Children.Add(CreateTriangleModel(materialGroup, p[2], p[1], p[0]));
bowl.Children.Add(CreateTriangleModel(materialGroup, p[2], p[3], p[0]));
bowl.Children.Add(CreateTriangleModel(materialGroup, p[0], p[3], p[2]));
}
}
return bowl;
}
private static Model3DGroup CreateTriangleModel(Material material, Point3D p0, Point3D p1, Point3D p2)
{
var mesh = new MeshGeometry3D();
mesh.Positions.Add(p0);
mesh.Positions.Add(p1);
mesh.Positions.Add(p2);
mesh.TriangleIndices.Add(0);
mesh.TriangleIndices.Add(1);
mesh.TriangleIndices.Add(2);
var normal = CalculateNormal(p0, p1, p2);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
mesh.Normals.Add(normal);
var model = new GeometryModel3D(mesh, material);
var group = new Model3DGroup();
group.Children.Add(model);
return group;
}
private static Vector3D CalculateNormal(Point3D p0, Point3D p1, Point3D p2)
{
var v0 = new Vector3D(p1.X - p0.X, p1.Y - p0.Y, p1.Z - p0.Z);
var v1 = new Vector3D(p2.X - p1.X, p2.Y - p1.Y, p2.Z - p1.Z);
return Vector3D.CrossProduct(v0, v1);
}
private static Point3D GetPosition(double radius, double theta, double phi)
{
var pt = new Point3D();
var snt = Math.Sin(theta * Math.PI / 180);
var cnt = Math.Cos(theta * Math.PI / 180);
var snp = Math.Sin(phi * Math.PI / 180);
var cnp = Math.Cos(phi * Math.PI / 180);
pt.X = radius * snt * cnp;
pt.Y = radius * cnt;
pt.Z = -radius * snt * snp;
return pt;
}
public static MaterialGroup GetSurfaceMaterial(Color colour)
{
var materialGroup = new MaterialGroup();
var emmMat = new EmissiveMaterial(new SolidColorBrush(colour));
materialGroup.Children.Add(emmMat);
materialGroup.Children.Add(new DiffuseMaterial(new SolidColorBrush(colour)));
var specMat = new SpecularMaterial(new SolidColorBrush(Colors.White), 30);
materialGroup.Children.Add(specMat);
return materialGroup;
}
public static Model3DGroup GetCube(MaterialGroup materialGroup, Point3D point, Size3D size)
{
var farPoint = new Point3D(point.X - (size.X / 2), point.Y - (size.Y / 2), point.Z - (size.Z / 2));
var nearPoint = new Point3D(point.X + (size.X / 2), point.Y + (size.Y / 2), point.Z + (size.Z / 2));
var cube = new Model3DGroup();
var p0 = new Point3D(farPoint.X, farPoint.Y, farPoint.Z);
var p1 = new Point3D(nearPoint.X, farPoint.Y, farPoint.Z);
var p2 = new Point3D(nearPoint.X, farPoint.Y, nearPoint.Z);
var p3 = new Point3D(farPoint.X, farPoint.Y, nearPoint.Z);
var p4 = new Point3D(farPoint.X, nearPoint.Y, farPoint.Z);
var p5 = new Point3D(nearPoint.X, nearPoint.Y, farPoint.Z);
var p6 = new Point3D(nearPoint.X, nearPoint.Y, nearPoint.Z);
var p7 = new Point3D(farPoint.X, nearPoint.Y, nearPoint.Z);
//front side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p3, p2, p6));
cube.Children.Add(CreateTriangleModel(materialGroup, p3, p6, p7));
//right side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p1, p5));
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p5, p6));
//back side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p1, p0, p4));
cube.Children.Add(CreateTriangleModel(materialGroup, p1, p4, p5));
//left side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p0, p3, p7));
cube.Children.Add(CreateTriangleModel(materialGroup, p0, p7, p4));
//top side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p7, p6, p5));
cube.Children.Add(CreateTriangleModel(materialGroup, p7, p5, p4));
//bottom side triangles
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p3, p0));
cube.Children.Add(CreateTriangleModel(materialGroup, p2, p0, p1));
return cube;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
}