//There are some river configurations that cause problems here, //particularly rivers without endpoints (circular river networks) //or pretty much any time a cell is completely surrounded by rivers. //It's currently thought that this configuration won't arise often //enough to warrant a solution. Circular rivers and many "island" //cells are probably a map generation bug, anyways. public void RefreshRivers() { rivers.Clear(); SectionCanon.RefreshRiverSections(); unassignedSections = new HashSet <RiverSection>(SectionCanon.Sections); while (unassignedSections.Count > 0) { RiverSection unassignedEndpoint = unassignedSections.FirstOrDefault( section => section.HasPreviousEndpoint || section.HasNextEndpoint ); unassignedSections.Remove(unassignedEndpoint); if (unassignedEndpoint == null) { break; } var newRiver = RiverBuilder.BuildRiverFromSection(unassignedEndpoint, unassignedSections); rivers.Add(newRiver.AsReadOnly()); } }
public void SetCurveStatusOfSection(RiverSection section, List <RiverSection> river) { IHexCell center = section.AdjacentCellOne; IHexCell left = Grid.GetNeighbor(section.AdjacentCellOne, section.DirectionFromOne.Previous()); IHexCell nextRight = Grid.GetNeighbor(section.AdjacentCellOne, section.DirectionFromOne.Next()); var centerLeftSection = SectionCanon.GetSectionBetweenCells(center, left); var centerNextRightSection = SectionCanon.GetSectionBetweenCells(center, nextRight); section.PreviousOnInternalCurve = centerLeftSection != null && river.Contains(centerLeftSection); section.NextOnInternalCurve = centerNextRightSection != null && river.Contains(centerNextRightSection); }
//The fact that we're building sections only from the NE, E, and SE //cell edges is very important. A lot of the code here is structured //as it is to deal with this reality. public void RefreshRiverSections() { sections.Clear(); SectionBetweenCells.Clear(); CellEdgeContourCanon.Clear(); foreach (var cell in Grid.Cells) { for (HexDirection direction = HexDirection.NE; direction <= HexDirection.SE; direction++) { if (RiverCanon.HasRiverAlongEdge(cell, direction)) { var neighbor = Grid.GetNeighbor(cell, direction); Vector3 start = cell.AbsolutePosition + RenderConfig.GetFirstCorner(direction); Vector3 end = cell.AbsolutePosition + RenderConfig.GetSecondCorner(direction); RiverFlow flow = RiverCanon.GetFlowOfRiverAtEdge(cell, direction); //Our control points need to operate differently if we're at endpoints, //and both ControlOne and ControlTwo might have alternate behavior. //We need to check both ends of the section for endpoints bool previousCellRiver = RiverCanon.HasRiverAlongEdge(cell, direction.Previous()); bool previousNeighborRiver = RiverCanon.HasRiverAlongEdge(neighbor, direction.Previous2()); bool hasPreviousEndpoint = !previousCellRiver && !previousNeighborRiver; bool hasNextEndpoint = !RiverCanon.HasRiverAlongEdge(cell, direction.Next()) && !RiverCanon.HasRiverAlongEdge(neighbor, direction.Next2()); var newRiverSection = new RiverSection() { AdjacentCellOne = cell, AdjacentCellTwo = neighbor, DirectionFromOne = direction, Start = start, End = end, FlowFromOne = flow, HasPreviousEndpoint = hasPreviousEndpoint, HasNextEndpoint = hasNextEndpoint }; SectionBetweenCells[new Tuple <IHexCell, IHexCell>(cell, neighbor)] = newRiverSection; sections.Add(newRiverSection); } } } }
public bool IsNeighborOf(RiverSection thisSection, RiverSection otherSection) { if (otherSection == null) { return(false); } IHexCell thisCenter = thisSection.AdjacentCellOne; IHexCell thisLeft = Grid.GetNeighbor(thisSection.AdjacentCellOne, thisSection.DirectionFromOne.Previous()); IHexCell thisRight = thisSection.AdjacentCellTwo; IHexCell thisNextRight = Grid.GetNeighbor(thisSection.AdjacentCellOne, thisSection.DirectionFromOne.Next()); return((thisLeft != null && GetSectionBetweenCells(thisCenter, thisLeft) == otherSection) || (thisLeft != null && GetSectionBetweenCells(thisLeft, thisRight) == otherSection) || (thisNextRight != null && GetSectionBetweenCells(thisCenter, thisNextRight) == otherSection) || (thisNextRight != null && GetSectionBetweenCells(thisRight, thisNextRight) == otherSection)); }
public void RationalizeRiverContoursInCorner(IHexCell center, HexDirection direction) { IHexCell left = Grid.GetNeighbor(center, direction.Previous()); //Contours will only fall short or overlap when we're at a river confluence, //so we can save ourselves time by checking for that if (left != null && RiverCanon.HasRiverAlongEdge(center, direction.Previous()) && RiverCanon.HasRiverAlongEdge(center, direction) && RiverCanon.HasRiverAlongEdge(left, direction.Next()) ) { IHexCell right = Grid.GetNeighbor(center, direction); RiverSection centerLeftSection = RiverSectionCanon.GetSectionBetweenCells(center, left); RiverSection centerRightSection = RiverSectionCanon.GetSectionBetweenCells(center, right); bool shouldCullContours = true; //Rationalizing contours also doesn't need to occur between segments //that are adjacent segments of the same river, which would put us on //the inside of a river curve. So we make some checks to exclude that //possibility, as well foreach (var river in RiverAssemblyCanon.Rivers) { int leftIndex = river.IndexOf(centerLeftSection); int rightIndex = river.IndexOf(centerRightSection); bool areAdjacentInRiver = ((leftIndex + 1) == rightIndex) || ((rightIndex + 1) == leftIndex); if (leftIndex != -1 && rightIndex != -1 && areAdjacentInRiver) { shouldCullContours = false; break; } } if (shouldCullContours) { ContourRationalizer.RationalizeCellContours(center, direction); } } }
public bool AreSectionFlowsCongruous(RiverSection thisSection, RiverSection otherSection) { if (thisSection.DirectionFromOne == HexDirection.NE) { switch (otherSection.DirectionFromOne) { case HexDirection.NE: return(thisSection.FlowFromOne != otherSection.FlowFromOne); case HexDirection.E: return(thisSection.FlowFromOne == otherSection.FlowFromOne); case HexDirection.SE: return(thisSection.FlowFromOne != otherSection.FlowFromOne); } } else if (thisSection.DirectionFromOne == HexDirection.E) { switch (otherSection.DirectionFromOne) { case HexDirection.NE: return(thisSection.FlowFromOne == otherSection.FlowFromOne); case HexDirection.E: return(false); case HexDirection.SE: return(thisSection.FlowFromOne == otherSection.FlowFromOne); } } else if (thisSection.DirectionFromOne == HexDirection.SE) { switch (otherSection.DirectionFromOne) { case HexDirection.NE: return(thisSection.FlowFromOne != otherSection.FlowFromOne); case HexDirection.E: return(thisSection.FlowFromOne == otherSection.FlowFromOne); case HexDirection.SE: return(thisSection.FlowFromOne != otherSection.FlowFromOne); } } return(false); }
public RiverSection GetNextActiveSectionForRiver( RiverSection activeSection, RiverSection lastSection, HashSet <RiverSection> unassignedSections ) { IHexCell center = activeSection.AdjacentCellOne; IHexCell left = Grid.GetNeighbor(activeSection.AdjacentCellOne, activeSection.DirectionFromOne.Previous()); IHexCell right = activeSection.AdjacentCellTwo; IHexCell nextRight = Grid.GetNeighbor(activeSection.AdjacentCellOne, activeSection.DirectionFromOne.Next()); RiverSection centerLeftSection = SectionCanon.GetSectionBetweenCells(center, left); //For a section to be a valid next-step, it must be non-null and unassigned. //But we also don't want to grab a river adjacent to the previous river, //since this could cause weird behavior (go up a river, and then immediately //back down it). We thus forbid the acquisition of such rivers. //We also need to take flow into account, since it's not valid for a river //to suddenly flip its direction if (centerLeftSection != null && unassignedSections.Contains(centerLeftSection) && !SectionCanon.IsNeighborOf(centerLeftSection, lastSection) && SectionCanon.AreSectionFlowsCongruous(activeSection, centerLeftSection) ) { return(centerLeftSection); } RiverSection leftRightSection = SectionCanon.GetSectionBetweenCells(left, right); if (leftRightSection != null && unassignedSections.Contains(leftRightSection) && !SectionCanon.IsNeighborOf(leftRightSection, lastSection) && SectionCanon.AreSectionFlowsCongruous(activeSection, leftRightSection) ) { return(leftRightSection); } RiverSection centerNextRightSection = SectionCanon.GetSectionBetweenCells(center, nextRight); if (centerNextRightSection != null && unassignedSections.Contains(centerNextRightSection) && !SectionCanon.IsNeighborOf(centerNextRightSection, lastSection) && SectionCanon.AreSectionFlowsCongruous(activeSection, centerNextRightSection) ) { return(centerNextRightSection); } RiverSection rightNextRightSection = SectionCanon.GetSectionBetweenCells(right, nextRight); if (rightNextRightSection != null && unassignedSections.Contains(rightNextRightSection) && !SectionCanon.IsNeighborOf(rightNextRightSection, lastSection) && SectionCanon.AreSectionFlowsCongruous(activeSection, rightNextRightSection) ) { return(rightNextRightSection); } return(null); }
private void AssembleRiverSplines() { RiverAssemblyCanon.RefreshRivers(); foreach (var river in RiverAssemblyCanon.Rivers) { RiverSection section = river[0]; var centerSpline = new BezierSpline(section.FlowFromOne == RiverFlow.Clockwise ? section.Start : section.End); Vector3 centerToV1Direction, centerToV4Direction; Vector3 v1Tangent, v4Tangent; Vector3 controlOne, controlTwo; Vector3 v1, v4; bool isV1Internal, isV4Internal; bool isControlOneNegative, isControlTwoNegative; for (int i = 0; i < river.Count; i++) { section = river[i]; if (section.FlowFromOne == RiverFlow.Clockwise) { v1 = section.Start; v4 = section.End; isV1Internal = section.PreviousOnInternalCurve; isV4Internal = section.NextOnInternalCurve; } else { v1 = section.End; v4 = section.Start; isV1Internal = section.NextOnInternalCurve; isV4Internal = section.PreviousOnInternalCurve; } if (isV1Internal) { centerToV1Direction = (v1 - section.AdjacentCellOne.AbsolutePosition).normalized; } else { centerToV1Direction = (v1 - section.AdjacentCellTwo.AbsolutePosition).normalized; } if (isV4Internal) { centerToV4Direction = (v4 - section.AdjacentCellOne.AbsolutePosition).normalized; } else { centerToV4Direction = (v4 - section.AdjacentCellTwo.AbsolutePosition).normalized; } isControlOneNegative = (section.FlowFromOne == RiverFlow.Clockwise && isV1Internal) || (section.FlowFromOne == RiverFlow.Counterclockwise && !isV1Internal); isControlTwoNegative = (section.FlowFromOne == RiverFlow.Clockwise && !isV4Internal) || (section.FlowFromOne == RiverFlow.Counterclockwise && isV4Internal); v1Tangent = new Vector3(-centerToV1Direction.z, centerToV1Direction.y, centerToV1Direction.x); v4Tangent = new Vector3(-centerToV4Direction.z, centerToV4Direction.y, centerToV4Direction.x); controlOne = v1 + v1Tangent * RenderConfig.RiverCurveStrength * (isControlOneNegative ? -1f : 1f); controlTwo = v4 + v4Tangent * RenderConfig.RiverCurveStrength * (isControlTwoNegative ? -1f : 1f); centerSpline.AddCubicCurve(controlOne, controlTwo, v4); } var newRiverSpline = new RiverSpline() { CenterSpline = centerSpline, WesternCells = river.Select(riverSection => riverSection.AdjacentCellOne).ToList(), EasternCells = river.Select(riverSection => riverSection.AdjacentCellTwo).ToList(), Directions = river.Select(riverSection => riverSection.DirectionFromOne).ToList(), Flows = river.Select(riverSection => riverSection.FlowFromOne).ToList() }; RiverSplines.Add(newRiverSpline); } }
public List <RiverSection> BuildRiverFromSection(RiverSection startingSection, HashSet <RiverSection> unassignedRiverSections) { if (startingSection == null) { throw new ArgumentNullException("startingSection"); } var newRiver = new List <RiverSection>(); if (startingSection.HasPreviousEndpoint && startingSection.HasNextEndpoint) { newRiver.Add(startingSection); unassignedRiverSections.Remove(startingSection); } else { RiverSection lastSection = null; RiverSection activeSection = startingSection; do { newRiver.Add(activeSection); unassignedRiverSections.Remove(activeSection); RiverSection oldActiveSection = activeSection; activeSection = RiverBuilderUtilities.GetNextActiveSectionForRiver(activeSection, lastSection, unassignedRiverSections); lastSection = oldActiveSection; }while(activeSection != null && !activeSection.HasPreviousEndpoint && !activeSection.HasNextEndpoint); if (activeSection != null) { newRiver.Add(activeSection); unassignedRiverSections.Remove(activeSection); } //Our construction guarantees that we grabbed at least one //valid endpoint to start with. But we don't know if we grabbed //the upriver endpoint or the downriver one first. If we grabbed //the downriver one, we need to reverse the whole river. if ((startingSection.HasPreviousEndpoint && startingSection.FlowFromOne == RiverFlow.Counterclockwise) || (startingSection.HasNextEndpoint && startingSection.FlowFromOne == RiverFlow.Clockwise) ) { newRiver.Reverse(); } } //Control point calculations vary based on whether the section is //on the inside or the outside of a curve. For straight rivers //(which contain a single segment) and endpoints this construction //causes an S shape to form, which is considered a reasonable result foreach (var section in newRiver) { RiverBuilderUtilities.SetCurveStatusOfSection(section, newRiver); } return(newRiver); }