我正在HTML画布中创建一个模式以进行挑战。
我如何随机生成类似下面编码的形状,以形成类似于图片的图案。我通过在Illustrator中使用drawingcript生成代码创建了一个版本但是它远非理想,我怎么能用循环做同样的事情呢?
由于
//triangles
ctx.fillStyle="rgb(75,128,166)";
ctx.beginPath();
ctx.moveTo(824,92);
ctx.lineTo(796,140);
ctx.lineTo(767,92);
ctx.lineTo(824,92);
ctx.fill();
//circles
ctx.fillStyle="rgba(35,121,67,0.8)";
ctx.beginPath();
ctx.moveTo(869,263);
ctx.bezierCurveTo(869,253,861,244,850,244);
ctx.bezierCurveTo(839,244,831,253,831,263);
ctx.bezierCurveTo(831,274,839,283,850,283);
ctx.bezierCurveTo(861,283,869,274,869,263);
ctx.fill();
答案 0 :(得分:3)
您可以为三角形制作一个倾斜的方形网格,然后随机插入以及该矩形的随机对角线分割,使其显示为三角形。
对于圆圈,我们可以使用伪六边形系统,这意味着圆圈将被放置在六边形位置,只是补偿为圆而不是实际的六边形。
如何用随机单元格覆盖可以通过多种方式完成,下面只是一种方法。其他可以使用固定网格系统并使用基于覆盖的步骤迭代它(需要跟踪余量以获得准确性)。第三个填充细胞的覆盖范围,然后对数组进行随机排序以改变细胞周围的细胞。
此处还将使用网格,但由于我们要将垂直空间打包为近似六边形网格,因此我们需要对其进行补偿。布局将通过以下因素完成:
( 1) 感谢@Jason和他提醒我的答案!)
为了补偿垂直的“打包”圆圈,因为它们不会填充底部,我们使用sqrt(3)* 0.5(1 / (sqrt(3) * 0.5)
)的倒数。
将这两者合并到一个画布中将导致:
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
w = canvas.width,
h = canvas.height,
cellsY = 14, // cells Y for triangles
cellsX = cellsY * 2, // cells X times two to overlap skew
cw = w / cellsX * 2, // cell width and height
ch = h / cellsY,
toggle, cx = 0, cy, // for circles
cells = 25, // cells for cirles + comp. (see below)
deltaY = 0.8660254037844386, // = sqrt(3) * 0.5
deltaYI = 1 / deltaY, // inverse deltaY
grid = new Uint8Array((cells * cells * deltaYI)|0), // circles "booleans"
i;
// Calc and Render Triangles ---
// main transform: skew
ctx.setTransform(1, 0, 0.51, 1, -cellsX * cw * 0.5, 0);
ctx.fillStyle = "rgb(90, 146, 176)";
// fill random cells based on likely cover:
var cover = 0.67, // how much of total area to cover
biasDiv = 0.6, // bias for splitting cell
biasUpper = 0.5, // bias for which part to draw
count = cellsX * cellsY * cover, // coverage
tris = [],
x, y, d, u, overlap; // generate cells
for (i = 0; i < count; i++) {
overlap = true;
while (overlap) { // if we have overlapping cells
x = (Math.random() * cellsX) | 0;
y = (Math.random() * cellsY) | 0;
overlap = hasCell(x, y);
if (!overlap) {
d = Math.random() < biasDiv; // divide cell?
u = Math.random() < biasUpper; // if divided, use upper part?
tris.push({
x: x,
y: y,
divide: d,
upper: u
})
}
}
}
function hasCell(x, y) {
for (var i = 0, c; c = tris[i++];) {
if (c.x === x && c.y === y) return true;
}
return false;
}
// render
for (i = 0; i < tris.length; i++) renderTri(tris[i]);
ctx.fill(); // fill all sub-paths
function renderTri(t) {
var x = t.x * cw, // convert to abs. position
y = t.y * ch;
if (t.divide) { // create triangle
ctx.moveTo(x + cw, y); // define common diagonal
ctx.lineTo(x, y + ch);
t.upper ? ctx.lineTo(x, y) : ctx.lineTo(x + cw, y + ch);
}
else {
ctx.rect(x, y, cw, ch); // fill complete cell
}
}
// Calc and Render Circles ---
cover = 0.5, // how much of total area to cover
count = Math.ceil(grid.length * cover); // coverage
cw = ch = w / cells;
ctx.setTransform(1,0,0,1,0,0); // reset transforms
ctx.fillStyle = "rgb(32, 141, 83)";
ctx.globalCompositeOperation = "multiply"; // blend mode instead of alpha
if (ctx.globalCompositeOperation !== "multiply") ctx.globalAlpha = 0.5; // for IE
for (i = 0; i < count; i++) {
overlap = true;
while (overlap) { // if we have overlapping cells
x = (Math.random() * cells) | 0; // x index
y = (Math.random() * cells * deltaYI) | 0; // calc y index + comp
overlap = hasCircle(x, y); // already has circle?
if (!overlap) {
grid[y * cells + x] = 1; // set "true"
}
}
}
function hasCircle(x, y) {
return grid[y * cells + x] === 1;
}
// render
ctx.beginPath();
cy = ch * 0.5; // start on Y axis
for (y = 0; y < (cells * deltaYI)|0; y++) { // iterate rows + comp.
toggle = !(y % 2); // toggle x offset
for (x = 0; x < cells; x++) { // columns
if (grid[y * cells + x]) { // has circle?
cx = x * cw + (toggle ? cw * 0.5 : 0); // calc x
ctx.moveTo(cx + cw * 0.5, cy); // creat sub-path
ctx.arc(cx, cy, cw * 0.5, 0, 2 * Math.PI); // add arc
ctx.closePath(); // close sub-path
}
}
cy += ch * deltaY; // add deltaY
}
ctx.fill(); // fill all at once
body {background:#777}
canvas {padding:50px;background: rgb(226, 226, 226)}
<canvas width=600 height=600></canvas>
这里有重构的空间,随机功能并不是最好的性能,但它应该足以让你前进。希望这有帮助!
答案 1 :(得分:1)
提示:使用context.arc
更轻松地创建圆圈,而不是将4条贝塞尔曲线串联起来(并且4条贝塞尔曲线不会像arc
那样产生完美的圆形一样)。
添加随机圈
如果您希望对圈子进行更随机的覆盖,则必须尝试一次添加一个圈子,并确保每次尝试都不会与现有圈子重叠。
下面是示例代码和演示,可根据需要添加任意数量的随机圆圈,以覆盖40%的画布区域:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var PI2=Math.PI*2;
var radius=10;
var radiusTest=(2*radius)*(2*radius);
var circleCoverageDesired=.40;
var circleCount=parseInt((cw*ch*circleCoverageDesired)/(Math.PI*radius*radius))+1;
var circles=[];
ctx.fillStyle='green';
ctx.globalAlpha=0.25;
addRandomCircles();
function addRandomCircles(){
// give up after "tries" to avoid unsolvable patterns
var tries=circleCount*200;
while(tries>0 && circles.length<circleCount){
var x=Math.random()*(cw-radius*2)+radius/2;
var y=Math.random()*(ch-radius*2)+radius/2;
testRandomCircle(x,y);
tries--;
}
}
function testRandomCircle(x,y){
for(var i=0;i<circles.length;i++){
var c=circles[i];
var dx=x-c.x;
var dy=y-c.y;
if(dx*dx+dy*dy<=radiusTest){
return(false);
}
}
var circle={x:x,y:y};
circles.push(circle);
ctx.beginPath();
ctx.arc(x,y,radius,0,PI2);
ctx.closePath();
ctx.fill();
var pct=parseInt((Math.PI*radius*radius*circles.length)/(cw*ch)*100);
$('#count').text('Added: '+circles.length+' of '+circleCount+' needed circles for '+pct+'% coverage.');
return(true);
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4 id=count>Count</h4>
<canvas id="canvas" width=300 height=300></canvas>
添加随机三角形
添加随机三角形需要与添加随机圆圈相同的限制。您必须一次添加一个新三角形,并确保每个新三角形不与任何现有三角形重叠。
测试是否有任何2个多边形(例如三角形)重叠可以使用分离轴定理来完成,
Mattias Buelens先前的Stackoverflow回答说明了如何实现分离轴定理:javascript polygon intersection
答案 2 :(得分:1)
OK这是一个函数,它将在页面的任何画布上呈现此艺术风格(包括尺寸和形状频率的选项,并考虑画布大小):
function art(options, canvas) {
var surface = document.getElementById(canvas),
context = surface.getContext("2d"),
row,
col,
triangleDirection = 1,
triangleSize = options.triangle.size,
circleSize = options.circle.size,
circleStep = Math.sqrt(3) * circleSize * 2,
circleOffset = 0;
function shouldDraw(chances) {
return Math.random() < chances;
}
function drawTriangle(x, y, direction, size, ctx) {
ctx.fillStyle = options.triangle.color;
ctx.beginPath();
ctx.moveTo(x, y - (direction * size));
ctx.lineTo(x - (direction * size), y + (direction * size));
ctx.lineTo(x + (direction * size), y + (direction * size));
ctx.lineTo(x, y - (direction * size));
ctx.fill();
ctx.strokeStyle = options.triangle.color;
ctx.stroke();
}
function drawCircle(x, y, size, ctx) {
//circles
ctx.fillStyle = options.circle.color;
ctx.beginPath();
ctx.arc(x, y, size, 0, 2 * Math.PI, false);
ctx.fill();
}
//Draw Tiangles
for (col = 1; col < (surface.width / triangleSize); col++) {
for (row = 1; row < (surface.height / triangleSize); row++) {
if (shouldDraw(options.triangle.density)) {
drawTriangle(row * triangleSize, col * triangleSize * 2, triangleDirection, triangleSize, context);
}
//Swap direction
triangleDirection = -1 * triangleDirection;
}
}
//Draw Circles
for (row = 1; row < (surface.height / circleSize) - 1; row++) {
for (col = 1; col < (surface.width / circleStep) - 1; col++) {
if (shouldDraw(options.circle.density)) {
drawCircle((row * circleSize), (col * circleStep) + circleOffset, circleSize, context);
}
}
//swap offset by row
if (row % 2 === 0) {
circleOffset = circleStep / 2;
} else {
circleOffset = 0;
}
}
}
art({triangle: {size:24, density: 0.7, color: 'rgb(75,128,166)'}, circle: {size: 14, density: 0.2, color: 'rgba(35,121,67,0.8)'}}, 'surface')
&#13;
#surface {
width: 600px;
height: 600px;
}
&#13;
<canvas id='surface' height='600' width='600' />
&#13;
以下是需要考虑的重点:
三角形模式是上下三角形的振荡模式,因此将三角形代码概括为可以根据参数绘制此函数的函数将使您的代码更容易理解。
圆形图案振荡,但这次是逐行并左右移动。为了解决这个问题,我需要清除一些基本的几何形状。看着彼此叠加的三个圆圈:
你可以看到它们的高度以等于半径(代码中的circleSize
)的步长移动。然而,它们从一侧到另一侧间隔是比较棘手的,但是当您将其视为等边三角形时,您可以将其计算为该三角形的高度或:
,然后您可以看到变量的间距是两倍,在减少分数后变为:Math.sqrt(3) * circleSize * 2
希望这会有所帮助:)