Lightning class in Mogre

Roy Berube

18-08-2008 07:06:06

I've been playing around with windows forms and put together a lightning class in c#. The basic idea is from a lightning class in Unity. I have more ideas for it yet but hope someone might find this useful.

This demonstrates the use of billboardchains. It generates a chain of billboards between 2 points. There is an unseen chain that creates the large amount of randomness near the middle of the lightning chain. I've tried to use simple math to speed up the calculations. Precision is sacrificed but it doesn't matter.

It is implemented as a fire and forget class, and needs just one line in the main code to start it. Currently it uses the windows forms timer so it has to be used with a windows form.

Here is the line to instantiate it:
LightningBolt mBolt3 = new LightningBolt(ref mgr, new Vector3(100f, 0f, 50f),new Vector3(250f, 500f, -50f), 9500);

Here is a screenshot. Multiple bolts can be fired off at once with no problems.



And here is the code. 2 classes at this point:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using Mogre;

namespace WinFormsMogre
{
//Adapted from unity demo. The class is self contained and generates the bolt and lights on its own. It will need to
//know the SceneManager to attach these entities to. Timer can currently only run in a windows form.
//Perlin noise class could be pulled into this class for simplicity.
public class LightningBolt
{
private const uint zigs = 50; //number of joints in the highest visible bolt. Multiple of baseZigs REQUIRED.
private const uint baseZigs = 5;
private float speed = .05f; // How fast the bolt will change over time. Lower = slower.
private float scale = 12f; //12f
private Light startLight;
private ColourValue mColor;
private Vector3 sPos;
private Vector3 ePos;
private Vector3 boltDir; //Bolt direction
private SceneManager mgrL;
private Perlin noise;
private static uint counter = 0;//to avoid duplication of names. Increments each time class is instantiated.
private float oneOverZigs;
private float mDuration; //Duration of the bolt
private float time; //current lifetime of the bolt
//System.Threading.Timer t;
private System.Windows.Forms.Timer fTimer; //Single thread timer-safe with Ogre.
private BillboardChain mBB;
private Mogre.SceneNode mNode;
private Vector3 offset,positionBak,position2,position3,position4,position5;//,PosReturn;
private float timex, timey, timez;
private float bloat;
/// <summary>
/// LightningBolt needs to run in a windows form - otherwise change the timer.
/// Duration is in milliseconds.
/// </summary>
/// <param name="mgr"></param>
/// <param name="StartPos"></param>
/// <param name="EndPos"></param>
/// <param name="Duration"></param>
public LightningBolt(ref SceneManager mgr, Vector3 StartPos, Vector3 EndPos, uint Duration)
{
LightningBolt.counter++; // To avoid duplicate names.
mgrL = mgr; //SceneManager reference
time = 10; // undesirable to start at time = 1
mDuration = Duration + time;
oneOverZigs = 1f / (float)zigs;
sPos = StartPos;
ePos = EndPos;
boltDir = ePos - sPos;
scale = boltDir.Length / 55f; //scale zig distortion according to length of bolt
boltDir.Normalise();
//LogManager.Singleton.LogMessage("BoltDir= "+ boltDir.x+boltDir.y+boltDir.z);
//create light
if (startLight == null)
{
//Avoid duplicated naming of entities or crashes will ensue
startLight = mgrL.CreateLight("LightningStart" + counter);
startLight.Type = Light.LightTypes.LT_POINT;
startLight.Position = lerp(sPos, ePos, 0.5f);
startLight.DiffuseColour = new ColourValue(1, 1, 1);
startLight.SpecularColour = new ColourValue(1, 1, 1);
}
mNode = new SceneNode(mgrL);
try
{
Setup();
}
catch
{
LogManager.Singleton.LogMessage("Error in lightning.setup");
}
fTimer = new System.Windows.Forms.Timer();
fTimer.Tick += new EventHandler(Update);
fTimer.Interval = 1; // Update never gets called directly.
fTimer.Start();
}
private void Setup()
{
if (noise == null)
noise = new Perlin();
mColor = new ColourValue(.7f, .7f, 1f); // Lightning color
if (mBB == null)
{
mBB = mgrL.CreateBillboardChain("LightBB" + counter);
mBB.NumberOfChains = 3; //base invisible and then visible.
mBB.MaxChainElements = zigs; //longest chain
//mBB.MaterialName = "Roys/Lightning"; //Similar effect to FlarePointSprite
mBB.MaterialName = "Examples/FlarePointSprite"; //no shader is used as of yet
mBB.Visible = true;
mNode = mgrL.RootSceneNode.CreateChildSceneNode();
mNode.AttachObject(mBB); //attach to a node.
}
}

/// <summary>
/// Lerp finds the position between 2 Vector3 points given the ratio a.
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
/// <param name="a"></param>
/// <returns></returns>
private Vector3 lerp(Vector3 start, Vector3 end, float a)
{
//lerp(x, y, a) = x + (y - x) * a
return (start + ((end - start) * a));
}
private void ShutDown()
{
fTimer.Dispose();
LogManager.Singleton.LogMessage("ShutDown in Lightning fired");
startLight.Visible = false;
startLight.Dispose();
startLight = null;
mBB.ClearAllChains();
mBB.Dispose();
mBB = null;

}
private void adjustLight(float v)
{
startLight.SetDiffuseColour(v, v, v);
//endLight.SetDiffuseColour(v, v, v);
startLight.SetSpecularColour(v, v, v);
//endLight.SetDiffuseColour(v, v, v);
}
/// <summary>
/// Calculates a lightning bolt. Allows center bulging. Can be invisible.
/// </summary>
/// <param name="ratio"> How far along the section. Used for texture placement.</param>
/// <param name="chain"> Which billboardset to place the chain into</param>
/// <param name="scaler"> Noise multiplier.</param>
/// <param name="bulger"> .5f = middle is noisy and ends are stable. Higher value for noiser ends.</param>
/// <param name="visible"></param>
/// <param name="position"></param>
/// <returns></returns>
private Vector3 calcBolt(float ratio, uint chain, float scaler,float bulger,bool visible, Vector3 position)
{

//float bloat = oneOverZigs * (float)i;
//float bloat = .25f * (float)i;

//returns in the range of -1 to 1
offset = new Vector3(noise.Noise2(timex + position.x, timex + position.y, timex + position.z,counter),
noise.Noise2(timey + position.x, timey + position.y, timey + position.z,counter),
noise.Noise2(timez + position.x, timez + position.y, timez + position.z,counter));
//LogManager.Singleton.LogMessage("offset = " + offset.x + " " + offset.y + " " + offset.z);
//eliminate random folds in direction of travel : boltDir
offset.x = offset.x * (Mogre.Math.Abs(boltDir.x) - 1);
offset.y = offset.y * (Mogre.Math.Abs(boltDir.y) - 1);
offset.z = offset.z * (Mogre.Math.Abs(boltDir.z) - 1);
//increase randomness in the middle and clamp at ends
offset.x = offset.x * (Mogre.Math.Abs(ratio - .5f) - bulger);//the last value -> .5 = stable ends. Larger = more random overall.
offset.y = offset.y * (Mogre.Math.Abs(ratio - .5f) - bulger);
offset.z = offset.z * (Mogre.Math.Abs(ratio - .5f) - bulger);

position += (offset * scaler);
if (visible == false)
{ return position; }
BillboardChain.Element_NativePtr bbPtr = BillboardChain.Element_NativePtr.Create(position, 8, ratio, mColor);
mBB.AddChainElement(chain, bbPtr); //first variable is which chain to inject the element into.
return position;
}
/// <summary>
/// Calculate and diplay the higher frequency chains of the lightning bolt
/// </summary>
/// <param name="ratio"></param>
/// <param name="chain"></param>
/// <param name="scaler"></param>
/// <param name="position"></param>
/// <returns></returns>
private Vector3 calcBolt2(float ratio, uint chain, float scaler, Vector3 position, uint aSeed )
{

//float bloat = oneOverZigs * (float)i;
//float bloat = .25f * (float)i;

//returns in the range of -1 to 1
offset = new Vector3(noise.Noise2(timex + position.x, timex + position.y, timex + position.z,aSeed),
noise.Noise2(timey + position.x, timey + position.y, timey + position.z,aSeed),
noise.Noise2(timez + position.x, timez + position.y, timez + position.z,aSeed));
//LogManager.Singleton.LogMessage("offset = " + offset.x + " " + offset.y + " " + offset.z);
//eliminate random folds in direction of travel : boltDir
offset.x = offset.x * (Mogre.Math.Abs(boltDir.x) - 1);
offset.y = offset.y * (Mogre.Math.Abs(boltDir.y) - 1);
offset.z = offset.z * (Mogre.Math.Abs(boltDir.z) - 1);
position += (offset * scaler);
BillboardChain.Element_NativePtr bbPtr = BillboardChain.Element_NativePtr.Create(position, 8, ratio, mColor);
mBB.AddChainElement(chain, bbPtr); //first variable is which chain to inject the element into.
return position;
}
private void Update(object sender, EventArgs e) //system.windows.forms version
{
timex = time * speed * 0.1365143f; //Time must start reasonably small for the perlin to work nicely
timey = time * speed * 1.21688f;
timez = time * speed * 2.5564f;
//position, positionBak, position2;
offset = new Vector3();
mBB.ClearAllChains();
positionBak = sPos;
float zigDiv = 1f/ (float)baseZigs; //save the loop from doing division
position2 = calcBolt(0, 0, scale * 16, .5f, false, sPos);//first loop is wasted on inner loops calculated for nothing
for (uint i = 1; i <= baseZigs; i++)
{
bloat = zigDiv * (float)i;
position2 = lerp(sPos, ePos, bloat);
position3 = calcBolt(bloat,0, scale * 16, .5f, false ,position2);//don't want to see this one at a low res
for (uint n = 0; n < (zigs/baseZigs); n++)
{
float ratio1 = (float)baseZigs * oneOverZigs * (float)n;
position4 = lerp(positionBak, position3, ratio1);
float ratio2 = bloat - zigDiv + ( ratio1 * zigDiv );
position5 = calcBolt2(ratio2, 1, scale * 1.5f, position4, counter + 5);
position5 = calcBolt2(ratio2, 2, scale * 1.5f, position4 , counter + 6);
}
positionBak = position3;
}
position5 = calcBolt2(1, 1, scale * 1.5f, position3,counter + 5); //final calculation because the loop skips ratio of 1
position5 = calcBolt2(1, 2, scale * 1.5f, position3, counter + 6);
if (time >= mDuration)
{
ShutDown();
}
else
{
//adjustLight(offset.Length);
time += 60; //kludge number for now.
fTimer.Interval = 60; //Temporary kludge
}
}
}
}

using System;
using System.Collections.Generic;
using System.Text;

namespace WinFormsMogre
{
//adapted from http://www.gamedev.net/community/forums/topic.asp?topic_id=502657
/// <summary>
/// A fast implementation of 3d perlin noise. Accuracy is sacrificed for speed.
/// </summary>
public class Perlin
{
private const int XNoiseGen = 1619;
private const int YNoiseGen = 31337;
private const int ZNoiseGen = 6971;
private const int SeedNoiseGen = 1013;
private const int ShiftNoiseGen = 8;
private int seed = 5; //change value to affect randomization

public float Noise(float x, float y, float z, uint newSeed)
{
// All constants are primes and must remain prime in order for this noise
// function to work correctly.
// This has to be done in integers. This function is not working properly.
seed = (int) newSeed;
int mx = (int)x;
int my = (int)y;
int mz = (int)z;
int n = (
XNoiseGen * mx
+ YNoiseGen * my
+ ZNoiseGen * mz
+ SeedNoiseGen * seed)
& 0x7fffffff;
n = (n >> 13) ^ n; //n = (n >> 13) ^ n;
return (float)(1-((n * (n * n * 60493 + 19990303) + 1376312589)) & 0x7fffffff);
}

//This function is working ok. The output is from -1 to 1
public float Noise2(float x, float y, float z)
{
int mx = (int)x;
int my = (int)y;
int mz = (int)z;
int n = (
XNoiseGen * mx
+ YNoiseGen * my
+ ZNoiseGen * mz
+ SeedNoiseGen * seed)
& 0x7fffffff;

n = (n<<13) ^ n;
float rn = (float)( 1.0 - (float)( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824);
return rn;
}
public float Noise2(float x, float y, float z, uint newSeed)
{
seed = (int) newSeed;
int mx = (int)x;
int my = (int)y;
int mz = (int)z;
int n = (
XNoiseGen * mx
+ YNoiseGen * my
+ ZNoiseGen * mz
+ SeedNoiseGen * seed)
& 0x7fffffff;

n = (n << 13) ^ n;
float rn = (float)(1.0 - (float)((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824);
return rn;


}

}
}

Beauty

23-08-2008 17:49:32

It sounds interesting. The picture is currently not available.
I think it would be a good idea to add this code snippit to the Wiki.
The picture also can be uploaded there.
Then it can be found better in the future and updates are easy.