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;