/* For circles only. */ private VectorNodeStruct CheckAngularDistance(VectorNodeStruct p1, VectorNodeStruct p2) { VectorNodeStruct prevNode = p1; double angDif = NormalizeAngle(prevNode.angle - p2.angle); if (p1 == p2) { angDif = 2 * Math.PI; } /* If the distance between two nodes is too great, we put in an intermediate node inbetween */ while (angDif > m_maxAngDistance) { recursionGuard(); double angle = NormalizeAngle(prevNode.angle - angDif / Math.Ceiling(angDif / m_maxAngDistance)); Vector3 vector = ellipse.VectorAtAbsoluteAngle(angle); ushort newNodeId = NetAccess.CreateNode(centerNodeNetInfo, vector); VectorNodeStruct newNode = new VectorNodeStruct(newNodeId); newNode.angle = angle; ConnectNodes(newNode, prevNode); prevNode = newNode; angDif = NormalizeAngle(prevNode.angle - p2.angle); } return(prevNode); }
/* Sometimes it happens that we split the road too close to another segment. If that occur, the roads do glitch. In that case * we remove one more segment up the road. This method is still glitchy, would need improvement. */ /* intersection - node outside the ellipse */ private bool nextSegmentInfo(ushort intersection, ushort closeSegmentId, out ushort outerNodeId, out Vector3 directions) { NetSegment closeSegment = NetAccess.Segment(closeSegmentId); outerNodeId = 0; directions = new Vector3(0, 0, 0); NetNode node = NetAccess.Node(intersection); int segmentcount = node.CountSegments(); /* If there is an intersection right behind the ellipse, we can't go on as we can merge only segments which are in fact * only one road without an intersection. */ if (segmentcount != 2) { //Debug.Log("Ambiguous node."); return(false); } /*string debugString = "Close segment id: " + closeSegmentId + "; "; * for(int i = 0; i < 8; i++) * { * debugString += node.GetSegment(i) + ", "; * } * Debug.Log(debugString);*/ ushort nextSegmentId = node.GetSegment(0); /* We need the segment that goes away from the ellipse, not the one we already have. */ if (closeSegmentId == nextSegmentId) { //Debug.Log("Taking the other of the two segments. " + node.GetSegment(1)); nextSegmentId = node.GetSegment(1); if (nextSegmentId == 0) { return(false); } } NetSegment nextSegment = NetAccess.Segment(nextSegmentId); nextSegment = NetAccess.Segment(nextSegmentId); outerNodeId = nextSegment.m_startNode; directions = nextSegment.m_startDirection; /* We need the node further away */ if (outerNodeId == intersection) { //Debug.Log("Taking the other of the nodes."); outerNodeId = nextSegment.m_endNode; directions = nextSegment.m_endDirection; if (outerNodeId == 0) { return(false); } } /* After merging the roads, we release the segment and intersection inbetween. When I was debugging this method, I tried to release them after * everything is done. It might not be necessary.*/ ToBeReleasedSegments.Add(nextSegmentId); ToBeReleasedNodes.Add(intersection); return(true); }
public FinalConnector(NetInfo centerNodeNetInfo, EdgeIntersections2 edgeIntersections, Ellipse ellipse, bool insertControllingVertices) { intersections = edgeIntersections?.Intersections ?? new List <VectorNodeStruct>(); m_group = edgeIntersections?.TmpeActionGroup(); this.ellipse = ellipse; pleasenoinfiniterecursion = 0; this.centerNodeNetInfo = centerNodeNetInfo; leftHandTraffic = Singleton <SimulationManager> .instance.m_metaData.m_invertTraffic == SimulationMetaData.MetaBool.True; // We ensure that the segments are not too long. For circles only (with ellipses it would be more difficult) m_maxAngDistance = Math.Min(Math.PI * 25 / ellipse.RadiusMain, Math.PI / 2 + 0.1d); bool isCircle = ellipse.IsCircle(); if (!isCircle && insertControllingVertices) { /* See doc in the method below */ InsertIntermediateNodes(); } /* If the list of edge nodes is empty, we add one default intersection. */ if (isCircle && intersections.Count == 0) { Vector3 defaultIntersection = new Vector3(ellipse.RadiusMain, 0, 0) + ellipse.Center; ushort newNodeId = NetAccess.CreateNode(centerNodeNetInfo, defaultIntersection); intersections.Add(new VectorNodeStruct(newNodeId)); } int count = intersections.Count; foreach (VectorNodeStruct item in intersections) { item.angle = Ellipse.VectorsAngle(item.vector - ellipse.Center); } /* We sort the nodes according to their angles */ intersections.Sort(); /* Goes over all the nodes and conntets each of them to the angulary closest neighbour. (In a given direction) */ for (int i = 0; i < count; i++) { VectorNodeStruct prevNode = intersections[i]; if (isCircle) { prevNode = CheckAngularDistance(intersections[i], intersections[(i + 1) % count]); } ConnectNodes(intersections[(i + 1) % count], prevNode); } if (m_group != null) { ModThreading.Timer(m_group); } }
public ushort Create(NetInfo netInfo) { if (exists) { return(nodeId); } ushort newNodeId = NetAccess.CreateNode(netInfo, vector); nodeId = newNodeId; vector = node.m_position; return(newNodeId); }
/* Turns segments into beziers. */ private List <Bezier2> makeBeziers(List <ushort> netSegmentsIds) { List <Bezier2> beziers = new List <Bezier2>(); for (int i = 0; i < netSegmentsIds.Count; i++) { NetSegment netSegment = NetAccess.Segment(netSegmentsIds[i]); bool smoothStart = (NetAccess.Node(netSegment.m_startNode).m_flags & NetNode.Flags.Middle) != NetNode.Flags.None; bool smoothEnd = (NetAccess.Node(netSegment.m_endNode).m_flags & NetNode.Flags.Middle) != NetNode.Flags.None; Bezier3 bezier = new Bezier3(); bezier.a = NetAccess.Node(netSegment.m_startNode).m_position; bezier.d = NetAccess.Node(netSegment.m_endNode).m_position; NetSegment.CalculateMiddlePoints(bezier.a, netSegment.m_startDirection, bezier.d, netSegment.m_endDirection, smoothStart, smoothEnd, out bezier.b, out bezier.c); beziers.Add(Bezier2.XZ(bezier)); } return(beziers); }
public EdgeIntersections2(GraphTraveller2 traveller, ushort centerNodeId, Ellipse ellipse) { CenterNodeId = centerNodeId; CenterNode = NetAccess.Node(centerNodeId); this.traveller = traveller; this.ellipse = ellipse; if (RoundAboutBuilder.UseOldSnappingAlgorithm.value) { SnappingAlgorithmOld(); } else { SnappingAlgorithmNew(); } ReleaseNodesAndSegments(traveller); }
private void ConnectNodes(VectorNodeStruct vectorNode1, VectorNodeStruct vectorNode2) { bool invert = leftHandTraffic; vectorNode1.Create(centerNodeNetInfo); vectorNode2.Create(centerNodeNetInfo); /* NetNode node1 = GetNode(vectorNode1.nodeId); * NetNode node2 = GetNode(vectorNode2.nodeId);*/ double angle1 = getAbsoluteAngle(vectorNode1.vector); double angle2 = getAbsoluteAngle(vectorNode2.vector); Vector3 vec1 = ellipse.TangentAtAbsoluteAngle(angle1); Vector3 vec2 = ellipse.TangentAtAbsoluteAngle(angle2); vec1.Normalize(); vec2.Normalize(); vec2 = -vec2; /*EllipseTool.Instance.debugDrawVector(10*vec1, vectorNode1.vector); * EllipseTool.Instance.debugDrawVector(10*vec2, vectorNode2.vector);*/ //NetInfo netPrefab = PrefabCollection<NetInfo>.FindLoaded("Oneway Road"); NetInfo netPrefab = UI.UIWindow2.instance.dropDown.Value; ushort newSegmentId = NetAccess.CreateSegment(vectorNode1.nodeId, vectorNode2.nodeId, vec1, vec2, netPrefab, invert, leftHandTraffic, true); /* Sometime in the future ;) */ try { SetupTMPE(newSegmentId); } catch (Exception e) { Debug.LogError(e); } //Debug.Log(string.Format("Building segment between nodes {0}, {1}, bezier scale {2}", node1, node2, scale)); }
private void ReleaseNodesAndSegments(GraphTraveller2 traveller) { foreach (ushort segment in traveller.InnerSegments) { NetAccess.ReleaseSegment(segment); } foreach (ushort segment in traveller.OuterSegments) { NetAccess.ReleaseSegment(segment); } foreach (ushort segment in ToBeReleasedSegments) { NetAccess.ReleaseSegment(segment); } foreach (ushort node in traveller.InnerNodes) { NetAccess.ReleaseNode(node); } foreach (ushort node in ToBeReleasedNodes) { NetAccess.ReleaseNode(node); } }
private void SnappingAlgorithmNew() { //Debug //EllipseTool.Instance.debugDraw = segmentBeziers; List <Bezier2> segmentBeziers = makeBeziers(traveller.OuterSegments); List <Bezier2> ellipseBeziers = traveller.Ellipse.Beziers; List <ushort> processedSegments = new List <ushort>(); /* We find all intersections between roads and ellipse beziers */ for (int i = 0; i < segmentBeziers.Count; i++) { for (int j = 0; j < ellipseBeziers.Count; j++) { if (ellipseBeziers[j].Intersect(segmentBeziers[i], out float t1, out float t2, ITERATIONS)) { if (processedSegments.Contains(traveller.OuterSegments[i])) { continue; } else { processedSegments.Add(traveller.OuterSegments[i]); } //Debug.Log("Segment " + i.ToString() + " intersects ellipse bezier " + j.ToString()); Vector3 intersection = new Vector3(ellipseBeziers[j].Position(t1).x, CenterNode.m_position.y, ellipseBeziers[j].Position(t1).y); segmentBeziers[i].Divide(out Bezier2 segementBezier1, out Bezier2 segementBezier2, t2); Bezier2 outerBezier; Vector2 outerNodePos = new Vector2(NetAccess.Node(traveller.OuterNodes[i]).m_position.x, NetAccess.Node(traveller.OuterNodes[i]).m_position.z); bool invert = false; // outerBezier - the bezier outside the ellipse (not the one inside) if (segementBezier1.Position(0f) == outerNodePos || segementBezier1.Position(1f) == outerNodePos) { //Debug.Log("first is outer"); outerBezier = segementBezier1.Invert(); invert = true; } else if (segementBezier2.Position(0f) == outerNodePos || segementBezier2.Position(1f) == outerNodePos) { //Debug.Log("second is probably outer"); outerBezier = segementBezier2; invert = false; } else { throw new Exception("Error - Failed to determine segment geometry."); } //debug: //EllipseTool.Instance.debugDraw.Add(outerBezier); /* We create a node at the intersection. */ ushort newNodeId = NetAccess.CreateNode(CenterNode.Info, intersection); Intersections.Add(new VectorNodeStruct(newNodeId)); BezierToSegment(outerBezier, traveller.OuterSegments[i], newNodeId, traveller.OuterNodes[i], invert); } } } }
private void BezierToSegment(Bezier2 bezier2, ushort oldSegmentId, ushort startNodeId, ushort endNodeId, bool invert) { NetSegment oldSegment = NetAccess.Segment(oldSegmentId); Vector2 startDirection2d; Vector2 endDirection2d; Vector2 nodePos2d = new Vector2(NetAccess.Node(startNodeId).m_position.x, NetAccess.Node(startNodeId).m_position.z); /*if ( Distance(nodePos2d,bezier2.Position(0f)) < 10e-3d) * { * //0f is on the ellipse * } * else if(Distance(nodePos2d, bezier2.Position(1f)) < 10e-3d) * { * //1f is on the ellipse * bezier2 = bezier2.Invert(); * invert = true; * } * else * { * throw new Exception(string.Format("Error - no intersection of bezier and point. Dist: {0}, {1}",Distance(nodePos2d,bezier2.Position(0f)), Distance(nodePos2d, bezier2.Position(1f)))); * }*/ startDirection2d = bezier2.Tangent(0f); endDirection2d = bezier2.Tangent(1f); Vector3 startDirection = (new Vector3(startDirection2d.x, 0, startDirection2d.y)); Vector3 endDirection = -(new Vector3(endDirection2d.x, 0, endDirection2d.y)); /* Unlike from the old algorithm, we use no padding when looking for the segments. That means the obtained segments can be arbitrarily short. * In that case, we take one more segment away from the ellipse.*/ if (VectorDistance(bezier2.a, bezier2.d) < MIN_BEZIER_LENGTH) { //Debug.Log("Segment is too short. Launching repair mechainsm." + VectorDistance(bezier2.a, bezier2.d)); if (nextSegmentInfo(endNodeId, oldSegmentId, out ushort endNodeIdNew, out Vector3 endDirectionNew)) { endNodeId = endNodeIdNew; endDirection = endDirectionNew; //Debug.Log("The segment length should be " + VectorDistance(GetNode(startNodeId).m_position,GetNode(endNodeId).m_position)); //EllipseTool.Instance.debugDrawPositions.Add(GetNode(endNodeIdNew).m_position); } } // Debug // EllipseTool.Instance.debugDrawVector(20*startDirection, GetNode(startNodeId).m_position); // EllipseTool.Instance.debugDrawVector(20*endDirection, GetNode(endNodeId).m_position); startDirection.Normalize(); endDirection.Normalize(); if (oldSegment.m_flags.IsFlagSet(NetSegment.Flags.Invert)) { invert = !invert; } try { ushort newSegmentId = NetAccess.CreateSegment(startNodeId, endNodeId, startDirection, endDirection, oldSegment.Info, invert); m_group.Actions.Add(new EnteringBlockedJunctionAllowedAction(newSegmentId, true, true)); m_group.Actions.Add(new YieldSignAction(newSegmentId, true)); } catch (Exception e) { UIWindow2.instance.ThrowErrorMsg("The game failed to create one of the road segments."); Debug.LogError(e.ToString()); } }
/* Old algorithm. Originally intended only for circles. From older documentation: */ /* "For now, the intersection isn't created at the exact point where the segment crosses the circle, but rather on the intersection of * the circle and straight line, which goes from origin and ends at outer node of that segment. That could be unfortunately very * inaccurate, as the first note outside the circle could be quite far away". */ private void SnappingAlgorithmOld() { float centerX = CenterNode.m_position.x; float centerY = CenterNode.m_position.y; float centerZ = CenterNode.m_position.z; for (int i = 0; i < traveller.OuterNodes.Count; i++) { NetNode curNode = NetAccess.Node(traveller.OuterNodes[i]); Vector3 circleIntersection = new Vector3(); float directionX = (curNode.m_position.x - centerX) / VectorDistance(CenterNode.m_position, curNode.m_position); float directionZ = (curNode.m_position.z - centerZ) / VectorDistance(CenterNode.m_position, curNode.m_position); float radius = (float)ellipse.RadiusAtAbsoluteAngle(Math.Abs(Ellipse.VectorsAngle(curNode.m_position - ellipse.Center))); if (radius > 10000) { throw new Exception("Algortithm error"); } circleIntersection.x = (directionX * radius + centerX); circleIntersection.y = centerY; circleIntersection.z = (directionZ * radius + centerZ); ushort newNodeId; ushort newSegmentId; newNodeId = NetAccess.CreateNode(CenterNode.Info, circleIntersection); Intersections.Add(new VectorNodeStruct(newNodeId)); //EllipseTool.Instance.debugDrawPositions.Add(Intersections.Last().vector); NetSegment curSegment = NetAccess.Segment(traveller.OuterSegments[i]); /* For now ignoring anything regarding Y coordinate */ //float directionY2 = (GetNode(newNodeId).m_position.y - curNode.m_position.z) / NodeDistance(GetNode(newNodeId), curNode); float directionY2 = 0f; Vector3 startDirection = new Vector3(); startDirection.x = (directionX /** NodeDistance( GetNode( newNodeId ), curNode ) / 2*/); startDirection.y = directionY2; startDirection.z = (directionZ /** NodeDistance(GetNode(newNodeId), curNode) / 2*/); Vector3 endDirection = new Vector3(); endDirection.x = -startDirection.x; endDirection.y = -startDirection.y; endDirection.z = -startDirection.z; bool invert; //Debug.Log(string.Format("same node: {0}, invert: {1}", curSegment.m_startNode == traveller.OuterNodes[i], curSegment.m_flags.IsFlagSet(NetSegment.Flags.Invert))); if (curSegment.m_startNode == traveller.OuterNodes[i] ^ curSegment.m_flags.IsFlagSet(NetSegment.Flags.Invert)) { invert = true; } else { invert = false; } newSegmentId = NetAccess.CreateSegment(newNodeId, traveller.OuterNodes[i], startDirection, endDirection, curSegment.Info, invert); m_group.Actions.Add(new EnteringBlockedJunctionAllowedAction(newSegmentId, true, true)); m_group.Actions.Add(new YieldSignAction(newSegmentId, true)); //Debug.Log(string.Format("Segment and node created... ")); } }