WPF 是 Microsoft 在 Framework3.0 中支持的一種技術,它能作出很絢麗的界面,同時它也支持3D的操作。在3D操作主要包括平移(Translate)、旋轉(Rotation)、縮放(Scale)。
本文中主要是討論3D 模型的旋轉。對於旋轉操作可以采用旋轉模型,通過模型的Transform屬性來完成;也可以通過第一人稱的攝像機通過旋轉攝像機來完成旋轉。當然處於對性能的考慮本文選擇第二種方法的方式來旋轉模型。
完成攝像機的旋轉需要通過以下幾個步驟:
1. 對於不同的模型,設置攝像機的位置(Position),遠焦距(FarPlaneDistance),近焦距(NearPlaneDistance),看的方向(LookDirection),向上的向量(UpDirection)。
2. 旋轉圍繞的旋轉抽和圍繞旋轉的中心。
首先,對於不同的模型放入到世界坐標中攝像機的位置肯定會不同。如何確定攝像機的位置呢?在XNA中XNA框架提供了一個SphereBounding的屬性來獲得模型的外切圓,從而得到了圓心。然而在WPF中卻沒有發現SphereBounding的身影。不過仔細想想,Microsoft 不可能在XNA中提供了計算外切圓的方法,在WPF中不可能不提供類似的方法。然后我們仔細尋找,你會發現在MeshGeometry3D有一個Bounds的屬性,該屬性是返回MesheGeometry3D的邊界Rect3D。Rect3D表示一個三維矩形,也就是模型的外切矩形。
對於一個模型很有可能是由多個ModelVisual3D組成,這就預味着要把這些ModelVisual3D的外切矩形合並,好在在Rect3D中提供了Union方法(MSDN: 已重載。 更新指定的 Rect3D 以反映該 Rect3D 與第二個指定 Rect3D 的聯合)。最后通過對三維矩形的Size 和 Location 來計算出矩形的中心。

1 private void UnionRect(ModelVisual3D model, ref Rect3D rect3D) 2 { 3 for (int i = 0; i < model.Children.Count; i++) 4 { 5 var child = model.Children[i] as ModelVisual3D; 6 UnionRect(child, ref rect3D); 7 8 } 9 if (model.Content != null) 10 rect3D.Union(model.Content.Bounds); 11 }
通過上面獲得的中心和矩形的對角線來計算出矩形外切圓的半徑。在本例中我們讓模型圍繞Y軸以自己為中心旋轉,所以只需要通過中心點和半徑來設置攝像機的Z或者X,通過設置攝像機的X和Z。這樣就使得中心點 X,Z在同一個平面中,同時還在同一個圓形中,這樣就是的攝像機的旋轉圍繞着圓點做圓心運動。
public void MeasureModel(ModelVisual3D model) { var camera = _baseModel.Camera; var rect3D = Rect3D.Empty; UnionRect(model, ref rect3D); _center = new Point3D((rect3D.X + rect3D.SizeX / 2), (rect3D.Y + rect3D.SizeY / 2), (rect3D.Z + rect3D.SizeZ / 2)); double radius = (_center - rect3D.Location).Length; Point3D position = _center; position.Z += radius * 1.2; position.X = position.Z; camera.Position = position; camera.LookDirection = _center - position; camera.NearPlaneDistance = radius / 100; camera.FarPlaneDistance = radius * 100; }
設置攝像機的相關屬性后,接下來就該是對攝像機做旋轉操作了。WPF中旋轉提供了兩個類AxisAngleRotation3D 和 QuaternionRotation3D。本例中我們選擇最常用的AxisAngleRotation3D來完成。在AxisAngleRotation3D提供了CenterX,CenterY,CenterZ這幾個屬性,用來設置旋轉圍繞的中心,默認的中心點為(0,0,0),本例中為了實現模型以自己為中心旋轉,因此我們需要設置旋轉中心。
public void Yaw(bool leftRight, double angleDeltaFactor) { var camera = _baseModel.Camera; var axis = new AxisAngleRotation3D(camera.UpDirection, leftRight ? angleDeltaFactor : -angleDeltaFactor); var rt3D = new RotateTransform3D(axis) { CenterX = _center.X, CenterY = _center.Y, CenterZ = _center.Z }; Matrix3D matrix3D = rt3D.Value; Point3D point3D = camera.Position; Point3D position = matrix3D.Transform(point3D); camera.Position = position; camera.LookDirection = camera.LookDirection = _center - position; }