import {Part} from "./part"; import {RNG} from "@/utils/rng"; export type Region = { id: string; axes: RegionAxis[]; childIds: string[]; partMap: Record; } export type RegionAxis = { name: string; min?: number; max?: number; align?: 'start' | 'end' | 'center'; } export function createRegion(id: string, axes: RegionAxis[]): Region { return { id, axes, childIds: [], partMap: {}, }; } function buildPartMap(region: Region, parts: Record>) { const map: Record = {}; for (const childId of region.childIds) { const part = parts[childId]; if (part) { map[part.position.join(',')] = childId; } } return map; } export function applyAlign(region: Region, parts: Record>) { if (region.childIds.length === 0) return; for (let axisIndex = 0; axisIndex < region.axes.length; axisIndex++) { const axis = region.axes[axisIndex]; if (!axis.align) continue; const positionValues = new Set(); for (const childId of region.childIds) { const part = parts[childId]; if (part) { positionValues.add(part.position[axisIndex] ?? 0); } } const sortedPositions = Array.from(positionValues).sort((a, b) => a - b); const positionMap = new Map(); if (axis.align === 'start' && axis.min !== undefined) { sortedPositions.forEach((pos, index) => { positionMap.set(pos, axis.min! + index); }); } else if (axis.align === 'end' && axis.max !== undefined) { const count = sortedPositions.length; sortedPositions.forEach((pos, index) => { positionMap.set(pos, axis.max! - (count - 1 - index)); }); } else if (axis.align === 'center') { const count = sortedPositions.length; const min = axis.min ?? 0; const max = axis.max ?? count - 1; const range = max - min; const center = min + range / 2; sortedPositions.forEach((pos, index) => { const offset = index - (count - 1) / 2; positionMap.set(pos, center + offset); }); } for (const childId of region.childIds) { const part = parts[childId]; if (part) { const currentPos = part.position[axisIndex] ?? 0; part.position[axisIndex] = positionMap.get(currentPos) ?? currentPos; } } } region.childIds.sort((aId, bId) => { const a = parts[aId]; const b = parts[bId]; if (!a || !b) return 0; for (let i = 0; i < region.axes.length; i++) { const diff = (a.position[i] ?? 0) - (b.position[i] ?? 0); if (diff !== 0) return diff; } return 0; }); region.partMap = buildPartMap(region, parts); } export function shuffle(region: Region, parts: Record>, rng: RNG){ if (region.childIds.length <= 1) return; const childIds = [...region.childIds]; for (let i = childIds.length - 1; i > 0; i--) { const j = rng.nextInt(i + 1); const partI = parts[childIds[i]]; const partJ = parts[childIds[j]]; if (!partI || !partJ) continue; const posI = [...partI.position]; const posJ = [...partJ.position]; partI.position = posJ; partJ.position = posI; } region.partMap = buildPartMap(region, parts); } export function moveToRegion(part: Part, sourceRegion: Region | null, targetRegion: Region, position?: number[]) { if (sourceRegion && part.regionId === sourceRegion.id) { sourceRegion.childIds = sourceRegion.childIds.filter(id => id !== part.id); delete sourceRegion.partMap[part.position.join(',')]; } targetRegion.childIds.push(part.id); if (position) { part.position = position; } targetRegion.partMap[part.position.join(',')] = part.id; part.regionId = targetRegion.id; } export function moveToRegionAll(parts: Record>, sourceRegion: Region | null, targetRegion: Region, positions?: number[][]) { const partIds = Object.keys(parts); for (let i = 0; i < partIds.length; i++) { const part = parts[partIds[i]]; if (sourceRegion && part.regionId === sourceRegion.id) { sourceRegion.childIds = sourceRegion.childIds.filter(id => id !== part.id); delete sourceRegion.partMap[part.position.join(',')]; } targetRegion.childIds.push(part.id); if (positions && positions[i]) { part.position = positions[i]; } targetRegion.partMap[part.position.join(',')] = part.id; part.regionId = targetRegion.id; } } export function removeFromRegion(part: Part, region: Region) { region.childIds = region.childIds.filter(id => id !== part.id); delete region.partMap[part.position.join(',')]; }