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() {
if (last != null) nitroAudio.instaKill(last);
document.getElementById('seq').innerText = "Current SSEQ: "+i;
last = nitroAudio.playSound(i++, {}, 2);
last = nitroAudio.playSound(i++, {}, 0);
})
/*
var ctx = new AudioContext();

View File

@ -1,113 +1,129 @@
//
// nitroAudio.js
//--------------------
// Provides an interface for playing nds music and sound effects.
// by RHY3756547
//
window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.nitroAudio = new (function() {
var t = this;
var ctx;
t.sounds = [];
t.tick = tick;
t.playSound = playSound;
t.kill = kill;
t.init = init;
t.instaKill = instaKill;
t.sdat = null;
function init(sdat) {
ctx = new AudioContext();
t.ctx = ctx;
var listener = ctx.listener;
listener.dopplerFactor = 1;
listener.speedOfSound = 100/1024; //343.3
SSEQWaveCache.init(sdat, ctx);
t.sdat = sdat;
}
function tick() {
for (var i=0; i<t.sounds.length; i++) {
var snd = t.sounds[i];
snd.seq.tick();
if (snd.obj != null && snd.obj.soundProps != null && snd.panner != null) updatePanner(snd.panner, snd.obj.soundProps);
}
for (var i=0; i<t.sounds.length; i++) {
var snd = t.sounds[i];
snd.dead = snd.seq.dead;
if (snd.dead) {
snd.gainN.disconnect();
t.sounds.splice(i--, 1);
}
}
}
function kill(sound) {
if (!sound.killing) {
sound.killing = true;
sound.seq.kill();
}
}
function instaKill(sound) { //instantly kills a sound
if (sound == null) return;
var ind = t.sounds.indexOf(sound)
sound.gainN.disconnect();
if (ind == -1) return;
t.sounds.splice(ind, 1);
}
function playSound(seqN, params, arcN, obj) { //if arc is not specified, we just play a normal sequence. this allows 3 overloads.
//obj should have a property "soundProps" where it sets its falloff, position and velocity relative to the oberver occasionally
var sound = { dead: false, killing: false, obj: obj };
var output;
if (obj != null) { //if obj is not null then we have a 3d target to assign this sound to.
output = ctx.createPanner();
sound.gainN = ctx.createGain();
sound.gainN.connect(ctx.destination);
output.connect(sound.gainN);
sound.panner = output;
updatePanner(sound.panner, sound.obj.soundProps);
} else {
output = ctx.createGain();
sound.gainN = output;
output.connect(ctx.destination);
}
var player;
if (arcN == null) {
var seq = t.sdat.sections["$INFO"][0][seqN];
if (seq == null) return;
sound.seq = new SSEQPlayer(seq, t.sdat, ctx, output, params);
} else {
var arc = t.sdat.sections["$INFO"][1][arcN];
if (arc == null) return;
var seq = arc.arc.entries[seqN];
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]);
if (soundProps.refDistance != null) panner.refDistance = soundProps.refDistance;
if (soundProps.panningModel != null) panner.panningModel = soundProps.panningModel;
if (soundProps.rolloffFactor != null) panner.rolloffFactor = soundProps.rolloffFactor;
}
//
// nitroAudio.js
//--------------------
// Provides an interface for playing nds music and sound effects.
// by RHY3756547
//
window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.nitroAudio = new (function() {
var t = this;
var ctx;
t.sounds = [];
t.tick = tick;
t.playSound = playSound;
t.kill = kill;
t.init = init;
t.instaKill = instaKill;
t.updateListener = updateListener;
t.sdat = null;
function init(sdat) {
ctx = new AudioContext();
t.ctx = ctx;
var listener = ctx.listener;
listener.dopplerFactor = 1;
listener.speedOfSound = 100/1024; //343.3
SSEQWaveCache.init(sdat, ctx);
t.sdat = sdat;
}
function updateListener(pos, view) {
var listener = ctx.listener;
listener.positionX.value = pos[0];
listener.positionY.value = pos[1];
listener.positionZ.value = pos[2];
listener.forwardX.value = view[8];
listener.forwardY.value = -view[9];
listener.forwardZ.value = -view[10];
listener.upX.value = view[4];
listener.upY.value = view[5];
listener.upZ.value = view[6];
}
function tick() {
for (var i=0; i<t.sounds.length; i++) {
var snd = t.sounds[i];
snd.seq.tick();
if (snd.obj != null && snd.obj.soundProps != null && snd.panner != null) updatePanner(snd.panner, snd.obj.soundProps);
}
for (var i=0; i<t.sounds.length; i++) {
var snd = t.sounds[i];
snd.dead = snd.seq.dead;
if (snd.dead) {
snd.gainN.disconnect();
t.sounds.splice(i--, 1);
}
}
}
function kill(sound) {
if (!sound.killing) {
sound.killing = true;
sound.seq.kill();
}
}
function instaKill(sound) { //instantly kills a sound
if (sound == null) return;
var ind = t.sounds.indexOf(sound)
sound.gainN.disconnect();
if (ind == -1) return;
t.sounds.splice(ind, 1);
}
function playSound(seqN, params, arcN, obj) { //if arc is not specified, we just play a normal sequence. this allows 3 overloads.
//obj should have a property "soundProps" where it sets its falloff, position and velocity relative to the oberver occasionally
var sound = { dead: false, killing: false, obj: obj };
var output;
if (obj != null) { //if obj is not null then we have a 3d target to assign this sound to.
output = ctx.createPanner();
sound.gainN = ctx.createGain();
sound.gainN.connect(ctx.destination);
output.connect(sound.gainN);
sound.panner = output;
if (sound.obj.soundProps == null) sound.obj.soundProps = obj;
updatePanner(sound.panner, sound.obj.soundProps);
} else {
output = ctx.createGain();
sound.gainN = output;
output.connect(ctx.destination);
}
var player;
if (arcN == null) {
var seq = t.sdat.sections["$INFO"][0][seqN];
if (seq == null) return;
sound.seq = new SSEQPlayer(seq, t.sdat, ctx, output, params);
} else {
var arc = t.sdat.sections["$INFO"][1][arcN];
if (arc == null) return;
var seq = arc.arc.entries[seqN];
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.SMALLJUMP = 0x0E; //choco island 2's disaster ramps
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.BOOST2 = 0x12;
this.LOOP = 0x13; //like sticky but with boost applied. see rainbow road ds
this.SOUNDROAD = 0x14;
this.RR_SPECIAL_WALL = 0x15;
this.KNOCKBACK_DAMAGE = 0x1F;
this.GROUP_ROAD = [
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,
@ -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.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.WALL, this.WALL2, this.RR_SPECIAL_WALL
this.WALL, this.WALL2, this.WALLOOB, this.RR_SPECIAL_WALL, this.KNOCKBACK_DAMAGE
]
this.GROUP_BOOST = [
this.BOOST, this.BOOST2, this.LOOP
]
this.GROUP_OOB = [
this.OOB, this.FALL
]
this.PHYS_MAP = new Array(31);
this.PHYS_MAP[this.ROAD] = 0;
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_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_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},
],
0x10: //wall 3
0x10: //wall oob
[
{hit: MKDS_COLSOUNDS.HIT_CONCRETE},
{},

View File

@ -45,6 +45,8 @@ window.controlRaceCPU = function(nkm) {
var destConst;
var destPoint;
var item = false;
function fetchInput() {
//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.
@ -83,6 +85,7 @@ window.controlRaceCPU = function(nkm) {
offTrans += 1/240;
if (offTrans >= 1) chooseNewOff();
item = !item;
return {
accel: accel, //x

View File

@ -5,6 +5,137 @@
// 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) {
var r = this;
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.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;
this.getChar = getChar;

View File

@ -19,6 +19,8 @@ window.ItemController = function(scene) {
t.changeItem = changeItem;
t.update = update;
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
@ -47,10 +49,25 @@ window.ItemController = function(scene) {
}
function draw(mvMatrix, pMatrix, gl) {
nitroRender.setShadBias(0.001);
for (var i=0; i<t.items.length; i++) {
var e = t.items[i];
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) {

View File

@ -14,12 +14,28 @@ window.lsc = new (function() {
this.sweepEllipse = sweepEllipse;
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 t=1;
var tris = getTriList(pos, dir, kclO);
var colPlane = null;
var colPoint = null; //can be calculated from t, but we calculate it anyway so why not include
t=1;
var tris = getTriList(pos, dir, scn.kcl);
colPlane = null;
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++) {
//first, check if we intersect the plane within reasonable t.
//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;
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 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
@ -44,24 +60,72 @@ window.lsc = new (function() {
}
}
}
*/
if (colPlane != null) {
return {
t: t,
plane: colPlane,
colPoint: colPoint,
object: colO,
normal: colPlane.Normal
}
} 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) {
var obj = {};
obj.Vertex1 = vec3.transformMat4([], tri.Vertex1, mat);
obj.Vertex2 = vec3.transformMat4([], tri.Vertex2, mat);
obj.Vertex3 = vec3.transformMat4([], tri.Vertex3, mat);
obj.Vertices = [];
obj.Vertices[0] = vec3.transformMat4([], tri.Vertices[0], 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);
obj.CollisionType = tri.CollisionType;
return obj;
@ -69,17 +133,16 @@ window.lsc = new (function() {
function scaleTri(tri, eDim) {
var obj = {};
obj.Vertex1 = vec3.divide([], tri.Vertex1, eDim);
obj.Vertex2 = vec3.divide([], tri.Vertex2, eDim);
obj.Vertex3 = vec3.divide([], tri.Vertex3, eDim);
obj.Vertices = [];
obj.Vertices[0] = vec3.divide([], tri.Vertices[0], eDim);
obj.Vertices[1] = vec3.divide([], tri.Vertices[1], eDim);
obj.Vertices[2] = vec3.divide([], tri.Vertices[2], eDim);
obj.Normal = tri.Normal
obj.CollisionType = tri.CollisionType;
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.
t=1;
@ -140,7 +203,7 @@ window.lsc = new (function() {
if (ignoreList.indexOf(oTri) != -1) continue;
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 modDir = vec3.dot(tri.Normal, dir);
@ -200,14 +263,14 @@ window.lsc = new (function() {
}
//no inside intersection check vertices:
for (var j=1; j<=3; j++) {
var vert = vec3.sub([], pos, tri["Vertex"+j]);
for (var j=0; j<=2; 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);
if (root != null) {
t = root;
colPlane = oTri;
colO = targ;
colPoint = vec3.clone(tri["Vertex"+j]); //result!
colPoint = vec3.clone(tri.Vertices[j]); //result!
planeNormal = tri.Normal;
edge = false;
}
@ -215,9 +278,9 @@ window.lsc = new (function() {
//... and lines
for (var j=1; j<=3; j++) {
var vert = tri["Vertex"+j];
var nextV = tri["Vertex"+((j%3)+1)];
for (var j=0; j<=2; j++) {
var vert = tri.Vertices[j];
var nextV = tri.Vertices[(j+1)%3];
var distVert = vec3.sub([], vert, pos);
var distLine = vec3.sub([], nextV, vert);
@ -277,9 +340,9 @@ window.lsc = new (function() {
function pointInTriangle(tri, point, error) { //barycentric check
//compute direction vectors to the other verts and the point
var v0 = vec3.sub([], tri.Vertex3, tri.Vertex1);
var v1 = vec3.sub([], tri.Vertex2, tri.Vertex1);
var v2 = vec3.sub([], point, tri.Vertex1);
var v0 = vec3.sub([], tri.Vertices[2], tri.Vertices[0]);
var v1 = vec3.sub([], tri.Vertices[1], tri.Vertices[0]);
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
//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() {
this.DAMAGE_SPIN = 0;
this.DAMAGE_FLIP = 0;
this.DAMAGE_EXPLODE = 0;
this.DAMAGE_FLIP = 1;
this.DAMAGE_EXPLODE = 2;
this.COURSEDIR = "/data/Course/";

View File

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

View File

@ -10,7 +10,7 @@ window.fileStore = new (function(){
|| window.mozIndexedDB
|| window.shimIndexedDB;
var request = indexedDB.open("MKJS_DB", 1);
var request = indexedDB.open("MKJS-DB", 1);
request.onerror = window.onerror;
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) {
if (typeof url == "string") {
var xml = new XMLHttpRequest();
@ -77,6 +89,7 @@ window.fileStore = new (function(){
callback(dat);
};
request.onsuccess = function(event) {
validateFiles();
callback(dat);
};
}

View File

@ -1,7 +1,7 @@
//
// bowserPlatforms.js
//--------------------
// Provides platforms for Bowser's Castle
// Provides moving platforms for Bowser's Castle and Delfino
// by RHY3756547
//
// 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) {
var obji = obji;
var res = [];
@ -111,12 +238,14 @@ window.ObjRoutePlatform = function(obji, scene) {
t.elapsedTime = 0;
t.mode = 0;
t.colFrame = 0;
var movVel;
//t.speed = (obji.setting1&0xFFFF)/8192;
function update(scene) {
t.colFrame++;
if (t.mode == 0) {
t.elapsedTime += t.routeSpeed;
movVel = vec3.sub([], t.nextNode.pos, t.prevPos);
@ -159,15 +288,11 @@ window.ObjRoutePlatform = function(obji, scene) {
function generateCol() {
genCol = {dat: [
{
Vertex1: [25, 0, 11],
Vertex2: [25, 0, -11],
Vertex3: [-25, 0, -11],
Vertices: [[25, 0, 11], [25, 0, -11], [-25, 0, -11]],
Normal: [0, 1, 0]
},
{
Vertex1: [-25, 0, -11],
Vertex2: [-25, 0, 11],
Vertex3: [25, 0, 11],
Vertices: [[-25, 0, -11], [-25, 0, 11], [25, 0, 11]],
Normal: [0, 1, 0]
},
], 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));
obj.mat = mat;
obj.frame = t.colFrame;
return obj;
}

View File

@ -32,34 +32,44 @@ var itemTypes = {
'$koura_w': BlueShellC
}
window.Item = function(scene, owner, type) {
window.Item = function(scene, owner, type, id) {
var t = this;
var minimumMove = 0.01;
this.id = 0;
this.id = id;
this.pos = vec3.transformMat4([], [0, (-owner.params.colRadius)+1, 16], owner.mat);
this.vel = vec3.create();
this.gravity = [0, -0.17, 0]; //100% confirmed by me messing around with the gravity value in mkds
this.minBounceVel = 0.5;
this.airResist = 0.95;
this.airResist = 0.99;
this.enablePhysics = true;
this.floorBounce = 0.5;
this.held = true;
this.type = type;
this.owner = owner;
this.holdTime = 20;
this.dead = false;
this.angle = owner.angle;
this.speed = 10;
this.yvel = 0;
this.xyScale = [1, 1];
this.colRadius = 4;
this.holdDist = 16;
this.holdDist = 2;
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;
var throwVelocity = 16;
var throwAngle = (Math.PI / 3) * 2;
this.groundTime = 0;
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
//a controller makes this item what it is...
@ -69,6 +79,8 @@ window.Item = function(scene, owner, type) {
// update?: (scene: CourseScene) => void
// 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
// 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)
// collideKart?: (item: Kart)
var subtypeInd = type.indexOf('-');
@ -91,10 +103,16 @@ window.Item = function(scene, owner, type) {
function updateHold(kart) {
//move the object behind the kart (physical direction without drift off)
//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)
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
vec3.transformMat4(pos, pos, kart.mat);
@ -105,18 +123,37 @@ window.Item = function(scene, owner, type) {
function release(forward) {
//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);
else {
//default drop and throw. just here for template purposes
if (forward >= 0) {
var dir = owner.physicalDir;
vec3.zero(t.vel);
if (forward > 0) {
nitroAudio.playSound(218, {volume: 2}, 0, owner);
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 {
vec3.zero(t.vel);
t.vel = vec3.create();
t.safeKart = null;
}
}
this.held = false;
t.held = false;
}
function canBeHeld() {
@ -124,7 +161,8 @@ window.Item = function(scene, owner, type) {
}
function canBeDropped() {
return t.controller.canBeDropped || true;
if (t.controller.canBeDropped == null) return true;
return t.controller.canBeDropped;
}
function isDestructive() {
@ -132,13 +170,22 @@ window.Item = function(scene, owner, type) {
}
function isSolid() {
return t.controller.isSolid || true;
if (t.controller.isSolid == null) return true;
return t.controller.isSolid;
}
function finalize() {
//kill instantly
if (t.controller.onDie) t.controller.onDie(true);
t.deadTimer = deadTimerLength;
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) {
@ -152,15 +199,19 @@ window.Item = function(scene, owner, type) {
if (item.isDestructive() || t.isDestructive()) {
//mutual destruction. other side will deal with how they handle the collision
t.deadTimer++;
item.deadTimer++;
} else if (item.isSolid() && t.isSolid()) {
//bounce off other items that are not destructive
//set our velocity to move away (not too intensely)
//(only apply if our id is before, to avoid double adding the velocity)
if (t.id < item.id) {
var diff = vec3.sub([], t.pos, item.pos);
vec3.scale(diff, diff, 0.5);
vec3.add(t.vel, t.vel, diff);
vec3.sub(item.vel, item.vel, diff);
var diff = vec3.sub(working, t.pos, item.pos);
vec3.scale(diff, diff, 0.33);
intensityMax(t.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;
item.enablePhysics = true;
}
@ -175,77 +226,131 @@ window.Item = function(scene, owner, type) {
function 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 == 1 && t.controller.onDie) t.controller.onDie(false);
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;
}
if (t.held) {
t.updateHold(owner);
}
var hitSafe = false;
//search for player collisions, collisions with other items
for (var i=0; i<scene.karts.length; 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.
//do we need to do something?
if (ok === t.safeKart) {
hitSafe = true;
continue;
}
t.collide(ok);
}
}
for (var i=0; i<scene.items.length; i++) {
var ot = scene.items[i];
var dist = vec3.dist(t.pos, ot.pos);
if (dist < t.colRadius + ot.colRadius) {
//two items are colliding.
t.collide(ot);
if (t.safeKart && !hitSafe && !t.held) {
t.safeTime--;
if (t.safeTime <= 0) {
t.safeKart = null;
}
}
if (t.enablePhysics) {
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.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;
if (t.holdTime == 0) { //avoid mutual item destruction on the first frame
for (var i=0; i<scene.items.items.length; i++) {
var ot = scene.items.items[i];
if (ot == t || (t.held && ot.held)) continue;
var dist = vec3.dist(t.pos, ot.pos);
if (dist < t.colRadius + ot.colRadius && ot.holdTime <= 7 && ot.deadTimer == 0) {
//two items are colliding.
t.collide(ot);
}
}
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) {
if (t.holdTime > 7) return;
if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1-(t.deadTimer/deadTimerLength)]); //fade out
if (t.controller.draw) {
t.controller.draw(mvMatrix, pMatrix);
} 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);
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]);
}
@ -272,13 +377,28 @@ window.Item = function(scene, owner, type) {
//normally, item collision with a wall cause a perfect reflection of the velocity.
var proj = vec3.dot(t.vel, n) * 2;
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) {
//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));
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 {
adjustPos = false;
ignoreList.push(plane);

View File

@ -98,12 +98,15 @@ window.ItemBox = function(obji, scene) {
}
function sndUpdate(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);
else t.soundProps.vel = [0, 0, 0];
*/
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;
}

View File

@ -1,15 +1,42 @@
window.BananaC = function(item, scene, type) {
var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = false;
item.minBounceVel = 0;
item.floorBounce = 0;
this.collideKart = collideKart;
this.onRest = onRest;
this.update = update;
function collideKart(kart) {
item.deadTimerLength = 20;
item.deadTimer = 1;
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) {
@ -28,27 +55,89 @@ window.BananaGroupC = function(item, scene, type) {
}
window.FakeBoxC = function(item, scene, type) {
var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = false;
this.isSolid = true;
var model = scene.gameRes.fakeBox;
this.isSolid = false;
item.floorBounce = 0;
item.airResist = 0.98;
this.collideKart = collideKart;
this.onRest = onRest;
this.update = update;
this.draw = draw;
function draw(view, pMatrix) {
mat4.translate(mat, view, t.pos);
mat4.translate(mat, view, [0, 16, 0]);
this.xyScale = [1,1];
this.dir = 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
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));
model.draw(mat, pMatrix, animMat);
var mdl = scene.gameRes.items.fakeBox;
mdl.draw(mat, pMatrix);
}
}
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) {
var t = this;
this.canBeHeld = true;
this.canBeDropped = 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) {
@ -11,11 +101,100 @@ window.RedShellC = function(item, scene) {
}
window.ShellGroupC = function(item, scene, type) {
this.canBeHeld = false;
this.canBeDropped = 'func';
var t = this;
this.canBeHeld = "func";
this.canBeDropped = "func";
this.rotationPeriod = 45;
item.colRadius = -Infinity;
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) {
//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.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.boostMT = 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));
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.angle = k.physicalDir;
@ -438,7 +444,6 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
}
} else { //default kart mode
if (k.OOB > 0) {
playCharacterSound(0);
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 boosting = (k.boostNorm + k.boostMT)>0;
if (boosting) {
var top2
if (k.boostNorm>0){
top2 = params.topSpeed*1.3;
k.boostNorm--;
} 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 (k.specialControlHandler != null) k.specialControlHandler();
else {
if (boosting) {
var top2
if (k.boostNorm>0){
top2 = params.topSpeed*1.3;
k.boostNorm--;
} else {
top2 = params.topSpeed*((effect.topSpeedMul >= 1)?1.3:effect.topSpeedMul);
}
//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.boostMT>0) {
k.boostMT--;
}
if (k.speed <= top2) {
k.speed += 1;
if (k.speed > top2) k.speed = top2;
} else {
k.speed *= 0.95;
}
}
if (onGround) {
if (!k.driftLanded) {
if (k.driftMode == 0) {
k.drifting = false;
clearWheelParticles();
//kart controls
if (k.drifting) {
if ((onGround) && !(input.accel && input.drift && (k.speed > 2 || !k.driftLanded))) {
//end drift, execute miniturbo
endDrift();
if (k.driftPSMode == 3) {
k.boostMT = params.miniTurbo;
}
else {
k.driftPSMode = 0;
k.driftPSTick = 0;
k.driftLanded = true;
if (k.drifting) setWheelParticles(20, 1); //20 = smoke, 1 = drift priority
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.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 (k.speed <= top) {
k.speed += (k.speed/top > params.driftAccelSwitch)?params.driftAccel2:params.driftAccel1;
if (k.speed > top) k.speed = top;
if (input.accel) {
if (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 {
k.speed *= 0.95;
k.speed *= params.decel;
}
}
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
sounds.powerslide = nitroAudio.playSound(209, {}, 0, k);
sounds.powerslide.gainN.gain.value = 2;
} else k.driftPSTick = 0;
break;
case 3: //turbo charged
break;
}
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 (!k.drifting) {
if (onGround) {
var effect = params.colParam[groundEffect];
if (!boosting) {
if (input.accel) {
if (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 {
k.speed *= params.decel;
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;
}
}
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.
var steps = 0;
var remainingT = 1;
var baseVel = k.vel;
@ -754,6 +753,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
var mat = mat4.create();
mat4.translate(mat, mat, k.pos);
k.mat = mat4.mul(mat, mat, k.basis);
if (k.damageMat != null) mat4.mul(mat, mat, k.damageMat);
if (input.item) {
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);
}
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) {
if (k.cannon != null) return;
k.cannon = id;
@ -923,7 +1000,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.kartColVel[1] = 0;
//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) {
@ -940,6 +1017,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
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 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) {
/*
k.soundProps.pos = vec3.transformMat4([], k.pos, view);
if (k.soundProps.lastPos != null) k.soundProps.vel = vec3.sub([], k.soundProps.pos, k.soundProps.lastPos);
else k.soundProps.vel = [0, 0, 0];
*/
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;
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 adjustPos = true;
if (colType == MKDS_COLTYPE.OOB || colType == MKDS_COLTYPE.FALL) {
if (MKDS_COLTYPE.GROUP_OOB.indexOf(colType) != -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));
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
var v = k.vel;
k.speed = Math.sqrt(v[0]*v[0]+v[2]*v[2]);
k.angle = Math.atan2(v[0], -v[2]);
stuckTo = dat.object;
} else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
//sliding plane
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;
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));
if (stick) {

View File

@ -2,19 +2,20 @@
window.KartItems = function(kart, scene) {
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.specificItem = null;
t.empty = true;
t.cycleTime = 0;
t.totalTime = 230;
var maxItemTime = 230;
var minItemTime = 80;
var carouselSfx = null;
var lastItemState = false;
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 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) {
var pressed = (input.item && !lastItemState);
var released = (lastItemState && !input.item);
if (!t.empty) {
if (t.currentItem == null) {
//carousel
@ -53,24 +68,48 @@ window.KartItems = function(kart, scene) {
if (carouselSfx != null) nitroAudio.kill(carouselSfx);
//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);
t.currentItem = item;
} else {
//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);
}
}
} else {
} else if (t.heldItem == null) {
if (pressed) {
//fire?
t.currentItem = null;
t.empty = true;
kart.playCharacterSound(7);
t.heldItem = createItem();
//t.currentItem = null;
//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;
}

View File

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

View File

@ -41,11 +41,15 @@ window.ObjGear = function(obji, scene) {
t.angle = 0;
t.dir = (t.wB1 == 0)
t.colFrame = 0;
var colRes;
var dirVel = 0;
var prevMat;
var curMat;
setMat();
var colMat = mat4.create();
prevMat = curMat;
function setMat() {
@ -59,6 +63,8 @@ window.ObjGear = function(obji, scene) {
mat4.rotateX(mat, mat, obji.angle[0]*(Math.PI/180));
mat4.rotateY(mat, mat, t.angle);
mat4.scale(colMat, mat, [colRes.scale, colRes.scale, colRes.scale]);
t.colFrame++;
curMat = mat;
}
@ -119,43 +125,24 @@ window.ObjGear = function(obji, scene) {
}
}
function cloneKCL(kcl) {
return JSON.parse(JSON.stringify(kcl));
}
function provideRes(r) {
res = r; //...and gives them to us. :)
colRes = cloneKCL(res.mdl[0].getCollisionModel(0, 0));
}
function getCollision() {
var obj = {};
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;
return { tris: colRes.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)
/*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 ignoreList = [];
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) {
colResponse(posSeg, velSeg, result, ignoreList)
remainingT -= result.t;
@ -87,7 +87,9 @@ window.GreenShell = function(scene, owner, time, itemID, cliID, params) {
var v = t.vel;
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) {
//sliding plane
var proj = vec3.dot(t.vel, n);

View File

@ -44,16 +44,20 @@ window.ObjSoundMaker = function(obji, scene) {
}
function sndUpdate(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 = vec3.transformMat4([], t.pos, view);
//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);
//else t.soundProps.vel = [0, 0, 0];
//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;
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 (sound != null) {

View File

@ -17,6 +17,7 @@ window.ObjTruck = function(obji, scene) {
t.pos = vec3.clone(obji.pos);
//t.angle = vec3.clone(obji.angle);
t.scale = vec3.clone(obji.scale);
t.vel = vec3.create();
t.requireRes = requireRes;
t.provideRes = provideRes;
@ -24,45 +25,138 @@ window.ObjTruck = function(obji, scene) {
t.draw = draw;
t.route = scene.paths[obji.routeID];
t.routeSpeed = (obji.setting1>>16)/100;
t.routePos = (obji.setting1&0xFFFF)%t.route.length;
t.routeSpeed = 0.01;
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.prevPos = t.pos;
t.elapsedTime = 0;
var facingNormal = [0, 1, 0];
var curNormal = [0, 1, 0];
var curFloorNormal = [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) {
//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.pos = vec3.lerp([], t.prevPos, t.nextNode.pos, t.elapsedTime/t.nextNode.duration);
if (t.elapsedTime >= t.nextNode.duration) {
t.elapsedTime = 0;
t.prevPos = t.nextNode.pos;
var i = t.elapsedTime;
var newPos = cubic3D(t.nodes, i); //vec3.lerp([], t.prevPos, t.nextNode.pos, i);
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.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)
vec3.normalize(facingNormal, facingNormal);
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;
curNormal = vec3.sub([], t.prevPos, t.pos)
t.prevPos = vec3.clone(t.pos);
vec3.normalize(curNormal, curNormal);
if (isNaN(curNormal[0])) curNormal = [0, 0, 1];
var spos = vec3.clone(t.pos);
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) {
floorNormal = result.normal;
} else {
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) {
@ -70,23 +164,44 @@ window.ObjTruck = function(obji, scene) {
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);
}
function requireRes() { //scene asks what resources to load
switch (obji.ID) {
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:
return {mdl:[{nsbmd:"truck_a.nsbmd"}]}; //one model, truck
return {mdl:[{nsbmd:"truck_a.nsbmd"}], other:["truck_a.nsbtp"]}; //one model, truck
case 0x0195:
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) {
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.draw = draw;
var frame = 0;
var wheight = 6.144;
var wosc = 12.288;
var wstay = 5*60;
var wchange = 4*60;
function draw(view, pMatrix) {
if (nitroRender.flagShadow) return;
@ -34,34 +38,35 @@ window.ObjWater = function(obji, scene) {
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)
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])
nitroRender.setAlpha(0x0A/31);
mat4.translate(waterM, view, [Math.sin(frame/180)*96, height, Math.cos(frame/146)*96])
nitroRender.setColMult([1, 1, 1, 0x0A/31]);
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.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
if (!obji.ID == 9) {
if (obji.ID != 9) {
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
}
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() {
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
@ -75,6 +80,8 @@ window.ObjWater = function(obji, scene) {
case 0x0009:
return {mdl:[{nsbmd:"hyudoro_waterC.nsbmd"}, {nsbmd:"hyudoro_waterA.nsbmd"}]};
case 0x000C:
wheight = 38;
wosc = 16;
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();
pos[0] = view.getInt32(off, true)/4096;
pos[1] = view.getInt32(off+4, true)/4096;
console.log("charPos: "+pos[1]);
pos[2] = view.getInt32(off+8, true)/4096;
off += 12;
chars.push(pos);

View File

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

View File

@ -5,10 +5,34 @@
// by RHY3756547
//
// for reading from multiple narcs as one. (eg. Race.narc, Race_us.narc)
window.narcGroup = function(files) {
this.getFile = getFile;
this.list = list;
function getFile(name) {
for (var i=0; i<files.length; i++) {
var file = files[i].getFile(name);
if (file != null) return file;
}
console.error("File not found: "+name);
return null;
}
function list() {
var result = [];
for (var i=0; i<files.length; i++) {
files[i].list(result);
}
return result;
}
}
window.narc = function(input) {
this.load = load;
this.getFile = getFile;
this.list = list;
var arc = this;
var handlers = [];
@ -18,8 +42,6 @@ window.narc = function(input) {
mouseY = evt.pageY;
}
this.scopeEval = function(code) {return eval(code)} //for debug purposes
function load(buffer) {
arc.buffer = buffer; //we will use this data in the future.
@ -84,6 +106,22 @@ window.narc = function(input) {
return null; //incomplete path; we ended on a directory, not a file!
}
function list(files, curDir, path) {
path = path || "/";
files = files || [];
var table = arc.sections["BTNF"].directories;
curDir = curDir || table[0].entries; //root
for (var i=0; i<curDir.length; i++) {
if (curDir[i].dir) {
list(files, table[curDir[i].id-0xF000].entries, path+curDir[i].name+"/");
} else {
files.push(path + curDir[i].name);
}
}
return files;
}
function readFileWithID(id) {
var table = arc.sections["BTAF"].files;
var file = table[id];

View File

@ -8,6 +8,7 @@
window.ndsFS = function(input) {
this.load = load;
this.getFile = getFile;
this.list = list;
var arc = this;
var handlers = [];
@ -65,6 +66,22 @@ window.ndsFS = function(input) {
return null; //incomplete path; we ended on a directory, not a file!
}
function list(files, curDir, path) {
path = path || "/";
files = files || [];
var table = arc.sections["BTNF"].directories;
curDir = curDir || table[0].entries; //root
for (var i=0; i<curDir.length; i++) {
if (curDir[i].dir) {
list(files, table[curDir[i].id-0xF000].entries, path+curDir[i].name+"/");
} else {
files.push(path + curDir[i].name);
}
}
return files;
}
function readFileWithID(id) {
var off = arc.fileOff+id*8;
/*var table = arc.sections["BTAF"].files;

View File

@ -11,13 +11,17 @@
window.nftr = function(input) {
var mainOff;
var mainObj = this;
var t = this;
this.info = {};
if (input != null) {
load(input);
}
this.load = load;
this.drawToCanvas = drawToCanvas;
this.measureText = measureText;
this.measureMapped = measureMapped;
this.mapText = mapText;
function load(input) {
var view = new DataView(input);
@ -29,9 +33,249 @@ window.nftr = function(input) {
header = nitro.readHeader(view);
//debugger;
if (header.stamp != "RTFN") throw "NFTR invalid. Expected RTFN, found "+header.stamp;
offset = header.sectionOffsets[0];
offset = 0x10; //nitro header for nftr doesn't have section offsets - they are in order
//end nitro
var info = t.info;
info.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
info.blockSize = view.getUint32(offset+0x4, true);
info.unknown1 = view.getUint8(offset+0x8);
info.height = view.getUint8(offset+0x9);
info.nullCharIndex = view.getUint16(offset+0xA, true);
info.unknown2 = view.getUint8(offset+0xC);
info.width = view.getUint8(offset+0xD);
info.widthBis = view.getUint8(offset+0xE);
info.encoding = view.getUint8(offset+0xF);
info.offsetCGLP = view.getUint32(offset+0x10, true); //character graphics
info.offsetCWDH = view.getUint32(offset+0x14, true); //character width
info.offsetCMAP = view.getUint32(offset+0x18, true); //character map
if (info.blockSize == 0x20) {
//extra info
info.fontHeight = view.getUint8(offset+0x1C);
info.fontWidth = view.getUint8(offset+0x1D);
info.bearingX = view.getUint8(offset+0x1E);
info.bearingY = view.getUint8(offset+0x1F);
}
loadCGLP(view);
loadCWDH(view);
loadCMAP(view);
mainOff = offset;
}
function loadCGLP(view) {
var offset = t.info.offsetCGLP - 8;
var cglp = {};
cglp.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
cglp.blockSize = view.getUint32(offset+0x4, true);
cglp.tileWidth = view.getUint8(offset+0x8);
cglp.tileHeight = view.getUint8(offset+0x9);
cglp.tileLength = view.getUint16(offset+0xA, true);
cglp.unknown = view.getUint16(offset+0xC, true);
cglp.depth = view.getUint8(offset+0xE);
cglp.rotateMode = view.getUint8(offset+0xF);
offset += 0x10;
cglp.tiles = [];
var total = (cglp.blockSize - 0x10) / cglp.tileLength;
for (var i=0; i<total; i++) {
cglp.tiles.push(new Uint8Array(view.buffer.slice(offset, offset+cglp.tileLength)));
offset += cglp.tileLength;
}
t.cglp = cglp;
}
function loadCWDH(view) {
var offset = t.info.offsetCWDH - 8;
var cwdh = {};
cwdh.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
cwdh.blockSize = view.getUint32(offset+0x4, true);
cwdh.firstCode = view.getUint16(offset+0x8, true);
cwdh.lastCode = view.getUint16(offset+0xA, true);
cwdh.unknown = view.getUint32(offset+0xC, true);
cwdh.info = [];
offset += 0x10;
for (var i=0; i<t.cglp.tiles.length; i++) {
var info = {};
info.pixelStart = view.getInt8(offset);
info.pixelWidth = view.getUint8(offset+1);
info.pixelLength = view.getUint8(offset+2);
cwdh.info.push(info);
offset += 3;
}
t.cwdh = cwdh;
}
function loadCMAP(view) {
var offset = t.info.offsetCMAP - 8;
var cmaps = [];
var charMap = {};
while (offset > 0 && offset < view.byteLength) {
var cmap = {};
cmap.type = readChar(view, offset+0x0)+readChar(view, offset+0x1)+readChar(view, offset+0x2)+readChar(view, offset+0x3);
cmap.blockSize = view.getUint32(offset+0x4, true);
cmap.firstChar = view.getUint16(offset+0x8, true);
cmap.lastChar = view.getUint16(offset+0xA, true);
cmap.typeSection = view.getUint32(offset+0xC, true);
cmap.nextOffset = view.getUint32(offset+0x10, true);
offset += 0x14;
switch (cmap.typeSection & 0xFFFF) {
case 1: //char code list (first to last)
cmap.charCodes = [];
var total = (cmap.lastChar - cmap.firstChar) + 1;
var charCode = cmap.firstChar;
for (var i=0; i<total; i++) {
var char = view.getUint16(offset, true);
cmap.charCodes.push(char);
if (char != 65535) {
charMap[String.fromCharCode(charCode)] = char;
}
charCode++;
offset += 2;
}
break;
case 2: //char code map
cmap.numChars = view.getUint16(offset, true);
offset += 2;
cmap.charMap = [];
for (var i=0; i<cmap.numChars; i++) {
var charCode = view.getUint16(offset, true);
var char = view.getUint16(offset+2, true);
cmap.charMap.push([charCode, char]);
charMap[String.fromCharCode(charCode)] = char;
offset += 4;
}
break;
default:
cmap.firstCharCode = view.getUint16(offset, true);
var total = (cmap.lastChar - cmap.firstChar) + 1;
var charCode = cmap.firstChar;
var char = cmap.firstCharCode;
for (var i=0; i<total; i++) {
charMap[String.fromCharCode(charCode++)] = char++;
}
break;
}
cmaps.push(cmap);
offset = cmap.nextOffset - 8;
}
t.charMap = charMap;
t.cmaps = cmaps;
}
function readChar(view, offset) {
return String.fromCharCode(view.getUint8(offset));
}
// RENDERING FUNCTIONS
function mapText(text, missing) {
if (missing == null) missing = "*";
var map = t.charMap;
var result = [];
for (var i=0; i<text.length; i++) {
var code = text[i];
var mapped = map[code];
if (mapped == null) mapped = map[missing];
result.push(mapped);
}
return result;
}
function measureText(text, missing, spacing) {
return measureMapped(mapText(text, missing), spacing);
}
function measureMapped(mapped, spacing) {
if (spacing == null) spacing = 1;
var width = 0;
var widths = t.cwdh.info;
for (var i=0; i<mapped.length; i++) {
width += widths[mapped[i]].pixelLength + spacing; // pixelWidth is the width of drawn section - length is how wide the char is
}
return [width, t.info.height];
}
function drawToCanvas(text, palette, spacing) {
if (spacing == null) spacing = 1;
var mapped = mapText(text, "");
var size = measureMapped(mapped, spacing);
var canvas = document.createElement("canvas");
canvas.width = size[0];
canvas.height = size[1];
var ctx = canvas.getContext("2d");
//draw chars
var widths = t.cwdh.info;
var position = 0;
for (var i=0; i<mapped.length; i++) {
var c = mapped[i];
var cinfo = widths[c];
var data = getCharData(c, palette);
ctx.putImageData(data, position + cinfo.pixelStart, 0, 0, 0, cinfo.pixelWidth, data.height);
position += cinfo.pixelLength + spacing;
}
return canvas;
}
function getCharData(id, pal) {
//todo: cache?
var cglp = t.cglp;
var tile = cglp.tiles[id];
var pixels = cglp.tileWidth*cglp.tileHeight;
var d = new Uint8ClampedArray(pixels*4);
var data = new ImageData(d, cglp.tileWidth, cglp.tileHeight);
var depth = t.cglp.depth;
var mask = (1<<depth)-1;
var bit = 8;
var byte = 0;
var curByte = tile[byte];
var ind = 0;
for (var i=0; i<pixels; i++) {
bit -= depth;
var pind = 0;
if (bit < 0) {
//overlap into next
var end = bit + 8;
if (end < 8) {
//still some left in this byte
pind = (curByte << (-bit)) & mask;
}
curByte = tile[++byte];
bit += 8;
pind |= (curByte >> (bit)) & mask;
} else {
pind = (curByte >> (bit)) & mask;
}
var col = pal[pind];
d[ind++] = col[0];
d[ind++] = col[1];
d[ind++] = col[2];
d[ind++] = col[3];
}
return data;
}
}

View File

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

View File

@ -15,7 +15,7 @@ window.nsbca = function(input) {
var mainOff;
var animData;
var speeds = [1.0, 0.5, 1/3];
var speeds = [1.0, 0.5, 1/4];
var mainObj = this;
if (input != null) {
@ -72,10 +72,10 @@ window.nsbca = function(input) {
function readTrans(view, off, obj) {
var flag = view.getUint16(off, true); //--zyx-Sr-RZYX-T-
off += 4;
var transform = {};
transform.flag = flag;
if (!((flag>>1) & 1)) { //T: translation
var translate = [[], [], []]; //store translations in x,y,z arrays
@ -96,7 +96,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
var length = Math.floor((obj.frames+extra)*inf.speed);
var length = Math.ceil((inf.endFrame-inf.startFrame)*inf.speed);
var w = (inf.halfSize)?2:4;
var off2 = obj.baseOff+inf.off;
@ -132,7 +132,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
//florian's rotate code seems to ignore this extra value. I'll need more examples of nsbca to test this more thoroughly.
var length = Math.floor((obj.frames+extra)*inf.speed);
var length = Math.ceil((inf.endFrame-inf.startFrame)*inf.speed);
var off2 = obj.baseOff+inf.off;
try {
@ -173,7 +173,7 @@ window.nsbca = function(input) {
inf.off = view.getUint32(off+4, true);
var extra = (obj.unknown != 3)?0:(obj.frames-inf.endFrame);
var length = Math.ceil((obj.frames+extra)*inf.speed);
var length = Math.ceil((inf.endFrame-inf.startFrame)*inf.speed);
var w = ((inf.halfSize)?2:4);
var off2 = obj.baseOff+inf.off;
@ -211,21 +211,26 @@ window.nsbca = function(input) {
};
} else {
var off2 = obj.baseOff+obj.off2+ind*10; //jump to rotation data
var d1 = view.getInt16(off2, true);
var d2 = view.getInt16(off2+2, true);
var d3 = view.getInt16(off2+4, true);
var d4 = view.getInt16(off2+6, true);
var d5 = view.getInt16(off2+8, true);
var d1 = view.getUint16(off2, true);
var d2 = view.getUint16(off2+2, true);
var d3 = view.getUint16(off2+4, true);
var d4 = view.getUint16(off2+6, true);
var d5 = view.getUint16(off2+8, true);
var i6 = ((d5&7)<<12) | ((d1&7)<<9) | ((d2&7)<<6) | ((d3&7)<<3) | ((d4&7));
if (i6&4096) i6 = (-8192)+i6;
//if (i6&4096) i6 = (-8192)+i6;
var v1 = [d1>>3, d2>>3, d3>>3]
var v2 = [d4>>3, d5>>3, i6]
var v1 = [d1>>3, d2>>3, d3>>3];
var v2 = [d4>>3, d5>>3, i6];
for (var i=0; i<3; i++) {
if (v1[i] & 4096) v1[i] -= 8192;
if (v2[i] & 4096) v2[i] -= 8192;
}
vec3.scale(v1, v1, 1/4096);
vec3.scale(v2, v2, 1/4096);
var v3 = vec3.cross([], v1, v2)
var v3 = vec3.cross([], v1, v2);
var mat = [
v1[0], v1[1], v1[2],

View File

@ -70,6 +70,7 @@ window.nsbmd = function(input) {
head.maxStack = view.getUint8(offset+0x1A);
head.scale = view.getInt32(offset+0x1C, true)/4096;
head.downScale = view.getInt32(offset+0x20, true)/4096; //usually inverse of the above
head.numVerts = view.getUint16(offset+0x24, true);
head.numSurfaces = view.getUint16(offset+0x26, true);
@ -93,6 +94,12 @@ window.nsbmd = function(input) {
var tex = nitro.read3dInfo(view, head.materialsOffset+view.getUint16(head.materialsOffset, true), texInfoHandler);
var palt = nitro.read3dInfo(view, head.materialsOffset+view.getUint16(head.materialsOffset+2, true), palInfoHandler);
//bind tex and palt names to their materials
for (var i=0; i<materials.objectData.length; i++) {
materials.objectData[i].texName = tex.names[materials.objectData[i].tex];
materials.objectData[i].palName = palt.names[materials.objectData[i].pal];
}
var commands = parseBones(head.bonesOffset, view, polys, materials, objects, head.maxStack);
return {head: head, objects: objects, polys: polys, materials: materials, tex:tex, palt:palt, commands:commands}
@ -101,14 +108,18 @@ window.nsbmd = function(input) {
function parseBones(offset, view, polys, materials, objects, maxStack) {
var last;
var commands = [];
var debug = true;
if (debug) console.log("== Begin Parse Bones ==");
var freeStack = maxStack;
var forceID=null;
var lastMat = null;
var bound = [];
var matMap = [];
while (offset<texPalOff) { //bones
last = view.getUint8(offset++);
console.log("bone cmd: 0x"+last.toString(16));
switch (last) {
case 0x06: //bind object transforms to parent. bone exists but is not placed in the stack
var obj = view.getUint8(offset++);
@ -117,21 +128,50 @@ window.nsbmd = function(input) {
var object = objects.objectData[obj];
object.parent = parent;
if (debug) console.log("[0x"+last.toString(16)+"] Multiply stack with object " + obj + " bound to parent " + parent);
commands.push({obj:obj, parent:parent, stackID:freeStack++});
break;
case 0x26:
case 0x46: //placed in the stack at stack id
case 0x66:
var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++);
var zero = view.getUint8(offset++);
var stackID = view.getUint8(offset++);
var restoreID = null;
if (last == 0x66) restoreID = view.getUint8(offset++);
if (last == 0x46) {
restoreID = stackID;
stackID = freeStack++;
}
var object = objects.objectData[obj];
object.parent = parent;
commands.push({obj:obj, parent:parent, stackID:((last == 0x26)?stackID:freeStack++), restoreID:((last == 0x46)?stackID:null)});
if (debug) {
var debugMessage = "[0x"+last.toString(16)+"] ";
if (restoreID != null) debugMessage += "Restore matrix at " + stackID + ", ";
debugMessage += "Multiply stack with object " + obj + " bound to parent " + parent;
if (stackID != null) debugMessage += ", store in " + stackID;
console.log(debugMessage);
}
var item = {obj:obj, parent:parent, stackID:stackID, restoreID:restoreID};
if (bound[stackID]) {
//we're updating a matrix that is already bound...
//we must move copy the old value of the matrix to another place, and update the polys that point to it to the new location.
//(does not play well with skinned meshes (they don't do this anyways), but fixes lots of "multiple object" meshes.)
console.log("!! Already bound !! Moving old matrix at " + stackID + " to " + freeStack);
var poly = polys.objectData;
for (var i=0; i<poly.length; i++) {
if (poly[i].stackID == stackID) poly[i].stackID = freeStack;
}
commands.push({copy: stackID, dest: freeStack++})
}
commands.push(item);
break;
/*
case 0x66: //has ability to restore to another stack id. no idea how this works
var obj = view.getUint8(offset++);
var parent = view.getUint8(offset++);
@ -142,8 +182,11 @@ window.nsbmd = function(input) {
var object = objects.objectData[obj];
object.parent = parent;
if (debug) console.log("[0x"+last.toString(16)+"] Restore matrix at " + restoreID + ", multiply stack with object " + obj + " bound to parent " + parent + ", store in " + stackID);
commands.push({obj:obj, parent:parent, stackID:stackID, restoreID:restoreID});
break;
*/
case 0x04:
case 0x24:
case 0x44: //bind material to polygon: matID, 5, polyID
@ -151,54 +194,69 @@ window.nsbmd = function(input) {
lastMat = mat;
var five = view.getUint8(offset++); //???
var poly = view.getUint8(offset++);
polys.objectData[poly].stackID = (forceID == null)?(commands[commands.length-1].stackID):forceID;
var bindID = (forceID == null)?(commands[commands.length-1].stackID):forceID;
bound[bindID] = true;
polys.objectData[poly].stackID = bindID;
polys.objectData[poly].mat = mat;
if (debug) console.log("[0x"+last.toString(16)+"] Bind material " + mat + " to poly " + poly + " (with stack id " + polys.objectData[poly].stackID + ")");
break;
case 1:
//end of all
if (debug) console.log("[0x"+last.toString(16)+"] END OF BONES");
break;
case 2: //node visibility (maybe to implement this set matrix to 0)
var node = view.getUint8(offset++);
var vis = view.getUint8(offset++);
objects.objectData[node].vis = vis;
console.log("visibility thing "+node);
if (debug) console.log("[0x"+last.toString(16)+"] Set object " + node + " visibility: " + vis);
if (node > 10) debugger;
break;
case 3: //stack id for poly (wit)
forceID = view.getUint8(offset++);
console.log("stackid is "+forceID);
if (debug) console.log("[0x"+last.toString(16)+"] Force stack id to " + forceID);
case 0:
break;
case 5:
//i don't... what??
//holy shp!
case 5: //"draw a mesh" supposedly. might require getting a snapshot of the matrices at this point
var poly = view.getUint8(offset++);
polys.objectData[poly].stackID = (stackID == null)?(commands[commands.length-1].forceID):forceID;
var bindID = (forceID == null)?(commands[commands.length-1].stackID):forceID;
bound[bindID] = true;
polys.objectData[poly].stackID = bindID;
polys.objectData[poly].mat = lastMat;
if (debug) console.log("[0x"+last.toString(16)+"] Draw " + poly + "(stack id " + polys.objectData[poly].stackID + ")");
break;
case 7:
//sets object to be billboard
var obj = view.getUint8(offset++);
objects.objectData[obj].billboardMode = 1;
if (debug) console.log("[0x"+last.toString(16)+"] Object " + obj + " set to full billboard mode.");
mainObj.hasBillboards = true;
break;
case 8:
//sets object to be billboard around only y axis. (xz remain unchanged)
var obj = view.getUint8(offset++);
objects.objectData[obj].billboardMode = 2;
if (debug) console.log("[0x"+last.toString(16)+"] Object " + obj + " set to Y billboard mode.");
mainObj.hasBillboards = true;
break;
case 9: //skinning equ. not used?
if (debug) console.log("[0x"+last.toString(16)+"] Skinning Equation (UNIMPLEMENTED)");
debugger;
break;
case 0x0b:
break; //begin, not quite sure what of. doesn't seem to change anything
if (debug) console.log("[0x"+last.toString(16)+"] BEGIN PAIRING.");
break; //begin polygon material paring (scale up? using scale value in model..)
case 0x2b:
break; //end
if (debug) console.log("[0x"+last.toString(16)+"] END PAIRING.");
break; //end polygon material pairing (scale down? using scale(down) value in model..)
default:
console.log("bone transform unknown: "+last);
console.log("bone transform unknown: 0x"+last.toString(16));
break;
}
}
//if (window.throwWhatever) debugger;
if (debug) console.log("== End Parse Bones ==");
return commands;
}
@ -213,7 +271,6 @@ window.nsbmd = function(input) {
}*/
var polyAttrib = view.getUint16(offset+12, true);
console.log(polyAttrib);
var flags = view.getUint16(offset+22, true); //other info in here is specular data etc.

View File

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

View File

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

View File

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

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;
}
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) {
var b = bca.animData.objectData[anim];
var totalLength = getLength(anim);
frame %= getLength(anim);
var fLow = Math.floor(frame);
var fHigh = Math.ceil(frame);
var iterp = frame%1;
@ -64,27 +79,21 @@ window.nitroAnimator = function(bmd, bca) {
if (a.translate != null) {
translate = [];
if (a.tlExtra[0] != null) {
var f = frame * a.tlExtra[0].speed;
var fLow = Math.floor(f)%a.translate[0].length;
var fHigh = Math.ceil(f)%a.translate[0].length;
var p = f%1;
translate[0] = a.translate[0][fHigh]*(p) + a.translate[0][fLow]*(1-p);
var f = getFrames(a.tlExtra[0], a.translate[0].length, frame, totalLength);
var p = f[2];
translate[0] = a.translate[0][f[0]]*(1-p) + a.translate[0][f[1]]*(p);
} else translate[0] = a.translate[0][0];
if (a.tlExtra[1] != null) {
var f = frame * a.tlExtra[1].speed;
var fLow = Math.floor(f)%a.translate[1].length;
var fHigh = Math.ceil(f)%a.translate[1].length;
var p = f%1;
translate[1] = a.translate[1][fHigh]*(p) + a.translate[1][fLow]*(1-p);
var f = getFrames(a.tlExtra[1], a.translate[1].length, frame, totalLength);
var p = f[2];
translate[1] = a.translate[1][f[0]]*(1-p) + a.translate[1][f[1]]*(p);
} else translate[1] = a.translate[1][0];
if (a.tlExtra[2] != null) {
var f = frame * a.tlExtra[2].speed;
var fLow = Math.floor(f)%a.translate[2].length;
var fHigh = Math.ceil(f)%a.translate[2].length;
var p = f%1;
translate[2] = a.translate[2][fHigh]*(p) + a.translate[2][fLow]*(1-p);
var f = getFrames(a.tlExtra[2], a.translate[2].length, frame, totalLength);
var p = f[2];
translate[2] = a.translate[2][f[0]]*(1-p) + a.translate[2][f[1]]*(p);
} else translate[2] = a.translate[2][0];
} else {
translate = fa.translate;
@ -93,13 +102,11 @@ window.nitroAnimator = function(bmd, bca) {
var rotate;
if (a.rotate != null) {
if (a.rotExtra != null) {
var f = frame * a.rotExtra.speed;
var fLow = Math.floor(f)%a.rotate.length;
var fHigh = Math.ceil(f)%a.rotate.length;
var p = f%1;
var f = getFrames(a.rotExtra, a.rotate.length, frame, totalLength);
var p = f[2];
var r1 = parseRotation(a.rotate[fLow]);
var r2 = parseRotation(a.rotate[fHigh]);
var r1 = parseRotation(a.rotate[f[0]]);
var r2 = parseRotation(a.rotate[f[1]]);
rotate = lerpMat3(r1, r2, p);
} else {
rotate = parseRotation(a.rotate[0]);
@ -112,27 +119,21 @@ window.nitroAnimator = function(bmd, bca) {
if (a.scale != null) {
scale = [];
if (a.scExtra[0] != null) {
var f = frame * a.scExtra[0].speed;
var fLow = Math.floor(f)%a.scale[0].length;
var fHigh = Math.ceil(f)%a.scale[0].length;
var p = f%1;
scale[0] = a.scale[0][fHigh].s1*(p) + a.scale[0][fLow].s1*(1-p);
var f = getFrames(a.scExtra[0], a.scale[0].length, frame, totalLength);
var p = f[2];
scale[0] = a.scale[0][f[0]].s1*(1-p) + a.scale[0][f[1]].s1*(p);
} else scale[0] = a.scale[0][0].s1;
if (a.scExtra[1] != null) {
var f = frame * a.scExtra[1].speed;
var fLow = Math.floor(f)%a.scale[1].length;
var fHigh = Math.ceil(f)%a.scale[1].length;
var p = f%1;
scale[1] = a.scale[1][fHigh].s1*(p) + a.scale[1][fLow].s1*(1-p);
var f = getFrames(a.scExtra[1], a.scale[1].length, frame, totalLength);
var p = f[2];
scale[1] = a.scale[1][f[0]].s1*(1-p) + a.scale[1][f[1]].s1*(p);
} else scale[1] = a.scale[1][0].s1;
if (a.scExtra[2] != null) {
var f = frame * a.scExtra[2].speed;
var fLow = Math.floor(f)%a.scale[2].length;
var fHigh = Math.ceil(f)%a.scale[2].length;
var p = f%1;
scale[2] = a.scale[2][fHigh].s1*(p) + a.scale[2][fLow].s1*(1-p);
var f = getFrames(a.scExtra[2], a.scale[2].length, frame, totalLength);
var p = f[2];
scale[2] = a.scale[2][f[0]].s1*(1-p) + a.scale[2][f[1]].s1*(p);
} else scale[2] = a.scale[2][0].s1;
} else {
scale = fa.scale;
@ -154,9 +155,15 @@ window.nitroAnimator = function(bmd, bca) {
var cmds = model.commands;
var curMat = mat4.create();
var lastStackID = 0;
var highestUsed = -1;
for (var i=0; i<cmds.length; 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]);
var o = objs[cmd.obj];
mat4.multiply(curMat, curMat, objMats[cmd.obj]);
@ -165,6 +172,7 @@ window.nitroAnimator = function(bmd, bca) {
if (cmd.stackID != null) {
matrices[cmd.stackID] = mat4.clone(curMat);
lastStackID = cmd.stackID;
if (lastStackID > highestUsed) highestUsed = lastStackID;
} else {
matrices[lastStackID] = mat4.clone(curMat);
}
@ -172,10 +180,14 @@ window.nitroAnimator = function(bmd, bca) {
model.lastStackID = lastStackID;
var scale = [model.head.scale, model.head.scale, model.head.scale];
targ.set(matBufEmpty);
var off=0;
for (var i=0; i<31; i++) {
if (matrices[i] != null) targ.set(matrices[i], off);
for (var i=0; i<=highestUsed; i++) {
if (matrices[i] != null) {
mat4.scale(matrices[i], matrices[i], scale);
targ.set(matrices[i], off);
}
off += 16;
}

View File

@ -223,6 +223,8 @@ window.nitroRender = new function() {
this.init = function(ctx) {
gl = ctx;
this.gl = gl;
this.billboardMat = mat4.create();
this.yBillboardMat = mat4.create();
shaders = nitroShaders.compileShaders(gl);
@ -233,7 +235,7 @@ window.nitroRender = new function() {
this.prepareShader = function() {
//prepares the shader so no redundant calls have to be made. Should be called upon every program change.
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 = {};
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(this.nitroShader.samplerUniform, 0);
@ -261,6 +263,12 @@ window.nitroRender = new function() {
this.prepareShader();
}
this.setShadBias = function(bias) {
var shader = shaders[1];
gl.uniform1f(shader.shadOffUniform, bias);
gl.uniform1f(shader.farShadOffUniform, bias);
}
this.resetShadOff = function() {
var shader = shaders[1];
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;
this.bmd = bmd;
var thisObj = this;
@ -399,7 +407,9 @@ function nitroModel(bmd, btx, remap) {
this.draw = draw;
this.drawPoly = externDrawPoly;
this.drawModel = externDrawModel;
this.getBoundingCollisionModel = getBoundingCollisionModel;
this.getCollisionModel = getCollisionModel;
this.baseMat = mat4.create();
modelBuffers = []
this.modelBuffers = modelBuffers;
@ -409,10 +419,6 @@ function nitroModel(bmd, btx, remap) {
matBuf.push({built: false, dat: new Float32Array(31*16)});
}
if (remap != null) {
setTextureRemap(remap);
}
if (btx != null) {
loadTexture(btx);
} else if (bmd.tex != null) {
@ -461,41 +467,47 @@ function nitroModel(bmd, btx, remap) {
var model = models[j];
var mat = model.materials.objectData
for (var i=0; i<mat.length; i++) {
var m = mat[i];
var texI = mat[i].tex;
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));
mat[i].texInd = tex.length;
loadMatTex(mat[i], btx);
}
}
}
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) {
var cacheID = truetex+":"+truepal;
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) {
texAnim = bta;
texFrame = 0;
@ -560,6 +567,11 @@ function nitroModel(bmd, btx, remap) {
texFrame = frame;
}
this.setBaseMat = function(mat) {
thisObj.baseMat = mat;
thisObj.billboardID = -1;
}
function externDrawModel(mv, project, mdl) {
var models = bmd.modelData.objectData;
drawModel(models[mdl], mv, project, mdl);
@ -584,7 +596,7 @@ function nitroModel(bmd, btx, remap) {
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.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][polyind] != null) return collisionModel[modelind][polyind];
var model = bmd.modelData.objectData[modelind];
@ -617,14 +707,16 @@ function nitroModel(bmd, btx, remap) {
var off = 0;
for (var i=0; i<tC; i++) {
var t = {}
t.Vertex1 = [tris[off++], tris[off++], tris[off++]];
t.Vertex2 = [tris[off++], tris[off++], tris[off++]];
t.Vertex3 = [tris[off++], tris[off++], tris[off++]];
t.Vertices = [];
t.Vertices[0] = [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
var v = vec3.sub([], t.Vertex2, t.Vertex1);
var w = vec3.sub([], t.Vertex3, t.Vertex1);
t.Normal = vec3.cross([], v, w)
var v = vec3.sub([], t.Vertices[1], t.Vertices[0]);
var w = vec3.sub([], t.Vertices[2], t.Vertices[0]);
t.Normal = vec3.cross([], v, w);
t.CollisionType = colType;
vec3.normalize(t.Normal, t.Normal);
out.push(t);
}
@ -647,7 +739,7 @@ function nitroModel(bmd, btx, remap) {
}
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.pMatrixUniform, false, project);
@ -678,9 +770,13 @@ function nitroModel(bmd, btx, remap) {
//we got a match! it's wonderful :')
var anim = anims.objectData[animNum];
//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) {
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]);
*/
break;
}
}
}
@ -692,7 +788,7 @@ function nitroModel(bmd, btx, remap) {
}
var material = model.materials.objectData[pmat];
nitroRender.setAlpha(material.alpha)
nitroRender.setAlpha(material.alpha);
if (texAnim != null) {
//generate and send texture matrix from data
@ -703,9 +799,7 @@ function nitroModel(bmd, btx, remap) {
if (animNum != -1) {
//we got a match! it's wonderful :')
var anim = anims.objectData[animNum];
var mat = mat3.create(); //material texture mat is ignored
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
var mat = matAtFrame(texFrame, anim);
gl.uniformMatrix3fv(shader.texMatrixUniform, false, mat);
} else {
gl.uniformMatrix3fv(shader.texMatrixUniform, false, material.texMat);
@ -717,24 +811,52 @@ function nitroModel(bmd, btx, remap) {
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.
var matrices = [];
var objs = model.objects.objectData;
var cmds = model.commands;
var curMat = mat4.create();
var curMat = mat4.clone(thisObj.baseMat);
var lastStackID = 0;
var highestUsed = -1;
for (var i=0; i<cmds.length; 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]);
var o = objs[cmd.obj];
mat4.multiply(curMat, curMat, o.mat);
if (o.billboardMode == 1) mat4.multiply(curMat, curMat, nitroRender.billboardMat);
if (o.billboardMode == 2) mat4.multiply(curMat, curMat, nitroRender.yBillboardMat);
if (cmd.stackID != null) {
matrices[cmd.stackID] = mat4.clone(curMat);
lastStackID = cmd.stackID;
if (lastStackID > highestUsed) highestUsed = lastStackID;
} else {
matrices[lastStackID] = mat4.clone(curMat);
}
@ -742,10 +864,14 @@ function nitroModel(bmd, btx, remap) {
model.lastStackID = lastStackID;
var scale = [model.head.scale, model.head.scale, model.head.scale];
targ.set(matBufEmpty);
var off=0;
for (var i=0; i<31; i++) {
if (matrices[i] != null) targ.set(matrices[i], off);
for (var i=0; i<=highestUsed; i++) {
if (matrices[i] != null) {
mat4.scale(matrices[i], matrices[i], scale);
targ.set(matrices[i], off);
}
off += 16;
}

View File

@ -113,7 +113,8 @@ window.nitroShaders = new (function() {
}\n\
\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\
vec2 ldNorm = abs((lightDist.xy)-vec2(0.5, 0.5));\n\
float dist = max(ldNorm.x, ldNorm.y);\n\

View File

@ -32,22 +32,25 @@ window.Race3DUI = function(scene, type, animStart) {
var params = {
"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
-128/1024, 128/1024, -(192+66)/1024, -66/1024
-128/1024, 128/1024, -(96)/1024, 96/1024
],
"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)
//right now the vertical range of the viewport is large to try figure out where the hell it's going?
"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": [
-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