2022-02-01 01:14:21 +01:00
|
|
|
package com.simibubi.create.content.logistics.trains;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
2022-02-11 00:20:58 +01:00
|
|
|
import java.util.Collection;
|
2022-02-01 01:14:21 +01:00
|
|
|
import java.util.HashSet;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
import com.simibubi.create.Create;
|
|
|
|
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
|
2022-03-08 03:51:03 +01:00
|
|
|
import com.simibubi.create.content.logistics.trains.management.edgePoint.signal.SignalPropagator;
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
import net.minecraft.core.BlockPos;
|
|
|
|
import net.minecraft.util.Mth;
|
|
|
|
import net.minecraft.world.level.LevelAccessor;
|
|
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
|
|
import net.minecraft.world.phys.Vec3;
|
|
|
|
|
|
|
|
public class TrackPropagator {
|
|
|
|
|
|
|
|
static class FrontierEntry {
|
|
|
|
DiscoveredLocation prevNode;
|
|
|
|
DiscoveredLocation currentNode;
|
|
|
|
DiscoveredLocation parentNode;
|
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
public FrontierEntry(DiscoveredLocation parent, DiscoveredLocation previousNode, DiscoveredLocation location) {
|
2022-02-01 01:14:21 +01:00
|
|
|
parentNode = parent;
|
|
|
|
prevNode = previousNode;
|
|
|
|
currentNode = location;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void onRailRemoved(LevelAccessor reader, BlockPos pos, BlockState state) {
|
2022-11-10 01:52:22 +01:00
|
|
|
if (!(state.getBlock() instanceof ITrackBlock track))
|
2022-02-11 00:20:58 +01:00
|
|
|
return;
|
|
|
|
|
|
|
|
Collection<DiscoveredLocation> ends = track.getConnected(reader, pos, state, false, null);
|
2022-02-01 01:14:21 +01:00
|
|
|
GlobalRailwayManager manager = Create.RAILWAYS;
|
|
|
|
TrackGraphSync sync = manager.sync;
|
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
// 1. Remove any nodes this rail was part of
|
2022-02-16 03:14:31 +01:00
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
for (DiscoveredLocation removedLocation : ends) {
|
2022-02-28 22:52:14 +01:00
|
|
|
List<TrackGraph> intersecting = manager.getGraphs(reader, removedLocation);
|
|
|
|
for (TrackGraph foundGraph : intersecting) {
|
|
|
|
TrackNode removedNode = foundGraph.locateNode(removedLocation);
|
|
|
|
if (removedNode == null)
|
|
|
|
continue;
|
2022-02-11 00:20:58 +01:00
|
|
|
foundGraph.removeNode(reader, removedLocation);
|
|
|
|
sync.nodeRemoved(foundGraph, removedNode);
|
2022-02-28 22:52:14 +01:00
|
|
|
if (!foundGraph.isEmpty())
|
|
|
|
continue;
|
2022-04-25 17:53:45 +02:00
|
|
|
manager.removeGraphAndGroup(foundGraph);
|
2022-02-28 22:52:14 +01:00
|
|
|
sync.graphRemoved(foundGraph);
|
2022-02-01 01:14:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
Set<BlockPos> positionsToUpdate = new HashSet<>();
|
|
|
|
for (DiscoveredLocation removedEnd : ends)
|
|
|
|
positionsToUpdate.addAll(removedEnd.allAdjacent());
|
|
|
|
|
|
|
|
// 2. Re-run railAdded for any track that was disconnected from this track
|
2022-02-16 03:14:31 +01:00
|
|
|
|
2022-02-01 01:14:21 +01:00
|
|
|
Set<TrackGraph> toUpdate = new HashSet<>();
|
2022-02-11 00:20:58 +01:00
|
|
|
for (BlockPos blockPos : positionsToUpdate)
|
|
|
|
if (!blockPos.equals(pos)) {
|
|
|
|
TrackGraph onRailAdded = onRailAdded(reader, blockPos, reader.getBlockState(blockPos));
|
|
|
|
if (onRailAdded != null)
|
|
|
|
toUpdate.add(onRailAdded);
|
|
|
|
}
|
2022-02-16 03:14:31 +01:00
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
// 3. Ensure any affected graph gets checked for segmentation
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
for (TrackGraph railGraph : toUpdate)
|
2022-05-15 21:19:02 +02:00
|
|
|
manager.updateSplitGraph(reader, railGraph);
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
manager.markTracksDirty();
|
|
|
|
}
|
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
public static TrackGraph onRailAdded(LevelAccessor reader, BlockPos pos, BlockState state) {
|
2022-05-04 01:05:03 +02:00
|
|
|
if (!(state.getBlock()instanceof ITrackBlock track))
|
2022-02-11 00:20:58 +01:00
|
|
|
return null;
|
|
|
|
|
2022-02-01 01:14:21 +01:00
|
|
|
// 1. Remove all immediately reachable node locations
|
|
|
|
|
|
|
|
GlobalRailwayManager manager = Create.RAILWAYS;
|
|
|
|
TrackGraphSync sync = manager.sync;
|
|
|
|
List<FrontierEntry> frontier = new ArrayList<>();
|
|
|
|
Set<DiscoveredLocation> visited = new HashSet<>();
|
|
|
|
Set<TrackGraph> connectedGraphs = new HashSet<>();
|
2022-02-11 00:20:58 +01:00
|
|
|
addInitialEndsOf(reader, pos, state, track, frontier, false);
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
int emergencyExit = 1000;
|
|
|
|
while (!frontier.isEmpty()) {
|
|
|
|
if (emergencyExit-- == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
FrontierEntry entry = frontier.remove(0);
|
2022-02-28 22:52:14 +01:00
|
|
|
List<TrackGraph> intersecting = manager.getGraphs(reader, entry.currentNode);
|
|
|
|
for (TrackGraph graph : intersecting) {
|
2022-02-01 01:14:21 +01:00
|
|
|
TrackNode node = graph.locateNode(entry.currentNode);
|
2022-02-02 01:21:28 +01:00
|
|
|
graph.removeNode(reader, entry.currentNode);
|
2022-02-01 01:14:21 +01:00
|
|
|
sync.nodeRemoved(graph, node);
|
|
|
|
connectedGraphs.add(graph);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-02-28 22:52:14 +01:00
|
|
|
if (!intersecting.isEmpty())
|
|
|
|
continue;
|
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
Collection<DiscoveredLocation> ends = ITrackBlock.walkConnectedTracks(reader, entry.currentNode, false);
|
|
|
|
if (entry.prevNode != null)
|
|
|
|
ends.remove(entry.prevNode);
|
2022-02-01 01:14:21 +01:00
|
|
|
continueSearch(frontier, visited, entry, ends);
|
|
|
|
}
|
|
|
|
|
|
|
|
frontier.clear();
|
|
|
|
visited.clear();
|
|
|
|
TrackGraph graph = null;
|
|
|
|
|
|
|
|
// Remove empty graphs
|
|
|
|
for (Iterator<TrackGraph> iterator = connectedGraphs.iterator(); iterator.hasNext();) {
|
|
|
|
TrackGraph railGraph = iterator.next();
|
|
|
|
if (!railGraph.isEmpty() || connectedGraphs.size() == 1)
|
|
|
|
continue;
|
2022-04-25 17:53:45 +02:00
|
|
|
manager.removeGraphAndGroup(railGraph);
|
2022-02-01 01:14:21 +01:00
|
|
|
sync.graphRemoved(railGraph);
|
|
|
|
iterator.remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Merge graphs if more than 1
|
|
|
|
if (connectedGraphs.size() > 1) {
|
|
|
|
for (TrackGraph other : connectedGraphs)
|
|
|
|
if (graph == null)
|
|
|
|
graph = other;
|
|
|
|
else {
|
|
|
|
other.transferAll(graph);
|
2022-04-25 17:53:45 +02:00
|
|
|
manager.removeGraphAndGroup(other);
|
2022-02-01 01:14:21 +01:00
|
|
|
sync.graphRemoved(other);
|
|
|
|
}
|
|
|
|
} else if (connectedGraphs.size() == 1) {
|
|
|
|
graph = connectedGraphs.stream()
|
|
|
|
.findFirst()
|
|
|
|
.get();
|
|
|
|
} else
|
2022-04-25 17:53:45 +02:00
|
|
|
manager.putGraphWithDefaultGroup(graph = new TrackGraph());
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
DiscoveredLocation startNode = null;
|
|
|
|
|
|
|
|
// 2. Find the first graph node candidate nearby
|
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
addInitialEndsOf(reader, pos, state, track, frontier, true);
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
emergencyExit = 1000;
|
|
|
|
while (!frontier.isEmpty()) {
|
|
|
|
if (emergencyExit-- == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
FrontierEntry entry = frontier.remove(0);
|
2022-02-11 00:20:58 +01:00
|
|
|
Collection<DiscoveredLocation> ends = ITrackBlock.walkConnectedTracks(reader, entry.currentNode, false);
|
|
|
|
boolean first = entry.prevNode == null;
|
|
|
|
if (!first)
|
|
|
|
ends.remove(entry.prevNode);
|
|
|
|
if (isValidGraphNodeLocation(entry.currentNode, ends, first)) {
|
2022-02-01 01:14:21 +01:00
|
|
|
startNode = entry.currentNode;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
continueSearch(frontier, visited, entry, ends);
|
|
|
|
}
|
|
|
|
|
|
|
|
frontier.clear();
|
2022-02-16 03:14:31 +01:00
|
|
|
Set<TrackNode> addedNodes = new HashSet<>();
|
2022-02-28 22:52:14 +01:00
|
|
|
graph.createNodeIfAbsent(startNode);
|
2022-02-11 00:20:58 +01:00
|
|
|
frontier.add(new FrontierEntry(startNode, null, startNode));
|
2022-02-01 01:14:21 +01:00
|
|
|
|
|
|
|
// 3. Build up the graph via all connected nodes
|
|
|
|
|
|
|
|
emergencyExit = 1000;
|
|
|
|
while (!frontier.isEmpty()) {
|
|
|
|
if (emergencyExit-- == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
FrontierEntry entry = frontier.remove(0);
|
|
|
|
DiscoveredLocation parentNode = entry.parentNode;
|
2022-02-11 00:20:58 +01:00
|
|
|
Collection<DiscoveredLocation> ends = ITrackBlock.walkConnectedTracks(reader, entry.currentNode, false);
|
|
|
|
boolean first = entry.prevNode == null;
|
|
|
|
if (!first)
|
|
|
|
ends.remove(entry.prevNode);
|
2022-02-01 01:14:21 +01:00
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
if (isValidGraphNodeLocation(entry.currentNode, ends, first) && entry.currentNode != startNode) {
|
2022-02-28 22:52:14 +01:00
|
|
|
boolean nodeIsNew = graph.createNodeIfAbsent(entry.currentNode);
|
2022-04-25 17:53:45 +02:00
|
|
|
graph.connectNodes(reader, parentNode, entry.currentNode, entry.currentNode.getTurn());
|
2022-02-16 03:14:31 +01:00
|
|
|
addedNodes.add(graph.locateNode(entry.currentNode));
|
2022-02-01 01:14:21 +01:00
|
|
|
parentNode = entry.currentNode;
|
|
|
|
if (!nodeIsNew)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
continueSearchWithParent(frontier, entry, parentNode, ends);
|
|
|
|
}
|
|
|
|
|
|
|
|
manager.markTracksDirty();
|
2022-02-16 03:14:31 +01:00
|
|
|
for (TrackNode trackNode : addedNodes)
|
|
|
|
SignalPropagator.notifySignalsOfNewNode(graph, trackNode);
|
2022-02-01 01:14:21 +01:00
|
|
|
return graph;
|
|
|
|
}
|
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
private static void addInitialEndsOf(LevelAccessor reader, BlockPos pos, BlockState state, ITrackBlock track,
|
|
|
|
List<FrontierEntry> frontier, boolean ignoreTurns) {
|
|
|
|
for (DiscoveredLocation initial : track.getConnected(reader, pos, state, ignoreTurns, null)) {
|
|
|
|
frontier.add(new FrontierEntry(null, null, initial));
|
2022-02-01 01:14:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void continueSearch(List<FrontierEntry> frontier, Set<DiscoveredLocation> visited,
|
2022-02-11 00:20:58 +01:00
|
|
|
FrontierEntry entry, Collection<DiscoveredLocation> ends) {
|
|
|
|
for (DiscoveredLocation location : ends)
|
|
|
|
if (visited.add(location))
|
|
|
|
frontier.add(new FrontierEntry(null, entry.currentNode, location));
|
2022-02-01 01:14:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private static void continueSearchWithParent(List<FrontierEntry> frontier, FrontierEntry entry,
|
2022-02-11 00:20:58 +01:00
|
|
|
DiscoveredLocation parentNode, Collection<DiscoveredLocation> ends) {
|
|
|
|
for (DiscoveredLocation location : ends)
|
|
|
|
frontier.add(new FrontierEntry(parentNode, entry.currentNode, location));
|
2022-02-01 01:14:21 +01:00
|
|
|
}
|
|
|
|
|
2022-02-11 00:20:58 +01:00
|
|
|
public static boolean isValidGraphNodeLocation(DiscoveredLocation location, Collection<DiscoveredLocation> next,
|
|
|
|
boolean first) {
|
|
|
|
int size = next.size() - (first ? 1 : 0);
|
|
|
|
if (size != 1)
|
2022-02-01 01:14:21 +01:00
|
|
|
return true;
|
2022-02-11 00:20:58 +01:00
|
|
|
if (location.shouldForceNode())
|
2022-02-01 01:14:21 +01:00
|
|
|
return true;
|
2023-05-09 18:23:47 +02:00
|
|
|
if (location.differentMaterials())
|
|
|
|
return true;
|
2022-02-11 00:20:58 +01:00
|
|
|
if (next.stream()
|
2022-05-15 21:19:02 +02:00
|
|
|
.anyMatch(DiscoveredLocation::shouldForceNode))
|
2022-02-01 01:14:21 +01:00
|
|
|
return true;
|
2023-05-09 18:23:47 +02:00
|
|
|
|
2022-05-04 01:05:03 +02:00
|
|
|
Vec3 direction = location.direction;
|
|
|
|
if (direction != null && next.stream()
|
|
|
|
.anyMatch(dl -> dl.notInLineWith(direction)))
|
|
|
|
return true;
|
|
|
|
|
2022-02-01 01:14:21 +01:00
|
|
|
Vec3 vec = location.getLocation();
|
|
|
|
boolean centeredX = !Mth.equal(vec.x, Math.round(vec.x));
|
|
|
|
boolean centeredZ = !Mth.equal(vec.z, Math.round(vec.z));
|
|
|
|
if (centeredX && !centeredZ)
|
|
|
|
return ((int) Math.round(vec.z)) % 16 == 0;
|
|
|
|
return ((int) Math.round(vec.x)) % 16 == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|