fix: avoid paths corssing each other

This commit is contained in:
2026-04-13 12:56:39 +08:00
parent 17dca6303c
commit fe361dc877
2 changed files with 128 additions and 32 deletions
@@ -127,6 +127,12 @@ function pickLayerNodeType(_layerIndex: number, rng: RNG): MapNodeType {
* Constraints:
* - Each source node gets 12 edges to target nodes
* - Every target node has at least one incoming edge (no dead ends)
* - Nodes only connect to nearby nodes (by index) to avoid crossing paths
*
* Strategy to avoid crossings:
* - Partition target nodes among source nodes (no overlap)
* - Each source can only connect to targets in its assigned partition
* - This guarantees no crossings by construction
*/
function generateLayerEdges(
sourceIds: string[],
@@ -139,47 +145,71 @@ function generateLayerEdges(
for (const id of sourceIds) sourceBranches.set(id, 0);
for (const id of targetIds) targetIncoming.set(id, 0);
// --- Pass 1: give each source 12 targets, prioritising uncovered targets ---
const pickRandom = (arr: string[]): string => arr[rng.nextInt(arr.length)];
// Partition targets among sources (no overlap)
// Each source gets a contiguous range of targets
const getTargetRange = (srcIndex: number): { start: number; end: number } => {
if (sourceIds.length === 1) {
return { start: 0, end: targetIds.length };
}
// Calculate proportional boundaries
const start = Math.floor((srcIndex * targetIds.length) / sourceIds.length);
const end = Math.floor(((srcIndex + 1) * targetIds.length) / sourceIds.length);
return { start, end };
};
// --- Pass 1: give each source 12 targets within its partition ---
const uncovered = new Set(targetIds);
for (const srcId of sourceIds) {
const branches = rng.nextInt(2) + 1; // 1 or 2
for (let s = 0; s < sourceIds.length; s++) {
const srcId = sourceIds[s];
const range = getTargetRange(s);
const availableInPartition = [];
for (let b = 0; b < branches; b++) {
if (uncovered.size > 0) {
// Pick a random uncovered target
const arr = Array.from(uncovered);
const idx = rng.nextInt(arr.length);
const tgtId = arr[idx];
nodes.get(srcId)!.childIds.push(tgtId);
sourceBranches.set(srcId, sourceBranches.get(srcId)! + 1);
targetIncoming.set(tgtId, targetIncoming.get(tgtId)! + 1);
uncovered.delete(tgtId);
} else if (sourceBranches.get(srcId)! < 2) {
// All targets covered; pick any random target
const tgtId = targetIds[rng.nextInt(targetIds.length)];
nodes.get(srcId)!.childIds.push(tgtId);
sourceBranches.set(srcId, sourceBranches.get(srcId)! + 1);
targetIncoming.set(tgtId, targetIncoming.get(tgtId)! + 1);
}
// Collect available targets in this partition
for (let t = range.start; t < range.end; t++) {
availableInPartition.push(targetIds[t]);
}
// Decide branches (1 or 2), but limited by available targets
const maxBranches = Math.min(2, availableInPartition.length);
if (maxBranches === 0) continue;
const branches = rng.nextInt(maxBranches) + 1;
// Shuffle and pick
const shuffled = [...availableInPartition].sort(() => rng.next() - 0.5);
const selected = shuffled.slice(0, branches);
for (const tgtId of selected) {
nodes.get(srcId)!.childIds.push(tgtId);
sourceBranches.set(srcId, sourceBranches.get(srcId)! + 1);
targetIncoming.set(tgtId, targetIncoming.get(tgtId)! + 1);
uncovered.delete(tgtId);
}
}
// --- Pass 2: cover any remaining uncovered targets ---
// Since partitions don't overlap, we must assign to the owning source
for (const tgtId of uncovered) {
// Find a source that still has room (< 2 branches)
const available = sourceIds.filter(id => sourceBranches.get(id)! < 2);
if (available.length > 0) {
const srcId = available[rng.nextInt(available.length)];
nodes.get(srcId)!.childIds.push(tgtId);
sourceBranches.set(srcId, sourceBranches.get(srcId)! + 1);
targetIncoming.set(tgtId, targetIncoming.get(tgtId)! + 1);
} else {
// All sources are at 2 branches; force-add to a random source
const srcId = sourceIds[rng.nextInt(sourceIds.length)];
nodes.get(srcId)!.childIds.push(tgtId);
targetIncoming.set(tgtId, targetIncoming.get(tgtId)! + 1);
const tgtIndex = targetIds.indexOf(tgtId);
// Find which source partition this target belongs to
let owningSource = 0;
for (let s = 0; s < sourceIds.length; s++) {
const range = getTargetRange(s);
if (tgtIndex >= range.start && tgtIndex < range.end) {
owningSource = s;
break;
}
}
const srcId = sourceIds[owningSource];
nodes.get(srcId)!.childIds.push(tgtId);
sourceBranches.set(srcId, sourceBranches.get(srcId)! + 1);
targetIncoming.set(tgtId, targetIncoming.get(tgtId)! + 1);
}
}