Car Commercial – 8 – Lightning Solver in Greater Detail
November 2, 2017
I’ve definitely gone way to long without fully explaining my lightning system, so here we go.
A quick recap from this post, we get start points and end points sourced from the ground and car. We initialize some connections in basically the same way as seen in the solver but only on frame 1. Rand life is just as it is, just sets a random value for its current age. Note this is not setting how long it lives, but where it is in its life cycle so that we don’t get all the bolts connecting at the same time.
Inside we update the position of the start points, set the animation and reinitialize the connection of the lightning bolt if has reached the end of its life cycle, and then simply count the age up of each prim.
Like I said in my other post, one of the most challenging parts about this project was how to organize the data and keep everything in order. I struggled seriously with trying to find this out until I finally found 2 extremely useful VEX functions, findattribval() and idtopoint(). These were used with one another to find the id values of the primitives and points as I needed them and then use that id to find the current point or prim number. This may seem unnecessary, the number or prims and points never changes at all, when a connection has reached its end another is made in its place, the system is completely contained. The problem is when that connection is reset and a new point and prim are created, it loses its past point and prim number and gets shifted to the end of the group. With these connections being reset just about every single frame as well as multiple at a single time, if the correct primitives and points aren’t selected perfectly each time then one will get out of place. When one gets out of place the next prim assumes the system is perfect and selects a bad point or prim, and the system quickly devolves into chaos. So yeah, it’s really important.
So instead of breaking up my code into different snippets I am keeping it together in a single image for continuity. There are numbers 1-5 used to reference the different sections of the code as I discuss it. This is the code for the Set_Anim_And_Reset_Connection node.
(1) Create Line Function
This is a small one, but man its so annoying having to constantly write and rewrite this or comment it out, so I wrote a quick function for creating a line. Just give it two points and it will make the primitive. Ironically enough after saying all of this I end up not even using it because I need to set primitive attributes and I cant reference the prim variable when using this function. I’m sure I could work in a return argument or something, but I am not terribly familiar with writing functions in VEX, so I just manually did it.
(2) Set Variables and Parameters
In this section I set all of my variables and get other attributes and parameters necessary for later. Its very important to note that I’m using the for Begin Block node to fetch meta data about which iteration we are currently on. In doing so I can use this current iteration to select different prims. We then grab the correct prim and point numbers through the id workflow. A couple other parameters like how long the animation takes and how long the bolt actually stays connected for some fun and interesting control.
(3) Check and Set Animation Values
We are using the attribute ‘lifeCount’ to….. you guessed it, count the life of a lightning bolt. When this returns 0 in a modulus relationship with the animLength parameter, it means that it’s life is up and its time to reinitialize its connection. But in the meantime, it needs a value for ‘animControl’ the primitive value used later down the line to properly select a range of points and remove them to animate the connection. The first if statement compares the current animation frame (taken from the life%animLength) to a pretty dense function. Basically what the ceil((animLength-1)/2)+/-connectionLength returns is the middle of the animationLength (rounded up) with an int added on or subtracted to get a short range of frames in the middleish of the animationLength to make sure that there is a definite and direct connection. This length of direct connection is then controlled by the connectionLength parameter. We set the prim attribute to be .5 then, when animControl is .5, the bolts are fully connected.
Now the second part, what happens if its not in the middle? This little helpful function:
k being animLength and x being currentAnim
In my code it says animLength-1, this is incorrect, it should just be animLength. I’m happy I found that now! This function sets a perfect curve between 0-.5, the values needed for the animControl, over the length of out animLength. Some examples:
animLength = 1
animLength = 2
animLength = 3
animLength = 4
So now we don’t have to ever worry about the animControl again!
(4) Primitive Re-initialization
So when the prim’s life has run its full length so lifeCount%animLength == 0, its time to find another connection. To do so we will sample nearby points, randomize their values, and select one. I tried to randomize this as much as possible, hence the while loop. We grab an array of nearby points and get the length of that array. In this loop we have 3 very important variables, test, count, and range. Test either continues or breaks the loop and is tested every iteration. Count just simply counts which iteration we are on. Range plays into how we select the point to connect to. For each iteration we grab the current value in the array with the count variable. We add a random amount based on the currentIteration and take the random of all of that. I think that effectively creates a pretty random number. we then test if that random number is below the range amount. If it is we store that point number for later and break the loop. If it isn’t, we move on. We check if the next iteration of count is the same as the length of the array, effectively determining the end of our nearpoints[] array. So if we are at the end, then we reset the count to 0 to reset the loop and raise the range a little bit, allowing a larger range that the random number can be in. If its just another iteration, bump up count by one and lets run through again. I understand this is lie really inefficient and could be accomplished with getting the rand of each point and storing that in an array itself and then selecting from an array instead of rerunning the loop over and over again. That’s something I would love to put in but I don’t want to currently break the system since its all working fine and we are a bit crunched on time. it doesn’t significantly impact work times, so it stays. I also just wanted to use a while loop, I use too many for loops.
Why do 2 levels of randomization? If two start points that are near one another both share a nearby point that just happens to get under the range on the first pass, then that point will be selected for both of them and effectively have 2 bolts of lightning connecting to the same point. We don’t want that. So more layers of randomization helps.
(5) Attribute Declaration and Line Creation
So now we have the point number of the newly selected connection, so now we have to make sure everything has correct attributes for future selection and the primitive is correctly created. I first grab the primitive attributes that define the point ids that connect the prim together. So for each primitive there are 2 defined point ids that make up that connection, they are always the same. We grab these ids and define their current point numbers from them. Next we remove the old point and prim and create the new ones. We set the ground point to have the correct id and normal values. We also set both points to have a connectToPoint attribute that just says to which point id it is connected to. We restate the connectionEnd and connectionStart attribs on the newly created prim to make sure we can access these again in the future when these attributes need to be passed on again. We set id to the currentIteration of the for loop that this wrangle is in. Finally we reset the life count to 1 to start over its life cycle. I’ll be honest, I’m not completely sure if all of these attributes are needed, but they are all definitely useful for adding in different functionality later or just debugging exactly what is going wrong with the system at any point.
So. After all of that we finally move on to the age count.
Real simple at this point, just grab the correct primitive and set the lifeCount to be +1. Bang, it just got 1 frame older.
Finally we have an awesome way of creating primitives that are all procedurally animated and completely ready to become lightning. This system took something like 16 hours of straight work to get working correctly but it was so much fun to solve and I learned so much about the workflow as well as various structuring of attributes. So much cool stuff learned, I’m so excited to improve it. I’m still looking at moving the points a bit and I have the ground/end points somewhat moving but I need to make sure those positions are being updated in the solver. As for moving the start points I have been having quite a few problems with it so I might have to scarp that idea for the time being. If you made it to the bottom of this post, congratulations, you made it all the way through my solver! I’m sorry I wrote near 1700 words about procedurally creating lightning lines, but If you made it here I hope you enjoyed it as much as I did.