// For junctions with more outgoing (out) lanes than incoming (in) lanes. private static List <LaneConnectionInfo> AssignLanesMoreOutThanIn(int inLanes, int leftOut, int forwardOut, int rightOut) { List <NetLane.Flags> outLanes = CreateLaneList(leftOut, forwardOut, rightOut); List <List <int> > possibleLaneArrangements = GetAllPossibleLaneConfigurations(inLanes, outLanes.Count); List <LaneConnectionInfo> bestLanesInfo = GetLaneSetup(outLanes, possibleLaneArrangements[0]); LaneSetupFeatures bestFeatures = EvaluateLaneSetup(bestLanesInfo, outLanes); // Mod.LogMessage("Initial setup:\n" + ToString(bestLanesInfo) + "\nevaluated as:\n" + bestFeatures.ToString()); for (int i = 1; i < possibleLaneArrangements.Count; ++i) { var lanesInfo = GetLaneSetup(outLanes, possibleLaneArrangements[i]); var features = EvaluateLaneSetup(lanesInfo, outLanes); // Mod.LogMessage(ToString(lanesInfo) + "\nevaluated as:\n" + features.ToString()); if (features.IsBetterThan(bestFeatures)) { // Mod.LogMessage("This is better than previous best"); bestFeatures = features; bestLanesInfo = lanesInfo; } } if (!bestFeatures.valid) { // Impossible (in theory) Mod.LogMessage("Selected setup does not meet minimum requirements! " + string.Join(", ", bestLanesInfo.Select(x => x.ToString()).ToArray()) + "Lanes in: " + inLanes + "Lanes out: " + leftOut + "L/" + forwardOut + "F/" + rightOut + "R"); } return(bestLanesInfo); }
// Returns true if the lane setup described by this object is preferred over 'other'. Assumes RHT public bool IsBetterThan(LaneSetupFeatures other) { // Invalid lane setups should never be used. if (valid != other.valid) { return(valid); } // Avoid L+F+R lanes if possible. if (hasLeftFwdRightLane != other.hasLeftFwdRightLane) { return(!hasLeftFwdRightLane); } // Avoid L+R if possible. if (hasLeftRightLane != other.hasLeftRightLane) { return(!hasLeftRightLane); } // Avoid L+F lanes if possible. if (hasLeftFwdLane != other.hasLeftFwdLane) { return(!hasLeftFwdLane); } // Avoid F+R lanes if possible. if (hasFwdRightLane != other.hasFwdRightLane) { return(!hasFwdRightLane); } // Prefer setups with a larger number of incoming lanes with forward direction (i.e. limit lane splitting on forward connections). if (numFwdLanes != other.numFwdLanes) { return(numFwdLanes > other.numFwdLanes); } // Prefer setups where the number of forward connections is (more) evenly distributed among incoming lanes (including L+F and F+R lanes). if (fwdConnectionImbalance != other.fwdConnectionImbalance) { return(fwdConnectionImbalance < other.fwdConnectionImbalance); } if ((rightConnectionImbalance < 2) != (other.rightConnectionImbalance < 2)) { return(rightConnectionImbalance < 2); } if ((leftConnectionImbalance < 2) != (other.leftConnectionImbalance < 2)) { return(leftConnectionImbalance < 2); } // Prefer setups where the number of left connections is (more) evenly distributed among incoming lanes (including L+F and L+R lanes). // Difference of 1 is OK. In practice, we are only trying to avoid imbalance between Left-only lanes and Left+Fwd or Left+Right lanes. if ((leftConnectionImbalance < 2) != (other.leftConnectionImbalance < 2)) { return(leftConnectionImbalance < 2); } // As above, but for right-turning lanes. if ((rightConnectionImbalance < 2) != (other.rightConnectionImbalance < 2)) { return(rightConnectionImbalance < 2); } // Prefer setups where left and right turning lanes are (more) proportionally distributed if (leftRightOutInRatioImbalance != other.leftRightOutInRatioImbalance) { return(leftRightOutInRatioImbalance < other.leftRightOutInRatioImbalance); } // equivalent return(false); }
// Identifies all features of a lane setup, which may be needed to determine which setup is best. Assumes RHT private static LaneSetupFeatures EvaluateLaneSetup(List <LaneConnectionInfo> lanesInfo, List <NetLane.Flags> outLanes) { var features = new LaneSetupFeatures(); // If two in lanes with the same direction (e.g. two forward-only lanes) connect to a different // number of out lanes, the lane with more connections MUST be to the left (in RHT) of the other lane. for (int i = 1; i < lanesInfo.Count; ++i) { if (lanesInfo[i - 1].direction == lanesInfo[i].direction) { if (lanesInfo[i - 1].GetLaneCount() < lanesInfo[i].GetLaneCount()) { features.valid = false; return(features); } } } int minLeftLaneConns = 255; int maxLeftLaneConns = 0; int minFwdLaneConns = 255; int maxFwdLaneConns = 0; int minRightLaneConns = 255; int maxRightLaneConns = 0; int numLeftInLanes = 0; int numRightInLanes = 0; foreach (var laneInfo in lanesInfo) { features.hasFwdRightLane |= (laneInfo.direction == NetLane.Flags.ForwardRight); features.hasLeftFwdLane |= (laneInfo.direction == NetLane.Flags.LeftForward); features.hasLeftRightLane |= (laneInfo.direction == NetLane.Flags.LeftRight); features.hasLeftFwdRightLane |= (laneInfo.direction == NetLane.Flags.LeftForwardRight); int numFwdConns = 0; int numLeftConns = 0; int numRightConns = 0; for (int i = laneInfo.firstTarget; i <= laneInfo.lastTarget; ++i) { var connectionDir = outLanes[i]; switch (connectionDir) { case NetLane.Flags.Left: numLeftConns += 1; break; case NetLane.Flags.Forward: numFwdConns += 1; break; case NetLane.Flags.Right: numRightConns += 1; break; default: break; } } if (numFwdConns > 0) { minFwdLaneConns = Math.Min(minFwdLaneConns, numFwdConns); maxFwdLaneConns = Math.Max(maxFwdLaneConns, numFwdConns); features.numFwdLanes += 1; } if (numLeftConns > 0) { minLeftLaneConns = Math.Min(minLeftLaneConns, numLeftConns); maxLeftLaneConns = Math.Max(maxLeftLaneConns, numLeftConns); numLeftInLanes += 1; } if (numRightConns > 0) { minRightLaneConns = Math.Min(minRightLaneConns, numRightConns); maxRightLaneConns = Math.Max(maxRightLaneConns, numRightConns); numRightInLanes += 1; } } features.leftConnectionImbalance = Math.Max(0, maxLeftLaneConns - minLeftLaneConns); features.fwdConnectionImbalance = Math.Max(0, maxFwdLaneConns - minFwdLaneConns); features.rightConnectionImbalance = Math.Max(0, maxRightLaneConns - minRightLaneConns); if (numLeftInLanes > 0 && numRightInLanes > 0) { int numLeftOutLanes = 0; int numRightOutLanes = 0; foreach (var outLane in outLanes) { if (outLane == NetLane.Flags.Left) { numLeftOutLanes += 1; } else if (outLane == NetLane.Flags.Right) { numRightOutLanes += 1; } } float leftOutInRatio = (float)numLeftOutLanes / numLeftInLanes; float rightOutInRatio = (float)numRightOutLanes / numRightInLanes; features.leftRightOutInRatioImbalance = Math.Max(leftOutInRatio, rightOutInRatio) / Math.Min(leftOutInRatio, rightOutInRatio); } return(features); }