Added continuous dice roll calculation and scene render sleep

This commit is contained in:
Mitchell McCaffrey 2020-05-14 22:51:06 +10:00
parent d9c928dfcd
commit 6e027cd9a3
5 changed files with 269 additions and 169 deletions

View File

@ -5,7 +5,7 @@ import ExpandMoreDiceIcon from "../../icons/ExpandMoreDiceIcon";
import DiceTray from "./dice/DiceTray";
function MapDice() {
const [isExpanded, setIsExpanded] = useState(true);
const [isExpanded, setIsExpanded] = useState(false);
return (
<Flex

View File

@ -0,0 +1,81 @@
import React from "react";
import { Flex } from "theme-ui";
import SunsetDice from "../../../dice/galaxy/GalaxyDice";
import D20Icon from "../../../icons/D20Icon";
import D12Icon from "../../../icons/D12Icon";
import D10Icon from "../../../icons/D10Icon";
import D8Icon from "../../../icons/D8Icon";
import D6Icon from "../../../icons/D6Icon";
import D4Icon from "../../../icons/D4Icon";
import D100Icon from "../../../icons/D100Icon";
import DiceButton from "./DiceButton";
function DiceButtons({ diceRolls, onDiceAdd }) {
const diceCounts = {};
for (let dice of diceRolls) {
if (dice.type in diceCounts) {
diceCounts[dice.type] += 1;
} else {
diceCounts[dice.type] = 1;
}
}
return (
<Flex>
<DiceButton
title="Add D20"
count={diceCounts.d20}
onClick={() => onDiceAdd(SunsetDice, "d20")}
>
<D20Icon />
</DiceButton>
<DiceButton
title="Add D12"
count={diceCounts.d12}
onClick={() => onDiceAdd(SunsetDice, "d12")}
>
<D12Icon />
</DiceButton>
<DiceButton
title="Add D10"
count={diceCounts.d10}
onClick={() => onDiceAdd(SunsetDice, "d10")}
>
<D10Icon />
</DiceButton>
<DiceButton
title="Add D8"
count={diceCounts.d8}
onClick={() => onDiceAdd(SunsetDice, "d8")}
>
<D8Icon />
</DiceButton>
<DiceButton
title="Add D6"
count={diceCounts.d6}
onClick={() => onDiceAdd(SunsetDice, "d6")}
>
<D6Icon />
</DiceButton>
<DiceButton
title="Add D4"
count={diceCounts.d4}
onClick={() => onDiceAdd(SunsetDice, "d4")}
>
<D4Icon />
</DiceButton>
<DiceButton
title="Add D100"
count={diceCounts.d100}
onClick={() => onDiceAdd(SunsetDice, "d100")}
>
<D100Icon />
</DiceButton>
</Flex>
);
}
export default DiceButtons;

View File

@ -1,80 +1,123 @@
import React from "react";
import { Flex } from "theme-ui";
import React, { useEffect, useState } from "react";
import * as BABYLON from "babylonjs";
import SunsetDice from "../../../dice/galaxy/GalaxyDice";
import DiceButtons from "./DiceButtons";
import DiceResults from "./DiceResults";
import D20Icon from "../../../icons/D20Icon";
import D12Icon from "../../../icons/D12Icon";
import D10Icon from "../../../icons/D10Icon";
import D8Icon from "../../../icons/D8Icon";
import D6Icon from "../../../icons/D6Icon";
import D4Icon from "../../../icons/D4Icon";
import D100Icon from "../../../icons/D100Icon";
function DiceControls({
diceRefs,
sceneVisibleRef,
onDiceAdd,
onDiceClear,
onDiceReroll,
}) {
const [diceRolls, setDiceRolls] = useState([]);
import DiceButton from "./DiceButton";
function DiceControls({ diceRolls, onDiceAdd }) {
const diceCounts = {};
for (let dice of diceRolls) {
if (dice.type in diceCounts) {
diceCounts[dice.type] += 1;
} else {
diceCounts[dice.type] = 1;
// Update dice rolls
useEffect(() => {
// Find the number facing up on a dice object
function getDiceRoll(dice) {
let number = getDiceInstanceRoll(dice.instance);
// If the dice is a d100 add the d10
if (dice.type === "d100") {
const d10Number = getDiceInstanceRoll(dice.d10Instance);
// Both zero set to 100
if (d10Number === 0 && number === 0) {
number = 100;
} else {
number += d10Number;
}
} else if (dice.type === "d10" && number === 0) {
number = 10;
}
return { type: dice.type, roll: number };
}
}
// Find the number facing up on a mesh instance of a dice
function getDiceInstanceRoll(instance) {
let highestDot = -1;
let highestLocator;
for (let locator of instance.getChildTransformNodes()) {
let dif = locator
.getAbsolutePosition()
.subtract(instance.getAbsolutePosition());
let direction = dif.normalize();
const dot = BABYLON.Vector3.Dot(direction, BABYLON.Vector3.Up());
if (dot > highestDot) {
highestDot = dot;
highestLocator = locator;
}
}
return parseInt(highestLocator.name.slice(12));
}
function updateDiceRolls() {
const die = diceRefs.current;
const sceneVisible = sceneVisibleRef.current;
if (!sceneVisible) {
return;
}
const diceAwake = die.map((dice) => dice.asleep).includes(false);
if (!diceAwake) {
return;
}
let newRolls = [];
for (let i = 0; i < die.length; i++) {
const dice = die[i];
let roll = getDiceRoll(dice);
newRolls[i] = roll;
}
setDiceRolls(newRolls);
}
const updateInterval = setInterval(updateDiceRolls, 100);
return () => {
clearInterval(updateInterval);
};
}, [diceRefs, sceneVisibleRef]);
return (
<Flex>
<DiceButton
title="Add D20"
count={diceCounts.d20}
onClick={() => onDiceAdd(SunsetDice, "d20")}
<>
<div
style={{
position: "absolute",
bottom: "16px",
left: 0,
right: 0,
display: "flex",
color: "white",
}}
>
<D20Icon />
</DiceButton>
<DiceButton
title="Add D12"
count={diceCounts.d12}
onClick={() => onDiceAdd(SunsetDice, "d12")}
<DiceResults
diceRolls={diceRolls}
onDiceClear={() => {
onDiceClear();
setDiceRolls([]);
}}
onDiceReroll={onDiceReroll}
/>
</div>
<div
style={{
position: "absolute",
top: "24px",
left: "50%",
transform: "translateX(-50%)",
}}
>
<D12Icon />
</DiceButton>
<DiceButton
title="Add D10"
count={diceCounts.d10}
onClick={() => onDiceAdd(SunsetDice, "d10")}
>
<D10Icon />
</DiceButton>
<DiceButton
title="Add D8"
count={diceCounts.d8}
onClick={() => onDiceAdd(SunsetDice, "d8")}
>
<D8Icon />
</DiceButton>
<DiceButton
title="Add D6"
count={diceCounts.d6}
onClick={() => onDiceAdd(SunsetDice, "d6")}
>
<D6Icon />
</DiceButton>
<DiceButton
title="Add D4"
count={diceCounts.d4}
onClick={() => onDiceAdd(SunsetDice, "d4")}
>
<D4Icon />
</DiceButton>
<DiceButton
title="Add D100"
count={diceCounts.d100}
onClick={() => onDiceAdd(SunsetDice, "d100")}
>
<D100Icon />
</DiceButton>
</Flex>
<DiceButtons
diceRolls={diceRolls}
onDiceAdd={(style, type) => {
onDiceAdd(style, type);
setDiceRolls((prevRolls) => [
...prevRolls,
{ type, roll: "unknown" },
]);
}}
/>
</div>
</>
);
}

View File

@ -3,7 +3,7 @@ import * as BABYLON from "babylonjs";
import * as AMMO from "ammo.js";
import "babylonjs-loaders";
function DiceScene({ onSceneMount }) {
function DiceScene({ onSceneMount, onPointerDown, onPointerUp }) {
const sceneRef = useRef();
const engineRef = useRef();
const canvasRef = useRef();
@ -86,6 +86,7 @@ function DiceScene({ onSceneMount }) {
selectedMeshRef.current = pickInfo.pickedMesh;
}
}
onPointerDown();
}
function handlePointerUp() {
@ -105,6 +106,8 @@ function DiceScene({ onSceneMount }) {
}
selectedMeshRef.current = null;
selectedMeshDeltaPositionRef.current = null;
onPointerUp();
}
return (
@ -121,4 +124,9 @@ function DiceScene({ onSceneMount }) {
);
}
DiceScene.defaultProps = {
onPointerDown() {},
onPointerUp() {},
};
export default DiceScene;

View File

@ -1,4 +1,4 @@
import React, { useRef, useState, useCallback, useEffect } from "react";
import React, { useRef, useCallback, useEffect } from "react";
import * as BABYLON from "babylonjs";
import { Box } from "theme-ui";
@ -6,7 +6,6 @@ import environment from "../../../dice/environment.dds";
import Scene from "./DiceScene";
import DiceControls from "./DiceControls";
import DiceResults from "./DiceResults";
import Dice from "../../../dice/Dice";
import createDiceTray, {
@ -16,18 +15,29 @@ import createDiceTray, {
function DiceTray({ isOpen }) {
const sceneRef = useRef();
const shadowGeneratorRef = useRef();
const dieRef = useRef([]);
const dieSleepRef = useRef([]);
const [diceRolls, setDiceRolls] = useState([]);
const sceneSleepRef = useRef(true);
const diceRefs = useRef([]);
const sceneVisibleRef = useRef(false);
const sceneInteractionRef = useRef(false);
// Set to true to ignore scene sleep and visible values
const forceSceneRenderRef = useRef(false);
useEffect(() => {
if (!isOpen) {
sceneSleepRef.current = true;
let openTimeout;
if (isOpen) {
sceneVisibleRef.current = true;
// Force scene rendering on open for 1s to ensure dice tray is rendered
forceSceneRenderRef.current = true;
openTimeout = setTimeout(() => {
forceSceneRenderRef.current = false;
}, 1000);
} else {
sceneSleepRef.current = false;
sceneVisibleRef.current = false;
}
return () => {
if (openTimeout) {
clearTimeout(openTimeout);
}
};
}, [isOpen]);
const handleSceneMount = useCallback(({ scene, engine }) => {
@ -124,69 +134,34 @@ function DiceTray({ isOpen }) {
}
}
// Find the number facing up on a dice object
function getDiceRoll(dice) {
let number = getDiceInstanceRoll(dice.instance);
// If the dice is a d100 add the d10
if (dice.type === "d100") {
const d10Number = getDiceInstanceRoll(dice.d10Instance);
// Both zero set to 100
if (d10Number === 0 && number === 0) {
number = 100;
} else {
number += d10Number;
}
} else if (dice.type === "d10" && number === 0) {
number = 10;
}
return { type: dice.type, roll: number };
const die = diceRefs.current;
const sceneVisible = sceneVisibleRef.current;
if (!sceneVisible) {
return;
}
// Find the number facing up on a mesh instance of a dice
function getDiceInstanceRoll(instance) {
let highestDot = -1;
let highestLocator;
for (let locator of instance.getChildTransformNodes()) {
let dif = locator
.getAbsolutePosition()
.subtract(instance.getAbsolutePosition());
let direction = dif.normalize();
const dot = BABYLON.Vector3.Dot(direction, BABYLON.Vector3.Up());
if (dot > highestDot) {
highestDot = dot;
highestLocator = locator;
}
}
return parseInt(highestLocator.name.slice(12));
}
const die = dieRef.current;
const shouldSleep = sceneSleepRef.current;
if (shouldSleep) {
const sceneInteraction = sceneInteractionRef.current;
const forceSceneRender = forceSceneRenderRef.current;
const diceAwake = die.map((dice) => dice.asleep).includes(false);
// Return early if scene doesn't need to be re-rendered
if (!forceSceneRender && !sceneInteraction && !diceAwake) {
return;
}
for (let i = 0; i < die.length; i++) {
const dice = die[i];
const diceIsAsleep = dieSleepRef.current[i];
const speed = getDiceSpeed(dice);
if (speed < 0.01 && !diceIsAsleep) {
dieSleepRef.current[i] = true;
let roll = getDiceRoll(dice);
setDiceRolls((prevRolls) => {
let newRolls = [...prevRolls];
newRolls[i] = roll;
return newRolls;
});
} else if (speed > 0.5 && diceIsAsleep) {
dieSleepRef.current[i] = false;
setDiceRolls((prevRolls) => {
let newRolls = [...prevRolls];
newRolls[i].roll = "unknown";
return newRolls;
});
// If the speed has been below 0.01 for 1s set dice to sleep
if (speed < 0.01 && !dice.sleepTimout) {
dice.sleepTimout = setTimeout(() => {
dice.asleep = true;
}, 1000);
} else if (speed > 0.5 && (dice.asleep || dice.sleepTimout)) {
dice.asleep = false;
clearTimeout(dice.sleepTimout);
dice.sleepTimout = null;
}
}
if (scene) {
scene.render();
}
@ -199,7 +174,7 @@ function DiceTray({ isOpen }) {
const instance = await style.createInstance(type, scene);
shadowGenerator.addShadowCaster(instance);
Dice.roll(instance);
let dice = { type, instance };
let dice = { type, instance, asleep: false };
// If we have a d100 add a d10 as well
if (type === "d100") {
const d10Instance = await style.createInstance("d10", scene);
@ -207,32 +182,36 @@ function DiceTray({ isOpen }) {
Dice.roll(d10Instance);
dice.d10Instance = d10Instance;
}
dieRef.current.push(dice);
dieSleepRef.current.push(false);
setDiceRolls((prevRolls) => [...prevRolls, { type, roll: "unknown" }]);
diceRefs.current.push(dice);
}
}
function handleDiceClear() {
const die = dieRef.current;
const die = diceRefs.current;
for (let dice of die) {
dice.instance.dispose();
if (dice.type === "d100") {
dice.d10Instance.dispose();
}
}
dieRef.current = [];
dieSleepRef.current = [];
setDiceRolls([]);
diceRefs.current = [];
// Force scene rendering to show cleared dice
forceSceneRenderRef.current = true;
setTimeout(() => {
if (forceSceneRenderRef) {
forceSceneRenderRef.current = false;
}
}, 100);
}
function handleDiceReroll() {
const die = dieRef.current;
const die = diceRefs.current;
for (let dice of die) {
Dice.roll(dice.instance);
if (dice.type === "d100") {
Dice.roll(dice.d10Instance);
}
dice.asleep = false;
}
}
@ -248,33 +227,22 @@ function DiceTray({ isOpen }) {
}}
bg="background"
>
<Scene onSceneMount={handleSceneMount} />
<div
style={{
position: "absolute",
bottom: "16px",
left: 0,
right: 0,
display: "flex",
color: "white",
<Scene
onSceneMount={handleSceneMount}
onPointerDown={() => {
sceneInteractionRef.current = true;
}}
>
<DiceResults
diceRolls={diceRolls}
onDiceClear={handleDiceClear}
onDiceReroll={handleDiceReroll}
/>
</div>
<div
style={{
position: "absolute",
top: "24px",
left: "50%",
transform: "translateX(-50%)",
onPointerUp={() => {
sceneInteractionRef.current = false;
}}
>
<DiceControls diceRolls={diceRolls} onDiceAdd={handleDiceAdd} />
</div>
/>
<DiceControls
diceRefs={diceRefs}
sceneVisibleRef={sceneVisibleRef}
onDiceAdd={handleDiceAdd}
onDiceClear={handleDiceClear}
onDiceReroll={handleDiceReroll}
/>
</Box>
);
}