Using the dddice API to Create Your Own 3D Dice Themes

Using the dddice API to Create Your Own 3D Dice Themes
Custom dice and the manifest definition

While we do have an editor for our online dice, one that let's you remix our themes or even create your own, it has its limits. If you want a truly unlimited dice creation experience you need to use the API, and core to that is understanding the dddice manifest definition.

Typically, a 3D model consists of meshes, shaders, and parameters to those shaders, aka uniforms. A dice set is, simply put, a collection of all the things that make up 3D models, enough to define all the dice in the set. The dddice manifest is what the API uses to bind all these pieces together.

Manifest example

The dddice manifest is a well-defined JSON object, here is an example:

{
  "id": "dddice-standard",
  "version": "1.0.0",
  "api_version": "2.0",
  "name": "Standard",
  "description": "Just your average set of dice, nothing too fancy.",
  "label": {
    "color": "#000000",
    "background_color": "#e3d12d"
  },
  "frag_shader": "shader.frag",
  "vert_shader": "shader.vert",
  "available_dice": [
    {
      "id": "d4",
      "type": "d4"
    },
    {
      "id": "d6",
      "type": "d6"
    },
    {
      "id": "d8",
      "type": "d8"
    },
    {
      "id": "d10",
      "type": "d10"
    },
    {
      "id": "d10x",
      "type": "d10"
    },
    {
      "id": "d12",
      "type": "d12"
    },
    {
      "id": "d20",
      "type": "d20"
    }
  ],
  "textures": [
    {
      "binding": "u_numberMap",
      "src": {
        "d4": "standard.png",
        "d6": "standard.png",
        "d8": "standard.png",
        "d10": "standard.png",
        "d12": "standard.png",
        "d20": "standard.png",
        "d10x": "standard_d10x.png"
      }
    }
  ],
  "uniforms": {
    "diffuse": {
      "type": "color",
      "value": {
        "r": 1,
        "g": 0.929,
        "b": 0
      }
    },
    "number": {
      "type": "color",
      "value": {
        "r": 0.463,
        "g": 0.424,
        "b": 0
      }
    },
    "specular": {
      "type": "color",
      "value": {
        "r": 0.1,
        "g": 0.1,
        "b": 0.1
      }
    },
    "shininess": {
      "type": "f",
      "value": 30
    }
  },
  "meshes": {
    "d4": "d04.fbx",
    "d6": "d06.fbx",
    "d8": "d08.fbx",
    "d10": "d10.fbx",
    "d10x": "d10.fbx",
    "d12": "d12.fbx",
    "d20": "d20.fbx"
  },
  "sizes": {
    "d4": 1,
    "d6": 1,
    "d8": 1,
    "d10": 1,
    "d10x": 1,
    "d12": 1,
    "d20": 1
  },
  "values": {
    "d4": [
      1,
      2,
      3,
      4
    ],
    "d6": [
      1,
      2,
      3,
      4,
      5,
      6
    ],
    "d8": [
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8
    ],
    "d10": [
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10
    ],
    "d10x": [
      10,
      20,
      30,
      40,
      50,
      60,
      70,
      80,
      90,
      0
    ],
    "d12": [
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10
    ],
    "d20": [
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10,
      11,
      12,
      13,
      14,
      15,
      16,
      17,
      18,
      19,
      20
    ]
  }
}
sample manifest from from our standard theme

The src element

Before we get into each individual section, let's start with how we handle the src element. Everywhere in the manifest this can either be a string, or an object. The string is used to apply that value to all dice in the set; the object to define different sources for each die in the set. For example, we can use a string to define a single texture that is used for all dice in a set OR we can define an object to define different textures for different dice models.

Metadata

The first section above defines metadata about the die, the required ones are:

  1. id – a unique ID following slug format to identify your dice to the system.
  2. version – a version number for this die following semver 2.0.0. The API does not allow updating a version once submitted, so your id/version combo must be unique.
  3. api_version – the version of the API you are targeting with your file. Right now only version 2.0 themes are allowed.
  4. name –  the human readable name that is displayed to users, doesn't have to be unique.
  5. description – the description you want for your dice, shows up on the dice share and info pages.
  6. label – defines the colors used to display the name of your dice within the app, color is the font color and background_color is the... background color

Available Dice

The available_dice definition is an important bit of data that has effects on the rest of the manifest definition. It defines which dice are in the set, and what roll physics they use. You can read more about the data structure in our SDK documentation. The important thing to point out here is that the id's you select are used as the keys in the other objects in the file, particularly the src objects. If you are lazy and you intend for all of id, notation, and type to be equal, you can use a string instead of an object.

The second important point is that the order of this array is important. Its the order the dice are shown in to the user in previews. The final die is used if only one die can be shown.

Shaders

This section defines the 3D shaders to apply to you dice. You have 3 general options here: supply no shader info, add your shader code as a JSON-encoded string, or reference an external .frag, .vert or .glsl file. There are two keys: frag_shader for your fragment shade and vert_shader for your vertex shader.

In the example we reference external files shader.frag and shader.vert which define our fragment shader and vertex shader, respecitvely.

If you supply no shader information, the dice will default to the shader used by the dddice-standard theme. Its based on phong, and you can read about it in a previous blog post of ours. We also have an entire section of shader articles for you to read with articles that have details on how we built our other shaders.

Textures

This section is for specifying the textures that will be applied to your mesh. Its an array of objects. Each object has a binding key which is the uniform name to bind to and a src key which is the file name of the texture. As noted above, src can be a string, meaning a single file that applies to all dice in the set, or a src object (like in the example) where the file varies.

Our shaders are based off of three.js shaders, so look there for more details. For our standard shader we added u_numberMap on top of those from the MeshPhongMaterial of three.js.

The u_numberMap is a texture layer that contains the numbers to put on the faces. When a user rolls a "hidden" die, this texture isn't displayed (thus hiding the numbers). When that die gets unhidden, this texture is faded in.

Uniforms

This section defines uniforms that are passed to the shader. Like the textures (which are just really uniforms from the pov of the shader) we support the uniforms defined in the three.js shaders we stand on top of. You can set values for any uniforms the shaders expose here. Our standard shader supports all the uniforms defined in MeshPhongMaterial just like the textures.

The data model is based on the uniform data model of three.js, with one additional feature, a color uniform type. Despite what the linked doc says, the native underlying material we use (ShaderMaterial) doesn't support colors defined using rgb hex strings. So we added our own layer. If you use the color type you can use normal everyday hex strings. If you use vec3 you need to specify the colors in a linear color space, a float from 0 to 1.

In addition our standard shader defines a numbers uniform to tint the color of the u_numbersMap texture. If the numbers in that texture are in white, this becomes a coinvent way to change their color. It will also allow users who remix your die the most flexibility in changing the number's colors.

If you are not using a u_numberMap then the number uniform needs to be set to white . Otherwise the color acts as a diffuse color, and if not set it will make your die all black!

Sounds

The sounds section isn't included in the example above as its optional, but also a bit complicated. This section allows you to customize the sounds the dice make when they collide with each other during the roll and the sounds the dice make when "shaken" before a roll. You can also select a sound to play when a particular face is the final result. Check out our musical dice for an example of that!

{
...
"sounds":[
    {
      "on": "die.value",
      "src": "G4.wav",
      "value": 20
    },
    {
      "on": "die.collide",
      "src": "roll.mp3"
    },
    {
      "on": "roll.loading",
      "src": "shake.mp3",
      "value": ">=2"
    }
  ]
...
}
Example sounds section of the manifest

Collision Sounds

Sounds that roll on collisions need the on key set to die.collide. The src key then contains the name of the mp3 file to play. We recommend these sounds to be abut 1s in length and have little starting and trailing silence.

If you specify more than one die.collide sound, for each roll made, one of the sounds will be used at random.

Shake Sounds

Sounds that play during the "shaking your hand" animation. This is only applicable to the dice rooms on dddice.com at the moment. Set on to die.loading and src to the mp3 you want to play.

You can supply different sounds for different hand sizes using the value key. Its a simple conditional expression with an implied hand_size as the left-hand-side of the evaluation.

Rolled Sounds

Sounds can be played when a ceritian face is rolled. These will play once the roll animation copmletes and the die comes to rest with a particular face showing. Set the on to  die.value and the value to the index of the face to play the sound on.

Optional items

The rest of the keys meshes, sizes and values are purely optional. If you don't specify them, the values shown in the example will be used as defaults.

  1. meshes – names of the .fbx files that define the geometries for the dice. Be careful if you edit these, at current the physical simulations don't get their data from the models. If you want to edit the uv coordinates, have at it!
  2. sizes – want a huge d20? a tiny d6? edit these values.
  3. values – these are the values that get displayed in the chat box when the die is rolled. They are indexed from 1 to max die face. If you do change this, make sure it aligns with your textures and uv coords.

Extending Other Themes

If all the above is daunting, and yeah it kinda is ... you can cut out a bunch of work by extending other themes. Include an extend key with the id of the theme you want to extend. Your new theme will inherit all the values from the theme you extend instead of the defaults. We used this to create the variations of dddice-standard and dddice-old-school. So yeah, programmers be lazy

Upload via the API

Once you have you manifest definition written out and all your textures ready, it is time to upload them with the API. You should read the documentation to familiarize yourself with creating a theme. When I upload my themes I use a bash script to automate most of it. I automation.

To use this script you will need node.js and jq installed as well as a bash run environment.

#!/usr/bin/env bash
shopt -s nullglob
shopt -s extglob
API_URI='https://dddice.com'

# set the working dir
WORKING_DIR=${1:-.}
WORKING_DIR=${WORKING_DIR%/}
MANIFEST="${WORKING_DIR}/manifest.json"

# incriment the minor version number
# this is the part that needs node.js and jq
# you can comment these out safely if you don't have them
# you will need to incriment the version number by hand though
VERSION=$(jq -r ".version" "$MANIFEST")
VERSION=$(npx semver -i patch "$VERSION")
CONTENTS=$(jq ".version=\"${VERSION}\"" "$MANIFEST")
echo -E "${CONTENTS}" > "$MANIFEST"

# build the curl command
COMMAND="curl --header 'Authorization: Bearer ${API_KEY}' --header 'Accept: application/json' -F manifest='<${MANIFEST}'"

# find all the assets in the working dir
for asset in "$WORKING_DIR"/*.+(jpg|png|fbx|glb|gltf|mp3|wav|webp); do
    COMMAND="${COMMAND} -F $(basename ${asset})='@${asset}'";
done

# find all the shaders
for shader in "$WORKING_DIR"/*frag*; do
    COMMAND="${COMMAND} -F frag_shader='<${shader}'";
done
for shader in "$WORKING_DIR"/*vert*; do
    COMMAND="${COMMAND} -F vert_shader='<${shader}'";
done

# execute the curl command
bash -c "$COMMAND $API_URI/api/1.0/theme"

This script searches a directory for any .fbx, .png, .frag, and .vert files, and then combines them with the manifest.json in the same directory and uploads them via curl. Super helpful. Make sure to generate your API key first and export API_KEY=<your api key here>.

I look forward to all the dice you will create with this API.

If you're interested in joining our growing community, join our Discord, follow us on Twitter, join the subreddit, and stay tuned for more updates.