UNPKG

12.5 kBMarkdownView Raw
1**NO LONGER ACTIVELY MAINTAINED.**
2
3mdx-m3-viewer
4=============
5
6At the core of it, a 3D model viewer for MDX and M3 models used by the games Warcraft 3 and Starcraft 2 respectively.
7
8The viewer part handles the following formats:
9* MDX (Warcraft 3 model): extensive support, almost everything should work.
10* M3 (Starcraft 2 model): partial support.
11* W3M/W3X (Warcraft 3 map): partial support.
12* BLP1 (Warcraft 3 texture): extensive support, almost everything should work.
13* TGA (image): partial support, only simple 24bit images.
14* DDS (compressed texture): partial support - DXT1/DXT3/DXT5/RGTC.
15* PNG/JPG/GIF/WebP: supported by the browser.
16
17There are file parsers that the viewer depends on.\
18These don't rely on the viewer or indeed on even running in a browser.\
19They include:
20* MDX/MDL: read/write.
21* M3: read.
22* BLP1: read.
23* INI: read.
24* SLK: read.
25* MPQ1: read/write.
26* W3M/W3X/W3N: read/write, including all of the internal files.
27* DDS: read (DXT1/DXT3/DXT5/RGTC).
28
29There are all sorts of utilities that were made over the years.\
30These include things like...
31* The library's unit tester, which compares rendered results against stored images that were generated in the same way.
32* The MDX sanity test, which looks for errors and weird things in MDX models.
33* A Jass context that can...well, run Jass code. That being said, it really runs Lua code converted from Jass, on a JS Lua VM. What a tongue twiser. While it supports some Warcraft 3 natives, don't expect it run whole maps. Maybe in the future 😉
34* A utility that makes it possible to open Warcraft 3 maps in the vanilla World Editor, in cases were said maps used a non-vanilla editor with extended GUI, in which case they crash upon opening in the official editor.
35* etc.
36
37Finally, the library also comes with a bunch of clients.\
38A "client" in this context means external code that uses the library.\
39Most of these clients are simple and messy, since they were made as side projects while working on the library.\
40Most of these clients are also just wrappers around the viewer and the utilities, merely giving them an interface on a web page.\
41These include things like...
42* A simple example client.
43* The unit tester's page, which allows to run the unit tests, and to download the results.
44* The MDX sanity test's page, which visually shows the results of sanity tests, and other nifty things.
45* etc.
46
47------------------------
48
49#### Building
50
51```
52npm install mdx-m3-viewer
53npm run build
54```
55This will generate `dist/viewer.min.js`.
56
57If you are using Typescript, you can also import anything in the library directly.\
58For example, if you only care about an MDX viewer, you could do the following:
59```javascript
60import ModelViewer from 'mdx-m3-viewer/src/viewer/viewer';
61import mdxHandler from 'mdx-m3-viewer/src/viewer/handlers/mdx/handler';
62```
63This way, you don't import any parsers/handlers/utilities that are not needed.
64
65------------------------
66
67#### Getting started
68
691. Run the given demo HTTP server `npm run serve`.
702. In your browser, open `http://localhost/clients/example`.
713. You can also check the other more advanced clients.
72
73------------------------
74
75#### Usage
76
77You can import the viewer in different ways:
78```javascript
79// webpack export in the browser.
80new ModelViewer.default.viewer.ModelViewer(canvas);
81
82// require/import the library.
83const ModelViewer = require('mdx-m3-viewer'); // CommonJS.
84import ModelViewer from 'mdx-m3-viewer'; // ES6.
85new ModelViewer.viewer.ModelViewer(canvas);
86
87// require/import something directly.
88const ModelViewer = require('mdx-m3-viewer/src/viewer/viewer'); // CommonJS.
89import ModelViewer from 'mdx-m3-viewer/src/viewer/viewer'; // ES6.
90new ModelViewer(canvas);
91
92```
93
94All code snippets will use the names as if you imported them directly to avoid some mess. See the examples for actual namespacing.
95
96First, let's create the viewer:
97```javascript
98let canvas = ...; // A <canvas> aka HTMLCanvasElement object.
99
100let viewer = new ModelViewer(canvas);
101```
102
103If the client doesn't have the WebGL requierments to run the viewer, an exception will be thrown when trying to create it.
104
105When a new viewer instance is created, it doesn't yet support loading anything, since it has no handlers.\
106Handlers are simple JS objects with a specific signature, that give information to the viewer (such as a file format(s), and the implementation objects).\
107When you want to load something, the viewer will select the appropriate handler, if there is one, and use it to construct the object.
108
109Let's add the MDX and BLP handlers.
110
111```javascript
112viewer.addHandler(handlers.mdx);
113viewer.addHandler(handlers.blp);
114```
115
116Next, let's add a new scene to the viewer. Each scene has its own camera and viewport, and holds a list of things to render.
117```javascript
118let scene = viewer.addScene();
119```
120
121Finally, let's move the scene's camera backwards a bit.
122```javascript
123scene.camera.move([0, 0, 500]);
124```
125
126The viewer class acts as a sort-of resource manager.\
127Loading models and textures happens by using handlers and `load`, while other files are loaded generically with `loadGeneric`.
128
129For handlers, the viewer uses path solving functions.\
130You supply a function that takes a source you want to load, such as an url, and you need to return whatever you want loaded by the viewer.\
131The load function itself looks like this:
132
133```javascript
134let resourcePromise = viewer.load(src, pathSolver[, solverParams])
135```
136
137In other words, you give it a source, and a promise to a resource is returned, where a resource in this context means a model or a texture.
138
139The source can be anything - a string, an object, a typed array, something else - it highly depends on your code, and on the path solver.
140
141The path solver is a function with this signature: `function(src[, solverParams]) => finalSrc`, where:
142* `src` is the source you gave the load call, or one given by the resource itself when loading internal resources.
143* `finalSrc` is the actual source to load from. If this is a server fetch, then this is the url to fetch from. If it's an in-memory load, it depends on what each handler expects, typically an ArrayBuffer or a string.
144
145Generally speaking, you'll need a simple path solver that expects urls and prepends them by some base directory or API url.\
146There are however times when this is not the case, such as loading models with custom textures, and handling both in-memory and fetches in the same solver as done in the map viewer.
147
148For information about `solverParams`, see the [solver parameters section](#solver-params-reforged-and-map-loading).
149
150So let's use an example.
151
152Suppose we have the following directory structure:
153
154```
155├── index.html
156└── Resources
157 ├── model.mdx
158 └── texture.blp
159```
160
161Where `model.mdx` uses the texture `texture.blp`.
162
163Let's see how a possible path solver could look.\
164I'll make it assume it's getting urls, and automatically prepend "Resources/" to sources.
165
166```javascript
167function myPathSolver(path) {
168 return "Resources/" + path;
169}
170```
171
172Now let's try to load the model.
173
174```javascript
175let modelPromise = viewer.load("model.mdx", myPathSolver);
176```
177
178This function call results in the following:
179
1801. myPathSolver is called with `"model.mdx"` and returns `"Resources/model.mdx"`.
1812. The viewer starts the fetch, and emits the `loadstart` event.
1823. A promise is returned.
1834. ...time passes until the file finishes loading...
1845. The viewer detects the format as MDX.
1856. The model is constructed successfuly, or not, and sends a `load` or `error` event respectively, followed by the `loadend` event.
1867. In the case of an MDX model, the previous step will also cause it to load its textures, in this case `texture.blp`.
1878. myPathSolver is called with `"texture.blp"`, which returns `"Resources/texture.blp"`, and we loop back to step 2, but with a texture this time.
188
189Once the promise is resolved, we have a model, however a model in this context is simply a source of data, not something that you see.\
190The next step is to create an instance of this model.\
191Instances can be rendered, moved, rotated, scaled, parented to other instances or nodes, play animations, and so on.
192```javascript
193let instance = model.addInstance();
194```
195
196Let's add the instance to the scene, so it's rendered:
197```javascript
198scene.addInstance(instance);
199// Equivalent to:
200instance.setScene(scene);
201```
202
203Finally, we need to actually let the viewer update and render:
204```javascript
205(function step() {
206 requestAnimationFrame(step);
207
208 viewer.updateAndRender();
209}());
210```
211
212---
213
214Loading other files is simpler:
215```javascript
216let resourcePromise = viewer.loadGeneric(path, dataType[, callback]);
217```
218
219Where:
220* `path` is an url string.
221* `dataType` is a string with one of these values: `text`, `arrayBuffer`, `blob`, or `image`.
222* `callback` is a function that will be called with the data once the fetch is complete, and should return the resource's data.
223
224If a callback is given, `resource.data` will be whatever the callback returns.\
225If a promise is returned, the loader waits for it to resolve, and uses whatever it resolved to.\
226If no callback is given, the data will be the fetch data itself, according to the given data type.
227
228`loadGeneric` is a simple layer above the standard `fetch` function.\
229The purpose of loading other files through the viewer is to cache the results and avoid multiple loads, while also allowing the viewer itself to handle events correctly, such as `whenAllLoaded`.
230
231------------------------
232
233#### Events and Promises
234
235As mentioned above, there are emitted events, and they can be listened to, using the NodeJS EventEmitter API:
236```javascript
237viewer.on(eventName, listener)
238viewer.off(eventName, listener)
239viewer.once(eventName, listener)
240viewer.emit(eventName[, ...args])
241```
242
243The event name can be one of:
244* `loadstart` - a resource started loading.
245* `load` - a resource successfully loaded.
246* `error` - something bad happened.
247* `loadend` - a resource finished loading, follows both `load` and `error` when loading a resource.
248* `idle` - all loads finished for now.
249
250For example:
251```javascript
252viewer.on('error', (viewer, error, reason) => {
253 console.log(`Error: ${error}, Reason: ${reason}`);
254});
255```
256
257In addition there is `ModelViewer.whenAllLoaded()` which will trigger both on the `idle` event, and if there is currently nothing loading.
258
259------------------------
260
261#### Solver Params, Reforged, and map loading
262
263As mentioned above, when loading resources, the `solverParams` parameter can be supplied.
264Solver parameters allow to give additional information about a load to the path solver.
265A client can supply its own solver parameters, and the handler implementations can supply their own parameters for internal resources.
266
267This is used by the MDX handler to request SD/HD resources from Reforged.
268
269It's also used by the map viewer to request SD/HD resources from Reforged, and to select the tileset.
270
271For example, let's suppose we want to load the Warcraft 3 Footman model, but with a twist - we want all three versions of it - TFT, Reforged SD, and Reforged HD.
272
273The MDX handler defines the parameters as such: `{reforged?: boolean, hd?: boolean}`.
274
275If `reforged` is falsy or doesn't exist, then it wants a TFT resource.\
276If `reforged` is true, then it wants a Reforged SD resource and...\
277If `hd` and `reforged` are true, then it wants a Reforged HD resource.
278
279Following this, the loading code can be something along these lines:
280```js
281let modelTFT = viewer.load('Units/Human/Footman/Footman.mdx', mySolver);
282let modelReforged = viewer.load('Units/Human/Footman/Footman.mdx', mySolver, {reforged: true});
283let modelHD = viewer.load('Units/Human/Footman/Footman.mdx', mySolver, {reforged: true, hd: true});
284```
285
286Note that you don't have to use these exact values.
287Rather, these are the values that will be sent by the MDX handler or map viewer, when loading internal textures, models, and generic files.
288
289What does the path solver do, then?
290As always, that depends on the client and the server.
291For example, the client may append the parameters as url parameters, which can be seen in the existing clients.
292The client can also completely ignore these parameters and return whatever resources it wants.