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

gh-pages
riperiperi 2019-06-09 23:50:55 +01:00
parent e010627b5d
commit ff21142744
45 changed files with 3400 additions and 1193 deletions

View File

@ -26,7 +26,7 @@
play.addEventListener('click', function() { play.addEventListener('click', function() {
if (last != null) nitroAudio.instaKill(last); if (last != null) nitroAudio.instaKill(last);
document.getElementById('seq').innerText = "Current SSEQ: "+i; document.getElementById('seq').innerText = "Current SSEQ: "+i;
last = nitroAudio.playSound(i++, {}, 2); last = nitroAudio.playSound(i++, {}, 0);
}) })
/* /*
var ctx = new AudioContext(); var ctx = new AudioContext();

View File

@ -1,113 +1,129 @@
// //
// nitroAudio.js // nitroAudio.js
//-------------------- //--------------------
// Provides an interface for playing nds music and sound effects. // Provides an interface for playing nds music and sound effects.
// by RHY3756547 // by RHY3756547
// //
window.AudioContext = window.AudioContext || window.webkitAudioContext; window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.nitroAudio = new (function() { window.nitroAudio = new (function() {
var t = this; var t = this;
var ctx; var ctx;
t.sounds = []; t.sounds = [];
t.tick = tick; t.tick = tick;
t.playSound = playSound; t.playSound = playSound;
t.kill = kill; t.kill = kill;
t.init = init; t.init = init;
t.instaKill = instaKill; t.instaKill = instaKill;
t.updateListener = updateListener;
t.sdat = null;
t.sdat = null;
function init(sdat) {
ctx = new AudioContext(); function init(sdat) {
t.ctx = ctx; ctx = new AudioContext();
t.ctx = ctx;
var listener = ctx.listener;
listener.dopplerFactor = 1; var listener = ctx.listener;
listener.speedOfSound = 100/1024; //343.3 listener.dopplerFactor = 1;
listener.speedOfSound = 100/1024; //343.3
SSEQWaveCache.init(sdat, ctx);
t.sdat = sdat; SSEQWaveCache.init(sdat, ctx);
} t.sdat = sdat;
}
function tick() {
for (var i=0; i<t.sounds.length; i++) { function updateListener(pos, view) {
var snd = t.sounds[i]; var listener = ctx.listener;
snd.seq.tick(); listener.positionX.value = pos[0];
if (snd.obj != null && snd.obj.soundProps != null && snd.panner != null) updatePanner(snd.panner, snd.obj.soundProps); listener.positionY.value = pos[1];
} listener.positionZ.value = pos[2];
for (var i=0; i<t.sounds.length; i++) { listener.forwardX.value = view[8];
var snd = t.sounds[i]; listener.forwardY.value = -view[9];
snd.dead = snd.seq.dead; listener.forwardZ.value = -view[10];
if (snd.dead) { listener.upX.value = view[4];
snd.gainN.disconnect(); listener.upY.value = view[5];
t.sounds.splice(i--, 1); listener.upZ.value = view[6];
} }
}
} function tick() {
for (var i=0; i<t.sounds.length; i++) {
function kill(sound) { var snd = t.sounds[i];
if (!sound.killing) { snd.seq.tick();
sound.killing = true; if (snd.obj != null && snd.obj.soundProps != null && snd.panner != null) updatePanner(snd.panner, snd.obj.soundProps);
sound.seq.kill(); }
} for (var i=0; i<t.sounds.length; i++) {
} var snd = t.sounds[i];
snd.dead = snd.seq.dead;
function instaKill(sound) { //instantly kills a sound if (snd.dead) {
if (sound == null) return; snd.gainN.disconnect();
var ind = t.sounds.indexOf(sound) t.sounds.splice(i--, 1);
sound.gainN.disconnect(); }
if (ind == -1) return; }
t.sounds.splice(ind, 1); }
}
function kill(sound) {
function playSound(seqN, params, arcN, obj) { //if arc is not specified, we just play a normal sequence. this allows 3 overloads. if (!sound.killing) {
//obj should have a property "soundProps" where it sets its falloff, position and velocity relative to the oberver occasionally sound.killing = true;
var sound = { dead: false, killing: false, obj: obj }; sound.seq.kill();
}
var output; }
if (obj != null) { //if obj is not null then we have a 3d target to assign this sound to.
output = ctx.createPanner(); function instaKill(sound) { //instantly kills a sound
sound.gainN = ctx.createGain(); if (sound == null) return;
sound.gainN.connect(ctx.destination); var ind = t.sounds.indexOf(sound)
output.connect(sound.gainN); sound.gainN.disconnect();
sound.panner = output; if (ind == -1) return;
t.sounds.splice(ind, 1);
updatePanner(sound.panner, sound.obj.soundProps); }
} else {
output = ctx.createGain(); function playSound(seqN, params, arcN, obj) { //if arc is not specified, we just play a normal sequence. this allows 3 overloads.
sound.gainN = output; //obj should have a property "soundProps" where it sets its falloff, position and velocity relative to the oberver occasionally
output.connect(ctx.destination); var sound = { dead: false, killing: false, obj: obj };
}
var output;
var player; if (obj != null) { //if obj is not null then we have a 3d target to assign this sound to.
if (arcN == null) { output = ctx.createPanner();
var seq = t.sdat.sections["$INFO"][0][seqN]; sound.gainN = ctx.createGain();
if (seq == null) return; sound.gainN.connect(ctx.destination);
sound.seq = new SSEQPlayer(seq, t.sdat, ctx, output, params); output.connect(sound.gainN);
} else { sound.panner = output;
var arc = t.sdat.sections["$INFO"][1][arcN];
if (arc == null) return; if (sound.obj.soundProps == null) sound.obj.soundProps = obj;
var seq = arc.arc.entries[seqN]; updatePanner(sound.panner, sound.obj.soundProps);
if (seq == null) return; } else {
sound.seq = new SSEQPlayer(seq, t.sdat, ctx, output, params); output = ctx.createGain();
} sound.gainN = output;
output.connect(ctx.destination);
//now that we have the player, package it in an object }
t.sounds.push(sound);
return sound; var player;
} if (arcN == null) {
var seq = t.sdat.sections["$INFO"][0][seqN];
function updatePanner(panner, soundProps) { if (seq == null) return;
if (panner == null || soundProps == null) return; sound.seq = new SSEQPlayer(seq, t.sdat, ctx, output, params);
if (soundProps.pos != null) panner.setPosition(soundProps.pos[0], soundProps.pos[1], soundProps.pos[2]); } else {
//if (soundProps.vel != null) panner.setVelocity(soundProps.vel[0], soundProps.vel[1], soundProps.vel[2]); var arc = t.sdat.sections["$INFO"][1][arcN];
if (soundProps.refDistance != null) panner.refDistance = soundProps.refDistance; if (arc == null) return;
if (soundProps.panningModel != null) panner.panningModel = soundProps.panningModel; var seq = arc.arc.entries[seqN];
if (soundProps.rolloffFactor != null) panner.rolloffFactor = soundProps.rolloffFactor; if (seq == null) return;
} sound.seq = new SSEQPlayer(seq, t.sdat, ctx, output, params);
}
//now that we have the player, package it in an object
t.sounds.push(sound);
return sound;
}
function updatePanner(panner, soundProps) {
if (panner == null || soundProps == null) return;
if (soundProps.pos != null) panner.setPosition(soundProps.pos[0], soundProps.pos[1], soundProps.pos[2]);
//if (soundProps.vel != null) panner.setVelocity(soundProps.vel[0], soundProps.vel[1], soundProps.vel[2]);
panner.refDistance = soundProps.refDistance || 192;
if (soundProps.panningModel != null) panner.panningModel = soundProps.panningModel;
panner.rolloffFactor = soundProps.rolloffFactor || 1;
}
})(); })();

View File

@ -0,0 +1,166 @@
//
// tileFlattener.js
//--------------------
// Renders screens or cells to 2d canvas. Useful for drawing UI elements from the ROM.
// by RHY3756547
//
// includes: main.js
//
window.TileFlattener = function(palette, tiles, map) {
this.palette = palette;
this.tiles = tiles;
this.map = map;
this.toCanvas = toCanvas;
this.cellMode = map.cebk != null;
var tileCache = {};
var zero = [0, 0, 0, 0];
var emptyTile = new Uint8ClampedArray(64);
function getTileImg(pal0trans, tile, pal) {
var cacheID = tile + ":" + pal;
var item = tileCache[cacheID];
if (item != null) return item;
//make the tile
var canvas = document.createElement("canvas");
canvas.width = 8;
canvas.height = 8;
var ctx = canvas.getContext("2d");
var d = new Uint8ClampedArray(8*8*4);
var data = new ImageData(d, 8, 8);
var targ = 0;
var colors = palette.pltt.palettes[pal] || [];
var tileData = tiles.char.tiles[tile] || emptyTile;
for (var i=0; i<64; i++) {
var colID = tileData[i];
var col = (pal0trans && colID == 0) ? zero : (colors[colID] || zero);
d[targ++] = col[0];
d[targ++] = col[1];
d[targ++] = col[2];
d[targ++] = col[3];
}
ctx.putImageData(data, 0, 0);
tileCache[cacheID] = canvas;
return canvas;
}
function calcImageSize(image) {
var xMin = 65536;
var yMin = 65536;
var xMax = 0;
var yMax = 0;
for (var i=0; i<image.cells.length; i++) {
var cell = image.cells[i];
var size = cell.size;
var x = cell.x + size[0];
if (x > xMax) xMax = x;
x -= size[0];
if (x < xMin) xMin = x;
var y = cell.y + size[1];
if (y > yMax) yMax = y;
y -= size[1];
if (y < yMin) yMin = y;
}
return [xMin, yMin, xMax, yMax];
}
function toCanvas(pal0trans, imageInd, palInd) {
var canvas = document.createElement("canvas");
if (this.cellMode) {
//essentially a collection of ds sprites
//render out the image the user has requested
var image = map.cebk.images[imageInd];
var isize = calcImageSize(image);
canvas.width = isize[2] - isize[0];
canvas.height = isize[3] - isize[1];
var ctx = canvas.getContext("2d");
var tileWidth = this.tiles.char.tilesX;
image.cells.sort(function(a, b){return b.priority - a.priority});
for (var i=image.cells.length-1; i>=0; i--) {
var cell = image.cells[i];
var size = cell.size;
var sx2 = size[0]/2;
var sy2 = size[1]/2;
ctx.save();
ctx.translate(cell.x + sx2 - isize[0], cell.y + sy2 - isize[1]);
ctx.scale(cell.xFlip?(-1):1, cell.yFlip?(-1):1);
var tile = cell.tileOffset;
var pal = cell.pal;
ctx.strokeStyle = "white";
ctx.strokeWidth = 1;
if (cell.disable) continue;
//draw oam sprite
var base = tile;
for (var y=0; y<size[1]; y+=8) {
for (var x=0; x<size[0]; x+=8) {
var img = getTileImg(pal0trans, tile++, pal);
ctx.drawImage(img, x-sx2, y-sy2);
}
if (tileWidth != 65535) { //when defined, wrap to the next row when drawing a lower portion of the sprite
base += tileWidth;
tile = base;
}
}
ctx.restore();
}
} else {
//screen render, very simple
var screen = map.scrn;
canvas.width = screen.screenWidth;
canvas.height = screen.screenHeight;
var ctx = canvas.getContext("2d");
var data = screen.data;
var tileWidth = (screen.screenWidth / 8);
var tileHeight = (screen.screenHeight / 8);
var x = 0;
var y = 0;
for (var i=0; i<data.length; i++) {
var info = data[i];
/*
Format is (YYYYXXNNNNNNNNNN)
Y4 Palette Number
X2 Transformation (YFlip/XFlip)
N10 Tile Number
*/
var pal = info >> 12;
var trans = (info >> 10) & 3;
var tile = info & 0x3FF;
var img = getTileImg(pal0trans, tile, pal);
var xf = (trans&1) > 0;
var yf = (trans&2) > 0;
if (xf || yf) {
//transform
ctx.save();
ctx.translate(x + 4, y + 4);
ctx.scale(xf?(-1):1, yf?(-1):1);
ctx.drawImage(img, -4, -4);
ctx.restore();
} else {
ctx.drawImage(img, x, y);
}
x += 8;
if (x >= screen.screenWidth) {
x -= screen.screenWidth;
y += 8;
}
}
}
return canvas;
}
}

View File

@ -99,13 +99,15 @@ window.MKDS_COLTYPE = new (function(){
this.STICKY = 0x0D; //sets gravity to negative this plane's normal until the object hasn't collided for a few frames. this.STICKY = 0x0D; //sets gravity to negative this plane's normal until the object hasn't collided for a few frames.
this.SMALLJUMP = 0x0E; //choco island 2's disaster ramps this.SMALLJUMP = 0x0E; //choco island 2's disaster ramps
this.CANNON = 0x0F; //activates cannon. basic effect id is the cannon to use. this.CANNON = 0x0F; //activates cannon. basic effect id is the cannon to use.
this.UNKNOWN = 0x10; //it is a mystery... this.WALLOOB = 0x10; //like a wall. normally only appears oob, but doesn't seem to have any special behaviour apart from maybe slowing you down more.
this.FALLSWATER = 0x11; //points to falls object in nkm, gets motion parameters from there. this.FALLSWATER = 0x11; //points to falls object in nkm, gets motion parameters from there.
this.BOOST2 = 0x12; this.BOOST2 = 0x12;
this.LOOP = 0x13; //like sticky but with boost applied. see rainbow road ds this.LOOP = 0x13; //like sticky but with boost applied. see rainbow road ds
this.SOUNDROAD = 0x14; this.SOUNDROAD = 0x14;
this.RR_SPECIAL_WALL = 0x15; this.RR_SPECIAL_WALL = 0x15;
this.KNOCKBACK_DAMAGE = 0x1F;
this.GROUP_ROAD = [ this.GROUP_ROAD = [
this.ROAD, this.OFFROAD1, this.OFFROAD2, this.OFFROAD3, this.OFFROAD4, this.SLIPPERY, this.BOOST, this.ROAD, this.OFFROAD1, this.OFFROAD2, this.OFFROAD3, this.OFFROAD4, this.SLIPPERY, this.BOOST,
this.JUMP_PAD, this.STICKY, this.SMALLJUMP, this.FALLSWATER, this.BOOST2, this.LOOP, this.SOUNDROAD, this.JUMP_PAD, this.STICKY, this.SMALLJUMP, this.FALLSWATER, this.BOOST2, this.LOOP, this.SOUNDROAD,
@ -117,17 +119,23 @@ window.MKDS_COLTYPE = new (function(){
this.JUMP_PAD, this.STICKY, this.SMALLJUMP, this.FALLSWATER, this.BOOST2, this.LOOP, this.SOUNDROAD, this.JUMP_PAD, this.STICKY, this.SMALLJUMP, this.FALLSWATER, this.BOOST2, this.LOOP, this.SOUNDROAD,
this.OOB, this.OFFROADMAIN, this.OOB, this.OFFROADMAIN,
this.WALL, this.WALL2, this.RR_SPECIAL_WALL this.WALL, this.WALL2, this.WALLOOB, this.RR_SPECIAL_WALL,
this.KNOCKBACK_DAMAGE
] ]
this.GROUP_WALL = [ this.GROUP_WALL = [
this.WALL, this.WALL2, this.RR_SPECIAL_WALL this.WALL, this.WALL2, this.WALLOOB, this.RR_SPECIAL_WALL, this.KNOCKBACK_DAMAGE
] ]
this.GROUP_BOOST = [ this.GROUP_BOOST = [
this.BOOST, this.BOOST2, this.LOOP this.BOOST, this.BOOST2, this.LOOP
] ]
this.GROUP_OOB = [
this.OOB, this.FALL
]
this.PHYS_MAP = new Array(31); this.PHYS_MAP = new Array(31);
this.PHYS_MAP[this.ROAD] = 0; this.PHYS_MAP[this.ROAD] = 0;
this.PHYS_MAP[this.OFFROAD3] = 2; this.PHYS_MAP[this.OFFROAD3] = 2;
@ -194,7 +202,7 @@ window.MKDS_COLTYPE = new (function(){
{drift: MKDS_COLSOUNDS.DRIFT_ASPHALT, brake: MKDS_COLSOUNDS.BRAKE, land: MKDS_COLSOUNDS.LAND_GRASS, drive: MKDS_COLSOUNDS.DRIVE_GRASS, particle: 32}, {drift: MKDS_COLSOUNDS.DRIFT_ASPHALT, brake: MKDS_COLSOUNDS.BRAKE, land: MKDS_COLSOUNDS.LAND_GRASS, drive: MKDS_COLSOUNDS.DRIVE_GRASS, particle: 32},
{drift: MKDS_COLSOUNDS.DRIFT_SAND, brake: MKDS_COLSOUNDS.BRAKE_SAND, land: MKDS_COLSOUNDS.LAND_SAND, drive: MKDS_COLSOUNDS.DRIVE_SAND, particle: 28}, {drift: MKDS_COLSOUNDS.DRIFT_SAND, brake: MKDS_COLSOUNDS.BRAKE_SAND, land: MKDS_COLSOUNDS.LAND_SAND, drive: MKDS_COLSOUNDS.DRIVE_SAND, particle: 40}, //sky garden cloud
{drift: MKDS_COLSOUNDS.DRIFT_SAND, brake: MKDS_COLSOUNDS.BRAKE_SAND, land: MKDS_COLSOUNDS.LAND_SAND, drive: MKDS_COLSOUNDS.DRIVE_SAND, particle: 28}, {drift: MKDS_COLSOUNDS.DRIFT_SAND, brake: MKDS_COLSOUNDS.BRAKE_SAND, land: MKDS_COLSOUNDS.LAND_SAND, drive: MKDS_COLSOUNDS.DRIVE_SAND, particle: 28},
{drift: MKDS_COLSOUNDS.DRIFT_ASPHALT, brake: MKDS_COLSOUNDS.BRAKE, land: MKDS_COLSOUNDS.LAND_SNOW, particle:112}, //snow {drift: MKDS_COLSOUNDS.DRIFT_ASPHALT, brake: MKDS_COLSOUNDS.BRAKE, land: MKDS_COLSOUNDS.LAND_SNOW, particle:112}, //snow
{}, {},
@ -263,7 +271,7 @@ window.MKDS_COLTYPE = new (function(){
{hit: MKDS_COLSOUNDS.HIT_ICE}, {hit: MKDS_COLSOUNDS.HIT_ICE},
], ],
0x10: //wall 3 0x10: //wall oob
[ [
{hit: MKDS_COLSOUNDS.HIT_CONCRETE}, {hit: MKDS_COLSOUNDS.HIT_CONCRETE},
{}, {},

View File

@ -45,6 +45,8 @@ window.controlRaceCPU = function(nkm) {
var destConst; var destConst;
var destPoint; var destPoint;
var item = false;
function fetchInput() { function fetchInput() {
//basically as a cpu, we're really dumb and need a constant supply of points to drive to. //basically as a cpu, we're really dumb and need a constant supply of points to drive to.
//battle mode AI is a lot more complex, but since we're only going in one direction it can be kept simple. //battle mode AI is a lot more complex, but since we're only going in one direction it can be kept simple.
@ -83,6 +85,7 @@ window.controlRaceCPU = function(nkm) {
offTrans += 1/240; offTrans += 1/240;
if (offTrans >= 1) chooseNewOff(); if (offTrans >= 1) chooseNewOff();
item = !item;
return { return {
accel: accel, //x accel: accel, //x

View File

@ -5,6 +5,137 @@
// by RHY3756547 // by RHY3756547
// //
window.GraphicTester = function(rom) {
findGraphicsRecursive(rom);
function listRecursive(resource, path) {
path = path || "";
var files = resource.list();
for (var i=0; i<files.length; i++) {
var file = files[i];
console.log(path + file);
if (file.toLowerCase().endsWith(".carc")) {
listRecursive(new narc(lz77.decompress(resource.getFile(file))), path + file);
}
if (file.toLowerCase().endsWith(".nftr")) {
testFont(new nftr(resource.getFile(file)), 0, path + file);
}
}
}
function findGraphicsRecursive(resource, path) {
path = path || "";
var files = resource.list();
var pals = files.filter(x => x.toLowerCase().endsWith(".nclr"));
var graphics = files.filter(x => x.toLowerCase().endsWith(".ncgr"));
for (var i=0; i<files.length; i++) {
var file = files[i];
console.log(path + file);
if (file.toLowerCase().endsWith(".carc")) {
if (/\_..\./.exec(file) != null) continue; //a localization carc (format _us.carc). only scan the main ones. (+us)
var mainCarc = new narc(lz77.decompress(resource.getFile(file)));
var locCarc = resource.getFile(file.replace(".carc", "_us.carc"));
if (locCarc != null) {
//create a combo
mainCarc = new narcGroup([mainCarc, new narc(lz77.decompress(locCarc))]);
}
findGraphicsRecursive(mainCarc, path + file);
}
if (file.toLowerCase().endsWith(".nscr")) {
//screen
//try to find a pal
//...not a friend, like a color palette
var palFile = mostSimilarString(file, pals, "b");
var grFile = mostSimilarString(file, graphics.filter(x => !x.endsWith(".nce.ncgr")), "b");
if (palFile != null && grFile != null) {
var scr = new nscr(resource.getFile(file));
var pal = new nclr(resource.getFile(palFile));
var graphic = new ncgr(resource.getFile(grFile));
var flattener = new TileFlattener(pal, graphic, scr);
var render = flattener.toCanvas(true, 0, 0);
var split = document.createElement("h3");
split.innerText = path + file;
document.body.appendChild(split);
split = document.createElement("h4");
split.innerText = path + palFile + " " + path + grFile;
document.body.appendChild(split);
document.body.appendChild(render);
}
}
if (file.toLowerCase().endsWith(".ncer")) {
//cell resource
//try to find a pal
//...not a friend, like a color palette
var palFile = mostSimilarString(file, pals, "o");
var grFile = mostSimilarString(file, graphics, "o");
if (palFile != null && grFile != null) {
var cer = new ncer(resource.getFile(file));
var pal = new nclr(resource.getFile(palFile));
var graphic = new ncgr(resource.getFile(grFile));
var flattener = new TileFlattener(pal, graphic, cer);
var split = document.createElement("h3");
split.innerText = path + file;
document.body.appendChild(split);
split = document.createElement("h4");
split.innerText = path + palFile + " " + path + grFile;
document.body.appendChild(split);
//render all images
var imageCount = cer.cebk.imageCount;
for (var j=0; j<imageCount; j++) {
var render = flattener.toCanvas(true, j, 0);
document.body.appendChild(render);
}
}
}
}
}
function mostSimilarString(text, list, pref) {
var bestString = null;
var bestScore = 0;
for (var i=0; i<list.length; i++) {
var score = startSimilarity(text, list[i], pref);
if (score > bestScore) {
bestScore = score;
bestString = list[i];
}
}
return bestString;
}
function countStr(text, char) {
var count = 0;
for (var i=0; i<text.length; i++) {
if (text[i] == char) count++;
}
return count;
}
function startSimilarity(text1, text2, pref) {
var min = Math.min(text1.length, text2.length);
var score = 0;
for (var i=0; i<min; i++) {
if (text1[i] != text2[i]) {
if (pref != null) {
score += countStr(text2.substr(i), pref) / 10;
}
return score;
}
score++;
}
return score; //as similar as possible
}
}
window.IngameRes = function(rom) { window.IngameRes = function(rom) {
var r = this; var r = this;
this.kartPhys = new kartphysicalparam(rom.getFile("/data/KartModelMenu/kartphysicalparam.bin")); this.kartPhys = new kartphysicalparam(rom.getFile("/data/KartModelMenu/kartphysicalparam.bin"));
@ -21,6 +152,56 @@ window.IngameRes = function(rom) {
this.RaceEffect = new spa(r.MainEffect.getFile("RaceEffect.spa")); this.RaceEffect = new spa(r.MainEffect.getFile("RaceEffect.spa"));
this.MainFont = new nftr(r.Main2D.getFile("marioFont.NFTR")); this.MainFont = new nftr(r.Main2D.getFile("marioFont.NFTR"));
this.MFont = new nftr(r.Main2D.getFile("LC_Font_m.NFTR"));
this.SFont = new nftr(r.Main2D.getFile("LC_Font_s.NFTR"));
//testFont(this.MainFont, 0);
//testFont(this.MFont, 16*4);
//testFont(this.SFont, 32*4);
/*
var test = new GraphicTester(rom);
listRecursive(rom);
*/
function testFont(font, off, name) {
var all = Object.keys(font.charMap).join("");
var split = document.createElement("h3");
split.innerText = name;
document.body.appendChild(split);
for (var i=0; i<4; i++) {
var sliceF = Math.floor((all.length * i) / 4);
var sliceT = Math.floor((all.length * (i+1)) / 4);
var canvas = font.drawToCanvas(all.substring(sliceF, sliceT), [[0, 0, 0, 0], [255, 0, 0, 255], [255, 255, 255, 255],
[32, 0, 0, 255], [64, 0, 0, 255], [96, 0, 0, 255], [128, 0, 0, 255]]);
document.body.appendChild(canvas);
//canvas.style.position = "absolute";
//canvas.style.left = 0;
//canvas.style.top = off + "px";
//off += 16;
}
}
function listRecursive(resource, path) {
path = path || "";
var files = resource.list();
for (var i=0; i<files.length; i++) {
var file = files[i];
console.log(path + file);
if (file.toLowerCase().endsWith(".carc")) {
listRecursive(new narc(lz77.decompress(resource.getFile(file))), path + file);
}
if (file.toLowerCase().endsWith(".nftr")) {
if (file == "/selectFont.NFTR") debugger;
testFont(new nftr(resource.getFile(file)), 0, path + file);
}
}
}
//debugger; //debugger;
this.getChar = getChar; this.getChar = getChar;

View File

@ -19,6 +19,8 @@ window.ItemController = function(scene) {
t.changeItem = changeItem; t.changeItem = changeItem;
t.update = update; t.update = update;
t.draw = draw; t.draw = draw;
t.createItem = createItem;
t.removeItem = removeItem;
var RedShell, Banana, Bomb, BlueShell, Star, MultiItem, Shroom, TripleShroom, QueenShroom, Bullet, Ghost, Squid //these are all null var RedShell, Banana, Bomb, BlueShell, Star, MultiItem, Shroom, TripleShroom, QueenShroom, Bullet, Ghost, Squid //these are all null
@ -47,10 +49,25 @@ window.ItemController = function(scene) {
} }
function draw(mvMatrix, pMatrix, gl) { function draw(mvMatrix, pMatrix, gl) {
nitroRender.setShadBias(0.001);
for (var i=0; i<t.items.length; i++) { for (var i=0; i<t.items.length; i++) {
var e = t.items[i]; var e = t.items[i];
t.items[i].draw(mvMatrix, pMatrix, gl); t.items[i].draw(mvMatrix, pMatrix, gl);
} }
nitroRender.resetShadOff();
}
function createItem(type, kart) {
var item = new Item(scene, kart, type, t.curInd++);
t.items.push(item);
return item;
}
function removeItem(item) {
var ind = t.items.indexOf(item);
if (ind !== -1) {
t.items.splice(ind, 1);
}
} }
function addItem(type, ownerKart, params) { function addItem(type, ownerKart, params) {

View File

@ -14,12 +14,28 @@ window.lsc = new (function() {
this.sweepEllipse = sweepEllipse; this.sweepEllipse = sweepEllipse;
this.pointInTriangle = pointInTriangle; //expose this because its kinda useful this.pointInTriangle = pointInTriangle; //expose this because its kinda useful
function raycast(pos, dir, kclO, error, ignoreList) { //used for shells, bananas and spammable items. Much faster than sphere sweep. Error used to avoid falling through really small seams between tris. var t, colPlane, colPoint, emb, edge, colO, planeNormal;
function raycast(pos, dir, scn, error, ignoreList) { //used for shells, bananas and spammable items. Much faster than sphere sweep. Error used to avoid falling through really small seams between tris.
var error = (error==null)?0:error; var error = (error==null)?0:error;
var t=1; t=1;
var tris = getTriList(pos, dir, kclO); var tris = getTriList(pos, dir, scn.kcl);
var colPlane = null; colPlane = null;
var colPoint = null; //can be calculated from t, but we calculate it anyway so why not include colPoint = null; //can be calculated from t, but we calculate it anyway so why not include
colO = null;
rayVTris(pos, dir, tris, null, ignoreList, null, error);
for (var i=0; i<scn.colEnt.length; i++) {
var c = scn.colEnt[i];
var col = c.getCollision();
if (vec3.distance(pos, c.pos) < c.colRad) {
rayVTris(pos, dir, col.tris, col.mat, ignoreList, c, error, col.frame);
}
}
/*
for (var i=0; i<tris.length; i++) { for (var i=0; i<tris.length; i++) {
//first, check if we intersect the plane within reasonable t. //first, check if we intersect the plane within reasonable t.
//only if this happens do we check if the point is in the triangle. //only if this happens do we check if the point is in the triangle.
@ -29,7 +45,7 @@ window.lsc = new (function() {
if (ignoreList.indexOf(tri) != -1) continue; if (ignoreList.indexOf(tri) != -1) continue;
var planeConst = -vec3.dot(tri.Normal, tri.Vertex1); var planeConst = -vec3.dot(tri.Normal, tri.Vertices[0]);
var dist = vec3.dot(tri.Normal, pos) + planeConst; var dist = vec3.dot(tri.Normal, pos) + planeConst;
var modDir = vec3.dot(tri.Normal, dir); var modDir = vec3.dot(tri.Normal, dir);
if (dist < 0 || modDir == 0) continue; //can't collide with back side of polygons! also can't intersect plane with ray perpendicular to plane if (dist < 0 || modDir == 0) continue; //can't collide with back side of polygons! also can't intersect plane with ray perpendicular to plane
@ -44,24 +60,72 @@ window.lsc = new (function() {
} }
} }
} }
*/
if (colPlane != null) { if (colPlane != null) {
return { return {
t: t, t: t,
plane: colPlane, plane: colPlane,
colPoint: colPoint, colPoint: colPoint,
object: colO,
normal: colPlane.Normal normal: colPlane.Normal
} }
} else return null; } else return null;
} }
function rayVTris(pos, dir, tris, mat, ignoreList, targ, error, colFrame) {
for (var i=0; i<tris.length; i++) {
//first, check if we intersect the plane within reasonable t.
//only if this happens do we check if the point is in the triangle.
//we would also only do sphere sweep if this happens.
var tri = tris[i];
if (mat != null) {
if (tri.colFrame === colFrame && tri.cache) {
tri = tri.cache;
} else {
var oT = tri;
tri = modTri(tris[i], mat);
oT.cache = tri;
oT.colFrame = colFrame;
}
}
if (ignoreList.indexOf(tri) != -1) continue;
var planeConst = -vec3.dot(tri.Normal, tri.Vertices[0]);
var dist = vec3.dot(tri.Normal, pos) + planeConst;
var modDir = vec3.dot(tri.Normal, dir);
if (dist < 0 || modDir == 0) continue; //can't collide with back side of polygons! also can't intersect plane with ray perpendicular to plane
var newT = -dist/modDir;
if (newT>0 && newT<t) {
//we have a winner! check if the plane intersecion point is in the triangle.
var pt = vec3.add([], pos, vec3.scale([], dir, newT))
if (pointInTriangle(tri, pt, error)) {
t = newT;
colPlane = tri;
colPoint = pt; //result!
colO = targ;
}
}
}
}
function transformMat3Normal(out, a, m) {
var x = a[0], y = a[1], z = a[2];
out[0] = x * m[0] + y * m[4] + z * m[8];
out[1] = x * m[1] + y * m[5] + z * m[9];
out[2] = x * m[2] + y * m[6] + z * m[10];
return out;
}
function modTri(tri, mat) { function modTri(tri, mat) {
var obj = {}; var obj = {};
obj.Vertex1 = vec3.transformMat4([], tri.Vertex1, mat); obj.Vertices = [];
obj.Vertex2 = vec3.transformMat4([], tri.Vertex2, mat); obj.Vertices[0] = vec3.transformMat4([], tri.Vertices[0], mat);
obj.Vertex3 = vec3.transformMat4([], tri.Vertex3, mat); obj.Vertices[1] = vec3.transformMat4([], tri.Vertices[1], mat);
obj.Vertices[2] = vec3.transformMat4([], tri.Vertices[2], mat);
obj.Normal = vec3.transformMat3([], tri.Normal, mat3.fromMat4([], mat)); obj.Normal = transformMat3Normal([], tri.Normal, mat);
vec3.normalize(obj.Normal, obj.Normal); vec3.normalize(obj.Normal, obj.Normal);
obj.CollisionType = tri.CollisionType; obj.CollisionType = tri.CollisionType;
return obj; return obj;
@ -69,17 +133,16 @@ window.lsc = new (function() {
function scaleTri(tri, eDim) { function scaleTri(tri, eDim) {
var obj = {}; var obj = {};
obj.Vertex1 = vec3.divide([], tri.Vertex1, eDim); obj.Vertices = [];
obj.Vertex2 = vec3.divide([], tri.Vertex2, eDim); obj.Vertices[0] = vec3.divide([], tri.Vertices[0], eDim);
obj.Vertex3 = vec3.divide([], tri.Vertex3, eDim); obj.Vertices[1] = vec3.divide([], tri.Vertices[1], eDim);
obj.Vertices[2] = vec3.divide([], tri.Vertices[2], eDim);
obj.Normal = tri.Normal obj.Normal = tri.Normal
obj.CollisionType = tri.CollisionType; obj.CollisionType = tri.CollisionType;
return obj; return obj;
} }
var t, colPlane, colPoint, emb, edge, colO, planeNormal;
function sweepEllipse(pos, dir, scn, eDimensions, ignoreList) { //used for karts or things that need to occupy physical space. function sweepEllipse(pos, dir, scn, eDimensions, ignoreList) { //used for karts or things that need to occupy physical space.
t=1; t=1;
@ -140,7 +203,7 @@ window.lsc = new (function() {
if (ignoreList.indexOf(oTri) != -1) continue; if (ignoreList.indexOf(oTri) != -1) continue;
var tri = (eDims)?scaleTri(tris[i], mat):modTri(tris[i], mat); var tri = (eDims)?scaleTri(tris[i], mat):modTri(tris[i], mat);
var planeConst = -vec3.dot(tri.Normal, tri.Vertex1); var planeConst = -vec3.dot(tri.Normal, tri.Vertices[0]);
var dist = vec3.dot(tri.Normal, pos) + planeConst; var dist = vec3.dot(tri.Normal, pos) + planeConst;
var modDir = vec3.dot(tri.Normal, dir); var modDir = vec3.dot(tri.Normal, dir);
@ -200,14 +263,14 @@ window.lsc = new (function() {
} }
//no inside intersection check vertices: //no inside intersection check vertices:
for (var j=1; j<=3; j++) { for (var j=0; j<=2; j++) {
var vert = vec3.sub([], pos, tri["Vertex"+j]); var vert = vec3.sub([], pos, tri.Vertices[j]);
var root = getSmallestRoot(vec3.dot(dir, dir), 2*vec3.dot(dir, vert), vec3.dot(vert, vert)-1, t); var root = getSmallestRoot(vec3.dot(dir, dir), 2*vec3.dot(dir, vert), vec3.dot(vert, vert)-1, t);
if (root != null) { if (root != null) {
t = root; t = root;
colPlane = oTri; colPlane = oTri;
colO = targ; colO = targ;
colPoint = vec3.clone(tri["Vertex"+j]); //result! colPoint = vec3.clone(tri.Vertices[j]); //result!
planeNormal = tri.Normal; planeNormal = tri.Normal;
edge = false; edge = false;
} }
@ -215,9 +278,9 @@ window.lsc = new (function() {
//... and lines //... and lines
for (var j=1; j<=3; j++) { for (var j=0; j<=2; j++) {
var vert = tri["Vertex"+j]; var vert = tri.Vertices[j];
var nextV = tri["Vertex"+((j%3)+1)]; var nextV = tri.Vertices[(j+1)%3];
var distVert = vec3.sub([], vert, pos); var distVert = vec3.sub([], vert, pos);
var distLine = vec3.sub([], nextV, vert); var distLine = vec3.sub([], nextV, vert);
@ -277,9 +340,9 @@ window.lsc = new (function() {
function pointInTriangle(tri, point, error) { //barycentric check function pointInTriangle(tri, point, error) { //barycentric check
//compute direction vectors to the other verts and the point //compute direction vectors to the other verts and the point
var v0 = vec3.sub([], tri.Vertex3, tri.Vertex1); var v0 = vec3.sub([], tri.Vertices[2], tri.Vertices[0]);
var v1 = vec3.sub([], tri.Vertex2, tri.Vertex1); var v1 = vec3.sub([], tri.Vertices[1], tri.Vertices[0]);
var v2 = vec3.sub([], point, tri.Vertex1); var v2 = vec3.sub([], point, tri.Vertices[0]);
//we need to find u and v across the two vectors v0 and v1 such that adding them will result in our point's position //we need to find u and v across the two vectors v0 and v1 such that adding them will result in our point's position
//where the unit length of both vectors v0 and v1 is 1, the sum of both u and v should not exceed 1 and neither should be negative //where the unit length of both vectors v0 and v1 is 1, the sum of both u and v should not exceed 1 and neither should be negative

View File

@ -8,8 +8,8 @@
window.MKDSCONST = new (function() { window.MKDSCONST = new (function() {
this.DAMAGE_SPIN = 0; this.DAMAGE_SPIN = 0;
this.DAMAGE_FLIP = 0; this.DAMAGE_FLIP = 1;
this.DAMAGE_EXPLODE = 0; this.DAMAGE_EXPLODE = 2;
this.COURSEDIR = "/data/Course/"; this.COURSEDIR = "/data/Course/";

View File

@ -207,6 +207,8 @@ window.courseScene = function(mainNarc, texNarc, music, chars, options, gameRes)
} }
entsToRemove = []; entsToRemove = [];
var mat = scn.camera.getView(scn, nitroRender.getViewWidth(), nitroRender.getViewHeight()); var mat = scn.camera.getView(scn, nitroRender.getViewWidth(), nitroRender.getViewHeight());
nitroAudio.updateListener(mat.pos, mat.mv);
frame++; frame++;
} }

View File

@ -10,7 +10,7 @@ window.fileStore = new (function(){
|| window.mozIndexedDB || window.mozIndexedDB
|| window.shimIndexedDB; || window.shimIndexedDB;
var request = indexedDB.open("MKJS_DB", 1); var request = indexedDB.open("MKJS-DB", 1);
request.onerror = window.onerror; request.onerror = window.onerror;
request.onsuccess = function(event) { request.onsuccess = function(event) {
@ -40,6 +40,18 @@ window.fileStore = new (function(){
}; };
} }
function validateFiles() {
var transaction = db.transaction(["files"]);
var objectStore = transaction.objectStore("files");
var request = objectStore.get("mkds.nds");
request.onerror = function(event) {
alert("Fatal database error!");
};
request.onsuccess = function(event) {
if (request.result == null) alert("Locally storing files failed!");
};
}
function downloadGame(url, callback) { function downloadGame(url, callback) {
if (typeof url == "string") { if (typeof url == "string") {
var xml = new XMLHttpRequest(); var xml = new XMLHttpRequest();
@ -77,6 +89,7 @@ window.fileStore = new (function(){
callback(dat); callback(dat);
}; };
request.onsuccess = function(event) { request.onsuccess = function(event) {
validateFiles();
callback(dat); callback(dat);
}; };
} }

View File

@ -1,7 +1,7 @@
// //
// bowserPlatforms.js // bowserPlatforms.js
//-------------------- //--------------------
// Provides platforms for Bowser's Castle // Provides moving platforms for Bowser's Castle and Delfino
// by RHY3756547 // by RHY3756547
// //
// includes: // includes:
@ -78,6 +78,133 @@ window.ObjRotaryRoom = function(obji, scene) {
} }
window.ObjBridge = function(obji, scene) {
var obji = obji;
var res = [];
var t = this;
t.collidable = true;
t.colMode = 0;
t.colRad = 512;
t.getCollision = getCollision;
t.moveWith = moveWith;
t.pos = vec3.clone(obji.pos);
t.angle = vec3.clone(obji.angle);
t.scale = vec3.clone(obji.scale);
t.requireRes = requireRes;
t.provideRes = provideRes;
t.update = update;
t.draw = draw;
t.largerUpAngle = obji.setting1&0xFFFF;
t.upDuration = obji.setting1>>16;
t.statDuration = obji.setting2&0xFFFF;
t.downDuration = obji.setting2>>16;
t.upAngle = obji.setting3&0xFFFF;
t.unknown = obji.setting4>>16; //10001
t.obji = obji;
t.colFrame = 0;
var dirVel = 0;
var genCol;
var prevMat;
var curMat;
var colMat = mat4.create();
prevMat = curMat;
var anim;
var animMat;
var frame = 0;
var mode = 0; //going up, stationary, going down, stationary
function setMat() {
prevMat = curMat;
var mat = mat4.create();
mat4.translate(mat, mat, t.pos);
if (t.angle[2] != 0) mat4.rotateZ(mat, mat, t.angle[2]*(Math.PI/180));
if (t.angle[1] != 0) mat4.rotateY(mat, mat, t.angle[1]*(Math.PI/180));
if (t.angle[0] != 0) mat4.rotateX(mat, mat, t.angle[0]*(Math.PI/180));
mat4.scale(mat, mat, vec3.scale([], t.scale, 16));
mat4.scale(colMat, mat, [genCol.scale, genCol.scale, genCol.scale]);
t.colFrame++;
curMat = mat;
}
function update(scene) {
var angle = 0;
frame++;
switch (mode) {
case 0:
var p = frame / t.upDuration;
angle = (0.5 - Math.cos(p * Math.PI) / 2) * t.largerUpAngle;
if (frame >= t.upDuration) {
mode = 1;
frame = 0;
}
break;
case 1:
case 3:
angle = (mode == 1) ? t.largerUpAngle : 0;
if (frame >= t.statDuration) {
mode = (mode + 1) % 4;
frame = 0;
}
break;
case 2:
var p = 1 - frame / t.downDuration;
angle = (0.5 - Math.cos(p * Math.PI) / 2) * t.largerUpAngle;
if (frame >= t.downDuration) {
mode = 3;
frame = 0;
}
break;
}
t.angle[0] = -angle;
animMat = anim.setFrame(0, 0, angle*1.5);
setMat();
}
function draw(view, pMatrix) {
var mat = mat4.create();
mat4.mul(mat, view, curMat);
res.mdl[0].draw(mat, pMatrix, animMat);
}
function requireRes() { //scene asks what resources to load
return {mdl:[{nsbmd:"bridge.nsbmd"}], other:[null, "bridge.nsbca"]};
}
function provideRes(r) {
res = r; //...and gives them to us. :)
var inf = res.mdl[0].getCollisionModel(0, 1, 7<<8); //dash
var inf2 = res.mdl[0].getCollisionModel(0, 0, 0); //regular
anim = new nitroAnimator(r.mdl[0].bmd, r.other[1]);
genCol = {dat:JSON.parse(JSON.stringify(inf.dat.concat(inf2.dat))), scale:inf.scale};
}
function getCollision() {
return { tris: genCol.dat, mat: colMat, frame: t.colFrame };
}
function moveWith(obj) { //used for collidable objects that move.
//the most general way to move something with an object is to multiply its position by the inverse mv matrix of that object, and then the new mv matrix.
vec3.transformMat4(obj.pos, obj.pos, mat4.invert([], prevMat))
vec3.transformMat4(obj.pos, obj.pos, curMat)
}
}
window.ObjRoutePlatform = function(obji, scene) { window.ObjRoutePlatform = function(obji, scene) {
var obji = obji; var obji = obji;
var res = []; var res = [];
@ -111,12 +238,14 @@ window.ObjRoutePlatform = function(obji, scene) {
t.elapsedTime = 0; t.elapsedTime = 0;
t.mode = 0; t.mode = 0;
t.colFrame = 0;
var movVel; var movVel;
//t.speed = (obji.setting1&0xFFFF)/8192; //t.speed = (obji.setting1&0xFFFF)/8192;
function update(scene) { function update(scene) {
t.colFrame++;
if (t.mode == 0) { if (t.mode == 0) {
t.elapsedTime += t.routeSpeed; t.elapsedTime += t.routeSpeed;
movVel = vec3.sub([], t.nextNode.pos, t.prevPos); movVel = vec3.sub([], t.nextNode.pos, t.prevPos);
@ -159,15 +288,11 @@ window.ObjRoutePlatform = function(obji, scene) {
function generateCol() { function generateCol() {
genCol = {dat: [ genCol = {dat: [
{ {
Vertex1: [25, 0, 11], Vertices: [[25, 0, 11], [25, 0, -11], [-25, 0, -11]],
Vertex2: [25, 0, -11],
Vertex3: [-25, 0, -11],
Normal: [0, 1, 0] Normal: [0, 1, 0]
}, },
{ {
Vertex1: [-25, 0, -11], Vertices: [[-25, 0, -11], [-25, 0, 11], [25, 0, 11]],
Vertex2: [-25, 0, 11],
Vertex3: [25, 0, 11],
Normal: [0, 1, 0] Normal: [0, 1, 0]
}, },
], scale: 1}; ], scale: 1};
@ -182,6 +307,7 @@ window.ObjRoutePlatform = function(obji, scene) {
mat4.scale(mat, mat, vec3.mul([], [16*inf.scale, 16*inf.scale, 16*inf.scale], t.scale)); mat4.scale(mat, mat, vec3.mul([], [16*inf.scale, 16*inf.scale, 16*inf.scale], t.scale));
obj.mat = mat; obj.mat = mat;
obj.frame = t.colFrame;
return obj; return obj;
} }

View File

@ -32,34 +32,44 @@ var itemTypes = {
'$koura_w': BlueShellC '$koura_w': BlueShellC
} }
window.Item = function(scene, owner, type) { window.Item = function(scene, owner, type, id) {
var t = this; var t = this;
var minimumMove = 0.01; var minimumMove = 0.01;
this.id = 0; this.id = id;
this.pos = vec3.transformMat4([], [0, (-owner.params.colRadius)+1, 16], owner.mat); this.pos = vec3.transformMat4([], [0, (-owner.params.colRadius)+1, 16], owner.mat);
this.vel = vec3.create(); this.vel = vec3.create();
this.gravity = [0, -0.17, 0]; //100% confirmed by me messing around with the gravity value in mkds this.gravity = [0, -0.17, 0]; //100% confirmed by me messing around with the gravity value in mkds
this.minBounceVel = 0.5; this.minBounceVel = 0.5;
this.airResist = 0.95; this.airResist = 0.99;
this.enablePhysics = true; this.enablePhysics = true;
this.floorBounce = 0.5; this.floorBounce = 0.5;
this.held = true; this.held = true;
this.type = type; this.type = type;
this.owner = owner; this.owner = owner;
this.holdTime = 20;
this.dead = false;
this.angle = owner.angle; this.angle = owner.angle;
this.speed = 10; this.speed = 10;
this.yvel = 0; this.yvel = 0;
this.xyScale = [1, 1];
this.colRadius = 4; this.colRadius = 4;
this.holdDist = 16; this.holdDist = 2;
this.safeKart = owner; this.safeKart = owner;
var safeTimeMax = 4;
this.safeTime = safeTimeMax; //time the object needs to not be colliding with the source to start considering collisions with it
this.stuckTo = null;
var deadTimerLength = 20; this.groundTime = 0;
var throwVelocity = 16;
var throwAngle = (Math.PI / 3) * 2;
var deadTimerLength = 30;
var throwVelocity = 7; //xz velocity for throw. angle adds a y component
var throwAngle = (Math.PI / 10) * 2;
var working = vec3.create();
this.deadTimer = 0; //animates death. goes to 20, then deletes for real. dead objects can't run update or otherwise this.deadTimer = 0; //animates death. goes to 20, then deletes for real. dead objects can't run update or otherwise
//a controller makes this item what it is... //a controller makes this item what it is...
@ -69,6 +79,8 @@ window.Item = function(scene, owner, type) {
// update?: (scene: CourseScene) => void // update?: (scene: CourseScene) => void
// draw?: (mvMatrix, pMatrix) => void // OVERRIDES NORMAL DRAW FUNCTION! // draw?: (mvMatrix, pMatrix) => void // OVERRIDES NORMAL DRAW FUNCTION!
// release?: (direction: number) => boolean //direction is 1 for forward, -1 for back. returns if the item has more uses // release?: (direction: number) => boolean //direction is 1 for forward, -1 for back. returns if the item has more uses
// onRest?: (normal: vec3) => void //when the object comes to a rest (first time, or after leaving the ground for a while)
// onDie?: (final: boolean) => void //when the object dies
// collide?: (item: Item | Kart) // collide?: (item: Item | Kart)
// collideKart?: (item: Kart) // collideKart?: (item: Kart)
var subtypeInd = type.indexOf('-'); var subtypeInd = type.indexOf('-');
@ -91,10 +103,16 @@ window.Item = function(scene, owner, type) {
function updateHold(kart) { function updateHold(kart) {
//move the object behind the kart (physical direction without drift off) //move the object behind the kart (physical direction without drift off)
//assuming this will only be called for something that can be held //assuming this will only be called for something that can be held
var dir = -kart.driftOff; var dir = kart.driftOff / 4;
//offset the kart's drift offset (on direction) //offset the kart's drift offset (on direction)
var pos = [Math.sin(dir)*t.holdDist, 0, -Math.cos(dir)*t.holdDist]; var pos;
if (t.holdPos != null) {
pos = vec3.clone(t.holdPos);
} else {
var dist = t.colRadius + kart.params.colRadius + t.holdDist;
var pos = [Math.sin(dir)*dist, -kart.params.colRadius, -Math.cos(dir)*dist];
}
//make relative to the kart's position //make relative to the kart's position
vec3.transformMat4(pos, pos, kart.mat); vec3.transformMat4(pos, pos, kart.mat);
@ -105,18 +123,37 @@ window.Item = function(scene, owner, type) {
function release(forward) { function release(forward) {
//release the item, either forward or back //release the item, either forward or back
if (t.canBeHeld()) t.updateHold(owner); t.holdTime = 0;
if (t.canBeHeld()) {
t.updateHold(owner);
updateCollision(scene);
}
t.enablePhysics = true;
if (t.controller.release) t.controller.release(forward); if (t.controller.release) t.controller.release(forward);
else { else {
//default drop and throw. just here for template purposes //default drop and throw. just here for template purposes
if (forward >= 0) { if (forward > 0) {
var dir = owner.physicalDir; nitroAudio.playSound(218, {volume: 2}, 0, owner);
vec3.zero(t.vel); var dir = owner.driftOff / 4;
//offset the kart's drift offset (on direction). add y component
var vel = [-Math.sin(dir)*throwVelocity, Math.tan(throwAngle) * throwVelocity, Math.cos(dir)*throwVelocity];
var z = [0, 0, 0];
//make relative to the kart's orientation
vec3.transformMat4(vel, vel, owner.mat);
vec3.transformMat4(z, z, owner.mat);
vec3.sub(vel, vel, z);
var v2 = vec3.scale([], owner.vel, 2);
vec3.add(vel, vel, v2);
t.vel = vel;
} else { } else {
vec3.zero(t.vel); t.vel = vec3.create();
t.safeKart = null;
} }
} }
this.held = false; t.held = false;
} }
function canBeHeld() { function canBeHeld() {
@ -124,7 +161,8 @@ window.Item = function(scene, owner, type) {
} }
function canBeDropped() { function canBeDropped() {
return t.controller.canBeDropped || true; if (t.controller.canBeDropped == null) return true;
return t.controller.canBeDropped;
} }
function isDestructive() { function isDestructive() {
@ -132,13 +170,22 @@ window.Item = function(scene, owner, type) {
} }
function isSolid() { function isSolid() {
return t.controller.isSolid || true; if (t.controller.isSolid == null) return true;
return t.controller.isSolid;
} }
function finalize() { function finalize() {
//kill instantly //kill instantly
if (t.controller.onDie) t.controller.onDie(true);
t.deadTimer = deadTimerLength; t.deadTimer = deadTimerLength;
scene.items.removeItem(t); scene.items.removeItem(t);
t.dead = true;
}
function intensityMax(targ, vec) {
if (Math.abs(vec[0]) > Math.abs(targ[0])*0.5) targ[0] = vec[0];
if (Math.abs(vec[1]) > Math.abs(targ[1])*0.5) targ[1] = vec[1];
if (Math.abs(vec[2]) > Math.abs(targ[2])*0.5) targ[2] = vec[2];
} }
function collide(item) { function collide(item) {
@ -152,15 +199,19 @@ window.Item = function(scene, owner, type) {
if (item.isDestructive() || t.isDestructive()) { if (item.isDestructive() || t.isDestructive()) {
//mutual destruction. other side will deal with how they handle the collision //mutual destruction. other side will deal with how they handle the collision
t.deadTimer++; t.deadTimer++;
item.deadTimer++;
} else if (item.isSolid() && t.isSolid()) { } else if (item.isSolid() && t.isSolid()) {
//bounce off other items that are not destructive //bounce off other items that are not destructive
//set our velocity to move away (not too intensely) //set our velocity to move away (not too intensely)
//(only apply if our id is before, to avoid double adding the velocity) //(only apply if our id is before, to avoid double adding the velocity)
if (t.id < item.id) { if (t.id < item.id) {
var diff = vec3.sub([], t.pos, item.pos); var diff = vec3.sub(working, t.pos, item.pos);
vec3.scale(diff, diff, 0.5); vec3.scale(diff, diff, 0.33);
vec3.add(t.vel, t.vel, diff); intensityMax(t.vel, diff);
vec3.sub(item.vel, item.vel, diff); vec3.scale(diff, diff, -1);
intensityMax(item.vel, diff);
//vec3.add(t.vel, t.vel, diff);
//vec3.sub(item.vel, item.vel, diff);
t.enablePhysics = true; t.enablePhysics = true;
item.enablePhysics = true; item.enablePhysics = true;
} }
@ -175,77 +226,131 @@ window.Item = function(scene, owner, type) {
function update(scene) { function update(scene) {
if (t.controller.update) t.controller.update(scene); if (t.controller.update) t.controller.update(scene);
if (t.holdTime > 0 && t.holdTime-- > 7) {
if (t.holdTime == 7) {
nitroAudio.playSound(231, {volume: 2}, 0, owner);
}
return;
}
if (t.pos[2] < -10000) finalize(); //out of bounds failsafe
if (t.deadTimer > 0) { if (t.deadTimer > 0) {
if (t.deadTimer == 1 && t.controller.onDie) t.controller.onDie(false);
t.deadTimer++; t.deadTimer++;
if (t.deadTimer >= 20) t.finalize(); t.sprMat = mat4.create();
mat4.translate(t.sprMat, t.sprMat, [t.deadTimer/50, Math.sin((t.deadTimer/30) * Math.PI) * 0.5, 0]);
mat4.rotateZ(t.sprMat, t.sprMat, (t.deadTimer/-15) * Math.PI);
if (t.deadTimer >= 30) t.finalize();
return; return;
} }
if (t.held) {
t.updateHold(owner);
}
var hitSafe = false;
//search for player collisions, collisions with other items //search for player collisions, collisions with other items
for (var i=0; i<scene.karts.length; i++) { for (var i=0; i<scene.karts.length; i++) {
var ok = scene.karts[i]; var ok = scene.karts[i];
var dist = vec3.dist(vec3.add([], t.pos, [0,1,0]), ok.pos);
if (dist < t.colRadius + 12) { var dist = vec3.dist(vec3.add(working, t.pos, [0,t.colRadius/2,0]), ok.pos);
if (dist < t.colRadius + ok.params.colRadius) {
//colliding with a kart. //colliding with a kart.
//do we need to do something? //do we need to do something?
if (ok === t.safeKart) {
hitSafe = true;
continue;
}
t.collide(ok); t.collide(ok);
} }
} }
for (var i=0; i<scene.items.length; i++) { if (t.safeKart && !hitSafe && !t.held) {
var ot = scene.items[i]; t.safeTime--;
var dist = vec3.dist(t.pos, ot.pos); if (t.safeTime <= 0) {
if (dist < t.colRadius + ot.colRadius) { t.safeKart = null;
//two items are colliding.
t.collide(ot);
} }
} }
if (t.holdTime == 0) { //avoid mutual item destruction on the first frame
if (t.enablePhysics) { for (var i=0; i<scene.items.items.length; i++) {
if (!t.held) { var ot = scene.items.items[i];
vec3.add(t.vel, t.vel, t.gravity); if (ot == t || (t.held && ot.held)) continue;
vec3.scale(t.vel, t.vel, t.airResist); var dist = vec3.dist(t.pos, ot.pos);
} if (dist < t.colRadius + ot.colRadius && ot.holdTime <= 7 && ot.deadTimer == 0) {
//two items are colliding.
//by default, items use raycast collision against the world (rather than ellipse) t.collide(ot);
//this speeds things up considerably
var steps = 0;
var remainingT = 1;
var velSeg = vec3.clone(t.vel);
var posSeg = vec3.clone(t.pos);
var ignoreList = [];
while (steps++ < 10 && remainingT > 0.01) {
var result = lsc.raycast(posSeg, velSeg, scene.kcl, 0.05, ignoreList);
if (result != null) {
if (t.controller.colResponse) t.controller.colResponse(posSeg, velSeg, result, ignoreList)
else colResponse(posSeg, velSeg, result, ignoreList)
remainingT -= result.t;
if (remainingT > 0.01) {
velSeg = vec3.scale(vec3.create(), t.vel, remainingT);
}
} else {
vec3.add(posSeg, posSeg, velSeg);
remainingT = 0;
} }
} }
t.pos = posSeg; }
if (t.groundTime > 0) t.groundTime++;
if (t.stuckTo != null) {
if (t.stuckTo.moveWith != null) t.stuckTo.moveWith(t);
t.enablePhysics = true;
t.stuckTo = null;
}
if (t.enablePhysics) {
updateCollision(scene);
} }
} }
function updateCollision(scene) {
if (!t.held) {
vec3.add(t.vel, t.vel, t.gravity);
vec3.scale(t.vel, t.vel, t.airResist);
}
//by default, items use raycast collision against the world (rather than ellipse)
//this speeds things up considerably
var steps = 0;
var remainingT = 1;
var velSeg = vec3.clone(t.vel);
var posSeg = vec3.clone(t.pos);
var ignoreList = [];
while (steps++ < 10 && remainingT > 0.01) {
var result = lsc.raycast(posSeg, velSeg, scene, 0.05, ignoreList);
if (result != null) {
if (t.controller.colResponse && !t.held) t.controller.colResponse(posSeg, velSeg, result, ignoreList)
else colResponse(posSeg, velSeg, result, ignoreList)
remainingT -= result.t;
if (remainingT > 0.01) {
velSeg = vec3.scale(velSeg, t.vel, remainingT);
}
} else {
vec3.add(posSeg, posSeg, velSeg);
remainingT = 0;
}
}
t.pos = posSeg;
}
function draw(mvMatrix, pMatrix) { function draw(mvMatrix, pMatrix) {
if (t.holdTime > 7) return;
if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1-(t.deadTimer/deadTimerLength)]); //fade out if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1-(t.deadTimer/deadTimerLength)]); //fade out
if (t.controller.draw) { if (t.controller.draw) {
t.controller.draw(mvMatrix, pMatrix); t.controller.draw(mvMatrix, pMatrix);
} else { } else {
var mat = mat4.translate(mat4.create(), mvMatrix, vec3.add(vec3.create(), t.pos, [0, 3, 0])); var mat = mat4.translate(mat4.create(), mvMatrix, vec3.add(vec3.create(), t.pos, [0, t.colRadius * t.xyScale[1], 0]));
spritify(mat); spritify(mat);
mat4.scale(mat, mat, [16, 16, 16]); var scale = 6*t.colRadius * (1 - t.holdTime/7);
mat4.scale(mat, mat, [scale, scale, scale]);
scene.gameRes.items[type].draw(mat, pMatrix); var mdl = scene.gameRes.items[type];
//apply our custom mat (in sprite space), if it exists
//used for destruction animation, scaling
if (t.sprMat) {
var oldMat = mdl.baseMat;
mdl.setBaseMat(t.sprMat);
mdl.draw(mat, pMatrix);
mdl.setBaseMat(oldMat);
} else {
mdl.draw(mat, pMatrix);
}
} }
if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1]); if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1]);
} }
@ -272,13 +377,28 @@ window.Item = function(scene, owner, type) {
//normally, item collision with a wall cause a perfect reflection of the velocity. //normally, item collision with a wall cause a perfect reflection of the velocity.
var proj = vec3.dot(t.vel, n) * 2; var proj = vec3.dot(t.vel, n) * 2;
vec3.sub(t.vel, t.vel, vec3.scale(vec3.create(), n, proj)); vec3.sub(t.vel, t.vel, vec3.scale(vec3.create(), n, proj));
t.safeKart = null;
} else if (colType == MKDS_COLTYPE.OOB || colType == MKDS_COLTYPE.FALL) {
if (t.deadTimer == 0) t.deadTimer++;
} else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) { } else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
//sliding plane //sliding plane
var proj = vec3.dot(t.vel, n) * (1 + t.floorBounce); var bounce = t.held ? 0 : t.floorBounce;
var proj = vec3.dot(t.vel, n) * (1 + bounce);
vec3.sub(t.vel, t.vel, vec3.scale(vec3.create(), n, proj)); vec3.sub(t.vel, t.vel, vec3.scale(vec3.create(), n, proj));
if (t.floorBounce == 0 || Math.abs(proj) < t.minBounceVel) t.enablePhysics = false; if (!t.held && (t.floorBounce == 0 || Math.abs(proj) < t.minBounceVel)) {
t.vel[0] = 0;
t.vel[1] = 0;
t.vel[2] = 0;
t.enablePhysics = false;
if (t.groundTime == 0) {
t.groundTime = 1;
if (t.controller.onRest) {
t.controller.onRest(n);
}
}
}
t.stuckTo = dat.object;
} else { } else {
adjustPos = false; adjustPos = false;
ignoreList.push(plane); ignoreList.push(plane);

View File

@ -98,12 +98,15 @@ window.ItemBox = function(obji, scene) {
} }
function sndUpdate(view) { function sndUpdate(view) {
/*
t.soundProps.pos = vec3.transformMat4([], t.pos, view); t.soundProps.pos = vec3.transformMat4([], t.pos, view);
if (t.soundProps.lastPos != null) t.soundProps.vel = vec3.sub([], t.soundProps.pos, t.soundProps.lastPos); if (t.soundProps.lastPos != null) t.soundProps.vel = vec3.sub([], t.soundProps.pos, t.soundProps.lastPos);
else t.soundProps.vel = [0, 0, 0]; else t.soundProps.vel = [0, 0, 0];
*/
t.soundProps.lastPos = t.soundProps.pos; t.soundProps.lastPos = t.soundProps.pos;
t.soundProps.pos = t.pos; //todo: reintroduce doppler via emulation
t.soundProps.refDistance = 192/1024; t.soundProps.refDistance = 192;
t.soundProps.rolloffFactor = 1; t.soundProps.rolloffFactor = 1;
} }

View File

@ -1,15 +1,42 @@
window.BananaC = function(item, scene, type) { window.BananaC = function(item, scene, type) {
var t = this;
this.canBeHeld = true; this.canBeHeld = true;
this.canBeDropped = true; this.canBeDropped = true;
this.isDestructive = false; this.isDestructive = false;
item.minBounceVel = 0; item.floorBounce = 0;
this.collideKart = collideKart; this.collideKart = collideKart;
this.onRest = onRest;
this.update = update;
function collideKart(kart) { function collideKart(kart) {
item.deadTimerLength = 20; item.deadTimer = 1;
kart.damage(MKDSCONST.DAMAGE_SPIN); kart.damage(MKDSCONST.DAMAGE_SPIN);
} }
function onRest(normal) {
nitroAudio.playSound(219, {volume: 2}, 0, item);
}
function update(argument) {
if (!item.held && item.colRadius < 6) {
item.colRadius += 0.2;
if (item.colRadius > 6) item.colRadius = 6;
}
if (item.groundTime < 30) {
var t = (1-item.groundTime/29);
var s = Math.sin(item.groundTime * Math.PI/14);
var sprMat = mat4.create();
mat4.translate(sprMat, sprMat, [0, -1/6, 0]);
mat4.scale(sprMat, sprMat, [1 + s * 0.6 * t, 1 - s * 0.6 * t, 1]);
mat4.translate(sprMat, sprMat, [0, 1/6, 0]);
item.sprMat = sprMat;
} else {
item.sprMat = null;
}
}
} }
window.BananaGroupC = function(item, scene, type) { window.BananaGroupC = function(item, scene, type) {
@ -28,27 +55,89 @@ window.BananaGroupC = function(item, scene, type) {
} }
window.FakeBoxC = function(item, scene, type) { window.FakeBoxC = function(item, scene, type) {
var t = this;
this.canBeHeld = true; this.canBeHeld = true;
this.canBeDropped = true; this.canBeDropped = true;
this.isDestructive = false; this.isDestructive = false;
this.isSolid = true; this.isSolid = false;
var model = scene.gameRes.fakeBox; item.floorBounce = 0;
item.airResist = 0.98;
this.collideKart = collideKart;
this.onRest = onRest;
this.update = update;
this.draw = draw; this.draw = draw;
function draw(view, pMatrix) { this.xyScale = [1,1];
mat4.translate(mat, view, t.pos); this.dir = 0;
mat4.translate(mat, view, [0, 16, 0]);
function collideKart(kart) {
item.deadTimer = 1;
nitroAudio.playSound(250, {volume: 2}, 0, item);
kart.damage(MKDSCONST.DAMAGE_FLIP);
}
function onRest(normal) {
nitroAudio.playSound(251, {volume: 2}, 0, item);
}
function update(argument) {
if (item.held) {
t.dir = -(item.owner.physicalDir + item.owner.driftOff / 4);
}
if (!item.held && item.colRadius < 8) {
item.colRadius += 0.2;
if (item.colRadius > 8) item.colRadius = 8;
}
if (item.groundTime < 20) {
var linear = (1-item.groundTime/19);
var s = Math.sin(item.groundTime * Math.PI/8);
t.xyScale = [1 + s * 0.25 * linear, 1 - s * 0.25 * linear];
} else {
t.xyScale = [1,1];
}
}
function draw(mvMatrix, pMatrix) {
var mat = mat4.translate(mat4.create(), mvMatrix, vec3.add(vec3.create(), item.pos, [0, item.colRadius*1.5 * t.xyScale[1], 0]));
var scale = 2*item.colRadius * (1 - item.holdTime/7);
mat4.scale(mat, mat, [scale*t.xyScale[0], scale*t.xyScale[1], scale*t.xyScale[0]]);
mat4.rotateY(mat, mat, t.dir);
mat4.rotateZ(mat, mat, Math.PI/-6);
mat4.rotateY(mat, mat, Math.PI/6);
mat4.rotateX(mat, mat, Math.PI/-6);
/* adjust to make it rest on a corner var mdl = scene.gameRes.items.fakeBox;
if (t.angle[2] != 0) mat4.rotateZ(mat, mat, t.angle[2]*(Math.PI/180)); mdl.draw(mat, pMatrix);
if (t.angle[1] != 0) mat4.rotateY(mat, mat, t.angle[1]*(Math.PI/180));
if (t.angle[0] != 0) mat4.rotateX(mat, mat, t.angle[0]*(Math.PI/180));
*/
mat4.scale(mat, mat, vec3.scale([], t.scale, 16));
model.draw(mat, pMatrix, animMat);
} }
} }
window.BombC = null; window.BombC = function(item, scene, type) {
var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = true;
this.isExploding = false;
this.collideKart = collideKart;
this.onRest = onRest;
this.update = update;
function collideKart(kart) {
item.deadTimer = 1;
kart.damage(MKDSCONST.DAMAGE_EXPLODE);
}
function onRest(normal) {
}
function update(argument) {
if (!item.held && item.colRadius < 6) {
item.colRadius += 0.2;
if (item.colRadius > 6) item.colRadius = 6;
}
}
}

View File

@ -1,7 +1,97 @@
window.GreenShellC = function(item, scene) { window.GreenShellC = function(item, scene) {
var t = this;
this.canBeHeld = true; this.canBeHeld = true;
this.canBeDropped = true; this.canBeDropped = true;
this.isDestructive = true; this.isDestructive = true;
this.angle = 0;
this.speed = 6; //base speed + kart speed
this.sound = null;
this.soundCooldown = 0;
item.colRadius = 3;
var minimumMove = 0.17;
this.gravity = [0, -0.17, 0]; //100% confirmed by me messing around with the gravity value in mkds
this.collideKart = collideKart;
this.update = update;
this.release = release;
this.onDie = onDie;
this.colResponse = colResponse;
function release(forward) {
t.sound = nitroAudio.playSound(215, {volume: 1.5}, 0, item);
t.speed = 6;
t.angle = item.owner.physicalDir;
if (forward < 0) {
t.angle += Math.PI;
t.angle %= Math.PI*2;
} else {
t.speed += item.owner.speed;
}
}
function onDie(final) {
if (!final) {
nitroAudio.playSound(214, {volume: 2}, 0, item);
}
if (t.sound) {
nitroAudio.instaKill(t.sound);
t.sound = null;
}
}
function collideKart(kart) {
item.deadTimer = 1;
kart.damage(MKDSCONST.DAMAGE_FLIP);
}
function update(scene) {
item.vel = [Math.sin(t.angle)*t.speed, item.vel[1], -Math.cos(t.angle)*t.speed]
vec3.add(item.vel, item.vel, t.gravity);
if (this.soundCooldown > 0) this.soundCooldown--;
}
function colResponse(pos, pvel, dat, ignoreList) {
var plane = dat.plane;
var colType = (plane.CollisionType>>8)&31;
vec3.add(pos, pos, vec3.scale(vec3.create(), pvel, dat.t));
var n = dat.normal;
vec3.normalize(n, n);
var gravS = Math.sqrt(vec3.dot(t.gravity, t.gravity));
var angle = Math.acos(vec3.dot(vec3.scale(vec3.create(), t.gravity, -1/gravS), n));
var adjustPos = true
if (MKDS_COLTYPE.GROUP_WALL.indexOf(colType) != -1) { //wall
//shell reflection code - slide y vel across plane, bounce on xz
if (this.soundCooldown <= 0) {
nitroAudio.playSound(213, {volume: 2.5}, 0, item);
this.soundCooldown = 30;
}
vec3.add(item.vel, vec3.scale(vec3.create(), n, -2*(vec3.dot(item.vel, n)/vec3.dot(n,n))), item.vel);
item.vel[1] = 0;
var v = item.vel;
t.angle = Math.atan2(v[0], -v[2]);
} else if (colType == MKDS_COLTYPE.OOB || colType == MKDS_COLTYPE.FALL) {
if (item.deadTimer == 0) item.deadTimer++;
} else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
//sliding plane
var proj = vec3.dot(item.vel, n);
vec3.sub(item.vel, item.vel, vec3.scale(vec3.create(), n, proj));
} else {
adjustPos = false;
ignoreList.push(plane);
}
var rVelMag = Math.sqrt(vec3.dot(item.vel, item.vel));
vec3.scale(item.vel, item.vel, t.speed/rVelMag); //force speed to shell speed for green shells.
if (adjustPos) { //move back from plane slightly
vec3.add(pos, pos, vec3.scale(vec3.create(), n, minimumMove));
}
}
} }
window.RedShellC = function(item, scene) { window.RedShellC = function(item, scene) {
@ -11,11 +101,100 @@ window.RedShellC = function(item, scene) {
} }
window.ShellGroupC = function(item, scene, type) { window.ShellGroupC = function(item, scene, type) {
this.canBeHeld = false; var t = this;
this.canBeDropped = 'func'; this.canBeHeld = "func";
this.canBeDropped = "func";
this.rotationPeriod = 45; this.rotationPeriod = 45;
item.colRadius = -Infinity;
this.draw = draw; this.draw = draw;
this.update = update;
this.release = release;
this.onDie = onDie;
this.children = [];
var itemType = "koura_g";
var itemCount = 3;
if (type.length > 0) {
var typeParse = type.split("-");
if (typeParse.length == 1) {
itemType = type;
} else if (typeParse.length == 2 && !isNaN(typeParse[1]-0)) {
itemType = typeParse[0];
itemCount = typeParse[1]-0;
}
}
this.phase = 0;
var spinDist = 6;
init();
function init() {
t.remaining = itemCount;
item.holdPos = [0, 0, 0];
//create children
for (var i=0; i<itemCount; i++) {
var sub = scene.items.createItem(itemType, item.owner);
sub.holdTime = 7;
t.children.push(sub);
}
nitroAudio.playSound(231, {volume: 2}, 0, item);
this.sound = nitroAudio.playSound(227, {volume: 1.5}, 0, item);
}
function onDie(final) {
if (t.sound) {
nitroAudio.instaKill(t.sound);
t.sound = null;
}
}
function update(scene) {
for (var i=0; i<t.children.length; i++) {
var child = t.children[i];
if (child == null) continue;
if (child.deadTimer > 0) {
t.children[i] = null;
t.remaining--;
continue;
}
var angle = ((i / itemCount + t.phase / t.rotationPeriod) * Math.PI * 2);
var rad = item.owner.params.colRadius;
var dist = spinDist + rad;
child.holdPos = [-Math.sin(angle) * dist, -item.owner.params.colRadius, Math.cos(angle) * dist];
}
t.phase++;
t.phase %= t.rotationPeriod;
}
function release(forward) {
//forward the release to our last child
var toUse;
for (var i=0; i<t.children.length; i++) {
var child = t.children[i];
if (child == null) continue;
if (child.deadTimer > 0) {
t.children[i] = null;
t.remaining--;
continue;
}
toUse = child;
t.children[i] = null;
t.remaining--;
break;
}
if (toUse != null) {
toUse.release(forward);
}
if (t.remaining == 0) {
item.finalize();
}
}
function draw(mvMatrix, pMatrix) { function draw(mvMatrix, pMatrix) {
//the group itself is invisible - the shells draw individually //the group itself is invisible - the shells draw individually

View File

@ -73,6 +73,11 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
this.getPosition = getPosition; this.getPosition = getPosition;
this.playCharacterSound = playCharacterSound; this.playCharacterSound = playCharacterSound;
//functions for external objects to trigger
this.damage = damage;
this.damageTime = 0;
this.damageType = -1;
this.trackAttach = null; //a normal for the kart to attach to (loop) this.trackAttach = null; //a normal for the kart to attach to (loop)
this.boostMT = 0; this.boostMT = 0;
this.boostNorm = 0; this.boostNorm = 0;
@ -399,7 +404,8 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
mat4.rotateX(mat, mat, c.angle[0]*(-Math.PI/180)); mat4.rotateX(mat, mat, c.angle[0]*(-Math.PI/180));
k.pos = vec3.clone(c2.pos); k.pos = vec3.clone(c2.pos);
vec3.add(k.pos, k.pos, vec3.transformMat4([], [0,16,16], mat)); vec3.add(k.pos, k.pos, vec3.transformMat4([], [0,16,32], mat));
k.airTime = 4;
k.physicalDir = (180-c2.angle[1])*(Math.PI/180); k.physicalDir = (180-c2.angle[1])*(Math.PI/180);
k.angle = k.physicalDir; k.angle = k.physicalDir;
@ -438,7 +444,6 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
} }
} }
} else { //default kart mode } else { //default kart mode
if (k.OOB > 0) { if (k.OOB > 0) {
playCharacterSound(0); playCharacterSound(0);
var current = checkpoints[k.checkPointNumber]; var current = checkpoints[k.checkPointNumber];
@ -466,180 +471,176 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
var top = params.topSpeed*effect.topSpeedMul; //if you let go of accel, drift ends anyway, so always accel in drift. var top = params.topSpeed*effect.topSpeedMul; //if you let go of accel, drift ends anyway, so always accel in drift.
var boosting = (k.boostNorm + k.boostMT)>0; var boosting = (k.boostNorm + k.boostMT)>0;
if (k.specialControlHandler != null) k.specialControlHandler();
if (boosting) { else {
var top2 if (boosting) {
if (k.boostNorm>0){ var top2
top2 = params.topSpeed*1.3; if (k.boostNorm>0){
k.boostNorm--; top2 = params.topSpeed*1.3;
} else { k.boostNorm--;
top2 = params.topSpeed*((effect.topSpeedMul >= 1)?1.3:effect.topSpeedMul); } else {
} top2 = params.topSpeed*((effect.topSpeedMul >= 1)?1.3:effect.topSpeedMul);
if (k.boostMT>0) {
k.boostMT--;
}
if (k.speed <= top2) {
k.speed += 1;
if (k.speed > top2) k.speed = top2;
} else {
k.speed *= 0.95;
}
}
//kart controls
if (k.drifting) {
if ((onGround) && !(input.accel && input.drift && (k.speed > 2 || !k.driftLanded))) {
//end drift, execute miniturbo
k.drifting = false;
clearWheelParticles();
if (sounds.powerslide != null) {
nitroAudio.instaKill(sounds.powerslide);
sounds.powerslide = null;
}
if (k.driftPSMode == 3) {
k.boostMT = params.miniTurbo;
}
k.driftPSMode = 0;
k.driftPSTick = 0;
}
if (k.driftMode == 0) {
if (input.turn > 0.30) {
k.driftMode = 2;
} else if (input.turn < -0.30) {
k.driftMode = 1;
}
} else {
if (k.driftLanded) {
var change = (((k.driftMode-1.5)*Math.PI/1.5)-k.driftOff)*0.05;
k.driftOff += change;
k.physicalDir -= change;
} }
//if we're above the initial y position, add a constant turn with a period of 180 frames. if (k.boostMT>0) {
if (!k.driftLanded && k.ylock>=0) { k.boostMT--;
k.physicalDir += (Math.PI*2/180)*(k.driftMode*2-3); }
if (k.speed <= top2) {
k.speed += 1;
if (k.speed > top2) k.speed = top2;
} else {
k.speed *= 0.95;
} }
} }
if (onGround) { //kart controls
if (!k.driftLanded) { if (k.drifting) {
if (k.driftMode == 0) { if ((onGround) && !(input.accel && input.drift && (k.speed > 2 || !k.driftLanded))) {
k.drifting = false; //end drift, execute miniturbo
clearWheelParticles(); endDrift();
if (k.driftPSMode == 3) {
k.boostMT = params.miniTurbo;
} }
else { k.driftPSMode = 0;
k.driftPSMode = 0; k.driftPSTick = 0;
k.driftPSTick = 0; }
k.driftLanded = true;
if (k.drifting) setWheelParticles(20, 1); //20 = smoke, 1 = drift priority if (k.driftMode == 0) {
if (input.turn > 0.30) {
k.driftMode = 2;
} else if (input.turn < -0.30) {
k.driftMode = 1;
}
} else {
if (k.driftLanded) {
var change = (((k.driftMode-1.5)*Math.PI/1.5)-k.driftOff)*0.05;
k.driftOff += change;
k.physicalDir -= change;
}
//if we're above the initial y position, add a constant turn with a period of 180 frames.
if (!k.driftLanded && k.ylock>=0) {
k.physicalDir += (Math.PI*2/180)*(k.driftMode*2-3);
} }
} }
if (k.drifting) {
if (onGround) {
if (!k.driftLanded) {
if (k.driftMode == 0) {
endDrift();
}
else {
k.driftPSMode = 0;
k.driftPSTick = 0;
k.driftLanded = true;
if (k.drifting) setWheelParticles(20, 1); //20 = smoke, 1 = drift priority
}
}
if (k.drifting) {
if (!boosting) {
if (k.speed <= top) {
k.speed += (k.speed/top > params.driftAccelSwitch)?params.driftAccel2:params.driftAccel1;
if (k.speed > top) k.speed = top;
} else {
k.speed *= 0.95;
}
}
var turn = ((k.driftMode == 1)?(input.turn-1):(input.turn+1))/2;
k.physicalDir += params.driftTurnRate*turn+((k.driftMode == 1)?-1:1)*(50/32768)*Math.PI; //what is this mystery number i hear you ask? well my friend, this is the turn rate for outward drift.
//miniturbo code
if (input.turn != 0) {
var inward = ((input.turn>0) == k.driftMode-1); //if we're turning
switch (k.driftPSMode) {
case 0: //dpad away from direction for 10 frames
if (!inward) k.driftPSTick++;
else if (k.driftPSTick > 9) {
k.driftPSMode++;
k.driftPSTick = 1;
//play blue spark sound, flare
setWheelParticles(126, 2); //126 = blue flare, 2 = flare priority
var blue = nitroAudio.playSound(210, {}, 0, k);
blue.gainN.gain.value = 2;
} else k.driftPSTick = 0;
break;
case 1: //dpad toward direction for 10 frames
if (inward) k.driftPSTick++;
else if (k.driftPSTick > 9) {
k.driftPSMode++;
k.driftPSTick = 1;
} else k.driftPSTick = 0;
break;
case 2: //dpad away from direction for 10 frames
if (!inward) k.driftPSTick++;
else if (k.driftPSTick > 9) {
k.driftPSMode++;
k.driftPSTick = 1;
//play red sparks sound, full MT!
setWheelParticles(22, 2); //22 = red flare, 2 = flare priority
setWheelParticles(17, 1); //17 = red mt, 1 = drift priority ... 18 is sparks that come out - but their mode is not working yet (spark mode)
sounds.powerslide = nitroAudio.playSound(209, {}, 0, k);
sounds.powerslide.gainN.gain.value = 2;
} else k.driftPSTick = 0;
break;
case 3: //turbo charged
break;
}
}
}
}
}
if (!k.drifting) {
if (onGround) {
var effect = params.colParam[groundEffect];
if (!boosting) { if (!boosting) {
if (k.speed <= top) { if (input.accel) {
k.speed += (k.speed/top > params.driftAccelSwitch)?params.driftAccel2:params.driftAccel1; if (k.speed <= top) {
if (k.speed > top) k.speed = top; k.speed += (k.speed/top > params.accelSwitch)?params.accel2:params.accel1;
if (k.speed > top) k.speed = top;
} else {
k.speed *= 0.95;
}
} else { } else {
k.speed *= 0.95; k.speed *= params.decel;
} }
} }
var turn = ((k.driftMode == 1)?(input.turn-1):(input.turn+1))/2; if ((input.accel && k.speed >= 0) || (k.speed > 0.1)) {
k.physicalDir += params.turnRate*input.turn;
k.physicalDir += params.driftTurnRate*turn+((k.driftMode == 1)?-1:1)*(50/32768)*Math.PI; //what is this mystery number i hear you ask? well my friend, this is the turn rate for outward drift. } else if ( k.speed < -0.1) {
k.physicalDir -= params.turnRate*input.turn;
//miniturbo code
if (input.turn != 0) {
var inward = ((input.turn>0) == k.driftMode-1); //if we're turning
switch (k.driftPSMode) {
case 0: //dpad away from direction for 10 frames
if (!inward) k.driftPSTick++;
else if (k.driftPSTick > 9) {
k.driftPSMode++;
k.driftPSTick = 1;
//play blue spark sound, flare
setWheelParticles(126, 2); //126 = blue flare, 2 = flare priority
var blue = nitroAudio.playSound(210, {}, 0, k);
blue.gainN.gain.value = 2;
} else k.driftPSTick = 0;
break;
case 1: //dpad toward direction for 10 frames
if (inward) k.driftPSTick++;
else if (k.driftPSTick > 9) {
k.driftPSMode++;
k.driftPSTick = 1;
} else k.driftPSTick = 0;
break;
case 2: //dpad away from direction for 10 frames
if (!inward) k.driftPSTick++;
else if (k.driftPSTick > 9) {
k.driftPSMode++;
k.driftPSTick = 1;
//play red sparks sound, full MT!
setWheelParticles(22, 2); //22 = red flare, 2 = flare priority
setWheelParticles(17, 1); //17 = red mt, 1 = drift priority
sounds.powerslide = nitroAudio.playSound(209, {}, 0, k);
sounds.powerslide.gainN.gain.value = 2;
} else k.driftPSTick = 0;
break;
case 3: //turbo charged
break;
}
} }
}
}
}
if (!k.drifting) { if (input.drift) {
if (onGround) { ylvel = 1.25;
var effect = params.colParam[groundEffect]; k.vel[1] += 1.25;
if (!boosting) { k.airTime = 4;
if (input.accel) { k.drifting = true;
if (k.speed <= top) { k.driftLanded = false;
k.speed += (k.speed/top > params.accelSwitch)?params.accel2:params.accel1; k.driftMode = 0;
if (k.speed > top) k.speed = top; k.ylock = 0;
} else { onGround = false;
k.speed *= 0.95;
} var boing = nitroAudio.playSound(207, {transpose: -4}, 0, k);
} else { boing.gainN.gain.value = 2;
k.speed *= params.decel; }
} else {
if (input.drift) {
ylvel = 0;
k.drifting = true;
k.driftLanded = false;
k.driftMode = 0;
k.ylock = -0.001;
} }
}
if ((input.accel && k.speed >= 0) || (k.speed > 0.1)) {
k.physicalDir += params.turnRate*input.turn;
} else if ( k.speed < -0.1) {
k.physicalDir -= params.turnRate*input.turn;
}
if (input.drift) {
ylvel = 1.25;
k.vel[1] += 1.25;
k.airTime = 4;
k.drifting = true;
k.driftLanded = false;
k.driftMode = 0;
k.ylock = 0;
onGround = false;
var boing = nitroAudio.playSound(207, {transpose: -4}, 0, k);
boing.gainN.gain.value = 2;
}
} else {
if (input.drift) {
ylvel = 0;
k.drifting = true;
k.driftLanded = false;
k.driftMode = 0;
k.ylock = -0.001;
} }
} }
} }
@ -708,8 +709,6 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
//move kart. //move kart.
var steps = 0; var steps = 0;
var remainingT = 1; var remainingT = 1;
var baseVel = k.vel; var baseVel = k.vel;
@ -754,6 +753,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
var mat = mat4.create(); var mat = mat4.create();
mat4.translate(mat, mat, k.pos); mat4.translate(mat, mat, k.pos);
k.mat = mat4.mul(mat, mat, k.basis); k.mat = mat4.mul(mat, mat, k.basis);
if (k.damageMat != null) mat4.mul(mat, mat, k.damageMat);
if (input.item) { if (input.item) {
scene.items.addItem(0, scene.karts.indexOf(k), {}) scene.items.addItem(0, scene.karts.indexOf(k), {})
@ -763,6 +763,83 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
positionChanged(lastPos, k.pos); positionChanged(lastPos, k.pos);
} }
function endDrift() {
k.drifting = false;
clearWheelParticles();
if (sounds.powerslide != null) {
nitroAudio.instaKill(sounds.powerslide);
sounds.powerslide = null;
}
}
function damage(damageType) {
if (k.damageType >= damageType) {
return; //we are already damaged
}
//TODO: check invuln state
k.specialControlHandler = damagedControls;
playCharacterSound((damageType == 0) ? 1 : 0);
k.damageType = damageType;
k.ylock = 0;
k.anim.setAnim(k.charRes.spinA);
k.animMode = "spin";
if (k.drifting) {
endDrift();
}
k.boostMT = 0;
k.boostNorm = 0;
switch (damageType) {
case 0:
k.damageTime = 40;
break;
case 1:
k.damageTime = 80;
k.vel[1] += 3;
ylvel = 3;
k.airTime = 4;
break;
case 2:
k.damageTime = 105;
k.vel[1] += 8;
ylvel = 8;
k.airTime = 4;
break;
}
}
function damagedControls(kart) {
if (--k.damageTime == 0) {
k.anim.setAnim(k.charRes.driveA);
k.animMode = "drive";
k.specialControlHandler = null;
k.damageType = -1;
k.damageMat = null;
}
vec3.scale(k.vel, k.vel, 0.98);
k.speed *= 0.98;
var total = 40;
switch (k.damageType) {
case 1:
total = 80;
break;
case 2:
total = 105;
break;
}
var anim = 1 - (k.damageTime / total);
k.damageMat = mat4.create();
var flip = ((k.damageType%2) == 1)? 1 : -1;
var animOff = Math.min(1, anim*1.75);
mat4.rotateX(k.damageMat, k.damageMat, Math.PI*2 * animOff * k.damageType * flip);
if (k.damageType == 0) mat4.rotateY(k.damageMat, k.damageMat, Math.PI*-2 * anim);
else mat4.rotateY(k.damageMat, k.damageMat, Math.PI/12 * Math.sin(animOff*Math.PI));
}
function triggerCannon(id) { function triggerCannon(id) {
if (k.cannon != null) return; if (k.cannon != null) return;
k.cannon = id; k.cannon = id;
@ -923,7 +1000,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.kartColVel[1] = 0; k.kartColVel[1] = 0;
//play this kart's horn //play this kart's horn
nitroAudio.playSound(192 + charRes.sndOff/14, { volume: 2 }, 0, k); nitroAudio.playSound(193 + charRes.sndOff/14, { volume: 2 }, 0, k);
} }
function fixDir(dir) { function fixDir(dir) {
@ -940,6 +1017,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
} }
function updateKartSound(mode, input) { function updateKartSound(mode, input) {
if (!k.local) return; //for now, don't play kart sounds from other racers.
var turn = (onGround && !k.drifting)?(1-Math.abs(input.turn)/11):1; var turn = (onGround && !k.drifting)?(1-Math.abs(input.turn)/11):1;
var transpose = (mode == 0)?0:(22*turn*Math.min(1.3, k.speed/params.topSpeed)); var transpose = (mode == 0)?0:(22*turn*Math.min(1.3, k.speed/params.topSpeed));
@ -1028,12 +1106,15 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
} }
function sndUpdate(view) { function sndUpdate(view) {
/*
k.soundProps.pos = vec3.transformMat4([], k.pos, view); k.soundProps.pos = vec3.transformMat4([], k.pos, view);
if (k.soundProps.lastPos != null) k.soundProps.vel = vec3.sub([], k.soundProps.pos, k.soundProps.lastPos); if (k.soundProps.lastPos != null) k.soundProps.vel = vec3.sub([], k.soundProps.pos, k.soundProps.lastPos);
else k.soundProps.vel = [0, 0, 0]; else k.soundProps.vel = [0, 0, 0];
*/
k.soundProps.lastPos = k.soundProps.pos; k.soundProps.lastPos = k.soundProps.pos;
k.soundProps.pos = k.pos; //todo: reintroduce doppler via emulation
k.soundProps.refDistance = 192/1024; k.soundProps.refDistance = 192;
k.soundProps.rolloffFactor = 1; k.soundProps.rolloffFactor = 1;
var calcVol = (k.soundProps.refDistance / (k.soundProps.refDistance + k.soundProps.rolloffFactor * (Math.sqrt(vec3.dot(k.soundProps.pos, k.soundProps.pos)) - k.soundProps.refDistance))); var calcVol = (k.soundProps.refDistance / (k.soundProps.refDistance + k.soundProps.rolloffFactor * (Math.sqrt(vec3.dot(k.soundProps.pos, k.soundProps.pos)) - k.soundProps.refDistance)));
@ -1080,7 +1161,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
var angle = Math.acos(vec3.dot(vec3.scale(vec3.create(), k.gravity, -1/gravS), n)); var angle = Math.acos(vec3.dot(vec3.scale(vec3.create(), k.gravity, -1/gravS), n));
var adjustPos = true; var adjustPos = true;
if (colType == MKDS_COLTYPE.OOB || colType == MKDS_COLTYPE.FALL) { if (MKDS_COLTYPE.GROUP_OOB.indexOf(colType) != -1) {
k.OOB = 1; k.OOB = 1;
} }
@ -1101,12 +1182,18 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
} }
vec3.sub(k.vel, k.vel, vec3.scale(vec3.create(), adjN, proj)); vec3.sub(k.vel, k.vel, vec3.scale(vec3.create(), adjN, proj));
if (colType == MKDS_COLTYPE.KNOCKBACK_DAMAGE && k.damageType == -1) {
if (dat.object.vel) vec3.add(k.vel, k.vel, dat.object.vel);
vec3.add(k.vel, k.vel, vec3.scale(vec3.create(), adjN, 1.25));
k.damage(MKDSCONST.DAMAGE_FLIP);
}
//convert back to angle + speed to keep change to kart vel //convert back to angle + speed to keep change to kart vel
var v = k.vel; var v = k.vel;
k.speed = Math.sqrt(v[0]*v[0]+v[2]*v[2]); k.speed = Math.sqrt(v[0]*v[0]+v[2]*v[2]);
k.angle = Math.atan2(v[0], -v[2]); k.angle = Math.atan2(v[0], -v[2]);
stuckTo = dat.object;
} else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) { } else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
//sliding plane //sliding plane
if (MKDS_COLTYPE.GROUP_BOOST.indexOf(colType) != -1) { if (MKDS_COLTYPE.GROUP_BOOST.indexOf(colType) != -1) {
@ -1117,7 +1204,8 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
if (k.vel[1] > 0) k.vel[1] = 0; if (k.vel[1] > 0) k.vel[1] = 0;
var proj = vec3.dot(k.vel, an); var proj = vec3.dot(k.vel, an);
if (!stick && proj < -4 && k.vel[1] < -2) { proj -= 1.5; } if (k.damageType > 0) proj *= 1.7;
else if (!stick && proj < -4 && k.vel[1] < -2) { proj -= 1.5; }
vec3.sub(k.vel, k.vel, vec3.scale(vec3.create(), an, proj)); vec3.sub(k.vel, k.vel, vec3.scale(vec3.create(), an, proj));
if (stick) { if (stick) {

View File

@ -2,19 +2,20 @@
window.KartItems = function(kart, scene) { window.KartItems = function(kart, scene) {
var t = this; var t = this;
t.heldItem = null; //of type Item t.heldItem = null; //held item, or item that is bound to us. (bound items have hold type 'func', eg. triple shell)
t.currentItem = null; //string name for item t.currentItem = null; //string name for item
t.specificItem = null; t.specificItem = null;
t.empty = true; t.empty = true;
t.cycleTime = 0; t.cycleTime = 0;
t.totalTime = 230; t.totalTime = 230;
var maxItemTime = 230; var maxItemTime = 230;
var minItemTime = 80; var minItemTime = 80;
var carouselSfx = null; var carouselSfx = null;
var lastItemState = false; var lastItemState = false;
var holdAppearDelay = 15; var holdAppearDelay = 15;
var hurtExplodeDelay = 105 //turn right slightly, huge double backflip, small bounces. var hurtExplodeDelay = 105; //turn right slightly, huge double backflip, small bounces.
var hurtFlipDelay = 80; //turn right slightly, bounce twice, forward flip var hurtFlipDelay = 80; //turn right slightly, bounce twice, forward flip
var hurtSpinDelay = 40; //counter clockwise spin var hurtSpinDelay = 40; //counter clockwise spin
@ -43,8 +44,22 @@ window.KartItems = function(kart, scene) {
} }
} }
function createItem() {
var item = scene.items.createItem(t.currentItem, kart);
return item;
}
function release(input) {
if (t.heldItem != null) {
t.heldItem.release(input.airTurn);
}
t.heldItem = null;
kart.playCharacterSound(7);
}
function update(input) { function update(input) {
var pressed = (input.item && !lastItemState); var pressed = (input.item && !lastItemState);
var released = (lastItemState && !input.item);
if (!t.empty) { if (!t.empty) {
if (t.currentItem == null) { if (t.currentItem == null) {
//carousel //carousel
@ -53,24 +68,48 @@ window.KartItems = function(kart, scene) {
if (carouselSfx != null) nitroAudio.kill(carouselSfx); if (carouselSfx != null) nitroAudio.kill(carouselSfx);
//decide on an item //decide on an item
var item = "koura_g"; var item = "banana"; //koura_g, banana, f_box, koura_group, koura_group-bomb-7
sfx((specialItems.indexOf(item) == -1) ? 63 : 64); sfx((specialItems.indexOf(item) == -1) ? 63 : 64);
t.currentItem = item; t.currentItem = item;
} else { } else {
//if item button is pressed, we speed up the carousel //if item button is pressed, we speed up the carousel
if (pressed) { if (pressed && t.heldItem == null) {
t.totalTime = Math.max(minItemTime, t.totalTime - 20); t.totalTime = Math.max(minItemTime, t.totalTime - 20);
} }
} }
} else { } else if (t.heldItem == null) {
if (pressed) { if (pressed) {
//fire? //fire?
t.currentItem = null; t.heldItem = createItem();
t.empty = true; //t.currentItem = null;
kart.playCharacterSound(7); //t.empty = true;
if (t.heldItem.canBeHeld()) {
//begin holding
} else {
release(input);
}
pressed = false;
} }
} }
}
//todo: if held item has been destroyed, stop holding it.
if (t.heldItem != null) {
if (t.heldItem.dead) {
t.heldItem = null;
} else {
//t.heldItem.updateHold(kart);
if (released) {
if (t.heldItem.canBeHeld() !== 'func') release(input);
} else if (pressed) {
//special release: triple shells, bananas. object stays bound when released
t.heldItem.release(input.airTurn);
kart.playCharacterSound(7);
if (t.heldItem.dead) t.heldItem = null;
}
}
} }
lastItemState = input.item; lastItemState = input.item;
} }

View File

@ -75,7 +75,7 @@ window.objDatabase = new (function(){
t[0x0195] = ObjBus; t[0x0195] = ObjBus;
t[0x00CC] = ObjDecor; //DEBUG: pianta bridge t[0x00CC] = ObjBridge; //DEBUG: pianta bridge
t[0x000D] = ObjDecor; //DEBUG: puddle t[0x000D] = ObjDecor; //DEBUG: puddle
t[0x0158] = ObjDecor; //DEBUG: airship (routed) t[0x0158] = ObjDecor; //DEBUG: airship (routed)

View File

@ -41,11 +41,15 @@ window.ObjGear = function(obji, scene) {
t.angle = 0; t.angle = 0;
t.dir = (t.wB1 == 0) t.dir = (t.wB1 == 0)
t.colFrame = 0;
var colRes;
var dirVel = 0; var dirVel = 0;
var prevMat; var prevMat;
var curMat; var curMat;
setMat(); var colMat = mat4.create();
prevMat = curMat; prevMat = curMat;
function setMat() { function setMat() {
@ -59,6 +63,8 @@ window.ObjGear = function(obji, scene) {
mat4.rotateX(mat, mat, obji.angle[0]*(Math.PI/180)); mat4.rotateX(mat, mat, obji.angle[0]*(Math.PI/180));
mat4.rotateY(mat, mat, t.angle); mat4.rotateY(mat, mat, t.angle);
mat4.scale(colMat, mat, [colRes.scale, colRes.scale, colRes.scale]);
t.colFrame++;
curMat = mat; curMat = mat;
} }
@ -119,43 +125,24 @@ window.ObjGear = function(obji, scene) {
} }
} }
function cloneKCL(kcl) {
return JSON.parse(JSON.stringify(kcl));
}
function provideRes(r) { function provideRes(r) {
res = r; //...and gives them to us. :) res = r; //...and gives them to us. :)
colRes = cloneKCL(res.mdl[0].getCollisionModel(0, 0));
} }
function getCollision() { function getCollision() {
var obj = {}; return { tris: colRes.dat, mat: colMat, frame: t.colFrame };
var inf = res.mdl[0].getCollisionModel(0, 0);
obj.tris = inf.dat;
var mat = mat4.translate([], mat4.create(), t.pos);
mat4.scale(mat, mat, vec3.mul([], [16*inf.scale, 16*inf.scale, 16*inf.scale], t.scale));
mat4.rotateY(mat, mat, obji.angle[1]*(Math.PI/180));
mat4.rotateX(mat, mat, obji.angle[0]*(Math.PI/180));
mat4.rotateY(mat, mat, t.angle);
obj.mat = mat;
return obj;
} }
function moveWith(obj) { //used for collidable objects that move. function moveWith(obj) { //used for collidable objects that move.
//the most general way to move something with an object is to multiply its position by the inverse mv matrix of that object, and then the new mv matrix. //the most general way to move something with an object is to multiply its position by the inverse mv matrix of that object, and then the new mv matrix.
vec3.transformMat4(obj.pos, obj.pos, mat4.invert([], prevMat)) vec3.transformMat4(obj.pos, obj.pos, mat4.invert([], prevMat))
vec3.transformMat4(obj.pos, obj.pos, curMat) vec3.transformMat4(obj.pos, obj.pos, curMat)
/*var p = vec3.sub([], obj.pos, t.pos);
if (obji.ID == 0x00D1) { //todo: maybe something more general
vec3.transformMat4(p, p, mat4.rotateX([], mat4.create(), dirVel));
vec3.add(obj.pos, t.pos, p);
} else {
vec3.transformMat4(p, p, mat4.rotateY([], mat4.create(), dirVel));
vec3.add(obj.pos, t.pos, p);
obj.physicalDir -= dirVel;
}*/
} }
} }

View File

@ -34,7 +34,7 @@ window.GreenShell = function(scene, owner, time, itemID, cliID, params) {
var posSeg = vec3.clone(t.pos); var posSeg = vec3.clone(t.pos);
var ignoreList = []; var ignoreList = [];
while (steps++ < 10 && remainingT > 0.01) { while (steps++ < 10 && remainingT > 0.01) {
var result = lsc.raycast(posSeg, velSeg, scene.kcl, 0.05, ignoreList); var result = lsc.raycast(posSeg, velSeg, kcl, 0.05, ignoreList);
if (result != null) { if (result != null) {
colResponse(posSeg, velSeg, result, ignoreList) colResponse(posSeg, velSeg, result, ignoreList)
remainingT -= result.t; remainingT -= result.t;
@ -87,7 +87,9 @@ window.GreenShell = function(scene, owner, time, itemID, cliID, params) {
var v = t.vel; var v = t.vel;
t.angle = Math.atan2(v[0], -v[2]); t.angle = Math.atan2(v[0], -v[2]);
item.safeKart = null;
} else if (colType == MKDS_COLTYPE.OOB || colType == MKDS_COLTYPE.FALL) {
if (item.deadTimer == 0) item.deadTimer++;
} else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) { } else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
//sliding plane //sliding plane
var proj = vec3.dot(t.vel, n); var proj = vec3.dot(t.vel, n);

View File

@ -44,16 +44,20 @@ window.ObjSoundMaker = function(obji, scene) {
} }
function sndUpdate(view) { function sndUpdate(view) {
t.soundProps.pos = vec3.transformMat4([], t.pos, view); //t.soundProps.pos = vec3.transformMat4([], t.pos, view);
t.soundProps.pos = [0, 0, Math.sqrt(vec3.dot(t.soundProps.pos, t.soundProps.pos))] //t.soundProps.pos = [0, 0, Math.sqrt(vec3.dot(t.soundProps.pos, t.soundProps.pos))]
//if (t.soundProps.lastPos != null) t.soundProps.vel = vec3.sub([], t.soundProps.pos, t.soundProps.lastPos); //if (t.soundProps.lastPos != null) t.soundProps.vel = vec3.sub([], t.soundProps.pos, t.soundProps.lastPos);
//else t.soundProps.vel = [0, 0, 0]; //else t.soundProps.vel = [0, 0, 0];
//t.soundProps.lastPos = t.soundProps.pos; //t.soundProps.lastPos = t.soundProps.pos;
t.soundProps.refDistance = 1024/1024; t.soundProps.pos = t.pos; //todo: when reintroducing doppler, disable it on this source
t.soundProps.refDistance = 1024;
//t.soundProps.rolloffFactor = 1; //t.soundProps.rolloffFactor = 1;
var calcVol = (t.soundProps.refDistance / (t.soundProps.refDistance + t.soundProps.rolloffFactor * (t.soundProps.pos[2] - t.soundProps.refDistance))); var relPos = vec3.transformMat4([], t.pos, view);
var calcVol = (t.soundProps.refDistance / (t.soundProps.refDistance + t.soundProps.rolloffFactor * (relPos[2] - t.soundProps.refDistance)));
if (calcVol<threshold) { if (calcVol<threshold) {
if (sound != null) { if (sound != null) {

View File

@ -17,6 +17,7 @@ window.ObjTruck = function(obji, scene) {
t.pos = vec3.clone(obji.pos); t.pos = vec3.clone(obji.pos);
//t.angle = vec3.clone(obji.angle); //t.angle = vec3.clone(obji.angle);
t.scale = vec3.clone(obji.scale); t.scale = vec3.clone(obji.scale);
t.vel = vec3.create();
t.requireRes = requireRes; t.requireRes = requireRes;
t.provideRes = provideRes; t.provideRes = provideRes;
@ -24,45 +25,138 @@ window.ObjTruck = function(obji, scene) {
t.draw = draw; t.draw = draw;
t.route = scene.paths[obji.routeID]; t.route = scene.paths[obji.routeID];
t.routeSpeed = (obji.setting1>>16)/100; t.routeSpeed = 0.01;
t.routePos = (obji.setting1&0xFFFF)%t.route.length; t.unknown = (obji.setting1&0xFFFF);
t.routePos = (obji.setting1>>16); //(obji.setting1&0xFFFF)%t.route.length;
t.variant = (obji.setting2&0xFFFF); //sets design for this car (from nsbtp)
t.variant2 = (obji.setting2>>16); //0 or 1. unknown purpose
t.routePos = (t.routePos+1)%t.route.length;
t.nodes = [
t.route[(t.routePos+t.route.length-2)%t.route.length].pos,
t.route[(t.routePos+t.route.length-1)%t.route.length].pos,
t.route[t.routePos].pos,
t.route[(t.routePos+1)%t.route.length].pos
];
t.nextNode = t.route[t.routePos]; t.nextNode = t.route[t.routePos];
t.prevPos = t.pos; t.prevPos = t.pos;
t.elapsedTime = 0; t.elapsedTime = 0;
var facingNormal = [0, 1, 0];
var curNormal = [0, 1, 0]; var curNormal = [0, 1, 0];
var curFloorNormal = [0, 1, 0];
var floorNormal = [0, 1, 0]; var floorNormal = [0, 1, 0];
//collision stuff
t.collidable = true;
t.colMode = 0;
t.colRad = 512;
t.colFrame = 0;
t.moveWith = moveWith;
t.getCollision = getCollision;
var colRes;
var dirVel = 0;
var prevMat;
var curMat;
var colMat = mat4.create();
prevMat = curMat;
function setMat() {
prevMat = curMat;
var mat = mat4.create();
mat4.translate(mat, mat, t.pos);
mat4.scale(mat, mat, vec3.scale([], t.scale, 16));
mat4.mul(mat, mat, mat4.invert([], mat4.lookAt([], [0, 0, 0], curNormal, curFloorNormal)));
mat4.scale(colMat, mat, [colRes.scale, colRes.scale, colRes.scale]);
t.colFrame++;
curMat = mat;
}
//end collision stuff
function cubic1D(y0, y1, y2, y3, i) {
var a0, a1, a2, a3, i2;
i2 = i*i;
a0 = -0.5*y0 + 1.5*y1 - 1.5*y2 + 0.5*y3;
a1 = y0 - 2.5*y1 + 2*y2 - 0.5*y3;
a2 = -0.5*y0 + 0.5*y2;
a3 = y1;
return(a0 * i * i2 + a1 * i2 + a2 * i + a3);
}
function cubic3D(points, i) { //note: i is 0-1 between point 1 and 2. (0 and 3 are used to better define the curve)
var p0 = points[0];
var p1 = points[1];
var p2 = points[2];
var p3 = points[3];
return [
cubic1D(p0[0], p1[0], p2[0], p3[0], i),
cubic1D(p0[1], p1[1], p2[1], p3[1], i),
cubic1D(p0[2], p1[2], p2[2], p3[2], i)
];
}
function update(scene) { function update(scene) {
//simple behaviour, just follow the path! piece of cake. //simple behaviour, just follow the path! piece of cake.
//recalculate our route speed using a target real world speed.
var nextTime = t.elapsedTime + t.routeSpeed;
for (var i=0; i<((t.elapsedTime == 0)?3:1); i++) {
if (nextTime < 1) {
var targSpeed = 2;
var estimate = cubic3D(t.nodes, nextTime);
var estDistance = vec3.dist(estimate, t.pos);
t.routeSpeed *= targSpeed/estDistance; //correct
if (t.routeSpeed > 0.2) t.routeSpeed = 0.2;
}
}
if (t.routeSpeed <= 0) t.routeSpeed = 0.01;
t.elapsedTime += t.routeSpeed; t.elapsedTime += t.routeSpeed;
t.pos = vec3.lerp([], t.prevPos, t.nextNode.pos, t.elapsedTime/t.nextNode.duration); var i = t.elapsedTime;
if (t.elapsedTime >= t.nextNode.duration) {
t.elapsedTime = 0; var newPos = cubic3D(t.nodes, i); //vec3.lerp([], t.prevPos, t.nextNode.pos, i);
t.prevPos = t.nextNode.pos; vec3.sub(t.vel, newPos, t.pos);
t.pos = newPos;
if (t.elapsedTime >= 1) {
t.elapsedTime -= 1;
t.routePos = (t.routePos+1)%t.route.length; t.routePos = (t.routePos+1)%t.route.length;
t.nextNode = t.route[t.routePos]; t.nextNode = t.route[t.routePos];
t.nodes.splice(0, 1);
t.nodes.push(t.route[(t.routePos+1)%t.route.length].pos);
t.routeSpeed = 0.25;
} }
facingNormal = vec3.sub([], t.prevPos, t.nextNode.pos) curNormal = vec3.sub([], t.prevPos, t.pos)
vec3.normalize(facingNormal, facingNormal); t.prevPos = vec3.clone(t.pos);
var rate = 0.025
curNormal[0] += (facingNormal[0]-curNormal[0])*rate;
curNormal[1] += (facingNormal[1]-curNormal[1])*rate;
curNormal[2] += (facingNormal[2]-curNormal[2])*rate;
vec3.normalize(curNormal, curNormal); vec3.normalize(curNormal, curNormal);
if (isNaN(curNormal[0])) curNormal = [0, 0, 1];
var spos = vec3.clone(t.pos); var spos = vec3.clone(t.pos);
spos[1] += 32; spos[1] += 32;
var result = lsc.raycast(spos, [0, -100, 0], scene.kcl, 0.05, []); var result = lsc.raycast(spos, [0, -100, 0], scene, 0.05, []);
if (result != null) { if (result != null) {
floorNormal = result.normal; floorNormal = result.normal;
} else { } else {
floorNormal = [0,1,0]; floorNormal = [0,1,0];
} }
var rate = 0.025;
curFloorNormal[0] += (floorNormal[0]-curFloorNormal[0])*rate;
curFloorNormal[1] += (floorNormal[1]-curFloorNormal[1])*rate;
curFloorNormal[2] += (floorNormal[2]-curFloorNormal[2])*rate;
vec3.normalize(curFloorNormal, curFloorNormal);
setMat();
} }
function draw(view, pMatrix) { function draw(view, pMatrix) {
@ -70,23 +164,44 @@ window.ObjTruck = function(obji, scene) {
mat4.scale(mat, mat, vec3.scale([], t.scale, 16)); mat4.scale(mat, mat, vec3.scale([], t.scale, 16));
mat4.mul(mat, mat, mat4.invert([], mat4.lookAt([], [0, 0, 0], curNormal, floorNormal))); mat4.mul(mat, mat, mat4.invert([], mat4.lookAt([], [0, 0, 0], curNormal, curFloorNormal)));
res.mdl[0].setFrame(t.variant);
res.mdl[0].draw(mat, pMatrix); res.mdl[0].draw(mat, pMatrix);
} }
function requireRes() { //scene asks what resources to load function requireRes() { //scene asks what resources to load
switch (obji.ID) { switch (obji.ID) {
case 0x019A: case 0x019A:
return {mdl:[{nsbmd:"car_a.nsbmd"}]}; //one model, car return {mdl:[{nsbmd:"car_a.nsbmd"}], other:["car_a.nsbtp"]}; //one model, car
case 0x019C: case 0x019C:
return {mdl:[{nsbmd:"truck_a.nsbmd"}]}; //one model, truck return {mdl:[{nsbmd:"truck_a.nsbmd"}], other:["truck_a.nsbtp"]}; //one model, truck
case 0x0195: case 0x0195:
return {mdl:[{nsbmd:"bus_a.nsbmd"}]}; //one model, bus return {mdl:[{nsbmd:"bus_a.nsbmd"}]}; //one model, bus
} }
} }
function moveWith(obj) { //used for collidable objects that move.
//the most general way to move something with an object is to multiply its position by the inverse mv matrix of that object, and then the new mv matrix.
vec3.transformMat4(obj.pos, obj.pos, mat4.invert([], prevMat))
vec3.transformMat4(obj.pos, obj.pos, curMat);
}
function getCollision() {
return { tris: colRes.dat, mat: colMat, frame: t.colFrame };
}
function provideRes(r) { function provideRes(r) {
res = r; //...and gives them to us. :) res = r; //...and gives them to us. :)
if (r.other != null) {
if (r.other.length > 0 && r.other[0] != null) {
res.mdl[0].loadTexPAnim(r.other[0]);
}
}
colRes = res.mdl[0].getBoundingCollisionModel(0, 0);
for (var i=0; i<colRes.dat.length; i++) {
colRes.dat[i].CollisionType = MKDS_COLTYPE.KNOCKBACK_DAMAGE << 8;
}
} }
} }

View File

@ -23,6 +23,10 @@ window.ObjWater = function(obji, scene) {
t.update = update; t.update = update;
t.draw = draw; t.draw = draw;
var frame = 0; var frame = 0;
var wheight = 6.144;
var wosc = 12.288;
var wstay = 5*60;
var wchange = 4*60;
function draw(view, pMatrix) { function draw(view, pMatrix) {
if (nitroRender.flagShadow) return; if (nitroRender.flagShadow) return;
@ -34,34 +38,35 @@ window.ObjWater = function(obji, scene) {
gl.stencilFunc(gl.ALWAYS, 1, 0xFF); gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); //when depth test passes for water lower layer, pixel is already drawn, do not cover it with the white overlay (set stencil bit) gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); //when depth test passes for water lower layer, pixel is already drawn, do not cover it with the white overlay (set stencil bit)
var height = (t.pos[1])+6.144+Math.sin(frame/150)*12.288 //0.106 var height = (t.pos[1])+wheight+Math.sin(frame/150)*wosc //0.106
mat4.translate(waterM, view, [Math.sin(frame/180)*96, height-3.072, Math.cos(frame/146)*96]) mat4.translate(waterM, view, [Math.sin(frame/180)*96, height, Math.cos(frame/146)*96])
nitroRender.setAlpha(0x0A/31); nitroRender.setColMult([1, 1, 1, 0x0A/31]);
res.mdl[0].drawPoly(mat4.scale([], waterM, [16, 16, 16]), pMatrix, 0, 0); //water res.mdl[0].drawPoly(mat4.scale([], waterM, [16, 16, 16]), pMatrix, 0, 0); //water
if (res.mdl[1] != null) {
mat4.translate(waterM, view, [-Math.sin((frame+30)/180)*96, height-3, Math.cos((frame+100)/146)*96])
nitroRender.setAlpha(0x02/31);
res.mdl[1].draw(mat4.scale([], waterM, [16, 16, 16]), pMatrix); //water white detail part. stencil should do nothing here, since it's in the same position as the above.
}
gl.stencilFunc(gl.EQUAL, 0, 0xFF); gl.stencilFunc(gl.EQUAL, 0, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
if (!obji.ID == 9) { if (obji.ID != 9) {
mat4.translate(waterM, view, [0, height, 0]) mat4.translate(waterM, view, [0, height, 0])
nitroRender.setAlpha(0x10/31); nitroRender.setColMult([1, 1, 1, 0x10/31]);
res.mdl[0].drawPoly(mat4.scale([], waterM, [16, 16, 16]), pMatrix, 0, 1); //white shore wash part, water is stencil masked out res.mdl[0].drawPoly(mat4.scale([], waterM, [16, 16, 16]), pMatrix, 0, 1); //white shore wash part, water is stencil masked out
} }
gl.disable(gl.STENCIL_TEST); gl.disable(gl.STENCIL_TEST);
nitroRender.setAlpha(1); if (res.mdl[1] != null) {
mat4.translate(waterM, view, [-Math.sin((frame+30)/180)*96, height, Math.cos((frame+100)/146)*96])
nitroRender.setColMult([1, 1, 1, 0x04/31]);
res.mdl[1].draw(mat4.scale([], waterM, [16, 16, 16]), pMatrix); //water white detail part. stencil should do nothing here, since it's in the same position as the above.
}
nitroRender.setColMult([1, 1, 1, 1]);
} }
function update() { function update() {
frame = (frame+1)%197100; //it's a big number but yolo... we have the technology... frame = (frame+1)%197100; //it's a big number but yolo... we have the technology...
//TODO: physics and void-out for karts
} }
function requireRes() { //scene asks what resources to load function requireRes() { //scene asks what resources to load
@ -75,6 +80,8 @@ window.ObjWater = function(obji, scene) {
case 0x0009: case 0x0009:
return {mdl:[{nsbmd:"hyudoro_waterC.nsbmd"}, {nsbmd:"hyudoro_waterA.nsbmd"}]}; return {mdl:[{nsbmd:"hyudoro_waterC.nsbmd"}, {nsbmd:"hyudoro_waterA.nsbmd"}]};
case 0x000C: case 0x000C:
wheight = 38;
wosc = 16;
return {mdl:[{nsbmd:"mini_stage3_waterC.nsbmd"}, {nsbmd:"mini_stage3_waterA.nsbmd"}]}; return {mdl:[{nsbmd:"mini_stage3_waterC.nsbmd"}, {nsbmd:"mini_stage3_waterA.nsbmd"}]};
} }
} }

156
code/formats/2d/ncer.js Normal file
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
code/formats/2d/ncgr.js Normal file
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
code/formats/2d/nclr.js Normal file
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
code/formats/2d/nscr.js Normal file
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));
}
}

View File

@ -42,7 +42,6 @@ window.kartoffsetdata = function(input) {
var pos = vec3.create(); var pos = vec3.create();
pos[0] = view.getInt32(off, true)/4096; pos[0] = view.getInt32(off, true)/4096;
pos[1] = view.getInt32(off+4, true)/4096; pos[1] = view.getInt32(off+4, true)/4096;
console.log("charPos: "+pos[1]);
pos[2] = view.getInt32(off+8, true)/4096; pos[2] = view.getInt32(off+8, true)/4096;
off += 12; off += 12;
chars.push(pos); chars.push(pos);

View File

@ -84,7 +84,7 @@ window.kcl = function(input, mkwii) {
while (offset < octreeOffset) { while (offset < octreeOffset) {
planes.push(new Plane(view, offset)); planes.push(new Plane(view, offset));
offset += 0x10; 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] < minx) minx=vert[0];
if (vert[0] > maxx) maxx=vert[0]; if (vert[0] > maxx) maxx=vert[0];
if (vert[2] < minz) minz=vert[2]; if (vert[2] < minz) minz=vert[2];
@ -177,9 +177,9 @@ window.kcl = function(input, mkwii) {
var plane = planes[i]; var plane = planes[i];
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(plane.Vertex1[0], plane.Vertex1[2]); ctx.moveTo(plane.Vertices[0][0], plane.Vertices[0][2]);
ctx.lineTo(plane.Vertex2[0], plane.Vertex2[2]); ctx.lineTo(plane.Vertices[1][0], plane.Vertices[1][2]);
ctx.lineTo(plane.Vertex3[0], plane.Vertex3[2]); ctx.lineTo(plane.Vertices[2][0], plane.Vertices[2][2]);
ctx.closePath(); ctx.closePath();
ctx.stroke(); ctx.stroke();
@ -253,7 +253,8 @@ window.kcl = function(input, mkwii) {
function Plane(view, offset) { function Plane(view, offset) {
this.Len = readBigDec(view, offset, mkwiiMode); 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.Normal = readNormal(view.getUint16(offset+0x6, end), view);
this.NormalA = readNormal(view.getUint16(offset+0x8, end), view); this.NormalA = readNormal(view.getUint16(offset+0x8, end), view);
this.NormalB = readNormal(view.getUint16(offset+0xA, 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 crossA = vec3.cross(vec3.create(), this.NormalA, this.Normal);
var crossB = vec3.cross(vec3.create(), this.NormalB, 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.Vertices[1] = vec3.scaleAndAdd(vec3.create(), this.Vertices[0], 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[2] = vec3.scaleAndAdd(vec3.create(), this.Vertices[0], crossA, (this.Len/vec3.dot(crossA, this.NormalC)));
} }
function readVert(num, view) { function readVert(num, view) {

View File

@ -5,10 +5,34 @@
// by RHY3756547 // 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) { window.narc = function(input) {
this.load = load; this.load = load;
this.getFile = getFile; this.getFile = getFile;
this.list = list;
var arc = this; var arc = this;
var handlers = []; var handlers = [];
@ -18,8 +42,6 @@ window.narc = function(input) {
mouseY = evt.pageY; mouseY = evt.pageY;
} }
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(buffer) { function load(buffer) {
arc.buffer = buffer; //we will use this data in the future. 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! 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) { function readFileWithID(id) {
var table = arc.sections["BTAF"].files; var table = arc.sections["BTAF"].files;
var file = table[id]; var file = table[id];

View File

@ -8,6 +8,7 @@
window.ndsFS = function(input) { window.ndsFS = function(input) {
this.load = load; this.load = load;
this.getFile = getFile; this.getFile = getFile;
this.list = list;
var arc = this; var arc = this;
var handlers = []; var handlers = [];
@ -65,6 +66,22 @@ window.ndsFS = function(input) {
return null; //incomplete path; we ended on a directory, not a file! 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) { function readFileWithID(id) {
var off = arc.fileOff+id*8; var off = arc.fileOff+id*8;
/*var table = arc.sections["BTAF"].files; /*var table = arc.sections["BTAF"].files;

View File

@ -11,13 +11,17 @@
window.nftr = function(input) { window.nftr = function(input) {
var mainOff; var mainOff;
var mainObj = this; var t = this;
this.info = {};
if (input != null) { if (input != null) {
load(input); load(input);
} }
this.load = load; this.load = load;
this.drawToCanvas = drawToCanvas;
this.measureText = measureText;
this.measureMapped = measureMapped;
this.mapText = mapText;
function load(input) { function load(input) {
var view = new DataView(input); var view = new DataView(input);
@ -29,9 +33,249 @@ window.nftr = function(input) {
header = nitro.readHeader(view); header = nitro.readHeader(view);
//debugger; //debugger;
if (header.stamp != "RTFN") throw "NFTR invalid. Expected RTFN, found "+header.stamp; 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 //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; 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;
}
} }

View File

@ -66,6 +66,7 @@ window.nitro = new function() {
for (var j=0; j<16; j++) { for (var j=0; j<16; j++) {
name += readChar(view, offset++) name += readChar(view, offset++)
} }
objectData[i].name = name;
names.push(name); names.push(name);
} }

View File

@ -15,7 +15,7 @@ window.nsbca = function(input) {
var mainOff; var mainOff;
var animData; var animData;
var speeds = [1.0, 0.5, 1/3]; var speeds = [1.0, 0.5, 1/4];
var mainObj = this; var mainObj = this;
if (input != null) { if (input != null) {
@ -72,10 +72,10 @@ window.nsbca = function(input) {
function readTrans(view, off, obj) { function readTrans(view, off, obj) {
var flag = view.getUint16(off, true); //--zyx-Sr-RZYX-T- var flag = view.getUint16(off, true); //--zyx-Sr-RZYX-T-
off += 4; off += 4;
var transform = {}; var transform = {};
transform.flag = flag;
if (!((flag>>1) & 1)) { //T: translation if (!((flag>>1) & 1)) { //T: translation
var translate = [[], [], []]; //store translations in x,y,z arrays var translate = [[], [], []]; //store translations in x,y,z arrays
@ -96,7 +96,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true); inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame); 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 w = (inf.halfSize)?2:4;
var off2 = obj.baseOff+inf.off; var off2 = obj.baseOff+inf.off;
@ -132,7 +132,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true); inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame); 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. //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; var off2 = obj.baseOff+inf.off;
try { try {
@ -173,7 +173,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true); inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame); 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 w = ((inf.halfSize)?2:4);
var off2 = obj.baseOff+inf.off; var off2 = obj.baseOff+inf.off;
@ -211,21 +211,26 @@ window.nsbca = function(input) {
}; };
} else { } else {
var off2 = obj.baseOff+obj.off2+ind*10; //jump to rotation data var off2 = obj.baseOff+obj.off2+ind*10; //jump to rotation data
var d1 = view.getInt16(off2, true); var d1 = view.getUint16(off2, true);
var d2 = view.getInt16(off2+2, true); var d2 = view.getUint16(off2+2, true);
var d3 = view.getInt16(off2+4, true); var d3 = view.getUint16(off2+4, true);
var d4 = view.getInt16(off2+6, true); var d4 = view.getUint16(off2+6, true);
var d5 = view.getInt16(off2+8, true); var d5 = view.getUint16(off2+8, true);
var i6 = ((d5&7)<<12) | ((d1&7)<<9) | ((d2&7)<<6) | ((d3&7)<<3) | ((d4&7)); 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 v1 = [d1>>3, d2>>3, d3>>3];
var v2 = [d4>>3, d5>>3, i6] 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(v1, v1, 1/4096);
vec3.scale(v2, v2, 1/4096); vec3.scale(v2, v2, 1/4096);
var v3 = vec3.cross([], v1, v2) var v3 = vec3.cross([], v1, v2);
var mat = [ var mat = [
v1[0], v1[1], v1[2], v1[0], v1[1], v1[2],

View File

@ -70,6 +70,7 @@ window.nsbmd = function(input) {
head.maxStack = view.getUint8(offset+0x1A); head.maxStack = view.getUint8(offset+0x1A);
head.scale = view.getInt32(offset+0x1C, true)/4096; 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.numVerts = view.getUint16(offset+0x24, true);
head.numSurfaces = view.getUint16(offset+0x26, 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 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 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); 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} 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) { function parseBones(offset, view, polys, materials, objects, maxStack) {
var last; var last;
var commands = []; var commands = [];
var debug = true;
if (debug) console.log("== Begin Parse Bones ==");
var freeStack = maxStack; var freeStack = maxStack;
var forceID=null; var forceID=null;
var lastMat = null; var lastMat = null;
var bound = [];
var matMap = [];
while (offset<texPalOff) { //bones while (offset<texPalOff) { //bones
last = view.getUint8(offset++); last = view.getUint8(offset++);
console.log("bone cmd: 0x"+last.toString(16));
switch (last) { switch (last) {
case 0x06: //bind object transforms to parent. bone exists but is not placed in the stack case 0x06: //bind object transforms to parent. bone exists but is not placed in the stack
var obj = view.getUint8(offset++); var obj = view.getUint8(offset++);
@ -117,21 +128,50 @@ window.nsbmd = function(input) {
var object = objects.objectData[obj]; var object = objects.objectData[obj];
object.parent = parent; 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++}); commands.push({obj:obj, parent:parent, stackID:freeStack++});
break; break;
case 0x26: case 0x26:
case 0x46: //placed in the stack at stack id case 0x46: //placed in the stack at stack id
case 0x66:
var obj = view.getUint8(offset++); var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++); var parent = view.getUint8(offset++);
var zero = view.getUint8(offset++); var zero = view.getUint8(offset++);
var stackID = 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]; var object = objects.objectData[obj];
object.parent = parent; 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; break;
/*
case 0x66: //has ability to restore to another stack id. no idea how this works case 0x66: //has ability to restore to another stack id. no idea how this works
var obj = view.getUint8(offset++); var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++); var parent = view.getUint8(offset++);
@ -142,8 +182,11 @@ window.nsbmd = function(input) {
var object = objects.objectData[obj]; var object = objects.objectData[obj];
object.parent = parent; 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}); commands.push({obj:obj, parent:parent, stackID:stackID, restoreID:restoreID});
break; break;
*/
case 0x04: case 0x04:
case 0x24: case 0x24:
case 0x44: //bind material to polygon: matID, 5, polyID case 0x44: //bind material to polygon: matID, 5, polyID
@ -151,54 +194,69 @@ window.nsbmd = function(input) {
lastMat = mat; lastMat = mat;
var five = view.getUint8(offset++); //??? var five = view.getUint8(offset++); //???
var poly = 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; 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; break;
case 1: case 1:
//end of all
if (debug) console.log("[0x"+last.toString(16)+"] END OF BONES");
break; break;
case 2: //node visibility (maybe to implement this set matrix to 0) case 2: //node visibility (maybe to implement this set matrix to 0)
var node = view.getUint8(offset++); var node = view.getUint8(offset++);
var vis = view.getUint8(offset++); var vis = view.getUint8(offset++);
objects.objectData[node].vis = vis; 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; if (node > 10) debugger;
break; break;
case 3: //stack id for poly (wit) case 3: //stack id for poly (wit)
forceID = view.getUint8(offset++); forceID = view.getUint8(offset++);
console.log("stackid is "+forceID); if (debug) console.log("[0x"+last.toString(16)+"] Force stack id to " + forceID);
case 0: case 0:
break; break;
case 5: case 5: //"draw a mesh" supposedly. might require getting a snapshot of the matrices at this point
//i don't... what??
//holy shp!
var poly = view.getUint8(offset++); 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; polys.objectData[poly].mat = lastMat;
if (debug) console.log("[0x"+last.toString(16)+"] Draw " + poly + "(stack id " + polys.objectData[poly].stackID + ")");
break; break;
case 7: case 7:
//sets object to be billboard //sets object to be billboard
var obj = view.getUint8(offset++); var obj = view.getUint8(offset++);
objects.objectData[obj].billboardMode = 1; objects.objectData[obj].billboardMode = 1;
if (debug) console.log("[0x"+last.toString(16)+"] Object " + obj + " set to full billboard mode.");
mainObj.hasBillboards = true; mainObj.hasBillboards = true;
break; break;
case 8: case 8:
//sets object to be billboard around only y axis. (xz remain unchanged) //sets object to be billboard around only y axis. (xz remain unchanged)
var obj = view.getUint8(offset++); var obj = view.getUint8(offset++);
objects.objectData[obj].billboardMode = 2; objects.objectData[obj].billboardMode = 2;
if (debug) console.log("[0x"+last.toString(16)+"] Object " + obj + " set to Y billboard mode.");
mainObj.hasBillboards = true; mainObj.hasBillboards = true;
break; break;
case 9: //skinning equ. not used?
if (debug) console.log("[0x"+last.toString(16)+"] Skinning Equation (UNIMPLEMENTED)");
debugger;
break;
case 0x0b: 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: 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: default:
console.log("bone transform unknown: "+last); console.log("bone transform unknown: 0x"+last.toString(16));
break; break;
} }
} }
//if (window.throwWhatever) debugger;
if (debug) console.log("== End Parse Bones ==");
return commands; return commands;
} }
@ -213,7 +271,6 @@ window.nsbmd = function(input) {
}*/ }*/
var polyAttrib = view.getUint16(offset+12, true); 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. var flags = view.getUint16(offset+22, true); //other info in here is specular data etc.

View File

@ -120,6 +120,7 @@ window.nsbta = function(input) {
if (i == 2) obj[prop[i]].push(data2); if (i == 2) obj[prop[i]].push(data2);
} else { //data is found at offset } else { //data is found at offset
frames = frames>>obj.frameStep[prop[i]]; frames = frames>>obj.frameStep[prop[i]];
if (obj.frameStep[prop[i]] > 0) frames++; //one extra frame, for interpolation
//frames -= 1; //frames -= 1;
var off = base + value-8; var off = base + value-8;
for (var j=0; j<frames*((i==2)?2:1); j++) { for (var j=0; j<frames*((i==2)?2:1); j++) {

View File

@ -38,7 +38,11 @@ window.nsbtp = function(input) {
var texTotal; var texTotal;
var palTotal; var palTotal;
var texNamesOff;
var palNamesOff;
var texNames;
var palNames;
function load(input) { function load(input) {
var view = new DataView(input); var view = new DataView(input);
@ -96,13 +100,35 @@ window.nsbtp = function(input) {
var duration = view.getUint16(offset, true); var duration = view.getUint16(offset, true);
texTotal = view.getUint8(offset+2); texTotal = view.getUint8(offset+2);
palTotal = view.getUint8(offset+3); palTotal = view.getUint8(offset+3);
var unknown = view.getUint16(offset+4, true); texNamesOff = view.getUint16(offset+4, true);
var unknown2 = view.getUint16(offset+6, 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 //...then another nitro
var data = nitro.read3dInfo(view, offset+8, matInfoHandler); 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) { function matInfoHandler(view, offset, base) {
@ -124,9 +150,9 @@ window.nsbtp = function(input) {
// 16char texname // 16char texname
//then (frame of these) //then (frame of these)
// 16char palname // 16char palname
// texture animations are bound to the material via the name of this block.
var frames = view.getUint16(offset, true); var frames = view.getUint32(offset, true);
obj.matinfo = view.getUint16(offset+2, true);
obj.flags = view.getUint16(offset+4, true); obj.flags = view.getUint16(offset+4, true);
var offset2 = view.getUint16(offset+6, true); var offset2 = view.getUint16(offset+6, true);
offset += 8; offset += 8;
@ -135,33 +161,16 @@ window.nsbtp = function(input) {
offset = matOff + offset2; offset = matOff + offset2;
//info and timing for each frame //info and timing for each frame
for (var i=0; i<frames; i++) { for (var i=0; i<frames; i++) {
obj.frames[i] = { var entry = {
time: view.getUint16(offset, true), time: view.getUint16(offset, true),
tex: view.getUint8(offset+2), tex: view.getUint8(offset+2), //index into names?
mat: view.getUint8(offset+3), 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; 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; return obj;
} }

View File

@ -74,10 +74,21 @@ window.nsbtx = function(input, tex0) {
paletteInfo = nitro.read3dInfo(view, mainOff + palInfoOff, palInfoHandler); paletteInfo = nitro.read3dInfo(view, mainOff + palInfoOff, palInfoHandler);
textureInfo = nitro.read3dInfo(view, mainOff + texInfoOff, texInfoHandler); textureInfo = nitro.read3dInfo(view, mainOff + texInfoOff, texInfoHandler);
buildNameToIndex(paletteInfo);
buildNameToIndex(textureInfo);
thisObj.paletteInfo = paletteInfo; thisObj.paletteInfo = paletteInfo;
thisObj.textureInfo = textureInfo; 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) { function readTexWithPal(textureId, palId) {
var tex = textureInfo.objectData[textureId]; var tex = textureInfo.objectData[textureId];
var pal = paletteInfo.objectData[palId]; var pal = paletteInfo.objectData[palId];
@ -106,6 +117,7 @@ window.nsbtx = function(input, tex0) {
var dat = texView.getUint8(off++) var dat = texView.getUint8(off++)
col = readPalColour(palView, palOff, dat&31, trans); col = readPalColour(palView, palOff, dat&31, trans);
col[3] = (dat>>5)*(255/7); col[3] = (dat>>5)*(255/7);
premultiply(col);
} else if (format == 2) { //2 bit pal } else if (format == 2) { //2 bit pal
if (i%4 == 0) databuf = texView.getUint8(off++); if (i%4 == 0) databuf = texView.getUint8(off++);
@ -126,6 +138,7 @@ window.nsbtx = function(input, tex0) {
var dat = texView.getUint8(off++) var dat = texView.getUint8(off++)
col = readPalColour(palView, palOff, dat&7, trans); col = readPalColour(palView, palOff, dat&7, trans);
col[3] = (dat>>3)*(255/31); col[3] = (dat>>3)*(255/31);
premultiply(col);
} else if (format == 7) { //raw color data } else if (format == 7) { //raw color data
col = texView.getUint16(off, true); col = texView.getUint16(off, true);
@ -134,6 +147,7 @@ window.nsbtx = function(input, tex0) {
colourBuffer[2] = Math.round((((col>>10)&31)/31)*255) colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
colourBuffer[3] = Math.round((col>>15)*255); colourBuffer[3] = Math.round((col>>15)*255);
col = colourBuffer; col = colourBuffer;
premultiply(col);
off += 2; off += 2;
} else { } else {
@ -146,6 +160,12 @@ window.nsbtx = function(input, tex0) {
return canvas; 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. 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 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) var texView = new DataView(compData); //real texture data - 32 bits per 4x4 block (one byte per 4px horizontal line, each descending 1px)

File diff suppressed because it is too large Load Diff

View File

@ -42,10 +42,25 @@ window.nitroAnimator = function(bmd, bca) {
return bca.animData.objectData[anim].frames; return bca.animData.objectData[anim].frames;
} }
function getFrames(anim, length, frame, totalLength) {
//totalLength (realtime)
//startFrame (realtime)
//endFrame (realtime)
//frame is between 0 and totalLength
var f = Math.max(0, (Math.min(frame, anim.endFrame) - anim.startFrame) * anim.speed); //speed relative time
var end = Math.floor((anim.endFrame - anim.startFrame) * anim.speed);
var realEnd = Math.min(length-1, end);
return [Math.min(realEnd, Math.floor(f)), Math.min(realEnd, Math.ceil(f)), f%1];
}
function setFrame(anim, modelind, frame) { function setFrame(anim, modelind, frame) {
var b = bca.animData.objectData[anim]; var b = bca.animData.objectData[anim];
var totalLength = getLength(anim);
frame %= getLength(anim);
var fLow = Math.floor(frame); var fLow = Math.floor(frame);
var fHigh = Math.ceil(frame); var fHigh = Math.ceil(frame);
var iterp = frame%1; var iterp = frame%1;
@ -64,27 +79,21 @@ window.nitroAnimator = function(bmd, bca) {
if (a.translate != null) { if (a.translate != null) {
translate = []; translate = [];
if (a.tlExtra[0] != null) { if (a.tlExtra[0] != null) {
var f = frame * a.tlExtra[0].speed; var f = getFrames(a.tlExtra[0], a.translate[0].length, frame, totalLength);
var fLow = Math.floor(f)%a.translate[0].length; var p = f[2];
var fHigh = Math.ceil(f)%a.translate[0].length; translate[0] = a.translate[0][f[0]]*(1-p) + a.translate[0][f[1]]*(p);
var p = f%1;
translate[0] = a.translate[0][fHigh]*(p) + a.translate[0][fLow]*(1-p);
} else translate[0] = a.translate[0][0]; } else translate[0] = a.translate[0][0];
if (a.tlExtra[1] != null) { if (a.tlExtra[1] != null) {
var f = frame * a.tlExtra[1].speed; var f = getFrames(a.tlExtra[1], a.translate[1].length, frame, totalLength);
var fLow = Math.floor(f)%a.translate[1].length; var p = f[2];
var fHigh = Math.ceil(f)%a.translate[1].length; translate[1] = a.translate[1][f[0]]*(1-p) + a.translate[1][f[1]]*(p);
var p = f%1;
translate[1] = a.translate[1][fHigh]*(p) + a.translate[1][fLow]*(1-p);
} else translate[1] = a.translate[1][0]; } else translate[1] = a.translate[1][0];
if (a.tlExtra[2] != null) { if (a.tlExtra[2] != null) {
var f = frame * a.tlExtra[2].speed; var f = getFrames(a.tlExtra[2], a.translate[2].length, frame, totalLength);
var fLow = Math.floor(f)%a.translate[2].length; var p = f[2];
var fHigh = Math.ceil(f)%a.translate[2].length; translate[2] = a.translate[2][f[0]]*(1-p) + a.translate[2][f[1]]*(p);
var p = f%1;
translate[2] = a.translate[2][fHigh]*(p) + a.translate[2][fLow]*(1-p);
} else translate[2] = a.translate[2][0]; } else translate[2] = a.translate[2][0];
} else { } else {
translate = fa.translate; translate = fa.translate;
@ -93,13 +102,11 @@ window.nitroAnimator = function(bmd, bca) {
var rotate; var rotate;
if (a.rotate != null) { if (a.rotate != null) {
if (a.rotExtra != null) { if (a.rotExtra != null) {
var f = frame * a.rotExtra.speed; var f = getFrames(a.rotExtra, a.rotate.length, frame, totalLength);
var fLow = Math.floor(f)%a.rotate.length; var p = f[2];
var fHigh = Math.ceil(f)%a.rotate.length;
var p = f%1;
var r1 = parseRotation(a.rotate[fLow]); var r1 = parseRotation(a.rotate[f[0]]);
var r2 = parseRotation(a.rotate[fHigh]); var r2 = parseRotation(a.rotate[f[1]]);
rotate = lerpMat3(r1, r2, p); rotate = lerpMat3(r1, r2, p);
} else { } else {
rotate = parseRotation(a.rotate[0]); rotate = parseRotation(a.rotate[0]);
@ -112,27 +119,21 @@ window.nitroAnimator = function(bmd, bca) {
if (a.scale != null) { if (a.scale != null) {
scale = []; scale = [];
if (a.scExtra[0] != null) { if (a.scExtra[0] != null) {
var f = frame * a.scExtra[0].speed; var f = getFrames(a.scExtra[0], a.scale[0].length, frame, totalLength);
var fLow = Math.floor(f)%a.scale[0].length; var p = f[2];
var fHigh = Math.ceil(f)%a.scale[0].length; scale[0] = a.scale[0][f[0]].s1*(1-p) + a.scale[0][f[1]].s1*(p);
var p = f%1;
scale[0] = a.scale[0][fHigh].s1*(p) + a.scale[0][fLow].s1*(1-p);
} else scale[0] = a.scale[0][0].s1; } else scale[0] = a.scale[0][0].s1;
if (a.scExtra[1] != null) { if (a.scExtra[1] != null) {
var f = frame * a.scExtra[1].speed; var f = getFrames(a.scExtra[1], a.scale[1].length, frame, totalLength);
var fLow = Math.floor(f)%a.scale[1].length; var p = f[2];
var fHigh = Math.ceil(f)%a.scale[1].length; scale[1] = a.scale[1][f[0]].s1*(1-p) + a.scale[1][f[1]].s1*(p);
var p = f%1;
scale[1] = a.scale[1][fHigh].s1*(p) + a.scale[1][fLow].s1*(1-p);
} else scale[1] = a.scale[1][0].s1; } else scale[1] = a.scale[1][0].s1;
if (a.scExtra[2] != null) { if (a.scExtra[2] != null) {
var f = frame * a.scExtra[2].speed; var f = getFrames(a.scExtra[2], a.scale[2].length, frame, totalLength);
var fLow = Math.floor(f)%a.scale[2].length; var p = f[2];
var fHigh = Math.ceil(f)%a.scale[2].length; scale[2] = a.scale[2][f[0]].s1*(1-p) + a.scale[2][f[1]].s1*(p);
var p = f%1;
scale[2] = a.scale[2][fHigh].s1*(p) + a.scale[2][fLow].s1*(1-p);
} else scale[2] = a.scale[2][0].s1; } else scale[2] = a.scale[2][0].s1;
} else { } else {
scale = fa.scale; scale = fa.scale;
@ -154,9 +155,15 @@ window.nitroAnimator = function(bmd, bca) {
var cmds = model.commands; var cmds = model.commands;
var curMat = mat4.create(); var curMat = mat4.create();
var lastStackID = 0; var lastStackID = 0;
var highestUsed = -1;
for (var i=0; i<cmds.length; i++) { for (var i=0; i<cmds.length; i++) {
var cmd = cmds[i]; var cmd = cmds[i];
if (cmd.copy != null) {
//copy this matrix to somewhere else, because it's bound and is going to be overwritten.
matrices[cmd.dest] = mat4.clone(matrices[cmd.copy]);
continue;
}
if (cmd.restoreID != null) curMat = mat4.clone(matrices[cmd.restoreID]); if (cmd.restoreID != null) curMat = mat4.clone(matrices[cmd.restoreID]);
var o = objs[cmd.obj]; var o = objs[cmd.obj];
mat4.multiply(curMat, curMat, objMats[cmd.obj]); mat4.multiply(curMat, curMat, objMats[cmd.obj]);
@ -165,6 +172,7 @@ window.nitroAnimator = function(bmd, bca) {
if (cmd.stackID != null) { if (cmd.stackID != null) {
matrices[cmd.stackID] = mat4.clone(curMat); matrices[cmd.stackID] = mat4.clone(curMat);
lastStackID = cmd.stackID; lastStackID = cmd.stackID;
if (lastStackID > highestUsed) highestUsed = lastStackID;
} else { } else {
matrices[lastStackID] = mat4.clone(curMat); matrices[lastStackID] = mat4.clone(curMat);
} }
@ -172,10 +180,14 @@ window.nitroAnimator = function(bmd, bca) {
model.lastStackID = lastStackID; model.lastStackID = lastStackID;
var scale = [model.head.scale, model.head.scale, model.head.scale];
targ.set(matBufEmpty); targ.set(matBufEmpty);
var off=0; var off=0;
for (var i=0; i<31; i++) { for (var i=0; i<=highestUsed; i++) {
if (matrices[i] != null) targ.set(matrices[i], off); if (matrices[i] != null) {
mat4.scale(matrices[i], matrices[i], scale);
targ.set(matrices[i], off);
}
off += 16; off += 16;
} }

View File

@ -223,6 +223,8 @@ window.nitroRender = new function() {
this.init = function(ctx) { this.init = function(ctx) {
gl = ctx; gl = ctx;
this.gl = gl; this.gl = gl;
this.billboardMat = mat4.create();
this.yBillboardMat = mat4.create();
shaders = nitroShaders.compileShaders(gl); shaders = nitroShaders.compileShaders(gl);
@ -233,7 +235,7 @@ window.nitroRender = new function() {
this.prepareShader = function() { this.prepareShader = function() {
//prepares the shader so no redundant calls have to be made. Should be called upon every program change. //prepares the shader so no redundant calls have to be made. Should be called upon every program change.
gl.enable(gl.BLEND); gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
this.last = {}; this.last = {};
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(this.nitroShader.samplerUniform, 0); gl.uniform1i(this.nitroShader.samplerUniform, 0);
@ -261,6 +263,12 @@ window.nitroRender = new function() {
this.prepareShader(); this.prepareShader();
} }
this.setShadBias = function(bias) {
var shader = shaders[1];
gl.uniform1f(shader.shadOffUniform, bias);
gl.uniform1f(shader.farShadOffUniform, bias);
}
this.resetShadOff = function() { this.resetShadOff = function() {
var shader = shaders[1]; var shader = shaders[1];
gl.uniform1f(shader.shadOffUniform, 0.00005+((mobile)?0.0005:0)); gl.uniform1f(shader.shadOffUniform, 0.00005+((mobile)?0.0005:0));
@ -372,7 +380,7 @@ window.nitroRender = new function() {
}; };
function nitroModel(bmd, btx, remap) { function nitroModel(bmd, btx) {
var bmd = bmd; var bmd = bmd;
this.bmd = bmd; this.bmd = bmd;
var thisObj = this; var thisObj = this;
@ -399,7 +407,9 @@ function nitroModel(bmd, btx, remap) {
this.draw = draw; this.draw = draw;
this.drawPoly = externDrawPoly; this.drawPoly = externDrawPoly;
this.drawModel = externDrawModel; this.drawModel = externDrawModel;
this.getBoundingCollisionModel = getBoundingCollisionModel;
this.getCollisionModel = getCollisionModel; this.getCollisionModel = getCollisionModel;
this.baseMat = mat4.create();
modelBuffers = [] modelBuffers = []
this.modelBuffers = modelBuffers; this.modelBuffers = modelBuffers;
@ -409,10 +419,6 @@ function nitroModel(bmd, btx, remap) {
matBuf.push({built: false, dat: new Float32Array(31*16)}); matBuf.push({built: false, dat: new Float32Array(31*16)});
} }
if (remap != null) {
setTextureRemap(remap);
}
if (btx != null) { if (btx != null) {
loadTexture(btx); loadTexture(btx);
} else if (bmd.tex != null) { } else if (bmd.tex != null) {
@ -461,41 +467,47 @@ function nitroModel(bmd, btx, remap) {
var model = models[j]; var model = models[j];
var mat = model.materials.objectData var mat = model.materials.objectData
for (var i=0; i<mat.length; i++) { for (var i=0; i<mat.length; i++) {
var m = mat[i]; mat[i].texInd = tex.length;
var texI = mat[i].tex; loadMatTex(mat[i], btx);
var palI = mat[i].pal;
//remap below
var nTex = texMap.tex[texI];
var nPal = texMap.pal[palI];
if ((texI == null && nTex == null) || (palI == null && nPal == null)) {
debugger;
console.warn("WARNING: material "+i+" in model could not be assigned a texture.")
var fC = document.createElement("canvas");
fC.width = 2;
fC.height = 2;
var ctx = fC.getContext("2d")
ctx.fillStyle = "white";
ctx.fillRect(0,0,2,2);
texCanvas.push(fC);
var t = loadTex(fC, gl, !m.repeatX, !m.repeatY);
t.realWidth = 2;
t.realHeight = 2;
tex.push(t);
continue;
}
var truetex = (nTex==null)?texI:nTex;
var truepal = (nPal==null)?palI:nPal;
var cacheID = truetex+":"+truepal;
var cached = btx.cache[cacheID];
tex.push(cacheTex(btx, truetex, truepal, m));
} }
} }
} }
function loadMatTex(mat, btx, matReplace) {
var m = mat;
if (matReplace) m = matReplace;
var texI = m.texName;
var palI = m.palName;
if (texI == null || palI == null) {
debugger;
console.warn("WARNING: material "+i+" in model could not be assigned a texture.");
/*
var fC = document.createElement("canvas");
fC.width = 2;
fC.height = 2;
var ctx = fC.getContext("2d")
ctx.fillStyle = "white";
ctx.fillRect(0,0,2,2);
texCanvas.push(fC);
var t = loadTex(fC, gl, !mat.repeatX, !mat.repeatY);
t.realWidth = 2;
t.realHeight = 2;
tex.push(t);
*/
return;
}
var truetex = loadedTex.textureInfo.nameToIndex["$" + texI] || 0;
var truepal = loadedTex.paletteInfo.nameToIndex["$" + palI] || 0;
var cacheID = truetex+":"+truepal;
var cached = btx.cache[cacheID];
tex[mat.texInd] = cacheTex(btx, truetex, truepal, mat);
}
function cacheTex(btx, truetex, truepal, m) { function cacheTex(btx, truetex, truepal, m) {
var cacheID = truetex+":"+truepal; var cacheID = truetex+":"+truepal;
var cached = btx.cache[cacheID]; var cached = btx.cache[cacheID];
@ -542,11 +554,6 @@ function nitroModel(bmd, btx, remap) {
} }
} }
function setTextureRemap(remap) {
texMap = remap;
if (loadedTex != null) loadTexture(loadedTex)
}
this.loadTexAnim = function(bta) { this.loadTexAnim = function(bta) {
texAnim = bta; texAnim = bta;
texFrame = 0; texFrame = 0;
@ -560,6 +567,11 @@ function nitroModel(bmd, btx, remap) {
texFrame = frame; texFrame = frame;
} }
this.setBaseMat = function(mat) {
thisObj.baseMat = mat;
thisObj.billboardID = -1;
}
function externDrawModel(mv, project, mdl) { function externDrawModel(mv, project, mdl) {
var models = bmd.modelData.objectData; var models = bmd.modelData.objectData;
drawModel(models[mdl], mv, project, mdl); drawModel(models[mdl], mv, project, mdl);
@ -584,7 +596,7 @@ function nitroModel(bmd, btx, remap) {
var shader = nitroRender.nitroShader; var shader = nitroRender.nitroShader;
var mv = mat4.scale([], mv, [model.head.scale, model.head.scale, model.head.scale]); //var mv = mat4.scale([], mv, [model.head.scale, model.head.scale, model.head.scale]);
gl.uniformMatrix4fv(shader.mvMatrixUniform, false, mv); gl.uniformMatrix4fv(shader.mvMatrixUniform, false, mv);
gl.uniformMatrix4fv(shader.pMatrixUniform, false, project); gl.uniformMatrix4fv(shader.pMatrixUniform, false, project);
@ -603,7 +615,85 @@ function nitroModel(bmd, btx, remap) {
} }
} }
function getCollisionModel(modelind, polyind) { //simple func to get collision model for a model. used when I'm too lazy to define my own... REQUIRES TRI MODE ACTIVE! function getBoundingCollisionModel(modelind, polyind) { //simple func to get collision model for a model. used when I'm too lazy to define my own... REQUIRES TRI MODE ACTIVE!
var model = bmd.modelData.objectData[modelind];
var poly = model.polys.objectData[polyind];
if (modelBuffers[modelind][polyind] == null) modelBuffers[modelind][polyind] = nitroRender.renderDispList(poly.disp, tex[poly.mat], (poly.stackID == null)?model.lastStackID:poly.stackID);
var tris = modelBuffers[modelind][polyind].strips[0].posArray;
var tC = tris.length/3;
var off = 0;
var min = [Infinity, Infinity, Infinity];
var max = [-Infinity, -Infinity, -Infinity];
for (var i=0; i<tC; i++) {
var tri = [tris[off++], tris[off++], tris[off++]];
for (var j=0; j<3; j++) {
if (tri[j] < min[j]) min[j] = tri[j];
if (tri[j] > max[j]) max[j] = tri[j];
}
}
//create the bounding box
out = [
{ //top
Vertices: [[max[0], max[1], max[2]], [max[0], max[1], min[2]], [min[0], max[1], min[2]]],
Normal: [0, 1, 0]
},
{
Vertices: [[min[0], max[1], min[2]], [min[0], max[1], max[2]], [max[0], max[1], max[2]]],
Normal: [0, 1, 0]
},
{ //bottom
Vertices: [[min[0], min[1], min[2]], [max[0], min[1], min[2]], [max[0], min[1], max[2]] ],
Normal: [0, -1, 0]
},
{
Vertices: [[max[0], min[1], max[2]], [min[0], min[1], max[2]], [min[0], min[1], min[2]] ],
Normal: [0, -1, 0]
},
{ //back (farthest z)
Vertices: [[max[0], max[1], max[2]], [max[0], min[1], max[2]], [min[0], min[1], max[2]]],
Normal: [0, 0, 1]
},
{
Vertices: [[min[0], min[1], max[2]], [min[0], max[1], max[2]], [max[0], max[1], max[2]]],
Normal: [0, 0, 1]
},
{ //front (closest z)
Vertices: [[min[0], min[1], min[2]], [max[0], min[1], min[2]], [max[0], max[1], min[2]]],
Normal: [0, 0, -1]
},
{
Vertices: [[max[0], max[1], min[2]], [min[0], max[1], min[2]], [min[0], min[1], min[2]]],
Normal: [0, 0, -1]
},
{ //right (pos x)
Vertices: [[max[0], max[1], max[2]], [max[0], min[1], max[2]], [max[0], min[1], min[2]]],
Normal: [1, 0, 0]
},
{
Vertices: [[max[0], min[1], min[2]], [max[0], max[1], min[2]], [max[0], max[1], max[2]]],
Normal: [1, 0, 0]
},
{ //left (neg x)
Vertices: [[-max[0], min[1], min[2]], [-max[0], min[1], max[2]], [-max[0], max[1], max[2]]],
Normal: [-1, 0, 0]
},
{
Vertices: [[-max[0], max[1], max[2]], [-max[0], max[1], min[2]], [-max[0], min[1], min[2]]],
Normal: [-1, 0, 0]
},
]
out.push()
return {dat:out, scale:model.head.scale};
}
function getCollisionModel(modelind, polyind, colType) { //simple func to get collision model for a model. used when I'm too lazy to define my own... REQUIRES TRI MODE ACTIVE!
if (collisionModel[modelind] == null) collisionModel[modelind] = []; if (collisionModel[modelind] == null) collisionModel[modelind] = [];
if (collisionModel[modelind][polyind] != null) return collisionModel[modelind][polyind]; if (collisionModel[modelind][polyind] != null) return collisionModel[modelind][polyind];
var model = bmd.modelData.objectData[modelind]; var model = bmd.modelData.objectData[modelind];
@ -617,14 +707,16 @@ function nitroModel(bmd, btx, remap) {
var off = 0; var off = 0;
for (var i=0; i<tC; i++) { for (var i=0; i<tC; i++) {
var t = {} var t = {}
t.Vertex1 = [tris[off++], tris[off++], tris[off++]]; t.Vertices = [];
t.Vertex2 = [tris[off++], tris[off++], tris[off++]]; t.Vertices[0] = [tris[off++], tris[off++], tris[off++]];
t.Vertex3 = [tris[off++], tris[off++], tris[off++]]; t.Vertices[1] = [tris[off++], tris[off++], tris[off++]];
t.Vertices[2] = [tris[off++], tris[off++], tris[off++]];
//calculate normal //calculate normal
var v = vec3.sub([], t.Vertex2, t.Vertex1); var v = vec3.sub([], t.Vertices[1], t.Vertices[0]);
var w = vec3.sub([], t.Vertex3, t.Vertex1); var w = vec3.sub([], t.Vertices[2], t.Vertices[0]);
t.Normal = vec3.cross([], v, w) t.Normal = vec3.cross([], v, w);
t.CollisionType = colType;
vec3.normalize(t.Normal, t.Normal); vec3.normalize(t.Normal, t.Normal);
out.push(t); out.push(t);
} }
@ -647,7 +739,7 @@ function nitroModel(bmd, btx, remap) {
} }
var shader = nitroRender.nitroShader; var shader = nitroRender.nitroShader;
var mv = mat4.scale([], mv, [model.head.scale, model.head.scale, model.head.scale]); //var mv = mat4.scale([], mv, [model.head.scale, model.head.scale, model.head.scale]);
gl.uniformMatrix4fv(shader.mvMatrixUniform, false, mv); gl.uniformMatrix4fv(shader.mvMatrixUniform, false, mv);
gl.uniformMatrix4fv(shader.pMatrixUniform, false, project); gl.uniformMatrix4fv(shader.pMatrixUniform, false, project);
@ -678,9 +770,13 @@ function nitroModel(bmd, btx, remap) {
//we got a match! it's wonderful :') //we got a match! it's wonderful :')
var anim = anims.objectData[animNum]; var anim = anims.objectData[animNum];
//look thru frames for the approprate point in the animation //look thru frames for the approprate point in the animation
for (var i=0; i<anim.frames.length; i++) { for (var i=anim.frames.length-1; i>=0; i--) {
if (offFrame >= anim.frames[i].time) { if (offFrame >= anim.frames[i].time) {
loadMatTex(model.materials.objectData[pmat], btx == null ? bmd.tex : btx, anim.frames[i]);
/*
tex[pmat] = cacheTex(btx == null ? bmd.tex : btx, anim.frames[i].tex, anim.frames[i].mat, model.materials.objectData[pmat]); tex[pmat] = cacheTex(btx == null ? bmd.tex : btx, anim.frames[i].tex, anim.frames[i].mat, model.materials.objectData[pmat]);
*/
break;
} }
} }
} }
@ -692,7 +788,7 @@ function nitroModel(bmd, btx, remap) {
} }
var material = model.materials.objectData[pmat]; var material = model.materials.objectData[pmat];
nitroRender.setAlpha(material.alpha) nitroRender.setAlpha(material.alpha);
if (texAnim != null) { if (texAnim != null) {
//generate and send texture matrix from data //generate and send texture matrix from data
@ -703,9 +799,7 @@ function nitroModel(bmd, btx, remap) {
if (animNum != -1) { if (animNum != -1) {
//we got a match! it's wonderful :') //we got a match! it's wonderful :')
var anim = anims.objectData[animNum]; var anim = anims.objectData[animNum];
var mat = mat3.create(); //material texture mat is ignored var mat = matAtFrame(texFrame, anim);
mat3.scale(mat, mat, [anim.scaleS[(texFrame>>anim.frameStep.scaleS)%anim.scaleS.length], anim.scaleT[(texFrame>>anim.frameStep.scaleT)%anim.scaleT.length]]);
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
gl.uniformMatrix3fv(shader.texMatrixUniform, false, mat); gl.uniformMatrix3fv(shader.texMatrixUniform, false, mat);
} else { } else {
gl.uniformMatrix3fv(shader.texMatrixUniform, false, material.texMat); gl.uniformMatrix3fv(shader.texMatrixUniform, false, material.texMat);
@ -717,24 +811,52 @@ function nitroModel(bmd, btx, remap) {
drawModelBuffer(modelBuffers[modelind][polyind], gl, shader); drawModelBuffer(modelBuffers[modelind][polyind], gl, shader);
} }
function frameLerp(frame, step, values) {
if (values.length == 1) return values[0];
var i = (frame / (1 << step)) % 1;
var len = values.length
if (step > 0) len -= 1;
var frame1 = (frame>>step)%len;
var from = values[frame1];
var to = values[frame1+1] || values[frame1];
return to * i + from * (1-i);
}
function matAtFrame(frame, anim) {
var mat = mat3.create(); //material texture mat is ignored
mat3.scale(mat, mat, [frameLerp(frame, anim.frameStep.scaleS, anim.scaleS), frameLerp(frame, anim.frameStep.scaleT, anim.scaleT)]);
mat3.translate(mat, mat, [-frameLerp(frame, anim.frameStep.translateS, anim.translateS), frameLerp(frame, anim.frameStep.translateT, anim.translateT)]);
return mat;
}
function generateMatrixStack(model, targ) { //this generates a matrix stack with the default bones. use nitroAnimator to pass custom matrix stacks using nsbca animations. function generateMatrixStack(model, targ) { //this generates a matrix stack with the default bones. use nitroAnimator to pass custom matrix stacks using nsbca animations.
var matrices = []; var matrices = [];
var objs = model.objects.objectData; var objs = model.objects.objectData;
var cmds = model.commands; var cmds = model.commands;
var curMat = mat4.create(); var curMat = mat4.clone(thisObj.baseMat);
var lastStackID = 0; var lastStackID = 0;
var highestUsed = -1;
for (var i=0; i<cmds.length; i++) { for (var i=0; i<cmds.length; i++) {
var cmd = cmds[i]; var cmd = cmds[i];
if (cmd.copy != null) {
//copy this matrix to somewhere else, because it's bound and is going to be overwritten.
matrices[cmd.dest] = mat4.clone(matrices[cmd.copy]);
continue;
}
if (cmd.restoreID != null) curMat = mat4.clone(matrices[cmd.restoreID]); if (cmd.restoreID != null) curMat = mat4.clone(matrices[cmd.restoreID]);
var o = objs[cmd.obj]; var o = objs[cmd.obj];
mat4.multiply(curMat, curMat, o.mat); mat4.multiply(curMat, curMat, o.mat);
if (o.billboardMode == 1) mat4.multiply(curMat, curMat, nitroRender.billboardMat); if (o.billboardMode == 1) mat4.multiply(curMat, curMat, nitroRender.billboardMat);
if (o.billboardMode == 2) mat4.multiply(curMat, curMat, nitroRender.yBillboardMat); if (o.billboardMode == 2) mat4.multiply(curMat, curMat, nitroRender.yBillboardMat);
if (cmd.stackID != null) { if (cmd.stackID != null) {
matrices[cmd.stackID] = mat4.clone(curMat); matrices[cmd.stackID] = mat4.clone(curMat);
lastStackID = cmd.stackID; lastStackID = cmd.stackID;
if (lastStackID > highestUsed) highestUsed = lastStackID;
} else { } else {
matrices[lastStackID] = mat4.clone(curMat); matrices[lastStackID] = mat4.clone(curMat);
} }
@ -742,10 +864,14 @@ function nitroModel(bmd, btx, remap) {
model.lastStackID = lastStackID; model.lastStackID = lastStackID;
var scale = [model.head.scale, model.head.scale, model.head.scale];
targ.set(matBufEmpty); targ.set(matBufEmpty);
var off=0; var off=0;
for (var i=0; i<31; i++) { for (var i=0; i<=highestUsed; i++) {
if (matrices[i] != null) targ.set(matrices[i], off); if (matrices[i] != null) {
mat4.scale(matrices[i], matrices[i], scale);
targ.set(matrices[i], off);
}
off += 16; off += 16;
} }

View File

@ -113,7 +113,8 @@ window.nitroShaders = new (function() {
}\n\ }\n\
\n\ \n\
void main(void) {\n\ void main(void) {\n\
vec4 col = texture2D(uSampler, vTextureCoord)*color;\n\ vec4 colorPM = vec4(color.rgb * color.a, color.a);\n\
vec4 col = texture2D(uSampler, vTextureCoord)*colorPM;\n\
\n\ \n\
vec2 ldNorm = abs((lightDist.xy)-vec2(0.5, 0.5));\n\ vec2 ldNorm = abs((lightDist.xy)-vec2(0.5, 0.5));\n\
float dist = max(ldNorm.x, ldNorm.y);\n\ float dist = max(ldNorm.x, ldNorm.y);\n\

View File

@ -32,22 +32,25 @@ window.Race3DUI = function(scene, type, animStart) {
var params = { var params = {
"count": [ //offset 21 down "count": [ //offset 21 down
-128/1024, 128/1024, -(192-11)/1024, 11/1024 -128/1024, 128/1024, -(96)/1024, 96/1024
], ],
"start": [ //offset 86 up "start": [ //offset 86 up
-128/1024, 128/1024, -(192+66)/1024, -66/1024 -128/1024, 128/1024, -(96)/1024, 96/1024
], ],
"goal": [ //why are these all so different? "goal": [ //why are these all so different?
-128/1024, 128/1024, -(512 + 64)/1024, -(512 - 128)/1024 -128/1024, 128/1024, -(96)/1024, 96/1024
//-128/1024, 128/1024, -(512 + 64)/1024, -(512 - 128)/1024
], ],
//animations seem completely broken for these two (quickly files off screen after start) //animations seem completely broken for these two (quickly files off screen after start)
//right now the vertical range of the viewport is large to try figure out where the hell it's going? //right now the vertical range of the viewport is large to try figure out where the hell it's going?
"win": [ "win": [
-128/1024, 128/1024, -(1024)/1024, 1024/1024 -128/1024, 128/1024, -(96)/1024, 96/1024
//-128/1024, 128/1024, -(1024)/1024, 1024/1024
], ],
"lose": [ "lose": [
-128/1024, 128/1024, -(1024)/1024, 1024/1024 -128/1024, 128/1024, -(96)/1024, 96/1024
//-128/1024, 128/1024, -(1024)/1024, 1024/1024
], ],
} }

File diff suppressed because one or more lines are too long