Jump to content

Procedural Lightning 2017 Script


Tomos Halsey
 Share

You are about to reply to a thread that has been inactive for 2060 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

integer LINK_currentSeg;
list    LINK_segments;

//increment segment link number
integer incLink() {
    integer returnLink = llList2Integer(LINK_segments, LINK_currentSeg);
    LINK_currentSeg++;
    
    if( LINK_currentSeg > llGetListLength(LINK_segments) ) {
        //llOwnerSay("reached end of segments: "+(string)LINK_currentSeg);
        LINK_currentSeg = llGetListLength(LINK_segments)-1;
        return -2;
    }
        
    return returnLink;
}
//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;
    integer linkNum;
    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
        linkNum = incLink();
        
        if( linkNum != -2 ) {
            results += [
                PRIM_LINK_TARGET, linkNum
            ]+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
    linkNum = incLink();
    //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 ) {
    LINK_currentSeg = 0;
    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
        metaData += ","+(string)beforeLen;
        //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 ];
            }
            llSetLinkPrimitiveParamsFast(length,[PRIM_COLOR, ALL_SIDES, <1.0,1.0,1.0>, 1.0,PRIM_SIZE,<0.2,0.2,0.2>,PRIM_LINK_TARGET,LINK_THIS,PRIM_TEXT,(string)(llGetFreeMemory()*0.000977)+"kb",<1.0,1.0,1.0>,1.0]);
        }        
        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_LINK_TARGET, linkNumOfBoltSegment,
        //   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 ) ) );
        }*/
        
        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_TEXT,(string)(llGetFreeMemory()*0.000977)+"KB",<1.0,1.0,1.0>,1.0]+params);
        
        llSetTimerEvent(4.0);
    }
    //make single bolt of lightning for testing
    touch_start(integer total_number) {
        LINK_currentSeg = 0;
        llSetLinkPrimitiveParamsFast(LINK_ALL_CHILDREN,[PRIM_POS_LOCAL,ZERO_VECTOR,PRIM_SIZE,<0.2,0.2,0.2>]);
        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 );
        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_TEXT,(string)(llGetFreeMemory()*0.000977)+"KB",<1.0,1.0,1.0>,1.0]+params);
    }
    
    timer() {
        llSetLinkPrimitiveParamsFast(LINK_ALL_CHILDREN,[PRIM_POS_LOCAL,ZERO_VECTOR,PRIM_SIZE,<0.2,0.2,0.2>]);
        vector aviPos = llList2Vector(llGetObjectDetails(llGetOwner(),[OBJECT_POS]),0);
        
        vector localOffset = (aviPos - llGetRootPosition())/llGetRootRotation();        
        
        list params = generateLightning( ZERO_VECTOR, localOffset, 0.1 );
        llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_TEXT,(string)(llGetFreeMemory()*0.000977)+"KB",<1.0,1.0,1.0>,1.0]+params);
    }
}

 

 

  • Like 6
Link to comment
Share on other sites

Thanks for trhe kind words :D

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.

  • Like 1
Link to comment
Share on other sites

  • 3 weeks later...
You are about to reply to a thread that has been inactive for 2060 days.

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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...