Compare commits

...

16 Commits

Author SHA1 Message Date
riperiperi c45f9e2ba7 Make item flick easier to trigger. 2019-06-12 19:30:23 +01:00
riperiperi 7d09b4790b Fix touch velocity tracking 2019-06-12 19:19:26 +01:00
riperiperi f972b8e616 [pwa] even more pwa stuff 2019-06-11 22:36:18 +01:00
riperiperi f979ef5379 [PWA] Add manifest 2019-06-11 22:33:08 +01:00
riperiperi 2ae06f6345 [HTML] Change screen resize logic to be more general. 2019-06-11 22:02:50 +01:00
riperiperi 1d9588e9a2 [dot] fix for dot 2019-06-11 21:45:48 +01:00
riperiperi b85b071ad8 [mobile] Disable aa on mobile
It's not that it runs slow, it just sets my phone on fire.
2019-06-11 20:47:49 +01:00
riperiperi 3637ea13a4 [mobile] more changes 2019-06-11 20:39:03 +01:00
riperiperi 446d07f1b4 [Mobile] Add mobile controls.
They're invisible. Dpad on left, drift button on right, reverse button
above it. Pressing the dpad starts holding an item - releasing touch
releases the item. Flick forward or back to choose item direction.
2019-06-11 20:31:16 +01:00
riperiperi a773b60b95 Fix audio on iOS 2019-06-11 00:11:11 +01:00
riperiperi bbb61394f4 Greatly improve shading model
- Directional toon lighting component on entities. (extension of the
shadow light source)
The shadows were doing this anyways, but with extremely bad
stairstepping. Now it smoothly blends into the shadow.
- Greatly reduce shadow acne via the above and a larger bias.
- Respect culling mode for each model.
- Two sided shading for course models.
- Disable alpha for water that does not use it.
- Only GBA Sky Garden's skybox receives shadows now.
- Custom light positions for some maps. (you can guess which)
2019-06-10 23:06:38 +01:00
riperiperi f583ad11b1 Slightly less offensive kart collision. Missing in-air component persisting, and need to look more
at the original to find out how it really works.
2019-06-10 01:16:18 +01:00
riperiperi ff21142744 All courses now render properly. WIP items (shell, banana, fake item) 2019-06-09 23:50:55 +01:00
riperiperi e010627b5d Tweak physics, cannons, nsbtp support, prepare to add items. 2019-05-29 01:08:48 +01:00
riperiperi e1dc8603d1
Fix typo 2019-05-11 23:36:41 +01:00
riperiperi 4fdee73a5b
Fix battle maps. 2019-05-11 23:34:36 +01:00
60 changed files with 4591 additions and 1517 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.nds
*.exe
*.sdat
mongoose.conf
Code/Engine/col.lua

View File

@ -13,13 +13,13 @@
fileQuota = 1;
filesLoaded = 0;
window.onload = function(argument) {
loadFile("sound_data.sdat");
loadFile("SD_BBP2p.sdat");
}
var i=0;
var last = null;
function init() {
nitroAudio.init(new sdat(files["sound_data.sdat"])); //89
nitroAudio.init(new sdat(files["SD_BBP2p.sdat"])); //89
//you need to extract this one yourself!
play.addEventListener('click', function() {
@ -29,7 +29,7 @@
})
/*
var ctx = new AudioContext();
test = new sdat(files["sound_data.sdat"]);
test = new sdat(files["SD_BBP2p.sdat"]);
testAud = new SSEQPlayer(test.sections["$INFO"][0][5], test, ctx);
var elem = document.getElementById('play'),

View File

@ -18,6 +18,7 @@ window.nitroAudio = new (function() {
t.kill = kill;
t.init = init;
t.instaKill = instaKill;
t.updateListener = updateListener;
t.sdat = null;
@ -33,6 +34,25 @@ window.nitroAudio = new (function() {
t.sdat = sdat;
}
function updateListener(pos, view) {
var listener = ctx.listener;
if (listener.positionX == null) {
//use old setters. safari ios
listener.setPosition(pos[0], pos[1], pos[2]);
listener.setOrientation(view[8], -view[9], -view[10], view[4], view[5], view[6]);
} else {
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];
@ -76,6 +96,7 @@ window.nitroAudio = new (function() {
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();
@ -105,9 +126,10 @@ window.nitroAudio = new (function() {
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;
panner.refDistance = soundProps.refDistance || 192;
if (soundProps.panningModel != null) panner.panningModel = soundProps.panningModel;
if (soundProps.rolloffFactor != null) panner.rolloffFactor = soundProps.rolloffFactor;
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

@ -20,35 +20,48 @@ window.cameraIngame = function(kart) {
var lookAtOffset = [0, 16, 0]
var camNormal = [0, 1, 0];
var forwardNormal = null;
var camAngle = 0;
var boostOff = 0;
function tweenVec3(from, to) {
from[0] += (to[0]-from[0])*0.075;
from[1] += (to[1]-from[1])*0.075;
from[2] += (to[2]-from[2])*0.075;
}
function getView(scene) {
var loop = kart.physBasis != null && kart.physBasis.loop;
var basis = buildBasis();
tweenVec3(camOffset, loop ? [0, 12, -57] : [0, 32, -48]);
var camPos = vec3.transformMat4([], camOffset, basis);
var lookAtPos = vec3.transformMat4([], lookAtOffset, basis);
vec3.scale(camPos, camPos, 1/1024);
vec3.scale(lookAtPos, lookAtPos, 1/1024);
var mat = mat4.lookAt(mat4.create(), camPos, lookAtPos, [0, 1, 0]);
var mat = mat4.lookAt(mat4.create(), camPos, lookAtPos, (kart.physBasis) ? camNormal : [0, 1, 0]);
var kpos = vec3.clone(kart.pos);
if (kart.drifting && !kart.driftLanded && kart.ylock>0) kpos[1] -= kart.ylock;
mat4.translate(mat, mat, vec3.scale([], kpos, -1/1024));
//interpolate visual normal roughly to target
camNormal[0] += (kart.kartNormal[0]-camNormal[0])*0.075;
camNormal[1] += (kart.kartNormal[1]-camNormal[1])*0.075;
camNormal[2] += (kart.kartNormal[2]-camNormal[2])*0.075;
tweenVec3(camNormal, kart.kartNormal);
vec3.normalize(camNormal, camNormal);
if (kart.physBasis != null) {
if (loop) {
var kartA = kart.physicalDir+kart.driftOff/2;
var forward = [Math.sin(kartA), 0, -Math.cos(kartA)];
vec3.transformMat4(forward, forward, kart.physBasis.mat);
camAngle += dirDiff(Math.atan2(forward[0], -forward[2]), camAngle)*0.075;
if (forwardNormal == null) {
forwardNormal = [Math.sin(camAngle), 0, -Math.cos(camAngle)];
} else {
tweenVec3(forwardNormal, forward);
}
} else {
camAngle += dirDiff(kart.physicalDir+kart.driftOff/2, camAngle)*0.075;
forwardNormal = null;
}
camAngle = fixDir(camAngle);
@ -67,8 +80,8 @@ window.cameraIngame = function(kart) {
function buildBasis() {
//order y, x, z
var kart = thisObj.kart;
var forward = [Math.sin(camAngle), 0, -Math.cos(camAngle)];
var side = [Math.cos(camAngle), 0, Math.sin(camAngle)];
var forward = (forwardNormal != null) ? forwardNormal : [Math.sin(camAngle), 0, -Math.cos(camAngle)];
var side = vec3.cross([], forward, camNormal);
/*
if (kart.physBasis != null) {
vec3.transformMat4(forward, forward, kart.physBasis.mat);

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

@ -7,6 +7,11 @@
// includes: main.js
//
function getPlayerControls() {
if (mobile) return controlMobile;
else return controlDefault;
}
window.controlDefault = function() {
var thisObj = this;
@ -28,7 +33,96 @@ window.controlDefault = function() {
//-1 to 1, intensity.
turn: (keysArray[37]?-1:0)+(keysArray[39]?1:0),
airTurn: (keysArray[40]?-1:0)+(keysArray[38]?1:0) //air excitebike turn, doesn't really have much function
airTurn: (keysArray[40]?-1:0)+(keysArray[38]?1:0) //air excitebike turn, item fire direction
};
}
}
window.controlMobile = function() {
var thisObj = this;
this.local = true;
var kart;
var item = false;
this.setKart = function(k) {
kart = k;
thisObj.kart = k;
}
this.fetchInput = fetchInput;
function searchForTouch(rect) { //{touch: Touch, enterLeave: number} 1 is enter, leave is 2,
for (var i=0; i<touches.length; i++) {
var touch = touches[i];
var inNow = (touch.x > rect[0] && touch.y > rect[1] && touch.x < rect[2] && touch.y < rect[3]);
var inBefore = (touch.lastx > rect[0] && touch.lasty > rect[1] && touch.lastx < rect[2] && touch.lasty < rect[3]);
var active = inNow && !touch.released;
if (inNow == inBefore && inNow) {
return {touch: touch, enterLeave: 0, active: active};
} else if (inNow) {
return {touch: touch, enterLeave: 1, active: active};
} else if (inBefore) {
return {touch: touch, enterLeave: 2, active: active};
}
}
return null;
}
function step(start, end, value) {
return Math.max(0, Math.min(1, (value-start)/(end-start)));
}
function fetchInput() {
var targW = 1136;
var targH = 640;
//window.touches array is filled by the game container
//touches [{x:number (0-1), y:number (0-1), pressed:boolean, released:boolean, lastx:number (0-1), lasty:number (0-1)}]
//accel unless reverse button is pressed
var reverse = searchForTouch([955/targW, 320/targH, (955+125)/targW, (320+125)/targH]);
reverse = (reverse != null) && reverse.active;
var driftTouch = searchForTouch([780/targW, 468/targH, (780+300)/targW, (468+125)/targH]); //drift button on the right
var itemTouch = searchForTouch([50/targW, 468/targH, (50+300)/targW, (468+125)/targH]); //touch the button exactly
var dPadTouch = searchForTouch([0/targW, (468-50)/targH, (0+400)/targW, (468+225)/targH]); //allow for some space
var turn = 0;
if (dPadTouch != null && dPadTouch.active) {
turn = step(0/targW, 400/targW, dPadTouch.touch.x);
//digitize
turn = Math.floor(turn*3) - 1;
}
var itemDir = 0;
if (!item) {
//if we touch the dpad (more exact than direction), start pressing item
if (itemTouch != null && itemTouch.active && itemTouch.touch.pressed) {
item = true;
}
} else {
//if we release dpad, fire the item
if (dPadTouch == null || !dPadTouch.active) {
if (dPadTouch != null) {
//set direction based on flick direction or position
var vel = dPadTouch.touch.lasty - dPadTouch.touch.y;
if (vel > 2/targH) itemDir = -1; //flicked down
if (vel < -2/targH) itemDir = 1; //flicked up
}
item = false;
}
}
return {
accel: !reverse, //x
decel: reverse, //z
drift: (driftTouch != null && driftTouch.active), //s
item: item, //a
//-1 to 1, intensity.
turn: turn,
airTurn: itemDir //air excitebike turn, item fire direction
};
}

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;
@ -61,6 +242,7 @@ window.IngameRes = function(rom) {
}
t.blueShell = new nitroModel(new nsbmd(r.MainRace.getFile("/Item/koura_w.nsbmd")));
t.splat = new nitroModel(new nsbmd(r.MainRace.getFile("/Item/geso_sumi.nsbmd")));
t.fakeBox = new nitroModel(new nsbmd(r.MainRace.getFile("/MapObj/box.nsbmd")));
r.items = t;
}

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

@ -7,56 +7,60 @@
window.MKDSCONST = new (function() {
this.DAMAGE_SPIN = 0;
this.DAMAGE_FLIP = 1;
this.DAMAGE_EXPLODE = 2;
this.COURSEDIR = "/data/Course/";
this.COURSES = [ //in order of course id, nitro through retro
"cross_course",
"bank_course",
"beach_course",
"mansion_course",
{name:"cross_course", music: 74},
{name:"bank_course", music: 16},
{name:"beach_course", music: 15},
{name:"mansion_course", music: 21, lightHeight: 20/180, lightAngle: 160/180},
"desert_course",
"town_course",
"pinball_course",
"ridge_course",
{name:"desert_course", music: 38, lightHeight: 40/180},
{name:"town_course", music: 17},
{name:"pinball_course", music: 19},
{name:"ridge_course", music: 36},
"snow_course",
"clock_course",
"mario_course",
"airship_course",
{name:"snow_course", music: 37},
{name:"clock_course", music: 39},
{name:"mario_course", music: 74},
{name:"airship_course", music: 18, lightHeight: 40/180, lightAngle: 140/180},
"stadium_course",
"garden_course",
"koopa_course",
"rainbow_course",
{name:"stadium_course", music: 19},
{name:"garden_course", music: 20},
{name:"koopa_course", music: 40},
{name:"rainbow_course", music: 41},
"old_mario_sfc",
"old_momo_64",
"old_peach_agb",
"old_luigi_gc",
{name:"old_mario_sfc", music: 22},
{name:"old_momo_64", music: 30},
{name:"old_peach_agb", music: 26},
{name:"old_luigi_gc", music: 33},
"old_donut_sfc",
"old_frappe_64",
"old_koopa_agb",
"old_baby_gc",
{name:"old_donut_sfc", music: 24},
{name:"old_frappe_64", music: 31},
{name:"old_koopa_agb", music: 27},
{name:"old_baby_gc", music: 34},
"old_noko_sfc",
"old_choco_64",
"old_luigi_agb",
"old_kinoko_gc",
{name:"old_noko_sfc", music: 23},
{name:"old_choco_64", music: 29},
{name:"old_luigi_agb", music: 26},
{name:"old_kinoko_gc", music: 35},
"old_choco_sfc",
"old_hyudoro_64",
"old_sky_agb",
"old_yoshi_gc",
{name:"old_choco_sfc", music: 25},
{name:"old_hyudoro_64", music: 32},
{name:"old_sky_agb", music: 28, skyboxShadows: true},
{name:"old_yoshi_gc", music: 33, lightHeight: 30/180, lightAngle: 111/180},
"mini_stage1",
"mini_stage2",
"mini_stage3",
"mini_stage4",
"mini_block_64",
"mini_dokan_gc"
{name:"mini_stage1", music: 43, battle: true},
{name:"mini_stage2", music: 43, battle: true, lightHeight: 20/180, lightAngle: 160/180},
{name:"mini_stage3", music: 43, battle: true},
{name:"mini_stage4", music: 43, battle: true},
{name:"mini_block_64", music: 43, battle: true},
{name:"mini_dokan_gc", music: 43, battle: true}
]

View File

@ -88,11 +88,11 @@ window.clientScene = function(wsUrl, wsInstance, res) {
var mainNarc, texNarc
if (obj.c.substr(0, 5) == "mkds/") {
var cnum = Number(obj.c.substr(5));
var music = MKDSCONST.COURSE_MUSIC[cnum];
var cDir = MKDSCONST.COURSEDIR+MKDSCONST.COURSES[cnum];
var course = MKDSCONST.COURSE[cnum];
var cDir = MKDSCONST.COURSEDIR+course.name;
var mainNarc = new narc(lz77.decompress(gameROM.getFile(cDir+".carc")));
var texNarc = new narc(lz77.decompress(gameROM.getFile(cDir+"Tex.carc")));
setUpCourse(mainNarc, texNarc, music, obj)
setUpCourse(mainNarc, texNarc, course, obj)
}
else throw "custom tracks are not implemented yet!"
}
@ -101,7 +101,7 @@ window.clientScene = function(wsUrl, wsInstance, res) {
wsH["$+"] = function(obj) { //add kart. only used in debug circumstances. (people can't join normal gamemodes midgame)
console.log("kart added");
if (t.mode != 1) return;
var kart = new Kart([0, -2000, 0], 0, 0, obj.k.kart, obj.k.char, new ((obj.p)?((window.prompt("press y for cpu controlled") == "y")?controlRaceCPU:controlDefault):controlNetwork)(t.activeScene.nkm, {}), t.activeScene);
var kart = new Kart([0, -2000, 0], 0, 0, obj.k.kart, obj.k.char, new ((obj.p)?((window.prompt("press y for cpu controlled") == "y")?controlRaceCPU:getPlayerControls()):controlNetwork)(t.activeScene.nkm, {}), t.activeScene);
t.activeScene.karts.push(kart);
}
@ -124,13 +124,13 @@ window.clientScene = function(wsUrl, wsInstance, res) {
}
}
function setUpCourse(mainNarc, texNarc, music, obj) {
function setUpCourse(mainNarc, texNarc, course, obj) {
var chars = [];
for (var i=0; i<obj.k.length; i++) {
var k = obj.k[i];
var pKart = (i == obj.p);
//TODO: custom character support
chars.push({charN:k.char, kartN:k.kart, controller:((pKart)?((window.prompt("press y for cpu controlled") == "y")?controlRaceCPU:controlDefault):controlNetwork), raceCam:pKart, extraParams:[{k:"name", v:k.name}, {k:"active", v:k.active}]});
chars.push({charN:k.char, kartN:k.kart, controller:((pKart)?((window.prompt("press y for cpu controlled") == "y")?controlRaceCPU:getPlayerControls()):controlNetwork), raceCam:pKart, extraParams:[{k:"name", v:k.name}, {k:"active", v:k.active}]});
if (pKart) {
for (var i=0; i<7; i++) {
@ -142,7 +142,7 @@ window.clientScene = function(wsUrl, wsInstance, res) {
}
}
t.activeScene = new courseScene(mainNarc, texNarc, music, chars, {}, res);
t.activeScene = new courseScene(mainNarc, texNarc, course, chars, {}, res);
for (var i=0; i<obj.k.length; i++) {
t.activeScene.karts[i].active = obj.k[i].active;

View File

@ -12,8 +12,8 @@
// render/*
//
window.courseScene = function(mainNarc, texNarc, music, chars, options, gameRes) {
window.courseScene = function(mainNarc, texNarc, courseObj, chars, options, gameRes) {
var music = courseObj.music;
var startSetups = [
{maxplayers:12, toAline:4, xspacing:32, yspacing:32, liney:160},
{maxplayers:24, toAline:4, xspacing:32, yspacing:32, liney:80},
@ -56,12 +56,17 @@ window.courseScene = function(mainNarc, texNarc, music, chars, options, gameRes)
//load main course
var courseTx = new nsbtx(texNarc.getFile("/course_model.nsbtx"), false, true);
var taFile = mainNarc.getFile("/course_model.nsbta");
if (taFile != null) var courseTa = new nsbta(taFile); //can be null
var tpFile = mainNarc.getFile("/course_model.nsbtp");
if (tpFile != null) var courseTp = new nsbtp(tpFile); //can be null
var courseMdl = new nsbmd(mainNarc.getFile("/course_model.nsbmd"));
var course = new nitroModel(courseMdl, courseTx)
if (taFile != null) course.loadTexAnim(courseTa);
if (tpFile != null) course.loadTexPAnim(courseTp);
//load sky
var skyTx = new nsbtx(texNarc.getFile("/course_model_V.nsbtx"), false, true);
@ -107,12 +112,18 @@ window.courseScene = function(mainNarc, texNarc, music, chars, options, gameRes)
if (!shadow) {
var skyMat = mat4.scale(mat4.create(), mvMatrix, [1/64, 1/64, 1/64]);
sky.setFrame(frame);
if (!courseObj.skyboxShadows) nitroRender.setLightIntensities(0, 0);
sky.draw(skyMat, pMatrix);
if (!courseObj.skyboxShadows) nitroRender.setLightIntensities();
}
var lvlMat = mat4.scale(mat4.create(), mvMatrix, [1/64, 1/64, 1/64]);//[2, 2, 2]);
course.setFrame(frame);
nitroRender.forceFlatNormals = true;
nitroRender.setLightIntensities(0);
course.draw(lvlMat, pMatrix);
nitroRender.setLightIntensities();
nitroRender.forceFlatNormals = false;
var transE = [];
@ -134,13 +145,14 @@ window.courseScene = function(mainNarc, texNarc, music, chars, options, gameRes)
else e.draw(mvMatrix, pMatrix, gl);
}
nitroRender.setLightIntensities(0);
for (var i=0; i<scn.particles.length; i++) {
var e = scn.particles[i];
e.draw(mvMatrix, pMatrix, gl);
}
scn.items.draw(mvMatrix, pMatrix, gl);
nitroRender.setLightIntensities();
}
function sndUpdate(view) {
@ -202,6 +214,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++;
}
@ -229,12 +243,28 @@ window.courseScene = function(mainNarc, texNarc, music, chars, options, gameRes)
scn.paths = paths;
}
function getLightCenter() {
var average = vec3.create();
var objs = scn.nkm.sections["OBJI"].entries;
for (var i=0; i<objs.length; i++) {
vec3.add(average, average, objs[i].pos);
}
vec3.scale(average, average, (1/objs.length) /-1024);
return average;
}
function startCourse() {
scn.lightMat = mat4.create();
mat4.rotateX(scn.lightMat, scn.lightMat, Math.PI*(61/180));
mat4.rotateY(scn.lightMat, scn.lightMat, Math.PI*(21/180));
mat4.mul(scn.farShadMat, mat4.ortho(mat4.create(), -5, 5, -5, 5, -5, 5), scn.lightMat);
mat4.rotateX(scn.lightMat, scn.lightMat, Math.PI*(courseObj.lightHeight || (61/180)));
mat4.rotateY(scn.lightMat, scn.lightMat, Math.PI*(courseObj.lightAngle || (21/180)));
scn.lightDir = [0, 0, 1];
vec3.transformMat3(scn.lightDir, scn.lightDir, mat3.invert([], mat3.fromMat4([], scn.lightMat)));
scn.farShadMat = mat4.create();
mat4.translate(scn.farShadMat, scn.lightMat, getLightCenter());
mat4.mul(scn.farShadMat, mat4.ortho(mat4.create(), -5, 5, -5, 5, -5, 5), scn.farShadMat);
compilePaths();
@ -388,7 +418,7 @@ window.courseScene = function(mainNarc, texNarc, music, chars, options, gameRes)
switch (mode.id) {
case 0:
//race init. fade scene in and play init music.
nitroAudio.playSound(11, {volume:2}, null); //7:race (gp), 11:race2 (vs), 12:battle
nitroAudio.playSound((courseObj.battle)?12:11, {volume:2}, null); //7:race (gp), 11:race2 (vs), 12:battle
break;
case 1:
//spawn lakitu and countdown animation. allow pre-acceleration.

View File

@ -84,7 +84,7 @@ window.sceneDrawer = new function() {
gl.colorMask(false, false, false, false);
scn.draw(gl, shadMat, true);
nitroRender.setShadowMode(shadowTarg.depth, scn.farShad.depth, shadMat, scn.farShadMat);
nitroRender.setShadowMode(shadowTarg.depth, scn.farShad.depth, shadMat, scn.farShadMat, scn.lightDir);
nitroRender.flagShadow = false;
nitroRender.updateBillboards(view.mv);

View File

@ -52,18 +52,18 @@ window.singleScene = function(course, wsInstance, res) {
var mainNarc, texNarc
if (course.substr(0, 5) == "mkds/") {
var cnum = Number(course.substr(5));
var music = MKDSCONST.COURSE_MUSIC[cnum];
var cDir = MKDSCONST.COURSEDIR+MKDSCONST.COURSES[cnum];
var course = MKDSCONST.COURSES[cnum];
var cDir = MKDSCONST.COURSEDIR+course.name;
var mainNarc = new narc(lz77.decompress(gameROM.getFile(cDir+".carc")));
var texNarc = new narc(lz77.decompress(gameROM.getFile(cDir+"Tex.carc")));
setUpCourse(mainNarc, texNarc, music)
setUpCourse(mainNarc, texNarc, course)
} else throw "custom tracks are not implemented yet!"
}
function setUpCourse(mainNarc, texNarc, music) {
function setUpCourse(mainNarc, texNarc, course) {
var chars = [];
chars.push({charN:mchar, kartN:mkart, controller:((window.prompt("press y for cpu controlled") == "y")?controlRaceCPU:controlDefault), raceCam:true, extraParams:[{k:"name", v:"single"}, {k:"active", v:true}]});
chars.push({charN:mchar, kartN:mkart, controller:((window.prompt("press y for cpu controlled") == "y")?controlRaceCPU:getPlayerControls()), raceCam:true, extraParams:[{k:"name", v:"single"}, {k:"active", v:true}]});
for (var i=0; i<7; i++) {
var tchar = Math.floor(Math.random()*12);
@ -72,7 +72,7 @@ window.singleScene = function(course, wsInstance, res) {
chars.push({charN:tchar, kartN:tkart, controller:controlRaceCPU, raceCam:false, extraParams:[{k:"name", v:"no"}, {k:"active", v:true}]});
}
t.activeScene = new courseScene(mainNarc, texNarc, music, chars, {}, res);
t.activeScene = new courseScene(mainNarc, texNarc, course, chars, {}, res);
t.myKart = t.activeScene.karts[0];
t.mode = {

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

@ -31,22 +31,24 @@ window.ObjDecor = function(obji, scene) {
var animMat = null;
function draw(view, pMatrix) {
if (forceBill) nitroRender.setShadBias(0.001);
mat4.translate(mat, view, 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));
if (anim != null) {
animMat = anim.setFrame(0, 0, animFrame++);
}
mat4.scale(mat, mat, vec3.scale([], t.scale, 16));
res.mdl[0].draw(mat, pMatrix, animMat);
if (forceBill) nitroRender.resetShadOff();
}
function update() {
res.mdl[0].setFrame(animFrame);
if (anim != null) {
animMat = anim.setFrame(0, 0, animFrame);
}
animFrame++;
}
function requireRes() { //scene asks what resources to load
@ -75,7 +77,7 @@ window.ObjDecor = function(obji, scene) {
case 0x0138:
return {mdl:[{nsbmd:"GardenTree1.nsbmd"}]};
case 0x0139:
return {mdl:[{nsbmd:"kamome.nsbmd"}], other:[null, null, "kamone.nsbtp"]}; //animates using nsbtp, and uses route to move
return {mdl:[{nsbmd:"kamome.nsbmd"}], other:[null, null, "kamome.nsbtp"]}; //animates using nsbtp, and uses route to move
case 0x013A:
return {mdl:[{nsbmd:"CrossTree1.nsbmd"}]};
@ -265,9 +267,13 @@ window.ObjDecor = function(obji, scene) {
if (r.other.length > 0 && r.other[0] != null) {
res.mdl[0].loadTexAnim(r.other[0]);
}
if (r.other.length > 1 && r.other[1] != null)
if (r.other.length > 1 && r.other[1] != null) {
anim = new nitroAnimator(r.mdl[0].bmd, r.other[1]);
}
if (r.other.length > 2 && r.other[2] != null) {
res.mdl[0].loadTexPAnim(r.other[2]);
}
}
}
}

412
code/entities/item.js Normal file
View File

@ -0,0 +1,412 @@
//
// shell.js
//--------------------
// Entity type for any item. Specific item types in `/item` folder
// Has a default collision handler, but can pass control to the specific item code.
// by RHY3756547
//
// includes: gl-matrix.js (glMatrix 2.0)
// /formats/kcl.js
//
var itemTypes = {
//physics, holdable
'$koura_g': GreenShellC,
'$koura_r': RedShellC,
'$banana': BananaC,
'$bomb': BombC,
'$f_box': FakeBoxC,
//groups
'$koura_group': ShellGroupC,
'$banana_group': BananaGroupC,
//one use items
'$kinoko': MushroomC,
'$kinoko_group': MushroomGroupC,
'$kinoko_p': QueenMushroomC,
'$star': StarC,
'$thunder': ThunderC,
'$gesso': BlooperC,
'$teresa': BooC,
'$killer': KillerC,
'$koura_w': BlueShellC
}
window.Item = function(scene, owner, type, id) {
var t = this;
var minimumMove = 0.01;
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.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 = 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;
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...
// canBeHeld: boolean
// canBeDropped: boolean | 'func'
// isDestructive: boolean
// 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('-');
if (subtypeInd == -1) subtypeInd = type.length;
this.controller = new itemTypes["$"+type.substr(0, subtypeInd)](this, scene, type.substr(subtypeInd + 1));
//functions
this.update = update;
this.draw = draw;
this.updateHold = updateHold;
this.release = release;
this.canBeHeld = canBeHeld;
this.canBeDropped = canBeDropped;
this.isDestructive = isDestructive;
this.isSolid = isSolid;
this.finalize = finalize;
this.collide = collide;
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 / 4;
//offset the kart's drift offset (on direction)
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);
vec3.sub(t.vel, pos, t.pos); //set the object's velocity to try move it to the hold location. (gravity is disabled)
t.enablePhysics = true;
}
function release(forward) {
//release the item, either forward or back
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) {
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 {
t.vel = vec3.create();
t.safeKart = null;
}
}
t.held = false;
}
function canBeHeld() {
return t.controller.canBeHeld || false;
}
function canBeDropped() {
if (t.controller.canBeDropped == null) return true;
return t.controller.canBeDropped;
}
function isDestructive() {
return t.controller.isDestructive || false;
}
function isSolid() {
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) {
if (t.controller.collide) {
t.controller.collide(item);
return;
}
if (item.type) {
//has a type, definitely an item
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(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;
}
}
} else {
//is a kart. usually this is where objects differ
if (t.controller.collideKart) {
t.controller.collideKart(item);
}
}
}
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++;
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(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);
}
}
if (t.safeKart && !hitSafe && !t.held) {
t.safeTime--;
if (t.safeTime <= 0) {
t.safeKart = null;
}
}
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);
}
}
}
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, t.colRadius * t.xyScale[1], 0]));
spritify(mat);
var scale = 6*t.colRadius * (1 - t.holdTime/7);
mat4.scale(mat, mat, [scale, scale, scale]);
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]);
}
var spritify = function(mat, scale) {
var scale = (scale == null)?Math.sqrt(mat[0]*mat[0]+mat[1]*mat[1]+mat[2]*mat[2]):scale;
mat[0]=scale; mat[1]=0; mat[2]=0;
mat[4]=0; mat[5]=scale; mat[6]=0;
mat[8]=0; mat[9]=0; mat[10]=scale;
}
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 adjustPos = true;
if (MKDS_COLTYPE.GROUP_WALL.indexOf(colType) != -1) { //wall
//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 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.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);
}
if (adjustPos) { //move back from plane slightly
vec3.add(pos, pos, vec3.scale(vec3.create(), n, minimumMove));
}
}
}

View File

@ -48,6 +48,7 @@ window.ItemBox = function(obji, scene) {
scene.particles.push(new NitroEmitter(scene, ok, 47));
t.mode = 1;
t.time = 0;
ok.items.getItem(null); //todo: specific item from some
break;
}
}
@ -97,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

@ -0,0 +1,147 @@
window.BananaC = function(item, scene, type) {
var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = false;
item.floorBounce = 0;
this.collideKart = collideKart;
this.onRest = onRest;
this.update = update;
function collideKart(kart) {
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) {
this.canBeHeld = false;
this.canBeDropped = 'func';
this.rotationPeriod = 45;
item.colRadius = -Infinity;
item.enablePhysics = false;
this.draw = draw;
function draw(mvMatrix, pMatrix) {
//the group itself is invisible - the bananas draw individually
}
}
window.FakeBoxC = function(item, scene, type) {
var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = false;
this.isSolid = false;
item.floorBounce = 0;
item.airResist = 0.98;
this.collideKart = collideKart;
this.onRest = onRest;
this.update = update;
this.draw = draw;
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);
var mdl = scene.gameRes.items.fakeBox;
mdl.draw(mat, pMatrix);
}
}
window.BombC = function(item, scene, type) {
var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = true;
this.explodeTime = 0;
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.deadTimer > 0 && t.explodeTime == 0) {
//begin explosion
t.explodeTime = 1;
}
if (!item.held && item.colRadius < 6) {
item.colRadius += 0.2;
if (item.colRadius > 6) item.colRadius = 6;
}
}
}

View File

@ -0,0 +1,10 @@
//boost, 3x boost, queen boost, star, ghost
window.MushroomC = null;
window.MushroomGroupC = null;
window.QueenMushroomC = null;
window.StarC = null;
window.ThunderC = null;
window.BlooperC = null;
window.BooC = null;
window.KillerC = null;

View File

@ -0,0 +1,205 @@
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));
item.stuckTo = dat.object;
} 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) {
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = true;
}
window.ShellGroupC = function(item, scene, type) {
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
}
}
window.BlueShellC = null;

View File

@ -17,7 +17,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
var kartSoundBase = 170;
var COLBOUNCE_TIME = 20;
var COLBOUNCE_STRENGTH = 1;
var COLBOUNCE_STRENGTH = 4;
var params = scene.gameRes.kartPhys.karts[kartN];
var offsets = scene.gameRes.kartOff.karts[kartN];
@ -27,6 +27,9 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
this.active = true;
this.preboost = true;
//supplimentary controllers
this.items = new KartItems(this, scene);
this.soundProps = {};
this.pos = pos;
this.angle = angle;
@ -70,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;
@ -311,11 +319,12 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
kartAnim = (kartAnim+1)%8;
var input = k.controller.fetchInput();
k.lastInput = input;
k.items.update(input);
if (input.turn > 0.3) {
if (k.driveAnimF < 28) k.driveAnimF++;
} else if (input.turn < -0.3) {
if (k.driveAnimF > 0) k.driveAnimF--;
} else if (input.turn < -0.3) {
if (k.driveAnimF < 28) k.driveAnimF++;
} else {
if (k.driveAnimF > 14) k.driveAnimF--;
else if (k.driveAnimF < 14) k.driveAnimF++;
@ -386,7 +395,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
} else if (k.cannon != null) { //when cannon is active, we fly forward at max move speed until we get to the cannon point.
var c = scene.nkm.sections["KTPC"].entries[k.cannon];
if (c.id2 != 0) {
if (c.id1 != -1 && c.id2 != -1) {
var c2 = scene.nkm.sections["KTPC"].entries[c.id2];
c = c2;
@ -395,16 +404,25 @@ 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;
k.cannon = null;
} else {
var mat = mat4.create();
mat4.rotateY(mat, mat, c.angle[1]*(Math.PI/180));
if (true) {
//vertical angle from position? airship fortress is impossible otherwise
//var c2 = scene.nkm.sections["KTPC"].entries[c.id2];
var diff = vec3.sub([], c.pos, k.pos);
var dAdj = Math.sqrt(diff[0]*diff[0] + diff[2]*diff[2]);
var dHyp = Math.sqrt(diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2]);
mat4.rotateX(mat, mat, ((diff[1] > 0) ? -1 : 1) * Math.acos(dAdj/dHyp));
} else {
mat4.rotateX(mat, mat, c.angle[0]*(-Math.PI/180));
}
var forward = [0, 0, 1];
var up = [0, 1, 0];
@ -415,17 +433,25 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.physicalDir = (180-c.angle[1])*(Math.PI/180);
k.angle = k.physicalDir;
k.kartTargetNormal = vec3.transformMat4(up, up, mat);
k.airTime = 0;
var planeConst = -vec3.dot(c.pos, forward);
var cannonDist = vec3.dot(k.pos, forward) + planeConst;
if (cannonDist > 0) k.cannon = null;
if (cannonDist > 0) {
k.cannon = null; //leaving cannon state
k.speed = params.topSpeed;
k.vel = vec3.scale([], vec3.transformMat4(forward, forward, mat), k.speed);
}
}
} else { //default kart mode
if (k.OOB > 0) {
playCharacterSound(0);
var current = checkpoints[k.checkPointNumber];
var respawn = respawns[current.respawn];
var respawn;
if (current == null)
respawn = respawns[(Math.random() * respawns.length) | 0]; //todo: deterministic
else
respawn = respawns[current.respawn];
k.physicalDir = (180-respawn.angle[1])*(Math.PI/180);
k.angle = k.physicalDir;
k.speed = 0;
@ -445,7 +471,8 @@ 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 (k.specialControlHandler != null) k.specialControlHandler();
else {
if (boosting) {
var top2
if (k.boostNorm>0){
@ -471,12 +498,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
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;
}
endDrift();
if (k.driftPSMode == 3) {
k.boostMT = params.miniTurbo;
}
@ -506,8 +528,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
if (onGround) {
if (!k.driftLanded) {
if (k.driftMode == 0) {
k.drifting = false;
clearWheelParticles();
endDrift();
}
else {
k.driftPSMode = 0;
@ -564,7 +585,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
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
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;
@ -607,6 +628,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
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;
@ -621,6 +643,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
}
}
}
k.physicalDir = fixDir(k.physicalDir);
@ -647,19 +670,30 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.ylock += ylvel;
}
/*
if (k.kartColTimer == COLBOUNCE_TIME) {
vec3.add(k.vel, k.vel, k.kartColVel);
}
*/
} else {
k.angle += dirDiff(k.physicalDir, k.angle)*effect.handling/2;
k.angle = fixDir(k.physicalDir);
k.angle += dirDiff(k.physicalDir, k.angle)*effect.handling;
k.angle += dirDiff(k.physicalDir, k.angle)*effect.handling; //applying this twice appears to be identical to the original
k.angle = fixDir(k.angle);
//reduce our forward speed by how much of our velocity is not going forwards
var factor = Math.sin(k.physicalDir)*Math.sin(k.angle) + Math.cos(k.physicalDir)*Math.cos(k.angle);
k.speed *= 1 - ((1-factor) * (1 - k.params.decel));
//var reducedSpeed = k.vel[0]*Math.sin(k.angle) + k.vel[2]*(-Math.cos(k.angle));
//reducedSpeed = ((reducedSpeed < 0) ? -1 : 1) * Math.sqrt(Math.abs(reducedSpeed));
k.vel[1] += k.gravity[1];
k.vel = [Math.sin(k.angle)*k.speed, k.vel[1], -Math.cos(k.angle)*k.speed]
//k.speed = reducedSpeed;
/*
if (k.kartColTimer > 0) {
vec3.add(k.vel, k.vel, vec3.scale([], k.kartColVel, k.kartColTimer/10))
}
*/
}
if (k.kartColTimer > 0) k.kartColTimer--;
@ -679,8 +713,6 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
//move kart.
var steps = 0;
var remainingT = 1;
var baseVel = k.vel;
@ -692,6 +724,9 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
}
var velSeg = vec3.clone(baseVel);
if (k.kartColTimer > 0) {
vec3.add(velSeg, velSeg, vec3.scale([], k.kartColVel, k.kartColTimer/COLBOUNCE_TIME));
}
var posSeg = vec3.clone(k.pos);
var ignoreList = [];
while (steps++ < 10 && remainingT > 0.01) {
@ -725,6 +760,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), {})
@ -734,6 +770,101 @@ 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;
var c = scene.nkm.sections["KTPC"].entries[k.cannon];
if (c.id1 != -1 && c.id2 != -1) {
nitroAudio.playSound(345, {volume: 2.5}, 0, k);
} else {
nitroAudio.playSound(347, {volume: 2.5}, 0, k);
if (k.local) {
if (c.id2 == 0) {
nitroAudio.playSound(380, {volume: 2}, 0, null); //airship fortress
} else {
nitroAudio.playSound(456, {volume: 2}, 0, null); //waluigi
}
}
}
}
function playCharacterSound(sound, volume) {
//0 - hit
//1 - hit spin
@ -822,7 +953,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
function getPosition() {
if (futureChecks.length == 0) return 0;
if (checkpoints.length == 0 || futureChecks.length == 0) return 0;
var check = checkpoints[futureChecks[0]];
var dist = vec2.sub([], [check.x1, check.z1], [k.pos[0], k.pos[2]]);
var dot = vec2.dot(dist, [check.sinus, check.cosinus]);
@ -852,7 +983,6 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
if (k != ok) {
var dist = vec3.dist(k.pos, ok.pos);
if (dist < 16) {
kartBounce(ok);
ok.kartBounce(k);
}
@ -862,13 +992,18 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
function kartBounce(ok) {
//play this kart's horn
if (k.kartColTimer == 0) { //not if we're still being bounced
nitroAudio.playSound(208, { volume: 2 }, 0, k);
nitroAudio.playSound(193 + charRes.sndOff/14, { volume: 1.5 }, 0, k);
}
k.kartColTimer = COLBOUNCE_TIME;
var weightMul = COLBOUNCE_STRENGTH*(1+(ok.weight-k.weight))*((ok.boostNorm>0 || ok.boostMT>0)?2:1)*((k.boostNorm>0 || k.boostMT>0)?0.5:1);
var weightMul = COLBOUNCE_STRENGTH*(1.5+(ok.weight-k.weight))*((ok.boostNorm>0 || ok.boostMT>0)?2:1)*((k.boostNorm>0 || k.boostMT>0)?0.5:1);
//as well as side bounce also add velocity difference if other vel > mine.
vec3.sub(k.kartColVel, k.pos, ok.pos);
k.kartColVel[1] = 0;
vec3.normalize(k.kartColVel, k.kartColVel);
vec3.scale(k.kartColVel, k.kartColVel, weightMul);
@ -891,8 +1026,9 @@ 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*k.speed/params.topSpeed);
var transpose = (mode == 0)?0:(22*turn*Math.min(1.3, k.speed/params.topSpeed));
sounds.transpose += (transpose-sounds.transpose)/15;
if (mode != soundMode) {
@ -909,7 +1045,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
//order y, x, z
var dir = k.physicalDir+k.driftOff+(Math.sin((COLBOUNCE_TIME-k.kartColTimer)/3)*(Math.PI/6)*(k.kartColTimer/COLBOUNCE_TIME));
var forward = [Math.sin(dir), 0, -Math.cos(dir)];
var side = [Math.cos(dir), 0, Math.sin(dir)];
var side = [-Math.cos(dir), 0, -Math.sin(dir)];
if (k.physBasis != null) {
vec3.transformMat4(forward, forward, k.physBasis.mat);
vec3.transformMat4(side, side, k.physBasis.mat);
@ -961,6 +1097,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.physBasis = {
mat: m4,
inv: mat4.invert([], m4),
normal: normal,
time: 15,
loop: false
};
@ -978,12 +1115,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)));
@ -1030,7 +1170,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;
}
@ -1051,12 +1191,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) {
@ -1067,7 +1213,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) {
@ -1095,7 +1242,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
stuckTo = dat.object;
} else if (colType == MKDS_COLTYPE.CANNON) {
//cannon!!
k.cannon = colBE;
triggerCannon(colBE);
} else {
adjustPos = false;
ignoreList.push(plane);

116
code/entities/kartItems.js Normal file
View File

@ -0,0 +1,116 @@
//item state for a kart. not an entity, just supplemental to one.
window.KartItems = function(kart, scene) {
var t = this;
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 hurtFlipDelay = 80; //turn right slightly, bounce twice, forward flip
var hurtSpinDelay = 40; //counter clockwise spin
t.getItem = getItem;
t.update = update;
var specialItems = ["star"];
function sfx(id) {
if (kart.local) {
return nitroAudio.playSound(id, {volume: 2}, 0, null);
}
return null;
}
function getItem(specific) {
if (!t.empty) return false;
else {
//begin carousel
t.cycleTime = 0;
t.totalTime = (specific) ? 60 : maxItemTime;
if (specific) t.specificItem = specific;
t.empty = false;
carouselSfx = sfx(62);
}
}
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
t.cycleTime++;
if (t.cycleTime >= t.totalTime) {
if (carouselSfx != null) nitroAudio.kill(carouselSfx);
//decide on an item
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 && t.heldItem == null) {
t.totalTime = Math.max(minItemTime, t.totalTime - 20);
}
}
} else if (t.heldItem == null) {
if (pressed) {
//fire?
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,11 @@ 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;
var useAlpha = true; //probably a crutch - this should be defined in the water material (though it might be in nsbma)
function draw(view, pMatrix) {
if (nitroRender.flagShadow) return;
@ -34,34 +39,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])
if (useAlpha) 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);
if (useAlpha) 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])
if (useAlpha) 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
@ -69,12 +75,17 @@ window.ObjWater = function(obji, scene) {
case 0x0001:
return {mdl:[{nsbmd:"beach_waterC.nsbmd"}, {nsbmd:"beach_waterA.nsbmd"}]};
case 0x0003:
useAlpha = false;
return {mdl:[{nsbmd:"town_waterC.nsbmd"}, {nsbmd:"town_waterA.nsbmd"}]};
case 0x0006:
useAlpha = false;
return {mdl:[{nsbmd:"yoshi_waterC.nsbmd"}]};
case 0x0009:
useAlpha = false;
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

@ -36,6 +36,13 @@ window.nsbtp = function(input) {
}
this.load = load;
var texTotal;
var palTotal;
var texNamesOff;
var palNamesOff;
var texNames;
var palNames;
function load(input) {
var view = new DataView(input);
@ -56,7 +63,6 @@ window.nsbtp = function(input) {
if (stamp != "PAT0") throw "NSBTP invalid. Expected PAT0, found "+stamp;
animData = nitro.read3dInfo(view, mainOff+8, animInfoHandler);
debugger;
mainObj.animData = animData;
}
@ -92,15 +98,37 @@ window.nsbtp = function(input) {
//8 bytes here? looks like texinfo
var duration = view.getUint16(offset, true);
var tframes = view.getUint8(offset+2);
var pframes = view.getUint8(offset+3);
var unknown = view.getUint16(offset+4, true);
var unknown2 = view.getUint16(offset+6, true);
texTotal = view.getUint8(offset+2);
palTotal = view.getUint8(offset+3);
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, tframes:tframes, pframes:pframes, 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) {
@ -122,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;
@ -133,22 +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;
}
//read 16char tex names
for (var i=0; i<frames; i++) {
}
//read 16char pal names
for (var i=0; i<frames; i++) {
}
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)

View File

@ -406,6 +406,7 @@ window.spa = function(input) {
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++);
@ -426,6 +427,7 @@ window.spa = function(input) {
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);
@ -434,6 +436,7 @@ window.spa = function(input) {
colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
colourBuffer[3] = Math.round((col>>15)*255);
col = colourBuffer;
premultiply(col);
off += 2;
} else {
@ -446,6 +449,12 @@ window.spa = function(input) {
return canvas;
}
function premultiply(col) {
col[0] *= col[3]/255;
col[1] *= col[3]/255;
col[2] *= col[3]/255;
}
function readCompressedTex(tex) { //format 5, 4x4 texels. I'll keep this well documented so it's easy to understand.
throw "compressed tex not supported for particles! (unknowns for tex data offsets and lengths?)";
var off = 0;//tex.texOffset;

View File

@ -199,6 +199,7 @@ window.NitroParticle = function(scene, emitter, pos, vel, dir, dirVel, duration,
nitroRender.last.obj = obj;
}
gl.disable(gl.CULL_FACE);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
nitroRender.setColMult([1, 1, 1, 1]);

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

@ -14,8 +14,9 @@
window.nitroRender = new function() {
var gl, frag, vert, nitroShader;
var cVec, color, texCoord, norm;
var vecMode, vecPos, vecNorm, vecTx, vecCol, vecNum, vecMat, curMat;
var vecMode, vecPos, vecNorm, vecTx, vecCol, vecNum, vecMat, curMat, stripAlt;
var texWidth, texHeight, alphaMul = 1;
var t = this;
this.cullModes = [];
@ -35,6 +36,7 @@ window.nitroRender = new function() {
this.getViewHeight = getViewHeight;
this.flagShadow = false;
this.forceFlatNormals = false; //generate flat normals for this mesh. Used for course model for better shadows.
var parameters = {
0: 0,
@ -127,6 +129,7 @@ window.nitroRender = new function() {
vecMat = [];
}
vecNum = 0;
stripAlt = 0;
}
instructions[0x41] = function(view, off) { //end vtx
@ -154,6 +157,24 @@ window.nitroRender = new function() {
var norm = gl.createBuffer();
var posArray = new Float32Array(vecPos);
if (t.forceFlatNormals && modes[vecMode] == gl.TRIANGLES) {
//calculate new flat normals for each triangle
for (var i=0; i<vecPos.length; i+=9) {
var v1 = [vecPos[i], vecPos[i+1], vecPos[i+2]];
var v2 = [vecPos[i+3], vecPos[i+4], vecPos[i+5]];
var v3 = [vecPos[i+6], vecPos[i+7], vecPos[i+8]];
vec3.sub(v2, v2, v1);
vec3.sub(v3, v3, v1);
var newNorm = vec3.cross([], v2, v3);
vec3.normalize(newNorm, newNorm);
for (var j=0; j<3; j++) {
for (var k=0; k<3; k++) {
vecNorm[i+(j*3)+k] = newNorm[k];
}
}
}
}
gl.bindBuffer(gl.ARRAY_BUFFER, pos);
gl.bufferData(gl.ARRAY_BUFFER, posArray, gl.STATIC_DRAW);
@ -192,11 +213,14 @@ window.nitroRender = new function() {
}
if (optimiseTriangles && (vecMode > 1) && (vecNum > 2)) { //convert tri strips to individual triangles so we get one buffer per polygon
vecPos = vecPos.concat(vecPos.slice(vecPos.length-6));
vecNorm = vecNorm.concat(vecNorm.slice(vecNorm.length-6));
vecTx = vecTx.concat(vecTx.slice(vecTx.length-4));
vecCol = vecCol.concat(vecCol.slice(vecCol.length-8));
vecMat = vecMat.concat(vecMat.slice(vecMat.length-2));
var b = vecMat.length - (((stripAlt % 2) == 0)?1:3);
var s2 = vecMat.length - (((stripAlt % 2) == 0)?2:1);
vecPos = vecPos.concat(vecPos.slice(b*3, b*3+3)).concat(vecPos.slice(s2*3, s2*3+3));
vecNorm = vecNorm.concat(vecNorm.slice(b*3, b*3+3)).concat(vecNorm.slice(s2*3, s2*3+3));
vecTx = vecTx.concat(vecTx.slice(b*2, b*2+2)).concat(vecTx.slice(s2*2, s2*2+2));
vecCol = vecCol.concat(vecCol.slice(b*4, b*4+4)).concat(vecCol.slice(s2*4, s2*4+4));
vecMat = vecMat.concat(vecMat.slice(b, b+1)).concat(vecMat.slice(s2, s2+1));
stripAlt++;
}
vecNum++;
@ -206,7 +230,6 @@ window.nitroRender = new function() {
vecCol = vecCol.concat(color);
vecNorm = vecNorm.concat(norm);
vecMat.push(curMat);
}
function tenBitSign(val) {
@ -223,6 +246,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,22 +258,25 @@ 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);
}
this.setShadowMode = function(sTex, fsTex, sMat, fsMat) {
this.setShadowMode = function(sTex, fsTex, sMat, fsMat, dir) {
this.nitroShader = shaders[1];
var shader = shaders[1];
gl.useProgram(shader);
vec3.normalize(dir, dir);
gl.uniform3fv(shader.lightDirUniform, dir);
gl.uniformMatrix4fv(shader.shadowMatUniform, false, sMat);
gl.uniformMatrix4fv(shader.farShadowMatUniform, false, fsMat);
gl.uniform1f(shader.lightIntensityUniform, 0.3);
this.resetShadOff();
this.setNormalFlip(1);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, sTex);
gl.uniform1i(shader.lightSamplerUniform, 1);
@ -261,10 +289,33 @@ window.nitroRender = new function() {
this.prepareShader();
}
this.setLightIntensities = function(intensity, shadIntensity) {
if (intensity == null) intensity = 0.3;
if (shadIntensity == null) shadIntensity = 1;
var shader = this.nitroShader;
gl.useProgram(this.nitroShader);
gl.uniform1f(shader.lightIntensityUniform, intensity);
gl.uniform1f(shader.shadLightenUniform, 1-shadIntensity);
}
this.setShadBias = function(bias) {
var shader = this.nitroShader;
gl.useProgram(this.nitroShader);
gl.uniform1f(shader.shadOffUniform, bias);
gl.uniform1f(shader.farShadOffUniform, bias);
}
this.setNormalFlip = function(flip) {
var shader = this.nitroShader;
gl.useProgram(this.nitroShader);
gl.uniform1f(shader.normalFlipUniform, flip);
}
this.resetShadOff = function() {
var shader = shaders[1];
gl.uniform1f(shader.shadOffUniform, 0.00005+((mobile)?0.0005:0));
gl.uniform1f(shader.farShadOffUniform, 0.0005);
var shader = this.nitroShader;
gl.useProgram(this.nitroShader);
gl.uniform1f(shader.shadOffUniform, 0.0005+((mobile)?0.0005:0));
gl.uniform1f(shader.farShadOffUniform, 0.0020);
}
this.unsetShadowMode = function() {
@ -275,6 +326,26 @@ window.nitroRender = new function() {
this.prepareShader();
}
var paused = false;
this.pauseShadowMode = function() {
this.nitroShader = shaders[0];
if (this.nitroShader == shaders[1]) paused = true;
gl.useProgram(this.nitroShader);
this.setColMult([1, 1, 1, 1]);
this.prepareShader();
}
this.unpauseShadowMode = function() {
if (!paused) return;
this.nitroShader = shaders[1];
gl.useProgram(this.nitroShader);
this.setColMult([1, 1, 1, 1]);
this.prepareShader();
}
this.setColMult = function(color) {
gl.useProgram(this.nitroShader);
gl.uniform4fv(this.nitroShader.colMultUniform, color);
@ -324,6 +395,7 @@ window.nitroRender = new function() {
vecMode = 0;
vecNum = 0;
stripAlt = 0;
vecPos = [];
vecNorm = [];
vecTx = [];
@ -352,7 +424,7 @@ window.nitroRender = new function() {
};
function nitroModel(bmd, btx, remap) {
function nitroModel(bmd, btx) {
var bmd = bmd;
this.bmd = bmd;
var thisObj = this;
@ -360,6 +432,7 @@ function nitroModel(bmd, btx, remap) {
var texCanvas;
var tex;
var texAnim;
var texPAnim;
var texFrame;
var modelBuffers;
var collisionModel = [];
@ -378,7 +451,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;
@ -388,10 +463,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) {
@ -402,7 +473,7 @@ function nitroModel(bmd, btx, remap) {
}
function loadWhiteTex(btx) { //examines the materials in the loaded model and generates textures for each.
function loadWhiteTex(btx) { //examines the materials in the loaded model and generates textures for each.
var gl = nitroRender.gl; //get gl object from nitro render singleton
loadedTex = btx;
texCanvas = [];
@ -440,15 +511,22 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
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)) {
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.")
console.warn("WARNING: material "+i+" in model could not be assigned a texture.");
/*
var fC = document.createElement("canvas");
fC.width = 2;
@ -457,16 +535,24 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
ctx.fillStyle = "white";
ctx.fillRect(0,0,2,2);
texCanvas.push(fC);
var t = loadTex(fC, gl, !m.repeatX, !m.repeatY);
var t = loadTex(fC, gl, !mat.repeatX, !mat.repeatY);
t.realWidth = 2;
t.realHeight = 2;
tex.push(t);
*/
continue;
return;
}
var truetex = (nTex==null)?texI:nTex;
var truepal = (nPal==null)?palI:nPal;
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];
@ -497,37 +583,39 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
var t = loadTex(fC, gl, !m.repeatX, !m.repeatY);
t.realWidth = canvas.width;
t.realHeight = canvas.height;
tex.push(t);
btx.cache[cacheID] = t;
return t;
} else {
texCanvas.push(canvas);
var t = loadTex(canvas, gl, !m.repeatX, !m.repeatY);
t.realWidth = canvas.width;
t.realHeight = canvas.height;
tex.push(t);
btx.cache[cacheID] = t;
return t;
}
} else {
tex.push(cached);
return cached;
}
}
}
}
function setTextureRemap(remap) {
texMap = remap;
if (loadedTex != null) loadTexture(loadedTex)
}
this.loadTexAnim = function(bta) {
texAnim = bta;
texFrame = 0;
}
this.loadTexPAnim = function(btp) {
texPAnim = btp;
}
this.setFrame = function(frame) {
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);
@ -552,7 +640,7 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
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);
@ -571,7 +659,85 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
}
}
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];
@ -585,14 +751,16 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
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);
}
@ -615,7 +783,7 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
}
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);
@ -635,26 +803,47 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
var gl = nitroRender.gl;
//texture 0 SHOULD be bound, assuming the nitrorender program has been prepared
if (nitroRender.last.tex != tex[poly.mat]) {
gl.bindTexture(gl.TEXTURE_2D, tex[poly.mat]); //load up material texture
nitroRender.last.tex = tex[poly.mat];
var pmat = poly.mat;
var matname = model.materials.names[pmat]; //attach tex anim to mat with same name
if (texPAnim != null) {
var info = texPAnim.animData.objectData[modelind];
var anims = texPAnim.animData.objectData[modelind].data;
var animNum = anims.names.indexOf(matname);
if (animNum != -1) {
var offFrame = texFrame % info.duration;
//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=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;
}
}
}
}
var material = model.materials.objectData[poly.mat];
nitroRender.setAlpha(material.alpha)
if (nitroRender.last.tex != tex[pmat]) {
gl.bindTexture(gl.TEXTURE_2D, tex[pmat]); //load up material texture
nitroRender.last.tex = tex[pmat];
}
var material = model.materials.objectData[pmat];
nitroRender.setAlpha(material.alpha);
if (texAnim != null) {
//generate and send texture matrix from data
var matname = model.materials.names[poly.mat]; //attach tex anim to mat with same name
var matname = model.materials.names[pmat]; //attach tex anim to mat with same name
var anims = texAnim.animData.objectData[modelind].data;
var animNum = anims.names.indexOf(matname);
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);
@ -663,27 +852,73 @@ function loadWhiteTex(btx) { //examines the materials in the loaded model and ge
} else gl.uniformMatrix3fv(shader.texMatrixUniform, false, material.texMat);
if (modelBuffers[modelind][polyind] == null) modelBuffers[modelind][polyind] = nitroRender.renderDispList(poly.disp, tex[poly.mat], (poly.stackID == null)?model.lastStackID:poly.stackID);
if (material.cullMode < 3) {
gl.enable(gl.CULL_FACE);
gl.cullFace(nitroRender.cullModes[material.cullMode]);
} else {
if (nitroRender.forceFlatNormals) {
//dual side lighting model, course render mode essentially
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
drawModelBuffer(modelBuffers[modelind][polyind], gl, shader);
nitroRender.setNormalFlip(-1);
gl.cullFace(gl.FRONT);
drawModelBuffer(modelBuffers[modelind][polyind], gl, shader);
nitroRender.setNormalFlip(1);
return;
}
gl.disable(gl.CULL_FACE);
}
drawModelBuffer(modelBuffers[modelind][polyind], gl, shader);
}
function generateMatrixStack(model, targ) { //this generates a matrix stack with the default bones. use nitroAnimator to pass custom matrix stacks using nsbca animations.
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);
}
@ -691,10 +926,14 @@ function generateMatrixStack(model, targ) { //this generates a matrix stack with
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;
}
@ -733,10 +972,11 @@ function loadTex(img, gl, clampx, clampy) { //general purpose function for loadi
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
if (clampx) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
if (clampy) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.generateMipmap(gl.TEXTURE_2D);
texture.width = img.width;
texture.height = img.height;

View File

@ -84,13 +84,16 @@ window.nitroShaders = new (function() {
varying vec4 color;\n\
varying vec4 lightDist;\n\
varying vec4 fLightDist;\n\
varying vec3 normal;\n\
\n\
uniform float shadOff; \n\
uniform float farShadOff; \n\
uniform sampler2D lightDSampler;\n\
uniform sampler2D farLightDSampler;\n\
uniform float shadLighten; \n\
\n\
uniform sampler2D uSampler;\n\
uniform vec3 lightDir;\n\
\n\
float shadowCompare(sampler2D map, vec2 pos, float compare, float so) {\n\
float depth = texture2D(map, pos).r;\n\
@ -113,21 +116,25 @@ 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\
float shadIntensity;\n\
\n\
if (dist > 0.5) {\n\
gl_FragColor = col*mix(vec4(0.5, 0.5, 0.7, 1.0), vec4(1.0, 1.0, 1.0, 1.0), shadowLerp(farLightDSampler, vec2(4096.0, 4096.0), fLightDist.xy, fLightDist.z-farShadOff, farShadOff*2.0));\n\
shadIntensity = shadowLerp(farLightDSampler, vec2(4096.0, 4096.0), fLightDist.xy, fLightDist.z-farShadOff, farShadOff*0.5);\n\
} else if (dist > 0.4) {\n\
float lerp1 = shadowLerp(farLightDSampler, vec2(4096.0, 4096.0), fLightDist.xy, fLightDist.z-farShadOff, farShadOff*2.0);\n\
float lerp2 = shadowLerp(lightDSampler, vec2(2048.0, 2048.0), lightDist.xy, lightDist.z-shadOff, shadOff*4.0);\n\
float lerp1 = shadowLerp(farLightDSampler, vec2(4096.0, 4096.0), fLightDist.xy, fLightDist.z-farShadOff, farShadOff*0.5);\n\
float lerp2 = shadowLerp(lightDSampler, vec2(2048.0, 2048.0), lightDist.xy, lightDist.z-shadOff, shadOff*0.5);\n\
\n\
gl_FragColor = col*mix(vec4(0.5, 0.5, 0.7, 1.0), vec4(1.0, 1.0, 1.0, 1.0), mix(lerp2, lerp1, (dist-0.4)*10.0));\n\
shadIntensity = mix(lerp2, lerp1, (dist-0.4)*10.0);\n\
} else {\n\
gl_FragColor = col*mix(vec4(0.5, 0.5, 0.7, 1.0), vec4(1.0, 1.0, 1.0, 1.0), shadowLerp(lightDSampler, vec2(2048.0, 2048.0), lightDist.xy, lightDist.z-shadOff, shadOff*4.0));\n\
shadIntensity = shadowLerp(lightDSampler, vec2(2048.0, 2048.0), lightDist.xy, lightDist.z-shadOff, shadOff*0.5);\n\
}\n\
shadIntensity = min(shadIntensity, max(0.0, dot(normalize(normal), lightDir) * 5.0));\n\
gl_FragColor = col*mix(vec4(0.5, 0.5, 0.7, 1.0), vec4(1.0, 1.0, 1.0, 1.0), min(1.0, shadIntensity + shadLighten));\n\
\n\
if (gl_FragColor.a == 0.0) discard;\n\
}\n\
@ -143,6 +150,7 @@ window.nitroShaders = new (function() {
uniform mat4 uPMatrix;\n\
uniform mat3 texMatrix;\n\
uniform mat4 matStack[16];\n\
uniform float normalFlip;\n\
\n\
uniform vec4 colMult;\n\
\n\
@ -154,6 +162,7 @@ window.nitroShaders = new (function() {
varying vec4 color;\n\
varying vec4 lightDist;\n\
varying vec4 fLightDist;\n\
varying vec3 normal;\n\
\n\
\n\
void main(void) {\n\
@ -163,7 +172,8 @@ window.nitroShaders = new (function() {
\n\
lightDist = (shadowMat*pos + vec4(1, 1, 1, 0)) / 2.0;\n\
fLightDist = (farShadowMat*pos + vec4(1, 1, 1, 0)) / 2.0;\n\
vec3 adjNorm = normalize(vec3(uMVMatrix * matStack[int(matrixID)] * vec4(aNormal, 0.0)));\n\
vec3 adjNorm = normalize(vec3(uMVMatrix * matStack[int(matrixID)] * vec4(aNormal, 0.0))) * normalFlip;\n\
normal = adjNorm; \n\
float diffuse = (1.0-lightIntensity)-dot(adjNorm, vec3(0.0, -1.0, 0.0))*lightIntensity;\n\
\n\
color = aColor*colMult;\n\
@ -302,12 +312,15 @@ window.nitroShaders = new (function() {
["shadowMatUniform", "shadowMat"],
["farShadowMatUniform", "farShadowMat"],
["lightIntensityUniform", "lightIntensity"],
["shadLightenUniform", "shadLighten"],
["lightDirUniform", "lightDir"],
["normalFlipUniform", "normalFlip"],
["shadOffUniform", "shadOff"],
["farShadOffUniform", "farShadOff"],
["lightSamplerUniform", "lightDSampler"],
["farLightSamplerUniform", "farLightDSampler"]
["farLightSamplerUniform", "farLightDSampler"],
]
config[0] = baseConf;

View File

@ -32,15 +32,30 @@ 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, -(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, -(96)/1024, 96/1024
//-128/1024, 128/1024, -(1024)/1024, 1024/1024
],
"lose": [
-128/1024, 128/1024, -(96)/1024, 96/1024
//-128/1024, 128/1024, -(1024)/1024, 1024/1024
],
}
var param = params[type];
if (param == null) param = params["count"]
if (param == null) param = params["count"];
mat4.ortho(proj, param[0], param[1], param[2], param[3], -0.001, 10);
buildOrtho(nitroRender.getViewWidth(), nitroRender.getViewHeight());
@ -54,10 +69,13 @@ window.Race3DUI = function(scene, type, animStart) {
bmd = new nsbmd(bmd);
var bca = new nsbca(scene.gameRes.Race.getFile(type+".nsbca"));
var btp = scene.gameRes.Race.getFile(type+".nsbtp");
if (btp != null) btp = new nsbtp(btp);
anim = new nitroAnimator(bmd, bca);
length = anim.getLength(0);
if (type == "count") length *= 3;
model = new nitroModel(bmd);
model.loadTexPAnim(btp)
}
function buildOrtho(width, height) {
@ -72,11 +90,14 @@ window.Race3DUI = function(scene, type, animStart) {
var width = nitroRender.getViewWidth();
if (width != lastWidth) buildOrtho(width, nitroRender.getViewHeight());
mat4.translate(mat, view, t.pos);
nitroRender.pauseShadowMode();
model.draw(mat, proj, animMat);
nitroRender.unpauseShadowMode();
}
function update() {
if (anim != null) {
model.setFrame(animFrame);
animMat = anim.setFrame(0, 0, Math.max(0, animFrame++));
}
if (animFrame > length) {

File diff suppressed because one or more lines are too long

29
manifest.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "mkjs",
"display": "fullscreen",
"orientation": "landscape",
"background_color": "black",
"icons": [
{
"src": "/resource/icon-512-512.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "/resource/icon-192-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/resource/icon-180-180.png",
"type": "image/png",
"sizes": "180x180"
},
{
"src": "/resource/icon-120-120.png",
"type": "image/png",
"sizes": "120x120"
}
]
}

BIN
resource/icon-120x120.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
resource/icon-180x180.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
resource/icon-192x192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
resource/icon-512x512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB