window.model3d = (function () { const CONTENT_PATH = '/mv/'; let libsLoaded = false; function loadScript(src) { return new Promise((resolve, reject) => { const s = document.createElement('script'); s.src = src; s.onload = resolve; s.onerror = () => reject(new Error('Impossible de charger ' + src)); document.head.appendChild(s); }); } async function ensureLibs() { if (libsLoaded && window.jQuery && window.ZamModelViewer) return; if (!window.jQuery) await loadScript('https://code.jquery.com/jquery-3.7.1.min.js'); if (!window.ZamModelViewer) await loadScript(CONTENT_PATH + 'viewer/viewer.min.js'); if (!window.jQuery) throw new Error('jQuery indisponible'); if (!window.ZamModelViewer) throw new Error('Moteur 3D indisponible'); if (!window.WH) window.WH = {}; window.WH.defaultAnimation = 'Stand'; if (!window.WH.debug) window.WH.debug = function () {}; if (!window.WH.WebP) { window.WH.WebP = { getImageExtension: function () { return '.webp'; } }; } libsLoaded = true; } async function loadCharacterOptions(race, gender) { const id = race * 2 - 1 + gender; const res = await fetch(`${CONTENT_PATH}meta/charactercustomization/${id}.json`); if (!res.ok) return { Options: [] }; return res.json(); } function buildOptions(model, fullOptions) { const map = { 'Face': model.appearance && model.appearance.face, 'Skin Color': model.appearance && model.appearance.skin_color, 'Hair Style': model.appearance && model.appearance.hair_variation, 'Hair Color': model.appearance && model.appearance.hair_color, 'Facial Hair': model.appearance && model.appearance.facial_hair }; const hasAppearance = Object.values(map).some((v) => v !== null && v !== undefined && v !== false); const options = []; if (!hasAppearance) return options; for (const part of fullOptions.Options || []) { const val = map[part.Name]; if (val === null || val === undefined || val === false) continue; const choice = part.Choices && (part.Choices[val] || part.Choices.find((c) => String(c.Id) === String(val))); if (choice) options.push({ optionId: part.Id, choiceId: choice.Id }); } return options; } async function render(container, model) { await ensureLibs(); const $c = jQuery(container); $c.empty(); const viewerOptions = { type: 2, contentPath: CONTENT_PATH, container: $c, aspect: 1, models: { id: model.race * 2 - 1 + model.gender, type: 16 }, items: model.items || [], hd: true }; let viewer = null; try { const options = buildOptions(model, await loadCharacterOptions(model.race, model.gender)); if (options.length) viewerOptions.charCustomization = { options }; viewer = await new window.ZamModelViewer(viewerOptions); } catch (e) { viewer = null; } if (!viewer) { const options = buildOptions(model, { Options: [] }); viewer = await new window.ZamModelViewer(Object.assign({}, viewerOptions, { charCustomization: { options } })); } try { viewer.renderer.distance = 2.1; } catch (e) {} try { const actor = viewer.renderer.actors && viewer.renderer.actors[0]; if (actor && actor.setAnimation) actor.setAnimation('Stand'); } catch (e) {} return viewer; } function dispose(viewer) { try { viewer && viewer.dispose && viewer.dispose(); } catch (e) {} } return { render, dispose, ensureLibs }; })();