diff --git a/clients/sanitytest/index.html b/clients/sanitytest/index.html index 33bb3966..86e488df 100644 --- a/clients/sanitytest/index.html +++ b/clients/sanitytest/index.html @@ -14,7 +14,8 @@ font-size: 2ch; } - #canvas { + #canvas, + #controls { position: absolute; left: 0px; top: 0px; @@ -22,6 +23,15 @@ height: 50%; } + #canvas { + z-index: 0; + } + + #controls { + z-index: 1; + pointer-events: none; + } + #sourceview { position: absolute; left: 0px; @@ -126,6 +136,10 @@ user-select: none; pointer-events: none; } + + .inputs { + pointer-events: all; + } @@ -133,6 +147,24 @@
+
+ + + +

     
diff --git a/clients/sanitytest/main.js b/clients/sanitytest/main.js index acdae6db..6dc1d8fb 100644 --- a/clients/sanitytest/main.js +++ b/clients/sanitytest/main.js @@ -1,15 +1,14 @@ ModelViewer = ModelViewer.default; -let math = ModelViewer.common.math; let glMatrix = ModelViewer.common.glMatrix; -let vec2 = glMatrix.vec2; let vec3 = glMatrix.vec3; -let vec4 = glMatrix.vec4; let quat = glMatrix.quat; -let mat3 = glMatrix.mat3; -let mat4 = glMatrix.mat4; let geometry = ModelViewer.common.geometry; let handlers = ModelViewer.viewer.handlers; +let parsers = ModelViewer.parsers; +let mdlx = parsers.mdlx; +let blp = parsers.blp; +let w3x = parsers.w3x; let testsElement = document.getElementById('tests'); @@ -22,7 +21,6 @@ let canvas = document.getElementById('canvas'); let viewer = new ModelViewer.viewer.ModelViewer(canvas); viewer.gl.clearColor(0.7, 0.7, 0.7, 1); -viewer.noCulling = true; viewer.addHandler(handlers.mdx); // Will add BLP too. viewer.addHandler(handlers.geo); @@ -51,9 +49,17 @@ setupCamera(scene, 500); viewer.updateAndRender(); }()); +document.getElementById('animation_toggle').addEventListener('click', () => { + if (viewer.frameTime === 0) { + viewer.frameTime = 1000 / 60; + } else { + viewer.frameTime = 0; + } +}); + let textureModel = viewer.load({ geometry: geometry.createUnitRectangle(), - material: {renderMode: 0, twoSided: true, isBGR: true} + material: {renderMode: 0, twoSided: true, isBGR: false} }, src => [src, '.geo', false]); function nodeName(node) { @@ -135,125 +141,160 @@ function handleTestNode(stream, node) { } } -function renderModelTest(name, model) { - let stream = new LogStream(document.createElement('div')); - let result = ModelViewer.utils.mdxSanityTest(model); - let header = stream.start(); - let body = null; +/** + * + */ +class TestInstance { + /** + * @param {string} name + * @param {Model|Texture} resource + * @param {ModelInstance} instance + * @param {mdlx.Model|blp.Texture} parser + */ + constructor(name, resource, instance, parser) { + this.name = name.toLowerCase(); + this.resource = resource; + this.instance = instance; + this.parser = parser; + this.rendered = false; + this.container = null; + this.header = null; + this.body = null; + this.sourceMapContainer = null; - stream.info(`${name}: `); + if (instance.model.extension === '.mdx') { + this.type = 'model'; - if (result.errors || result.warnings || result.unused) { - let added = false; + this.renderModelTest(name, parser); + } else { + this.type = 'texture'; - if (result.errors) { - stream.error(`${result.errors} error${result.errors === 1 ? '' : 's'}`); - added = true; + this.renderTextureTest(name, parser); } - if (result.warnings) { - if (added) { - stream.info(', '); - } + this.hide(); + } - stream.warn(`${result.warnings} warning${result.warnings === 1 ? '' : 's'}`); + show() { + this.rendered = true; + this.header.classList.remove('closed'); + this.header.classList.add('opened'); - added = true; + if (this.body) { + this.body.style.display = 'initial'; } - if (result.unused) { - if (added) { - stream.info(', '); - } + this.instance.show(); + } - stream.unused(`${result.unused} unused`); + hide() { + this.rendered = false; + this.header.classList.remove('opened'); + this.header.classList.add('closed'); + + if (this.body) { + this.body.style.display = 'none'; } - stream.commit(); + this.instance.hide(); + } - body = stream.start(); - stream.indent(); + getModel() { + return this.instance.model; + } - for (let node of result.nodes) { - handleTestNode(stream, node); - } + getTexture() { + return this.instance.modelView.textures.get(null); + } - stream.commit(); + renderModelTest(name, model) { + let stream = new LogStream(document.createElement('div')); + let result = ModelViewer.utils.mdxSanityTest(model); + let header = stream.start(); + let body = null; - } else { - stream.log('Passed'); + stream.info(`${name}: `); - stream.commit(); - } + if (result.errors || result.warnings || result.unused) { + let added = false; - return {container: stream.container, header, body}; -} + if (result.errors) { + stream.error(`${result.errors} error${result.errors === 1 ? '' : 's'}`); + added = true; + } -function renderTextureTest(name, texture) { - let stream = new LogStream(document.createElement('div')); - let results = ModelViewer.utils.blpSanityTest(texture); - let header = stream.start(); - let body = null; + if (result.warnings) { + if (added) { + stream.info(', '); + } - stream.info(`${name}: `); + stream.warn(`${result.warnings} warning${result.warnings === 1 ? '' : 's'}`); - if (results.length) { - stream.warn(`${results.length} warning${results.length === 1 ? '' : 's'}`) + added = true; + } - stream.commit(); + if (result.unused) { + if (added) { + stream.info(', '); + } - body = stream.start(); - stream.indent(); + stream.unused(`${result.unused} unused`); + } - for (let result of results) { - stream.warn(result); - stream.br(); - } + stream.commit(); - stream.commit(); - } else { - stream.log('Passed'); + body = stream.start(); + stream.indent(); - stream.commit(); - } + for (let node of result.nodes) { + handleTestNode(stream, node); + } - return {container: stream.container, header, body}; -} + stream.commit(); -class TestInstance { - constructor(container, header, body, instance, sourceMapContainer) { - this.container = container; + } else { + stream.log('Passed'); + + stream.commit(); + } + + this.container = stream.container; this.header = header; this.body = body; - this.instance = instance; - this.rendered = false; - this.sourceMapContainer = sourceMapContainer || null; - - this.hide(); + this.sourceMapContainer = handleSourceMap(model.saveMdl()); } - show() { - this.rendered = true; - this.header.classList.remove('closed'); - this.header.classList.add('opened'); + renderTextureTest(name, texture) { + let stream = new LogStream(document.createElement('div')); + let results = ModelViewer.utils.blpSanityTest(texture); + let header = stream.start(); + let body = null; - if (this.body) { - this.body.style.display = 'initial'; - } + stream.info(`${name}: `); - this.instance.show(); - } + if (results.length) { + stream.warn(`${results.length} warning${results.length === 1 ? '' : 's'}`) - hide() { - this.rendered = false; - this.header.classList.remove('opened'); - this.header.classList.add('closed'); + stream.commit(); - if (this.body) { - this.body.style.display = 'none'; + body = stream.start(); + stream.indent(); + + for (let result of results) { + stream.warn(result); + stream.br(); + } + + stream.commit(); + } else { + stream.log('Passed'); + + stream.commit(); } - this.instance.hide(); + this.container = stream.container; + this.header = header; + this.body = body; } } @@ -282,20 +323,20 @@ function showTest(test) { } } -function addTest(container, header, body, instance, sourceMapContainer) { - container.style.paddingBottom = '3px'; +function addTest(name, resource, instance, parser) { + let test = new TestInstance(name, resource, instance, parser); - testsElement.appendChild(container); + test.container.style.paddingBottom = '3px'; - let test = new TestInstance(container, header, body, instance, sourceMapContainer); + testsElement.appendChild(test.container); - header.addEventListener('click', (e) => { + test.header.addEventListener('click', (e) => { if (!test.rendered) { - header.classList.remove('closed'); - header.classList.add('opened'); + test.header.classList.remove('closed'); + test.header.classList.add('opened'); - if (body) { - body.style.display = 'initial'; + if (test.body) { + test.body.style.display = 'initial'; } showTest(test); @@ -303,17 +344,19 @@ function addTest(container, header, body, instance, sourceMapContainer) { }); allTests.push(test); -} -function addModelTest(name, ext, buffer, pathSolver) { - let model = new ModelViewer.parsers.mdlx.Model(); + showTest(test); - model.load(buffer); + statusElement.textContent = ''; + + return test; +} - let {container, header, body} = renderModelTest(name, model); +function addModelTest(name, ext, buffer, pathSolver) { + let parser = new mdlx.Model(buffer); - let viewerModel = viewer.load(buffer, (src) => { - if (src === buffer) { + let viewerModel = viewer.load(parser, (src) => { + if (src === parser) { return [src, ext, false] } else if (pathSolver) { // If an external path solver is given, this is a Hive resource, and it will handle custom textures. @@ -323,33 +366,100 @@ function addModelTest(name, ext, buffer, pathSolver) { } }); - let instance = viewerModel.addInstance(); + viewerModel.whenLoaded().then(() => { + let instance = viewerModel.addInstance(); - instance.setScene(scene); + instance.setScene(scene); - instance.setSequence(0); + instance.setSequence(0); + instance.setSequenceLoopMode(2); - instance.on('seqend', () => { - let sequence = instance.sequence + 1; + instance.on('seqend', () => { + let sequence = instance.sequence + 1; - if (sequence === model.sequences.length) { - sequence = 0; - } + if (sequence === viewerModel.sequences.length) { + sequence = 0; + } - instance.setSequence(sequence); + instance.setSequence(sequence); + }); + + let test = addTest(name, viewerModel, instance, parser); + + tryToInjectCustomTextures(test); }); +} - addTest(container, header, body, instance, handleSourceMap(model.saveMdl())); +function getPathFileName(path) { + const url = path.replace(/\\/g, '/'); + + return url.slice(url.lastIndexOf('/') + 1).toLowerCase(); } -function addTextureTest(name, ext, buffer) { - let texture = new ModelViewer.parsers.blp.Texture(); +function areSameFiles(a, b) { + return getPathFileName(a) === getPathFileName(b); +} - texture.load(buffer); +function* eachModelTest() { + for (let test of allTests) { + if (test.type === 'model') { + yield test; + } + } +} + +function* eachTextureTest() { + for (let test of allTests) { + if (test.type === 'texture') { + yield test; + } + } +} + +// Given a new custom model, go over all of the textures, and see if any match any of the model's textures. +// Matches are replaced with the matched textures. +function tryToInjectCustomTextures(modelTest) { + let model = modelTest.getModel(); + let parser = modelTest.parser; + + for (let textureTest of eachTextureTest()) { + for (let i = 0, l = model.textures.length; i < l; i++) { + const texture = model.textures[i]; + + texture.whenLoaded().then(() => { + if (!texture.ok && areSameFiles(parser.textures[i].path, textureTest.name)) { + model.textures[i] = textureTest.getTexture(); + + console.log(`NOTE: loaded ${textureTest.name} as a custom texture for model: ${modelTest.name}`); + } + }); + } + } +} + +// Given a new texture test, go over all of the models, and see if it can match one of their textures. +// Matches are replaced with this texture. +function tryToLoadCustomTexture(textureTest) { + for (let modelTest of eachModelTest()) { + const model = modelTest.getModel(); + + for (let i = 0, l = model.textures.length; i < l; i++) { + const modelTexture = model.textures[i]; - let {container, header, body} = renderTextureTest(name, texture); + // If the texture failed to load, check if it matches the name. + if (!modelTexture.ok && areSameFiles(modelTexture.fetchUrl, textureTest.name)) { + model.textures[i] = textureTest.getTexture(); - let viewerTexture = viewer.load(buffer, (src) => { + console.log(`NOTE: loaded ${textureTest.name} as a custom texture for model: ${modelTest.name}`); + } + } + } +} + +function addTextureTest(name, ext, buffer) { + let parser = new blp.Texture(buffer); + + let viewerTexture = viewer.load(parser, (src) => { return [src, ext, false] }); @@ -363,13 +473,16 @@ function addTextureTest(name, ext, buffer) { .then(() => { // Don't really care about the size of the texture instance, just the proportions. instance.scale([viewerTexture.width / viewerTexture.height, 1, 1]); - }); - addTest(container, header, body, instance); + let test = addTest(name, viewerTexture, instance, parser); + + // Try to load this texture as a custom texture, in case a model that uses it was loaded before. + tryToLoadCustomTexture(test); + }); } function addMapTest(buffer) { - let map = new ModelViewer.parsers.w3x.Map(); + let map = new w3x.Map(); map.load(buffer); @@ -394,16 +507,6 @@ function onLocalFileLoaded(name, ext, buffer, pathSolver) { addTextureTest(name, ext, buffer); } else if (ext === '.w3x' || ext === '.w3m') { addMapTest(buffer); - } else { - return; - } - - if (allTests.length) { - statusElement.textContent = ''; - - showTest(allTests[allTests.length - 1]) - } else { - statusElement.textContent = `Nothing to check for ${name}!`; } } diff --git a/package.json b/package.json index 3aa5c94e..cd9b231b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mdx-m3-viewer", - "version": "4.7.6", + "version": "4.7.7", "description": "A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.", "main": "src/index.js", "directories": { diff --git a/src/index.js b/src/index.js index a0634e1e..334f2925 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,7 @@ import viewer from './viewer'; import utils from './utils'; export default { - version: '4.7.6', + version: '4.7.7', common, parsers, viewer, diff --git a/src/parsers/blp/texture.js b/src/parsers/blp/texture.js index 66e692ff..2728b530 100644 --- a/src/parsers/blp/texture.js +++ b/src/parsers/blp/texture.js @@ -11,9 +11,9 @@ let CONTENT_JPG = 0x0; */ export default class BlpTexture { /** - * + * @param {?ArrayBuffer} buffer */ - constructor() { + constructor(buffer) { /** @member {number} */ this.content = 0; /** @member {number} */ @@ -44,6 +44,10 @@ export default class BlpTexture { * @member {?Uint8Array} */ this.pallete = null; + + if (buffer) { + this.load(buffer); + } } /** diff --git a/src/parsers/w3x/imp/file.js b/src/parsers/w3x/imp/file.js index ed0b58a0..da30ef9d 100644 --- a/src/parsers/w3x/imp/file.js +++ b/src/parsers/w3x/imp/file.js @@ -6,13 +6,17 @@ import Import from './import'; */ export default class War3MapImp { /** - * + * @param {?ArrayBuffer} buffer */ - constructor() { + constructor(buffer) { /** @member {number} */ this.version = 1; /** @member {Map} */ this.entries = new Map(); + + if (buffer) { + this.load(buffer); + } } /** diff --git a/src/parsers/w3x/w3e/file.js b/src/parsers/w3x/w3e/file.js index 87290719..f8ee9ada 100644 --- a/src/parsers/w3x/w3e/file.js +++ b/src/parsers/w3x/w3e/file.js @@ -26,7 +26,7 @@ export default class War3MapW3e { /** @member {Array>} */ this.corners = []; - if (buffer instanceof ArrayBuffer) { + if (buffer) { this.load(buffer); } } diff --git a/src/parsers/w3x/w3f/file.js b/src/parsers/w3x/w3f/file.js index f53af436..37d3ce14 100644 --- a/src/parsers/w3x/w3f/file.js +++ b/src/parsers/w3x/w3f/file.js @@ -7,9 +7,9 @@ import MapOrder from './maporder'; */ export default class War3CampaignW3f { /** - * + * @param {?ArrayBuffer} buffer */ - constructor() { + constructor(buffer) { this.version = 0; this.campaignVersion = 0; this.editorVersion = 0; @@ -31,6 +31,10 @@ export default class War3CampaignW3f { this.userInterface = -1; // 0 = human this.mapTitles = []; this.mapOrders = []; + + if (buffer) { + this.load(buffer); + } } /** diff --git a/src/viewer/handlers/blp/texture.js b/src/viewer/handlers/blp/texture.js index a43fead2..36e55a3e 100644 --- a/src/viewer/handlers/blp/texture.js +++ b/src/viewer/handlers/blp/texture.js @@ -6,12 +6,17 @@ import Texture from '../../texture'; */ export default class BlpTexture extends Texture { /** - * @param {ArrayBuffer} src + * @param {ArrayBuffer|Parser} bufferOrParser * @param {?Object} options */ - load(src, options) { - let parser = new Parser(); - parser.load(src); + load(bufferOrParser, options) { + let parser; + + if (bufferOrParser instanceof Parser) { + parser = bufferOrParser; + } else { + parser = new Parser(bufferOrParser); + } let viewer = this.viewer; let gl = viewer.gl; @@ -58,7 +63,6 @@ export default class BlpTexture extends Texture { this.imageData = imageData; this.width = imageData.width; this.height = imageData.height; - this.parser = parser; this.webglResource = id; } } diff --git a/src/viewer/handlers/m3/model.js b/src/viewer/handlers/m3/model.js index 5b63739d..01610b98 100644 --- a/src/viewer/handlers/m3/model.js +++ b/src/viewer/handlers/m3/model.js @@ -1,4 +1,4 @@ -import M3Parser from '../../../parsers/m3/model'; +import Parser from '../../../parsers/m3/model'; import TexturedModel from '../../texturedmodel'; import M3StandardMaterial from './standardmaterial'; import M3Bone from './bone'; @@ -37,14 +37,20 @@ export default class M3Model extends TexturedModel { } /** - * @param {ArrayBuffer} buffer + * @param {ArrayBuffer|Parser} bufferOrParser */ - load(buffer) { - let parser = new M3Parser(buffer); + load(bufferOrParser) { + let parser; + + if (bufferOrParser instanceof Parser) { + parser = bufferOrParser; + } else { + parser = new Parser(bufferOrParser); + } + let model = parser.model; let div = model.divisions.get(); - this.parser = parser; this.name = model.modelName.getAll().join(''); this.setupGeometry(model, div); diff --git a/src/viewer/handlers/mdx/model.js b/src/viewer/handlers/mdx/model.js index 6a3093a4..44b9d7bf 100644 --- a/src/viewer/handlers/mdx/model.js +++ b/src/viewer/handlers/mdx/model.js @@ -63,15 +63,16 @@ export default class Model extends TexturedModel { } /** - * @param {ArrayBuffer|string} buffer + * @param {ArrayBuffer|string|Parser} bufferOrParser */ - load(buffer) { - // Parsing - let parser = new Parser(); + load(bufferOrParser) { + let parser; - parser.load(buffer); - - this.parser = parser; + if (bufferOrParser instanceof Parser) { + parser = bufferOrParser; + } else { + parser = new Parser(bufferOrParser); + } // Model this.name = parser.name;