Friday 24 February 2012

Actionscript 3- Vector to Array

There’s currently no way to convert a Vector to an Array (or ArrayCollection), so here’s a simple util that’ll do it. Accepts any type within the Vector, so the parameter is just an Object, then we convert it back to a vector before the Array conversion. Phew.
  1. public static function VectorToArray( v:Object ):Array  
  2. {  
  3.     var vec : Vector.<object> = Vector.<object>(v);  
  4.     var arr : Array = new Array()  
  5.     for each( var i : Object in vec ) {  
  6.         arr.push(i);  
  7.     }  
  8.     return arr;  
  9. }  
  10. </object></object>  
Post to Twitter

2 Responses to “Actionscript 3- Vector to Array”

Daniel Bunte says:
Hi there!
Just found this post. Am I allowed to clean and speed it up a bit?
public static function VectorToArray( v:Object ):Array
{
var vec : Vector. = v as Vector.,
arr : Array = [];
for each( var i : Object in vec )
{
arr[arr.length] = i;
}
return arr;
}
Cheers,
Daniel

Thursday 23 February 2012

Flash Using AS3 Vectors in Flash CS3

Here is the source code for an AS3 vector 2d class I wrote (Vec2.as and Vec2Const.as), feel free to use it and link back if you like it. Here are some features:
  • Implemented for most operations, add, sub, div, mul, scale, dot, normalize, cross, length, distance, rotation, spinor, lerp, slerp, reflection etc
  • Separation of read only operations and write operations.
  • Most methods have support for self modification (postfix with Self, e.g. addSelf).
  • Most methods have support for component wise operations (postfix with XY, e.g. addXY and addXYSelf).
Over the years I’ve written a couple of vector classes for different languages this time I ported an old C++ class to AS3. Well I couldn’t exactly port it, I more or less had to rewrite it due to differences in the two languages. What I really like about C++ is it’s strict typing and how it allows (good) developers to create code that is more or less impossible to misuse by for example using ‘const’ keywords.

As I mentioned earlier in my post AS3 and constants AS3 is missing such a feature. Another feature it’s missing is operator overloading (ability to create your own += operators). Both of those features are highly desirable for vector implementations.

Operator overloading and naming convention

It’s not hard to see why we want operator overloading considering this c++ example
1
2
3
Vec2 v1(1, 2);
Vec2 v2(3, 4);
v1 += v2; // Operator overloading of +=
However, in AS3 we have to settle with something less nice -methods that do the same work. Many implementations (even Adobes Vector3d among others) name the += ‘operator’ methods to ‘incrementBy’ or ‘decrementBy’ while ‘add’ and ‘sub’ returns a copy (v1 + v2). As an API user I find this rather confusing – how do I know which is modifying itself and which is returning a copy per intuition?
I decided to postfix the name all methods that modify itself with ‘Self’, so for example ‘v1.addSelf(v2);’ means v1 adds v2 to itself. This can easily be applied on any method without having to break your brain to figure out a good descriptive name.
For example what would you name a normalize() method if you wanted one that modified itself and another that returned a normalized vector? This naming convention makes it easy to keep things consequent and pretty self explanatory.
1
2
3
Vec2 v1 = new Vec2(1, 2);
Vec2 v2 = new Vec2(3, 4);
v1.addSelf(v2); // Substitute for +=

Const, or the absense of const

Just to sketch out why we want const consider the following scenario. You have a bot class, the bot class has a position. Only the bot is allowed to modify it position. Everything outside the bot is only allowed to read the position, how do we solve that? There are several ways mentioned here. For this Vector2d class I decided to go with inheritance for performance reasons. Since the vector objects often are involved in many computations, performance is relevant.
In short, using an interface for read only methods would force us to always access the components (x and y) via an intermediate get/set accessor (much slower than direct access). By doing this by inheritance internal operations will always work directly on the components and it’s easy to convert (use precompile options) to make direct access externally.
The class that contains read only methods is called Vec2Const and read and write operations are in Vec2.
1
2
3
public function getPos():Vec2Const { return _pos; } // only expose readables
...
private const _pos:Vec2 = new Vec2;
If you don’t get the point of const you could just go with the Vec2 and you will be fine =)

Enough talking – just give me the code

Most of the code is self explanatory, however I am aware that some parts would require some documentation (spinors, crossDet, lerp and slerp among other). Alrighy!

Vec2Const.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package shared.math
{
    /**
     * A 2d Vector class to perform constant operations. Use this class to make sure that objects stay consts, e.g.
     * public function getPos():Vec2Const { return _pos; } // pos is not allowed to change outside of bot.
     *
     * Many method has a postfix of XY - this allows you to operate on the components directly i.e.
     * instead of writing add(new Vec2(1, 2)) you can directly write addXY(1, 2);
     *
     * For performance reasons I am not using an interface for read only specification since internally it should be possible
     * to use direct access to x and y. Externally x and y is obtained via getters which are a bit slower than direct access of
     * a public variable. I suggest you stick with this during development. If there is a bottleneck you can just remove the get
     * accessors and directly expose _x and _y (rename it to x and replace all _x and _y to this.x, this.y internally).
     *
     * The class in not commented properly yet - just subdivided into logical chunks.
     *
     * @author playchilla.com
     *
     * License: Use it as you wish and if you like it - link back!
     */
    public class Vec2Const
    {
        public static const Zero:Vec2Const = new Vec2Const;
        public static const Epsilon:Number = 0.0000001;
        public static const EpsilonSqr:Number = Epsilon * Epsilon;
 
        public function get x():Number { return _x; }
        public function get y():Number { return _y; }
 
        public function Vec2Const(x:Number = 0, y:Number = 0)
        {
            _x = x;
            _y = y;
        }
 
        public function clone():Vec2 { return new Vec2(_x, _y); }
 
        /**
         * Add, sub, mul and div
         */
        public function add(pos:Vec2Const):Vec2 { return new Vec2(_x + pos._x, _y + pos._y); }
        public function addXY(x:Number, y:Number):Vec2 { return new Vec2(_x + x, _y + y); }
 
        public function sub(pos:Vec2Const):Vec2 { return new Vec2(_x - pos._x, _y - pos._y); }
        public function subXY(x:Number, y:Number):Vec2 { return new Vec2(_x - x, _y - y); }
 
        public function mul(vec:Vec2Const):Vec2 { return new Vec2(_x * vec._x, _y * vec._y); }
        public function mulXY(x:Number, y:Number):Vec2 { return new Vec2(_x * x, _y * y); }
 
        public function div(vec:Vec2Const):Vec2 { return new Vec2(_x / vec._x, _y / vec._y); }
        public function divXY(x:Number, y:Number):Vec2 { return new Vec2(_x / x, _y / y); }
 
        /**
         * Scale
         */
        public function scale(s:Number):Vec2 { return new Vec2(_x * s, _y * s); }
 
        /**
         * Normalize
         */
        public function normalize():Vec2
        {
            const nf:Number = 1 / Math.sqrt(_x * _x + _y * _y);
            return new Vec2(_x * nf, _y * nf);
        }
 
        /**
         * Distance
         */
        public function length():Number { return Math.sqrt(_x * _x + _y * _y); }
        public function lengthSqr():Number { return _x * _x + _y * _y; }
        public function distance(vec:Vec2Const):Number
        {
            const xd:Number = _x - vec._x;
            const yd:Number = _y - vec._y;
            return Math.sqrt(xd * xd + yd * yd);
        }
        public function distanceXY(x:Number, y:Number):Number
        {
            const xd:Number = _x - x;
            const yd:Number = _y - y;
            return Math.sqrt(xd * xd + yd * yd);
        }
        public function distanceSqr(vec:Vec2Const):Number
        {
            const xd:Number = _x - vec._x;
            const yd:Number = _y - vec._y;
            return xd * xd + yd * yd;
        }
        public function distanceXYSqr(x:Number, y:Number):Number
        {
            const xd:Number = _x - x;
            const yd:Number = _y - y;
            return xd * xd + yd * yd;
        }
 
        /**
         * Queries.
         */
        public function equals(vec:Vec2Const):Boolean { return _x == vec._x && _y == vec._y; }
        public function equalsXY(x:Number, y:Number):Boolean { return _x == x && _y == y; }
        public function isNormalized():Boolean { return Math.abs((_x * _x + _y * _y)-1) < EpsilonSqr; }
        public function isZero():Boolean { return _x == 0 && _y == 0; }
        public function isNear(vec2:Vec2Const):Boolean { return distanceSqr(vec2) < EpsilonSqr; }
        public function isNearXY(x:Number, y:Number):Boolean { return distanceXYSqr(x, y) < EpsilonSqr; }
        public function isWithin(vec2:Vec2Const, epsilon:Number):Boolean { return distanceSqr(vec2) < epsilon*epsilon; }
        public function isWithinXY(x:Number, y:Number, epsilon:Number):Boolean { return distanceXYSqr(x, y) < epsilon*epsilon; }
        public function isValid():Boolean { return !isNaN(_x) && !isNaN(_y) && isFinite(_x) && isFinite(_y); }
        public function getDegrees():Number { return getRads() * _RadsToDeg; }
        public function getRads():Number { return Math.atan2(_y, _x); }
 
        /**
         * Dot product
         */
        public function dot(vec:Vec2Const):Number { return _x * vec._x + _y * vec._y; }
        public function dotXY(x:Number, y:Number):Number { return _x * x + _y * y; }
 
        /**
         * Cross determinant
         */
        public function crossDet(vec:Vec2Const):Number { return _x * vec._y - _y * vec._x; }
        public function crossDetXY(x:Number, y:Number):Number { return _x * y - _y * x; }
 
        /**
         * Rotate
         */
        public function rotate(rads:Number):Vec2
        {
            const s:Number = Math.sin(rads);
            const c:Number = Math.cos(rads);
            return new Vec2(_x * c - _y * s, _x * s + _y * c);
        }
        public function normalRight():Vec2 { return new Vec2(-_y, _x); }
        public function normalLeft():Vec2 { return new Vec2(_y, -_x); }
        public function negate():Vec2 { return new Vec2( -_x, -_y); }
 
        /**
         * Spinor rotation
         */
        public function rotateSpinor(vec:Vec2Const):Vec2 { return new Vec2(_x * vec._x - _y * vec._y, _x * vec._y + _y * vec._x); }
        public function spinorBetween(vec:Vec2Const):Vec2
        {
            const d:Number = lengthSqr();
            const r:Number = (vec._x * _x + vec._y * _y) / d;
            const i:Number = (vec._y * _x - vec._x * _y) / d;
            return new Vec2(r, i);
        }
 
        /**
         * Lerp / slerp
         * Note: Slerp is not well tested yet.
         */
        public function lerp(to:Vec2Const, t:Number):Vec2 { return new Vec2(_x + t * (to._x - _x), _y + t * (to._y - _y)); }
 
        public function slerp(vec:Vec2Const, t:Number):Vec2
        {
            const cosTheta:Number = dot(vec);
            const theta:Number = Math.acos(cosTheta);
            const sinTheta:Number = Math.sin(theta);
            if (sinTheta <= Epsilon)
                return vec.clone();
            const w1:Number = Math.sin((1 - t) * theta) / sinTheta;
            const w2:Number = Math.sin(t * theta) / sinTheta;
            return scale(w1).add(vec.scale(w2));
        }
 
        /**
         * Reflect
         */
        public function reflect(normal:Vec2Const):Vec2
        {
            const d:Number = 2 * (_x * normal._x + _y * normal._y);
            return new Vec2(_x - d * normal._x, _y - d * normal._y);
        }
 
        /**
         * String
         */
        public function toString():String { return "[" + _x + ", " + _y + "]"; }
 
        internal var _x:Number;
        internal var _y:Number;
 
        private static const _RadsToDeg:Number = 180 / Math.PI;
    }
}

Vec2.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package shared.math
{
    /**
     * A 2d Vector class to that is mutable.
     *
     * Due to the lack of AS3 operator overloading most methods exists in different names,
     * all methods that ends with Self actually modifies the object itself (including obvious ones copy, copyXY and zero).
     * For example v1 += v2; is written as v1.addSelf(v2);
     *
     * The class in not commented properly yet - just subdivided into logical chunks.
     *
     * @author playchilla.com
     *
     * License: Use it as you wish and if you like it - link back!
     */
    public class Vec2 extends Vec2Const
    {
        public function Vec2(x:Number = 0, y:Number = 0) { super(x, y); }
 
        /**
         * Copy / assignment
         */
        public function set x(x:Number):void { _x = x; }
        public function set y(y:Number):void { _y = y; }
 
        public function copy(pos:Vec2Const):Vec2
        {
            _x = pos._x;
            _y = pos._y;
            return this;
        }
        public function copyXY(x:Number, y:Number):Vec2
        {
            _x = x;
            _y = y;
            return this;
        }
        public function zero():Vec2
        {
            _x = 0;
            _y = 0;
            return this;
        }
 
        /**
         * Add
         */
        public function addSelf(pos:Vec2Const):Vec2
        {
            _x += pos._x;
            _y += pos._y;
            return this;
        }
        public function addXYSelf(x:Number, y:Number):Vec2
        {
            _x += x;
            _y += y;
            return this;
        }
 
        /**
         * Sub
         */
        public function subSelf(pos:Vec2Const):Vec2
        {
            _x -= pos._x;
            _y -= pos._y;
            return this;
        }
        public function subXYSelf(x:Number, y:Number):Vec2
        {
            _x -= x;
            _y -= y;
            return this;
        }
 
        /**
         * Mul
         */
        public function mulSelf(vec:Vec2Const):Vec2
        {
            _x *= vec._x;
            _y *= vec._y;
            return this;
        }
        public function mulXYSelf(x:Number, y:Number):Vec2
        {
            _x *= x;
            _y *= y;
            return this;
        }
 
        /**
         * Div
         */
        public function divSelf(vec:Vec2Const):Vec2
        {
            _x /= vec._x;
            _y /= vec._y;
            return this;
        }
        public function divXYSelf(x:Number, y:Number):Vec2
        {
            _x /= x;
            _y /= y;
            return this;
        }
 
        /**
         * Scale
         */
        public function scaleSelf(s:Number):Vec2
        {
            _x *= s;
            _y *= s;
            return this;
        }
 
        /**
         * Normalize
         */
        public function normalizeSelf():Vec2
        {
            const nf:Number = 1 / Math.sqrt(_x * _x + _y * _y);
            _x *= nf;
            _y *= nf;
            return this;
        }
 
        /**
         * Rotate
         */
        public function rotateSelf(rads:Number):Vec2
        {
            const s:Number = Math.sin(rads);
            const c:Number = Math.cos(rads);
            const xr:Number = _x * c - _y * s;
            _y = _x * s + _y * c;
            _x = xr;
            return this;
        }
        public function normalRightSelf():Vec2
        {
            const xr:Number = _x;
            _x = -_y
            _y = xr;
            return this;
        }
        public function normalLeftSelf():Vec2
        {
            const xr:Number = _x;
            _x = _y
            _y = -xr;
            return this;
        }
        public function negateSelf():Vec2
        {
            _x = -_x;
            _y = -_y;
            return this;
        }
 
        /**
         * Spinor
         */
        public function rotateSpinorSelf(vec:Vec2Const):Vec2
        {
            const xr:Number = _x * vec._x - _y * vec._y;
            _y = _x * vec._y + _y * vec._x;
            _x = xr;
            return this;
        }
 
        /**
         * lerp
         */
        public function lerpSelf(to:Vec2Const, t:Number):Vec2
        {
            _x = _x + t * (to._x - _x);
            _y = _y + t * (to._y - _y);
            return this;
        }
 
        /**
         * Helpers
         */
        public static function swap(a:Vec2, b:Vec2):void
        {
            const x:Number = a._x;
            const y:Number = a._y;
            a._x = b._x;
            a._y = b._y;
            b._x = x;
            b._y = y;
        }
    }
}

Vec2Test.as

Finally some unit test so you can see how it’s used.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
private function _testBasic():void
{
    var v1:Vec2 = new Vec2;
    Debug.assert(v1.isZero());
 
    // add sub mul div
    v1.addSelf(new Vec2(1, 2)).subSelf(new Vec2(3, 4)).mulSelf(new Vec2(2, -3)).divSelf(new Vec2(2, 3));
    Debug.assert(v1.isNearXY( -2, 2));
 
    v1 = new Vec2().addXY(1, 2).subXY(3, 4).mulXY(2, -3).divXY(2, 3);
    Debug.assert(v1.isNearXY( -2, 2));
 
    v1 = new Vec2().add(new Vec2(1, 2)).sub(new Vec2(3, 4)).mul(new Vec2(2, -3)).div(new Vec2(2, 3));
    Debug.assert(v1.isNearXY( -2, 2));
 
    // scale
    Debug.assert(v1.copyXY(1, 2).scaleSelf(3).equalsXY(3, 6));
    Debug.assert(v1.copyXY(1, 2).scale(3).equalsXY(3, 6));
 
    // normalize
    Debug.assert(v1.copyXY(10, 0).normalizeSelf().equalsXY(1, 0));
    Debug.assert(v1.copyXY(0, 10).normalizeSelf().equalsXY(0, 1));
    Debug.assert(v1.copyXY(1, 1).normalizeSelf().isWithinXY(0.7, 0.7, 0.1));
    Debug.assert(v1.isNormalized());
    Debug.assert(v1.copyXY(1, 1).normalize().isWithinXY(0.7, 0.7, 0.1));
    Debug.assert(!v1.isNormalized());
 
    // rotate
    Debug.assert(v1.copyXY(1, 0).normalLeft().equalsXY(0, -1));
    Debug.assert(v1.copyXY(1, 0).normalRight().equalsXY(0, 1));
    Debug.assert(v1.copyXY(1, 0).normalLeftSelf().equalsXY(0, -1));
    Debug.assert(v1.copyXY(1, 0).normalRightSelf().equalsXY(0, 1));
    Debug.assert(v1.copyXY(-13, 3).negate().equalsXY(13, -3));
    Debug.assert(v1.copyXY( -13, 3).negateSelf().equalsXY(13, -3));
    Debug.assert(v1.copyXY(1, 0).rotate(Math.PI * 0.5).isNearXY(0, 1));
    Debug.assert(v1.copyXY(1, 0).rotateSelf(Math.PI * 0.5).isNearXY(0, 1));
    Debug.assert(Near.isNear(v1.getRads(), Math.PI * 0.5));
    Debug.assert(Near.isNear(v1.getDegrees(), 90));
 
    // swap
    var v2:Vec2 = new Vec2(3, 4);
    Vec2.swap(v1.copyXY(12, 13), v2);
    Debug.assert(v1.equalsXY(3, 4));
    Debug.assert(v2.equalsXY(12, 13));
 
    // distance
    Debug.assert(Near.isNear(v1.copyXY(3, 4).length(), 5));
    Debug.assert(v1.lengthSqr() == 25);
    Debug.assert(v1.distance(new Vec2(3, 4)) == 0);
    Debug.assert(v1.distance(new Vec2(3, 0)) == 4);
    Debug.assert(v1.distanceXY(3, 0) == 4);
 
    // dot
    Debug.assert(v1.copyXY(1, 0).dotXY(1, 0) == 1);
    Debug.assert(v1.copyXY(1, 0).dotXY( -1, 0) == -1);
    Debug.assert(v1.copyXY(1, 0).dotXY(0, 1) == 0);
    Debug.assert(v1.copyXY(1, 1).normalize().dot(v1.normalLeft()) == 0);
 
    // cross
    Debug.assert(v1.copyXY(1, 0).crossDetXY(1, 0) == 0)
    Debug.assert(v1.copyXY(1, 0).crossDetXY(0, -1) == -1)
    Debug.assert(v1.copyXY(1, 0).crossDetXY(0, 1) == 1)
    Debug.assert(v1.copyXY(1, 0).crossDet(new Vec2(1, 0)) == 0)
    Debug.assert(v1.copyXY(1, 0).crossDet(new Vec2(0, -1)) == -1)
    Debug.assert(v1.copyXY(1, 0).crossDet(new Vec2(0, 1)) == 1)
 
    // lerp
    Debug.assert(v1.copyXY(1, 0).lerp(new Vec2(0, -1), 0.5).isWithinXY(0.5, -0.5, 0.01));
    Debug.assert(v1.copyXY(1, 0).lerp(new Vec2(-1, 0), 0.5).isWithinXY(0, 0, 0.01));
    Debug.assert(v1.copyXY(1, 0).lerpSelf(new Vec2(0, -1), 0.5).isWithinXY(0.5, -0.5, 0.01));
    Debug.assert(v1.copyXY(1, 0).lerpSelf(new Vec2(-1, 0), 0.5).isWithinXY(0, 0, 0.01));
 
    // slerp (need more testing)
    Debug.assert(v1.copyXY(1, 0).slerp(new Vec2(0, -1), 0.5).isWithinXY(0.7, -0.7, 0.1));
}