0
\$\begingroup\$

I'm making a visual clue for the player to know where (and more or less when) an asteroid is going to enter the screen. For this I'm using a triangle pointing at the incoming object and color interpolation from green (far) to red (near).

My working thoughts are this:

  1. Determine from which part of the screen the aerolite will enter (top, left, ...) by calculating tha angle of the spawn point of the aerolite and the center of the screen with atan2.
  2. Asign a entering direction so the pointing arrow can stay close to the border that the aerolite will enter.
  3. Calculate the entering point using slope Y intersect formula b = y - mx and X intersect x = -b / m.
  4. Calculate, with the linear velocity of the aerolite, the time it will take to arrive to the entering point, so I can interpolate the colors.

So far so good for the aerolites that come from left or right directions, but I get wrong results from the ones that come from top or bottom. Here's the code:

// project a point from the position and the delta_v. Useful for working with lines / segments
const b2Vec2 point_b {aerolite_pos + delta_v};
float slope{}, distance{};
b2Vec2 intersect_point {};
// arrow: calculate the angle of attack respect to the screen's center
const auto theta {atan2Normalized(aerolite_pos.y - screen_center.y, aerolite_pos.x - screen_center.x)};
// asign an incoming direction so we can position the arrow
if (theta >= top_right && theta < top_left) {
  // TOP
  aerolite->arrow_->incoming_direction_ = Direction::Top;
  slope = (point_b.y - aerolite_pos.y) / (point_b.x - aerolite_pos.x);
  const auto y_intersect = aerolite_pos.y - (slope * aerolite_pos.x);
  intersect_point.Set(-y_intersect / slope, b2_screen_size_.y);

} else if (theta >= top_left && theta < bottom_left) {
  // LEFT
  aerolite->arrow_->incoming_direction_ = Direction::Left;
  slope = (point_b.y - aerolite_pos.y) / (point_b.x - aerolite_pos.x);
  intersect_point.Set(0.f, aerolite_pos.y - (slope * aerolite_pos.x));

} else if (theta >= bottom_left && theta < bottom_right) {
  // BOTTOM
  aerolite->arrow_->incoming_direction_ = Direction::Bottom;
  slope = (point_b.y - aerolite_pos.y) / (point_b.x - aerolite_pos.x);
  const auto y_intersect = aerolite_pos.y - (slope * aerolite_pos.x);
  intersect_point.Set(-y_intersect / slope , 0.f);

} else {
  // RIGHT
  aerolite->arrow_->incoming_direction_ = Direction::Right;
  slope = (point_b.y - aerolite_pos.y) / (point_b.x - aerolite_pos.x + b2_screen_size_.x);
  intersect_point.Set(b2_screen_size_.x, aerolite_pos.y - (slope * (aerolite_pos.x + b2_screen_size_.x)));
}
// distance from spawn point to intersect point
distance = b2Distance(aerolite_pos, intersect_point);
// calculate the time for the aerolite to enter the screen
aerolite->arrow_->time_to_enter_ = distance / delta_v.Length();

I've tried changing the sign, inverting formulae, googling, ... but nothing.

How can I calculate the exact point where the aerolite will enter the screen?

Edit: the aerolites always go to the center of the screen, no matter where they spawn.

\$\endgroup\$
2
  • \$\begingroup\$ So your screen resolution is sqrt(3):1? it's weird. And, What will happen if the target is directly above the center of the screen(90°)? \$\endgroup\$
    – Mangata
    May 17, 2022 at 13:12
  • \$\begingroup\$ Well, my aspect ratio is 16:9. I used this graph to get the angles, which may not be correct for my case. And for the aerolite spawning just above, it'll never cross Y, but it will cross topX somewhere. I just have to take into account that special cases when I've something working. \$\endgroup\$
    – Alex CB
    May 17, 2022 at 13:28

2 Answers 2

3
\$\begingroup\$

This is way too complicated.

Let's say the screen center is at (0,0), the asteroid coords are x,y, and the screen size is w,h. Then this should work:

float x2 = x / w;
float y2 = y / h;
float s = max(abs(x2), abs(y2));
float x3 = x2 / s * w / 2;
float y3 = y2 / s * h / 2;

x3,y3 are the desired coords.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ This is a neat and clever answer, I guess using similar triangles or something? But it should be pointed out that just using max is not enough, you need to compare their absolute values, I guess? \$\endgroup\$
    – Mangata
    May 18, 2022 at 4:25
  • \$\begingroup\$ @Mangata Yep, thanks. Fixed. \$\endgroup\$ May 18, 2022 at 5:26
  • \$\begingroup\$ I like this a lot! I've tried on paper and works flawless. However, and I'm sorry if it's too trivial, but I tried this in a inverted Y-axis with the 0,0 coord being the top left and gives wrong results, of course. I suppose I've to add width/2 and height/2 of the screen somewhere and do something with the Y coord. Can you tweak your code to reflex this? \$\endgroup\$
    – Alex CB
    May 18, 2022 at 12:00
  • \$\begingroup\$ @AlexCB Inverted-ness of Y doesn't affect the math. If 0,0 is in the corner, you need to subtract half window size from the coords before doing anything else, then add it back when you're done. \$\endgroup\$ May 18, 2022 at 12:07
  • \$\begingroup\$ (assuming Y is inverted for both the world and the gui) \$\endgroup\$ May 18, 2022 at 12:29
0
\$\begingroup\$
const b2Vec2 point_b {aerolite_pos + delta_v};
...
slope = (point_b.y - aerolite_pos.y) / (point_b.x - aerolite_pos.x);

This is equivalent to:

slope = delta_v.y / delta_v.x;

I don't know more details of what the variables mean, but it's definitely not right. Maybe I can interpret it as relative coordinates from camera to (0,0)(delta_view)? But you use screen_center as coordinates from camera later, which confuses me even more.

Given that you have a lot of magic numbers in your code(which is bad coding practice), And the use of angles will have a series of limit value problems. I recommend you to use the vector way to handle it.

The 2 diagonals of the screen rectangle(v1,v2) divide the space into 4 parts. We need to know which of the four areas the target is in. Use Cross to determine whether a point is on the left or right of a vector. With two results we can get the final result.

enter image description here

class Vec2:
    def __init__(self, x, y) -> None:
        self.x = x
        self.y = y

    def __sub__(self, v):
        return Vec2(self.x-v.x, self.y-v.y)

    def __truediv__(self, n):
        return Vec2(self.x/n, self.y/n)

def Cross(v1, v2):
    return v1.x*v2.y-v2.x*v1.y

if __name__ == "__main__":
    screenSize = Vec2(1920, 1080)
    centerPos = Vec2(0, 0)
    aerolitePos = Vec2(100, 100)

    v1 = screenSize
    v2 = Vec2(screenSize.x, -screenSize.y)

    isLeftTop = Cross(v1, aerolitePos-centerPos) > 0
    isRightTop = Cross(v2, aerolitePos-centerPos) > 0

    if isLeftTop:
        if isRightTop:
            print("TOP")
        else:
            print("LEFT")
    else:
        if isRightTop:
            print("RIGHT")
        else:
            print("BOTTOM")

There is no c++ environment at present, I use python to illustrate, I hope it helps :p

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your time. The redundant slope calculus was of help. I've removed the magic numbers to make it more clear for the readers. The camera is fixed. what I use is the screen's length and height to displace the crossing line: left is 0 and right is 0+screen.x. Coord 0,0 is at top left of the screen. But my question isn't from where it´s coming the aerolite, but at which exact point will it enter the screen. \$\endgroup\$
    – Alex CB
    May 17, 2022 at 16:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .