SVG曲线的接口与区分联合

时间:2018-12-22 14:26:58

标签: c#

对于可缩放矢量图形的GPU渲染器的预处理步骤之一,我正在处理SVG曲线(四种类型:直线,二次Bézier曲线和三次Bézier曲线以及椭圆弧)。其中一个步骤是在相交处进行曲线细分,这是填充双重连接边列表的预处理算法。

以前,我曾将每种类型的曲线存储在单独的结构(LineQuadraticBezierCubicBezierEllipticArc)中,这意味着要对它们,我需要为每个组合编写相同(相似)的代码(导致该代码出现10次惊人的变化)。现在,我想尝试一些不同的东西。我有两个选择:使用接口ICurve,或使用具有struct Curve的{​​{1}}并重新组合所有操作。

在曲线上运行的代码看起来像这样(重复):

Type

我想到的第一种方法是定义一个// Reunite all the shapes var curves = new List<ICurve>(path.PathCommands.Length); foreach (var cmd in path.PathCommands) /* generate the curves by evaluating the path commands */; // Reunite all intersections to subdivide the curves var curveRootSets = new SortedSet<float>[curves.Count]; for (int i = 0; i < curveRootSets.Length; i++) curveRootSets[i] = new SortedSet<float>(new[] { 1f }, Half.Comparer); // Get all intersections for (int i = 0; i < curves.Count; i++) for (int j = i+1; i < curves.Count; j++) foreach (var pair in CurveIntersectionPairs(curves[i], curves[j])) { curveRootSets[i].Add(pair.A); curveRootSets[j].Add(pair.B); } // Account for possibly-duplicate curves foreach (var set in curveRootSets) set.Remove(0f); // Subdivide the curves var curvesSubdiv = curves.Zip(curveRootSets, delegate (ICurve curve, SortedSet<float> set) { float v = 0f; var list = new List<ICurve>(); foreach (var l in set) { list.Add(curve.Subcurve(v, l)); v = l; } return list; }).Aggregate(Enumerable.Empty<ICurve>(), Enumerable.Concat); 并带有必要的合同,以使它可以在细分阶段和DCEL阶段工作。界面如下:

interface ICurve

然后我将使每个结构都实现该接口:

public interface ICurve
{
    // Evaluates the curve at parameter "t"
    Vector2 At(float t);

    // The derivative of the curve (aka the "velocity curve")
    ICurve Derivative { get; }

    // Gets the curve that maps [0,1] to [l,r] on this curve
    ICurve Subcurve(float l, float r);

    // The bounding box of the curve
    FRectangle BoundingBox { get; }

    // The measure of how much counterclockwise the curve is
    // (aka double the area swept by it and the segments that
    // connect the endpoints to the origin)
    float Winding { get; }

    // Does the curve degenerate to a single point?
    bool IsDegenerate { get; }
}

这很神奇,每个类都正确地实现了contract方法,因此我可以抽象地使用public struct Line : ICurve { /* ... */ } public struct QuadraticBezier : ICurve { /* ... */ } public struct CubicBezier : ICurve { /* ... */ } public struct EllipticArc : ICurve { /* ... */ } ,甚至可以根据SVG规范的要求实现更多曲线。但是我读了一些关于接口装箱/拆箱的恐怖故事(因为它们是引用类型)以及分配在堆上的结构,这些结构会分散在内存中。我想我可以通过设置一个ICurves和一个struct Curve及其所有必要字段来提高(在缓存一致性,间接性和其他方式上):

public readonly CurveType Type

由于public enum CurveType { Line, QuadraticBezier, CubicBezier, EllipticArc } public partial struct Curve { // four Vector2's are sufficient for now readonly Vector2 A, B, C, D; public readonly CurveType Type; // Evaluates the curve at parameter "t" public Vector2 At(float t) { // "Dispatch" the function based on the type switch (Type) { case CurveType.Line: return LineAt(t); case CurveType.QuadraticBezier: return QuadraticBezierAt(t); case CurveType.CubicBezier: return CubicBezierAt(t); case CurveType.EllipticArc: return EllipticArcAt(t); default: return new Vector2(float.NaN, float.NaN); } } // Other contract methods implemented similarly ... } 是值类型,因此我可以使用struct(内部使用数组)在本地分配它们,并从缓存局部性中受益(至少一点)。但是,我将失去“开放继承”的能力(我认为这是没有问题的,因为在当今的SVG中仅使用了少数曲线)。

但是也许我过度设计了?我是C#的新手(已经使用C ++几年了),我不知道接口将为值类型带来的复杂性,也许接口提供的动态调度比“手动调度”要快。因此,我想请您了解这些特定方法。也许我不知道还有另一个更好的选择?

请记住,我正在准备将其用于实际的SVG文件,这意味着每条路径可能存在数十或数百条曲线,然后必须对其进行细分。当我要生成包含每个曲线的图元,分配SVG的填充和其他细微差别时,我可以重用这些构造。

1 个答案:

答案 0 :(得分:3)

问题是试图利用值类型的类型差异。这是一个很大的不,在C#中不。你做不到令人遗憾的是,没有List<T>的结构,其中T是可以接受所有不同值类型的结构。 c#中的类型差异必须保留身份,并且根据定义,它与值类型不兼容。

显而易见的第一种选择是您已经弄清楚的是:ICurve,但这会遇到拳击处罚,而处罚可能会非常昂贵。

第二种选择实际上是使曲线成为参考类型,但这可能会对GC造成很大的收集压力。

第三个选项是实现单个结构Curve,并在创建时定义它实际上是其状态的一部分的类型。这可能是更好的选择,并且您已经确定这是可行的选择。