Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import com.simibubi.create.content.trains.graph.TrackNodeLocation;
import com.simibubi.create.content.trains.signal.SignalEdgeGroup;
import net.createmod.catnip.platform.CatnipServices;
import com.simibubi.create.foundation.utility.DistExecutor;
import com.simibubi.create.infrastructure.config.AllConfigs;

import net.minecraft.server.MinecraftServer;
Expand Down Expand Up @@ -220,8 +219,9 @@ private void tickTrains(Level level) {
// keeping two lists ensures a tick order starting at longest waiting
for (Train train : waitingTrains)
train.earlyTick(level);
for (Train train : movingTrains)
train.earlyTick(level);
for (Train train : movingTrains){
train.updateCollisionCache();
train.earlyTick(level);}
for (Train train : waitingTrains)
train.tick(level);
for (Train train : movingTrains)
Expand Down
200 changes: 148 additions & 52 deletions src/main/java/com/simibubi/create/content/trains/entity/Train.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ public class Train {
int ticksSinceLastMailTransfer;
double[] stress;


private CollisionCache collisionCache;

// advancements
public Player backwardsDriver;

Expand Down Expand Up @@ -620,6 +623,64 @@ public boolean hasBackwardConductor() {
return false;
}


public void updateCollisionCache() {
//
if (derailed || graph == null) {
if (collisionCache != null)
return;
}

int maxExpectedSegments = carriages.size() * 2 - 1;

if (collisionCache == null || collisionCache.start.length < maxExpectedSegments) {
collisionCache = new CollisionCache(maxExpectedSegments);
}

//For adding segments between carriages
Vec3 lastPoint = null;
int segmentIndex = 0;
ResourceKey<Level> trainDimension = null;

for (Carriage carriage : carriages) {
TravellingPoint leading = carriage.getLeadingPoint();
TravellingPoint trailing = carriage.getTrailingPoint();

if (leading.edge == null || trailing.edge == null ||
leading.node1 == null || trailing.node1 == null)
continue;

ResourceKey<Level> leadingDim = leading.node1.getLocation().dimension;
ResourceKey<Level> trailingDim = trailing.node1.getLocation().dimension;

if (!leadingDim.equals(trailingDim))
continue;

if (trainDimension == null)
trainDimension = leadingDim;
else if (!trainDimension.equals(leadingDim))
continue;

Vec3 start = leading.getPosition(graph);
Vec3 end = trailing.getPosition(graph);

if (lastPoint != null) {
collisionCache.addSegment(lastPoint, start, segmentIndex++);
}

collisionCache.addSegment(start, end, segmentIndex++);


lastPoint = end;
}

collisionCache.segmentCount = segmentIndex;
collisionCache.dimension = trainDimension;
if (segmentIndex == 0) {
collisionCache = null;
}
}

private void collideWithOtherTrains(Level level, Carriage carriage) {
if (derailed)
return;
Expand All @@ -633,10 +694,13 @@ private void collideWithOtherTrains(Level level, Carriage carriage) {
if (!dimension.equals(trailingPoint.node1.getLocation().dimension))
return;

Vec3 start = (speed < 0 ? trailingPoint : leadingPoint).getPosition(graph);
Vec3 end = (speed < 0 ? leadingPoint : trailingPoint).getPosition(graph);
if (collisionCache == null) {
updateCollisionCache();
}
Vec3 start = collisionCache.start[0];
Vec3 end = collisionCache.end[collisionCache.segmentCount - 1];

Pair<Train, Vec3> collision = findCollidingTrain(level, start, end, dimension);
Pair<Train, Vec3> collision = findCollidingTrain(level,start,end, dimension);
if (collision == null)
return;

Expand All @@ -654,77 +718,64 @@ private void collideWithOtherTrains(Level level, Carriage carriage) {

public Pair<Train, Vec3> findCollidingTrain(Level level, Vec3 start, Vec3 end, ResourceKey<Level> dimension) {
Vec3 diff = end.subtract(start);
double maxDistanceSqr = Math.pow(AllConfigs.server().trains.maxAssemblyLength.get(), 2.0);
double length = diff.length();

Vec3 normedDiff = diff.normalize();
double maxDistance = AllConfigs.server().trains.maxAssemblyLength.get();
double maxDistanceSqr = maxDistance * maxDistance;

Trains: for (Train train : Create.RAILWAYS.sided(level).trains.values()) {
if (train == this)
continue;
if (train.graph != null && train.graph != graph)
continue;
if (train.collisionCache == null) {continue;}
if (!train.collisionCache.dimension.equals(dimension))
continue;

Vec3 lastPoint = null;

for (Carriage otherCarriage : train.carriages) {
for (boolean betweenBits : Iterate.trueAndFalse) {
if (betweenBits && lastPoint == null)
continue;
// Fast path: iterate through precomputed cache
CollisionCache cache = train.collisionCache;
for (int i = 0; i < cache.segmentCount; i++) {
Vec3 start2 = cache.start[i];
Vec3 end2 = cache.end[i];

TravellingPoint otherLeading = otherCarriage.getLeadingPoint();
TravellingPoint otherTrailing = otherCarriage.getTrailingPoint();
if (otherLeading.edge == null || otherTrailing.edge == null)
continue;
ResourceKey<Level> otherDimension = otherLeading.node1.getLocation().dimension;
if (!otherDimension.equals(otherTrailing.node1.getLocation().dimension))
continue;
if (!otherDimension.equals(dimension))
continue;
// Early distance culling using cached positions
if (Math.min(start2.distanceToSqr(start), end2.distanceToSqr(start)) > maxDistanceSqr)
continue Trains;

Vec3 start2 = otherLeading.getPosition(train.graph);
Vec3 end2 = otherTrailing.getPosition(train.graph);
// Vertical separation check
if ((end.y < end2.y - 3 || end2.y < end.y - 3)
&& (start.y < start2.y - 3 || start2.y < start.y - 3))
continue;

if (Math.min(start2.distanceToSqr(start), end2.distanceToSqr(start)) > maxDistanceSqr)
continue Trains;
// Use precomputed direction vectors from cache
Vec3 normedDiff2 = cache.direction[i];
double[] intersect = VecHelper.intersect(start, start2, normedDiff, normedDiff2, Axis.Y);

if (betweenBits) {
end2 = start2;
start2 = lastPoint;
}

lastPoint = end2;
if (intersect == null) {
// Sphere intersection fallback
Vec3 intersectSphere = VecHelper.intersectSphere(start2, normedDiff2, start, .125f);
if (intersectSphere == null)
continue;

if ((end.y < end2.y - 3 || end2.y < end.y - 3)
&& (start.y < start2.y - 3 || start2.y < start.y - 3))
if (!Mth.equal(normedDiff2.dot(intersectSphere.subtract(start2).normalize()), 1))
continue;

Vec3 diff2 = end2.subtract(start2);
Vec3 normedDiff = diff.normalize();
Vec3 normedDiff2 = diff2.normalize();
double[] intersect = VecHelper.intersect(start, start2, normedDiff, normedDiff2, Axis.Y);
intersect = new double[2];
intersect[0] = intersectSphere.distanceTo(start) - .125;
intersect[1] = intersectSphere.distanceTo(start2) - .125;

if (intersect == null) {
Vec3 intersectSphere = VecHelper.intersectSphere(start2, normedDiff2, start, .125f);
if (intersectSphere == null)
continue;
if (!Mth.equal(normedDiff2.dot(intersectSphere.subtract(start2)
.normalize()), 1))
continue;
intersect = new double[2];
intersect[0] = intersectSphere.distanceTo(start) - .125;
intersect[1] = intersectSphere.distanceTo(start2) - .125;
}

if (intersect[0] > diff.length())
continue;
if (intersect[1] > diff2.length())
continue;
if (intersect[0] < 0)
// Bounds checking using cached lengths
if (intersect[0] > length || intersect[0] < 0)
continue;
if (intersect[1] < 0)
if (intersect[1] > cache.segmentLength[i] || intersect[1] < 0)
continue;

return Pair.of(train, start.add(normedDiff.scale(intersect[0])));
}
}

}
return null;
}
Expand Down Expand Up @@ -1090,6 +1141,51 @@ public Couple<Couple<TrackNode>> getEndpointEdges() {
.map(tp -> Couple.create(tp.node1, tp.node2));
}


/**
* Collision detection cache structure.
* Stores precomputed train segment positions to avoid repeated graph lookups.
*/
private static class CollisionCache {
int segmentCount;

// Position arrays - precomputed to avoid repeated graph lookups
Vec3[] start;
Vec3[] end;

// Direction vectors (normalized) - precomputed for intersection tests
Vec3[] direction;
double[] segmentLength;

// Metadata
ResourceKey<Level> dimension;

CollisionCache(int maxSegments) {
this.segmentCount = 0;
this.start = new Vec3[maxSegments];
this.end = new Vec3[maxSegments];
this.direction = new Vec3[maxSegments];
this.segmentLength = new double[maxSegments];
}


/**
* Add a segment to the cache with precomputed direction and length
*/
void addSegment(Vec3 startPos, Vec3 endPos, int index) {
start[index] = startPos;
end[index] = endPos;

// Precompute direction vector and length
Vec3 diff = endPos.subtract(startPos);
double length = diff.length();

segmentLength[index] = length;
direction[index] = diff.normalize();

}
}

public static class Penalties {
static final int STATION = 50, STATION_WITH_TRAIN = 300;
static final int MANUAL_TRAIN = 200, IDLE_TRAIN = 700, ARRIVING_TRAIN = 50, WAITING_TRAIN = 50, ANY_TRAIN = 25,
Expand Down