3d-dice / dice-box

3D Game Dice for any JavaScript App
https://fantasticdice.games/
MIT License
124 stars 27 forks source link

Custom, dynamic themes #109

Open wtfcarlos opened 4 weeks ago

wtfcarlos commented 4 weeks ago

Hello. Love your library and am integrating it into my new project. Thank you for your hard work. I am wondering what you think would be the bst way to go about creating themes dynamically, for custom dice applications?

For instance, given an array of 6 emoji, i'd like to somehow configure the dicebox such that the dice will show one of each emoji in the dice faces.

The theming system seems a bit imposing, hopefully ther'es a way to do it?

wtfcarlos commented 4 weeks ago

For my purposes, a theme that exclusively supports d6 where I can dynamically provide images or text for each of the faces is what I am lookign to build. I am a seasoned engineer, so any ideas or a few pointers would be appreciated and I might be able to implement and contribute back.

frankieali commented 4 weeks ago

Hi @wtfcarlos. Unfortunately, the theme system is not set up for dynamic face values. I use blender to create the 3d models and bake the textures. I know BabylonJS has support for custom fragment shaders, which I have implemented to dynamically alter the die colors, but altering the die faces was much more complex. These are written in Graphics Library Shader Language (GLSL) which I do not know well enough. My other dice roller dice-box-threejs should support this feature though. It's a fork of Major's 3D Dice which was forked from Teal Dice (no longer online but mirrored here Darkside Teal Dice). You can see in the dice presets file how the labels and values are configurable. I know other libraries do support this feature, such as dddice.com and Dice so Nice. Dice So Nice was originally forked from Major's 3D Dice as well but has been heavily altered to integrate with FoundryVTT.

Overall, this seems like a popular feature, so I'm going to add it to the list for the future.

frankieali commented 4 weeks ago

Digging deeper into dice-box-three, the ability to make a custom set or assign the face labels is not exposed, but I imagine it wouldn't take much work to expose them as a config option. Color, texture and material have already be exposed as a custom color set.

wtfcarlos commented 4 weeks ago

Hello, I was able to implement this for d6 only on the mainstream 3d-dice/dice-box. The way I did it was by supporting a new theme type that I call dynamic-canvas. I set it up in such a way that I can provide an array of base64 strings called canvasProvider that would represent each of the 6 faces of the die.

I then use Babylon's CubeTexture to create a compossite custom cube texture that I can apply to a custom material. Below is the quick and dirty version I came up with, which works in my project with some accessory modifications in other parts of the code:


class ThemeLoader {
  loadedThemes = {};
  themeData = {};
  constructor(options) {
    console.log("constructor options");
    console.log(options);
    this.scene = options.scene;
    this.canvasProvider = options.canvasProvider;
  }

  async createCubeTextureFromBase64(base64Array) {
    if (base64Array.length !== 6) {
      throw new Error(
        "CubeTexture requires exactly 6 images (one for each face)",
      );
    }

    return new CubeTexture(
      "", // Root URL (we don't need this as we are setting the images manually)
      this.scene,
      null,
      false,
      this.canvasProvider,
    );
  }

  async loadDynamicCanvasMaterial(options) {
    const { theme } = options;

    const cubeTexture = await this.createCubeTextureFromBase64(
      this.canvasProvider,
    );

    const material = new StandardMaterial(theme, this.scene);
    material.diffuseTexture = cubeTexture;
    material.diffuseTexture.level = 1;
    material.diffuseTexture.coordinatesMode = Texture.SKYBOX_MODE;
    material.diffuseColor = new Color3(0, 0, 0);

    material.specularPower = 1;
    material.specularColor = new Color3(0, 0, 0); // Use a lower value to reduce brightness

    material.reflectionTexture = cubeTexture; // Or set this as diffuseTexture based on your needs
    material.reflectionTexture.coordinatesMode = Texture.SKYBOX_MODE;
    material.reflectionTexture.level = 1;

    material.backFaceCulling = false;
    material.sideOrientation = StandardMaterial.ClockWiseSideOrientation;

    return material;
  }

  }```
wtfcarlos commented 4 weeks ago

This video shows how it works for my project.

https://github.com/user-attachments/assets/6ffbc025-38c2-493b-9650-87b10a51133f

This is obviously a hack that only supports d6, but this is just exactly what I need for my project.

My project will continue using this library heavily, and I can foresee needing more custom adjustments, but I think I'm confident I can modify the library to do anythign I need for my basic d6 purposes

wtfcarlos commented 4 weeks ago

The mesh file config I'm using declares a simple cubic mesh d6 and a simple cubic d6_collider, with simple UV mappings. Hope this can help out someone in the future! I could see a way of supporting other dice in the future with a more careful approach.

{
  "producer": {
    "name": "Carlos Rey",
    "version": "0.0.1",
  },
  "gravity": [
    0,
    -9.81,
    0
  ],
  "colliderFaceMap": {
        "d6": {
            "0": 1,
            "1": 5,
            "2": 4,
            "3": 2,
            "4": 6,
            "5": 3,
            "6": 1,
            "7": 5,
            "8": 4,
            "9": 2,
            "10": 6,
            "11": 3
        }
  },
  "meshes": [
    {
      "name": "d6",
      "id": "d6",
      "billboardMode": 0,
      "position": [-0.6, 0, 0],
      "rotationQuaternion": [0, 0, 0, -1],
      "scaling": [1, 1, 1],
      "isVisible": true,
      "isEnabled": true,
      "pickable": false,
      "positions": [
        -1, -1, -1,
         1, -1, -1,
         1,  1, -1,
        -1,  1, -1,
        -1, -1,  1,
         1, -1,  1,
         1,  1,  1,
        -1,  1,  1
      ],
      "normals": [
        0, 0, -1,
        0, 0, -1,
        0, 0,  1,
        0, 0,  1,
        0, -1, 0,
        0, -1, 0,
        0,  1, 0,
        0,  1, 0,
        -1, 0, 0,
        -1, 0, 0,
        1, 0, 0,
        1, 0, 0
      ],
        "uvs": [
          0, 0, 0.16666666666666666, 1,
          0.16666666666666666, 0, 0.3333333333333333, 1,
          0.3333333333333333, 0, 0.5, 1,
          0.5, 0, 0.6666666666666666, 1,
          0.6666666666666666, 0, 0.8333333333333334, 1,
          0.8333333333333334, 0, 1, 1
        ],
      "indices": [
        0, 1, 2, 0, 2, 3,
        4, 5, 6, 4, 6, 7,
        0, 1, 5, 0, 5, 4,
        2, 3, 7, 2, 7, 6,
        0, 3, 7, 0, 7, 4,
        1, 2, 6, 1, 6, 5
      ],
      "metadata": {
        "formatVersion": 3
      }
    },
    {
      "name": "d6_collider",
      "id": "d6_collider",
      "billboardMode": 0,
      "position": [-0.6, 0, 0],
      "rotationQuaternion": [0, 0, 0, -1],
      "scaling": [1, 1, 1],
      "isVisible": true,
      "isEnabled": true,
      "pickable": false,
      "positions": [
        -1, -1, -1,
         1, -1, -1,
         1,  1, -1,
        -1,  1, -1,
        -1, -1,  1,
         1, -1,  1,
         1,  1,  1,
        -1,  1,  1
      ],
      "normals": [
        0, 0, -1,
        0, 0, -1,
        0, 0,  1,
        0, 0,  1,
        0, -1, 0,
        0, -1, 0,
        0,  1, 0,
        0,  1, 0,
        -1, 0, 0,
        -1, 0, 0,
        1, 0, 0,
        1, 0, 0
      ],
        "tangents": [
            1, 0, 0,
            1, 0, 0,
            1, 0, 0,
            1, 0, 0,

            -1, 0, 0,
            -1, 0, 0,
            -1, 0, 0,
            -1, 0, 0,

            0, 0, -1,
            0, 0, -1,
            0, 0, -1,
            0, 0, -1,

            0, 0, 1,
            0, 0, 1,
            0, 0, 1,
            0, 0, 1,

            1, 0, 0,
            1, 0, 0,
            1, 0, 0,
            1, 0, 0,

            1, 0, 0,
            1, 0, 0,
            1, 0, 0,
            1, 0, 0
        ],
      "uvs": [
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1,
        0, 0, 1, 0, 1, 1, 0, 1
      ],
      "indices": [
        0, 1, 2, 0, 2, 3,
        4, 5, 6, 4, 6, 7,
        0, 1, 5, 0, 5, 4,
        2, 3, 7, 2, 7, 6,
        0, 3, 7, 0, 7, 4,
        1, 2, 6, 1, 6, 5
      ],
      "metadata": {
        "formatVersion": 3
      }
    }
  ]
}