Skip to content
Permalink
Browse files

Initial commit

  • Loading branch information...
magcius committed Oct 6, 2016
1 parent 310f40c commit 954aa09ef3f73d4c3c778abc4424b87e186c9761
205 bmd.js
@@ -0,0 +1,205 @@
(function(exports) {
"use strict";

// SM64 .bmd format

function readString(buffer, offs, length) {
var buf = new Uint8Array(buffer, offs, length);
var S = '';
for (var i = 0; i < length; i++) {
if (buf[i] === 0)
break;
S += String.fromCharCode(buf[i]);
}
return S;
}

function parseModel(bmd, view, idx) {
var offs = bmd.modelOffsBase + idx * 0x40;

var model = {};
model.id = view.getUint32(offs + 0x00, true);
model.name = readString(view.buffer, view.getUint32(offs + 0x04, true), 0xFF);

model.parentID = view.getUint16(offs + 0x08, true);

// Local transform.
var xs = view.getUint32(offs + 0x10, true);
var ys = view.getUint32(offs + 0x14, true);
var zs = view.getUint32(offs + 0x18, true);
var xr = view.getUint16(offs + 0x1C, true);
var yr = view.getUint16(offs + 0x1E, true);
var zr = view.getUint16(offs + 0x20, true);
var xt = view.getUint16(offs + 0x24, true);
var yt = view.getUint16(offs + 0x28, true);
var zt = view.getUint16(offs + 0x2C, true);

// A "batch" is a combination of a material and a poly.
var batchCount = view.getUint32(offs + 0x30, true);
var batchMaterialOffs = view.getUint32(offs + 0x34, true);
var batchPolyOffs = view.getUint32(offs + 0x38, true);

model.batches = [];

for (var i = 0; i < batchCount; i++) {
var materialIdx = view.getUint8(batchMaterialOffs + i);
var material = parseMaterial(bmd, view, materialIdx);
var baseCtx = { s_color: material.diffuse, alpha: material.alpha };

var polyIdx = view.getUint8(batchPolyOffs + i);
var poly = parsePoly(bmd, view, polyIdx, baseCtx);

model.batches.push({ material: material, poly: poly });
}

return model;
}

function parsePoly(bmd, view, idx, baseCtx) {
var offs = view.getUint32((bmd.polyOffsBase + idx * 0x08) + 0x04, true);

var gxCmdSize = view.getUint32(offs + 0x08, true);
var gxCmdOffs = view.getUint32(offs + 0x0C, true);

var gxCmdBuf = view.buffer.slice(gxCmdOffs, gxCmdOffs + gxCmdSize);

return { packets: NITRO_GX.readCmds(gxCmdBuf, baseCtx) };
}

function parseMaterial(bmd, view, idx) {
var offs = bmd.materialOffsBase + idx * 0x30;

var material = {};
material.name = readString(view.buffer, view.getUint32(offs + 0x00, true), 0xFF);
material.texCoordMat = mat4.create();

var textureIdx = view.getUint32(offs + 0x04, true);
if (textureIdx !== 0xFFFFFFFF) {
var paletteIdx = view.getUint32(offs + 0x08, true);
material.texture = parseTexture(bmd, view, textureIdx, paletteIdx);
material.texParams = material.texture.params | view.getUint32(offs + 0x20, true);

if (material.texParams >> 30) {
var scaleS = view.getInt32(offs + 0x0C, true) / 4096.0;
var scaleT = view.getInt32(offs + 0x10, true) / 4096.0;
var transS = view.getInt32(offs + 0x18, true) / 4096.0;
var transT = view.getInt32(offs + 0x1C, true) / 4096.0;
mat4.translate(material.texCoordMat, material.texCoordMat, [transS, transT, 0.0]);
mat4.scale(material.texCoordMat, material.texCoordMat, [scaleS, scaleT, 1.0]);
}
mat4.scale(material.texCoordMat, material.texCoordMat, [1/material.texture.width, 1/material.texture.height, 1]);
} else {
material.texture = null;
material.texParams = 0;
}

var polyAttribs = view.getUint32(offs + 0x24, true);
var alpha = (polyAttribs >> 16) & 0x1F;
alpha = (alpha << (8-5)) | (alpha >>> (10-8));

// NITRO's Rendering Engine uses two passes. Opaque, then Transparent.
// A transparent polygon is one that has an alpha of < 0xFF, or uses
// A5I3 / A3I5 textures.

material.isTranslucent = (alpha < 0xFF) || (material.texture && material.texture.isTranslucent);

// Do transparent polys write to the depth buffer?
var xl = (polyAttribs >>> 1) & 0x01;
if (xl)
material.depthWrite = true;
else
material.depthWrite = !material.isTranslucent;

var difAmb = view.getUint32(offs + 0x28, true);
if (difAmb & 0x8000)
material.diffuse = NITRO_GX.rgb5(difAmb & 0x07FF);
else
material.diffuse = [0xFF, 0xFF, 0xFF];

material.alpha = alpha;

return material;
}

function textureToCanvas(texture) {
var canvas = document.createElement("canvas");
canvas.width = texture.width;
canvas.height = texture.height;

var ctx = canvas.getContext("2d");
var imgData = ctx.createImageData(canvas.width, canvas.height);

for (var i = 0; i < imgData.data.length; i++)
imgData.data[i] = texture.pixels[i];

canvas.title = texture.name;

ctx.putImageData(imgData, 0, 0);
return canvas;
}

function parseTexture(bmd, view, texIdx, palIdx) {
var texOffs = bmd.textureOffsBase + texIdx * 0x14;

var texture = {};
texture.id = texIdx;
texture.name = readString(view.buffer, view.getUint32(texOffs + 0x00, true), 0xFF);

var texDataOffs = view.getUint32(texOffs + 0x04, true);
var texDataSize = view.getUint32(texOffs + 0x08, true);
var texData = view.buffer.slice(texDataOffs);

texture.params = view.getUint32(texOffs + 0x10, true);
texture.format = (texture.params >> 26) & 0x07;
texture.width = 8 << ((texture.params >> 20) & 0x07);
texture.height = 8 << ((texture.params >> 23) & 0x07);
var color0 = (texture.params >> 29) & 0x01;

var palData = null;
if (palIdx != 0xFFFFFFFF) {
var palOffs = bmd.paletteOffsBase + palIdx * 0x10;
texture.paletteName = readString(view.buffer, view.getUint32(palOffs + 0x00, true), 0xFF);
var palDataOffs = view.getUint32(palOffs + 0x04, true);
var palDataSize = view.getUint32(palOffs + 0x08, true);
palData = view.buffer.slice(palDataOffs, palDataOffs + palDataSize);
}

texture.pixels = NITRO_Tex.readTexture(texture.format, texture.width, texture.height, texData, palData, color0);

if (texture.pixels)
document.querySelector('#textures').appendChild(textureToCanvas(texture));

texture.isTranslucent = (texture.format === NITRO_Tex.Format.Tex_A5I3 ||
texture.format === NITRO_Tex.Format.Tex_A3I5);

return texture;
}

var BMD = {};
BMD.parse = function(buffer) {
var view = new DataView(buffer);

var bmd = {};

bmd.scaleFactor = (1 << view.getUint32(0x00, true));

bmd.modelCount = view.getUint32(0x04, true);
bmd.modelOffsBase = view.getUint32(0x08, true);
bmd.polyCount = view.getUint32(0x0C, true);
bmd.polyOffsBase = view.getUint32(0x10, true);
bmd.textureCount = view.getUint32(0x14, true);
bmd.textureOffsBase = view.getUint32(0x18, true);
bmd.paletteCount = view.getUint32(0x1C, true);
bmd.paletteOffsBase = view.getUint32(0x20, true);
bmd.materialCount = view.getUint32(0x24, true);
bmd.materialOffsBase = view.getUint32(0x28, true);

bmd.models = [];
for (var i = 0; i < bmd.modelCount; i++)
bmd.models.push(parseModel(bmd, view, i));

return bmd;
};
exports.BMD = BMD;

})(window);
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN +61.4 KB exk/castle_2f_all.bmd
Binary file not shown.
Binary file not shown.
BIN +70.8 KB exk/cave_all.bmd
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN +29.7 KB exk/desert_py_all.bmd
Binary file not shown.
BIN +27.3 KB exk/ex_l_map_all.bmd
Binary file not shown.
BIN +18.9 KB exk/ex_luigi_all.bmd
Binary file not shown.
BIN +31.4 KB exk/ex_m_map_all.bmd
Binary file not shown.
BIN +16.7 KB exk/ex_mario_all.bmd
Binary file not shown.
BIN +32.5 KB exk/ex_w_map_all.bmd
Binary file not shown.
BIN +12.1 KB exk/ex_wario_all.bmd
Binary file not shown.
BIN +26.2 KB exk/fire_land_all.bmd
Binary file not shown.
BIN +28.5 KB exk/fire_mt_all.bmd
Binary file not shown.
BIN +13.4 KB exk/habatake_all.bmd
Binary file not shown.
BIN +44.3 KB exk/high_mt_all.bmd
Binary file not shown.
Binary file not shown.
BIN +14.3 KB exk/horisoko_all.bmd
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN +38.2 KB exk/koopa3_map_all.bmd
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN +69.8 KB exk/playroom_all.bmd
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN +6.48 KB exk/snow_kama_all.bmd
Binary file not shown.
BIN +37.3 KB exk/snow_land_all.bmd
Binary file not shown.
BIN +45.2 KB exk/snow_mt_all.bmd
Binary file not shown.
Binary file not shown.
BIN +6.12 KB exk/suisou_all.bmd
Binary file not shown.
Binary file not shown.
BIN +18.5 KB exk/test_map_all.bmd
Binary file not shown.
BIN +356 Bytes exk/test_map_b_all.bmd
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN +38.4 KB exk/water_city_all.bmd
Binary file not shown.
BIN +27.8 KB exk/water_land_all.bmd
Binary file not shown.
@@ -0,0 +1,30 @@
<!doctype html>
<html>
<head>
<meta charset="utf8">
<title>NITRO_BMD</title>
<style>
button, select { font-size: 150%; }
canvas { cursor: grab; cursor: -webkit-grab; }
.grabbing { cursor: grabbing; cursor: -webkit-grabbing; }
.grabbing canvas { cursor: inherit; }
</style>
</head>
<body class>
<center>
<div id="pl"></div>

<canvas id="scene" width="1280" height="800"></canvas>

<script src="https://cdn.rawgit.com/toji/gl-matrix/master/dist/gl-matrix-min.js"></script>
<script src="lz77.js"></script>
<script src="bmd.js"></script>
<script src="nitro_tex.js"></script>
<script src="nitro_gx.js"></script>
<script src="render.js"></script>

<p>Use WASD to move around, hold shift to go faster. Click to rotate, and press B to reset the camera.</p>
<h2>Textures</h2>
<div id="textures"></div>
</body>
</html>
76 lz77.js
@@ -0,0 +1,76 @@
(function(exports) {
"use strict";

// Nintendo DS LZ77 (LZ10) format.

// Header (8 bytes):
// Magic: "LZ77\x10" (5 bytes)
// Uncompressed size (3 bytes, little endian)
// Data:
// Flags (1 byte)
// For each bit in the flags byte, from MSB to LSB:
// If flag is 1:
// LZ77 (2 bytes, little endian):
// Length: bits 0-3
// Offset: bits 4-15
// Copy Length+3 bytes from Offset back in the output buffer.
// If flag is 0:
// Literal: copy one byte from src to dest.

function assert(b) {
if (!b) XXX;
}

function readString(buffer, offs, length) {
var buf = new Uint8Array(buffer, offs, length);
var S = '';
for (var i = 0; i < length; i++) {
if (buf[i] === 0)
break;
S += String.fromCharCode(buf[i]);
}
return S;
}

var LZ77 = {};
LZ77.decompress = function(srcBuffer) {
var srcView = new DataView(srcBuffer);
assert(readString(srcBuffer, 0x00, 0x05) == 'LZ77\x10');

var uncompressedSize = srcView.getUint32(0x04, true) >> 8;
var dstBuffer = new Uint8Array(uncompressedSize);

var srcOffs = 0x08;
var dstOffs = 0x00;

while (true) {
var commandByte = srcView.getUint8(srcOffs++);
var i = 8;
while (i--) {
if (commandByte & (1 << i)) {
var tmp = srcView.getUint16(srcOffs, false);
srcOffs += 2;

var windowOffset = (tmp & 0x0FFF) + 1;
var windowLength = (tmp >> 12) + 3;

var copyOffs = dstOffs - windowOffset;

uncompressedSize -= windowLength;
while (windowLength--)
dstBuffer[dstOffs++] = dstBuffer[copyOffs++];
} else {
// Literal.
uncompressedSize--;
dstBuffer[dstOffs++] = srcView.getUint8(srcOffs++);
}

if (uncompressedSize <= 0)
return dstBuffer.buffer;
}
}
};

exports.LZ77 = LZ77;

})(window);
Oops, something went wrong.

0 comments on commit 954aa09

Please sign in to comment.
You can’t perform that action at this time.