/// <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 <Vector> CreateRandomShape(List <Vector> polygon, double ellipseRelaxationFactor, int angleStepCount, bool randomAngleMovement, bool keepOriginalShape, bool randomStartAngle) { Vector meanVector = PolygonUtils.GetMeanVector(polygon); double length = meanVector.Length * 2; // get the list of angles to step through List <double> angleRadList = CreateAngleList(angleStepCount, randomAngleMovement, randomStartAngle); // resample the original polygon by angle line intersection List <Vector> resampledPolygon = ResamplePolygon(polygon, meanVector, angleRadList, keepOriginalShape); #region create new polygon using the intersections List <Vector> newPolygon = new List <Vector>(); for (int i = 0; i < resampledPolygon.Count; i++) { Vector curr = resampledPolygon[i]; // position on the ellipse Vector position = curr; // from center to position double distance = (position - meanVector).Length; Vector direction = (position - meanVector); direction.Normalize(); // 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 double min = distance * ellipseRelaxationFactor; double 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; } double moveDistance = Utils.GetRandomRange(min, max); distance = moveDistance; } position = meanVector + distance * direction; newPolygon.Add(position); } #endregion create new polygon using the intersections /* * TODO: Douglas-Peucker reduction */ // sort again PolygonUtils.SortClockWise(newPolygon); return(newPolygon); }
// resample the polygon by creating vertices which are created by casting lines from mean vector to the polygon lines and using the intersection points // this assumes that the mean vector is enclosed in a polygon public static List <Vector> ResamplePolygon(List <Vector> polygon, Vector meanVector, List <double> angleRadList, bool keepOriginalShape) { double length = meanVector.Length * 2; List <Vector> resampledPolygon = new List <Vector>(); // add existing points in order to keep the general voronoi shape if (keepOriginalShape) { resampledPolygon.AddRange(polygon); } for (int i = 0; i < angleRadList.Count; i++) { // convert angle from deg to rad double angle = angleRadList[i]; double x = meanVector.X + length * Math.Cos(angle); double z = meanVector.X + length * Math.Sin(angle); // position on the ellipse Vector line1PositionA = meanVector; Vector line1PositionB = new Vector(x, z); for (var j = 0; j < polygon.Count; j++) { int currIndex = j; int nextIndex = j + 1; if (nextIndex == polygon.Count) { nextIndex = 0; } Vector line2PositionA = new Vector(polygon[currIndex].X, polygon[currIndex].Y); Vector line2PositionB = new Vector(polygon[nextIndex].X, polygon[nextIndex].Y); Vector intersection = new Vector(0, 0); // try and find an intersection. if one is found, add the point to the list if (PolygonUtils.LineSegmentsIntersection(line1PositionA, line1PositionB, line2PositionA, line2PositionB, out intersection)) { resampledPolygon.Add(intersection); break; } } } // sort again PolygonUtils.SortClockWise(resampledPolygon); return(resampledPolygon); }
/// <summary> /// Create a random ellipse and paint all voronoi cells in the same color which have the the centroid inside the ellipse /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Testing_ColorizeVoronoiWithinRandomEllipseBounds(object sender, RoutedEventArgs e) { // parameters double ellipseWidthMin = 50; double ellipseHeightMin = 50; double ellipseWidthMax = 200; double ellipseHeightMax = 200; double width = CanvasBorder.ActualWidth; double height = CanvasBorder.ActualHeight; double positionMargin = 0; double x = Utils.GetRandomRange(positionMargin, width - positionMargin); double y = Utils.GetRandomRange(positionMargin, height - positionMargin); Vector position = new Vector(x, y); double ellipseWidth = Utils.GetRandomRange(ellipseWidthMin, ellipseWidthMax); double ellipseHeight = Utils.GetRandomRange(ellipseHeightMin, ellipseHeightMax); // ellipse x/y (and check if point is in ellipse) spans the entire canvas var ellipseX = position.X; var ellipseY = position.Y; // check if point is inside ellipse Vector[] clipPolygon = GetClipPolygon(); for (int i = 0; i < graph.GetAllVectors().Count; i++) { Cell cell = graph.GetVoronoiCell(i, clipPolygon); Vector compareVector = cell.Centroid; double checkEllipse = PolygonUtils.IsInEllipse(ellipseX, ellipseY, ellipseWidth / 2, ellipseHeight / 2, compareVector.X, compareVector.Y); if (checkEllipse < 1) { colorMap[i] = Colors.Black; } } CreateGraph(); // draw ellipse SolidColorBrush brush = new SolidColorBrush(Colors.Black); // create ellipse var ellipse = new Ellipse { Stroke = brush, StrokeThickness = 2, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Width = ellipseWidth, Height = ellipseHeight }; var ellipseDrawX = ellipseX - 0.5 * ellipseWidth; var ellipseDrawY = ellipseY - 0.5 * ellipseHeight; ellipse.Margin = new Thickness(ellipseDrawX, ellipseDrawY, 0, 0); Graph.Children.Add(ellipse); }