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...
// 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