diff --git a/src/components/map/MapDice.js b/src/components/map/MapDice.js index fb7b7ae..1f606e0 100644 --- a/src/components/map/MapDice.js +++ b/src/components/map/MapDice.js @@ -4,6 +4,8 @@ import { Flex, IconButton } from "theme-ui"; import ExpandMoreDiceIcon from "../../icons/ExpandMoreDiceIcon"; import DiceTrayOverlay from "./dice/DiceTrayOverlay"; +import { DiceLoadingProvider } from "../../contexts/DiceLoadingContext"; + function MapDice() { const [isExpanded, setIsExpanded] = useState(false); @@ -32,7 +34,9 @@ function MapDice() { > - + + + ); } diff --git a/src/components/map/dice/DiceButtons.js b/src/components/map/dice/DiceButtons.js index 08b1890..0d2d3c7 100644 --- a/src/components/map/dice/DiceButtons.js +++ b/src/components/map/dice/DiceButtons.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Flex, IconButton } from "theme-ui"; import D20Icon from "../../../icons/D20Icon"; @@ -20,10 +20,18 @@ import { dice } from "../../../dice"; function DiceButtons({ diceRolls, onDiceAdd, + onDiceLoad, diceTraySize, onDiceTraySizeChange, }) { - const [currentDice, setCurrentDice] = useState(dice[0]); + const [currentDice, setCurrentDice] = useState(); + + useEffect(() => { + const initialDice = dice[0]; + onDiceLoad(initialDice); + setCurrentDice(initialDice); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const diceCounts = {}; for (let dice of diceRolls) { @@ -34,6 +42,11 @@ function DiceButtons({ } } + async function handleDiceChange(dice) { + await onDiceLoad(dice); + setCurrentDice(dice); + } + return ( diff --git a/src/components/map/dice/DiceControls.js b/src/components/map/dice/DiceControls.js index df5ee28..4902f65 100644 --- a/src/components/map/dice/DiceControls.js +++ b/src/components/map/dice/DiceControls.js @@ -10,6 +10,7 @@ function DiceControls({ onDiceAdd, onDiceClear, onDiceReroll, + onDiceLoad, diceTraySize, onDiceTraySizeChange, }) { @@ -116,6 +117,7 @@ function DiceControls({ { type, roll: "unknown" }, ]); }} + onDiceLoad={onDiceLoad} onDiceTraySizeChange={onDiceTraySizeChange} diceTraySize={diceTraySize} /> diff --git a/src/components/map/dice/DiceTrayOverlay.js b/src/components/map/dice/DiceTrayOverlay.js index 6b0dbba..f47daf3 100644 --- a/src/components/map/dice/DiceTrayOverlay.js +++ b/src/components/map/dice/DiceTrayOverlay.js @@ -13,10 +13,12 @@ import environment from "../../../dice/environment.dds"; import Scene from "./DiceScene"; import DiceControls from "./DiceControls"; import Dice from "../../../dice/Dice"; +import LoadingOverlay from "../../LoadingOverlay"; import DiceTray from "../../../dice/diceTray/DiceTray"; import MapInteractionContext from "../../../contexts/MapInteractionContext"; +import DiceLoadingContext from "../../../contexts/DiceLoadingContext"; function DiceTrayOverlay({ isOpen }) { const sceneRef = useRef(); @@ -29,6 +31,10 @@ function DiceTrayOverlay({ isOpen }) { const diceTrayRef = useRef(); const [diceTraySize, setDiceTraySize] = useState("single"); + const { assetLoadStart, assetLoadFinish, isLoading } = useContext( + DiceLoadingContext + ); + useEffect(() => { const diceTray = diceTrayRef.current; let resizeTimout; @@ -70,9 +76,11 @@ function DiceTrayOverlay({ isOpen }) { sceneRef.current = scene; initializeScene(scene); engine.runRenderLoop(() => update(scene)); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); async function initializeScene(scene) { + assetLoadStart(); let light = new BABYLON.DirectionalLight( "DirectionalLight", new BABYLON.Vector3(-0.5, -1, -0.5), @@ -95,6 +103,7 @@ function DiceTrayOverlay({ isOpen }) { let diceTray = new DiceTray("single", scene, shadowGenerator); await diceTray.load(); diceTrayRef.current = diceTray; + assetLoadFinish(); } function update(scene) { @@ -194,6 +203,15 @@ function DiceTrayOverlay({ isOpen }) { } } + async function handleDiceLoad(dice) { + assetLoadStart(); + const scene = sceneRef.current; + if (scene) { + await dice.class.load(scene); + } + assetLoadFinish(); + } + const { setPreventMapInteraction } = useContext(MapInteractionContext); return ( @@ -229,9 +247,11 @@ function DiceTrayOverlay({ isOpen }) { onDiceAdd={handleDiceAdd} onDiceClear={handleDiceClear} onDiceReroll={handleDiceReroll} + onDiceLoad={handleDiceLoad} diceTraySize={diceTraySize} onDiceTraySizeChange={setDiceTraySize} /> + {isLoading && } ); } diff --git a/src/contexts/DiceLoadingContext.js b/src/contexts/DiceLoadingContext.js new file mode 100644 index 0000000..4d75d40 --- /dev/null +++ b/src/contexts/DiceLoadingContext.js @@ -0,0 +1,31 @@ +import React, { useState } from "react"; + +const DiceLoadingContext = React.createContext(); + +export function DiceLoadingProvider({ children }) { + const [loadingAssetCount, setLoadingAssetCount] = useState(0); + + function assetLoadStart() { + setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets + 1); + } + + function assetLoadFinish() { + setLoadingAssetCount((prevLoadingAssets) => prevLoadingAssets - 1); + } + + const isLoading = loadingAssetCount > 0; + + const value = { + assetLoadStart, + assetLoadFinish, + isLoading, + }; + + return ( + + {children} + + ); +} + +export default DiceLoadingContext; diff --git a/src/dice/galaxy/GalaxyDice.js b/src/dice/galaxy/GalaxyDice.js index bdd0ec1..2c345ab 100644 --- a/src/dice/galaxy/GalaxyDice.js +++ b/src/dice/galaxy/GalaxyDice.js @@ -8,7 +8,7 @@ class GalaxyDice extends Dice { static meshes; static material; - static async createInstance(diceType, scene) { + static async load(scene) { if (!this.material) { this.material = this.loadMaterial( "galaxy_pbr", @@ -19,6 +19,12 @@ class GalaxyDice extends Dice { if (!this.meshes) { this.meshes = await this.loadMeshes(this.material, scene); } + } + + static async createInstance(diceType, scene) { + if (!this.material || !this.meshes) { + throw Error("Dice not loaded, call load before creating an instance"); + } return Dice.createInstance( this.meshes[diceType], diff --git a/src/dice/iron/IronDice.js b/src/dice/iron/IronDice.js index 61393e9..1f9c29f 100644 --- a/src/dice/iron/IronDice.js +++ b/src/dice/iron/IronDice.js @@ -13,7 +13,7 @@ class IronDice extends Dice { return { mass: properties.mass * 2, friction: properties.friction }; } - static async createInstance(diceType, scene) { + static async load(scene) { if (!this.material) { this.material = this.loadMaterial( "iron_pbr", @@ -24,6 +24,12 @@ class IronDice extends Dice { if (!this.meshes) { this.meshes = await this.loadMeshes(this.material, scene); } + } + + static async createInstance(diceType, scene) { + if (!this.material || !this.meshes) { + throw Error("Dice not loaded, call load before creating an instance"); + } return Dice.createInstance( this.meshes[diceType], diff --git a/src/dice/nebula/NebulaDice.js b/src/dice/nebula/NebulaDice.js index 6e4c261..19f614b 100644 --- a/src/dice/nebula/NebulaDice.js +++ b/src/dice/nebula/NebulaDice.js @@ -8,10 +8,10 @@ class NebulaDice extends Dice { static meshes; static material; - static async createInstance(diceType, scene) { + static async load(scene) { if (!this.material) { this.material = this.loadMaterial( - "nebula_pbr", + "neubula_pbr", { albedo, metalRoughness, normal }, scene ); @@ -19,6 +19,12 @@ class NebulaDice extends Dice { if (!this.meshes) { this.meshes = await this.loadMeshes(this.material, scene); } + } + + static async createInstance(diceType, scene) { + if (!this.material || !this.meshes) { + throw Error("Dice not loaded, call load before creating an instance"); + } return Dice.createInstance( this.meshes[diceType], diff --git a/src/dice/sunrise/SunriseDice.js b/src/dice/sunrise/SunriseDice.js index e9cd654..62f0db1 100644 --- a/src/dice/sunrise/SunriseDice.js +++ b/src/dice/sunrise/SunriseDice.js @@ -8,7 +8,7 @@ class SunriseDice extends Dice { static meshes; static material; - static async createInstance(diceType, scene) { + static async load(scene) { if (!this.material) { this.material = this.loadMaterial( "sunrise_pbr", @@ -19,6 +19,12 @@ class SunriseDice extends Dice { if (!this.meshes) { this.meshes = await this.loadMeshes(this.material, scene); } + } + + static async createInstance(diceType, scene) { + if (!this.material || !this.meshes) { + throw Error("Dice not loaded, call load before creating an instance"); + } return Dice.createInstance( this.meshes[diceType], diff --git a/src/dice/sunset/SunsetDice.js b/src/dice/sunset/SunsetDice.js index e4240d8..5ea5d84 100644 --- a/src/dice/sunset/SunsetDice.js +++ b/src/dice/sunset/SunsetDice.js @@ -8,7 +8,7 @@ class SunsetDice extends Dice { static meshes; static material; - static async createInstance(diceType, scene) { + static async load(scene) { if (!this.material) { this.material = this.loadMaterial( "sunset_pbr", @@ -19,6 +19,12 @@ class SunsetDice extends Dice { if (!this.meshes) { this.meshes = await this.loadMeshes(this.material, scene); } + } + + static async createInstance(diceType, scene) { + if (!this.material || !this.meshes) { + throw Error("Dice not loaded, call load before creating an instance"); + } return Dice.createInstance( this.meshes[diceType], diff --git a/src/dice/walnut/WalnutDice.js b/src/dice/walnut/WalnutDice.js index f17c8eb..0ba0ae5 100644 --- a/src/dice/walnut/WalnutDice.js +++ b/src/dice/walnut/WalnutDice.js @@ -8,7 +8,7 @@ class WalnutDice extends Dice { static meshes; static material; - static async createInstance(diceType, scene) { + static async load(scene) { if (!this.material) { this.material = this.loadMaterial( "walnut_pbr", @@ -19,6 +19,12 @@ class WalnutDice extends Dice { if (!this.meshes) { this.meshes = await this.loadMeshes(this.material, scene); } + } + + static async createInstance(diceType, scene) { + if (!this.material || !this.meshes) { + throw Error("Dice not loaded, call load before creating an instance"); + } return Dice.createInstance( this.meshes[diceType],