mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
229 lines (196 loc) • 6.83 kB
text/typescript
import BinaryStream from '../../common/binarystream';
import TokenStream from './tokenstream';
import AnimatedObject from './animatedobject';
const filterModeToMdx = {
None: 0,
Transparent: 1,
Blend: 2,
Additive: 3,
AddAlpha: 4,
Modulate: 5,
Modulate2x: 6,
};
const filterModeToMdl = {
0: 'None',
1: 'Transparent',
2: 'Blend',
3: 'Additive',
4: 'AddAlpha',
5: 'Modulate',
6: 'Modulate2x',
};
/**
* A layer.
*/
export default class Layer extends AnimatedObject {
filterMode: number = 0;
flags: number = 0;
textureId: number = -1;
textureAnimationId: number = -1;
coordId: number = 0;
alpha: number = 1;
/**
* @since 900
*/
emissiveGain: number = 1;
/**
* @since 1000
*/
fresnelColor: Float32Array = new Float32Array([1, 1, 1]);
/**
* @since 1000
*/
fresnelOpacity: number = 0;
/**
* @since 1000
*/
fresnelTeamColor: number = 0;
readMdx(stream: BinaryStream, version: number) {
const start = stream.index;
const size = stream.readUint32();
this.filterMode = stream.readUint32();
this.flags = stream.readUint32();
this.textureId = stream.readInt32();
this.textureAnimationId = stream.readInt32();
this.coordId = stream.readUint32();
this.alpha = stream.readFloat32();
// Note that even though these fields were introduced in versions 900 and 1000 separately, the game does not offer backwards compatibility.
if (version > 800) {
this.emissiveGain = stream.readFloat32();
stream.readFloat32Array(this.fresnelColor);
this.fresnelOpacity = stream.readFloat32();
this.fresnelTeamColor = stream.readFloat32();
}
this.readAnimations(stream, size - (stream.index - start));
}
writeMdx(stream: BinaryStream, version: number) {
stream.writeUint32(this.getByteLength(version));
stream.writeUint32(this.filterMode);
stream.writeUint32(this.flags);
stream.writeInt32(this.textureId);
stream.writeInt32(this.textureAnimationId);
stream.writeUint32(this.coordId);
stream.writeFloat32(this.alpha);
// See note above in readMdx.
if (version > 800) {
stream.writeFloat32(this.emissiveGain);
stream.writeFloat32Array(this.fresnelColor);
stream.writeFloat32(this.fresnelOpacity);
stream.writeFloat32(this.fresnelTeamColor);
}
this.writeAnimations(stream);
}
readMdl(stream: TokenStream) {
for (let token of super.readAnimatedBlock(stream)) {
if (token === 'FilterMode') {
this.filterMode = filterModeToMdx[stream.read()];
} else if (token === 'Unshaded') {
this.flags |= 0x1;
} else if (token === 'SphereEnvMap') {
this.flags |= 0x2;
} else if (token === 'TwoSided') {
this.flags |= 0x10;
} else if (token === 'Unfogged') {
this.flags |= 0x20;
} else if (token === 'NoDepthTest') {
this.flags |= 0x40;
} else if (token === 'NoDepthSet') {
this.flags |= 0x80;
} else if (token === 'Unlit') {
this.flags |= 0x100;
} else if (token === 'static TextureID') {
this.textureId = stream.readInt();
} else if (token === 'TextureID') {
this.readAnimation(stream, 'KMTF');
} else if (token === 'TVertexAnimId') {
this.textureAnimationId = stream.readInt();
} else if (token === 'CoordId') {
this.coordId = stream.readInt();
} else if (token === 'static Alpha') {
this.alpha = stream.readFloat();
} else if (token === 'Alpha') {
this.readAnimation(stream, 'KMTA');
} else if (token === 'static EmissiveGain') {
this.emissiveGain = stream.readFloat();
} else if (token === 'EmissiveGain') {
this.readAnimation(stream, 'KMTE')
} else if (token === 'static FresnelColor') {
stream.readVector(this.fresnelColor);
} else if (token === 'FresnelColor') {
this.readAnimation(stream, 'KFC3')
} else if (token === 'static FresnelOpacity') {
this.fresnelOpacity = stream.readFloat();
} else if (token === 'FresnelOpacity') {
this.readAnimation(stream, 'KFCA')
} else if (token === 'static FresnelTeamColor') {
this.fresnelTeamColor = stream.readFloat();
} else if (token === 'FresnelTeamColor') {
this.readAnimation(stream, 'KFTC')
} else {
throw new Error(`Unknown token in Layer: "${token}"`);
}
}
}
writeMdl(stream: TokenStream, version: number) {
stream.startBlock('Layer');
stream.writeFlagAttrib('FilterMode', filterModeToMdl[this.filterMode]);
if (this.flags & 0x1) {
stream.writeFlag('Unshaded');
}
if (this.flags & 0x2) {
stream.writeFlag('SphereEnvMap');
}
if (this.flags & 0x10) {
stream.writeFlag('TwoSided');
}
if (this.flags & 0x20) {
stream.writeFlag('Unfogged');
}
if (this.flags & 0x40) {
stream.writeFlag('NoDepthTest');
}
if (this.flags & 0x80) {
stream.writeFlag('NoDepthSet');
}
if (version > 800) {
if (this.flags & 0x100) {
stream.writeFlag('Unlit');
}
}
if (!this.writeAnimation(stream, 'KMTF')) {
stream.writeNumberAttrib('static TextureID', this.textureId);
}
if (this.textureAnimationId !== -1) {
stream.writeNumberAttrib('TVertexAnimId', this.textureAnimationId);
}
if (this.coordId !== 0) {
stream.writeNumberAttrib('CoordId', this.coordId);
}
if (!this.writeAnimation(stream, 'KMTA') && this.alpha !== 1) {
stream.writeNumberAttrib('static Alpha', this.alpha);
}
if (version > 800) {
if (!this.writeAnimation(stream, 'KMTE') && this.emissiveGain !== 1) {
stream.writeNumberAttrib('static EmissiveGain', this.emissiveGain);
}
if (!this.writeAnimation(stream, 'KFC3') && (this.fresnelColor[0] !== 1 || this.fresnelColor[1] !== 1 || this.fresnelColor[2] !== 1)) {
stream.writeVectorAttrib('static FresnelColor', this.fresnelColor);
}
if (!this.writeAnimation(stream, 'KFCA') && this.fresnelOpacity !== 0) {
stream.writeNumberAttrib('static FresnelOpacity', this.fresnelOpacity);
}
if (!this.writeAnimation(stream, 'KFTC') && this.fresnelTeamColor !== 0) {
stream.writeNumberAttrib('static FresnelTeamColor', this.fresnelTeamColor);
}
}
stream.endBlock();
}
getByteLength(version: number) {
let size = 28 + super.getByteLength();
// See note above in readMdx.
if (version > 800) {
size += 24;
}
return size;
}
}