[Solved] Pointing at an object that's out of sight
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
 Gunroar:Cannon()
 Party member
 Posts: 141
 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:
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
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:
 A simple approach, where you just point in the approximate direction: 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).
 A complex approach, where you point directly at the object with the correct angle: 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 3315 times
 Gunroar:Cannon()
 Party member
 Posts: 141
 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.yp.y,obj.xp.x)?
me: I don't always code but when I do it's done flawlessly.
also me:
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
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):
 The first step is to find out which screenborder the pointer needs to be. This can be done by calculating the angles of the screencorners (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" (yvalue) of the lower screen border (I_y in my notation).
 The second step is to calculate the xvalue of the intersection (I_x). This can be done with simple trigonometry (see my sketch).
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 axisaligned line. The formula for lineline intersection is very simple when one of the lines is axisaligned.
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.
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

 arrowtoenemy.love
 (1.49 KiB) Downloaded 82 times
 Gunroar:Cannon()
 Party member
 Posts: 141
 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:
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
Re: Pointing at an object that's out of sight
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 pointslope 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
function love.load()
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 pointslope 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 pointslope 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
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!
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.
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.
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:
[edited to remove excess pickiness]
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
Who is online
Users browsing this forum: No registered users and 49 guests