Skip to content

Commit

Permalink
Merge pull request #44 from 3d-dice/bugfix-1.0.3
Browse files Browse the repository at this point in the history
Bugfix 1.0.3
  • Loading branch information
frankieali authored Jun 8, 2022
2 parents 7cca25c + c0652ff commit 00563e1
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 196 deletions.
220 changes: 109 additions & 111 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"name": "Frank Ali"
},
"description": "A 3D environment for rolling game dice",
"version": "1.0.2",
"version": "1.0.3",
"keywords": [
"3D",
"dice",
Expand Down Expand Up @@ -41,16 +41,16 @@
"postinstall": "node copyAssets.js"
},
"dependencies": {
"@babylonjs/core": "^5.8.0",
"@babylonjs/loaders": "^5.8.0",
"@babylonjs/materials": "^5.8.0",
"@babylonjs/core": "^5.9.1",
"@babylonjs/loaders": "^5.9.1",
"@babylonjs/materials": "^5.9.1",
"copy-dir": "^1.3.0",
"node-abort-controller": "^3.0.1"
},
"devDependencies": {
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-visualizer": "^5.6.0",
"vite": "^2.9.9"
"vite": "^2.9.10"
},
"browserslist": {
"production": [
Expand Down
72 changes: 32 additions & 40 deletions src/WorldFacad.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { Color3 } from '@babylonjs/core/Maths/math.color'
import { createCanvas } from './components/world/canvas'
import physicsWorker from './components/physics.worker.js?worker&inline'
import { debounce, createAsyncQueue, Random } from './helpers'

const defaultOptions = {
id: `dice-canvas-${Date.now()}`, // set the canvas id
enableShadows: true, // do dice cast shadows onto DiceBox mesh?
shadowOpacity: .8,
shadowTransparency: .8,
lightIntensity: 1,
delay: 10, // delay between dice being generated - 0 causes stuttering and physics popping
gravity: 2, // note: high gravity will cause dice piles to jiggle
startingHeight: 8, // height to drop the dice from - will not exceed the DiceBox height set by zoom
spinForce: 4, // passed on to physics as an impulse force
throwForce: 5, // passed on to physics as linear velocity
scale: 6, // scale the dice
scale: 5, // scale the dice
theme: 'default', // can be a hex color or a pre-defined theme such as 'purpleRock'
themeColor: '#2e8555', // used for color values or named theme variants - not fully implemented yet // green: #2e8555 // yellow: #feea03
offscreen: true, // use offscreen canvas browser feature for performance improvements - will fallback to false based on feature detection
Expand All @@ -38,28 +35,26 @@ class WorldFacad {
#DicePhysics
#dicePhysicsPromise
#dicePhysicsResolve
onDieComplete = () => {}
onRollComplete = () => {}
onRemoveComplete = () => {}
onThemeConfigLoaded = () => {}
onThemeLoaded = () => {}
noop = () => {}

constructor(container, options = {}){
if(typeof options !== 'object') {
throw new Error('Config options should be an object. Config reference: https://fantasticdice.games/docs/usage/config#configuration-options')
}
// pull out callback functions from options
const { onDieComplete, onRollComplete, onRemoveComplete, onThemeConfigLoaded, onThemeLoaded, ...boxOptions } = options

// extend defaults with options
this.config = {...defaultOptions, ...options}
// if options do not provide a theme color then it should be null
if(options.theme){
if(options.themeColor) {
this.config.themeColor = options.themeColor
} else {
this.config.themeColor = null
}
}
this.config = {...defaultOptions, ...boxOptions}

// assign callback functions
this.onDieComplete = options.onDieComplete || this.noop
this.onRollComplete = options.onRollComplete || this.noop
this.onRemoveComplete = options.onRemoveComplete || this.noop
this.onThemeLoaded = options.onThemeLoaded || this.noop
this.onThemeConfigLoaded = options.onThemeConfigLoaded || this.noop


// this.config.themeColor = options.theme ? options.themeColor ? options.themeColor : null : this.config.themeColor
// if a canvas selector is provided then that will be used for the dicebox, otherwise a canvas will be created using the config.id
this.canvas = createCanvas({
selector: container,
Expand Down Expand Up @@ -302,17 +297,6 @@ class WorldFacad {
}
}

if(themeData.material.type === 'color') {
if (!this.config.themeColor || !themeData.themeColor){
themeData.themeColor ??= defaultOptions.themeColor
this.config.themeColor = themeData.themeColor
}
} else if(themeData.material.type !== 'color') {
if (this.config.themeColor || themeData.themeColor){
// null them both out
this.config.themeColor = themeData.themeColor = null
}
}

Object.assign(themeData,
{
Expand Down Expand Up @@ -357,10 +341,7 @@ class WorldFacad {
const newConfig = {...this.config,...options}
// console.log('newConfig', newConfig)
const config = await this.loadThemeQueue.push(() => this.loadTheme(newConfig.theme))
const themeData = config.at(-1) //get the last entry returned from the queue
if(themeData.material.type !== 'color') {
newConfig.themeColor = undefined
}
// const themeData = config.at(-1) //get the last entry returned from the queue

this.config = newConfig
// pass updates to DiceWorld
Expand Down Expand Up @@ -409,7 +390,7 @@ class WorldFacad {
}

// TODO: pass data with roll - such as roll name. Passed back at the end in the results
roll(notation, {theme = undefined,newStartPoint = true} = {}) {
roll(notation, {theme, themeColor, newStartPoint = true} = {}) {
// note: to add to a roll on screen use .add method
// reset the offscreen worker and physics worker with each new roll
this.clear()
Expand All @@ -419,6 +400,7 @@ class WorldFacad {
id: collectionId,
notation,
theme,
themeColor,
newStartPoint
})

Expand All @@ -429,14 +411,15 @@ class WorldFacad {
return this.rollCollectionData[collectionId].promise
}

add(notation, {theme = undefined,newStartPoint = true} = {}) {
add(notation, {theme, themeColor, newStartPoint = true} = {}) {

const collectionId = this.#collectionIndex++

this.rollCollectionData[collectionId] = new Collection({
id: collectionId,
notation,
theme,
themeColor,
newStartPoint
})

Expand Down Expand Up @@ -514,9 +497,16 @@ class WorldFacad {
const loadTheme = () => this.loadTheme(theme)
await this.loadThemeQueue.push(loadTheme)

const {meshName, diceAvailable, diceInherited = {}} = this.themesLoadedData[theme]
const {meshName, diceAvailable, diceInherited = {}, material: { type: materialType }} = this.themesLoadedData[theme]
const diceExtra = Object.keys(diceInherited)

let colorSuffix = '', color

if(materialType === "color") {
color = Color3.FromHexString(themeColor)
colorSuffix = ((color.r*256*0.299 + color.g*256*0.587 + color.b*256*0.114) > 175) ? '_dark' : '_light'
}

// TODO: should I validate that added dice are only joining groups of the same "sides" value - e.g.: d6's can only be added to groups when sides: 6? Probably.
for (var i = 0, len = notation.qty; i < len; i++) {
// id's start at zero and zero can be falsy, so we check for undefined
Expand Down Expand Up @@ -563,6 +553,8 @@ class WorldFacad {
newStartPoint,
theme: parentTheme?.systemName || theme,
meshName: parentTheme?.meshName || meshName,
colorSuffix,
color
})
}

Expand Down Expand Up @@ -644,7 +636,7 @@ class WorldFacad {
parse(notation) {
const diceNotation = /(\d+)[dD](\d+)(.*)$/i
const percentNotation = /(\d+)[dD]([0%]+)(.*)$/i
const fudgeNotation = /(\d+)[dD]([fF]+)(.*)$/i
const fudgeNotation = /(\d+)df+(ate)*$/i
const modifier = /([+-])(\d+)/
const cleanNotation = notation.trim().replace(/\s+/g, '')
const validNumber = (n, err) => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/Container.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ class Container{

// Bottom of the Box
const ground = CreateBox("ground",{
width: this.size,
width: this.size * 2,
height: 1,
depth: this.size
depth: this.size * 2
}, this.config.scene)
ground.scaling = new Vector3(aspect, 1, 1)
ground.material = boxMaterial
Expand Down
50 changes: 17 additions & 33 deletions src/components/Dice.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { SceneLoader } from '@babylonjs/core/Loading/sceneLoader'
import { Vector3 } from '@babylonjs/core/Maths/math.vector'
import { Color3 } from '@babylonjs/core/Maths/math.color'
import { Ray } from "@babylonjs/core/Culling/ray";
// import { RayHelper } from '@babylonjs/core/Debug';
import '../helpers/babylonFileLoader'
Expand All @@ -17,8 +16,6 @@ const defaultOptions = {
lights: [],
rollId: null,
scene: null,
sides: 6,
theme: 'purpleRock'
}

// TODO: this would probably be better as a factory pattern
Expand All @@ -36,50 +33,41 @@ class Dice {
}

createInstance() {
const { colorSuffix, color } = Dice.parseColor(this.config.themeColor)

// piece together the name of the die we want to instance
const targetDie = `${this.config.meshName}_${this.dieType}_${this.config.theme}${colorSuffix}`
const targetDie = `${this.config.meshName}_${this.dieType}_${this.config.theme}${this.config.colorSuffix}`
// create a new unique name for this instance
const instanceName = `${targetDie}-instance-${this.id}`

// create the instance
const dieInstance = this.scene.getMeshByName(targetDie).createInstance(instanceName)

if(color){
dieInstance.instancedBuffers.customColor = color
if(this.config.color){
dieInstance.instancedBuffers.customColor = this.config.color
}

// start the instance under the floor, out of camera view
dieInstance.position.y = -100
dieInstance.scaling = new Vector3(this.config.scale,this.config.scale,this.config.scale)

if(this.config.enableShadows){
for (const key in this.config.lights) {
if(key !== 'hemispheric' ) {
this.config.lights[key].shadowGenerator.addShadowCaster(dieInstance)
}
}
// let's keep this simple for now since we know there's only one directional light
this.config.lights["directional"].shadowGenerator.addShadowCaster(dieInstance)
// for (const key in this.config.lights) {
// if(key !== 'hemispheric' ) {
// this.config.lights[key].shadowGenerator.addShadowCaster(dieInstance)
// }
// }
}

// attach the instance to the class object
this.mesh = dieInstance
}

static parseColor(themeColor){
let colorSuffix = ''
let color = themeColor ? Color3.FromHexString(themeColor) : undefined
if (color && (color.r*256*0.299 + color.g*256*0.587 + color.b*256*0.114) > 175){
colorSuffix = '_dark'
} else {
colorSuffix = '_light'
}
return {colorSuffix, color}
}


// TODO: add themeOptions for colored materials, must ensure theme and themeOptions are unique somehow
static async loadDie(options, scene) {
const { sides, theme = 'default', themeColor, meshName} = options
const { colorSuffix } = Dice.parseColor(themeColor)
const { sides, theme = 'default', meshName, colorSuffix} = options

// create a key for this die type and theme for caching and instance creation
const dieMeshName = meshName + '_d' + sides
Expand All @@ -89,17 +77,13 @@ class Dice {
if (!die) {
die = scene.getMeshByName(dieMeshName).clone(dieMaterialName)
}

if(!die.material) {
if(themeColor){
if (colorSuffix === '_dark'){
die.material = scene.getMaterialByName(theme + '_dark')
} else {
die.material = scene.getMaterialByName(theme + '_light')
}
die.material = scene.getMaterialByName(theme + colorSuffix)
if(colorSuffix.length > 0){
die.registerInstancedBuffer("customColor", 3)
} else {
die.material = scene.getMaterialByName(theme)
}

// die.material.freeze()
}

Expand Down
13 changes: 13 additions & 0 deletions src/components/physics.worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ const addBoxToWorld = (size, height) => {
const tempParts = []
// ground
const localInertia = setVector3(0, 0, 0);

const groundTransform = new Ammo.btTransform()
groundTransform.setIdentity()
groundTransform.setOrigin(setVector3(0, -.5, 0))
Expand All @@ -320,6 +321,18 @@ const addBoxToWorld = (size, height) => {
physicsWorld.addRigidBody(groundBody)
tempParts.push(groundBody)

const ceilingTransform = new Ammo.btTransform()
ceilingTransform.setIdentity()
ceilingTransform.setOrigin(setVector3(0, height - .5, 0))
const ceilingShape = new Ammo.btBoxShape(setVector3(size * aspect, 1, size))
const ceilingMotionState = new Ammo.btDefaultMotionState(ceilingTransform)
const ceilingInfo = new Ammo.btRigidBodyConstructionInfo(0, ceilingMotionState, ceilingShape, localInertia)
const ceilingBody = new Ammo.btRigidBody(ceilingInfo)
ceilingBody.setFriction(config.friction)
ceilingBody.setRestitution(config.restitution)
physicsWorld.addRigidBody(ceilingBody)
tempParts.push(ceilingBody)

const wallTopTransform = new Ammo.btTransform()
wallTopTransform.setIdentity()
wallTopTransform.setOrigin(setVector3(0, 0, (size/-2) - .5))
Expand Down
6 changes: 3 additions & 3 deletions src/components/world.onscreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class WorldOnscreen {
this.#camera = createCamera({engine:this.#engine, scene: this.#scene})
this.#lights = createLights({
enableShadows: this.config.enableShadows,
shadowOpacity: this.config.shadowOpacity,
shadowTransparency: this.config.shadowTransparency,
intensity: this.config.lightIntensity,
scene: this.#scene
})
Expand Down Expand Up @@ -106,8 +106,8 @@ class WorldOnscreen {
}
})
}
if(prevConfig.shadowOpacity !== this.config.shadowOpacity) {
this.#lights.directional.shadowGenerator.darkness = this.config.shadowOpacity
if(prevConfig.shadowTransparency !== this.config.shadowTransparency) {
this.#lights.directional.shadowGenerator.darkness = this.config.shadowTransparency
}
if(prevConfig.lightIntensity !== this.config.lightIntensity) {
this.#lights.directional.intensity = .65 * this.config.lightIntensity
Expand Down
4 changes: 2 additions & 2 deletions src/components/world/lights.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const defaultOptions = {
}

function createLights(options = defaultOptions) {
const { enableShadows, shadowOpacity, intensity, scene } = options
const { enableShadows, shadowTransparency, intensity, scene } = options
const d_light = new DirectionalLight("DirectionalLight", new Vector3(-0.3, -1, 0.4), scene)
d_light.position = new Vector3(-50,65,-50)
d_light.intensity = .65 * intensity
Expand All @@ -23,7 +23,7 @@ function createLights(options = defaultOptions) {
// d_light.autoCalcShadowZBounds = true
d_light.shadowGenerator = new ShadowGenerator(2048, d_light);
d_light.shadowGenerator.useCloseExponentialShadowMap = true; // best
d_light.shadowGenerator.darkness = shadowOpacity
d_light.shadowGenerator.darkness = shadowTransparency
// d_light.shadowGenerator.usePoissonSampling = true
// d_light.shadowGenerator.bias = .01
}
Expand Down

0 comments on commit 00563e1

Please sign in to comment.