AddPathSpline

Share your advanced PureBasic knowledge/code with the community.
User avatar
pf shadoko
Enthusiast
Enthusiast
Posts: 421
Joined: Thu Jul 09, 2015 9:07 am

AddPathSpline

Post by pf shadoko »

A function for drawing spline curves (closed or open)
This function is not included in the SVG standard, yet it is very simple to implement.

Code: Select all

Structure Vector2:x.f:y.f:EndStructure

Procedure AddPathSpline(Array po.Vector2(1),Flags=0,roundness.f=0.5)
  Protected n,i,close,coef.f=roundness/3
  Protected.Vector2 b0,b1,b2,b3,c
  n=ArraySize(po())+2
  Dim p.Vector2(n)
  
  If flags=#PB_Path_Relative:c\x=PathCursorX():c\y=PathCursorY():EndIf
  For i=0 To n-2:p(i+1)\x=po(i)\x+c\x:p(i+1)\y=po(i)\y+c\y:Next
  If p(n-1)\X = p(1)\X And p(n-1)\Y = p(1)\Y:close=1: p(0) = p(n-2): p(n) = p(2):Else:p(0) = p(2):p(n) = p(n-2):EndIf
  
  MovePathCursor(p(1)\x,p(1)\y)
  For i=1 To n-2    
    b0=p(i)
    b3=p(i+1)
    b1\x=b0\x+coef*(b3\x-p(i-1)\x)
    b1\y=b0\y+coef*(b3\y-p(i-1)\y)
    b2\x=b3\x-coef*(p(i+2)\x-b0\x)
    b2\y=b3\y-coef*(p(i+2)\y-b0\y)
    AddPathCurve(b1\x,b1\y, b2\x,b2\y, b3\x,b3\y)  
  Next
  If close:ClosePath():EndIf
EndProcedure

OpenWindow(0, 0, 0, 600,600, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0,0,0,600,600)

Dim p.Vector2(8)

Repeat
  b.f+0.04
  For i=0 To 7:a.f=i*2*#PI/8
    r=200+Sin(b+i*15)*50
    p(i)\x=Cos(a)*r+300
    p(i)\y=Sin(a)*r+300
  Next
  p(8)=p(0); to close the curve
  
  StartVectorDrawing(CanvasVectorOutput(0))  
  VectorSourceColor($11ff6666)
  FillVectorOutput()
  addpathspline(p())
  VectorSourceColor($ffffaaaa):FillPath(#PB_Path_Preserve)
  VectorSourceColor($ff000000):StrokePath(4)
  For i=0 To 7:AddPathCircle(p(i)\x,p(i)\y,4):Next
  VectorSourceColor($ff0000ff):FillPath()
  StopVectorDrawing()
  Delay(32)
  event=WindowEvent()
Until event=#PB_Event_CloseWindow Or event=#PB_Event_DeactivateWindow
Last edited by pf shadoko on Wed Oct 29, 2025 10:33 pm, edited 1 time in total.
Booger
Enthusiast
Enthusiast
Posts: 147
Joined: Tue Sep 04, 2007 2:18 pm

Re: AddPathSpline

Post by Booger »

That is so neat. I have no idea what a good use would be yet but I'm sure you'll tell me.
I saw a huge amount of on the fly calculations, in the 1970's we made a table for such things so there was time
left to actually do something else. Hope you don't mind the tables mod.

Code: Select all

Structure Vector2:x.f:y.f:EndStructure

Procedure AddPathSpline(Array po.Vector2(1),Flags=0,roundness.f=0.5)
  Protected n,i,close,coef.f=roundness/3
  Protected.Vector2 b0,b1,b2,b3,c
  n=ArraySize(po())+2
  Dim p.Vector2(n)
  
  If flags=#PB_Path_Relative:c\x=PathCursorX():c\y=PathCursorY():EndIf
  
  For i=0 To n-2:p(i+1)\x=po(i)\x+c\x:p(i+1)\y=po(i)\y+c\y:Next
  If p(n-1)\X = p(1)\X And p(n-1)\Y = p(1)\Y:close=1: p(0) = p(n-2): p(n) = p(2):Else:p(0) = p(2):p(n) = p(n-2):EndIf
  MovePathCursor(p(1)\x,p(1)\y)
  For i=1 To n-2  
    b0=p(i)
    b3=p(i+1)
    b1\x=b0\x+coef*(b3\x-p(i-1)\x)
    b1\y=b0\y+coef*(b3\y-p(i-1)\y)
    b2\x=b3\x-coef*(p(i+2)\x-b0\x)
    b2\y=b3\y-coef*(p(i+2)\y-b0\y)
    AddPathCurve(b1\x,b1\y, b2\x,b2\y, b3\x,b3\y)  
  Next
  If close:ClosePath():EndIf
EndProcedure

; --- Pre-calculation tables (This is the optimization) ---
Global Dim CosLookup.f(7)
Global Dim SinLookup.f(7)
Global Dim AngleOffset.f(7)

Procedure SetupTables()
  Protected i.i, a.f
  For i = 0 To 7
    a = i * 2 * #PI / 8
    CosLookup(i) = Cos(a)
    SinLookup(i) = Sin(a)
    AngleOffset(i) = i * 15
  Next
EndProcedure

; --- Main Program ---
OpenWindow(0, 0, 0, 600,600, "Optimized Spline", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0,0,0,600,600)

; Fill the lookup tables ONE time
SetupTables()

Dim p.Vector2(8)
Define.i event, i
Define.f b, r

Repeat
  
  b + 0.04
  For i=0 To 7
    ; Use pre-calculated values from the tables
    r = 200 + Sin(b + AngleOffset(i)) * 50
    p(i)\x = CosLookup(i) * r + 300
    p(i)\y = SinLookup(i) * r + 300
  Next
  p(8)=p(0); to close the curve
  
  ; Clear the screen (background)
  StartDrawing(CanvasOutput(0)):Box(0,0,600,600,$ff8888):StopDrawing()  
  
  ; Draw the vector graphics
  StartVectorDrawing(CanvasVectorOutput(0))  
  AddPathSpline(p())
  VectorSourceColor($22ffffff):FillPath(#PB_Path_Preserve)
  VectorSourceColor($ff000000):StrokePath(2)
  For i=0 To 7:AddPathCircle(p(i)\x,p(i)\y,4):Next
  VectorSourceColor($ff0000ff):FillPath()
  StopVectorDrawing()
  
  event = WindowEvent() 
  
  ; Delay 16ms to target ~60 FPS
  Delay(16) 
  
Until event=#PB_Event_CloseWindow
Justin
Addict
Addict
Posts: 958
Joined: Sat Apr 26, 2003 2:49 pm

Re: AddPathSpline

Post by Justin »

Nice, using FillVectorOutput removes the flickering here

Code: Select all

Structure Vector2:x.f:y.f:EndStructure

Procedure AddPathSpline(Array po.Vector2(1),Flags=0,roundness.f=0.5)
  Protected n,i,close,coef.f=roundness/3
  Protected.Vector2 b0,b1,b2,b3,c
  n=ArraySize(po())+2
  Dim p.Vector2(n)
  
  If flags=#PB_Path_Relative:c\x=PathCursorX():c\y=PathCursorY():EndIf
  For i=0 To n-2:p(i+1)\x=po(i)\x+c\x:p(i+1)\y=po(i)\y+c\y:Next
  If p(n-1)\X = p(1)\X And p(n-1)\Y = p(1)\Y:close=1: p(0) = p(n-2): p(n) = p(2):Else:p(0) = p(2):p(n) = p(n-2):EndIf
  MovePathCursor(p(1)\x,p(1)\y)
  For i=1 To n-2    
    b0=p(i)
    b3=p(i+1)
    b1\x=b0\x+coef*(b3\x-p(i-1)\x)
    b1\y=b0\y+coef*(b3\y-p(i-1)\y)
    b2\x=b3\x-coef*(p(i+2)\x-b0\x)
    b2\y=b3\y-coef*(p(i+2)\y-b0\y)
    AddPathCurve(b1\x,b1\y, b2\x,b2\y, b3\x,b3\y)  
  Next
  If close:ClosePath():EndIf
EndProcedure

OpenWindow(0, 0, 0, 600,600, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0,0,0,600,600)

Dim p.Vector2(8)

Repeat
  b.f+0.04
  For i=0 To 7:a.f=i*2*#PI/8
    r=200+Sin(b+i*15)*50
    p(i)\x=Cos(a)*r+300
    p(i)\y=Sin(a)*r+300
  Next
  p(8)=p(0); to close the curve
  
;   StartDrawing(CanvasOutput(0)):Box(0,0,600,600,$ff8888):StopDrawing() 
  
  StartVectorDrawing(CanvasVectorOutput(0))  
  VectorSourceColor($ffff8888)
  FillVectorOutput()
  addpathspline(p())
  VectorSourceColor($22ffffff):FillPath(#PB_Path_Preserve)
  VectorSourceColor($ff000000):StrokePath(2)
  For i=0 To 7:AddPathCircle(p(i)\x,p(i)\y,4):Next
  VectorSourceColor($ff0000ff):FillPath()
  StopVectorDrawing()
  Delay(32)
  event=WindowEvent()
Until event=#PB_Event_CloseWindow Or event=#PB_Event_DeactivateWindow

User avatar
pf shadoko
Enthusiast
Enthusiast
Posts: 421
Joined: Thu Jul 09, 2015 9:07 am

Re: AddPathSpline

Post by pf shadoko »

@Booger
Splines are used to create smooth curves using control points.
I think these optimizations are completely negligible compared to the graphics functions.

@Justin
Indeed.
I modified the code in my first post.
Booger
Enthusiast
Enthusiast
Posts: 147
Joined: Tue Sep 04, 2007 2:18 pm

Re: AddPathSpline

Post by Booger »

I get it. I was making things work in code before the Internet so i don't know anything. I'll ignore your posts from now on and won't share how bits and bytes work down below.
pjay
Enthusiast
Enthusiast
Posts: 281
Joined: Thu Mar 30, 2006 11:14 am

Re: AddPathSpline

Post by pjay »

@pf - Thanks for posting.
Booger wrote: Thu Oct 30, 2025 12:09 am I get it. I was making things work in code before the Internet so i don't know anything. I'll ignore your posts from now on and won't share how bits and bytes work down below.
Quite a disappointing attitude really; There are times that our age and past experiences can work against us.

The speed of modern CPUs means that lookup tables for most mathematical functions will often be slower than direct calculation.
Booger
Enthusiast
Enthusiast
Posts: 147
Joined: Tue Sep 04, 2007 2:18 pm

Re: AddPathSpline

Post by Booger »

That's today's youth's common assumption and that's why even as far as technology has advanced things are still clunky. I'll run some tests again and see if you're right.
User avatar
minimy
Enthusiast
Enthusiast
Posts: 695
Joined: Mon Jul 08, 2013 8:43 pm
Location: off world

Re: AddPathSpline

Post by minimy »

This form of transformation is very interesting.
I think pjay is correct, I did a test on an old and new computer and on the new one it is a really fast direct calculation, but on the old one the table is more or less the same speed (it is not very old compare with me, it is Atom, slow horse :lol: ) It may be that in a pentiun or 486 the table wins.
Anyway thanks for sharing code and knowledge.
Sorry for my English, I have it rusty today. :mrgreen:
If translation=Error: reply="Sorry, Im Spanish": Endif
pjay
Enthusiast
Enthusiast
Posts: 281
Joined: Thu Mar 30, 2006 11:14 am

Re: AddPathSpline

Post by pjay »

Booger wrote: Thu Oct 30, 2025 12:04 pm That's today's youth's common assumption and that's why even as far as technology has advanced things are still clunky. I'll run some tests again and see if you're right.
Don't get me wrong, I still use lookup tables for some computationally-heavy image processing (as it's doing millions of operations in one go & I knew the tables would remain in cache), but to use them in this particular case seems unnecessary.
Booger
Enthusiast
Enthusiast
Posts: 147
Joined: Tue Sep 04, 2007 2:18 pm

Re: AddPathSpline

Post by Booger »

I like opinions, but I still don't know a practical use for this, is this to be transitional button on a window. How many of these would be used in a practical simulation. Please tell me more.
pjay
Enthusiast
Enthusiast
Posts: 281
Joined: Thu Mar 30, 2006 11:14 am

Re: AddPathSpline

Post by pjay »

Users such as Idle & pf have recently been making large strides towards providing other PB users with the ability to use modern (motion) graphics in their projects. Rendering closed paths (such as the one pf has presented here) form an important part of this.

See what Idle is working on here: viewtopic.php?t=87734
Booger
Enthusiast
Enthusiast
Posts: 147
Joined: Tue Sep 04, 2007 2:18 pm

Re: AddPathSpline

Post by Booger »

I'll have a look when I have time to open a computer. Thanks. I'll have to buy 30 extra whiteboards to see what the idle advanced code works though. I'm a few brain cells short to just look at his code and interpret it's so advanced.
Little John
Addict
Addict
Posts: 4807
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: AddPathSpline

Post by Little John »

pf shadoko wrote: Wed Oct 29, 2025 10:43 pm @Justin
Indeed.
I modified the code in my first post.
Works fine here now, without flickering.
Thanks for sharing this code!
Booger
Enthusiast
Enthusiast
Posts: 147
Joined: Tue Sep 04, 2007 2:18 pm

Re: AddPathSpline

Post by Booger »

On the machine I tested that on it just like the cache, on two other machines it doesn't like the cache. Hit and miss, I eat my words and apologize.

Code: Select all

Structure Vector2:x.f:y.f:EndStructure

Procedure AddPathSpline(Array po.Vector2(1),Flags=0,roundness.f=0.5)
  Protected n,i,close,coef.f=roundness/3
  Protected.Vector2 b0,b1,b2,b3,c
  n=ArraySize(po())+2
  Dim p.Vector2(n)
  
  If flags=#PB_Path_Relative:c\x=PathCursorX():c\y=PathCursorY():EndIf
  
  For i=0 To n-2:p(i+1)\x=po(i)\x+c\x:p(i+1)\y=po(i)\y+c\y:Next
  If p(n-1)\X = p(1)\X And p(n-1)\Y = p(1)\Y:close=1: p(0) = p(n-2): p(n) = p(2):Else:p(0) = p(2):p(n) = p(n-2):EndIf
  MovePathCursor(p(1)\x,p(1)\y)
  For i=1 To n-2  
    b0=p(i)
    b3=p(i+1)
    b1\x=b0\x+coef*(b3\x-p(i-1)\x)
    b1\y=b0\y+coef*(b3\y-p(i-1)\y)
    b2\x=b3\x-coef*(p(i+2)\x-b0\x)
    b2\y=b3\y-coef*(p(i+2)\y-b0\y)
    AddPathCurve(b1\x,b1\y, b2\x,b2\y, b3\x,b3\y)  
  Next
  If close:ClosePath():EndIf
EndProcedure

Global Dim CosLookup.f(7)
Global Dim SinLookup.f(7)
Global Dim AngleOffset.f(7)

Procedure SetupTables()
  Protected i.i, a.f
  For i = 0 To 7
    a = i * 2 * #PI / 8
    CosLookup(i) = Cos(a)
    SinLookup(i) = Sin(a)
    AngleOffset(i) = i * 15
  Next
EndProcedure

Global Dim p.Vector2(8)
Global TestDuration.i = 5000 ; 5 seconds
Global.i event, i
Global.f b, r, a

; -------------------------------------------
; --- TEST 1: ORIGINAL MATH (Full Vector Draw)
; -------------------------------------------
Procedure.i RunOriginalTest()
  Protected Frames.i = 0
  Protected.q StartTime, EndTime, CurrentTime, Frequency
  
  QueryPerformanceFrequency_(@Frequency.q)
  QueryPerformanceCounter_(@StartTime.q)
  EndTime.q = StartTime + (TestDuration * Frequency / 1000)
  
  Repeat
    b + 0.04
    For i=0 To 7
      a.f=i*2*#PI/8
      r=200+Sin(b+i*15)*50
      p(i)\x=Cos(a)*r+300
      p(i)\y=Sin(a)*r+300
    Next
    p(8)=p(0)
    
    StartDrawing(CanvasOutput(0)):Box(0,0,600,400,$ff8888):StopDrawing()  
    StartVectorDrawing(CanvasVectorOutput(0))  
    AddPathSpline(p())
    VectorSourceColor($ff000000):StrokePath(2)
    StopVectorDrawing()
    
    Frames + 1
    event = WindowEvent()
    QueryPerformanceCounter_(@CurrentTime.q)
    
  Until CurrentTime > EndTime Or event = #PB_Event_CloseWindow
  ProcedureReturn Frames
EndProcedure

; -------------------------------------------
; --- TEST 2: OPTIMIZED MATH (Full Vector Draw)
; -------------------------------------------
Procedure.i RunOptimizedTest()
  Protected Frames.i = 0
  Protected.q StartTime, EndTime, CurrentTime, Frequency
  
  SetupTables()
  QueryPerformanceFrequency_(@Frequency.q)
  QueryPerformanceCounter_(@StartTime.q)
  EndTime.q = StartTime + (TestDuration * Frequency / 1000)
  
  Repeat
    b + 0.04
    For i=0 To 7
      r = 200 + Sin(b + AngleOffset(i)) * 50
      p(i)\x = CosLookup(i) * r + 300
      p(i)\y = SinLookup(i) * r + 300
    Next
    p(8)=p(0)
    
    StartDrawing(CanvasOutput(0)):Box(0,0,600,400,$ff8888):StopDrawing()  
    StartVectorDrawing(CanvasVectorOutput(0))  
    AddPathSpline(p())
    VectorSourceColor($ff000000):StrokePath(2)
    StopVectorDrawing()
    
    Frames + 1
    event = WindowEvent()
    QueryPerformanceCounter_(@CurrentTime.q)
    
  Until CurrentTime > EndTime Or event = #PB_Event_CloseWindow
  ProcedureReturn Frames
EndProcedure

; -------------------------------------------
; --- TEST 3: OPTIMIZED MATH (Minimal Draw)
; -------------------------------------------
Procedure.i RunMinimalOptimizedTest()
  Protected Frames.i = 0
  Protected.q StartTime, EndTime, CurrentTime, Frequency
  
  SetupTables()
  QueryPerformanceFrequency_(@Frequency.q)
  QueryPerformanceCounter_(@StartTime.q)
  EndTime.q = StartTime + (TestDuration * Frequency / 1000)
  
  Repeat
    b + 0.04
    For i=0 To 7
      r = 200 + Sin(b + AngleOffset(i)) * 50
      p(i)\x = CosLookup(i) * r + 300
      p(i)\y = SinLookup(i) * r + 300
    Next
    p(8)=p(0)
    
    StartDrawing(CanvasOutput(0))
      Box(0,0,600,400,$ff8888)
      FrontColor($ff0000ff)
      For i = 0 To 7
        Plot(p(i)\x, p(i)\y)
      Next
    StopDrawing()
    
    Frames + 1
    event = WindowEvent()
    QueryPerformanceCounter_(@CurrentTime.q)
    
  Until CurrentTime > EndTime Or event = #PB_Event_CloseWindow
  ProcedureReturn Frames
EndProcedure

; -------------------------------------------
; --- TEST 4: ORIGINAL MATH (Minimal Draw)
; -------------------------------------------
Procedure.i RunMinimalOriginalTest()
  Protected Frames.i = 0
  Protected.q StartTime, EndTime, CurrentTime, Frequency
  
  QueryPerformanceFrequency_(@Frequency.q)
  QueryPerformanceCounter_(@StartTime.q)
  EndTime.q = StartTime + (TestDuration * Frequency / 1000)
  
  Repeat
    b + 0.04
    For i=0 To 7
      a.f=i*2*#PI/8
      r=200+Sin(b+i*15)*50
      p(i)\x=Cos(a)*r+300
      p(i)\y=Sin(a)*r+300
    Next
    p(8)=p(0)
    
    StartDrawing(CanvasOutput(0))
      Box(0,0,600,400,$ff8888)
      FrontColor($ff0000ff)
      For i = 0 To 7
        Plot(p(i)\x, p(i)\y)
      Next
    StopDrawing()
    
    Frames + 1
    event = WindowEvent()
    QueryPerformanceCounter_(@CurrentTime.q)
    
  Until CurrentTime > EndTime Or event = #PB_Event_CloseWindow
  ProcedureReturn Frames
EndProcedure

; -------------------------------------------
; --- MAIN BENCHMARK
; -------------------------------------------

Procedure LogMessage(text.s)
  Protected ExistingText.s = GetGadgetText(1)
  SetGadgetText(1, ExistingText + text.s + #CRLF$)
EndProcedure

OpenWindow(0, 0, 0, 600, 600, "Benchmark", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 0, 0, 600, 400)
EditorGadget(1, 0, 400, 600, 200, #PB_Editor_ReadOnly)

CompilerIf #PB_Compiler_Debugger
  LogMessage("BENCHMARK DISABLED")
  LogMessage("")
  LogMessage("The PureBasic debugger is active.")
  LogMessage("Please create a standalone .exe and run the")
  LogMessage("file directly to get accurate performance results.")
CompilerElse
  LogMessage("--- RUNNING BENCHMARK (5 SECONDS EACH) ---")
  LogMessage(" ")
  
  Define OriginalFrames.i = RunOriginalTest()
  LogMessage("1. ORIGINAL MATH (Full Vector Draw):")
  LogMessage("   Total Frames: " + OriginalFrames)
  LogMessage("   Average FPS: " + StrF(OriginalFrames / (TestDuration / 1000)))
  
  Define OptimizedFrames.i = RunOptimizedTest()
  LogMessage(" ")
  LogMessage("2. OPTIMIZED MATH (Full Vector Draw):")
  LogMessage("   Total Frames: " + OptimizedFrames)
  LogMessage("   Average FPS: " + StrF(OptimizedFrames / (TestDuration / 1000)))
  
  Define MinimalFrames.i = RunMinimalOptimizedTest()
  LogMessage(" ")
  LogMessage("3. OPTIMIZED MATH (Minimal Draw):")
  LogMessage("   Total Frames: " + MinimalFrames)
  LogMessage("   Average FPS: " + StrF(MinimalFrames / (TestDuration / 1000)))
  
  Define MinimalOriginalFrames.i = RunMinimalOriginalTest()
  LogMessage(" ")
  LogMessage("4. ORIGINAL MATH (Minimal Draw):")
  LogMessage("   Total Frames: " + MinimalOriginalFrames)
  LogMessage("   Average FPS: " + StrF(MinimalOriginalFrames / (TestDuration / 1000)))
  
  LogMessage(" ")
  LogMessage("--- TEST COMPLETE ---")
  LogMessage("Benchmark finished. Close the window to exit.")
CompilerEndIf


Repeat
  event = WaitWindowEvent() 
Until event = #PB_Event_CloseWindow
Post Reply