/* Map 3D * 3D Model : Matthias Onestras * Code : Ronan Bonnet * */ /** repository containing this script and the map3D.glb **/ var rep = "./assets/map"; 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 // var maps = document.getElementById("maps"); container = document.createElement('div'); container.id = 'map3d'; height = maps.clientHeight; width = maps.clientWidth; maps.appendChild(container); // // 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); loader.load(rep + '/' + 'map3D.glb', function(gltf) { 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 } function doChecks(response) { if (!response.ok) { throw Error(response.statusText); } return response; } function readIt(response) { return response.json() } /** * Get all the selectors (buildings identifiers) * @returns Array with all the selectors */ function getSelectors() { const payload = { "function": 'get_map_selectors' } return fetch(rep + '/' + 'ajax.php', { method: 'POST', body: JSON.stringify(payload), headers: { "Content-type": "application/json; charset=UTF-8" } }).then(doChecks).then(readIt) } /* * 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 console.log(selector) // Wait for getSelectors() to be done // If we do not wait, everything will be executed before checking what is inside the database getSelectors().then((data) => { if (data.map(x => x.selector).includes(selector)){ let payload = { "function": 'get_map_info', 'selector': selector, }; fetch(rep + '/' + 'ajax.php', { method: 'POST', body: JSON.stringify(payload), headers: { "Content-type": "application/json; charset=UTF-8" } }).then(doChecks).then(readIt).then((data) => { if (data.length > 0) { Swal.fire({ title: '' + data[0]['title'] + '', html: data[0]['description'] }) } else { Swal.fire({ title: "Erreur", html: "Une erreur est survenue", timer: 3000, timerProgressBar: true }) } }).catch((error) => { console.error(error); }); } }).catch((error) => { console.error(error); }); } } /** * Get the position where the user clicked (mouse) on a building and process it */ function onClick(e) { e.preventDefault(); //height = document.querySelector('#maps').clientHeight; //width = document.querySelector('#maps').clientWidth; const rect = renderer.domElement.getBoundingClientRect(); mouse.x = ( ( e.clientX - rect.left ) / ( rect.right - rect.left ) ) * 2 - 1; mouse.y = - ( ( e.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(e) { var clientX, clientY; clientX = e.changedTouches[0].clientX; clientY = e.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(e) { e.preventDefault(); height = document.querySelector('#maps').clientHeight; width = document.querySelector('#maps').clientWidth; const rect = renderer.domElement.getBoundingClientRect(); mouse.x = ( ( e.clientX - rect.left ) / ( rect.right - rect.left ) ) * 2 - 1; mouse.y = - ( ( e.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() { var maps = document.getElementById("maps"); height = maps.clientHeight; width = maps.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(); }