refactor: use mutative for entity signals
This commit is contained in:
+30
-42
@@ -1,13 +1,11 @@
|
||||
import {Entity, EntityAccessor} from "../utils/entity";
|
||||
import {Entity} from "../utils/entity";
|
||||
import {Part} from "./part";
|
||||
import {RNG} from "../utils/rng";
|
||||
|
||||
export type Region = Entity & {
|
||||
// aligning axes of the region, expect a part's position to have a matching number of elements
|
||||
export type Region = {
|
||||
id: string;
|
||||
axes: RegionAxis[];
|
||||
|
||||
// current children; expect no overlapped positions
|
||||
children: EntityAccessor<Part>[];
|
||||
children: Entity<Part>[];
|
||||
}
|
||||
|
||||
export type RegionAxis = {
|
||||
@@ -17,47 +15,35 @@ export type RegionAxis = {
|
||||
align?: 'start' | 'end' | 'center';
|
||||
}
|
||||
|
||||
/**
|
||||
* for each axis, try to remove gaps in positions.
|
||||
* - if min exists and align is start, and there are parts at (for example) min+2 and min+4, then move them to min and min+1
|
||||
* - if max exists and align is end, and there are parts at (for example) max-2 and max-4, then move them to max-1 and max-3
|
||||
* - for center, move parts to the center, possibly creating parts placed at 0.5 positions
|
||||
* - sort children so that they're in ascending order on each axes.
|
||||
* @param region
|
||||
*/
|
||||
export function applyAlign(region: Region){
|
||||
export function applyAlign(region: Entity<Region>) {
|
||||
region.produce(applyAlignCore);
|
||||
}
|
||||
|
||||
function applyAlignCore(region: Region) {
|
||||
if (region.children.length === 0) return;
|
||||
|
||||
// Process each axis independently while preserving spatial relationships
|
||||
for (let axisIndex = 0; axisIndex < region.axes.length; axisIndex++) {
|
||||
const axis = region.axes[axisIndex];
|
||||
if (!axis.align) continue;
|
||||
|
||||
// Collect all unique position values on this axis, preserving original order
|
||||
const positionValues = new Set<number>();
|
||||
for (const accessor of region.children) {
|
||||
positionValues.add(accessor.value.position[axisIndex] ?? 0);
|
||||
for (const child of region.children) {
|
||||
positionValues.add(child.value.position[axisIndex] ?? 0);
|
||||
}
|
||||
|
||||
// Sort position values
|
||||
const sortedPositions = Array.from(positionValues).sort((a, b) => a - b);
|
||||
|
||||
// Create position mapping: old position -> new position
|
||||
const positionMap = new Map<number, number>();
|
||||
|
||||
if (axis.align === 'start' && axis.min !== undefined) {
|
||||
// Compact from min, preserving relative order
|
||||
sortedPositions.forEach((pos, index) => {
|
||||
positionMap.set(pos, axis.min! + index);
|
||||
});
|
||||
} else if (axis.align === 'end' && axis.max !== undefined) {
|
||||
// Compact towards max
|
||||
const count = sortedPositions.length;
|
||||
sortedPositions.forEach((pos, index) => {
|
||||
positionMap.set(pos, axis.max! - (count - 1 - index));
|
||||
});
|
||||
} else if (axis.align === 'center') {
|
||||
// Center alignment
|
||||
const count = sortedPositions.length;
|
||||
const min = axis.min ?? 0;
|
||||
const max = axis.max ?? count - 1;
|
||||
@@ -70,14 +56,14 @@ export function applyAlign(region: Region){
|
||||
});
|
||||
}
|
||||
|
||||
// Apply position mapping to all parts
|
||||
for (const accessor of region.children) {
|
||||
const currentPos = accessor.value.position[axisIndex] ?? 0;
|
||||
accessor.value.position[axisIndex] = positionMap.get(currentPos) ?? currentPos;
|
||||
for (const child of region.children) {
|
||||
child.produce(draft => {
|
||||
const currentPos = draft.position[axisIndex] ?? 0;
|
||||
draft.position[axisIndex] = positionMap.get(currentPos) ?? currentPos;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort children by all axes at the end
|
||||
region.children.sort((a, b) => {
|
||||
for (let i = 0; i < region.axes.length; i++) {
|
||||
const diff = (a.value.position[i] ?? 0) - (b.value.position[i] ?? 0);
|
||||
@@ -87,21 +73,23 @@ export function applyAlign(region: Region){
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* shuffle on each axis. for each axis, try to swap position.
|
||||
* @param region
|
||||
* @param rng
|
||||
*/
|
||||
export function shuffle(region: Region, rng: RNG){
|
||||
export function shuffle(region: Entity<Region>, rng: RNG) {
|
||||
region.produce(region => shuffleCore(region, rng));
|
||||
}
|
||||
|
||||
function shuffleCore(region: Region, rng: RNG){
|
||||
if (region.children.length <= 1) return;
|
||||
|
||||
// Fisher-Yates 洗牌算法
|
||||
const children = [...region.children];
|
||||
for (let i = children.length - 1; i > 0; i--) {
|
||||
const j = rng.nextInt(i + 1);
|
||||
// 交换两个 part 的整个 position 数组
|
||||
const temp = children[i].value.position;
|
||||
children[i].value.position = children[j].value.position;
|
||||
children[j].value.position = temp;
|
||||
const posI = [...children[i].value.position];
|
||||
const posJ = [...children[j].value.position];
|
||||
children[i].produce(draft => {
|
||||
draft.position = posJ;
|
||||
});
|
||||
children[j].produce(draft => {
|
||||
draft.position = posI;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user