fix dithering bug
This commit is contained in:
parent
e3eeb432e1
commit
1797fe00b3
49
script.js
49
script.js
@ -599,9 +599,11 @@
|
|||||||
const pCols = OUTPUT_SIZE;
|
const pCols = OUTPUT_SIZE;
|
||||||
const pRows = OUTPUT_SIZE;
|
const pRows = OUTPUT_SIZE;
|
||||||
|
|
||||||
outputCanvas.width = pCols * currentPatternSize;
|
const canvasWidth = pCols + currentPatternSize - 1;
|
||||||
outputCanvas.height = pRows * currentPatternSize;
|
const canvasHeight = pRows + currentPatternSize - 1;
|
||||||
outputCtx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
|
outputCanvas.width = canvasWidth;
|
||||||
|
outputCanvas.height = canvasHeight;
|
||||||
|
outputCtx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||||
|
|
||||||
stats.grid.pCols = pCols;
|
stats.grid.pCols = pCols;
|
||||||
stats.grid.pRows = pRows;
|
stats.grid.pRows = pRows;
|
||||||
@ -743,19 +745,34 @@
|
|||||||
Object.assign(stats.memory, payload.memory || {});
|
Object.assign(stats.memory, payload.memory || {});
|
||||||
break;
|
break;
|
||||||
case 'WFC_COMPLETE':
|
case 'WFC_COMPLETE':
|
||||||
phase = 'done';
|
phase = 'done';
|
||||||
statusEl.textContent = 'WFC complete!';
|
statusEl.textContent = 'WFC complete!';
|
||||||
stats.wfcRun.status = 'WFC Complete!';
|
stats.wfcRun.status = 'WFC Complete!';
|
||||||
if (payload.finalStats)
|
if (payload.finalStats) {
|
||||||
Object.assign(stats.wfcRun, payload.finalStats.wfcRun || {});
|
Object.assign(stats.wfcRun, payload.finalStats.wfcRun || {});
|
||||||
if (stats.wfcRun.startTime > 0 && stats.wfcRun.endTime === 0) {
|
}
|
||||||
|
if (stats.wfcRun.startTime > 0 && stats.wfcRun.endTime === 0) {
|
||||||
stats.wfcRun.endTime = performance.now();
|
stats.wfcRun.endTime = performance.now();
|
||||||
stats.wfcRun.durationMs =
|
stats.wfcRun.durationMs = stats.wfcRun.endTime - stats.wfcRun.startTime;
|
||||||
stats.wfcRun.endTime - stats.wfcRun.startTime;
|
}
|
||||||
}
|
|
||||||
processAndDrawDirtyTiles();
|
// Use the definitive final grid from the payload to force a full redraw.
|
||||||
if (solverWorker) solverWorker.terminate();
|
if (payload.finalGrid) {
|
||||||
break;
|
payload.finalGrid.forEach((row, r) => {
|
||||||
|
row.forEach((cellOptions, c) => {
|
||||||
|
// We use addDirtyTile to update our internal state and queue the draw
|
||||||
|
addDirtyTile(c, r, cellOptions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immediately draw all the tiles from the final grid.
|
||||||
|
processAndDrawDirtyTiles();
|
||||||
|
|
||||||
|
if (solverWorker) {
|
||||||
|
solverWorker.terminate();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'WFC_FAILED':
|
case 'WFC_FAILED':
|
||||||
phase = 'error';
|
phase = 'error';
|
||||||
statusEl.textContent = payload.message || 'WFC failed.';
|
statusEl.textContent = payload.message || 'WFC failed.';
|
||||||
@ -854,8 +871,8 @@
|
|||||||
function drawTile(c, r, cellOptions, patternArrayForDrawing, pSize) {
|
function drawTile(c, r, cellOptions, patternArrayForDrawing, pSize) {
|
||||||
if (!cellOptions || !patternArrayForDrawing) return;
|
if (!cellOptions || !patternArrayForDrawing) return;
|
||||||
const numOptions = cellOptions.length;
|
const numOptions = cellOptions.length;
|
||||||
const pixelX = c * pSize;
|
const pixelX = c;
|
||||||
const pixelY = r * pSize;
|
const pixelY = r;
|
||||||
|
|
||||||
if (numOptions === 1) {
|
if (numOptions === 1) {
|
||||||
drawCollapsedPatternForCell(
|
drawCollapsedPatternForCell(
|
||||||
|
|||||||
@ -215,6 +215,9 @@ function extractPatterns() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- CORRECTED `buildAdjacency` for the OVERLAPPING MODEL ---
|
||||||
|
// --- Paste this into wfc_setup_worker.js ---
|
||||||
|
|
||||||
function buildAdjacency() {
|
function buildAdjacency() {
|
||||||
const t1 = performance.now();
|
const t1 = performance.now();
|
||||||
adjacency = {};
|
adjacency = {};
|
||||||
@ -223,7 +226,6 @@ function buildAdjacency() {
|
|||||||
let ruleCount = 0;
|
let ruleCount = 0;
|
||||||
|
|
||||||
for (const pat of patterns) {
|
for (const pat of patterns) {
|
||||||
// patterns is an array of pattern objects
|
|
||||||
adjacency[pat.id] = { up: [], down: [], left: [], right: [] };
|
adjacency[pat.id] = { up: [], down: [], left: [], right: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,15 +242,18 @@ function buildAdjacency() {
|
|||||||
ruleCount++;
|
ruleCount++;
|
||||||
continue;
|
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;
|
let canSitRight = true;
|
||||||
if (pSize > 1) {
|
if (pSize > 1) {
|
||||||
for (let r = 0; r < pSize; r++) {
|
for (let r = 0; r < pSize; r++) { // For each row
|
||||||
for (let c = 0; c < pSize - 1; c++) {
|
for (let c = 0; c < pSize - 1; c++) { // For each overlapping column
|
||||||
for (let ch = 0; ch < 4; ch++) {
|
for (let ch = 0; ch < 4; ch++) {
|
||||||
if (
|
// Compare p's pixel at [r][c+1] with q's pixel at [r][c]
|
||||||
p.cells[(r * pSize + (c + 1)) * 4 + ch] !==
|
const pPixel = p.cells[(r * pSize + (c + 1)) * 4 + ch];
|
||||||
q.cells[(r * pSize + c) * 4 + ch]
|
const qPixel = q.cells[(r * pSize + c) * 4 + ch];
|
||||||
) {
|
if (pPixel !== qPixel) {
|
||||||
canSitRight = false;
|
canSitRight = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -258,7 +263,7 @@ function buildAdjacency() {
|
|||||||
if (!canSitRight) break;
|
if (!canSitRight) break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
canSitRight = true;
|
canSitRight = true; // 1x1 patterns are always adjacent
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canSitRight) {
|
if (canSitRight) {
|
||||||
@ -268,15 +273,17 @@ function buildAdjacency() {
|
|||||||
ruleCount++;
|
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;
|
let canSitDown = true;
|
||||||
if (pSize > 1) {
|
if (pSize > 1) {
|
||||||
for (let c_col = 0; c_col < pSize; c_col++) {
|
for (let r = 0; r < pSize - 1; r++) { // For each overlapping row
|
||||||
for (let r_row = 0; r_row < pSize - 1; r_row++) {
|
for (let c = 0; c < pSize; c++) { // For each column in that row
|
||||||
for (let ch = 0; ch < 4; ch++) {
|
for (let ch = 0; ch < 4; ch++) {
|
||||||
if (
|
// Compare p's pixel at [r+1][c] with q's pixel at [r][c]
|
||||||
p.cells[((r_row + 1) * pSize + c_col) * 4 + ch] !==
|
const pPixel = p.cells[((r + 1) * pSize + c) * 4 + ch];
|
||||||
q.cells[(r_row * pSize + c_col) * 4 + ch]
|
const qPixel = q.cells[(r * pSize + c) * 4 + ch];
|
||||||
) {
|
if (pPixel !== qPixel) {
|
||||||
canSitDown = false;
|
canSitDown = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -286,7 +293,7 @@ function buildAdjacency() {
|
|||||||
if (!canSitDown) break;
|
if (!canSitDown) break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
canSitDown = true;
|
canSitDown = true; // 1x1 patterns are always adjacent
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canSitDown) {
|
if (canSitDown) {
|
||||||
@ -395,4 +402,4 @@ self.onerror = function (message, source, lineno, colno, error) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true; // Prevents default error handling if possible
|
return true; // Prevents default error handling if possible
|
||||||
};
|
};
|
||||||
@ -372,112 +372,105 @@ function runStepInWorker() {
|
|||||||
payload: { wfcRun: { totalBacktracks: solverRunStats.totalBacktracks } },
|
payload: { wfcRun: { totalBacktracks: solverRunStats.totalBacktracks } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// THIS IS THE CORRECTED BACKTRACKING LOOP
|
||||||
while (backtrackStack.length > 0) {
|
while (backtrackStack.length > 0) {
|
||||||
const lastDecision = backtrackStack.pop();
|
const lastDecision = backtrackStack.pop();
|
||||||
const {
|
const {
|
||||||
gridBeforeChoice,
|
gridBeforeChoice,
|
||||||
queueBeforeChoice,
|
queueBeforeChoice,
|
||||||
queuedCoordinatesSetBeforeChoice,
|
queuedCoordinatesSetBeforeChoice,
|
||||||
cellCoords,
|
cellCoords,
|
||||||
optionsAtCell,
|
optionsAtCell,
|
||||||
lastTriedOptionIndexInList,
|
lastTriedOptionIndexInList,
|
||||||
} = lastDecision;
|
} = lastDecision;
|
||||||
|
|
||||||
const [cc, cr] = cellCoords;
|
const [cc, cr] = cellCoords;
|
||||||
let nextOptionIndexToTryInList = lastTriedOptionIndexInList + 1;
|
let nextOptionIndexToTryInList = lastTriedOptionIndexInList + 1;
|
||||||
|
|
||||||
if (nextOptionIndexToTryInList < optionsAtCell.length) {
|
if (nextOptionIndexToTryInList < optionsAtCell.length) {
|
||||||
grid = deepCopyGrid(gridBeforeChoice); // Restore state
|
grid = deepCopyGrid(gridBeforeChoice); // Restore state
|
||||||
queue = deepCopyQueue(queueBeforeChoice);
|
queue = deepCopyQueue(queueBeforeChoice);
|
||||||
queuedCoordinatesSet = new Set(queuedCoordinatesSetBeforeChoice);
|
queuedCoordinatesSet = new Set(queuedCoordinatesSetBeforeChoice);
|
||||||
|
|
||||||
const newChosenPatternIndex = optionsAtCell[nextOptionIndexToTryInList];
|
const newChosenPatternIndex = optionsAtCell[nextOptionIndexToTryInList];
|
||||||
grid[cr][cc] = [newChosenPatternIndex];
|
grid[cr][cc] = [newChosenPatternIndex];
|
||||||
// Post update for the cell being retried
|
// Post update for the cell being retried
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
type: 'TILE_UPDATE',
|
type: 'TILE_UPDATE',
|
||||||
payload: { updates: [{ c: cc, r: cr, cellOptions: grid[cr][cc] }] },
|
payload: { updates: [{ c: cc, r: cr, cellOptions: grid[cr][cc] }] },
|
||||||
});
|
});
|
||||||
|
|
||||||
const coordKeyBacktrack = `${cc},${cr}`;
|
const coordKeyBacktrack = `${cc},${cr}`;
|
||||||
if (!queuedCoordinatesSet.has(coordKeyBacktrack)) {
|
if (!queuedCoordinatesSet.has(coordKeyBacktrack)) {
|
||||||
queue.push([cc, cr]);
|
queue.push([cc, cr]);
|
||||||
queuedCoordinatesSet.add(coordKeyBacktrack);
|
queuedCoordinatesSet.add(coordKeyBacktrack);
|
||||||
}
|
|
||||||
|
|
||||||
// Push new decision point for this choice
|
|
||||||
if (backtrackStack.length < MAX_BACKTRACK_DEPTH_W) {
|
|
||||||
// Check before pushing
|
|
||||||
backtrackStack.push({
|
|
||||||
...lastDecision, // Keep most of the old data
|
|
||||||
lastTriedOptionIndexInList: nextOptionIndexToTryInList, // Update the tried index
|
|
||||||
});
|
|
||||||
solverRunStats.maxBacktrackStackDepthReached = Math.max(
|
|
||||||
solverRunStats.maxBacktrackStackDepthReached,
|
|
||||||
backtrackStack.length
|
|
||||||
);
|
|
||||||
solverMemoryStats.backtrackStackPeakBytes = Math.max(
|
|
||||||
solverMemoryStats.backtrackStackPeakBytes,
|
|
||||||
backtrackStack.length * solverMemoryStats.estimatedGridSnapshotBytes
|
|
||||||
);
|
|
||||||
self.postMessage({
|
|
||||||
type: 'STATS_UPDATE',
|
|
||||||
payload: {
|
|
||||||
wfcRun: {
|
|
||||||
maxBacktrackStackDepthReached:
|
|
||||||
solverRunStats.maxBacktrackStackDepthReached,
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
backtrackStackPeakBytes:
|
|
||||||
solverMemoryStats.backtrackStackPeakBytes,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Cannot push new state if max depth would be exceeded by this push.
|
|
||||||
// This implies this new choice, if it leads to immediate contradiction, cannot be further backtracked from this exact point.
|
|
||||||
// This state is tricky; the original code had a check *before* collapse.
|
|
||||||
// For now, we just don't push if it would exceed.
|
|
||||||
}
|
|
||||||
|
|
||||||
self.postMessage({
|
|
||||||
type: 'STATUS_UPDATE',
|
|
||||||
payload: {
|
|
||||||
message: `Backtracked. Retrying option ${
|
|
||||||
nextOptionIndexToTryInList + 1
|
|
||||||
}/${optionsAtCell.length} at (${cc},${cr}). Stack: ${
|
|
||||||
backtrackStack.length
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
solverPhase = 'propagate';
|
|
||||||
backtrackedSuccessfully = true;
|
|
||||||
|
|
||||||
// When backtracking, the entire grid might have changed, so signal main to redraw all from current worker grid state.
|
|
||||||
// This is simpler than tracking all individual changes during backtrack restoration.
|
|
||||||
const allTilesUpdate = [];
|
|
||||||
for (let r_idx = 0; r_idx < pRows; r_idx++) {
|
|
||||||
for (let c_idx = 0; c_idx < pCols; c_idx++) {
|
|
||||||
if (grid[r_idx] && grid[r_idx][c_idx]) {
|
|
||||||
// Ensure cell exists
|
|
||||||
allTilesUpdate.push({
|
|
||||||
c: c_idx,
|
|
||||||
r: r_idx,
|
|
||||||
cellOptions: [...grid[r_idx][c_idx]],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// THE FIX: Push the updated decision back onto the stack
|
||||||
|
if (backtrackStack.length < MAX_BACKTRACK_DEPTH_W) {
|
||||||
|
backtrackStack.push({
|
||||||
|
...lastDecision,
|
||||||
|
lastTriedOptionIndexInList: nextOptionIndexToTryInList,
|
||||||
|
});
|
||||||
|
solverRunStats.maxBacktrackStackDepthReached = Math.max(
|
||||||
|
solverRunStats.maxBacktrackStackDepthReached,
|
||||||
|
backtrackStack.length
|
||||||
|
);
|
||||||
|
solverMemoryStats.backtrackStackPeakBytes = Math.max(
|
||||||
|
solverMemoryStats.backtrackStackPeakBytes,
|
||||||
|
backtrackStack.length * solverMemoryStats.estimatedGridSnapshotBytes
|
||||||
|
);
|
||||||
|
self.postMessage({
|
||||||
|
type: 'STATS_UPDATE',
|
||||||
|
payload: {
|
||||||
|
wfcRun: {
|
||||||
|
maxBacktrackStackDepthReached:
|
||||||
|
solverRunStats.maxBacktrackStackDepthReached,
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
backtrackStackPeakBytes:
|
||||||
|
solverMemoryStats.backtrackStackPeakBytes,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.postMessage({
|
||||||
|
type: 'STATUS_UPDATE',
|
||||||
|
payload: {
|
||||||
|
message: `Backtracked. Retrying option ${
|
||||||
|
nextOptionIndexToTryInList + 1
|
||||||
|
}/${optionsAtCell.length} at (${cc},${cr}). Stack: ${
|
||||||
|
backtrackStack.length
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
solverPhase = 'propagate';
|
||||||
|
backtrackedSuccessfully = true;
|
||||||
|
|
||||||
|
const allTilesUpdate = [];
|
||||||
|
for (let r_idx = 0; r_idx < pRows; r_idx++) {
|
||||||
|
for (let c_idx = 0; c_idx < pCols; c_idx++) {
|
||||||
|
if (grid[r_idx] && grid[r_idx][c_idx]) {
|
||||||
|
allTilesUpdate.push({
|
||||||
|
c: c_idx,
|
||||||
|
r: r_idx,
|
||||||
|
cellOptions: [...grid[r_idx][c_idx]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allTilesUpdate.length > 0) {
|
||||||
|
self.postMessage({
|
||||||
|
type: 'TILE_UPDATE',
|
||||||
|
payload: { updates: allTilesUpdate },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break; // Exit backtrack loop
|
||||||
}
|
}
|
||||||
if (allTilesUpdate.length > 0) {
|
|
||||||
self.postMessage({
|
|
||||||
type: 'TILE_UPDATE',
|
|
||||||
payload: { updates: allTilesUpdate },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break; // Exit backtrack loop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!backtrackedSuccessfully) {
|
if (!backtrackedSuccessfully) {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
type: 'REQUEST_RESTART_FROM_SOLVER',
|
type: 'REQUEST_RESTART_FROM_SOLVER',
|
||||||
@ -493,14 +486,18 @@ function runStepInWorker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allCollapsed()) {
|
if (allCollapsed()) {
|
||||||
solverPhase = 'stop';
|
solverPhase = 'stop';
|
||||||
|
// Add the final grid to the payload. This is the definitive final state.
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
type: 'WFC_COMPLETE',
|
type: 'WFC_COMPLETE',
|
||||||
payload: { finalStats: { wfcRun: solverRunStats } },
|
payload: {
|
||||||
|
finalStats: { wfcRun: solverRunStats },
|
||||||
|
finalGrid: grid
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxTriesInWorker-- <= 0 && solverPhase !== 'propagate') {
|
if (maxTriesInWorker-- <= 0 && solverPhase !== 'propagate') {
|
||||||
self.postMessage({
|
self.postMessage({
|
||||||
@ -638,4 +635,4 @@ self.onerror = function (message, source, lineno, colno, error) {
|
|||||||
});
|
});
|
||||||
solverPhase = 'stop'; // Stop processing on unhandled error
|
solverPhase = 'stop'; // Stop processing on unhandled error
|
||||||
return true; // Prevents default error handling
|
return true; // Prevents default error handling
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user