diff --git a/src/main/java/com/simibubi/create/content/trains/GlobalRailwayManager.java b/src/main/java/com/simibubi/create/content/trains/GlobalRailwayManager.java index 806c3a5c15..e89e466b4d 100644 --- a/src/main/java/com/simibubi/create/content/trains/GlobalRailwayManager.java +++ b/src/main/java/com/simibubi/create/content/trains/GlobalRailwayManager.java @@ -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; @@ -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) diff --git a/src/main/java/com/simibubi/create/content/trains/entity/Train.java b/src/main/java/com/simibubi/create/content/trains/entity/Train.java index 39fb6b7642..6d1f345888 100644 --- a/src/main/java/com/simibubi/create/content/trains/entity/Train.java +++ b/src/main/java/com/simibubi/create/content/trains/entity/Train.java @@ -150,6 +150,9 @@ public class Train { int ticksSinceLastMailTransfer; double[] stress; + + private CollisionCache collisionCache; + // advancements public Player backwardsDriver; @@ -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 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 leadingDim = leading.node1.getLocation().dimension; + ResourceKey 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; @@ -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 collision = findCollidingTrain(level, start, end, dimension); + Pair collision = findCollidingTrain(level,start,end, dimension); if (collision == null) return; @@ -654,77 +718,64 @@ private void collideWithOtherTrains(Level level, Carriage carriage) { public Pair findCollidingTrain(Level level, Vec3 start, Vec3 end, ResourceKey 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 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; } @@ -1090,6 +1141,51 @@ public Couple> 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 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,