409 lines
12 KiB
JavaScript
409 lines
12 KiB
JavaScript
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;
|
|
}
|
|
|
|
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();
|
|
|
|
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);
|
|
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();
|
|
pointLight.intensity = 0;
|
|
ledView.style.display = "none";
|
|
selectedObject.innerText = 'aucune';
|
|
} else {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
export { init };
|