Issues with triangle collisions

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
snibo
Prole
Posts: 23
Joined: Sat May 04, 2024 6:56 am

Issues with triangle collisions

Post by snibo »

So right now I am making a basic touch control dpad that instead of having square buttons it has triangular buttons similar to the ones that you can find in the snes9xex emulator, and I am having some confusing issues

Shortly the up and down buttons are working as a charm but the left and right button are extremely weird
Because they do not detect when you press them like the prevew and the detected area do not match at all
But there's still a detected area wich I don't know how it looks exactly but it is there

I have one lead for why that happens and it is that the function that I use for detecting the triangles is confused because of the order of the positions because remember that the up and down buttons work properly and the prevew on screen is correct

I have left an attachment with the main.lua file that way you can see it for yourself and when running it and you have an error that sais "wtf" I made it this means that you have been able to press the right button
Attachments
main.lua
(2.93 KiB) Downloaded 355 times
Rigachupe
Party member
Posts: 100
Joined: Fri Jun 18, 2021 11:21 am

Re: Issues with triangle collisions

Post by Rigachupe »

It would be easier to use a point-circle collision around the buttons. Just a tip to reconsider as an option.
RNavega
Party member
Posts: 385
Joined: Sun Aug 16, 2020 1:28 pm

Re: Issues with triangle collisions

Post by RNavega »

I think I agree with Rigachupe's suggestion, though I'm not sure if it's about a circle around each individual button, or one single big circle around all of them.
I think it should be a single big circle that encompasses all buttons, and with some extra pixels in the radius.
Among your players there'll be people with fat fingers or playing on a moving bus etc. and you want to be as permissive as possible with your d-pad.

I was going to suggest using dot products to test the touch position against each cardinal direction (up, down, left, right) based on the circle center, but it turns out that the dot products and the perfect diagonal angles cause things to reduce down to just picking the (absolute) biggest of the two touch coordinates as the d-pad direction. See the code below.

Note that this d-pad circle is drawn only for debugging purposes. The circle should not be visible in your actual game, where you'll only draw the triangle symbols for each button, as each button represents a slice of that circle.
preview.png
preview.png (14.65 KiB) Viewed 6017 times

Code: Select all

-- Virtual D-Pad example.

io.stdout:setvbuf('no')

local mathAbs = math.abs
local HALF_SQUARE_ROOT = math.sqrt(2.0) / 2.0
local QUARTER_PI = math.pi / 4.0

local DPAD = {
    radius = 128,
    radiusSquared = 128 * 128,
    center = {x = 200, y = 250},
    direction = nil,
}


function love.load()
    love.window.setTitle('Virtual D-Pad Example')
    love.mouse.setCursor(love.mouse.getSystemCursor('crosshair'))
end


function love.update(dt)
    local mx, my = love.mouse.getPosition()

    local mouseVectorX = mx - DPAD.center.x
    local mouseVectorY = my - DPAD.center.y

    local mouseVectorLengthSquared = (mouseVectorX * mouseVectorX
                                     + mouseVectorY * mouseVectorY)
    if mouseVectorLengthSquared > DPAD.radiusSquared then
        DPAD.direction = nil
        return
    end

    if mathAbs(mouseVectorX) > mathAbs(mouseVectorY) then
        -- Horizontal direction.
        DPAD.direction = mouseVectorX > 0.0 and 'right' or 'left'
    else
        -- Vertical direction.
        DPAD.direction = mouseVectorY > 0.0 and 'down' or 'up'
    end
end


function love.draw()
    if DPAD.direction then
        love.graphics.setColor(0.3, 0.75, 0.14, 0.3)
    else
        love.graphics.setColor(0.3, 0.75, 0.14, 0.1)
    end
    love.graphics.circle('fill', DPAD.center.x, DPAD.center.y, DPAD.radius, DPAD.radius)

    love.graphics.setLineWidth(1.0)
    love.graphics.setColor(0.5, 0.5, 0.5)
    local cornerOffset = DPAD.radius * HALF_SQUARE_ROOT
    love.graphics.line(DPAD.center.x - cornerOffset, DPAD.center.y - cornerOffset,
                       DPAD.center.x + cornerOffset, DPAD.center.y + cornerOffset)
    love.graphics.line(DPAD.center.x + cornerOffset, DPAD.center.y - cornerOffset,
                       DPAD.center.x - cornerOffset, DPAD.center.y + cornerOffset)

    love.graphics.setColor(1.0, 1.0, 0.0)
    local mx, my = love.mouse.getPosition()
    love.graphics.line(DPAD.center.x, DPAD.center.y, mx, my)

    love.graphics.setColor(1.0, 1.0, 1.0)
    love.graphics.print('- Hover the mouse on the virtual D-Pad.', 10, 10)
    love.graphics.print('- Press Esc to quit.', 10, 30)
    love.graphics.print('Mouse: ' .. mx .. ', ' .. my, 10, 50)

    if DPAD.direction then
        local angle1, angle2
        if DPAD.direction == 'up' then
            angle1, angle2 = -QUARTER_PI, -QUARTER_PI * 3.0
        elseif DPAD.direction == 'down' then
            angle1, angle2 = QUARTER_PI, QUARTER_PI * 3.0
        elseif DPAD.direction == 'right' then
            angle1, angle2 = -QUARTER_PI, QUARTER_PI
        elseif DPAD.direction == 'left' then
            angle1, angle2 = QUARTER_PI * 3.0, QUARTER_PI * 5.0
        end
        love.graphics.setColor(1.0, 1.0, 0.2, 0.5)
        love.graphics.arc('fill', DPAD.center.x, DPAD.center.y, DPAD.radius, angle1, angle2)
        love.graphics.setColor(1.0, 1.0, 1.0)
        love.graphics.print(DPAD.direction, DPAD.center.x - 20, DPAD.center.y + DPAD.radius + 10, 0.0, 2.0, 2.0)
    end

    love.graphics.setColor(0.3, 0.75, 0.14, 1.0)
    love.graphics.setLineWidth(3.0)
    love.graphics.circle('line', DPAD.center.x, DPAD.center.y, DPAD.radius, DPAD.radius)
end


function love.keypressed(key)
    if key == 'escape' then
        love.event.quit()
    end
end
Rigachupe
Party member
Posts: 100
Joined: Fri Jun 18, 2021 11:21 am

Re: Issues with triangle collisions

Post by Rigachupe »

A button is usually a rectangle. In your case triangle. A circle can be put around the button of this two shapes or inside of them. It is just like an collision shape attached to the image.

Btw you can put the images into attachements array while writing post and then insert them into text.
snibo
Prole
Posts: 23
Joined: Sat May 04, 2024 6:56 am

Re: Issues with triangle collisions

Post by snibo »

RNavega wrote: Sun Jul 28, 2024 5:06 am I think I agree with Rigachupe's suggestion, though I'm not sure if it's about a circle around each individual button, or one single big circle around all of them.
I think it should be a single big circle that encompasses all buttons, and with some extra pixels in the radius.
Among your players there'll be people with fat fingers or playing on a moving bus etc. and you want to be as permissive as possible with your d-pad.

I was going to suggest using dot products to test the touch position against each cardinal direction (up, down, left, right) based on the circle center, but it turns out that the dot products and the perfect diagonal angles cause things to reduce down to just picking the (absolute) biggest of the two touch coordinates as the d-pad direction. See the code below.

Note that this d-pad circle is drawn only for debugging purposes. The circle should not be visible in your actual game, where you'll only draw the triangle symbols for each button, as each button represents a slice of that circle.
preview.png

Code: Select all

-- Virtual D-Pad example.

io.stdout:setvbuf('no')

local mathAbs = math.abs
local HALF_SQUARE_ROOT = math.sqrt(2.0) / 2.0
local QUARTER_PI = math.pi / 4.0

local DPAD = {
    radius = 128,
    radiusSquared = 128 * 128,
    center = {x = 200, y = 250},
    direction = nil,
}


function love.load()
    love.window.setTitle('Virtual D-Pad Example')
    love.mouse.setCursor(love.mouse.getSystemCursor('crosshair'))
end


function love.update(dt)
    local mx, my = love.mouse.getPosition()

    local mouseVectorX = mx - DPAD.center.x
    local mouseVectorY = my - DPAD.center.y

    local mouseVectorLengthSquared = (mouseVectorX * mouseVectorX
                                     + mouseVectorY * mouseVectorY)
    if mouseVectorLengthSquared > DPAD.radiusSquared then
        DPAD.direction = nil
        return
    end

    if mathAbs(mouseVectorX) > mathAbs(mouseVectorY) then
        -- Horizontal direction.
        DPAD.direction = mouseVectorX > 0.0 and 'right' or 'left'
    else
        -- Vertical direction.
        DPAD.direction = mouseVectorY > 0.0 and 'down' or 'up'
    end
end


function love.draw()
    if DPAD.direction then
        love.graphics.setColor(0.3, 0.75, 0.14, 0.3)
    else
        love.graphics.setColor(0.3, 0.75, 0.14, 0.1)
    end
    love.graphics.circle('fill', DPAD.center.x, DPAD.center.y, DPAD.radius, DPAD.radius)

    love.graphics.setLineWidth(1.0)
    love.graphics.setColor(0.5, 0.5, 0.5)
    local cornerOffset = DPAD.radius * HALF_SQUARE_ROOT
    love.graphics.line(DPAD.center.x - cornerOffset, DPAD.center.y - cornerOffset,
                       DPAD.center.x + cornerOffset, DPAD.center.y + cornerOffset)
    love.graphics.line(DPAD.center.x + cornerOffset, DPAD.center.y - cornerOffset,
                       DPAD.center.x - cornerOffset, DPAD.center.y + cornerOffset)

    love.graphics.setColor(1.0, 1.0, 0.0)
    local mx, my = love.mouse.getPosition()
    love.graphics.line(DPAD.center.x, DPAD.center.y, mx, my)

    love.graphics.setColor(1.0, 1.0, 1.0)
    love.graphics.print('- Hover the mouse on the virtual D-Pad.', 10, 10)
    love.graphics.print('- Press Esc to quit.', 10, 30)
    love.graphics.print('Mouse: ' .. mx .. ', ' .. my, 10, 50)

    if DPAD.direction then
        local angle1, angle2
        if DPAD.direction == 'up' then
            angle1, angle2 = -QUARTER_PI, -QUARTER_PI * 3.0
        elseif DPAD.direction == 'down' then
            angle1, angle2 = QUARTER_PI, QUARTER_PI * 3.0
        elseif DPAD.direction == 'right' then
            angle1, angle2 = -QUARTER_PI, QUARTER_PI
        elseif DPAD.direction == 'left' then
            angle1, angle2 = QUARTER_PI * 3.0, QUARTER_PI * 5.0
        end
        love.graphics.setColor(1.0, 1.0, 0.2, 0.5)
        love.graphics.arc('fill', DPAD.center.x, DPAD.center.y, DPAD.radius, angle1, angle2)
        love.graphics.setColor(1.0, 1.0, 1.0)
        love.graphics.print(DPAD.direction, DPAD.center.x - 20, DPAD.center.y + DPAD.radius + 10, 0.0, 2.0, 2.0)
    end

    love.graphics.setColor(0.3, 0.75, 0.14, 1.0)
    love.graphics.setLineWidth(3.0)
    love.graphics.circle('line', DPAD.center.x, DPAD.center.y, DPAD.radius, DPAD.radius)
end


function love.keypressed(key)
    if key == 'escape' then
        love.event.quit()
    end
end
Thanks for the code man only by reading it I learned so much and I think that I will go for a circle now it's much better I just need to change it in a way that makes diagonals possible
RNavega
Party member
Posts: 385
Joined: Sun Aug 16, 2020 1:28 pm

Re: Issues with triangle collisions

Post by RNavega »

No problem bro

PS I take back what I said about "not drawing the circle". I guess it's a case by case basis. Some mobile apps draw virtual dpads all the time, and they look fine:
screen-0.jpg
screen-0.jpg (212.61 KiB) Viewed 5922 times
RNavega
Party member
Posts: 385
Joined: Sun Aug 16, 2020 1:28 pm

Re: Issues with triangle collisions

Post by RNavega »

And about the diagonals, while you could do the dot product of the touch vector against the diagonal vectors and pick the biggest one, there's a cheaper way by getting the ratios (mouseVectorX / mouseVectorY) and (mouseVectorY / mouseVectorX).
If the absolute values of both of these ratios are above some number between 1.0 (when mvx == mvy) and 0.0 (when either mvx or mvy is zero), then the touch is happening in one of the four diagonal "zones" of the d-pad.
Then you can use the signs of mvx and mvy to tell which diagonal it is, that is, which of NE, NW, SE or SW the touch is happening at.

This graph shows these zones formed by the ratios of (mvx/mvy) and (mvy/mvx). Starting from a ratio limit of 0.6, adjust "r" to see how it looks with different limits:
https://www.desmos.com/calculator/jhbuk6ic7e

So you're going for something like:

Code: Select all

if mathAbs(mvx/mvy) >= limit and mathAbs(mvy/mvx) >= limit then
    (Determine which of the four diagonals it is, using the signs of mvx and mvy)
else
    (Not a diagonal, but the standard up,down,left,right directions like in the demo)
end
snibo
Prole
Posts: 23
Joined: Sat May 04, 2024 6:56 am

Re: Issues with triangle collisions

Post by snibo »

RNavega wrote: Sun Jul 28, 2024 1:30 pm And about the diagonals, while you could do the dot product of the touch vector against the diagonal vectors and pick the biggest one, there's a cheaper way by getting the ratios (mouseVectorX / mouseVectorY) and (mouseVectorY / mouseVectorX).
If the absolute values of both of these ratios are above some number between 1.0 (when mvx == mvy) and 0.0 (when either mvx or mvy is zero), then the touch is happening in one of the four diagonal "zones" of the d-pad.
Then you can use the signs of mvx and mvy to tell which diagonal it is, that is, which of NE, NW, SE or SW the touch is happening at.
Actually the way that I did the diagonals is by getting the angle of the vector and just check if it is in the range that I am looking for this made it much more flexible I can change the diagonal offset pretty easily and I can even turn it into a joystick if I want

I used this equation in order to get the angle it's pretty janky do you think there's a better way to do it?

Code: Select all

if mouseVectorX < 0 then anglePlus = 180 else anglePlus = 0 end
    vecAngle = (math.atan(mouseVectorY/mouseVectorX) * 180/math.pi) + anglePlus
the anglePlus is there to add 180 if mouseVectorX is negative because unless it will just loop itself but I would prefer using purely mathematical equations for that and avoid using an if statement so tell me if you have any idea
RNavega
Party member
Posts: 385
Joined: Sun Aug 16, 2020 1:28 pm

Re: Issues with triangle collisions

Post by RNavega »

Other than how it's creating a global (anglePlus), there's nothing wrong with your code, it's great.
I had never used Lua's math.atan() before, it's a bit confusing... I'm used to atan2().

Anyway, the top-right quadrant gives out a negative angle, so if you want a full 360º range I'd try this:

Code: Select all

local RAD2DEG = 180.0 / math.pi

(...)
    
    local anglePlus -- Making sure that anglePlus is a local.
    if mouseVectorY > 0.0 then
        anglePlus = mouseVectorX < 0.0 and 180.0 or 0.0
    else
        anglePlus = mouseVectorX < 0.0 and 180.0 or 360.0
    end
    local vecAngle = math.atan(mouseVectorY / mouseVectorX) * RAD2DEG + anglePlus
snibo
Prole
Posts: 23
Joined: Sat May 04, 2024 6:56 am

Re: Issues with triangle collisions

Post by snibo »

Yeah I realized after sending the response that atan2 exist wich was made specifically for what I was doing and there actually is a function in lua that is also specifically made for converting radiants to degrees so here's what I ended up using

Code: Select all

    vecAngle = math.deg(math.atan2(mouseVectorX, mouseVectorY))*-1 + 90
The `*-1+90` was just to make it compatible with what I had before
Post Reply

Who is online

Users browsing this forum: No registered users and 15 guests