125 lines
4.5 KiB
JavaScript
125 lines
4.5 KiB
JavaScript
/* global buildPiecePath */
|
|
|
|
class PieceRenderer {
|
|
/**
|
|
* Render all puzzle pieces as Phaser textures via off-screen canvas clipping.
|
|
* In Phaser 3.9, TextureManager emits 'addtexture' with the key as argument
|
|
* (not 'addtexture-KEY' per-texture events that arrived in later versions).
|
|
*
|
|
* @param {Phaser.Scene} scene
|
|
* @param {HTMLImageElement} sourceImage
|
|
* @param {PieceData[]} pieceDataArray
|
|
* @param {number} cols
|
|
* @param {number} rows
|
|
* @param {number} imageW
|
|
* @param {number} imageH
|
|
* @param {function(number, number)} onProgress
|
|
* @returns {Promise<{ pieceW, pieceH, tabSize, canvasW, canvasH }>}
|
|
*/
|
|
static renderAll(scene, sourceImage, pieceDataArray, cols, rows, imageW, imageH, onProgress) {
|
|
const pieceW = imageW / cols;
|
|
const pieceH = imageH / rows;
|
|
const tabSize = Math.max(pieceW, pieceH) * 0.32;
|
|
const canvasW = Math.ceil(pieceW + 2 * tabSize);
|
|
const canvasH = Math.ceil(pieceH + 2 * tabSize);
|
|
const total = pieceDataArray.length;
|
|
const dims = { pieceW, pieceH, tabSize, canvasW, canvasH };
|
|
|
|
// Build a set of expected keys so the listener ignores unrelated textures
|
|
// Track both outlined (piece_N) and clean (piece_clean_N) textures
|
|
const pending = new Set();
|
|
pieceDataArray.forEach(p => {
|
|
pending.add(`piece_${p.id}`);
|
|
pending.add(`piece_clean_${p.id}`);
|
|
});
|
|
const totalTextures = pending.size;
|
|
let texDone = 0;
|
|
let done = 0;
|
|
|
|
return new Promise(resolve => {
|
|
const onAdded = (key) => {
|
|
if (!pending.has(key)) return;
|
|
pending.delete(key);
|
|
texDone++;
|
|
// Only count progress for main piece textures (not clean variants)
|
|
if (key.startsWith('piece_') && !key.startsWith('piece_clean_')) {
|
|
done++;
|
|
if (onProgress) onProgress(done, total);
|
|
}
|
|
if (texDone === totalTextures) {
|
|
scene.textures.off('addtexture', onAdded);
|
|
resolve(dims);
|
|
}
|
|
};
|
|
scene.textures.on('addtexture', onAdded);
|
|
|
|
// Kick off all canvas renders + addBase64 calls
|
|
pieceDataArray.forEach(piece => {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = canvasW;
|
|
canvas.height = canvasH;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// Clip path
|
|
ctx.save();
|
|
buildPiecePath(ctx, piece.gridCol, piece.gridRow, cols, rows,
|
|
pieceW, pieceH, piece.edges, tabSize);
|
|
ctx.clip();
|
|
// Draw the source region that corresponds to the full canvas extent,
|
|
// including the tab zones that overlap neighbouring cells.
|
|
// srcX/srcY may be slightly negative for pieces on the top/left border;
|
|
// the canvas spec clips out-of-bounds source regions automatically.
|
|
ctx.drawImage(
|
|
sourceImage,
|
|
piece.gridCol * pieceW - tabSize, piece.gridRow * pieceH - tabSize,
|
|
canvasW, canvasH,
|
|
0, 0,
|
|
canvasW, canvasH
|
|
);
|
|
ctx.restore();
|
|
|
|
// Subtle edge shading
|
|
ctx.save();
|
|
buildPiecePath(ctx, piece.gridCol, piece.gridRow, cols, rows,
|
|
pieceW, pieceH, piece.edges, tabSize);
|
|
ctx.clip();
|
|
ctx.strokeStyle = 'rgba(0,0,0,0.35)';
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
|
|
// Save clean version (used after pieces merge)
|
|
const cleanKey = `piece_clean_${piece.id}`;
|
|
if (scene.textures.exists(cleanKey)) scene.textures.remove(cleanKey);
|
|
scene.textures.addBase64(cleanKey, canvas.toDataURL('image/png'));
|
|
|
|
// Dark outer glow (drawn behind the stroke, outside the clip)
|
|
ctx.save();
|
|
buildPiecePath(ctx, piece.gridCol, piece.gridRow, cols, rows,
|
|
pieceW, pieceH, piece.edges, tabSize);
|
|
ctx.shadowColor = 'rgba(0, 0, 0, 0.7)';
|
|
ctx.shadowBlur = 8;
|
|
ctx.shadowOffsetX = 0;
|
|
ctx.shadowOffsetY = 0;
|
|
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
|
|
ctx.lineWidth = 2;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
|
|
// Bright off-white stroke along the piece outline
|
|
ctx.save();
|
|
buildPiecePath(ctx, piece.gridCol, piece.gridRow, cols, rows,
|
|
pieceW, pieceH, piece.edges, tabSize);
|
|
ctx.strokeStyle = 'rgba(240, 240, 255, 0.6)';
|
|
ctx.lineWidth = 1.5;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
|
|
const key = `piece_${piece.id}`;
|
|
if (scene.textures.exists(key)) scene.textures.remove(key);
|
|
scene.textures.addBase64(key, canvas.toDataURL('image/png'));
|
|
});
|
|
});
|
|
}
|
|
}
|