CLS: CLEAR: CLOSE
$RESIZE:ON
REM $DYNAMIC

SCREEN _NEWIMAGE(1000, 1000, 32)
COLOR _RGBA(255, 255, 255, 255), _RGBA(0, 0, 0, 255)

TYPE point
    AS DOUBLE x, y
    AS DOUBLE handlex, handley
END TYPE
REDIM SHARED AS point P(0)

REDIM SHARED AS INTEGER mousex, mousey, mousel, mouser, mousem, mousemlock, mousellock
REDIM SHARED AS _BYTE mainexit
REDIM SHARED AS _INTEGER64 activeGrab, activeHandleGrab, roundsSinceEdit, keyhit

RANDOMIZE TIMER

start:

' creates two random points
'P(1).x = INT(RND * 1000)
'P(1).handlex = P(1).x
'P(1).y = INT(RND * 1000)
'P(1).handley = P(1).y
'P(2).x = INT(RND * 1000)
'P(2).handlex = P(2).x
'P(2).y = INT(RND * 1000)
'P(2).handley = P(2).y

DO
    CLS

    getMouseCoords
    KeyboardHandler

    displayPoints
    displayLines
    displayText

    _DISPLAY

    IF roundsSinceEdit < 1000 THEN roundsSinceEdit = roundsSinceEdit + 1
    '_LIMIT 60
LOOP UNTIL mainexit = -1 OR restart = -1
IF restart = -1 THEN GOTO start

SUB displayText
    PRINT "Create point", "[CTRL] + Left Mouse"
    PRINT "Delete point", "[CTRL] + Left Mouse"
    PRINT "Move point", "Left Mouse"
    PRINT "Move handle", "Right Mouse"
    'PRINT UBOUND(P); " points"
    'PRINT roundsSinceEdit; " rounds since edit"
    'PRINT _KEYDOWN(100306), mousex, mousey, mousel, mouser, mousem, mousemlock, mousellock
END SUB

SUB displayLines
    IF UBOUND(P) < 2 THEN EXIT SUB

    ' sample rate factor
    roundLimit = 10
    roundFactor = (roundsSinceEdit + 1) / roundLimit
    IF roundFactor > 1 THEN roundFactor = 1

    DO: i = i + 1
        'LINE (P(i).x, P(i).y)-(P(i + 1).x, P(i + 1).y), _RGBA(255, 255, 255, 30) ' straight line
        bVecLength = SQR((ABS(P(i + 1).x - P(i).x) ^ 2) + (ABS(P(i + 1).y - P(i).y) ^ 2))
        sampleCount = bVecLength * roundFactor

        ' base vector
        bVecX = P(i + 1).x - P(i).x
        bVecY = P(i + 1).y - P(i).y

        ' handle vectors
        vec1x = P(i).handlex - P(i).x
        vec1y = P(i).handley - P(i).y
        handle1Length = SQR((vec1x ^ 2) + (vec1y ^ 2))
        IF handle1Length > sampleCount / 5 THEN handle1oversampling = 1 + (handle1Length / bVecLength) ELSE handle1oversampling = 1
        vec2x = P(i + 1).handlex - P(i + 1).x
        vec2y = P(i + 1).handley - P(i + 1).y
        handle2Length = SQR((vec2x ^ 2) + (vec2y ^ 2))
        IF handle2Length > sampleCount / 5 THEN handle2oversampling = 1 + (handle2Length / bVecLength) ELSE handle2oversampling = 1
        sampleCount = INT(sampleCount * handle1oversampling * handle2oversampling)

        ' dev
        'PRINT "Point "; i; " sample count: "; sampleCount, "h1OS: "; handle1oversampling, "h2OS: "; handle2oversampling

        s = 0: DO: s = s + 1
            handle1Influence = 1 - ((2 * ABS((s / sampleCount) - 0.5)) ^ 2)
            handle1Influence = handle1Influence * (1 - (s / sampleCount))

            handle2Influence = 1 - ((2 * ABS((s / sampleCount) - 0.5)) ^ 2)
            handle2Influence = -handle2Influence * (s / sampleCount)

            ' merged vector
            pX = bVecX * (s / sampleCount) + vec1x * handle1Influence + vec2x * handle2Influence
            pY = bVecY * (s / sampleCount) + vec1y * handle1Influence + vec2y * handle2Influence

            'PSET (P(i).x + bVecX * (s / sampleCount), P(i).y + bVecY * (s / sampleCount)), _RGBA(255, 255, 255, 20)
            PSET (P(i).x + pX, P(i).y + pY), _RGBA(255, 255, 255, 255)
        LOOP UNTIL s > sampleCount
    LOOP UNTIL i = UBOUND(P) - 1
END SUB

SUB displayPoints
    IF UBOUND(P) > 0 THEN
        DO: i = i + 1
            ' base point
            LINE (P(i).x - 1, P(i).y - 1)-(P(i).x + 1, P(i).y + 1), _RGBA(255, 255, 255, 255), BF
            IF clickCondition("movePoint", P(i).x, P(i).y) OR activeGrab = i THEN
                HoffX = P(i).handlex - P(i).x
                HoffY = P(i).handley - P(i).y
                P(i).x = mousex: P(i).y = mousey
                P(i).handlex = P(i).x + HoffX
                P(i).handley = P(i).y + HoffY
                activeGrab = i
                roundsSinceEdit = 0
            END IF
            IF clickCondition("deletePoint", P(i).x, P(i).y) THEN
                IF i < UBOUND(P) THEN
                    i2 = i: DO
                        P(i2) = P(i2 + 1)
                        i2 = i2 + 1
                    LOOP UNTIL i2 >= UBOUND(P) - 1
                END IF
                P(UBOUND(P)) = P(0)
                REDIM _PRESERVE P(UBOUND(P) - 1) AS point
                pointDeleted = -1
                roundsSinceEdit = 0
            END IF

            ' handle
            IF pointDeleted = 0 THEN
                LINE (P(i).x, P(i).y)-(P(i).handlex, P(i).handley), _RGBA(255, 255, 255, 100)
                CIRCLE (P(i).handlex, P(i).handley), 5, _RGBA(255, 255, 255, 255)
                IF clickCondition("moveHandle", P(i).handlex, P(i).handley) OR activeHandleGrab = i THEN
                    P(i).handlex = mousex: P(i).handley = mousey
                    activeHandleGrab = i
                    roundsSinceEdit = 0
                END IF
            END IF
        LOOP UNTIL i >= UBOUND(P)
    END IF

    IF pointDeleted = 0 AND clickCondition("createPoint", 0, 0) THEN
        REDIM _PRESERVE P(UBOUND(P) + 1) AS point
        P(UBOUND(P)).x = mousex
        P(UBOUND(P)).y = mousey
        P(UBOUND(P)).handlex = mousex
        P(UBOUND(P)).handley = mousey
        roundsSinceEdit = 0
    END IF
END SUB

FUNCTION clickCondition (conditionName AS STRING, x AS DOUBLE, y AS DOUBLE)
    SELECT CASE conditionName
        CASE "deletePoint"
            IF mousem AND inRadius(x, y, mousex, mousey, 20) AND mousemlock = 0 THEN
                mousemlock = -1: clickCondition = -1
            ELSEIF ctrldown AND mousel AND inRadius(x, y, mousex, mousey, 20) AND mousellock = 0 THEN
                mousellock = -1: clickCondition = -1
            ELSE clickCondition = 0
            END IF
        CASE "movePoint"
            IF mousel AND inRadius(x, y, mousex, mousey, 20) THEN clickCondition = -1 ELSE clickCondition = 0
        CASE "createPoint"
            IF mousem AND mousemlock = 0 THEN
                mousemlock = -1: clickCondition = -1
            ELSEIF ctrldown AND mousel AND mousellock = 0 THEN
                mousellock = -1: clickCondition = -1
            ELSE clickCondition = 0
            END IF
        CASE "moveHandle"
            IF mouser AND inRadius(x, y, mousex, mousey, 20) THEN clickCondition = -1 ELSE clickCondition = 0
    END SELECT
END FUNCTION

FUNCTION ctrldown
    IF _KEYDOWN(100306) OR _KEYDOWN(100305) THEN
        ctrldown = -1
    ELSE
        ctrldown = 0
    END IF
END FUNCTION

FUNCTION inRadius (boxX, boxY, pointX, pointY, radius)
    IF pointX > boxX - (radius / 2) AND pointY > boxY - (radius / 2) AND pointX < boxX + (radius / 2) AND pointY < boxY + (radius / 2) THEN
        inRadius = -1
    ELSE
        inRadius = 0
    END IF
END FUNCTION

SUB getMouseCoords
    IF _MOUSEINPUT THEN
        mousex = _MOUSEX
        mousey = _MOUSEY
        mousel = _MOUSEBUTTON(1)
        mouser = _MOUSEBUTTON(2)
        mousem = _MOUSEBUTTON(3)
        IF mouser = 0 THEN activeHandleGrab = 0
        IF mousel = 0 THEN activeGrab = 0
        IF mousemlock = -1 AND mousem = 0 THEN mousemlock = 0
        IF mousellock = -1 AND mousel = 0 THEN mousellock = 0
    END IF
END SUB

SUB KeyboardHandler
    IF _KEYDOWN(27) THEN mainexit = -1
    IF _KEYDOWN(ASC("r")) THEN restart = -1
END SUB
