2022-01-20 01:24:29 +01:00
|
|
|
/* Map 3D
|
|
|
|
* 3D Model : Matthias Onestras
|
|
|
|
* Code : Ronan Bonnet
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.119.1/build/three.module.js';
|
|
|
|
|
|
|
|
import {
|
|
|
|
OrbitControls
|
|
|
|
} from 'https://cdn.jsdelivr.net/npm/three@0.119.1/examples/jsm/controls/OrbitControls.js';
|
|
|
|
import {
|
|
|
|
GLTFLoader
|
|
|
|
} from 'https://cdn.jsdelivr.net/npm/three@0.119.1/examples/jsm/loaders/GLTFLoader.js';
|
|
|
|
|
|
|
|
var container, stats, controls;
|
|
|
|
var camera, scene, renderer;
|
|
|
|
|
|
|
|
var raycaster, mouse;
|
|
|
|
|
|
|
|
init();
|
|
|
|
render();
|
|
|
|
|
|
|
|
var height, width;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes the 3D plan
|
|
|
|
* Creates and loads every needed things
|
|
|
|
*/
|
|
|
|
function init() {
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Creates HTML
|
|
|
|
//
|
|
|
|
container = document.createElement('div');
|
|
|
|
container.id = 'map3d';
|
|
|
|
|
|
|
|
height = document.querySelector('#maps').clientHeight;
|
|
|
|
width = document.querySelector('#maps').clientWidth;
|
|
|
|
var svg = document.querySelector('#maps #map');
|
|
|
|
document.querySelector('#maps').insertBefore(container, svg);
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Creates cameras and scene
|
|
|
|
//
|
|
|
|
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.75, 20000);
|
|
|
|
camera.position.set(500,1500,500);
|
|
|
|
scene = new THREE.Scene();
|
|
|
|
|
|
|
|
//
|
|
|
|
// LIGHTS
|
|
|
|
//
|
|
|
|
let sol = new THREE.AmbientLight(0x404040, 1.0);
|
|
|
|
scene.add(sol);
|
|
|
|
|
|
|
|
var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444 );
|
|
|
|
hemiLight.position.set( 0, 20, 0 );
|
|
|
|
scene.add( hemiLight );
|
|
|
|
|
|
|
|
var dirLight = new THREE.DirectionalLight( 0xffffff );
|
|
|
|
dirLight.position.set( - 3, 10, - 10 );
|
|
|
|
scene.add( dirLight );
|
|
|
|
|
|
|
|
//scene.background = new THREE.Color( 0xff0000 );
|
|
|
|
|
|
|
|
|
|
|
|
raycaster = new THREE.Raycaster();
|
|
|
|
mouse = new THREE.Vector2()
|
|
|
|
|
|
|
|
//
|
|
|
|
// Loading screen
|
|
|
|
//
|
|
|
|
const loadingManager = new THREE.LoadingManager( () => {
|
|
|
|
const loadingScreen = document.getElementById('loading-screen');
|
|
|
|
loadingScreen.classList.add('fade-out');
|
|
|
|
loadingScreen.addEventListener('transitionend', onTransitionEnd);
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Load the 3D model
|
|
|
|
//
|
|
|
|
var loader = new GLTFLoader(loadingManager);
|
|
|
|
|
2022-02-17 00:46:05 +01:00
|
|
|
loader.load('map3D.glb', function(gltf) {
|
2022-01-20 01:24:29 +01:00
|
|
|
var object = gltf.scene;
|
|
|
|
gltf.scene.scale.set( 2, 2, 2 );
|
|
|
|
gltf.scene.position.x = 0; //Position (x = right+ left-)
|
|
|
|
gltf.scene.position.y = 0; //Position (y = up+, down-)
|
|
|
|
gltf.scene.position.z = 0;
|
|
|
|
|
|
|
|
scene.add(gltf.scene);
|
|
|
|
|
|
|
|
render();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// RENDERER
|
|
|
|
//
|
|
|
|
renderer = new THREE.WebGLRenderer({
|
|
|
|
antialias: true,
|
|
|
|
});
|
|
|
|
renderer.setClearColor( 0x000000 );
|
|
|
|
|
|
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
|
|
|
renderer.setSize(width, window.innerHeight * 0.75);
|
|
|
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
|
|
renderer.toneMappingExposure = 1;
|
|
|
|
renderer.outputEncoding = THREE.sRGBEncoding;
|
|
|
|
container.appendChild(renderer.domElement);
|
|
|
|
|
|
|
|
var pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
|
|
pmremGenerator.compileEquirectangularShader();
|
|
|
|
|
|
|
|
//
|
|
|
|
// CONTROLS
|
|
|
|
//
|
|
|
|
controls = new OrbitControls(camera, renderer.domElement);
|
|
|
|
controls.addEventListener('change', render); // use if there is no animation loop
|
|
|
|
controls.minDistance = 0;
|
|
|
|
controls.maxDistance = 3500;
|
|
|
|
controls.enablePan = true;
|
|
|
|
controls.target.set(-110, 300, 0);
|
|
|
|
controls.maxPolarAngle = Math.PI/2.05;
|
|
|
|
controls.update();
|
|
|
|
|
|
|
|
//
|
|
|
|
// Load Light
|
|
|
|
//
|
|
|
|
var ambientLight = new THREE.AmbientLight( 0xcccccc );
|
|
|
|
scene.add( ambientLight );
|
|
|
|
|
|
|
|
var directionalLight = new THREE.DirectionalLight( 0xffffff );
|
|
|
|
directionalLight.position.set( 0, 1, 1 ).normalize();
|
|
|
|
scene.add( directionalLight );
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// EVENTS
|
|
|
|
//
|
|
|
|
window.addEventListener('resize', onWindowResize, false);
|
|
|
|
|
|
|
|
renderer.domElement.addEventListener('click', onClick, false); // Mouse
|
|
|
|
//renderer.domElement.addEventListener('mousemove', onMouseOver,false);
|
|
|
|
renderer.domElement.addEventListener('touchend', onTouchEnd, false); // Smartphone
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all the selectors (buildings identifiers)
|
|
|
|
* @returns Array with all the selectors
|
|
|
|
*/
|
|
|
|
function getSelectors() {
|
|
|
|
let info = {};
|
|
|
|
let object = {
|
|
|
|
"function": 'get_map_selectors',
|
|
|
|
'info': info,
|
|
|
|
}
|
|
|
|
return $.ajax({
|
2022-02-17 00:46:05 +01:00
|
|
|
url: 'ajax.php',
|
2022-01-20 01:24:29 +01:00
|
|
|
data: object,
|
|
|
|
method: 'get',
|
|
|
|
success: function(data){
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Show a screen page if the building on which was clicked is in the database
|
|
|
|
* Display the name of the building and the description of the building
|
|
|
|
*/
|
|
|
|
function handleClickOnBuilding(x,y) {
|
|
|
|
|
|
|
|
mouse.x = x;
|
|
|
|
mouse.y = y;
|
|
|
|
|
|
|
|
raycaster.setFromCamera(mouse, camera);
|
|
|
|
|
|
|
|
var intersects = raycaster.intersectObjects(scene.children, true);
|
|
|
|
|
|
|
|
|
|
|
|
// If we clicked on a building
|
|
|
|
if (intersects.length > 0) {
|
|
|
|
|
|
|
|
var selector = intersects[0].object.name.toString().toLowerCase(); // Name of the building we clicked on
|
|
|
|
|
|
|
|
// Wait for getSelectors() to be done
|
|
|
|
// If we do not wait, everything will be executed before checking what is inside the database
|
|
|
|
$.when(getSelectors().done(function(data) {
|
|
|
|
if (data.map(x => x.selector).includes(selector)){
|
|
|
|
// Use the same thing as the one for the 2D map
|
|
|
|
$.alert({
|
|
|
|
title: 'Chargement...',
|
|
|
|
content: function () {
|
|
|
|
let self = this;
|
|
|
|
let object = {
|
|
|
|
"function": 'get_map_info',
|
|
|
|
'selector': selector,
|
|
|
|
};
|
|
|
|
return $.ajax({
|
2022-02-17 00:46:05 +01:00
|
|
|
url: 'ajax.php',
|
2022-01-20 01:24:29 +01:00
|
|
|
data: object,
|
|
|
|
method: 'get'
|
|
|
|
}).done(function (data) {
|
|
|
|
if (data.length > 0) {
|
|
|
|
self.setTitle(data[0]['title']);
|
|
|
|
self.setContent(data[0]['description']);
|
|
|
|
} else {
|
|
|
|
self.setTitle('Erreur');
|
|
|
|
self.setContent('Une erreur est survenue')
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}).fail(function(){
|
|
|
|
self.setContent('Something went wrong.');
|
|
|
|
});
|
|
|
|
},
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the position where the user clicked (mouse) on a building and process it
|
|
|
|
*/
|
|
|
|
function onClick() {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
height = document.querySelector('#maps').clientHeight;
|
|
|
|
width = document.querySelector('#maps').clientWidth;
|
|
|
|
|
|
|
|
|
|
|
|
const rect = renderer.domElement.getBoundingClientRect();
|
|
|
|
mouse.x = ( ( event.clientX - rect.left ) / ( rect.right - rect.left ) ) * 2 - 1;
|
|
|
|
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
|
|
|
|
handleClickOnBuilding(mouse.x, mouse.y);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the position where the user clicked (smartphone) on a building and process it
|
|
|
|
*/
|
|
|
|
function onTouchEnd() {
|
|
|
|
var clientX, clientY;
|
|
|
|
|
|
|
|
clientX = event.changedTouches[0].clientX;
|
|
|
|
clientY = event.changedTouches[0].clientY;
|
|
|
|
|
|
|
|
const rect = renderer.domElement.getBoundingClientRect();
|
|
|
|
mouse.x = ( ( clientX - rect.left ) / ( rect.right - rect.left ) ) * 2 - 1;
|
|
|
|
mouse.y = - ( ( clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
|
|
|
|
|
|
|
|
handleClickOnBuilding(mouse.x, mouse.y);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process something when the user moved the mouse over a building
|
|
|
|
* @todo add text over the building
|
|
|
|
*/
|
|
|
|
function onMouseOver() {
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
height = document.querySelector('#maps').clientHeight;
|
|
|
|
width = document.querySelector('#maps').clientWidth;
|
|
|
|
|
|
|
|
|
|
|
|
const rect = renderer.domElement.getBoundingClientRect();
|
|
|
|
mouse.x = ( ( event.clientX - rect.left ) / ( rect.right - rect.left ) ) * 2 - 1;
|
|
|
|
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
|
|
|
|
|
|
|
|
createTextOverBuilding(mouse.x,mouse.y)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function createTextOverBuilding(x,y) {
|
|
|
|
mouse.x = x;
|
|
|
|
mouse.y = y;
|
|
|
|
|
|
|
|
raycaster.setFromCamera(mouse, camera);
|
|
|
|
|
|
|
|
var intersects = raycaster.intersectObjects(scene.children, true);
|
|
|
|
|
|
|
|
|
|
|
|
// If we clicked on a building
|
|
|
|
if (intersects.length > 0) {
|
|
|
|
console.log(intersects);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function makeLabelCanvas(baseWidth, size, name) {
|
|
|
|
const borderSize = 2;
|
|
|
|
const ctx = document.createElement('canvas').getContext('2d');
|
|
|
|
const font = `${size}px bold sans-serif`;
|
|
|
|
ctx.font = font;
|
|
|
|
// measure how long the name will be
|
|
|
|
const textWidth = ctx.measureText(name).width;
|
|
|
|
|
|
|
|
const doubleBorderSize = borderSize * 2;
|
|
|
|
const width = baseWidth + doubleBorderSize;
|
|
|
|
const height = size + doubleBorderSize;
|
|
|
|
ctx.canvas.width = width;
|
|
|
|
ctx.canvas.height = height;
|
|
|
|
|
|
|
|
// need to set font again after resizing canvas
|
|
|
|
ctx.font = font;
|
|
|
|
ctx.textBaseline = 'middle';
|
|
|
|
ctx.textAlign = 'center';
|
|
|
|
|
|
|
|
ctx.fillStyle = 'blue';
|
|
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
|
|
|
|
// scale to fit but don't stretch
|
|
|
|
const scaleFactor = Math.min(1, baseWidth / textWidth);
|
|
|
|
ctx.translate(width / 2, height / 2);
|
|
|
|
ctx.scale(scaleFactor, 1);
|
|
|
|
ctx.fillStyle = 'white';
|
|
|
|
ctx.fillText(name, 0, 0);
|
|
|
|
|
|
|
|
|
|
|
|
const labelBaseScale = 0.01;
|
|
|
|
const label = new THREE.Sprite(labelMaterial);
|
|
|
|
scene.add(label);
|
|
|
|
label.position.y = head.position.y + headRadius + size * labelBaseScale;
|
|
|
|
|
|
|
|
label.scale.x = canvas.width * labelBaseScale;
|
|
|
|
label.scale.y = canvas.height * labelBaseScale;
|
|
|
|
return ctx.canvas;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Auto-resizes the canvas when window size is updated
|
|
|
|
*/
|
|
|
|
function onWindowResize() {
|
|
|
|
|
|
|
|
height = document.querySelector('#main-content .inner').clientHeight;
|
|
|
|
width = document.querySelector('#main-content .inner').clientWidth;
|
|
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
|
|
|
|
renderer.setSize(width * 0.9, window.innerHeight * 0.75); // 0.9 and 0.75 so it looks comfortable on the screen
|
|
|
|
|
|
|
|
render();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Render the scene
|
|
|
|
*/
|
|
|
|
function render() {
|
|
|
|
|
|
|
|
renderer.render(scene, camera);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Remove the loader when the model loaded
|
|
|
|
*/
|
|
|
|
function onTransitionEnd( event) {
|
|
|
|
event.target.remove();
|
|
|
|
}
|