public void BezierSegmentsToPath_WithConnectedSegments_ReturnsConnectedPath() { var segments = new BezierSegment[] { new BezierSegment() { P0 = new Vector2(0, 0), P1 = new Vector2(1, 1), P2 = new Vector2(2, 2), P3 = new Vector2(3, 3) }, new BezierSegment() { P0 = new Vector2(3, 3), P1 = new Vector2(4, 4), P2 = new Vector2(5, 5), P3 = new Vector2(6, 6) } }; var path = VectorUtils.BezierSegmentsToPath(segments); Assert.AreEqual(3, path.Length); var pathSeg = path[0]; Assert.AreEqual(new Vector2(0, 0), pathSeg.P0); Assert.AreEqual(new Vector2(1, 1), pathSeg.P1); Assert.AreEqual(new Vector2(2, 2), pathSeg.P2); pathSeg = path[1]; Assert.AreEqual(new Vector2(3, 3), pathSeg.P0); Assert.AreEqual(new Vector2(4, 4), pathSeg.P1); Assert.AreEqual(new Vector2(5, 5), pathSeg.P2); pathSeg = path[2]; Assert.AreEqual(new Vector2(6, 6), pathSeg.P0); }
private static VectorShape TryParseShapeToCircle(Shape shape, Matrix2D transform) { if (shape.Contours.Length > 1) { return(null); } BezierContour contour = shape.Contours[0]; if (contour.Segments.Length < 5) { return(null); } if (!contour.Closed) { return(null); } BezierSegment[] segments = new BezierSegment[contour.Segments.Length - 1]; for (int i = 0; i < segments.Length; i++) { segments[i].P0 = transform.MultiplyPoint(contour.Segments[i].P0); segments[i].P1 = transform.MultiplyPoint(contour.Segments[i].P1); segments[i].P2 = transform.MultiplyPoint(contour.Segments[i].P2); segments[i].P3 = transform.MultiplyPoint(contour.Segments[(i + 1)].P0); } Rect shapeBounds = VectorUtils.Bounds(VectorUtils.BezierSegmentsToPath(segments)); Vector2 center = shapeBounds.center; float radius = (shapeBounds.width + shapeBounds.height) / 4f; float error = radius / 200f; for (int i = 0; i < segments.Length; i++) { if (Mathf.Abs(Vector2.Distance(center, segments[i].P0) - radius) > error) { return(null); } Vector2 midpoint = VectorUtils.Eval(segments[i], 0.5f); if (Mathf.Abs(Vector2.Distance(center, midpoint) - radius) > error) { return(null); } } CircleShape circle = new CircleShape(center, radius); circle.colorOutline = Color.red; return(circle); }
/// <summary> /// Tessellate the shape into geometry data. /// </summary> protected override void GenerateGeometry() { if ((shapeGeometry != null) && (!shapeDirty)) { return; } Shape ellipse = new Shape(); ellipse.PathProps = new PathProperties() { Stroke = new Stroke() { Color = colorOutline, HalfThickness = penSize / 2f * penToMeshScale } }; if (colorFill != Color.clear) { ellipse.Fill = new SolidFill() { Color = colorFill }; } BezierSegment[] segments = GenerateSegments(); ellipse.Contours = new BezierContour[1]; ellipse.Contours[0] = new BezierContour(); ellipse.Contours[0].Segments = VectorUtils.BezierSegmentsToPath(segments); shapeNode = new SceneNode() { Transform = matrixTransform, Shapes = new List <Shape> { ellipse } }; tessellationScene.Root = shapeNode; shapeGeometry = VectorUtils.TessellateScene(tessellationScene, tessellationOptions); shapeDirty = false; }
public void BezierSegmentsToPath_WithDisconnectedSegments_ReturnsConnectedPathWithStraightLine() { var segments = new BezierSegment[] { new BezierSegment() { P0 = new Vector2(0, 0), P1 = new Vector2(1, 1), P2 = new Vector2(2, 2), P3 = new Vector2(3, 3) }, new BezierSegment() { P0 = new Vector2(6, 6), P1 = new Vector2(7, 7), P2 = new Vector2(8, 8), P3 = new Vector2(9, 9) } }; var path = VectorUtils.BezierSegmentsToPath(segments); Assert.AreEqual(4, path.Length); var pathSeg = path[0]; Assert.AreEqual(new Vector2(0, 0), pathSeg.P0); Assert.AreEqual(new Vector2(1, 1), pathSeg.P1); Assert.AreEqual(new Vector2(2, 2), pathSeg.P2); // The second segment should be a straight line between (3,3) and (6,6) pathSeg = path[1]; Assert.AreEqual(new Vector2(3, 3), pathSeg.P0); Assert.AreEqual(new Vector2(4, 4), pathSeg.P1); Assert.AreEqual(new Vector2(5, 5), pathSeg.P2); pathSeg = path[2]; Assert.AreEqual(new Vector2(6, 6), pathSeg.P0); Assert.AreEqual(new Vector2(7, 7), pathSeg.P1); Assert.AreEqual(new Vector2(8, 8), pathSeg.P2); pathSeg = path[3]; Assert.AreEqual(new Vector2(9, 9), pathSeg.P0); }
/// <summary> /// Tessellate the shape into geometry data. /// </summary> protected override void GenerateGeometry() { if ((shapeGeometry != null) && (!shapeDirty)) { return; } Shape ellipse = new Shape(); float theta = Mathf.Atan2(MajorAxis.y, MajorAxis.x); VectorUtils.MakeEllipseShape(ellipse, Vector2.zero, MajorAxis.magnitude, MinorAxis.magnitude); ellipse.PathProps = new PathProperties() { Stroke = new Stroke() { Color = colorOutline, HalfThickness = penSize / Screen.dpi } }; if (colorFill != Color.clear) { ellipse.Fill = new SolidFill() { Color = colorFill }; } int numCurves = 4; // Supposed to calculate from max error BezierSegment[] segments = new BezierSegment[numCurves]; float deltaAngle = sweepAngle / numCurves; float sinTheta = Mathf.Sin(theta); float cosTheta = Mathf.Cos(theta); float angleB = startAngle; float a = MajorAxis.magnitude; float b = MinorAxis.magnitude; float t = Mathf.Tan(0.5f * deltaAngle); float alpha = Mathf.Sin(deltaAngle) * (Mathf.Sqrt(4f + 3f * t * t) - 1f) / 3f; float sinAngleB = Mathf.Sin(angleB); float cosAngleB = Mathf.Cos(angleB); float aSinAngleB = a * sinAngleB; float aCosAngleB = a * cosAngleB; float bSinAngleB = b * sinAngleB; float bCosAngleB = b * cosAngleB; Vector2 ptB = new Vector2(); ptB.x = position.x + aCosAngleB * cosTheta - bSinAngleB * sinTheta; ptB.y = position.y + aCosAngleB * sinTheta + bSinAngleB * cosTheta; Vector2 dotB = new Vector2(); dotB.x = -aSinAngleB * cosTheta - bCosAngleB * sinTheta; dotB.y = -aSinAngleB * sinTheta + bCosAngleB * cosTheta; for (int i = 0; i < numCurves; ++i) { float angleA = angleB; Vector2 ptA = ptB; Vector2 dotA = dotB; angleB += deltaAngle; sinAngleB = Mathf.Sin(angleB); cosAngleB = Mathf.Cos(angleB); aSinAngleB = a * sinAngleB; aCosAngleB = a * cosAngleB; bSinAngleB = b * sinAngleB; bCosAngleB = b * cosAngleB; ptB.x = position.x + aCosAngleB * cosTheta - bSinAngleB * sinTheta; ptB.y = position.y + aCosAngleB * sinTheta + bSinAngleB * cosTheta; dotB.x = -aSinAngleB * cosTheta - bCosAngleB * sinTheta; dotB.y = -aSinAngleB * sinTheta + bCosAngleB * cosTheta; segments[i].P0 = ptA; segments[i].P1.x = ptA.x + alpha * dotA.x; segments[i].P1.y = ptA.y + alpha * dotA.y; segments[i].P2.x = ptB.x - alpha * dotB.x; segments[i].P2.y = ptB.y - alpha * dotB.y; segments[i].P3 = ptB; } Shape testShape = new Shape(); testShape.PathProps = new PathProperties() { Stroke = new Stroke() { Color = Color.red, HalfThickness = penSize * 1.5f / Screen.dpi } }; testShape.Contours = new BezierContour[1]; testShape.Contours[0] = new BezierContour(); testShape.Contours[0].Segments = VectorUtils.BezierSegmentsToPath(segments); shapeNode = new SceneNode() { Transform = Matrix2D.Translate(position) * Matrix2D.Rotate(-theta), Shapes = new List <Shape> { ellipse } }; SceneNode testNode = new SceneNode() { Transform = matrixTransform, Shapes = new List <Shape> { testShape }, Children = new List <SceneNode> { shapeNode } }; tessellationScene.Root = testNode; shapeGeometry = VectorUtils.TessellateScene(tessellationScene, tessellationOptions); shapeMesh = null; shapeDirty = false; }