This page looks plain and unstyled because you're using a non-standard compliant browser. To see it in its best form, please visit upgrade to a browser that supports web standards. It's free and painless.

Fillano's Learning Notes 會員登入 會員註冊

我之前用的方法需要計算出多邊形的邊的點集合,速度比較慢,不適合用來快速偵測滑鼠位置。

現在改用點與多邊形頂點夾角總和的方法,只要用多邊形的頂點就可以快速判斷一個點是否在多邊形內。因為javascript的Math.atan2函數可以處理點與角的關係,所以用javascript的內建函數就可以快速做出計算。(當然,這個方法還是只適用於凸多邊形)

我用下面這個函數來處理多邊形頂點與點的關係:

function inPolygon2 (_points, _point) {
    try {
        if (_points.length < 3) throw "_points must contain at least 3 Point!";
        var sum = new Array();
        for (var i=0; i<_points.length; i++) {
            sum.push(Math.atan2(_points[i].x-_point.x,_points[i].y-_point.y)/Math.PI*180);
        }
        var sum1 = new Array();
        for (var i=0; i 359 && summary < 361) {
            return true;
        } else {
            return false;
        }
    } catch(e) {alert(e);return false;}
}

傳進去的第一個參數是多邊形頂點集合,依照順時鐘或逆時鐘順序排列。第二個參數是要檢測位置的點。

我用另外一個函數來計算夾角:

function countAngle (a1,a2) {
    try {
        if (a1*a2 < 0) {
            var tmp = Math.abs(a1)+Math.abs(a2);
            if (tmp>180) {tmp = 360-tmp;}
            return tmp;
        } else if (a1*a2 >0) {
            return Math.abs(a1-a2);
        } else {
            return Math.abs(a1+a2);
        }
    } catch (e) {alert(e);}
}

比起舊的方法,因為需要判斷的點少很多,速度會非常快。

完成的效果可以參考這個網頁:http://www.fillano.idv.tw/game.htm

點選「網頁遊戲測試 V0.10」的連結就可以看到目前做出來的效果。

為了自己網站改版,隨手寫了一個留言板。並且想了一個簡單的驗證碼機制,防止他人使用機器人在我的留言板亂倒東西。

由於是使用php,所以有一些方便的工具(GD、Session),可以整合出完整的功能。

我的想法如下:

  1. 驗證碼(check_code)存放在資料庫,用一個check_id作為key
  2. 留言板的留言與看留言的介面在同一個頁面,每次進入這個頁面時,驗證碼都要更換。所以不能直接使用session_id作為key
  3. 每次進入頁面時,會刪除舊的check_id,產生新的check_id,同時產生一組check_code存放在資料庫
  4. 用gd產生check_code的圖片,在輸入留言的form上面顯示
  5. 程式之間利用session來取得check_id,check_code不會在client端出現,所以可以確保一些安全性

以下簡述一下我的程式:

第一步還是要在資料庫中建立一個資料表(我用的是mysql),我取名叫做fsession:

CREATE TABLE `fsession` (
  `check_id` varchar(40) NOT NULL default '',
  `check_code` varchar(8) NOT NULL default '00000000',
  PRIMARY KEY  (`check_id`)
) TYPE=MyISAM;

接下來是產生check_id的code:

session_start();
$sql = sprintf("SELECT * FROM fsession WHERE check_id='%s'", $_SESSION['check_id']);
$result = $db->query($sql);
$tmp = $db->num_rows($result);
if ($tmp > 0) {//如果有舊的check_id,就把他從資料庫刪掉
	$sql = sprintf("DELETE FROM fsession WHERE check_id='%s'", $_SESSION['check_id']);
	$db->query($sql);
}
//接下來產生新的check_id以及check_code
$_SESSION['check_id'] = md5(time().rand());
//上面產生check_id
$sql = sprintf("INSERT INTO fsession (check_id, check_code) VALUES ('%s', '%s')", $_SESSION['checkid'], mt_rand(10000000,99999999));
//上面產生一組八位數的check_code
$db->query($sql);
//ok,存入資料庫

(這個方法不太好,因為我也不太確定不同字串產生的md5摘要是否會一樣.....不過因為摘要長度是一樣的,比較方便,所以先用了:D。也許應該用auto_increment的id比較好?只要改一下程式就可以了(先存入資料庫再取得check_id,但是check_id的type要改成int))

接下來是產生驗證碼圖片的程式(使用GD,幾乎從php manual上面的例子照抄....):

session_start();

if (!empty($_SESSION['check_id'])) {
	$sql = sprintf("SELECT check_code FROM fsession WHERE check_id='%s'", $_SESSION['check_id']);
	$result = $db->query($sql);
	if ($db->num_rows($result) == 0) {
		$code = "error! db";
	} else {
		list($code) = $db->fetch_row($result);
	}
} else {
	$code = "error! session";
}

header("Content-type: image/jpeg");
//指定輸出內容的type
$im = imagecreatefromjpeg("images/code_bg.jpg");
//用一張背景圖產生需要的背景
$color = imagecolorallocate($im, 32, 32, 32);
//指定顏色
imagettftext($im, 12, 0, 2, 13, $color, "fonts/georgiai.ttf",$code);
//利用true type字型來產生圖片
imagejpeg($im);
//輸出所產生的圖片內容到瀏覽器
imagedestroy($im);

假設這一隻程式叫做authcode.php,只要在需要圖片的地方使用<img src="authcode.php">就可以顯示圖片。另外,字型檔案與字型大小需要依照系統做調整(也可以用ps、type1等字型,尤其是在linux系統上)

最後,只要在留言存入資料庫的地方,先檢查使用者輸入的檢查碼,與存在資料庫中的檢查碼比對,就可以確認。

之前用Javascript OOP的方式寫的polygon物件,在產生大量polygons時會明顯地變慢。

用Javascript來繪圖可預期的就是會慢,不過原來的寫法還是有改進的空間。想到的方法就是捨棄定義Line以及Polygon物件,而以元素為Point的陣列來代替。一些需要的操作包括產生Line與Polygon的Point陣列、偵測點是否在多邊形內等,都用函數來實做。

如果要進一步改進,可能連Point物件都捨棄,全部用陣列來做,只是要改比較多程式,有空再做吧。

Point的定義仍然相同:

function Point (_x, _y) {
    this.x = _x;
    this.y = _y;
}

產生Line的Point陣列的函數:

function getLine (_points) {
    if (_points.length != 2) {
        return false;//Line只有兩個頂點
    }
    var points = new Array();
    if (Math.abs(_points[1].x-_points[0].x) > Math.abs(_points[1].y-_points[0].y)) {
        var accu = _points[0].y;
        if (_points[1].x > _points[0].x) {
            for (var x = _points[0].x; x < _points[1].x+1; x++) {
                y = Math.floor(accu);
                accu += (_points[1].y - _points[0].y) / (_points[1].x - _points[0].x);
                points.push(new Point(x,y));
            }
        } else if (_points[0].x > _points[1].x) {
            for (var x = _points[0].x; x > _points[1].x-1; x--) {
                y = Math.floor(accu);
                accu -= (_points[1].y - _points[0].y) / (_points[1].x - _points[0].x);
                points.push(new Point(x,y));
            }
        } else {
            if (_points[1].y > _points[0].y) {
                for (var y=_points[0].y; y<_points[1].y+1; y++) {
                    points.push(new Point(_points[0].x,y))
                }
            } else {
                for (var y=_points[0].y; y>_points[1].y-1; y--) {
                    points.push(new Point(_points[0].x,y))
                }
            }
        }
    } else {
        var accu = _points[0].x;
        if (_points[1].y > _points[0].y) {
            for (var y = _points[0].y; y < _points[1].y+1; y++) {
                x = Math.floor(accu);
                accu += (_points[1].x - _points[0].x) / (_points[1].y - _points[0].y);
                points.push(new Point(x,y));
            }
        } else if (_points[0].y > _points[1].y) {
            for (var y = _points[0].y; y > _points[1].y-1; y--) {
                x = Math.floor(accu);
                accu -= (_points[1].x - _points[0].x) / (_points[1].y - _points[0].y);
                points.push(new Point(x,y));
            }
        } else {
            if (_points[1].x > _points[0].x) {
                for (var x=_points[0].x; x<_points[1].x+1; x++) {
                    points.push(new Point(x,_points[0].y))
                }
            } else {
                for (var x=_points[0].x; x>_points[1].x-1; x--) {
                    points.push(new Point(x,_points[0].y))
                }
            }
        }
    }
    return points;
}

產生Polygon的Point陣列的函數:

function getPolygon (_points) {
    if (_points.length < 3) {
        return false;//polygon至少要有三個頂點
    }
    var ret = new Array();
    for (var i=0; i<_points.length; i++) {
        if ((i+1) == _points.length) {
            ret = ret.concat(getLine(new Array(_points[i], _points[0])));
            ret.pop();//刪掉最後一個點,才不會重複
        } else {
            ret = ret.concat(getLine(new Array(_points[i], _points[i+1])));
            ret.pop();
        }
    }
    return ret;
}

偵測一個點是否在多邊形中的函數(簡化很多):

function inPolygon (_polygon, _point) {
    var testUp = false;
    var testDn = false;
    var testLt = false;
    var testRt = false;
//修改了測試方法,由一個點向上下左右延伸出直線,如果與多邊形的邊相交
//則點在多邊形中(必須是凸多邊形才有效,不過我沒考慮邊是水平或垂直的狀況)
    for (var i=0; i<_polygon.length; i++) {
        if (_polygon.x == _point.x && _polygon.y > _point.y)
            testUp = true;
        if (_polygon.x == _point.x && _polygon.y < _point.y)
            testDn = true;
        if (_polygon.y == _point.y && _polygon.x > _point.x)
            testLt = true;
        if (_polygon.y == _point.y && _polygon.x < _point.x)
            testRt = true;
    }
    return (testUp && testDn && testLt && testRt);
}

移動Polygon的函數:

function movePolygon(_polygon, _point) {
    for (var i=0; i<_polygon.length; i++) {
        _polygon[i].x += _point.x;
        _polygon[i].y += _point.y;
    }
}

畫出多邊形的函數,因為用一個<div>代表一個點,其實速度是很慢的:

//處理點陣列,呼叫drawPixel畫出點
function drawPixels(_points, _color) {
    for (var j=0;j<_points.length; j++) {
        drawPixel(_points[j], _color);
    }
}
//畫出點的函數
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 = " ";
    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";
    obj.style.cursor = "default";
    document.body.appendChild(obj);
}

最後做了一點小測試,速度比舊的方法提昇了不少。
(產生256個多邊形、移動、測試點是否在其中、畫出32個多邊形。時間單位:milliseconds)

舊的方法:

Firefox 1 2 3 平均
產生多邊形 1219 1203 1219 1213.67
移動多邊形 1000 984 1000 994.67
測試點位置 1203 1125 1063 1130.33
畫出多邊形 21266 17578 20453 19765.67
IE 6 1 2 3 平均
產生多邊形 29672 29687 29547 29635.33
移動多邊形 50016 51500 50719 50745
測試點位置 1297 860 1157 1104.67
畫出多邊形 103922 103250 103250 103474
新的方法:
Firefox 1 2 3 平均
產生多邊形 797 500 500 599
移動多邊形 63 63 63 63
測試點位置 907 875 921 901
畫出多邊形 4532 4656 4750 4646
IE 6 1 2 3 平均
產生多邊形 1703 1641 1641 1661.67
移動多邊形 94 94 94 94
測試點位置 110 109 94 104.33
畫出多邊形 10062 10297 10031 10130

以下是我測試的連結(慢一點、記憶體較少的機器有可能會當掉,請小心使用):

  1. 舊的方法http://www.fillano.idv.tw/test18.html
  2. 新的方法http://www.fillano.idv.tw/test20.html

有時候總是覺得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的樣子,我想判斷點在多邊形內的函數還需要精確地調整。

不論在IE利用msxml2裡面的xmlhttp物件或是在Mozilla裡面利用XMLHttpRequest物件,都可以使用http的POST方法來向伺服器提出請求。

寫一個簡單的javascript來測試一下。只是看看ajax到底怎麼操作,所以並沒有做多餘的功能。

程式如下:

function xmlhttp() 
{
	try{return new ActiveXObject("Msxml2.XMLHTTP");} catch(e){}
	try{return new ActiveXObject("Microsoft.XMLHTTP");} catch(e){}
	try{return new XMLHttpRequest();} catch(e){}
	alert("XMLHttpRequest Object not existed!!");
	return null;
}
var debug = 0;
function fwXMLHttpForm (_host) {

// Public Properties

	this.xmlhttp = new xmlhttp();
	this.result = "";

// Private Methods

	this._createBoundary = function () {
		var tmp = Math.random();
		var thisDate = new Date();
		tmp = Math.abs(tmp*thisDate.getTime());
		tmp = "--------" + tmp + "--------";
		return tmp;
	}

	this._createField = function (_field, _value) {
		var tmp = "--" + this._boundary + this.CRLF;
		tmp += "Content-Disposition: form-data; name="" + _field + """ + this.CRLF;
		tmp += "Content-Transfer-Encoding: binary" + this.CRLF + this.CRLF;
		tmp += _value + this.CRLF
		tmp += "--" + this._boundary + "--" + this.CRLF + this.CRLF;
		return tmp;
	}

	var formObj = this;
	this.xmlhttp.onreadystatechange = function () {
		if (formObj.xmlhttp.readyState == 4) {
			if (formObj.xmlhttp.status == 200) {
				formObj.result = formObj.xmlhttp.responseText;
			}
			else {
				formObj.errCode = formObj.xmlhttp.status;
				formObj.result = formObj.xmlhttp.responseText;
				alert("Error in request!! HTTP RESPONSE STATUS: " + formObj.errCode);
			}
		}
	}

// Private Properties

	this._boundary = this._createBoundary();
	this._host = _host;
	this._fields = new Array();
	this.CRLF = "rn";

// Public Methods

	this.addField = function (_field, _value) {
		this._fields.push(new Array(_field, _value));
	}

	this.removeField = function (_field) {
		for (var i=0; i<this._fields.length; i++) {
			var tmp = this._fields[i];
			if (tmp[0] == _field) {
				this._fields.splice(i,1);
			}
		}
	}

	this.emptyField = function () {
		this._fields = new Array();
	}

	this.send = function () {
		this.xmlhttp.open("POST", this._host);
		var msgBody = "";
		for (var i=0; i<this._fields.length; i++){
			var tmp = this._fields[i];
			msgBody += this._createField(tmp[0], tmp[1]);
		}
		this.xmlhttp.setRequestHeader("Content-Type","multipart/form-data; boundary="+this._boundary);
		this.xmlhttp.setRequestHeader("Connection","Keep-Alive");
		this.xmlhttp.setRequestHeader("Content-Length",msgBody.length);
		this.xmlhttp.send(msgBody);
	}
}



使用方法:

  • var form1=new fwXMLHttpForm("backend.php");
  • form1.addField("field_name","field_value");
  • form1.send();
  • 檢查form1.xmlhttp.readyState,看看資料是否接收完畢
  • 從form1.result取得結果,顯示在畫面上
  • 檢查form1.xmlhttp.readyState,最好用setTimeout或是setInterval的方式做,用while迴圈會有問題。
  • 這裡是一個簡單的測試程式