This week was interface week! I wanted to experiment with three.js, WebBluetooth, and the ESP32 in service of my Final Project - light-up juggling balls. The idea is to have a simluator that lets you test out light patterns before uploading them to the juggling balls.
See the interface here: Juggling Light Simluator Interface and corresponding ESP32 Arduino code.
dat.gui was nice to work with. I created menu items I could use to trigger a WebBluetooth connection.
I was able to have a button that would trigger the bluetooth device selector list. In my lab, that list was hundreds of items long. Unwieldy!
var scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); var geometry = new THREE.SphereGeometry(.4, 32, 32); var material = new THREE.MeshPhongMaterial( { color: 0x156289, emissive: 0x072534, side: THREE.DoubleSide, flatShading: true } ); var material1 = new THREE.MeshPhongMaterial( { color: 0x156289, emissive: 0x072534, side: THREE.DoubleSide, flatShading: true } ); var material2 = new THREE.MeshPhongMaterial( { color: 0x156289, emissive: 0x072534, side: THREE.DoubleSide, flatShading: true } ); const gravity = -0.001; let balls = [ new THREE.Mesh(geometry, material), new THREE.Mesh(geometry, material1), new THREE.Mesh(geometry, material2) ]; balls[0].position.x = 1; balls[0].position.y = 0; balls[0].velocity = {x: 0, y: 0}; balls[1].position.x = -1; balls[1].position.y = 0; balls[1].velocity = {x: 0, y: 0}; balls[2].position.x = 1.8; balls[2].position.y = 0; balls[2].velocity = {x: -.01, y: 0}; balls.forEach((o) => scene.add(o)) let characteristic; const datGui = new dat.GUI({autoPlace: true}); datGui.domElement.id = 'gui' datGui.add({throw1: () => throwBall(balls[0])}, 'throw1') datGui.add({connect: () => { navigator.bluetooth.requestDevice({ acceptAllDevices: true, optionalServices: ['4fafc201-1fb5-459e-8fcc-c5c9c331914b'] }) .then(device => device.gatt.connect()) .then(server => server.getPrimaryService('4fafc201-1fb5-459e-8fcc-c5c9c331914b')) .then(service => service.getCharacteristic('beb5483e-36e1-4688-b7f5-ea07361b26a8')) .then(characteristic => { // Writing 1 is the signal to reset energy expended. var resetEnergyExpended = Uint8Array.of(1); return characteristic.writeValue(resetEnergyExpended); }) .then(_ => { console.log('Energy expended has been reset.'); }) .catch(error => { console.log(error); }); }}, 'connect') datGui.add({upload: () => throwBall(balls[0])}, 'upload') camera.position.z = 5; var renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); let currentBall = 2; function throwBall() { currentBall = (currentBall + 1) % 3 balls[currentBall].velocity.y = .1; let isRightHand = balls[currentBall].position.x > 0; let throwSpeedX = .01; balls[currentBall].velocity.x = isRightHand ? -throwSpeedX : throwSpeedX; } function throwIfClose() { if (balls[currentBall].position.y < -1.5 && balls[currentBall].velocity.y < 0) { throwBall() } } var controls = new THREE.OrbitControls( camera, renderer.domElement ); controls.autoRotate = true; controls.autoRotateSpeed = 5; function animate() { requestAnimationFrame(animate); throwIfClose(); controls.update(); balls.forEach((o) => { if (o.position.y > 1) { o.material.color.setHex(0xff0000); // there is also setHSV and setRGB o.material.emissive.setHex(0xff0000); // there is also setHSV and setRGB } else { o.material.color.setHex(0x156289); // there is also setHSV and setRGB o.material.emissive.setHex(0x072534); // there is also setHSV and setRGB } if (o.position.y <= -2 && o.velocity.y <= 0) { o.velocity.y = 0; o.velocity.x = 0; o.velocity.x = 0; o.position.y = -2; o.material.color.setHex(0xFF00FF); // there is also setHSV and setRGB o.material.emissive.setHex(0xFF00FF); // there is also setHSV and setRGB } else { o.velocity.y += gravity; } o.position.x += o.velocity.x; o.position.y += o.velocity.y; o.rotation.x += 0.01; o.rotation.y += 0.01; }); renderer.render(scene, camera); } animate();