Added multiple dice tray sizes

This commit is contained in:
Mitchell McCaffrey 2020-05-27 11:24:17 +10:00
parent 0a5b33a69f
commit b786b176b0
17 changed files with 269 additions and 81 deletions

View File

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { Flex, IconButton } from "theme-ui";
import ExpandMoreDiceIcon from "../../icons/ExpandMoreDiceIcon";
import DiceTray from "./dice/DiceTray";
import DiceTrayOverlay from "./dice/DiceTrayOverlay";
function MapDice() {
const [isExpanded, setIsExpanded] = useState(false);
@ -32,7 +32,7 @@ function MapDice() {
>
<ExpandMoreDiceIcon isExpanded={isExpanded} />
</IconButton>
<DiceTray isOpen={isExpanded} />
<DiceTrayOverlay isOpen={isExpanded} />
</Flex>
);
}

View File

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { Flex } from "theme-ui";
import { Flex, IconButton } from "theme-ui";
import D20Icon from "../../../icons/D20Icon";
import D12Icon from "../../../icons/D12Icon";
@ -8,6 +8,7 @@ import D8Icon from "../../../icons/D8Icon";
import D6Icon from "../../../icons/D6Icon";
import D4Icon from "../../../icons/D4Icon";
import D100Icon from "../../../icons/D100Icon";
import ExpandMoreDiceTrayIcon from "../../../icons/ExpandMoreDiceTrayIcon";
import DiceButton from "./DiceButton";
import SelectDiceButton from "./SelectDiceButton";
@ -16,7 +17,12 @@ import Divider from "../../Divider";
import { dice } from "../../../dice";
function DiceButtons({ diceRolls, onDiceAdd }) {
function DiceButtons({
diceRolls,
onDiceAdd,
diceTraySize,
onDiceTraySizeChange,
}) {
const [currentDice, setCurrentDice] = useState(dice[0]);
const diceCounts = {};
@ -91,6 +97,23 @@ function DiceButtons({ diceRolls, onDiceAdd }) {
>
<D100Icon />
</DiceButton>
<Divider vertical color="hsl(210, 50%, 96%)" />
<IconButton
aria-label={
diceTraySize === "single" ? "Expand Dice Tray" : "Shrink Dice Tray"
}
title={
diceTraySize === "single" ? "Expand Dice Tray" : "Shrink Dice Tray"
}
sx={{
transform: diceTraySize === "single" ? "rotate(0)" : "rotate(180deg)",
}}
onClick={() =>
onDiceTraySizeChange(diceTraySize === "single" ? "double" : "single")
}
>
<ExpandMoreDiceTrayIcon />
</IconButton>
</Flex>
);
}

View File

@ -10,6 +10,8 @@ function DiceControls({
onDiceAdd,
onDiceClear,
onDiceReroll,
diceTraySize,
onDiceTraySizeChange,
}) {
const [diceRolls, setDiceRolls] = useState([]);
@ -114,6 +116,8 @@ function DiceControls({
{ type, roll: "unknown" },
]);
}}
onDiceTraySizeChange={onDiceTraySizeChange}
diceTraySize={diceTraySize}
/>
</div>
</>

View File

@ -74,7 +74,7 @@ function DiceScene({ onSceneMount, onPointerDown, onPointerUp }) {
const scene = sceneRef.current;
if (scene) {
const pickInfo = scene.pick(scene.pointerX, scene.pointerY);
if (pickInfo.hit && pickInfo.pickedMesh.id !== "tray") {
if (pickInfo.hit && pickInfo.pickedMesh.name !== "dice_tray") {
pickInfo.pickedMesh.physicsImpostor.setLinearVelocity(
BABYLON.Vector3.Zero()
);

View File

@ -1,4 +1,10 @@
import React, { useRef, useCallback, useEffect, useContext } from "react";
import React, {
useRef,
useCallback,
useEffect,
useContext,
useState,
} from "react";
import * as BABYLON from "babylonjs";
import { Box } from "theme-ui";
@ -8,13 +14,11 @@ import Scene from "./DiceScene";
import DiceControls from "./DiceControls";
import Dice from "../../../dice/Dice";
import createDiceTray, {
diceTraySize,
} from "../../../dice/diceTray/DiceTrayMesh";
import DiceTray from "../../../dice/diceTray/DiceTray";
import MapInteractionContext from "../../../contexts/MapInteractionContext";
function DiceTray({ isOpen }) {
function DiceTrayOverlay({ isOpen }) {
const sceneRef = useRef();
const shadowGeneratorRef = useRef();
const diceRefs = useRef([]);
@ -22,6 +26,26 @@ function DiceTray({ isOpen }) {
const sceneInteractionRef = useRef(false);
// Set to true to ignore scene sleep and visible values
const forceSceneRenderRef = useRef(false);
const diceTrayRef = useRef();
const [diceTraySize, setDiceTraySize] = useState("single");
useEffect(() => {
const diceTray = diceTrayRef.current;
let resizeTimout;
if (diceTray) {
diceTray.size = diceTraySize;
// Force rerender
forceSceneRenderRef.current = true;
resizeTimout = setTimeout(() => {
forceSceneRenderRef.current = false;
}, 1000);
}
return () => {
if (resizeTimout) {
clearTimeout(resizeTimout);
}
};
}, [diceTraySize]);
useEffect(() => {
let openTimeout;
@ -49,7 +73,7 @@ function DiceTray({ isOpen }) {
}, []);
async function initializeScene(scene) {
var light = new BABYLON.DirectionalLight(
let light = new BABYLON.DirectionalLight(
"DirectionalLight",
new BABYLON.Vector3(-0.5, -1, -0.5),
scene
@ -62,7 +86,7 @@ function DiceTray({ isOpen }) {
shadowGenerator.darkness = 0.7;
shadowGeneratorRef.current = shadowGenerator;
var ground = BABYLON.Mesh.CreateGround("ground", 100, 100, 2, scene);
let ground = BABYLON.Mesh.CreateGround("ground", 100, 100, 2, scene);
ground.physicsImpostor = new BABYLON.PhysicsImpostor(
ground,
BABYLON.PhysicsImpostor.BoxImpostor,
@ -72,36 +96,7 @@ function DiceTray({ isOpen }) {
ground.isVisible = false;
ground.position.y = 0.2;
const wallSize = 50;
function createWall(name, x, z, yaw) {
let wall = BABYLON.Mesh.CreateBox(
name,
wallSize,
scene,
true,
BABYLON.Mesh.DOUBLESIDE
);
wall.rotation = new BABYLON.Vector3(0, yaw, 0);
wall.position.z = z;
wall.position.x = x;
wall.physicsImpostor = new BABYLON.PhysicsImpostor(
wall,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0, friction: 10.0 },
scene
);
wall.isVisible = false;
}
const wallOffsetWidth = wallSize / 2 + diceTraySize.width / 2 - 0.5;
const wallOffsetHeight = wallSize / 2 + diceTraySize.height / 2 - 0.5;
createWall("wallTop", 0, -wallOffsetHeight, 0);
createWall("wallRight", -wallOffsetWidth, 0, Math.PI / 2);
createWall("wallBottom", 0, wallOffsetHeight, Math.PI);
createWall("wallLeft", wallOffsetWidth, 0, -Math.PI / 2);
var roof = BABYLON.Mesh.CreateGround("roof", 100, 100, 2, scene);
let roof = BABYLON.Mesh.CreateGround("roof", 100, 100, 2, scene);
roof.physicsImpostor = new BABYLON.PhysicsImpostor(
roof,
BABYLON.PhysicsImpostor.BoxImpostor,
@ -117,7 +112,9 @@ function DiceTray({ isOpen }) {
);
scene.environmentIntensity = 1.0;
createDiceTray(scene, shadowGenerator);
let diceTray = new DiceTray("single", scene, shadowGenerator);
await diceTray.load();
diceTrayRef.current = diceTray;
}
function update(scene) {
@ -222,9 +219,12 @@ function DiceTray({ isOpen }) {
return (
<Box
sx={{
width: "500px",
maxWidth: "calc(50vh - 48px)",
paddingBottom: "200%",
width: diceTraySize === "single" ? "500px" : "1000px",
maxWidth:
diceTraySize === "single"
? "calc(50vh - 48px)"
: "calc(100vh - 48px)",
paddingBottom: diceTraySize === "single" ? "200%" : "100%",
borderRadius: "4px",
display: isOpen ? "block" : "none",
position: "relative",
@ -249,9 +249,11 @@ function DiceTray({ isOpen }) {
onDiceAdd={handleDiceAdd}
onDiceClear={handleDiceClear}
onDiceReroll={handleDiceReroll}
diceTraySize={diceTraySize}
onDiceTraySizeChange={setDiceTraySize}
/>
</Box>
);
}
export default DiceTray;
export default DiceTrayOverlay;

View File

@ -26,7 +26,12 @@ function SelectDiceButton({ onDiceChange, currentDice }) {
return (
<>
<IconButton color="hsl(210, 50%, 96%)" onClick={openModal}>
<IconButton
aria-label="Select Dice Style"
title="Select Dice Style"
color="hsl(210, 50%, 96%)"
onClick={openModal}
>
<SelectDiceIcon />
</IconButton>
<SelectDiceModal

View File

@ -8,8 +8,6 @@ import d12Source from "./meshes/d12.glb";
import d20Source from "./meshes/d20.glb";
import d100Source from "./meshes/d100.glb";
import { diceTraySize } from "./diceTray/DiceTrayMesh";
import { lerp } from "../helpers/shared";
const minDiceRollSpeed = 400;
@ -83,11 +81,20 @@ class Dice {
instance.physicsImpostor.setLinearVelocity(BABYLON.Vector3.Zero());
instance.physicsImpostor.setAngularVelocity(BABYLON.Vector3.Zero());
const trayOffsetHeight = diceTraySize.height / 2 - 0.5;
const scene = instance.getScene();
const diceTraySingle = scene.getNodeByID("dice_tray_single");
const diceTrayDouble = scene.getNodeByID("dice_tray_double");
const visibleDiceTray = diceTraySingle.isVisible
? diceTraySingle
: diceTrayDouble;
const trayBounds = visibleDiceTray.getBoundingInfo().boundingBox;
const position = new BABYLON.Vector3(
((Math.random() * 2 - 1) * diceTraySize.width) / 2,
lerp(trayBounds.minimumWorld.x, trayBounds.maximumWorld.x, Math.random()),
5,
Math.random() > 0.5 ? trayOffsetHeight : -trayOffsetHeight
Math.random() > 0.5
? trayBounds.maximumWorld.z
: trayBounds.minimumWorld.z
);
instance.position = position;
instance.addRotation(

View File

@ -0,0 +1,156 @@
import * as BABYLON from "babylonjs";
import singleMeshSource from "../meshes/diceTraySingle.glb";
import doubleMeshSource from "../meshes/diceTrayDouble.glb";
import singleAlbedo from "./singleAlbedo.jpg";
import singleMetalRoughness from "./singleMetalRoughness.jpg";
import singleNormal from "./singleNormal.jpg";
import doubleAlbedo from "./doubleAlbedo.jpg";
import doubleMetalRoughness from "./doubleMetalRoughness.jpg";
import doubleNormal from "./doubleNormal.jpg";
class DiceTray {
_size;
get size() {
return this._size;
}
set size(newSize) {
this._size = newSize;
const wallOffsetWidth = this.wallSize / 2 + this.width / 2 - 0.5;
const wallOffsetHeight = this.wallSize / 2 + this.height / 2 - 0.5;
this.wallTop.position.z = -wallOffsetHeight;
this.wallRight.position.x = -wallOffsetWidth;
this.wallBottom.position.z = wallOffsetHeight;
this.wallLeft.position.x = wallOffsetWidth;
this.singleMesh.isVisible = newSize === "single";
this.doubleMesh.isVisible = newSize === "double";
}
scene;
shadowGenerator;
get width() {
return this.size === "single" ? 10 : 20;
}
height = 20;
wallSize = 50;
wallTop;
wallRight;
wallBottom;
wallLeft;
singleMesh;
doubleMesh;
constructor(initialSize, scene, shadowGenerator) {
this._size = initialSize;
this.scene = scene;
this.shadowGenerator = shadowGenerator;
}
async load() {
this.loadWalls();
await this.loadMeshes();
}
createWall(name, x, z, yaw) {
let wall = BABYLON.Mesh.CreateBox(
name,
this.wallSize,
this.scene,
true,
BABYLON.Mesh.DOUBLESIDE
);
wall.rotation = new BABYLON.Vector3(0, yaw, 0);
wall.position.z = z;
wall.position.x = x;
wall.physicsImpostor = new BABYLON.PhysicsImpostor(
wall,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0, friction: 10.0 },
this.scene
);
wall.isVisible = false;
return wall;
}
loadWalls() {
const wallOffsetWidth = this.wallSize / 2 + this.width / 2 - 0.5;
const wallOffsetHeight = this.wallSize / 2 + this.height / 2 - 0.5;
this.wallTop = this.createWall("wallTop", 0, -wallOffsetHeight, 0);
this.wallRight = this.createWall(
"wallRight",
-wallOffsetWidth,
0,
Math.PI / 2
);
this.wallBottom = this.createWall(
"wallBottom",
0,
wallOffsetHeight,
Math.PI
);
this.wallLeft = this.createWall(
"wallLeft",
wallOffsetWidth,
0,
-Math.PI / 2
);
}
async loadMeshes() {
this.singleMesh = (
await BABYLON.SceneLoader.ImportMeshAsync(
"",
singleMeshSource,
"",
this.scene
)
).meshes[1];
this.singleMesh.id = "dice_tray_single";
this.singleMesh.name = "dice_tray";
let singleMaterial = new BABYLON.PBRMaterial(
"dice_tray_mat_single",
this.scene
);
singleMaterial.albedoTexture = new BABYLON.Texture(singleAlbedo);
singleMaterial.normalTexture = new BABYLON.Texture(singleNormal);
singleMaterial.metallicTexture = new BABYLON.Texture(singleMetalRoughness);
singleMaterial.useRoughnessFromMetallicTextureAlpha = false;
singleMaterial.useRoughnessFromMetallicTextureGreen = true;
singleMaterial.useMetallnessFromMetallicTextureBlue = true;
this.singleMesh.material = singleMaterial;
this.singleMesh.receiveShadows = true;
this.shadowGenerator.addShadowCaster(this.singleMesh);
this.singleMesh.isVisible = this.size === "single";
this.doubleMesh = (
await BABYLON.SceneLoader.ImportMeshAsync(
"",
doubleMeshSource,
"",
this.scene
)
).meshes[1];
this.doubleMesh.id = "dice_tray_double";
this.doubleMesh.name = "dice_tray";
let doubleMaterial = new BABYLON.PBRMaterial(
"dice_tray_mat_double",
this.scene
);
doubleMaterial.albedoTexture = new BABYLON.Texture(doubleAlbedo);
doubleMaterial.normalTexture = new BABYLON.Texture(doubleNormal);
doubleMaterial.metallicTexture = new BABYLON.Texture(doubleMetalRoughness);
doubleMaterial.useRoughnessFromMetallicTextureAlpha = false;
doubleMaterial.useRoughnessFromMetallicTextureGreen = true;
doubleMaterial.useMetallnessFromMetallicTextureBlue = true;
this.doubleMesh.material = doubleMaterial;
this.doubleMesh.receiveShadows = true;
this.shadowGenerator.addShadowCaster(this.doubleMesh);
this.doubleMesh.isVisible = this.size === "double";
}
}
export default DiceTray;

View File

@ -1,28 +0,0 @@
import * as BABYLON from "babylonjs";
import meshSource from "../meshes/diceTraySingle.glb";
import albedo from "./albedo.jpg";
import metalRoughness from "./metalRoughness.jpg";
import normal from "./normal.jpg";
export const diceTraySize = { width: 10, height: 20 };
export default async function createDiceTray(scene, shadowGenerator) {
let mesh = (
await BABYLON.SceneLoader.ImportMeshAsync("", meshSource, "", scene)
).meshes[1];
mesh.id = "tray";
let material = new BABYLON.PBRMaterial("dice_tray_mat", scene);
material.albedoTexture = new BABYLON.Texture(albedo);
material.normalTexture = new BABYLON.Texture(normal);
material.metallicTexture = new BABYLON.Texture(metalRoughness);
material.useRoughnessFromMetallicTextureAlpha = false;
material.useRoughnessFromMetallicTextureGreen = true;
material.useMetallnessFromMetallicTextureBlue = true;
mesh.material = material;
mesh.receiveShadows = true;
shadowGenerator.addShadowCaster(mesh);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

View File

@ -0,0 +1,19 @@
import React from "react";
function ExpandMoreDotIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
fill="currentcolor"
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M20.08,11.42l-4.04-5.65C15.7,5.29,15.15,5,14.56,5h0c-1.49,0-2.35,1.68-1.49,2.89L16,12l-2.93,4.11 c-0.87,1.21,0,2.89,1.49,2.89h0c0.59,0,1.15-0.29,1.49-0.77l4.04-5.65C20.33,12.23,20.33,11.77,20.08,11.42z" />
<path d="M13.08,11.42L9.05,5.77C8.7,5.29,8.15,5,7.56,5h0C6.07,5,5.2,6.68,6.07,7.89L9,12l-2.93,4.11C5.2,17.32,6.07,19,7.56,19h0 c0.59,0,1.15-0.29,1.49-0.77l4.04-5.65C13.33,12.23,13.33,11.77,13.08,11.42z" />
</svg>
);
}
export default ExpandMoreDotIcon;