Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
magcius committed Oct 6, 2016
1 parent 310f40c commit 954aa09
Show file tree
Hide file tree
Showing 54 changed files with 1,322 additions and 0 deletions.
205 changes: 205 additions & 0 deletions 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 added exk/battan_king_map_all.bmd
Binary file not shown.
Binary file added exk/bombhei_map_all.bmd
Binary file not shown.
Binary file added exk/castle_1f_all.bmd
Binary file not shown.
Binary file added exk/castle_2f_all.bmd
Binary file not shown.
Binary file added exk/castle_b1_all.bmd
Binary file not shown.
Binary file added exk/cave_all.bmd
Binary file not shown.
Binary file added exk/clock_tower_all.bmd
Binary file not shown.
Binary file added exk/desert_land_all.bmd
Binary file not shown.
Binary file added exk/desert_py_all.bmd
Binary file not shown.
Binary file added exk/ex_l_map_all.bmd
Binary file not shown.
Binary file added exk/ex_luigi_all.bmd
Binary file not shown.
Binary file added exk/ex_m_map_all.bmd
Binary file not shown.
Binary file added exk/ex_mario_all.bmd
Binary file not shown.
Binary file added exk/ex_w_map_all.bmd
Binary file not shown.
Binary file added exk/ex_wario_all.bmd
Binary file not shown.
Binary file added exk/fire_land_all.bmd
Binary file not shown.
Binary file added exk/fire_mt_all.bmd
Binary file not shown.
Binary file added exk/habatake_all.bmd
Binary file not shown.
Binary file added exk/high_mt_all.bmd
Binary file not shown.
Binary file added exk/high_slider_all.bmd
Binary file not shown.
Binary file added exk/horisoko_all.bmd
Binary file not shown.
Binary file added exk/kaizoku_irie_all.bmd
Binary file not shown.
Binary file added exk/kaizoku_ship_all.bmd
Binary file not shown.
Binary file added exk/koopa1_boss_all.bmd
Binary file not shown.
Binary file added exk/koopa1_map_all.bmd
Binary file not shown.
Binary file added exk/koopa2_boss_all.bmd
Binary file not shown.
Binary file added exk/koopa2_map_all.bmd
Binary file not shown.
Binary file added exk/koopa3_boss_all.bmd
Binary file not shown.
Binary file added exk/koopa3_map_all.bmd
Binary file not shown.
Binary file added exk/main_castle_all.bmd
Binary file not shown.
Binary file added exk/main_garden_all.bmd
Binary file not shown.
Binary file added exk/metal_switch_all.bmd
Binary file not shown.
Binary file added exk/playroom_all.bmd
Binary file not shown.
Binary file added exk/rainbow_cruise_all.bmd
Binary file not shown.
Binary file added exk/rainbow_mario_all.bmd
Binary file not shown.
Binary file added exk/snow_kama_all.bmd
Binary file not shown.
Binary file added exk/snow_land_all.bmd
Binary file not shown.
Binary file added exk/snow_mt_all.bmd
Binary file not shown.
Binary file added exk/snow_slider_all.bmd
Binary file not shown.
Binary file added exk/suisou_all.bmd
Binary file not shown.
Binary file added exk/teresa_house_all.bmd
Binary file not shown.
Binary file added exk/test_map_all.bmd
Binary file not shown.
Binary file added exk/test_map_b_all.bmd
Binary file not shown.
Binary file added exk/tibi_deka_d_all.bmd
Binary file not shown.
Binary file added exk/tibi_deka_in_all.bmd
Binary file not shown.
Binary file added exk/tibi_deka_t_all.bmd
Binary file not shown.
Binary file added exk/water_city_all.bmd
Binary file not shown.
Binary file added exk/water_land_all.bmd
Binary file not shown.
30 changes: 30 additions & 0 deletions index.html
@@ -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 changes: 76 additions & 0 deletions 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);

0 comments on commit 954aa09

Please sign in to comment.