# Procedural Lightning 2017 Script

You are about to reply to a thread that has been inactive for 2492 days.

Please take a moment to consider if this thread is worth bumping.

## Recommended Posts

I just finished this little project for my localized weather system and thought others could use it too. I wanted a 3D lightning that sprouts torward an object and noticed there wasn't any I could locate. I tried to place it on the wiki but it seems that I can't edit anything anywhere so not sure what that's all about. So I figured the forums would be a nice place to put it.

Example

Instructions

Create a ton of blocks and name them "segment".

Link them to a root prim of some sort and name it anything.

Drop this script into the root prim and away we go!

```//  2017 written by To-mos Codewarrior (tomos.halsey)
//  Procedural 3D lightning based on XNA tutorial
//  https://gamedevelopment.tutsplus.com/tutorials/how-to-generate-shockingly-good-2d-lightning-effects--gamedev-2681

//increment segment link number

//llOwnerSay("reached end of segments: "+(string)LINK_currentSeg);
return -2;
}

}
//generate and return prim parameters for each lightning segment
list placeSegment( vector v1, vector v2, float thickness ) {
//generate rotations between the vectors
vector offset = v1 - v2;
rotation rot  = llEuler2Rot(<0.0,llAtan2(-offset.z,offset.x),0.0>);
//localize offset to perspective of y rot
offset /= rot;

return [
PRIM_POS_LOCAL, ( v1 + v2 ) * 0.5,//solve for z rotation locally to y
PRIM_ROT_LOCAL, llEuler2Rot(<0.0,0.0,llAtan2(offset.y,offset.x)>)*rot,
PRIM_SIZE,      <llVecDist(v1, v2), thickness, thickness>
];
}
//dissect the parameters list into sections based on metadata and
//return the segment position based on percentage of whole bolt index range
vector getBoltPos( list primParams, integer boltNumber, float segmentPercent ) {
//first index metadata for bolts
if( llGetListEntryType( primParams, 0 ) != TYPE_STRING ) {
llOwnerSay("getBoltPos(): Supplied parameters have no metadata.");
return ZERO_VECTOR;
}

list boltMeta = llParseString2List( llList2String( primParams, 0 ), [","], [] );
if( boltNumber > llGetListLength( boltMeta ) - 1 ) {
llOwnerSay("getBoltPos(): Bolt index \"" + (string)boltNumber + "\" is out of range");
return ZERO_VECTOR;
}

string boltIndexRange = llList2String( boltMeta, boltNumber );
if( boltIndexRange == "" ) {
llOwnerSay("getBoltPos(): No metadata for bolt: \"" + boltIndexRange + "\"");
return ZERO_VECTOR;
}

//find delimiter and use it
integer splitIndex = llSubStringIndex( boltIndexRange, ":" );
//For those wondering, if the return value is -1, then ~-1 is 0 because -1 is a string of all 1 bits.
//Any value greater than or equal to zero will give a non-zero result.
if( ~splitIndex ) {
//start index
integer startIndex  = (integer)llGetSubString( boltIndexRange, 0, splitIndex-1 );
//end index
integer endIndex    = (integer)llGetSubString( boltIndexRange, splitIndex+1, -1 );
//get the range of the indices
integer indexRange  = endIndex - startIndex;
//grab an index based off a percentage llFloor
integer segmentIndex = (integer)( ( indexRange / 8.0 ) * segmentPercent );
//grab the segment chain representing a single bolt
//lets reuse the variable to prevent extra allocation
boltMeta = llList2List( primParams, startIndex, endIndex );
//return our located position
if( 8*segmentIndex+3 > llGetListLength( boltMeta )-1 ) {
llOwnerSay("getBoltPos(): Segment index \"" + (string)(8*segmentIndex+3) + "\" of bolt \"" + (string)boltNumber + "\" is out of range");
}else {
return llList2Vector( boltMeta, 8*segmentIndex+3);
}
}else {
llOwnerSay("getBoltPos(): Malformed metadata: \"" + boltIndexRange + "\"");
}

return ZERO_VECTOR;
}
//meat and potatoes of this script. This takes a start and end local position
//with a thickness and generates prim link params representing a single lightning bolt
list createBolt( vector source, vector dest, float thickness ) {
list   results    = [ 0.0 ];
vector tangent    = dest - source;
vector normal     = llVecNorm( tangent );
float  boltMag    = llVecMag( tangent );
float  Sway       = 5.0;//meters
float  Jaggedness = 1.0 / Sway;
integer i = 0;
integer points = llRound( boltMag / 3.0 );

if( points < 1.0 ) {
//llOwnerSay("Bolt not long enough: "+(string)points);
return [];
}

//random points between 0.0 (0%) to 1.0 (100%)
for( ;i < points; i++ )
results += [ llFrand( 1.0 ) ];

//now sort the points so we have randomness from A to B
results = llListSort( results, 1, TRUE );

//store length before adding on optional random points
integer length = llGetListLength( results );
vector prevPoint = source;
vector prevDisplacement = ZERO_VECTOR;
for ( i = 1; i < length; i++ ) {

//since we are set inside a range from the for loop
//the first few indices will work
float pos = llList2Float( results, i );

// used to prevent sharp angles by ensuring very close
//positions also have small perpendicular variation.
float scale = ( boltMag * Jaggedness ) * ( pos - llList2Float( results, i - 1 ) );

//great way to make circular bullet spray
//to all those weapon makers out there :)
float  sprayAng     = llFrand( TWO_PI );
float  sprayScale   = Sway + llFrand( -Sway*2.0 );
vector displacement = <llCos(sprayAng)*sprayScale, llSin(sprayAng)*sprayScale, 0.0>;
displacement.x -= (displacement.x - prevDisplacement.x) * (1.0 - scale);
displacement.y -= (displacement.y - prevDisplacement.y) * (1.0 - scale);

// defines an envelope. Points near the middle of the bolt can be further from the central line.
if( pos > 0.95 ) displacement *= 10.0 * (1.0 - pos);
//project a point from A to B then
//align the circular spray to the normal with a cross product
vector point = source + pos * tangent + displacement % normal;
//grab link and then increment it

if( linkNum != -2 ) {
results += [
]+placeSegment(prevPoint, point, thickness);
}

prevPoint = point;
prevDisplacement = displacement;
}
//clean up the list by removing the
//prefixed random points we no longer need
results = llDeleteSubList( results, 0, length-1 );
//grab link and then increment it
//concatenate last bolt params and return it
if( linkNum != -2 ) {
return results + [PRIM_LINK_TARGET, linkNum] + placeSegment(prevPoint, dest, thickness);
}else {
return results;
}
}
//generate one single bolt and then tack on a random amount
//based on a start and end pos and bolt thickness
list generateLightning( vector startingPoint, vector endingPoint, float thickness ) {
list boltParams = createBolt( startingPoint, endingPoint, thickness );
//tack on some metadata; index may start at 0 but since meta data
//is there don't -1 from get list length
string metaData = "1:"+(string)(llGetListLength(boltParams));
boltParams = [metaData] + boltParams;
list randNum = [];
//3 to 10 random bolts
integer numOfRandBolts = llRound( 10.0 - llFrand( 7.0 ) );
integer i = 0;
//random points between 0% to 100%
for( ;i < numOfRandBolts; i++ )
randNum += [ llFrand( 1.0 ) ];

//now sort the points so we have randomness from A to B
randNum = llListSort( randNum, 1, TRUE );
//build perspective to target we are striking
//so the 30 degrees is localized to that target
vector tangent = endingPoint - startingPoint;
//generate rotations between the vectors
rotation rotToAvi  = llEuler2Rot(<0.0,llAtan2(tangent.x,tangent.z),0.0>);
//localize offset to perspective of y rot
tangent /= rotToAvi;
//build us the final angle
rotToAvi = llEuler2Rot(<PI+llAtan2(-tangent.y,tangent.z),0.0,0.0>)*rotToAvi;
//generate each child bolt
for ( i=0; i < numOfRandBolts; i++ ) {
//read the bolt metadata and get a random segment pos
vector spawnPos = getBoltPos( boltParams, 0, llList2Float( randNum, i ) );
//use the scale of the overall bolt to
//parametrize the child lengths
//(this is a bit performance heavy so adjust accordingly)
float distance = llVecMag( endingPoint - spawnPos );
//rotate 30 degrees(in radians). Alternate between rotating left and right.
//generate child lightning from 30% to 70% length
vector posOut = spawnPos - <0.0, 0.0, distance> * (rotToAvi * llEuler2Rot( <llFrand(1.5708)-0.785398, llFrand(1.5708)-0.785398 ,0.0> ));
//grab the length before for start index and diff to end
integer beforeLen = llGetListLength(boltParams);
//comma and start index of thee bolt params
//actually generate and store the params for child bolts
boltParams += createBolt( spawnPos, posOut, thickness );
//add colon to separate data later and index of end of bolt params
metaData += ":"+(string)(beforeLen+(llGetListLength(boltParams) - beforeLen)-1);
//now store the altered metadata back onto the list
boltParams = [metaData] + llDeleteSubList( boltParams, 0, 0 );
}
//we are done with the metadata
return llDeleteSubList( boltParams, 0, 0 );
}

default {
state_entry() {
integer length = llGetNumberOfPrims();
for(; length > 1; length--) {
string name = llGetLinkName( length );
if( name == "segment" ) {
LINK_segments += [ length ];
}
}
vector aviPos = llList2Vector(llGetObjectDetails(llGetOwner(),[OBJECT_POS]),0);
vector localOffset = (aviPos - llGetRootPosition())/llGetRootRotation();
//list params = createBolt( ZERO_VECTOR, localOffset, 0.2 );
//typical bolt structure
//   PRIM_POS_LOCAL, <-9.312323, -9.211586, -38.696487>,
//   PRIM_ROT_LOCAL, <0.299013, -0.810049, -0.174666, 0.473185>,
//   PRIM_SIZE,      <2.527075, 0.100000, 0.100000>
list params = generateLightning( ZERO_VECTOR, localOffset, 0.1 );
//print the params for debug
/*length = llGetListLength(params)/8;
integer i = 0;
for( ; i<length; i++ ) {
llOwnerSay("param: "+llList2CSV( llList2List( params, i*8, i*8+7 ) ) );
}*/

llSetTimerEvent(4.0);
}
//make single bolt of lightning for testing
touch_start(integer total_number) {
vector aviPos = llDetectedPos(0);
vector localOffset = (aviPos - llGetRootPosition())/llGetRootRotation();
list params = createBolt( ZERO_VECTOR, localOffset, 0.2);
//list params = generateLightning( ZERO_VECTOR, localOffset, 0.1 );
}

timer() {
vector aviPos = llList2Vector(llGetObjectDetails(llGetOwner(),[OBJECT_POS]),0);

vector localOffset = (aviPos - llGetRootPosition())/llGetRootRotation();

list params = generateLightning( ZERO_VECTOR, localOffset, 0.1 );
}
}```

• 6
##### Share on other sites

That's awesome Tomos! Great to see.

Maybe hide the prims first for a moment (llSetLinkAlpha) so you can flicker them visible? Otherwise you see the prims moving due to client-side interpolation

##### Share on other sites

Thanks for trhe kind words

I wanted to leave the interpolation in so that people could add their own final polish on it. This is for other scripters to use as a base. I am using it also for tree roots and a dynamic pathway in a treehouse so lightning isn't the only thing it has to look like. My product version hides and then shows after they are constructed. Since the UV space on my bolts is really compact I scroll a texture and that causes the flashing but in this it's a technical concept to expand on.

• 1
##### Share on other sites

• 3 weeks later...

This is really great, I am just enjoying just watching the photo, especially the shadow, above

##### Share on other sites

You are about to reply to a thread that has been inactive for 2492 days.

Please take a moment to consider if this thread is worth bumping.