// Global ATON import { AppState, getSceneStatus, setSceneStatus } from "./state.js"; import { config } from "../config.js"; const material = { color: "#fff", //color: "#e6f2ff", emissive: true, }; const Scene = {}; Scene.UI = {}; Scene.UI.domParser = new DOMParser; /** * @todo Get clipping button from state? Review logic!! * @param {String} triggerSelector * @param {String} targetSelector The selector for the target toolbar to be displayed */ Scene.UI.toggleClipper = function(triggerSelector, targetSelector) { const trigger = document.querySelector(triggerSelector); const toolbar = document.querySelector(targetSelector); trigger.addEventListener( 'click', () => { toolbar.classList.toggle('d-none'); const aoCurrentState = AppState.ambientOcclusion; if (!AppState.clipping.enabled) { AppState.clipping.enabled = true; Scene.toggleAmbientOcclusion(false); const btns = toolbar.querySelectorAll('button'); btns.forEach(btn => { btn.classList.remove('border', 'border-2', 'border-warning'); }); trigger.className += ' border border-2 border-white'; toolbar.addEventListener('click', event => { console.log('Clipping target:', event.target); if (event.target.id === 'clipX') { // Clip along X... Scene.addClippingPlane('x', -1); // Export to function... event.target.classList.add('border', 'border-2', 'border-warning'); btns.forEach(btn => { if (btn.id !== event.target.id) { btn.classList.remove('border', 'border-2', 'border-warning'); } }); } else if (event.target.id === 'clipY') { // Clip along Y... Scene.addClippingPlane('y', -1); event.target.classList.add('border', 'border-2', 'border-warning'); btns.forEach(btn => { if (btn.id !== event.target.id) { btn.classList.remove('border', 'border-2', 'border-warning'); } }) } else if (event.target.id === 'clipZ') { // Clip along Z... Scene.addClippingPlane('z', 1); event.target.classList.add('border', 'border-2', 'border-warning'); btns.forEach(btn => { if (btn.id !== event.target.id) { btn.classList.remove('border', 'border-2', 'border-warning'); } }) } }); } else { AppState.clipping.enabled = false; ATON.disableClipPlanes(); AppState.root.remove(AppState.clipping.helper); AppState.clipping.helper = null; let noBorder = trigger.className.replace(/ border.*$/g, ''); trigger.className = noBorder; Scene.toggleAmbientOcclusion(aoCurrentState); } } ); } /** * @todo Experimental... * @param {THREE.Object3D} object - A THREE Object3D instance */ Scene.showEdges = function(object) { const edgeMaterial = new THREE.LineBasicMaterial( { color: 0x000000 } ); object.traverse(function(child) { if (child.isMesh) { let edges = new THREE.EdgesGeometry(child.geometry, 45); let line = new THREE.LineSegments(edges, edgeMaterial); child.add(line); console.log(child); } }); } /** * @param {String} axis - The axis along wich the plane's normal should be directed, * one of 'x', 'y', 'z' * @param {Number} orientation - Positive (1) or negative (-1) orientation on the axis */ Scene.addClippingPlane = function(axis, orientation = -1) { axis = axis.toLowerCase(); const defaultPoint = new THREE.Vector3( ...config.scene.clipping.defaultPoint ); const vector = [ axis === 'x' ? orientation : 0, axis === 'y' ? orientation : 0, axis === 'z' ? orientation : 0, ]; // First, add a default clipping plane // at a predefined point (bad?) Scene.activateClipper(vector, defaultPoint); console.log(vector, defaultPoint); window.addEventListener('mousedown', event => { // Activate clipping when left-clicking on the scene if (AppState.clipping.enabled && event.buttons === 1) { Scene.activateClipper(vector); } }); } /** * @todo WIP! * Activate clipping plane * @param {Number[]} vector - The vector array to direct the plane */ Scene.activateClipper = function(vector, point = null) { point ??= ATON.getSceneQueriedPoint(); if (point) { console.log('Queried point:', point); // First remove any existing clipping planes ATON.disableClipPlanes(); // Normal of the clipping plane along the Y axis facing down const plane = ATON.addClipPlane(new THREE.Vector3(...vector), point); // Add a visible plane helper for the clipping plane const helper = new THREE.PlaneHelper(plane, 24, 0xffff00); // Remove any already visbile helper plane if (AppState.clipping.helper !== null) AppState.root.remove(AppState.clipping.helper); AppState.root.add(helper); AppState.clipping.helper = helper; console.log("I'm clipping, baby!"); } } /** * * @param {THREE.Vector3} vector - An object with x,y,z coordinates */ Scene.changeLightDirection = function(vector) { ATON.setMainLightDirection(vector); } /** * * @param {Boolean} isEnabled */ Scene.toggleAmbientOcclusion = function(isEnabled) { ATON.FX.togglePass(ATON.FX.PASS_AO, isEnabled); console.log('Ambient occlusion', isEnabled ? 'ON' : 'OFF'); } /** * * @param {String} direction - The axis direction, one of 'x','y','z' * @param {String} label - The slider label * @param {Number[]} range - The slider's range * @param {Number} step - The slider's step */ Scene.createLightSlider = function(direction, label, range, step) { const currentVal = ATON.getMainLightDirection()[direction]; const lightSlider = ATON.UI.createSlider({ range, label, value: Number.parseFloat(currentVal).toPrecision(1), oninput: val => { const lightDir = ATON.getMainLightDirection(); // Keep existing direction values for the other axes lightDir[direction] = Number.parseFloat(val); this.changeLightDirection(lightDir); }, }); lightSlider.classList.add('ms-4'); lightSlider.querySelector('input').step = step; return lightSlider; } /** * Right-side main menu panel * @param {String} triggerId - The menu button id */ Scene.toggleContentMenu = function(triggerId) { const btn = document.querySelector(`#${triggerId}`); const audio1 = this.UI.domParser.parseFromString(config.menu.audioBtn1, 'text/html') .querySelector('button'); btn.addEventListener('click', () => { ATON.UI.setSidePanelRight(); ATON.UI.showSidePanel({header: ' Contenuti'}); ATON.UI.elSidePanel.appendChild(audio1); }); } /** * A left side settings panel * @param {String} triggerId - The settings button id */ Scene.toggleSettingsPanel = function(triggerId) { const btn = document.querySelector(`#${triggerId}`); const lightHeading = document.createElement('h2'); lightHeading.className = 'fs-5 ms-2 mb-3 mt-3'; lightHeading.innerHTML = ' Direzione luce'; const envHeading = document.createElement('h2'); envHeading.className = 'fs-5 ms-2 mb-3 mt-3'; envHeading.innerHTML = ' Ambiente'; btn.addEventListener('click', () => { ATON.UI.setSidePanelLeft(); ATON.UI.showSidePanel({header: ' Impostazioni'}); ATON.UI.elSidePanel.appendChild(lightHeading); const lightSliderX = this.createLightSlider('x', 'Asse X', [-2, 2], 0.1); const lightSliderY = this.createLightSlider('y', 'Asse Y', [-2, 2], 0.1); const lightSliderZ = this.createLightSlider('z', 'Asse Z', [-2, 2], 0.1); ATON.UI.elSidePanel.appendChild(lightSliderX); ATON.UI.elSidePanel.appendChild(lightSliderY); ATON.UI.elSidePanel.appendChild(lightSliderZ); ATON.UI.elSidePanel.appendChild(envHeading); const ambientOcclSwitch = document.createElement('div'); ambientOcclSwitch.className = 'form-check form-switch ms-4 mt-2'; ambientOcclSwitch.innerHTML = ` `; const shadowsSwitch = document.createElement('div'); shadowsSwitch.className = 'form-check form-switch ms-4 mt-2'; shadowsSwitch.innerHTML = ` `; const lightProbeSwitch = document.createElement('div'); lightProbeSwitch.className = 'form-check form-switch ms-4 mt-2'; lightProbeSwitch.innerHTML = ` `; shadowsSwitch.querySelector('input[type="checkbox"').checked = AppState.shadows; ambientOcclSwitch.querySelector('input[type="checkbox"').checked = AppState.ambientOcclusion; lightProbeSwitch.querySelector('input[type="checkbox"').checked = AppState.lightProbe; ATON.UI.elSidePanel.appendChild(ambientOcclSwitch); ATON.UI.elSidePanel.appendChild(shadowsSwitch); ATON.UI.elSidePanel.appendChild(lightProbeSwitch); // TODO: move somewhere else... document.querySelector('#aoSwitch').addEventListener( 'change', event => { this.toggleAmbientOcclusion(event.target.checked); AppState.ambientOcclusion = event.target.checked; } ); document.querySelector('#shadowsSwitch').addEventListener( 'change', event => { const checked = event.target.checked; ATON.toggleShadows(checked); AppState.shadows = checked; } ); // Not working properly? document.querySelector('#lpSwitch').addEventListener( 'change', event => { const checked = event.target.checked; ATON.setAutoLP(checked); //if (!checked) ATON.clearLightProbes(); AppState.lightProbe = checked; if (checked) ATON.updateLightProbes(); console.log('Light probe: ', checked); } ); }); } Scene.init = function() { ATON.realize(); ATON.UI.addBasicEvents(); ATON.UI.init(); // All assets for this app are stored here ATON.setPathCollection('./assets/'); // Initial light direction ATON.setMainLightDirection(new THREE.Vector3(0.2,-0.3,-0.7)); ATON.toggleShadows(true); ATON.setExposure(config.scene.initialExposure); // Open settings side panel when clicking on settings btn Scene.toggleSettingsPanel('settings'); Scene.toggleContentMenu('menu'); } /** * @param {String} id - The back-to-map button id */ Scene.toggleScene = function(id) { const btn = document.querySelector(`#${id}`); const scene = document.querySelector('#scene'); btn.addEventListener('click', () => { scene.classList.toggle('d-none'); // Pause rendering the 3D scene to free resources (hopefully) // when browsing the map ATON.renderPause(); document.querySelector('#map').classList.toggle('d-none'); AppState.map.invalidateSize(); }); } /** * @param {Object} marker - The marker object from config */ Scene.openScene = function(marker) { let canvas = document.querySelector('canvas'); let scene = document.querySelector('#scene'); if (canvas === null) { Scene.init(); } scene.classList.toggle('d-none'); ATON.renderResume(); if (!getSceneStatus(marker.id)) { // Set scene as active setSceneStatus(marker.id, true); // Load 3D model let mainNode = ATON.createSceneNode(marker.label).load(marker.model); ATON.setMainPanorama(marker.pano); //mainNode.setMaterial(new THREE.MeshPhongMaterial(material)); mainNode.setRotation(0, 1.5, 0) Scene.showEdges(mainNode); mainNode.attachToRoot(); ATON.setAutoLP(config.scene.autoLP); AppState.lightProbe = config.scene.autoLP; Scene.toggleAmbientOcclusion(true); AppState.ambientOcclusion = true; Scene.UI.toggleClipper('#clipper', '#clipper-bar'); AppState.root = ATON.getRootScene(); } } export default Scene;