Typescript
This commit is contained in:
parent
62c65896d9
commit
15caad7fad
|
|
@ -1,4 +1,6 @@
|
||||||
data
|
data
|
||||||
|
node_modules
|
||||||
|
static/calibration-visualiser.*
|
||||||
|
|
||||||
# Created by https://www.toptal.com/developers/gitignore/api/python
|
# Created by https://www.toptal.com/developers/gitignore/api/python
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
# Edit at https://www.toptal.com/developers/gitignore?templates=python
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
all: ts/*ts
|
||||||
|
tsc
|
||||||
|
esbuild ts/main.ts --bundle --minify --sourcemap --target=firefox57 --outfile=static/calibration-visualiser.js
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"name": "nenu-scanner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "nenu-scanner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/three": "^0.165.0",
|
||||||
|
"three": "^0.165.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tweenjs/tween.js": {
|
||||||
|
"version": "23.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.2.tgz",
|
||||||
|
"integrity": "sha512-kMCNaZCJugWI86xiEHaY338CU5JpD0B97p1j1IKNn/Zto8PgACjQx0UxbHjmOcLl/dDOBnItwD07KmCs75pxtQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/stats.js": {
|
||||||
|
"version": "0.17.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz",
|
||||||
|
"integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/three": {
|
||||||
|
"version": "0.165.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.165.0.tgz",
|
||||||
|
"integrity": "sha512-AJK8JZAFNBF0kBXiAIl5pggYlzAGGA8geVYQXAcPCEDRbyA+oEjkpUBcJJrtNz6IiALwzGexFJGZG2yV3WsYBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tweenjs/tween.js": "~23.1.1",
|
||||||
|
"@types/stats.js": "*",
|
||||||
|
"@types/webxr": "*",
|
||||||
|
"fflate": "~0.8.2",
|
||||||
|
"meshoptimizer": "~0.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/webxr": {
|
||||||
|
"version": "0.5.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.17.tgz",
|
||||||
|
"integrity": "sha512-JYcclaQIlisHRXM9dMF7SeVvQ54kcYc7QK1eKCExCTLKWnZDxP4cp/rXH4Uoa1j5+5oQJ0Cc2sZC/PWiiG4q2g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fflate": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||||
|
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/meshoptimizer": {
|
||||||
|
"version": "0.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
|
||||||
|
"integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/three": {
|
||||||
|
"version": "0.165.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz",
|
||||||
|
"integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "nenu-scanner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/three": "^0.165.0",
|
||||||
|
"three": "^0.165.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,454 +0,0 @@
|
||||||
import * as THREE from 'three';
|
|
||||||
import { OrbitControls } from 'orbit-controls';
|
|
||||||
|
|
||||||
class Pyramid extends THREE.Object3D {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
let width = 1;
|
|
||||||
let height = 1;
|
|
||||||
let length = 2;
|
|
||||||
|
|
||||||
let vertices = [
|
|
||||||
new THREE.Vector3( 0, 0, 0),
|
|
||||||
new THREE.Vector3( width, height, length),
|
|
||||||
new THREE.Vector3(-width, height, length),
|
|
||||||
new THREE.Vector3(-width, -height, length),
|
|
||||||
new THREE.Vector3( width, -height, length),
|
|
||||||
]
|
|
||||||
|
|
||||||
// Faces
|
|
||||||
{
|
|
||||||
let material = new THREE.MeshPhongMaterial({color: 0xffffff});
|
|
||||||
material.transparent = true;
|
|
||||||
material.opacity = 0.8;
|
|
||||||
|
|
||||||
let geometry = new THREE.BufferGeometry();
|
|
||||||
let faces = [
|
|
||||||
// Sides of the pyramid
|
|
||||||
[0, 2, 1],
|
|
||||||
[0, 3, 2],
|
|
||||||
[0, 4, 3],
|
|
||||||
[0, 1, 4],
|
|
||||||
|
|
||||||
// Base of the pyramid
|
|
||||||
[1, 2, 3],
|
|
||||||
[1, 3, 4],
|
|
||||||
]
|
|
||||||
|
|
||||||
let buffer = new Float32Array(3 * 3 * faces.length);
|
|
||||||
|
|
||||||
for (let faceIndex in faces) {
|
|
||||||
let face = faces[faceIndex];
|
|
||||||
buffer[faceIndex * 3 * 3 + 0] = vertices[face[0]].x;
|
|
||||||
buffer[faceIndex * 3 * 3 + 1] = vertices[face[0]].y;
|
|
||||||
buffer[faceIndex * 3 * 3 + 2] = vertices[face[0]].z;
|
|
||||||
|
|
||||||
buffer[faceIndex * 3 * 3 + 3] = vertices[face[1]].x;
|
|
||||||
buffer[faceIndex * 3 * 3 + 4] = vertices[face[1]].y;
|
|
||||||
buffer[faceIndex * 3 * 3 + 5] = vertices[face[1]].z;
|
|
||||||
|
|
||||||
buffer[faceIndex * 3 * 3 + 6] = vertices[face[2]].x;
|
|
||||||
buffer[faceIndex * 3 * 3 + 7] = vertices[face[2]].y;
|
|
||||||
buffer[faceIndex * 3 * 3 + 8] = vertices[face[2]].z;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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: 0x990000});
|
|
||||||
let geometry = new THREE.BufferGeometry();
|
|
||||||
|
|
||||||
let width = 1;
|
|
||||||
let height = 1;
|
|
||||||
let length = 2;
|
|
||||||
|
|
||||||
let lines = [
|
|
||||||
[0, 1],
|
|
||||||
[0, 2],
|
|
||||||
[0, 3],
|
|
||||||
[0, 4],
|
|
||||||
[1, 2],
|
|
||||||
[2, 3],
|
|
||||||
[3, 4],
|
|
||||||
[4, 1],
|
|
||||||
]
|
|
||||||
|
|
||||||
let buffer = new Float32Array(2 * 3 * lines.length);
|
|
||||||
|
|
||||||
for (let lineIndex in lines) {
|
|
||||||
let line = lines[lineIndex];
|
|
||||||
buffer[lineIndex * 2 * 3 + 0] = vertices[line[0]].x;
|
|
||||||
buffer[lineIndex * 2 * 3 + 1] = vertices[line[0]].y;
|
|
||||||
buffer[lineIndex * 2 * 3 + 2] = vertices[line[0]].z;
|
|
||||||
|
|
||||||
buffer[lineIndex * 2 * 3 + 3] = vertices[line[1]].x;
|
|
||||||
buffer[lineIndex * 2 * 3 + 4] = vertices[line[1]].y;
|
|
||||||
buffer[lineIndex * 2 * 3 + 5] = vertices[line[1]].z;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLines(lines, spheres) {
|
|
||||||
const material = new THREE.LineBasicMaterial({
|
|
||||||
color: 0x0000ff
|
|
||||||
});
|
|
||||||
this.lines = new THREE.Object3D();
|
|
||||||
for (let index = 0; index < lines.length; index++) {
|
|
||||||
let line = lines[index];
|
|
||||||
let sphere = spheres[index];
|
|
||||||
let vertices = new Float32Array([
|
|
||||||
-this.position.x - sphere[1],
|
|
||||||
-this.position.y - sphere[0],
|
|
||||||
-this.position.z + sphere[2],
|
|
||||||
-line[1] * 100, -line[0] * 100, line[2] * 100,
|
|
||||||
]);
|
|
||||||
let geometry = new THREE.BufferGeometry();
|
|
||||||
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
|
|
||||||
let mesh = new THREE.Line(geometry, material);
|
|
||||||
this.lines.add(mesh);
|
|
||||||
}
|
|
||||||
this.lines.visible = false;
|
|
||||||
this.add(this.lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(showLines) {
|
|
||||||
this.on = true;
|
|
||||||
for (let child of this.children) {
|
|
||||||
child.visible = true;
|
|
||||||
}
|
|
||||||
this.refreshColor();
|
|
||||||
this.lines.visible = showLines;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
turnOff(showLines) {
|
|
||||||
this.on = false;
|
|
||||||
for (let child of this.children) {
|
|
||||||
child.visible = false;
|
|
||||||
}
|
|
||||||
this.refreshColor();
|
|
||||||
this.lines.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshColor() {
|
|
||||||
this.material.color.setHex(this.getColor());
|
|
||||||
}
|
|
||||||
|
|
||||||
showLines(showLines) {
|
|
||||||
if (this.on) {
|
|
||||||
this.lines.visible = showLines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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, showLinesCheckbox;
|
|
||||||
|
|
||||||
async function init(dataPath, domElementArg = document.body) {
|
|
||||||
|
|
||||||
showLinesCheckbox = document.getElementById('show-lines');
|
|
||||||
showLinesCheckbox.addEventListener('change', () => {
|
|
||||||
for (let led of leds.children) {
|
|
||||||
led.showLines(showLinesCheckbox.checked);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
center = new THREE.Vector3(0, 0, 10);
|
|
||||||
animationStep = NaN;
|
|
||||||
beforeAnimationPosition = new THREE.Vector3();
|
|
||||||
beforeAnimationTarget = new THREE.Vector3();
|
|
||||||
|
|
||||||
let request = await fetch('/data/calibration.json');
|
|
||||||
let data = await request.json();
|
|
||||||
|
|
||||||
domElement = domElementArg;
|
|
||||||
let w = domElement === document.body ? window.innerWidth : domElement.offsetWidth;
|
|
||||||
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, -30);
|
|
||||||
|
|
||||||
scene = new THREE.Scene();
|
|
||||||
|
|
||||||
leds = new THREE.Object3D();
|
|
||||||
|
|
||||||
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 = ledInfo.name;
|
|
||||||
sphere.layers.enable(1);
|
|
||||||
sphere.setLines(ledInfo.directions, data.spheres);
|
|
||||||
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);
|
|
||||||
|
|
||||||
pointLight = new THREE.PointLight(0xffffff, 0);
|
|
||||||
scene.add(pointLight);
|
|
||||||
|
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.15);
|
|
||||||
scene.add(ambientLight);
|
|
||||||
|
|
||||||
renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
|
|
||||||
renderer.setAnimationLoop(animate);
|
|
||||||
|
|
||||||
selectedObject = document.getElementById('selected-object');
|
|
||||||
ledView = document.getElementById('led-view');
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(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.hover();
|
|
||||||
} else {
|
|
||||||
led.unhover();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWindowResize() {
|
|
||||||
let w = domElement === document.body ? window.innerWidth : domElement.offsetWidth;
|
|
||||||
let h = domElement === document.body ? window.innerHeight : domElement.offsetHeight;
|
|
||||||
|
|
||||||
camera.aspect = w / h;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
renderer.setSize(w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPointerMove(event) {
|
|
||||||
// calculate pointer position in normalized device coordinates
|
|
||||||
// (-1 to +1) for both components
|
|
||||||
|
|
||||||
if (pointer === undefined) {
|
|
||||||
pointer = new THREE.Vector2();
|
|
||||||
}
|
|
||||||
let w = domElement === document.body ? window.innerWidth : domElement.offsetWidth;
|
|
||||||
let h = domElement === document.body ? window.innerHeight : domElement.offsetHeight;
|
|
||||||
pointer.x = (event.offsetX / w) * 2 - 1;
|
|
||||||
pointer.y = - (event.offsetY / h) * 2 + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPointerUp(event) {
|
|
||||||
raycaster.setFromCamera(pointer, camera);
|
|
||||||
const intersects = raycaster.intersectObjects(scene.children);
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
|
|
||||||
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(showLinesCheckbox.checked);
|
|
||||||
pointLight.intensity = 0;
|
|
||||||
ledView.style.display = "none";
|
|
||||||
selectedObject.innerText = 'aucune';
|
|
||||||
} else {
|
|
||||||
led.turnOn(showLinesCheckbox.checked);
|
|
||||||
pointLight.intensity = 100;
|
|
||||||
pointLight.position.copy(led.position);
|
|
||||||
ledView.src = 'data/small/' + led.name;
|
|
||||||
ledView.style.display = "block";
|
|
||||||
selectedObject.innerText = led.name;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
child.turnOff(showLinesCheckbox.checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { init };
|
|
||||||
|
|
@ -55,13 +55,5 @@
|
||||||
{% endblock extracss %}
|
{% endblock extracss %}
|
||||||
|
|
||||||
{% block extrajs %}
|
{% block extrajs %}
|
||||||
<script type="importmap">{ "imports": {
|
<script src="/static/calibration-visualiser.js"></script>
|
||||||
"three": "/static/three.module.js",
|
|
||||||
"orbit-controls": "/static/orbit-controls.js",
|
|
||||||
"calibration-visualiser": "/static/calibration-visualiser.js"
|
|
||||||
} }</script>
|
|
||||||
<script type="module">
|
|
||||||
import { init } from 'calibration-visualiser';
|
|
||||||
init('coucou', document.getElementById('visualiser'));
|
|
||||||
</script>
|
|
||||||
{% endblock extrajs %}
|
{% endblock extrajs %}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A camera pose.
|
||||||
|
*/
|
||||||
|
export interface Pose {
|
||||||
|
/** The position of the camera. */
|
||||||
|
position: THREE.Vector3;
|
||||||
|
|
||||||
|
/** The point where the camera is looking. */
|
||||||
|
target: THREE.Vector3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to easily manage a camera animation.
|
||||||
|
*/
|
||||||
|
export default class Animation {
|
||||||
|
|
||||||
|
/** The beginning of the animation. */
|
||||||
|
start: Pose;
|
||||||
|
|
||||||
|
/** The end of the animation. */
|
||||||
|
end: Pose;
|
||||||
|
|
||||||
|
/** Moment in the animation, between 0 and 1. */
|
||||||
|
t: number;
|
||||||
|
|
||||||
|
/** Initialises a new animation. */
|
||||||
|
constructor(start: Pose, end: Pose) {
|
||||||
|
this.start = { position: start.position, target: start.target };
|
||||||
|
this.end = { position: end.position, target: end.target };
|
||||||
|
this.t = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Updates the animation. */
|
||||||
|
update(delay: number): Pose {
|
||||||
|
this.t += delay;
|
||||||
|
return {
|
||||||
|
position: new THREE.Vector3()
|
||||||
|
.addScaledVector(this.start.position, 1 - this.t)
|
||||||
|
.addScaledVector(this.end.position, this.t),
|
||||||
|
target: new THREE.Vector3()
|
||||||
|
.addScaledVector(this.start.target, 1 - this.t)
|
||||||
|
.addScaledVector(this.end.target, this.t),
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* Alias type for an array of three numbers.
|
||||||
|
*/
|
||||||
|
export type Vector3 = [number, number, number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A led, with its name, its estimated position and the directions of the lights.
|
||||||
|
*/
|
||||||
|
export interface Led {
|
||||||
|
/** The name of the led. */
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/** The estimated position of the led. */
|
||||||
|
position: Vector3;
|
||||||
|
|
||||||
|
/** The estimated directions of the light that allowed the estimation of the position of the led. */
|
||||||
|
directions: Vector3[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type for the calibration data.
|
||||||
|
*/
|
||||||
|
export interface Calibration {
|
||||||
|
/** Information about the leds. */
|
||||||
|
leds: Led[];
|
||||||
|
|
||||||
|
/** Position of the spheres. */
|
||||||
|
spheres: Vector3[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small pyramid object to represent the camera.
|
||||||
|
*/
|
||||||
|
export default class CameraObject extends THREE.Object3D {
|
||||||
|
|
||||||
|
/** Faces of the pyramid. */
|
||||||
|
mesh: THREE.Mesh<THREE.BufferGeometry, THREE.MeshPhongMaterial>;
|
||||||
|
|
||||||
|
/** Wireframe of the pyramid. */
|
||||||
|
lines: THREE.Line<THREE.BufferGeometry, THREE.LineBasicMaterial>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the full pyramid, with mesh and lines.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
let width = 1;
|
||||||
|
let height = 1;
|
||||||
|
let length = 2;
|
||||||
|
|
||||||
|
let vertices = [
|
||||||
|
new THREE.Vector3( 0, 0, 0),
|
||||||
|
new THREE.Vector3( width, height, length),
|
||||||
|
new THREE.Vector3(-width, height, length),
|
||||||
|
new THREE.Vector3(-width, -height, length),
|
||||||
|
new THREE.Vector3( width, -height, length),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Faces
|
||||||
|
{
|
||||||
|
let material = new THREE.MeshPhongMaterial({color: 0xffffff});
|
||||||
|
material.transparent = true;
|
||||||
|
material.opacity = 0.8;
|
||||||
|
|
||||||
|
let geometry = new THREE.BufferGeometry();
|
||||||
|
let faces = [
|
||||||
|
// Sides of the pyramid
|
||||||
|
[0, 2, 1],
|
||||||
|
[0, 3, 2],
|
||||||
|
[0, 4, 3],
|
||||||
|
[0, 1, 4],
|
||||||
|
|
||||||
|
// Base of the pyramid
|
||||||
|
[1, 2, 3],
|
||||||
|
[1, 3, 4],
|
||||||
|
];
|
||||||
|
|
||||||
|
let buffer = new Float32Array(3 * 3 * faces.length);
|
||||||
|
|
||||||
|
for (let faceIndex = 0; faceIndex < faces.length; faceIndex++) {
|
||||||
|
let face = faces[faceIndex];
|
||||||
|
buffer[faceIndex * 3 * 3 + 0] = vertices[face[0]].x;
|
||||||
|
buffer[faceIndex * 3 * 3 + 1] = vertices[face[0]].y;
|
||||||
|
buffer[faceIndex * 3 * 3 + 2] = vertices[face[0]].z;
|
||||||
|
|
||||||
|
buffer[faceIndex * 3 * 3 + 3] = vertices[face[1]].x;
|
||||||
|
buffer[faceIndex * 3 * 3 + 4] = vertices[face[1]].y;
|
||||||
|
buffer[faceIndex * 3 * 3 + 5] = vertices[face[1]].z;
|
||||||
|
|
||||||
|
buffer[faceIndex * 3 * 3 + 6] = vertices[face[2]].x;
|
||||||
|
buffer[faceIndex * 3 * 3 + 7] = vertices[face[2]].y;
|
||||||
|
buffer[faceIndex * 3 * 3 + 8] = vertices[face[2]].z;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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: 0x990000});
|
||||||
|
let geometry = new THREE.BufferGeometry();
|
||||||
|
|
||||||
|
let lines = [
|
||||||
|
[0, 1],
|
||||||
|
[0, 2],
|
||||||
|
[0, 3],
|
||||||
|
[0, 4],
|
||||||
|
[1, 2],
|
||||||
|
[2, 3],
|
||||||
|
[3, 4],
|
||||||
|
[4, 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
let buffer = new Float32Array(2 * 3 * lines.length);
|
||||||
|
|
||||||
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
||||||
|
let line = lines[lineIndex];
|
||||||
|
buffer[lineIndex * 2 * 3 + 0] = vertices[line[0]].x;
|
||||||
|
buffer[lineIndex * 2 * 3 + 1] = vertices[line[0]].y;
|
||||||
|
buffer[lineIndex * 2 * 3 + 2] = vertices[line[0]].z;
|
||||||
|
|
||||||
|
buffer[lineIndex * 2 * 3 + 3] = vertices[line[1]].x;
|
||||||
|
buffer[lineIndex * 2 * 3 + 4] = vertices[line[1]].y;
|
||||||
|
buffer[lineIndex * 2 * 3 + 5] = vertices[line[1]].z;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the style of the model to a nice hovered style.
|
||||||
|
*/
|
||||||
|
hover(): void {
|
||||||
|
this.mesh.material.opacity = 1;
|
||||||
|
this.lines.material.color.setHex(0xff0000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores original style.
|
||||||
|
*/
|
||||||
|
unHover(): void {
|
||||||
|
this.mesh.material.opacity = 0.8;
|
||||||
|
this.lines.material.color.setHex(0x990000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,314 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
||||||
|
|
||||||
|
import Animation from './Animation';
|
||||||
|
import { Calibration } from './Calibration';
|
||||||
|
import { Led, Leds } from './Led';
|
||||||
|
import CameraObject from './CameraObject';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an HTML element from its id, and throw an error if it doens't exist.
|
||||||
|
*/
|
||||||
|
function getElementById(id: string): HTMLElement {
|
||||||
|
let element = document.getElementById(id);
|
||||||
|
if (element === null) {
|
||||||
|
throw new Error('No element with id ' + id);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an HTML input element from its id, and throw an error if it doens't exist.
|
||||||
|
*/
|
||||||
|
function getInputElementById(id: string): HTMLInputElement {
|
||||||
|
let element = getElementById(id);
|
||||||
|
if (! (element instanceof HTMLInputElement)) {
|
||||||
|
throw new Error('Element with id ' + id + ' is not an input element');
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an HTML image element from its id, and throw an error if it doens't exist.
|
||||||
|
*/
|
||||||
|
function getImageElementById(id: string): HTMLImageElement {
|
||||||
|
let element = getElementById(id);
|
||||||
|
if (! (element instanceof HTMLImageElement)) {
|
||||||
|
throw new Error('Element with id ' + id + ' is not an input element');
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class that manages the interface for the calibration visualisation.
|
||||||
|
*/
|
||||||
|
export class Engine {
|
||||||
|
/** HTML element on which the renderer will be added. */
|
||||||
|
domElement: HTMLElement;
|
||||||
|
|
||||||
|
/** Checkbox indicating whether wants to show the lines from the spheres to the lights. */
|
||||||
|
showLinesCheckbox: HTMLInputElement;
|
||||||
|
|
||||||
|
/** HTML span where we will show the name of the current selected led. */
|
||||||
|
selectedObject: HTMLElement;
|
||||||
|
|
||||||
|
/** HTML image where we will show the real photo corresponding to the selected led. */
|
||||||
|
ledView: HTMLImageElement;
|
||||||
|
|
||||||
|
/** Target point of the camera. */
|
||||||
|
center: THREE.Vector3;
|
||||||
|
|
||||||
|
/** Scene containing all the elements to be rendered. */
|
||||||
|
scene: THREE.Scene;
|
||||||
|
|
||||||
|
/** Camera from which the scene will be rendered. */
|
||||||
|
camera: THREE.PerspectiveCamera;
|
||||||
|
|
||||||
|
/** Object containing the representation of the camera (grey pyramid). */
|
||||||
|
cameraObject: CameraObject;
|
||||||
|
|
||||||
|
/** Object containing all the representations of the leds (yellow spheres). */
|
||||||
|
leds: Leds;
|
||||||
|
|
||||||
|
/** Object containing all the representations of the spheres (white). */
|
||||||
|
spheres: THREE.Object3D;
|
||||||
|
|
||||||
|
/** Axes that will be shown to help the visualisation of the scene. */
|
||||||
|
axes: THREE.AxesHelper;
|
||||||
|
|
||||||
|
/** Ambient light to be able to see stuff in the scene. */
|
||||||
|
ambientLight: THREE.AmbientLight;
|
||||||
|
|
||||||
|
/** Renderer that will be used to render the scene. */
|
||||||
|
renderer: THREE.WebGLRenderer;
|
||||||
|
|
||||||
|
/** Controls to let the user move the camera. */
|
||||||
|
controls: OrbitControls;
|
||||||
|
|
||||||
|
/** 2D Vector representing the position of the mouse on the renderer. */
|
||||||
|
pointer: THREE.Vector2;
|
||||||
|
|
||||||
|
/** Object that will help us when users will point or click 3D objects. */
|
||||||
|
raycaster: THREE.Raycaster;
|
||||||
|
|
||||||
|
/** Object to manage the animation when the user clicks on the camera. */
|
||||||
|
animation: Animation | null;
|
||||||
|
|
||||||
|
/** Initialises the engine. */
|
||||||
|
static async create(domId: string) {
|
||||||
|
let domElement = getElementById(domId);
|
||||||
|
let engine = new Engine();
|
||||||
|
engine.domElement = domElement;
|
||||||
|
engine.initHtml();
|
||||||
|
|
||||||
|
let request = await fetch('/data/calibration.json');
|
||||||
|
let calibration = await request.json();
|
||||||
|
engine.initScene(calibration);
|
||||||
|
engine.initListeners();
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the available width to perform the redering. */
|
||||||
|
get width(): number {
|
||||||
|
return this.domElement === document.body ? window.innerWidth : this.domElement.offsetWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the available height to perform the redering. */
|
||||||
|
get height(): number {
|
||||||
|
return this.domElement === document.body ? window.innerHeight : this.domElement.offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the HTML components of the engine.
|
||||||
|
*/
|
||||||
|
initHtml(): void {
|
||||||
|
this.showLinesCheckbox = getInputElementById('show-lines');
|
||||||
|
this.selectedObject = getElementById('selected-object');
|
||||||
|
this.ledView = getImageElementById('led-view');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the 3D components of the engine.
|
||||||
|
*/
|
||||||
|
initScene(calibration: Calibration): void {
|
||||||
|
this.center = new THREE.Vector3(0, 0, 10);
|
||||||
|
this.scene = new THREE.Scene();
|
||||||
|
|
||||||
|
this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 0.001, 1000);
|
||||||
|
this.camera.position.set(0, 0, -30);
|
||||||
|
|
||||||
|
this.cameraObject = new CameraObject();
|
||||||
|
this.scene.add(this.cameraObject);
|
||||||
|
|
||||||
|
this.leds = new Leds(calibration, this.showLinesCheckbox.checked);
|
||||||
|
this.scene.add(this.leds);
|
||||||
|
|
||||||
|
this.spheres = new THREE.Object3D();
|
||||||
|
for (let row of calibration.spheres) {
|
||||||
|
let sphere = new THREE.Mesh(new THREE.SphereGeometry(1, 32, 16), new THREE.MeshPhongMaterial({ color: 0xffffff }));
|
||||||
|
sphere.position.set(-row[1], -row[0], row[2]);
|
||||||
|
sphere.layers.enable(1);
|
||||||
|
this.spheres.add(sphere);
|
||||||
|
}
|
||||||
|
this.scene.add(this.spheres);
|
||||||
|
|
||||||
|
this.axes = new THREE.AxesHelper(10);
|
||||||
|
this.scene.add(this.axes);
|
||||||
|
|
||||||
|
this.ambientLight = new THREE.AmbientLight(0xffffff, 0.15);
|
||||||
|
this.scene.add(this.ambientLight);
|
||||||
|
|
||||||
|
this.renderer = new THREE.WebGLRenderer({antialias: true, alpha: true});
|
||||||
|
this.renderer.setSize(this.width, this.height);
|
||||||
|
this.renderer.setAnimationLoop(() => this.animate());
|
||||||
|
|
||||||
|
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||||
|
this.controls.zoomSpeed = 5;
|
||||||
|
this.controls.target.copy(this.center);
|
||||||
|
this.controls.update();
|
||||||
|
|
||||||
|
this.pointer = new THREE.Vector2();
|
||||||
|
|
||||||
|
this.raycaster = new THREE.Raycaster();
|
||||||
|
this.raycaster.layers.set(1);
|
||||||
|
|
||||||
|
this.animation = null;
|
||||||
|
|
||||||
|
this.onWindowResize();
|
||||||
|
this.domElement.appendChild(this.renderer.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the event listeners of the engine.
|
||||||
|
*/
|
||||||
|
initListeners(): void {
|
||||||
|
window.addEventListener('resize', () => this.onWindowResize(), false);
|
||||||
|
window.addEventListener('pointermove', (e) => this.onPointerMove(e));
|
||||||
|
window.addEventListener('pointerup', () => this.onPointerUp());
|
||||||
|
this.showLinesCheckbox.addEventListener('change', () => this.leds.setShowLines(this.showLinesCheckbox.checked));
|
||||||
|
document.addEventListener('keyup', (e) => {
|
||||||
|
switch (e.code) {
|
||||||
|
case "ArrowDown":
|
||||||
|
case "ArrowRight":
|
||||||
|
this.showImage(this.leds.next());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "ArrowUp":
|
||||||
|
case "ArrowLeft":
|
||||||
|
this.showImage(this.leds.previous());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Triggers the animation. */
|
||||||
|
startAnimation(): void {
|
||||||
|
this.animation = new Animation({
|
||||||
|
position: this.camera.position,
|
||||||
|
target: this.controls.target,
|
||||||
|
}, {
|
||||||
|
position: new THREE.Vector3(),
|
||||||
|
target: this.center,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content of the render loop.
|
||||||
|
*/
|
||||||
|
animate(): void {
|
||||||
|
// Update user controls
|
||||||
|
this.controls.update();
|
||||||
|
|
||||||
|
// Manage animation
|
||||||
|
if (this.animation !== null) {
|
||||||
|
if (this.animation.t > 1) {
|
||||||
|
this.animation = null;
|
||||||
|
this.camera.position.set(0, 0, 0);
|
||||||
|
this.controls.target.copy(this.center);
|
||||||
|
} else {
|
||||||
|
let current = this.animation.update(0.01);
|
||||||
|
this.camera.position.copy(current.position);
|
||||||
|
this.controls.target.copy(current.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.controls.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manage mouse interaction
|
||||||
|
this.raycaster.setFromCamera(this.pointer, this.camera);
|
||||||
|
let intersects = this.raycaster.intersectObjects(this.scene.children);
|
||||||
|
let firstIntersection = intersects[0];
|
||||||
|
|
||||||
|
// If the pointer points at the camera, make it hover
|
||||||
|
if (firstIntersection && firstIntersection.object.parent instanceof CameraObject && firstIntersection.distance > 1) {
|
||||||
|
this.cameraObject.hover();
|
||||||
|
} else {
|
||||||
|
this.cameraObject.unHover();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pointer points at a led, make it hover, but unhover other leds first
|
||||||
|
for (let led of this.leds.children) {
|
||||||
|
if (led instanceof Led) {
|
||||||
|
led.unHover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (firstIntersection && firstIntersection.object instanceof Led) {
|
||||||
|
firstIntersection.object.hover();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the rendering
|
||||||
|
this.renderer.render(this.scene, this.camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the pointer moves on the screen.
|
||||||
|
*/
|
||||||
|
onPointerMove(e: PointerEvent): void {
|
||||||
|
// Normalize pointer position in [-1, 1]²
|
||||||
|
this.pointer.x = (e.offsetX / this.width) * 2 - 1;
|
||||||
|
this.pointer.y = - (e.offsetY / this.height) * 2 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the photo associated to a led.
|
||||||
|
*/
|
||||||
|
showImage(led: Led): void {
|
||||||
|
if (led.on) {
|
||||||
|
this.selectedObject.innerText = led.name;
|
||||||
|
this.ledView.src = '/data/small/' + led.name;
|
||||||
|
this.ledView.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
this.selectedObject.innerText = 'aucune';
|
||||||
|
this.ledView.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the pointer moves is released (i.e. click).
|
||||||
|
*/
|
||||||
|
onPointerUp(): void {
|
||||||
|
this.raycaster.setFromCamera(this.pointer, this.camera);
|
||||||
|
let intersects = this.raycaster.intersectObjects(this.scene.children);
|
||||||
|
let firstIntersection = intersects[0];
|
||||||
|
|
||||||
|
if (firstIntersection && firstIntersection.object instanceof Led) {
|
||||||
|
this.leds.toggle(firstIntersection.object);
|
||||||
|
this.showImage(firstIntersection.object);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstIntersection && firstIntersection.object.parent instanceof CameraObject && firstIntersection.distance > 1) {
|
||||||
|
this.startAnimation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the pointer window is resized.
|
||||||
|
*/
|
||||||
|
onWindowResize(): void {
|
||||||
|
this.camera.aspect = this.width / this.height;
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
this.renderer.setSize(this.width, this.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,248 @@
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import * as Calibration from './Calibration';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to render leds as spheres.
|
||||||
|
*/
|
||||||
|
export class Led extends THREE.Mesh<THREE.SphereGeometry, THREE.MeshBasicMaterial> {
|
||||||
|
|
||||||
|
/** Whether the led is on or off. */
|
||||||
|
on: boolean;
|
||||||
|
|
||||||
|
/** Whether the mouse is hovering the led or not. */
|
||||||
|
isHovered: boolean;
|
||||||
|
|
||||||
|
/** Lines going from the sphere to the led. */
|
||||||
|
lines: THREE.LineSegments;
|
||||||
|
|
||||||
|
/** Point light to produce a nice lighting effect when the light is on. */
|
||||||
|
light: THREE.PointLight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new led from its information and the spheres.
|
||||||
|
*/
|
||||||
|
constructor(ledInfo: Calibration.Led, spheres: Calibration.Vector3[]) {
|
||||||
|
super(
|
||||||
|
new THREE.SphereGeometry(0.7, 32, 16),
|
||||||
|
new THREE.MeshBasicMaterial({ color: 0x555500 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.position.set(-ledInfo.position[1], -ledInfo.position[0], ledInfo.position[2]);
|
||||||
|
this.name = ledInfo.name;
|
||||||
|
this.layers.enable(1);
|
||||||
|
this.on = false;
|
||||||
|
this.isHovered = false;
|
||||||
|
|
||||||
|
const material = new THREE.LineBasicMaterial({
|
||||||
|
color: 0x0000ff
|
||||||
|
});
|
||||||
|
|
||||||
|
let vertices = new Float32Array(3 * 2 * spheres.length);
|
||||||
|
|
||||||
|
for (let index = 0; index < ledInfo.directions.length; index++) {
|
||||||
|
let line = ledInfo.directions[index];
|
||||||
|
let sphere = spheres[index];
|
||||||
|
vertices[3 * 2 * index ] = -this.position.x - sphere[1];
|
||||||
|
vertices[3 * 2 * index + 1] = -this.position.y - sphere[0];
|
||||||
|
vertices[3 * 2 * index + 2] = -this.position.z + sphere[2];
|
||||||
|
vertices[3 * 2 * index + 3] = -line[1] * 100;
|
||||||
|
vertices[3 * 2 * index + 4] = -line[0] * 100;
|
||||||
|
vertices[3 * 2 * index + 5] = line[2] * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
let geometry = new THREE.BufferGeometry();
|
||||||
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||||
|
this.lines = new THREE.LineSegments(geometry, material);
|
||||||
|
this.lines.visible = false;
|
||||||
|
this.add(this.lines);
|
||||||
|
|
||||||
|
this.light = new THREE.PointLight(0xffffff, 100);
|
||||||
|
this.light.visible = false;
|
||||||
|
this.add(this.light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the style of the model to a nice hovered style.
|
||||||
|
*/
|
||||||
|
hover() {
|
||||||
|
if (this.on) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isHovered = true;
|
||||||
|
this.refreshColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores original style.
|
||||||
|
*/
|
||||||
|
unHover() {
|
||||||
|
if (this.on) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isHovered = false;
|
||||||
|
this.refreshColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn on the led if its off, turn it off if its on.
|
||||||
|
*/
|
||||||
|
toggle() {
|
||||||
|
this.on = !this.on;
|
||||||
|
this.refreshColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the style of the model to make it visible that the led is on.
|
||||||
|
*/
|
||||||
|
turnOn(showLines: boolean) {
|
||||||
|
this.on = true;
|
||||||
|
this.light.visible = true;
|
||||||
|
for (let child of this.children) {
|
||||||
|
child.visible = true;
|
||||||
|
}
|
||||||
|
this.refreshColor();
|
||||||
|
this.lines.visible = showLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the style of the model to make it visible that the led is off.
|
||||||
|
*/
|
||||||
|
turnOff() {
|
||||||
|
this.on = false;
|
||||||
|
this.light.visible = false;
|
||||||
|
for (let child of this.children) {
|
||||||
|
child.visible = false;
|
||||||
|
}
|
||||||
|
this.refreshColor();
|
||||||
|
this.lines.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the color according to the led state.
|
||||||
|
*/
|
||||||
|
refreshColor() {
|
||||||
|
this.material.color.setHex(this.getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows or hides the lines from the spheres following the light direction.
|
||||||
|
*/
|
||||||
|
showLines(showLines: boolean) {
|
||||||
|
if (this.on) {
|
||||||
|
this.lines.visible = showLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the hexadecimal value of the color of the led depending on the state.
|
||||||
|
*/
|
||||||
|
getColor() {
|
||||||
|
if (this.on) {
|
||||||
|
return 0xffff00;
|
||||||
|
} else if (this.isHovered) {
|
||||||
|
return 0x888800;
|
||||||
|
} else {
|
||||||
|
return 0x555500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for all the leds that will help managing which led is on.
|
||||||
|
* Only one led can be on at a time.
|
||||||
|
*/
|
||||||
|
export class Leds extends THREE.Object3D {
|
||||||
|
|
||||||
|
/** Index of the led that is currently on, null if all leds are off. */
|
||||||
|
currentLedIndex: number | null;
|
||||||
|
|
||||||
|
/** Whether we need to show the lines of the leds. */
|
||||||
|
showLines: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a set of leds from their configuration.
|
||||||
|
*/
|
||||||
|
constructor(calibration: Calibration.Calibration, showLines: boolean) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.showLines = showLines;
|
||||||
|
this.currentLedIndex = null;
|
||||||
|
|
||||||
|
for (let ledInfo of calibration.leds) {
|
||||||
|
this.add(new Led(ledInfo, calibration.spheres));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns of the current led if any, and turns on the led given in argument.
|
||||||
|
* If the led given in argument is the one on, it will be turned off.
|
||||||
|
*/
|
||||||
|
toggle(led: Led): void {
|
||||||
|
// If the specified led is the one on.
|
||||||
|
if (this.currentLedIndex !== null && led === this.children[this.currentLedIndex]) {
|
||||||
|
this.currentLedIndex = null;
|
||||||
|
led.turnOff();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let index = 0; index < this.children.length; index++) {
|
||||||
|
let child = <Led> this.children[index];
|
||||||
|
if (led === this.children[index]) {
|
||||||
|
child.turnOn(this.showLines);
|
||||||
|
this.currentLedIndex = index;
|
||||||
|
} else {
|
||||||
|
child.turnOff();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes whether we should show or not show the led lines.
|
||||||
|
*/
|
||||||
|
setShowLines(showLines: boolean): void {
|
||||||
|
this.showLines = showLines;
|
||||||
|
for (let child of this.children) {
|
||||||
|
if (child instanceof Led && child.on) {
|
||||||
|
child.lines.visible = showLines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn off the current led and goes to the next one.
|
||||||
|
*/
|
||||||
|
next(): Led {
|
||||||
|
if (this.currentLedIndex === null) {
|
||||||
|
this.currentLedIndex = 0;
|
||||||
|
let led = <Led> this.children[0];
|
||||||
|
led.turnOn(this.showLines);
|
||||||
|
return led;
|
||||||
|
}
|
||||||
|
|
||||||
|
(<Led> this.children[this.currentLedIndex]).turnOff();
|
||||||
|
this.currentLedIndex = (this.currentLedIndex + 1) % this.children.length;
|
||||||
|
(<Led> this.children[this.currentLedIndex]).turnOn(this.showLines);
|
||||||
|
return <Led> this.children[this.currentLedIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn off the current led and goes to the previous one.
|
||||||
|
*/
|
||||||
|
previous(): Led {
|
||||||
|
if (this.currentLedIndex === null) {
|
||||||
|
this.currentLedIndex = 0;
|
||||||
|
(<Led> this.children[0]).turnOn(this.showLines);
|
||||||
|
return <Led> this.children[0];;
|
||||||
|
}
|
||||||
|
|
||||||
|
(<Led> this.children[this.currentLedIndex]).turnOff();
|
||||||
|
this.currentLedIndex = (this.currentLedIndex + this.children.length - 1) % this.children.length;
|
||||||
|
let led = (<Led> this.children[this.currentLedIndex]);
|
||||||
|
led.turnOn(this.showLines);
|
||||||
|
return led;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
import { Engine } from './Engine';
|
||||||
|
Engine.create('visualiser');
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
|
"module": "amd",
|
||||||
|
"lib": ["dom", "ESnext"],
|
||||||
|
"removeComments": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue