101 lines
3.5 KiB
JavaScript
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 };
|
|
})();
|