【问题标题】:Randomly Generate HTML Canva Objects随机生成 HTML Canva 对象
【发布时间】:2015-07-30 14:50:43
【问题描述】:

我正在 HTML 画布中创建一个模式以应对挑战。

我将如何随机生成如下编码的形状以形成类似于图片的图案。我通过在 Illustrator 中使用 drawscript 生成代码创建了一个版本,但它远非理想,我怎么能用循环做同样的事情?

谢谢

//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();

【问题讨论】:

标签: javascript css html canvas


【解决方案1】:

提示:使用context.arc 比将 4 条贝塞尔曲线串在一起更容易创建圆(并且 4 条贝塞尔曲线不会像 arc 那样产生完美的圆)。

添加随机圈子

如果您想要更随机地覆盖圈子,则必须尝试一次添加一个圈子,并确保每次新尝试都不会与任何现有圈子重叠。

下面是示例代码和一个 Demo,它根据需要添加任意数量的随机圆圈以覆盖 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】:

    您可以为三角形创建一个倾斜的方形网格,然后对该矩形进行随机插入和随机对角分割,使其显示为三角形。

    对于圆圈,我们可以使用伪六边形系统,这意味着圆圈将放置在六边形位置,只是为了补偿圆形而不是实际的六边形。

    三角形

    • 首先定义一个简单的网格,但它与绘图区域重叠(这是一种方法,另一种方法是包装坐标,但这需要跟踪变换,因此在本例中是蛮力)
    • 倾斜变换,使垂直对齐变为对角线
    • 定义覆盖范围,填充单元格,这些单元格依次定义是否拆分、是否上部等。

    如何用随机单元格覆盖可以通过多种方式完成,以下只是一种方法。其他可以使用固定网格系统并使用基于覆盖的步骤对其进行迭代(需要跟踪余数以确保准确性)。第三个填充单元格的覆盖范围,然后随机排序数组以打乱单元格。

    圆圈

    这里也将使用网格,但由于我们要将垂直空间压缩为近似六边形网格,因此我们需要对其进行补偿。布局将根据以下因素完成:

    • 垂直距离 = 直径 x sqrt(3) x 0.5 1)
    • 水平距离 = 半径(偏移量每 2. 行切换一次)

    (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)}
    &lt;canvas width=600 height=600&gt;&lt;/canvas&gt;

    这里有重构的空间,随机函数并不是最好的性能,但它应该足以让你继续前进。希望这会有所帮助!

    【讨论】:

      【解决方案3】:

      好的,这是一个函数,可以在页面上的任何画布上呈现这种艺术风格(具有大小和形状频率选项,并考虑画布大小):

      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')
      #surface {
          width: 600px;
          height: 600px;
      }
      &lt;canvas id='surface' height='600' width='600' /&gt;

      以下是一些需要考虑的重点:

      三角形图案是上下三角形的振荡图案,因此将您的三角形代码概括为一个可以根据参数绘制它的函数将使您的代码更容易理解。

      圆形图案会摆动,但这次是逐行并左右移动。为了弄清楚这一点,我需要清理一些基本的几何形状。看着三个相互重叠的圆圈:

      您可以看到它们的高度以等于半径的步长移动(代码中的circleSize)。然而,它们并排排列的距离比较棘手,但是当您将其视为等边三角形时,您可以将其计算为该三角形的高度或:

      ,那么您可以看到变量的间距是该距离的两倍,在您减少分数后变成:Math.sqrt(3) * circleSize * 2

      希望这会有所帮助:)

      【讨论】:

      • 你的意思是分裂,Math.sqrt(3) * circleSize / 2?顺便说一句,不错的方法。 (点赞)
      • 我只是放弃了迄今为止的进展,因为我无法放弃这个问题,我认为也存在一些行/列转置错误。我认为您在最终解决方案中找到了答案。
      猜你喜欢
      • 2014-05-13
      • 2019-08-02
      • 2015-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-13
      • 1970-01-01
      • 2020-09-22
      相关资源
      最近更新 更多