Posted: 23rd Jan 2016 7:48
This is an elaboration upon my last submission which performs arbitrary axis billboarding at a per-vertex level along the path of a cubic spline. This technique has many applications such as creating power lines, fluorescent light tubes, lightning bolts, ribbon trails, etc...



+ Code Snippet
// Example: Cubic Spline Ribbon Billboards via Direct Vertexbuffer Manipulation
// By: Jesse George (revenant_chaos@yahoo.com) Jan/2016
///////////////////////////////////////////////////////////////////////////////////
Set Display Mode 1024,768,32,0
Sync On : Sync Rate 0
color Backdrop RGB(14,19,25)
autocam off : move camera -1000
Fog On : Fog color 0
Fog distance (camera range far())
  
//custom Vec3 structure
    Type Vector3
       X as float
       Y as float
       Z as float
   endtype
  
  
//Spline Init
    `define the number of knots within the spline
    SplineKnotCount = 300
    `Global Spline array
    Global Dim SplineKnots(SplineKnotCount) as Vector3
  
    `generate random points for use as spline knots
    For i=1 To SplineKnotCount
        SplineKnots(i).X = Rnd(1500)-750
        SplineKnots(i).Y = Rnd(1500)-750
        SplineKnots(i).Z = Rnd(1500)-750
    Next i
  
  
//Billboard Init  
    `number of billboard segments (quads) per knot-pair (path between two knots)
    BillboardSegments = 24
    `get inverse of BillboardSegments to use for interpolation alpha step
    BillboardSegInc# = 1.0/BillboardSegments
    `Beam Width
    BillboardWidth# = 15.0
   
    `Prepare Billboard Object
    ObjID=1
    Make Object Plane ObjID,1,1,1,BillboardSegments*(SplineKnotCount-1) `Matrix1Utils version for multi-segment planes
    `disable the object's culling and lighting
    Set Object Radius ObjID,0 `object bounds are not updated after vertexbuffer manipulation
    set object light ObjID,0 `vertex normals are not transformed
  
    `obtain a pointer to the object's vertexbuffer
    Lock Vertexdata for limb ObjID,0
        sMeshPtr as dword
        sMeshPtr = get vertexdata ptr()
        FVFsize = peek dword( sMeshPtr + 8)
        VBufferPtr as dword
        VBufferPtr = peek dword( sMeshPtr + 20)
    Unlock Vertexdata
  
  
//Init Vectors
    `Vectors for matrix assembly
    Tangent as Vector3
    Binormal as Vector3
    Normal as Vector3
    `Vectors for cubic interpolation
    AnchorA as Vector3
    Origin as Vector3
    Destination as Vector3
    AnchorD as Vector3
    `Vectors for billboard segment end-points
    SegmentOrig as Vector3
    SegmentDest as Vector3
  
  
Ink RGB(192,128,0)
do
    set cursor 0,0
     print screen fps()
    Print "Use the Mouse and Arrowkeys to navigate"
    Print "Use the Mousewheel to adjust billboard width"
    Print "Press 'A' to toggle additive alpha blending"
    Print "Press 'W' to toggle wireframe"
  
   
    `Camera navigation
    move camera (upkey()-downkey())*4
    move camera right (rightkey()-leftkey())*4 `Matrix1Utils
    rotate camera wrapvalue(camera angle X()+(mousemoveY()*0.2)),wrapvalue(camera angle Y()+(mousemoveX()*0.2)),0
  
  
    `Toggle wireframe mode using "W" key
    Wkey = keystate(17)
    if Wkey > 0 and Wkey <> old_Wkey
        ObjWireFrame = 1 - ObjWireFrame
        set Object Wireframe ObjID,ObjWireFrame
    endif
    old_Wkey = Wkey
      
    `Toggle additive alphablending using "A" key
    Akey = keystate(30)
    if Akey > 0 and Akey <> old_Akey
        ObjAlphaBlend = 1 - ObjAlphaBlend
        if ObjAlphaBlend
            ghost object on ObjID,2
        else
            ghost object off ObjID
        endif 
    endif
    old_Akey = Akey
  
    `Adjust beam width using mousewheel
    MMZ = MouseMoveZ()
    if MMZ
        if MMZ>0
            Inc BillboardWidth#
        else
            Dec BillboardWidth#
        endif
    endif
 
 
  
//Billboard Spline Update
    `store camera position within floats to avoid the overhead of repeatedly calling (intrinsic) functions.
    CamPosX# = camera position X()
    CamPosY# = camera position Y()
    CamPosZ# = camera position Z()
 
    `set vertex pointer to beginning of vertex buffer
    CurVtxPtr = VBufferPtr
      
    `loop through each spline knot-pair
    For Knot=1 To SplineKnotCount-1
//Get interpolation control points   
        Origin.X = SplineKnots(Knot).X
        Origin.Y = SplineKnots(Knot).Y
        Origin.Z = SplineKnots(Knot).Z
      `if is first knot
        If Knot=1
            `copy AnchorA from origin
            AnchorA.X = Origin.X
            AnchorA.Y = Origin.Y
            AnchorA.Z = Origin.Z
        Else
            `use knot from before the origin
            AnchorA.X = SplineKnots(Knot-1).X
            AnchorA.Y = SplineKnots(Knot-1).Y
            AnchorA.Z = SplineKnots(Knot-1).Z
        EndIf
       
        `use next knot as destination
        Destination.X = SplineKnots(Knot+1).X
        Destination.Y = SplineKnots(Knot+1).Y
        Destination.Z = SplineKnots(Knot+1).Z
          
        `if this is the last knot-pair
        If Knot=SplineKnotCount-1
            `copy AnchorD from destination
            AnchorD.X = Destination.X
            AnchorD.Y = Destination.Y
            AnchorD.Z = Destination.Z
        Else
            `use knot from after destination
            AnchorD.X = SplineKnots(Knot+2).X
            AnchorD.Y = SplineKnots(Knot+2).Y
            AnchorD.Z = SplineKnots(Knot+2).Z
        EndIf
  
        `reset interpolation alpha
        Alpha#=0.0
        `loop through billboard segments for current spline knot-pair
        For i=1 To BillboardSegments+1
  
            `increment interpolation alpha
            Alpha# = Alpha# + BillboardSegInc#
  
//Calculate the current segment's end-points
            `if is the first segment
            If i=1
                `just use the knot's position as segment origin
                SegmentOrig.X = Origin.X
                SegmentOrig.Y = Origin.Y
                SegmentOrig.Z = Origin.Z
            Else
                `reuse old segment destination as new segment origin
                SegmentOrig.X = SegmentDest.X
                SegmentOrig.Y = SegmentDest.Y
                SegmentOrig.Z = SegmentDest.Z
            EndIf
  
            `if alpha value is below 1.0
            If i<BillboardSegments
                `Use cubic interpolation to calculate the current segment's destination coordinate
                AlphaSq# = Alpha#*Alpha#
                Tmp# = (AnchorD.X - Destination.X - AnchorA.X + Origin.X)
                SegmentDest.X = ((Tmp#*Alpha#*AlphaSq#)+((AnchorA.X-Origin.X-Tmp#)*AlphaSq#)+((Destination.X-AnchorA.X)*Alpha#)+Origin.X)
                Tmp# = (AnchorD.Y - Destination.Y - AnchorA.Y + Origin.Y)
                SegmentDest.Y = ((Tmp#*Alpha#*AlphaSq#)+((AnchorA.Y-Origin.Y-Tmp#)*AlphaSq#)+((Destination.Y-AnchorA.Y)*Alpha#)+Origin.Y)
                Tmp# = (AnchorD.Z - Destination.Z - AnchorA.Z + Origin.Z)
                SegmentDest.Z = ((Tmp#*Alpha#*AlphaSq#)+((AnchorA.Z-Origin.Z-Tmp#)*AlphaSq#)+((Destination.Z-AnchorA.Z)*Alpha#)+Origin.Z)
            Else
                `Use linear interpolation to extrapolate a point beyond the knot-pair's destination
                SegmentDest.X = (Origin.X + (Alpha#*(Destination.X-Origin.X)))
                SegmentDest.Y = (Origin.Y + (Alpha#*(Destination.Y-Origin.Y)))
                SegmentDest.Z = (Origin.Z + (Alpha#*(Destination.Z-Origin.Z)))
            EndIf
  
//Construct a transformation matrix for the current billboard segment
            `calculate the segment's direction vector
            Tangent.X = SegmentDest.X - SegmentOrig.X
            Tangent.Y = SegmentDest.Y - SegmentOrig.Y
            Tangent.Z = SegmentDest.Z - SegmentOrig.Z
       
            `get a vector from the segment origin to the camera
            Binormal.X = SegmentOrig.X - CamPosX#
            Binormal.Y = SegmentOrig.Y - CamPosY#
            Binormal.Z = SegmentOrig.Z - CamPosZ#
       
            `Normal = Tangent <cross> Binormal
            Normal.X = (Tangent.Y*Binormal.Z) - (Tangent.Z*Binormal.Y)
            Normal.Y = (Tangent.Z*Binormal.X) - (Tangent.X*Binormal.Z)
            Normal.Z = (Tangent.X*Binormal.Y) - (Tangent.Y*Binormal.X)
       
            `scale normal vector's length to match the desired beam width
            Tmp# = (((Normal.X*Normal.X)+(Normal.Y*Normal.Y)+(Normal.Z*Normal.Z))^0.5)/BillboardWidth#
            Normal.X = Normal.X / Tmp#
            Normal.Y = Normal.Y / Tmp#
            Normal.Z = Normal.Z / Tmp#
  
//Calculate and write transformed vertex positions to the vtxbuffer
            `calculate vertex position
            TmpVtxPosX# = (0.5 * Normal.X) + SegmentOrig.X
            TmpVtxPosY# = (0.5 * Normal.Y) + SegmentOrig.Y
            TmpVtxPosZ# = (0.5 * Normal.Z) + SegmentOrig.Z
  
            `write position to the vertexbuffer
            *CurVtxPtr = TmpVtxPosX# `CurVtxPtr already points to PosX within the vtxbuffer
            TmpPtr = CurVtxPtr+4    `get pointer to PosY
            *TmpPtr = TmpVtxPosY#
            TmpPtr = TmpPtr+4       `get pointer to PosZ
            *TmpPtr = TmpVtxPosZ#
              
            `advance pointer to the next vertex
            CurVtxPtr = CurVtxPtr + FVFsize
            `write position to the vertexbuffer (calculated by mirroring last vertex coord around segment's origin)
            *CurVtxPtr = (SegmentOrig.X - TmpVtxPosX#) + SegmentOrig.X
            TmpPtr = CurVtxPtr+4
            *TmpPtr = (SegmentOrig.Y - TmpVtxPosY#) + SegmentOrig.Y
            TmpPtr = TmpPtr+4
            *TmpPtr = (SegmentOrig.Z - TmpVtxPosZ#) + SegmentOrig.Z
  
            `advance pointer to the next vertex in preparation for the next loop
            CurVtxPtr = CurVtxPtr + FVFsize
        Next i
          
          `move pointer back two verts, they wind up being transformed again by the next curve...
        CurVtxPtr = CurVtxPtr - (FVFsize*2)
    Next Knot
  
//lock and unlock to apply changes made to the object's vertex buffer
    lock vertexdata for limb ObjID,0 : unlock vertexdata
  
    Sync
loop