Thread Rating:
  • 1 Vote(s) - 4 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Circle-line collision
#21
(05-18-2026, 11:20 AM)kevin Wrote: Some progress with the issue of the balls moving inside the polygons. I think the issue is with the ResolveBallCollision() function, which seems, in certain circumstances, to be pushing the balls inside the polygons. To demonstrate, if you disable this function, the problem disappears.

I think it may be a little more difficult to find a solution though Smile

I think I have found a way to avoid trapped ball inside polygon...give me 2-3 days more....Big Grin
Reply
#22
(05-18-2026, 11:25 AM)1micha.elok Wrote:
(05-18-2026, 11:20 AM)kevin Wrote: Some progress with the issue of the balls moving inside the polygons. I think the issue is with the ResolveBallCollision() function, which seems, in certain circumstances, to be pushing the balls inside the polygons. To demonstrate, if you disable this function, the problem disappears.

I think it may be a little more difficult to find a solution though Smile

I think I have found a way to avoid trapped ball inside polygon...give me 2-3 days more....Big Grin
That's great to hear, please let me know if you need any help with testing.....
Reply
#23
(05-18-2026, 11:50 AM)kevin Wrote:
(05-18-2026, 11:25 AM)1micha.elok Wrote:
(05-18-2026, 11:20 AM)kevin Wrote: Some progress with the issue of the balls moving inside the polygons. I think the issue is with the ResolveBallCollision() function, which seems, in certain circumstances, to be pushing the balls inside the polygons. To demonstrate, if you disable this function, the problem disappears.

I think it may be a little more difficult to find a solution though Smile

I think I have found a way to avoid trapped ball inside polygon...give me 2-3 days more....Big Grin
That's great to hear, please let me know if you need any help with testing.....

Hi, Kevin
I rewrote the ResolveBallCollision() function, and then limit the ballcount (It's a good idea, I think Big Grin ) ...add few more codes and shapes...
By the way, I'm using Windows 7 64bit, 4GB RAM, Intel Core i5 ... 
Test the code 5 times, no more trapped balls inside polygons... 

[Modified]
Code:
#win32
set window "Collision", 640, 480
set redraw off
randomize time()

'Vertices
v  = []
v.square    = [-50, -50, 50, -50, 50, 50, -50, 50]
v.star      = [0, -100, 25, -25, 100, 0, 25, 25, 0, 100, -25, 25, -100, 0, -25, -25]
v.cross     = [-19,-59, -19,-19, -59,-19, -59,20, -19,20, -19,60, 20,60, 20,20, 60,20, 60,-19, 20,-19, 20,-59]
v.rectangle = [-64.5, -15.5, 64.5, -15.5, 64.5, 15.5, -64.5, 15.5]
v.tmp1      = [0, 0, 200, 0, 200, 31, 0, 31]
v.tmp2      = [0, 0, 31, 0, 31, 415, 0, 415]

visible lines = [];filledPolys = []

'             MakePoly(verts,       x, y,       r, g, b,        lines, renderList)
square      = MakePoly(v.square,    460, 320, 255, 100, 100,    lines, filledPolys)
star        = MakePoly(v.star,      350, 150, 100, 55, 100,     lines, filledPolys)
cross       = MakePoly(v.cross,     175, 125, 100, 100, 255,    lines, filledPolys)
rectangle   = MakePoly(v.rectangle, 180, 280, 255, 50, 0,       lines, filledPolys)
rectangle2  = MakePoly(v.rectangle, 550, 150, 255, 50, 0,       lines, filledPolys)
rectangle3  = MakePoly(v.rectangle, 370, 430, 55, 100, 0,       lines, filledPolys)
rectangle4  = MakePoly(v.rectangle, 550, 430, 55, 100, 0,       lines, filledPolys)
tmp1        = MakePoly(v.tmp1,       50, 416, 255, 100, 255,    lines, filledPolys)
tmp2        = MakePoly(v.tmp2,       32,  32, 255, 100, 255,    lines, filledPolys)

'Balls
visible objs = []
ballCount = 0
objs[ballCount] = Object(rnd(50,width()-50), 0, 16)
ballCount = ballCount + 1

'Flipper position and speed
visible flipper_left    = rad(30)
visible flipper_left2   = rad(-30)
visible flipper_right   = rad(-30)
visible flipper_right2  = rad(30)
visible flipper_speed   = rad(10)

'Initialize flippers position
visible flipper3Angle = flipper_left
visible flipper4Angle = flipper_right
rectangle3.SetAngle(flipper3Angle)
rectangle4.SetAngle(flipper4Angle)

############  to calculate FPS  ##########################'added by kevin
visible framecount,lasttime = 999,fps,frametime = 0,starttime = 0,endtime = 0
##########################################################

'-----------
' Main Loop
'-----------
while not keydown(KEY_ESCAPE, true)
    'added by kevin
    ###  for FPS calc #########
    framecount = framecount + 1
    starttime = clock()
    ###########################

    ' Clear screen
    set color 0, 0, 0; cls

    ' Draw grid
    set color 100, 100, 100
    for gx = 0 to 640 step 30  draw line gx, 0, gx, 480
    for gy = 0 to 480 step 30  draw line 0, gy, 640, gy

    ' Rotate obstacles: + = clockwise, - = counter-clockwise
    square.SetAngle(square.Angle() + rad(0.5))
    star.SetAngle(star.Angle() + rad(0.5))
    cross.SetAngle(cross.Angle() - rad(0.5))
    rectangle.SetAngle(rectangle.Angle() - rad(0.5))
    rectangle2.SetAngle(rectangle2.Angle() + rad(0.5))
   
    ' Flipper control
    target3 = flipper_left
    target4 = flipper_right
    if keydown(KEY_SPACE)
        target3 = flipper_left2
        target4 = flipper_right2
    endif

    ' flipper
    if flipper3Angle < target3
        flipper3Angle = flipper3Angle + flipper_speed
        if flipper3Angle > target3  then
            flipper3Angle = target3
        endif
    elseif flipper3Angle > target3
        flipper3Angle = flipper3Angle - flipper_speed
        if flipper3Angle < target3  then
            flipper3Angle = target3
        endif
    endif

    if flipper4Angle < target4
        flipper4Angle = flipper4Angle + flipper_speed
        if flipper4Angle > target4  then
            flipper4Angle = target4
        endif
    elseif flipper4Angle > target4
        flipper4Angle = flipper4Angle - flipper_speed
        if flipper4Angle < target4 then
            flipper4Angle = target4
        endif
    endif
    rectangle3.SetAngle(flipper3Angle)
    rectangle4.SetAngle(flipper4Angle)
  
    ' draw obstacles
    foreach polygn in filledPolys  polygn.DrawFilled()

    ' coordinate labels, infobox
    set color 255, 255, 255
    shapes = [square,star,cross,rectangle,rectangle2,rectangle3,rectangle4]
    for i = 0 to sizeof(shapes)-1  
        DrawCoordLabel(shapes[i])
    next
    set caret width()/2, height()-120; center "SPACE: flippers | ENTER: balls"

    ' balls
    foreach ball in objs
        UpdateObject(ball)
        DrawObject(ball)
    next
  
    ' Remove out-of-bounds balls to prevent unnecessary collision checks
    i = 0
    while i < ballCount
        if objs[i].x > 690 or objs[i].x < -50 or objs[i].y > 530
            j = i
            while j < ballCount - 1
                objs[j] = objs[j + 1]
                j = j + 1
            wend
            ballCount = ballCount - 1
        else
            i = i + 1
        endif
    wend
  
    i = 0
    while i < ballCount - 1
        j = i + 1
        while j < ballCount
            ResolveBallCollision(objs[i], objs[j])
            j = j + 1
        wend
        i = i + 1
    wend
  
    'Drop ball [modified, limit ballCount]
    if keydown(KEY_RETURN,true) and ballCount < 32
        objs[ballCount] = Object(rnd(50,width()-50), 0, 16)
        ballCount = ballCount + 1
    endif   
   
    set color 255,255,255
    ###########################################
    'added by kevin
    set caret 320,10;set justification center
    write "FPS = " + fps;wln
    write "ballCount is " + ballCount + " | objs Table size is " + sizeof(objs);wln
    'end
    ###########################################

    redraw
    fwait 60

    #######  FPS calc  ############################
    endtime = clock()
    frametime = frametime + endtime - starttime
    if frametime > 1000 # 1 seconde
        fps = framecount
        framecount = 0
        frametime = 0
    endif
    ################################################
wend


'------------
' Functions
'------------
function Object(x, y, r)
    return [x: x, y: y, r: r, rsqr: r*r, dx: 0, dy: 0, pdx: 0, pdy: 0]
endfunc

' Draw ball
function DrawObject(obj)
    set color 255, 255, 0
    draw ellipse obj.x, obj.y, obj.r, obj.r, true
endfunc

' Update ball
function UpdateObject(obj)
    obj.dy = obj.dy + 0.1
    if obj.dy > 6 then obj.dy = 6
  
    ' Update position
    obj.x = obj.x + obj.dx
    obj.y = obj.y + obj.dy
  
    ' Collision detection
    PushOut(obj, lines)
endfunc

' ResolveBallCollision
function ResolveBallCollision(b1, b2)
    dx = b2.x - b1.x; dy = b2.y - b1.y
    distSq = dx*dx + dy*dy
    minDist = b1.r + b2.r
    minDistSq = minDist * minDist
  
    ' Early exit if no collision or zero distance
    if distSq >= minDistSq or distSq = 0 then return
  
    dist = sqr(distSq)
          
    ' [modified]
    ' Push them apart so they stop overlapping
    overlap = minDist - dist
    nx = dx / dist; ny = dy / dist
    b1.x = b1.x - nx * overlap * 0.5; b1.y = b1.y - ny * overlap * 0.5
    b2.x = b2.x + nx * overlap * 0.5; b2.y = b2.y + ny * overlap * 0.5
   
    ' Adjust speeds if they're moving toward each other
    dvx = b2.dx - b1.dx; dvy = b2.dy - b1.dy
    dvn = dvx*nx + dvy*ny
    if dvn < 0
        bounce = -(1 + 0.7) * dvn * 0.3
        b1.dx = b1.dx - bounce * nx;  b1.dy = b1.dy - bounce * ny
        b2.dx = b2.dx + bounce * nx;  b2.dy = b2.dy + bounce * ny
    endif
endfunc

' PushOut: collision resolution
function PushOut(obj, lines)
    for iter = 1 to 4
        col = false
        deepest = 0
        nx = 0; ny = 0
   
        foreach ln in lines
            t = (obj.x - ln[0])*ln[4] + (obj.y - ln[1])*ln[5]
            if t < 0 then t = 0
            if t > ln[6] then t = ln[6]
            px = ln[0] + t*ln[4]; py = ln[1] + t*ln[5]
   
            dx = obj.x - px; dy = obj.y - py
            dSq = dx*dx + dy*dy
            if dSq < obj.rsqr and dSq > 0.001
                dist = sqr(dSq)
                pen = obj.r - dist
                if pen > deepest
                    deepest = pen
                    nx = dx / dist; ny = dy / dist
                    col = true
                endif
            endif
        next
   
        if not col break
   
        obj.x = obj.x + nx * (deepest + 0.5)
        obj.y = obj.y + ny * (deepest + 0.5)
   
        vn = obj.dx * nx + obj.dy * ny
        if vn < 0
            obj.dx = obj.dx - 1.7 * vn * nx
            obj.dy = obj.dy - 1.7 * vn * ny
        endif
   
        obj.dx = obj.dx * 0.98
        obj.dy = obj.dy * 0.98
    next
endfunc

' Polygon
function Polygon(points, x, y, r, g, b)
    p = [trans: unset, org: [], verts: [], x: x, y: y, a: 0, cx: 0, cy: 0, r: r, g: g, b: b]
    pcount = sizeof(points)/2
  
    ' Store original vertices
    for i = 0 to pcount - 1
        p.verts[sizeof(p.verts)] = points[i*2]
        p.verts[sizeof(p.verts)] = points[i*2 + 1]
    next
  
    ' collision lines
    n = pcount - 1
  
    for i = 0 to n
        j = (i + 1)%pcount
        p.org[sizeof(p.org)] = Line(
                points[i*2], points[i*2 + 1],
                points[j*2], points[j*2 + 1])
    next
  
    for i = 0 to pcount - 1
        p.cx = p.cx + points[i*2]; p.cy = p.cy + points[i*2 + 1]
    next
    p.cx = p.cx/pcount; p.cy = p.cy/pcount
    p.trans = copy(p.org)
  
    p.AddTo = function(lines)
        foreach ln in .trans  lines[sizeof(lines)] = ln
    endfunc

    p.X = function(); return .x ;endfunc
    p.Y = function(); return .y ;endfunc
    p.Angle = function(); return .a ;endfunc
  
    p.SetTransform = function(x, y, angle)
        .x = x; .y = y; .a = angle; .Transform()
    endfunc
    p.SetPosition = function(x, y)
        .x = x; .y = y; .Transform()
    endfunc
    p.SetAngle = function(angle)
        .a = angle; .Transform()
    endfunc
  
    p.Transform = function()
        RotateLines(.org, .trans, .cx, .cy, .a)
        foreach ln in .trans
            ln[0] = ln[0] + .x; ln[1] = ln[1] + .y
            ln[2] = ln[2] + .x; ln[3] = ln[3] + .y
            ln[4] = (ln[2] - ln[0])/ln[6]; ln[5] = (ln[3] - ln[1])/ln[6]
        next
    endfunc
  
    p.DrawFilled = function()
        set color .r, .g, .b
        vcount = sizeof(.verts)/2
        polyArgs = []
        for i = 0 to vcount - 1
            vx = .verts[i*2] - .cx; vy = .verts[i*2 + 1] - .cy
            ' Apply rotation
            rx = vx*cos(.a) - vy*sin(.a)
            ry = vx*sin(.a) + vy*cos(.a)
            polyArgs[sizeof(polyArgs)] = rx + .cx + .x
            polyArgs[sizeof(polyArgs)] = ry + .cy + .y
        next
        draw poly polyArgs, true
    endfunc
  
    p.Transform()
    return p
  
    function RotateLines(srcLines, dstLines, aroundX, aroundY, angle)
        c = cos(angle); s = sin(angle)
        for i = 0 to sizeof(srcLines) - 1
            srcLn = srcLines[i]; dstLn = dstLines[i]
            x = srcLn[0] - aroundX; y = srcLn[1] - aroundY
            dstLn[0] = aroundX + x*c - y*s; dstLn[1] = aroundY + y*c + x*s
            x = srcLn[2] - aroundX; y = srcLn[3] - aroundY
            dstLn[2] = aroundX + x*c - y*s; dstLn[3] = aroundY + y*c + x*s
        next
    endfunc  
endfunc

function Line(x0, y0, x1, y1)
    ln = [x0, y0, x1, y1]
    dx = ln[2] - ln[0]; dy = ln[3] - ln[1]
    ln[6] = sqr(dx*dx + dy*dy)
    ln[4] = dx/ln[6]
    ln[5] = dy/ln[6]
    return ln
endfunc

function MakePoly(verts, x, y, r, g, b, lines, renderList)
    p = Polygon(verts, x, y, r, g, b)
    p.AddTo(lines)
    renderList[sizeof(renderList)] = p
    return p
endfunc

function DrawCoordLabel(p)
    set caret p.X(), p.Y()
    wln "X"
    wln (int(p.X())) + "," + (int(p.Y()))
endfunc
Reply
#24
[quote pid="2688" dateline="1779181953"]

Hi, Kevin
I rewrote the ResolveBallCollision() function, and then limit the ballcount (It's a good idea, I think Big Grin ) ...add few more codes and shapes...
By the way, I'm using Windows 7 64bit, 4GB RAM, Intel Core i5 ... 
Test the code 5 times, no more trapped balls inside polygons... 

[Modified]
Code:
#win32
set window "Collision", 640, 480
set redraw off
randomize time()

'Vertices
v  = []
v.square    = [-50, -50, 50, -50, 50, 50, -50, 50]
v.star      = [0, -100, 25, -25, 100, 0, 25, 25, 0, 100, -25, 25, -100, 0, -25, -25]
v.cross     = [-19,-59, -19,-19, -59,-19, -59,20, -19,20, -19,60, 20,60, 20,20, 60,20, 60,-19, 20,-19, 20,-59]
v.rectangle = [-64.5, -15.5, 64.5, -15.5, 64.5, 15.5, -64.5, 15.5]
v.tmp1      = [0, 0, 200, 0, 200, 31, 0, 31]
v.tmp2      = [0, 0, 31, 0, 31, 415, 0, 415]

visible lines = [];filledPolys = []

'             MakePoly(verts,       x, y,       r, g, b,        lines, renderList)
square      = MakePoly(v.square,    460, 320, 255, 100, 100,    lines, filledPolys)
star        = MakePoly(v.star,      350, 150, 100, 55, 100,     lines, filledPolys)
cross       = MakePoly(v.cross,     175, 125, 100, 100, 255,    lines, filledPolys)
rectangle   = MakePoly(v.rectangle, 180, 280, 255, 50, 0,       lines, filledPolys)
rectangle2  = MakePoly(v.rectangle, 550, 150, 255, 50, 0,       lines, filledPolys)
rectangle3  = MakePoly(v.rectangle, 370, 430, 55, 100, 0,       lines, filledPolys)
rectangle4  = MakePoly(v.rectangle, 550, 430, 55, 100, 0,       lines, filledPolys)
tmp1        = MakePoly(v.tmp1,       50, 416, 255, 100, 255,    lines, filledPolys)
tmp2        = MakePoly(v.tmp2,       32,  32, 255, 100, 255,    lines, filledPolys)

'Balls
visible objs = []
ballCount = 0
objs[ballCount] = Object(rnd(50,width()-50), 0, 16)
ballCount = ballCount + 1

'Flipper position and speed
visible flipper_left    = rad(30)
visible flipper_left2   = rad(-30)
visible flipper_right   = rad(-30)
visible flipper_right2  = rad(30)
visible flipper_speed   = rad(10)

'Initialize flippers position
visible flipper3Angle = flipper_left
visible flipper4Angle = flipper_right
rectangle3.SetAngle(flipper3Angle)
rectangle4.SetAngle(flipper4Angle)

############  to calculate FPS  ##########################'added by kevin
visible framecount,lasttime = 999,fps,frametime = 0,starttime = 0,endtime = 0
##########################################################

'-----------
' Main Loop
'-----------
while not keydown(KEY_ESCAPE, true)
    'added by kevin
    ###  for FPS calc #########
    framecount = framecount + 1
    starttime = clock()
    ###########################

    ' Clear screen
    set color 0, 0, 0; cls

    ' Draw grid
    set color 100, 100, 100
    for gx = 0 to 640 step 30  draw line gx, 0, gx, 480
    for gy = 0 to 480 step 30  draw line 0, gy, 640, gy

    ' Rotate obstacles: + = clockwise, - = counter-clockwise
    square.SetAngle(square.Angle() + rad(0.5))
    star.SetAngle(star.Angle() + rad(0.5))
    cross.SetAngle(cross.Angle() - rad(0.5))
    rectangle.SetAngle(rectangle.Angle() - rad(0.5))
    rectangle2.SetAngle(rectangle2.Angle() + rad(0.5))
   
    ' Flipper control
    target3 = flipper_left
    target4 = flipper_right
    if keydown(KEY_SPACE)
        target3 = flipper_left2
        target4 = flipper_right2
    endif

    ' flipper
    if flipper3Angle < target3
        flipper3Angle = flipper3Angle + flipper_speed
        if flipper3Angle > target3  then
            flipper3Angle = target3
        endif
    elseif flipper3Angle > target3
        flipper3Angle = flipper3Angle - flipper_speed
        if flipper3Angle < target3  then
            flipper3Angle = target3
        endif
    endif

    if flipper4Angle < target4
        flipper4Angle = flipper4Angle + flipper_speed
        if flipper4Angle > target4  then
            flipper4Angle = target4
        endif
    elseif flipper4Angle > target4
        flipper4Angle = flipper4Angle - flipper_speed
        if flipper4Angle < target4 then
            flipper4Angle = target4
        endif
    endif
    rectangle3.SetAngle(flipper3Angle)
    rectangle4.SetAngle(flipper4Angle)
  
    ' draw obstacles
    foreach polygn in filledPolys  polygn.DrawFilled()

    ' coordinate labels, infobox
    set color 255, 255, 255
    shapes = [square,star,cross,rectangle,rectangle2,rectangle3,rectangle4]
    for i = 0 to sizeof(shapes)-1  
        DrawCoordLabel(shapes[i])
    next
    set caret width()/2, height()-120; center "SPACE: flippers | ENTER: balls"

    ' balls
    foreach ball in objs
        UpdateObject(ball)
        DrawObject(ball)
    next
  
    ' Remove out-of-bounds balls to prevent unnecessary collision checks
    i = 0
    while i < ballCount
        if objs[i].x > 690 or objs[i].x < -50 or objs[i].y > 530
            j = i
            while j < ballCount - 1
                objs[j] = objs[j + 1]
                j = j + 1
            wend
            ballCount = ballCount - 1
        else
            i = i + 1
        endif
    wend
  
    i = 0
    while i < ballCount - 1
        j = i + 1
        while j < ballCount
            ResolveBallCollision(objs[i], objs[j])
            j = j + 1
        wend
        i = i + 1
    wend
  
    'Drop ball [modified, limit ballCount]
    if keydown(KEY_RETURN,true) and ballCount < 32
        objs[ballCount] = Object(rnd(50,width()-50), 0, 16)
        ballCount = ballCount + 1
    endif   
   
    set color 255,255,255
    ###########################################
    'added by kevin
    set caret 320,10;set justification center
    write "FPS = " + fps;wln
    write "ballCount is " + ballCount + " | objs Table size is " + sizeof(objs);wln
    'end
    ###########################################

    redraw
    fwait 60

    #######  FPS calc  ############################
    endtime = clock()
    frametime = frametime + endtime - starttime
    if frametime > 1000 # 1 seconde
        fps = framecount
        framecount = 0
        frametime = 0
    endif
    ################################################
wend


'------------
' Functions
'------------
function Object(x, y, r)
    return [x: x, y: y, r: r, rsqr: r*r, dx: 0, dy: 0, pdx: 0, pdy: 0]
endfunc

' Draw ball
function DrawObject(obj)
    set color 255, 255, 0
    draw ellipse obj.x, obj.y, obj.r, obj.r, true
endfunc

' Update ball
function UpdateObject(obj)
    obj.dy = obj.dy + 0.1
    if obj.dy > 6 then obj.dy = 6
  
    ' Update position
    obj.x = obj.x + obj.dx
    obj.y = obj.y + obj.dy
  
    ' Collision detection
    PushOut(obj, lines)
endfunc

' ResolveBallCollision
function ResolveBallCollision(b1, b2)
    dx = b2.x - b1.x; dy = b2.y - b1.y
    distSq = dx*dx + dy*dy
    minDist = b1.r + b2.r
    minDistSq = minDist * minDist
  
    ' Early exit if no collision or zero distance
    if distSq >= minDistSq or distSq = 0 then return
  
    dist = sqr(distSq)
          
    ' [modified]
    ' Push them apart so they stop overlapping
    overlap = minDist - dist
    nx = dx / dist; ny = dy / dist
    b1.x = b1.x - nx * overlap * 0.5; b1.y = b1.y - ny * overlap * 0.5
    b2.x = b2.x + nx * overlap * 0.5; b2.y = b2.y + ny * overlap * 0.5
   
    ' Adjust speeds if they're moving toward each other
    dvx = b2.dx - b1.dx; dvy = b2.dy - b1.dy
    dvn = dvx*nx + dvy*ny
    if dvn < 0
        bounce = -(1 + 0.7) * dvn * 0.3
        b1.dx = b1.dx - bounce * nx;  b1.dy = b1.dy - bounce * ny
        b2.dx = b2.dx + bounce * nx;  b2.dy = b2.dy + bounce * ny
    endif
endfunc

' PushOut: collision resolution
function PushOut(obj, lines)
    for iter = 1 to 4
        col = false
        deepest = 0
        nx = 0; ny = 0
   
        foreach ln in lines
            t = (obj.x - ln[0])*ln[4] + (obj.y - ln[1])*ln[5]
            if t < 0 then t = 0
            if t > ln[6] then t = ln[6]
            px = ln[0] + t*ln[4]; py = ln[1] + t*ln[5]
   
            dx = obj.x - px; dy = obj.y - py
            dSq = dx*dx + dy*dy
            if dSq < obj.rsqr and dSq > 0.001
                dist = sqr(dSq)
                pen = obj.r - dist
                if pen > deepest
                    deepest = pen
                    nx = dx / dist; ny = dy / dist
                    col = true
                endif
            endif
        next
   
        if not col break
   
        obj.x = obj.x + nx * (deepest + 0.5)
        obj.y = obj.y + ny * (deepest + 0.5)
   
        vn = obj.dx * nx + obj.dy * ny
        if vn < 0
            obj.dx = obj.dx - 1.7 * vn * nx
            obj.dy = obj.dy - 1.7 * vn * ny
        endif
   
        obj.dx = obj.dx * 0.98
        obj.dy = obj.dy * 0.98
    next
endfunc

' Polygon
function Polygon(points, x, y, r, g, b)
    p = [trans: unset, org: [], verts: [], x: x, y: y, a: 0, cx: 0, cy: 0, r: r, g: g, b: b]
    pcount = sizeof(points)/2
  
    ' Store original vertices
    for i = 0 to pcount - 1
        p.verts[sizeof(p.verts)] = points[i*2]
        p.verts[sizeof(p.verts)] = points[i*2 + 1]
    next
  
    ' collision lines
    n = pcount - 1
  
    for i = 0 to n
        j = (i + 1)%pcount
        p.org[sizeof(p.org)] = Line(
                points[i*2], points[i*2 + 1],
                points[j*2], points[j*2 + 1])
    next
  
    for i = 0 to pcount - 1
        p.cx = p.cx + points[i*2]; p.cy = p.cy + points[i*2 + 1]
    next
    p.cx = p.cx/pcount; p.cy = p.cy/pcount
    p.trans = copy(p.org)
  
    p.AddTo = function(lines)
        foreach ln in .trans  lines[sizeof(lines)] = ln
    endfunc

    p.X = function(); return .x ;endfunc
    p.Y = function(); return .y ;endfunc
    p.Angle = function(); return .a ;endfunc
  
    p.SetTransform = function(x, y, angle)
        .x = x; .y = y; .a = angle; .Transform()
    endfunc
    p.SetPosition = function(x, y)
        .x = x; .y = y; .Transform()
    endfunc
    p.SetAngle = function(angle)
        .a = angle; .Transform()
    endfunc
  
    p.Transform = function()
        RotateLines(.org, .trans, .cx, .cy, .a)
        foreach ln in .trans
            ln[0] = ln[0] + .x; ln[1] = ln[1] + .y
            ln[2] = ln[2] + .x; ln[3] = ln[3] + .y
            ln[4] = (ln[2] - ln[0])/ln[6]; ln[5] = (ln[3] - ln[1])/ln[6]
        next
    endfunc
  
    p.DrawFilled = function()
        set color .r, .g, .b
        vcount = sizeof(.verts)/2
        polyArgs = []
        for i = 0 to vcount - 1
            vx = .verts[i*2] - .cx; vy = .verts[i*2 + 1] - .cy
            ' Apply rotation
            rx = vx*cos(.a) - vy*sin(.a)
            ry = vx*sin(.a) + vy*cos(.a)
            polyArgs[sizeof(polyArgs)] = rx + .cx + .x
            polyArgs[sizeof(polyArgs)] = ry + .cy + .y
        next
        draw poly polyArgs, true
    endfunc
  
    p.Transform()
    return p
  
    function RotateLines(srcLines, dstLines, aroundX, aroundY, angle)
        c = cos(angle); s = sin(angle)
        for i = 0 to sizeof(srcLines) - 1
            srcLn = srcLines[i]; dstLn = dstLines[i]
            x = srcLn[0] - aroundX; y = srcLn[1] - aroundY
            dstLn[0] = aroundX + x*c - y*s; dstLn[1] = aroundY + y*c + x*s
            x = srcLn[2] - aroundX; y = srcLn[3] - aroundY
            dstLn[2] = aroundX + x*c - y*s; dstLn[3] = aroundY + y*c + x*s
        next
    endfunc  
endfunc

function Line(x0, y0, x1, y1)
    ln = [x0, y0, x1, y1]
    dx = ln[2] - ln[0]; dy = ln[3] - ln[1]
    ln[6] = sqr(dx*dx + dy*dy)
    ln[4] = dx/ln[6]
    ln[5] = dy/ln[6]
    return ln
endfunc

function MakePoly(verts, x, y, r, g, b, lines, renderList)
    p = Polygon(verts, x, y, r, g, b)
    p.AddTo(lines)
    renderList[sizeof(renderList)] = p
    return p
endfunc

function DrawCoordLabel(p)
    set caret p.X(), p.Y()
    wln "X"
    wln (int(p.X())) + "," + (int(p.Y()))
endfunc

[/quote]

This is very good progress. Performance is good, with fps around 100 most of the time, with very short occasional dips to 50. 
Circle collision is much better. I thought it was perfect, but unfortunately, after starting to move the flippers, a ball did get stuck inside one. I re-tested, and this happened again. I have tested for probably 40-45 minutes in total, and the flipper issue did happen quite a few times, and twice, a ball went inside the rectangle above the flippers.

   
   
I think that there is a workaround for now for the flippers. I reduced the thickness of the flippers from 31 to 20 pixels, and this seems to have fixed it. 20 pixels is a random size - it may be that it could be much closer to the original 31 if necessary, but this would need testing. It looks to me as though the ball will still go inside the flipper now, but immediately it will come back out on the same side. 
It seems relevant to me that I have only seen the ball go inside the flipper, and also the the square that is closest to the flippers. I think that this may be fixed by moving the square a little further away from the flippers. I have moved it up 40 pixels, and so far, I have not seen a ball go inside the square. I think that this needs further testing to be confident about this though, and I will let the code run this afternoon (I have made a small amendment to release the balls automatically, and move the flippers randomly, so I can leave it running unattended).
Hope this helps? All the best - Kevin.
******************************************************************************************
Quick update. I have run the code with the 2 modifications (20 pixel flippers and 40 pixel higher square) for 2 hours now, and no ball has entered the square, or stayed within the flipper. But, I have observed that sometimes, when a ball starts to move inside the flipper, it will pass through it, rather than bouncing off, as I had originally thought.
Also, on one occasion in the 2 hours, a ball entered the L shape, which is again 31 pixels wide.
   
Reply
#25
I think the issue with the flippers is the speed of movement. Currently, moving the flippers constantly, I see a ball moving inside the flipper within 1-2 minutes. By reducing flipper_speed to rad(5), from rad(10), I haven't seen any issues with the balls after 15 minutes running the code, which is very encouraging. I will let it run for a while longer, and post the results.
Reply
#26
(05-20-2026, 08:17 AM)kevin Wrote: I think the issue with the flippers is the speed of movement. Currently, moving the flippers constantly, I see a ball moving inside the flipper within 1-2 minutes. By reducing flipper_speed to rad(5), from rad(10), I haven't seen any issues with the balls after 15 minutes running the code, which is very encouraging. I will let it run for a while longer, and post the results.

This project has turned into a mini research class Big Grin
Reply
#27
(05-20-2026, 08:22 AM)1micha.elok Wrote:
(05-20-2026, 08:17 AM)kevin Wrote: I think the issue with the flippers is the speed of movement. Currently, moving the flippers constantly, I see a ball moving inside the flipper within 1-2 minutes. By reducing flipper_speed to rad(5), from rad(10), I haven't seen any issues with the balls after 15 minutes running the code, which is very encouraging. I will let it run for a while longer, and post the results.

This project has turned into a mini research class Big Grin

I'm really enjoying studying your/Marcus's code Smile

So, the reduced flipper speed was great for 29 minutes - no ball inside any of the polygons. For comparison, using the original speed, 3 balls went inside the flippers within 5 minutes.

 But then, on the 30th minute, a ball got stuck inside one of the flippers. So I think that reducing the speed may be a workaround, it just needs a bit of experimentation to find the optimum speed. I'll try it at rad(3) next (over 30 minutes running currently, and no issues anywhere). 

I expect that you have a good way of testing this, but in case anyone else wants to have a play with it, I'll add the code below that I use to allow me to run the code and just leave it running - the balls are added, and the flippers move, automatically.
Code:
'This is for testing only

#win32
set window "Collision", 640, 480
set redraw off
randomize time()

'Vertices
v  = []
v.square    = [-50, -50, 50, -50, 50, 50, -50, 50]
v.star      = [0, -100, 25, -25, 100, 0, 25, 25, 0, 100, -25, 25, -100, 0, -25, -25]
v.cross     = [-19,-59, -19,-19, -59,-19, -59,20, -19,20, -19,60, 20,60, 20,20, 60,20, 60,-19, 20,-19, 20,-59]
v.rectangle = [-64.5, -15.5, 64.5, -15.5, 64.5, 15.5, -64.5, 15.5]
'v.rectangle = [-64.5, -10, 64.5, -10, 64.5, 10, -64.5, 10]
'v.rectangle = [-64.5, -14, 64.5, -14, 64.5, 14, -64.5, 14]
v.tmp1      = [0, 0, 200, 0, 200, 31, 0, 31]
v.tmp2      = [0, 0, 31, 0, 31, 415, 0, 415]

visible lines = [];filledPolys = []

'             MakePoly(verts,       x, y,       r, g, b,        lines, renderList)
square      = MakePoly(v.square,    460, 320, 255, 100, 100,    lines, filledPolys)
star        = MakePoly(v.star,      350, 150, 100, 55, 100,     lines, filledPolys)
cross       = MakePoly(v.cross,     175, 125, 100, 100, 255,    lines, filledPolys)
rectangle   = MakePoly(v.rectangle, 180, 320, 255, 50, 0,       lines, filledPolys)
rectangle2  = MakePoly(v.rectangle, 550, 150, 255, 50, 0,       lines, filledPolys)
rectangle3  = MakePoly(v.rectangle, 370, 430, 55, 100, 0,       lines, filledPolys)
rectangle4  = MakePoly(v.rectangle, 550, 430, 55, 100, 0,       lines, filledPolys)
tmp1        = MakePoly(v.tmp1,       50, 416, 255, 100, 255,    lines, filledPolys)
tmp2        = MakePoly(v.tmp2,       32,  32, 255, 100, 255,    lines, filledPolys)

'Balls
visible objs = []
ballCount = 0
objs[ballCount] = Object(rnd(50,width()-50), 0, 16)
ballCount = ballCount + 1

'Flipper position and speed
visible flipper_left    = rad(30)
visible flipper_left2   = rad(-30)
visible flipper_right   = rad(-30)
visible flipper_right2  = rad(30)
visible flipper_speed   = rad(10)

'Initialize flippers position
visible flipper3Angle = flipper_left
visible flipper4Angle = flipper_right
rectangle3.SetAngle(flipper3Angle)
rectangle4.SetAngle(flipper4Angle)

############  to calculate FPS  ##########################'added by kevin
visible framecount,lasttime = 999,fps,frametime = 0,starttime = 0,endtime = 0
##########################################################
visible drop_timer = 0,paddle_timer = 0,paddle_target = rnd(30) + 10
'-----------
' Main Loop
'-----------
while not keydown(KEY_ESCAPE, true)
    'added by kevin
    ###  for FPS calc #########
    framecount = framecount + 1
    starttime = clock()
    ###########################
    'automate for testing - kevin
    drop_timer = drop_timer + 1
    if drop_timer >= 10
        drop_timer = 0
        objs[ballCount] = Object(rnd(50,width()-50), 0, 16)
        ballCount = ballCount + 1
    endif
  '  target3 = flipper_left
  '  target4 = flipper_right   
    paddle_timer = paddle_timer + 1
   ' if paddle_timer >= paddle_target
   '     paddle_timer = 0
   '     paddle_target = rnd(30) + 10
   '     target3 = flipper_left2
   '     target4 = flipper_right2
    'endif
    'end of added code
    ' Clear screen
    set color 0, 0, 0; cls

    ' Draw grid
    set color 100, 100, 100
    for gx = 0 to 640 step 30  draw line gx, 0, gx, 480
    for gy = 0 to 480 step 30  draw line 0, gy, 640, gy

    ' Rotate obstacles: + = clockwise, - = counter-clockwise
    square.SetAngle(square.Angle() + rad(0.5))
    star.SetAngle(star.Angle() + rad(0.5))
    cross.SetAngle(cross.Angle() - rad(0.5))
    rectangle.SetAngle(rectangle.Angle() - rad(0.5))
    rectangle2.SetAngle(rectangle2.Angle() + rad(0.5))
   
    ' Flipper control
    target3 = flipper_left
    target4 = flipper_right
    'if keydown(KEY_SPACE)
    if paddle_timer >= paddle_target
      '  paddle_timer = 0
        paddle_target = rnd(30) + 10
        target3 = flipper_left2
        target4 = flipper_right2
    endif
    if paddle_timer >= paddle_target + 60 then paddle_timer = 0
    ' flipper
    if flipper3Angle < target3
        flipper3Angle = flipper3Angle + flipper_speed
        if flipper3Angle > target3  then
            flipper3Angle = target3
        endif
    elseif flipper3Angle > target3
        flipper3Angle = flipper3Angle - flipper_speed
        if flipper3Angle < target3  then
            flipper3Angle = target3
        endif
    endif

    if flipper4Angle < target4
        flipper4Angle = flipper4Angle + flipper_speed
        if flipper4Angle > target4  then
            flipper4Angle = target4
        endif
    elseif flipper4Angle > target4
        flipper4Angle = flipper4Angle - flipper_speed
        if flipper4Angle < target4 then
            flipper4Angle = target4
        endif
    endif
    rectangle3.SetAngle(flipper3Angle)
    rectangle4.SetAngle(flipper4Angle)
 
    ' draw obstacles
    foreach polygn in filledPolys  polygn.DrawFilled()

    ' coordinate labels, infobox
    set color 255, 255, 255
    shapes = [square,star,cross,rectangle,rectangle2,rectangle3,rectangle4]
    for i = 0 to sizeof(shapes)-1 
        DrawCoordLabel(shapes[i])
    next
    set caret width()/2, height()-120; center "SPACE: flippers | ENTER: balls"

    ' balls
    foreach ball in objs
        UpdateObject(ball)
        DrawObject(ball)
    next
 
    ' Remove out-of-bounds balls to prevent unnecessary collision checks
    i = 0
    while i < ballCount
        if objs[i].x > 690 or objs[i].x < -50 or objs[i].y > 530
            j = i
            while j < ballCount - 1
                objs[j] = objs[j + 1]
                j = j + 1
            wend
            ballCount = ballCount - 1
        else
            i = i + 1
        endif
    wend
 
    i = 0
    while i < ballCount - 1
        j = i + 1
        while j < ballCount
            ResolveBallCollision(objs[i], objs[j])
            j = j + 1
        wend
        i = i + 1
    wend
 
    'Drop ball [modified, limit ballCount]
    if keydown(KEY_RETURN,true) and ballCount < 60
        objs[ballCount] = Object(rnd(50,width()-50), 0, 16)
        ballCount = ballCount + 1
    endif   
   
    set color 255,255,255
    ###########################################
    'added by kevin
    set caret 320,10;set justification center
    write "FPS = " + fps;wln
    write "ballCount is " + ballCount + " | objs Table size is " + sizeof(objs);wln
    'end
    ###########################################

    redraw
    fwait 60

    #######  FPS calc  ############################
    endtime = clock()
    frametime = frametime + endtime - starttime
    if frametime > 1000 # 1 seconde
        fps = framecount
        framecount = 0
        frametime = 0
    endif
    ################################################
wend


'------------
' Functions
'------------
function Object(x, y, r)
    return [x: x, y: y, r: r, rsqr: r*r, dx: 0, dy: 0, pdx: 0, pdy: 0]
endfunc

' Draw ball
function DrawObject(obj)
    set color 255, 255, 0
    draw ellipse obj.x, obj.y, obj.r, obj.r, true
endfunc

' Update ball
function UpdateObject(obj)
    obj.dy = obj.dy + 0.1
    if obj.dy > 6 then obj.dy = 6
 
    ' Update position
    obj.x = obj.x + obj.dx
    obj.y = obj.y + obj.dy
 
    ' Collision detection
    PushOut(obj, lines)
endfunc

' ResolveBallCollision
function ResolveBallCollision(b1, b2)
    dx = b2.x - b1.x; dy = b2.y - b1.y
    distSq = dx*dx + dy*dy
    minDist = b1.r + b2.r
    minDistSq = minDist * minDist
 
    ' Early exit if no collision or zero distance
    if distSq >= minDistSq or distSq = 0 then return
 
    dist = sqr(distSq)
         
    ' [modified]
    ' Push them apart so they stop overlapping
    overlap = minDist - dist
    nx = dx / dist; ny = dy / dist
    b1.x = b1.x - nx * overlap * 0.5; b1.y = b1.y - ny * overlap * 0.5
    b2.x = b2.x + nx * overlap * 0.5; b2.y = b2.y + ny * overlap * 0.5
   
    ' Adjust speeds if they're moving toward each other
    dvx = b2.dx - b1.dx; dvy = b2.dy - b1.dy
    dvn = dvx*nx + dvy*ny
    if dvn < 0
        bounce = -(1 + 0.7) * dvn * 0.3
        b1.dx = b1.dx - bounce * nx;  b1.dy = b1.dy - bounce * ny
        b2.dx = b2.dx + bounce * nx;  b2.dy = b2.dy + bounce * ny
    endif
endfunc

' PushOut: collision resolution
function PushOut(obj, lines)
    for iter = 1 to 4
        col = false
        deepest = 0
        nx = 0; ny = 0
   
        foreach ln in lines
            t = (obj.x - ln[0])*ln[4] + (obj.y - ln[1])*ln[5]
            if t < 0 then t = 0
            if t > ln[6] then t = ln[6]
            px = ln[0] + t*ln[4]; py = ln[1] + t*ln[5]
   
            dx = obj.x - px; dy = obj.y - py
            dSq = dx*dx + dy*dy
            if dSq < obj.rsqr and dSq > 0.001
                dist = sqr(dSq)
                pen = obj.r - dist
                if pen > deepest
                    deepest = pen
                    nx = dx / dist; ny = dy / dist
                    col = true
                endif
            endif
        next
   
        if not col break
   
        obj.x = obj.x + nx * (deepest + 0.5)
        obj.y = obj.y + ny * (deepest + 0.5)
   
        vn = obj.dx * nx + obj.dy * ny
        if vn < 0
            obj.dx = obj.dx - 1.7 * vn * nx
            obj.dy = obj.dy - 1.7 * vn * ny
        endif
   
        obj.dx = obj.dx * 0.98
        obj.dy = obj.dy * 0.98
    next
endfunc

' Polygon
function Polygon(points, x, y, r, g, b)
    p = [trans: unset, org: [], verts: [], x: x, y: y, a: 0, cx: 0, cy: 0, r: r, g: g, b: b]
    pcount = sizeof(points)/2
 
    ' Store original vertices
    for i = 0 to pcount - 1
        p.verts[sizeof(p.verts)] = points[i*2]
        p.verts[sizeof(p.verts)] = points[i*2 + 1]
    next
 
    ' collision lines
    n = pcount - 1
 
    for i = 0 to n
        j = (i + 1)%pcount
        p.org[sizeof(p.org)] = Line(
                points[i*2], points[i*2 + 1],
                points[j*2], points[j*2 + 1])
    next
 
    for i = 0 to pcount - 1
        p.cx = p.cx + points[i*2]; p.cy = p.cy + points[i*2 + 1]
    next
    p.cx = p.cx/pcount; p.cy = p.cy/pcount
    p.trans = copy(p.org)
 
    p.AddTo = function(lines)
        foreach ln in .trans  lines[sizeof(lines)] = ln
    endfunc

    p.X = function(); return .x ;endfunc
    p.Y = function(); return .y ;endfunc
    p.Angle = function(); return .a ;endfunc
 
    p.SetTransform = function(x, y, angle)
        .x = x; .y = y; .a = angle; .Transform()
    endfunc
    p.SetPosition = function(x, y)
        .x = x; .y = y; .Transform()
    endfunc
    p.SetAngle = function(angle)
        .a = angle; .Transform()
    endfunc
 
    p.Transform = function()
        RotateLines(.org, .trans, .cx, .cy, .a)
        foreach ln in .trans
            ln[0] = ln[0] + .x; ln[1] = ln[1] + .y
            ln[2] = ln[2] + .x; ln[3] = ln[3] + .y
            ln[4] = (ln[2] - ln[0])/ln[6]; ln[5] = (ln[3] - ln[1])/ln[6]
        next
    endfunc
 
    p.DrawFilled = function()
        set color .r, .g, .b
        vcount = sizeof(.verts)/2
        polyArgs = []
        for i = 0 to vcount - 1
            vx = .verts[i*2] - .cx; vy = .verts[i*2 + 1] - .cy
            ' Apply rotation
            rx = vx*cos(.a) - vy*sin(.a)
            ry = vx*sin(.a) + vy*cos(.a)
            polyArgs[sizeof(polyArgs)] = rx + .cx + .x
            polyArgs[sizeof(polyArgs)] = ry + .cy + .y
        next
        draw poly polyArgs, true
    endfunc
 
    p.Transform()
    return p
 
    function RotateLines(srcLines, dstLines, aroundX, aroundY, angle)
        c = cos(angle); s = sin(angle)
        for i = 0 to sizeof(srcLines) - 1
            srcLn = srcLines[i]; dstLn = dstLines[i]
            x = srcLn[0] - aroundX; y = srcLn[1] - aroundY
            dstLn[0] = aroundX + x*c - y*s; dstLn[1] = aroundY + y*c + x*s
            x = srcLn[2] - aroundX; y = srcLn[3] - aroundY
            dstLn[2] = aroundX + x*c - y*s; dstLn[3] = aroundY + y*c + x*s
        next
    endfunc 
endfunc

function Line(x0, y0, x1, y1)
    ln = [x0, y0, x1, y1]
    dx = ln[2] - ln[0]; dy = ln[3] - ln[1]
    ln[6] = sqr(dx*dx + dy*dy)
    ln[4] = dx/ln[6]
    ln[5] = dy/ln[6]
    return ln
endfunc

function MakePoly(verts, x, y, r, g, b, lines, renderList)
    p = Polygon(verts, x, y, r, g, b)
    p.AddTo(lines)
    renderList[sizeof(renderList)] = p
    return p
endfunc

function DrawCoordLabel(p)
    set caret p.X(), p.Y()
    wln "X"
    wln (int(p.X())) + "," + (int(p.Y()))
endfunc
Reply
#28
Quick update:

Using a flipper_speed of rad(3), all was perfect for about an hour, when a ball got trapped in a flipper. So, I changed it to rad(1), which I believe is twice the speed of the other polygon rotations, and it has run perfectly for just over 2 hours - I will keep it running for now.

If this continues to work well, I'm sure there are many things that could be done with this code. However, it would be really good if a different solution could be found, where faster movement would be allowed?
Reply
#29
Final update today.....so with the paddle_speed set to rad(1), there were no issues for over 3 hours. I then left it unobserved for a total of 6 hours, and when I checked back, there were 2 balls stuck in the "L" shaped polygon - none anywhere else, including the paddles. So a definite improvement over rad(10), but still with a frustrating issue, albeit minor. I wonder whether it may be necessary to either restrict the paddle_speed further (not sure about this, as no balls got stuck in the paddles), or maybe restrict the top speed of the balls (I have noticed that if the balls bounce off of polygons at particular angles, they can do so pretty fast)?
Incidentally, thanks are due to Johnno. It was his post last week about possible cause being balls moving through single pixels that have set me on this path.
Reply
#30
Thanks for the updates on the collision issues. I’m thinking about implementing delta time for the ball movement so it runs exactly the same on old and new machines alike.
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)