/// <summary> /// Get a position and angle along the biome mask's edge. /// </summary> /// <param name="position"></param> /// <param name="angle"></param> private void GetRandomBiomeEdgePosition(out Vector3 position, out float angle) { BiomeMaskArea mask = editor.extension.lineSettings.biomeMaskArea; // parameter consistency check if (mask == null) { Debug.LogError("No mask defined"); position = Vector3.zero; angle = 0; return; } List <Vector3> positions = BiomeMaskUtils.GetPositions(mask); // sort clockwise, so that the pick algorithm works // if this were counterclockwise, then the angle would make the lines face inwards PolygonUtils.SortClockWise(positions); // get from node index int nodeIndexFrom = Random.Range(0, positions.Count); // note: int is exclusive last // get to node index, consider overlap int nodeIndexTo = nodeIndexFrom + 1; if (nodeIndexTo >= mask.Nodes.Count) { nodeIndexTo = 0; } // get nodes Vector3 positionFrom = mask.transform.position + positions[nodeIndexFrom]; Vector3 positionTo = mask.transform.position + positions[nodeIndexTo]; // having the lines flip inwards into the biome is just a matter of changing the access order of the nodes // leaving this here, maybe we find a use case later bool flipAngle = editor.extension.lineSettings.attachedAngleFlip; if (flipAngle) { Vector3 tmp = positionFrom; positionFrom = positionTo; positionTo = tmp; } float distance = (positionTo - positionFrom).magnitude; Vector3 direction = (positionTo - positionFrom).normalized; // the position along the edge. 0=from, 0.5=center, 1=to float relativePosition = Random.Range(0f, 1f); // calculate the position position = positionFrom + direction * distance * relativePosition; // calculate the angle 90 degrees to the from-to points and convert to degrees angle = Mathf.Atan2(positionTo.z - positionFrom.z, positionTo.x - positionFrom.x) * Mathf.Rad2Deg; }
private void CreateRoads(Bounds bounds) { // not too close to the bounds; the easy roads spline might move the road outside bounds.size *= 0.9f; Vector2[] clipBounds = new Vector2[] { new Vector2(bounds.center.x - bounds.extents.x, bounds.center.z - bounds.extents.z), new Vector2(bounds.center.x + bounds.extents.x, bounds.center.z - bounds.extents.z), new Vector2(bounds.center.x + bounds.extents.x, bounds.center.z + bounds.extents.z), new Vector2(bounds.center.x - bounds.extents.x, bounds.center.z + bounds.extents.z), }; int pointCount = editor.extension.voronoiSettings.pointCount; List <Vector3> maskNodes = CreateRoad(bounds, pointCount, clipBounds); List <Vector3> nodes = ShapeCreator.CreateRandomShape(maskNodes, // editor.extension.shapeSettings.RandomConvexity, // editor.extension.shapeSettings.keepOriginalPoints, // editor.extension.shapeSettings.RandomPointsCount, // editor.extension.shapeSettings.randomAngle, // editor.extension.shapeSettings.douglasPeuckerReductionTolerance); PolygonUtils.SortClockWise(nodes); if (smoothEnabled.boolValue) { List <Vector2> positionsXY = nodes.ConvertAll(item => new Vector2(item.x, item.z)); positionsXY = getCurveSmoothingChaikin(positionsXY, 0.5f, 0); nodes = positionsXY.ConvertAll(item => new Vector3(item.x, 0, item.y)); } nodes = AlignToTerrainHeight(nodes); // remove nodes that are too close to each other RemoveNodes(nodes, minDistance.floatValue); // set ER markers erRoad.AddMarkers(nodes.ToArray()); // set closed track erRoad.ClosedTrack(closedTrack.boolValue); }
/// <summary> /// Modifies a polygon and creates a random shape. /// /// Algorithm: /// /// * create a new polygon, subdivided this way: /// + get center of polygon /// + create a list of angles for iteration /// + iterate through the angles, create a line using the angle and find the intersection with a line of the polygon /// * iterate through the subdivided polygon /// + move the vertex towards the center /// + consider previous vertex in order to not move in too large steps /// </summary> public static List <Vector3> CreateRandomShape(List <Vector3> polygon, float ellipseRelaxationFactor, int angleStepCount, bool randomAngleMovement, bool keepOriginalShape, float douglasPeuckerReductionTolerance) { Vector3 meanVector = PolygonUtils.GetMeanVector(polygon); float length = meanVector.magnitude * 2; #region create angles // get the list of angles to step through List <float> angleRadList = CreateAngleList(angleStepCount, randomAngleMovement); #endregion create angles #region create new polygon using angles List <Vector3> subdividedPolygon = new List <Vector3>(); // add existing points in order to keep the general voronoi shape if (keepOriginalShape) { subdividedPolygon.AddRange(polygon); } for (int i = 0; i < angleRadList.Count; i++) { // convert angle from deg to rad float angle = angleRadList[i]; float x = meanVector.x + length * Mathf.Cos(angle); float z = meanVector.z + length * Mathf.Sin(angle); // position on the ellipse Vector3 line1PositionA = meanVector; Vector3 line1PositionB = new Vector3(x, 0, z); for (var j = 0; j < polygon.Count; j++) { int currIndex = j; int nextIndex = j + 1; if (nextIndex == polygon.Count) { nextIndex = 0; } Vector3 line2PositionA = new Vector3(polygon[currIndex].x, 0, polygon[currIndex].z); Vector3 line2PositionB = new Vector3(polygon[nextIndex].x, 0, polygon[nextIndex].z); Vector3 intersection = Vector3.zero; // try and find an intersection. if one is found, add the point to the list if (PolygonUtils.LineSegmentsIntersection(line1PositionA, line1PositionB, line2PositionA, line2PositionB, out intersection)) { subdividedPolygon.Add(intersection); break; } } } // sort again PolygonUtils.SortClockWise(subdividedPolygon); #endregion create new polygon using angles #region create new polygon using the intersections List <Vector3> newPolygon = new List <Vector3>(); float prevDistance = 0; for (int i = 0; i < subdividedPolygon.Count; i++) { Vector3 curr = subdividedPolygon[i]; // position on the ellipse Vector3 position = curr; // from center to position float distance = (position - meanVector).magnitude; Vector3 direction = (position - meanVector).normalized; // move from center towards new position. but not too much, let it depend on the previous distance { // move initially from 0 to max distance. otherwise use the previous value float min = i == 0 ? distance * ellipseRelaxationFactor : prevDistance * ellipseRelaxationFactor; float max = distance; // the radius must be smaller during the next iteration, we are navigating around an ellipse => clamp the values if (min > max) { min = max; } float moveDistance = UnityEngine.Random.Range(min, max); distance = moveDistance; } position = meanVector + distance * direction; newPolygon.Add(position); prevDistance = distance; } #endregion create new polygon using the intersections if (douglasPeuckerReductionTolerance > 0) { // convert to vector2 List <Vector2> vector2List = newPolygon.ConvertAll <Vector2>(item => new Vector2(item.x, item.z)); // use vspro's DouglasPeuckerReduction algorithm vector2List = PolygonUtility.DouglasPeuckerReduction(vector2List, douglasPeuckerReductionTolerance); // convert back to vector3 if (vector2List.Count >= 3) { newPolygon = vector2List.ConvertAll <Vector3>(item => new Vector3(item.x, 0, item.y)); } } return(newPolygon); }