【译】催眠方块—Hypnotic Squares
JChehe opened this issue · 0 comments
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();
现在有了一个方块,接着进行递归操作,即 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);
}
哇喔,这就是递归!下面让我解释一下上述代码。
- newSize 基于剩余方块数量计算得出。
- newX & newY 确保新方块能放置在上一层方块内的合适位置上。
- steps - 1 是 draw 函数的最后一个参数,它会越来越接近 0。
不同的 startSteps 就会有不同的递归程度。
var startSteps = 8
或
var startSteps = 4
当为该值赋予随机数时会有不同的效果。但现在先让我们添加其他效果。xMovement
和 yMovement
两个变量是用于将方块放置在特定方向上。
先更改 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
这看起来有点复杂。我们计算了相邻方块的间距,然后将其按照剩步数(译者注:代码中是 steps + 2
)切分。+2
是为了确保新方块永远不会触碰到上一个方块的边界(译者注:若 + 1,则当 steps 最后为 0 时,会导致发生碰撞)。
依次更改 draw 函数的 xMovement
和 yMovement
两个参数,你将会看到它是如何移动的。
draw(0, 0, startSize, startSize, 1, 0, startSteps);
draw(0, 0, startSize, startSize, 1, -1, startSteps);
draw(0, 0, startSize, startSize, 0, -1, startSteps);
draw(0, 0, startSize, startSize, -1, -1, startSteps);
接着我们需要做先前 瓷砖线 教程的事了——将其作为一个瓷砖,然后平铺。
首先定义变量,如方块的平铺密度、方块的偏移量等。我们会将最终尺寸变得更小,并根据 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];
现在也许看起来有点奇怪,因为我们还没将这些变量应用上。
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);
}
}
现在可以开始玩耍啦,可以为 steps
赋予随机数。
startSteps = 2 + Math.ceil(Math.random() * 3)
再设置随机方向!
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);
这就是最终效果——催眠方块。这是使用递归的好例子,也是一副易于上色的艺术作品,特别是在较大的 canvas 上。