feat: integrate Draco compression for GLB models and implement encryption for secure loading

- Added DRACOLoader to starter.js for handling compressed GLB models.
- Created a createGltfLoader function to initialize GLTFLoader with DRACOLoader.
- Implemented dracoModels plugin in vite.config.js to compress GLB models during build.
- Introduced model_loader.js for loading encrypted GLB models with WebCrypto.
- Developed encryptModels plugin to encrypt GLB files, ensuring no clear models are exposed.
- Updated asset handling to remove uncompressed GLB files post-build.
This commit is contained in:
2026-10-28 10:00:09 +01:00
parent f539e4e185
commit 24df94cdc3
9 changed files with 805 additions and 5 deletions
+79
View File
@@ -0,0 +1,79 @@
// NON ATTIVO: per ora nessuno lo importa (vedi vite-plugins/encrypt-models.js).
//
// Carica un modello GLB cifrato in build (vedi vite-plugins/encrypt-models.js): scarica il .bin,
// lo decifra in memoria con WebCrypto e lo passa al GLTFLoader. Nessun .glb in chiaro passa in rete.
//
// WebCrypto richiede un contesto sicuro: https, oppure http://localhost.
/* global __KREIOS_MK__, __KREIOS_MM__ */
let keyPromise = null;
function rawKey() {
const masked = __KREIOS_MK__;
const mask = __KREIOS_MM__;
return Uint8Array.from(masked, (b, i) => b ^ mask[i]);
}
function getKey() {
keyPromise ??= (async () => {
const raw = rawKey();
const cryptoKey = await crypto.subtle.importKey('raw', raw, 'AES-GCM', false, ['decrypt']);
return { raw, cryptoKey };
})();
return keyPromise;
}
async function fileName(raw, relPath) {
const data = new Uint8Array([...raw, ...new TextEncoder().encode(relPath)]);
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', data));
return [...digest].map((b) => b.toString(16).padStart(2, '0')).join('').slice(0, 32) + '.bin';
}
// Scarica con avanzamento, compatibile con il callback di progresso del GLTFLoader ({ loaded, total }).
async function download(url, onProgress) {
const res = await fetch(url);
if (!res.ok) throw new Error(`Modello non disponibile (${res.status})`);
const total = Number(res.headers.get('Content-Length')) || 0;
if (!res.body || !total) {
const buf = await res.arrayBuffer();
onProgress?.({ loaded: buf.byteLength, total: buf.byteLength });
return new Uint8Array(buf);
}
const out = new Uint8Array(total);
const reader = res.body.getReader();
let loaded = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
out.set(value, loaded);
loaded += value.length;
onProgress?.({ loaded, total });
}
return out.subarray(0, loaded);
}
/**
* @param {import('three/examples/jsm/loaders/GLTFLoader').GLTFLoader} loader
* @param {string} relPath percorso sotto assets/models, es. "pvc/finestra_1_anta.glb"
*/
export async function caricaModelloCifrato(loader, relPath, onProgress) {
const { raw, cryptoKey } = await getKey();
const data = await download(`./assets/m/${await fileName(raw, relPath)}`, onProgress);
const glb = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: data.subarray(0, 12), additionalData: new TextEncoder().encode(relPath) },
cryptoKey,
data.subarray(12),
);
const dir = relPath.includes('/') ? relPath.slice(0, relPath.lastIndexOf('/') + 1) : '';
return loader.parseAsync(glb, `./assets/models/${dir}`);
}
/** Stessa firma di GLTFLoader.load, ma su modelli cifrati. */
export function loadModello(loader, relPath, onLoad, onProgress, onError) {
caricaModelloCifrato(loader, relPath, onProgress).then(onLoad, (err) => (onError ? onError(err) : console.error(err)));
}
+10 -3
View File
@@ -2,6 +2,7 @@
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/examples/jsm/loaders/DRACOLoader';
import $ from 'jquery';
import { APP } from './app.js';
import ParamsParser from './params_parser.js';
@@ -20,7 +21,13 @@ import { API_BASE as PREVENTIVI_API_BASE } from './api_base.js';
window.$ = $;
window.THREE = THREE; // Used by APP Scripts.
window.OrbitControls = OrbitControls; // Used by APP Scripts.
window.GLTFLoader = GLTFLoader; // Used by APP Scripts.
// I modelli sono compressi con Draco in build (vite-plugins/draco-models.js). I decoder (versione
// ridotta per glTF) li risolve three via import.meta.url e Vite li include nella build.
const dracoLoader = new DRACOLoader().setDecoderPath(DRACO_GLTF_CONFIG);
function createGltfLoader() {
return new GLTFLoader().setDRACOLoader(dracoLoader);
}
// --- Integrazione backend preventivi ---
const CART_TOKEN_KEY = 'kreios_cart_token';
@@ -239,7 +246,7 @@ const start = async () => {
playerOff.rotateBackground(Math.PI);
playerOff.setSize(playerOff.dom.clientWidth, playerOff.dom.clientHeight);
var gltfLoader = new GLTFLoader();
var gltfLoader = createGltfLoader();
gltfLoader.load(`./assets/models/${params.sistema}/${modello.modello}`, function (gltf) {
document.getElementById('loadingBarContainer').style.display = 'none';
@@ -446,7 +453,7 @@ const start = async () => {
document.getElementById('loadingBarProgressAlt').style.width = '0%';
document.getElementById('loadingPercentageAlt').innerText = '0%';
var gltfLoader = new GLTFLoader();
var gltfLoader = createGltfLoader();
gltfLoader.load(`./assets/models/${modello}`, function (gltf) {
document.getElementById('loadingBarContainerAlt').style.display = 'none';