Ray*_*rns 36
您正在寻找的是有效的非线性变换.Visual上的Transform属性只能进行线性变换.幸运的是,WPF的3D功能可以帮助你解决问题.您可以通过创建一个如下所示的简单自定义控件轻松完成您要查找的内容:
<local:DisplayOnPath Path="{Binding ...}" Content="Text to display" />
Run Code Online (Sandbox Code Playgroud)
这是怎么做的:
首先创建"DisplayOnPath"自定义控件.
Geometry
(使用wpfdp片段)Geometry3D
(使用wpfdpro片段)PropertyChangedCallback
for Path来调用"ComputeDisplayMesh"方法将Path转换为Geometry3D,然后从中设置DisplayMesh它看起来像这样:
public class DisplayOnPath : ContentControl
{
static DisplayOnPath()
{
DefaultStyleKeyProperty.OverrideMetadata ...
}
public Geometry Path { get { return (Geometry)GetValue(PathProperty) ...
public static DependencyProperty PathProperty = ... new UIElementMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var displayOnPath = obj as DisplayOnPath;
displayOnPath.DisplayMesh = ComputeDisplayMesh(displayOnPath.Path);
}));
public Geometry3D DisplayMesh { get { ... } private set { ... } }
private static DependencyPropertyKey DisplayMeshPropertyKey = ...
public static DependencyProperty DisplayMeshProperty = ...
}
Run Code Online (Sandbox Code Playgroud)
接下来,在任何自定义控件中创建样式和控件模板Themes/Generic.xaml
(或ResourceDictionary
由其包含).模板将包含以下内容:
<Style TargetType="{x:Type local:DisplayOnPath}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DisplayOnPath}">
<Viewport3DVisual ...>
<ModelVisual3D>
<ModelVisual3D.Content>
<GeometryModel3D Geometry="{Binding DisplayMesh, RelativeSource={RelativeSource TemplatedParent}}">
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<VisualBrush ...>
<VisualBrush.Visual>
<ContentPresenter />
...
Run Code Online (Sandbox Code Playgroud)
这样做是显示一个3D模型,它使用DisplayMesh进行定位,并使用控件的内容作为画笔材质.
请注意,您可能需要在Viewport3DVisual和VisualBrush上设置其他属性,以使布局按您希望的方式工作,并使内容视觉适当地拉伸.
剩下的就是"ComputeDisplayMesh"功能.如果您希望内容的顶部(您正在显示的单词)与路径垂直一定距离,则这是一个简单的映射.当然,您可以选择其他算法,例如创建并行路径并使用每个算法的百分比距离.
在任何情况下,基本算法都是相同的:
PathGeometry
使用PathGeometry.CreateFromGeometry
Positions
矩形所有角的值.顶部有n + 1个角,底部有n + 1个角.每个底角都可以通过调用找到PathGeometry.GetPointAtFractionOfLength
.这也会返回切线,因此也很容易找到顶角.TriangleIndices
.这是微不足道的.每个矩形将是两个三角形,因此每个矩形将有六个索引.TextureCoordinates
.这更加微不足道,因为它们都是0,1或i/n(其中i是矩形索引).请注意,如果使用固定值n,则路径更改时唯一需要重新计算的是Posisions
数组.其他一切都是固定的.
以下是此方法的主要部分:
var pathGeometry = PathGeometry.CreateFromGeometry(path);
int n=50;
// Compute points in 2D
var positions = new List<Point>();
for(int i=0; i<=n; i++)
{
Point point, tangent;
pathGeometry.GetPointAtFractionOfLength((double)i/n, out point, out tangent);
var perpendicular = new Vector(tangent.Y, -tangent.X);
perpendicular.Normalize();
positions.Add(point + perpendicular * height); // Top corner
positions.Add(point); // Bottom corner
}
// Convert to 3D by adding 0 'Z' value
mesh.Positions = new Point3DCollection(from p in positions select new Point3D(p.X, p.Y, 0));
// Now compute the triangle indices, same way
for(int i=0; i<n; i++)
{
// First triangle
mesh.TriangleIndices.Add(i*2+0); // Upper left
mesh.TriangleIndices.Add(i*2+2); // Upper right
mesh.TriangleIndices.Add(i*2+1); // Lower left
// Second triangle
mesh.TriangleIndices.Add(i*2+1); // Lower left
mesh.TriangleIndices.Add(i*2+2); // Upper right
mesh.TriangleIndices.Add(i*2+3); // Lower right
}
// Add code here to create the TextureCoordinates
Run Code Online (Sandbox Code Playgroud)
就是这样.大部分代码都是上面写的.我留给你填写其余部分.
顺便说一句,请注意,通过创造"Z"值,您可以获得一些真正令人敬畏的效果.
更新
Mark为此实现了代码并遇到了三个问题.以下是问题和解决方案:
我在三角形#1的TriangleIndices命令中犯了一个错误.它在上面得到纠正.我最初的那些指数在左上角 - 左下角 - 右上角.通过逆时针绕三角形,我们实际上看到三角形的背面,所以没有画任何东西.通过简单地改变索引的顺序,我们顺时针转动,以便三角形可见.
GeometryModel3D上的绑定最初是一个TemplateBinding
.这不起作用,因为TemplateBinding不会以相同的方式处理更新.将其更改为常规绑定可以解决问题.
3D的坐标系是+ Y是向上的,而对于2D + Y是向下的,所以路径是颠倒的.这可以通过在代码中否定Y或者根据您的喜好添加RenderTransform
on 来解决ViewPort3DVisual
.我个人更喜欢RenderTransform,因为它使ComputeDisplayMesh代码更具可读性.
以下是马克代码的快照,其中包含了我认为我们共享的情绪:
动画文本快照"StackOverflowIsFun"http://rayburnsresume.com/StackOverflowImages/WavyLetters.png