## [Solved] Pointing at an object that's out of sight

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Gunroar:Cannon()
Party member
Posts: 217
Joined: Thu Dec 10, 2020 1:57 am

### [Solved] Pointing at an object that's out of sight

How(what's the logic) do you draw an arrow, at the edge of the screen, to point at an object (in a 2d game) that can't be seen in the screen, like another player or a power up.
Last edited by Gunroar:Cannon() on Fri Jan 08, 2021 10:21 pm, edited 1 time in total.
me: I don't always code but when I do it's done flawlessly.
also me:

Code: Select all

 function Gunroar:Cannon()
for x, enemy in ipairs(self.allEnemies) do
self:Cannon(enemy)
end
end

Code: Select all

Lua Error: [file Gunroar.lua]:18: C stack overflow

Xugro
Citizen
Posts: 89
Joined: Wed Sep 29, 2010 8:14 pm

### Re: Pointing at an object that's out of sight

There are many ways to do this. It depends on what you want. I can see two possibilities:
1. A simple approach, where you just point in the approximate direction:
simple.jpg (132.57 KiB) Viewed 4195 times
Hereby you determine in which part of the playing field the object/enemy (X) is relative to the player (P): the number shown. Here the screen of the player is the shown by thick black lines. If it is above (2), below (8), to the left (4) or the right (6) just draw an arrow at the position of the object on the border (example in light blue). If the object/enemy is in one of the other parts of the playing field (1, 3, 7 or 9), just draw an arrow at the corresponding corner (example in purple).
2. A complex approach, where you point directly at the object with the correct angle:
complex.jpg (126.36 KiB) Viewed 4195 times
Here you have to calculate the angle between the player (P) and the enemy/object (X) and the intersection point between the screen border (thick black lines) and the line from player to enemy/object (pencil). Then you can draw the arrow at the right position with the correct angle. Hereby the angle is the easy part, but the calculation the intersection is more difficult. I need about a page of math to get a formula for calculation the intersection point. And the formula won't look pretty.
Attachments
simple.jpg (132.57 KiB) Viewed 4195 times
Gunroar:Cannon()
Party member
Posts: 217
Joined: Thu Dec 10, 2020 1:57 am

### Re: Pointing at an object that's out of sight

Thnx, I want 2. Don't you use something like math.atan2(obj.y-p.y,obj.x-p.x)?
me: I don't always code but when I do it's done flawlessly.
also me:

Code: Select all

 function Gunroar:Cannon()
for x, enemy in ipairs(self.allEnemies) do
self:Cannon(enemy)
end
end

Code: Select all

Lua Error: [file Gunroar.lua]:18: C stack overflow

Xugro
Citizen
Posts: 89
Joined: Wed Sep 29, 2010 8:14 pm

### Re: Pointing at an object that's out of sight

Yes, that is the formula to calculate the angle. But that is the easy part. Calculating the point on the border is harder. But I found a simpler in my sleep (simpler than what I thought of yesterday):
calculate_intersection.jpg (143.93 KiB) Viewed 4145 times
1. The first step is to find out which screen-border the pointer needs to be. This can be done by calculating the angles of the screen-corners (e.g. alpha and gamma) and compare them to the angle between player and object (beta). In my case this is the lower screen border (since alpha <= beta <= gamma). With this you know the "height" (y-value) of the lower screen border (I_y in my notation).
2. The second step is to calculate the x-value of the intersection (I_x). This can be done with simple trigonometry (see my sketch).
With the angle beta and the point of intersection I you can draw a pointer to the object.
pgimeno
Party member
Posts: 2566
Joined: Sun Oct 18, 2015 2:58 pm

### Re: Pointing at an object that's out of sight

Dealing with angles is prone to bugs due to lack of handling special cases, depending on the signs of the angles. This task can be solved by casting 4 rays, each to an axis-aligned line. The formula for line-line intersection is very simple when one of the lines is axis-aligned.

Problem: calculate the intersection between a line of the form v*t + p (where v is a vector from the player to the enemy, p is the player position and t is an arbitrary number; each value of t yields a point in the line) and either a vertical line (of the form x = constant) or a horizontal one (of the form y = constant). We need to find the point at which one line intersects the other, that is, we need to find the value of t that gives the point where the lines meet. We then have a linear equation which is quickly solved:

vx*t + px = x (for vertical line) -> t = (x - px) / vx
vy*t + py = y (for horizontal line) -> t = (y - py) / vy

Do that for each of the four lines, and take the smallest positive t of all four. Plug the obtained t into v*t + p and you will have the missing coordinate.

But if the arrow is an image, you still need to use atan2 to find the angle with which to draw it, because Löve doesn't allow specifying the rotation as a matrix for drawing, you need an angle. Furthermore, the image needs to rotate over its centre, and the lines must be at a certain distance from the border (half the image's width and height).

PoC attached. Mouse wheel to zoom out, LMB to place player, RMB to place enemy. The algorithm fails when the player is not in the screen, due to the condition that t must be positive, but that shouldn't be a problem.
Attachments
arrow-to-enemy.love
Gunroar:Cannon()
Party member
Posts: 217
Joined: Thu Dec 10, 2020 1:57 am

### Re: Pointing at an object that's out of sight

Thnxs alot, Xugro for your input and help! And your example wrapped it all up and fixed my problem, pgimeno, thnx!! I definitely couldn't have done it myself .
me: I don't always code but when I do it's done flawlessly.
also me:

Code: Select all

 function Gunroar:Cannon()
for x, enemy in ipairs(self.allEnemies) do
self:Cannon(enemy)
end
end

Code: Select all

Lua Error: [file Gunroar.lua]:18: C stack overflow

RNavega
Citizen
Posts: 69
Joined: Sun Aug 16, 2020 1:28 pm

### Re: Pointing at an object that's out of sight

pgimeno wrote: Thu Jan 07, 2021 2:37 pm This task can be solved by casting 4 rays, each to an axis-aligned line. The formula for line-line intersection is very simple when one of the lines is axis-aligned.
That's a cool solution pgimeno, very fast and elegant.
The method I came up with when thinking of this is more verbose and I think slower aswell, using the point-slope form of the line equation to get the intersections, but I wanted to put it to practice anyway. The code is inline as I made the arrow with a mesh object so it can be standalone:

Code: Select all

local arrow
local arrowHalfW, arrowHalfH
local zoom = 0.75

local playerX, playerY, playerW, playerH = 100, 200, 20, 40
local enemyX, enemyY, enemyW, enemyH = 300, 400, 20, 40
local isEnemyOff = false
local arrowPosX, arrowPosY

arrow = love.graphics.newMesh(
{{'VertexPosition', 'float', 2}},
{{0, 10}, {15, 10}, {15, 20}, {15, 20}, {0, 20}, {0, 10}, {15, 0}, {30, 15}, {15, 30}},
'triangles',
'static'
)
arrowHalfW = 15
arrowHalfH = 15
end

function love.wheelmoved(x, y)
zoom = zoom * 1.1 ^ y
if zoom > 1 then zoom = 1 end
end

-- Using the point-slope method, by RNavega.
function love.update(dt)
local screenW, screenH = love.graphics.getDimensions()
isEnemyOff = enemyX < 0 or enemyX > screenW
or enemyY < 0 or enemyY > screenH

if isEnemyOff then
local dx, dy = enemyX - playerX, enemyY - playerY

if dx == 0.0 then -- Happens when (enemyX == playerX).
arrowPosX = enemyX
arrowPosY = (enemyY > screenH) and (screenH - arrowHalfH) or arrowHalfH

elseif dy == 0.0 then -- Happens when (enemyY == playerY).
arrowPosX = (enemyX > screenW) and (screenW - arrowHalfW) or arrowHalfW
arrowPosY = enemyY

else
-- The point-slope equation of a line:
-- y - y1 = m * (x - x1)

local m = dy / dx

-- We actually test intersections with the edges of a smaller screen, padded by
-- arrowHalfW horizontally and arrowHalfH vertically.
local screenRightX = screenW - arrowHalfW
local screenBottomY = screenH - arrowHalfH

-- Isolating 'y' for use with the vertical edges:
-- y = m * (x - x1) + y1

if enemyX < arrowHalfW then
-- Left edge.
local intersectY = m * (arrowHalfW - playerX) + playerY
if intersectY >= arrowHalfH and intersectY <= screenBottomY then
arrowPosX = arrowHalfW
arrowPosY = intersectY
end
elseif enemyX > screenRightX then
-- Right edge.
local intersectY = m * (screenRightX - playerX) + playerY
if intersectY >= arrowHalfH and intersectY <= screenBottomY then
arrowPosX = screenRightX
arrowPosY = intersectY
end
end

-- Isolating 'x' for use with the horizontal edges:
-- x = (y - y1 + m*x1) / m

if enemyY < arrowHalfH then
-- Top edge.
local intersectX = (arrowHalfH - playerY + m * playerX) / m
if intersectX >= arrowHalfW and intersectX <= screenRightX then
arrowPosX = intersectX
arrowPosY = arrowHalfH
end
elseif enemyY > screenBottomY then
-- Bottom edge.
local intersectX = (screenBottomY - playerY + m * playerX) / m
if intersectX >= arrowHalfW and intersectX <= screenRightX then
arrowPosX = intersectX
arrowPosY = screenBottomY
end
end
end
end
end

-- Using 2D vectors, by pgimeno.
function love.updateOriginal(dt)
isEnemyOff = enemyX < 0 or enemyX >= love.graphics.getWidth()
or enemyY < 0 or enemyY >= love.graphics.getHeight()
if isEnemyOff then
local screenW, screenH = love.graphics.getDimensions()
local vx, vy = enemyX - playerX, enemyY - playerY
local t, minT
minT = math.huge
t = (arrowHalfW - playerX) / vx
if t == t and t > 0 and t < minT then
minT = t
arrowPosX = arrowHalfW
arrowPosY = vy * t + playerY
end
t = (arrowHalfH - playerY) / vy
if t == t and t > 0 and t < minT then
minT = t
arrowPosX = vx * t + playerX
arrowPosY = arrowHalfH
end
t = (screenW - arrowHalfW - playerX) / vx
if t == t and t > 0 and t < minT then
minT = t
arrowPosX = screenW - arrowHalfW
arrowPosY = vy * t + playerY
end
t = (screenH - arrowHalfH - playerY) / vy
if t > 0 and t < minT then
-- minT = t -- doesn't matter anymore
arrowPosX = vx * t + playerX
arrowPosY = screenH - arrowHalfH
end
end
end

function love.draw()
local screenW, screenH = love.graphics.getDimensions()
love.graphics.translate(screenW/2, screenH/2)
love.graphics.scale(zoom)
love.graphics.translate(screenW/-2, screenH/-2)

-- Draw screen border
love.graphics.setLineWidth(1/zoom)
love.graphics.rectangle("line", -.5, -.5, screenW+1, screenH+1)

-- Draw player and enemy
love.graphics.setColor(0, 1, 1)
love.graphics.rectangle("fill", playerX - playerW/2, playerY - playerH/2, playerW, playerH)
love.graphics.setColor(1, 0, 0)
love.graphics.rectangle("fill", enemyX - enemyW/2, enemyY - enemyH/2, enemyW, enemyH)

-- Draw arrow if visible
if isEnemyOff then
love.graphics.setColor(1, 1, 0)
love.graphics.draw(arrow, arrowPosX, arrowPosY,
math.atan2(enemyY - playerY, enemyX - playerX),
1, 1, arrowHalfW, arrowHalfH)
end

-- Restore colour
love.graphics.setColor(1, 1, 1)
end

function love.mousemoved(x, y)
-- transform screen to zoomed coordinates
x = (x - love.graphics.getWidth() / 2) / zoom + love.graphics.getWidth() / 2
y = (y - love.graphics.getHeight() / 2) / zoom + love.graphics.getHeight() / 2

if love.mouse.isDown(1) then
playerX = x
playerY = y
elseif love.mouse.isDown(2) then
enemyX = x
enemyY = y
end
end
love.mousepressed = love.mousemoved
love.mousereleased = love.mousemoved

function love.keypressed(key)
if key == 'escape' then
love.event.quit()
end
end

RNavega
Citizen
Posts: 69
Joined: Sun Aug 16, 2020 1:28 pm

### Re: [Solved] Pointing at an object that's out of sight

...I should add, I had the idea while looking at the diagrams drawn by Xugro above, so thanks for that!
ivan
Party member
Posts: 1719
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

### Re: [Solved] Pointing at an object that's out of sight

I have dealt with this issue before and the easiest way is to use my algorithm for SegmentVSAABB:
https://2dengine.com/?p=intersections#section_15
The segment is the ray from the center of the screen to the target point and the AABB is defined by the edges of the screen.
pgimeno
Party member
Posts: 2566
Joined: Sun Oct 18, 2015 2:58 pm

### Re: Pointing at an object that's out of sight

I forgot one 't == t' (to discard this edge when t is NaN) in the condition for the last screen border. The last lines in love.update should be like this:

Code: Select all

    t = (screenH - arrowHalfH - playerY) / vy
if t == t and t > 0 and t < minT then --<<-- 't == t' was missing here ***
-- minT = t -- doesn't matter anymore
arrowPosX = vx * t + playerX
arrowPosY = screenH - arrowHalfH
end
end
end

[edited to remove excess pickiness]

### Who is online

Users browsing this forum: No registered users and 13 guests