Initial commit

This commit is contained in:
2026-07-13 09:25:40 +07:00
parent c08ff54676
commit bccfb156d7
1938 changed files with 641646 additions and 0 deletions

View File

@@ -0,0 +1,340 @@
/**
* MapLocalization: canvas drawing + view/events (gộp từ mapCanvas.js và mapLocalization.js).
* Dùng ElementReference từ Blazor, không dùng element ID.
*/
// ==================== Canvas: occupancy grid ====================
function drawCell(pixels, canvasWidth, canvasHeight, x, y, r, g, b, a) {
if (x >= 0 && x < canvasWidth && y >= 0 && y < canvasHeight) {
const pixelIndex = (y * canvasWidth + x) * 4;
pixels[pixelIndex] = r;
pixels[pixelIndex + 1] = g;
pixels[pixelIndex + 2] = b;
pixels[pixelIndex + 3] = a;
}
}
/**
* @param {HTMLCanvasElement} canvas
* @param {number} width
* @param {number} height
* @param {Uint8Array|number[]|ArrayBuffer} knownCells - sparse: 5 bytes per cell [index_byte0..3, value]
*/
export function drawOccupancyGrid(canvas, width, height, knownCells) {
if (!canvas || !knownCells || knownCells.length === 0) return;
if (typeof canvas.getContext !== 'function') return;
let cellsArray;
if (knownCells instanceof ArrayBuffer) {
cellsArray = new Uint8Array(knownCells);
} else if (knownCells instanceof Uint8Array) {
cellsArray = knownCells;
} else if (Array.isArray(knownCells)) {
cellsArray = new Uint8Array(knownCells);
} else if (typeof knownCells === 'string') {
try {
const binaryString = atob(knownCells);
cellsArray = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) cellsArray[i] = binaryString.charCodeAt(i);
} catch (e) {
return;
}
} else {
return;
}
const canvasWidth = width;
const canvasHeight = height;
const sizeChanged = canvas.width !== canvasWidth || canvas.height !== canvasHeight;
const previousKnownCells = canvas._previousKnownCells || new Set();
const previousWidth = canvas._previousWidth || width;
const previousHeight = canvas._previousHeight || height;
if (sizeChanged || previousWidth !== width || previousHeight !== height) {
canvas.width = canvasWidth;
canvas.height = canvasHeight;
previousKnownCells.clear();
}
const ctx = canvas.getContext('2d');
if (!ctx) return;
if (sizeChanged || previousWidth !== width || previousHeight !== height) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
const unknownA = 0;
const currentKnownCellsSet = new Set();
for (let i = 0; i < cellsArray.length; i += 5) {
const cellIndex = (cellsArray[i] | (cellsArray[i + 1] << 8) | (cellsArray[i + 2] << 16) | (cellsArray[i + 3] << 24)) >>> 0;
currentKnownCellsSet.add(cellIndex);
}
if (!sizeChanged && previousWidth === width && previousHeight === height) {
previousKnownCells.forEach(cellIndex => {
if (!currentKnownCellsSet.has(cellIndex)) {
const gridX = cellIndex % width;
const gridY = Math.floor(cellIndex / width);
const canvasX = gridX;
const canvasY = gridY; // No Y-flip: ROS convention (gridY=0 at bottom) → CSS scale(1,-1) handles display flip
if (canvasX >= 0 && canvasX < width && canvasY >= 0 && canvasY < height) {
drawCell(pixels, canvas.width, canvas.height, canvasX, canvasY, 0, 0, 0, unknownA);
}
}
});
}
for (let i = 0; i < cellsArray.length; i += 5) {
if (i + 4 >= cellsArray.length) break;
const cellIndex = (cellsArray[i] | (cellsArray[i + 1] << 8) | (cellsArray[i + 2] << 16) | (cellsArray[i + 3] << 24)) >>> 0;
const cellValue = cellsArray[i + 4];
if (cellValue < 0 || cellValue > 100) continue;
const gridX = cellIndex % width;
const gridY = Math.floor(cellIndex / width);
const canvasX = gridX;
const canvasY = gridY; // No Y-flip: ROS convention (gridY=0 at bottom) → CSS scale(1,-1) handles display flip
if (canvasX < 0 || canvasX >= width || canvasY < 0 || canvasY >= height) continue;
let intensity;
if (cellValue === 0) {
intensity = 254;
} else if (cellValue >= 1 && cellValue <= 100) {
const v = 254.0 - (cellValue * 254.0 / 100.0);
intensity = Math.max(0, Math.min(254, Math.round(v)));
} else {
continue;
}
drawCell(pixels, canvas.width, canvas.height, canvasX, canvasY, intensity, intensity, intensity, 255);
}
ctx.putImageData(imageData, 0, 0);
canvas._previousKnownCells = currentKnownCellsSet;
canvas._previousWidth = width;
canvas._previousHeight = height;
}
/**
* @param {HTMLCanvasElement} canvas
*/
export function clearCanvas(canvas) {
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas._previousKnownCells = new Set();
canvas._previousWidth = 0;
canvas._previousHeight = 0;
}
/**
* @param {HTMLCanvasElement} canvas
* @param {number} width
* @param {number} height
*/
export function setCanvasSize(canvas, width, height) {
if (!canvas) return;
canvas.width = width;
canvas.height = height;
}
/**
* @param {HTMLElement} element
* @returns {number[]} [width, height]
*/
export function getElementSize(element) {
if (!element) return [800, 600];
try {
if (typeof element.getBoundingClientRect === 'function') {
const rect = element.getBoundingClientRect();
return [rect.width || 0, rect.height || 0];
}
} catch (e) {}
return [800, 600];
}
/**
* @param {HTMLCanvasElement} canvas
* @param {number[][]} points - [[x,y], ...]
* @param {string} color
* @param {number} radius
*/
export function drawPointsOnCanvas(canvas, points, color, radius) {
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
/*ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = color;
for (const point of points) {
if (point.length >= 2) {
ctx.beginPath();
ctx.arc(point[0], point[1], radius, 0, 2 * Math.PI);
ctx.fill();
}
}*/
ctx.clearRect(0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
for (const point of points) {
if (point[0] < 0 || point[0] >= canvas.width || point[1] < 0 || point[1] >= canvas.height) continue;
const index = (Math.floor(point[1]) * canvas.width + Math.floor(point[0])) * 4;
pixels[index] = 255;
pixels[index + 1] = 0;
pixels[index + 2] = 0;
pixels[index + 3] = 255;
}
ctx.putImageData(imageData, 0, 0);
}
// ==================== View / SVG / Rect ====================
/**
* @param {SVGElement} svg
* @param {number} width
* @param {number} height
* @param {number} originX
* @param {number} originY
*/
export function setSvgConfig(svg, width, height, originX, originY) {
if (!svg) return;
svg.setAttribute('width', width.toString());
svg.setAttribute('height', height.toString());
svg.setAttribute('viewBox', `${originX} ${originY} ${width} ${height}`);
}
/**
* @param {SVGElement} svg
* @param {number} width
* @param {number} height
*/
export function setSvgRect(svg, width, height) {
if (!svg) return;
svg.setAttribute('width', width.toString());
svg.setAttribute('height', height.toString());
}
/**
* @param {HTMLCanvasElement} canvas
* @param {number} width
* @param {number} height
*/
export function setCanvasRect(canvas, width, height) {
if (!canvas) return;
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
}
/**
* @param {HTMLElement} element
* @param {number} top
* @param {number} left
* @param {number} [width]
* @param {number} [height]
*/
export function setMapMovement(element, top, left, width = null, height = null) {
if (!element) return;
element.style.top = top + 'px';
element.style.left = left + 'px';
if (width !== null && height !== null) {
element.style.width = width + 'px';
element.style.height = height + 'px';
}
}
/**
* @param {SVGPolylineElement} polylineElement
* @param {string} points - "x1,y1 x2,y2 ..."
*/
export function setPolylinePointsOnly(polylineElement, points) {
if (!polylineElement) return;
polylineElement.setAttribute('points', points || '');
}
// ==================== Events (container, wheel, mouse) ====================
/**
* @param {any} dotnetRef
* @param {HTMLElement} element
* @param {string} funcName
*/
export async function updateContainerRect(dotnetRef, element, funcName) {
if (!element || !dotnetRef) return;
const rect = element.getBoundingClientRect();
await dotnetRef.invokeMethodAsync(funcName, rect.x, rect.y, rect.width, rect.height, rect.top, rect.right, rect.bottom, rect.left);
}
/**
* @param {any} dotnetRef
* @param {HTMLElement} element
* @param {string} funcName
*/
export function registerResizeObserver(dotnetRef, element, funcName) {
if (!element || !dotnetRef) return;
const resizeObserver = new ResizeObserver(async () => {
await updateContainerRect(dotnetRef, element, funcName);
});
resizeObserver.observe(element);
}
/**
* @param {any} dotnetRef
* @param {HTMLElement} element
* @param {string} funcName
*/
export function addMouseWheelEventListener(dotnetRef, element, funcName) {
if (!element || !dotnetRef) return;
element.addEventListener('wheel', async (ev) => {
ev.preventDefault();
ev.stopPropagation();
await dotnetRef.invokeMethodAsync(funcName, ev.deltaY, ev.clientX, ev.clientY);
}, { passive: false });
}
/**
* @param {any} dotnetRef
* @param {HTMLElement} element
* @param {string} funcName
*/
export function addMouseMoveEventListener(dotnetRef, element, funcName) {
if (!element || !dotnetRef) return;
element.addEventListener('mousemove', async (ev) => {
ev.preventDefault();
ev.stopPropagation();
await dotnetRef.invokeMethodAsync(funcName, ev.clientX, ev.clientY, ev.buttons, ev.ctrlKey, ev.movementX, ev.movementY);
});
}
/**
* @param {any} dotnetRef
* @param {HTMLElement} element
* @param {string} funcName
*/
export function addMouseDownEventListener(dotnetRef, element, funcName) {
if (!element || !dotnetRef) return;
element.addEventListener('mousedown', async (ev) => {
ev.preventDefault();
ev.stopPropagation();
await dotnetRef.invokeMethodAsync(funcName, ev.button, ev.altKey, ev.ctrlKey, ev.shiftKey);
});
}
/**
* @param {any} dotnetRef
* @param {HTMLElement} element
* @param {string} funcName
*/
export function addMouseUpEventListener(dotnetRef, element, funcName) {
if (!element || !dotnetRef) return;
element.addEventListener('mouseup', async (ev) => {
ev.preventDefault();
ev.stopPropagation();
await dotnetRef.invokeMethodAsync(funcName, ev.button, ev.altKey, ev.ctrlKey, ev.shiftKey);
});
}