Crossroad Development

two roads crossed to make an X

Finding 3D Object Orientation the Easy Way

2023-06-17

When building a dice game, I came to a particular problem, how will I determine the value of the dice roll? You may think the answer should be easy, reading the rotation values, and making a switch statement to give the correct value based on a range of the rotation values for all 3 axes. Well, that might be a suitable solution for a six-sided die, but I was working with several different-value polyhedral dice. So initially I did some research on if this would be the best approach, and I didn’t really find anything conclusive. 

Then I decided to make several instances of my game engine with a different die in each and an overlay that reported the rotation values. I decided to conscript my friends to collect enough data so that I could be sure my ranges would give the expected output. This approach was slow and I was racking my brain about what a better solution would be. After falling down a rabbit hole on stack overflow, I found a solution I could implement.

I’ll take the time now to introduce my game engine of choice, PlayCanvas. This javascript engine uses webGL in a canvas element to build complex games with physics that use a web assembly version of the Bullit engine. There are quite a lot of features and I will probably be publishing something about their webXR APIs, but I enjoy it because you can hit a lot of platforms with the web, and most hobby developers don’t need an engine with a lot of graphic fidelity as they are usually smaller teams. I only bring this up now because the concept will carry to other platforms, but I will be showing screenshots and using examples for PlayCanvas.

The idea of this solution is to use invisible child objects that are offset at the normal from every face, then write a function that finds the Y value of every child element and returns the name of the child that has the largest value. You could probably do some kind of collision detection with the floor object and return the value on the opposite side, and that could be event-driven but would fire as it rolls. The first thing we have to do is place the children objects to the corresponding values. I texture-painted this D20 myself, but it does need to be redone with a margin to account for mipmapping.

screenshot that shows how the children objects are attached to the die

After placing the objects to reference you can give them a transparent texture, because you won’t want to see them during gameplay. For this demo, I added a button that lets you roll the die and a text overlay that’s updated with the current value of the D20. Setting up the 2D overlay isn’t really part of this explanation, but the PlayCanvas docs are very helpful, and both the forum and Discord are very active. So, skipping that we need the code, which I’ve made into a single script file and attached it to the root of the scene. 

let Roller = pc.createScript('roller');
let dice;
// initialize code called once per entity
Roller.prototype.initialize = function() {
let button = this.app.root.findByName('toss');
button.element.on('click', this.roll, this);
};
//click listener that applies random force to the die
Roller.prototype.roll = function() {
    let dice = this.app.root.findByName("D20");
    let hold1 = Math.random();
    let hold2 = Math.random();
    let hold3 = Math.random();
    dice.rigidbody.applyTorqueImpulse(Math.random() * 5
     *flip(hold1), Math.random() * 5 * flip(hold2), Math.random() * 5 * flip(hold3));
}
// update code called every frame
Roller.prototype.update = function(dt) {
this.app.root.findByName('value').element.text = this.highestY();
    
};
//finds the highest Y value from the reference children of the die
Roller.prototype.highestY = function (){
    let dice = this.app.root.findByName("D20");
    let buffer = [];
    let index = -1;
    let finalIndex;
    for(let i = 0;i < dice.children.length;i++){
        buffer.push(dice.children[i].getPosition().y);
        
    }
    for(let i = 0;i < dice.children.length;i++){
        if(buffer[i] > index){
            index = buffer[i];
            finalIndex = i;
            
        }
    }
    return finalIndex+1;
  }
//function that determines is the force applied to the die is positive or negative
  function flip(e) {
       if (e > 0.5){
           return 1;
       }
      else{ return -1;}
  }

I start by adding the click listener in the initialization function which applies a random amount of torque force in a random direction to the die. Then in the update function, we set the text value of the value object to display the result from our function that finds the largest Y value of the die’s children. There is probably a sort array method that would do this in fewer lines, but pushing all the Y values to a temporary array “buffer” and then iterating through them replacing the “finalIndex” variable with the buffer index that beat the last largest number, add one to that to account for zero-indexed arrays and you’re golden.

If you’re interested in forking this demo to tinker around with it, you can find it at https://playcanvas.com/project/1090609/overview/dice-roller. If you end up making something with it, shoot me an email, I would love to see it.

Comments