spritesheet-generator/src/application/spriteOperations.ts
2025-04-05 13:50:07 +02:00

205 lines
5.6 KiB
TypeScript

// spriteOperations.ts
import { type Sprite } from '@/application/types';
import { sprites, cellSize, canvas, ctx, columns } from '@/application/state';
import { logger, getSpriteOffset } from '@/application/utilities';
import { updateCanvasSize, renderSpritesheetPreview } from '@/application/canvasOperations';
/**
* Add new sprites to the spritesheet
*/
export function addSprites(newSprites: Sprite[]) {
if (newSprites.length === 0) {
logger.warn('Attempted to add empty sprites array');
return;
}
try {
// Validate sprites before adding them
const validSprites = newSprites.filter(sprite => {
if (!sprite.img || sprite.width <= 0 || sprite.height <= 0) {
logger.error('Invalid sprite detected', sprite);
return false;
}
return true;
});
if (validSprites.length === 0) {
logger.error('No valid sprites to add');
return;
}
sprites.value.push(...validSprites);
sprites.value.sort((a, b) => a.uploadOrder - b.uploadOrder);
// Update cell size before arranging sprites
updateCellSize();
autoArrangeSprites();
} catch (error) {
logger.error('Error adding sprites:', error);
}
}
/**
* Update the cell size based on the largest sprite dimensions
*/
export function updateCellSize() {
if (sprites.value.length === 0) {
return;
}
try {
let maxWidth = 0;
let maxHeight = 0;
// Find the maximum dimensions across all sprites
sprites.value.forEach(sprite => {
if (!sprite.img || sprite.width <= 0 || sprite.height <= 0) {
logger.warn('Sprite with invalid dimensions detected', sprite);
return;
}
maxWidth = Math.max(maxWidth, sprite.width);
maxHeight = Math.max(maxHeight, sprite.height);
});
if (maxWidth === 0 || maxHeight === 0) {
logger.error('Failed to calculate valid cell size');
return;
}
// Add a small buffer to ensure sprites fit completely (optional)
const buffer = 0; // Increase if you want padding between sprites
cellSize.width = maxWidth + buffer;
cellSize.height = maxHeight + buffer;
// Ensure all sprites are within their cell bounds after resize
sprites.value.forEach((sprite, index) => {
const column = index % columns.value;
const row = Math.floor(index / columns.value);
// Calculate base position for the sprite's cell
const cellX = column * cellSize.width;
const cellY = row * cellSize.height;
// Center the sprite within its cell if smaller than cell size
const offsetX = Math.floor((cellSize.width - sprite.width) / 2);
const offsetY = Math.floor((cellSize.height - sprite.height) / 2);
sprite.x = cellX + offsetX;
sprite.y = cellY + offsetY;
});
updateCanvasSize();
renderSpritesheetPreview();
} catch (error) {
logger.error('Error updating cell size:', error);
}
}
/**
* Automatically arrange sprites in a grid
*/
export function autoArrangeSprites() {
if (sprites.value.length === 0) {
return;
}
try {
if (cellSize.width <= 0 || cellSize.height <= 0) {
logger.error('Invalid cell size for auto-arranging', cellSize);
return;
}
// First update the canvas size to ensure it's large enough
updateCanvasSize();
// Then position each sprite in its grid cell
sprites.value.forEach((sprite, index) => {
const column = index % columns.value;
const row = Math.floor(index / columns.value);
sprite.x = column * cellSize.width;
sprite.y = row * cellSize.height;
});
// Check if the canvas is ready before attempting to render
if (!ctx.value || !canvas.value) {
logger.warn('Canvas or context not available for rendering after auto-arrange');
return;
}
renderSpritesheetPreview();
} catch (error) {
logger.error('Error auto-arranging sprites:', error);
}
}
/**
* Highlight a specific sprite by its ID
*/
export function highlightSprite(spriteId: string) {
if (!ctx.value || !canvas.value) return;
const sprite = sprites.value.find(s => s.id === spriteId);
if (!sprite) return;
// Calculate the cell coordinates
const cellX = Math.floor(sprite.x / cellSize.width);
const cellY = Math.floor(sprite.y / cellSize.height);
// Briefly flash the cell
ctx.value.save();
ctx.value.fillStyle = 'rgba(0, 150, 255, 0.3)';
ctx.value.fillRect(cellX * cellSize.width, cellY * cellSize.height, cellSize.width, cellSize.height);
ctx.value.restore();
// Reset after a short delay
setTimeout(() => {
renderSpritesheetPreview();
}, 500);
}
/**
* Clear all sprites and reset the canvas
*/
export function clearAllSprites(animation: any) {
if (!canvas.value || !ctx.value) return;
sprites.value = [];
canvas.value.width = 400;
canvas.value.height = 300;
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
if (animation.canvas && animation.ctx) {
animation.canvas.width = 200;
animation.canvas.height = 200;
animation.ctx.clearRect(0, 0, animation.canvas.width, animation.canvas.height);
}
animation.currentFrame = 0;
}
/**
* Apply frame-specific offsets to the main sprite positions
*/
export function applyOffsetsToMainView(currentSpriteOffset: any) {
sprites.value.forEach((sprite, index) => {
const frameOffset = getSpriteOffset(index);
if (frameOffset.x !== 0 || frameOffset.y !== 0) {
// Update the sprite's position to include the offset
sprite.x += frameOffset.x;
sprite.y += frameOffset.y;
// Reset the offset
frameOffset.x = 0;
frameOffset.y = 0;
}
});
// Reset current offset
currentSpriteOffset.x = 0;
currentSpriteOffset.y = 0;
// Re-render the main view
renderSpritesheetPreview();
}