在網頁上實做簡單的多邊形

 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">&nbsp;</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的樣子,我想判斷點在多邊形內的函數還需要精確地調整。