Producing Convincingly-Rolling Dice

For a project that I have in mind, I want to depict a roll of the dice–and I find myself unsure of how to go about this.

I’ll note that I don’t require that the rolling produce nicely-randomised results: I intend instead to generate the results behind the scenes and simply place them on the dice once the latter have stopped.

My thoughts thus far:

  • I could create an animation of a die rolling
    • This would probably look okay in-and-of itself–but once multiple dice are rolled at once the static nature of it would, I daresay, become obvious.
    • I suspect that animating a die-roll of decent duration would be somewhat tedious, and maybe fiddly, too.
  • I could use Bullet
    • This should largely “just work”–but it seems overkill when nothing else in the game is intended to use physics of any sort
      • (The game is by-and-large essentially a visual novel–more specifically, something in the vein of the game-books of old, those with dice-based combat-mechanics.)
  • I could try to use Panda’s built-in physics
    • Would Panda’s build-in physics do this…?
      • (I’m not particularly familiar with the built-in physics system–the collision system, yes, but not the physics system.)

So… what do you suggest?

I think Panda physics will help you. A cube can be represented as a sphere with six rays, bringing the ray to a perpendicular (90°) to the table can be done mathematically, Something like fine-tuning, after the loss of inertia, which may be noticeable…

I think this is already implemented in the example: “ball-in-maze”

Ah, I forgot to mention that I had thought about a sphere–but the thing there is that a cube sphere won’t (in general) roll in the same way as a sphere: the edges and corners will produce different physical responses to those produced by the uniformity of a sphere.

Initially, the cube can be inscribed into a sphere, with the loss of inertia (rotation), the choice of a face for fitting to the table can be carried out mathematically. My thought was that at first the cube rotates randomly - which actually looks like a sphere, then it becomes obvious which face (ray) is in contact with the table, from that moment you can just smoothly (animation via lerp) the final position of the cube.

I follow–but I really don’t think that a rolling cube looks much like a rolling sphere, even initially. The edges and corners change how it moves; a sphere would be too smooth, I feel.

I agree, the falsity will be noticeable.

Hmm… More and more I’m beginning to think that Bullet is likely the best option that I’ve thus far seen…

@Thaumaturge

Maybe you have already seen this information, but I found a script in the panda3d manual that could help you as a starting point.
I made a die and modified some lines.

I hope it can be useful for you.

I attach the die model to the message.
dice.rar (11.8 KB)

Link from the panda3d manual.
bullet samples

import sys, random
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject
from panda3d.core import AmbientLight
from panda3d.core import DirectionalLight
from panda3d.core import Vec3
from panda3d.core import TransformState
from panda3d.core import BitMask32
from panda3d.bullet import BulletWorld
from panda3d.bullet import BulletPlaneShape
from panda3d.bullet import BulletBoxShape
from panda3d.bullet import BulletRigidBodyNode
from panda3d.bullet import BulletDebugNode

class Sample:
    def __init__(self):
        base.cam.setPos(10, -30, 20)
        base.cam.lookAt(0, 0, 5)
        self.worldNP = render.attachNewNode('World')
        self.dice = []
        base.accept('r', self.roll)
        # World
        self.world = BulletWorld()
        self.world.setGravity(Vec3(0, 0, -9.81))
        
        self.debugNP = self.worldNP.attachNewNode(BulletDebugNode('Debug'))
        self.debugNP.show()
        self.debugNP.node().showWireframe(True)
        self.debugNP.node().showConstraints(True)
        self.debugNP.node().showBoundingBoxes(False)
        self.debugNP.node().showNormals(True)
        self.world.setDebugNode(self.debugNP.node())

        # Plane
        shape = BulletPlaneShape(Vec3(0, 0, 1), 1)
        node = BulletRigidBodyNode('Ground')
        node.addShape(shape)
        np = render.attachNewNode(node)
        np.setPos(0, 0, -2)
        self.world.attachRigidBody(node)

        # Boxes
        self.model = loader.loadModel('models/dice.egg')
        self.model.setScale(0.3)
        self.model.flattenLight()

        for i in range(2):
            node = BulletRigidBodyNode('Box')
            node.setMass(1.0)
            shape = BulletBoxShape(Vec3(0.6, 0.6, 0.6))
            node.addShape(shape)
            np = self.worldNP.attachNewNode(node)
            np.setCollideMask(BitMask32.allOn())
            self.world.attachRigidBody(np.node())

            m = self.worldNP.attachNewNode("")
            self.model.copyTo(m)
            m.clearModelNodes()
            np.setPos(0,0,0)
            m.reparentTo(np)
            self.dice.append({"model": m, "box": np})

        self.model.hide()
        taskMgr.add(self.update, 'update')
        self.roll()
            
    def roll(self):
        i = 0
        for dice in self.dice:
            dice["box"].setPos(-0.2+i*3, 0, 13)
            dice["box"].setHpr(random.randint(25,80),random.randint(35,85),random.randint(15,115))
            i+=1

    # Update
    def update(self, task):
      dt = globalClock.getDt()
      self.world.doPhysics(dt)
      return task.cont
  
game = Sample()
run()

Thank you! I appreciate it! :slight_smile:

As it happens, I’ve actually already started implementing a version using Bullet. And I’ll admit that so far it does seem to be a good way of going about it!

(My hesitation with Bullet wasn’t that I didn’t know how to go about it, but rather that it initially seemed like overkill for a project with no other physics.)

(That said, I’m not entirely happy with the effects of the angular sleep threshold: too high, and dice can end up standing on their edges; too low, and dice take time to go to sleep after seemingly having stopped.

This is important to me because I want to use the sleeping state to detect when a die has “finished rolling”…

But what I have for now seems to work acceptably, at least.)

Nevertheless, let your example stand for any others who might want to do something like this! :slight_smile:

If I may add some points:

  • To give the impression of the dice being “thrown” after being “shaken”, I like to add a torque- and central- impulse to each die when rolling them.
  • I’m not sure that it’s wise to set the collide-mask to “all-on”, at least for general usage.