Skip to content

Instantly share code, notes, and snippets.

@brett19
Created September 22, 2014 23:44
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brett19/13c83c2e5e38933757c2 to your computer and use it in GitHub Desktop.
Save brett19/13c83c2e5e38933757c2 to your computer and use it in GitHub Desktop.
// TODO: More efficient uncompressed loading
// TODO: Test RGB, RBA, Alpha, Luminance, LuminanceAlpha
// TODO: Test Cubemap
/**
* @constructor
* @property {DDS.Header} header
*/
var DDS = function() {
};
/**
* @constructor
* @property {Number} magic
* @property {Number} size
* @property {Number} flags
* @property {Number} height
* @property {Number} width
* @property {Number} pitch
* @property {Number} depth
* @property {Number} mipmaps
* @property {DDS.Header.PixelFormat} pixelFormat
* @property {Number} caps1
* @property {Number} caps2
* @property {Number} caps3
* @property {Number} caps4
*/
DDS.Header = function() {
this.pixelFormat = new DDS.Header.PixelFormat();
};
/**
* @constructor
* @property {Number} size
* @property {Number} flags
* @property {Number} fourCC
* @property {Number} bitCount
* @property {Number} maskR
* @property {Number} maskG
* @property {Number} maskB
* @property {Number} maskA
*/
DDS.Header.PixelFormat = function() {
};
/**
* @constructor
* @param {Number} width
* @param {Number} height
* @property {Uint8Array} data
* @property {Number} width
* @property {Number} height
*/
DDS.Mipmap = function(width, height) {
this.width = width;
this.height = height;
this.data = null;
};
/**
* Make a Uint32 FOURCC from a String
* @param {String} string
* @returns {Number} fourcc
*/
DDS.makeFourCC = function(string) {
return string.charCodeAt(0) +
(string.charCodeAt(1) << 8) +
(string.charCodeAt(2) << 16) +
(string.charCodeAt(3) << 24);
};
/**
* @type {number}
* @readonly
*/
DDS.MAGIC = 0x20534444;
/**
* @enum {Number}
* @readonly
*/
DDS.FLAGS = {
CAPS: 0x1,
HEIGHT: 0x2,
WIDTH: 0x4,
PITCH: 0x8,
PIXEL_FORMAT: 0x1000,
MIPMAP_COUNT: 0x20000,
LINEAR_SIZE: 0x80000,
DEPTH: 0x800000
};
/**
* @enum {Number}
* @readonly
*/
DDS.CAPS = {
COMPLEX: 0x8,
MIPMAP: 0x400000,
TEXTURE: 0x1000
};
/**
* @enum {Number}
* @readonly
*/
DDS.CAPS2 = {
CUBEMAP: 0x200,
CUBEMAP_POSITIVEX: 0x400,
CUBEMAP_NEGATIVEX: 0x800,
CUBEMAP_POSITIVEY: 0x1000,
CUBEMAP_NEGATIVEY: 0x2000,
CUBEMAP_POSITIVEZ: 0x4000,
CUBEMAP_NEGATIVEZ: 0x8000,
VOLUME: 0x200000
};
/**
* @enum {Number}
* @readonly
*/
DDS.PIXEL_FORMAT_FOURCC = {
DXT1: DDS.makeFourCC('DXT1'),
DXT3: DDS.makeFourCC('DXT3'),
DXT5: DDS.makeFourCC('DXT5')
};
/**
* @enum {Number}
* @readonly
*/
DDS.PIXEL_FORMAT_FLAGS = {
ALPHA_PIXELS: 0x1,
ALPHA: 0x2,
FOURCC: 0x4,
RGB: 0x40,
YUV: 0x200,
LUMINANCE: 0x20000
};
/**
* @param {BinaryReader} rh
* @returns {DDS.Header} header
*/
DDS.loadHeader = function(rh) {
var header = new DDS.Header();
header.magic = rh.readUint32();
header.size = rh.readUint32();
header.flags = rh.readUint32();
header.height = rh.readUint32();
header.width = rh.readUint32();
header.pitch = rh.readUint32();
header.depth = rh.readUint32();
header.mipmaps = Math.max(1, rh.readUint32());
rh.skip(4 * 11); // DWORD dwReserved1[11]
header.pixelFormat.size = rh.readUint32();
header.pixelFormat.flags = rh.readUint32();
header.pixelFormat.fourCC = rh.readUint32();
header.pixelFormat.bitCount = rh.readUint32();
header.pixelFormat.maskR = rh.readUint32();
header.pixelFormat.maskG = rh.readUint32();
header.pixelFormat.maskB = rh.readUint32();
header.pixelFormat.maskA = rh.readUint32();
header.caps1 = rh.readUint32();
header.caps2 = rh.readUint32();
header.caps3 = rh.readUint32();
header.caps4 = rh.readUint32();
rh.skip(4); // DWORD dwReserved2
return header;
};
/**
* @param {Number} mask
* @returns {Number} shift
*/
DDS.getMaskShift = function(mask) {
var str = mask.toString(2);
return str.length - (str.lastIndexOf('1') + 1);
};
/**
* @param {Number} mask
* @returns {Number} scale
*/
DDS.getMaskScale = function(mask) {
var str = mask.toString(2);
var pos = str.lastIndexOf('1');
if (pos === 0) {
return 256.0;
} else {
return 256.0 / (1 << (pos + 1));
}
};
/**
* @callback DDS~onLoad
* @param {THREE.CompressedTexture} dds
*/
/**
* @param {String} path
* @param {DDS~onLoad} callback
* @return {THREE.CompressedTexture} dds
*/
DDS.load = function(path, callback) {
var texture = new THREE.CompressedTexture();
texture.image = [];
texture.flipY = false;
texture.generateMipmaps = false;
ROSELoader.load(path, function(/** BinaryReader */rh) {
var maskR, maskG, maskB, maskA;
var shiftR, shiftG, shiftB, shiftA;
var scaleR, scaleG, scaleB, scaleA;
var header, format, dxtBlockSize;
var faces, cubemap, readPixelFn;
var mipmaps = [];
header = DDS.loadHeader(rh);
if (header.magic !== DDS.MAGIC) {
console.error('DDS.Loader.load: Invalid magic number in DDS header.');
return null;
}
cubemap = !!(header.caps1 & DDS.CAPS2.CUBEMAP);
faces = cubemap ? 6 : 1;
if (header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.FOURCC) {
switch (header.pixelFormat.fourCC) {
case DDS.PIXEL_FORMAT_FOURCC.DXT1:
dxtBlockSize = 8;
format = THREE.RGB_S3TC_DXT1_Format;
break;
case DDS.PIXEL_FORMAT_FOURCC.DXT3:
dxtBlockSize = 16;
format = THREE.RGBA_S3TC_DXT3_Format;
break;
case DDS.PIXEL_FORMAT_FOURCC.DXT5:
dxtBlockSize = 16;
format = THREE.RGBA_S3TC_DXT5_Format;
break;
}
} else if (header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.RGB) {
if (header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.ALPHA_PIXELS) {
format = THREE.RGBAFormat;
} else {
format = THREE.RGBFormat;
}
} else if (header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.LUMINANCE) {
if (header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.ALPHA_PIXELS) {
if (header.pixelFormat.bitCount == 16) {
format = THREE.LuminanceAlphaFormat;
}
} else if (header.pixelFormat.bitCount == 8) {
format = THREE.LuminanceFormat;
}
} else if (header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.ALPHA) {
if (header.pixelFormat.bitCount == 8) {
format = THREE.AlphaFormat;
}
}
if (format === undefined) {
console.warn('Unable to load DDS, unsupported pixel format.');
console.log(path);
callback(null);
return;
}
if (!(header.flags & DDS.FLAGS.MIPMAP_COUNT)) {
header.mipmaps = 1;
}
rh.seek(header.size + 4);
// If we are not a compressed texture, we need to calculate our mask & shift
if (!(header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.FOURCC)) {
if (header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.RGB) {
maskR = header.pixelFormat.maskR;
maskG = header.pixelFormat.maskG;
maskB = header.pixelFormat.maskB;
shiftR = DDS.getMaskShift(maskR);
shiftG = DDS.getMaskShift(maskG);
shiftB = DDS.getMaskShift(maskB);
scaleR = DDS.getMaskScale(maskR);
scaleG = DDS.getMaskScale(maskG);
scaleB = DDS.getMaskScale(maskB);
}
if (header.pixelFormat.flags & DDS.PIXEL_FORMAT_FLAGS.ALPHA_PIXELS) {
maskA = header.pixelFormat.maskA;
shiftA = DDS.getMaskShift(maskA);
scaleA = DDS.getMaskScale(maskA);
}
switch (header.pixelFormat.bitCount) {
case 8:
readPixelFn = rh.readUint8;
break;
case 16:
readPixelFn = rh.readUint16;
break;
case 24:
readPixelFn = rh.readUint24;
break;
case 32:
readPixelFn = rh.readUint32;
break;
default:
throw 'Invalid format, header.pixelFormat.bitCount must be a multiple of 8';
}
}
for (var i = 0; i < faces; ++i) {
var width = header.width;
var height = header.height;
for (var j = 0; j < header.mipmaps; ++j) {
var mipmap = new DDS.Mipmap(width, height);
var pixelCount = width * height;
var byteCount = pixelCount * (header.pixelFormat.bitCount / 8);
switch (format) {
case THREE.RGB_S3TC_DXT1_Format:
case THREE.RGBA_S3TC_DXT3_Format:
case THREE.RGBA_S3TC_DXT5_Format:
byteCount = dxtBlockSize * (Math.max(4, width) / 4) * (Math.max(4, height) / 4);
mipmap.data = rh.readUint8Array(byteCount);
mipmap.width = Math.max(4, mipmap.width);
mipmap.height = Math.max(4, mipmap.height);
break;
case THREE.RGBAFormat:
if (shiftR === 0 && shiftG === 8 && shiftB === 16 && shiftA === 24) {
mipmap.data = rh.readUint8Array(byteCount);
} else if (shiftR === 24 && shiftG === 16 && shiftB === 8 && shiftA === 0) {
mipmap.data = rh.readUint8Array(byteCount);
for (var k = 0; k < byteCount; k += 4) {
var r = mipmap.data[k];
mipmap.data[k] = mipmap.data[k + 2];
mipmap.data[k + 2] = r;
}
} else {
mipmap.data = new Uint8Array(byteCount);
for (var k = 0, idx = 0; k < pixelCount; ++k) {
var pixel = readPixelFn.apply(rh);
var r = (pixel & maskR) >> shiftR;
var g = (pixel & maskG) >> shiftG;
var b = (pixel & maskB) >> shiftB;
var a = (pixel & maskA) >> shiftA;
mipmap.data[idx++] = r * scaleR;
mipmap.data[idx++] = g * scaleG;
mipmap.data[idx++] = b * scaleB;
mipmap.data[idx++] = a * scaleA;
}
}
break;
case THREE.RGBFormat:
if (shiftR === 0 && shiftG === 8 && shiftB === 16) {
mipmap.data = rh.readUint8Array(byteCount);
} else if (shiftR === 16 && shiftG === 8 && shiftB === 0) {
mipmap.data = rh.readUint8Array(byteCount);
for (var k = 0; k < byteCount; k += 3) {
var r = mipmap.data[k];
mipmap.data[k] = mipmap.data[k + 2];
mipmap.data[k + 2] = r;
}
} else {
mipmap.data = new Uint8Array(byteCount);
for (var k = 0, idx = 0; k < pixelCount; k++) {
var pixel = readPixelFn.apply(rh);
var r = (pixel & maskR) >> shiftR;
var g = (pixel & maskG) >> shiftG;
var b = (pixel & maskB) >> shiftB;
mipmap.data[idx++] = r * scaleR;
mipmap.data[idx++] = g * scaleG;
mipmap.data[idx++] = b * scaleB;
}
}
break;
case THREE.LuminanceAlphaFormat:
if (shiftR === 0 && shiftA === 8) {
mipmap.data = rh.readUint8Array(byteCount);
} else if (shiftR === 8 && shiftA === 0) {
mipmap.data = rh.readUint8Array(byteCount);
for (var k = 0; k < byteCount; k += 2) {
var r = mipmap.data[k];
mipmap.data[k] = mipmap.data[k + 1];
mipmap.data[k + 1] = r;
}
} else {
mipmap.data = new Uint8Array(byteCount);
for (var k = 0, idx = 0; k < pixelCount; ++k) {
var pixel = readPixelFn.apply(rh);
var r = (pixel & maskR) >> shiftR;
var a = (pixel & maskA) >> shiftA;
mipmap.data[idx++] = r * scaleR;
mipmap.data[idx++] = a * scaleA;
}
}
break;
case THREE.AlphaFormat:
case THREE.LuminanceFormat:
if (shiftA === 0) {
mipmap.data = rh.readUint8Array(byteCount);
} else {
for (var k = 0; k < pixelCount; ++k) {
var pixel = readPixelFn.apply(rh);
var a = (pixel & maskA) >> shiftA;
mipmap.data[k] = a * scaleA;
}
}
break;
}
mipmaps.push(mipmap);
width = Math.max(width * 0.5, 1);
height = Math.max(height * 0.5, 1);
}
}
if (cubemap) {
var idx = 0;
for (var i = 0; i < faces; ++i) {
var image = { mipmaps: [] };
for (var j = 0; j < header.mipmaps; ++j) {
image.mipmaps.push(mipmaps[idx++]);
image.format = texture.format;
image.width = texture.width;
image.height = texture.height;
}
texture.images[i] = face;
}
} else {
texture.image.width = header.width;
texture.image.height = header.height;
texture.mipmaps = mipmaps;
}
texture.format = format;
texture.needsUpdate = true;
callback(texture);
});
return texture;
};
@majimboo
Copy link

Would this still work with the latest three.js? I have an old dds file from 2001 and the dds loader included with three.js now doesn't work. (it doesn't seem to support compression = none).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment