405 lines
13 KiB
JavaScript
405 lines
13 KiB
JavaScript
// wfc_setup_worker.js
|
|
let sampleWidth,
|
|
sampleHeight,
|
|
srcDataView,
|
|
currentPatternSizeW,
|
|
enableRotationsW;
|
|
let patterns, patternByID, patternByIndex, patternIdToIndex;
|
|
let adjacency;
|
|
|
|
// --- Helper functions (subset of original, or new for worker) ---
|
|
function Uint8ArrayToHexString(u8a) {
|
|
let hex = '';
|
|
for (let i = 0; i < u8a.length; i++) {
|
|
let h = u8a[i].toString(16);
|
|
if (h.length < 2) h = '0' + h;
|
|
hex += h;
|
|
}
|
|
return hex;
|
|
}
|
|
|
|
function rotatePatternCells(flatCells, pSize, angle) {
|
|
const newFlatCells = new Uint8Array(pSize * pSize * 4);
|
|
if (angle === 0) {
|
|
newFlatCells.set(flatCells);
|
|
return newFlatCells;
|
|
}
|
|
for (let r_orig = 0; r_orig < pSize; r_orig++) {
|
|
for (let c_orig = 0; c_orig < pSize; c_orig++) {
|
|
const oldIdxBase = (r_orig * pSize + c_orig) * 4;
|
|
let nr, nc;
|
|
if (angle === 90) {
|
|
nr = c_orig;
|
|
nc = pSize - 1 - r_orig;
|
|
} else if (angle === 180) {
|
|
nr = pSize - 1 - r_orig;
|
|
nc = pSize - 1 - c_orig;
|
|
} else if (angle === 270) {
|
|
nr = pSize - 1 - c_orig;
|
|
nc = r_orig;
|
|
} else {
|
|
newFlatCells.set(flatCells);
|
|
return newFlatCells;
|
|
} // Should not happen with defined angles
|
|
const newIdxBase = (nr * pSize + nc) * 4;
|
|
newFlatCells[newIdxBase] = flatCells[oldIdxBase];
|
|
newFlatCells[newIdxBase + 1] = flatCells[oldIdxBase + 1];
|
|
newFlatCells[newIdxBase + 2] = flatCells[oldIdxBase + 2];
|
|
newFlatCells[newIdxBase + 3] = flatCells[oldIdxBase + 3];
|
|
}
|
|
}
|
|
return newFlatCells;
|
|
}
|
|
|
|
// --- Stats helpers (subset for this worker) ---
|
|
function estimateStringBytes(str) {
|
|
return str ? str.length * 2 : 0;
|
|
}
|
|
function estimateObjectOverhead(obj) {
|
|
if (!obj) return 0;
|
|
return (
|
|
Object.keys(obj).length *
|
|
(estimateStringBytes('key_placeholder_avg') + 8 + 4) +
|
|
16
|
|
);
|
|
}
|
|
|
|
let workerStats = {
|
|
patternExtraction: {},
|
|
adjacency: {},
|
|
memory: {},
|
|
};
|
|
|
|
function extractPatterns() {
|
|
console.log(
|
|
'[Setup Worker] extractPatterns started. sampleWidth:',
|
|
sampleWidth,
|
|
'sampleHeight:',
|
|
sampleHeight,
|
|
'pSize:',
|
|
currentPatternSizeW
|
|
);
|
|
// Log a small portion of srcDataView to check its general content
|
|
if (srcDataView && srcDataView.length > 0) {
|
|
console.log(
|
|
'[Setup Worker] srcDataView first 100 bytes:',
|
|
srcDataView.slice(0, 100)
|
|
);
|
|
} else {
|
|
console.error(
|
|
'[Setup Worker] srcDataView is null, empty, or undefined at start of extractPatterns!'
|
|
);
|
|
// If srcDataView is bad, we can't proceed meaningfully.
|
|
throw new Error('srcDataView is invalid in extractPatterns');
|
|
}
|
|
|
|
const t0 = performance.now();
|
|
const countMap = new Map();
|
|
const keyToFlatCells = new Map();
|
|
const pSize = currentPatternSizeW;
|
|
let rawPatternCounter = 0;
|
|
|
|
for (let y = 0; y <= sampleHeight - pSize; y++) {
|
|
for (let x = 0; x <= sampleWidth - pSize; x++) {
|
|
rawPatternCounter++;
|
|
const originalFlatBlock = new Uint8Array(pSize * pSize * 4);
|
|
let k = 0;
|
|
for (let dy = 0; dy < pSize; dy++) {
|
|
for (let dx = 0; dx < pSize; dx++) {
|
|
const imgPixelY = y + dy;
|
|
const imgPixelX = x + dx;
|
|
const srcIdx = (imgPixelY * sampleWidth + imgPixelX) * 4;
|
|
|
|
if (srcIdx + 3 >= srcDataView.length) {
|
|
// Boundary check
|
|
console.error(
|
|
`[Setup Worker] Out of bounds access attempt: srcIdx=${srcIdx}, y=${y},x=${x}, dy=${dy},dx=${dx}, imgPixelY=${imgPixelY},imgPixelX=${imgPixelX}, sampleWidth=${sampleWidth}, sampleHeight=${sampleHeight}, srcDataView.length=${srcDataView.length}`
|
|
);
|
|
// Fill with a debug color or handle error
|
|
originalFlatBlock[k++] = 255;
|
|
originalFlatBlock[k++] = 0;
|
|
originalFlatBlock[k++] = 0;
|
|
originalFlatBlock[k++] = 255; // Red
|
|
continue; // Skip this pixel
|
|
}
|
|
|
|
originalFlatBlock[k++] = srcDataView[srcIdx];
|
|
originalFlatBlock[k++] = srcDataView[srcIdx + 1];
|
|
originalFlatBlock[k++] = srcDataView[srcIdx + 2];
|
|
originalFlatBlock[k++] = srcDataView[srcIdx + 3];
|
|
}
|
|
}
|
|
|
|
if (x === 0 && y === 0) {
|
|
// Log only the very first extracted block
|
|
console.log(
|
|
'[Setup Worker] First originalFlatBlock (x=0, y=0, pSize=' +
|
|
pSize +
|
|
'):'
|
|
);
|
|
let pixelsToLog = [];
|
|
for (let i = 0; i < originalFlatBlock.length; i += 4) {
|
|
pixelsToLog.push(
|
|
`(${originalFlatBlock[i]},${originalFlatBlock[i + 1]},${
|
|
originalFlatBlock[i + 2]
|
|
},${originalFlatBlock[i + 3]})`
|
|
);
|
|
}
|
|
console.log(pixelsToLog.join(' '));
|
|
}
|
|
|
|
const rotationsToConsider = enableRotationsW ? [0, 90, 180, 270] : [0];
|
|
for (const angle of rotationsToConsider) {
|
|
const currentFlatBlockCells = rotatePatternCells(
|
|
originalFlatBlock,
|
|
pSize,
|
|
angle
|
|
);
|
|
const key = Uint8ArrayToHexString(currentFlatBlockCells);
|
|
countMap.set(key, (countMap.get(key) || 0) + 1);
|
|
if (!keyToFlatCells.has(key)) {
|
|
keyToFlatCells.set(key, currentFlatBlockCells);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
workerStats.patternExtraction.rawPatternCount =
|
|
rawPatternCounter * (enableRotationsW ? 4 : 1);
|
|
|
|
patterns = [];
|
|
patternByID = {};
|
|
patternByIndex = []; // This will be an array of pattern objects
|
|
patternIdToIndex = {};
|
|
let idx = 0;
|
|
for (const [key, freq] of countMap.entries()) {
|
|
const patID = 'p' + idx;
|
|
const flatCellsData = keyToFlatCells.get(key); // This is a Uint8Array
|
|
const patObj = {
|
|
id: patID,
|
|
cells: flatCellsData,
|
|
frequency: freq,
|
|
index: idx,
|
|
};
|
|
patterns.push(patObj);
|
|
patternByID[patID] = patObj;
|
|
patternByIndex[idx] = patObj; // Store by index
|
|
patternIdToIndex[patID] = idx;
|
|
idx++;
|
|
}
|
|
workerStats.patternExtraction.timeMs = performance.now() - t0;
|
|
workerStats.patternExtraction.uniquePatternCount = patterns.length;
|
|
workerStats.patternExtraction.rotationsEnabled = enableRotationsW;
|
|
|
|
workerStats.memory.patternCellsBytes = 0;
|
|
workerStats.memory.patternsArrayBytes = 0;
|
|
patterns.forEach((p) => {
|
|
workerStats.memory.patternCellsBytes += p.cells.byteLength;
|
|
workerStats.memory.patternsArrayBytes +=
|
|
estimateStringBytes(p.id) + 4 + 4 + 8 + 16;
|
|
});
|
|
workerStats.memory.patternsArrayBytes += 16;
|
|
workerStats.memory.patternByIdBytes = estimateObjectOverhead(patternByID);
|
|
Object.keys(patternByID).forEach((key) => {
|
|
workerStats.memory.patternByIdBytes += estimateStringBytes(key) + 8;
|
|
});
|
|
|
|
self.postMessage({
|
|
type: 'SETUP_PROGRESS',
|
|
payload: {
|
|
statusMessage: `Found ${patterns.length} unique patterns. Building adjacency...`,
|
|
statsUpdate: {
|
|
patternExtraction: workerStats.patternExtraction,
|
|
memory: workerStats.memory,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
// --- CORRECTED `buildAdjacency` for the OVERLAPPING MODEL ---
|
|
// --- Paste this into wfc_setup_worker.js ---
|
|
|
|
function buildAdjacency() {
|
|
const t1 = performance.now();
|
|
adjacency = {};
|
|
if (!patterns) return;
|
|
const pSize = currentPatternSizeW;
|
|
let ruleCount = 0;
|
|
|
|
for (const pat of patterns) {
|
|
adjacency[pat.id] = { up: [], down: [], left: [], right: [] };
|
|
}
|
|
|
|
for (const p of patterns) {
|
|
for (const q of patterns) {
|
|
if (p.id === q.id && patterns.length === 1) {
|
|
adjacency[p.id].right.push(q.index);
|
|
ruleCount++;
|
|
adjacency[q.id].left.push(p.index);
|
|
ruleCount++;
|
|
adjacency[p.id].down.push(q.index);
|
|
ruleCount++;
|
|
adjacency[q.id].up.push(p.index);
|
|
ruleCount++;
|
|
continue;
|
|
}
|
|
|
|
// Check if q can be to the RIGHT of p (OVERLAPPING MODEL)
|
|
// The right N-1 columns of p must match the left N-1 columns of q.
|
|
let canSitRight = true;
|
|
if (pSize > 1) {
|
|
for (let r = 0; r < pSize; r++) { // For each row
|
|
for (let c = 0; c < pSize - 1; c++) { // For each overlapping column
|
|
for (let ch = 0; ch < 4; ch++) {
|
|
// Compare p's pixel at [r][c+1] with q's pixel at [r][c]
|
|
const pPixel = p.cells[(r * pSize + (c + 1)) * 4 + ch];
|
|
const qPixel = q.cells[(r * pSize + c) * 4 + ch];
|
|
if (pPixel !== qPixel) {
|
|
canSitRight = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!canSitRight) break;
|
|
}
|
|
if (!canSitRight) break;
|
|
}
|
|
} else {
|
|
canSitRight = true; // 1x1 patterns are always adjacent
|
|
}
|
|
|
|
if (canSitRight) {
|
|
adjacency[p.id].right.push(q.index);
|
|
ruleCount++;
|
|
adjacency[q.id].left.push(p.index);
|
|
ruleCount++;
|
|
}
|
|
|
|
// Check if q can be DOWN from p (OVERLAPPING MODEL)
|
|
// The bottom N-1 rows of p must match the top N-1 rows of q.
|
|
let canSitDown = true;
|
|
if (pSize > 1) {
|
|
for (let r = 0; r < pSize - 1; r++) { // For each overlapping row
|
|
for (let c = 0; c < pSize; c++) { // For each column in that row
|
|
for (let ch = 0; ch < 4; ch++) {
|
|
// Compare p's pixel at [r+1][c] with q's pixel at [r][c]
|
|
const pPixel = p.cells[((r + 1) * pSize + c) * 4 + ch];
|
|
const qPixel = q.cells[(r * pSize + c) * 4 + ch];
|
|
if (pPixel !== qPixel) {
|
|
canSitDown = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!canSitDown) break;
|
|
}
|
|
if (!canSitDown) break;
|
|
}
|
|
} else {
|
|
canSitDown = true; // 1x1 patterns are always adjacent
|
|
}
|
|
|
|
if (canSitDown) {
|
|
adjacency[p.id].down.push(q.index);
|
|
ruleCount++;
|
|
adjacency[q.id].up.push(p.index);
|
|
ruleCount++;
|
|
}
|
|
}
|
|
}
|
|
workerStats.adjacency.totalRules = ruleCount;
|
|
workerStats.adjacency.timeMs = performance.now() - t1;
|
|
|
|
let adjMem = estimateObjectOverhead(adjacency);
|
|
if (adjacency) {
|
|
for (const patId in adjacency) {
|
|
adjMem += estimateStringBytes(patId);
|
|
adjMem += estimateObjectOverhead(adjacency[patId]);
|
|
['up', 'down', 'left', 'right'].forEach((dir) => {
|
|
adjMem += estimateStringBytes(dir);
|
|
adjMem +=
|
|
adjacency[patId][dir].length * (patterns.length > 65535 ? 4 : 2) + 16;
|
|
});
|
|
}
|
|
}
|
|
workerStats.memory.adjacencyBytes = adjMem;
|
|
}
|
|
|
|
self.onmessage = function (e) {
|
|
const { type, imageDataBuffer, width, height, patternSize, enableRotations } =
|
|
e.data;
|
|
|
|
if (type === 'START_SETUP') {
|
|
console.log(
|
|
'[Setup Worker] Received START_SETUP. Image data buffer length:',
|
|
imageDataBuffer ? imageDataBuffer.byteLength : 'N/A'
|
|
);
|
|
sampleWidth = width;
|
|
sampleHeight = height;
|
|
srcDataView = new Uint8ClampedArray(imageDataBuffer);
|
|
currentPatternSizeW = patternSize;
|
|
enableRotationsW = enableRotations;
|
|
|
|
try {
|
|
extractPatterns();
|
|
if (!patterns || patterns.length === 0) {
|
|
self.postMessage({
|
|
type: 'SETUP_COMPLETE',
|
|
error: 'No patterns extracted.',
|
|
}); // Send error within SETUP_COMPLETE for main thread to handle
|
|
return;
|
|
}
|
|
buildAdjacency();
|
|
|
|
// patterns[i].cells are Uint8Arrays. Their .buffer will be transferred.
|
|
const transferablePatternCellBuffers = patterns.map(
|
|
(p) => p.cells.buffer
|
|
);
|
|
|
|
self.postMessage(
|
|
{
|
|
type: 'SETUP_COMPLETE',
|
|
payload: {
|
|
patternsData: {
|
|
patterns: patterns, // Array of pattern objects, .cells are Uint8Array
|
|
patternByID: patternByID,
|
|
patternByIndex: patternByIndex, // Send this explicitly if main thread uses it by this name
|
|
patternIdToIndex: patternIdToIndex,
|
|
adjacency: adjacency,
|
|
},
|
|
stats: workerStats,
|
|
},
|
|
},
|
|
transferablePatternCellBuffers
|
|
);
|
|
} catch (err) {
|
|
console.error('[Setup Worker] Error during setup process:', err);
|
|
self.postMessage({
|
|
type: 'SETUP_COMPLETE',
|
|
error: err.message + (err.stack ? '\n' + err.stack : ''),
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// Catch unhandled errors in the worker
|
|
self.onerror = function (message, source, lineno, colno, error) {
|
|
console.error(
|
|
'[Setup Worker] Unhandled error:',
|
|
message,
|
|
source,
|
|
lineno,
|
|
colno,
|
|
error
|
|
);
|
|
// Try to inform the main thread, though if postMessage itself fails, this might not work.
|
|
try {
|
|
self.postMessage({
|
|
type: 'SETUP_COMPLETE',
|
|
error: `Unhandled error in setup worker: ${message}`,
|
|
});
|
|
} catch (e) {
|
|
console.error(
|
|
'[Setup Worker] Failed to postMessage about unhandled error.',
|
|
e
|
|
);
|
|
}
|
|
return true; // Prevents default error handling if possible
|
|
}; |