Compare commits

...

3 Commits

Author SHA1 Message Date
2df5a71241 WIP: first reworking based on requirements
NOTE: node loading logic not working yet!
2026-02-20 17:25:10 +01:00
32424e7b0b Tabs in content menu 2026-01-18 15:59:24 +01:00
9c29894241 Fix config error 2026-01-17 15:41:30 +01:00
8 changed files with 173 additions and 125 deletions

BIN
assets/pano/gradient.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

210
config.js
View File

@@ -14,19 +14,13 @@ const theater2Popup = `
</div> </div>
`; `;
const audioBtn1 = `
<button type="button" class="text-left btn aton-btn fs-6 mx-2" data-bs-toggle="modal" data-bs-target="#audio1">
<i class="bi bi-play-btn me-2"></i> Esempio audio (<em>Che fiero costume</em>)
</button>
`;
export const config = { export const config = {
scene : { scene : {
initialExposure: 0.7, initialExposure: 0.7,
autoLP: true, autoLP: false,
}, },
menu : { menu : {
audioBtn1 //audioBtn1
}, },
markers : [ markers : [
{ {
@@ -35,7 +29,13 @@ export const config = {
uri : `${BASE_URI}/scenes/salvador/`, uri : `${BASE_URI}/scenes/salvador/`,
popup: theater1Popup, popup: theater1Popup,
coords: [45.4363, 12.3352], coords: [45.4363, 12.3352],
model: "teatro_san_salvador_20250926.gltf", nodes: [
{
label: 'Teatro',
model: "teatro_san_salvador_20250926.gltf",
isMain: true,
},
],
pano: `pano/defsky-grass.jpg`, pano: `pano/defsky-grass.jpg`,
}, },
{ {
@@ -44,89 +44,115 @@ export const config = {
uri : `${BASE_URI}/scenes/ssgp/`, uri : `${BASE_URI}/scenes/ssgp/`,
popup: theater2Popup, popup: theater2Popup,
coords: [45.4401, 12.3408], coords: [45.4401, 12.3408],
nodes: [ nodes: {
/* groups: [
{ {
label: 'Struttura principale', label: 'Teatro',
model: 'models/ssgp/Teatro_SSGP_Full_ConSottrazioni.glb', layers: [
isMain: true, {
}, label: 'Struttura complessiva',
*/ 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: 'Involucro',
}, model: 'models/ssgp/Teatro_SSGP_Layer_Struttura_parete_di_fondo.glb',
{ isMain: true,
label: 'Ballatoio', opacity: 0.2,
model: 'models/ssgp/Teatro_SSGP_Ballatoio.glb', },
}, ]
{ },
label: 'Boccascena', {
model: 'models/ssgp/Teatro_SSGP_Boccascena.glb', label: 'Sala / Auditorium',
}, layers: [
{ {
label: 'Fossa orchestra', label: 'Peplano / Platea',
model: 'models/ssgp/Teatro_SSGP_Fossa_orchestra.glb', model: 'models/ssgp/Teatro_SSGP_Platea_peplano.glb',
}, },
{ {
label: 'Graticcia', label: 'Ordine 1',
model: 'models/ssgp/Teatro_SSGP_Graticcia.glb', model: 'models/ssgp/Teatro_SSGP_Ordine1.glb',
}, },
{ {
label: 'Ordine 1', label: 'Ordine 2',
model: 'models/ssgp/Teatro_SSGP_Ordine1.glb', model: 'models/ssgp/Teatro_SSGP_Ordine2.glb',
}, },
{ {
label: 'Ordine 2', label: 'Ordine 3',
model: 'models/ssgp/Teatro_SSGP_Ordine2.glb', model: 'models/ssgp/Teatro_SSGP_Ordine3.glb',
}, },
{ {
label: 'Ordine 3', label: 'Ordine 4',
model: 'models/ssgp/Teatro_SSGP_Ordine3.glb', model: 'models/ssgp/Teatro_SSGP_Ordine4.glb',
}, },
{ {
label: 'Ordine 4', label: 'Ordine 5',
model: 'models/ssgp/Teatro_SSGP_Ordine4.glb', model: 'models/ssgp/Teatro_SSGP_Ordine5.glb',
}, },
{ {
label: 'Ordine 5', label: 'Parapetto',
model: 'models/ssgp/Teatro_SSGP_Ordine5.glb', model: 'models/ssgp/Teatro_SSGP_parapetto_scala_piani.glb',
}, },
{ {
label: 'Palcoscenico', label: 'Percorsi pubblico',
model: 'models/ssgp/Teatro_SSGP_Palcoscenico.glb', model: 'models/ssgp/Teatro_SSGP_Percorsi_scale_corridoi.glb',
}, },
{ ]
label: 'Parapetto scala piani', },
model: 'models/ssgp/Teatro_SSGP_parapetto_scala_piani.glb', {
}, label: 'Scena',
{ layers: [
label: 'Percorsi scale corridoi', {
model: 'models/ssgp/Teatro_SSGP_Percorsi_scale_corridoi.glb', label: 'Palcoscenico',
}, model: 'models/ssgp/Teatro_SSGP_Palcoscenico.glb',
{ },
label: 'Platea peplano', {
model: 'models/ssgp/Teatro_SSGP_Platea_peplano.glb', label: 'Boccascena / Proscenio',
}, model: 'models/ssgp/Teatro_SSGP_Boccascena.glb',
{ },
label: 'Quinte architettoniche fisse', {
model: 'models/ssgp/Teatro_SSGP_Layer_quinte_architettoniche_fisse.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: '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: 'Spazi tecnici',
label: 'Spazio tecnico inferiore', layers: [
model: 'models/ssgp/Teatro_SSGP_Spazio_tecnico_inf.glb', {
}, label: 'Spazio tecnico superiore',
], model: 'models/ssgp/Teatro_SSGP_Layer_Spazio_tecnico_sup_soffitta.glb',
pano: `pano/defsky-grass.jpg`, },
{
label: 'Graticcia',
model: 'models/ssgp/Teatro_SSGP_Graticcia.glb',
},
{
label: 'Ballatoio',
model: 'models/ssgp/Teatro_SSGP_Ballatoio.glb',
},
{
label: 'Spazio tecnico inferiore',
model: 'models/ssgp/Teatro_SSGP_Spazio_tecnico_inf.glb',
},
]
},
{
label: 'Orchestra',
layers: [
{
label: 'Fossa orchestra',
model: 'models/ssgp/Teatro_SSGP_Fossa_orchestra.glb',
},
]
}
],
},
pano: `pano/gradient.jpg`,
} }
], ],
map : { map : {

View File

@@ -62,7 +62,7 @@ Scene.createClippingPlaneMesh = function (boundingSphere) {
const planeSize = boundingSphere.radius * 1.5; const planeSize = boundingSphere.radius * 1.5;
const mesh = new THREE.Mesh( const mesh = new THREE.Mesh(
new THREE.PlaneGeometry(planeSize, planeSize), new THREE.PlaneGeometry(planeSize, planeSize),
new THREE.MeshBasicMaterial({ color: 0xffff00, opacity: 0.1, side: THREE.DoubleSide, transparent: true }) new THREE.MeshBasicMaterial({ color: 0xffff00, opacity: 0.05, side: THREE.DoubleSide, transparent: true })
); );
return mesh; return mesh;
@@ -261,6 +261,12 @@ Scene.loadNodes = function (nodes) {
let node = ATON.createSceneNode(n.label); let node = ATON.createSceneNode(n.label);
node.load(n.model); node.load(n.model);
node.setRotation(0, 1.5, 0); node.setRotation(0, 1.5, 0);
// Apply any transparency before attaching to scene
if (n.opacity) {
node.setMaterial(new THREE.MeshPhongMaterial({transparent: true, opacity: n.opacity, color: '#fff'}));
}
node.attachToRoot(); node.attachToRoot();
if (n.isMain) { if (n.isMain) {
@@ -269,6 +275,7 @@ Scene.loadNodes = function (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: true});
}); });
} }

View File

@@ -2,12 +2,15 @@
* @namespace AppState * @namespace AppState
*/ */
let AppState = { let AppState = {
// The Leaflet map object
map : null,
// The root scene object // The root scene object
root: null, root: null,
nodeIds: [],
// {id: String, active: Boolean} // {id: String, active: Boolean}
nodes: [], nodes: [],
mainNodeId: null, mainNodeId: null,
currentScene: null,
sceneHasAudio: false,
layersMenuBuilt: false, layersMenuBuilt: false,
initialRotation: null, initialRotation: null,
camera: null, camera: null,
@@ -15,7 +18,6 @@ let AppState = {
ambientOcclusion : true, ambientOcclusion : true,
shadows : true, shadows : true,
lightProbe : false, lightProbe : false,
map : null,
clipping : { clipping : {
enabled: false, enabled: false,
plane : null, plane : null,

View File

@@ -7,6 +7,35 @@ import AppState from "./state.js";
const UI = {}; const UI = {};
UI.domParser = new DOMParser; UI.domParser = new DOMParser;
UI.contentMenuTabs = `
<!-- Nav tabs -->
<ul class="nav nav-pills" id="content-tabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="layer-tab" data-bs-toggle="tab" data-bs-target="#layer" type="button" role="tab" aria-controls="layer" aria-selected="false">
<i class="bi bi-boxes me-1"></i> Elementi 3D
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="media-tab" data-bs-toggle="tab" data-bs-target="#media" type="button" role="tab" aria-controls="media" aria-selected="true">
<i class="bi bi-diagram-3 me-1"></i> Contenuti
</button>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane active p-3" id="layer" role="tabpanel" aria-labelledby="layer-tab" tabindex="0"></div>
<div class="tab-pane p-3" id="media" role="tabpanel" aria-labelledby="media-tab" tabindex="0"></div>
</div>
`;
UI.audioExample = `
<button type="button" id="audio-example" class="text-left btn aton-btn fs-6 mx-2" data-bs-toggle="modal" data-bs-target="#audio1">
<i class="bi bi-play-btn me-2"></i> Esempio audio (<em>Che fiero costume</em>)
</button>
`;
/** /**
* *
* @param {String} triggerSelector - Usually, the close modal trigger element(s) selector * @param {String} triggerSelector - Usually, the close modal trigger element(s) selector
@@ -137,20 +166,11 @@ UI.toggleSettingsPanel = function(triggerId) {
<label class="form-check-label" for="shadowsSwitch">Ombre <i class="bi bi-info-circle ms-2 c-hand" title=""></i></label> <label class="form-check-label" for="shadowsSwitch">Ombre <i class="bi bi-info-circle ms-2 c-hand" title=""></i></label>
`; `;
const lightProbeSwitch = document.createElement('div');
lightProbeSwitch.className = 'form-check form-switch ms-4 mt-2';
lightProbeSwitch.innerHTML = `
<input class="form-check-input" type="checkbox" role="switch" id="lpSwitch" title="Abilita / disabilita mappa HDRi">
<label class="form-check-label" for="lpSwitch">Mappa HDRi <em>(light probe)</em> <i class="bi bi-info-circle ms-2 c-hand"></i></label>
`;
shadowsSwitch.querySelector('input[type="checkbox"').checked = AppState.shadows; shadowsSwitch.querySelector('input[type="checkbox"').checked = AppState.shadows;
ambientOcclSwitch.querySelector('input[type="checkbox"').checked = AppState.ambientOcclusion; 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(ambientOcclSwitch);
ATON.UI.elSidePanel.appendChild(shadowsSwitch); ATON.UI.elSidePanel.appendChild(shadowsSwitch);
ATON.UI.elSidePanel.appendChild(lightProbeSwitch);
// TODO: move somewhere else... // TODO: move somewhere else...
document.querySelector('#aoSwitch').addEventListener( document.querySelector('#aoSwitch').addEventListener(
@@ -168,18 +188,6 @@ UI.toggleSettingsPanel = function(triggerId) {
AppState.shadows = 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);
}
);
}); });
} }
/** /**
@@ -214,15 +222,15 @@ UI.createLightSlider = function(direction, label, range, step) {
*/ */
UI.toggleContentMenu = function(triggerId) { UI.toggleContentMenu = function(triggerId) {
const btn = document.querySelector(`#${triggerId}`); const btn = document.querySelector(`#${triggerId}`);
let audio1 = this.domParser.parseFromString(config.menu.audioBtn1, 'text/html');
audio1 = audio1.querySelector('button');
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
ATON.UI.setSidePanelRight(); ATON.UI.setSidePanelRight();
ATON.UI.showSidePanel({header: '<i class="bi bi-music-note-list me-1"></i> Contenuti'}); ATON.UI.showSidePanel({header: 'Menu'});
ATON.UI.elSidePanel.appendChild(audio1); // Append tabs, then tab panes
ATON.UI.elSidePanel.appendChild(this.domParser.parseFromString('<h5 class="ms-3">Layer</h5>', 'text/html').querySelector('h5')); const tabs = this.domParser.parseFromString(UI.contentMenuTabs, 'text/html');
this.buildLayersMenu(AppState.nodes, ATON.UI.elSidePanel); ATON.UI.elSidePanel.appendChild(tabs.querySelector('#content-tabs'));
ATON.UI.elSidePanel.appendChild(tabs.querySelector('.tab-content'));
this.buildLayersMenu(AppState.nodes, ATON.UI.elSidePanel.querySelector('#layer'));
}); });
} }
/** /**

View File

@@ -79,7 +79,6 @@
</a> </a>
</div> </div>
<!-- TODO CSS-only popover --> <!-- TODO CSS-only popover -->
<div class="card d-none" id="shadows-popover" popover>Disabilitare le ombre può migliorare le prestazioni</div> <div class="card d-none" id="shadows-popover" popover>Disabilitare le ombre può migliorare le prestazioni</div>

View File

@@ -1,5 +1,9 @@
import Scene from "../../js/scene.js"; import Scene from "../../js/scene.js";
import { config } from "../../config.js"; import { config } from "../../config.js";
import AppState from "../../js/state.js";
AppState.currentScene = 'salvador';
AppState.sceneHasAudio = true;
Scene.openScene(config.markers.find(m => m.id === 'salvador')); Scene.openScene(config.markers.find(m => m.id === 'salvador'));
Scene.UI.pauseAudio('[data-bs-dismiss="modal"]'); Scene.UI.pauseAudio('[data-bs-dismiss="modal"]');

View File

@@ -1,4 +1,6 @@
import Scene from "../../js/scene.js"; import Scene from "../../js/scene.js";
import { config } from "../../config.js"; import { config } from "../../config.js";
import AppState from "../../js/state.js";
AppState.currentScene = 'ssgp';
Scene.openScene(config.markers.find(m => m.id === 'ssgp')); Scene.openScene(config.markers.find(m => m.id === 'ssgp'));