diff --git a/src/main/java/net/tfminecraft/vehicleframework/vehicles/handlers/train/Connector.java b/src/main/java/net/tfminecraft/vehicleframework/vehicles/handlers/train/Connector.java index d770d1f..e93e49e 100644 --- a/src/main/java/net/tfminecraft/vehicleframework/vehicles/handlers/train/Connector.java +++ b/src/main/java/net/tfminecraft/vehicleframework/vehicles/handlers/train/Connector.java @@ -51,6 +51,11 @@ public Vector positionAt(TrackPose pose) { return offsetAt(pose).add(new Vector(pose.x, pose.y, pose.z)); } + /** + * Places a car so this coupler meets {@code target} seen from above. The car keeps the + * height of its own rail: where the grade changes between two cars, their couplers + * sit at different heights, and meeting exactly would lift or sink the car off its track. + */ public TrackPose coupledPose(TrackPose nominal, Vector target) { double dx = target.getX() - nominal.x; double dz = target.getZ() - nominal.z; @@ -58,7 +63,7 @@ public TrackPose coupledPose(TrackPose nominal, Vector target) { ? (float) Math.toDegrees(Math.atan2(-dx, dz)) : nominal.yaw; TrackPose facing = new TrackPose(nominal.x, nominal.y, nominal.z, yaw, nominal.pitch); Vector offset = offsetAt(facing); - return new TrackPose(target.getX() - offset.getX(), target.getY() - offset.getY(), + return new TrackPose(target.getX() - offset.getX(), nominal.y, target.getZ() - offset.getZ(), yaw, nominal.pitch); } diff --git a/src/test/java/net/tfminecraft/vehicleframework/vehicles/handlers/train/ConnectorTest.java b/src/test/java/net/tfminecraft/vehicleframework/vehicles/handlers/train/ConnectorTest.java index 87e66f9..aee1478 100644 --- a/src/test/java/net/tfminecraft/vehicleframework/vehicles/handlers/train/ConnectorTest.java +++ b/src/test/java/net/tfminecraft/vehicleframework/vehicles/handlers/train/ConnectorTest.java @@ -58,11 +58,27 @@ void passengerFrontMeetsLocomotiveBackWithRealPivotsAndBaseRotation( TrackPose nominal = new TrackPose(96, 64, 194, yaw - 25, pitch); TrackPose coupled = front.coupledPose(nominal, back.positionAt(parent)); Vector renderedFront = renderedAnchor(coupled, PASSENGER_FRONT, new Vector3f(), -entityYaw, size); - assertEquals(0, expectedBack.distance(renderedFront), 1e-5); + // Couplers meet seen from above; the car stays at its own rail height. + assertEquals(0, Math.hypot(expectedBack.getX() - renderedFront.getX(), + expectedBack.getZ() - renderedFront.getZ()), 1e-5); + assertEquals(nominal.y, coupled.y, 1e-9); assertEquals(pitch, coupled.pitch); assertEquals(new Vector3f(0, 15f / 16, 78f / 16), PASSENGER_FRONT); } + @Test + void carOnLevelTrackBehindLocomotiveOnGradeStaysOnItsRail() { + // Lab case: locomotive 11 degrees nose down, passenger car still on the level. + Connector back = connector(LOCO_BACK, LOCO_PIVOT, 88, new Vector3f(1)); + Connector front = connector(PASSENGER_FRONT, new Vector3f(), 88, new Vector3f(1)); + TrackPose loco = new TrackPose(5449.57, 430.384, 3462.5, 88.4f, 11.1f); + TrackPose nominal = new TrackPose(5457.82, 432.0, 3462.49, 88.4f, 0f); + TrackPose coupled = front.coupledPose(nominal, back.positionAt(loco)); + assertEquals(432.0, coupled.y, 1e-9); + assertEquals(nominal.x, coupled.x, 0.5); + assertEquals(nominal.z, coupled.z, 0.5); + } + private static Vector renderedAnchor(TrackPose pose, Vector3f point, Vector3f pivot, float entityYaw, Vector3f scale) { Vector3d result = new Matrix4d().translation(pose.x, pose.y, pose.z)