# Thread: Bias in setting random rotation

1. ## Bias in setting random rotation

Well, THIS is an interesting one. I made an item for the workshop called the Holy Roller. Basically, it take dice, sets a "random" rotation to them, and places them around itself. A little dice roller.

But somebody pointed out in the comments that the dice rolls WOULDN'T be random. This was their reference they provided: http://mathworld.wolfram.com/SpherePointPicking.html. I did some testing and they were 100% correct. 2 and 5, on the poles of the dice, were heavily biased. I made an automated repeater to keep rolling the dice over and over. These were the results. Here is how I was attempting to set the angles.

Code:
```xRot = math.random()*360
yRot = math.random()*360
zRot = math.random()*360
object.setRotation({xRot, yRot, zRot})```
So now I am trying to figure out a real solution to this problem. I just don't understand it well enough to solve. I have been asking for help on various math forums, and gotten some very helpful responses, but still don't have this solved. In the mean time, I just cheated and started throwing torque onto the dice after they are rotated. But I was hoping someone with better maths, or that had already solved this problem, could help.

Thanks  Reply With Quote

2. I really want to know the answer to this now, I don't really understand why you're having this problem and that "Sphere Point Picking" page isn't easy to understand without some higher level Maths knowledge...

Now I'm wondering whether using the "R" key on a die is even truly random or not...  Reply With Quote

3. I think the R key is, it just picks a rotation within a certain range (so you don't just spin it like a top) and a toss-up height of a variable amount. That's as random as shaking the dice and rolling them.

My limited understanding of this is that it basically ties into Gimbal lock. When you try manipulate all 3 angles of a rotation at once you can get some unexpected results. In this case, getting fully random numbers and applying a different one to x/y/z will cause, more often than not, the angle to point one side or the opposite side up. Because that is where the "poles" of the model are.

There are mathematical ways to distribute it so that doesn't happen. But so far, I don't think I'm going to be able to figure it out. Unless I can find someone to just tell me the exact answer I need, I may not get it. This is my latest failed attempt:

Code:
```u1 = math.random()
u2 = math.random()
u3 = math.random()
u4 = math.random()
--w/x/y/z from your example
q0 = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
q1 = math.sqrt(-2 * math.log(u1)) * math.sin(2 * math.pi * u2)
q2 = math.sqrt(-2 * math.log(u3)) * math.cos(2 * math.pi * u4)
q3 = math.sqrt(-2 * math.log(u3)) * math.sin(2 * math.pi * u4)
--My conversion from w/x/y/z to Euler x/y/z
xr = math.atan(  (2*(q0*q1+q2*q3)) / (1-2*(q1^2+q2^2))  )
yr = math.asin(  2*(q0*q2 - q3*q1)  )
zr = math.atan(  (2*(q0*q3+q1*q2)) / (1-2*(q2^2+q3^2))
--Converting radians to degrees to set the rotation
rotation = {math.deg(xr), math.deg(yr), math.deg(zr)}```  Reply With Quote

4. Developer Join Date
Jan 2014
Posts
986
I'm using random quaternions (avoids gimbal lock) for the R dice rolling plus the whole physics component as well.

We unfortunately don't expose rotations as quaternions so you can't use that method (less then helpful I know). The combination of random forces plus rotation will probably be as random as necessary though.  Reply With Quote

5. Yeah I think that is where it will end. Trying to crunch the math behind the scenes converting quats and Euler is way outside my paygrade haha.

Thanks for the confirmation though!  Reply With Quote

6. Player Join Date
Apr 2016
Posts
85
Have you tried using math.atan2? e.g.

Code:
```xr = math.atan2(  (2*(q0*q1+q2*q3)) , (1-2*(q1^2+q2^2))  )
yr = math.asin(  2*(q0*q2 - q3*q1)  )
zr = math.atan2(  (2*(q0*q3+q1*q2)) , (1-2*(q2^2+q3^2)) )```
Difference is that atan only produces results between -90 deg and 90 deg, i.e. only two of the four quadrants. atan2 uses the signs of the numerator and denominator to place the result in the correct quadrant.  Reply With Quote

7. The second version from the quat -> Euler conversion? Sadly, yes that was one of the things I tried. I tried to copy/paste yours, but this version actually gives me significantly worse distribution than the first conversion method. That was without me converting the result from radians to degrees, because I am honestly not sure which result that conversion is supposed to give me. So just to be sure, I converted the results for each (x/y/z) into degrees at the end to be sure. Bias changed, but persisted.  Reply With Quote

8. Player Join Date
Apr 2016
Posts
85
It should be converted from radians to degrees. The trigonometric functions return radians but the TTS API expects degrees.  Reply With Quote

9. Recruit Join Date
Dec 2016
Posts
12
This is a piece of code which sets properly uniform rotations. You would need to convert it from quaternion to angles if required. Please ignore double/float conversions, it was required due to framework I was writing it in.

Code:
```		double u1 = Math.random();
double u2 = Math.random();
double u3 = Math.random();

double u1sqrt = Math.sqrt(u1);
double u1m1sqrt = Math.sqrt(1-u1);
double x = u1m1sqrt *Math.sin(2*Math.PI*u2);
double y = u1m1sqrt *Math.cos(2*Math.PI*u2);
double z = u1sqrt *Math.sin(2*Math.PI*u3);
double w = u1sqrt *Math.cos(2*Math.PI*u3);

return new Quaternion((float)x,(float)y,(float)w,(float)z);```
I was running it overnight to generate results of rolls for multiple dice types and it was quite uniform.  Reply With Quote

10. My understanding is that this implementation of Lua doesn't support quats.  Reply With Quote

11. Recruit Join Date
Dec 2016
Posts
12
Then it is just matter of converting x,y,z,w into euler angles - you have posted it few messages above in your example (https://en.wikipedia.org/wiki/Conver...les_Conversion). Trick is how to generate uniform quaternion, conversion is trivial Edit:
Found the original formula
http://planning.cs.uiuc.edu/node198.html  Reply With Quote

12. I made an attempt at this. Somewhere I had issues, either in the execution of the conversion or the generation of the whatevers. Not sure. If you want to take a swing at it and test it out, then by all means please do. Or someone else can? I made 20 or 40 dice which would constantly "re-roll" themselves and print their result to chat, about once every second or two. Then I let it run for a while, printing its cumulative results as it went. I always wound up with significant biases

Unfortunately, I've thrown the towel in. If somebody gets it to work effectively in game, please share it. I don't have a particular use for it currently, but I'm sure others might. And you never know, I may revisit the project in the future.  Reply With Quote

13. Recruit Join Date
Dec 2016
Posts
12
Can I get your test program somewhere? I could try to take a stab at solving the bias problem.  Reply With Quote

14. Sorry, I ended up deleting it in a huff once upon a time haha  Reply With Quote

15. Recruit Join Date
Dec 2016
Posts
12
I managed to hack your dice roller ring (learning lua at same time, so took me a moment). I have yet to run rollers for long period of time and results are hard to interpret. For D6 I got
61 52 61 52 69 45
and now I'm running it for D8
50 36 38 47 44 54 41 55
Hard to say at the moment if it is really fair. I will try to run it overnight for D6 and see if result 6 is still underrepresented (it was 40 1s versus 20 6s at some point).

Here is the code - I have put it into rotation = randomRotation() and commented out 'lameFix' part for now.
Code:
```function randomRotation()

local u1 = math.random();
local u2 = math.random();
local u3 = math.random();

local u1sqrt = math.sqrt(u1);
local u1m1sqrt = math.sqrt(1-u1);
local qx = u1m1sqrt *math.sin(2*math.pi*u2);
local qy = u1m1sqrt *math.cos(2*math.pi*u2);
local qz = u1sqrt *math.sin(2*math.pi*u3);
local qw = u1sqrt *math.cos(2*math.pi*u3);

local ysqr = qy * qy;
local t0 = -2.0 * (ysqr + qz * qz) + 1.0;
local t1 = 2.0 * (qx * qy - qw * qz);
local t2 = -2.0 * (qx * qz + qw * qy);
local t3 = 2.0 * (qy * qz - qw * qx);
local t4 = -2.0 * (qx * qx + ysqr) + 1.0;

if t2 > 1.0 then t2 = 1.0 end
if t2 < -1.0 then ts = -1.0 end

local xr = math.asin(t2);
local yr = math.atan2(t3, t4);
local zr = math.atan2(t1, t0);

return {math.deg(xr),math.deg(yr),math.deg(zr)}
end```  Reply With Quote

16. I tried something similar before (3 random numbers into 4 factors converted into X/Y/Z degrees. But I'm sure I was doing something wrong (likely failing to convert into degrees)

It looks pretty solid. I'm hopeful you may have cracked it. Nice =)  Reply With Quote

17. Recruit Join Date
Dec 2016
Posts
12
1151 1174 1185 1192 1188 1178
I think that numbers look pretty ok. Entire of your modified code follows (sorry for lack of idiomatic lua in modifications, but I learned it yesterday evenining)

Code:
```--[[    Holy Roller: by MrStump

Instructions for edit:
radius
This is how far away from the tool the dice will fall.
If you set it too low, items will fall back into the bag FOREVER. Fun.
height
This is how high off the table, relative to the tool, the dice will fall.
If you set it too low (negative numbers), dice can spawn in the table. Bad.
print
This decides if the results of the dice will print to chat. Will not work with custom dice.
Can be true or false. True prints, false does not print. ]]--

radius = 4
height = 4
printResultsToChat = true

results = {0,0,0,0,0,0,0,0}

--[[    End of edit section. below is the functional code, commented.    ]]--

--[[Runs any time an object touches the bag (or it touches anything)]]
function onCollisionEnter()
--Gets # of objects in bag. If there are some, it runs the emptyContents function
if self.getQuantity() > 0 then
allDice = {}
emptyContents()
end
end

--[[Empties the contents of the bag into a ring around the object.]]
function emptyContents()
--Establish some necessary basic information on what is in the bag and where it is
local contents = self.getObjects()
local selfRot = self.getRotation()
local selfPos = self.getPosition()

--Establishes the X/Y/Z of position, and the Y of rotation.
local yRot = selfRot.y
local xPos = selfPos.x
local yPos = selfPos.y
local zPos = selfPos.z
--Establishes the variable we need for each spoke (placed item). Like spokes on a wheel.
--360 is our number of degrees, #contents is our number of individual spokes we will have.
local spokes = 360/#contents

--Loop, runs through each item in the bag, sets its position and rotation for its destination, and pulls it out
for i, v in pairs(contents) do
--This is how we determine the position of each individual removed item. (see end for more)
local xp = xPos + math.sin( ((spokes*i)+yRot)*0.0174532 ) * radius
local yp = yPos + height
local zp = zPos + math.cos( ((spokes*i)+yRot)*0.0174532 ) * radius
--We place our x/y/z for position into this table. We also place 3 random numbers for rotation X/Y/Z
takeParam = {
position = {xp,yp,zp},
--rotation = {math.random(1,360),math.random(1,360),math.random(1,360)},
rotation = randomRotation(),
callback = 'lameFix'
}
--Use the info in the table to remove the item.
local die = self.takeObject(takeParam)
allDice[i] = die
end
--Activates a timer to call a function to print the dice values
if printResultsToChat == true then
--destroy makes sure a currently existing one is gone, in case timers overlap.
Timer.destroy(self.getGUID())
local timerParams = {
identifier=self.getGUID(), function_name='printResults',
function_owner=self, delay=3
}
Timer.create(timerParams)
end
end

--[[A fix to the bias towards 2/5. This puts torque on the dice, effectively rolling them.]]
function lameFix(dieObject)
--[[  dieObject.addTorque( {math.random()*360, math.random()*360, math.random()*360} ) ]]
end

--[[Activated by the timer if printResultsToChat is true]]
function printResults()
local printString = dropper .. ' rolled: '
for i, v in pairs(allDice) do
printString = printString .. v.getValue() .. '  '
results[v.getValue()] = results[v.getValue()]+1
local p = self.getPosition()
p["y"] = p["y"] + i
v.setPositionSmooth(p)
end

--[[ printToAll(printString, {1,1,1}) ]]
print(results .. ' ' .. results.. ' ' .. results.. ' ' .. results.. ' ' .. results .. ' ' .. results )
-- print(results .. ' ' .. results.. ' ' .. results.. ' ' .. results.. ' ' .. results .. ' ' .. results .. ' ' .. results .. ' ' .. results)

end

--[[Tracks every object drops and adds it to a table. Needs]]--
function onObjectDropped(col, obj)
if printResultsToChat == true then
dropper = Player[col].steam_name
end
end

function randomRotation()

local u1 = math.random();
local u2 = math.random();
local u3 = math.random();

local u1sqrt = math.sqrt(u1);
local u1m1sqrt = math.sqrt(1-u1);
local qx = u1m1sqrt *math.sin(2*math.pi*u2);
local qy = u1m1sqrt *math.cos(2*math.pi*u2);
local qz = u1sqrt *math.sin(2*math.pi*u3);
local qw = u1sqrt *math.cos(2*math.pi*u3);

local ysqr = qy * qy;
local t0 = -2.0 * (ysqr + qz * qz) + 1.0;
local t1 = 2.0 * (qx * qy - qw * qz);
local t2 = -2.0 * (qx * qz + qw * qy);
local t3 = 2.0 * (qy * qz - qw * qx);
local t4 = -2.0 * (qx * qx + ysqr) + 1.0;

if t2 > 1.0 then t2 = 1.0 end
if t2 < -1.0 then ts = -1.0 end

local xr = math.asin(t2);
local yr = math.atan2(t3, t4);
local zr = math.atan2(t1, t0);

return {math.deg(xr),math.deg(yr),math.deg(zr)}
end

--[[The core of determining direction from an object, based on its rotation, is based in trig.
I barely understand it, but I might be able to explain how to use it (if that is what you are here for).
x = sourceXposition + math.sin( (0+sourceYrotation)*0.0174532)  * distance
y = yPosition
z = sourceZposition + math.cos( (0+sourceYrotation)*0.0174532)  * distance
This is the heart of the formula. Let me explain each part of x.

sourceXposition || the halo's x position.
When we're giving our placed item coordinates, they will be relative to the world. Where 0,0,0 is the middle of the table.
Basically, if we didn't add sourceXposition, we would be making a ring of objects around table center instead of this object.
math.sin( (0 + sourceYrotation) * 0.017453 ) || converting an angle into a vector
math.sin() is our mathmatical function. for Z, you will notice we use math.cos() instead, and the z position, but the rest is the same.
sourceYrotation is the angle we are looking to convert into a vector (for our item to be placed)
0 is how much we want to add to our angle. If 0 comes out the left side, 90 would come out the top, 180 out the right, etc
0.0174532 is the number used to conver radians/degrees to/from eachother. Dont try to change this number.
distance || measurement of distance
How far from our item's center we want to spawn the object.

If you cam here looking to understand how this works, I hope this helped. ]]--```  Reply With Quote

18. Very nicely done =)  Reply With Quote

#### Posting Permissions

• You may not post new threads
• You may not post replies
• You may not post attachments
• You may not edit your posts
•