A WebGL model viewer. Meant to view MDX and M3 models used by the games Warcraft 3 and Starcraft 2 respectively, but it can be extended.
Running compiler.rb creates a standalone minified JavaScript file that can be included to any HTML page. To minify the GLSL shaders, use glsl-minifier. To minify the resulting JavaScript, use the Google Closure compiler. Both options can be disabled or enabled in compiler.rb.
A live version can be seen on Hiveworkshop for which this viewer was made, here are two examples:
Terminology:
Term | Description |
---|---|
Model | Heavy weight object that contains all the vertices, polygon indices, animation data, skeletal structure, and so on |
ModelInstance | Light weight object that contains a shallow skeleton, particle emitters, animation timers, and so on |
AsyncModel | An object that wraps a model implementation, and takes care of the asynchronous job that is loading files |
AsyncModelInstance | An object that wraps a model instance implementation, and takes care of the asynchronous job that is loading files |
The API can be seen here.
Note that the constructor isn't called with the new
operator.
viewer = ModelViewer(canvas, urls, debugMode)
The urls
argument must have the following functions in it:
Function | Description |
---|---|
header(id) |
Returns the path for a metadata header about a custom resource. More on this in the custom models section |
mpqFile(path) |
Returns a path for a file in any of the Warcraft 3 or Starcraft 2 MPQs |
localFile(path) |
Returns a path for a local file. This should point to a directory with the following images: grass.png , water.png , bedrock.png , and sky.png |
If the client has the requierments to run the viewer, the API will be returned, otherwise, an exception will be thrown, describing the error.
Objects can send events in their life span, very similar to those of native JavaScript objects. Use the EventTarget API to register events:
viewer.addEventListener(type, listener)
viewer.removeEventListener(type, listener)
The type can be one of:
render
- called every frame after rendering is done.loadstart
- an object started loading.progress
- progress updates for loads. The relevant progress properties will be set (loaded, total, lengthComputable).load
- an object finished loading.error
- an error occured when loading an object. In this case, theerror
value will contain a short string that will tell what the error is.loadend
- sent when an object finishes loading, either because of an error, or because it loaded successfully.unload
- an object was unloaded.abort
- an object was aborted before finishing to load. This happens if an object is unloaded before its internal HTTP request finished, in which case it is aborted.
The target property is set to the object that the event is related to. In the case of the render event, this is the viewer itself.
The urls.header stub is used to give information about custom models. Given some form of identifier, it should return an url that will point to a JSON object of the following form:
{
"models": [{"url": "url/to/some/model.mdx", "hidden": true/false}, ...],
"textures": {"path/texture.blp": "real/url/to/texture.blp", ...}
}
The models
object holds objects containing paths to model files, with an optional hidden value. If it is true, the instance created for the model will be hidden by default.
The textures
object holds a map from texture paths used by the models, to custom textures that the custom models might be using. For example, the Warcraft 3 Azura Dragon uses the path "textures/azuredragon.blp" for its main diffuse texture. If the textures
object would contain "textures/azuradragon.blp": "some/other/texture.blp"
, then the path will be overriden for every model in the models
object before they are loaded.
The original texture paths (the keys in the textures
object) must all be lower cased, and with forward slashes (Warcraft 3 uses back slashes).
It is possible to extend the viewer with new model and texture formats, also without editing the library itself. The registerModelHandler, and registerTextureHandler, register a new handler for the given file extension.
The handlers must conform to specific APIs.
The same API as BaseModel.
The same API as BaseModelInstance.
The same API as [Texture](http://htmlpreview.github.io/?https://raw.githubusercontent.com/flowtsohg/mdx-m3-viewer/master/docs/Texture .html).
BaseModel and BaseModelInstance are exported as global objects. They can be extended to ease the creation of making handlers.
An example of setting up a new texture handler:
function MyTexture(arrayBuffer, options, ctx, onerror, onload, compressedTextures) {
this.id = glContext.createTexture();
// Parse the buffer and construct the texture...
this.ready = true; // Signal that this texture was parsed successfully. If it isn't set, the texture wont be used.
onload();
}
// --------
// Register
// --------
myViewer.registerTextureHandler(".myFileType", MyTexture);
An example of setting up a new model handler, using BaseModel and BaseModelInstance:
// ------
// Model
// ------
// data can either be a string, or ArrayBuffer, depending on how you registered this handler.
function MyModel(data, textureMap, context) {
// Sets default values for the default function implementations of BaseModel
BaseModel.call(textureMap);
this.ready = true; // Signal that this modsel was parsed successfully. If it isn't set, the model wont be used.
}
// Extend BaseModel
MyModel.prototype = Object.create(BaseModel.prototype);
// ---------------
// Model Instance
// ---------------
function MyModelInstance(model, textureMap, context) {
// Sets default values for the default function implementations of BaseModelInstance
BaseModelInstance.call(model, textureMap);
}
// Extend BaseModelInstance
MyModelInstance.prototype = Object.create(BaseModel.BaseModelInstance);
// --------
// Register
// --------
var isBinaryFormat = true/false;
myViewer.registerModelHandler(".myFileType", MyModel, MyModelInstance, isBinaryFormat);
The examples folder has an example with partially working OBJ model and BMP texture handlers.