Vector2 GetDirection(Vector2 start, StreetLine toAvoid) { //this version doesn't check for heading to Vector2.zero, //because it has to check for toAvoid if (GS.RChance(diagonalChance)) { Vector2 result = diags.RandomElement(); while (Mathf.Abs(Vector2.Dot(result, (toAvoid.A - toAvoid.B).normalized)) > 0.5) { result = diags.RandomElement(); } return(result); } List <Vector2> options = new List <Vector2>(); for (int i = 0; i < GS.CardinalDirs.Count; i++) { float dot = (toAvoid != null) ? Vector2.Dot(GS.CardinalDirs[i], (toAvoid.B - toAvoid.A).normalized) : 0; if (Mathf.Abs(dot) < 0.1f) { options.Add(GS.CardinalDirs[i]); } } return(options.RandomElement()); }
IntersectionPoint SplitStreetLine(StreetLine line, Vector2 point) { //replaces line with 2 lines with one shared intersection placed.Remove(line); IntersectionPoint inter = new IntersectionPoint(point); placed.Add((line.AOnRim) ? new StreetLine(line.A, inter) : new StreetLine(line.InterPointA, inter)); placed.Add((line.BOnRim) ? new StreetLine(inter, line.B) : new StreetLine(inter, line.InterPointB)); return(inter); }
void PlaceStreetLine(Vector2 start, Vector2 end, StreetLine intersectedAtStart, StreetLine intersectedAtEnd) { IntersectionPoint interStart; IntersectionPoint interEnd; if (intersectedAtStart != null) { if (intersectedAtStart.A == start && !intersectedAtStart.AOnRim) { interStart = intersectedAtStart.InterPointA; } else if (intersectedAtStart.B == start && !intersectedAtStart.BOnRim) { interStart = intersectedAtStart.InterPointB; } else { //the StreetLine intersected at start of the line-to-be has not been split yet, //so we need to split it interStart = SplitStreetLine(intersectedAtStart, start); } if (intersectedAtEnd != null) { interEnd = SplitStreetLine(intersectedAtEnd, end); placed.Add(new StreetLine(interStart, interEnd)); } else { placed.Add(new StreetLine(interStart, end)); } } else if (intersectedAtEnd != null) { interEnd = SplitStreetLine(intersectedAtEnd, end); placed.Add(new StreetLine(start, interEnd)); } else { placed.Add(new StreetLine(start, end)); } }
IEnumerator Generate() { yield return(StartCoroutine(lineGen.GenStreetLines())); yield return(StartCoroutine(lineGen.FillIntersectionPointConnections())); List <StreetLine> streetLines = lineGen.GetPlaced(); List <IntersectionPoint> interPoints = lineGen.GetPlacedInterPoints(); diagnostics.CheckIfIntersectionPointsOverlap(interPoints); yield return(StartCoroutine(lineExpander.ExpandStreetLines(streetLines, interPoints))); //diagnostics.DebugDrawStreetLineCorners(streetLines, 300f); List <Intersection> intersections = lineVerticalizer.ConvertIntersectionPointsToIntersections(interPoints); List <Street> streets = lineVerticalizer.ConvertStreetLinesToStreets(streetLines); for (int i = 0; i < intersections.Count; i++) { intersections[i].ExtractConnected(); } yield return(StartCoroutine(streetMeshGen.GenerateIntersectionMeshes(intersections))); yield return(StartCoroutine(streetMeshGen.GenerateStreetMeshes(streets))); List <RimStreetLine> streetLinesOnRim = StreetLine.ExtractRimStreetLines(streetLines); yield return(StartCoroutine(cityBlockGen.ExtractCityBlocks(intersections, streetLinesOnRim))); yield return(StartCoroutine(cityBlockGen.GenerateCityBlockTerrainMeshes())); //List<CityBlock> blocks = cityBlockGen.Blocks; //cityBlockPurposeDeterminer.DeterminePurposeOfCityBlocks(blocks); //for (int i = 0; i < blocks.Count; i++) //{ // if (cityBlockContentGenerators.ContainsKey(blocks[i].Purpose)) // { // yield return StartCoroutine(cityBlockContentGenerators[blocks[i].Purpose].Generate(blocks[i])); // } //} FinalizeGeneration(); }
public IEnumerator GenStreetLines() { DB.Log("GENERATING STREET LINES", 1); //to keep track of how many streets we create we need a seperate variable, //since placed.Count doesn't reflect the same thing - 1 whole street can be //comprised of many StreetLine objects one after the other with intersections in between //That's because a SreetLine obj represents a street from intersection to intersection, //but in terms of count we care more about those long continous streets int wholeStreetsCount = 0; int tries = 0; float rayRange = rimRadius * 2.1f; while (wholeStreetsCount < desiredStreetCount) { tries += 1; bool genFromExisting = (placed.Count >= thresholdForGeneratingFromExistingLines && GS.RChance(generateFromExistingChance)); Vector2 start, dir; StreetLine existingStartLine = null; if (genFromExisting) { existingStartLine = placed.RandomElement(); start = Vector2.Lerp(existingStartLine.A, existingStartLine.B, Random.Range(0.15f, 0.85f)); dir = GetDirection(start, existingStartLine); } else { start = GS.ROnUnitCircle(rimRadius); dir = GetDirection(start); } RaycastHit hit; Ray ray = new Ray(start.ShiftToV3() + dir.ShiftToV3() * rayRange, -dir.ShiftToV3()); //this raycast has to be reversed because they dont work from within colliders if (!Physics.Raycast(ray, out hit, rayRange, maskRimSphere, QueryTriggerInteraction.Collide)) { continue; //this should never happen, the raycast is set up to always hit } Vector2 end = hit.point.UnShiftToV2(); //end of the whole street List <StreetLineIntersectionResult> potentiallyCrossed = GetIntersectWithStreetLineResults(start, end, placed, existingStartLine); //propagating the whole street forward bool placedAtLeastOne = false; for (int i = 0; i <= potentiallyCrossed.Count; i++) //mind the <= { Vector2 streetLineStart = (i == 0) ? start : potentiallyCrossed[i - 1].intersection; Vector2 streetLineEnd = (i == potentiallyCrossed.Count) ? end : potentiallyCrossed[i].intersection; if (streetLineStart == streetLineEnd) { DB.Log("StreetLine start and end are same."); } //DB.Log($"streetLineStart: {streetLineStart} | streetLineEnd: {streetLineEnd} | dir: {dir} | i: {i}/{potentiallyCrossed.Count}"); if (Vector2.Distance(streetLineStart, streetLineEnd) < minSeparation) { //if the distance is smaller than min separation, it means the potential streetline encounters //another street line and crosses it very near from start point, which would make it difficult later //for placing intersection meshes //we also need a separate check for this, because the regular separation check only takes //into account separation with lines that are nearly paralell //DB.Log("Length too small"); break; } if (!PotentialLineHasCorrectSeparation(streetLineStart, streetLineEnd, placed)) { //we need to check if this potential line isn't to close to other lines that are running nearly paralel to it //to prevent creating narrow city blocks //DB.Log("Separation not correct."); break; } StreetLine lineOnStart = null; if (genFromExisting && i == 0) { lineOnStart = existingStartLine; } else if (i > 0) { lineOnStart = FindStreetLineWithIntersectionAtPoint(streetLineStart, placed); if (lineOnStart == null) { DB.Error("line on start is null"); } } StreetLine lineOnEnd = (i == potentiallyCrossed.Count) ? null : potentiallyCrossed[i].line; PlaceStreetLine(streetLineStart, streetLineEnd, lineOnStart, lineOnEnd); placedAtLeastOne = true; placed.Last().DebugDraw(300f); //yield return new WaitForSeconds(2.5f); if (!GS.RChance(propagateChance)) { break; } } if (placedAtLeastOne) { tries = 0; wholeStreetsCount += 1; } yield return(null); if (tries == maxTriesPerLine) //safety feature { tries = 0; desiredStreetCount -= 1; DB.Log("Max tries reached."); } } }
public StreetLineIntersectionResult(StreetLine line, Vector2 intersection) { this.line = line; this.intersection = intersection; }
List <StreetLineIntersectionResult> GetIntersectWithStreetLineResults(Vector2 start, Vector2 end, List <StreetLine> options, StreetLine ignored) { List <StreetLineIntersectionResult> intersectionResults = new List <StreetLineIntersectionResult>(); Vector2 intersection; for (int i = 0; i < options.Count; i++) { if (ignored != null && ignored == options[i]) { continue; } if (GeoMath.LineSegmentsIntersect(start, end, options[i].A, options[i].B, out intersection)) { intersectionResults.Add(new StreetLineIntersectionResult(options[i], intersection)); } } intersectionResults.Sort((a, b) => (Vector2.SqrMagnitude(start - a.intersection) < Vector2.SqrMagnitude(start - b.intersection)) ? -1 : 1); return(intersectionResults); }
public RimStreetLine(StreetLine line, bool calcAngleFromA) { Line = line; AngularPosCalculatedFromA = calcAngleFromA; AngularPosOnRim = (calcAngleFromA) ? -Vector2.SignedAngle(-Vector2.up, Line.A) : -Vector2.SignedAngle(-Vector2.up, Line.B); }
public RimStreetLine(StreetLine line) { Line = line; AngularPosCalculatedFromA = (Line.AOnRim); AngularPosOnRim = (Line.AOnRim) ? -Vector2.SignedAngle(-Vector2.up, Line.A) : -Vector2.SignedAngle(-Vector2.up, Line.B); }
public void CreateCornersForConnected() { //DB.Log($"Creating corners for Connected (ConnectingStreetLines.Count: {Connected.Count})."); if (Connected.Count < 3 || Connected.Count > 4) { return; } for (int i = 0; i < Connected.Count; i++) { for (int o = 0; o < Connected.Count; o++) { if (o == i) { continue; } //we do this to ensure that those dirs are dirs pointing away from this intersection point and not directly towards it Vector2 iDir = (this == Connected[i].InterPointA) ? Connected[i].B - Connected[i].A : Connected[i].A - Connected[i].B; Vector2 oDir = (this == Connected[o].InterPointA) ? Connected[o].B - Connected[o].A : Connected[o].A - Connected[o].B; iDir = iDir.normalized; oDir = oDir.normalized; if (iDir.SameOrOppositeDir(oDir, 0.01f) && Connected.Count == 3) { //connected are basically continuations of each other, so the corners are in a direction //that is exactly half way between each of them and a line perpendicular to them, // x\ | /x // [i]____\|/____[o] // //but there are of course two perpendicular lines we could pick from, so we check which one doesn't interfere with other //Connected StreetLines, we will always find one that doesn't, because this scenario assumes Connected.Count == 3 Vector2 perp1 = Vector2.Perpendicular(iDir); Vector2 perp2 = -Vector2.Perpendicular(iDir); StreetLine onlyOtherConnected = null; for (int p = 0; p < Connected.Count; p++) { if (p != o && p != i) { onlyOtherConnected = Connected[p]; break; } } //we do this to ensure this dir is the one that points away from this intersection point, not straight towards it Vector2 otherDir = (this == onlyOtherConnected.InterPointA) ? onlyOtherConnected.B - onlyOtherConnected.A : onlyOtherConnected.A - onlyOtherConnected.B; otherDir = otherDir.normalized; float angle1 = Vector2.Angle(perp1, otherDir); float angle2 = Vector2.Angle(perp2, otherDir); Vector2 pickedPerp = (angle1 > angle2) ? perp1 : perp2; Vector2 oCorner = Position + (pickedPerp + oDir) * Connected[o].VirtualWidth * 0.5f; Vector2 iCorner = Position + (pickedPerp + iDir) * Connected[i].VirtualWidth * 0.5f; AddCornerIfAbsent(oCorner); AddCornerIfAbsent(iCorner); Connected[i].AddCornerIfAbsent(iCorner); Connected[o].AddCornerIfAbsent(oCorner); } else if (!iDir.SameOrOppositeDir(oDir, 0.01f)) { Vector2 corner = Position + (iDir * Connected[o].VirtualWidth + oDir * Connected[i].VirtualWidth) * 0.5f; AddCornerIfAbsent(corner); Connected[i].AddCornerIfAbsent(corner); Connected[o].AddCornerIfAbsent(corner); } } } if (Corners.Count != 4) { DB.Error("Invalid number of Corners in IntersectionPoint."); //Debug.DrawRay(Position.ShiftToV3(), Vector3.up * 30f, Color.magenta, 300f); } }