mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
398 lines (352 loc) • 14 kB
JavaScript
// Note: This file is largely based on https://github.com/toji/webctx-texture-utils/blob/master/texture-util/dds.js
import convertBitRange from './convertbitrange';
import {uint8ToUint24} from './typecast';
let dxt4to8 = convertBitRange(4, 8);
let dxt5to8 = convertBitRange(5, 8);
let dxt6to8 = convertBitRange(6, 8);
/**
* 4 bit alpha
*
* @param {Uint8Array} dst
* @param {number} i
* @param {number} int565
* @param {number} a
*/
function setRgba8888Dxt3(dst, i, int565, a) {
dst[i] = (((int565 >> 11) & 31) * dxt5to8) | 0;
dst[i + 1] = (((int565 >> 5) & 63) * dxt6to8) | 0;
dst[i + 2] = ((int565 & 31) * dxt5to8) | 0;
dst[i + 3] = a * dxt4to8;
}
/**
* 8 bit alpha
*
* @param {Uint8Array} dst
* @param {number} i
* @param {number} int565
* @param {number} a
*/
function setRgba8888Dxt5(dst, i, int565, a) {
dst[i] = (((int565 >> 11) & 31) * dxt5to8) | 0;
dst[i + 1] = (((int565 >> 5) & 63) * dxt6to8) | 0;
dst[i + 2] = ((int565 & 31) * dxt5to8) | 0;
dst[i + 3] = a;
}
/**
* Decodes DXT1 data to a Uint16Array typed array with 5-6-5 RGB bits.
*
* @param {Uint16Array} src The DXT1 data.
* @param {number} width The width of the data.
* @param {number} height The height of the data.
* @return {Uint16Array}
*/
export function decodeDxt1(src, width, height) {
let c = new Uint16Array(4);
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 = 4 * (blockY * blockWidth + blockX);
c[0] = src[i];
c[1] = src[i + 1];
let r0 = c[0] & 0x1f;
let g0 = c[0] & 0x7e0;
let b0 = c[0] & 0xf800;
let r1 = c[1] & 0x1f;
let g1 = c[1] & 0x7e0;
let b1 = c[1] & 0xf800;
if (c[0] > c[1]) {
c[2] = ((5 * r0 + 3 * r1) >> 3) | (((5 * g0 + 3 * g1) >> 3) & 0x7e0) | (((5 * b0 + 3 * b1) >> 3) & 0xf800);
c[3] = ((5 * r1 + 3 * r0) >> 3) | (((5 * g1 + 3 * g0) >> 3) & 0x7e0) | (((5 * b1 + 3 * b0) >> 3) & 0xf800);
} else {
c[2] = (c[0] + c[1]) >> 1;
c[3] = 0;
}
let m = src[i + 2];
let dstI = (blockY * 4) * width + blockX * 4;
dst[dstI] = c[m & 0x3];
dst[dstI + 1] = c[(m >> 2) & 0x3];
dst[dstI + 2] = c[(m >> 4) & 0x3];
dst[dstI + 3] = c[(m >> 6) & 0x3];
dstI += width;
dst[dstI] = c[(m >> 8) & 0x3];
dst[dstI + 1] = c[(m >> 10) & 0x3];
dst[dstI + 2] = c[(m >> 12) & 0x3];
dst[dstI + 3] = c[(m >> 14)];
m = src[i + 3];
dstI += width;
dst[dstI] = c[m & 0x3];
dst[dstI + 1] = c[(m >> 2) & 0x3];
dst[dstI + 2] = c[(m >> 4) & 0x3];
dst[dstI + 3] = c[(m >> 6) & 0x3];
dstI += width;
dst[dstI] = c[(m >> 8) & 0x3];
dst[dstI + 1] = c[(m >> 10) & 0x3];
dst[dstI + 2] = c[(m >> 12) & 0x3];
dst[dstI + 3] = c[(m >> 14)];
}
}
return dst;
}
/**
* Decodes DXT3 data to a Uint8Array typed array with 8-8-8-8 RGBA bits.
*
* @param {Uint16Array} src The DXT3 data.
* @param {number} width The width of the data.
* @param {number} height The height of the data.
* @return {Uint8Array}
*/
export function decodeDxt3(src, width, height) {
let c = new Uint16Array(4);
let dst = new Uint8Array(width * height * 4);
let widthBytes = width * 4;
for (let blockY = 0, blockHeight = width / 4; blockY < blockHeight; blockY++) {
for (let blockX = 0, blockWidth = height / 4; blockX < blockWidth; blockX++) {
let i = 8 * (blockY * blockWidth + blockX);
c[0] = src[i + 4];
c[1] = src[i + 5];
let r0 = c[0] & 0x1f;
let g0 = c[0] & 0x7e0;
let b0 = c[0] & 0xf800;
let r1 = c[1] & 0x1f;
let g1 = c[1] & 0x7e0;
let b1 = c[1] & 0xf800;
c[2] = ((5 * r0 + 3 * r1) >> 3) | (((5 * g0 + 3 * g1) >> 3) & 0x7e0) | (((5 * b0 + 3 * b1) >> 3) & 0xf800);
c[3] = ((5 * r1 + 3 * r0) >> 3) | (((5 * g1 + 3 * g0) >> 3) & 0x7e0) | (((5 * b1 + 3 * b0) >> 3) & 0xf800);
let m = src[i + 6];
let a = src[i];
let dstI = (blockY * 16) * width + blockX * 16;
setRgba8888Dxt3(dst, dstI, c[m & 0x3], a & 0xf);
setRgba8888Dxt3(dst, dstI + 4, c[(m >> 2) & 0x3], (a >> 4) & 0xf);
setRgba8888Dxt3(dst, dstI + 8, c[(m >> 4) & 0x3], (a >> 8) & 0xf);
setRgba8888Dxt3(dst, dstI + 12, c[(m >> 6) & 0x3], (a >> 12) & 0xf);
a = src[i + 1];
dstI += widthBytes;
setRgba8888Dxt3(dst, dstI, c[(m >> 8) & 0x3], a & 0xf);
setRgba8888Dxt3(dst, dstI + 4, c[(m >> 10) & 0x3], (a >> 4) & 0xf);
setRgba8888Dxt3(dst, dstI + 8, c[(m >> 12) & 0x3], (a >> 8) & 0xf);
setRgba8888Dxt3(dst, dstI + 12, c[m >> 14], (a >> 12) & 0xf);
m = src[i + 7];
a = src[i + 2];
dstI += widthBytes;
setRgba8888Dxt3(dst, dstI, c[m & 0x3], a & 0xf);
setRgba8888Dxt3(dst, dstI + 4, c[(m >> 2) & 0x3], (a >> 4) & 0xf);
setRgba8888Dxt3(dst, dstI + 8, c[(m >> 4) & 0x3], (a >> 8) & 0xf);
setRgba8888Dxt3(dst, dstI + 12, c[(m >> 6) & 0x3], (a >> 12) & 0xf);
a = src[i + 3];
dstI += widthBytes;
setRgba8888Dxt3(dst, dstI, c[(m >> 8) & 0x3], a & 0xf);
setRgba8888Dxt3(dst, dstI + 4, c[(m >> 10) & 0x3], (a >> 4) & 0xf);
setRgba8888Dxt3(dst, dstI + 8, c[(m >> 12) & 0x3], (a >> 8) & 0xf);
setRgba8888Dxt3(dst, dstI + 12, c[m >> 14], (a >> 12) & 0xf);
}
}
return dst;
}
/**
* Decodes DXT5 data to a Uint8Array typed array with 8-8-8-8 RGBA bits.
*
* @param {Uint16Array} src The DXT5 data.
* @param {number} width The width of the data.
* @param {number} height The height of the data.
* @return {Uint8Array}
*/
export function decodeDxt5(src, width, height) {
let c = new Uint16Array(4);
let a = new Uint8Array(8);
let dst = new Uint8Array(width * height * 4);
let widthBytes = width * 4;
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);
c[0] = src[i + 4];
c[1] = src[i + 5];
let r0 = c[0] & 0x1f;
let g0 = c[0] & 0x7e0;
let b0 = c[0] & 0xf800;
let r1 = c[1] & 0x1f;
let g1 = c[1] & 0x7e0;
let b1 = c[1] & 0xf800;
c[2] = ((5 * r0 + 3 * r1) >> 3) | (((5 * g0 + 3 * g1) >> 3) & 0x7e0) | (((5 * b0 + 3 * b1) >> 3) & 0xf800);
c[3] = ((5 * r1 + 3 * r0) >> 3) | (((5 * g1 + 3 * g0) >> 3) & 0x7e0) | (((5 * b1 + 3 * b0) >> 3) & 0xf800);
let alphaBits = src[i + 1] + 65536 * (src[i + 2] + 65536 * src[i + 3]);
a[0] = src[i] & 0xff;
a[1] = src[i] >> 8;
if (a[0] > a[1]) {
a[2] = (54 * a[0] + 9 * a[1]) >> 6;
a[3] = (45 * a[0] + 18 * a[1]) >> 6;
a[4] = (36 * a[0] + 27 * a[1]) >> 6;
a[5] = (27 * a[0] + 36 * a[1]) >> 6;
a[6] = (18 * a[0] + 45 * a[1]) >> 6;
a[7] = (9 * a[0] + 54 * a[1]) >> 6;
/*
a[2] = (6 * a[0] + a[1]) / 7;
a[3] = (5 * a[0] + 2 * a[1]) / 7;
a[4] = (4 * a[0] + 3 * a[1]) / 7;
a[5] = (3 * a[0] + 4 * a[1]) / 7;
a[6] = (2 * a[0] + 5 * a[1]) / 7;
a[7] = (a[0] + 6 * a[1]) / 7;
//*/
} else {
a[2] = (12 * a[0] + 3 * a[1]) >> 4;
a[3] = (9 * a[0] + 6 * a[1]) >> 4;
a[4] = (6 * a[0] + 9 * a[1]) >> 4;
a[5] = (3 * a[0] + 12 * a[1]) >> 4;
a[6] = 0;
a[7] = 1;
/*
a[2] = (4 * a[0] + a[1]) / 5;
a[3] = (3 * a[0] + 2 * a[1]) / 5;
a[4] = (2 * a[0] + 3 * a[1]) / 5;
a[5] = (a[0] + 4 * a[1]) / 5;
a[6] = 0;
a[7] = 1;
//*/
}
let m = src[i + 6];
let dstI = (blockY * 16) * width + blockX * 16;
setRgba8888Dxt5(dst, dstI, c[m & 0x3], a[alphaBits & 0x7]);
setRgba8888Dxt5(dst, dstI + 4, c[(m >> 2) & 0x3], a[(alphaBits >> 3) & 0x7]);
setRgba8888Dxt5(dst, dstI + 8, c[(m >> 4) & 0x3], a[(alphaBits >> 6) & 0x7]);
setRgba8888Dxt5(dst, dstI + 12, c[(m >> 6) & 0x3], a[(alphaBits >> 9) & 0x7]);
dstI += widthBytes;
setRgba8888Dxt5(dst, dstI, c[(m >> 8) & 0x3], a[(alphaBits >> 12) & 0x7]);
setRgba8888Dxt5(dst, dstI + 4, c[(m >> 10) & 0x3], a[(alphaBits >> 15) & 0x7]);
setRgba8888Dxt5(dst, dstI + 8, c[(m >> 12) & 0x3], a[(alphaBits >> 18) & 0x7]);
setRgba8888Dxt5(dst, dstI + 12, c[m >> 14], a[(alphaBits >> 21) & 0x7]);
m = src[i + 7];
dstI += widthBytes;
setRgba8888Dxt5(dst, dstI, c[m & 0x3], a[(alphaBits >> 24) & 0x7]);
setRgba8888Dxt5(dst, dstI + 4, c[(m >> 2) & 0x3], a[(alphaBits >> 27) & 0x7]);
setRgba8888Dxt5(dst, dstI + 8, c[(m >> 4) & 0x3], a[(alphaBits >> 30) & 0x7]);
setRgba8888Dxt5(dst, dstI + 12, c[(m >> 6) & 0x3], a[(alphaBits >> 33) & 0x7]);
dstI += widthBytes;
setRgba8888Dxt5(dst, dstI, c[(m >> 8) & 0x3], a[(alphaBits >> 36) & 0x7]);
setRgba8888Dxt5(dst, dstI + 4, c[(m >> 10) & 0x3], a[(alphaBits >> 39) & 0x7]);
setRgba8888Dxt5(dst, dstI + 8, c[(m >> 12) & 0x3], a[(alphaBits >> 42) & 0x7]);
setRgba8888Dxt5(dst, dstI + 12, c[m >> 14], a[(alphaBits >> 45) & 0x7]);
}
}
return dst;
}
/**
* Decodes RGTC data to a Uint8Array typed array with 8-8 RG bits.
* RGTC is also known as BC5, ATI2, and 3Dc.
*
* @param {Uint8Array} src
* @param {number} width
* @param {number} height
* @return {Uint8Array}
*/
export function decodeRgtc(src, width, height) {
let red = new Uint8Array(8);
let green = new Uint8Array(8);
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);
// Minimum and maximum red colors.
red[0] = src[i];
red[1] = src[i + 1];
// Interpolated red colors.
if (red[0] > red[1]) {
red[2] = (6 * red[0] + 1 * red[1]) / 7;
red[3] = (5 * red[0] + 2 * red[1]) / 7;
red[4] = (4 * red[0] + 3 * red[1]) / 7;
red[5] = (3 * red[0] + 4 * red[1]) / 7;
red[6] = (2 * red[0] + 5 * red[1]) / 7;
red[7] = (1 * red[0] + 6 * red[1]) / 7;
} else {
red[2] = (4 * red[0] + 1 * red[1]) / 5;
red[3] = (3 * red[0] + 2 * red[1]) / 5;
red[4] = (2 * red[0] + 3 * red[1]) / 5;
red[5] = (1 * red[0] + 4 * red[1]) / 5;
red[6] = 0;
red[7] = 1;
}
// Minimum and maximum green colors.
green[0] = src[i + 8];
green[1] = src[i + 9];
// Interpolated green colors.
if (green[0] > green[1]) {
green[2] = (6 * green[0] + 1 * green[1]) / 7;
green[3] = (5 * green[0] + 2 * green[1]) / 7;
green[4] = (4 * green[0] + 3 * green[1]) / 7;
green[5] = (3 * green[0] + 4 * green[1]) / 7;
green[6] = (2 * green[0] + 5 * green[1]) / 7;
green[7] = (1 * green[0] + 6 * green[1]) / 7;
} else {
green[2] = (4 * green[0] + 1 * green[1]) / 5;
green[3] = (3 * green[0] + 2 * green[1]) / 5;
green[4] = (2 * green[0] + 3 * green[1]) / 5;
green[5] = (1 * green[0] + 4 * green[1]) / 5;
green[6] = 0;
green[7] = 1;
}
// Each block is made of 16 3bit indices = 48 bits.
// Easiest way to read it is to split it to two 24bit integers, and use bitshifts.
// This is because JS bitwise operators only work with 32bit integers.
let red24 = uint8ToUint24(src[i + 2], src[i + 3], src[i + 4]);
let red48 = uint8ToUint24(src[i + 5], src[i + 6], src[i + 7]);
let green24 = uint8ToUint24(src[i + 10], src[i + 11], src[i + 12]);
let green48 = uint8ToUint24(src[i + 13], src[i + 14], src[i + 15]);
// The offset to the first pixel in the destination.
let dstI = (blockY * 8) * width + blockX * 8;
// Pixel 1.
dst[dstI + 0] = red[red24 & 0b111];
dst[dstI + 1] = green[green24 & 0b111];
// Pixel 2.
dst[dstI + 2] = red[(red24 >> 3) & 0b111];
dst[dstI + 3] = green[(green24 >> 3) & 0b111];
// Pixel 3.
dst[dstI + 4] = red[(red24 >> 6) & 0b111];
dst[dstI + 5] = green[(green24 >> 6) & 0b111];
// Pixel 4.
dst[dstI + 6] = red[(red24 >> 9) & 0b111];
dst[dstI + 7] = green[(green24 >> 9) & 0b111];
// Next row.
dstI += rowBytes;
// Pixel 5.
dst[dstI + 0] = red[(red24 >> 12) & 0b111];
dst[dstI + 1] = green[(green24 >> 12) & 0b111];
// Pixel 6.
dst[dstI + 2] = red[(red24 >> 15) & 0b111];
dst[dstI + 3] = green[(green24 >> 15) & 0b111];
// Pixel 7.
dst[dstI + 4] = red[(red24 >> 18) & 0b111];
dst[dstI + 5] = green[(green24 >> 18) & 0b111];
// Pixel 8.
dst[dstI + 6] = red[(red24 >> 21) & 0b111];
dst[dstI + 7] = green[(green24 >> 21) & 0b111];
// Next row.
dstI += rowBytes;
// Pixel 9.
dst[dstI + 0] = red[red48 & 0b111];
dst[dstI + 1] = green[green48 & 0b111];
// Pixel 10.
dst[dstI + 2] = red[(red48 >> 3) & 0b111];
dst[dstI + 3] = green[(green48 >> 3) & 0b111];
// Pixel 11.
dst[dstI + 4] = red[(red48 >> 6) & 0b111];
dst[dstI + 5] = green[(green48 >> 6) & 0b111];
// Pixel 12.
dst[dstI + 6] = red[(red48 >> 9) & 0b111];
dst[dstI + 7] = green[(green48 >> 9) & 0b111];
// Next row.
dstI += rowBytes;
// Pixel 13.
dst[dstI + 0] = red[(red48 >> 12) & 0b111];
dst[dstI + 1] = green[(green48 >> 12) & 0b111];
// Pixel 14.
dst[dstI + 2] = red[(red48 >> 15) & 0b111];
dst[dstI + 3] = green[(green48 >> 15) & 0b111];
// Pixel 15.
dst[dstI + 4] = red[(red48 >> 18) & 0b111];
dst[dstI + 5] = green[(green48 >> 18) & 0b111];
// Pixel 16.
dst[dstI + 6] = red[(red48 >> 21) & 0b111];
dst[dstI + 7] = green[(green48 >> 21) & 0b111];
}
}
return dst;
}