diff --git a/config.js b/config.js
index 00b9d9a..478a971 100644
--- a/config.js
+++ b/config.js
@@ -44,7 +44,88 @@ export const config = {
uri : `${BASE_URI}/scenes/ssgp/`,
popup: theater2Popup,
coords: [45.4401, 12.3408],
- model: `SSGP.glb`,
+ nodes: [
+ /*
+ {
+ label: 'Struttura principale',
+ model: 'models/ssgp/Teatro_SSGP_Full_ConSottrazioni.glb',
+ isMain: true,
+ },
+ */
+ {
+ label: 'Struttura parete di fondo',
+ model: 'models/ssgp/Teatro_SSGP_Layer_Struttura_parete_di_fondo.glb',
+ isMain: true,
+ },
+ {
+ label: 'Ballatoio',
+ model: 'models/ssgp/Teatro_SSGP_Ballatoio.glb',
+ },
+ {
+ label: 'Boccascena',
+ model: 'models/ssgp/Teatro_SSGP_Boccascena.glb',
+ },
+ {
+ label: 'Fossa orchestra',
+ model: 'models/ssgp/Teatro_SSGP_Fossa_orchestra.glb',
+ },
+ {
+ label: 'Graticcia',
+ model: 'models/ssgp/Teatro_SSGP_Graticcia.glb',
+ },
+ {
+ label: 'Ordine 1',
+ model: 'models/ssgp/Teatro_SSGP_Ordine1.glb',
+ },
+ {
+ label: 'Ordine 2',
+ model: 'models/ssgp/Teatro_SSGP_Ordine2.glb',
+ },
+ {
+ label: 'Ordine 3',
+ model: 'models/ssgp/Teatro_SSGP_Ordine3.glb',
+ },
+ {
+ label: 'Ordine 4',
+ model: 'models/ssgp/Teatro_SSGP_Ordine4.glb',
+ },
+ {
+ label: 'Ordine 5',
+ model: 'models/ssgp/Teatro_SSGP_Ordine5.glb',
+ },
+ {
+ label: 'Palcoscenico',
+ model: 'models/ssgp/Teatro_SSGP_Palcoscenico.glb',
+ },
+ {
+ label: 'Parapetto scala piani',
+ model: 'models/ssgp/Teatro_SSGP_parapetto_scala_piani.glb',
+ },
+ {
+ label: 'Percorsi scale corridoi',
+ model: 'models/ssgp/Teatro_SSGP_Percorsi_scale_corridoi.glb',
+ },
+ {
+ label: 'Platea peplano',
+ model: 'models/ssgp/Teatro_SSGP_Platea_peplano.glb',
+ },
+ {
+ label: 'Quinte architettoniche fisse',
+ model: 'models/ssgp/Teatro_SSGP_Layer_quinte_architettoniche_fisse.glb',
+ },
+ {
+ label: 'Quinte architettoniche mobili',
+ model: 'models/ssgp/Teatro_SSGP_Layer_quinte_architettoniche_mobili.glb',
+ },
+ {
+ label: 'Spazio tecnico superiore',
+ model: 'models/ssgp/Teatro_SSGP_Layer_Spazio_tecnico_sup_soffitta.glb',
+ },
+ {
+ label: 'Spazio tecnico inferiore',
+ model: 'models/ssgp/Teatro_SSGP_Spazio_tecnico_inf.glb',
+ },
+ ],
pano: `pano/defsky-grass.jpg`,
}
],
diff --git a/js/scene.js b/js/scene.js
index 7fcb053..3dfea54 100644
--- a/js/scene.js
+++ b/js/scene.js
@@ -2,111 +2,11 @@
import AppState from "./state.js";
import { config } from "../config.js";
+import UI from "./ui.js";
const Scene = {};
-Scene.UI = {};
-
-Scene.UI.domParser = new DOMParser;
-/**
- *
- * @param {String} triggerSelector - Usually, the close modal trigger element(s) selector
- */
-Scene.UI.pauseAudio = function(triggerSelector) {
- // What if more than one audio element is playing?
- const audio = document.querySelector('audio');
-
- if (audio) {
- document.querySelectorAll(triggerSelector).forEach(el => {
- el.addEventListener('click', () => audio.pause());
- });
- document.querySelector('.modal').addEventListener('blur', () => {
- audio.pause();
- });
- }
-}
-
-/**
- * Resets the UI state (essentially hides the clipper toolbar if visible...)
- * @todo Other elements to reset?? Restore inital lighting conditions and viewpoint...
- */
-Scene.UI.reset = function() {
- document.querySelector('#clipper-bar')?.classList.add('d-none');
- document.querySelector('#clipper')?.classList.remove('border', 'border-2', 'border-white');
-}
-/**
- * @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);
-
- if (!AppState.clipping.listeners.button) {
- trigger.addEventListener(
- 'click',
- () => {
- toolbar.classList.toggle('d-none');
- const aoCurrentState = AppState.ambientOcclusion;
-
- if (!toolbar.classList.contains('d-none')) {
- AppState.clipping.enabled = true;
-
- //if (AppState.clipping.controls) AppState.clipping.controls.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 => {
- 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 {
- Scene.resetClipping();
- let noBorder = trigger.className.replace(/ border.*$/g, '');
- trigger.className = noBorder;
- Scene.toggleAmbientOcclusion(aoCurrentState);
- }
- }
- );
- AppState.clipping.listeners.button = true;
- }
-}
+Scene.UI = UI;
/**
* @todo Experimental...
@@ -289,137 +189,6 @@ Scene.toggleAmbientOcclusion = function(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();
@@ -431,19 +200,19 @@ Scene.init = function() {
ATON.toggleShadows(true);
ATON.setExposure(config.scene.initialExposure);
// Open settings side panel when clicking on settings btn
- Scene.toggleSettingsPanel('settings');
- Scene.toggleContentMenu('menu');
+ Scene.UI.toggleSettingsPanel('settings');
+ Scene.UI.toggleContentMenu('menu');
AppState.camera = ATON.Nav._camera;
AppState.renderer = ATON._renderer;
ATON.Nav.setUserControl(true);
}
-
+/**
+ * Reset clipping state after disabling from UI
+ * @todo DragControls errors!!
+ */
Scene.resetClipping = function () {
-
- console.warn('Resetting clipping!!');
-
AppState.clipping.enabled = false;
ATON.disableClipPlanes();
AppState.clipping.controls.deactivate();
@@ -466,31 +235,42 @@ Scene.resetClipping = function () {
*/
Scene.openScene = function(marker) {
Scene.init();
+ Scene.UI.toggleClipperBar('#clipper', '#clipper-bar');
- Scene.UI.toggleClipper('#clipper', '#clipper-bar');
+ // Load 3D models and create nodes
+ Scene.loadNodes(marker.nodes);
- // Load 3D model then
- let mainNode = ATON.createSceneNode(marker.label);
- mainNode.load(marker.model);
- // TODO: only for the main ('larger') node in the scene
- AppState.mainNodeId = marker.label;
ATON.setMainPanorama(marker.pano);
- //mainNode.setMaterial(new THREE.MeshPhongMaterial(material));
// TODO: hardcoded...
- mainNode.setRotation(0, 1.5, 0)
AppState.initialRotation = new THREE.Vector3(0, 1.5, 0);
- Scene.showEdges(mainNode);
-
- mainNode.attachToRoot();
+ //Scene.showEdges(mainNode);
ATON.setAutoLP(config.scene.autoLP);
AppState.lightProbe = config.scene.autoLP;
Scene.toggleAmbientOcclusion(true);
AppState.ambientOcclusion = true;
-
AppState.root = ATON.getRootScene();
- // ATON.Node.getBound() returns a THREE.Sphere object
- AppState.clipping.boundingSphere = mainNode.getBound();
+}
+
+/**
+ *
+ * @param {Array} nodes
+ */
+Scene.loadNodes = function (nodes) {
+ nodes.forEach(n => {
+ let node = ATON.createSceneNode(n.label);
+ node.load(n.model);
+ node.setRotation(0, 1.5, 0);
+ node.attachToRoot();
+
+ if (n.isMain) {
+ AppState.mainNodeId = n.label;
+ // ATON.Node.getBound() returns a THREE.Sphere object
+ AppState.clipping.boundingSphere = node.getBound();
+ }
+
+ AppState.nodes.push({id: n.label, active: true});
+ });
}
export default Scene;
\ No newline at end of file
diff --git a/js/state.js b/js/state.js
index c4e6c0c..00f7877 100644
--- a/js/state.js
+++ b/js/state.js
@@ -4,7 +4,11 @@
let AppState = {
// The root scene object
root: null,
+ nodeIds: [],
+ // {id: String, active: Boolean}
+ nodes: [],
mainNodeId: null,
+ layersMenuBuilt: false,
initialRotation: null,
camera: null,
renderer: null,
diff --git a/js/ui.js b/js/ui.js
new file mode 100644
index 0000000..39c6f0e
--- /dev/null
+++ b/js/ui.js
@@ -0,0 +1,261 @@
+import { config } from "../config.js";
+import Scene from "./scene.js";
+import AppState from "./state.js";
+/**
+ * @namespace UI
+ */
+const UI = {};
+
+UI.domParser = new DOMParser;
+/**
+ *
+ * @param {String} triggerSelector - Usually, the close modal trigger element(s) selector
+ */
+UI.pauseAudio = function(triggerSelector) {
+ // What if more than one audio element is playing?
+ const audio = document.querySelector('audio');
+
+ if (audio) {
+ document.querySelectorAll(triggerSelector).forEach(el => {
+ el.addEventListener('click', () => audio.pause());
+ });
+ document.querySelector('.modal').addEventListener('blur', () => {
+ audio.pause();
+ });
+ }
+}
+/**
+ * Resets the UI state (essentially hides the clipper toolbar if visible...)
+ * @todo Other elements to reset?? Restore inital lighting conditions and viewpoint...
+ */
+UI.reset = function() {
+ document.querySelector('#clipper-bar')?.classList.add('d-none');
+ document.querySelector('#clipper')?.classList.remove('border', 'border-2', 'border-white');
+}
+
+/**
+ *
+ * @param {HTMLElement} target
+ * @param {NodeListOf} btns
+ * @param {String} axis - One of 'x', 'y', 'z'
+ */
+UI.showClipping = function(target, btns, axis) {
+ if (axis) {
+ Scene.addClippingPlane(axis, -1);
+ target.classList.add('border', 'border-2', 'border-warning');
+ btns.forEach(btn => {
+ if (btn.id !== target.id) {
+ btn.classList.remove('border', 'border-2', 'border-warning');
+ }
+ });
+ }
+}
+/**
+ * @todo Get clipping button from state? Review logic!!
+ * @param {String} triggerSelector
+ * @param {String} targetSelector The selector for the target toolbar to be displayed
+ */
+UI.toggleClipperBar = function(triggerSelector, targetSelector) {
+ const trigger = document.querySelector(triggerSelector);
+ const toolbar = document.querySelector(targetSelector);
+ const btns = toolbar.querySelectorAll('button');
+ const clipTargets = {
+ clipX: {axis: 'x'},
+ clipY: {axis: 'y'},
+ clipZ: {axis: 'z'},
+ };
+
+ if (!AppState.clipping.listeners.button) {
+ trigger.addEventListener(
+ 'click',
+ () => {
+ toolbar.classList.toggle('d-none');
+ const aoCurrentState = AppState.ambientOcclusion;
+
+ if (!toolbar.classList.contains('d-none')) {
+ AppState.clipping.enabled = true;
+ Scene.toggleAmbientOcclusion(false);
+
+ btns.forEach(btn => {
+ btn.classList.remove('border', 'border-2', 'border-warning');
+ });
+
+ trigger.className += ' border border-2 border-white';
+ toolbar.addEventListener('click', event => {
+ UI.showClipping(event.target, btns, clipTargets[event.target.id]?.axis);
+ });
+ } else {
+ Scene.resetClipping();
+ let noBorder = trigger.className.replace(/ border.*$/g, '');
+ trigger.className = noBorder;
+ Scene.toggleAmbientOcclusion(aoCurrentState);
+ }
+ }
+ );
+ AppState.clipping.listeners.button = true;
+ }
+}
+/**
+ * A left side settings panel
+ * @param {String} triggerId - The settings button id
+ */
+UI.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 => {
+ Scene.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);
+ }
+ );
+ });
+}
+/**
+ *
+ * @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
+ */
+UI.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);
+ Scene.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
+ */
+UI.toggleContentMenu = function(triggerId) {
+ const btn = document.querySelector(`#${triggerId}`);
+ let audio1 = this.domParser.parseFromString(config.menu.audioBtn1, 'text/html');
+ audio1 = audio1.querySelector('button');
+
+ btn.addEventListener('click', () => {
+ ATON.UI.setSidePanelRight();
+ ATON.UI.showSidePanel({header: ' Contenuti'});
+ ATON.UI.elSidePanel.appendChild(audio1);
+ ATON.UI.elSidePanel.appendChild(this.domParser.parseFromString('Layer
', 'text/html').querySelector('h5'));
+ this.buildLayersMenu(AppState.nodes, ATON.UI.elSidePanel);
+ });
+}
+/**
+ * @todo Don't rebuild it every time the side panel is shown...
+ * @param {Array} nodes The scenes nodes (IDs and status)
+ * @param {HTMLElement} sidePanel ATON's side panel element
+ */
+UI.buildLayersMenu = function(nodes, sidePanel) {
+ for(let node of nodes) {
+ const checkboxStr = `
+
+
+
+
+ `;
+
+ let element = this.domParser.parseFromString(checkboxStr, 'text/html');
+ element = element.querySelector('div.form-check');
+
+ sidePanel.appendChild(element);
+ // Will this ever work??
+ element.addEventListener('change', event => toggleNode(node.id, event.target.checked));
+ }
+
+ /**
+ * This is terrible...
+ * @param {String} id
+ * @param {Boolean} status
+ */
+ const toggleNode = (id, status) => {
+ ATON.getSceneNode(id).toggle(status);
+ AppState.nodes.find(n => n.id === id).active = status;
+ }
+}
+
+export default UI;
\ No newline at end of file