Compare commits

4 Commits

Author SHA1 Message Date
cabfe687e2 Handle invisible nodes (temp) 2026-04-22 10:34:27 +02:00
26733a4b84 Remove UI module 2026-04-22 10:30:04 +02:00
380999ff4b Add exposure slider (not working?) 2026-04-22 09:35:33 +02:00
d0d24c0e6c Fix handling of light direction (hopefully) 2026-04-22 09:06:29 +02:00
8 changed files with 102 additions and 61 deletions

View File

@@ -49,6 +49,7 @@ export const config = {
label: 'Teatro', label: 'Teatro',
model: 'models/ssgp/Teatro_SSGP_Full_ConSottrazioni.glb', model: 'models/ssgp/Teatro_SSGP_Full_ConSottrazioni.glb',
opacity: 0.0, opacity: 0.0,
isInvisible: true,
children: [ children: [
/* /*
{ {

View File

@@ -1,9 +1,12 @@
// Global ATON // Global ATON
import { Controller } from "@hotwired/stimulus" import { Controller } from "@hotwired/stimulus"
import AppState from "../state.js"; import AppState from "../state.js";
import { traverseOntology } from "../ontology.js";
const html = String.raw; const html = String.raw;
const domParser = new DOMParser; const domParser = new DOMParser;
// TODO: hard-coded, but follows a convention...
const ontologyJsonPath = location.pathname + 'ontology.json';
export default class extends Controller { export default class extends Controller {
static targets = ['trigger', 'layers', 'ontology']; static targets = ['trigger', 'layers', 'ontology'];
@@ -15,11 +18,12 @@ export default class extends Controller {
* Open settings panel * Open settings panel
* @param {Event} event * @param {Event} event
*/ */
toggleMenu(event) { async toggleMenu() {
ATON.UI.setSidePanelRight(); ATON.UI.setSidePanelRight();
ATON.UI.showSidePanel({header: 'Menu'}); ATON.UI.showSidePanel({header: 'Menu'});
this.#buildMenuPanel(ATON.UI.elSidePanel); this.#buildMenuPanel(ATON.UI.elSidePanel);
this.#buildLayersMenu(AppState.normalizedNodes, this.layersTarget); this.#buildLayersMenu(AppState.normalizedNodes, this.layersTarget);
this.#buildOntologyMenu(await traverseOntology(ontologyJsonPath), this.ontologyTarget);
} }
/** /**
* @param {Event} event * @param {Event} event
@@ -75,4 +79,31 @@ export default class extends Controller {
); );
} }
} }
/**
* Temporary implementation to show domains only
* @todo Don't rebuild it every time, use caching, return a container
* @param {Object} ontology The traversed ontology object (temp)
* @param {HTMLElement} tab Tab content element
*/
#buildOntologyMenu(ontology, tab) {
console.debug(ontology);
const mainNode = tab.querySelector('#ontology-list');
mainNode.textContent = ontology.ontology;
let domainList = html`
<ul class="list-group mt-2" id="domains-list"></ul>
`;
// Very fragile and ugly!!
mainNode.innerHTML += domainList;
domainList = tab.querySelector('#domains-list');
for(let domain of ontology.domains) {
const domainItem = html`
<li class="list-group-item">${domain.label}</li>
`;
domainList.innerHTML += domainItem;
}
}
} }

View File

@@ -1,7 +1,7 @@
// Global ATON // Global ATON
import { Controller } from "@hotwired/stimulus" import { Controller } from "@hotwired/stimulus"
import AppState from "../state.js"; import AppState from "../state.js";
import { createLightSlider } from "../utils/environment.js"; import { createExposureSlider, createLightSlider } from "../utils/environment.js";
const html = String.raw; const html = String.raw;
const panelHeader = html` const panelHeader = html`
@@ -39,12 +39,15 @@ export default class extends Controller {
#buildSettingsPanel(panel) { #buildSettingsPanel(panel) {
const fragment = this.#cloneTemplate('tmpl-settings'); const fragment = this.#cloneTemplate('tmpl-settings');
let sliderContainer = fragment.querySelector('[data-sliders-container]'); let sliderContainer = fragment.querySelector('[data-sliders-container]');
let exposureContainer = fragment.querySelector('[data-slider-exposure-container]');
['x', 'y', 'z'].forEach((axis, i) => { ['x', 'y', 'z'].forEach((axis, i) => {
const label = ['Asse X', 'Asse Y', 'Asse Z'][i]; const label = ['Asse X', 'Asse Y', 'Asse Z'][i];
sliderContainer.appendChild(createLightSlider(axis, label, [-2, 2], 0.1)); sliderContainer.appendChild(createLightSlider(axis, label, [-2, 2], 0.1));
}) })
exposureContainer.appendChild(createExposureSlider('Valore', [0, 5]));
panel.appendChild(fragment); panel.appendChild(fragment);
} }
} }

View File

@@ -42,6 +42,8 @@ function init () {
AppState.camera = ATON.Nav._camera; AppState.camera = ATON.Nav._camera;
AppState.renderer = ATON._renderer; AppState.renderer = ATON._renderer;
AppState.shadows = config.scene.shadows; AppState.shadows = config.scene.shadows;
AppState.lightDirection = ATON.getMainLightDirection();
AppState.exposure = config.scene.initialExposure;
ATON.Nav.setUserControl(true); ATON.Nav.setUserControl(true);
} }
@@ -86,6 +88,9 @@ function loadNodes(nodes) {
})); }));
} }
// Disable a node for picking (shadows, light probe etc.)
if (n.isInvisible) node.hide();
node.attachToRoot(); node.attachToRoot();
if (n.isMain) { if (n.isMain) {
@@ -94,7 +99,7 @@ function loadNodes(nodes) {
AppState.clipping.boundingSphere = node.getBound(); AppState.clipping.boundingSphere = node.getBound();
} }
AppState.nodes.push({id: n.label, active: true}); AppState.nodes.push({id: n.label, active: n.isInvisible ? false: true});
}); });
if (!AppState.clipping.boundingSphere) { if (!AppState.clipping.boundingSphere) {

View File

@@ -20,7 +20,7 @@ let AppState = {
// {id: String, active: Boolean} // {id: String, active: Boolean}
nodes: [], nodes: [],
/** /**
* @property {NormalizedSceneNode[]} normalizedNodes * @type {NormalizedSceneNode[]} normalizedNodes
*/ */
normalizedNodes: [], normalizedNodes: [],
mainNodeId: null, mainNodeId: null,
@@ -28,6 +28,11 @@ let AppState = {
sceneHasAudio: false, sceneHasAudio: false,
layersMenuBuilt: false, layersMenuBuilt: false,
initialRotation: null, initialRotation: null,
lightDirection: [],
/**
* @type {Number}
*/
exposure: null,
camera: null, camera: null,
renderer: null, renderer: null,
ambientOcclusion : true, ambientOcclusion : true,

View File

@@ -1,49 +0,0 @@
import AppState from "./state.js";
import { traverseOntology } from "./ontology.js";
/**
* @module UI
*/
/**
*
* @param {String} triggerSelector - Usually, the close modal trigger element(s) selector
*/
export function pauseAudio(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();
});
}
}
/**
* @see traverseOntology
* @param {Object} ontology The traversed ontology object (temp)
* @param {HTMLElement} sidePanel ATON's side panel element
*/
function buildOntologyMenu(ontology, sidePanel) {
const list = document.createElement('ul');
list.className = 'list-group';
const mainNode = document.createElement('li');
mainNode.className = 'list-group-item';
mainNode.textContent = ontology.ontology;
const domainList = document.createElement('ul');
domainList.className = 'list-group';
for(let domain of ontology.domains) {
const domainItem = document.createElement('li');
domainItem.textContent = domain.label;
domainItem.className = 'list-group-item';
domainList.appendChild(domainItem);
}
mainNode.appendChild(domainList);
list.appendChild(mainNode);
sidePanel.appendChild(list);
}

View File

@@ -1,5 +1,7 @@
// Global ATON and THREE // Global ATON and THREE
import AppState from "../state.js";
/** /**
* @module Environment * @module Environment
*/ */
@@ -21,23 +23,28 @@ export function toggleAmbientOcclusion(isEnabled) {
console.log('Ambient occlusion', isEnabled ? 'ON' : 'OFF'); console.log('Ambient occlusion', isEnabled ? 'ON' : 'OFF');
} }
/** /**
* * Slider to change light direction, based on ATON.UI
* @param {String} direction - The axis direction, one of 'x','y','z' * @param {String} direction - The axis direction, one of 'x','y','z'
* @param {String} label - The slider label * @param {String} label - The slider label
* @param {Number[]} range - The slider's range * @param {Array<Number>} range - The slider's range
* @param {Number} step - The slider's step * @param {Number} step - The slider's step
*/ */
export function createLightSlider(direction, label, range, step) { export function createLightSlider(direction, label, range, step) {
const currentVal = ATON.getMainLightDirection()[direction]; const currentVal = AppState.lightDirection[direction];
console.debug(currentVal);
const lightSlider = ATON.UI.createSlider({ const lightSlider = ATON.UI.createSlider({
range, range,
label, label,
value: Number.parseFloat(currentVal).toPrecision(1), value: Number.parseFloat(currentVal).toPrecision(2),
oninput: val => { oninput: val => {
const lightDir = ATON.getMainLightDirection(); const lightDir = AppState.lightDirection;
// Keep existing direction values for the other axes // Keep existing direction values for the other axes
lightDir[direction] = Number.parseFloat(val); lightDir[direction] = Number.parseFloat(val);
changeLightDirection(lightDir); changeLightDirection(lightDir);
AppState.lightDirection = lightDir;
}, },
}); });
@@ -45,4 +52,30 @@ export function createLightSlider(direction, label, range, step) {
lightSlider.querySelector('input').step = step; lightSlider.querySelector('input').step = step;
return lightSlider; return lightSlider;
}
/**
* Slider to change the env exposure level, based on ATON.UI
* @param {String} label - The slider label
* @param {Array<Number>} range - The slider's range
* @param {Number} step - The slider's step
*/
export function createExposureSlider(label, range, step = 0.05) {
const currentVal = AppState.exposure;
const exposureSlider = ATON.UI.createSlider({
range,
label,
value: Number.parseFloat(currentVal).toPrecision(1),
oninput: val => {
ATON.setExposure(val);
AppState.exposure = val;
console.debug('Current exposure:', ATON.getExposure());
},
});
exposureSlider.classList.add('ms-4');
exposureSlider.querySelector('input').step = step;
return exposureSlider;
} }

View File

@@ -99,9 +99,16 @@
<template id="tmpl-settings"> <template id="tmpl-settings">
<div data-controller="settings"> <div data-controller="settings">
<h2 class="fs-5 ms-2 mb-3 mt-3"> <h2 class="fs-5 ms-2 mb-3 mt-3">
<i class="bi bi-lightbulb me-1"></i> Direzione luce <i class="bi bi-lightbulb me-1"></i> Illuminazione
</h2> </h2>
<h3 class="fs-6 ms-4 mb-3 mt-3">
Direzione (x, y, z)
</h3>
<div data-sliders-container></div> <div data-sliders-container></div>
<h3 class="fs-6 ms-4 mb-3 mt-3">
Intensità
</h3>
<div data-slider-exposure-container></div>
<h2 class="fs-5 ms-2 mb-3 mt-3"> <h2 class="fs-5 ms-2 mb-3 mt-3">
<i class="bi bi-brightness-high me-1"></i> Ambiente <i class="bi bi-brightness-high me-1"></i> Ambiente
</h2> </h2>
@@ -127,7 +134,7 @@
</button> </button>
</li> </li>
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link" id="media-tab" data-bs-toggle="tab" data-bs-target="#content" type="button" role="tab" aria-controls="media" aria-selected="true"> <button class="nav-link" id="content-tab" data-bs-toggle="tab" data-bs-target="#content" type="button" role="tab" aria-controls="media" aria-selected="true">
<i class="bi bi-diagram-3 me-1"></i> Contenuti <i class="bi bi-diagram-3 me-1"></i> Contenuti
</button> </button>
</li> </li>
@@ -135,7 +142,12 @@
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content ps-4 ms-3 overflow-y-auto"> <div class="tab-content ps-4 ms-3 overflow-y-auto">
<div class="tab-pane active p-3 ms-2" data-menu-target="layers" id="layer" role="tabpanel" aria-labelledby="layer-tab" tabindex="0"></div> <div class="tab-pane active p-3 ms-2" data-menu-target="layers" id="layer" role="tabpanel" aria-labelledby="layer-tab" tabindex="0"></div>
<div class="tab-pane pt-3" data-menu-target="ontology" id="content" role="tabpanel" aria-labelledby="media-tab" tabindex="0"></div> <div class="tab-pane pt-3" data-menu-target="ontology" id="content" role="tabpanel" aria-labelledby="media-tab" tabindex="0">
<!-- Temporary -->
<ul class="list-group me-4 ms-0">
<li class="list-group-item pt-2 pb-2" id="ontology-list"> </li>
</ul>
</div>
</div> </div>
</template> </template>