iPuzzle/js/puzzle/ConnectorGeometry.js

148 lines
5.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ConnectorGeometry.js
*
* Draws a closed canvas-2D path for a single puzzle piece.
* The piece's grid cell occupies [tabSize, tabSize] to [tabSize+pieceW, tabSize+pieceH]
* within an off-screen canvas of size (pieceW+2*tabSize) × (pieceH+2*tabSize).
*
* Tab/blank bumps occupy the middle third of each internal edge.
* tabSize should be >= pieceW * 0.3 to give the tab room to protrude.
*/
/**
* Add a connector bump between two points in canvas space.
* @param {CanvasRenderingContext2D} ctx
* @param {number} x0,y0 - start of edge segment
* @param {number} x1,y1 - end of edge segment
* @param {number} dir - +1 = tab (protrudes to left of travel), -1 = blank (indents)
*
* "Left of travel" in canvas coords:
* Travelling right (+X): left = -Y (tab protrudes upward on canvas)
* Travelling down (+Y): left = +X
* etc.
* We want the tab to protrude *outward* from the piece, so callers pass the
* edge direction and we compute the perpendicular accordingly.
*/
function _addConnectorBump(ctx, x0, y0, x1, y1, dir) {
// Edge vector
const ex = x1 - x0;
const ey = y1 - y0;
const L = Math.sqrt(ex * ex + ey * ey);
// Unit edge and perpendicular (perpendicular points "outward" when dir=+1)
const ux = ex / L;
const uy = ey / L;
// Outward perpendicular = rotate edge 90° clockwise: (uy, -ux)
const px = uy * dir;
const py = -ux * dir;
// Bump parameters as fractions of edge length
const H = L * 0.28; // bump height
const R = L * 0.10; // knob half-width
// Bump occupies centre third: t ∈ [1/3, 2/3]
// Points along the edge
const bx0 = x0 + ux * (L / 3);
const by0 = y0 + uy * (L / 3);
const bx1 = x0 + ux * (L * 2 / 3);
const by1 = y0 + uy * (L * 2 / 3);
const bmx = x0 + ux * (L / 2); // midpoint of edge
const bmy = y0 + uy * (L / 2);
// Base of knob (at H * 0.55 along perpendicular)
const kbx = bmx + px * H * 0.55;
const kby = bmy + py * H * 0.55;
// Tip of knob (at H along perpendicular)
const ktx = bmx + px * H;
const kty = bmy + py * H;
// Draw: lead-in from bump start → left knob base → knob arc → right knob base → lead-out
// Lead-in cubic
ctx.bezierCurveTo(
bx0 + ux * L * 0.04 + px * H * 0.2, by0 + uy * L * 0.04 + py * H * 0.2, // cp1
kbx - ux * R * 1.2, kby - uy * R * 1.2, // cp2
kbx - ux * R, kby - uy * R // end
);
// Knob left arc
ctx.bezierCurveTo(
ktx - ux * R, kty - uy * R, // cp1
ktx - ux * R, kty - uy * R, // cp2 (same → straight-ish)
ktx, kty // end (tip)
);
// Knob right arc
ctx.bezierCurveTo(
ktx + ux * R, kty + uy * R, // cp1
ktx + ux * R, kty + uy * R, // cp2
kbx + ux * R, kby + uy * R // end
);
// Lead-out cubic
ctx.bezierCurveTo(
kbx + ux * R * 1.2, kby + uy * R * 1.2,
bx1 - ux * L * 0.04 + px * H * 0.2, by1 - uy * L * 0.04 + py * H * 0.2,
bx1, by1
);
}
/**
* Build the full closed clip path for a single piece.
*
* @param {CanvasRenderingContext2D} ctx
* @param {number} gridCol
* @param {number} gridRow
* @param {number} cols - total columns in grid
* @param {number} rows - total rows in grid
* @param {number} pieceW - width of one grid cell in pixels
* @param {number} pieceH - height of one grid cell in pixels
* @param {{ top, right, bottom, left }} edges - 'flat'|'tab'|'blank' per side
* @param {number} tabSize - padding around cell in canvas (= max(pieceW,pieceH)*0.3 or similar)
*/
function buildPiecePath(ctx, gridCol, gridRow, cols, rows, pieceW, pieceH, edges, tabSize) {
const x0 = tabSize; // left edge of cell on canvas
const y0 = tabSize; // top edge of cell on canvas
const x1 = tabSize + pieceW; // right edge
const y1 = tabSize + pieceH; // bottom edge
ctx.beginPath();
ctx.moveTo(x0, y0);
// TOP edge: left → right
if (edges.top === 'flat') {
ctx.lineTo(x1, y0);
} else {
// Tab protrudes upward (Y), blank indents downward (+Y)
// "outward" for top edge = Y → dir = +1 gives perpendicular toward Y (correct for tab)
ctx.lineTo(x0 + (x1 - x0) / 3, y0);
_addConnectorBump(ctx, x0, y0, x1, y0, edges.top === 'tab' ? -1 : +1);
ctx.lineTo(x1, y0);
}
// RIGHT edge: top → bottom
if (edges.right === 'flat') {
ctx.lineTo(x1, y1);
} else {
ctx.lineTo(x1, y0 + (y1 - y0) / 3);
_addConnectorBump(ctx, x1, y0, x1, y1, edges.right === 'tab' ? +1 : -1);
ctx.lineTo(x1, y1);
}
// BOTTOM edge: right → left
if (edges.bottom === 'flat') {
ctx.lineTo(x0, y1);
} else {
ctx.lineTo(x1 - (x1 - x0) / 3, y1);
_addConnectorBump(ctx, x1, y1, x0, y1, edges.bottom === 'tab' ? -1 : +1);
ctx.lineTo(x0, y1);
}
// LEFT edge: bottom → top
if (edges.left === 'flat') {
ctx.lineTo(x0, y0);
} else {
ctx.lineTo(x0, y1 - (y1 - y0) / 3);
_addConnectorBump(ctx, x0, y1, x0, y0, edges.left === 'tab' ? +1 : -1);
ctx.lineTo(x0, y0);
}
ctx.closePath();
}