在網頁上實做簡單的多邊形
Sun, 16 Jul 2006 19:01:41 +0800有時候總是覺得Image Map的功能不合用,所以想要自己做出類似的功能。試了一下,的確可以做出類似的效果。
我的想法是先定義點,線段可以視為點的集合,而多邊形可視為線段的集合。偵測點是否在多邊形內,就可以做到類似Image Map的效果。
雖然大致上可以達到目的(判斷點是否在多邊形內),但是並不是很精確就是了,我並沒有仔細考慮點在靠近端點時可能的問題,或是邊線是垂直或水平的狀況。另外,我判斷的方法也只能適用於簡單的多邊形(沒有凹下去的部份),碰到星形就會有問題。
點比較簡單:
function point (_x, _y) this.x = _x this.y = _y }
線可以如下定義:
function line (_point1, _point2) { this.terminus = new Array(2);//存放端點 this.terminus.push(_point1); this.terminus.push(_point2); this.points = new Array();//存放所有點的集合 //用Bresenham法來畫線 if (Math.abs(_point2.x-_point1.x) > Math.abs(_point2.y-_point1.y)) { var accu = _point1.y; if (_point2.x > _point1.x) { for (var x = _point1.x; x < _point2.x+1; x++) { y = Math.floor(accu); accu += (_point2.y - _point1.y) / (_point2.x - _point1.x); this.points.push(new Point(x,y)); } } else if (_point1.x > _point2.x) { for (var x = _point1.x; x > _point2.x-1; x--) { y = Math.floor(accu); accu -= (_point2.y - _point1.y) / (_point2.x - _point1.x); this.points.push(new Point(x,y)); } } else { if (_point2.y > _point1.y) { for (var y=_point1.y; y<_point2.y+1; y++) { this.points.push(new Point(_point1.x,y)); } } else { for (var y=_point1.y; y>_point2.y-1; y--) { this.points.push(new Point(_point1.x,y)); } } } } else { var accu = _point1.x; if (_point2.y > _point1.y) { for (var y = _point1.y; y < _point2.y+1; y++) { x = Math.floor(accu); accu += (_point2.x - _point1.x) / (_point2.y - _point1.y); this.points.push(new Point(x,y)); } } else if (_point1.y > _point2.y) { for (var y = _point1.y; y > _point2.y-1; y--) { x = Math.floor(accu); accu -= (_point2.x - _point1.x) / (_point2.y - _point1.y); this.points.push(new Point(x,y)); } } else { if (_point2.x > _point1.x) { for (var x=_point1.x; x<_point2.x+1; x++) { this.points.push(new Point(x,_point1.y)); } } else { for (var x=_point1.x; x>_point2.x-1; x--) { this.points.push(new Point(x,_point1.y)); } } } } //以下兩個方法是為了測試一個點是否在多邊形內時用的 //測試一個點在垂直方向是否與本線段相交 //如有相交則傳回相交的點,否則傳回false this.testPointV = function (_point) { var ret = false; for (var i=0; i<this.data.length; i++) { if (this.points[i].x == _point.x) { ret = this.points[i]; } } return ret; } //測試一個點在水平方向是否與本線段相交 //如有相交則傳回相交的點,否則傳回false this.testPointH = function (_point) { var ret = false; for (var i=0; i<this.data.length; i++) { if (this.data[i].y == _point.y) { ret = this.data[i]; } } return ret; } }
接下來定義多邊形:
(測試點是否在多邊形內的方法,目前只有在凸多邊形有效,我還沒想出測試一個點是否在像是星形這樣的多邊形內的方法。所以只要超過四個頂點的多邊形,每個角的角度必須大於九十度....)
function polygon () { this.points = new Array();//存放端點 this.lines = new Array();存放邊線 this.done = false; //加入端點,為了省麻煩,沒有加入檢查的功能 //所以必須依照順序加入端點 this.addPoint = function (_point) { this.points.push(_point); if (this.points.length >2) { this._create(); } } //依照端點產生線段 //為了讓多邊形可以移動位置,所以寫了這個方法 this._create = function () { this.lines = new Array(); for (i=0; i<this.points.length; i++) { if (i+1 == this.points.length) { this.lines.push(new Line(this.points[i], this.points[0])); } else { this.lines.push(new Line(this.points[i], this.points[i+1])); } } this.done = true; } //測試一個點是否在目前的多邊形中 //是則傳回true,否則傳回false //邊線不包含在內,所以點在邊線上時,也視為否 this.testPoint = function (_point) { var pointv = new Array(); var pointh = new Array(); for (var i=0; i<this.lines.length; i++) { var tmp = this.lines[i].testPointV(_point); if (tmp != false) { var test = true; for (var j=0; j<pointv.length; j++) { if (pointv[j].compare(tmp)) { test = false; } } if (test) pointv.push(tmp); } tmp = this.lines[i].testPointH(_point); if (tmp != false) { test = true; for (var j=0; j<pointh.length; j++) { if (pointh[j].compare(tmp)) { test = false; } } if (test) pointh.push(tmp); } } if (pointv.length < 2) { return false; } if (pointh.length < 2) { return false; } var retv = false; var reth = false; var tmp1 = pointv[0].y; if (pointv.length > 2) { var tmp2 = pointv[2].y; } else { var tmp2 = pointv[1].y; } if (tmp1 > tmp2) { if (_point.y < tmp1 && _point.y > tmp2) { retv = true; } } else { if (_point.y > tmp1 && _point.y < tmp2) { retv = true; } } tmp1 = pointh[0].x; if (pointh.length > 2) { tmp2 = pointh[2].x; } else { tmp2 = pointh[1].x; } if (tmp1 > tmp2) { if (_point.x < tmp1 && _point.x > tmp2) { reth = true; } } else { if (_point.x > tmp1 && _point.x < tmp2) { reth = true; } } return (retv && reth); } //將多邊形移動到_point所代表的偏移量的位置 this.move = function (_point) { this.done = false; for (var i=0; i<this.points.length; i++) { this.points[i].x += _point.x; this.points[i].y += _point.y; } this._create(); }
}
下面的函數可以用來畫出一個點,配合以上的功能就可以在任意位置畫出點、線、以及空心的多邊形。
function drawPixel(_point, _color) { var obj = document.createElement("div"); obj.style.position = "absolute"; obj.style.clip = "rect(0,1,1,0)"; obj.style.background = _color; obj.innerHTML = "<span style="line-height:1px;font-size:1px"> </span>"; obj.width = 1; obj.height = 1; obj.style.left = _point.x+"px"; obj.style.top = _point.y+"px"; obj.style.zIndex = 50; obj.style.margin = "0"; obj.style.padding = "0"; document.body.appendChild(obj); }
寫了兩個個簡單的測試網頁:
例一:http://www.fillano.idv.tw/test14.html(抽換圖片)
例二:http://www.fillano.idv.tw/test16.html(顯示多邊形)
不過感覺位置比我預期好像差了一個pixel的樣子,我想判斷點在多邊形內的函數還需要精確地調整。