Bézier Curve Jigsaw Puzzles

I was trying to do some puzzle pieces like this -

enter image description here  enter image description here

What I have tried so far with 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)
    },

      

Link to script

+2


source to share


2 answers


Effective Jigsaw design is simple and works like this:

The linked code already shows you how to efficiently assemble one of your puzzles by reusing a single design.

The piece on the right side of the illustration is traditional (or "Japanese style"). This means that its sides are uniform in length and completely blocked. The Japanese style pieces are the easiest to design because one piece of design code can be reused in a puzzle.

Ironically, while Japanese style puzzles are the easiest to code, they find it harder to solve the problem as many pieces will physically fit together without solving the puzzle correctly.

How to create a Japanese style puzzle

  • Create one side (no more!) Of the puzzle by combining multiple cubic Bézier curves.

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

  • Assemble the puzzle from the pieces, reflecting the design of each adjacent side:

    • Give the top left (0,0) a random right side (either inny or outy).
    • Suppose piece (0,0) was assigned the right-hand side. Then the next piece to the right (1,0) should have an inner left side.
    • Now give chunk (1,0) a random right side (either inny or outy) and chunk (2,0) should get the mirrored side type. Etc...
    • So, in general, complete the puzzle by assigning random right-hand pieces to all pieces and mirroring the assigned side to the left-hand side of the next piece.
    • Do the same vertically. complete the puzzle by assigning random bottom sides to all pieces and mirroring the assigned side on the top side of the next piece.

Designing 2 examples you illustrated

I am assuming the linked code is not your code, because it already shows how to create this snippet to the right of the 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);

      

You can then reuse that one set of Bezier curves along with transformations to create your entire puzzle. Transformations == moving, rotating and reflecting one single structure to make up any side of any puzzle piece.

The piece to the left of your illustration is probably from a Freeform Style puzzle piece. It is more difficult because it uses 3 different side designs. I am assuming there are additional side structures that you did not show, because the three-sided design you show will not allow all the pieces to be blocked to complete the puzzle.

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

Freeform non-blocking style

In this style, you basically make an image and draw lines that cut it into uneven shapes that can be positioned to form an image. Think of it like a pizza that has been sliced ​​at random. You can put the pieces together to reform the pizza, even if the pieces are not blocking. Mmmmm, pizza! :-)

Freeform style lock

In this style, you create two sides and create a puzzle just like a traditional style puzzle. Typically, you create one design that you will use for all left and right sides, and a second design that you will use for all the top sides. The tricky part is that the 2 types of sides have to match where they meet. This means that side-type-1 must share a mirrored pattern where it intersects side-type-2.

So, to create the drawing on the left side of the illustration, you must decide whether you want it to be a free-form-lock or no-free-form lock function.



Unbound Freeform is easier. Just spread the three types of sides apart and use them with your mirror partners to slice the image.

Interlocking-Freeform requires more design work on your part. You have to create additional side structures that will block with the 3 projects you have already created.

This is a quick overview of the puzzles ... Good luck with your project!

[Additional Information]

For the part on the right side of the illustration, the usual "outer" looks like a "head and shoulders silhouette".

The Bezier set for creating shoulders and head breaks down as follows:

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

The shoulder and Bezier head might look like this:

enter image description here

Here's one specific example of breakpoints for creating the outer side with a "shoulders and 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 an "outer" set of curves, you can use canvas context transforms to flip the "outer" into its mirrored "inside". Alternatively, you can manually undo the "outer" curve control point array.

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

enter image description here

An example of displaying the 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>
      

Run codeHide result


+8


source


I would like to suggest an alternative approach -

SVG Source

Why not consider using an SVG image source that you create in Illustrator or some similar software. The tools will allow you to trace the outline of the fragment (it can actually do this almost completely automatically - see the result below). SVG can use Beziers too, or just a path that matches the original image perfectly. Just hit the trace, adjust the threshold, save and clean up the result a bit.

So you can just import the SVG, draw it on screen outside of the size you want to rasterize. The result can then be used for any composition or "cut" graphics directly.

It is much faster and easier than calculating and using splines.

Example

This result is illustrated in Illustrator, converted to SVG, with some manual clean-up like removing comments and background, adding width and height, and adjusting the viewport:



snap

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

Now I can load it as a basis for composition so that I can:

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>
      

Run codeHide result


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

+4


source







All Articles