Keyboard shortcuts

This commit is contained in:
Thomas Forgione 2024-06-19 17:42:57 +02:00
parent 75587e697b
commit 3be4e0a1c5
4 changed files with 227 additions and 37 deletions

28
app.py
View File

@ -15,17 +15,25 @@ def calibration():
@app.route("/api/calibration-data") @app.route("/api/calibration-data")
def calibration_data(): def calibration_data():
# Return dummy output
return { return {
'led_0040': [-5.55700564, 8.14046472, 10.54967666], 'leds': [
'led_0090': [-7.06683588, 3.12804841, 6.68884105], {'name': 'led_0010', 'position': [4.7568647, -7.99294013, 7.96453843]},
'led_0070': [4.75589064, -4.53847239, 4.59339772], {'name': 'led_0020', 'position': [-2.72752819, -9.82011953, 9.24452802]},
'led_0020': [-2.70015523, -9.65693344, 9.3807489], {'name': 'led_0030', 'position': [-7.71848326, -3.84971189, 8.54659339]},
'led_0050': [3.26747465, 9.09127322, 8.52493055], {'name': 'led_0040', 'position': [-5.50536593, 8.13132868, 10.61159615]},
'led_0010': [4.69548335, -7.92767838, 7.98930158], {'name': 'led_0050', 'position': [3.2939535, 9.13207673, 8.55962855]},
'led_0030': [-7.78342731, -3.88182141, 8.52941532], {'name': 'led_0060', 'position': [9.95077473, 3.18431267, 10.01643023]},
'led_0060': [9.8648123, 3.14267492, 10.09358765], {'name': 'led_0070', 'position': [4.80205465, -4.56261438, 4.56638023]},
'led_0080': [-3.3681401, -7.6538693, 6.36718535], {'name': 'led_0080', 'position': [-3.37632619, -7.72890365, 6.26207315]},
{'name': 'led_0090', 'position': [-7.05810901, 3.15661764, 6.64505902]}
],
'spheres': [
[0.30415745, -0.17907307, 16.7683142],
[3.98316763, 3.44079019, 17.03800586],
[-2.83333147, 4.22778253, 16.95089368],
[2.88769696, -4.29023411, 16.96762258],
[-2.48223398, -4.19363008, 16.89485543],
],
} }

View File

@ -19,7 +19,7 @@ def print_error(msg: str):
def calibrate(input_dir: str): def calibrate(input_dir: str):
# Load all images # Load all images
image_names = os.listdir(input_dir) image_names = sorted(os.listdir(input_dir))
images = [np.asarray(Image.open(os.path.join(input_dir, x))) for x in image_names] images = [np.asarray(Image.open(os.path.join(input_dir, x))) for x in image_names]
# Camera parameters # Camera parameters
@ -91,7 +91,7 @@ def calibrate(input_dir: str):
light_positions = utils.lines_intersections(sphere_centers, estimated_lights) light_positions = utils.lines_intersections(sphere_centers, estimated_lights)
# Return value as dictionnary # Return value as dictionnary
return dict(zip(image_names, light_positions)) return [{'name': name, 'position': position} for name, position in zip(image_names, light_positions)], sphere_centers
def main(): def main():

View File

@ -56,20 +56,20 @@ class Pyramid extends THREE.Object3D {
geometry.setAttribute('position', new THREE.BufferAttribute(buffer, 3)); geometry.setAttribute('position', new THREE.BufferAttribute(buffer, 3));
const mesh = new THREE.Mesh(geometry, material); const mesh = new THREE.Mesh(geometry, material);
mesh.layers.enable(1);
this.mesh = mesh;
this.add(mesh); this.add(mesh);
} }
// Lines // Lines
{ {
let material = new THREE.LineBasicMaterial({color: 0xff0000}); let material = new THREE.LineBasicMaterial({color: 0x990000});
let geometry = new THREE.BufferGeometry(); let geometry = new THREE.BufferGeometry();
let width = 1; let width = 1;
let height = 1; let height = 1;
let length = 2; let length = 2;
let lines = [ let lines = [
[0, 1], [0, 1],
[0, 2], [0, 2],
@ -97,14 +97,82 @@ class Pyramid extends THREE.Object3D {
geometry.setAttribute('position', new THREE.BufferAttribute(buffer, 3)); geometry.setAttribute('position', new THREE.BufferAttribute(buffer, 3));
const mesh = new THREE.Line(geometry, material); const mesh = new THREE.Line(geometry, material);
mesh.layers.enable(1);
this.lines = mesh;
this.add(mesh); this.add(mesh);
} }
} }
} }
let renderer, scene, camera, controls, leds, cameraObject, domElement, raycaster, pointer, selectedObject, ledView; class Led extends THREE.Mesh {
constructor() {
super(
new THREE.SphereGeometry(0.7, 32, 16),
new THREE.MeshBasicMaterial({ color: 0x555500 }),
);
this.on = false;
this.isHovering = false;
}
hover() {
if (this.on) {
return;
}
this.isHovering = true;
this.refreshColor();
}
unhover() {
if (this.on) {
return;
}
this.isHovering = false;
this.refreshColor();
}
toggle() {
this.on = !this.on;
this.refreshColor();
}
turnOn() {
this.on = true;
this.refreshColor();
}
turnOff() {
this.on = false;
this.refreshColor();
}
refreshColor() {
this.material.color.setHex(this.getColor());
}
getColor() {
if (this.on) {
return 0xffff00;
} else if (this.isHovering) {
return 0x888800;
} else {
return 0x555500;
}
}
}
let center, renderer, scene, camera, controls, pointLight, leds, spheres, cameraObject, domElement, raycaster, pointer, selectedObject, ledView, animationStep, beforeAnimationPosition, beforeAnimationTarget;
async function init(dataPath, domElementArg = document.body) { async function init(dataPath, domElementArg = document.body) {
center = new THREE.Vector3(0, 0, 10);
animationStep = NaN;
beforeAnimationPosition = new THREE.Vector3();
beforeAnimationTarget = new THREE.Vector3();
let request = await fetch('/api/calibration-data'); let request = await fetch('/api/calibration-data');
let data = await request.json(); let data = await request.json();
@ -113,38 +181,53 @@ async function init(dataPath, domElementArg = document.body) {
let h = domElement === document.body ? window.innerHeight : domElement.offsetHeight; let h = domElement === document.body ? window.innerHeight : domElement.offsetHeight;
raycaster = new THREE.Raycaster(); raycaster = new THREE.Raycaster();
raycaster.layers.set(1);
camera = new THREE.PerspectiveCamera(45, w / h, 0.001, 1000); camera = new THREE.PerspectiveCamera(45, w / h, 0.001, 1000);
camera.position.set(0, 0, -50); camera.position.set(0, 0, -30);
scene = new THREE.Scene(); scene = new THREE.Scene();
leds = new THREE.Object3D(); leds = new THREE.Object3D();
for (let key in data) { for (let ledInfo of data.leds) {
let row = data[key]; let row = ledInfo.position;
const geometry = new THREE.SphereGeometry(1, 32, 16); let sphere = new Led();
const material = new THREE.MeshPhongMaterial({ color: 0xffff00 });
let sphere = new THREE.Mesh(geometry, material);
sphere.position.x = -row[1]; sphere.position.x = -row[1];
sphere.position.y = -row[0]; sphere.position.y = -row[0];
sphere.position.z = row[2]; sphere.position.z = row[2];
sphere.name = key sphere.name = ledInfo.name;
sphere.layers.enable(1);
leds.add(sphere); leds.add(sphere);
} }
scene.add(leds); scene.add(leds);
let spheres = new THREE.Object3D();
for (let row of data.spheres) {
const geometry = new THREE.SphereGeometry(1, 32, 16);
const material = new THREE.MeshPhongMaterial({ color: 0xffffff });
let sphere = new THREE.Mesh(geometry, material);
sphere.position.x = -row[1];
sphere.position.y = -row[0];
sphere.position.z = row[2];
sphere.layers.enable(1);
spheres.add(sphere);
}
scene.add(spheres);
cameraObject = new Pyramid(); cameraObject = new Pyramid();
scene.add(cameraObject); scene.add(cameraObject);
const axesHelper = new THREE.AxesHelper(10); const axesHelper = new THREE.AxesHelper(10);
scene.add(axesHelper); scene.add(axesHelper);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); pointLight = new THREE.PointLight(0xffffff, 0);
scene.add(directionalLight); scene.add(pointLight);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.25); const ambientLight = new THREE.AmbientLight(0xffffff, 0.15);
scene.add(ambientLight); scene.add(ambientLight);
renderer = new THREE.WebGLRenderer({antialias: true, alpha: true}); renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
@ -155,11 +238,27 @@ async function init(dataPath, domElementArg = document.body) {
// Add listeners // Add listeners
controls = new OrbitControls(camera, renderer.domElement); controls = new OrbitControls(camera, renderer.domElement);
controls.zoomSpeed = 5;
controls.target.copy(center);
controls.update(); controls.update();
window.addEventListener('pointermove', onPointerMove); window.addEventListener('pointermove', onPointerMove);
window.addEventListener('pointerup', onPointerUp); window.addEventListener('pointerup', onPointerUp);
window.addEventListener('resize', onWindowResize, false); window.addEventListener('resize', onWindowResize, false);
document.addEventListener('keyup', function(e) {
switch (e.code) {
case "ArrowDown":
case "ArrowRight":
nextLed();
break;
case "ArrowUp":
case "ArrowLeft":
previousLed();
break;
}
});
onWindowResize(); onWindowResize();
domElement.appendChild(renderer.domElement); domElement.appendChild(renderer.domElement);
@ -168,15 +267,46 @@ async function init(dataPath, domElementArg = document.body) {
function animate(time) { function animate(time) {
controls.update(); controls.update();
if (!isNaN(animationStep)) {
animationStep += 0.025;
if (animationStep > 1) {
controls.enabled = true;
camera.position.set(0, 0, 0);
controls.target.copy(center);
animationStep = NaN;
controls.update();
} else {
camera.position.set(0, 0, 0);
camera.position.addScaledVector(beforeAnimationPosition, 1 - animationStep);
controls.target.set(0, 0, 0);
controls.target.addScaledVector(beforeAnimationTarget, 1 - animationStep);
controls.target.addScaledVector(center, animationStep);
controls.update();
}
}
if (pointer !== undefined) { if (pointer !== undefined) {
raycaster.setFromCamera(pointer, camera); raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(leds.children); const intersects = raycaster.intersectObjects(scene.children);
const intersection = intersects.length > 0 ? intersects[0].object : undefined; const intersection = intersects.length > 0 ? intersects[0].object : undefined;
if (intersection && intersection.parent instanceof Pyramid) {
cameraObject.mesh.material.opacity = 1;
cameraObject.lines.material.color.setHex(0xff0000);
} else {
cameraObject.mesh.material.opacity = 0.8;
cameraObject.lines.material.color.setHex(0x990000);
}
if (intersection && intersection instanceof Led) {
intersection.hover();
}
for (let led of leds.children) { for (let led of leds.children) {
if (led === intersection) { if (led === intersection) {
led.scale.set(1.5, 1.5, 1.5); led.hover();
} else { } else {
led.scale.set(1, 1, 1); led.unhover();
} }
} }
} }
@ -209,16 +339,68 @@ function onPointerMove(event) {
function onPointerUp(event) { function onPointerUp(event) {
raycaster.setFromCamera(pointer, camera); raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(leds.children); const intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) { if (intersects.length > 0) {
for (let led of leds.children) {
if (led === intersects[0].object) { if (intersects[0].object.parent instanceof Pyramid && intersects[0].distance > 1) {
ledView.src = 'data/small/' + led.name + '.PNG'; triggerAnimation();
led.material.color.setHex(0xff0000); return;
selectedObject.innerText = led.name; }
if (intersects[0].object instanceof Led) {
selectLed(intersects[0].object);
return;
}
}
}
function triggerAnimation() {
beforeAnimationPosition.copy(camera.position);
beforeAnimationTarget.copy(controls.target);
animationStep = 0;
controls.enabled = false;
}
function nextLed() {
for (let index = 0; index < leds.children.length; index++) {
if (leds.children[index].on) {
selectLed(leds.children[(index + 1) % leds.children.length]);
return;
}
}
selectLed(leds.children[0]);
}
function previousLed() {
for (let index = 0; index < leds.children.length; index++) {
if (leds.children[index].on) {
selectLed(leds.children[(index - 1 + leds.children.length) % leds.children.length]);
return;
}
}
selectLed(leds.children[0]);
}
function selectLed(led) {
for (let child of leds.children) {
if (led === child) {
if (led.on) {
led.turnOff();
pointLight.intensity = 0;
ledView.style.display = "none";
selectedObject.innerText = 'aucune';
} else { } else {
led.material.color.setHex(0xffff00); led.turnOn();
pointLight.intensity = 100;
pointLight.position.copy(led.position);
ledView.src = 'data/small/' + led.name + '.PNG';
ledView.style.display = "block";
selectedObject.innerText = led.name;
} }
} else {
child.turnOff();
} }
} }
} }

View File

@ -9,7 +9,7 @@
<div id="visualiser-container"> <div id="visualiser-container">
<div id="visualiser"> <div id="visualiser">
<div id="info"> <div id="info">
Objet sélectionné : <span id="selected-object">aucun</span> LED sélectionnée : <span id="selected-object">aucune</span>
</div> </div>
</div> </div>
</div> </div>