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();