-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Implements a cache for train collision checks for better performance #9513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: mc1.21.1/dev
Are you sure you want to change the base?
Changes from all commits
f18968c
3f80853
7e58290
956dcc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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; | ||
|
|
@@ -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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do this here if all trains are supposed to have it baked ahead of time here? |
||
| 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; | ||
|
|
||
|
|
@@ -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;} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Formatting if (train.collisionCache == null)
continue;or if (train.collisionCache == null) {
continue;
}but be consistent |
||
| 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; | ||
| } | ||
|
|
@@ -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, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to write something here?