如何剪裁非闭合几何体

Posted

技术标签:

【中文标题】如何剪裁非闭合几何体【英文标题】:How to clip non-closed geometry 【发布时间】:2014-08-29 07:56:51 【问题描述】:

简介

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

看起来 UIElement.Clip 仍然渲染不可见的部分

渲染相对较小的几何体(仅填充 1920x1200 区域 ~ 2000 条垂直线的线条)需要很多时间。当使用Clip移动屏幕外的几何体(以便剪辑应该删除它的重要部分)时,仍然需要相同时间(大约1秒)。

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

问题

Geometry.Combine 不适用于非闭合几何。它产生封闭的几何图形。而且它看起来很丑,连接第一点和最后一点:

问题

如何对非封闭图形执行裁剪(减少要渲染的几何图形数量)?

编辑

这是之前的几何图形(图片中显示的小和平)

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,000,30371 52,00006076293946p>4762935876p;

通知一开始的变化,是M 0;50 L ...,变成F 1 M 55;34 L ...

F1 表示NonZero 填充

确定一个点是否在路径的填充区域中的规则,方法是从该点向任意方向无限远地绘制一条射线,然后检查形状的一部分与射线相交的位置。从 0 开始,每次一条线段从左到右穿过射线时加一,每次路径段从右到左穿过射线时减一。在计算交叉点后,如果结果为零,则该点位于路径之外。否则,它在里面。

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

编辑

我应该一直在看字符串的结尾。 Path.Data末尾有z,表示图形已关闭。

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

编辑

演示问题的示例:

Xaml:

<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);

geometry1geometry2的图片:

结果Combine:

可以看到剪裁后2行变成3行,调试证明:

F1M100;100L200;100 200;200 100;100z

注意,不只是z,最后还有100;100点,连接起点。

【问题讨论】:

您能否分享一些针对同一问题的工作代码? @pushpraj,见编辑。 好的,请给我一些时间。这里有点晚了,希望我明天之前回复你不会介意。 Geometry.Combine 创建一个形状而不是路径(据我对msdn.microsoft.com/en-us/library/… 的理解),如果不关闭它,您无法从路径中创建形状(查看您的结果)我不知道是否有是合并几何的给定方法,但我认为 geometry.Combine 是错误的开始。 @SebastianL 看看Geometry.Combine 方法的返回类型。它不会创建一个形状,但实际上是一个PathGeometry。但是,这并不意味着 Combine 不会自动关闭生成的几何图形,因此这里的方法是错误的。 【参考方案1】:

我尝试基于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);
    

结果

测试代码 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);
    

结果

试一试,看看距离有多近。

【讨论】:

【参考方案2】:

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

要使其正常工作,您必须了解 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 你必须做一些其他的工作。您必须检查是否在某个点连接,无论开始点、结束点还是介于两者之间的某个位置都无关紧要。 如果你已经这样做了,你可以像这样合并而不是合并路径:

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;
              

【讨论】:

我原来的问题仍然是“如何裁剪非封闭几何体”。你正确地称之为path。我现在比评论更好地理解你的观点,谢谢。您的回答让我想到了尝试使 path 成为 geometry,因此我必须生成更多而不是 2 行(例如,向前两次细线和落后)。或者甚至尝试使用 fake shadow 技术(封闭的图形,然后是另一个,移动几个像素)来与封闭的几何图形相交 裁剪。仍然,看起来不是一个好的解决方案..如果存在的话..

以上是关于如何剪裁非闭合几何体的主要内容,如果未能解决你的问题,请参考以下文章

使用几何的正交投影被远平面剪裁

关于几何基础的序言

关于几何基础的序言

关于几何基础的序言

关于几何基础的序言

MySQL数据类型 - 空间数据类型