部分童鞋应该可以从《使用方形游移匹配算法来勾勒图像轮廓》一文看到它的潜在用途了,只要获得图像的轮廓,转换成Box2D形状就相当容易。
但有个问题,所生成的形状会有太多点,范例中处理的logo就多达2200点,太多。
点的数量真心需要减少。为了解决这个问题,Ramer-Douglas-Peucker算法闪亮登场。
该算法的思想为,给定一条由若干线段组成的曲线,找出包含的点数少且外观相近的曲线。
该算法基于原始曲线与简化曲线直接最大间距定义了“dissimilar”。简化曲线由定义原始曲线的点的一个子集组成。
Marius Karthaus为RDP算法写了一个非常不错的javascript范例,我在此基础上添加了部分内容将其移植到AS3,请参见19~75行:
介于排版问题,代码贴于沙发
RDP函数的第二个参数,即ε,用于约束全局,,范围介于0.2到0.8。
看最后看一个例子,舞台右侧勾勒的轮廓仅仅用了343个点,使用的ε值为0.50:
(wtf!背景白色看不出了,移驾原始页面看效果吧http://www.emanueleferonato.com/ ... archingsquares1.swf)
无需下载任何东西,只要将这段代码制粘贴到原始范例的Main类即可。
View Code
1 package {
2 import flash.display.Sprite;
3 import flash.display.BitmapData;
4 import flash.display.Bitmap;
5 import flash.geom.Matrix;
6 import flash.geom.Point;
7 public class Main extends Sprite {
8 private var bitmapData:BitmapData=new BitmapData(640,480,true,0x00000000);
9 // tolerance is the amount of alpha for a pixel to be considered solid
10 private var tolerance:Number=0x01;
11 public function Main() {
12 // adding a png image with transparency
13 bitmapData.draw(new Logo(278,429),new Matrix(1,0,0,1,20,40));
14 var bitmap:Bitmap=new Bitmap(bitmapData);
15 addChild(bitmap);
16 bitmap.alpha=0.5
17 // at the end of this function, marchingVector will contain the points tracing the contour
18 var marchingVector:Vector.<Point>=marchingSquares(bitmapData);
19 marchingVector=RDP(marchingVector,0.50);
20 var canvas:Sprite=new Sprite();
21 addChild(canvas);
22 canvas.graphics.moveTo(marchingVector[0].x+320,marchingVector[0].y);
23 for (var i:Number=0; i<marchingVector.length; i++) {
24 canvas.graphics.lineStyle(2,0xffffff);
25 canvas.graphics.lineTo(marchingVector[i].x+320,marchingVector[i].y);
26 canvas.graphics.lineStyle(1,0xff0000);
27 canvas.graphics.drawCircle(marchingVector[i].x+320,marchingVector[i].y, 2);
28 }
29 canvas.graphics.lineStyle(2,0xffffff);
30 canvas.graphics.lineTo(marchingVector[0].x+320,marchingVector[0].y);
31 }
32
33 public function RDP(v:Vector.<Point>,epsilon:Number):Vector.<Point> {
34 var firstPoint:Point=v[0];
35 var lastPoint:Point=v[v.length-1];
36 if (v.length<3) {
37 return v;
38 }
39 var index:Number=-1;
40 var dist:Number=0;
41 for (var i:Number=1; i<v.length-1; i++) {
42 var cDist:Number=findPerpendicularDistance(v[i],firstPoint,lastPoint);
43 if (cDist>dist) {
44 dist=cDist;
45 index=i;
46 }
47 }
48 if (dist>epsilon) {
49 var l1:Vector.<Point>=v.slice(0,index+1);
50 var l2:Vector.<Point>=v.slice(index);
51 var r1=RDP(l1,epsilon);
52 var r2=RDP(l2,epsilon);
53 var rs:Vector.<Point>=r1.slice(0,r1.length-1).concat(r2);
54 return rs;
55 }
56 else {
57 return new Vector.<Point>(firstPoint,lastPoint);
58 }
59 return null;
60 }
61
62 private function findPerpendicularDistance(p:Point, p1:Point,p2:Point) {
63 var result;
64 var slope;
65 var intercept;
66 if (p1.x==p2.x) {
67 result=Math.abs(p.x-p1.x);
68 }
69 else {
70 slope = (p2.y - p1.y) / (p2.x - p1.x);
71 intercept=p1.y-(slope*p1.x);
72 result = Math.abs(slope * p.x - p.y + intercept) / Math.sqrt(Math.pow(slope, 2) + 1);
73 }
74 return result;
75 }
76
77 public function marchingSquares(bitmapData:BitmapData):Vector.<Point> {
78 var contourVector:Vector.<Point> = new Vector.<Point>();
79 // this is the canvas we'll use to draw the contour
80 var canvas:Sprite=new Sprite();
81 addChild(canvas);
82 canvas.graphics.lineStyle(2,0x00ff00);
83 // getting the starting pixel;
84 var startPoint:Point=getStartingPixel(bitmapData);
85 // if we found a starting pixel we can begin
86 if (startPoint!=null) {
87 // moving the graphic pen to the starting pixel
88 canvas.graphics.moveTo(startPoint.x,startPoint.y);
89 // pX and pY are the coordinates of the starting point;
90 var pX:Number=startPoint.x;
91 var pY:Number=startPoint.y;
92 // stepX and stepY can be -1, 0 or 1 and represent the step in pixels to reach
93 // next contour point
94 var stepX:Number;
95 var stepY:Number;
96 // we also need to save the previous step, that's why we use prevX and prevY
97 var prevX:Number;
98 var prevY:Number;
99 // closedLoop will be true once we traced the full contour
100 var closedLoop:Boolean=false;
101 while (!closedLoop) {
102 // the core of the script is getting the 2x2 square value of each pixel
103 var squareValue:Number=getSquareValue(pX,pY);
104 switch (squareValue) {
105 /* going UP with these cases:
106
107 +---+---+ +---+---+ +---+---+
108 | 1 | | | 1 | | | 1 | |
109 +---+---+ +---+---+ +---+---+
110 | | | | 4 | | | 4 | 8 |
111 +---+---+ +---+---+ +---+---+
112
113 */
114 case 1 :
115 case 5 :
116 case 13 :
117 stepX=0;
118 stepY=-1;
119 break;
120 /* going DOWN with these cases:
121
122 +---+---+ +---+---+ +---+---+
123 | | | | | 2 | | 1 | 2 |
124 +---+---+ +---+---+ +---+---+
125 | | 8 | | | 8 | | | 8 |
126 +---+---+ +---+---+ +---+---+
127
128 */
129 case 8 :
130 case 10 :
131 case 11 :
132 stepX=0;
133 stepY=1;
134 break;
135 /* going LEFT with these cases:
136
137 +---+---+ +---+---+ +---+---+
138 | | | | | | | | 2 |
139 +---+---+ +---+---+ +---+---+
140 | 4 | | | 4 | 8 | | 4 | 8 |
141 +---+---+ +---+---+ +---+---+
142
143 */
144 case 4 :
145 case 12 :
146 case 14 :
147 stepX=-1;
148 stepY=0;
149 break;
150 /* going RIGHT with these cases:
151
152 +---+---+ +---+---+ +---+---+
153 | | 2 | | 1 | 2 | | 1 | 2 |
154 +---+---+ +---+---+ +---+---+
155 | | | | | | | 4 | |
156 +---+---+ +---+---+ +---+---+
157
158 */
159 case 2 :
160 case 3 :
161 case 7 :
162 stepX=1;
163 stepY=0;
164 break;
165 case 6 :
166 /* special saddle point case 1:
167
168 +---+---+
169 | | 2 |
170 +---+---+
171 | 4 | |
172 +---+---+
173
174 going LEFT if coming from UP
175 else going RIGHT
176
177 */
178 if (prevX==0&&prevY==-1) {
179 stepX=-1;
180 stepY=0;
181 }
182 else {
183 stepX=1;
184 stepY=0;
185 }
186 break;
187 case 9 :
188 /* special saddle point case 2:
189
190 +---+---+
191 | 1 | |
192 +---+---+
193 | | 8 |
194 +---+---+
195
196 going UP if coming from RIGHT
197 else going DOWN
198
199 */
200 if (prevX==1&&prevY==0) {
201 stepX=0;
202 stepY=-1;
203 }
204 else {
205 stepX=0;
206 stepY=1;
207 }
208 break;
209 }
210 // moving onto next point
211 pX+=stepX;
212 pY+=stepY;
213 // saving contour point
214 contourVector.push(new Point(pX, pY));
215 prevX=stepX;
216 prevY=stepY;
217 // drawing the line
218 canvas.graphics.lineTo(pX,pY);
219 // if we returned to the first point visited, the loop has finished;
220 if (pX==startPoint.x&&pY==startPoint.y) {
221 closedLoop=true;
222 }
223 }
224 }
225 return contourVector;
226 }
227
228 private function getStartingPixel(bitmapData:BitmapData):Point {
229 // finding the starting pixel is a matter of brute force, we need to scan
230 // the image pixel by pixel until we find a non-transparent pixel
231 var zeroPoint:Point=new Point(0,0);
232 var offsetPoint:Point=new Point(0,0);
233 for (var i:Number=0; i<bitmapData.height; i++) {
234 for (var j:Number=0; j<bitmapData.width; j++) {
235 offsetPoint.x=j;
236 offsetPoint.y=i;
237 if (bitmapData.hitTest(zeroPoint,tolerance,offsetPoint)) {
238 return offsetPoint;
239 }
240 }
241 }
242 return null;
243 }
244
245 private function getSquareValue(pX:Number,pY:Number):Number {
246 /*
247
248 checking the 2x2 pixel grid, assigning these values to each pixel, if not transparent
249
250 +---+---+
251 | 1 | 2 |
252 +---+---+
253 | 4 | 8 | <- current pixel (pX,pY)
254 +---+---+
255
256 */
257 var squareValue:Number=0;
258 // checking upper left pixel
259 if (getAlphaValue(bitmapData.getPixel32(pX-1,pY-1))>=tolerance) {
260 squareValue+=1;
261 }
262 // checking upper pixel
263 if (getAlphaValue(bitmapData.getPixel32(pX,pY-1))>tolerance) {
264 squareValue+=2;
265 }
266 // checking left pixel
267 if (getAlphaValue(bitmapData.getPixel32(pX-1,pY))>tolerance) {
268 squareValue+=4;
269 }
270 // checking the pixel itself
271 if (getAlphaValue(bitmapData.getPixel32(pX,pY))>tolerance) {
272 squareValue+=8;
273 }
274 return squareValue;
275 }
276
277 private function getAlphaValue(n:Number):Number {
278 // given an ARGB color value, returns the alpha 0 -> 255
279 return n >> 24 & 0xFF;
280 }
281 }
282 }
原文链接:算法减少多边形轮廓点数