如何剪裁非闭合几何体

时间:2014-08-29 07:56:52

标签: c# wpf geometry

简介

我在实现剪辑时发现了一个问题(请参阅this)。

  

看起来UIElement.Clip仍然呈现不可见的部分

渲染相对较小的几何体(线条仅填充 1920x1200区域~2000条垂直线条)需要花费大量时间。在屏幕上使用Clip移动这个几何图形时(因此裁剪应删除它的重要部分),它仍然需要相同的时间(大约1秒)。< / p>

好的,我发现使用Geometry.Combine会做一个剪辑(渲染时间按裁剪几何体后的比例减少)。完美!

问题

Geometry.Combine无法正确处理非闭合几何体。它产生封闭的几何形状它看起来很难看,连接第一点和最后一点:

Ugly sin(x)

问题

如何为非闭合数字执行裁剪(减少要渲染的几何体的数量)?

修改

这是几何之前(图片所示的小和平)

  

{M0; 50L0; 50L1; 53,1395259764657L2; 56,2666616782152L3; 59,3690657292862L4; 62,4344943582427L5; 65,4508497187474L6; 68,4062276342339L7; 71,2889645782536L8; ...

之后

  

{F1M54,9999923706055; 34,5491371154785L53,9999885559082; 37,5655174255371 53,0000114440918; 40,6309471130371 52,0000076293945; 43,4333335876465 ...

注意在开始时更改为M 0;50 L ...,变为F 1 M 55;34 L ...

F1表示填充<{1}}

  

通过在任何方向上将光线从该点绘制到无穷大然后检查形状的一段与光线相交的位置来确定点是否在路径的填充区域中的规则。从零开始计数,每当一个段从左到右穿过光线时添加一个,并且每当一个路径段从右到左穿过光线时减去一个。计算交叉点后,如果结果为零,则该点位于路径之外。否则,它在里面。

我完全不知道这意味着什么。但也许这很重要?

修改

我应该一直在看字符串的结尾。 <{1}}末尾有NonZero,表示数字已关闭。

奇怪的是,尝试删除z(使用Path.Data / z组合)不起作用。经过一些调查后,我发现Geometry.ToString()产生的物理封闭数字(命令Geometry.Parse(),其中Combine是最左边的点)。最糟糕的是它并不总是最后一点,因此在解析之前简单地删除最后L x;y也不起作用。 =(

修改

演示问题的示例:

的Xaml:

x;y

代码:

L x;y

<Path x:Name="path" Stroke="Red"/> var geometry1 = new RectangleGeometry(new Rect(100, 100, 100, 100)); var geometry2 = new PathGeometry(new[] { new PathFigure(new Point(0,0), new[] { new LineSegment(new Point(300, 300), true), new LineSegment(new Point(300, 0), true), }, false) }); //path.Data = geometry1; //path.Data = geometry2; //path.Data = Geometry.Combine(geometry1, geometry2, GeometryCombineMode.Intersect, null); 的图片:

结果geometry1

正如你所看到的那样,裁剪后2行成为3行,调试证明了这一点:

  

{F1M100; 100L200; 100 200; 200 100; 100z}

请注意,不仅geometry2,而且Combine指向最后,连接起点。

2 个答案:

答案 0 :(得分:2)

我尝试基于this线交叉算法

为非闭合几何实现剪裁解决方案

<强>代码

    public static PathGeometry ClipGeometry(PathGeometry geom, Rect clipRect)
    {
        PathGeometry clipped = new PathGeometry();
        foreach (var fig in geom.Figures)
        {
            PathSegmentCollection segments = new PathSegmentCollection();
            Point lastPoint = fig.StartPoint;
            foreach (LineSegment seg in fig.Segments)
            {
                List<Point> points;
                if (LineIntersectsRect(lastPoint, seg.Point, clipRect, out points))
                {
                    LineSegment newSeg = new LineSegment(points[1], true);
                    PathFigure newFig = new PathFigure(points[0], new[] { newSeg }, false);
                    clipped.Figures.Add(newFig);
                }
                lastPoint = seg.Point;
            }
        }
        return clipped;
    }

    static bool LineIntersectsRect(Point lineStart, Point lineEnd, Rect rect, out List<Point> points)
    {
        points = new List<Point>();

        if (rect.Contains(lineStart) && rect.Contains(lineEnd))
        {
            points.Add(lineStart);
            points.Add(lineEnd);
            return true;
        }

        Point outPoint;
        if (Intersects(lineStart, lineEnd, rect.TopLeft, rect.TopRight, out outPoint))
        {
            points.Add(outPoint);
        }

        if (Intersects(lineStart, lineEnd, rect.BottomLeft, rect.BottomRight, out outPoint))
        {
            points.Add(outPoint);
        }

        if (Intersects(lineStart, lineEnd, rect.TopLeft, rect.BottomLeft, out outPoint))
        {
            points.Add(outPoint);
        }

        if (Intersects(lineStart, lineEnd, rect.TopRight, rect.BottomRight, out outPoint))
        {
            points.Add(outPoint);
        }

        if (points.Count == 1)
        {
            if (rect.Contains(lineStart))
                points.Add(lineStart);
            else
                points.Add(lineEnd);
        }

        return points.Count > 0;
    }

    static bool Intersects(Point a1, Point a2, Point b1, Point b2, out Point intersection)
    {
        intersection = new Point(0, 0);

        Vector b = a2 - a1;
        Vector d = b2 - b1;
        double bDotDPerp = b.X * d.Y - b.Y * d.X;

        if (bDotDPerp == 0)
            return false;

        Vector c = b1 - a1;
        double t = (c.X * d.Y - c.Y * d.X) / bDotDPerp;
        if (t < 0 || t > 1)
            return false;

        double u = (c.X * b.Y - c.Y * b.X) / bDotDPerp;
        if (u < 0 || u > 1)
            return false;

        intersection = a1 + t * b;

        return true;
    }

目前的解决方案适用于基于线的几何体,如果需要,可能还需要包含其他类型。

测试xaml

<UniformGrid Columns="2"
             Margin="250,250,0,0">
    <Grid>
        <Path x:Name="pathClip"
              Fill="#22ff0000" />
        <Path x:Name="path"
              Stroke="Black" />

    </Grid>
    <Path x:Name="path2"
          Margin="100,0,0,0"
          Stroke="Black" />
</UniformGrid>

测试代码1

    void test()
    {
        var geometry = new PathGeometry(new[] { new PathFigure(new Point(0,0), new[] {
                                                    new LineSegment(new Point(300, 300), true),
                                                    new LineSegment(new Point(300, 0), true),
                                                }, false) });

        Rect clipRect= new Rect(10, 10, 180, 180);
        path.Data = ClipGeometry(geometry, clipRect);
        path2.Data = geometry;
        pathClip.Data = new RectangleGeometry(clipRect);
    }

结果

result

测试代码2

    void test()
    {
        var radius = 1.0;
        var figures = new List<LineSegment>();
        for (int i = 0; i < 2000; i++, radius += 0.1)
        {
            var segment = new LineSegment(new Point(radius * Math.Sin(i), radius * Math.Cos(i)), true);
            segment.Freeze();
            figures.Add(segment);
        }
        var geometry = new PathGeometry(new[] { new PathFigure(figures[0].Point, figures, false) });

        Rect clipRect= new Rect(10, 10, 180, 180);
        path.Data = ClipGeometry(geometry, clipRect);
        path2.Data = geometry;
        pathClip.Data = new RectangleGeometry(clipRect);
    }

结果

result

试一试,看看它有多接近。

答案 1 :(得分:0)

如果我是对的,你的主要是:“我如何提高绘制多种形状的性能?”

要实现这一目标,您必须了解Geometry Math

几何对象只有在连接或重叠时才能合并/组合。路径几何和形状几何之间存在很大差异。

例如,如果两个圆圈重叠,您可以在WPF中组合它们

  • 获取重叠区域:Intersect
  • 以获得差异:Xor
  • 获取组合曲面:Union
  • 只能获得一个形状的区别:Exclude

对于路径几何,它有点不同,因为路径没有表面,路径不能Intersect | Xor | Union | Exclude另一条路径或形状。

但是WPF认为你只是忘了关闭这条道路而且正在为你做这件事,这会导致你问题的结果。

因此,为了实现性能提升,您必须首先过滤所有几何体的形状和路径。

foreach(Shape geometryObj in ControlsOrWhatEver)
{
    if(geometryObj  is Line || geometryObj  is Path || geometryObj  is Polypath)
    {
        pathList.Add(geometryObj);
    } 
    else
    {
        shapeList.Add(geometryObj);
    }
}

对于shapeList,您可以使用Geometry.Combine,但是对于pathList,您还必须完成其他工作。您必须检查连接是否在某个时刻,如果在BeginPoint,endPoint或somwhere之间无关紧要。 如果你已经这样做了,你就可以合并而不是自己组合路径,如:

public Polyline mergePaths(Shape line1, Shape line2)
{
     if(!checkLineType(line1) || !checkLineType(line2))
     {
         return null;
     }

     if(hitTest(line1, line2))
     {
         //here you have to do some math to determine the overlapping points
         //on these points you can do something like this:

         foreach(Point p in Overlapping Points)
         {
              //add the first line until p then add line2 and go on to add lin 1 until another p
         }
     }
     else
     {
         return null;
     }         
}