对于可缩放矢量图形的GPU渲染器的预处理步骤之一,我正在处理SVG曲线(四种类型:直线,二次Bézier曲线和三次Bézier曲线以及椭圆弧)。其中一个步骤是在相交处进行曲线细分,这是填充双重连接边列表的预处理算法。
以前,我曾将每种类型的曲线存储在单独的结构(Line
,QuadraticBezier
,CubicBezier
和EllipticArc
)中,这意味着要对它们,我需要为每个组合编写相同(相似)的代码(导致该代码出现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的填充和其他细微差别时,我可以重用这些构造。
答案 0 :(得分:3)
问题是试图利用值类型的类型差异。这是一个很大的不,在C#中不。你做不到令人遗憾的是,没有List<T>
的结构,其中T
是可以接受所有不同值类型的结构。 c#中的类型差异必须保留身份,并且根据定义,它与值类型不兼容。
显而易见的第一种选择是您已经弄清楚的是:ICurve
,但这会遇到拳击处罚,而处罚可能会非常昂贵。
第二种选择实际上是使曲线成为参考类型,但这可能会对GC造成很大的收集压力。
第三个选项是实现单个结构Curve
,并在创建时定义它实际上是其状态的一部分的类型。这可能是更好的选择,并且您已经确定这是可行的选择。