110 lines
3.4 KiB
TypeScript
110 lines
3.4 KiB
TypeScript
import config from '@/application/config'
|
|
import type { Tile } from '@/application/types'
|
|
import type { TileAnalysisResult, TileWorkerMessage } from '@/types/tileTypes'
|
|
import { ref } from 'vue'
|
|
|
|
// Constants for image processing
|
|
const DOWNSCALE_WIDTH = 16
|
|
const DOWNSCALE_HEIGHT = 8
|
|
const COLOR_SIMILARITY_THRESHOLD = 30
|
|
const EDGE_SIMILARITY_THRESHOLD = 20
|
|
const BATCH_SIZE = 8
|
|
|
|
export function useTileProcessingComposable() {
|
|
const tileAnalysisCache = ref<Map<string, { color: { r: number; g: number; b: number }; edge: number; namePrefix: string }>>(new Map())
|
|
const processingQueue = ref<Tile[]>([])
|
|
let isProcessing = false
|
|
|
|
const NUM_WORKERS = 4
|
|
const workers = Array.from({ length: NUM_WORKERS }, () => new Worker(new URL('@/workers/tileAnalyzerWorker.ts', import.meta.url), { type: 'module' }))
|
|
let currentWorker = 0
|
|
|
|
// Modify worker message handling
|
|
workers.forEach((worker) => {
|
|
worker.onmessage = (e: MessageEvent<TileAnalysisResult>) => {
|
|
const { tileId, color, edge, namePrefix } = e.data
|
|
tileAnalysisCache.value.set(tileId, { color, edge, namePrefix })
|
|
isProcessing = false
|
|
processBatch()
|
|
}
|
|
})
|
|
|
|
async function processTileAsync(tile: Tile, worker: Worker): Promise<void> {
|
|
if (tileAnalysisCache.value.has(tile.id)) return
|
|
|
|
return new Promise((resolve) => {
|
|
const img = new Image()
|
|
img.crossOrigin = 'Anonymous'
|
|
img.onload = () => {
|
|
const canvas = document.createElement('canvas')
|
|
const ctx = canvas.getContext('2d')
|
|
if (!ctx) {
|
|
resolve()
|
|
return
|
|
}
|
|
|
|
canvas.width = DOWNSCALE_WIDTH
|
|
canvas.height = DOWNSCALE_HEIGHT
|
|
ctx.drawImage(img, 0, 0, DOWNSCALE_WIDTH, DOWNSCALE_HEIGHT)
|
|
|
|
const imageData = ctx.getImageData(0, 0, DOWNSCALE_WIDTH, DOWNSCALE_HEIGHT)
|
|
const message: TileWorkerMessage = {
|
|
imageData,
|
|
tileId: tile.id,
|
|
tileName: tile.name
|
|
}
|
|
worker.postMessage(message)
|
|
resolve()
|
|
}
|
|
img.onerror = () => resolve()
|
|
img.src = `${config.server_endpoint}/textures/tiles/${tile.id}.png`
|
|
})
|
|
}
|
|
|
|
function processBatch() {
|
|
if (isProcessing || processingQueue.value.length === 0) return
|
|
isProcessing = true
|
|
|
|
const batch = processingQueue.value.splice(0, BATCH_SIZE)
|
|
Promise.all(
|
|
batch.map((tile) => {
|
|
currentWorker = (currentWorker + 1) % NUM_WORKERS
|
|
return processTileAsync(tile, workers[currentWorker])
|
|
})
|
|
).then(() => {
|
|
isProcessing = false
|
|
if (processingQueue.value.length > 0) {
|
|
setTimeout(processBatch, 0)
|
|
}
|
|
})
|
|
}
|
|
|
|
function processTile(tile: Tile) {
|
|
if (!processingQueue.value.includes(tile)) {
|
|
processingQueue.value.push(tile)
|
|
processBatch()
|
|
}
|
|
}
|
|
|
|
function areTilesRelated(tile1: Tile, tile2: Tile): boolean {
|
|
const data1 = tileAnalysisCache.value.get(tile1.id)
|
|
const data2 = tileAnalysisCache.value.get(tile2.id)
|
|
|
|
if (!data1 || !data2) return false
|
|
|
|
const colorDifference = Math.sqrt(Math.pow(data1.color.r - data2.color.r, 2) + Math.pow(data1.color.g - data2.color.g, 2) + Math.pow(data1.color.b - data2.color.b, 2))
|
|
|
|
return colorDifference <= COLOR_SIMILARITY_THRESHOLD && Math.abs(data1.edge - data2.edge) <= EDGE_SIMILARITY_THRESHOLD && data1.namePrefix === data2.namePrefix
|
|
}
|
|
|
|
function cleanup() {
|
|
workers.forEach((worker) => worker.terminate())
|
|
}
|
|
|
|
return {
|
|
processTile,
|
|
areTilesRelated,
|
|
cleanup
|
|
}
|
|
}
|