ID:1863471
 
(See the best response by Kaiochao.)
I have this code but the problem is that the angle it returns isn't relative to the forward vector of my character. If the target "b" is directly in front of "a" then the angle should be 0, directly behind should be 180. etc. But how would I do that?

Right now it is based on some sort of world directional value, not relative to any particular object.

Code:
proc/get_angle(mob/a,mob/b)
//first you have to find the distance between the ref and the object
var/dx=(b.x+b.step_x/world.icon_size) - (a.x+a.step_x/world.icon_size)
var/dy=(b.y+b.step_y/world.icon_size) - (a.y+a.step_y/world.icon_size)
//then the angle equals arctan(distx/disty)
return arctan(dx,dy)

proc/arctan(x,y)
//x=clamp(x,1,x)
//y=clamp(y,1,y)
return (y>=0)?(arccos(x/sqrt(x*x+y*y))):(-arccos(x/sqrt(x*x+y*y)))
Best response
There are a few ways to get a relative angle.
1. Get the difference between your forward direction angle (you'll need to convert a dir to an angle) and the angle to the target (found using atan2, which is what you have).
2. Use the scalar or vector product of a normalized forward vector and the normalized vector to your target.

I gotta head to class, so I'll explain more later unless someone else does.
Perfect. Thanks for the information it now works great.
I might as well post it here in case it is somehow useful to people
proc/get_abs_angle(mob/a,mob/b)
return abs(get_angle(a,b))

proc/get_angle(mob/a,mob/b)
//first you have to find the distance between the ref and the object
var/dx=(b.x+b.step_x/world.icon_size) - (a.x+a.step_x/world.icon_size)
var/dy=(b.y+b.step_y/world.icon_size) - (a.y+a.step_y/world.icon_size)
//then the angle equals arctan(distx/disty)
var/ang = arctan(dx,dy)
//convert to relative angle
ang-=a.dir_to_angle()
return ang

atom/proc/dir_to_angle()
switch(dir)
if(EAST) return 0
if(NORTHEAST) return 45
if(NORTH) return 90
if(NORTHWEST) return 135
if(WEST) return 180
if(SOUTHWEST) return -135
if(SOUTH) return -90
if(SOUTHEAST) return -45

proc/arctan(x,y)
var/n = arccos(x/sqrt(x*x+y*y))
if(y>=0) return n
else return -n

I'm sure it's not the best code in the world but it functions
I'd probably do this as a new function called get_angle_relative() or something. The original get_angle() is useful on its own for other things.

Also remember, after you subtract dir_to_angle() you probably want to normalize the result to 0 to 360, or -180 to 180 depending on your needs.

Better dir to angle conversion:
// way faster than a switch!
var/list/dir_to_angle = list(0,180,0,90,45,135,0,270,315,225)

proc/get_angle_relative(mob/a,mob/b)
. = get_angle(a, b)
. -= dir_to_angle[a.dir]
// normalize to 0 to 360
. %= 360
// if you want to normalize to -180 to 180 instead:
// if(. > 180) . -= 360

What's going on with that dir_to_angle you got there, lummox?

NORTH (1) = 0
SOUTH (2) = 0
NORTH|SOUTH (3) = 180
EAST (4) = 0
NORTH|EAST (5) = 90
SOUTH|EAST (6) = 45
NORTH|SOUTH|EAST (7) = 135
WEST (8) = 0
NORTH|WEST (9) = 270
SOUTH|WEST (10) = 315
NORTH|SOUTH|WEST (11) = 225

These are all kinds of weird.

I also really dislike the deg 0 is SOUTH/NORTH thing that's been so common around here. Deg 0 corresponds to a positive orientation along the local x axis. Never liked the confusing/backward atan2 that's been passed around here.

Here's a bunch of projectile helper stuff I've been using for about a year now.

var
list/__ang_dirs = list(EAST,NORTHEAST,NORTH,NORTHWEST,WEST,SOUTHWEST,SOUTH,SOUTHEAST)
list/__dir_angs = list(90,270,null,0,45,315,0,180,135,225,180,null,90,180,null)

#define floor(V) round(V)
#define ceil(V) (-round(-V))
#define round_inner(V) (V<0 ? ceil(V) : floor(V))
#define round_outer(V) (V<0 ? floor(V) : ceil(V))
#define clamp(V,L,H) min(max(V,L),H)

#define left_x(O) (O:x*TILE_WIDTH + (ismovable(O) ? O:step_x + O:bound_x : 0))
#define bottom_y(O) (O:y*TILE_HEIGHT + (ismovable(O) ? O:step_y + O:bound_y : 0))
#define center_x(O) (O:x*TILE_WIDTH + (ismovable(O) ? O:step_x + O:bound_x + O:bound_width/2 - 1 : floor(TILE_WIDTH/2)-1))
#define center_y(O) (O:y*TILE_HEIGHT + (ismovable(O) ? O:step_y + O:bound_y + O:bound_height/2 - 1 : floor(TILE_HEIGHT/2)-1))
#define right_x(O) (O:x*TILE_WIDTH + (ismovable(O) ? O:step_x + O:bound_x + O:bound_width - 1 : TILE_WIDTH-1))
#define top_y(O) (O:y*TILE_HEIGHT + (ismovable(O) ? O:step_y + O:bound_y + O:bound_height - 1 : TILE_HEIGHT-1))

proc
atan2(x,y)
. = (x||y)&&(y>=0 ? arccos(x/sqrt(x*x+y*y)) : 360-arccos(x/sqrt(x*x+y*y)))

#define ang2dir(ang) (__ang_dirs[floor(((ang) + 22.5) / 45) % 8 + 1])
#define dir2ang(dir) (__dir_angs[dir])

#define get_angle(Ref1,Ref2) atan2(center_x(Ref2)-center_x(Ref1),center_y(Ref2)-center_y(Ref1))
In response to Ter13
Ter13 wrote:
What's going on with that dir_to_angle you got there, lummox?

Oops. Forgot for a minute that this was 1-based indexing. Fixed it.

I also really dislike the deg 0 is SOUTH/NORTH thing that's been so common around here. Deg 0 corresponds to a positive orientation along the local x axis. Never liked the confusing/backward atan2 that's been passed around here.

0° being in the +x direction is a mathematical convention. 0° being north is navigational convention. Both are equally correct, but I find the navigational one makes way more sense in BYOND games because directions are used so often.
You are talking about vectors vs bearings. I guess it's a matter of background, but every programmer I've ever known has exclusively used vectors rather than bearings in game development. Maybe bearings are more common in aviation, shipping, and locomotive programming jobs, but I don't like bucking at convention.

The vast majority of tutorials and resources you will find for gamedev are written using vectors and not bearings, so for the purpose of teaching, it's likely good to remember that many of our folk are still not quite at the point where they are branching out to other languages and translating tutorials from C/Java into DM. But when they get to that point, teaching them the nonstandard bearings is almost setting them up for failure.

But that's just my two cents.