-
[F9] Vector class help
Hi,
Never been any good with vectors, but I decided to give it a shot and write a vector class, as part of a simple physics engine. But since I'm no good at it, I'm not sure if it's, well, any good. Could someone please have a look and see if the calculations look right, and if there's anything I could/should have done differently? Thanks.
PHP Code:
package com.gamesquid.physicsquid {
// Written by Sven Magnus
public class Vector {
// public variables
// use these in favour of the getters for slightly better performance
// however, don't set them directly unless you know what you're doing
public var _x1:Number=0;
public var _y1:Number=0;
public var _x2:Number=0;
public var _y2:Number=0;
public var _vx:Number=0;
public var _vy:Number=0;
public var _length:Number=0;
public var _angle:Number=0;
// getter functions
public function get x1():Number {
return this._x1;
}
public function get y1():Number {
return this._y1;
}
public function get x2():Number {
return this._x2;
}
public function get y2():Number {
return this._y2;
}
public function get vx():Number {
return this._vx;
}
public function get vy():Number {
return this._vy;
}
public function get length():Number {
return this._length;
}
public function get angle():Number {
return this._angle;
}
public function get degrees():Number {
return this._angle*180/Math.PI;
}
public function get dx():Number {
return this._vx/this._length;
}
public function get dy():Number {
return this._vy/this._length;
}
public function get rx():Number {
return -this._vy;
}
public function get ry():Number {
return this._vx;
}
public function get lx():Number {
return this._vy;
}
public function get ly():Number {
return -this._vx;
}
public function get normal():Vector {
return new Vector(this._x1,this._y1,this.dx,this.dy);
}
public function get right():Vector {
return new Vector(this._x1,this._y1,this.rx,this.ry);
}
public function get left():Vector {
return new Vector(this._x1,this._y1,this.lx,this.ly);
}
// setter functions
public function set x1(value:Number):void {
this._x1=value;
this._vx=this._x2-value;
this._length=Math.sqrt(this._vx*this._vx+this._vy*this._vy);
this._angle=Math.atan2(this._vy,this._vx);
}
public function set y1(value:Number):void {
this._y1=value;
this._vy=this._y2-value;
this._length=Math.sqrt(this._vx*this._vx+this._vy*this._vy);
this._angle=Math.atan2(this._vy,this._vx);
}
public function set x2(value:Number):void {
this._x2=value;
this._vx=value-this._x1;
this._length=Math.sqrt(this._vx*this._vx+this._vy*this._vy);
this._angle=Math.atan2(this._vy,this._vx);
}
public function set y2(value:Number):void {
this._y2=value;
this._vy=value-this._y1;
this._length=Math.sqrt(this._vx*this._vx+this._vy*this._vy);
this._angle=Math.atan2(this._vy,this._vx);
}
public function set vx(value:Number):void {
this._vx=value;
this._x2=this._x1+value;
this._length=Math.sqrt(this._vx*this._vx+this._vy*this._vy);
this._angle=Math.atan2(this._vy,this._vx);
}
public function set vy(value:Number):void {
this._vy=value;
this._y2=this._y1+value;
this._length=Math.sqrt(this._vx*this._vx+this._vy*this._vy);
this._angle=Math.atan2(this._vy,this._vx);
}
public function set angle(value:Number):void {
this._angle=value;
this._vx=this._length*Math.cos(value);
this._vy=this._length*Math.sin(value);
this._x2=this.x1+this._vx;
this._y2=this.y1+this._vy;
}
public function set length(value:Number):void {
this._length=value;
this._vx=value*Math.cos(this._angle);
this._vy=value*Math.sin(this._angle);
this._x2=this.x1+this._vx;
this._y2=this.y1+this._vy;
}
// constructor
public function Vector(x1:Number,y1:Number,vx:Number,vy:Number):void {
this._x1=x1;
this._y1=y1;
this._vx=vx;
this._vy=vy;
this._x2=this._x1+this._vx;
this._y2=this._y1+this._vy;
this._length=Math.sqrt(this._vx*this._vx+this._vy*this._vy);
this._angle=Math.atan2(this._vy,this._vx);
}
// public functions
public function add(v:Vector):Vector {
return new Vector(this._x1,this._y1,this._vx+v._vx,this._vy+v._vy);
}
public function substract(v:Vector):Vector {
return new Vector(this._x1,this._y1,this._vx-v._vx,this._vy-v._vy);
}
public function dotProduct(v:Vector):Number {
return this._vx*v._vx+this._vy*v._vy;
}
public function project(v:Vector):Vector {
var dp:Number=this._vx*v._vx+this._vy*v._vy;
return new Vector(this._x1,this._y1,dp*this.dx,dp*this.dy);
return new Vector(this._x1,this._y1,dp*this.dx,dp*this.dy);
}
}
}
EDIT: I realize there's some redundancy in there, some stuff that gets repeated that could have been put in functions - like the calculation of the dotproduct when projecting vectors, or recalculating the angle and length - but I figured it would be a tiny bit faster like this, and it's not like these calculations will ever change, right? Unless I made a mistake with them, that is.
-
Not one to critique another persons code, but I'll offer up my own opinions since you asked. :) I use vector types in my own codes quite a bit.
I would pitch length and angle - they are derived quantities and instead of recalculating them every time a x/y component changes, compute them "on demand" through member functions. Also, I think that this class muddies the difference between displacement vectors and position vectors. The way this class defines them, all vectors are displacement vectors with strict start and end points (through the ..1 and ..2 notation).
To me, it makes more sense to model any vector with just 2 components (or 3 .. when thinking in 3D spaces). A vector just has one .x and one .y member. If you need to model a displacement with explicit start and end points, use 2 position vectors - but I find I rarely need to model something like this. (In my codes, position is always a vector with 2 components, velocity is a vector with 2 components. The displacement / change in position that occurs through an update event is not modeled as a "point1", "point2" structure - its just "position += velocity*timestep".)
For what it's worth, here's the vector class that I am using in my current project (doom clone). Take whatever you like.
PHP Code:
package
{
public class Vector2D
{
public var x : Number;
public var y : Number;
public function Vector2D (_x : Number = 0, _y : Number = 0)
{
set(_x,_y);
}
public function set (_x : Number, _y : Number) : void
{
x = _x;
y = _y;
}
public function length () : Number
{
return Math.sqrt(x*x + y*y);
}
public function lengthSquared() : Number
{
return x*x+y*y;
}
public function scale (s : Number) : void
{
x *= s;
y *= s;
}
public function normalize() : void
{
scale( 1.0 / length() );
}
public function add (arg : Vector2D) : void
{
x += arg.x;
y += arg.y;
}
public function subtract(arg : Vector2D) : void
{
x -= arg.x;
y -= arg.y;
}
// Public static "Library" functions that deal with vectors.
// These are binary factory functions that return Vector2D's
public static function createSubtract (v1 : Vector2D, v2 : Vector2D) : Vector2D
{
return new Vector2D (v1.x - v2.x, v1.y - v2.y);
}
public static function createAdd (v1 : Vector2D, v2 : Vector2D) : Vector2D
{
return new Vector2D (v1.x + v2.x, v1.y + v2.y);
}
// These are binary Vector2D ops that return scalars.
public static function dot (v1 : Vector2D, v2 : Vector2D) : Number
{
return v1.x*v2.x + v1.y*v2.y;
}
public static function cross (v1 : Vector2D, v2 : Vector2D) : Number
{
return v1.x*v2.y - v1.y*v2.x;
}
}
}
Not many frills.
I don't mean to say what you have is wrong. Really, a vector class is so simple that I would not fuss much about it's implementation or put a lot of focus on making a flexible / reusable component. Learning the math behind using vector arithmetic effectively is much more important. I would not be ashamed of reimplementing vector arithmetic from time to time, because different applications have different functional requirements. (Maybe you DO need to compute angle all the time, and it should be a stored member... but that's a choice that IMO should be made on an application-by-application basis)
In reality, I find vector arithmetic is often happening inside tight inner loops and I end up just writing a lot of these functions (dot, cross, add, sub) in-place to avoid the overhead of a function call. (If AS3 had an inline function declaration that would do this for you, I would agree that making a full-fledged vector is a lot more sensible proposition).
On the upshot, the implementation you have looks correct. :) But you forgot the cross product! That's the most important op of all (IMO).
-
APE (Actionscript Physics Engine) has a fairly complete Vector class.
---------------------------------------------------
Glaze :2D Tile, Physics, Particle, AI Game Engine
http://yaa-blog.blogspot.com/search/label/Glaze
---------------------------------------------------
-
Thanks for the advice. Exactly what I was hoping for.
I havenow changed my implementation so that I have a NullVector base class (without first coordinates, they are assumed 0 in the calculations), which should be a bit faster when you don't need the first coordinates. Then my Vector class extends this. I plan to use vectors to describe levels (for collision), so it makes sense to have a class with two sets of coordinates. Not too sure about the name NullVector though, but I couldn't think of anything appropriate. (edit: think I'm going to go for DisplacementVector which extends PositionVector, but I'm not sure yet).
As for calculating the angle each time something changes, well, that was a choice I had to make. Faster getting or faster setting of variables. It should be easy to change anyway.
-
I saw you site & followed the discussion on tile engines. I think we have a common interest http://board.flashkit.com/board/showthread.php?t=742953
-
Yeah, I've seen your engine. Nice work!
I think I know quite a bit about scrolling and tile stuff, but my knowledge about physics is way more limited than yours, so I had already planned on looking at your source code asap :)