Proper Propa

- Improvements to graph building
- Fixed a number of issues caused by junctions and inconsistent track propagation
- Trains no longer disassemble with reversed carriage spacing
- Trains can no longer jump to perpendicular tracks on an intersection
This commit is contained in:
simibubi 2022-02-11 00:20:58 +01:00
parent 2ca099ce6b
commit 033e7e1a0d
10 changed files with 301 additions and 260 deletions

View file

@ -1,9 +1,18 @@
package com.simibubi.create.content.logistics.trains; package com.simibubi.create.content.logistics.trains;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.core.PartialModel; import com.jozufozu.flywheel.core.PartialModel;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.track.TrackBlock;
import com.simibubi.create.content.logistics.trains.track.TrackShape;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Pair; import com.simibubi.create.foundation.utility.Pair;
@ -27,11 +36,72 @@ public interface ITrackBlock {
public BlockState getBogeyAnchor(BlockGetter world, BlockPos pos, BlockState state); // should be on bogey side public BlockState getBogeyAnchor(BlockGetter world, BlockPos pos, BlockState state); // should be on bogey side
public boolean trackEquals(BlockState state1, BlockState state2); public boolean trackEquals(BlockState state1, BlockState state2);
public default BlockState overlay(BlockGetter world, BlockPos pos, BlockState existing, BlockState placed) { public default BlockState overlay(BlockGetter world, BlockPos pos, BlockState existing, BlockState placed) {
return existing; return existing;
} }
public default double getElevationAtCenter(BlockGetter world, BlockPos pos, BlockState state) {
return isSlope(world, pos, state) ? .5 : 0;
}
public static Collection<DiscoveredLocation> walkConnectedTracks(BlockGetter world, TrackNodeLocation location,
boolean linear) {
List<DiscoveredLocation> list = new ArrayList<>();
for (BlockPos blockPos : location.allAdjacent()) {
BlockState blockState = world.getBlockState(blockPos);
if (blockState.getBlock()instanceof ITrackBlock track)
list.addAll(track.getConnected(world, blockPos, blockState, linear, location));
}
return list;
}
public default Collection<DiscoveredLocation> getConnected(BlockGetter world, BlockPos pos, BlockState state,
boolean linear, @Nullable TrackNodeLocation connectedTo) {
Vec3 center = Vec3.atBottomCenterOf(pos)
.add(0, getElevationAtCenter(world, pos, state), 0);
List<DiscoveredLocation> list = new ArrayList<>();
TrackShape shape = state.getValue(TrackBlock.SHAPE);
getTrackAxes(world, pos, state).forEach(axis -> {
addToListIfConnected(connectedTo, list, (d, b) -> axis.scale(b ? d : -d)
.add(center), b -> shape.getNormal(), null);
});
return list;
}
public static void addToListIfConnected(@Nullable TrackNodeLocation fromEnd, Collection<DiscoveredLocation> list,
BiFunction<Double, Boolean, Vec3> offsetFactory, Function<Boolean, Vec3> normalFactory,
BezierConnection viaTurn) {
DiscoveredLocation firstLocation = new DiscoveredLocation(offsetFactory.apply(0.5d, true)).viaTurn(viaTurn)
.withNormal(normalFactory.apply(true));
DiscoveredLocation secondLocation = new DiscoveredLocation(offsetFactory.apply(0.5d, false)).viaTurn(viaTurn)
.withNormal(normalFactory.apply(false));
boolean skipFirst = false;
boolean skipSecond = false;
if (fromEnd != null) {
boolean equalsFirst = firstLocation.equals(fromEnd);
boolean equalsSecond = secondLocation.equals(fromEnd);
// not reachable from this end
if (!equalsFirst && !equalsSecond)
return;
if (equalsFirst)
skipFirst = true;
if (equalsSecond)
skipSecond = true;
}
if (!skipFirst)
list.add(firstLocation);
if (!skipSecond)
list.add(secondLocation);
}
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public PartialModel prepareStationOverlay(BlockGetter world, BlockPos pos, BlockState state, public PartialModel prepareStationOverlay(BlockGetter world, BlockPos pos, BlockState state,
AxisDirection direction, PoseStack transform); AxisDirection direction, PoseStack transform);

View file

@ -409,11 +409,12 @@ public class TrackGraph {
if (location.distanceTo(camera) > 50) if (location.distanceTo(camera) > 50)
continue; continue;
Vec3 v1 = location.add(0, 3 / 16f, 0); Vec3 yOffset = new Vec3(0, 3 / 16f, 0);
Vec3 v2 = v1.add(node.normal.scale(0.75f)); Vec3 v1 = location.add(yOffset);
Vec3 v2 = v1.add(node.normal.scale(0.125f));
CreateClient.OUTLINER.showLine(Integer.valueOf(node.netId), v1, v2) CreateClient.OUTLINER.showLine(Integer.valueOf(node.netId), v1, v2)
.colored(Color.mixColors(Color.WHITE, color, 1)) .colored(Color.mixColors(Color.WHITE, color, 1))
.lineWidth(1 / 8f); .lineWidth(1 / 4f);
Map<TrackNode, TrackEdge> map = connectionsByNode.get(node); Map<TrackNode, TrackEdge> map = connectionsByNode.get(node);
if (map == null) if (map == null)
@ -428,8 +429,10 @@ public class TrackGraph {
TrackEdge edge = entry.getValue(); TrackEdge edge = entry.getValue();
if (!edge.isTurn()) { if (!edge.isTurn()) {
CreateClient.OUTLINER CreateClient.OUTLINER.showLine(edge, edge.getPosition(node, other, 0)
.showLine(edge, edge.getPosition(node, other, 0), edge.getPosition(node, other, 1)) .add(yOffset),
edge.getPosition(node, other, 1)
.add(yOffset))
.colored(color) .colored(color)
.lineWidth(1 / 16f); .lineWidth(1 / 16f);
continue; continue;
@ -440,7 +443,7 @@ public class TrackGraph {
for (int i = 0; i <= turn.getSegmentCount(); i++) { for (int i = 0; i <= turn.getSegmentCount(); i++) {
Vec3 current = edge.getPosition(node, other, i * 1f / turn.getSegmentCount()); Vec3 current = edge.getPosition(node, other, i * 1f / turn.getSegmentCount());
if (previous != null) if (previous != null)
CreateClient.OUTLINER.showLine(previous, previous, current) CreateClient.OUTLINER.showLine(previous, previous.add(yOffset), current.add(yOffset))
.colored(color) .colored(color)
.lineWidth(1 / 16f); .lineWidth(1 / 16f);
previous = current; previous = current;

View file

@ -1,12 +1,13 @@
package com.simibubi.create.content.logistics.trains; package com.simibubi.create.content.logistics.trains;
import java.util.List; import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.management.GraphLocation; import com.simibubi.create.content.logistics.trains.management.GraphLocation;
import com.simibubi.create.foundation.utility.Couple; import com.simibubi.create.foundation.utility.Couple;
import com.simibubi.create.foundation.utility.Pair;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction.AxisDirection; import net.minecraft.core.Direction.AxisDirection;
@ -24,48 +25,88 @@ public class TrackGraphHelper {
Vec3 axis = targetAxis.scale(targetDirection.getStep()); Vec3 axis = targetAxis.scale(targetDirection.getStep());
double length = axis.length(); double length = axis.length();
List<Pair<BlockPos, DiscoveredLocation>> ends =
TrackPropagator.getEnds(level, pos, trackBlockState, true, null, null);
TrackGraph graph = null; TrackGraph graph = null;
// Case 1: Centre of block lies on a node
TrackNodeLocation location = new TrackNodeLocation(Vec3.atBottomCenterOf(pos)
.add(0, track.getElevationAtCenter(level, pos, trackBlockState), 0));
graph = Create.RAILWAYS.getGraph(level, location);
if (graph != null) {
TrackNode node = graph.locateNode(location);
if (node != null) {
Map<TrackNode, TrackEdge> connectionsFrom = graph.getConnectionsFrom(node);
for (Entry<TrackNode, TrackEdge> entry : connectionsFrom.entrySet()) {
TrackNode backNode = entry.getKey();
Vec3 direction = entry.getValue()
.getDirection(node, backNode, true);
if (direction.scale(length)
.distanceToSqr(axis.scale(-1)) > 1 / 4096f)
continue;
GraphLocation graphLocation = new GraphLocation();
graphLocation.edge = Couple.create(node.getLocation(), backNode.getLocation());
graphLocation.position = 0;
graphLocation.graph = graph;
return graphLocation;
}
}
}
// Case 2: Center of block is between two nodes
Collection<DiscoveredLocation> ends = track.getConnected(level, pos, trackBlockState, true, null);
Vec3 start = Vec3.atBottomCenterOf(pos)
.add(0, track.getElevationAtCenter(level, pos, trackBlockState), 0);
TrackNode frontNode = null; TrackNode frontNode = null;
TrackNode backNode = null; TrackNode backNode = null;
double position = 0; double position = 0;
for (Pair<BlockPos, DiscoveredLocation> pair : ends) { for (DiscoveredLocation current : ends) {
DiscoveredLocation current = pair.getSecond(); Vec3 offset = current.getLocation()
BlockPos currentPos = pair.getFirst(); .subtract(start)
Vec3 offset = Vec3.atLowerCornerOf(currentPos.subtract(pos)); .normalize()
.scale(length);
boolean forward = offset.distanceToSqr(axis.scale(-1)) < 1 / 4096f; boolean forward = offset.distanceToSqr(axis.scale(-1)) < 1 / 4096f;
boolean backwards = offset.distanceToSqr(axis) < 1 / 4096f; boolean backwards = offset.distanceToSqr(axis) < 1 / 4096f;
if (!forward && !backwards) if (!forward && !backwards)
continue; continue;
for (int i = 0; i < 32; i++) { DiscoveredLocation previous = null;
double distance = 0;
for (int i = 0; i < 100 && distance < 32; i++) {
DiscoveredLocation loc = current; DiscoveredLocation loc = current;
List<Pair<BlockPos, DiscoveredLocation>> list =
TrackPropagator.getEnds(level, currentPos, level.getBlockState(currentPos), true, current, null);
if (!list.isEmpty()) {
currentPos = list.get(0)
.getFirst();
current = list.get(0)
.getSecond();
}
if (graph == null) if (graph == null)
graph = Create.RAILWAYS.getGraph(level, loc); graph = Create.RAILWAYS.getGraph(level, loc);
if (graph == null)
if (graph == null || graph.locateNode(loc) == null) {
Collection<DiscoveredLocation> list = ITrackBlock.walkConnectedTracks(level, loc, true);
for (DiscoveredLocation discoveredLocation : list) {
if (discoveredLocation == previous)
continue;
Vec3 diff = discoveredLocation.getLocation()
.subtract(loc.getLocation());
if ((forward ? axis.scale(-1) : axis).distanceToSqr(diff.normalize()
.scale(length)) > 1 / 4096f)
continue;
previous = current;
current = discoveredLocation;
distance += diff.length();
break;
}
continue; continue;
}
TrackNode node = graph.locateNode(loc); TrackNode node = graph.locateNode(loc);
if (node == null)
continue;
if (forward) if (forward)
frontNode = node; frontNode = node;
if (backwards) { if (backwards) {
backNode = node; backNode = node;
position = (i + .5) * length; position = distance + .5;
} }
break; break;
} }

View file

@ -1,5 +1,11 @@
package com.simibubi.create.content.logistics.trains; package com.simibubi.create.content.logistics.trains;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import com.simibubi.create.foundation.utility.Iterate;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
@ -17,7 +23,7 @@ public class TrackNodeLocation extends Vec3i {
public static TrackNodeLocation fromPackedPos(BlockPos bufferPos) { public static TrackNodeLocation fromPackedPos(BlockPos bufferPos) {
return new TrackNodeLocation(bufferPos); return new TrackNodeLocation(bufferPos);
} }
private TrackNodeLocation(BlockPos readBlockPos) { private TrackNodeLocation(BlockPos readBlockPos) {
super(readBlockPos.getX(), readBlockPos.getY(), readBlockPos.getZ()); super(readBlockPos.getX(), readBlockPos.getY(), readBlockPos.getZ());
} }
@ -36,9 +42,21 @@ public class TrackNodeLocation extends Vec3i {
return (this.getY() + this.getZ() * 31) * 31 + this.getX(); return (this.getY() + this.getZ() * 31) * 31 + this.getX();
} }
public Collection<BlockPos> allAdjacent() {
Set<BlockPos> set = new HashSet<>();
Vec3 vec3 = getLocation();
double step = 1 / 8f;
for (int x : Iterate.positiveAndNegative)
for (int y : Iterate.positiveAndNegative)
for (int z : Iterate.positiveAndNegative)
set.add(new BlockPos(vec3.add(x * step, y * step, z * step)));
return set;
}
public static class DiscoveredLocation extends TrackNodeLocation { public static class DiscoveredLocation extends TrackNodeLocation {
BezierConnection turn = null; BezierConnection turn = null;
boolean forceNode = false;
Vec3 normal; Vec3 normal;
public DiscoveredLocation(double p_121865_, double p_121866_, double p_121867_) { public DiscoveredLocation(double p_121865_, double p_121866_, double p_121867_) {
@ -51,6 +69,13 @@ public class TrackNodeLocation extends Vec3i {
public DiscoveredLocation viaTurn(BezierConnection turn) { public DiscoveredLocation viaTurn(BezierConnection turn) {
this.turn = turn; this.turn = turn;
if (turn != null)
forceNode();
return this;
}
public DiscoveredLocation forceNode() {
forceNode = true;
return this; return this;
} }
@ -66,6 +91,10 @@ public class TrackNodeLocation extends Vec3i {
public BezierConnection getTurn() { public BezierConnection getTurn() {
return turn; return turn;
} }
public boolean shouldForceNode() {
return forceNode;
}
} }

View file

@ -1,70 +1,55 @@
package com.simibubi.create.content.logistics.trains; package com.simibubi.create.content.logistics.trains;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.track.TrackBlock;
import com.simibubi.create.content.logistics.trains.track.TrackShape;
import com.simibubi.create.content.logistics.trains.track.TrackTileEntity;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.VecHelper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
public class TrackPropagator { public class TrackPropagator {
static class FrontierEntry { static class FrontierEntry {
BlockPos prevPos;
DiscoveredLocation prevNode; DiscoveredLocation prevNode;
BlockPos currentPos;
DiscoveredLocation currentNode; DiscoveredLocation currentNode;
DiscoveredLocation parentNode; DiscoveredLocation parentNode;
public FrontierEntry(BlockPos previousPos, BlockPos pos, DiscoveredLocation location) { public FrontierEntry(DiscoveredLocation parent, DiscoveredLocation previousNode, DiscoveredLocation location) {
this(null, previousPos, null, pos, location);
}
public FrontierEntry(DiscoveredLocation parent, BlockPos previousPos, DiscoveredLocation previousNode,
BlockPos pos, DiscoveredLocation location) {
parentNode = parent; parentNode = parent;
prevPos = previousPos;
prevNode = previousNode; prevNode = previousNode;
currentPos = pos;
currentNode = location; currentNode = location;
} }
} }
public static void onRailRemoved(LevelAccessor reader, BlockPos pos, BlockState state) { public static void onRailRemoved(LevelAccessor reader, BlockPos pos, BlockState state) {
List<Pair<BlockPos, DiscoveredLocation>> ends = getEnds(reader, pos, state, false, null, null); if (!(state.getBlock()instanceof ITrackBlock track))
TrackGraph foundGraph = null; return;
Collection<DiscoveredLocation> ends = track.getConnected(reader, pos, state, false, null);
GlobalRailwayManager manager = Create.RAILWAYS; GlobalRailwayManager manager = Create.RAILWAYS;
TrackGraphSync sync = manager.sync; TrackGraphSync sync = manager.sync;
TrackGraph foundGraph = null;
for (Pair<BlockPos, DiscoveredLocation> removedEnd : ends) { // 1. Remove any nodes this rail was part of
DiscoveredLocation removedLocation = removedEnd.getSecond();
for (DiscoveredLocation removedLocation : ends) {
if (foundGraph == null) if (foundGraph == null)
foundGraph = manager.getGraph(reader, removedLocation); foundGraph = manager.getGraph(reader, removedLocation);
if (foundGraph != null) { if (foundGraph == null)
TrackNode removedNode = foundGraph.locateNode(removedLocation); continue;
if (removedNode != null) { TrackNode removedNode = foundGraph.locateNode(removedLocation);
foundGraph.removeNode(reader, removedLocation); if (removedNode != null) {
sync.nodeRemoved(foundGraph, removedNode); foundGraph.removeNode(reader, removedLocation);
} sync.nodeRemoved(foundGraph, removedNode);
} }
} }
@ -73,21 +58,21 @@ public class TrackPropagator {
sync.graphRemoved(foundGraph); sync.graphRemoved(foundGraph);
} }
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
Set<TrackGraph> toUpdate = new HashSet<>(); Set<TrackGraph> toUpdate = new HashSet<>();
for (Pair<BlockPos, DiscoveredLocation> removedEnd : ends) { for (BlockPos blockPos : positionsToUpdate)
BlockPos adjPos = removedEnd.getFirst(); if (!blockPos.equals(pos)) {
BlockState adjState = reader.getBlockState(adjPos); TrackGraph onRailAdded = onRailAdded(reader, blockPos, reader.getBlockState(blockPos));
List<Pair<BlockPos, DiscoveredLocation>> adjEnds = if (onRailAdded != null)
getEnds(reader, adjPos, adjState, true, removedEnd.getSecond(), null); toUpdate.add(onRailAdded);
if (adjEnds.isEmpty()) }
continue;
Vec3 filter = adjEnds.get(0) // 3. Ensure any affected graph gets checked for segmentation
.getSecond()
.getLocation()
.subtract(removedEnd.getSecond()
.getLocation());
toUpdate.add(onRailAdded(reader, adjPos, adjState, filter.normalize()));
}
for (TrackGraph railGraph : toUpdate) for (TrackGraph railGraph : toUpdate)
manager.updateSplitGraph(railGraph); manager.updateSplitGraph(railGraph);
@ -95,7 +80,10 @@ public class TrackPropagator {
manager.markTracksDirty(); manager.markTracksDirty();
} }
public static TrackGraph onRailAdded(LevelAccessor reader, BlockPos pos, BlockState state, Vec3 axisFilter) { public static TrackGraph onRailAdded(LevelAccessor reader, BlockPos pos, BlockState state) {
if (!(state.getBlock()instanceof ITrackBlock track))
return null;
// 1. Remove all immediately reachable node locations // 1. Remove all immediately reachable node locations
GlobalRailwayManager manager = Create.RAILWAYS; GlobalRailwayManager manager = Create.RAILWAYS;
@ -103,7 +91,7 @@ public class TrackPropagator {
List<FrontierEntry> frontier = new ArrayList<>(); List<FrontierEntry> frontier = new ArrayList<>();
Set<DiscoveredLocation> visited = new HashSet<>(); Set<DiscoveredLocation> visited = new HashSet<>();
Set<TrackGraph> connectedGraphs = new HashSet<>(); Set<TrackGraph> connectedGraphs = new HashSet<>();
addInitialEndsOf(reader, pos, state, frontier, false, axisFilter); addInitialEndsOf(reader, pos, state, track, frontier, false);
int emergencyExit = 1000; int emergencyExit = 1000;
while (!frontier.isEmpty()) { while (!frontier.isEmpty()) {
@ -111,7 +99,6 @@ public class TrackPropagator {
break; break;
FrontierEntry entry = frontier.remove(0); FrontierEntry entry = frontier.remove(0);
List<Pair<BlockPos, DiscoveredLocation>> ends = findReachableEnds(reader, entry);
TrackGraph graph = manager.getGraph(reader, entry.currentNode); TrackGraph graph = manager.getGraph(reader, entry.currentNode);
if (graph != null) { if (graph != null) {
TrackNode node = graph.locateNode(entry.currentNode); TrackNode node = graph.locateNode(entry.currentNode);
@ -121,6 +108,9 @@ public class TrackPropagator {
continue; continue;
} }
Collection<DiscoveredLocation> ends = ITrackBlock.walkConnectedTracks(reader, entry.currentNode, false);
if (entry.prevNode != null)
ends.remove(entry.prevNode);
continueSearch(frontier, visited, entry, ends); continueSearch(frontier, visited, entry, ends);
} }
@ -157,11 +147,10 @@ public class TrackPropagator {
manager.putGraph(graph = new TrackGraph()); manager.putGraph(graph = new TrackGraph());
DiscoveredLocation startNode = null; DiscoveredLocation startNode = null;
List<BlockPos> startPositions = new ArrayList<>();
// 2. Find the first graph node candidate nearby // 2. Find the first graph node candidate nearby
addInitialEndsOf(reader, pos, state, frontier, true, axisFilter); addInitialEndsOf(reader, pos, state, track, frontier, true);
emergencyExit = 1000; emergencyExit = 1000;
while (!frontier.isEmpty()) { while (!frontier.isEmpty()) {
@ -169,26 +158,12 @@ public class TrackPropagator {
break; break;
FrontierEntry entry = frontier.remove(0); FrontierEntry entry = frontier.remove(0);
Collection<DiscoveredLocation> ends = ITrackBlock.walkConnectedTracks(reader, entry.currentNode, false);
// CreateClient.OUTLINER boolean first = entry.prevNode == null;
// .showAABB(entry.currentNode, new AABB(entry.currentNode.getLocation(), entry.currentNode.getLocation() if (!first)
// .add(0, 2, 0)), 120) ends.remove(entry.prevNode);
// .colored(Color.GREEN) if (isValidGraphNodeLocation(entry.currentNode, ends, first)) {
// .lineWidth(1 / 16f);
// CreateClient.OUTLINER.showAABB(entry.currentPos, new AABB(entry.currentPos).contract(0, 1, 0), 120)
// .colored(0x7777ff)
// .lineWidth(1 / 16f);
// if (entry.prevPos != null) {
// CreateClient.OUTLINER.showAABB(entry.prevPos, new AABB(entry.prevPos).contract(0, 1, 0), 120)
// .colored(0x3333aa)
// .lineWidth(1 / 16f);
// }
List<Pair<BlockPos, DiscoveredLocation>> ends = findReachableEnds(reader, entry);
if (isValidGraphNodeLocation(entry.currentNode, ends)) {
startNode = entry.currentNode; startNode = entry.currentNode;
startPositions.add(entry.prevPos);
startPositions.add(entry.currentPos);
break; break;
} }
@ -199,12 +174,7 @@ public class TrackPropagator {
if (graph.createNode(startNode)) if (graph.createNode(startNode))
sync.nodeAdded(graph, graph.locateNode(startNode)); sync.nodeAdded(graph, graph.locateNode(startNode));
// CreateClient.OUTLINER.showAABB(graph, new AABB(startNode.getLocation(), startNode.getLocation() frontier.add(new FrontierEntry(startNode, null, startNode));
// .add(0, 2, 0)), 20)
// .lineWidth(1 / 32f);
for (BlockPos position : startPositions)
frontier.add(new FrontierEntry(startNode, null, null, position, startNode));
// 3. Build up the graph via all connected nodes // 3. Build up the graph via all connected nodes
@ -215,9 +185,12 @@ public class TrackPropagator {
FrontierEntry entry = frontier.remove(0); FrontierEntry entry = frontier.remove(0);
DiscoveredLocation parentNode = entry.parentNode; DiscoveredLocation parentNode = entry.parentNode;
List<Pair<BlockPos, DiscoveredLocation>> ends = findReachableEnds(reader, entry); 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) && entry.currentNode != startNode) { if (isValidGraphNodeLocation(entry.currentNode, ends, first) && entry.currentNode != startNode) {
boolean nodeIsNew = graph.createNode(entry.currentNode); boolean nodeIsNew = graph.createNode(entry.currentNode);
if (nodeIsNew) if (nodeIsNew)
sync.nodeAdded(graph, graph.locateNode(entry.currentNode)); sync.nodeAdded(graph, graph.locateNode(entry.currentNode));
@ -234,70 +207,35 @@ public class TrackPropagator {
return graph; return graph;
} }
private static void addInitialEndsOf(LevelAccessor reader, BlockPos pos, BlockState state, private static void addInitialEndsOf(LevelAccessor reader, BlockPos pos, BlockState state, ITrackBlock track,
List<FrontierEntry> frontier, boolean ignoreTurns, Vec3 axisFilter) { List<FrontierEntry> frontier, boolean ignoreTurns) {
for (Pair<BlockPos, DiscoveredLocation> initial : getEnds(reader, pos, state, ignoreTurns, null, axisFilter)) for (DiscoveredLocation initial : track.getConnected(reader, pos, state, ignoreTurns, null)) {
frontier.add(new FrontierEntry(initial.getFirst(), pos, initial.getSecond())); frontier.add(new FrontierEntry(null, null, initial));
}
private static List<Pair<BlockPos, DiscoveredLocation>> findReachableEnds(LevelAccessor reader,
FrontierEntry entry) {
BlockState currentState = reader.getBlockState(entry.currentPos);
List<Pair<BlockPos, DiscoveredLocation>> ends = new ArrayList<>();
if (entry.prevNode != null) {
BlockPos prevPos = entry.prevPos;
// PrevPos correction after a turn
if (entry.currentNode.connectedViaTurn()) {
boolean slope = false;
if (currentState.getBlock()instanceof ITrackBlock track)
slope = track.isSlope(reader, entry.currentPos, currentState);
BlockPos offset = new BlockPos(VecHelper.getCenterOf(entry.currentPos)
.subtract(entry.currentNode.getLocation()
.add(0, slope ? 0 : .5f, 0))
.scale(-2));
prevPos = entry.currentPos.offset(offset);
}
for (Pair<BlockPos, DiscoveredLocation> pair : getEnds(reader, prevPos, reader.getBlockState(prevPos),
false, entry.currentNode, null))
if (!pair.getSecond()
.equals(entry.prevNode))
ends.add(pair);
} }
ends.addAll(getEnds(reader, entry.currentPos, currentState, false, entry.currentNode, null));
return ends;
} }
private static void continueSearch(List<FrontierEntry> frontier, Set<DiscoveredLocation> visited, private static void continueSearch(List<FrontierEntry> frontier, Set<DiscoveredLocation> visited,
FrontierEntry entry, List<Pair<BlockPos, DiscoveredLocation>> ends) { FrontierEntry entry, Collection<DiscoveredLocation> ends) {
for (Pair<BlockPos, DiscoveredLocation> pair : ends) for (DiscoveredLocation location : ends)
if (visited.add(pair.getSecond())) if (visited.add(location))
frontier.add( frontier.add(new FrontierEntry(null, entry.currentNode, location));
new FrontierEntry(null, entry.currentPos, entry.currentNode, pair.getFirst(), pair.getSecond()));
} }
private static void continueSearchWithParent(List<FrontierEntry> frontier, FrontierEntry entry, private static void continueSearchWithParent(List<FrontierEntry> frontier, FrontierEntry entry,
DiscoveredLocation parentNode, List<Pair<BlockPos, DiscoveredLocation>> ends) { DiscoveredLocation parentNode, Collection<DiscoveredLocation> ends) {
for (Pair<BlockPos, DiscoveredLocation> pair : ends) for (DiscoveredLocation location : ends)
frontier.add( frontier.add(new FrontierEntry(parentNode, entry.currentNode, location));
new FrontierEntry(parentNode, entry.currentPos, entry.currentNode, pair.getFirst(), pair.getSecond()));
} }
public static boolean isValidGraphNodeLocation(DiscoveredLocation location, public static boolean isValidGraphNodeLocation(DiscoveredLocation location, Collection<DiscoveredLocation> next,
List<Pair<BlockPos, DiscoveredLocation>> next) { boolean first) {
if (next.size() != 1) int size = next.size() - (first ? 1 : 0);
if (size != 1)
return true; return true;
if (location.connectedViaTurn()) if (location.shouldForceNode())
return true; return true;
if (next.stream()
DiscoveredLocation nextLocation = next.iterator() .anyMatch(DiscoveredLocation::connectedViaTurn))
.next()
.getSecond();
if (nextLocation.connectedViaTurn())
return true; return true;
Vec3 vec = location.getLocation(); Vec3 vec = location.getLocation();
@ -308,86 +246,4 @@ public class TrackPropagator {
return ((int) Math.round(vec.x)) % 16 == 0; return ((int) Math.round(vec.x)) % 16 == 0;
} }
// TODO ITrackBlock
public static List<Pair<BlockPos, DiscoveredLocation>> getEnds(LevelReader reader, BlockPos pos, BlockState state,
boolean ignoreTurns, @Nullable DiscoveredLocation fromEnd, @Nullable Vec3 axisFilter) {
Vec3 center = VecHelper.getCenterOf(pos);
List<Pair<BlockPos, DiscoveredLocation>> list = new ArrayList<>();
if (!(state.getBlock() instanceof TrackBlock))
return list;
BlockEntity blockEntity = reader.getBlockEntity(pos);
if (state.getValue(TrackBlock.HAS_TURN) && blockEntity instanceof TrackTileEntity && !ignoreTurns) {
TrackTileEntity trackTileEntity = (TrackTileEntity) blockEntity;
trackTileEntity.getConnections()
.forEach((connectedPos, bc) -> {
Vec3 curveHandle = bc.axes.getFirst();
if (axisFilter != null && !testAxisFilter(curveHandle.normalize(), axisFilter))
return;
addToSet(fromEnd, list,
(d, b) -> d == 1 ? Vec3.atLowerCornerOf(bc.tePositions.get(b)) : bc.starts.get(b),
bc.normals::get, bc);
});
}
TrackShape shape = state.getValue(TrackBlock.SHAPE);
if (shape == TrackShape.NONE)
return list;
shape.getAxes()
.forEach(axis -> {
if (axisFilter != null && !testAxisFilter(axis.normalize(), axisFilter))
return;
addToSet(fromEnd, list, (d, b) -> axis.scale(b ? d : -d)
.add(center)
.add(0, axis.y == 0 ? -.5 : 0, 0), b -> shape.getNormal(), null);
});
return list;
}
private static boolean testAxisFilter(Vec3 axis, Vec3 filter) {
return Mth.equal(axis.distanceToSqr(filter), 0) || Mth.equal(axis.distanceToSqr(filter.scale(-1)), 0);
}
private static void addToSet(DiscoveredLocation fromEnd, List<Pair<BlockPos, DiscoveredLocation>> list,
BiFunction<Double, Boolean, Vec3> offsetFactory, Function<Boolean, Vec3> normalFactory,
BezierConnection viaTurn) {
DiscoveredLocation firstLocation = new DiscoveredLocation(offsetFactory.apply(0.5d, true));
DiscoveredLocation secondLocation = new DiscoveredLocation(offsetFactory.apply(0.5d, false));
Pair<BlockPos, DiscoveredLocation> firstNode =
Pair.of(new BlockPos(offsetFactory.apply(1.0d, true)), firstLocation.viaTurn(viaTurn)
.withNormal(normalFactory.apply(true)));
Pair<BlockPos, DiscoveredLocation> secondNode =
Pair.of(new BlockPos(offsetFactory.apply(1.0d, false)), secondLocation.viaTurn(viaTurn)
.withNormal(normalFactory.apply(false)));
boolean skipFirst = false;
boolean skipSecond = false;
if (fromEnd != null) {
boolean equalsFirst = firstNode.getSecond()
.equals(fromEnd);
boolean equalsSecond = secondNode.getSecond()
.equals(fromEnd);
// not reachable from this end, crossover rail
if (!equalsFirst && !equalsSecond)
return;
if (equalsFirst)
skipFirst = true;
if (equalsSecond)
skipSecond = true;
}
if (!skipFirst)
list.add(firstNode);
if (!skipSecond)
list.add(secondNode);
}
} }

View file

@ -338,7 +338,7 @@ public class Train {
offset += carriage.bogeySpacing; offset += carriage.bogeySpacing;
if (i < carriageSpacing.size()) if (i < carriageSpacing.size())
offset += carriageSpacing.get(carriageSpacing.size() - i - 1); offset += carriageSpacing.get(backwards ? carriageSpacing.size() - i - 1 : i);
} }
GlobalStation currentStation = getCurrentStation(); GlobalStation currentStation = getCurrentStation();

View file

@ -159,7 +159,7 @@ public class TravellingPoint {
TrackEdge newEdge = entry.getValue(); TrackEdge newEdge = entry.getValue();
Vec3 currentDirection = edge.getDirection(node1, node2, false); Vec3 currentDirection = edge.getDirection(node1, node2, false);
Vec3 newDirection = newEdge.getDirection(node2, newNode, true); Vec3 newDirection = newEdge.getDirection(node2, newNode, true);
if (currentDirection.dot(newDirection) < 0) if (currentDirection.dot(newDirection) < 3 / 4f)
continue; continue;
validTargets.add(entry); validTargets.add(entry);
@ -197,7 +197,7 @@ public class TravellingPoint {
.get(node1); .get(node1);
Vec3 currentDirection = edge.getDirection(node1, node2, true); Vec3 currentDirection = edge.getDirection(node1, node2, true);
Vec3 newDirection = newEdge.getDirection(newNode, node1, false); Vec3 newDirection = newEdge.getDirection(newNode, node1, false);
if (currentDirection.dot(newDirection) < 0) if (currentDirection.dot(newDirection) < 3 / 4f)
continue; continue;
validTargets.add(entry); validTargets.add(entry);

View file

@ -2,6 +2,7 @@ package com.simibubi.create.content.logistics.trains.management;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -16,7 +17,6 @@ import com.simibubi.create.content.logistics.trains.TrackEdge;
import com.simibubi.create.content.logistics.trains.TrackGraph; import com.simibubi.create.content.logistics.trains.TrackGraph;
import com.simibubi.create.content.logistics.trains.TrackNode; import com.simibubi.create.content.logistics.trains.TrackNode;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation; import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.TrackPropagator;
import com.simibubi.create.content.logistics.trains.entity.Carriage; import com.simibubi.create.content.logistics.trains.entity.Carriage;
import com.simibubi.create.content.logistics.trains.entity.Carriage.CarriageBogey; import com.simibubi.create.content.logistics.trains.entity.Carriage.CarriageBogey;
import com.simibubi.create.content.logistics.trains.entity.CarriageContraption; import com.simibubi.create.content.logistics.trains.entity.CarriageContraption;
@ -26,7 +26,6 @@ import com.simibubi.create.foundation.tileEntity.SmartTileEntity;
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour; import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
import com.simibubi.create.foundation.utility.Iterate; import com.simibubi.create.foundation.utility.Iterate;
import com.simibubi.create.foundation.utility.Lang; import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.foundation.utility.Pair;
import com.simibubi.create.foundation.utility.WorldAttached; import com.simibubi.create.foundation.utility.WorldAttached;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
@ -325,12 +324,15 @@ public class StationTileEntity extends SmartTileEntity {
BlockPos bogeyOffset = new BlockPos(track.getUpNormal(level, trackPosition, trackState)); BlockPos bogeyOffset = new BlockPos(track.getUpNormal(level, trackPosition, trackState));
DiscoveredLocation location = null; DiscoveredLocation location = null;
List<Pair<BlockPos, DiscoveredLocation>> ends = Vec3 centre = Vec3.atBottomCenterOf(trackPosition)
TrackPropagator.getEnds(level, trackPosition, trackState, true, null, null); .add(0, track.getElevationAtCenter(level, trackPosition, trackState), 0);
for (Pair<BlockPos, DiscoveredLocation> pair : ends) Collection<DiscoveredLocation> ends = track.getConnected(level, trackPosition, trackState, true, null);
if (trackPosition.relative(assemblyDirection) Vec3 targetOffset = Vec3.atLowerCornerOf(assemblyDirection.getNormal());
.equals(pair.getFirst())) for (DiscoveredLocation end : ends)
location = pair.getSecond(); if (Mth.equal(0, targetOffset.distanceToSqr(end.getLocation()
.subtract(centre)
.normalize())))
location = end;
if (location == null) if (location == null)
return; return;
@ -349,13 +351,14 @@ public class StationTileEntity extends SmartTileEntity {
TrackGraph graph = null; TrackGraph graph = null;
TrackNode secondNode = null; TrackNode secondNode = null;
for (int i = 0; i < assemblyLength + 20; i++) { for (int j = 0; j < assemblyLength * 2 + 40; j++) {
double i = j / 2d;
if (points.size() == pointOffsets.size()) if (points.size() == pointOffsets.size())
break; break;
DiscoveredLocation currentLocation = location; DiscoveredLocation currentLocation = location;
location = new DiscoveredLocation(location.getLocation() location = new DiscoveredLocation(location.getLocation()
.add(directionVec)); .add(directionVec.scale(.5)));
if (graph == null) if (graph == null)
graph = Create.RAILWAYS.getGraph(level, currentLocation); graph = Create.RAILWAYS.getGraph(level, currentLocation);

View file

@ -99,7 +99,7 @@ public class StandardBogeyBlock extends Block implements IBogeyBlock, ITE<Standa
ms.mulPose(Vector3f.YP.rotationDegrees(90)); ms.mulPose(Vector3f.YP.rotationDegrees(90));
} }
ms.translate(0, -1.5, 0); ms.translate(0, -1.5 - 1 / 128f, 0);
VertexConsumer vb = buffers.getBuffer(RenderType.cutoutMipped()); VertexConsumer vb = buffers.getBuffer(RenderType.cutoutMipped());
BlockState air = Blocks.AIR.defaultBlockState(); BlockState air = Blocks.AIR.defaultBlockState();

View file

@ -1,6 +1,9 @@
package com.simibubi.create.content.logistics.trains.track; package com.simibubi.create.content.logistics.trains.track;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Random; import java.util.Random;
@ -15,7 +18,10 @@ import com.simibubi.create.Create;
import com.simibubi.create.CreateClient; import com.simibubi.create.CreateClient;
import com.simibubi.create.content.contraptions.wrench.IWrenchable; import com.simibubi.create.content.contraptions.wrench.IWrenchable;
import com.simibubi.create.content.curiosities.girder.GirderBlock; import com.simibubi.create.content.curiosities.girder.GirderBlock;
import com.simibubi.create.content.logistics.trains.BezierConnection;
import com.simibubi.create.content.logistics.trains.ITrackBlock; import com.simibubi.create.content.logistics.trains.ITrackBlock;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation;
import com.simibubi.create.content.logistics.trains.TrackNodeLocation.DiscoveredLocation;
import com.simibubi.create.content.logistics.trains.TrackPropagator; import com.simibubi.create.content.logistics.trains.TrackPropagator;
import com.simibubi.create.content.logistics.trains.management.StationTileEntity; import com.simibubi.create.content.logistics.trains.management.StationTileEntity;
import com.simibubi.create.foundation.utility.AngleHelper; import com.simibubi.create.foundation.utility.AngleHelper;
@ -144,8 +150,41 @@ public class TrackBlock extends Block implements EntityBlock, IWrenchable, ITrac
@Override @Override
public void tick(BlockState p_60462_, ServerLevel p_60463_, BlockPos p_60464_, Random p_60465_) { public void tick(BlockState p_60462_, ServerLevel p_60463_, BlockPos p_60464_, Random p_60465_) {
for (Vec3 axis : getTrackAxes(p_60463_, p_60464_, p_60462_)) TrackPropagator.onRailAdded(p_60463_, p_60464_, p_60462_);
TrackPropagator.onRailAdded(p_60463_, p_60464_, p_60462_, axis.normalize()); }
@Override
public Collection<DiscoveredLocation> getConnected(BlockGetter world, BlockPos pos, BlockState state,
boolean linear, TrackNodeLocation connectedTo) {
Collection<DiscoveredLocation> list;
if (getTrackAxes(world, pos, state).size() > 1) {
Vec3 center = Vec3.atBottomCenterOf(pos)
.add(0, getElevationAtCenter(world, pos, state), 0);
TrackShape shape = state.getValue(TrackBlock.SHAPE);
list = new ArrayList<>();
for (Vec3 axis : getTrackAxes(world, pos, state))
for (boolean fromCenter : Iterate.trueAndFalse)
ITrackBlock.addToListIfConnected(connectedTo, list,
(d, b) -> axis.scale(b ? 0 : fromCenter ? -d : d)
.add(center),
b -> shape.getNormal(), null);
} else
list = ITrackBlock.super.getConnected(world, pos, state, linear, connectedTo);
if (!state.getValue(HAS_TURN))
return list;
if (linear)
return list;
BlockEntity blockEntity = world.getBlockEntity(pos);
if (!(blockEntity instanceof TrackTileEntity trackTE))
return list;
Map<BlockPos, BezierConnection> connections = trackTE.getConnections();
connections.forEach((connectedPos, bc) -> ITrackBlock.addToListIfConnected(connectedTo, list,
(d, b) -> d == 1 ? Vec3.atLowerCornerOf(bc.tePositions.get(b)) : bc.starts.get(b), bc.normals::get, bc));
return list;
} }
@Override @Override