Absolute Positions

by Kaiochao
Helper functions for pixel movement worlds.
ID:1468964
 
This library includes:
  • atom.Px(P), atom.Py(P) - The absolute pixel position of the bottom-left of this atom. Include a P percentage (0 to 1) to add a percentage of the atom's size.
  • atom.Cx(), atom.Cy() - The absolute pixel position of the center of this atom. Equivalent to atom.Px(0.5), atom.Py(0.5).
  • movable.SetLoc(Loc, StepX, StepY) - A function to directly set the loc, step_x, and step_y variables. Good for overriding; call ..() to actually do the thing.
  • movable.SetPosition(Px, Py, Z) - Set the absolute pixel position of the bottom-left corner of your movable atom's bounding box.
  • movable.SetCenter(Cx, Cy, Z) - Set the absolute pixel position of the center of the movable atom.
  • movable.Translate(Dx, Dy, Dir) - Move a movable atom by a certain number of pixels in the x and y direction. By including this library, you're able to move at less-than-pixel distances, e.g. Translate(0.5, 0.5) moves half a pixel north and east. You can provide a Dir for the mover to end up with.
  • movable.Project(Distance, Angle, Dir) - Move a movable atom by a certain distance and angle. 0 degrees is north and 90 degrees is east. This uses Translate().


Example
proc/move_towards(atom/movable/a, atom/b, speed)
// speed defaults to the mover's step size
if(isnull(speed)) speed = a.step_size

// if speed turns out to be 0, then there's no movement
if(!speed) return

// a simple vector
// from: the center of 'a'
// to: the center of 'b'
var delta_x = b.Cx() - a.Cx()
var delta_y = b.Cy() - a.Cy()

// a and b are in the same position!
if(!(delta_x || delta_y)) return

// if the delta vector is larger than speed,
// we need to scale it down to speed.
// we do this by dividing by the magnitude of the delta vector,
// which results in a unit vector,
// and then scaling that up by the speed.
var distance = sqrt(delta_x*delta_x + delta_y*delta_y)
if(distance > speed)
var s = speed / distance
delta_x *= s
delta_y *= s
a.Translate(delta_x, delta_y)
GatewayRa wrote:
i like how ur naming conventions are the same as the ones defined in dm's language and the reference as if ur libraries just extension of the language
Thanks for noticing. I started using that convention on purpose for that exact reason. Libraries should be used (and made) with the intention of extending (or abstracting) the language.

I actually have more unlisted libraries that I made hubs for just so I could include them with a checkbox... This was around when I went an entire week making one small game every day. Most of them used this library. It's easier to directly use my knowledge of vector math and trigonometry when I don't have to actively deal with BYOND always rounding my positions.
Shouldn't you cast abs() when finding the range between both a and b ?
In response to Liight
No, why?
In case b is smaller than a position wise. It would probably be a negative number if b is closer to 0 on both axis than a.
In response to Liight
Yes, it would be a negative number. If B is to the left of A, then A must move in the negative x-direction, after all.
In this situation I guess you don't need it. But when finding the distance for other reason Is best to cast abs.
In response to Liight
delta_x is not a distance, it's a difference in position.

distance is a distance (duh), calculated using the Pythagorean theorem. It also doesn't (explicitly) use the absolute value of anything because of the squared term; delta_x*delta_x is always a positive value (unless delta_x is an imaginary number).
distance = sqrt(delta_x*delta_x + delta_y*delta_y)


get_dist() uses a different formula to calculate what's known as Chebyshev distance. It does use absolute value, which is what you might be thinking of:
distance = abs(delta_x) + abs(delta_y)


But both equations use delta(s), which can be positive or negative.
        //  The absolute coordinates of this atom's bottom-left corner,
// + a percentage of the atom's dimensions.
Px(Pc) return (x - 1 + Pc) * tile_width
Py(Pc) return (y - 1 + Pc) * tile_height


Real quick: Is there a reason why the - 1 is there? What is its purpose? I'm wondering because that part was messing some things up with simple centering. Had me thinking I was doing something wrong all this time, though I didn't find anything other than that. Once I got rid of that part, it worked as expected.
In response to FKI
That comes from (1, 1) being the bottom-left tile of the map. If you don't include that, then the bottom-left pixel of the map will be (32, 32) instead of (0, 0).
Yeah, it was something I did somewhere, no idea what though. Github is a lifesaver however.

Again, appreciate the help (and your work as well).
The sin and cos in the Translate() return in Project() need to be flipped.

currently: return Translate(Distance * sin(Angle), Distance * cos(Angle), Dir)

should be: return Translate(Distance * cos(Angle), Distance * sin(Angle), Dir)
In response to Reformist
They are correct as-is. My BYOND projects (and thus my Project()) have always used the convention that angles increase clockwise from up. I always draw my rotatable artwork pointing up, and positive rotations effectively turn to the right when using matrix.Turn() and icon.Turn().
In response to Kaiochao
When using it to move one object towards another, re-calculating the angle to see if the object being moved to has moved as well, it performs strangely. In certain quadrants it will simply move away from the object instead of towards. In quadrants where it moves towards the object it ends up performing a parabola, moving really close to the object then bending away. Switching sin() and cos() corrects this issue.
In response to Reformist
1. You shouldn't really use angles to move one object towards another. All you need is rescale a vector in the desired direction. That is, the vector from one object to another is a vector that points from the first object to the target, and you just need to scale that to your step size; no angles necessary:
// vector from src to target
var dx = target.Cx() - Cx() // or Cx(target) - Cx(src) if you use the #define version
var dy = target.Cy() - Cy()

// no distance => no movement
if(!(dx || dy)) return

// vector magnitude
var distance = sqrt(dx * dx + dy * dy)

// scales the (dx, dy) vector to one with length == step_size
// when you divide a vector by its length (called "normalizing a vector")
// the result is a vector with length == 1. We multiply that by the desired length.
var scale = step_size / distance

// your resultant movement vector (in pixels because that's what Cx/y() gives)
var velocity_x = dx * scale
var velocity_y = dy * scale

// or more compactly
var dx = target.Cx() - Cx()
var dy = target.Cy() - Cy()
if(!(dx || dy)) return
var scale = step_size / sqrt(dx * dx + dy * dy)
var velocity_x = dx * scale
var velocity_y = dy * scale


2. Your angle isn't "clockwise from up", which is the more common navigational standard; it's probably "counter-clockwise from right", which is the more common math standard. Here's my atan2() that works in my convention:
proc/atan2(x, y)
return (x || y) && (x >= 0 \
? arccos(y / sqrt(x * x + y * y)) \
: -arccos(y / sqrt(x * x + y * y)))