Jigsaw Puzzle pices using Bezier Curve

天大地大妈咪最大 提交于 2019-12-06 06:51:35

Efficient Jigsaw design is simple and it works like this:

The linked code already shows how to efficiently assemble one of your jigsaw pieces by reusing a single side design.

The piece on the right side of you illustration is a traditional (or "Japanese Style") piece. This means its sides are uniform in length and fully interlocking. Japanese style pieces are the easiest to design because a single piece of design code and be reused throughout the puzzle.

Ironically, While Japanese Style puzzles are the easiest to code, they are more difficult for the user to solve since many pieces will physically fit together without correctly solving the puzzle.

How to design a Japanese Style jigsaw puzzle

  1. Design one side (not more!) of a jigsaw piece by combining multiple cubic Bezier curves.

  2. Use transforms to apply that one jigsaw design to the top, right, bottom or left sides as needed. (or code functions that automatically manipulate the original Bezier control points to apply that one jigsaw design to the 4 sides). Mirror the original side design to give your pieces a variety of "inny" and "outy" sides.

  3. Assemble a puzzle from pieces by mirroring the design of each neighboring side:

    • Give the top-left piece (0,0) a random right side (either inny or outy).
    • Let's assume piece (0,0) was assigned an outy right side. Then the next piece to the right (1,0) must get an inny left side.
    • Now give piece (1,0) a random right side (either inny or outy), and piece (2,0) must get the mirrored type of side. And so on...
    • So in general, fill the puzzle by assigning random right sides to all the pieces and mirroring the assigned side on the left side of the next piece.
    • Do the same vertically. fill the puzzle by assigning random bottom sides to all the pieces and mirroring the assigned side on the top side of the next piece.

Designing the 2 example pieces you've illustrated

I assume the linked code is not your code because it already shows how to design that piece on the right of your illustration(!).

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

Then you can reuse this one single set of Bezier curves along with transformations to create your entire puzzle. Transformations==moving, rotating and mirroring the one single design to make up any side of any puzzle piece.

The piece on the left of your illustration is probably from a Freeform Style jigsaw puzzle. It is more complex because it uses 3 different side designs. I assume there are additional side designs which you haven't shown because the 3-sided design you show would not allow all pieces to interlock to complete the puzzle.

You have several options when creating a Freeform Style jigsaw puzzle.

Non-interlocking Freeform Style

In this style, you basically take an image and draw lines that cut it into non-uniform pieces that can be arranged to form the image. Think of this like a pizza that's been sliced randomly. You can fit the pieces together to reform the pizza even if the pieces do not interlock. Mmmmm, pizza! :-)

Interlocking Freeform Style

In this style, you design 2+ sides and create the puzzle much the same way as the traditional style puzzle. Usually you create one design that you will use for all left-right sides and a second design that you will use for all top-bottom sides. The complexity is that the 2 types of sides must fit together where they meet. This means that side-type-1 must share a mirrored pattern where it intersects side-type-2.

So to design the piece on the left side of your illustration, you must decide if you want it to be Interlocking-Freeform or Non-interlocking-Freeform.

Non-interlocking Freeform is the easier. Just pull apart the 3 types of sides and use them with their mirrored partners to chop up your image.

For Interlocking-Freeform, more design work is necessary on your part. You must create additional side designs that will interlock with the 3 designs you've already created.

That's a quick tour of jigsaw puzzles...Good luck with your project!

[ Additional details ]

For the piece on the right side of your illustration, the common "outside" looks like a "shoulders & head" silhouette.

The Bezier-set to create the shoulders & head break down like this:

  • A Bezier for the "left shoulder"
  • A Bezier for the "left neck"
  • A Bezier for the "left head"
  • A Bezier for the "right head"
  • A Bezier for the "right neck"
  • A Bezier for the "right shoulder"

A shoulder & head Bezier set might look like this:

Here's one specific example of the control points to create an outside side with a "shoulders & head" shape:

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
];

Once you have the "outside" set of curves, you can use canvas's context transformations to flip the "outside" into its mirrored "inside". Alternatively you can manually reverse the "outside" array of curve control points.

Illustrations: top-tab and top-slot (top-slot is top-tab mirrored)

Example displaying top-tab and top-slot:

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);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>

I would like to propose an alternative approach -

SVG source

Why not consider using a SVG image source that you make in Illustrator or some similar software. The tools will allow you to trace the outline of a piece (it can in fact do it almost entirely automatic - see result below). The SVG may use Beziers too, or just a path that fits the original image almost perfectly. Just hit trace, adjust threshold, save out and clean up the result a little.

This way you can simply import the SVG, draw it to an off-screen canvas at the size you want to rasterize it. The result can then be used for either composition or "cutting out" graphics directly.

This is much faster and simpler than calculating and using splines.

Example

This result is from Illustrator converted to SVG, with some manual clean-up such as removing comments and background, adding width and height and adjusting viewbox:

(Note: in this demo I left the shadow in the original image - you would have to clean up the image itself, then process it to SVG etc.).

I can now load it as a basis for composition so I could do:

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>

And of course, if you don't want to use it as matte you can draw a piece of the image on top of it every time you need to update a piece on the board (use source-atop for example to replace the image on the same "matte").

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!