SA Model Viewer base
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { parseDFF } from './rw/dff.js';
|
||||
import { parseTXD } from './rw/txd.js';
|
||||
import { Viewer } from './viewer.js';
|
||||
|
||||
const canvas = document.getElementById('view');
|
||||
const statusEl = document.getElementById('status');
|
||||
|
||||
const viewer = new Viewer(canvas);
|
||||
|
||||
// The page shows nothing but the model, so a failure is the only thing worth
|
||||
// putting on screen. Everything else goes to the console.
|
||||
function setError(text) {
|
||||
statusEl.textContent = text || '';
|
||||
statusEl.style.display = text ? 'block' : 'none';
|
||||
}
|
||||
|
||||
async function fetchBuffer(url) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`${res.status} fetching ${url}`);
|
||||
return res.arrayBuffer();
|
||||
}
|
||||
|
||||
function render(name, dffBuffer, txdBuffer) {
|
||||
const dff = parseDFF(dffBuffer);
|
||||
const txd = txdBuffer ? parseTXD(txdBuffer) : null;
|
||||
const report = viewer.load(dff, txd);
|
||||
|
||||
console.log(
|
||||
`${name}: rw ${dff.versionText}, ${report.meshes} mesh(es), ` +
|
||||
`${report.vertices} verts, ${report.triangles} tris, ` +
|
||||
`${txd ? txd.textures.size : 0} textures`
|
||||
);
|
||||
const warnings = [...dff.errors, ...(txd ? txd.errors : [])];
|
||||
if (report.missingTextures.size) {
|
||||
warnings.push('not in txd: ' + [...report.missingTextures].join(', '));
|
||||
}
|
||||
warnings.forEach((warning) => console.warn(warning));
|
||||
|
||||
setError('');
|
||||
}
|
||||
|
||||
async function loadSkin(id) {
|
||||
try {
|
||||
const res = await fetch(`/api/skin/${encodeURIComponent(id)}`);
|
||||
const meta = await res.json();
|
||||
if (!res.ok) {
|
||||
setError(meta.error || `${res.status} loading ${id}`);
|
||||
return;
|
||||
}
|
||||
const [dffBuffer, txdBuffer] = await Promise.all([
|
||||
fetchBuffer(meta.dff),
|
||||
meta.txd ? fetchBuffer(meta.txd) : Promise.resolve(null),
|
||||
]);
|
||||
render(id, dffBuffer, txdBuffer);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function setupDragAndDrop() {
|
||||
document.body.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
document.body.classList.add('dragging');
|
||||
});
|
||||
document.body.addEventListener('dragleave', () => document.body.classList.remove('dragging'));
|
||||
document.body.addEventListener('drop', async (e) => {
|
||||
e.preventDefault();
|
||||
document.body.classList.remove('dragging');
|
||||
const files = [...e.dataTransfer.files];
|
||||
const dffFile = files.find((f) => f.name.toLowerCase().endsWith('.dff'));
|
||||
const txdFile = files.find((f) => f.name.toLowerCase().endsWith('.txd'));
|
||||
if (!dffFile) {
|
||||
setError('drop a .dff (and its .txd) together');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
render(
|
||||
dffFile.name.replace(/\.dff$/i, ''),
|
||||
await dffFile.arrayBuffer(),
|
||||
txdFile ? await txdFile.arrayBuffer() : null
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setError(err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupDragAndDrop();
|
||||
|
||||
const match = location.pathname.match(/^\/samp\/skin\/(.+)$/);
|
||||
if (match) loadSkin(decodeURIComponent(match[1]));
|
||||
Reference in New Issue
Block a user