Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【译】催眠方块—Hypnotic Squares #30

Open
JChehe opened this issue Sep 5, 2018 · 0 comments
Open

【译】催眠方块—Hypnotic Squares #30

JChehe opened this issue Sep 5, 2018 · 0 comments

Comments

@JChehe
Copy link
Owner

JChehe commented Sep 5, 2018

原文:Hypnotic Squares

William Kolomyjec 的工作让我们再次想起一些以前(old school)的生成艺术,专注于简单图形、平铺和递归。

今天我们要复现他的作品之一——催眠方块。

页面中仅有一个 320x320 像素的 <canvas>

老规矩,以下是初始化代码,其中包括设置 canvas 大小和使用 window.devicePixelRatio 缩放 canvas 以适配视网膜屏幕。

var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');

var size = window.innerWidth;
var dpr = window.devicePixelRatio;
canvas.width = size * dpr;
canvas.height = size * dpr;
context.scale(dpr, dpr);
context.lineWidth = 2;

现在,需要定义一些变量和创建 draw 函数。该函数会被递归调用,不断在方块内绘制方块,直至方块达到指定的最小尺寸。

如果对递归不太了解也没关系,后续会讲解。

var finalSize = 10;
var startSize = size;
var startSteps = 5;

function draw(x, y, width, height, xMovement, yMovement, steps) {
  // We will fill this in
}

draw(0, 0, startSize, startSize, 0, 0, startSteps);

draw 函数内的 steps 参数 代表方块递归的次数。目前是固定值,但随后我们会赋予它一些随机性。

finalSize 是绘制方块的最小尺寸,即当方块达到此尺寸时,我们将停止绘制。

startSteps 在递归时用于计算越来越小的方块尺寸。

我们先在 draw 函数内单纯画一个方块。

context.beginPath();
context.rect(x, y, width, height);
context.stroke();

01

现在有了一个方块,接着进行递归操作,即 draw 函数会调用自身多次,直至满足某个条件。该条件非常重要,否则会造成死循环。这里我们使用 steps 作为倒数的开始点。

if(steps >= 0) {
  var newSize = (startSize) * (steps / startSteps) + finalSize;
  var newX = x + (width - newSize) / 2
  var newY = y + (height - newSize) / 2
  draw(newX, newY, newSize, newSize, xMovement, yMovement, steps - 1);
}

02

哇喔,这就是递归!下面让我解释一下上述代码。

  • newSize 基于剩余方块数量计算得出。
  • newX & newY 确保新方块能放置在上一层方块内的合适位置上。
  • steps - 1 是 draw 函数的最后一个参数,它会越来越接近 0。

不同的 startSteps 就会有不同的递归程度。

var startSteps = 8

03

var startSteps = 4

04

当为该值赋予随机数时会有不同的效果。但现在先让我们添加其他效果。xMovementyMovement 两个变量是用于将方块放置在特定方向上。

先更改 draw 函数的调用参数,即 xMovement 和 yMovement 均置为 1。此举是为了让方块放置在右下方。

draw(0, 0, startSize, startSize, 1, 1, startSteps);

然后计算下方变量。

newX = newX - ((x - newX) / (steps + 2)) * xMovement
newY = newY - ((y - newY) / (steps + 2)) * yMovement

05

这看起来有点复杂。我们计算了相邻方块的间距,然后将其按照剩步数(译者注:代码中是 steps + 2)切分。+2 是为了确保新方块永远不会触碰到上一个方块的边界(译者注:若 + 1,则当 steps 最后为 0 时,会导致发生碰撞)。

依次更改 draw 函数的 xMovementyMovement 两个参数,你将会看到它是如何移动的。

draw(0, 0, startSize, startSize, 1, 0, startSteps);

06

draw(0, 0, startSize, startSize, 1, -1, startSteps);

07

draw(0, 0, startSize, startSize, 0, -1, startSteps);

08

draw(0, 0, startSize, startSize, -1, -1, startSteps);

09

接着我们需要做先前 瓷砖线 教程的事了——将其作为一个瓷砖,然后平铺。

首先定义变量,如方块的平铺密度、方块的偏移量等。我们会将最终尺寸变得更小,并根据 canvas 宽度减去 offset 后的大小分割成想要的份数(7)。directions 数组存储着所有可能的方向:-1, 0 & 1

var finalSize = 3;
var startSteps;
var offset = 2;
var tileStep = (size - offset * 2) / 7;
var startSize = tileStep;
var directions = [-1, 0, 1];

10

现在也许看起来有点奇怪,因为我们还没将这些变量应用上。

for( var x = offset; x < size - offset; x += tileStep) {
  for( var y = offset; y < size - offset; y += tileStep) {
    startSteps = 3
    draw(x, y, startSize, startSize, 1, 1, startSteps - 1);
  }
}

11

现在可以开始玩耍啦,可以为 steps 赋予随机数。

startSteps = 2 + Math.ceil(Math.random() * 3)

12

再设置随机方向!

var xDirection = directions[Math.floor(Math.random() * directions.length)]
var yDirection = directions[Math.floor(Math.random() * directions.length)]
draw(x, y, startSize, startSize, xDirection, yDirection, startSteps - 1);

13

这就是最终效果——催眠方块。这是使用递归的好例子,也是一副易于上色的艺术作品,特别是在较大的 canvas 上。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant