GameDev – Vector 2D cơ bản

Vector 2D ProjectionỨng dụng của vector rất quan trọng trong lĩnh vực lập trình game và đồ họa. Thông qua vector, ta có thể mô phỏng được các chuyển động, tính toán lực, hướng di chuyển sau khi va chạm,…

Khái niệm

“…một vectơ là một phần tử trong một không gian vectơ, được xác định bởi ba yếu tố: điểm đầu (hay điểm gốc), hướng (gồm phương và chiều) và độ lớn (hay độ dài).” (Wiki)

Từ một đoạn thẳng AB ta có thể xác định được vector (mã giả):

u.root = A;
u.x = B.x-A.x;
u.y = B.y-A.y;
u.length = sqrt(u.x*u.x+u.y*u.y); // |u|

Vector đơn vị (Unit Vector, Normalized Vector)

Vector đơn vị của u là vector có chiều dài bằng 1 và kí hiệu là û. Vector u sau được gọi là vector đơn vị của v bằng cách tính (mã giả):

û = u/|u|

Như vậy:
|û| = 1

Tích vô hướng (Dot product, Scalar product)

Phép toán tích vô hướng của hai vector được biểu diễn dấu chấm (nên được gọi dot product) và được tính như sau:

v.u = |v||u|cosθ
= v1u1 + v2u2 + … + vnun

Với θ (theta) là góc giữa v, u và n là chiều của không gian.

Từ công thức trên, ta có thể tính được θ:

cosθ = (v.u)/(|v||u|)
θ = arccos(cosθ)

Phép chiếu (Projection)

Một ứng dụng khác của tích vô hướng là tính phép chiếu của một vector này lên vector khác. Tương tự như việc chiếu một vector v thành 2 giá trị x và y lên trục Oxy. (Hình từ wikipedia)

Vector - Dot Product

Một vector a được gọi là vector chiếu của v lên u nếu như nó có cùng phương với u và có độ lớn:

|a| = |v|cosθ

Tổng quát hơn với hai vector bất kì, xét biểu thức:
|a| = |v|cosθ

Với u là vector tạo với v một góc θ, từ công thức:

cosθ = (v.u)/(|v||u|)

Suy ra:

|a| = |v|(v.u)/(|v||u|) = (v.u)/|u|

Vậy ta đã tính được độ lớn của vector v chiếu trên vector u. Từ giá trị |a| này, ta có thể tính được vector a bằng cách nhân với vector đơn vị của u:

a = |a|û

Hiện thực với javascript

Một đối tượng Line được biểu diễn bởi 2 điểm đầu (p1) và cuối (p2) của một đoạn thẳng. Từ hai điểm này, ta có phương thức getVector() để lấy về một đối tượng vector của đoạn thẳng.

Line.prototype.getVector = function() {
	var x = this.p2.x-this.p1.x;
	var y = this.p2.y-this.p1.y;

	return {
		x: x,
		y: y,
		root: this.p1,
		length: Math.sqrt(x*x+y*y)
	};
}

Phương thức update() sau được gọi mỗi khi một đoạn thẳng bị thay đổi:

function update(){
	var v1 = _line1.getVector();

	var v2 = _line2.getVector();

	// normalize vector v2
	v2.dx = v2.x/v2.length;
	v2.dy = v2.y/v2.length;

	// dot product
	var dp = v1.x*v2.x + v1.y*v2.y;
	// length of the projection of v1 on v2
	var length = dp/v2.length;

	// the projection vector of v1 on v2
	var v3 = {
		x: length*v2.dx,
		y: length*v2.dy,
	};
	// the projection line
	_line3.p2.x = _line3.p1.x+v3.x;
	_line3.p2.y = _line3.p1.y+v3.y;

	// calculate the angle between v1 and v2
	// and convert it from radians into degrees
	_angle = Math.acos(dp/(v1.length*v2.length))*(180/Math.PI);
	_angle = Math.round(_angle*100)/100;
}

Trong phần demo sau, tôi thực hiện phép chiếu vector màu đỏ lên vector xanh lá. Kết quả của phép chiếu là vector màu xanh lam. Bạn có thể thay đổi hướng và độ lớn của vector bằng cách nhấn nhấn và rê chuột vào đầu mũi tên.

Xem Demo.

Vector 2D Projection

Mã nguồn:

<html>
<head>
  <script>
 /***************** Circle ***************/

function Circle(x,y) {
    this.x = x;
    this.y = y;
	this.radius = 5;
}

Circle.prototype.draw = function(context) {
    context.beginPath();
    context.fillStyle = "black";
    context.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
    context.closePath();
    context.fill();
}
Circle.prototype.setPosition = function(x, y) {
    this.x = x + this.radius;
    this.y = y + this.radius;
}
Circle.prototype.contains = function(x, y) {
    var dx = this.x - x;
    var dy = this.y - y;

    return Math.sqrt(dx * dx + dy * dy) <= this.radius;
}

/***************** Line ***************/

function Line(x1,y1,x2,y2,color,hasHandles) {
    this.color = color;
	this.p1 = new Circle(x1,y1,5);
	this.p2 = new Circle(x2,y2,5);
	this.hasHandles = hasHandles;
}
Line.prototype.captureMouse = function(x,y) {
	if(this.p2.contains(x,y))
		return this.p2;
}
Line.prototype.getVector = function() {
	var x = this.p2.x-this.p1.x;
	var y = this.p2.y-this.p1.y;

	return {
		x: x,
		y: y,
		root: this.p1,
		length: Math.sqrt(x*x+y*y)
	};
}
Line.prototype.draw = function(context) {

	context.beginPath();
	context.strokeStyle = this.color;
	context.lineWidth = this.width;

	var headsize = 15;
    var angle = Math.atan2(this.p2.y-this.p1.y,this.p2.x-this.p1.x);
    context.moveTo(this.p1.x, this.p1.y);
    context.lineTo(this.p2.x, this.p2.y);
	context.lineTo(this.p2.x-headsize*Math.cos(angle-Math.PI/6),this.p2.y-headsize*Math.sin(angle-Math.PI/6));
    context.moveTo(this.p2.x, this.p2.y);
    context.lineTo(this.p2.x-headsize*Math.cos(angle+Math.PI/6),this.p2.y-headsize*Math.sin(angle+Math.PI/6));
	context.closePath();
	context.stroke();

	if(this.hasHandles){
		this.p1.draw(context);
		this.p2.draw(context);
	}
}
 /************** Main *************/

var _canvas;
var _context;
var _movingShape = false;
var _line1;
var _line2;
var _line3;
var _angle; // angle between _line1 and _line2

function canvas_mousedown(e) {

    var x = e.pageX - _canvas.offsetLeft;
    var y = e.pageY - _canvas.offsetTop;

    _movingShape = _line1.captureMouse(x, y);
    if(!_movingShape)
		_movingShape = _line2.captureMouse(x, y);
}

function canvas_mousemove(e) {
	var x = e.pageX - _canvas.offsetLeft;
	var y = e.pageY - _canvas.offsetTop;

	if (_movingShape) {
        _movingShape.setPosition(x, y);
		update();
		draw();
    }
}

function canvas_mouseup(e) {
    _movingShape = null;
}

function clear() {
    _context.clearRect(0, 0, _canvas.width, _canvas.height);
}
function update(){
	var v1 = _line1.getVector();

	var v2 = _line2.getVector();

	// normalize vector v2
	v2.dx = v2.x/v2.length;
	v2.dy = v2.y/v2.length;

	// dot product
	var dp = v1.x*v2.x + v1.y*v2.y;
	// length of the projection of v1 on v2
	var length = dp/v2.length;

	// the projection vector of v1 on v2
	var v3 = {
		x: length*v2.dx,
		y: length*v2.dy,
	};
	// the projection line
	_line3.p2.x = _line3.p1.x+v3.x;
	_line3.p2.y = _line3.p1.y+v3.y;

	// calculate the angle between v1 and v2
	// and convert it from radians into degrees
	_angle = Math.acos(dp/(v1.length*v2.length))*(180/Math.PI);
	_angle = Math.round(_angle*100)/100;
}
function draw() {
    clear();
    _line1.draw(_context);
	_line2.draw(_context);
	_line3.draw(_context);

	_context.fillText("Theta: "+_angle+"°",10,20);
}

window.onload = function(){
	_canvas = document.getElementById("canvas");
	_context = _canvas.getContext("2d");
	_context.font = "16px Arial";
	_context.lineWidth = 3;

	_line1 = new Line(100, 200, 250, 150,"red",true);
	_line2 = new Line(100, 200, 380, 200,"green",true);
	_line3 = new Line(100, 200, 100, 200,"blue",false);
	_canvas.onmousedown = canvas_mousedown;
	_canvas.onmousemove = canvas_mousemove;
	_canvas.onmouseup = canvas_mouseup;

	update();
	draw();
}
  </script>
</head>
<body>
<canvas id="canvas" width="400px" height="400px" style="border: 1px solid"></canvas>
</body>
</html>

Tham khảo

http://vi.wikipedia.org/wiki/Vect%C6%A1
http://en.wikipedia.org/wiki/Dot_product

Advertisements

7 thoughts on “GameDev – Vector 2D cơ bản

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s