89044a18ec
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import { Grid } from "./Grid";
|
|
|
|
/**
|
|
* Loads an image from a URL.
|
|
*/
|
|
function loadImage(url: string): Promise<HTMLImageElement> {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
img.crossOrigin = "anonymous"; // Enable CORS just in case
|
|
img.onload = () => resolve(img);
|
|
img.onerror = (err) =>
|
|
reject(new Error(`Failed to load image at ${url}: ${err}`));
|
|
img.src = url;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Loads a B&W image and scales it to gridWidth x gridHeight.
|
|
* Higher dimensions = higher accuracy but slower pathfinding.
|
|
* Lower dimensions = extremely fast pathfinding.
|
|
*
|
|
* Walkable roads should be white (or light gray). Non-walkable areas should be black.
|
|
*
|
|
* @param imageUrl The path or URL of the B&W navigation mask.
|
|
* @param gridWidth The target width of our A* pathfinding grid.
|
|
* @param gridHeight The target height of our A* pathfinding grid.
|
|
* @param threshold Brightness threshold (0-255) above which a pixel is considered walkable (default: 128).
|
|
*/
|
|
export async function createGridFromImage(
|
|
imageUrl: string,
|
|
gridWidth: number,
|
|
gridHeight: number,
|
|
threshold: number = 128,
|
|
): Promise<Grid> {
|
|
const img = await loadImage(imageUrl);
|
|
|
|
// Create an offscreen canvas to scale and analyze the image
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = gridWidth;
|
|
canvas.height = gridHeight;
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
if (!ctx) {
|
|
throw new Error("Could not get 2D context for offscreen canvas");
|
|
}
|
|
|
|
// Draw and scale the image onto the canvas
|
|
ctx.drawImage(img, 0, 0, gridWidth, gridHeight);
|
|
|
|
// Retrieve pixel data
|
|
const imgData = ctx.getImageData(0, 0, gridWidth, gridHeight);
|
|
const data = imgData.data;
|
|
|
|
// Initialize a 2D boolean matrix representing the walkable grid
|
|
const walkableMatrix: boolean[][] = [];
|
|
|
|
for (let y = 0; y < gridHeight; y++) {
|
|
const row: boolean[] = [];
|
|
for (let x = 0; x < gridWidth; x++) {
|
|
// Each pixel has 4 channels: R, G, B, A
|
|
const index = (y * gridWidth + x) * 4;
|
|
const r = data[index] ?? 0;
|
|
const g = data[index + 1] ?? 0;
|
|
const b = data[index + 2] ?? 0;
|
|
|
|
// Calculate brightness (standard grayscale weighting)
|
|
const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
|
|
// If bright enough, it is a road (walkable)
|
|
row.push(brightness >= threshold);
|
|
}
|
|
walkableMatrix.push(row);
|
|
}
|
|
|
|
return new Grid(walkableMatrix);
|
|
}
|