// RotatingCube React component, used in https://lipsum.dev/2020-09-1-rotations/ | |
import * as BABYLON from 'babylonjs' | |
import * as GUI from 'babylonjs-gui' | |
import React, { useEffect, useRef, useState } from 'react' | |
const canvasWidth = 500; | |
const canvasHeight = 300; | |
function initScene(canvas, rotationCb) { | |
const engine = new BABYLON.Engine(canvas); | |
const scene = new BABYLON.Scene(engine); | |
scene.clearColor = BABYLON.Color3.White(); | |
// Camera | |
const camera = new BABYLON.UniversalCamera("UniversalCamera", new BABYLON.Vector3(1, 1, -2), scene); | |
camera.setTarget(BABYLON.Vector3.Zero()); | |
// Lights | |
const hl1 = new BABYLON.HemisphericLight("HemiLight1", new BABYLON.Vector3(0, 1, 0), scene); | |
const hl2 = new BABYLON.HemisphericLight("HemiLight2", new BABYLON.Vector3(0, 0, -1), scene); | |
hl1.intensity = hl2.intensity = 0.6; | |
// Mesh | |
const faceColors = [ | |
BABYLON.Color3.Blue(), | |
BABYLON.Color3.Red(), | |
BABYLON.Color3.Green(), | |
BABYLON.Color3.Purple(), | |
BABYLON.Color3.Black(), | |
BABYLON.Color3.Yellow() | |
]; | |
const options = { | |
width: 1, | |
height: 1, | |
depth: 1, | |
faceColors: faceColors | |
}; | |
const mesh = BABYLON.MeshBuilder.CreateBox("Box", options, scene, true); | |
mesh.rotationQuaternion = BABYLON.Quaternion.Identity(); | |
rotationCb(mesh.rotationQuaternion); | |
// GUI | |
const advancedTexture = GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI"); | |
const btnSize = 30; | |
const btnCommon = { | |
width: `${btnSize-1}px`, | |
height: `${btnSize-1}px`, | |
color: "black", | |
background: "white", | |
}; | |
[ | |
{ | |
label: "←", | |
left: Math.floor(canvasWidth / 2) - 3 * btnSize, | |
top: Math.floor(canvasHeight / 2) - btnSize, | |
rotAxis: BABYLON.Axis.Y, | |
rotAngle: Math.PI/2 | |
}, | |
{ | |
label: "↓", | |
left: Math.floor(canvasWidth / 2) - 2 * btnSize, | |
top: Math.floor(canvasHeight / 2) - btnSize, | |
rotAxis: BABYLON.Axis.X, | |
rotAngle: -Math.PI/2 | |
}, | |
{ | |
label: "→", | |
left: Math.floor(canvasWidth / 2) - btnSize, | |
top: Math.floor(canvasHeight / 2) - btnSize, | |
rotAxis: BABYLON.Axis.Y, | |
rotAngle: -Math.PI/2 | |
}, | |
{ | |
label: "↑", | |
left: Math.floor(canvasWidth / 2) - 2 * btnSize, | |
top: Math.floor(canvasHeight / 2) - 2 * btnSize, | |
rotAxis: BABYLON.Axis.X, | |
rotAngle: Math.PI/2 | |
}, | |
].forEach((btnConf, idx) => { | |
const btn = GUI.Button.CreateSimpleButton(`btn_${idx}`, btnConf.label); | |
btn.left = `${btnConf.left}px`; | |
btn.top = `${btnConf.top}px`; | |
Object.assign(btn, btnCommon); | |
advancedTexture.addControl(btn); | |
btn.onPointerClickObservable.add(() => { | |
animateRotation(BABYLON.Quaternion.RotationAxis(btnConf.rotAxis, btnConf.rotAngle)); | |
}) | |
}); | |
scene.onKeyboardObservable.add((kbInfo) => { | |
if (kbInfo.type != BABYLON.KeyboardEventTypes.KEYDOWN) return; | |
switch (kbInfo.event.key) { | |
case "Left": // IE/Edge specific value | |
case "ArrowLeft": | |
animateRotation(BABYLON.Quaternion.RotationAxis(BABYLON.Axis.Y, Math.PI/2)); | |
break; | |
case "Up": // IE/Edge specific value | |
case "ArrowUp": | |
animateRotation(BABYLON.Quaternion.RotationAxis(BABYLON.Axis.X, Math.PI/2)); | |
break; | |
case "Right": // IE/Edge specific value | |
case "ArrowRight": | |
animateRotation(BABYLON.Quaternion.RotationAxis(BABYLON.Axis.Y, -Math.PI/2)); | |
break; | |
case "Down": // IE/Edge specific value | |
case "ArrowDown": | |
animateRotation(BABYLON.Quaternion.RotationAxis(BABYLON.Axis.X, -Math.PI/2)); | |
break; | |
} | |
}); | |
var busy = false; | |
function animateRotation(rotationQ, initQ, targetQ, steps=5, done=0, duration=500) { | |
if (done === 0 && busy) return; | |
else if (done === steps) { | |
rotationCb(mesh.rotationQuaternion); | |
busy = false; | |
return; | |
}; | |
busy = true; | |
if (!(initQ instanceof BABYLON.Quaternion)) initQ = mesh.rotationQuaternion; | |
if (!(targetQ instanceof BABYLON.Quaternion)) targetQ = rotationQ.multiply(initQ); | |
mesh.rotationQuaternion = BABYLON.Quaternion.Slerp(initQ, targetQ, (done + 1) / steps); | |
setTimeout(animateRotation.bind(null, rotationQ, initQ, targetQ, steps, done + 1, duration), duration / steps); | |
} | |
engine.runRenderLoop(function () { scene.render() }); | |
} | |
const coordLabels = ['w', 'x', 'y', 'z']; | |
function RotationQuaternion(props) { | |
function format(v, i) { | |
return `${coordLabels[i]}: ${(v === null) ? "—" : v.toFixed(2)}`; | |
} | |
return <div style={{fontSize: "0.7rem", fontStyle: "italic", textAlign: "center", marginBottom: "1rem"}}> | |
Quaternion : {props.coords.map(format).join(", ")} | |
</div> | |
} | |
function RotatingCube () { | |
const canvasRef = useRef(null); | |
const [coords, setCoords] = useState(coordLabels.map(() => null)); | |
useEffect(() => { | |
const canvas = canvasRef.current | |
initScene(canvas, function (q) { | |
console.log(q); | |
if (q !== null) setCoords(coordLabels.map(l => q[l])); | |
}); | |
}, []); | |
return <div> | |
<canvas ref={canvasRef} width={canvasWidth} height={canvasHeight}></canvas> | |
<RotationQuaternion coords={coords} /> | |
</div> | |
} | |
export { RotatingCube } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment