Added multiple dice tray sizes
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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()
|
||||
);
|
||||
|
@ -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;
|
@ -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
|
||||
|
@ -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(
|
||||
|
156
src/dice/diceTray/DiceTray.js
Normal 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;
|
@ -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);
|
||||
}
|
BIN
src/dice/diceTray/doubleAlbedo.jpg
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
src/dice/diceTray/doubleMetalRoughness.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
src/dice/diceTray/doubleNormal.jpg
Normal file
After Width: | Height: | Size: 1.2 MiB |
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
BIN
src/dice/meshes/diceTrayDouble.glb
Normal file
19
src/icons/ExpandMoreDiceTrayIcon.js
Normal 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;
|