diff --git a/app.py b/app.py index 456c440..3672e96 100755 --- a/app.py +++ b/app.py @@ -15,17 +15,25 @@ def calibration(): @app.route("/api/calibration-data") def calibration_data(): - # Return dummy output return { - 'led_0040': [-5.55700564, 8.14046472, 10.54967666], - 'led_0090': [-7.06683588, 3.12804841, 6.68884105], - 'led_0070': [4.75589064, -4.53847239, 4.59339772], - 'led_0020': [-2.70015523, -9.65693344, 9.3807489], - 'led_0050': [3.26747465, 9.09127322, 8.52493055], - 'led_0010': [4.69548335, -7.92767838, 7.98930158], - 'led_0030': [-7.78342731, -3.88182141, 8.52941532], - 'led_0060': [9.8648123, 3.14267492, 10.09358765], - 'led_0080': [-3.3681401, -7.6538693, 6.36718535], + 'leds': [ + {'name': 'led_0010', 'position': [4.7568647, -7.99294013, 7.96453843]}, + {'name': 'led_0020', 'position': [-2.72752819, -9.82011953, 9.24452802]}, + {'name': 'led_0030', 'position': [-7.71848326, -3.84971189, 8.54659339]}, + {'name': 'led_0040', 'position': [-5.50536593, 8.13132868, 10.61159615]}, + {'name': 'led_0050', 'position': [3.2939535, 9.13207673, 8.55962855]}, + {'name': 'led_0060', 'position': [9.95077473, 3.18431267, 10.01643023]}, + {'name': 'led_0070', 'position': [4.80205465, -4.56261438, 4.56638023]}, + {'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], + ], } diff --git a/calibration.py b/calibration.py index b206b4a..8eba988 100755 --- a/calibration.py +++ b/calibration.py @@ -19,7 +19,7 @@ def print_error(msg: str): def calibrate(input_dir: str): # 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] # Camera parameters @@ -91,7 +91,7 @@ def calibrate(input_dir: str): light_positions = utils.lines_intersections(sphere_centers, estimated_lights) # 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(): diff --git a/static/calibration-visualiser.js b/static/calibration-visualiser.js index 2f54290..33d340c 100644 --- a/static/calibration-visualiser.js +++ b/static/calibration-visualiser.js @@ -56,20 +56,20 @@ class Pyramid extends THREE.Object3D { geometry.setAttribute('position', new THREE.BufferAttribute(buffer, 3)); const mesh = new THREE.Mesh(geometry, material); + mesh.layers.enable(1); + this.mesh = mesh; this.add(mesh); } // Lines { - let material = new THREE.LineBasicMaterial({color: 0xff0000}); + let material = new THREE.LineBasicMaterial({color: 0x990000}); let geometry = new THREE.BufferGeometry(); let width = 1; let height = 1; let length = 2; - - let lines = [ [0, 1], [0, 2], @@ -97,14 +97,82 @@ class Pyramid extends THREE.Object3D { geometry.setAttribute('position', new THREE.BufferAttribute(buffer, 3)); const mesh = new THREE.Line(geometry, material); + mesh.layers.enable(1); + this.lines = 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) { + 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 data = await request.json(); @@ -113,38 +181,53 @@ async function init(dataPath, domElementArg = document.body) { let h = domElement === document.body ? window.innerHeight : domElement.offsetHeight; raycaster = new THREE.Raycaster(); + raycaster.layers.set(1); 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(); leds = new THREE.Object3D(); - for (let key in data) { - let row = data[key]; - const geometry = new THREE.SphereGeometry(1, 32, 16); - const material = new THREE.MeshPhongMaterial({ color: 0xffff00 }); - let sphere = new THREE.Mesh(geometry, material); + for (let ledInfo of data.leds) { + let row = ledInfo.position; + let sphere = new Led(); sphere.position.x = -row[1]; sphere.position.y = -row[0]; sphere.position.z = row[2]; - sphere.name = key + sphere.name = ledInfo.name; + sphere.layers.enable(1); leds.add(sphere); } 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(); scene.add(cameraObject); const axesHelper = new THREE.AxesHelper(10); scene.add(axesHelper); - const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); - scene.add(directionalLight); + pointLight = new THREE.PointLight(0xffffff, 0); + scene.add(pointLight); - const ambientLight = new THREE.AmbientLight(0xffffff, 0.25); + const ambientLight = new THREE.AmbientLight(0xffffff, 0.15); scene.add(ambientLight); renderer = new THREE.WebGLRenderer({antialias: true, alpha: true}); @@ -155,11 +238,27 @@ async function init(dataPath, domElementArg = document.body) { // Add listeners controls = new OrbitControls(camera, renderer.domElement); + controls.zoomSpeed = 5; + controls.target.copy(center); controls.update(); window.addEventListener('pointermove', onPointerMove); window.addEventListener('pointerup', onPointerUp); 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(); domElement.appendChild(renderer.domElement); @@ -168,15 +267,46 @@ async function init(dataPath, domElementArg = document.body) { function animate(time) { 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) { 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; + + 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) { if (led === intersection) { - led.scale.set(1.5, 1.5, 1.5); + led.hover(); } else { - led.scale.set(1, 1, 1); + led.unhover(); } } } @@ -209,16 +339,68 @@ function onPointerMove(event) { function onPointerUp(event) { raycaster.setFromCamera(pointer, camera); - const intersects = raycaster.intersectObjects(leds.children); + const intersects = raycaster.intersectObjects(scene.children); if (intersects.length > 0) { - for (let led of leds.children) { - if (led === intersects[0].object) { - ledView.src = 'data/small/' + led.name + '.PNG'; - led.material.color.setHex(0xff0000); - selectedObject.innerText = led.name; + + if (intersects[0].object.parent instanceof Pyramid && intersects[0].distance > 1) { + triggerAnimation(); + return; + } + + 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 { - 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(); } } } diff --git a/templates/calibration.html b/templates/calibration.html index e8c3c89..737ed87 100644 --- a/templates/calibration.html +++ b/templates/calibration.html @@ -9,7 +9,7 @@
- Objet sélectionné : aucun + LED sélectionnée : aucune