Weapon spread
Hey, I'm trying to understand the weapon spread code but it's been ages since I've done any trigonometry. Plus I just started learning lua a few days ago.
Here is the weapon spread code from WeaponUtility.lua:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function CalculateSpread(directionCoords, spreadAmount, randomizer)
local spreadAngle = spreadAmount / 2
local randomAngle = randomizer() * math.pi * 2
local randomRadius = randomizer() * randomizer() * math.tan(spreadAngle)
local spreadDirection = directionCoords.zAxis +
(directionCoords.xAxis * math.cos(randomAngle) +
directionCoords.yAxis * math.sin(randomAngle)) * randomRadius
spreadDirection:Normalize()
return spreadDirection
end<!--c2--></div><!--ec2-->
Here is some source engine C++ code that I think serves the same function that I am comparing it to:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> float x, y;
do
{
x = random->RandomFloat( -0.5, 0.5 ) + random->RandomFloat( -0.5, 0.5 );
y = random->RandomFloat( -0.5, 0.5 ) + random->RandomFloat( -0.5, 0.5 );
} while ( (x * x + y * y) > 1.0f );
Vector vecForward, vecRight, vecUp;
AngleVectors( vAngles, &vecForward, &vecRight, &vecUp );
Vector vecDirShooting = vecForward +
x * flSpread * vecRight +
y * flSpread * vecUp;
vecDirShooting.NormalizeInPlace();<!--c2--></div><!--ec2-->
I don't understand the need for both randomAngle and randomRadius. In source flSpread is just a constant value taken from the weapon, but in spark you take the tan of the spreadAmount divide it by 2 and then multiply it with 2 random numbers. What is the purpose of this?
Also, what is the purpose of taking the cos and sin of randomAngle? Is that just to get a random number between -1 and 1?
[EDIT] I guess the source engine is just calculating spread within a box around your crosshair rather than a circle. I'm just trying to see what the equivalent values would be for spread in spark.
Here is the weapon spread code from WeaponUtility.lua:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function CalculateSpread(directionCoords, spreadAmount, randomizer)
local spreadAngle = spreadAmount / 2
local randomAngle = randomizer() * math.pi * 2
local randomRadius = randomizer() * randomizer() * math.tan(spreadAngle)
local spreadDirection = directionCoords.zAxis +
(directionCoords.xAxis * math.cos(randomAngle) +
directionCoords.yAxis * math.sin(randomAngle)) * randomRadius
spreadDirection:Normalize()
return spreadDirection
end<!--c2--></div><!--ec2-->
Here is some source engine C++ code that I think serves the same function that I am comparing it to:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> float x, y;
do
{
x = random->RandomFloat( -0.5, 0.5 ) + random->RandomFloat( -0.5, 0.5 );
y = random->RandomFloat( -0.5, 0.5 ) + random->RandomFloat( -0.5, 0.5 );
} while ( (x * x + y * y) > 1.0f );
Vector vecForward, vecRight, vecUp;
AngleVectors( vAngles, &vecForward, &vecRight, &vecUp );
Vector vecDirShooting = vecForward +
x * flSpread * vecRight +
y * flSpread * vecUp;
vecDirShooting.NormalizeInPlace();<!--c2--></div><!--ec2-->
I don't understand the need for both randomAngle and randomRadius. In source flSpread is just a constant value taken from the weapon, but in spark you take the tan of the spreadAmount divide it by 2 and then multiply it with 2 random numbers. What is the purpose of this?
Also, what is the purpose of taking the cos and sin of randomAngle? Is that just to get a random number between -1 and 1?
[EDIT] I guess the source engine is just calculating spread within a box around your crosshair rather than a circle. I'm just trying to see what the equivalent values would be for spread in spark.
Comments
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// Weapon spread - from NS1/Half-life
ClipWeapon.kCone0Degrees = Math.Radians(0)
ClipWeapon.kCone1Degrees = Math.Radians(1)
ClipWeapon.kCone2Degrees = Math.Radians(2)
ClipWeapon.kCone3Degrees = Math.Radians(3)
ClipWeapon.kCone4Degrees = Math.Radians(4)
ClipWeapon.kCone5Degrees = Math.Radians(5)
ClipWeapon.kCone6Degrees = Math.Radians(6)
ClipWeapon.kCone7Degrees = Math.Radians(7)
ClipWeapon.kCone8Degrees = Math.Radians(8)
ClipWeapon.kCone9Degrees = Math.Radians(9)
ClipWeapon.kCone10Degrees = Math.Radians(10)
ClipWeapon.kCone15Degrees = Math.Radians(15)
ClipWeapon.kCone20Degrees = Math.Radians(20)
end
.......
// Return one of the ClipWeapon.kCone constants above
function ClipWeapon:GetSpread()
return ClipWeapon.kCone0Degrees
end<!--c2--></div><!--ec2-->
I know this just specifies the spread amount which is then used in the CalculateSpread function, but hey, this is all I need.
In regards to your question, theoretically, the source engine has a fixed spread, and theoretically NS2 has a random spread. I have discovered from my mods that NS2 spread isn't quite as random as it has been previously claimed.
I am not sure of any of this of course, so feel free to correct me wherever I am wrong. :)
Btw, how does randomizer() work? Does it just return a random number between 0 and 1? Is it a different number each time or the same for every time it is used in CalculateSpread?
There is no function in the NS2 lua code called randomizer() or Randomizer(), both of which are called. They are either built-in LUA functions, or a coding error.
It maybe worth playing with the code to see if it does make a difference...
it should return a float between 0 and 1 by default, networkrandom being used when the values need to match client/server side, or math.random if it doesnt need to match for sentries and hydras and stuff. Each call should return a new random number
<img src="https://pickhost.eu/images/0005/5299/Spread_Code_Attempt.png" border="0" class="linked-image" />
Edit: xDragon is right about the randomizer. It's just an argument being passed to the function that pretty much tells it which random number generator it is supposed to use.
######! Sorry! Z-Axis is actually the direction you are looking towards, not x-Axis. Now it makes a lot more sense. Could have worked both ways though. Even with the axes mislabeled, the general meaning of the stuff is not really altered, so I'll leave it like this.
[EDIT] It is a bit wrong actually as you said.
You can't have a random angle with just 1 co-ordinate.
cos gives you the x co-ordinate and sin gives you the y (I assume x is left and right and y is up and down, with z being forward and backwards, with respect to the direction you are looking)
1/2 tan spreadAngle then gives you the maximum radius, multiplied by a random value makes sure the bullets don't always go on the edge of the spread area, but vary for each shot fired. I don't know why it is multiplied by 2 random numbers though - maybe this is to make more of your bullets go towards the centre of the crosshair, but that's just a guess (multiplying 2 random numbers between 1 and 0 together would make the end result closer to 0).
I stated that already..
The randomiser() function not existing in the NS2 code was my point. Where is it? Is it in the c++ part of the engine? I have core files included in my search and it does not come up in any of the LUA files. Where is the function?
OK, I get it now, thanks Dghelneshi and Dragon :)
<a href="http://www.wolframalpha.com/input/?i=plot+-log%28abs%28x%29%29+x+in+-1+..+1" target="_blank">http://www.wolframalpha.com/input/?i=plot+...29+x+in+-1+..+1</a>
It has quite some variance compared to a gaussian.
In Source they do the sum of two uniform variables instead of a product, it give a triangular distribution (max at zero and go linearly to zero at +/- 1).
If you want to try something more gaussian you can use this:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->function CalculateSpread(directionCoords, spreadAmount, randomizer)
local spreadAngle = spreadAmount / 2
//BoxMuller transform
local u = randomizer()
u = (u + 0.01)/1.01
local v = randomizer()
local randomRadius = 0.2*math.sqrt(-2*math.log(u)) * math.cos(2*math.pi*v) * math.tan(spreadAngle)
local randomAngle = randomizer() * math.pi * 2
local spreadDirection = directionCoords.zAxis +
(directionCoords.xAxis * math.cos(randomAngle) +
directionCoords.yAxis * math.sin(randomAngle)) * randomRadius
spreadDirection:Normalize()
return spreadDirection
end<!--c2--></div><!--ec2-->
<b>Source engine </b>(day of defeat: source) spread, completely uniform:
<img src="http://i.imgur.com/pEDpk.jpg" border="0" class="linked-image" />
<b>NS2 default</b>, many bullets tending towards the centre:
<img src="http://i.imgur.com/lb8lx.jpg" border="0" class="linked-image" />
<b>NS2 with 1 random number removed from randomRadius</b> (i.e. randomRadius = math.random() * math.tan(spreadAngle)) - this is more similar to source but still has some bullets tending towards the centre :
<img src="http://i.imgur.com/MvdTm.jpg" border="0" class="linked-image" />
<b>Yukki's gaussian</b>, similar to ns2 default:
<img src="http://i.imgur.com/A9X5c.jpg" border="0" class="linked-image" />
Also, can someone explain the maths around dividing the spreadamount by 2?
What does this do to a specified angle of say 20 degrees?
Anyway it's just a convenient parametrization, but you could just put a number without units there and tweak it in-game.
local randomRadius = randomizer() * randomizer() * justANumber
Your "NS2 with 1 random number removed from randomRadius" plot is a bit weird, shouldn't it be uniform ?
You are right that you don't need to use tan and could just use any value, but I think using tan makes it correct mathematically for when you set the spread using ClipWeapon.kConeXDegrees. I think in source the spread amount is just a number that is essentially meaningless.
You are basically doing this for the calculation:
Random number (-1,1) * Radnom number (0,1) * tan(spreadAngle)
Since cos and sin are between -1 and 1 and then you multiply them by a random number between (0,1) that is like taking a percentage of a percentage. I.e. The more random numbers you multiply together between -1 and 1 the more likely the result will tend towards 0 (the centre of the crosshair). It doesn't matter what tan(spreadAngle) is, that only determines the maximum value.
When you add the second multiplication of a random number to the randomRadius, then you are making it even more likely that the result will be closer to 0 and that's why you see the bullets going more towards the centre of the crosshair, similar to the Gaussian spread.
In source you are just taking a random value for X and Y and then plotting them, so it is completely uniform.
Now I just play with logic and watch all this stuff sail over my head...
No, because the radius variable is a uniformly distributed random number. Radius 1 and radius 10 are equally likely, you're going to have as many points on a small circle at the center as on a big circle near the edge. That means they'll be packed much closer toghether.
For a uniform distribution you can either take the square root of the randomly chosen radius or pick uniform randoms on a square and reject the random numbers that fall outside the unit circle(rejection sampling).