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)));
}