使用Bezier曲线的拼图游戏

时间:2015-06-03 10:12:02

标签: html5 canvas bezier

我试图制作一些像这样的拼图 -

enter image description here  enter image description here

到目前为止,我一直尝试使用lineTo -

outside: function (ctx, s, cx, cy) {
        ctx.lineTo(cx, cy)
        ctx.lineTo(cx+s*.3, cy)
        ctx.lineTo(cx+s*.5, cy+s*-.2)
        ctx.lineTo(cx+s*.7, cy)
        ctx.lineTo(cx+s, cy)
    },
    inside: function (ctx, s, cx, cy) {
        ctx.lineTo(cx, cy)
        ctx.lineTo(cx+s*.3, cy)
        ctx.lineTo(cx+s*.5, cy+s*+.2)
        ctx.lineTo(cx+s*.7, cy)
        ctx.lineTo(cx+s, cy)
    },

Fiddle Link

2 个答案:

答案 0 :(得分:8)

高效的拼图设计很简单,它的工作原理如下:

链接代码已经展示了如何通过重复使用单面设计来有效地组装您的一个拼图。

你插图右侧的部分是传统的(或#34;日式")作品。这意味着它的两侧长度均匀并且完全互锁。日本风格的作品是最容易设计的,因为它是一个设计代码,可以在整个拼图中重复使用。

具有讽刺意味的是,虽然日式拼图是最容易编码的,但是对于用户而言,它们更难以解决,因为许多部件在没有正确解决拼图的情况下将在物理上融合在一起。

如何设计日式拼图

  1. 通过组合多个立方贝塞尔曲线设计拼图块的一侧(不是更多!)。

  2. 根据需要,使用变换将一个拼图设计应用于顶部,右侧,底部或左侧。 (或自动操作原始Bezier控制点以将一个拼图设计应用于4个边的代码函数)。镜像原始的侧面设计,为您的作品提供各种各样的" inny"和" outy"侧上。

  3. 通过镜像每个相邻方的设计来拼凑拼图:

    • 给左上角(0,0)一个随机的右侧(无论是inny还是outy)。
    • 假设片段(0,0)被分配了一个外行的右侧。然后右边的下一块(1,0)必须得到一个左侧。
    • 现在给一块(1,0)一个随机的右侧(无论是inny还是outy),而piece(2,0)必须得到镜像类型的一面。等等......
    • 所以一般来说,通过为所有棋子分配随机右侧并在下一张棋子的左侧镜像指定的一侧来填充拼图。
    • 垂直地做同样的事。通过为所有部分指定随机底部边,并在下一部分的顶部镜像指定的边来填充拼图。
  4. 设计您已经说明的2个示例

    我认为链接代码不是您的代码,因为它已经显示了如何在插图右侧设计该片段(!)。

    // Given the center point of the piece (cx,cy) and the side length (s)
    // The single side "outy" design is below
    // Use this single design (with transforms/mirroring) to make all pieces
    ctx.lineTo(cx + s * .34, cy);
    ctx.bezierCurveTo(cx + s * .5, cy, cx + s * .4, cy + s * -.15, cx + s * .4, cy + s * -.15);
    ctx.bezierCurveTo(cx + s * .3, cy + s * -.3, cx + s * .5, cy + s * -.3, cx + s * .5, cy + s * -.3);
    ctx.bezierCurveTo(cx + s * .7, cy + s * -.3, cx + s * .6, cy + s * -.15, cx + s * .6, cy + s * -.15);
    ctx.bezierCurveTo(cx + s * .5, cy, cx + s * .65, cy, cx + s * .65, cy);
    ctx.lineTo(cx + s, cy);
    

    然后您可以重复使用这一组贝塞尔曲线以及变换来创建整个拼图。转换==移动,旋转和镜像单一设计,构成任何拼图的任何一面。

    插图左侧的部分可能来自Freeform Style拼图游戏。它更复杂,因为它使用3种不同的侧面设计。我假设还有其他侧面设计你没有显示,因为你展示的3面设计不允许所有部件互锁以完成拼图。

    创建自由形式拼图时,您有多种选择。

    非互锁自由形式

    在这种风格中,你基本上可以拍摄一张图像并画出将其剪切成非均匀片段的线条,这些片段可以排列成图像。可以把它想象成随机切片的披萨。即使碎片没有互锁,你也可以将这些碎片装在一起改造披萨。嗯,披萨! : - )

    联锁自由形式

    在这种风格中,您可以设计2个以上的边并创建拼图,就像传统风格拼图一样。通常,您创建一个将用于所有左右侧的设计,并创建第二个用于所有上下侧的设计。复杂性是两种类型的边必须在它们相遇的地方相互配合。这意味着side-type-1必须共享一个镜像模式,它与side-type-2相交。

    因此,要设计插图左侧的部分,您必须决定是否要将其设置为Interlocking-Freeform或Non-interlocking-Freeform。

    非互锁自由形式更容易。只需拉开3种类型的边,然后将它们与镜像合作伙伴一起使用即可切割图像。

    对于Interlocking-Freeform,您需要进行更多的设计工作。您必须创建额外的侧面设计,这些侧面设计将与您已经创建的3个设计互锁。

    快速浏览拼图游戏......祝你的项目好运!

    [其他详细信息]

    对于插图右侧的部分,常见的"外部"看起来像一个"肩膀&头"轮廓。

    Bezier设定肩部和肩部。头像这样崩溃:

    • Bezier为"左肩"
    • Bezzier for the#34; left neck"
    • Bezier for the#34; left head"
    • Bezier为"右头"
    • Bezier为"右颈"
    • Bezier为"右肩"

    肩膀和肩膀Bezier头可能看起来像这样:

    enter image description here

    这是控制点的一个具体示例,用于创建一个带有肩膀和肩膀的外侧。头"形状:

    var ShouldersAndHeadCubicBezierControlPoints=[
        {cx1:0,  cy1:0,  cx2:35,cy2:15, ex:37, ey:5},   // left shoulder
        {cx1:37, cy1:5,  cx2:40,cy2:0,  ex:38, ey:-5},  // left neck
        {cx1:38, cy1:-5, cx2:20,cy2:-20,ex:50, ey:-20}, // left head
        {cx1:50, cy1:-20,cx2:80,cy2:-20,ex:62, ey:-5},  // right head
        {cx1:62, cy1:-5, cx2:60,cy2:0,  ex:63, ey:5},   // right neck
        {cx1:63, cy1:5,  cx2:65,cy2:15, ex:100,ey:0},   // right shoulder
    ];
    

    一旦你有"外面"一组曲线,您可以使用画布的上下文转换来翻转"外部"进入镜像"内部"。或者,您可以手动反转"外部"曲线控制点阵列。

    插图:top-tab和top-slot(top-slot是顶级选项卡镜像)

    enter image description here

    显示顶部标签和顶部广告位的示例:

    
    
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    function reOffset(){
      var BB=canvas.getBoundingClientRect();
      offsetX=BB.left;
      offsetY=BB.top;        
    }
    var offsetX,offsetY;
    reOffset();
    window.onscroll=function(e){ reOffset(); }
    
    ctx.lineWidth=3;
    var colors=['red','green','blue','gold','purple','cyan'];
    
    var bSet=makeBeziers();
    
    draw(bSet,50,100);
    
    var bSetMirrored=mirror(bSet,1,-1,0,0);
    
    draw(bSetMirrored,50,200);
    
    function draw(bSet,transX,transY){
      ctx.translate(transX,transY);
      ctx.scale(2,2);
      for(var i=0;i<bSet.length;i++){
        var b=bSet[i];
        ctx.beginPath();
        ctx.bezierCurveTo(b.cx1,b.cy1,b.cx2,b.cy2,b.ex,b.ey);
        ctx.strokeStyle=colors[i];
        ctx.stroke();
      }
      ctx.setTransform(1,0,0,1,0,0);
    }
    
    
    function makeBeziers(){
      return([
        {cx1:0,  cy1:0,  cx2:35,cy2:15, ex:37, ey:5},   // left shoulder
        {cx1:37, cy1:5,  cx2:40,cy2:0,  ex:38, ey:-5},  // left neck
        {cx1:38, cy1:-5, cx2:20,cy2:-20,ex:50, ey:-20}, // left head
        {cx1:50, cy1:-20,cx2:80,cy2:-20,ex:62, ey:-5},  // right head
        {cx1:62, cy1:-5, cx2:60,cy2:0,  ex:63, ey:5},   // right neck
        {cx1:63, cy1:5,  cx2:65,cy2:15, ex:100,ey:0},   // right shoulder
      ]);
        }
    
        function mirror(b,signX,signY,x,y){
        var a=[];
             for(var i=0;i<b.length;i++){
        var bb=b[i];
        a.push({
          cx1: bb.cx1*signX+x,
          cy1: bb.cy1*signY+y,
          cx2: bb.cx2*signX+x,
          cy2: bb.cy2*signY+y,
          ex:  bb.ex*signX+x,
          ey:  bb.ey*signY+y
        });
      }
      return(a);
    }
    &#13;
    body{ background-color: ivory; }
    #canvas{border:1px solid red; margin:0 auto; }
    &#13;
    <canvas id="canvas" width=300 height=300></canvas>
    &#13;
    &#13;
    &#13;

答案 1 :(得分:3)

我想提出另一种方法 -

SVG来源

为什么不考虑使用在Illustrator或某些类似软件中制作的SVG图像源。这些工具可以让你追踪一件作品的轮廓(事实上它几乎可以完全自动完成 - 见下面的结果)。 SVG也可以使用Beziers,或者只是几乎完美地适合原始图像的路径。只需点击跟踪,调整阈值,保存并清理结果。

这样您就可以简单地导入SVG,将其绘制到您要栅格化它的大小的离屏画布上。结果可以直接用于合成或“剪切”图形。

这比计算和使用样条更快更简单。

实施例

此结果来自Illustrator转换为SVG,并进行了一些手动清理,例如删除注释和背景,添加宽度和高度以及调整视图框:

snap

(注意:在本演示中,我将阴影留在原始图像中 - 您必须清理图像本身,然后将其处理为SVG等。)。

我现在可以加载它作为合成的基础,所以我可以这样做:

var ctx = document.querySelector("canvas").getContext("2d"),
    svg = document.querySelector("svg").outerHTML,
    img = new Image();

// fill some graphics to canvas
ctx.fillStyle = "#777";
ctx.fillRect(0, 0, 600, 600);

// load SVG so we can use the puzzle with canvas
img.onload = demo;
img.src = "data:image/svg+xml;base64," + btoa(svg);

function demo() {
  
  // create a matte so it becomes rasterized - choose the size dynamically if you need to
  var matte = document.createElement("canvas"),
      mctx = matte.getContext("2d");
  matte.width = matte.height = 100;
  mctx.drawImage(this, 0, 0, 100, 100);  // draw in (rasterize) the SVG
  
  // we can now use the puzzle as basis for an image region, or to mask out parts:
  ctx.globalCompositeOperation = "destination-out";
  ctx.drawImage(matte, 10, 10);
  ctx.drawImage(matte, 100, 100);
  ctx.drawImage(matte, 210, 10);
}
canvas {border:1px solid #000;background:url(http://i.stack.imgur.com/bEiyx.jpg)}
<canvas width=600 height=600></canvas>

<br><br>SVG (inlined for demo - use from URL instead):<br>

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg version="1.1" baseProfile="tiny" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
	 x="0px" y="0px" viewBox="0 0 90 90" width="300" height="300" xml:space="preserve">
 <path d="M16.1,34.2c-2.8,2.3-4.4,4.3-6.5,5.1c-1.9,0.7-5.2,1.2-6.3,0.1C1.9,38,1,34.6,1.7,32.7c0.9-2.6,3-5.2,5.3-6.7
	c6.5-4.4,6.5-4.3,3.9-11.8c-0.5-1.4,0.6-4.7,1.6-5c2.6-0.7,5.8-1,8.2,0c1.2,0.4,2.4,4.4,1.7,5.8c-2.3,4.9-0.3,7.7,3.5,10
	c3.4,2,6.7,1.5,9.3-1.8c2.4-3.1,1.9-5.9-1.1-8.3c-3.2-2.6-2.7-5.1,0.1-7.4c3.6-3.1,12.4-3.2,16-0.2c2.7,2.2,3.9,4.6,0.6,7.4
	c-2.7,2.3-4.1,4.9-1.6,8.2c2.4,3.2,5.6,4.2,9.2,2.3c3.5-1.8,5-4.7,4-8.7c-1.5-5.7,1-9.2,6.2-8.9c4.4,0.2,6.8,4.4,4.3,8
	c-2.5,3.7-1.6,6.1,1.8,8.3c1.7,1.1,3.3,2.3,4.9,3.5c3.9,3,5,9.1,2.2,12c-2.2,2.3-6.4,1.4-10.2-2.2c-0.7-0.7-1.3-1.4-2-2.1
		c-5.7,5.4-6.2,12.7-1.6,17.8c1.9,2.1,4.2,3.1,6.4,0.8c2.1-2.1,3.8-4.5,6.9-1.4c2.7,2.8,3.5,8,0.8,10.4c-2,1.7-4.8,2.9-7.4,3.4
	c-4.9,0.9-6.2,3.5-3.8,8c0.9,1.7,0.5,4.1,0.8,6.1c-2.2-0.3-5.2,0.2-6.5-1.1c-2.4-2.4-3.8-5.7-5.4-8.7c-2.2-4.2-4.2-4.6-9.5-2.1
	c-4,1.9-3.1,4.4-1.3,7.3c1.9,3.1,4.5,6.5-0.3,9.4c-4,2.5-13.5,1.7-14.8-1.6c-0.8-2.1,0-5.2,1.2-7.3c2-3.5,2.8-6-1.7-7.9
	c-5.4-2.4-7.1-1.7-9.5,3.2c-1.6,3.2-3.6,6.3-6.1,8.8c-0.9,0.9-3.7-0.1-5.7-0.3c0.2-1.8-0.2-3.9,0.6-5.3c3.4-5.3,2.4-7.6-4-8.3
		c-7-0.8-10.5-6.7-7.1-12.5c1.4-2.4,3.2-4,6-1.9c2.2,1.7,4.7,4.9,7.2,2.1c2.4-2.6,4.1-6.5,4.3-10C19.7,40.9,17.5,37.8,16.1,34.2z"/>
</svg>

当然,如果您不想将其用作遮罩,则每次需要更新电路板上的部件时,您都可以在其上方绘制一部分图像(例如,使用source-atop替换同一个“遮罩”上的图像。)