Skip to content

Commit

Permalink
Merge pull request #755 from kianzarrin/567-dedicated-turning-phase2
Browse files Browse the repository at this point in the history
fixes #567 dedicated turning lanes now has a state-machine of 2-states:
- 2 lanes: dedicated far turn | dedicated near turn
- 3+ lanes: favor left then forward | favor forward then left
- car+bus lanes: mix bus and car lanes | calculate bus arrows and car arrows separately

Notes:
- I moved code to dedicated UTIL class.
- I polished the code to use the same code path for near and far turns reducing code complexity.
  • Loading branch information
kianzarrin committed Apr 14, 2020
2 parents 8b063c0 + 84e45c7 commit c680381
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 326 deletions.
290 changes: 0 additions & 290 deletions TLM/TLM/Manager/Impl/LaneArrowManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,295 +310,5 @@ public static ICustomDataManager<string> AsLaneFlagsDM() {
public static ICustomDataManager<List<Configuration.LaneArrowData>> AsLaneArrowsDM() {
return Instance;
}

/// <summary>
/// returns the number of all target lanes from input segment toward the secified direction.
/// </summary>
private static int CountTargetLanesTowardDirection(ushort segmentId, ushort nodeId, ArrowDirection dir) {
int count = 0;
ref NetSegment seg = ref Singleton<NetManager>.instance.m_segments.m_buffer[segmentId];
bool startNode = seg.m_startNode == nodeId;
IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager;
ExtSegmentEnd segEnd = segEndMan.ExtSegmentEnds[segEndMan.GetIndex(segmentId, startNode)];

LaneArrowManager.Instance.Services.NetService.IterateNodeSegments(
nodeId,
(ushort otherSegmentId, ref NetSegment otherSeg) => {
ArrowDirection dir2 = segEndMan.GetDirection(ref segEnd, otherSegmentId);
if (dir == dir2) {
int forward = 0, backward = 0;
otherSeg.CountLanes(
otherSegmentId,
NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle,
VehicleInfo.VehicleType.Car,
ref forward,
ref backward);
bool startNode2 = otherSeg.m_startNode == nodeId;
//xor because inverting 2 times is redundant.
if (startNode2) {
count += forward;
} else {
count += backward;
}
Log._Debug(
$"dir={dir} startNode={startNode} segmentId={segmentId}\n" +
$"startNode2={startNode2} forward={forward} backward={backward} count={count}");
}
return true;
});
return count;
}

public static class SeparateTurningLanes{
/// <summary>
/// separates turning lanes for all segments attached to nodeId
/// </summary>
public static void SeparateNode(ushort nodeId, out SetLaneArrow_Result res ) {
NetNode node = Singleton<NetManager>.instance.m_nodes.m_buffer[nodeId];
if (nodeId == 0) {
res = SetLaneArrow_Result.Invalid;
return;
}
if ((node.m_flags & NetNode.Flags.Created) == NetNode.Flags.None) {
res = SetLaneArrow_Result.Invalid;
return;
}
if (LaneConnectionManager.Instance.HasNodeConnections(nodeId)) {
res = SetLaneArrow_Result.LaneConnection;
return;
}
if (Options.highwayRules && ExtNodeManager.JunctionHasHighwayRules(nodeId)) {
res = SetLaneArrow_Result.HighwayArrows;
return;
}
res = SetLaneArrow_Result.Success;

for (int i = 0; i < 8; i++) {
ushort segmentId = Singleton<NetManager>.instance.m_nodes.m_buffer[nodeId].GetSegment(i);
if (segmentId == 0) {
continue;
}

SeparateSegmentLanes(segmentId, nodeId, out res);
}
Debug.Assert(res == SetLaneArrow_Result.Success);
}

public static SetLaneArrow_Result CanChangeLanes(ushort segmentId, ushort nodeId) {
if (segmentId == 0 || nodeId == 0) {
return SetLaneArrow_Result.Invalid;
}
if (Options.highwayRules && ExtNodeManager.JunctionHasHighwayRules(nodeId)) {
return SetLaneArrow_Result.HighwayArrows;
}

ref NetSegment seg = ref Singleton<NetManager>.instance.m_segments.m_buffer[segmentId];
bool startNode = seg.m_startNode == nodeId;
//list of outgoing lanes from current segment to current node.
IList<LanePos> laneList =
Constants.ServiceFactory.NetService.GetSortedLanes(
segmentId,
ref seg,
startNode,
LaneArrowManager.LANE_TYPES,
LaneArrowManager.VEHICLE_TYPES,
true
);
int srcLaneCount = laneList.Count();
for (int i = 0; i < srcLaneCount; ++i) {
if (LaneConnectionManager.Instance.HasConnections(laneList[i].laneId, startNode)) {
return SetLaneArrow_Result.LaneConnection; ;
}
}

return SetLaneArrow_Result.Success;
}

/// <summary>
/// separates turning lanes for the input segment on the input node.
/// </summary>
public static void SeparateSegmentLanes(ushort segmentId, ushort nodeId, out SetLaneArrow_Result res) {
res = CanChangeLanes(segmentId,nodeId);
if(res != SetLaneArrow_Result.Success) {
return;
}
res = SetLaneArrow_Result.Success;

ref NetSegment seg = ref Singleton<NetManager>.instance.m_segments.m_buffer[segmentId];
bool startNode = seg.m_startNode == nodeId;

//list of outgoing lanes from current segment to current node.
IList<LanePos> laneList =
Constants.ServiceFactory.NetService.GetSortedLanes(
segmentId,
ref seg,
startNode,
LaneArrowManager.LANE_TYPES,
LaneArrowManager.VEHICLE_TYPES,
true
);
int srcLaneCount = laneList.Count();
if (srcLaneCount <= 1) {
return;
}

int leftLanesCount = LaneArrowManager.CountTargetLanesTowardDirection(segmentId, nodeId, ArrowDirection.Left);
int rightLanesCount = LaneArrowManager.CountTargetLanesTowardDirection(segmentId, nodeId, ArrowDirection.Right);
int forwardLanesCount = LaneArrowManager.CountTargetLanesTowardDirection(segmentId, nodeId, ArrowDirection.Forward);
int totalLaneCount = leftLanesCount + forwardLanesCount + rightLanesCount;
int numdirs = Convert.ToInt32(leftLanesCount > 0) + Convert.ToInt32(rightLanesCount > 0) + Convert.ToInt32(forwardLanesCount > 0);

Log._Debug($"SeparateSegmentLanes: totalLaneCount {totalLaneCount} | numdirs = {numdirs} | outgoingLaneCount = {srcLaneCount}");

if (numdirs < 2) {
return; // no junction
}
bool lht = LaneArrowManager.Instance.Services.SimulationService.TrafficDrivesOnLeft;

if (srcLaneCount == 2 && numdirs == 3) {
if (!lht) {
LaneArrowManager.Instance.SetLaneArrows(laneList[0].laneId, LaneArrows.LeftForward);
LaneArrowManager.Instance.SetLaneArrows(laneList[1].laneId, LaneArrows.Right);
}else {
LaneArrowManager.Instance.SetLaneArrows(laneList[1].laneId, LaneArrows.ForwardRight);
LaneArrowManager.Instance.SetLaneArrows(laneList[0].laneId, LaneArrows.Left);
}
return;
}

int l = 0, f = 0, r = 0;
if (numdirs == 2) {
if (!lht) {
//if traffic drives on right, then favour the more difficult left turns.
if (leftLanesCount == 0) {
DistributeLanes2(srcLaneCount, forwardLanesCount, rightLanesCount, out f, out r);
} else if (rightLanesCount == 0) {
DistributeLanes2(srcLaneCount, leftLanesCount, forwardLanesCount, out l, out f);
} else {
//forwarLanesCount == 0
DistributeLanes2(srcLaneCount, leftLanesCount, rightLanesCount, out l, out r);
}
} else {
//if traffic drives on left, then favour the more difficult right turns.
if (leftLanesCount == 0) {
DistributeLanes2(srcLaneCount, rightLanesCount, forwardLanesCount, out r, out f);
} else if (rightLanesCount == 0) {
DistributeLanes2(srcLaneCount, forwardLanesCount, leftLanesCount, out f, out l);
} else {
//forwarLanesCount == 0
DistributeLanes2(srcLaneCount, rightLanesCount, leftLanesCount, out r, out l);
}
}
} else {
Debug.Assert(numdirs == 3 && srcLaneCount >= 3);
if (!lht) {
DistributeLanes3(srcLaneCount, leftLanesCount, forwardLanesCount, rightLanesCount, out l, out f, out r);
} else {
DistributeLanes3(srcLaneCount, rightLanesCount, forwardLanesCount, leftLanesCount, out r, out f, out l);
}

}
//assign lanes
Log._Debug($"SeparateSegmentLanes: leftLanesCount {leftLanesCount} | forwardLanesCount {forwardLanesCount} | rightLanesCount {rightLanesCount}");
Log._Debug($"SeparateSegmentLanes: l {l} | f {f} | r {r}");

for (var i = 0; i < laneList.Count; i++) {
var flags = (NetLane.Flags)Singleton<NetManager>.instance.m_lanes.m_buffer[laneList[i].laneId].m_flags;

LaneArrows arrow = LaneArrows.None;
if (i < l) {
arrow = LaneArrows.Left;
} else if (l <= i && i < l + f) {
arrow = LaneArrows.Forward;
} else {
arrow = LaneArrows.Right;
}
LaneArrowManager.Instance.SetLaneArrows(laneList[i].laneId, arrow);
}
}

/// <summary>
/// calculates number of lanes in each direction such that the number of
/// turning lanes are in porportion to the size of the roads they are turning into
/// in other words calculate x and y such that a/b = x/y and x+y=total and x>=1 and y>=1.
/// x is favoured over y (may get more lanes)
/// </summary>
/// <param name="total"> number of source lanes</param>
/// <param name="a">number of target lanes in one direction</param>
/// <param name="b">number of target lanes in the other direction</param>
/// <param name="x">number of source lanes turning toward the first direction. </param>
/// <param name="y">number of the source lanes turning toward the second direction</param>
private static void DistributeLanes2(int total, int a, int b, out int x, out int y) {
/* x+y = total
* a/b = x/y
* y = total*b/(a+b)
* x = total - y
*/
y = (total * b) / (a + b); //floor y to favour x
if (y == 0) {
y = 1;
}
x = total - y;
}

/// <summary>
/// helper function to makes sure at least one source lane is assigned to every direction.
/// if x has zero lanes, it borrows lanes from y and z such that x+y+z remains the same.
/// </summary>
private static int AvoidZero3_Helper(int x, ref int y, ref int z) {
if (x == 0) {
x = 1;
if (y > z) {
--y;
} else {
--z;
}
}
return x;
}

/// <summary>
/// calculates number of lanes in each direction such that the number of
/// turning lanes are in porportion to the size of the roads they are turning into
/// x is favoured over y. y is favoured over z.
/// </summary>
/// <param name="total"> number of source lanes</param>
/// <param name="a">number of target lanes leftward </param>
/// <param name="b">number of target lanes forward</param>
/// <param name="c">number of target lanes right-ward</param>
/// <param name="x">number of source lanes leftward. </param>
/// <param name="y">number of the source lanes forward</param>
/// <param name="z">number of the source lanes right-ward</param>
private static void DistributeLanes3(int total, int a, int b, int c, out int x, out int y, out int z) {
//favour: x then y
float div = (float)(a + b + c) / (float)total;
x = (int)Math.Floor((float)a / div);
y = (int)Math.Floor((float)b / div);
z = (int)Math.Floor((float)c / div);
int rem = total - x - y - z;
switch (rem) {
case 3:
z++;
y++;
x++;
break;
case 2:
y++;
x++;
break;
case 1:
x++;
break;
case 0:
break;
default:
Log.Error($"rem = {rem} : expected rem <= 3");
break;
}
x = AvoidZero3_Helper(x, ref y, ref z);
y = AvoidZero3_Helper(y, ref x, ref z);
z = AvoidZero3_Helper(z, ref x, ref y);
}
}
}
}
1 change: 1 addition & 0 deletions TLM/TLM/TLM.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@
<Compile Include="Util\GenericFsm.cs" />
<Compile Include="Util\GeometryUtil.cs" />
<Compile Include="Util\PriorityRoad.cs" />
<Compile Include="Util\SeparateTurningLanesUtil.cs" />
<Compile Include="Util\Shortcuts.cs" />
<Compile Include="Util\FloatUtil.cs" />
<Compile Include="Util\GenericObservable.cs" />
Expand Down
44 changes: 24 additions & 20 deletions TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ namespace TrafficManager.UI.SubTools.LaneArrows {
/// LaneArrow Tool creates ToolWindow for lane arrow buttons.
/// </summary>
public class LaneArrowTool : TrafficManagerSubTool {
const bool DEFAULT_ALT_MODE = true;
private bool alternativeMode_ = DEFAULT_ALT_MODE;
private int framesSeparateTurningLanesModeActivated = 0;
bool SeparateSegmentLanesModifierIsPressed => AltIsPressed;
bool SeparateNodeLanesModifierIsPressed => ControlIsPressed;

/// <summary>
/// Finite State machine for the tool. Represents current UI state for Lane Arrows.
/// </summary>
Expand Down Expand Up @@ -323,27 +329,29 @@ private void OnToolLeftClick_Select() {
return;
}

bool ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
bool altDown = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
if (Time.frameCount - framesSeparateTurningLanesModeActivated > 80) {
// the mode resets after 2 seconds.
alternativeMode_ = DEFAULT_ALT_MODE;
}

if (altDown) {
// Holding Alt will set separate lanes for selected segment
LaneArrowManager.SeparateTurningLanes.SeparateSegmentLanes(
HoveredSegmentId,
HoveredNodeId,
out var res);
if (SeparateSegmentLanesModifierIsPressed) {
SeparateTurningLanesUtil.SeparateSegmentLanes(
HoveredSegmentId, HoveredNodeId, out var res, alternativeMode: alternativeMode_);
InformUserAboutPossibleFailure(res);
} else if (ctrlDown) {
// Holding Ctrl will set separate lanes for node
LaneArrowManager.SeparateTurningLanes.SeparateNode(
HoveredNodeId,
out var res);
} else if (SeparateNodeLanesModifierIsPressed) {
SeparateTurningLanesUtil.SeparateNode(HoveredNodeId, out var res, alternativeMode: alternativeMode_);
InformUserAboutPossibleFailure(res);
} else if (HasHoverLaneArrows()) {
SelectedSegmentId = HoveredSegmentId;
SelectedNodeId = HoveredNodeId;
alternativeMode_ = DEFAULT_ALT_MODE;
fsm_.SendTrigger(Trigger.SegmentClick);
}

if (SeparateSegmentLanesModifierIsPressed || SeparateNodeLanesModifierIsPressed) {
framesSeparateTurningLanesModeActivated = Time.frameCount;
alternativeMode_ = !alternativeMode_;
}
}

/// <summary>Called from the Main Tool when right mouse button clicked.</summary>
Expand Down Expand Up @@ -426,8 +434,7 @@ private bool HasSegmentEndLaneArrows(ushort segmentId, ushort nodeId) {

protected override ushort HoveredNodeId {
get {
bool ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
if (ctrlDown) {
if (SeparateNodeLanesModifierIsPressed) {
// When control is down, we are selecting node.
return base.HoveredNodeId;
}
Expand Down Expand Up @@ -486,15 +493,12 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
private void RenderOverlay_Select(RenderManager.CameraInfo cameraInfo) {
NetManager netManager = Singleton<NetManager>.instance;

bool ctrlDown = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);

// If CTRL is held, and hovered something: Draw hovered node
if (ctrlDown && HoveredNodeId != 0) {
if (SeparateNodeLanesModifierIsPressed && HoveredNodeId != 0) {
MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0));
return;
}

bool altDown = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
bool leftMouseDown = Input.GetMouseButton(0);

// Log._Debug($"LaneArrow Overlay: {HoveredNodeId} {HoveredSegmentId} {SelectedNodeId} {SelectedSegmentId}");
Expand All @@ -515,7 +519,7 @@ private void RenderOverlay_Select(RenderManager.CameraInfo cameraInfo) {
{
bool bStartNode = (bool)Constants.ServiceFactory.NetService.IsStartNode(HoveredSegmentId, HoveredNodeId);
Color color = MainTool.GetToolColor(leftMouseDown, false);
bool alpha = !altDown;
bool alpha = !SeparateSegmentLanesModifierIsPressed;
DrawSegmentEnd(cameraInfo, HoveredSegmentId, bStartNode, color, alpha);
}
}
Expand Down
Loading

0 comments on commit c680381

Please sign in to comment.