When I finally found my old spline source code, it was written in DBP which had hermite and catmull splines built in with the vector commands so I couldn't translate that to agk. So here's a super simple implementation. It's something I needed for my tower defense game, so I'll be building on this example and will update that bit here.
Cleaner code:
// Project: catmullrom
// Created: 2021-11-21
// Author: Phaelax
// Source: http://www.mvps.org/directx/articles/catmull/
SetWindowSize( 1024, 768, 0 )
SetVirtualResolution( 1024, 768 )
SetSyncRate(500, 0 )
#CONSTANT segments 16 // Number of line segments between each control point (higher means smoother curve)
c = makeColor(255,255,255) // control points
cr = makecolor(255,0,0) // spline
cg = makecolor(0,255,0) // control point on mouse hover
cb = makecolor(0,0,255) // current control point containing the moving point
cq = makeColor(255,255,0) // point along the spline
Type Point2D
x as float
y as float
index as float // distance on the spline of this control point
t as integer // text object
EndType
dim cp[] as Point2D
P as Point2D
t# = 0.0
i = 1
cpDistance# = 0
splineDistance# = 0
ship = createSprite(loadImage("ship.png"))
setSpriteSize(ship, 84,84)
setSpriteOffset(ship, 42, 42)
movePoint = -1
do
// Arrow keys to move ship along spline
if getRawKeyState(37) = 1 // Left key
dec splineDistance#, 0.75
if splineDistance# < 0 then splineDistance# = getSplineLength(cp)
endif
if getRawKeyState(39) = 1 // Right key
inc splineDistance#, 0.75
if splineDistance# > getSplineLength(cp) then splineDistance# = 0
endif
// Hold right mouse to move control point
if getRawMouseRightState() = 0 then movePoint = -1
// Left mouse to add control point
if getRawMouseLeftPressed()
addControlPoint(cp, getRawMouseX(), getRawMouseY())
endif
// draw control points
for j = 0 to cp.length
if ((cp[j].x - getRawMouseX())^2 + (cp[j].y - getRawMouseY())^2 <= 100 )
mindex = j
color = cg
cpDistance# = cp[j].index
if getRawMouseRightState() = 1 and movePoint = -1 then movePoint = j
else
color = c
endif
drawEllipse(cp[j].x, cp[j].y, 10, 10, color,color,1)
next j
if movePoint > -1
moveControlPoint(cp, movePoint, getRawMouseX(), getRawMouseY())
endif
// Draw spline
drawSpline(cp, makeColor(255,0,0))
// Get point on spline based on distance along its path
oldPx# = getSpriteXByOffset(ship)
oldPy# = getSpriteYByOffset(ship)
P = getPointOnSpline(cp, splineDistance#)
SetSpritePositionByOffset(ship, P.x, P.y)
if (p.x <> oldPx# OR p.y <> oldPy#)
a# = atanfull(p.x - oldPx#, p.y - oldPy#)-90
setSpriteAngle(ship, a#)
endif
if timer() - fps# >= 0.5
fps# = timer()
frames = screenfps()
endif
`Print("FPS: "+str(frames))
print("Position: "+str((round(splineDistance#))) + " / " + str(round(getSplineLength(cp))))
if mindex > 0 then print("Dist from origin: "+str(cp[mindex].index))
Sync()
loop
function getPointOnSpline(arr ref as Point2D[], distance as float)
q as Point2D
q.x = -1
q.y = -1
if arr.length > 3
// Determine which control point we're closest to
index = getIndex(arr, distance)
// Convert distance along spline into a local distance between two control points
localDistance# = distance - cp[index].index
i = index
d# = 0
for k = 1 to segments
if k = 1
oldX# = cp[i].x
oldY# = cp[i].y
endif
t# = k / (segments+0.0)
x# = 0.5 * ((2*arr[i].x) + (-arr[i-1].x + arr[i+1].x) * t# + (2*arr[i-1].x - 5*arr[i].x + 4*arr[i+1].x - arr[i+2].x) * t#^2 + (-arr[i-1].x + 3*arr[i].x - 3*arr[i+1].x + arr[i+2].x) * t#^3)
y# = 0.5 * ((2*arr[i].y) + (-arr[i-1].y + arr[i+1].y) * t# + (2*arr[i-1].y - 5*arr[i].y + 4*arr[i+1].y - arr[i+2].y) * t#^2 + (-arr[i-1].y + 3*arr[i].y - 3*arr[i+1].y + arr[i+2].y) * t#^3)
sd# = sqrt((oldX#-x#)^2 + (oldY#-y#)^2)
// Found segment, now interpolate to find exact point
if d#+sd# > localDistance#
t# = (localDistance# - d#) / sd#
q.x = oldX# + (x# - oldX#)*t#
q.y = oldY# + (y# - oldY#)*t#
exitfunction q
endif
d# = d# + sd#
oldX# = x#
oldY# = y#
next k
endif
endfunction q
function addControlPoint(arr ref as Point2D[], x as float, y as float)
p as Point2D
p.x = x
p.y = y
p.t = createText("")
setTextColor(p.t, 255,0,255,255)
setTextSize(p.t, 30)
setTextPosition(p.t, x+2, y+2)
arr.insert(p)
setTextString(arr[arr.length].t, str(arr.length))
calculatePath(arr)
endfunction
function moveControlPoint(arr ref as Point2D[], p, x as float, y as float)
arr[p].x = x
arr[p].y = y
setTextPosition(arr[p].t, x+2, y+2)
calculatePath(arr)
endfunction
function calculatePath(arr ref as Point2D[])
if arr.length >= 3
d# = 0
for i = 1 to arr.length-2
arr[i].index = d#
for k = 1 to segments
if k = 1
oldX# = arr[i].x
oldY# = arr[i].y
endif
t# = k / (segments+0.0)
x# = 0.5 * ((2*arr[i].x) + (-arr[i-1].x + arr[i+1].x) * t# + (2*arr[i-1].x - 5*arr[i].x + 4*arr[i+1].x - arr[i+2].x) * t#^2 + (-arr[i-1].x + 3*arr[i].x - 3*arr[i+1].x + arr[i+2].x) * t#^3)
y# = 0.5 * ((2*arr[i].y) + (-arr[i-1].y + arr[i+1].y) * t# + (2*arr[i-1].y - 5*arr[i].y + 4*arr[i+1].y - arr[i+2].y) * t#^2 + (-arr[i-1].y + 3*arr[i].y - 3*arr[i+1].y + arr[i+2].y) * t#^3)
d# = d# + sqrt((oldX#-x#)^2 + (oldY#-y#)^2)
oldX# = x#
oldY# = y#
next k
next i
arr[arr.length-1].index = d# // record overall spline length in last control point
endif
endfunction
function drawSpline(arr ref as Point2D[], color)
for i = 1 to arr.length-2
for k = 1 to segments
if k = 1
oldX# = arr[i].x
oldY# = arr[i].y
endif
t# = k / (segments+0.0)
x# = 0.5 * ((2*arr[i].x) + (-arr[i-1].x + arr[i+1].x) * t# + (2*arr[i-1].x - 5*arr[i].x + 4*arr[i+1].x - arr[i+2].x) * t#^2 + (-arr[i-1].x + 3*arr[i].x - 3*arr[i+1].x + arr[i+2].x) * t#^3)
y# = 0.5 * ((2*arr[i].y) + (-arr[i-1].y + arr[i+1].y) * t# + (2*arr[i-1].y - 5*arr[i].y + 4*arr[i+1].y - arr[i+2].y) * t#^2 + (-arr[i-1].y + 3*arr[i].y - 3*arr[i+1].y + arr[i+2].y) * t#^3)
drawLine(oldX#, oldY#, x#, y#, color, color)
oldX# = x#
oldY# = y#
next k
next i
endfunction
function getSplineLength(arr ref as Point2D[])
if arr.length > 0
d# = arr[arr.length-1].index
else
d# = -1
endif
endfunction d#
function getIndex(arr ref as Point2D[], d#)
if d# <= 0 then exitfunction 1
if d# >= arr[arr.length-1].index then exitfunction arr.length-2
for i = 2 to arr.length-1
if arr[i].index > d# then exitfunction i-1
next i
endfunction 0
messy code:
// Project: catmullrom
// Created: 2021-11-21
// Author: Phaelax
// Source: http://www.mvps.org/directx/articles/catmull/
SetWindowSize( 1024, 768, 0 )
SetVirtualResolution( 1024, 768 )
SetSyncRate(500, 0 )
#CONSTANT segments 20 // Number of line segments between each control point (higher means smoother curve)
c = makeColor(255,255,255) // control points
cr = makecolor(255,0,0) // spline
cg = makecolor(0,255,0) // control point on mouse hover
cb = makecolor(0,0,255) // current control point containing the moving point
cq = makeColor(255,255,0) // point along the spline
Type Point2D
x as float
y as float
index as float
EndType
dim cp[10] as Point2D
for i = 0 to cp.length-1
cp[i].x = i*100 + (random(0,200)-100)
cp[i].y = 300 + (random(0,400)-200)
`cp[i].x = random(0,1024)
`cp[i].y = random(0,768)
next i
`cp[0] = cp[1]
t# = 0.0
i = 1
cpDistance# = 0
splineDistance# = 0
ship = createSprite(loadImage("ship.png"))
setSpriteSize(ship, 84,84)
setSpriteOffset(ship, 42, 42)
do
// Spacebar to make new random spline
if getRawKeyPressed(32) = 1
for i = 0 to cp.length-1
cp[i].x = random(0,1024)
cp[i].y = random(0,768)
next i
`cp[0] = cp[1]
endif
if getRawKeyState(37) = 1 // Left key
dec splineDistance#, 0.75
endif
if getRawKeyState(39) = 1 // Right key
inc splineDistance#, 0.75
endif
if splineDistance# > cp[cp.length-1].index then splineDistance# = 0
// draw control points
for j = 0 to cp.length-1
if ((cp[j].x - getRawMouseX())^2 + (cp[j].y - getRawMouseY())^2 <= 100 )
mindex = j
color = cg
cpDistance# = cp[j].index
else
if j = index
color = cb
else
color = c
endif
endif
drawEllipse(cp[j].x, cp[j].y, 10, 10, color,color,1)
next j
// draw spline
d# = 0
for i = 1 to cp.length-2
cp[i].index = d#
for k = 1 to segments
if k = 1
oldX# = cp[i].x
oldY# = cp[i].y
endif
t# = k / (segments+0.0)
x# = 0.5 * ((2*cp[i].x) + (-cp[i-1].x + cp[i+1].x) * t# + (2*cp[i-1].x - 5*cp[i].x + 4*cp[i+1].x - cp[i+2].x) * t#^2 + (-cp[i-1].x + 3*cp[i].x - 3*cp[i+1].x + cp[i+2].x) * t#^3)
y# = 0.5 * ((2*cp[i].y) + (-cp[i-1].y + cp[i+1].y) * t# + (2*cp[i-1].y - 5*cp[i].y + 4*cp[i+1].y - cp[i+2].y) * t#^2 + (-cp[i-1].y + 3*cp[i].y - 3*cp[i+1].y + cp[i+2].y) * t#^3)
if i = index
drawLine(oldX#, oldY#, x#, y#, cb, cb)
else
drawLine(oldX#, oldY#, x#, y#, cr, cr)
endif
d# = d# + sqrt((oldX#-x#)^2 + (oldY#-y#)^2)
oldX# = x#
oldY# = y#
next k
next i
cp[cp.length-1].index = d# // record overall spline length in last control point
// Find point along spline
// Determine which control point we're closest to
index = getIndex(cp, splineDistance#)
// Convert distance along spline into a local distance between two control points
localDistance# = splineDistance# - cp[index].index
i = index
d# = 0
for k = 1 to segments
if k = 1
oldX# = cp[i].x
oldY# = cp[i].y
endif
t# = k / (segments+0.0)
x# = 0.5 * ((2*cp[i].x) + (-cp[i-1].x + cp[i+1].x) * t# + (2*cp[i-1].x - 5*cp[i].x + 4*cp[i+1].x - cp[i+2].x) * t#^2 + (-cp[i-1].x + 3*cp[i].x - 3*cp[i+1].x + cp[i+2].x) * t#^3)
y# = 0.5 * ((2*cp[i].y) + (-cp[i-1].y + cp[i+1].y) * t# + (2*cp[i-1].y - 5*cp[i].y + 4*cp[i+1].y - cp[i+2].y) * t#^2 + (-cp[i-1].y + 3*cp[i].y - 3*cp[i+1].y + cp[i+2].y) * t#^3)
sd# = sqrt((oldX#-x#)^2 + (oldY#-y#)^2)
if d#+sd# > localDistance#
t# = (localDistance# - d#) / sd#
px# = oldX# + (x# - oldX#)*t#
py# = oldY# + (y# - oldY#)*t#
`drawEllipse(px#, py#, 6,6, cq,cq,1)
a# = atanfull(x# - oldX#, y# - oldY#)-90
SetSpritePositionByOffset(ship, px#, py#)
setSpriteAngle(ship, a#)
exit
endif
d# = d# + sd#
oldX# = x#
oldY# = y#
next k
if timer() - fps# >= 0.5
fps# = timer()
frames = screenfps()
endif
`Print("FPS: "+str(frames))
print("Position: "+str(splineDistance#) + " / "+str(cp[cp.length-1].index))
print("Index: "+str(index))
print("Mouse at index: "+str(mindex))
print("Index#: "+str(cp[mindex].index))
Sync()
loop
function getIndex(arr ref as Point2D[], d#)
if d# <= 0 then exitfunction 1
if d# >= arr[arr.length-1].index then exitfunction arr.length-2
for i = 2 to arr.length-1
if arr[i].index > d# then exitfunction i-1
next i
endfunction 0