All courses now render properly. WIP items (shell, banana, fake item)

This commit is contained in:
riperiperi committed 2019-06-09 23:50:55 +01:00
1 parent e010627b5d
commit ff21142744
45 files changed
+3400 -1193

No files matched your search

+156
View File
@@ -0,0 +1,156 @@
//
// ncer.js
//--------------------
// Loads ncer files and provides a variety of functions for accessing and using the data.
// Cell data for nitro 2d graphics. Multiple images made out of multiple cells, sharing an input palette and ncgr.
// by RHY3756547
//
window.ncer = function(input) {
var dimensions = [ //indexed by width, then height
[[8,8], [16,8], [8,16]],
[[16,16], [32,8], [8,32]],
[[32,32], [32,16], [16,32]],
[[64,64], [64,32], [32,64]],
]
var mainOff;
var t = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "RECN") throw "NCER invalid. Expected RECN, found "+header.stamp;
if (header.numSections < 1 || header.numSections > 3) throw "NCER invalid. Too many sections - should have 1-3.";
//end nitro
t.sectionOffsets = header.sectionOffsets;
t.sectionOffsets[0] = 0x18;
mainOff = offset;
t.cebk = loadCEBK(view);
/* ignore for now
t.labl = loadLABL(view);
t.uext = loadUEXT(view); //external data?
*/
}
function getSize(shape, size, double) {
var dim = dimensions[size][shape];
if (double) return [dim[0]*2, dim[1]*2];
return [dim[0], dim[1]];
}
function readOAM(view, offset) {
//see ds docs. really, any of them?
var obj0 = view.getUint16(offset, true);
var obj1 = view.getUint16(offset + 2, true);
var obj2 = view.getUint16(offset + 4, true);
var rsFlag = (obj0 & 0x100) > 0;
var x = obj1 & 0x01FF;
var y = obj0 & 0xFF;
var result = {
y: (y > 0x80) ? y-0x100 : y,
rsFlag: rsFlag,
disable: (!rsFlag && (obj0 & 0x200) > 0),
doubleSize: (rsFlag && (obj0 & 0x200) > 0),
objMode: (obj0 >> 10) & 0x3,
mosaic: (obj0 & 0x1000) > 0,
depth: (obj0 & 0x2000) > 0,
shape: (obj0 >> 14) & 0x3, //used in combination with size to determine final x+y tile size.
x: (x > 0x100) ? x-0x200 : x,
xFlip: (!rsFlag && (obj1 & 0x1000) > 0),
yFlip: (!rsFlag && (obj1 & 0x2000) > 0),
selectParam: rsFlag ? ((obj1 >> 9) & 0x1F) : 0,
size: (obj1 >> 14) & 0x3,
tileOffset: obj2 & 0x03FF,
priority: (obj2 >> 10) & 3,
pal: (obj2 >> 12) & 0xF,
}
result.size = getSize(result.shape, result.size, result.doubleSize);
return result;
}
function loadCEBK(view) { //cell bank
var offset = t.sectionOffsets[0] - 8;
var cebk = {};
cebk.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (cebk.type != "KBEC") throw "NCER invalid. Expected KBEC, found "+cebk.type;
cebk.blockSize = view.getUint32(offset+0x4, true);
cebk.imageCount = view.getUint16(offset+0x8, true);
cebk.bankType = view.getUint16(offset+0xA, true); //type 1 has additional fields
cebk.unknown = view.getUint32(offset+0xC, true); //always 0x12
cebk.boundarySize = view.getUint32(offset+0x10, true) * 64; //area in which the image can be drawn (pixel height AND width)
cebk.partitionDataOffset = view.getUint32(offset+0x14, true);
//pad 0x18
//pad 0x1C
cebk.images = [];
offset += 0x20;
var tableEnd = offset + cebk.imageCount * (8 + cebk.bankType * 8);
for (var i=0; i<cebk.imageCount; i++) {
var image = {};
image.numCells = view.getUint16(offset, true);
image.readOnlyInfo = view.getUint16(offset + 0x2, true);
image.offset = view.getInt32(offset + 0x4, true);
offset += 0x8;
if (cebk.bankType == 1) {
image.xMax = view.getInt16(offset, true);
image.yMax = view.getInt16(offset+2, true);
image.xMin = view.getInt16(offset+4, true);
image.yMin = view.getInt16(offset+6, true);
offset += 0x8;
}
var offset2 = tableEnd + image.offset;
image.cells = [];
for (var j=0; j<image.numCells; j++) {
var cell = readOAM(view, offset2);
offset2 += 6;
image.cells.push(cell);
}
cebk.images.push(image);
}
if (cebk.partitionDataOffset != 0) { //not sure what this does, just that it's here
var pOff = t.sectionOffsets[0] + cebk.partitionDataOffset;
cebk.maxPartitionSize = view.getUint32(pOff, true);
cebk.firstOffset = view.getUint32(pOff+4, true);
pOff += cebk.firstOffset;
for (var i=0; i<cebk.imageCount; i++) {
cebk.images[i].partitionOffset = view.getUint32(pOff, true);
cebk.images[i].partitionSize = view.getUint32(pOff+4, true);
pOff += 8;
}
}
return cebk;
}
function readPalColour(view, ind) {
var col = view.getUint16(ind, true);
var f = 255/31;
return [Math.round((col&31)*f), Math.round(((col>>5)&31)*f), Math.round(((col>>10)&31)*f), 255];
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+100
View File
@@ -0,0 +1,100 @@
//
// ncgr.js
//--------------------
// Loads ncgr files and provides a variety of functions for accessing and using the data.
// "Graphics Resource", as in tile data. Usually rendered in conjunction with Palette (nclr) and Cell (ncer) / screen (nscr) data.
// by RHY3756547
//
window.ncgr = function(input) {
var mainOff;
var t = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "RGCN") throw "NCGR invalid. Expected RGCN, found "+header.stamp;
if (header.numSections < 1 || header.numSections > 2) throw "NCGR invalid. Too many sections - should have 2.";
offset = header.sectionOffsets[0];
//end nitro
t.sectionOffsets = header.sectionOffsets;
t.sectionOffsets[0] = 0x18;
mainOff = offset;
t.char = loadCHAR(view);
if (header.numSections > 1) t.cpos = loadCPOS(view);
}
function loadCHAR(view) {
var offset = t.sectionOffsets[0] - 8;
var char = {};
char.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (char.type != "RAHC") throw "NCGR invalid. Expected RAHC, found "+char.type;
char.blockSize = view.getUint32(offset+0x4, true);
t.sectionOffsets[1] = t.sectionOffsets[0] + char.blockSize;
char.tilesY = view.getUint16(offset+0x8, true); //(tiles y)
char.tilesX = view.getUint16(offset+0xA, true); //(tiles x)
char.bitDepth = view.getUint32(offset+0xC, true); //3 - 4bits, 4 - 8bits
//pad 0x10
char.tiledFlag = view.getUint32(offset+0x14, true);
char.tileDataSize = view.getUint32(offset+0x18, true);
char.unknown = view.getUint32(offset+0x1C, true); //usually 24
offset += 0x20;
//tiles are 8 or 4 bit index to pal data
//64 pixels per tile (8*8)
var tileCount = (char.blockSize-0x20) / ((char.bitDepth == 4) ? 64 : 32);
char.tiles = [];
for (var i=0; i<tileCount; i++) {
var tile = [];
if (char.bitDepth == 4) {
//easy, just read 1024 bytes
for (var j=0; j<64; j++) tile.push(view.getUint8(offset++));
} else {
for (var j=0; j<32; j++) {
var dat = view.getUint8(offset++);
tile.push(dat&0xF);
tile.push(dat>>4);
}
}
char.tiles.push(tile);
}
return char;
}
function loadCPOS(view) { //palette count map, supposedly. maps each palette to an ID
var offset = t.sectionOffsets[1] - 8;
var cpos = {};
cpos.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (cpos.type != "SOPC") throw "NCLR invalid. Expected SOPC, found "+stamp;
cpos.blockSize = view.getUint32(offset+0x4, true);
//padding 0x8
cpos.tileSize = view.getUint16(offset+0xC, true); //always 32
cpos.tileCount = view.getUint16(offset+0xE, true);
return cpos;
}
function readPalColour(view, ind) {
var col = view.getUint16(ind, true);
var f = 255/31;
return [Math.round((col&31)*f), Math.round(((col>>5)&31)*f), Math.round(((col>>10)&31)*f), 255];
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+102
View File
@@ -0,0 +1,102 @@
//
// nclr.js
//--------------------
// Loads nclr files and provides a variety of functions for accessing and using the data.
// Palette information for nitro 2d graphics.
// by RHY3756547
//
window.nclr = function(input) {
var mainOff;
var t = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "RLCN") throw "NCLR invalid. Expected RLCN, found "+header.stamp;
if (header.numSections < 1 || header.numSections > 2) throw "NCLR invalid. Too many sections - should have 2.";
offset = header.sectionOffsets[0];
//end nitro
t.sectionOffsets = header.sectionOffsets;
t.sectionOffsets[0] = 0x18;
mainOff = offset;
t.pltt = loadPLTT(view);
if (header.numSections > 1) t.pcmp = loadPCMP(view);
}
function loadPLTT(view) {
var offset = t.sectionOffsets[0] - 8;
var pltt = {};
pltt.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (pltt.type != "TTLP") throw "NCLR invalid. Expected TTLP, found "+pltt.type;
pltt.blockSize = view.getUint32(offset+0x4, true);
t.sectionOffsets[1] = t.sectionOffsets[0] + pltt.blockSize;
pltt.bitDepth = view.getUint32(offset+0x8, true); //3 -> 4bit, 4 -> 8bit
pltt.padding = view.getUint32(offset+0xC, true);
pltt.palEntries = view.getUint32(offset+0x10, true) / 2; //stored in bytes, 2 bytes per col. seems to be wrong sometimes? (8bit mode, padding as 1)
pltt.colorsPerPal = view.getUint32(offset+0x14, true); //usually 16
//16-bit pallete data
//XBBBBBGGGGGRRRRR
var colsPerPal = (pltt.bitDepth == 4)?256:16;
var realPalCount = (pltt.blockSize - 0x18) / 2;
offset += 0x18;
pltt.palettes = [];
var curPal = [];
for (var i=0; i<realPalCount; i++) {
curPal.push(readPalColour(view, offset));
if (curPal.length >= colsPerPal) {
pltt.palettes.push(curPal);
curPal = [];
}
offset += 2;
}
if (curPal.length > 0) pltt.palettes.push(curPal);
return pltt;
}
function loadPCMP(view) { //palette count map, supposedly. maps each palette to an ID
var offset = t.sectionOffsets[1] - 8;
var pcmp = {};
pcmp.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (pcmp.type != "PMCP") throw "NCLR invalid. Expected PMCP, found "+stamp;
pcmp.blockSize = view.getUint32(offset+0x4, true);
pcmp.palCount = view.getUint16(offset+0x8, true);
//unknown 16: 0?
pcmp.unknown = view.getUint32(offset+0xC, true);
offset += 0x10;
var palIDs = [];
for (var i=0; i<pcmp.palCount; i++) {
palIDs.push(view.getUint16(offset, true));
offset += 2;
}
return pcmp;
}
function readPalColour(view, ind) {
var col = view.getUint16(ind, true);
var f = 255/31;
return [Math.round((col&31)*f), Math.round(((col>>5)&31)*f), Math.round(((col>>10)&31)*f), 255];
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
+78
View File
@@ -0,0 +1,78 @@
//
// nscr.js
//--------------------
// Loads nscr files and provides a variety of functions for accessing and using the data.
// Screen data for nitro 2d graphics. Each cell references a graphic (ncgr) and palette (nclr).
// by RHY3756547
//
window.nscr = function(input) {
var mainOff;
var t = this;
if (input != null) {
load(input);
}
this.load = load;
function load(input) {
var view = new DataView(input);
var header = null;
var offset = 0;
var tex;
//nitro 3d header
header = nitro.readHeader(view);
if (header.stamp != "RCSN") throw "NSCR invalid. Expected RCSN, found "+header.stamp;
if (header.numSections != 1) throw "NSCR invalid. Too many sections - should have 1.";
offset = header.sectionOffsets[0];
//end nitro
t.sectionOffsets = header.sectionOffsets;
t.sectionOffsets[0] = 0x18;
mainOff = offset;
t.scrn = loadSCRN(view);
}
function loadSCRN(view) {
var offset = t.sectionOffsets[0] - 8;
var scrn = {};
scrn.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
if (scrn.type != "NRCS") throw "SCRN invalid. Expected NRCS, found "+scrn.type;
scrn.blockSize = view.getUint32(offset+0x4, true);
t.sectionOffsets[1] = t.sectionOffsets[0] + scrn.blockSize;
scrn.screenWidth = view.getUint16(offset+0x8, true); //in pixels
scrn.screenHeight = view.getUint16(offset+0xA, true);
scrn.padding = view.getUint32(offset+0xC, true); //always 0
scrn.screenDataSize = view.getUint32(offset+0x10, true);
offset += 0x14;
var entries = (scrn.blockSize - 0x14)/2;
scrn.data = [];
for (var i=0; i<entries; i++) {
scrn.data.push(view.getUint16(offset, true));
offset += 2;
}
return scrn;
/*
Format is (YYYYXXNNNNNNNNNN)
Y4 Palette Number
X2 Transformation (YFlip/XFlip)
N10 Tile Number
*/
}
function readPalColour(view, ind) {
var col = view.getUint16(ind, true);
var f = 255/31;
return [Math.round((col&31)*f), Math.round(((col>>5)&31)*f), Math.round(((col>>10)&31)*f), 255];
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
}
-1
View File
@@ -42,7 +42,6 @@ window.kartoffsetdata = function(input) {
var pos = vec3.create();
pos[0] = view.getInt32(off, true)/4096;
pos[1] = view.getInt32(off+4, true)/4096;
console.log("charPos: "+pos[1]);
pos[2] = view.getInt32(off+8, true)/4096;
off += 12;
chars.push(pos);
+8 -7
View File
@@ -84,7 +84,7 @@ window.kcl = function(input, mkwii) {
while (offset < octreeOffset) {
planes.push(new Plane(view, offset));
offset += 0x10;
var vert = planes[planes.length-1].Vertex1;
var vert = planes[planes.length-1].Vertices[0];
if (vert[0] < minx) minx=vert[0];
if (vert[0] > maxx) maxx=vert[0];
if (vert[2] < minz) minz=vert[2];
@@ -177,9 +177,9 @@ window.kcl = function(input, mkwii) {
var plane = planes[i];
ctx.beginPath();
ctx.moveTo(plane.Vertex1[0], plane.Vertex1[2]);
ctx.lineTo(plane.Vertex2[0], plane.Vertex2[2]);
ctx.lineTo(plane.Vertex3[0], plane.Vertex3[2]);
ctx.moveTo(plane.Vertices[0][0], plane.Vertices[0][2]);
ctx.lineTo(plane.Vertices[1][0], plane.Vertices[1][2]);
ctx.lineTo(plane.Vertices[2][0], plane.Vertices[2][2]);
ctx.closePath();
ctx.stroke();
@@ -253,7 +253,8 @@ window.kcl = function(input, mkwii) {
function Plane(view, offset) {
this.Len = readBigDec(view, offset, mkwiiMode);
this.Vertex1 = readVert(view.getUint16(offset+0x4, end), view);
this.Vertices = [];
this.Vertices[0] = readVert(view.getUint16(offset+0x4, end), view);
this.Normal = readNormal(view.getUint16(offset+0x6, end), view);
this.NormalA = readNormal(view.getUint16(offset+0x8, end), view);
this.NormalB = readNormal(view.getUint16(offset+0xA, end), view);
@@ -263,8 +264,8 @@ window.kcl = function(input, mkwii) {
var crossA = vec3.cross(vec3.create(), this.NormalA, this.Normal);
var crossB = vec3.cross(vec3.create(), this.NormalB, this.Normal);
this.Vertex2 = vec3.scaleAndAdd(vec3.create(), this.Vertex1, crossB, (this.Len/vec3.dot(crossB, this.NormalC)));
this.Vertex3 = vec3.scaleAndAdd(vec3.create(), this.Vertex1, crossA, (this.Len/vec3.dot(crossA, this.NormalC)));
this.Vertices[1] = vec3.scaleAndAdd(vec3.create(), this.Vertices[0], crossB, (this.Len/vec3.dot(crossB, this.NormalC)));
this.Vertices[2] = vec3.scaleAndAdd(vec3.create(), this.Vertices[0], crossA, (this.Len/vec3.dot(crossA, this.NormalC)));
}
function readVert(num, view) {
+40 -2
View File
@@ -5,10 +5,34 @@
// by RHY3756547
//
// for reading from multiple narcs as one. (eg. Race.narc, Race_us.narc)
window.narcGroup = function(files) {
this.getFile = getFile;
this.list = list;
function getFile(name) {
for (var i=0; i<files.length; i++) {
var file = files[i].getFile(name);
if (file != null) return file;
}
console.error("File not found: "+name);
return null;
}
function list() {
var result = [];
for (var i=0; i<files.length; i++) {
files[i].list(result);
}
return result;
}
}
window.narc = function(input) {
this.load = load;
this.getFile = getFile;
this.list = list;
var arc = this;
var handlers = [];
@@ -18,8 +42,6 @@ window.narc = function(input) {
mouseY = evt.pageY;
}
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(buffer) {
arc.buffer = buffer; //we will use this data in the future.
@@ -84,6 +106,22 @@ window.narc = function(input) {
return null; //incomplete path; we ended on a directory, not a file!
}
function list(files, curDir, path) {
path = path || "/";
files = files || [];
var table = arc.sections["BTNF"].directories;
curDir = curDir || table[0].entries; //root
for (var i=0; i<curDir.length; i++) {
if (curDir[i].dir) {
list(files, table[curDir[i].id-0xF000].entries, path+curDir[i].name+"/");
} else {
files.push(path + curDir[i].name);
}
}
return files;
}
function readFileWithID(id) {
var table = arc.sections["BTAF"].files;
var file = table[id];
+17
View File
@@ -8,6 +8,7 @@
window.ndsFS = function(input) {
this.load = load;
this.getFile = getFile;
this.list = list;
var arc = this;
var handlers = [];
@@ -65,6 +66,22 @@ window.ndsFS = function(input) {
return null; //incomplete path; we ended on a directory, not a file!
}
function list(files, curDir, path) {
path = path || "/";
files = files || [];
var table = arc.sections["BTNF"].directories;
curDir = curDir || table[0].entries; //root
for (var i=0; i<curDir.length; i++) {
if (curDir[i].dir) {
list(files, table[curDir[i].id-0xF000].entries, path+curDir[i].name+"/");
} else {
files.push(path + curDir[i].name);
}
}
return files;
}
function readFileWithID(id) {
var off = arc.fileOff+id*8;
/*var table = arc.sections["BTAF"].files;
+247 -3
View File
@@ -11,13 +11,17 @@
window.nftr = function(input) {
var mainOff;
var mainObj = this;
var t = this;
this.info = {};
if (input != null) {
load(input);
}
this.load = load;
this.drawToCanvas = drawToCanvas;
this.measureText = measureText;
this.measureMapped = measureMapped;
this.mapText = mapText;
function load(input) {
var view = new DataView(input);
@@ -29,9 +33,249 @@ window.nftr = function(input) {
header = nitro.readHeader(view);
//debugger;
if (header.stamp != "RTFN") throw "NFTR invalid. Expected RTFN, found "+header.stamp;
offset = header.sectionOffsets[0];
offset = 0x10; //nitro header for nftr doesn't have section offsets - they are in order
//end nitro
var info = t.info;
info.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
info.blockSize = view.getUint32(offset+0x4, true);
info.unknown1 = view.getUint8(offset+0x8);
info.height = view.getUint8(offset+0x9);
info.nullCharIndex = view.getUint16(offset+0xA, true);
info.unknown2 = view.getUint8(offset+0xC);
info.width = view.getUint8(offset+0xD);
info.widthBis = view.getUint8(offset+0xE);
info.encoding = view.getUint8(offset+0xF);
info.offsetCGLP = view.getUint32(offset+0x10, true); //character graphics
info.offsetCWDH = view.getUint32(offset+0x14, true); //character width
info.offsetCMAP = view.getUint32(offset+0x18, true); //character map
if (info.blockSize == 0x20) {
//extra info
info.fontHeight = view.getUint8(offset+0x1C);
info.fontWidth = view.getUint8(offset+0x1D);
info.bearingX = view.getUint8(offset+0x1E);
info.bearingY = view.getUint8(offset+0x1F);
}
loadCGLP(view);
loadCWDH(view);
loadCMAP(view);
mainOff = offset;
}
function loadCGLP(view) {
var offset = t.info.offsetCGLP - 8;
var cglp = {};
cglp.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
cglp.blockSize = view.getUint32(offset+0x4, true);
cglp.tileWidth = view.getUint8(offset+0x8);
cglp.tileHeight = view.getUint8(offset+0x9);
cglp.tileLength = view.getUint16(offset+0xA, true);
cglp.unknown = view.getUint16(offset+0xC, true);
cglp.depth = view.getUint8(offset+0xE);
cglp.rotateMode = view.getUint8(offset+0xF);
offset += 0x10;
cglp.tiles = [];
var total = (cglp.blockSize - 0x10) / cglp.tileLength;
for (var i=0; i<total; i++) {
cglp.tiles.push(new Uint8Array(view.buffer.slice(offset, offset+cglp.tileLength)));
offset += cglp.tileLength;
}
t.cglp = cglp;
}
function loadCWDH(view) {
var offset = t.info.offsetCWDH - 8;
var cwdh = {};
cwdh.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
cwdh.blockSize = view.getUint32(offset+0x4, true);
cwdh.firstCode = view.getUint16(offset+0x8, true);
cwdh.lastCode = view.getUint16(offset+0xA, true);
cwdh.unknown = view.getUint32(offset+0xC, true);
cwdh.info = [];
offset += 0x10;
for (var i=0; i<t.cglp.tiles.length; i++) {
var info = {};
info.pixelStart = view.getInt8(offset);
info.pixelWidth = view.getUint8(offset+1);
info.pixelLength = view.getUint8(offset+2);
cwdh.info.push(info);
offset += 3;
}
t.cwdh = cwdh;
}
function loadCMAP(view) {
var offset = t.info.offsetCMAP - 8;
var cmaps = [];
var charMap = {};
while (offset > 0 && offset < view.byteLength) {
var cmap = {};
cmap.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
cmap.blockSize = view.getUint32(offset+0x4, true);
cmap.firstChar = view.getUint16(offset+0x8, true);
cmap.lastChar = view.getUint16(offset+0xA, true);
cmap.typeSection = view.getUint32(offset+0xC, true);
cmap.nextOffset = view.getUint32(offset+0x10, true);
offset += 0x14;
switch (cmap.typeSection & 0xFFFF) {
case 1: //char code list (first to last)
cmap.charCodes = [];
var total = (cmap.lastChar - cmap.firstChar) + 1;
var charCode = cmap.firstChar;
for (var i=0; i<total; i++) {
var char = view.getUint16(offset, true);
cmap.charCodes.push(char);
if (char != 65535) {
charMap[String.fromCharCode(charCode)] = char;
}
charCode++;
offset += 2;
}
break;
case 2: //char code map
cmap.numChars = view.getUint16(offset, true);
offset += 2;
cmap.charMap = [];
for (var i=0; i<cmap.numChars; i++) {
var charCode = view.getUint16(offset, true);
var char = view.getUint16(offset+2, true);
cmap.charMap.push([charCode, char]);
charMap[String.fromCharCode(charCode)] = char;
offset += 4;
}
break;
default:
cmap.firstCharCode = view.getUint16(offset, true);
var total = (cmap.lastChar - cmap.firstChar) + 1;
var charCode = cmap.firstChar;
var char = cmap.firstCharCode;
for (var i=0; i<total; i++) {
charMap[String.fromCharCode(charCode++)] = char++;
}
break;
}
cmaps.push(cmap);
offset = cmap.nextOffset - 8;
}
t.charMap = charMap;
t.cmaps = cmaps;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
// RENDERING FUNCTIONS
function mapText(text, missing) {
if (missing == null) missing = "*";
var map = t.charMap;
var result = [];
for (var i=0; i<text.length; i++) {
var code = text[i];
var mapped = map[code];
if (mapped == null) mapped = map[missing];
result.push(mapped);
}
return result;
}
function measureText(text, missing, spacing) {
return measureMapped(mapText(text, missing), spacing);
}
function measureMapped(mapped, spacing) {
if (spacing == null) spacing = 1;
var width = 0;
var widths = t.cwdh.info;
for (var i=0; i<mapped.length; i++) {
width += widths[mapped[i]].pixelLength + spacing; // pixelWidth is the width of drawn section - length is how wide the char is
}
return [width, t.info.height];
}
function drawToCanvas(text, palette, spacing) {
if (spacing == null) spacing = 1;
var mapped = mapText(text, "");
var size = measureMapped(mapped, spacing);
var canvas = document.createElement("canvas");
canvas.width = size[0];
canvas.height = size[1];
var ctx = canvas.getContext("2d");
//draw chars
var widths = t.cwdh.info;
var position = 0;
for (var i=0; i<mapped.length; i++) {
var c = mapped[i];
var cinfo = widths[c];
var data = getCharData(c, palette);
ctx.putImageData(data, position + cinfo.pixelStart, 0, 0, 0, cinfo.pixelWidth, data.height);
position += cinfo.pixelLength + spacing;
}
return canvas;
}
function getCharData(id, pal) {
//todo: cache?
var cglp = t.cglp;
var tile = cglp.tiles[id];
var pixels = cglp.tileWidth*cglp.tileHeight;
var d = new Uint8ClampedArray(pixels*4);
var data = new ImageData(d, cglp.tileWidth, cglp.tileHeight);
var depth = t.cglp.depth;
var mask = (1<<depth)-1;
var bit = 8;
var byte = 0;
var curByte = tile[byte];
var ind = 0;
for (var i=0; i<pixels; i++) {
bit -= depth;
var pind = 0;
if (bit < 0) {
//overlap into next
var end = bit + 8;
if (end < 8) {
//still some left in this byte
pind = (curByte << (-bit)) & mask;
}
curByte = tile[++byte];
bit += 8;
pind |= (curByte >> (bit)) & mask;
} else {
pind = (curByte >> (bit)) & mask;
}
var col = pal[pind];
d[ind++] = col[0];
d[ind++] = col[1];
d[ind++] = col[2];
d[ind++] = col[3];
}
return data;
}
}
+1
View File
@@ -66,6 +66,7 @@ window.nitro = new function() {
for (var j=0; j<16; j++) {
name += readChar(view, offset++)
}
objectData[i].name = name;
names.push(name);
}
+19 -14
View File
@@ -15,7 +15,7 @@ window.nsbca = function(input) {
var mainOff;
var animData;
var speeds = [1.0, 0.5, 1/3];
var speeds = [1.0, 0.5, 1/4];
var mainObj = this;
if (input != null) {
@@ -72,10 +72,10 @@ window.nsbca = function(input) {
function readTrans(view, off, obj) {
var flag = view.getUint16(off, true); //--zyx-Sr-RZYX-T-
off += 4;
var transform = {};
transform.flag = flag;
if (!((flag>>1) & 1)) { //T: translation
var translate = [[], [], []]; //store translations in x,y,z arrays
@@ -96,7 +96,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
var length = Math.floor((obj.frames+extra)*inf.speed);
var length = Math.ceil((inf.endFrame-inf.startFrame)*inf.speed);
var w = (inf.halfSize)?2:4;
var off2 = obj.baseOff+inf.off;
@@ -132,7 +132,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
//florian's rotate code seems to ignore this extra value. I'll need more examples of nsbca to test this more thoroughly.
var length = Math.floor((obj.frames+extra)*inf.speed);
var length = Math.ceil((inf.endFrame-inf.startFrame)*inf.speed);
var off2 = obj.baseOff+inf.off;
try {
@@ -173,7 +173,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
var length = Math.ceil((obj.frames+extra)*inf.speed);
var length = Math.ceil((inf.endFrame-inf.startFrame)*inf.speed);
var w = ((inf.halfSize)?2:4);
var off2 = obj.baseOff+inf.off;
@@ -211,21 +211,26 @@ window.nsbca = function(input) {
};
} else {
var off2 = obj.baseOff+obj.off2+ind*10; //jump to rotation data
var d1 = view.getInt16(off2, true);
var d2 = view.getInt16(off2+2, true);
var d3 = view.getInt16(off2+4, true);
var d4 = view.getInt16(off2+6, true);
var d5 = view.getInt16(off2+8, true);
var d1 = view.getUint16(off2, true);
var d2 = view.getUint16(off2+2, true);
var d3 = view.getUint16(off2+4, true);
var d4 = view.getUint16(off2+6, true);
var d5 = view.getUint16(off2+8, true);
var i6 = ((d5&7)<<12) | ((d1&7)<<9) | ((d2&7)<<6) | ((d3&7)<<3) | ((d4&7));
if (i6&4096) i6 = (-8192)+i6;
//if (i6&4096) i6 = (-8192)+i6;
var v1 = [d1>>3, d2>>3, d3>>3]
var v2 = [d4>>3, d5>>3, i6]
var v1 = [d1>>3, d2>>3, d3>>3];
var v2 = [d4>>3, d5>>3, i6];
for (var i=0; i<3; i++) {
if (v1[i] & 4096) v1[i] -= 8192;
if (v2[i] & 4096) v2[i] -= 8192;
}
vec3.scale(v1, v1, 1/4096);
vec3.scale(v2, v2, 1/4096);
var v3 = vec3.cross([], v1, v2)
var v3 = vec3.cross([], v1, v2);
var mat = [
v1[0], v1[1], v1[2],
+72 -15
View File
@@ -70,6 +70,7 @@ window.nsbmd = function(input) {
head.maxStack = view.getUint8(offset+0x1A);
head.scale = view.getInt32(offset+0x1C, true)/4096;
head.downScale = view.getInt32(offset+0x20, true)/4096; //usually inverse of the above
head.numVerts = view.getUint16(offset+0x24, true);
head.numSurfaces = view.getUint16(offset+0x26, true);
@@ -93,6 +94,12 @@ window.nsbmd = function(input) {
var tex = nitro.read3dInfo(view, head.materialsOffset+view.getUint16(head.materialsOffset, true), texInfoHandler);
var palt = nitro.read3dInfo(view, head.materialsOffset+view.getUint16(head.materialsOffset+2, true), palInfoHandler);
//bind tex and palt names to their materials
for (var i=0; i<materials.objectData.length; i++) {
materials.objectData[i].texName = tex.names[materials.objectData[i].tex];
materials.objectData[i].palName = palt.names[materials.objectData[i].pal];
}
var commands = parseBones(head.bonesOffset, view, polys, materials, objects, head.maxStack);
return {head: head, objects: objects, polys: polys, materials: materials, tex:tex, palt:palt, commands:commands}
@@ -101,14 +108,18 @@ window.nsbmd = function(input) {
function parseBones(offset, view, polys, materials, objects, maxStack) {
var last;
var commands = [];
var debug = true;
if (debug) console.log("== Begin Parse Bones ==");
var freeStack = maxStack;
var forceID=null;
var lastMat = null;
var bound = [];
var matMap = [];
while (offset<texPalOff) { //bones
last = view.getUint8(offset++);
console.log("bone cmd: 0x"+last.toString(16));
switch (last) {
case 0x06: //bind object transforms to parent. bone exists but is not placed in the stack
var obj = view.getUint8(offset++);
@@ -117,21 +128,50 @@ window.nsbmd = function(input) {
var object = objects.objectData[obj];
object.parent = parent;
if (debug) console.log("[0x"+last.toString(16)+"] Multiply stack with object " + obj + " bound to parent " + parent);
commands.push({obj:obj, parent:parent, stackID:freeStack++});
break;
case 0x26:
case 0x46: //placed in the stack at stack id
case 0x66:
var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++);
var zero = view.getUint8(offset++);
var stackID = view.getUint8(offset++);
var restoreID = null;
if (last == 0x66) restoreID = view.getUint8(offset++);
if (last == 0x46) {
restoreID = stackID;
stackID = freeStack++;
}
var object = objects.objectData[obj];
object.parent = parent;
commands.push({obj:obj, parent:parent, stackID:((last == 0x26)?stackID:freeStack++), restoreID:((last == 0x46)?stackID:null)});
if (debug) {
var debugMessage = "[0x"+last.toString(16)+"] ";
if (restoreID != null) debugMessage += "Restore matrix at " + stackID + ", ";
debugMessage += "Multiply stack with object " + obj + " bound to parent " + parent;
if (stackID != null) debugMessage += ", store in " + stackID;
console.log(debugMessage);
}
var item = {obj:obj, parent:parent, stackID:stackID, restoreID:restoreID};
if (bound[stackID]) {
//we're updating a matrix that is already bound...
//we must move copy the old value of the matrix to another place, and update the polys that point to it to the new location.
//(does not play well with skinned meshes (they don't do this anyways), but fixes lots of "multiple object" meshes.)
console.log("!! Already bound !! Moving old matrix at " + stackID + " to " + freeStack);
var poly = polys.objectData;
for (var i=0; i<poly.length; i++) {
if (poly[i].stackID == stackID) poly[i].stackID = freeStack;
}
commands.push({copy: stackID, dest: freeStack++})
}
commands.push(item);
break;
/*
case 0x66: //has ability to restore to another stack id. no idea how this works
var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++);
@@ -142,8 +182,11 @@ window.nsbmd = function(input) {
var object = objects.objectData[obj];
object.parent = parent;
if (debug) console.log("[0x"+last.toString(16)+"] Restore matrix at " + restoreID + ", multiply stack with object " + obj + " bound to parent " + parent + ", store in " + stackID);
commands.push({obj:obj, parent:parent, stackID:stackID, restoreID:restoreID});
break;
*/
case 0x04:
case 0x24:
case 0x44: //bind material to polygon: matID, 5, polyID
@@ -151,54 +194,69 @@ window.nsbmd = function(input) {
lastMat = mat;
var five = view.getUint8(offset++); //???
var poly = view.getUint8(offset++);
polys.objectData[poly].stackID = (forceID == null)?(commands[commands.length-1].stackID):forceID;
var bindID = (forceID == null)?(commands[commands.length-1].stackID):forceID;
bound[bindID] = true;
polys.objectData[poly].stackID = bindID;
polys.objectData[poly].mat = mat;
if (debug) console.log("[0x"+last.toString(16)+"] Bind material " + mat + " to poly " + poly + " (with stack id " + polys.objectData[poly].stackID + ")");
break;
case 1:
//end of all
if (debug) console.log("[0x"+last.toString(16)+"] END OF BONES");
break;
case 2: //node visibility (maybe to implement this set matrix to 0)
var node = view.getUint8(offset++);
var vis = view.getUint8(offset++);
objects.objectData[node].vis = vis;
console.log("visibility thing "+node);
if (debug) console.log("[0x"+last.toString(16)+"] Set object " + node + " visibility: " + vis);
if (node > 10) debugger;
break;
case 3: //stack id for poly (wit)
forceID = view.getUint8(offset++);
console.log("stackid is "+forceID);
if (debug) console.log("[0x"+last.toString(16)+"] Force stack id to " + forceID);
case 0:
break;
case 5:
//i don't... what??
//holy shp!
case 5: //"draw a mesh" supposedly. might require getting a snapshot of the matrices at this point
var poly = view.getUint8(offset++);
polys.objectData[poly].stackID = (stackID == null)?(commands[commands.length-1].forceID):forceID;
var bindID = (forceID == null)?(commands[commands.length-1].stackID):forceID;
bound[bindID] = true;
polys.objectData[poly].stackID = bindID;
polys.objectData[poly].mat = lastMat;
if (debug) console.log("[0x"+last.toString(16)+"] Draw " + poly + "(stack id " + polys.objectData[poly].stackID + ")");
break;
case 7:
//sets object to be billboard
var obj = view.getUint8(offset++);
objects.objectData[obj].billboardMode = 1;
if (debug) console.log("[0x"+last.toString(16)+"] Object " + obj + " set to full billboard mode.");
mainObj.hasBillboards = true;
break;
case 8:
//sets object to be billboard around only y axis. (xz remain unchanged)
var obj = view.getUint8(offset++);
objects.objectData[obj].billboardMode = 2;
if (debug) console.log("[0x"+last.toString(16)+"] Object " + obj + " set to Y billboard mode.");
mainObj.hasBillboards = true;
break;
case 9: //skinning equ. not used?
if (debug) console.log("[0x"+last.toString(16)+"] Skinning Equation (UNIMPLEMENTED)");
debugger;
break;
case 0x0b:
break; //begin, not quite sure what of. doesn't seem to change anything
if (debug) console.log("[0x"+last.toString(16)+"] BEGIN PAIRING.");
break; //begin polygon material paring (scale up? using scale value in model..)
case 0x2b:
break; //end
if (debug) console.log("[0x"+last.toString(16)+"] END PAIRING.");
break; //end polygon material pairing (scale down? using scale(down) value in model..)
default:
console.log("bone transform unknown: "+last);
console.log("bone transform unknown: 0x"+last.toString(16));
break;
}
}
//if (window.throwWhatever) debugger;
if (debug) console.log("== End Parse Bones ==");
return commands;
}
@@ -213,7 +271,6 @@ window.nsbmd = function(input) {
}*/
var polyAttrib = view.getUint16(offset+12, true);
console.log(polyAttrib);
var flags = view.getUint16(offset+22, true); //other info in here is specular data etc.
+1
View File
@@ -120,6 +120,7 @@ window.nsbta = function(input) {
if (i == 2) obj[prop[i]].push(data2);
} else { //data is found at offset
frames = frames>>obj.frameStep[prop[i]];
if (obj.frameStep[prop[i]] > 0) frames++; //one extra frame, for interpolation
//frames -= 1;
var off = base + value-8;
for (var j=0; j<frames*((i==2)?2:1); j++) {
+37 -28
View File
@@ -38,7 +38,11 @@ window.nsbtp = function(input) {
var texTotal;
var palTotal;
var texNamesOff;
var palNamesOff;
var texNames;
var palNames;
function load(input) {
var view = new DataView(input);
@@ -96,13 +100,35 @@ window.nsbtp = function(input) {
var duration = view.getUint16(offset, true);
texTotal = view.getUint8(offset+2);
palTotal = view.getUint8(offset+3);
var unknown = view.getUint16(offset+4, true);
var unknown2 = view.getUint16(offset+6, true);
texNamesOff = view.getUint16(offset+4, true);
palNamesOff = view.getUint16(offset+6, true);
var nameOffset = matOff + texNamesOff;
texNames = [];
//read 16char tex names
for (var i=0; i<texTotal; i++) {
var name = "";
for (var j=0; j<16; j++) {
name += readChar(view, nameOffset++)
}
texNames[i] = name;
}
nameOffset = matOff + palNamesOff;
palNames = [];
//read 16char pal names
for (var i=0; i<palTotal; i++) {
var name = "";
for (var j=0; j<16; j++) {
name += readChar(view, nameOffset++)
}
palNames[i] = name;
}
//...then another nitro
var data = nitro.read3dInfo(view, offset+8, matInfoHandler);
return {data: data, nextoff: data.nextoff, texTotal:texTotal, palTotal:palTotal, unknown:unknown, unknown2:unknown2, duration:duration};
return {data: data, nextoff: data.nextoff, texTotal:texTotal, palTotal:palTotal, duration:duration, texNames: texNames, palNames: palNames};
}
function matInfoHandler(view, offset, base) {
@@ -124,9 +150,9 @@ window.nsbtp = function(input) {
// 16char texname
//then (frame of these)
// 16char palname
// texture animations are bound to the material via the name of this block.
var frames = view.getUint16(offset, true);
obj.matinfo = view.getUint16(offset+2, true);
var frames = view.getUint32(offset, true);
obj.flags = view.getUint16(offset+4, true);
var offset2 = view.getUint16(offset+6, true);
offset += 8;
@@ -135,33 +161,16 @@ window.nsbtp = function(input) {
offset = matOff + offset2;
//info and timing for each frame
for (var i=0; i<frames; i++) {
obj.frames[i] = {
var entry = {
time: view.getUint16(offset, true),
tex: view.getUint8(offset+2),
mat: view.getUint8(offset+3),
tex: view.getUint8(offset+2), //index into names?
pal: view.getUint8(offset+3), //index into pal names?
}
entry.texName = texNames[entry.tex];
entry.palName = palNames[entry.pal];
obj.frames.push(entry);
offset += 4;
}
obj.texNames = [];
//read 16char tex names
for (var i=0; i<texTotal; i++) {
var name = "";
for (var j=0; j<16; j++) {
name += readChar(view, offset++)
}
obj.texNames[i] = name;
}
obj.palNames = [];
//read 16char pal names
for (var i=0; i<palTotal; i++) {
var name = "";
for (var j=0; j<16; j++) {
name += readChar(view, offset++)
}
obj.palNames[i] = name;
}
return obj;
}
+20
View File
@@ -74,10 +74,21 @@ window.nsbtx = function(input, tex0) {
paletteInfo = nitro.read3dInfo(view, mainOff + palInfoOff, palInfoHandler);
textureInfo = nitro.read3dInfo(view, mainOff + texInfoOff, texInfoHandler);
buildNameToIndex(paletteInfo);
buildNameToIndex(textureInfo);
thisObj.paletteInfo = paletteInfo;
thisObj.textureInfo = textureInfo;
}
function buildNameToIndex(info) {
var nameToIndex = {};
for (var i=0; i<info.names.length; i++) {
nameToIndex["$" + info.names[i]] = i;
}
info.nameToIndex = nameToIndex;
}
function readTexWithPal(textureId, palId) {
var tex = textureInfo.objectData[textureId];
var pal = paletteInfo.objectData[palId];
@@ -106,6 +117,7 @@ window.nsbtx = function(input, tex0) {
var dat = texView.getUint8(off++)
col = readPalColour(palView, palOff, dat&31, trans);
col[3] = (dat>>5)*(255/7);
premultiply(col);
} else if (format == 2) { //2 bit pal
if (i%4 == 0) databuf = texView.getUint8(off++);
@@ -126,6 +138,7 @@ window.nsbtx = function(input, tex0) {
var dat = texView.getUint8(off++)
col = readPalColour(palView, palOff, dat&7, trans);
col[3] = (dat>>3)*(255/31);
premultiply(col);
} else if (format == 7) { //raw color data
col = texView.getUint16(off, true);
@@ -134,6 +147,7 @@ window.nsbtx = function(input, tex0) {
colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
colourBuffer[3] = Math.round((col>>15)*255);
col = colourBuffer;
premultiply(col);
off += 2;
} else {
@@ -146,6 +160,12 @@ window.nsbtx = function(input, tex0) {
return canvas;
}
function premultiply(col) {
col[0] *= col[3]/255;
col[1] *= col[3]/255;
col[2] *= col[3]/255;
}
function readCompressedTex(tex, pal) { //format 5, 4x4 texels. I'll keep this well documented so it's easy to understand.
var off = tex.texOffset;
var texView = new DataView(compData); //real texture data - 32 bits per 4x4 block (one byte per 4px horizontal line, each descending 1px)
+556 -547
View File
File diff suppressed because it is too large. Load diff