canvas 绘制 L-system 图形基础

174 阅读2分钟

一、canvas

HTML5 新增 <canvas> 元素用于图形的绘制。

绘制一条直线:

<!DOCTYPE html>
<html lang="en">
  <head> </head>
  <body>
    <canvas width="200" height="200" style="border: 1px solid #000"></canvas>
    <script>
      var ctx = document.querySelector("canvas").getContext("2d")
      ctx.beginPath()
      ctx.moveTo(0, 0)
      ctx.lineTo(100, 100)
      ctx.stroke()
    </script>
  </body>
</html>

二、L-system

L-system 是生物学领域有关生长发展中的细胞交互作用的数学模型,尤其被广泛应用于植物生长过程的研究,也是一种形态发生(morphogenesis)算法。

下面从 Hilbert 曲线图形来讲解:

image.png

Hilbert 曲线图形规则

const Hilbert = {
  // 基态 --> 最初的形态
  start: "A",
  // 裂变规则
  rules: { A: "-BF+AFA+FB-", B: "+AF-BFB-FA+" },
}
  • 第 0 次裂变 "A"
  • 第 1 次裂变 "A" 按照裂变规则为 "-BF+AFA+FB-"
  • 第 2 次裂变 "-BF+AFA+FB-" --> "-+AF-BFB-FA+F+-BF+AFA+FB-F-BF+AFA+FB-+F+AF-BFB-FA+-"

Hilbert 图形绘制规则

const drawRules = {
  angle: 0, // 旋转角度记录,起始角度
  len: 20, // 一格距离
  x: 100, // 起点 X 位置
  y: 100, // 起点 Y 位置
  rotate: Math.PI / 2, // 旋转叠加角度
  actions: { "-": "left", "+": "right", F: "forward" }, // x行动规则
}
符号意义
-左转
+右转
F前进一格
A禁止
B禁止

裂变获得对应的字符串,根据绘制规则 —— 设定起点(x,y)和方向(angle),从左到右扫描字符串,遇到 F 就前进一格,遇到 - 向左旋转 90°,遇到 + 向右旋转 90°,这样就得到一个 Hilbert 曲线。

裂变代码实现

/**
 * 裂变
 * @param graph 图形
 * @param times 裂变次数
 * @return
 */
function replacement(graph, times) {
  let { start, rules } = graph
  for (let i = 0; i < times; i++) {
    let temp = ""
    for (let j = 0; j < start.length; j++) {
      const s = start[j]
      if (Object.keys(rules).includes(s)) {
        temp += rules[s]
      } else {
        temp += s
      }
    }
    start = temp
  }
  return start
}

图形绘制代码实现

/**
 * 图形绘制规则
 * @param str 裂变后字符串
 * @return 图形点二维数组
 */
function interpretation(str, drawRules) {
  let { actions, rotate, angle, len, x, y } = drawRules,
    points = [[x, y]]
  for (let i = 0; i < str.length; i++) {
    const s = str[i]
    if (!Object.keys(actions).includes(s)) {
      continue
    } else if (actions[s] === "left") {
      angle -= rotate
    } else if (actions[s] === "right") {
      angle += rotate
    } else if (actions[s] === "forward") {
      const last = points.at(-1),
        next_x = last[0] + len * Math.cos(angle),
        next_y = last[1] + len * Math.sin(angle)
      points.push([next_x, next_y])
    }
  }
  return points
}

绘制代码实现

/**
 * 绘制
 * @param points 图形点二维数组
 * @param canvas = "canvas" 元素选择器
 */
function draw(points, canvas = "canvas") {
  const ctx = document.querySelector(canvas).getContext("2d")
  ctx.strokeStyle = "#2678b2"
  ctx.beginPath()
  for (let i = 0; i < points.length; i++) {
    ctx.lineTo(points[i][0], points[i][1])
  }
  ctx.stroke()
}