From f18968ce44388e9644899c5e13e521a7ec53faab Mon Sep 17 00:00:00 2001 From: stacode Date: Sat, 8 Nov 2025 15:15:20 +0100 Subject: [PATCH 1/4] Implement a cache for train collisions for better performance on large networks --- .../create/content/trains/entity/Train.java | 251 +++++++++++++++--- 1 file changed, 215 insertions(+), 36 deletions(-) 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..13733f2e21 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; @@ -433,6 +436,7 @@ public void tick(Level level) { if (index == 0) { distance = actualDistance; + updateCollisionCache(); collideWithOtherTrains(level, carriage); backwardsDriver = null; if (graph == null) @@ -620,6 +624,62 @@ public boolean hasBackwardConductor() { return false; } + + private void updateCollisionCache() { + if (derailed || graph == null) { + if (collisionCache != null) + collisionCache.invalidate(); + return; + } + + int totalSegments = carriages.size() * 2 - 1; + + if (collisionCache == null || collisionCache.start.length < totalSegments) { + collisionCache = new CollisionCache(totalSegments); + } + + 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 && segmentIndex < totalSegments) { + collisionCache.addSegment(lastPoint, start, segmentIndex++); + } + + if (segmentIndex < totalSegments) { + collisionCache.addSegment(start, end, segmentIndex++); + } + + lastPoint = end; + } + + collisionCache.segmentCount = segmentIndex; + collisionCache.dimension = trainDimension; + collisionCache.valid = segmentIndex > 0 && trainDimension != null; + } + private void collideWithOtherTrains(Level level, Carriage carriage) { if (derailed) return; @@ -654,6 +714,12 @@ private void collideWithOtherTrains(Level level, Carriage carriage) { public Pair findCollidingTrain(Level level, Vec3 start, Vec3 end, ResourceKey dimension) { Vec3 diff = end.subtract(start); + double length = diff.length(); + + if (length < 0.0001) + return null; + + Vec3 normedDiff = diff.normalize(); double maxDistanceSqr = Math.pow(AllConfigs.server().trains.maxAssemblyLength.get(), 2.0); Trains: for (Train train : Create.RAILWAYS.sided(level).trains.values()) { @@ -662,68 +728,121 @@ public Pair findCollidingTrain(Level level, Vec3 start, Vec3 end, R if (train.graph != null && train.graph != graph) continue; - Vec3 lastPoint = null; - - for (Carriage otherCarriage : train.carriages) { - for (boolean betweenBits : Iterate.trueAndFalse) { - if (betweenBits && lastPoint == null) - continue; + // Compute cache for trains that don't have it yet + if (train.collisionCache == null || !train.collisionCache.isValid()) { + train.updateCollisionCache(); + } - 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; + // Use cached data if available, otherwise fall back to old method + if (train.collisionCache != null && train.collisionCache.isValid()) { + if (!train.collisionCache.dimension.equals(dimension)) + continue; - Vec3 start2 = otherLeading.getPosition(train.graph); - Vec3 end2 = otherTrailing.getPosition(train.graph); + // 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]; + // Early distance culling using cached positions if (Math.min(start2.distanceToSqr(start), end2.distanceToSqr(start)) > maxDistanceSqr) continue Trains; - if (betweenBits) { - end2 = start2; - start2 = lastPoint; - } - - lastPoint = end2; - + // Vertical separation check if ((end.y < end2.y - 3 || end2.y < end.y - 3) - && (start.y < start2.y - 3 || start2.y < start.y - 3)) + && (start.y < start2.y - 3 || start2.y < start.y - 3)) continue; - Vec3 diff2 = end2.subtract(start2); - Vec3 normedDiff = diff.normalize(); - Vec3 normedDiff2 = diff2.normalize(); + // Use precomputed direction vectors from cache + Vec3 normedDiff2 = cache.direction[i]; double[] intersect = VecHelper.intersect(start, start2, normedDiff, normedDiff2, Axis.Y); if (intersect == null) { + // Sphere intersection fallback Vec3 intersectSphere = VecHelper.intersectSphere(start2, normedDiff2, start, .125f); if (intersectSphere == null) continue; - if (!Mth.equal(normedDiff2.dot(intersectSphere.subtract(start2) - .normalize()), 1)) + + 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()) + // Bounds checking using cached lengths + if (intersect[0] > length || intersect[0] < 0) continue; - if (intersect[1] > diff2.length()) - continue; - if (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]))); } + } else { + // Fallback: original algorithm for trains without valid cache + Vec3 lastPoint = null; + + for (Carriage otherCarriage : train.carriages) { + for (boolean betweenBits : Iterate.trueAndFalse) { + if (betweenBits && lastPoint == null) + continue; + + 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; + + Vec3 start2 = otherLeading.getPosition(train.graph); + Vec3 end2 = otherTrailing.getPosition(train.graph); + + if (Math.min(start2.distanceToSqr(start), end2.distanceToSqr(start)) > maxDistanceSqr) + continue Trains; + + if (betweenBits) { + end2 = start2; + start2 = lastPoint; + } + + lastPoint = end2; + + if ((end.y < end2.y - 3 || end2.y < end.y - 3) + && (start.y < start2.y - 3 || start2.y < start.y - 3)) + continue; + + Vec3 diff2 = end2.subtract(start2); + Vec3 normedDiff2 = diff2.normalize(); + double[] intersect = VecHelper.intersect(start, start2, normedDiff, normedDiff2, Axis.Y); + + 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] > length) + continue; + if (intersect[1] > diff2.length()) + continue; + if (intersect[0] < 0) + continue; + if (intersect[1] < 0) + continue; + + return Pair.of(train, start.add(normedDiff.scale(intersect[0]))); + } + } } } return null; @@ -1090,6 +1209,66 @@ 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; + boolean valid; + + 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]; + this.valid = false; + } + + void invalidate() { + this.valid = false; + } + + boolean isValid() { + return valid && segmentCount > 0; + } + + /** + * Add a segment to the cache with precomputed direction and length + */ + void addSegment(Vec3 startPos, Vec3 endPos, int index) { + if (index >= start.length) + return; + + start[index] = startPos; + end[index] = endPos; + + // Precompute direction vector and length + Vec3 diff = endPos.subtract(startPos); + double length = diff.length(); + + segmentLength[index] = length; + if (length > 0.0001) { + direction[index] = diff.normalize(); + } else { + direction[index] = Vec3.ZERO; + } + } + } + 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, From 3f80853a5f84f9cbc7bfdb52be91360122f66a02 Mon Sep 17 00:00:00 2001 From: stacode Date: Sat, 8 Nov 2025 15:19:22 +0100 Subject: [PATCH 2/4] Remove redundant fallback --- .../create/content/trains/entity/Train.java | 65 +------------------ 1 file changed, 1 insertion(+), 64 deletions(-) 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 13733f2e21..d3e64f9e8a 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 @@ -779,71 +779,8 @@ public Pair findCollidingTrain(Level level, Vec3 start, Vec3 end, R return Pair.of(train, start.add(normedDiff.scale(intersect[0]))); } - } else { - // Fallback: original algorithm for trains without valid cache - Vec3 lastPoint = null; - - for (Carriage otherCarriage : train.carriages) { - for (boolean betweenBits : Iterate.trueAndFalse) { - if (betweenBits && lastPoint == null) - continue; - - 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; - - Vec3 start2 = otherLeading.getPosition(train.graph); - Vec3 end2 = otherTrailing.getPosition(train.graph); - - if (Math.min(start2.distanceToSqr(start), end2.distanceToSqr(start)) > maxDistanceSqr) - continue Trains; - - if (betweenBits) { - end2 = start2; - start2 = lastPoint; - } - - lastPoint = end2; - - if ((end.y < end2.y - 3 || end2.y < end.y - 3) - && (start.y < start2.y - 3 || start2.y < start.y - 3)) - continue; - - Vec3 diff2 = end2.subtract(start2); - Vec3 normedDiff2 = diff2.normalize(); - double[] intersect = VecHelper.intersect(start, start2, normedDiff, normedDiff2, Axis.Y); - - 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] > length) - continue; - if (intersect[1] > diff2.length()) - continue; - if (intersect[0] < 0) - continue; - if (intersect[1] < 0) - continue; - - return Pair.of(train, start.add(normedDiff.scale(intersect[0]))); - } - } } + } return null; } From 7e58290edd925fef9e1a42cf09cff2e8fb6bb717 Mon Sep 17 00:00:00 2001 From: stacode Date: Sun, 9 Nov 2025 20:03:23 +0100 Subject: [PATCH 3/4] Address comments and further improve performance by reducing updateCollisionCache calls. --- .../content/trains/GlobalRailwayManager.java | 6 +- .../create/content/trains/entity/Train.java | 59 ++++++------------- 2 files changed, 21 insertions(+), 44 deletions(-) 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 d3e64f9e8a..68dedc7abb 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 @@ -436,7 +436,6 @@ public void tick(Level level) { if (index == 0) { distance = actualDistance; - updateCollisionCache(); collideWithOtherTrains(level, carriage); backwardsDriver = null; if (graph == null) @@ -625,17 +624,17 @@ public boolean hasBackwardConductor() { } - private void updateCollisionCache() { + public void updateCollisionCache() { if (derailed || graph == null) { if (collisionCache != null) - collisionCache.invalidate(); + collisionCache = null; return; } - int totalSegments = carriages.size() * 2 - 1; + int maxExpectedSegments = carriages.size() * 2 - 1; - if (collisionCache == null || collisionCache.start.length < totalSegments) { - collisionCache = new CollisionCache(totalSegments); + if (collisionCache == null || collisionCache.start.length < maxExpectedSegments) { + collisionCache = new CollisionCache(maxExpectedSegments); } Vec3 lastPoint = null; @@ -664,11 +663,11 @@ else if (!trainDimension.equals(leadingDim)) Vec3 start = leading.getPosition(graph); Vec3 end = trailing.getPosition(graph); - if (lastPoint != null && segmentIndex < totalSegments) { + if (lastPoint != null && segmentIndex < maxExpectedSegments) { collisionCache.addSegment(lastPoint, start, segmentIndex++); } - if (segmentIndex < totalSegments) { + if (segmentIndex < maxExpectedSegments) { collisionCache.addSegment(start, end, segmentIndex++); } @@ -677,7 +676,9 @@ else if (!trainDimension.equals(leadingDim)) collisionCache.segmentCount = segmentIndex; collisionCache.dimension = trainDimension; - collisionCache.valid = segmentIndex > 0 && trainDimension != null; + if(segmentIndex == 0) { + collisionCache=null; + } } private void collideWithOtherTrains(Level level, Carriage carriage) { @@ -693,10 +694,10 @@ 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); + 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; @@ -716,11 +717,8 @@ public Pair findCollidingTrain(Level level, Vec3 start, Vec3 end, R Vec3 diff = end.subtract(start); double length = diff.length(); - if (length < 0.0001) - return null; - Vec3 normedDiff = diff.normalize(); - double maxDistanceSqr = Math.pow(AllConfigs.server().trains.maxAssemblyLength.get(), 2.0); + double maxDistanceSqr = AllConfigs.server().trains.maxAssemblyLength.get()*AllConfigs.server().trains.maxAssemblyLength.get(); Trains: for (Train train : Create.RAILWAYS.sided(level).trains.values()) { if (train == this) @@ -728,13 +726,7 @@ public Pair findCollidingTrain(Level level, Vec3 start, Vec3 end, R if (train.graph != null && train.graph != graph) continue; - // Compute cache for trains that don't have it yet - if (train.collisionCache == null || !train.collisionCache.isValid()) { - train.updateCollisionCache(); - } - - // Use cached data if available, otherwise fall back to old method - if (train.collisionCache != null && train.collisionCache.isValid()) { + if (train.collisionCache != null ) { if (!train.collisionCache.dimension.equals(dimension)) continue; @@ -750,7 +742,7 @@ public Pair findCollidingTrain(Level level, Vec3 start, Vec3 end, R // Vertical separation check if ((end.y < end2.y - 3 || end2.y < end.y - 3) - && (start.y < start2.y - 3 || start2.y < start.y - 3)) + && (start.y < start2.y - 3 || start2.y < start.y - 3)) continue; // Use precomputed direction vectors from cache @@ -1164,7 +1156,6 @@ private static class CollisionCache { // Metadata ResourceKey dimension; - boolean valid; CollisionCache(int maxSegments) { this.segmentCount = 0; @@ -1172,24 +1163,13 @@ private static class CollisionCache { this.end = new Vec3[maxSegments]; this.direction = new Vec3[maxSegments]; this.segmentLength = new double[maxSegments]; - this.valid = false; } - void invalidate() { - this.valid = false; - } - - boolean isValid() { - return valid && segmentCount > 0; - } /** * Add a segment to the cache with precomputed direction and length */ void addSegment(Vec3 startPos, Vec3 endPos, int index) { - if (index >= start.length) - return; - start[index] = startPos; end[index] = endPos; @@ -1198,11 +1178,8 @@ void addSegment(Vec3 startPos, Vec3 endPos, int index) { double length = diff.length(); segmentLength[index] = length; - if (length > 0.0001) { - direction[index] = diff.normalize(); - } else { - direction[index] = Vec3.ZERO; - } + direction[index] = diff.normalize(); + } } From 956dcc1265a3621887af4d83ab02f506373724f2 Mon Sep 17 00:00:00 2001 From: stacode Date: Sun, 16 Nov 2025 13:47:45 +0100 Subject: [PATCH 4/4] Address comments --- .../create/content/trains/entity/Train.java | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) 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 68dedc7abb..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 @@ -625,10 +625,10 @@ public boolean hasBackwardConductor() { public void updateCollisionCache() { + // if (derailed || graph == null) { if (collisionCache != null) - collisionCache = null; - return; + return; } int maxExpectedSegments = carriages.size() * 2 - 1; @@ -637,6 +637,7 @@ public void updateCollisionCache() { collisionCache = new CollisionCache(maxExpectedSegments); } + //For adding segments between carriages Vec3 lastPoint = null; int segmentIndex = 0; ResourceKey trainDimension = null; @@ -663,21 +664,20 @@ else if (!trainDimension.equals(leadingDim)) Vec3 start = leading.getPosition(graph); Vec3 end = trailing.getPosition(graph); - if (lastPoint != null && segmentIndex < maxExpectedSegments) { + if (lastPoint != null) { collisionCache.addSegment(lastPoint, start, segmentIndex++); } - if (segmentIndex < maxExpectedSegments) { - collisionCache.addSegment(start, end, segmentIndex++); - } + collisionCache.addSegment(start, end, segmentIndex++); + lastPoint = end; } collisionCache.segmentCount = segmentIndex; collisionCache.dimension = trainDimension; - if(segmentIndex == 0) { - collisionCache=null; + if (segmentIndex == 0) { + collisionCache = null; } } @@ -694,6 +694,9 @@ private void collideWithOtherTrains(Level level, Carriage carriage) { if (!dimension.equals(trailingPoint.node1.getLocation().dimension)) return; + if (collisionCache == null) { + updateCollisionCache(); + } Vec3 start = collisionCache.start[0]; Vec3 end = collisionCache.end[collisionCache.segmentCount - 1]; @@ -718,50 +721,50 @@ public Pair findCollidingTrain(Level level, Vec3 start, Vec3 end, R double length = diff.length(); Vec3 normedDiff = diff.normalize(); - double maxDistanceSqr = AllConfigs.server().trains.maxAssemblyLength.get()*AllConfigs.server().trains.maxAssemblyLength.get(); + 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; - if (train.collisionCache != null ) { - if (!train.collisionCache.dimension.equals(dimension)) - 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]; - // 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]; + // Early distance culling using cached positions + if (Math.min(start2.distanceToSqr(start), end2.distanceToSqr(start)) > maxDistanceSqr) + continue Trains; - // Early distance culling using cached positions - if (Math.min(start2.distanceToSqr(start), end2.distanceToSqr(start)) > maxDistanceSqr) - continue Trains; + // 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; - // 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; + // Use precomputed direction vectors from cache + Vec3 normedDiff2 = cache.direction[i]; + double[] intersect = VecHelper.intersect(start, start2, normedDiff, normedDiff2, Axis.Y); - // Use precomputed direction vectors from cache - Vec3 normedDiff2 = cache.direction[i]; - double[] intersect = VecHelper.intersect(start, start2, normedDiff, normedDiff2, Axis.Y); + if (intersect == null) { + // Sphere intersection fallback + Vec3 intersectSphere = VecHelper.intersectSphere(start2, normedDiff2, start, .125f); + if (intersectSphere == null) + continue; - if (intersect == null) { - // Sphere intersection fallback - Vec3 intersectSphere = VecHelper.intersectSphere(start2, normedDiff2, start, .125f); - if (intersectSphere == null) - continue; + if (!Mth.equal(normedDiff2.dot(intersectSphere.subtract(start2).normalize()), 1)) + 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; - intersect = new double[2]; - intersect[0] = intersectSphere.distanceTo(start) - .125; - intersect[1] = intersectSphere.distanceTo(start2) - .125; - } // Bounds checking using cached lengths if (intersect[0] > length || intersect[0] < 0)