ID:650501
 
Applies to:DM Language
Status: Open

Issue hasn't been assigned a status value.
get_angle() would work like get_dir() or get_dist(), but would return the angle between two atoms as pixel specifically as possible.
In the meantime, you can use this: http://www.byond.com/forum/?post=162287#comment716390

You could of course adjust the parameters to simply accept two atoms.
I have already built my own systems for use in Box Zombies, but it is something that should be included for common use, especially now that pixel movement is.
Having vectors as a built in data type would probably be more useful. Any value measured as an angle and magnitude can also be represented as a vector, but vectors are also useful for things that you wouldn't think of as a direction and magnitude. You often don't care about the angle from one object to another, just the vector. If you want to make one mob move in the direction of another object (and not just limit the direction to the 8 cardinal directions) you don't need to get angles, just vectors.
Forum_account wrote:
You often don't care about the angle from one object to another, just the vector.
That sounds backwards...

If you want to make one mob move in the direction of another object (and not just limit the direction to the 8 cardinal directions) you don't need to get angles, just vectors.
Isn't a vector simply a list of slope/coordinates? You would just be determining the angle of movement by comparing the vectors, instead of directly comparing their coordinates (which is what the vectors would represent).

If you were to use vectors, additional math would have to be expected internally to handle their processing, since you would basically be adding values within two lists. You would also still need a get_angle() function (or something like vector.angle) to apply to the vectors, since a vector itself is not a numerical representation of an angle, or the movement procs would have to be updated to accept a vector as a direction.

Now that pixel movement is built-in, a standard check using get_dir() is rarely sufficient in determining when you are actually facing another object.
A vector is a direction. In 2D, it'd be a pair of numbers. The vector [1 0] points east, [0 1] points north, [0 -1] points south, etc.

The difference in position between two atoms can be expressed as a vector, so you could do something like this:

// find the vector pointing from mob to some_atom.
var/vector/v = vector(mob, some_atom)

// make the mob walk in the direction the vector points.
walk(mob, v)

For almost all cases you don't need to know the angle, knowing the vector is sufficient. For the cases where you'd use an angle you typically know it already. To make a projectile move in the direction the mob is facing, you'd know the mob's angle, you don't have to do any work to find it.

If you were to use vectors, additional math would have to be expected internally to handle their processing, since you would basically be adding values within two lists.

It'd take more work but it'd be worthwhile. Vectors have a lot of uses in games. Having built-in support for vectors would greatly improve performance and would make BYOND look more legitimate and appealing to game developers.

And you would still need a get_angle() function to apply to the vectors.

I don't see why. Do you have an example?
In response to Forum_account
Forum_account wrote:
For almost all cases you don't need to know the angle, knowing the vector is sufficient. For the cases where you'd use an angle you typically know it already. To make a projectile move in the direction the mob is facing, you'd know the mob's angle, you don't have to do any work to find it.
Hardly. If you make a game where you click to shoot, you have to calculate the direction between the player and the location clicked. If you are attempting to shoot a fireball from the player to some targeted monster, you would have to calculate the angle between them. If you wanted to update the path of some sort of homing attack, you would want to constantly update the angle between itself and a potentially moving target on the fly.

It'd take more work but it'd be worthwhile. Vectors have a lot of uses in games. Having built-in support for vectors would greatly improve performance and would make BYOND look more legitimate and appealing to game developers.
It wouldn't be all that much work, at least not through DM, and it would be generally beneficial, but I don't consider it necessary for this request. Even if there were vectors, a simple way to get the angle between two atoms would still be useful, without having to create a new vector by adding their vectors and then referencing a variable on that.

I don't see why. Do you have an example?
As I mentioned, a vector is not actually a direction, it is simply a list of coordinates that represent the slope from one point to another. For example, if somebody were standing at locate(3,5,1) their vector would most simply be list(3,5). To get an actual angle out of this, you have to apply trigonometric functions to that represented slope.
If somebody else were at locate(5,5,1), their vector would be list(5,5). You could compare these by doing var/vector/newVector=A1.vector-A2.vector, which would give you a new list of (-2,0), meaning they were standing 2 tiles to the left, with an angle of 180 degrees if you were to apply some trig to that slope.
Falacy wrote:
As I mentioned, a vector is not actually a direction

A vector is a direction and magnitude. You can find the angle represented by a vector but it's usually not needed. When you want to make a mob move by an angle, the angle has to be converted to a vector. Dealing with directions as angles is often a roundabout way to do things.

If you make a game where you click to shoot, you have to calculate the direction between the player and the location clicked. If you are attempting to shoot a fireball from the player to some targeted monster, you would have to calculate the angle between them. If you wanted to update the path of some sort of homing attack, you would want to constantly update the angle between itself and a potentially moving target on the fly

For both of those cases it's easier to use vectors. It'd work like the example in my last post. The only time you'd need to find the angle here is if you wanted to output a message to the user (ex: to say "your target is located 10 degrees west of north").
In response to Forum_account
Forum_account wrote:
A vector is a direction and magnitude.
A vector has direction and magnitude, but (2,3) is not a direction nor a magnitude. It is a list containing slope that represents such data. You can then use those numbers to determine an angle/direction and/or length/magnitude.

For both of those cases it's easier to use vectors.
Assuming all I give you is the slope, and you perform no additional operations (like converting it into an angle) you can not perform proper movement. If you just attempted moving some objects by the slope between them, step sizes could be 100 pixels per tick, and would incrementally decrease (if updated based on positions). The angle, on the other hand, would not change, regardless of the distance.

If you were making some sort of homing attack, you would most likely not want it jerking in directions from 90 degrees to 180 degrees, but instead slowly rotating by a certain maximum amount per step. You would need actual numerical representations of angles to regulate such an effect.

It'd work like the example in my last post.
Assuming that walk() is updated to take a vector as a direction, such angular conversions/calculations would just be getting done internally. I would still want an easy way to access the angular (and other) data myself, as is provided with Unity's Vectors.
(2,3) is not a direction nor a magnitude.

The vector [2 3] points at a 56.3099 degree angle and has a magnitude of sqrt(13).

Assuming that walk() is updated to take a vector as a direction, such angular conversations/calculations would just be getting done internally.

Do you know how you make an object move by an angle and magnitude? You convert it to a vector, then perform the move specified by the vector. If you give the proc a vector there's no conversion necessary. That's why this is roundabout - you're going out of your way to represent a vector as an angle which will just get converted to a vector internally.

If you just attempted moving some objects by the slope between them, step sizes could be 100 pixels per tick

There would likely be a built-in proc to scale the vector to a certain length. Also, the walk() proc takes a speed argument, so it'd scale the vector to that length by default to get the magnitude of the moves.

I would still want an easy way to access the angular data myself, as is provided in Unity's Vectors.

Unity's vectors don't provide that. The Angle() method there finds the angle between two vectors. If you had the vectors [1 0] and [-1 1] it'd return 135 degrees. This is useful but not in the way you think. They don't provide a built-in way to construct a vector from a given angle and magnitude because developers just use vectors.
Vector
var/x=0,y=0
New(var/x=0,var/y=0)
src.Set(x,y)
return ..()
proc/Set(var/x,var/y)
if(!isnull(x)) src.x=x
if(!isnull(y)) src.y=y
proc/GetAngle() return arctan(src.x,src.y)
proc/GetLength() return sqrt(src.x*src.x + src.y*src.y)
proc/SetAngle(var/newAngle)
var/curLen=src.GetLength()
src.x=curLen*cos(newAngle)
src.y=curLen*sin(newAngle)
proc/SetLength(var/newLen)
var/curAngle=src.GetAngle()
src.x=newLen*cos(curAngle)
src.y=newLen*sin(curAngle)
proc/Normalize()
var/curLen=src.GetLength()
if(curLen)
src.x/=curLen
src.y/=curLen
else src.SetLength(1)
proc/Scale(var/scaleBy)
src.x*=scaleBy
src.y*=scaleBy
proc/Output(var/outputTo)
outputTo<<"<b>([src.x], [src.y])"
outputTo<<"Angle: [src.GetAngle()]"
outputTo<<"Length: [src.GetLength()]"
I'd guess it's faster to, in the SetAngle() proc, to say:

src.x = curLen * cos(newAngle)

Instead of using sqrt().

There are also much easier ways to change the length of a vector. You don't need to find it's angle, you just need its length:

SetLength(newLen)
var/curLen = GetLength()
x = (x / curLen) * newLen
y = (y / curLen) * newLen

Dividing each component by the length normalizes the vector (makes it have a length of one). That's a useful operation to have on its own. Most implementations would just provide a normalizing function and a scaling function. If you want to set the length you'd call normalize, then scale.
Any suggestions for how to implement math? Can't overload operators in DM.
There are two ways to approach it. One way is to make procs that modify the current vector:

vector/proc/add(vector/v)
x += v.x
y += v.y

The other is to make these procs return a new vector:

vector/proc/add(vector/v)
return new /vector(x + v.x, y + v.y)

The problem with this whole approach is that you're creating a lot of objects and I don't trust the speed of BYOND's garbage collector. I'd lean towards the first approach to reduce the number of objects you create but it's not always intuitive to use vectors in that way. Often you'll want to find the sum of two vectors but you don't want to modify either of the vectors. If vectors were a built-in data type they could have much better performance.
ProcLib by Kaiochao is the best implementation of vectors I've seen for DM. I just ran a test where you create two random vectors and add them together. It took jt_vectors (which is similar to your approach) 0.120 seconds to do that 500 times while it only took Kaiochao's vectors 0.005 seconds. Another approach I came up with (shown below) took 0.021 seconds.

var
Vectors/Vec = new()

Vectors
var
list/x = list()
list/y = list()
list/len = list()

proc
vec2(u, v)
x += u
y += v
return x.len

add(a, b)
return vec2(x[a] + x[b], y[a] + y[b])

// to run the test you'd do:
var/a = Vec.vec2(rand(), rand())
var/b = Vec.vec2(rand(), rand())
var/c = Vec.add(a, b)
In response to Forum_account
Forum_account wrote:
There are two ways to approach it. One way is to make procs that modify the current vector:
I was more concerned about the type of proc to implement vs what it actually did. Something like add(v1,v2) & subtract(v1,v2) vs one proc that took an argument for the type of math to perform like VectorMath(v1,v2,ADD)

It took jt_vectors (which is similar to your approach) 0.120 seconds to do that 500 times while it only took Kaiochao's vectors 0.005 seconds. Another approach I came up with (shown below) took 0.021 seconds.
A. I'm not sure why you consider them similar approaches?
B. I'm not sure where you're getting those numbers
In my tests, running each 50,000 times:
His: 0.101 / Untraceable
Yours: 0.129 / 0.448
Mine: 0.171 / 0.546
I was more concerned about the type of proc to implement vs what it actually did. Something like add(v1,v2) & subtract(v1,v2) vs one proc that took an argument for the type of math to perform like VectorMath(v1,v2,ADD)

Oh, I'd definitely go with the first approach.

A. I'm not sure why you consider them similar approaches?

Of the three methods, you're both using objects to represent vectors.

B. I'm not sure where you're getting those numbers

I made three verbs that each looked like this:

mob/verb/test1()
for(var/i = 1 to 500)
var/a = new_vec2(rand(), rand())
var/b = new_vec2(rand(), rand())
var/c = vec2_add(a, b)

The times were the total CPU times for the calls to the verb. I wouldn't be surprised if garbage collection was not represented properly in the times, which would probably benefit my approach the most since it's not using lists or objects for each vector.
In response to Forum_account
Forum_account wrote:
Of the three methods, you're both using objects to represent vectors.
He isn't? He just has a bunch of global procedures defined, creating a new vector simply returns a list.
Yours is using a single Vector datum to hold a list of every vector in existence, which seems like it would be really awkward in practical use and never free memory.

I made three verbs that each looked like this:
My for loop was formatted a bit differently, and I had a start and stop debug message, but I pretty much did the same.

The times were the total CPU times for the calls to the verb.
The numbers I listed were Self / Total
He isn't? He just has a bunch of global procedures defined, creating a new vector simply returns a list.

I didn't look through jt_vectors too much, but you call new /vector(x, y) to create a vector.

Yours is using a single Vector datum to hold a list of every vector in existence, which seems like it would be really awkward in practical use and never free memory.

It's not great in terms of memory usage, I just wanted to see how low the CPU usage would be (to see if it beat Kaiochao's). Based on your numbers it looks like you might have some amount of overhead added to each one (or maybe that's the difference that catching the GC time makes).
Page: 1 2