Spline3D - Module

Share your advanced PureBasic knowledge/code with the community.
User avatar
StarBootics
Addict
Addict
Posts: 1006
Joined: Sun Jul 07, 2013 11:35 am
Location: Canada

Spline3D - Module

Post by StarBootics »

Hello everyone,

This is fairly small 3D Spline module derivated from Guimauve's General Maths Matrix calculation.
For the moment, it's a Bezier spline implementation but I'm working on B-Spline curves but it's not working just yet.

By the way, this module reuse the Vector3 module from my Gaming Maths Library available here https://www.dropbox.com/s/ei1cckqn22r9b ... y.zip?dl=0

Edit : A little addition, the tangent vector along the Path. Obviously you have to use the same definition polygon and the same amount of segment when you call Spline3D::BezierPath(PolygoneBezier(), 40, Path()) and Spline3D::BezierTangent(PolygoneBezier(), 40, Tangent())

Best regards
StarBootics

Code: Select all

; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
; Project name : Spline3D - Module
; File Name : Spline3D - Module.pb
; File version: 0.0.2
; Programming : OK
; Programmed by : StarBootics
; Date : 12-10-2016
; Last Update : 13-10-2016
; PureBasic code : V5.50
; Platform : Windows, Linux, MacOS X
; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
; This code was excerpt of Guimauve's General Maths Matrix calculation library.
;
; http://www.purebasic.fr/english/viewtopic.php?f=12&t=47084
;
; Very nice lib by the way, that being said I just need the bezier spline 
; calculation. I will try to 3D BSpline curves as well.
;
; Please note, my Gaming Maths library is needed :
; https://www.dropbox.com/s/ei1cckqn22r9brh/Gaming_Maths_Library.zip?dl=0
;
; This code is free to be use where ever you like but you use it at your own
; risk.
;
; The author can in no way be held responsible for data loss, damage or other
; annoying situations that may occur.
;
; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

CompilerIf #PB_Compiler_IsMainFile
  
  IncludeFile "Vector3 - Module.pb"
  
CompilerEndIf

DeclareModule Spline3D
  
  Declare BezierPath(List DefinitionPolygon.Vector3::Vector3(), SegmentCount.l, List Path.Vector3::Vector3())
  Declare BezierTangent(List DefinitionPolygon.Vector3::Vector3(), SegmentCount.l, List Tangent.Vector3::Vector3())
  
EndDeclareModule

Module Spline3D
  
  Macro LinearlySpacedValue(IncrementID, IncrementMax, MinValue, MaxValue)
    
    ((MinValue) + ((MaxValue) - (MinValue)) * ((IncrementID) / (IncrementMax)))
    
  EndMacro
    
  Procedure.d BernsteinPolynomial(P_n.l, P_i.l, P_u.d) 
    
    ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    ; The Bernstein Polynom function are based on Combinations
    ; calculation routine from Dr. Dri (French Forum)
    
    If P_n >= 0 And P_i >= 0 
      
      Part.d = Pow(P_u, P_i) * Pow(1 - P_u, P_n - P_i)
      
      If P_n >= P_i
        
        Combinations.d = 1.0 
        
        If P_i > P_n >> 1 
          P_i = P_n - P_i
        EndIf 
        
        While P_i > 0 
          Combinations * P_n / P_i 
          P_n - 1 
          P_i - 1 
        Wend 
        
        If Combinations - Round(Combinations, 0) > 0.5 
          Combinations + 1 
        EndIf 
        
      EndIf 
      
    EndIf 
    
    ProcedureReturn Round(Combinations, 0) * Part
  EndProcedure 
  
  Procedure.d BernsteinPolynomialDerivate1st(P_n.l, P_i.l, P_u.d) 
    
    ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    ; The Bernstein Polynom First Derivate function are based on Combinations
    ; calculation routine from Dr. Dri (French Forum)
    
    If P_n >= 0 And P_i >= 0 
      
      Part.d = (P_i / P_u - (P_i - P_n) / (P_u - 1)) * Pow(P_u, P_i) * Pow(1 - P_u, P_n - P_i)
      
      If P_n >= P_i
        
        Combinations.d = 1.0 
        
        If P_i > P_n >> 1
          P_i = P_n - P_i
        EndIf 
        
        While P_i > 0 
          Combinations * P_n / P_i 
          P_n - 1 
          P_i - 1 
        Wend 
        
        If Combinations - Round(Combinations, 0) > 0.5 
          Combinations + 1 
        EndIf 
        
      EndIf 
      
    EndIf 
    
    ProcedureReturn Round(Combinations, 0) * Part
  EndProcedure 
  
  Procedure BezierPath(List DefinitionPolygon.Vector3::Vector3(), SegmentCount.l, List Path.Vector3::Vector3())
    
    ClearList(Path())
    
    P_n = ListSize(DefinitionPolygon()) - 1
    
    For U_ID = 0 To SegmentCount
      
      ForEach DefinitionPolygon()
        BP.d = BernsteinPolynomial(P_n, ListIndex(DefinitionPolygon()), LinearlySpacedValue(U_ID, SegmentCount, 0.0, 1.0))
        SumX.d = SumX + BP * Vector3::GetI(DefinitionPolygon())
        SumY.d = SumY + BP * Vector3::GetJ(DefinitionPolygon())
        SumZ.d = SumZ + BP * Vector3::GetK(DefinitionPolygon())
      Next
      
      AddElement(Path())
      Vector3::Update(Path(), SumX, SumY, SumZ)
      SumX = 0.0
      SumY = 0.0
      SumZ = 0.0
      
    Next
    
  EndProcedure
  
  Procedure BezierTangent(List DefinitionPolygon.Vector3::Vector3(), SegmentCount.l, List Tangent.Vector3::Vector3())
    
    ClearList(Tangent())
    
    P_n = ListSize(DefinitionPolygon()) - 1
    
    For U_ID = 0 To SegmentCount
      
      ForEach DefinitionPolygon()
        BPD1st.d = BernsteinPolynomialDerivate1st(P_n, ListIndex(DefinitionPolygon()), LinearlySpacedValue(U_ID, SegmentCount, 0.0, 1.0))
        SumXP.d = SumXP + BPD1st * Vector3::GetI(DefinitionPolygon())
        SumYP.d = SumYP + BPD1st * Vector3::GetJ(DefinitionPolygon())
        SumZP.d = SumZP + BPD1st * Vector3::GetK(DefinitionPolygon())
      Next
      
      AddElement(Tangent())
      
      If Not IsNAN(SumXP)  
        Vector3::Update(Tangent(), SumXP, SumYP, SumZP)
        Vector3::Unit(Tangent())
      Else
        Vector3::Zero(Tangent())
      EndIf
      
      SumXP = 0.0
      SumYP = 0.0
      SumZP = 0.0
      
    Next
    
    ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    ; Now you can call me a cheater if you wish but we know for a fact that
    ; the Bézier Spline is tangent at P0 with the definition polygon so we 
    ; are using this information to compute the tangent vector at P0. It's
    ; the same deal at PN.
    
    ; Let's select the first Tangent
    FirstElement(Tangent())
    
    FirstElement(DefinitionPolygon())
    Vector3::Copy(DefinitionPolygon(), P0.Vector3::Vector3)
    NextElement(DefinitionPolygon())
    Vector3::Copy(DefinitionPolygon(), P1.Vector3::Vector3)
    Vector3::Minus(Tangent(), P1, P0)
    Vector3::Unit(Tangent())
    
    ; Let's select the last Tangent
    LastElement(Tangent())
    
    LastElement(DefinitionPolygon())
    Vector3::Copy(DefinitionPolygon(), P4.Vector3::Vector3)
    PreviousElement(DefinitionPolygon())
    Vector3::Copy(DefinitionPolygon(), P3.Vector3::Vector3)
    Vector3::Minus(Tangent(), P4, P3)
    Vector3::Unit(Tangent())
    
  EndProcedure
  
EndModule

CompilerIf #PB_Compiler_IsMainFile

  NewList PolygoneBezier.Vector3::Vector3()
  NewList Path.Vector3::Vector3()
  
  AddElement(PolygoneBezier())
  Vector3::Update(PolygoneBezier(), 0,0,0)

  AddElement(PolygoneBezier())
  Vector3::Update(PolygoneBezier(), 15,9,4)

  AddElement(PolygoneBezier())
  Vector3::Update(PolygoneBezier(), 19,25,-20)

  AddElement(PolygoneBezier())
  Vector3::Update(PolygoneBezier(), 34,15,-13)
  
  AddElement(PolygoneBezier())
  Vector3::Update(PolygoneBezier(), 26,-5,2)

  AddElement(PolygoneBezier())
  Vector3::Update(PolygoneBezier(), 20,-10,-9)
  
  Spline3D::BezierPath(PolygoneBezier(), 40, Path())
  
  ; Lets create csv points files to load them up in Rhinoceros 3D to see the results
  
  If CreateFile(0, "Polygone Bezier 3D.csv")
    
    ForEach PolygoneBezier()
      WriteStringN(0, Chr(34) + StrD(Vector3::GetI(PolygoneBezier()), 6) + Chr(34) + ", " + Chr(34) + StrD(Vector3::GetJ(PolygoneBezier()), 6) + Chr(34)  + ", " + Chr(34) + StrD(Vector3::GetK(PolygoneBezier()), 6) + Chr(34) )
    Next
    
    CloseFile(0)
    
  EndIf
  
  If CreateFile(0, "Bezier Path 3D.csv")
    
    ForEach Path()
      WriteStringN(0, Chr(34) + StrD(Vector3::GetI(Path()), 6) + Chr(34) + ", " + Chr(34) + StrD(Vector3::GetJ(Path()), 6) + Chr(34)  + ", " + Chr(34) + StrD(Vector3::GetK(Path()), 6) + Chr(34) )
    Next
    
    CloseFile(0)
    
  EndIf
  
CompilerEndIf

; <<<<<<<<<<<<<<<<<<<<<<<
; <<<<< END OF FILE <<<<<
; <<<<<<<<<<<<<<<<<<<<<<<
The Stone Age did not end due to a shortage of stones !