NaaLaa
PolyLine library - Printable Version

+- NaaLaa (https://www.naalaa.com/forum)
+-- Forum: NaaLaa (https://www.naalaa.com/forum/forum-1.html)
+--- Forum: NaaLaa 7 Code (https://www.naalaa.com/forum/forum-4.html)
+--- Thread: PolyLine library (/thread-90.html)

Pages: 1 2 3


RE: PolyLine library - kevin - 03-02-2024

Excellent, thank you - exactly what I was looking for. Have some interesting paths working with it, and no issues found.


RE: PolyLine library - Marcus - 03-02-2024

I updated the original post with a new version of the library (same as in the reply to kevin) and some game examples.


RE: PolyLine library - 1micha.elok - 03-05-2024

Problem
How to make closed loop and curved polyline ?
It makes sharp turn at X.


Code:
'===============================================
'                 TEST DRIVE
'
'Problem :
'- How to make closed loop and curved polyline ?
'  It makes sharp turn at X.
'===============================================

'------------------
'  INITIALIZATION
'------------------
#win32
include "polyline.n7"

set window "Test Drive", 160, 120,false,5
set redraw off

'color definition
white       = [255,255,255]
gray        = [128,128,128,50]
black       = [0,0,0]
black_alpha = [0,0,0,24]
red         = [168,0,0]
green       = [0,255,0]

'points' coordinates
points = []
points[0] = [20,60]
points[1] = [50,30]
points[2] = [80,60]
points[3] = [110,90]
points[4] = [140,60]
points[5] = [110,30]
points[6] = [80,60]
points[7] = [50,90]
points[8] = [20,60]

' Create a polyline
path = PolyLine(points)

' Variables
distance    = 1
move_pixel  = 4
radius      = 2

'-------------
'  MAIN LOOP
'-------------
while not keydown(KEY_ESCAPE, true)

    ' Title
    set caret width(primary)/2, 6; center "Test Drive"
    set caret width(primary)/2, height(primary)-20; center "Sharp turn at X"

    ' Racing Track
    set color gray  ; draw ellipse 50-1,60, 32,32,1;draw ellipse 110+2,60, 32,32,1
    set color black ; draw ellipse 50-1,60, 21,21,1;draw ellipse 110+2,60, 21,21,1
    set color white ; set caret 20-8,60-6; wln "x"

    'Polyline functions : .GetLength(), .GetPoint()
    distance = (distance + move_pixel)%path.GetLength()
    pos = path.GetPoint(distance,true) 'false = sharp, true = curved

    ' Draw circle that moves along path.
    set color black_alpha; cls 'clear background screen
    set color green; draw ellipse pos[0], pos[1], radius, radius, 1

    redraw
    fwait 10
wend



RE: PolyLine library - kevin - 03-05-2024

Hi, yes it's treating each lap as a separate path, which I guess is how it's designed to work. In the absence of a change to the code (which I can't think of), the impact can be lessened if we join the path in one of the straighter parts, rather than a curve. For example:

'points' coordinates
points = []
points[6] = [20,60]
points[7] = [50,30]
points[8] = [80,60]
points[0] = [80,60]
points[1] = [110,90]
points[2] = [140,60]
points[3] = [110,30]
points[4] = [80,60]
points[5] = [50,90]

All the best - Kevin


RE: PolyLine library - 1micha.elok - 03-05-2024

(03-05-2024, 08:42 AM)kevin Wrote: Hi, yes it's treating each lap as a separate path, which I guess is how it's designed to work. In the absence of a change to the code (which I can't think of), the impact can be lessened if we join the path in one of the straighter parts, rather than a curve. ...

Your solution solved my problem, thank you, Kevin  Big Grin
You are genius.


RE: PolyLine library - Marcus - 03-05-2024

(03-05-2024, 11:15 AM)1micha.elok Wrote:
(03-05-2024, 08:42 AM)kevin Wrote: Hi, yes it's treating each lap as a separate path, which I guess is how it's designed to work. In the absence of a change to the code (which I can't think of), the impact can be lessened if we join the path in one of the straighter parts, rather than a curve. ...

Your solution solved my problem, thank you, Kevin  Big Grin
You are genius.

Glad you could work around it! But I'll modify the library to support closed paths when I get the time.


RE: PolyLine library - Marcus - 03-08-2024

I've made some changes to the polyline library so that you can create closed paths. You now create a path with 'PolyLine(points, closed)'. Set 'closed' to true or false. You can also get the direction (normalized derivative) at any distance on the curve with 'GetDirection'. 

I hope it works, I haven't tested it much - writing code while watching a film on tv Smile

closed.n7
Code:
include "polyline.n7"

set window "test", 640, 480
set redraw off

points = [[50, 200], [200, 100], [500, 200], [320, 400], [64, 300]]
path = PolyLine(points, true)

d = 0
while not keydown(KEY_ESCAPE, true)
    d = d + 1
   
    set color 0, 0, 0
    cls
   
    ' draw control points and lines.
    set color 128, 128, 128
    for i = 0 to sizeof(points) - 1
        j = (i + 1)%sizeof(points)
        draw ellipse points[i][0], points[i][1], 4, 4, true
        draw line points[i][0], points[i][1], points[j][0], points[j][1]
    next
           
    set color 255, 255, 255
    ' when the curve is looped, d is wrapped and a valid pos is always returned.
    pos = path.GetPoint(d, true)
    ' you can use GetDirection to get the normalized derivative of the curve at distance d.
    dir = path.GetDirection(d, true)
    draw line pos[0] - dir[0]*16, pos[1] - dir[1]*16, pos[0], pos[1]
   
    redraw
    fwait 60
wend

polyline.n7
Code:
' polyline.n7
' -----------
' By Marcus.


' PolyLine
' --------
' Create a polyline from 'points', which is assumed to be a 2d array of the form [[x0, y0], [x1,
' y1], .. [xn, yn]].
function PolyLine(points, closed)
    assert sizeof(points) > 1, "PolyLine: too few points"
   
    pp = []
    pp.closed = closed
    pp.segs = []
    pp.length = 0
    pp.eval = dim(2)
    pp.deval = dim(2)
   
    ' Convert points to segments with some more data.
    if closed
        for i = 0 to sizeof(points) - 1
            dx = points[(i + 1)%sizeof(points)][0] - points[i][0]
            dy = points[(i + 1)%sizeof(points)][1] - points[i][1]
            d = sqr(dx*dx + dy*dy)
            pp.segs[i] = [
                x0: points[i][0], y0: points[i][1],
                x3: points[(i + 1)%sizeof(points)][0], y3: points[(i + 1)%sizeof(points)][1],
                dx: dx/d, dy: dy/d, d: d]
            pp.length = pp.length + d
        next
    else
        for i = 0 to sizeof(points) - 2
            dx = points[i + 1][0] - points[i][0]
            dy = points[i + 1][1] - points[i][1]
            d = sqr(dx*dx + dy*dy)
            pp.segs[i] = [
                x0: points[i][0], y0: points[i][1],
                x3: points[i + 1][0], y3: points[i + 1][1],
                dx: dx/d, dy: dy/d, d: d]
            pp.length = pp.length + d
        next
    endif
    ' Build bezier curve data.
    for i = 0 to sizeof(pp.segs) - 1  BuildBezierSegment(pp.segs, i, closed)
 
    ' GetPoint
    ' --------
    ' Return the point, [x, y], where the traveled distance along the polyline is 'dist'. If the
    ' path is not closed and 'dist' is not within the length of the polyline 'unset' is returned.
    ' Set 'smooth' to 'true' to get the point from a composite bezier curve that passes through all
    ' the original points.
    pp.GetPoint = function(dist, smooth)
        if this.closed  dist = dist%this.length
        elseif dist < 0  return unset
        d = 0
        for i = 0 to sizeof(this.segs) - 1
            if dist < d + this.segs[i].d
                if smooth
                    pt = this.segs[i]
                    p = (dist - d)/this.segs[i].d
                    b0 = (1 - p)^3
                    b1 = 3*(1 - p)^2*p
                    b2 = 3*(1 - p)*p^2
                    b3 = p^3
                    this.eval[0] = pt.x0*b0 + pt.x1*b1 + pt.x2*b2 + pt.x3*b3
                    this.eval[1] = pt.y0*b0 + pt.y1*b1 + pt.y2*b2 + pt.y3*b3
                else
                    this.eval[0] = this.segs[i].x0 + this.segs[i].dx*(dist - d)
                    this.eval[1] = this.segs[i].y0 + this.segs[i].dy*(dist - d)
                endif
                return this.eval
            else  d = d + this.segs[i].d
        next
        return unset
    endfunc

    ' GetDirection
    ' ------------   
    ' Return the normalized derivative, [dx, dy], where the traveled distance along the polyline is
    ' 'dist'. If the path is not closed and 'dist' is not within the length of the polyline 'unset'
    ' is returned. Set 'smooth' to 'true' to get the derivative from a composite bezier curve that
    ' passes through all the original points.
    pp.GetDirection = function(dist, smooth)
        if this.closed  dist = dist%this.length
        elseif dist < 0  return unset
        d = 0
        for i = 0 to sizeof(this.segs) - 1
            if dist < d + this.segs[i].d
                pt = this.segs[i]
                if smooth
                    p = (dist - d)/this.segs[i].d
                    b0 = 3*(1 - p)^2
                    b1 = 6*(1 - p)*p
                    b2 = 3*p*p
                    this.deval[0] = b0*(pt.x1 - pt.x0) + b1*(pt.x2 - pt.x1) + b2*(pt.x3 - pt.x2)
                    this.deval[1] = b0*(pt.y1 - pt.y0) + b1*(pt.y2 - pt.y1) + b2*(pt.y3 - pt.y2)
                else
                    this.deval[0] = pt.dx
                    this.deval[1] = pt.dy
                endif
                k = 1/sqr(this.deval[0]*this.deval[0] + this.deval[1]*this.deval[1])
                this.deval[0] = this.deval[0]*k
                this.deval[1] = this.deval[1]*k
                return this.deval
            else  d = d + this.segs[i].d
        next
        return unset
    endfunc
   
    ' GetControlPoint
    ' ---------------
    pp.GetControlPoint = function(index)
        if index >= 0 and index <= sizeof(this.segs)
            if index = sizeof(this.segs)  return [this.segs[index - 1].x3, this.segs[index - 1].y3]
            else  return [this.segs[index].x0, this.segs[index].y0]
        else
            return unset
        endif
    endfunc
   
    ' ModifyControlPoint
    ' ------------------
    ' Change the coordinates of a control point.
    pp.ModifyControlPoint = function(index, x, y)
        if this.closed
            assert index >= 0 and index < sizeof(this.segs), "PolyLine.ModifyControlPoint: index out of range"
            seg = this.segs[index]
            seg.x0 = x; seg.y0 = y;
            seg.dx = seg.x3 - x; seg.dy = seg.y3 - y
            seg.d = sqr(seg.dx*seg.dx + seg.dy*seg.dy)
            seg = this.segs[(index - 1)%sizeof(this.segs)]
            seg.x3 = x; seg.y3 = y
            seg.dx = x - seg.x0; seg.dy = y - seg.y0
            seg.d = sqr(seg.dx*seg.dx + seg.dy*seg.dy)
        else
            assert index >= 0 and index <= sizeof(this.segs), "PolyLine.ModifyControlPoint: index out of range"
            if index < sizeof(this.segs)
                seg = this.segs[index]
                seg.x0 = x; seg.y0 = y
                seg.dx = seg.x3 - x; seg.dy = seg.y3 - y
                seg.d = sqr(seg.dx*seg.dx + seg.dy*seg.dy)
            endif
            if index > 0
                seg = this.segs[index - 1]
                seg.x3 = x; seg.y3 = y
                seg.dx = x - seg.x0; seg.dy = y - seg.y0
                seg.d = sqr(seg.dx*seg.dx + seg.dy*seg.dy)           
            endif
        endif
        for i = index - 2 to index + 2  BuildBezierSegment(this.segs, i, this.closed)
        this.length = 0
        for i = 0 to sizeof(this.segs) - 1  this.length = this.length + this.segs[i].d
    endfunc
   
    ' GetLength
    ' ---------
    pp.GetLength = function()
        return this.length
    endfunc
   
    return pp
   
    ' BuildBezierSegment
    ' ------------------
    function BuildBezierSegment(segs, i, closed)
        if closed  i = i%sizeof(segs)
        elseif i < 0 or i >= sizeof(segs)  return
       
        dx = 1*(segs[i].x3 - segs[i].x0)
        dy = 1*(segs[i].y3 - segs[i].y0)
        if closed
            dx = dx + segs[i].x0 - segs[(i - 1)%sizeof(segs)].x0
            dy = dy + segs[i].y0 - segs[(i - 1)%sizeof(segs)].y0
        elseif i > 0
            dx = dx + segs[i].x0 - segs[i - 1].x0
            dy = dy + segs[i].y0 - segs[i - 1].y0
        endif
        k = 0.333*segs[i].d/sqr(dx*dx + dy*dy)
        segs[i].x1 = segs[i].x0 + k*dx
        segs[i].y1 = segs[i].y0 + k*dy
        dx = 1*(segs[i].x3 - segs[i].x0)
        dy = 1*(segs[i].y3 - segs[i].y0)
        if closed
            dx = dx + segs[(i + 1)%sizeof(segs)].x3 - segs[i].x3
            dy = dy + segs[(i + 1)%sizeof(segs)].y3 - segs[i].y3
        elseif i < sizeof(segs) - 1
            dx = dx + segs[i + 1].x3 - segs[i].x3
            dy = dy + segs[i + 1].y3 - segs[i].y3
        endif
        k = 0.333*segs[i].d/sqr(dx*dx + dy*dy)
        segs[i].x2 = segs[i].x3 - k*dx
        segs[i].y2 = segs[i].y3 - k*dy
    endfunc
endfunc


Edit If you want to draw an image with rotation based on the curve direction, you should be able to use 'atan2(dir[1], dir[0])' to get the angle if 'dir' was returned by 'GetDirection'.


RE: PolyLine library - 1micha.elok - 03-09-2024

(03-08-2024, 08:21 PM)Marcus Wrote: I've made some changes to the polyline library so that you can create closed paths.
... writing code while watching a film on tv Smile
 ... use 'atan2(dir[1], dir[0])' to get the angle ...

Last night I watched Gran Tourismo : A True Story, a video game player who became a professional real race car driver. Who knows that for the rest of my life, I could be a professional real race car driver too if I could make a car racing game  Big Grin ...just kidding...

With the changes in the polyline library especially the ability to get the angle 'atan2(dir[1], dir[0])' , you give me an idea to make car's dashboard for the car race game. The left dashboard for the angle direction, and the right dashboard for the car's speed.
   
click the image to zoom in

Code:
'===============================================
'                 TEST DRIVE
'
'Required : NEW POLYLINE LIBRARY
'===============================================

'------------------
'  INITIALIZATION
'------------------
set console oFF
include "polyline.n7"

set window "Test Drive", 160, 120,false,5
set redraw off

'color definition
white       = [255,255,255]
gray        = [128,128,128,50]
black       = [0,0,0]
black_alpha = [0,0,0,24]
red         = [255,0,0]
green       = [0,255,0]

'points' coordinates
points = []
points[0] = [20,60]
points[1] = [50,30]
points[2] = [80,60]
points[3] = [110,90]
points[4] = [140,60]
points[5] = [110,30]
points[6] = [80,60]
points[7] = [50,90]

' Variables
distance    = 1
move_pixel  = 6
radius      = 2

' Create a polyline
closed_path = true
path = PolyLine(points,closed_path)


'-------------
'  MAIN LOOP
'-------------
while not keydown(KEY_ESCAPE, true)
    ' Title
    set caret width(primary)/2, 6; center "Test Drive"
    if closed_path = true then; cp="true";else;cp="false";endif
    set caret width(primary)/2, height(primary)-20; center "closed "+cp
   
    ' Racing Track
    set color gray  ; draw ellipse 50-1,60, 32,32,1;draw ellipse 110+2,60, 32,32,1
    set color black ; draw ellipse 50-1,60, 21,21,1;draw ellipse 110+2,60, 21,21,1
    set color white ; set caret 20-8,60-6; wln "x"

    'Polyline functions : .GetLength(), .GetPoint(), .GetDirection()
    distance = (distance + move_pixel)%path.GetLength()
    pos = path.GetPoint(distance,true) 'false = sharp, true = curved
    dir = path.GetDirection(distance, true)

    'Car's Dashboard
    'Left Dashboard : Angle
    angle = atan2(dir[1], dir[0])
    set color red; draw line 49,60,18*cos(angle)+49,18*sin(angle)+60
    'Right Dashboard : move_pixel
    speedx = 18*cos(rad(360-30*move_pixel))+112
    speedy = 18*sin(rad(360-30*move_pixel))+60
    draw line 112,60, speedx, speedy  
    if move_pixel < 0 then; move_pixel = 6 ;else;move_pixel = move_pixel - 0.1;endif

    ' Draw circle that moves along path.
    set color black_alpha; cls 'clear background screen
    set color green; draw ellipse pos[0], pos[1], radius, radius, 1

    redraw
    fwait 10
wend

Perhaps, this dashboard and this race track could improve this game visually https://naalaa.com/forum/thread-92-post-539.html#pid539 along with improving the road ( to make a playable curved road ) ... a lot of homeworks  Big Grin

By the way, happy weekend and hope you and your family enjoy the gymnastics competition this weekend too


RE: PolyLine library - Marcus - 03-09-2024

(03-09-2024, 02:53 AM)1micha.elok Wrote:
(03-08-2024, 08:21 PM)Marcus Wrote: I've made some changes to the polyline library so that you can create closed paths.
... writing code while watching a film on tv Smile
 ... use 'atan2(dir[1], dir[0])' to get the angle ...

Last night I watched Gran Tourismo : A True Story, a video game player who became a professional real race car driver. Who knows that for the rest of my life, I could be a professional real race car driver too if I could make a car racing game  Big Grin ...just kidding...

With the changes in the polyline library especially the ability to get the angle 'atan2(dir[1], dir[0])' , you give me an idea to make car's dashboard for the car race game. The left dashboard for the angle direction, and the right dashboard for the car's speed.

click the image to zoom in

Code:
'===============================================
'                 TEST DRIVE
'
'Required : NEW POLYLINE LIBRARY
'===============================================

'------------------
'  INITIALIZATION
'------------------
set console oFF
include "polyline.n7"

set window "Test Drive", 160, 120,false,5
set redraw off

'color definition
white       = [255,255,255]
gray        = [128,128,128,50]
black       = [0,0,0]
black_alpha = [0,0,0,24]
red         = [255,0,0]
green       = [0,255,0]

'points' coordinates
points = []
points[0] = [20,60]
points[1] = [50,30]
points[2] = [80,60]
points[3] = [110,90]
points[4] = [140,60]
points[5] = [110,30]
points[6] = [80,60]
points[7] = [50,90]

' Variables
distance    = 1
move_pixel  = 6
radius      = 2

' Create a polyline
closed_path = true
path = PolyLine(points,closed_path)


'-------------
'  MAIN LOOP
'-------------
while not keydown(KEY_ESCAPE, true)
    ' Title
    set caret width(primary)/2, 6; center "Test Drive"
    if closed_path = true then; cp="true";else;cp="false";endif
    set caret width(primary)/2, height(primary)-20; center "closed "+cp
   
    ' Racing Track
    set color gray  ; draw ellipse 50-1,60, 32,32,1;draw ellipse 110+2,60, 32,32,1
    set color black ; draw ellipse 50-1,60, 21,21,1;draw ellipse 110+2,60, 21,21,1
    set color white ; set caret 20-8,60-6; wln "x"

    'Polyline functions : .GetLength(), .GetPoint(), .GetDirection()
    distance = (distance + move_pixel)%path.GetLength()
    pos = path.GetPoint(distance,true) 'false = sharp, true = curved
    dir = path.GetDirection(distance, true)

    'Car's Dashboard
    'Left Dashboard : Angle
    angle = atan2(dir[1], dir[0])
    set color red; draw line 49,60,18*cos(angle)+49,18*sin(angle)+60
    'Right Dashboard : move_pixel
    speedx = 18*cos(rad(360-30*move_pixel))+112
    speedy = 18*sin(rad(360-30*move_pixel))+60
    draw line 112,60, speedx, speedy  
    if move_pixel < 0 then; move_pixel = 6 ;else;move_pixel = move_pixel - 0.1;endif

    ' Draw circle that moves along path.
    set color black_alpha; cls 'clear background screen
    set color green; draw ellipse pos[0], pos[1], radius, radius, 1

    redraw
    fwait 10
wend

Perhaps, this dashboard and this race track could improve this game visually https://naalaa.com/forum/thread-92-post-539.html#pid539 along with improving the road ( to make a playable curved road ) ... a lot of homeworks  Big Grin

By the way, happy weekend and hope you and your family enjoy the gymnastics competition this weekend too

I once wrote a Mario Kart clone (just a test, more of an engine, I guess) in n5 or n6: https://youtu.be/Xr_UhCX9ta0?si=YPMi7_CBVAJVJRKm&t=212  For that game I actually did use something like the polyline library for the computer players' movements. I "recorded" (saved a position into an array every second or so) my self playing the game a couple of times. Then I let the computer played opponents follow these paths. The enemies could still collide with the eachother and the player, but they always tried to get back on their paths. It worked quite well Smile


RE: PolyLine library - Marcus - 03-10-2024

Here's a zip with the new version (posted above) of the library. The third game example now uses images and rotates the enemy sprites after the path direction.