• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Vectors (2d)

Status
Not open for further replies.
Level 12
Joined
Jan 2, 2016
Messages
973
Recently I started working with C#, and I noticed, that a code I write with about 10 rows in JASS can be written with 1 row in C#, and I figured, that the usage of Vectors was allowing me to do that, so I made a little library for Vectors.
Today I searched for such library, and found another one, but it uses 3d vectors, which I find to be unneeded for most purposes in Warcraft, so I'll just post what I've done :p
JASS:
library Points

   struct Point
       real x
       real y

       static method create takes real x, real y returns thistype
           local thistype this = thistype.allocate()
           set this.x = x
           set this.y = y
           return this
       endmethod

       method move takes real x, real y returns nothing
           set this.x = x
           set this.y = y
       endmethod

       static method distance takes Point pt1, Point pt2 returns real
           local real w = pt2.x - pt1.x
           local real h = pt2.y - pt1.y
           if w != 0 then
               if w > 0 then
                   return w/Cos(Atan2(h, w))
               else
                   return -w/Cos(Atan2(h, w))
               endif
           elseif h != 0 then
               if h > 0 then
                   return h/Sin(Atan2(h, w))
               else
                   return -h/Sin(Atan2(h, w))
               endif
           else
               return 0.
           endif
       endmethod

       static method angle takes Point pt1, Point pt2 returns real
           return Atan2(pt2.y - pt1.y, pt2.x - pt1.x)
       endmethod

       method moveUnit takes unit u returns nothing
           call SetUnitX(u, this.x)
           call SetUnitY(u, this.y)
       endmethod

       method moveUnit2 takes unit u returns nothing
           call SetUnitPosition(u, .x, .y)
       endmethod

       method moveByVector takes Vector v returns nothing
           set this.x = this.x + v.x
           set this.y = this.y + v.y
       endmethod

       static method polarOffset takes Point p, real d, real a returns Point
           local Point p2 = Point.allocate()
           set p2.x = p.x + d*Cos(a)
           set p2.y = p.y + d*Sin(a)
           return p2
       endmethod

       static method withOffset takes Point p, real x, real y returns Point
           local Point p2 = Point.allocate()
           set p2.x = p.x + x
           set p2.y = p.y + y
           return p2
       endmethod
   endstruct

endlibrary

library Vectors

   struct Vector
       real x
       real y

       static method create takes real x, real y returns thistype
           local thistype this = thistype.allocate()
           set this.x = x
           set this.y = y
           return this
       endmethod

       static method create2 takes real a, real l returns thistype
           local thistype this = thistype.allocate()
           set this.x = l*Cos(a)
           set this.y = l*Sin(a)
           return this
       endmethod

       method getLength takes nothing returns real
           return SquareRoot(.x*.x + .y*.y)
       endmethod

       method getAngle takes nothing returns real
           return Atan2(y, x)
       endmethod

       method getSquaredLength takes nothing returns real
           return .x*.x + .y*.y
       endmethod

       static method betweenPoints takes Point pt1, Point pt2 returns thistype
           return thistype.create(pt2.x - pt1.x, pt2.y - pt1.y)
       endmethod

       static method getNormalized takes real x, real y returns thistype
           local thistype this = thistype.allocate()
           local real a = Atan2(y, x)
           set this.y = Sin(a)
           set this.x = Cos(a)
           return this
       endmethod

       method normalize takes nothing returns nothing
           local real a = Atan2(.y, .x)
           set .x = Cos(a)
           set .y = Sin(a)
       endmethod

       static method fromAngle takes real a returns thistype
           local thistype this = thistype.allocate()
           set this.x = Cos(a)
           set this.y = Sin(a)
           return this
       endmethod

       method replace takes real x, real y returns nothing
           set this.x = x
           set this.y = y
       endmethod

       method replace2 takes real a, real l returns nothing
           set this.x = l*Cos(a)
           set this.y = l*Sin(a)
       endmethod

       method rotate takes real a returns nothing
           local real l = SquareRoot(.x*.x + .y*.y)
           set a = Atan2(.y, .x) + a
           set .x = l*Cos(a)
           set .y = l*Sin(a)
       endmethod

       method scale takes real s returns nothing
           set this.x = this.x*s
           set this.y = this.y*s
       endmethod

       method moveUnit takes unit u returns nothing
           call SetUnitX(u, GetUnitX(u) + this.x)
           call SetUnitY(u, GetUnitY(u) + this.y)
       endmethod

       method moveUnitBy takes unit u, real s returns nothing
           local real a = Atan2(.y, .x)
           call SetUnitX(u, GetUnitX(u) + s*Cos(a))
           call SetUnitY(u, GetUnitY(u) + s*Sin(a))
       endmethod

       method sum takes thistype v returns nothing
           set this.x = v.x + this.x
           set this.y = v.y + this.y
       endmethod

       method add takes real w, real h returns nothing
           set .x = .x + w
           set .y = .y + h
       endmethod

       method difference takes thistype v returns nothing
           set .x = .x - v.x
           set .y = .y - v.y
       endmethod

       method subtract takes real w, real h returns nothing
           set .x = .x - w
           set .y = .y - h
       endmethod

       static method dotProduct takes thistype v1, thistype v2 returns real
           return v1.x*v2.x + v1.y*v2.y
       endmethod

       method setLength takes real l returns nothing
           set l = l/SquareRoot(.x*.x + .y*.y)
           set .x = .x*l
           set .y = .y*l
       endmethod

       method setAngle takes real a returns nothing
           local real l = SquareRoot(.x*.x + .y*.y)
           set .x = l*Cos(a)
           set .y = l*Sin(a)
       endmethod
   endstruct
endlibrary
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
It is (as far as I know), but I find it useful to have the length and angle saved there as well, and not having to calculate them every time I need them.
Honestly I still have yet to rework most of my triggers to use vectors and points, so I'm not sure how often will I use them, but at least in theory they sount good to have :p
 
In maths vector is defined by pre defined scalars and it's also scaleable. It has a direction, and a length, and is not staticly located, but only applied.

Chaosy is talking of the data structure "vector" that is used in some programming languages to store data, similar like in lists.
It is also defined by scalar elements one could say, and also kind of has a length (vector size), though it has not really something to do with the maths vector from above. You can not apply it in geometric operations.

Wereelf, the other one you found was by Anitarf on wc3.net? iirc he has one in 3d.
 
Level 1
Joined
Feb 15, 2017
Messages
6
You're saying you don't want to calculate angle and length every time you need them, but now you're calculating them every time you might not even need them.

You'd make yourself a huge favor if you just used square root, it would make the code much more understandable. Square root won't be causing you performance issues - you're not going to write a heavy physics simulation in JASS anyway.

Also, your API is very confusing. What does moving units have anything to do with vectors. My suggestions:

1. Remove readonly attribute from x and y.
2. Replace a and l members with set/getAngle and set/getLength + getLengthSquared.
3. Try to make method names start with a verb, such as normalized -> normalize.
4. Remove unit related stuff.
5. Maybe add cross product, which is a scalar in 2D. (Cross Product -- from Wolfram MathWorld)
6. Point and Vector to separate libraries.
7. And maybe methods for adding scalar values.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Wereelf, the other one you found was by Anitarf on wc3.net? iirc he has one in 3d.
Yeah, that's the one I saw.
You're saying you don't want to calculate angle and length every time you need them, but now you're calculating them every time you might not even need them.

You'd make yourself a huge favor if you just used square root, it would make the code much more understandable. Square root won't be causing you performance issues - you're not going to write a heavy physics simulation in JASS anyway.

Also, your API is very confusing. What does moving units have anything to do with vectors. My suggestions:

1. Remove readonly attribute from x and y.
2. Replace a and l members with set/getAngle and set/getLength + getLengthSquared.
3. Try to make method names start with a verb, such as normalized -> normalize.
4. Remove unit related stuff.
5. Maybe add cross product, which is a scalar in 2D. (Cross Product -- from Wolfram MathWorld)
6. Point and Vector to separate libraries.
7. And maybe methods for adding scalar values.
1. Done (I actually originally posted it without the readonly stuff, and don't recall editing it in it).
3. "normalized" is a static method that creates a normalizeD vector. "normalize" sounds more like a method that normalizes an already exsisting vector. It'd be more confusing if I called this method "normalize". Will ADD a "normalize" method tho.
4. I am using the unit related methods, as I created this library mostly for making my triggers shorter, and simpler, and moving units around is something I do in my triggers.
7. Care to explain what exactly do you mean with that? Perhaps an example?

I'll consider the points I didn't reply to :p
 
Level 1
Joined
Feb 15, 2017
Messages
6
For example
JASS:
// Adding vector
method sum takes Vector other returns nothing
    set this.x = this.x + other.x
    set this.y = this.y + other.y
endmethod

// Adding scalar
method add takes real value returns nothing
    set this.x = this.x + value
    set this.y = this.y + value
endmethod

method difference takes Vector other returns nothing
    set this.x = this.x - other.x
    set this.y = this.y - other.y
endmethod

method subtract takes real value returns nothing
    set this.x = this.x - value
    set this.y = this.y - value
endmethod
Maybe normalize for normalizing an instance, and static getNormalized that returns a new Vector:
JASS:
call v1.normalize()
set v2 = Vector.getNormalized(v3)
Just throwing ideas. You do what you feel is right, of course.

Oh and maybe a function to get a perpendicular vector:
JASS:
static method getPerpendicular takes Vector v returns Vector
    return Vector.create(-this.y, this.x)
endmethod
And you can make methods chainable too by returning the instance:
JASS:
call v.add(16.0).normalize()
 
Level 1
Joined
Feb 15, 2017
Messages
6
Okay, I changed it, and included your suggestions. Without the perpendicular one, since there is the rotate method.
Removed the l and a as well :p
Not that speed is a huge issue when you need a perpendicular vector, but compare these:
JASS:
method rotate takes real a returns nothing
    local real l = SquareRoot(.x*.x + .y*.y)
    set a = Atan2(.y, .x) + a
    set .x = l*Cos(a)
    set .y = l*Sin(a)
endmethod

static method getPerpendicular takes Vector v returns Vector
    return Vector.create(-y, x)
endmethod

method exampleA takes nothing returns nothing
    local Vector v = Vector.create(.0, 16.0)
    local Vector perp = Vector.create(v.x, v.y)
    call perp.rotate(bj_PI * .5)
endmethod

method exampleB takes nothing returns nothing
    local Vector v = Vector.create(.0, 16.0)
    local Vector perp = Vector.getPerpendicular(v)
endmethod
But it's also a lot more readable.
 
Level 6
Joined
Jul 30, 2013
Messages
282
on the topic of normalize vs normalized

python has a similar duality:

list.sort() # sort a list in place, returns None
vs
sorted(list) # makes a copy of the list, sorts it then returns it (conceptually at least)

So i think both of these have value. doing things in place can be more efficcient sometimes but it means that the original value is effectively destroyed. creating a new object with d3esired properties is sometimes more expensive but can allow access to the original object (maybe you need to have both the original and normalized vector and use them in parallel or switch between them? compare them in some way maybe?)
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
You should probably put "[Math]" in the title because people confuses this between this vector and this vector.

Also, I would suggest caching the 'length' and 'angle' of the vector. What I mean is, the first time it is need, calculate it using SquareRoot/Atan and put it in some private attribute. Then unless the vector is changed (e.g. scaled, change direction, etc.), the next time it is needed, it will refer to the cached value before calculating it again.

Also in my opinion, add, subtract, etc should be a static method with two Vector input arguments. Example, it should be (in my opinion) Vector.add(v1, v2) instead of v1.add(v2).

Lastly, there are some redundant functions like method replace takes real x, real y returns nothing, static method getNormalized takes real x, real y returns thistype. Oh and uhm, your syntax is inconsistent, sometimes you put this.x, sometimes just .x and sometimes it is just x.
 
Level 1
Joined
Feb 15, 2017
Messages
6
I think having a static method named add is bad naming practice, as you are not adding anything to the struct. Static methods belong to the struct, not to the instance.

To me
JASS:
static method add takes Vector v1, Vector v2 returns nothing
looks like adding v1 and v2 to the Vector struct.
 
Last edited:
Level 6
Joined
Jul 30, 2013
Messages
282
if you say
c = a + b
.. or by extension c=add(a,b)... or c=a.add(b)

you would expect c to be the sum of a and b and for a and b to be unchanged.
for a method add (static or not..) that returns nothing you clearly violate this intuition.

a method called add should behave like maths. if you want to do things in place... you can do so if you will have significant savings of some sort... then name it something else.. like addInPlace or addi for example
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Depends if you want "add" to return a new vector or if you want it to manipulate the current vector. Actually, now that I think about it, my suggestion should be called sum. Vector.sum(v1, v2) returns a new vector that is the sum of v1 and v2 while v1.add(v2) will change the value of v1 by adding v2 to it.

Also, using Complex numbers will provide most of the same functionality.
 
Status
Not open for further replies.
Top