ID:1807330
 
(See the best response by Ter13.)
Problem description: I'm back again and it's projectiles. They are just plain inaccurate in some instances. You can see an example of the problem here.

I believe Ter touches on this issue in this post. However I believe I've accounted for that as seen in the test code below.

    var/v = 6
var/dx = (v) * sin(a)
var/dy = (v) * cos(a)
o.transform = turn(matrix(), a)
spawn()
for(var/i=1 to 100)
if(!o.loc)return
var/rx = dx - round(dx, 1)
var/ry = dy - round(dy, 1)
var/ox = dx + rx
var/oy = dy + ry

o.Move(o.loc, o.dir, o.step_x+round(ox,1), o.step_y+round(oy,1))
sleep(0.25)
o.dispose()


I appreciate any help in advance.
Best response
Well, here's my process:

1) Get the angle to the target via atan2:

#define floor(x) round(x)
#define ceil(x) (-round(-(x)))
#define ismovable(x) istype(x,/atom/movable)
//the following functions get the global pixel coordinates of the specified side of the atom's bounding box
#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)
return (x||y)&&(y>=0 ? arccos(x/sqrt(x*x+y*y)) : 360-arccos(x/sqrt(x*x+y*y)))

getang(atom/a,atom/b)
return atan2(center_x(b)-center_x(a),center_y(b)-center_y(a))


2) get angle to the target. You only need to do this if the target changes/moves.

var/ang = getang(src,target)


3) cache x_step and y_step values. You only need to do this when the angle changes.

projectile
var
x_step
y_step


x_step = cos(ang)
y_step = sin(ang)


4) Perform the movement:

var/absx = left_x(src)
var/absy = bottom_x(src)

while(loc)
absx += x_step * velocity
absy += y_step * velocity

. = Move(locate(absx/TILE_WIDTH,absy/TILE_HEIGHT,z),0,absx%TILE_WIDTH,absy%TILE_WIDTH)
if(!.)
loc = null
return
sleep(TICK_LAG)


Works fine for us: https://gfycat.com/RawWatchfulBrownbear
Also, we smooth out the projectile's movement a bit by preserving the decimal value at each step and applying it to the atom's transform, but it does cause blurring a bit. If you have a small resolution, don't do that. You can probably improve the projectile's accuracy a bit by rounding the absx/absy to the nearest value, rather than allowing it to be truncated. Just remember that you should maintain the non-rounded value for all further steps, rather than rounding the incremental value itself.
FKI wrote:
Problem description: I'm back again and it's projectiles. They are just plain inaccurate in some instances. You can see an example of the problem here.

I believe Ter touches on this issue in this post. However I believe I've accounted for that as seen in the test code below.

    var/v = 6
var/dx = (v) * sin(a)
var/dy = (v) * cos(a)
o.transform = turn(matrix(), a)
spawn()
for(var/i=1 to 100)
if(!o.loc)return
var/rx = dx - round(dx, 1)
var/ry = dy - round(dy, 1)
var/ox = dx + rx
var/oy = dy + ry

o.Move(o.loc, o.dir, o.step_x+round(ox,1), o.step_y+round(oy,1))
sleep(0.25)
o.dispose()

The problem comes from the fact that you're mis-applying the principles from this post when trying to hold onto your decimal remainder.

At each step, you're calculating the remainder anew instead of preserving it and making it part of dx/dy. And frankly I have no idea what the ox/oy vars are even supposed to do; they make no mathematical sense and I think they're only there because you didn't understand how to make the remainders work.

Look at your code again. rx/ry are constant in your code, because dx/dy are constant and they're the only things that factor into their calculation. ox/oy are therefore also constant. The formula you're using for ox is effectively dx*2-round(dx,1), and likewise for y, which means your direction and velocity will both be startlingly inaccurate any time you're not facing a cardinal direction. Then you're rounding that and discarding the decimal offsets.

Going back to my original post, this is how the concept should look in your code:
    var/v = 6
// vx/y are velocity
var/vx = (v) * sin(a)
var/vy = (v) * cos(a)
o.transform = turn(matrix(), a)
var/rx=0, ry=0, dx, dy
spawn()
for(var/i=1 to 100)
if(!o.loc) return
// adjust vx/y to include remainders from previous ticks
dx = rx + vx
dy = ry + vy
// get new remainders
rx = dx - round(dx,1)
ry = dy - round(dy,1)

o.Move(o.loc, o.dir, o.step_x+round(dx,1), o.step_y+round(dy,1))
sleep(0.25)
o.dispose()
@Ter13: The center_x/y did the trick; atan2 wasn't using the right formula. I could not, however, get your setup to work. Is there a particular reason you are moving the projectiles from the bottom left corner?

@Lummox JR: Good catch. I ended up confusing myself in the midst of writing messy test code.

Appreciate both posts, thanks.
It doesn't matter what point you move the projectiles from because they are always equidistant from one another.
The distance and angle could vary depending on what "home point" is used for targeting--center or corner--if the source and destination have different sizes.
The distance and angle could vary depending on what "home point" is used for targeting--center or corner--if the source and destination have different sizes.

You're talking about the delta for the atan2 call, which always uses center in my code.

I'm talking about after we've determined the angle, where we increment the x/y components determined from the angle we got from the delta between the centers of the two objects. We're considering the current position every frame from the bottom-left of the object that's moving. Not the delta.
Ah, gotcha.