Added expand controls to map dice

This commit is contained in:
Mitchell McCaffrey 2020-05-08 15:09:40 +10:00
parent 07e85f46a3
commit fee7a270e4
5 changed files with 253 additions and 192 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, Fragment, useRef } from "react";
import React, { useState, Fragment } from "react";
import { IconButton, Flex, Box } from "theme-ui";
import RadioIconButton from "./controls/RadioIconButton";
@ -144,9 +144,6 @@ function MapContols({
);
}
const controlsRef = useRef();
const settingsRef = useRef();
function getToolSettings() {
const Settings = toolsById[selectedToolId].SettingsComponent;
if (Settings) {
@ -161,7 +158,6 @@ function MapContols({
borderRadius: "4px",
}}
p={1}
ref={settingsRef}
>
<Settings
settings={toolSettings[selectedToolId]}
@ -189,7 +185,6 @@ function MapContols({
alignItems: "center",
}}
mx={1}
ref={controlsRef}
>
{controls}
</Flex>

View File

@ -1,200 +1,39 @@
import React, { useRef, useState, useCallback } from "react";
import * as BABYLON from "babylonjs";
import { Box } from "theme-ui";
import React, { useState } from "react";
import { Flex, IconButton } from "theme-ui";
import environment from "../../dice/environment.dds";
import ColorDice from "../../dice/color/ColorDice";
import GemStoneDice from "../../dice/gemStone/GemStoneDice";
import GlassDice from "../../dice/glass/GlassDice";
import MetalDice from "../../dice/metal/MetalDice";
import MetalStoneDice from "../../dice/metalStone/MetalStoneDice";
import WoodDice from "../../dice/wood/WoodDice";
import Scene from "./MapDiceScene";
import ExpandMoreDiceIcon from "../../icons/ExpandMoreDiceIcon";
import DiceTray from "./dice/DiceTray";
function MapDice() {
const sceneRef = useRef();
const dieRef = useRef([]);
const dieSleepRef = useRef([]);
const [dieNumbers, setDieNumbers] = useState([]);
const handleSceneMount = useCallback(({ scene, engine }) => {
sceneRef.current = scene;
initializeScene(scene);
engine.runRenderLoop(() => update(scene));
}, []);
async function initializeScene(scene) {
var ground = BABYLON.Mesh.CreateGround("ground", 100, 100, 2, scene);
ground.physicsImpostor = new BABYLON.PhysicsImpostor(
ground,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0, friction: 100.0 },
scene
);
ground.isVisible = false;
function createWall(name, x, z, yaw) {
let wall = BABYLON.Mesh.CreateBox(
name,
50,
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: 1.0 },
scene
);
wall.isVisible = false;
}
createWall("wallTop", 0, -35, 0);
createWall("wallRight", -39, 0, Math.PI / 2);
createWall("wallBottom", 0, 35, Math.PI);
createWall("wallLeft", 39, 0, -Math.PI / 2);
var roof = BABYLON.Mesh.CreateGround("roof", 100, 100, 2, scene);
roof.physicsImpostor = new BABYLON.PhysicsImpostor(
roof,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0, friction: 1.0 },
scene
);
roof.position.y = 10;
roof.isVisible = false;
scene.environmentTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
environment,
scene
);
scene.environmentIntensity = 1.5;
}
function update(scene) {
const die = dieRef.current;
if (die.length === 0) {
return;
}
for (let i = 0; i < die.length; i++) {
const dice = die[i];
const diceAsleep = dieSleepRef.current[i];
const speed = dice.physicsImpostor.getLinearVelocity().length();
if (speed < 0.01 && !diceAsleep) {
let highestDot = -1;
let highestLocator;
for (let locator of dice.getChildTransformNodes()) {
let dif = locator
.getAbsolutePosition()
.subtract(dice.getAbsolutePosition());
let direction = dif.normalize();
const dot = BABYLON.Vector3.Dot(direction, BABYLON.Vector3.Up());
if (dot > highestDot) {
highestDot = dot;
highestLocator = locator;
}
}
dieSleepRef.current[i] = true;
const newNumber = parseInt(highestLocator.name.slice(8));
setDieNumbers((prevNumbers) => {
let newNumbers = [...prevNumbers];
newNumbers[i] = newNumber;
return newNumbers;
});
} else if (speed > 0.5 && diceAsleep) {
dieSleepRef.current[i] = false;
setDieNumbers((prevNumbers) => {
let newNumbers = [...prevNumbers];
newNumbers[i] = null;
return newNumbers;
});
}
}
if (scene) {
scene.render();
}
}
async function handleAddDice(style, type) {
const scene = sceneRef.current;
if (scene) {
const instance = await style.createInstance(type, scene);
dieRef.current.push(instance);
dieSleepRef.current.push(false);
setDieNumbers((prevNumbers) => [...prevNumbers, null]);
}
}
const [isExpanded, setIsExpanded] = useState(false);
return (
<Box
<Flex
sx={{
position: "absolute",
top: 0,
left: 0,
width: "500px",
height: "500px",
borderRadius: "4px",
flexDirection: "column",
alignItems: "flex-start",
}}
bg="overlay"
m={2}
ml={1}
>
<Scene onSceneMount={handleSceneMount} />
<div
style={{
position: "absolute",
top: "8px",
left: "50%",
transform: "translateX(-50%)",
display: "flex",
color: "white",
<IconButton
aria-label={isExpanded ? "Hide Dice Tray" : "Show Dice Tray"}
title={isExpanded ? "Hide Dice Tray" : "Show Dice Tray"}
onClick={() => setIsExpanded(!isExpanded)}
sx={{
transform: `rotate(${isExpanded ? "0" : "180deg"})`,
display: "block",
backgroundColor: "overlay",
borderRadius: "50%",
}}
m={2}
>
{dieNumbers.map((num, index) => (
<h3 key={index}>
{num || "?"}
{index === dieNumbers.length - 1 ? "" : "+"}
</h3>
))}
<h3>
{dieNumbers.length > 0 &&
`= ${dieNumbers.reduce((a, b) => (a || 0) + (b || 0))}`}
</h3>
</div>
<div
style={{
position: "absolute",
bottom: "24px",
left: "50%",
transform: "translateX(-50%)",
}}
>
<button onClick={() => handleAddDice(ColorDice, "d20")}>
Add color d20
</button>
<button onClick={() => handleAddDice(GemStoneDice, "d20")}>
Add gem d20
</button>
<button onClick={() => handleAddDice(GlassDice, "d20")}>
Add glass d20
</button>
<button onClick={() => handleAddDice(MetalDice, "d20")}>
Add metal d20
</button>
<button onClick={() => handleAddDice(MetalStoneDice, "d20")}>
Add metal stone d20
</button>
<button onClick={() => handleAddDice(WoodDice, "d20")}>
Add wood d20
</button>
</div>
</Box>
<ExpandMoreDiceIcon />
</IconButton>
<DiceTray isOpen={isExpanded} />
</Flex>
);
}

View File

@ -3,7 +3,7 @@ import * as BABYLON from "babylonjs";
import * as AMMO from "ammo.js";
import "babylonjs-loaders";
function MapDiceScene({ onSceneMount }) {
function DiceScene({ onSceneMount }) {
const sceneRef = useRef();
const engineRef = useRef();
const canvasRef = useRef();
@ -121,4 +121,4 @@ function MapDiceScene({ onSceneMount }) {
);
}
export default MapDiceScene;
export default DiceScene;

View File

@ -0,0 +1,209 @@
import React, { useRef, useState, useCallback, useEffect } from "react";
import * as BABYLON from "babylonjs";
import { Box } from "theme-ui";
import environment from "../../../dice/environment.dds";
import ColorDice from "../../../dice/color/ColorDice";
import GemStoneDice from "../../../dice/gemStone/GemStoneDice";
import GlassDice from "../../../dice/glass/GlassDice";
import MetalDice from "../../../dice/metal/MetalDice";
import MetalStoneDice from "../../../dice/metalStone/MetalStoneDice";
import WoodDice from "../../../dice/wood/WoodDice";
import Scene from "./DiceScene";
function DiceTray({ isOpen }) {
const sceneRef = useRef();
const dieRef = useRef([]);
const dieSleepRef = useRef([]);
const [dieNumbers, setDieNumbers] = useState([]);
const sceneSleepRef = useRef(true);
useEffect(() => {
if (!isOpen) {
sceneSleepRef.current = true;
} else {
sceneSleepRef.current = false;
}
}, [isOpen]);
const handleSceneMount = useCallback(({ scene, engine }) => {
sceneRef.current = scene;
initializeScene(scene);
engine.runRenderLoop(() => update(scene));
}, []);
async function initializeScene(scene) {
var ground = BABYLON.Mesh.CreateGround("ground", 100, 100, 2, scene);
ground.physicsImpostor = new BABYLON.PhysicsImpostor(
ground,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0, friction: 100.0 },
scene
);
ground.isVisible = false;
function createWall(name, x, z, yaw) {
let wall = BABYLON.Mesh.CreateBox(
name,
50,
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: 1.0 },
scene
);
wall.isVisible = false;
}
createWall("wallTop", 0, -35, 0);
createWall("wallRight", -35, 0, Math.PI / 2);
createWall("wallBottom", 0, 35, Math.PI);
createWall("wallLeft", 35, 0, -Math.PI / 2);
var roof = BABYLON.Mesh.CreateGround("roof", 100, 100, 2, scene);
roof.physicsImpostor = new BABYLON.PhysicsImpostor(
roof,
BABYLON.PhysicsImpostor.BoxImpostor,
{ mass: 0, friction: 1.0 },
scene
);
roof.position.y = 10;
roof.isVisible = false;
scene.environmentTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
environment,
scene
);
scene.environmentIntensity = 1.5;
}
function update(scene) {
const die = dieRef.current;
const shouldSleep = sceneSleepRef.current;
if (die.length === 0 || shouldSleep) {
return;
}
for (let i = 0; i < die.length; i++) {
const dice = die[i];
const diceAsleep = dieSleepRef.current[i];
const speed = dice.physicsImpostor.getLinearVelocity().length();
if (speed < 0.01 && !diceAsleep) {
let highestDot = -1;
let highestLocator;
for (let locator of dice.getChildTransformNodes()) {
let dif = locator
.getAbsolutePosition()
.subtract(dice.getAbsolutePosition());
let direction = dif.normalize();
const dot = BABYLON.Vector3.Dot(direction, BABYLON.Vector3.Up());
if (dot > highestDot) {
highestDot = dot;
highestLocator = locator;
}
}
dieSleepRef.current[i] = true;
const newNumber = parseInt(highestLocator.name.slice(8));
setDieNumbers((prevNumbers) => {
let newNumbers = [...prevNumbers];
newNumbers[i] = newNumber;
return newNumbers;
});
} else if (speed > 0.5 && diceAsleep) {
dieSleepRef.current[i] = false;
setDieNumbers((prevNumbers) => {
let newNumbers = [...prevNumbers];
newNumbers[i] = null;
return newNumbers;
});
}
}
if (scene) {
scene.render();
}
}
async function handleAddDice(style, type) {
const scene = sceneRef.current;
if (scene) {
const instance = await style.createInstance(type, scene);
dieRef.current.push(instance);
dieSleepRef.current.push(false);
setDieNumbers((prevNumbers) => [...prevNumbers, null]);
}
}
return (
<Box
sx={{
width: "500px",
height: "500px",
borderRadius: "4px",
display: isOpen ? "box" : "none",
}}
bg="overlay"
>
<Scene onSceneMount={handleSceneMount} />
<div
style={{
position: "absolute",
top: "8px",
left: "50%",
transform: "translateX(-50%)",
display: "flex",
color: "white",
}}
>
{dieNumbers.map((num, index) => (
<h3 key={index}>
{num || "?"}
{index === dieNumbers.length - 1 ? "" : "+"}
</h3>
))}
<h3>
{dieNumbers.length > 0 &&
`= ${dieNumbers.reduce((a, b) => (a || 0) + (b || 0))}`}
</h3>
</div>
<div
style={{
position: "absolute",
bottom: "24px",
left: "50%",
transform: "translateX(-50%)",
}}
>
<button onClick={() => handleAddDice(ColorDice, "d20")}>
Add color d20
</button>
<button onClick={() => handleAddDice(GemStoneDice, "d20")}>
Add gem d20
</button>
<button onClick={() => handleAddDice(GlassDice, "d20")}>
Add glass d20
</button>
<button onClick={() => handleAddDice(MetalDice, "d20")}>
Add metal d20
</button>
<button onClick={() => handleAddDice(MetalStoneDice, "d20")}>
Add metal stone d20
</button>
<button onClick={() => handleAddDice(WoodDice, "d20")}>
Add wood d20
</button>
</div>
</Box>
);
}
export default DiceTray;

View File

@ -0,0 +1,18 @@
import React from "react";
function ExpandMoreDiceIcon() {
return (
<svg
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentcolor"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M13.006 3.564l5.986 3.42A2 2 0 0120 8.72v6.516a2 2 0 01-1 1.732l-5.983 3.456a2 2 0 01-1.997.002l-6.017-3.459A2 2 0 014 15.233v-6.53a2 2 0 011.015-1.74L11.03 3.56a2 2 0 011.977.004zm-.992 1.737L6 8.703v6.53l6.017 3.459L18 15.236V8.72L12.014 5.3zM13.92 10a1 1 0 01.781 1.625l-1.92 2.399a1 1 0 01-1.56 0l-1.92-2.4A1 1 0 0110.08 10z" />
</svg>
);
}
export default ExpandMoreDiceIcon;