Weapon spread

WilsonWilson Join Date: 2010-07-26 Member: 72867Members
edited October 2012 in Modding
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.

Comments

  • Soul_RiderSoul_Rider Mod Bean Join Date: 2004-06-19 Member: 29388Members, Constellation, Squad Five Blue
    Here is the weapon spread code from ClipWeapon.lua, much easier to understand :)

    <!--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. :)
  • WilsonWilson Join Date: 2010-07-26 Member: 72867Members
    I'm struggling to remember all the trigonometry, I did all that 1/2 tan theta stuff at school but I can't find my maths books so I just decided to port over the source version into spark for now. The thing about the source method is that the bigger the spread the less likely it is to shoot down the crosshair, since a bigger number multiplied by a random number between 0 and 1 is more likely to be further away from 0. But I don't think this is the case in NS2, it seems to be much more random (although most of the guns have a low spread so I doubt it makes much difference). If I could get it working that way with a circle spread I would, but it's easier to just keep it as a box right now.

    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?
  • Soul_RiderSoul_Rider Mod Bean Join Date: 2004-06-19 Member: 29388Members, Constellation, Squad Five Blue
    edited October 2012
    Whenever a call is passed to CalculateSpread, it includes 3 arguments. Direction, Spread and a randomiser. In the code, UWE uses either math.random, or networkRandom, to pass a number as the argument for randomizer.

    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...
  • xDragonxDragon Join Date: 2012-04-04 Member: 149948Members, NS2 Playtester, Squad Five Gold, NS2 Map Tester, Reinforced - Shadow
    edited October 2012
    thats just passing a function as the arg, which is then called when calculating the spread....
    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
  • DghelneshiDghelneshi Aims to surpass Fana in post edits. Join Date: 2011-11-01 Member: 130634Members, Squad Five Blue, Reinforced - Shadow
    edited October 2012
    I can't tell you exactly how Tangens, Cosinus and Sinus are related to the angles and directions etc. (too lazy to do that right now), but I've made a picture explaining how the spread actually works (warning, it's a bit weird, but <!--coloro:#FF8C00--><span style="color:#FF8C00"><!--/coloro-->C<!--colorc--></span><!--/colorc--><!--coloro:#FF00FF--><span style="color:#FF00FF"><!--/coloro-->O<!--colorc--></span><!--/colorc--><!--coloro:#FFFF00--><span style="color:#FFFF00"><!--/coloro-->L<!--colorc--></span><!--/colorc--><!--coloro:#00FF00--><span style="color:#00FF00"><!--/coloro-->O<!--colorc--></span><!--/colorc--><!--coloro:#800080--><span style="color:#800080"><!--/coloro-->R<!--colorc--></span><!--/colorc--><!--coloro:#0000FF--><span style="color:#0000FF"><!--/coloro-->F<!--colorc--></span><!--/colorc--><!--coloro:#FF0000--><span style="color:#FF0000"><!--/coloro-->U<!--colorc--></span><!--/colorc--><!--coloro:#00BFFF--><span style="color:#00BFFF"><!--/coloro-->L<!--colorc--></span><!--/colorc-->!).
    <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.
  • WilsonWilson Join Date: 2010-07-26 Member: 72867Members
    edited October 2012
    Wow, Dghelneshi, thanks for the explanation, really helpful :)

    [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).
  • Soul_RiderSoul_Rider Mod Bean Join Date: 2004-06-19 Member: 29388Members, Constellation, Squad Five Blue
    edited October 2012
    <!--quoteo(post=1992920:date=Oct 17 2012, 08:23 PM:name=xDragon)--><div class='quotetop'>QUOTE (xDragon @ Oct 17 2012, 08:23 PM) <a href="index.php?act=findpost&pid=1992920"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->thats just passing a function as the arg<!--QuoteEnd--></div><!--QuoteEEnd-->

    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 :)
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    For the radial spread Spark uses a uniform product distribution (multiplication of two uniformly distributed variables) that looks like that:

    <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-->
  • WilsonWilson Join Date: 2010-07-26 Member: 72867Members
    I wrote a script to plot the different algorithms on a graph (I feel like a real programmer! :D). Here are the results (the size of the circles is not exact, I just made them quickly. Obviously the size of the spread varies anyway depending on how far you are from the target) The purpose is to show the spread within the cone:

    <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" />
  • Soul_RiderSoul_Rider Mod Bean Join Date: 2004-06-19 Member: 29388Members, Constellation, Squad Five Blue
    I like your NS2 variation with 1 variable removed. I am going to implement that myself, in the hope that it really shakes up the shotgun spread.

    Also, can someone explain the maths around dividing the spreadamount by 2?

    What does this do to a specified angle of say 20 degrees?
  • YuukiYuuki Join Date: 2010-11-20 Member: 75079Members
    edited October 2012
    I think it's just that the spread amount is define as the full angle at the base of the cone, and that spreadAngle is the angle between the zAxis and the end of the cone, so half the spread amount.

    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 ?
  • WilsonWilson Join Date: 2010-07-26 Member: 72867Members
    You are taking cos and sin of the same random number each time. So if you just had them and didn't multiply it by the radius the bullets would all land in a perfect circle around the crosshair.

    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.
  • Soul_RiderSoul_Rider Mod Bean Join Date: 2004-06-19 Member: 29388Members, Constellation, Squad Five Blue
    This is what confuses me, the maths... As much as I enjoy maths, I never studied it because too far because I got bored with school.

    Now I just play with logic and watch all this stuff sail over my head...
  • Soylent_greenSoylent_green Join Date: 2002-12-20 Member: 11220Members, Reinforced - Shadow
    <!--quoteo(post=1993638:date=Oct 19 2012, 04:31 AM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Oct 19 2012, 04:31 AM) <a href="index.php?act=findpost&pid=1993638"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Your "NS2 with 1 random number removed from randomRadius" plot is a bit weird, shouldn't it be uniform ?<!--QuoteEnd--></div><!--QuoteEEnd-->

    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).
Sign In or Register to comment.