65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 F# 和 WPF 进行海龟图形和 L-系统

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2010年10月13日

CPOL

5分钟阅读

viewsIcon

35111

downloadIcon

456

本文将介绍如何使用海龟绘图程序,展示实现该程序所用的 F# 和 WPF 代码,并提供一些使用 L-系统语法生成分形形状的示例代码。

Dragon curve generated using L-system

引言

这是一个我编写的海龟绘图程序,用于提高我对 F# 的认识。海龟显示为一个光标,当接收到相对于自身位置的简单命令(如左转、右转和前进)时,会在画布上绘制线条。这些简单的命令可以作为绘制复杂形状(如螺旋和分形)的构建块。F# 的交互式提示提供了一种控制海龟的有用方式。本文将介绍如何使用海龟绘图程序,展示实现该程序所用的 F# 和 WPF 代码,并提供一些使用 L-系统语法生成分形形状的示例代码。

如何使用海龟

安装和运行 F#

这些说明假定您正在使用 Visual Studio 中的 F# Interactive,无论是 Visual Studio 2010 还是免费的 Shell。有关安装 F# 的信息可以在 Microsoft F# 开发中心找到。打开文件 FSharpTurtle.fsx,使用 Ctrl-A 全选,然后按 Alt-Enter 将脚本加载到 F# Interactive 中。此时会显示一个带有海龟的画布。

海龟命令

海龟响应命令 leftrightforwardmove 或它们的缩写 ltrtfdmv。这些命令后都跟一个浮点数和两个分号(例如 left 90.0;;)。leftright 命令以给定角度转动海龟,forward 绘制一条线,move 则在不绘制线条的情况下移动海龟。输入 clear();; 可以清除画布并将海龟恢复到原始位置。可以使用 settings 变量更改默认颜色(白色背景、黑色线条、红色箭头)。F# 使用 <- 运算符更改变量的值。

settings.BackgroundColour <- Brushes.Black;;
settings.ArrowColour <- Brushes.Green;;
settings.LineColour <- Brushes.Red;;

示例:绘制正方形

在 F# Interactive 中输入以下命令可以在画布上绘制一个正方形。

for i = 1 to 4 do
       forward 200.0
       left 90.0;;

海龟会绘制一条线并左转,直到回到其原始位置。

square

示例:绘制螺旋

let rec polyspi (angle:float)(inc:float)(side:float)(times:int) =
   if times > 0 then
      forward side
      right angle 
      polyspi angle inc (side + inc)(times - 1)
   else None |> ignore 

我从《海龟几何》(第 18-19 页) 中改编了上面的螺旋图案绘制函数。在 F# 中,let 关键字用于声明函数和变量,而 rec 表示函数是递归的。使用不同的参数调用此函数会创建不同形状的螺旋,因此可以使用偏函数应用(也称为柯里化)来创建更专业的 polyspi 函数。下面声明的函数预填充了其前 3 个 polyspi 参数,只接受最后一个参数 (times:int) 作为参数。

let polyspi90 = polyspi 90. 5. length

let polyspi95 = polyspi 95. 1. length

let polyspi117 = polyspi 117. 3. length

polyspi90 polyspi95 polyspi117

海龟绘图实现

类型定义

下面称为记录(records)的类型定义用于存储海龟的状态。海龟在画布上有一个 x, y 点和一个角度,分别由类型为 Point 的记录标签 P 和类型为 float 的记录标签 Angle 表示。关键词 mutable 用于指定变量的值可以被更改。

type Point =  {  mutable X : float
           mutable Y : float}
               
type Turtle = {   mutable P : Point
           mutable Angle : float }

下面的代码构建了海龟记录,并将其放在窗口的中心。Turtle 类型可以从记录标签推断出来,因此无需指定。

let turtle = {P = {X = w.ActualWidth/2.;Y = w.ActualHeight/2.}; Angle = 0.}

海龟坐标

海龟命令使用方向和距离来从起始点绘制线条。程序使用三角函数来查找线条终点的 x, y 坐标。下面定义的 nextPoint 函数将 forward 命令的距离视为直角三角形的斜边。这结合海龟的角度,用于查找三角形的邻边(x)和对边(y)的长度,并将它们添加到起始点,从而得到线条的终点。

let nextPoint hypotenuse =
     let newX = turtle.P.X + hypotenuse * Math.Cos(degreesRadians(turtle.Angle))
     let newY = turtle.P.Y + hypotenuse * Math.Sin(degreesRadians(turtle.Angle))
     let newPoint = {X = newX;Y = newY}
     newPoint

WPF 画布和海龟箭头多边形

海龟绘图绘制在 WPF 画布上。WPF 画布通常不可滚动,但我在这里找到了一些关于可滚动 Canvas 的 C# 代码 示例,并用 F# 重写了它。下面的代码创建了一个窗口、一个可滚动画布、一个滚动查看器以及一个表示海龟的箭头。

let w = new Window(Topmost=true)
w.Show()
let c = new ScrollableCanvas()
c.Background <- Brushes.White
let scrollViewer = new ScrollViewer()
scrollViewer.HorizontalScrollBarVisibility <- ScrollBarVisibility.Auto
scrollViewer.VerticalScrollBarVisibility <- ScrollBarVisibility.Auto
w.Content <- scrollViewer
scrollViewer.Content <- c
let makeArrow() = 
   let arrow = new Polygon()
   arrow.Fill <- Brushes.Red
   let p1 = new System.Windows.Point(0.,20.)
   let p2 = new System.Windows.Point(25.,10.)
   let p3 = new System.Windows.Point(0.,0.)
   let centrePoint = new System.Windows.Point(0.5,0.5)
   let pCollection = new PointCollection()
   pCollection.Add p1
   pCollection.Add p2
   pCollection.Add p3
   arrow.RenderTransformOrigin <- centrePoint
   arrow.Points <- pCollection
   arrow
  
let mutable arrow = makeArrow()
  
c.Children.Add(arrow)

下面定义的 leftright 函数改变海龟的角度,然后用它来旋转箭头多边形。

let left deg = turtle.Angle <- turtle.Angle - deg
            arrow.RenderTransform <- new RotateTransform(Angle = turtle.Angle)
let right deg = turtle.Angle <- turtle.Angle + deg
            arrow.RenderTransform <- new RotateTransform(Angle = turtle.Angle)

moveTo 函数将箭头移动到 WPF 画布上的给定 x, y 坐标。

let moveTo x y = turtle.P.X <- x
              turtle.P.Y <- y
              Canvas.SetLeft(arrow, turtle.P.X - 12.5)
              Canvas.SetTop(arrow, turtle.P.Y  - 10.0)
forward 函数使用 nextPoint 获取线条的终点,绘制线条,然后将箭头光标移动到终点。
let forward distance = 
   let next = nextPoint distance
   let l = new Line()
   l.X1 <- turtle.P.X
   l.Y1 <- turtle.P.Y
   l.X2 <- next.X
   l.Y2 <- next.Y
   l.Stroke <- settings.LineColour
   c.Children.Add(l) |> ignore
   moveTo next.X next.Y

使用 L-系统绘制分形形状

分形和 L-系统

分形是几何形状,由自相似的部分组成。分形形状(包括复杂、外观自然的结构)的生长可以使用 L-系统语法和海龟绘图来模拟。L-系统语法包含一组符号、一个起始符号字符串和描述符号如何被其他符号替换的产生规则。符号可以是变量或常量;常量在每一代中保持不变,变量则被替换为其他符号。其中一些符号代表海龟绘图命令。

DOL-系统

此海龟程序包含可用于绘制最简单的 L-系统(称为 DOL-系统)的代码,这些系统是确定性的(每个符号只有一个产生规则)且上下文无关的(产生规则仅取决于一个符号,而不取决于其相邻符号)。

L-系统实现

下面定义的数据类型可用于描述一个简单的 L-系统。它有一个起始符号字符串 Start,一个产生规则字典 Rules,其中 char 键是一个符号,string 值是生成的符号字符串。绘制形状时,海龟会向左或向右转动一定的 Angle

type LSystem = { Start     : string
                         Rules     : Dictionary<char,string< /> />
                         Angle     : float}

可以使用下面定义的 l-系统绘制上面植物状的图像。

    let branching =
    let rules = new Dictionary<char, />()
    rules.Add('F',"FF")
    rules.Add('X',"F-[[X]+X]+F[+FX]-X")
    rules.Add('+',"+")
    rules.Add('-',"-")
    rules.Add('[',"[")
    rules.Add(']',"]")
    {Start = "X";Rules = rules;Angle=22.5}

此 L-系统语法中的符号(X 除外)与下面定义的函数中的绘图命令匹配。pushTurtlepopTurtle 函数将海龟的当前状态添加到堆栈或从中移除,从而允许绘制分支形状。

let drawCommand (lsys:LSystem)(symbol:char) =
    match symbol with
    | 'F' -> forward length 
    | 'G' -> forward length
    | 'f' -> move length
    | '+' -> left (lsys.Angle)
    | '-' -> right (lsys.Angle)
    | '[' -> pushTurtle()
    | ']' -> popTurtle()
    | _   -> None |> ignore

drawLSystem 函数在生成 n 次迭代的 instructions 字符串后,绘制 L-系统 lsys

let drawLSystem (lsys:LSystem) (n:int) =
   let instructions = generateString lsys n in
      for command in instructions do
         drawCommand lsys command

文件 FSharpTurtle.fsx 中提供了其他 L-系统示例。

参考文献

F#

海龟绘图

  • 海龟几何 - Harold Abelson 和 Andrea diSessa

L-系统

WPF

历史

  • 2010 年 10 月 10 日:初始发布
© . All rights reserved.