// // nsbmd.js //-------------------- // Reads NSBMD models and any texture data within them. // by RHY3756547 // // includes: gl-matrix.js (glMatrix 2.0) // /formats/nitro.js // /formats/nsbtx.js // window.nsbmd = function(input) { var mainOff, modelData, texPalOff, materials; var mainObj = this; if (input != null) { load(input); } this.load = load; function load(input) { mainObj.hasBillboards = false; var view = new DataView(input); var header = null; var offset = 0; var tex; //nitro 3d header header = nitro.readHeader(view); if (header.stamp != "BMD0") throw "NSBMD invalid. Expected BMD0, found "+header.stamp; if (header.numSections > 2) throw "NSBMD invalid. Too many sections - should have 2 maximum."; if (header.numSections == 2) tex = new nsbtx(input.slice(header.sectionOffsets[1]), true, true); offset = header.sectionOffsets[0]; //end nitro mainOff = offset; var stamp = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3); if (stamp != "MDL0") throw "NSBMD invalid. Expected MDL0, found "+stamp; mainObj.tex = tex; modelData = nitro.read3dInfo(view, mainOff+8, modelInfoHandler); mainObj.modelData = modelData; } function modelInfoHandler(view, offset) { var mdlOff = view.getUint32(offset, true); var off = mainOff+mdlOff; var obj = readModelData(view, off); obj.nextoff = offset+4; return obj; } function readModelData(view, offset) { var head = {} head.blockSize = view.getUint32(offset, true); head.bonesOffset = offset+view.getUint32(offset+4, true); head.materialsOffset = offset+view.getUint32(offset+8, true); head.polyStartOffset = offset+view.getUint32(offset+0xC, true); head.polyEndOffset = offset+view.getUint32(offset+0x10, true); head.numObjects = view.getUint8(offset+0x17); head.numMaterials = view.getUint8(offset+0x18); head.numPolys = view.getUint8(offset+0x19); head.maxStack = view.getUint8(offset+0x1A); head.scale = view.getInt32(offset+0x1C, true)/4096; head.numVerts = view.getUint16(offset+0x24, true); head.numSurfaces = view.getUint16(offset+0x26, true); head.numTriangles = view.getUint16(offset+0x28, true); head.numQuads = view.getUint16(offset+0x2A, true); head.bboxX = view.getInt16(offset+0x2C, true)/4096; head.bboxY = view.getInt16(offset+0x2E, true)/4096; head.bboxZ = view.getInt16(offset+0x30, true)/4096; head.bboxWidth = view.getInt16(offset+0x32, true)/4096; head.bboxHeight = view.getInt16(offset+0x34, true)/4096; head.bboxDepth = view.getInt16(offset+0x36, true)/4096; //head.runtimeData = view.getUint64(offset+0x38, true); texPalOff = head.materialsOffset; //leak into local scope so it can be used by tex and pal bindings var objects = nitro.read3dInfo(view, offset+0x40, objInfoHandler); var polys = nitro.read3dInfo(view, head.polyStartOffset, polyInfoHandler); materials = nitro.read3dInfo(view, head.materialsOffset+4, matInfoHandler); 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); 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} } function parseBones(offset, view, polys, materials, objects, maxStack) { var last; var commands = []; var freeStack = maxStack; var forceID=null; var lastMat = null; while (offset 10) debugger; break; case 3: //stack id for poly (wit) forceID = view.getUint8(offset++); console.log("stackid is "+forceID); case 0: break; case 5: //i don't... what?? //holy shp! var poly = view.getUint8(offset++); polys.objectData[poly].stackID = (stackID == null)?(commands[commands.length-1].forceID):forceID; polys.objectData[poly].mat = lastMat; break; case 7: //sets object to be billboard var obj = view.getUint8(offset++); objects.objectData[obj].billboardMode = 1; 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; mainObj.hasBillboards = true; break; case 0x0b: break; //begin, not quite sure what of. doesn't seem to change anything case 0x2b: break; //end default: console.log("bone transform unknown: "+last); break; } } //if (window.throwWhatever) debugger; return commands; } function matInfoHandler(view, off, base) { var offset = texPalOff + view.getUint32(off, true); var rel = 0; /*while (rel < 40) { var flags = view.getUint16(offset+rel, true); if ((flags&15)==15) console.log("rel at "+rel); rel += 2; }*/ 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. //scale starts at 44; var mat; offset += 44; switch ((flags>>14) & 0x03) { //texture scaling mode case 0: mat = mat3.create(); //no scale break; case 1: mat = mat3.create(); mat3.scale(mat, mat, [view.getInt32(offset, true)/4096, view.getInt32(offset+4, true)/4096]); //mat3.translate(mat, mat, [-anim.translateS[(texFrame>>anim.frameStep.translateS)%anim.translateS.length], anim.translateT[(texFrame>>anim.frameStep.translateT)%anim.translateT.length]]) //for some mystery reason I need to negate the S translation break; case 2: case 3: mat = mat3.create(); //custom tex mat alert("custom"); for (var i=0; i<16; i++) { mat[i] = view.getInt32(offset, true)/4096; offset += 4; } } var cullMode = ((polyAttrib>>6)&3); var alpha = ((polyAttrib>>16)&31)/31; if (alpha == 0) alpha = 1; return { height: 8 << ((flags>>7)&7), width: 8 << ((flags>>4)&7), repeatX: flags&1, repeatY: (flags>>1)&1, flipX: (flags>>2)&1, flipY: (flags>>3)&1, texMat: mat, alpha: alpha, cullMode: cullMode, nextoff: off + 4 } } function texInfoHandler(view, off, base, ind) { var oDat = texPalOff+view.getUint16(off, true); //contains offset to array of materials to bind to var num = view.getUint8(off+2, true); var mats = []; for (var i=0; i>4)&15; neg = (flag>>8)&15; A = view.getInt16(offset+0x4, true)/4096; B = view.getInt16(offset+0x6, true)/4096; pivot[mode] = (neg&1)?-1:1; var horiz = mode%3; var vert = Math.floor(mode/3) var left = (horiz==0)?1:0; var top = ((vert==0)?1:0)*3; var right = (horiz==2)?1:2; var btm = ((vert==2)?1:2)*3; pivot[left+top] = A; pivot[right+top] = B; pivot[left+btm] = (neg&2)?-B:B; pivot[right+btm] = (neg&4)?-A:A; offset += 4; } else { pivot = mat3.create() } var scale = vec3.create(); if (!(flag&4)) { scale[0] = view.getInt32(offset+0x4, true)/4096; scale[1] = view.getInt32(offset+0x8, true)/4096; scale[2] = view.getInt32(offset+0xC, true)/4096; offset += 0xC; } else { scale[0] = 1; scale[1] = 1; scale[2] = 1; } if ((!(flag&8)) && (!(flag&2))) { //rotate matrix, replaces pivot pivot[0] = rotTerm1; pivot[1] = view.getInt16(offset+0x4, true)/4096; pivot[2] = view.getInt16(offset+0x6, true)/4096; pivot[3] = view.getInt16(offset+0x8, true)/4096; pivot[4] = view.getInt16(offset+0xA, true)/4096; pivot[5] = view.getInt16(offset+0xC, true)/4096; pivot[6] = view.getInt16(offset+0xE, true)/4096; pivot[7] = view.getInt16(offset+0x10, true)/4096; pivot[8] = view.getInt16(offset+0x12, true)/4096; offset += 16; } var mat = mat4.create(); mat4.translate(mat, mat, translate); mat4.multiply(mat, mat, mat4FromMat3(pivot)); mat4.scale(mat, mat, scale); return { translate: translate, pivot: pivot, pA: A, pB: B, pMode: mode, pNeg: neg, scale: scale, flag: flag, mat: mat, billboardMode: 0, nextoff: off + 4 } } function mat4FromMat3(mat) { dest = mat4.create(); dest[0] = mat[0]; dest[1] = mat[1]; dest[2] = mat[2]; dest[3] = 0; dest[4] = mat[3]; dest[5] = mat[4]; dest[6] = mat[5]; dest[7] = 0; dest[8] = mat[6]; dest[9] = mat[7]; dest[10] = mat[8]; dest[11] = 0; dest[12] = 0; dest[13] = 0; dest[14] = 0; dest[15] = 1; return dest; } function readChar(view, offset) { return String.fromCharCode(view.getUint8(offset)); } }