mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
286 lines (232 loc) • 9.52 kB
text/typescript
import convertBitRange from './convertbitrange';
const dxt4to8 = convertBitRange(4, 8);
const dxt5to8 = convertBitRange(5, 8);
const dxt6to8 = convertBitRange(6, 8);
const dx1colors = new Uint16Array(4);
const dx3colors = new Uint8Array(12);
const dx5alphas = new Uint8Array(8);
const red = new Uint8Array(8);
const green = new Uint8Array(8);
function dx1Colors16(out: Uint16Array, color0: number, color1: number) {
let r0 = color0 & 0x1f;
let g0 = color0 & 0x7e0;
let b0 = color0 & 0xf800;
let r1 = color1 & 0x1f;
let g1 = color1 & 0x7e0;
let b1 = color1 & 0xf800;
// Minimum and maximum colors.
out[0] = color0;
out[1] = color1;
// Interpolated colors.
out[2] = ((5 * r0 + 3 * r1) >> 3) | (((5 * g0 + 3 * g1) >> 3) & 0x7e0) | (((5 * b0 + 3 * b1) >> 3) & 0xf800);
out[3] = ((5 * r1 + 3 * r0) >> 3) | (((5 * g1 + 3 * g0) >> 3) & 0x7e0) | (((5 * b1 + 3 * b0) >> 3) & 0xf800);
}
function dx1Colors8(out: Uint8Array, color0: number, color1: number) {
let r0 = ((color0 >> 11) & 31) * dxt5to8;
let g0 = ((color0 >> 5) & 63) * dxt6to8;
let b0 = (color0 & 31) * dxt5to8;
let r1 = ((color1 >> 11) & 31) * dxt5to8;
let g1 = ((color1 >> 5) & 63) * dxt6to8;
let b1 = (color1 & 31) * dxt5to8;
// Minimum and maximum colors.
out[0] = r0;
out[1] = g0;
out[2] = b0;
out[3] = r1;
out[4] = g1;
out[5] = b1;
// Interpolated colors.
out[6] = (5 * r0 + 3 * r1) >> 3;
out[7] = (5 * g0 + 3 * g1) >> 3;
out[8] = (5 * b0 + 3 * b1) >> 3;
out[9] = (5 * r1 + 3 * r0) >> 3;
out[10] = (5 * g1 + 3 * g0) >> 3;
out[11] = (5 * b1 + 3 * b0) >> 3;
}
function dx5Alphas(out: Uint8Array, alpha0: number, alpha1: number) {
// Minimum and maximum alphas.
out[0] = alpha0;
out[1] = alpha1;
// Interpolated alphas.
if (alpha0 > alpha1) {
out[2] = (54 * alpha0 + 9 * alpha1) >> 6;
out[3] = (45 * alpha0 + 18 * alpha1) >> 6;
out[4] = (36 * alpha0 + 27 * alpha1) >> 6;
out[5] = (27 * alpha0 + 36 * alpha1) >> 6;
out[6] = (18 * alpha0 + 45 * alpha1) >> 6;
out[7] = (9 * alpha0 + 54 * alpha1) >> 6;
} else {
out[2] = (12 * alpha0 + 3 * alpha1) >> 4;
out[3] = (9 * alpha0 + 6 * alpha1) >> 4;
out[4] = (6 * alpha0 + 9 * alpha1) >> 4;
out[5] = (3 * alpha0 + 12 * alpha1) >> 4;
out[6] = 0;
out[7] = 255;
}
}
function rgColors(out: Uint8Array, color0: number, color1: number) {
// Minimum and maximum red colors.
out[0] = color0;
out[1] = color1;
// Interpolated red colors.
if (color0 > color1) {
out[2] = (6 * color0 + 1 * color1) / 7;
out[3] = (5 * color0 + 2 * color1) / 7;
out[4] = (4 * color0 + 3 * color1) / 7;
out[5] = (3 * color0 + 4 * color1) / 7;
out[6] = (2 * color0 + 5 * color1) / 7;
out[7] = (1 * color0 + 6 * color1) / 7;
} else {
out[2] = (4 * color0 + 1 * color1) / 5;
out[3] = (3 * color0 + 2 * color1) / 5;
out[4] = (2 * color0 + 3 * color1) / 5;
out[5] = (1 * color0 + 4 * color1) / 5;
out[6] = 0;
out[7] = 1;
}
}
/**
* Decodes DXT1 data to a Uint16Array typed array with 5-6-5 RGB bits.
*
* DXT1 is also known as BC1.
*/
export function decodeDxt1(src: Uint8Array, width: number, height: number) {
let dst = new Uint16Array(width * height);
for (let blockY = 0, blockHeight = height / 4; blockY < blockHeight; blockY++) {
for (let blockX = 0, blockWidth = width / 4; blockX < blockWidth; blockX++) {
let i = 8 * (blockY * blockWidth + blockX);
// Get the color values.
dx1Colors16(dx1colors, src[i] + 256 * src[i + 1], src[i + 2] + 256 * src[i + 3]);
// The offset to the first pixel in the destination.
let dstI = (blockY * 4) * width + blockX * 4;
// All 32 color bits.
let bits = src[i + 4] | (src[i + 5] << 8) | (src[i + 6] << 16) | (src[i + 7] << 24);
for (let row = 0; row < 4; row++) {
let rowOffset = row * 8;
let dstOffset = dstI + row * width;
for (let column = 0; column < 4; column++) {
dst[dstOffset + column] = dx1colors[(bits >> (rowOffset + column * 2)) & 3];
}
}
}
}
return dst;
}
/**
* Decodes DXT3 data to a Uint8Array typed array with 8-8-8-8 RGBA bits.
*
* DXT3 is also known as BC2.
*/
export function decodeDxt3(src: Uint8Array, width: number, height: number) {
let dst = new Uint8Array(width * height * 4);
let rowBytes = width * 4;
for (let blockY = 0, blockHeight = width / 4; blockY < blockHeight; blockY++) {
for (let blockX = 0, blockWidth = height / 4; blockX < blockWidth; blockX++) {
let i = 16 * (blockY * blockWidth + blockX);
// Get the color values.
dx1Colors8(dx3colors, src[i + 8] + 256 * src[i + 9], src[i + 10] + 256 * src[i + 11]);
let dstI = (blockY * 16) * width + blockX * 16;
for (let row = 0; row < 4; row++) {
// Get 16 bits of alpha indices.
let alphaBits = src[i + row * 2] + 256 * src[i + 1 + row * 2];
// Get 8 bits of color indices.
let colorBits = src[i + 12 + row];
for (let column = 0; column < 4; column++) {
let dstIndex = dstI + column * 4;
let colorIndex = ((colorBits >> (column * 2)) & 3) * 3;
dst[dstIndex + 0] = dx3colors[colorIndex + 0];
dst[dstIndex + 1] = dx3colors[colorIndex + 1];
dst[dstIndex + 2] = dx3colors[colorIndex + 2];
dst[dstIndex + 3] = ((alphaBits >> (column * 4)) & 0xf) * dxt4to8;
}
dstI += rowBytes;
}
}
}
return dst;
}
/**
* Decodes DXT5 data to a Uint8Array typed array with 8-8-8-8 RGBA bits.
*
* DXT5 is also known as BC3.
*/
export function decodeDxt5(src: Uint8Array, width: number, height: number) {
let dst = new Uint8Array(width * height * 4);
let rowBytes = width * 4;
for (let blockY = 0, blockHeight = height / 4; blockY < blockHeight; blockY++) {
for (let blockX = 0, blockWidth = width / 4; blockX < blockWidth; blockX++) {
let i = 16 * (blockY * blockWidth + blockX);
// Get the alpha values.
dx5Alphas(dx5alphas, src[i], src[i + 1]);
// Get the color values.
dx1Colors8(dx3colors, src[i + 8] + 256 * src[i + 9], src[i + 10] + 256 * src[i + 11]);
// The offset to the first pixel in the destination.
let dstI = (blockY * 16) * width + blockX * 16;
// The outer loop is only needed because JS bitwise operators only work on 32bit integers, while the alpha flags contain 48 bits.
// Processing is instead done in two blocks, where each one handles 24 bits, or two rows of 4 pixels.
for (let block = 0; block < 2; block++) {
let alphaOffset = i + 2 + block * 3;
let colorOffset = i + 12 + block * 2;
// 24 alpha bits.
let alphaBits = src[alphaOffset] + 256 * (src[alphaOffset + 1] + 256 * src[alphaOffset + 2]);
// Go over two rows.
for (let row = 0; row < 2; row++) {
let colorBits = src[colorOffset + row];
// Go over four columns.
for (let column = 0; column < 4; column++) {
let dstIndex = dstI + column * 4;
let colorIndex = ((colorBits >> (column * 2)) & 3) * 3;
let alphaIndex = (alphaBits >> (row * 12 + column * 3)) & 7;
// Set the pixel.
dst[dstIndex + 0] = dx3colors[colorIndex + 0];
dst[dstIndex + 1] = dx3colors[colorIndex + 1];
dst[dstIndex + 2] = dx3colors[colorIndex + 2];
dst[dstIndex + 3] = dx5alphas[alphaIndex];
}
// Next row.
dstI += rowBytes;
}
}
}
}
return dst;
}
/**
* Decodes RGTC data to a Uint8Array typed array with 8-8 RG bits.
*
* RGTC is also known as BC5, ATI2, and 3Dc.
*/
export function decodeRgtc(src: Uint8Array, width: number, height: number) {
let dst = new Uint8Array(width * height * 2);
let rowBytes = width * 2;
for (let blockY = 0, blockHeight = height / 4; blockY < blockHeight; blockY++) {
for (let blockX = 0, blockWidth = width / 4; blockX < blockWidth; blockX++) {
let i = 16 * (blockY * blockWidth + blockX);
// Get the red colors.
rgColors(red, src[i], src[i + 1]);
// Get the green colors.
rgColors(green, src[i + 8], src[i + 9]);
// The offset to the first pixel in the destination.
let dstI = (blockY * 8) * width + blockX * 8;
// Split to two blocks of two rows, because there are 48 color bits.
for (let block = 0; block < 2; block++) {
let blockOffset = i + block * 3;
// Get 24 bits of the color indices.
let redbits = src[blockOffset + 2] + 256 * (src[blockOffset + 3] + 256 * src[blockOffset + 4]);
let greenbits = src[blockOffset + 10] + 256 * (src[blockOffset + 11] + 256 * src[blockOffset + 12]);
for (let row = 0; row < 2; row++) {
let rowOffset = row * 4;
for (let column = 0; column < 4; column++) {
let dstOffset = dstI + column * 2;
let shifts = 3 * (rowOffset + column);
dst[dstOffset + 1] = red[(redbits >> shifts) & 7];
dst[dstOffset + 2] = green[(greenbits >> shifts) & 7];
}
// Next row.
dstI += rowBytes;
}
}
}
}
return dst;
}