QB64 Phoenix Edition
error doing image collision detection with _MemGet - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: QB64 Rising (https://qb64phoenix.com/forum/forumdisplay.php?fid=1)
+--- Forum: Code and Stuff (https://qb64phoenix.com/forum/forumdisplay.php?fid=3)
+---- Forum: Help Me! (https://qb64phoenix.com/forum/forumdisplay.php?fid=10)
+---- Thread: error doing image collision detection with _MemGet (/showthread.php?tid=3910)

Pages: 1 2 3 4 5 6


RE: error doing image collision detection with _MemGet - Herve - 09-14-2025

Hi,

Pixel-perfect collision detection demo; it was fun to create. There's definitely room for performance improvements.

Code: (Select All)
' This program demonstrates the application of a collision detection algorithm in 3 steps:
'
' Step 1. Inspecting areas to identify where at least 2 shapes are in.
' Step 2. For each area, checking the distance between the present shapes to determine
'        if they could potentially be in contact.
' Step 3. Testing for overlap in each area where the shapes are potentially in contact
'        using semi-transparent shape masks.

type point_type

    x as double
    y as double
end type

type shape_type
    index as integer                ' shape id
    radius as integer              ' half size of the shape
    firstPointIndex as integer      ' first pointer to the points array for vertices
    lastPointIndex as integer      ' lasr pointer for vertices
    x as double                    ' shape position
    y as double
    rgbaColor as long              ' base color
    speed as double                ' movement speed
    direction as double            ' direction speed
    rotation as double              ' angular speed (radians)
    orientation as double          ' angular orientation (radians)
    drawString as string            ' draw description
    area as integer                ' area where the center of the shape is located
    areaX as integer                '
    areaY as integer                '
    imageHandle as long            ' stock shape drawing
    maskHandle as long              ' stock mask drawing
    maskComputed as integer        ' mask indicator (-1 = computed / 0 = not computed)
    collid as integer              ' collision indicator (-1 = collision detected)
end type

const SHAPES_COUNT = 128            ' number of shapes
const MIN_SHAPE_RADIUS = 5          ' minimum radius of the circle containing a shape
const MAX_SHAPE_RADIUS = 30        ' maximum radius of the circle containing a shape
const MIN_SHAPE_VERTICES = 5        ' minimum number of vertices of a shape
const MAX_SHAPE_VERTICES = 15      ' maximum number of vertices of a shape
const MIN_SHAPE_SPEED = 0.5        ' minimum speed of a shape
const MAX_SHAPE_SPEED = 1.1        ' maximum speed of a shape

const PIXELS_PRECISION = 2          ' collision pixels precision

type area_type
    shapeLocator as string * SHAPES_COUNT  ' Character array to memorize shapes in area
    column as integer              ' area colum
    row as integer                  ' area row
    x as integer                    ' top-left position of the area on the screen
    y as integer                   
    center as point_type            ' center position of the area onthe screen
end type

const DRAW_MODE = 1                ' 0 = use DRAW instruction / 1 = use LINE instruction

const STEP_MODE_KEY = "S"          ' toggle stepmode
step_mode_activated% = 0            ' -1 = step by step / 0 = don't

const SHOW_DETECTION_KEY = "D"      ' toggle drawing proximity detection areas
show_detection_activated% = 0      ' -1 = show proximity detections areas / 0 = don't

const SHOW_GRID_KEY = "G"          ' toggle drawing grid to show area delimitations
show_grid_activated% = 0            ' -1 = show grid of areas / 0 = dont't

const SHOW_COLLISIONS_KEY = "C"    ' toogle drawing areas with potential collisions
show_collisions_activated% = 0      ' -1 = show areas where collisions are potentials / 0 = dont't

const EXPECTED_FPS = 60            ' Expected Frames Per Second

const VIEWPORT_WIDTH = 440          ' viewport width
const VIEWPORT_HEIGHT = 440        ' viewport height

dim shared TAU as single            ' 2 * PI
TAU = 8 * atn(1)

redim shapes(-1) as shape_type      ' array containing the shapes description
redim points(0) as point_type      ' array containing coordinates of the vertices of the shapes

' definition of the page dimension in which the shapes are drawn

const X_DECAY = _ceil(2.5 * MAX_SHAPE_RADIUS)      ' offset between canvas and viewport
const Y_DECAY = _ceil(2.5 * MAX_SHAPE_RADIUS)
const CANVAS_WIDTH = VIEWPORT_WIDTH + 2 * X_DECAY  ' canvas size
const CANVAS_HEIGHT = VIEWPORT_HEIGHT + 2 * Y_DECAY

' areas definitions

const AREAS_WIDTH = _ceil(CANVAS_WIDTH / MAX_SHAPE_RADIUS)      ' number of areas in a row
const AREAS_HEIGHT = _ceil(CANVAS_HEIGHT / MAX_SHAPE_RADIUS)    ' number of areas in a colum
const AREAS_COUNT = AREAS_WIDTH * AREAS_HEIGHT                  ' total number of areas (size of areas array)
const WIDTH_OF_AREA = 2 * MAX_SHAPE_RADIUS                      ' size of an area
const HEIGHT_OF_AREA = 2 * MAX_SHAPE_RADIUS

' areas array initialisation

redim areas(0 to AREAS_COUNT) as area_type

for i%=lbound(areas) to ubound(areas)
    areas(i%).column = i% mod AREAS_WIDTH
    areas(i%).row = i% \ AREAS_WIDTH + 1
    areas(i%).x = areas(i%).column * MAX_SHAPE_RADIUS
    areas(i%).y = areas(i%).row * MAX_SHAPE_RADIUS
    areas(i%).center.x = areas(i%).x + MAX_SHAPE_RADIUS
    areas(i%).center.y = areas(i%).y + MAX_SHAPE_RADIUS
next i%

' set random draw

randomize timer

' home screen

screen 0
cls
color _rgb(63,160,0)
locate 4,1
? "    ========================"
? "    Collision Detection Demo"
? "    ========================"
?
? "    What this program does:"
? "    - Generates moving and rotating irregular polygonal shapes."
? "    - Uses spatial partitioning (areas) to find near neighbours."
? "    - Performs circle-circle and pixel-level mask collision tests."
? "    - Show collisions by changing the color of the affected shapes."
?
? "    Keyboard shortcuts:"
? "    S  - Toggle step mode (when active, press any key to advance a frame)"
? "    D  - Toggle drawing of proximity detection areas"
? "    G  - Toggle grid of areas"
? "    C  - Toggle highlighting of areas with potential collisions"
? "    Esc - Quit"
?
? "    Press any key to start..."
do : k$ = inkey$ : loop while k$ = ""

' generate all shapes

for i% = 1 to SHAPES_COUNT
    generateShape _
        shapes(), points(), _
        MIN_SHAPE_RADIUS, MAX_SHAPE_RADIUS, _
        MIN_SHAPE_VERTICES, MAX_SHAPE_VERTICES, _
        MIN_SHAPE_SPEED, MAX_SHAPE_SPEED
next i%

' prepare screen rendering

viewport& = _newimage(VIEWPORT_WIDTH,VIEWPORT_HEIGHT,32)
canvas& = _newimage(CANVAS_WIDTH,CANVAS_HEIGHT,32)
textview& = _newimage(CANVAS_WIDTH,64,32)
screen viewport&

' timer to compute frames per second

t = timer(.001)

' main loop

do
    _limit EXPECTED_FPS

    _dest canvas&
    cls

    ' draw shapes

    for i%=lbound(shapes) to ubound(shapes)
        drawShape shapes(i%), points()
        shapes(i%).collid = 0
    next i%

    ' draw grid

    if show_grid_activated% then
        for x% = 0 to AREAS_WIDTH
            xx%= (x% + 0.5) * MAX_SHAPE_RADIUS
            line (xx%,0)-(xx%,CANVAS_HEIGHT),_rgba(255,255,255,63)
        next x%
        for y% = 0 to AREAS_HEIGHT
            yy% = (y% + 0.5) * MAX_SHAPE_RADIUS
            line (0,yy%)-(CANVAS_WIDTH,yy%),_rgba(255,255,255,63)
        next y%
    end if

    ' init areas for collision detection

    for i% = lbound(areas) to ubound(areas)
        areas(i%).shapeLocator = space$(SHAPES_COUNT)
    next i%

    ' move shapes

    for i%=lbound(shapes) to ubound(shapes)
        moveShape shapes(i%), points()
    next i%

    ' compute proximity detections areas
    ' and draw on demand
   
    for i%=lbound(shapes) to ubound(shapes)
        redim targets(-1) as integer
        evaluateShapeSector shapes(i%), areas(), targets()
        if show_detection_activated% then
            if shapes(i%).area>=0 then
                for j% = 0 to ubound(targets)
                    x% = targets(j%) mod AREAS_WIDTH
                    y% = targets(j%) \ AREAS_WIDTH + 1
                    circle ( _
                        x% * MAX_SHAPE_RADIUS, _
                        y% * MAX_SHAPE_RADIUS _
                        ), MAX_SHAPE_RADIUS\2, _rgba(255,255,255,127)
                next j%       
            end if
        end if
    next i%

    ' detect collisions
    ' and draw ondemand

    for i% = lbound(areas) to ubound(areas)
        if instr(areas(i%).shapeLocator,"*") <> _instrrev(areas(i%).shapeLocator,"*") then
            collid% = collision(shapes(),areas(i%))
            ' draw potentials collisions areas
            if show_collisions_activated% then
                if collid% then
                    c~& = _rgb(255,0,0)
                else
                    c~& = _rgba(255,255,0,127)
                end if
                circle ( _
                    areas(i%).x, _
                    areas(i%).y _
                    ), MAX_SHAPE_RADIUS\2, c~&
            end if
        end if
    next i%

    color &HFFFFFFFF
    _printstring (100,100), "fps:"+str$(fps%)

    frames% = frames% + 1
    if timer(.001)>=t+1 then
        fps% = frames%
        frames% = 0
        t = timer(.001)
    end if

    ' display result

    _putimage (- X_DECAY,- Y_DECAY), canvas&, viewport&

    ' keyboard interaction

    do
        key$ = inkey$
        if key$ = chr$(27) then
            system
        end if
        select case ucase$(key$)
            case STEP_MODE_KEY
                step_mode_activated% = - step_mode_activated% - 1
            case SHOW_DETECTION_KEY
                show_detection_activated% = - show_detection_activated% - 1
            case SHOW_GRID_KEY
                show_grid_activated% = - show_grid_activated% - 1
            case SHOW_COLLISIONS_KEY
                show_collisions_activated% = - show_collisions_activated% - 1
        end select
        if step_mode_activated% then
            if key$ <> "" then exit do
        else
            exit do
        end if
    loop

loop

' generate a polygonal shape
' with movement attributes

sub generateShape ( _
        shapes() as shape_type, _
        points() as point_type, _
        minRadius as integer, maxRadius as integer, _
        minPoints as integer, maxPoints as integer,_
        minSpeed as double, maxSpeed as double _
    )

    dim shape as shape_type
    dim pointsCounter as integer

    shape.radius = int(rnd * (maxRadius - minRadius)) + minRadius
    pointsCounter = int(rnd * (maxPoints - minPoints)) + minPoints
    shape.firstPointIndex = ubound(points) + 1
    shape.lastPointIndex = ubound(points) + pointsCounter
    shape.rgbaColor = _rgb32(int(rnd*127)+128,int(rnd*127)+128,int(rnd*127)+128)
    shape.x = int(rnd*CANVAS_WIDTH)
    shape.y = int(rnd*CANVAS_HEIGHT)
    shape.speed = rnd*(maxSpeed - minSpeed) + minSpeed
    shape.direction = rnd*TAU
    shape.rotation = (rnd*TAU - TAU/2)/100
    shape.orientation = 0
    shape.imageHandle = _newimage(2*MAX_SHAPE_RADIUS,2*MAX_SHAPE_RADIUS,32)
    shape.maskHandle = _newimage(2*MAX_SHAPE_RADIUS\PIXELS_PRECISION,2*MAX_SHAPE_RADIUS\PIXELS_PRECISION,32)

    redim _preserve points(1 to shape.lastPointIndex) as point_type

    delta! = TAU / pointsCounter
    angle! = 0

    for i% = shape.firstPointIndex to shape.lastPointIndex
        radius% = int(rnd * (shape.radius - minRadius)) + minRadius
        points(i%).x = int(cos(angle!) * radius%)
        points(i%).y = int(sin(angle!) * radius%)
        angle! = angle! + delta!
    next i%

    convertShapeToDrawString shape, points()

    redim _preserve shapes(1 to ubound(shapes) + 1) as shape_type

    shape.index = ubound(shapes)

    shapes(ubound(shapes)) = shape

end sub

' compute a shape movement and rotation

sub moveShape ( shape as shape_type, points() as point_type )
    shape.x = shape.x + cos(shape.direction) * shape.speed
    shape.y = shape.y + sin(shape.direction) * shape.speed
    if shape.x<0 then
        shape.x = CANVAS_WIDTH - shape.x
    elseif shape.x>CANVAS_WIDTH then
        shape.x = - (shape.x - CANVAS_WIDTH)
    end if
    if shape.y<0 then
        shape.y = CANVAS_HEIGHT - shape.y
    elseif shape.y > CANVAS_HEIGHT then
        shape.y = - (shape.y - CANVAS_HEIGHT)
    end if
    shape.orientation = shape.orientation + shape.rotation
    if shape.orientation < 0 then
        shape.orientation = TAU + shape.orientation
    elseif shape.orientation > TAU then
        shape.orientation = shape.orientation - TAU
    end if
    for i%=shape.firstPointIndex to shape.lastPointIndex
        x! = points(i%).x
        y! = points(i%).y
        rotation x!, y!, shape.rotation
        points(i%).x = x!
        points(i%).y = y!
    next i%
    convertShapeToDrawString shape, points()
    ' shape change the mask is not valid
    shape.maskComputed = 0
end sub

' helper to angular rotation

sub rotation (x!, y!, a!)
    xr! = x! * cos(a!) - y! * sin(a!)
    yr! = x! * sin(a!) + y! * cos(a!)
    x! = xr!
    y! = yr!
end sub

' convert a points array to DRAW string sequence

sub convertShapeToDrawString (shape as shape_type, points() as point_type)
    shape.drawString = ""
    n% = 0
    for i% = shape.firstPointIndex to shape.lastPointIndex
        x% = int(points(i%).x + shape.x)
        y% = int(points(i%).y + shape.y)
        if x%<0 or y%<0 then n% = -1
        rx$ = _tostr$(x%)
        ry$ = _tostr$(y%)
        shape.drawString = shape.drawString + "M " + rx$ + ", " + ry$
    next i%
    x% = int(points(shape.firstPointIndex).x + shape.x)
    y% = int(points(shape.firstPointIndex).y + shape.y)
    rx$ = _tostr$(x%)
    ry$ = _tostr$(y%)
    shape.drawString = shape.drawString + "M " + rx$ + ", " + ry$
    if n% then shape.drawString = ""
end sub

' draw shape
' CAUTION : the DRAW mode is not efficient, prefer then LINE mode

sub drawShape ( shape as shape_type, points() as point_type )

    h& = _dest
    _dest shape.imageHandle
    cls , &H00FFFFFF

    dim rgbaColor as _unsigned long
    rgbaColor = shape.rgbaColor
    if shape.collid then rgbaColor = _rgba(255,255,0,255)

    select case DRAW_MODE
        case 0
            if shape.drawString<>"" then
                draw "C"+str$(shape.rgbaColor)
                draw "B"+shape.drawString
                paint (MAX_SHAPE_RADIUS,MAX_SHAPE_RADIUS),rgbaColor and &H7FFFFFFF ,rgbaColor
            end if
        case 1
            pset (points(shape.firstPointIndex).x + MAX_SHAPE_RADIUS, points(shape.firstPointIndex).y + MAX_SHAPE_RADIUS), rgbaColor
            for i%=shape.firstPointIndex to shape.lastPointIndex
                line - (points(i%).x + MAX_SHAPE_RADIUS, points(i%).y + MAX_SHAPE_RADIUS), rgbaColor
            next i%
            line - (points(shape.firstPointIndex).x + MAX_SHAPE_RADIUS, points(shape.firstPointIndex).y + MAX_SHAPE_RADIUS), rgbaColor
            paint (MAX_SHAPE_RADIUS,MAX_SHAPE_RADIUS),rgbaColor and &H7FFFFFFF ,rgbaColor
    end select

    _putimage (shape.x - MAX_SHAPE_RADIUS, shape.y - MAX_SHAPE_RADIUS), shape.imageHandle, h&
    _dest h&
end sub

' generates a semi-transparent mask image

sub computeMask ( shape as shape_type )
    h& = _dest
    _source shape.imageHandle
    _dest shape.maskHandle
    cls , &H00000000
    m% = 2 * MAX_SHAPE_RADIUS - 1
    for x% = 0 to m% step PIXELS_PRECISION
        for y% = 0 to m% step PIXELS_PRECISION
            if point(x%,y%) <> &H00FFFFFF then
                pset(x%\PIXELS_PRECISION,y%\PIXELS_PRECISION), &H7FFFFFFF
            end if
        next y%
    next x%
    ' the mask is now valid
    shape.maskComputed = -1
    _dest h&
end sub

' calculation of the areas affected by the shape

sub evaluateShapeSector ( shape as shape_type, areas() as area_type, targets() as integer )
    x% = shape.x
    y% = shape.y

    ' shape outside the canvas, so not in an area

    if x% < 0 or x% > CANVAS_WIDTH or y% < 0 or y% > CANVAS_HEIGHT then
        shape.area = -1
        exit sub
    end if

    ' computes area

    shape.areaX = _round(x% / MAX_SHAPE_RADIUS)
    shape.areaY = _round(y% / MAX_SHAPE_RADIUS)
    shape.area = (shape.areaY - 1) * AREAS_WIDTH + shape.areaX

    ' search other potential areas around

    if shape.area>=0 then

        redim targets(-1) as integer

        x0! = shape.x - shape.radius
        y0! = shape.y - shape.radius
        sx0% = _round(x0! / MAX_SHAPE_RADIUS)
        sy0% = _round(y0! / MAX_SHAPE_RADIUS)

        x2! = shape.x + shape.radius
        y2! = shape.y + shape.radius
        sx2% = _round(x2! / MAX_SHAPE_RADIUS)
        sy2% = _round(y2! / MAX_SHAPE_RADIUS)

        s0% = (sy0% - 1) * AREAS_WIDTH + sx0%
        s1% = (sy0% - 1) * AREAS_WIDTH + shape.areaX
        s2% = (sy0% - 1) * AREAS_WIDTH + sx2%
        s3% = (shape.areaY - 1) * AREAS_WIDTH + sx0%
        s4% = (shape.areaY - 1) * AREAS_WIDTH + shape.areaX
        s5% = (shape.areaY - 1) * AREAS_WIDTH + sx2%
        s6% = (sy2% - 1) * AREAS_WIDTH + sx0%
        s7% = (sy2% - 1) * AREAS_WIDTH + shape.areaX
        s8% = (sy2% - 1) * AREAS_WIDTH + sx2%

        addUniqueInteger targets(), s0%
        addUniqueInteger targets(), s1%
        addUniqueInteger targets(), s2%
        addUniqueInteger targets(), s3%
        addUniqueInteger targets(), s4%
        addUniqueInteger targets(), s5%
        addUniqueInteger targets(), s6%
        addUniqueInteger targets(), s7%
        addUniqueInteger targets(), s8%

        ' remembers the presence of the shape in the area in the character array

        for i% = 0 to ubound(targets)
            if targets(i%)>=0 and targets(i%)<=AREAS_COUNT then mid$(areas(targets(i%)).shapeLocator,shape.index,1) = "*"
        next i%

    end if
end sub

' helper to keep only unique values

sub addUniqueInteger ( t() as integer, v as integer )
    for i%=lbound(t) to ubound(t)
        if t(i%)=v then exit sub
    next i%
    redim _preserve t(ubound(t)+1) as integer
    t(ubound(t)) = v
end sub

' detects collisions between shapes

function collision% ( shapes() as shape_type, area as area_type )
    res% = 0
    redim obj(-1) as integer

    ' collect unique shape indices from the area string (positions of '*')

    p0% = instr(area.shapeLocator,"*")
    do until p0% = 0
        s0% = p0%
        found% = 0
        for i%=lbound(obj) to ubound(obj)
            if obj(i%) = s0% then
                found% = -1
                exit for
            end if
        next i%
        if not found% then
            redim _preserve obj(ubound(obj)+1) as integer
            obj(ubound(obj)) = s0%
        end if
        p0% = instr(p0%+1,area.shapeLocator,"*")
    loop

    ' pairwise collision test (circle-circle) then test pixel-perfect
    ' when the distance is short enough to have a possible collision

    for i%=lbound(obj) to ubound(obj)
        for j%=i%+1 to ubound(obj)
            if shapes(obj(i%)).collid and shapes(obj(j%)).collid then exit for
            h% = _hypot(shapes(obj(i%)).x-shapes(obj(j%)).x,shapes(obj(i%)).y-shapes(obj(j%)).y)
            r% = shapes(obj(i%)).radius + shapes(obj(j%)).radius
            if h% <= r% then
                res% = fusion%(shapes(obj(i%)),shapes(obj(j%)),area)
                if res% then
                    shapes(obj(i%)).collid = -1
                    shapes(obj(j%)).collid = -1
                end if
            end if
        next j%
    next i%

    collision% = res%
end function

' detects the overlapping of shapes
'
' each shape mask is semi-transparent ;
' copies each shape mask to its actual position in the area ;
' if we find at least one point that is no longer transparent
' then there is overlap and therefore collision detection

function fusion% ( shapeA as shape_type, shapeB as shape_type, area as area_type )
    if not shapeA.maskComputed then
        computeMask shapeA
    end if
    if not shapeB.maskComputed then
        computeMask shapeB
    end if
    res% = 0
    m% = 2 * MAX_SHAPE_RADIUS \ PIXELS_PRECISION - 1
    buffer& = _newimage(WIDTH_OF_AREA\PIXELS_PRECISION,HEIGHT_OF_AREA\PIXELS_PRECISION,32)
    _putimage ( (shapeA.x - area.center.x)\PIXELS_PRECISION, (shapeA.y - area.center.y)\PIXELS_PRECISION ), shapeA.maskHandle, buffer&
    _putimage ( (shapeB.x - area.center.x)\PIXELS_PRECISION, (shapeB.y - area.center.y)\PIXELS_PRECISION ), shapeB.maskHandle, buffer&
    _source buffer&
    for x% = 0 to m%
        for y% = 0 to m%
            if _alpha32(point(x%,y%)) > &H7F then
                res% = -1
                exit for
            end if
        next y%
    next x%
    _freeimage buffer&
    fusion% = res%
end function



RE: error doing image collision detection with _MemGet - Unseen Machine - 09-15-2025

Sweet, as you say there is room for improvement but for someone who was asking only last week about it to have then dropped this...DAMN! Nice one bro! 

Questions, did you go the radius route to avoid calculating corners, rotation points, rectangle collision areas, etc? Also, what if a sprite is vastly longer than it is wide? How do you deal with all the blank space?

Unseen


RE: error doing image collision detection with _MemGet - Herve - 09-15-2025

Thank you Smile

The initial idea was that each sprite can fully fit within a circle with a radius of MAX_SHAPE_RADIUS; so if the distance between the centers of two sprites is less than twice the maximum radius, it is very likely that they are touching.

The size of the detection areas is also twice the maximum radius, which seemed optimal to me; if they were smaller, then many more areas would need to be explored, and if they were larger, it would take more time to check the pixel overlay.

For sprites that are wider than they are long, this should work, but I suppose the limit is six times the maximum radius for the length, because I set a limit to test only nine areas for each sprite (the one where its center is located + the eight surrounding areas).

Hey, I might just test that!


RE: error doing image collision detection with _MemGet - Herve - 09-15-2025

(09-15-2025, 06:12 PM)Herve Wrote: For sprites that are wider than they are long, this should work, but I suppose the limit is six times the maximum radius for the length, because I set a limit to test only nine areas for each sprite (the one where its center is located + the eight surrounding areas).

Hey, I might just test that!
Well, no, it doesn't work with sprites larger than 2 * MAX_SHAPE_RADIUS!!! And if you increase MAX_SHAPE_RADIUS too much, performance drops very quickly. But hey, it was mostly for fun; pixel-perfect is a luxury that isn't necessary in most applications.


RE: error doing image collision detection with _MemGet - Unseen Machine - 09-15-2025

(09-15-2025, 07:06 PM)Herve Wrote:
(09-15-2025, 06:12 PM)Herve Wrote: For sprites that are wider than they are long, this should work, but I suppose the limit is six times the maximum radius for the length, because I set a limit to test only nine areas for each sprite (the one where its center is located + the eight surrounding areas).

Hey, I might just test that!
Well, no, it doesn't work with sprites larger than 2 * MAX_SHAPE_RADIUS!!! And if you increase MAX_SHAPE_RADIUS too much, performance drops very quickly. But hey, it was mostly for fun; pixel-perfect is a luxury that isn't necessary in most applications.
Sorry for bursting your bubble bro...if youre still into collisions see if you can come up with an idea for this....im thinking a raycast will work but as Steve says, sometimes i overthink things so any ideas would be great.

John
[Image: Accurate-Collision.png]


RE: error doing image collision detection with _MemGet - SMcNeill - 09-15-2025

If current_x > target_x2 and old_x < _target_x1 then collision as bullet traveled through target.   

Or would that not work along that line?


RE: error doing image collision detection with _MemGet - madscijr - 09-15-2025

No thoughts on memory compare, or ways to simplify it? 

Another thought I had, was to encode each sprite as an array of integers - for example an 8x8 sprite would be 8 bytes, where each byte holds the on/off bits of a single row. A 64x64 sprite would be a 64 element array of Int64. To test if any pixels of one collide with the other, use a binary AND. This will get more complicated but for a first example assume the 2nd sprite is also 64x64 and its X coordinate is at an even multiple of 64 pixels. You can compare the elements in sprite #2 with the elements of sprite #1 that overlap its Y position. Then do a logical AND of the aligned Int64 values - if the result >0 then we know at least one pixel overlapped. But what if sprite2 X position is not an even multiple of 64? What if Sprite2 X is Sprite1 X + 5? Would it work to take the Int64 value for the row to compare, and bit shift it left by 5? I haven't worked out how that could be done. Also, what if we want to check collision between two sprites of different sizes, e.g., 32x32 & 64x64, or 8x8 and 64x64? Do we need to instead encode the 64x64 as 8 bytes per row (times 64 rows)? Then we need to track which block the 8x8 would coincide with and bit shift that? This could get complicated or maybe not, maybe someone who understands bit operations can see a way to do this easily. I am not sure. 

Another method to consider is a simple 2D array of booleans holding TRUE for each location with a pixel, so instead of looping through each image's pixels in the overlap region using POINT(x,y), we're now only comparing TRUE/FALSE. Would that improve efficiency? 

(I had thought about maybe a 2nd array holding ONLY the x,y of the points with pixels, to use to compare FROM, as a way to avoid having to loop over empty pixels, but that could get HUGE for a 64x64 sprite that has more pixels than empty spaces... Probably not worth it.)

Thoughts on doing the memory compare or leveraging the binary AND operation for finding overlap?


RE: error doing image collision detection with _MemGet - Herve - 09-18-2025

That really made me want to dig into collisions.

1st challenge: handle collisions for objects that are longer than they are wide.
2nd challenge: detect collisions before they happen using ray tracing.

But first, a short debrief of how my proximity-and-mask-overlap collision detection code works.

- All objects (shapes) are built to fit inside a circle with a predefined maximum radius.
- Space is divided into square areas whose side length equals the maximum radius.
- At any given moment an object will overlap at least 1 area and at most 9 areas.
- If an area is overlapped by several objects, the distance between the centers of those objects is computed pairwise.
- If the calculated distance is less than or equal to 2 times the maximum radius, the masks of the two objects are copied into an image buffer the size of the area, placed according to their actual positions relative to that area.
- Since the masks are semi‑transparent, overlapping pixels become opaque.
- If the resulting image buffer contains at least one opaque pixel, a collision is confirmed for both objects.
- Pairs of objects already detected as colliding do not need to be detected again.

I made two improvements (code updated in post #21):
1. Because an object’s mask must be recalculated every cycle due to the object rotating, the mask is now recomputed for an object only when a collision is suspected.
2. For better performance, detection precision can now be reduced by changing mask size with the PIXELS_PRECISION parameter. A mask’s area is divided by PIXELS_PRECISION squared.


RE: error doing image collision detection with _MemGet - Herve - 09-23-2025

Hi,

Here’s the new program that performs pixel‑perfect detection without using mask techniques; it’s based on polygon intersection calculations.

It handles shapes that are longer than they are wide well. Performance is pretty good (60 FPS for 128 objects on a Raspberry Pi 500). 

There are definitely optimizations to be found. And also need more comments to explain how it works. This could become a library — what do you think?

Code: (Select All)

type point_type
    x as double
    y as double
end type

type shape_type
    index as integer
    verticescounter as integer
    firstpointindex as integer
    lastpointindex as integer
    center as point_type
    min as point_type
    max as point_type
    coord as point_type
    rgbacolor as _unsigned long
    speed as double
    direction as double
    rotation as double
    orientation as double
    imageHandle as long
    touchdownHandle as long
    width as long
    height as long
    radius as long
    area as integer                ' area where the center of the shape is located
    areaX as integer                '
    areaY as integer                '
    collid as integer
end type

type context_type
    step_mode_activated as integer              ' -1 = step by step / 0 = don't
    show_grid_activated as integer              ' -1 = show grid of areas / 0 = dont't
    show_detection_activated as integer        ' -1 = show proximity detections areas / 0 = don't
    show_collisions_activated as integer        ' -1 = show areas where collisions are potentials / 0 = dont't
    show_radius_activated as integer
    show_index_activated as integer
    fullscreen_activated as integer
    show_help_activated as integer
    textHandle as long
    extra_shape_activated as integer
end type

const STEP_MODE_KEY = "S"          ' toggle stepmode
const SHOW_DETECTION_KEY = "P"      ' toggle drawing proximity detection areas
const SHOW_GRID_KEY = "G"          ' toggle drawing grid to show area delimitations
const SHOW_COLLISIONS_KEY = "C"    ' toogle drawing areas with potential collisions
const SHOW_RADIUS_KEY = "R"
const SHOW_INDEX_KEY = "I"
const SWAP_FULLSCREEN = "F"
const HIDE_HELP = "H"
const EXTRA_SHAPE_KEY = "E"
const UP_KEY = chr$(0) + chr$(72)
const RIGHT_KEY = chr$(0) + chr$(77)
const DOWN_KEY = chr$(0) + chr$(80)
const LEFT_KEY = chr$(0) + chr$(75)

dim context as context_type

const SHAPES_COUNTER = 64
const MIN_SHAPE_VERTICES = 3
const MAX_SHAPE_VERTICES = 12
const MIN_SHAPE_WIDTH = 30
const MAX_SHAPE_WIDTH = 60
const MIN_SHAPE_HEIGHT = 10
const MAX_SHAPE_HEIGHT = 20
const MIN_SHAPE_SPEED = 0.5        ' minimum speed of a shape
const MAX_SHAPE_SPEED = 1.1        ' maximum speed of a shape

const EXPECTED_FPS = 60            ' expected frames per second

const VIEWPORT_WIDTH = 640        ' viewport width
const VIEWPORT_HEIGHT = 480        ' viewport height

const FULLSCREEN = 0                ' -1 = fullscreen / 0 = don't

dim shared tau as single            ' 2 * pi
tau = 8 * atn(1)

CONST EPS = 0 '1E-9

randomize timer

' areas initialization

type area_type
    shapeLocator as string * SHAPES_COUNTER  ' Character array to memorize shapes in area
    column as integer              ' area colum
    row as integer                  ' area row
    x as integer                    ' top-left position of the area on the screen
    y as integer                   
    center as point_type            ' center position of the area onthe screen
end type

dim shared AREA_SIDE_WIDTH as integer
dim shared AREAS_ROWS_COUNT as integer
dim shared AREAS_COLUMNS_COUNT as integer
dim shared AREAS_COUNT as integer

AREA_SIDE_WIDTH = _max(MAX_SHAPE_WIDTH,MAX_SHAPE_HEIGHT)

' definition of the page dimension in which the shapes are drawn

const X_DECAY = 120 '_ceil(2.5 * MAX_SHAPE_WIDTH) ' offset between canvas and viewport
const Y_DECAY = 120 'X_DECAY
const CANVAS_WIDTH = VIEWPORT_WIDTH + 2 * X_DECAY  ' canvas size
const CANVAS_HEIGHT = VIEWPORT_HEIGHT + 2 * Y_DECAY

AREAS_COLUMNS_COUNT = _ceil(CANVAS_WIDTH / AREA_SIDE_WIDTH)
AREAS_ROWS_COUNT = _ceil(CANVAS_HEIGHT / AREA_SIDE_WIDTH)
AREAS_COUNT = AREAS_COLUMNS_COUNT * AREAS_ROWS_COUNT

areas_max = AREAS_COUNT
redim areas(0 to areas_max-1) as area_type
redim infos(-1) as string
dim i as integer

$console
_dest _console

for i=lbound(areas) to ubound(areas)
    areas(i).column = i mod AREAS_COLUMNS_COUNT
    areas(i).row = i \ AREAS_COLUMNS_COUNT
    areas(i).x = areas(i).column * area_side_width
    areas(i).y = areas(i).row * area_side_width
    areas(i).center.x = areas(i).x + area_side_width / 2
    areas(i).center.y = areas(i).y + area_side_width / 2
next i

' shapes initialization

redim shapes(-1) as shape_type
redim points(0) as point_type

for i=1 to SHAPES_COUNTER
    generateshape shapes(), points()
next i

' prepare screen rendering


viewport& = _newimage(VIEWPORT_WIDTH,VIEWPORT_HEIGHT,32)
_printmode _keepbackground, viewport&

canvas& = _newimage(CANVAS_WIDTH,CANVAS_HEIGHT,32)
textDest& = _newimage(CANVAS_WIDTH,CANVAS_HEIGHT,32)
context.textHandle = textDest&

screen viewport&
if FULLSCREEN then _fullscreen , _smooth

' timer to compute frames per second

t = timer(.001)
fps% = 0
frames% = 0

color &HFFFFFFFF, &H00000000

' main loop

do
    _limit EXPECTED_FPS

    for i=lbound(shapes) to ubound(shapes)
        moveShape shapes(i), points()
        shapes(i).collid = 0
    next i

    ' reset areas

    for i = lbound(areas) to ubound(areas)
        areas(i).shapeLocator = ""
    next i

    ' compute shapes' areas
    for i=lbound(shapes) to ubound(shapes)
        redim targets(-1) as integer
        evaluateShapeSector shapes(i), areas(), targets()
    next i

    _dest canvas&
    cls

    ' draw grid

    if context.show_grid_activated then
        for x% = 0 to AREAS_COLUMNS_COUNT
            xx%= x% * AREA_SIDE_WIDTH
            line (xx%,0)-(xx%,CANVAS_HEIGHT),_rgba(255,255,255,63)
        next x%
        for y% = 0 to AREAS_ROWS_COUNT
            yy% = y% * AREA_SIDE_WIDTH
            line (0,yy%)-(CANVAS_WIDTH,yy%),_rgba(255,255,255,63)
        next y%
    end if

    areasNotEmpty% = 0
    areasWithPossibleCollisions% = 0
    areasWithCollisions% = 0
    shapesInCollision% = 0

    for i = lbound(areas) to ubound(areas)
        showme% = 0
        if instr(areas(i).shapeLocator,"*") > 0 then
            showme% = 1
            areasNotEmpty% = areasNotEmpty% + 1
        end if
        if instr(areas(i).shapeLocator,"*") <> _instrrev(areas(i).shapeLocator,"*") then
            showme% = 2
            areasWithPossibleCollisions% = areasWithPossibleCollisions% + 1
            status% = collision(shapes(), points(), areas(i))
            if status% then areasWithCollisions% = areasWithCollisions% + 1
        end if
        if showme% > 0 then
            if context.show_detection_activated or context.show_collisions_activated then
                if context.show_detection_activated and not context.show_collisions_activated then
                    showme% = 1
                end if
                showcolor! = _iif(showme%=1,&H7FFFFF00,&HFFFF0000)
                if context.show_detection_activated or (context.show_collisions_activated and showme% = 2) then
                    pset (areas(i).x,areas(i).y)
                    line -step (area_side_width,0), showcolor!
                    line -step (0,area_side_width), showcolor!
                    line -step (-area_side_width,0), showcolor!
                    line -step (0,-area_side_width), showcolor!
                    if context.show_index_activated then
                        color &HFFFFFFFF '', &H00000000
                        _printstring (x%,y%), _tostr$(i), context.textHandle
                    end if
                end if
            end if
        end if
    next i

    _dest textDest&
    cls , _rgba(0,0,0,0)

    for i=lbound(shapes) to ubound(shapes)
        if shapes(i).collid then shapesInCollision% = shapesInCollision% + 1
        drawShape context,shapes(i), points(), 0
    next i

    ' fps

    frames% = frames% + 1
    if timer(.001)>=t+1 then
        fps% = frames%
        frames% = 0
        t = timer(.001)
    end if

    ' display result

    _dest textDest&
    color &HFFFFFFFF, &HE0A0A0FF
    if context.show_help_activated then
        resetInfo infos()
        addinfo infos(), "                                                                fps: "+right$("000"+_tostr$(fps%),3)
        addInfo infos(), "  *** Pixel Perfect Collision Detection Demonstrator ***"
        addinfo infos(), ""
        addInfo infos(), "SHAPES"
        addinfo infos(), "======"
        addInfo infos(), " - total: "+right$("000"+_tostr$(SHAPES_COUNTER),3)
        addInfo infos(), " - in collision: "+right$("000"+_tostr$(shapesInCollision%),3)
        addinfo infos(), ""
        addInfo infos(), "AREAS"
        addinfo infos(), "======"
        addinfo infos(), " - not empty: "+right$("000"+_tostr$(areasNotEmpty%),3)
        addinfo infos(), " - with possible collisions: "+right$("000"+_tostr$(areasWithPossibleCollisions%),3)
        addinfo infos(), " - with detected collisions: "+right$("000"+_tostr$(areasWithCollisions%),3)
        addinfo infos(), ""
        addinfo infos(), "ACTION KEYS"
        addinfo infos(), "==========="
        addinfo infos(), " - H) hide/show this screen (hiding it will increase the performance)"
        addInfo infos(), " - S) swicth step mode (then SPACE to move forward step by step): "+_iif(context.step_mode_activated,"ON","OFF")
        addinfo infos(), " - G) show areas grid (number,coordinates,1st overlaid shape): "+_iif(context.show_grid_activated,"ON","OFF")
        addinfo infos(), " - P) show proximity detection areas: "+_iif(context.show_detection_activated,"ON","OFF")
        addInfo infos(), " - C) show probable collision areas: "+_iif(context.show_collisions_activated,"ON","OFF")
        addinfo infos(), " - R) show shapes radius: "+_iif(context.show_radius_activated,"ON","OFF")
        addinfo infos(), " - I) show shapes index (+ coordinates + area number): "+_iif(context.show_index_activated,"ON","OFF")
        addInfo infos(), " - E) swicth control of a shape (use arrows key to move): "+_iif(context.extra_shape_activated,"ON","OFF")
        addinfo infos(), " - F) swicth fullscreen"
        addinfo infos(), " - ESC) stop and quit this program"
        addinfo infos(), ""
        showInfo infos()
    else
        color &HE0FFFFFF, &H00000000
        _printstring (1 + X_DECAY,1+Y_DECAY), "fps:"+right$("000"+_tostr$(fps%),3)
        _printstring (1 + X_DECAY,1+VIEWPORT_HEIGHT+Y_DECAY-16), "H)elp S)tep G)rid P)roximity C)ollision R)adius I)ndex E)xtra F)ullscr ESC)quit"
    end if

    if context.show_grid_activated then
        color &HA0FFFFFF, &H00000000
        for i = lbound(areas) to ubound(areas)
            _printstring (areas(i).x,areas(i).y), _tostr$(i)
            _printstring (areas(i).x,areas(i).y+12), _tostr$(areas(i).x)
            _printstring (areas(i).x,areas(i).y+24), _tostr$(areas(i).y)
            _printstring (areas(i).x,areas(i).y+36), _tostr$(instr(areas(i).shapeLocator,"*")-1)
        next i
    end if
   
    _putimage (0,0), textDest&, canvas&
    _putimage (- X_DECAY,- Y_DECAY), canvas&, viewport&

    ' keyboard interaction

    do
        key$ = inkey$
        if key$ = chr$(27) then system
        select case ucase$(key$)
            case STEP_MODE_KEY
                switch context.step_mode_activated
            case SHOW_DETECTION_KEY
                switch context.show_detection_activated
            case SHOW_GRID_KEY
                switch context.show_grid_activated
            case SHOW_COLLISIONS_KEY
                switch context.show_collisions_activated
            case SHOW_RADIUS_KEY
                switch context.show_radius_activated
            case SHOW_INDEX_KEY
                switch context.show_index_activated
            case HIDE_HELP
                switch context.show_help_activated
            case SWAP_FULLSCREEN
                switch context.fullscreen_activated
                if context.fullscreen_activated then
                    _fullscreen _squarepixels, _smooth
                else
                    _fullscreen _off
                end if
            case EXTRA_SHAPE_KEY
                switch context.extra_shape_activated
                if context.extra_shape_activated then
                    extraShape shapes(lbound(shapes)), points()
                else
                    disextraShape shapes(lbound(shapes)), points()
                end if
            case UP_KEY
                if context.extra_shape_activated then shapes(lbound(shapes)).coord.y = shapes(lbound(shapes)).coord.y - 5
            case DOWN_KEY
                if context.extra_shape_activated then shapes(lbound(shapes)).coord.y = shapes(lbound(shapes)).coord.y + 5
            case RIGHT_KEY
                if context.extra_shape_activated then shapes(lbound(shapes)).coord.x = shapes(lbound(shapes)).coord.x + 5
            case LEFT_KEY
                if context.extra_shape_activated then shapes(lbound(shapes)).coord.x = shapes(lbound(shapes)).coord.x - 5
        end select
        if context.step_mode_activated then
            if key$ <> "" then exit do
        else
            exit do
        end if
    loop

loop

sub switch (boolean as integer)
    boolean = not boolean
end sub

' draw a shape

sub drawShape (context as context_type, shape as shape_type, points() as point_type, mask as integer)

        x0 = shape.coord.x '- X_DECAY '- shape.width\2
        y0 = shape.coord.y '+ Y_DECAY '- shape.height\2

        if shape.collid then
            RotoZoom3 x0, y0, shape.touchdownHandle, 1, 1, shape.orientation
            circle (x0,y0), shape.radius/4, &HFFFF0000
        else
            RotoZoom3 x0, y0, shape.imageHandle, 1, 1, shape.orientation
        end if

        if context.show_radius_activated then
          circle (x0,y0), shape.radius, shape.rgbaColor
        end if

        if context.extra_shape_activated and shape.index = 0  then
          circle (x0,y0), shape.radius/4, &HFFFF0000
          paint (x0,y0), &HFFFF0000, &HFFFF0000
        end if

        if context.show_index_activated then
            color &HFFFFFFFF, &H00000000
            '_printstring (shape.center.x - 6,shape.center.y - 6), _tostr$(shape.index), shape.imageHandle
            _printstring (x0 - 6,y0 - 6), _tostr$(shape.index)+"("+_tostr$(int(shape.coord.x))+","+_tostr$(int(shape.coord.y))+")"+"["+_tostr$(shape.area)+"]", context.textHandle
        end if

end sub

' prepare the sprite image

sub prepareSpriteImage (shape as shape_type, points() as point_type)
    h& = _dest
    _dest shape.imageHandle
    cls , &H00FFFFFF

    borderColor& = shape.rgbacolor
    fillColor& = shape.rgbacolor AND &H80FFFFFF

    x0 = points(shape.firstpointindex).x + shape.center.x
    y0 = points(shape.firstpointindex).y + shape.center.y

    pset (x0,y0), borderColor&

    for p = shape.firstpointindex to shape.lastpointindex
        x1 = points(p).x + shape.center.x
        y1 = points(p).y + shape.center.y

        line -(x1, y1), borderColor&
    next p

    line -(x0, y0), borderColor&

    paint (shape.center.x,shape.center.y), borderColor&, borderColor&

    pset (shape.center.x,shape.center.y), borderColor&

    _dest h&
end sub

' prepare the touchdown image

sub prepareTouchdownImage (shape as shape_type, points() as point_type)
    colorizeImage shape.imageHandle, shape.touchdownHandle, &HA0FFFF00
end sub

sub colorizeImage (imageSource as long, imageDest as long, colorDest as _unsigned long)
    h& = _dest
    _dest imageDest
    cls , &H00FFFFFF
    _source imageSource
    for x% = 0 to _width(imageSource) - 1
        for y% = 0 to _height(imageSource) - 1
            if _alpha32(point(x%,y%))>0 then pset (x%,y%), colorDest
        next y%
    next x%
    _dest h&
end sub

' generate a shape

sub generateshape (shapes() as shape_type, points() as point_type)

    ' nombre de sommets
    n = int(rnd * (MAX_SHAPE_VERTICES - MIN_SHAPE_VERTICES)) + MIN_SHAPE_VERTICES + 1
    ' largeur
    w = int(rnd * (MAX_SHAPE_WIDTH - MIN_SHAPE_WIDTH)) + MIN_SHAPE_WIDTH + 1
    ' hauteur
    h = int(rnd * (MAX_SHAPE_HEIGHT - MIN_SHAPE_HEIGHT)) + MIN_SHAPE_HEIGHT + 1
    ' type (convexe / concave) t$
    t$ = _iif(rnd > 0.5, "convexe", "concave")
    ' facteur de concavité (0..1, pour concave) : ", cf
    cf = rnd

    redim polygon(n - 1) as point_type
    attempts = 0
    generatepolygon n, w, h, t$, cf, polygon()

    dim shape as shape_type
    shape.verticescounter = n
    shape.firstpointindex = ubound(points)+1
    shape.lastpointindex = shape.firstpointindex + n - 1

    redim _preserve points(1 to shape.lastpointindex) as point_type

    dim as point_type min, max
    min.x = 1.0E308 : max.x = - min.x
    min.y = 1.0E308 : max.y = - min.y

    for i = lbound(polygon) to ubound(polygon)
        min.x = _iif(polygon(i).x < min.x,polygon(i).x,min.x)
        min.y = _iif(polygon(i).y < min.y,polygon(i).y,min.y)
        max.x = _iif(polygon(i).x > max.x,polygon(i).x,max.x)
        max.y = _iif(polygon(i).y > max.y,polygon(i).y,max.y)
        points(shape.firstpointindex + i).x = polygon(i).x
        points(shape.firstpointindex + i).y = polygon(i).y
    next i

    shape.min = min
    shape.max = max
    shape.width = max.x - min.x + 1
    shape.height = max.y - min.y + 1
    shape.center.x = shape.width / 2
    shape.center.y = shape.height / 2
    shape.radius = _max(shape.width,shape.height)/2
    shape.imageHandle = _newimage(shape.width,shape.height,32)
    shape.touchdownHandle = _newimage(shape.width,shape.height,32)

    for i = lbound(polygon) to ubound(polygon)
        points(shape.firstpointindex + i).x = int(points(shape.firstpointindex + i).x - shape.center.x)
        points(shape.firstpointindex + i).y = int(points(shape.firstpointindex + i).y - shape.center.y)
    next i

    shape.speed = rnd*(MAX_SHAPE_SPEED - MIN_SHAPE_SPEED) + MIN_SHAPE_SPEED
    shape.direction = rnd*TAU
    shape.rotation = (rnd*TAU - TAU/2)/100
    shape.orientation = 0

    shape.rgbaColor = _rgb32(int(rnd*127)+128,int(rnd*127)+128,int(rnd*127)+128)
    shape.coord.x = int(rnd * CANVAS_WIDTH)
    shape.coord.y = int(rnd * CANVAS_HEIGHT)
    shape.index = ubound(shapes)+1

    redim _preserve shapes(lbound(shapes) to shape.index) as shape_type
    shapes(shape.index) = shape

    prepareSpriteImage shape, points()
    prepareTouchdownImage shape, points()

end sub

function randdouble(min#, max#) 'as double
    randdouble = min# + rnd * (max# - min#)
end function

' test for segment intersection (excluding shared endpoints of segments)

function segintersect(ax#, ay#, bx#, by#, cx#, cy#, dx#, dy#)
    dim s1x#, s1y#, s2x#, s2y#, s#, t#
    s1x = bx - ax: s1y = by - ay
    s2x = dx - cx: s2y = dy - cy
    denom = (-s2x * s1y + s1x * s2y)
    if abs(denom) < 1e-12 then
        segintersect = 0
        exit function
    end if
    s = (-s1y * (ax - cx) + s1x * (ay - cy)) / denom
    t = ( s2x * (ay - cy) - s2y * (ax - cx)) / denom
    if s > 0 and s < 1 and t > 0 and t < 1 then
        segintersect = 1
    else
        segintersect = 0
    end if
end function

' detect self intersection

function hasselfintersection(pts() as point_type, n as integer)
    dim i as integer, j as integer
    ' vérifier chaque paire d'arêtes non-adjacentes
    for i = 0 to n - 1
        a1 = i
        a2 = (i + 1) mod n
        for j = i + 1 to n - 1
            b1 = j
            b2 = (j + 1) mod n
            ' ignorer arêtes adjacentes et première/dernière partage
            if a1 = b1 or a1 = b2 or a2 = b1 or a2 = b2 then
                _continue
            end if
            if segintersect(pts(a1).x, pts(a1).y, pts(a2).x, pts(a2).y, _
                            pts(b1).x, pts(b1).y, pts(b2).x, pts(b2).y) then
                hasselfintersection = -1
                exit function
            end if
        next j
    next i
    hasselfintersection = 0
end function

' generate a new polygon

sub generatepolygon(n as integer, w as double, h as double, polytype$ , concavityfactor as double, pts() as point_type)

    if n < 3 then
        print "n doit être >= 3": end
    end if

    dim angles() as double
    dim radii() as double
    redim angles(n - 1)
    redim radii(n - 1)
    dim i as integer

    do
        ' Generate evenly distributed angles with jitter
        for i = 0 to n - 1
            basis = tau * i / n
            perturb = randdouble(-0.3, 0.3) * (tau / n) * 0.3
            angles(i) = basis + perturb
        next i
        ' simple sort (bubble sort acceptable for small n)
        for i = 0 to n - 2
            for j = i + 1 to n - 1
                if angles(i) > angles(j) then
                    swap angles(i), angles(j)
                    'tmp# = angles(i): angles(i) = angles(j): angles(j) = tmp#
                end if
            next j
        next i

        ' initial radii near 1 with small variation
        for i = 0 to n - 1
            radii(i) = 1 + randdouble(-0.15, 0.15)
        next i

        ' If concave: reduce a few radii
        if lcase$(polytype$) = "concave" then
            k = int(_max(1, _round(concavityfactor * n / 3)))
            if k < 1 then k = 1
            ' choose k distinct indices
            dim chosen() as integer
            redim chosen(k - 1)
            m = 0
            do while m < k
                idx = int(rnd * n)
                found = 0
                for t = 0 to m - 1
                    if chosen(t) = idx then found = 1: exit for
                next t
                if found = 0 then
                    chosen(m) = idx
                    m = m + 1
                end if
            loop
            for t = 0 to k - 1
                idx = chosen(t)
                reduction = 1 - (0.3 + 0.65 * concavityfactor * rnd)
                radii(idx) = radii(idx) * reduction
                left = (idx - 1 + n) mod n
                right = (idx + 1) mod n
                radii(left) = radii(left) * (1 + 0.05 * rnd)
                radii(right) = radii(right) * (1 + 0.05 * rnd)
            next t
        end if

        ' build points centered at (0,0) with ellipse rx, ry
        rx = w / 2
        ry = h / 2
        for i = 0 to n - 1
            pts(i).x = radii(i) * cos(angles(i)) * rx
            pts(i).y = radii(i) * sin(angles(i)) * ry
        next i

        ' normalize to fit exactly within [0,w]×[0,h]
        minx = pts(0).x: maxx = pts(0).x
        miny = pts(0).y: maxy = pts(0).y
        for i = 1 to n - 1
            if pts(i).x < minx then minx = pts(i).x
            if pts(i).x > maxx then maxx = pts(i).x
            if pts(i).y < miny then miny = pts(i).y
            if pts(i).y > maxy then maxy = pts(i).y
        next i
        curw = maxx - minx
        curh = maxy - miny
        if curw < 1e-9 then curw = 1
        if curh < 1e-9 then curh = 1
        sx = w / curw
        sy = h / curh
        for i = 0 to n - 1
            pts(i).x = (pts(i).x - minx) * sx
            pts(i).y = (pts(i).y - miny) * sy
        next i

        ' check intersections; if conflicting, regenerate (attempt limit)
        intersects = hasselfintersection(pts(), n)
        attempts = attempts + 1
        if attempts > 200 then
            exit do
        end if
    loop while intersects <> 0

end sub

' stop movement for a shape

sub extraShape ( shape as shape_type, points() as point_type )
    shape.coord.x = CANVAS_WIDTH \ 2
    shape.coord.y = CANVAS_HEIGHT \ 2
    shape.speed = 0
    shape.rotation = 0
end sub

'

sub disextraShape ( shape as shape_type, points() as point_type )
    shape.speed = rnd*(MAX_SHAPE_SPEED - MIN_SHAPE_SPEED) + MIN_SHAPE_SPEED
    shape.rotation = (rnd*TAU - TAU/2)/100
end sub

' compute a shape movement and rotation

sub moveShape ( shape as shape_type, points() as point_type )
    shape.coord.x = shape.coord.x + cos(shape.direction) * shape.speed
    shape.coord.y = shape.coord.y + sin(shape.direction) * shape.speed
    if shape.coord.x<0 then
        shape.coord.x = CANVAS_WIDTH - shape.coord.x
    elseif shape.coord.x>CANVAS_WIDTH then
        shape.coord.x = - (shape.coord.x - CANVAS_WIDTH)
    end if
    if shape.coord.y<0 then
        shape.coord.y = CANVAS_HEIGHT - shape.coord.y
    elseif shape.coord.y > CANVAS_HEIGHT then
        shape.coord.y = - (shape.coord.y - CANVAS_HEIGHT)
    end if
    shape.orientation = shape.orientation + shape.rotation
    if shape.orientation < 0 then
        shape.orientation = TAU + shape.orientation
    elseif shape.orientation > TAU then
        shape.orientation = shape.orientation - TAU
    end if
    for i%=shape.firstPointIndex to shape.lastPointIndex
        x! = points(i%).x
        y! = points(i%).y
        rotation x!, y!, shape.rotation
        points(i%).x = x!
        points(i%).y = y!
    next i%
end sub

' helper to angular rotation

sub rotation (x!, y!, a!)
    xr! = x! * cos(a!) - y! * sin(a!)
    yr! = x! * sin(a!) + y! * cos(a!)
    x! = xr!
    y! = yr!
end sub

Sub RotoZoom3 (X As Long, Y As Long, Image As Long, xScale As Single, yScale As Single, radianRotation As Single)
    ' This assumes you have set your drawing location with _DEST or default to screen.
    ' X, Y - is where you want to put the middle of the image
    ' Image - is the handle assigned with _LOADIMAGE
    ' xScale, yScale - are shrinkage < 1 or magnification > 1 on the given axis, 1 just uses image size.
    ' These are multipliers so .5 will create image .5 size on given axis and 2 for twice image size.
    ' radianRotation is the Angle in Radian units to rotate the image
    ' note: Radian units for rotation because it matches angle units of other Basic Trig functions
    '      and saves a little time converting from degree.
    '      Use the _D2R() function if you prefer to work in degree units for angles.

    Dim px(3) As Single: Dim py(3) As Single ' simple arrays for x, y to hold the 4 corners of image
    Dim W&, H&, sinr!, cosr!, i&, x2&, y2& '  variables for image manipulation
    W& = _Width(Image&): H& = _Height(Image&)
    px(0) = -W& / 2: py(0) = -H& / 2 'left top corner
    px(1) = -W& / 2: py(1) = H& / 2 ' left bottom corner
    px(2) = W& / 2: py(2) = H& / 2 '  right bottom
    px(3) = W& / 2: py(3) = -H& / 2 ' right top
    sinr! = Sin(-radianRotation): cosr! = Cos(-radianRotation) ' rotation helpers
    For i& = 0 To 3 ' calc new point locations with rotation and zoom
        x2& = xScale * (px(i&) * cosr! + sinr! * py(i&)) + X: y2& = yScale * (py(i&) * cosr! - px(i&) * sinr!) + Y
        px(i&) = x2&: py(i&) = y2&
    Next
    _MapTriangle _seamless (0, 0)-(0, H& - 1)-(W& - 1, H& - 1), Image To(px(0), py(0))-(px(1), py(1))-(px(2), py(2)),
    _MapTriangle _seamless (0, 0)-(W& - 1, 0)-(W& - 1, H& - 1), Image To(px(0), py(0))-(px(3), py(3))-(px(2), py(2)),
End Sub

' orient (cross product)

function orient (a as point_type, b as point_type, c as point_type)
    orient = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
end function

' on segment: p colinear and within bbox of [a,b]
' return -1 if p in the bbox
' EPS = small epsilon tolerance for comparaison

function onsegment (a as point_type, b as point_type, p as point_type)
    dim minx as double, maxx as double, miny as double, maxy as double
    if a.x < b.x then
        minx = a.x: maxx = b.x
    else
        minx = b.x: maxx = a.x
    end if
    if a.y < b.y then
        miny = a.y: maxy = b.y
    else
        miny = b.y: maxy = a.y
    end if

    if (p.x + EPS >= minx) and (p.x - EPS <= maxx) and (p.y + EPS >= miny) and (p.y - EPS <= maxy) then
        onsegment = -1
    else
        onsegment = 0
    end if
end function

' segments intersect
' return -1 if segment p1 to p2 intersects with segment q1 to q2
' EPS = small epsilon tolerance for comparaison

function segmentsintersect (p1 as point_type, p2 as point_type, q1 as point_type, q2 as point_type)
    dim d1 as double, d2 as double, d3 as double, d4 as double

    d1 = orient(p1, p2, q1)
    d2 = orient(p1, p2, q2)
    d3 = orient(q1, q2, p1)
    d4 = orient(q1, q2, p2)

    if (d1 * d2 < -EPS) and (d3 * d4 < -EPS) then
        segmentsintersect = -1
        exit function
    end if

    if (abs(d1) <= EPS) and (onsegment(p1, p2, q1) <> 0) then
        segmentsintersect = -1
        exit function
    end if
    if (abs(d2) <= EPS) and (onsegment(p1, p2, q2) <> 0) then
        segmentsintersect = -1
        exit function
    end if
    if (abs(d3) <= EPS) and (onsegment(q1, q2, p1) <> 0) then
        segmentsintersect = -1
        exit function
    end if
    if (abs(d4) <= EPS) and (onsegment(q1, q2, p2) <> 0) then
        segmentsintersect = -1
        exit function
    end if

    segmentsintersect = 0
end function

' point in polygon (ray casting to +x)
' returns -1 if inside or on edge, 0 if outside

function pointinpolygon (pt as point_type, shape as shape_type, points() as point_type)
    dim i as long, j as long
    dim inside as integer
    dim xi as point_type, xj as point_type
    inside = 0
    j = shape.lastPointIndex
    for i = shape.firstPointIndex to shape.lastPointIndex
        sum xi, points(i), shape.coord
        sum xj, points(j), shape.coord

        ' check if point is exactly on segment
        if abs(orient(xi, xj, pt)) <= eps then
            if onsegment(xi, xj, pt) <> 0 then
                pointinpolygon = -1
                exit function
            end if
        end if

        if ((xi.y > pt.y) <> (xj.y > pt.y)) then
            dim x_intersect as double
            x_intersect = xi.x + (pt.y - xi.y) * (xj.x - xi.x) / (xj.y - xi.y)
            if x_intersect > pt.x then
                inside = not inside
            end if
        end if

        j = i
    next i

    if inside then
        pointinpolygon = -1
    else
        pointinpolygon = 0
    end if
end function

' polygonsintersect
' returns -1 if intersecting (including touching), 0 otherwise

function polygonsintersect (shapeA as shape_type, shapeB as shape_type, points() as point_type)
    dim i as long, j as long
    dim a1 as point_type, a2 as point_type, b1 as point_type, b2 as point_type

    h% = _hypot(shapeA.coord.x-shapeB.coord.x,shapeA.coord.y-shapeA.coord.y)
    r% = shapeA.radius + shapeB.radius
    if h% > r% then
        polygonsintersect = 0
        exit function
    end if

    for i = shapeA.firstPointIndex to shapeA.lastPointIndex
        sum a1, points(i), shapeA.coord
        if i = shapeA.lastPointIndex then
            sum a2, points(shapeA.firstPointIndex), shapeA.coord
        else
            sum a2, points(i+1), shapeA.coord
        end if
        for j = shapeB.firstPointIndex to shapeB.lastPointIndex
            sum b1, points(j), shapeB.coord
            if i = shapeB.lastPointIndex then
                sum b2, points(shapeB.firstPointIndex), shapeB.coord
            else
                sum b2, points(i+1), shapeB.coord
            end if
            if segmentsintersect(a1, a2, b1, b2) <> 0 then
                polygonsintersect = -1
                exit function
            end if
        next j
    next i

    ' no edge intersections: test containment
    sum a1, points(shapeA.firstPointIndex), shapeA.coord
    if pointinpolygon(a1, shapeB, points()) <> 0 then
        polygonsintersect = -1
        exit function
    end if
    sum b1, points(shapeB.firstPointIndex), shapeB.coord
    if pointinpolygon(b1, shapeA, points()) <> 0 then
        polygonsintersect = -1
        exit function
    end if

    polygonsintersect = 0
end function

' sum point 1 and 2 given point 0

sub sum(p0 as point_type, p1 as point_type, p2 as point_type)
    p0.x = p1.x + p2.x
    p0.y = p1.y + p2.y
end sub

' calculation of the areas affected by the shape

sub evaluateShapeSector ( shape as shape_type, areas() as area_type, targets() as integer )

    ' shape outside the canvas, so not in an area

    if shape.coord.x < 0 or shape.coord.x > CANVAS_WIDTH or shape.coord.y < 0 or shape.coord.y > CANVAS_HEIGHT then
        shape.area = -1
        exit sub
    end if

    ' computes area

    shape.areaX = shape.coord.x \ AREA_SIDE_WIDTH
    shape.areaY = shape.coord.y \ AREA_SIDE_WIDTH
    shape.area = shape.areaY * AREAS_COLUMNS_COUNT + shape.areaX

    ' search other potential areas around

    if shape.area>=0 then

        redim targets(-1) as integer

        x0! = shape.coord.x - shape.radius
        y0! = shape.coord.y - shape.radius
        sx0% = x0! \ AREA_SIDE_WIDTH
        sy0% = y0! \ AREA_SIDE_WIDTH

        x2! = shape.coord.x + shape.radius
        y2! = shape.coord.y + shape.radius
        sx2% = x2! \ AREA_SIDE_WIDTH
        sy2% = y2! \ AREA_SIDE_WIDTH

        s0% = sy0% * AREAS_COLUMNS_COUNT + sx0%
        s1% = sy0% * AREAS_COLUMNS_COUNT + shape.areaX
        s2% = sy0% * AREAS_COLUMNS_COUNT + sx2%
        s3% = shape.areaY * AREAS_COLUMNS_COUNT + sx0%
        s4% = shape.areaY * AREAS_COLUMNS_COUNT + shape.areaX
        s5% = shape.areaY * AREAS_COLUMNS_COUNT + sx2%
        s6% = sy2% * AREAS_COLUMNS_COUNT + sx0%
        s7% = sy2% * AREAS_COLUMNS_COUNT + shape.areaX
        s8% = sy2% * AREAS_COLUMNS_COUNT + sx2%

        addUniqueInteger targets(), s0%
        addUniqueInteger targets(), s1%
        addUniqueInteger targets(), s2%
        addUniqueInteger targets(), s3%
        addUniqueInteger targets(), s4%
        addUniqueInteger targets(), s5%
        addUniqueInteger targets(), s6%
        addUniqueInteger targets(), s7%
        addUniqueInteger targets(), s8%

        ' remembers the presence of the shape in the area in the character array

        for i% = 0 to ubound(targets)
            if targets(i%)>=0 and targets(i%)<=AREAS_COUNT then
                if targets(i%)>=lbound(areas) and targets(i%)<=ubound(areas) then
                    mid$(areas(targets(i%)).shapeLocator,shape.index+1,1) = "*"
                end if
            end if
        next i%

    end if
end sub

' helper to keep only unique values

sub addUniqueInteger ( t() as integer, v as integer )
    for i%=lbound(t) to ubound(t)
        if t(i%)=v then exit sub
    next i%
    redim _preserve t(ubound(t)+1) as integer
    t(ubound(t)) = v
end sub

' detects collisions between shapes

function collision% ( shapes() as shape_type, points() as point_type, area as area_type )

    collision% = 0

    redim obj(-1) as integer

    ' collect unique shape indices from the area string (positions of '*')

    p0% = instr(area.shapeLocator,"*")
    do until p0% = 0
        s0% = p0%
        found% = 0
        for i%=lbound(obj) to ubound(obj)
            if obj(i%) = s0% then
                found% = -1
                exit for
            end if
        next i%
        if not found% then
            redim _preserve obj(ubound(obj)+1) as integer
            obj(ubound(obj)) = s0%
        end if
        p0% = instr(p0%+1,area.shapeLocator,"*")
    loop

    ' pairwise collision test (circle-circle) then test pixel-perfect
    ' when the distance is short enough to have a possible collision

    for i%=lbound(obj) to ubound(obj)
        for j%=i%+1 to ubound(obj)
            if shapes(obj(i%)-1).collid and shapes(obj(j%)-1).collid then exit sub
            if polygonsintersect(shapes(obj(i%)-1),shapes(obj(j%)-1),points()) then
                shapes(obj(i%)-1).collid = -1
                shapes(obj(j%)-1).collid = -1
                collision% = -1
                exit for
            end if
        next j%
    next i%

end function

sub showInfo (infos() as string)
    for i% = lbound(infos) to ubound(infos)
        _printstring (30  + X_DECAY,i%*16 + Y_DECAY), left$(" " + infos(i%) + space$(72-len(infos(i%))),73)
    next i%
end sub

sub resetInfo (infos() as string)
    redim infos(-1) as string
end sub

sub addInfo (infos() as string, info as string)
    redim _preserve infos(1 to ubound(infos)+1) as string
    infos(ubound(infos)) = info
end sub



RE: error doing image collision detection with _MemGet - bplus - 09-23-2025

Man I give you guys pixel perfect collision for any shape and you want to settle for less and with way more code.

I understand though, DIY! everyone can only understand their own stuff LOL