裁剪算法 - Cohen Sutherland Clipping的原理及Java实现
来源:互联网 发布:手机电话录音软件 编辑:程序博客网 时间:2024/05/02 16:42
裁剪是3D图形的一个非常重要的方面,二维裁剪功能被广泛的应用于三维图像领域。本文结合Java代码实例,介绍一个非常好,但又足够简单的裁剪算法-科恩-萨瑟兰算法.
在绘制2D线段时,线段的一个端点或者两个端点可能位于屏幕外面,而其中的一部分仍然是可见的。在这种情况下,需要一个有效的算法来查找可见部分的两个新端点,只绘制基于新端点的线段,所有在屏幕外的部分被裁剪掉,从而提高程序的效率。
算法
绘制线段时,如果线段的一个端点是屏幕外,另一个在里面,通过裁剪,只保留屏幕内部的部分。即使两个端点都在画面外,该线段的一部分也可能是可见的。裁剪算法需要找到可见部分线段的新端点,新端点位于屏幕内部或屏幕的边缘。如下图,黑色矩形表示屏幕,红色是原始线段的端点,蓝色为裁剪后线段的端点:
- A:两个端点都在屏幕上,无需裁剪。
- B:一个端点在屏幕外,一个端点在屏幕内部,屏幕外的端点需要被裁剪。
- C:两个端点都在屏幕外面,该线段的任何部分都不可见,无需裁剪
- D:两个端点是画面外,但线段的一部分是可见的,两个端点都需要被裁剪。
如果继续细分,还有很多不同的情况,比如,每个端点可以在屏幕内部,左边,右边,上面,下面,等… 本算法可以非常有效地识别这些情况,并作对应的裁剪。
该算法将2D空间分为9个区域:中心区域是在屏幕,其它的8个区域是在屏幕以外的不同侧面。每个区域用一个四位的二进制数来标识,该二进制数标识被称为区域码(“outcode”)。编码如下:
- 如果该区域在屏幕的上方,第一个字节位是1
- 如果该区域在屏幕的下方,第二个字节位是1
- 如果该区域在屏幕的右边,第三个字节位是1
- 如果该区域在屏幕的左侧,第四个字节位为1
显然,同一区域不能同时在左和右边,或同时在上方和下方,所以在第三字节位和第四字节位不能为同时为1,第一字节位和第二字节位的不能同时为1。屏幕区域的4个字节位全部为0。
屏幕区域的Java定义如下,
private static final int INSIDE = 0;private static final int LEFT = 1;private static final int RIGHT = 2;private static final int BOTTOM = 4;private static final int TOP = 8;
线段的两个端点可以位于任意9个区域,我们先从一些简单的情形入手:
- 如果两个端点均在屏幕的内部或边缘,该线段不需要裁剪并需要全部绘制。这种情况下,是简单接受(Trivial Accept)。
- 如果两个端点均在屏幕(例如,两个端点都在屏幕上方)的同一侧,线段的任何部分都不在屏幕上,该线段不需要裁剪并不需绘制,这种情况下,是简单拒绝(Trivial Reject)。
以上两种情况下可以很容易地通过各区域的区域码(outcode)识别出来:
- Trivial Accept:两个端点必须位于代码0000的区域中,所以Trivial Accept的情况可以通过 code1 | code2 == 0 来断定。(其中,code1 和code2 的线段两个端点的代码,’|’ 是二进制OR运算符,如果code1 和code2都是0,则code1 | code2 == 0)。
- Trivial Reject:两个端点均在区域的同一侧,这两个码有两个相应的字节位都是1。例如,如果只有两个端点是在屏幕的左侧,两个代码的第四位均为1。因此,Trivial Reject的情况可以通过code1 & code2 != 0来断定。
其它情况(既不是Trivial Accept,也不是Trivial Reject),通过裁剪操作,可以转化成如上的简单的情况。科恩萨瑟兰算法是一种循环,每个循环只做一个裁剪操作。该操作裁剪其中一个端点,直到新的端点位于屏幕的水平或者垂直边界。在许多情况下,需要多次裁剪才能够最终断定是否该线段被接受或拒绝。但裁剪的次数最多为4次。
屏幕可通过两个坐上方的点P1(xMin,yMin),右下方的点P2(xMax, yMax) 来定义, Java 定义如下
private double xMin; private double yMin; private double xMax; private double yMax;
Clip method用到了一个辅助功能,getRegionCode,该method返回给定端点的二进制区域代码
private final int getRegionCode(double x, double y) { int xcode = x < xMin ? LEFT : x > xMax ? RIGHT : INSIDE; int ycode = y < yMin ? BOTTOM : y > yMax ? TOP : INSIDE; return xcode | ycode; }
Clip功能开始检测简单的情形:
public boolean clip(Line2D.Float line) { double p1x = line.getX1(), p1y = line.getY1(); double p2x = line.getX2(), p2y = line.getY2(); double qx = 0d, qy = 0d; boolean vertical = p1x == p2x; double slope = vertical ? 0d : (p2y - p1y) / (p2x - p1x); int c1 = getRegionCode(p1x, p1y); int c2 = getRegionCode(p2x, p2y); while (true) { if(c1 == INSIDE & c2 == INSIDE){ break; } if ((c1 & c2) != INSIDE){ return false; }
如果c1 == INSIDE & c2 == INSIDE 为true, 即为简单接受(Trivial Accept),通过break 跳转到结束代码.
line.setLine(p1x, p1y, p2x, p2y); return true;
如果 (c1 & c2) != INSIDE为true, 即为简单拒绝(Trivial Reject)。直接返回false;
如果没有检测到简单的情形,该线段需要被裁剪。每个循环只作4个可能的剪裁操作其中的一个。剪辑,一个坐标的一个端点被设置为原线段与对应区域的边界的交点,新的端点是在屏幕的边界坐标之一,该点的其它坐标值是由直线的方程重新计算。为了找到对应的裁剪操作,我们需要找到屏幕的外部的端点。该端点的代码称为codeout,选择code1或code2中不等于0的一个。
int c = code1 == INSIDE ? code2 : code1; if ((c & LEFT) != INSIDE) { qx = xMin; qy = (qx - p1x) * slope + p1y; } else if ((c & RIGHT) != INSIDE) { qx = xMax; qy = (qx - p1x) * slope + p1y; } else if ((c & BOTTOM) != INSIDE) { qy = yMin; qx = vertical ? p1x : (qy - p1y) / slope + p1x; } else if ((c & TOP) != INSIDE) { qy = yMax; qx = vertical ? p1x : (qy - p1y) / slope + p1x; }
上述代码计算裁剪之后新的端点坐标,新的坐标必须赋给端点p1或端点p2的, p1 和 p2 的选取取决于哪个codeout的值。循环结束之后,新线段可能满足一个简单的情况下,如果仍然不满足一个简单的情况,则进行新的循环并作裁剪操作。
if (c == code1) { p1x = qx; p1y = qy; code1 = getRegionCode(p1x, p1y); } else { p2x = qx; p2y = qy; code2 = getRegionCode(p2x, p2y); }
最后附上完整的代码实现
public final class Clipping { private static final int INSIDE = 0; private static final int LEFT = 1; private static final int RIGHT = 2; private static final int BOTTOM = 4; private static final int TOP = 8; private double xMin; private double yMin; private double xMax; private double yMax; public Clipping() { } public Clipping(Rectangle2D clip) { setClip(clip); } public void setClip(Rectangle2D clip) { xMin = clip.getX(); xMax = xMin + clip.getWidth(); yMin = clip.getY(); yMax = yMin + clip.getHeight(); } private final int getRegionCode(double x, double y) { int xcode = x < xMin ? LEFT : x > xMax ? RIGHT : INSIDE; int ycode = y < yMin ? BOTTOM : y > yMax ? TOP : INSIDE; return xcode | ycode; } public boolean clip(Line2D.Float line) { double p1x = line.getX1(), p1y = line.getY1(); double p2x = line.getX2(), p2y = line.getY2(); double qx = 0d, qy = 0d; boolean vertical = p1x == p2x; double slope = vertical ? 0d : (p2y - p1y) / (p2x - p1x); int code1 = getRegionCode(p1x, p1y); int code2 = getRegionCode(p2x, p2y); while (true) { if(code1 == INSIDE & code2 == INSIDE){ break; } if ((code1 & code2) != INSIDE){ return false; } int codeout = code1 == INSIDE ? code2 : code1; if ((codeout & LEFT) != INSIDE) { qx = xMin; qy = (qx - p1x) * slope + p1y; } else if ((codeout & RIGHT) != INSIDE) { qx = xMax; qy = (qx - p1x) * slope + p1y; } else if ((codeout & BOTTOM) != INSIDE) { qy = yMin; qx = vertical ? p1x : (qy - p1y) / slope + p1x; } else if ((codeout & TOP) != INSIDE) { qy = yMax; qx = vertical ? p1x : (qy - p1y) / slope + p1x; } if (codeout == code1) { p1x = qx; p1y = qy; code1 = getRegionCode(p1x, p1y); } else { p2x = qx; p2y = qy; code2 = getRegionCode(p2x, p2y); } } line.setLine(p1x, p1y, p2x, p2y); return true; } }
本文主要参考自http://lodev.org/cgtutor/lineclipping.html, 并结合实际的游戏引擎,给出了Java版本的代码实现。希望对你有所帮助! 反馈请联系jinbing.peng@yahoo.com.
- 裁剪算法 - Cohen Sutherland Clipping的原理及Java实现
- 【裁剪】线段的裁剪——Cohen-Sutherland算法及代码实现
- Cohen-Sutherland裁剪算法
- Cohen-Sutherland裁剪算法
- Cohen-Sutherland直线段的裁剪算法
- Cohen-Sutherland线段裁剪算法
- Cohen-Sutherland算法线段裁剪
- Cohen-Sutherland线段裁剪算法
- Cohen-Sutherland线段裁剪算法
- Cohen-Sutherland算法裁剪线段
- Cohen Sutherland线段裁剪算法(C#实现)
- Cohen-SutherLand 裁剪算法 (vc++)
- Cohen-Sutherland线段裁剪算法
- Cohen-Sutherland 窗口裁剪线段算法
- 计算机图形学 - 线段裁剪 - Cohen Sutherland算法
- opengl 直线裁剪Cohen-Sutherland算法
- 计算机图形学-直线裁剪(Cohen-Sutherland编码裁剪算法)
- 编码实现Cohen-Sutherland端点编码算法
- Error in an XML file: aborting build 错误提示的解决办法
- LeetCode - Sudoku Solver
- java中插件机制和热升级的实现方案
- Mybatis入门实例(二)——添加ehcache缓存支持
- Search in Rotated Sorted Array II
- 裁剪算法 - Cohen Sutherland Clipping的原理及Java实现
- 中学白念了吧,初创企业掣肘于该物理模型下竟无所遁形!
- 伤心过新东方
- poj 2029 Get Many Persimmon Trees 二维树状数组
- Java中有关Null的9件事
- Eclipse编辑java文件报Unhandled event loop exception错误的解决办法
- 【Unity3D API的学习与使用】Unity实现GUI组件的位移、缩放和旋转
- B-Human代码建立与机器人配置
- 【BZOJ3901】棋盘游戏 局部暴枚取优