Files

101 lines
3.5 KiB
JavaScript

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