diff --git a/audioSFX.html b/audioSFX.html
index 446fb6a..ca92175 100644
--- a/audioSFX.html
+++ b/audioSFX.html
@@ -26,7 +26,7 @@
play.addEventListener('click', function() {
if (last != null) nitroAudio.instaKill(last);
document.getElementById('seq').innerText = "Current SSEQ: "+i;
- last = nitroAudio.playSound(i++, {}, 2);
+ last = nitroAudio.playSound(i++, {}, 0);
})
/*
var ctx = new AudioContext();
diff --git a/code/audio/nitroAudio.js b/code/audio/nitroAudio.js
index 44fe294..e285e92 100644
--- a/code/audio/nitroAudio.js
+++ b/code/audio/nitroAudio.js
@@ -1,113 +1,129 @@
-//
-// nitroAudio.js
-//--------------------
-// Provides an interface for playing nds music and sound effects.
-// by RHY3756547
-//
-
-window.AudioContext = window.AudioContext || window.webkitAudioContext;
-
-window.nitroAudio = new (function() {
- var t = this;
- var ctx;
-
- t.sounds = [];
-
- t.tick = tick;
- t.playSound = playSound;
- t.kill = kill;
- t.init = init;
- t.instaKill = instaKill;
-
- t.sdat = null;
-
- function init(sdat) {
- ctx = new AudioContext();
- t.ctx = ctx;
-
- var listener = ctx.listener;
- listener.dopplerFactor = 1;
- listener.speedOfSound = 100/1024; //343.3
-
- SSEQWaveCache.init(sdat, ctx);
- t.sdat = sdat;
- }
-
- function tick() {
- for (var i=0; i 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> 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;
+ }
+}
\ No newline at end of file
diff --git a/code/engine/collisionTypes.js b/code/engine/collisionTypes.js
index e09a485..3b902db 100644
--- a/code/engine/collisionTypes.js
+++ b/code/engine/collisionTypes.js
@@ -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},
{},
diff --git a/code/engine/controls/controlRaceCPU.js b/code/engine/controls/controlRaceCPU.js
index adc1156..50352d6 100644
--- a/code/engine/controls/controlRaceCPU.js
+++ b/code/engine/controls/controlRaceCPU.js
@@ -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
diff --git a/code/engine/ingameRes.js b/code/engine/ingameRes.js
index 205a937..1df016b 100644
--- a/code/engine/ingameRes.js
+++ b/code/engine/ingameRes.js
@@ -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 x.toLowerCase().endsWith(".nclr"));
+ var graphics = files.filter(x => x.toLowerCase().endsWith(".ncgr"));
+ for (var i=0; i !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 bestScore) {
+ bestScore = score;
+ bestString = list[i];
+ }
+ }
+ return bestString;
+ }
+
+ function countStr(text, char) {
+ var count = 0;
+ for (var i=0; i0 && newT>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;
}
diff --git a/code/entities/item.js b/code/entities/item.js
index 36c7508..56c365d 100644
--- a/code/entities/item.js
+++ b/code/entities/item.js
@@ -32,34 +32,44 @@ var itemTypes = {
'$koura_w': BlueShellC
}
-window.Item = function(scene, owner, type) {
+window.Item = function(scene, owner, type, id) {
var t = this;
var minimumMove = 0.01;
- this.id = 0;
+ this.id = id;
this.pos = vec3.transformMat4([], [0, (-owner.params.colRadius)+1, 16], owner.mat);
this.vel = vec3.create();
this.gravity = [0, -0.17, 0]; //100% confirmed by me messing around with the gravity value in mkds
this.minBounceVel = 0.5;
- this.airResist = 0.95;
+ this.airResist = 0.99;
this.enablePhysics = true;
this.floorBounce = 0.5;
this.held = true;
this.type = type;
this.owner = owner;
+ this.holdTime = 20;
+ this.dead = false;
this.angle = owner.angle;
this.speed = 10;
this.yvel = 0;
+ this.xyScale = [1, 1];
this.colRadius = 4;
- this.holdDist = 16;
+ this.holdDist = 2;
this.safeKart = owner;
+ var safeTimeMax = 4;
+ this.safeTime = safeTimeMax; //time the object needs to not be colliding with the source to start considering collisions with it
+ this.stuckTo = null;
- var deadTimerLength = 20;
- var throwVelocity = 16;
- var throwAngle = (Math.PI / 3) * 2;
+ this.groundTime = 0;
+
+
+ var deadTimerLength = 30;
+ var throwVelocity = 7; //xz velocity for throw. angle adds a y component
+ var throwAngle = (Math.PI / 10) * 2;
+ var working = vec3.create();
this.deadTimer = 0; //animates death. goes to 20, then deletes for real. dead objects can't run update or otherwise
//a controller makes this item what it is...
@@ -69,6 +79,8 @@ window.Item = function(scene, owner, type) {
// update?: (scene: CourseScene) => void
// draw?: (mvMatrix, pMatrix) => void // OVERRIDES NORMAL DRAW FUNCTION!
// release?: (direction: number) => boolean //direction is 1 for forward, -1 for back. returns if the item has more uses
+ // onRest?: (normal: vec3) => void //when the object comes to a rest (first time, or after leaving the ground for a while)
+ // onDie?: (final: boolean) => void //when the object dies
// collide?: (item: Item | Kart)
// collideKart?: (item: Kart)
var subtypeInd = type.indexOf('-');
@@ -91,10 +103,16 @@ window.Item = function(scene, owner, type) {
function updateHold(kart) {
//move the object behind the kart (physical direction without drift off)
//assuming this will only be called for something that can be held
- var dir = -kart.driftOff;
+ var dir = kart.driftOff / 4;
//offset the kart's drift offset (on direction)
- var pos = [Math.sin(dir)*t.holdDist, 0, -Math.cos(dir)*t.holdDist];
+ var pos;
+ if (t.holdPos != null) {
+ pos = vec3.clone(t.holdPos);
+ } else {
+ var dist = t.colRadius + kart.params.colRadius + t.holdDist;
+ var pos = [Math.sin(dir)*dist, -kart.params.colRadius, -Math.cos(dir)*dist];
+ }
//make relative to the kart's position
vec3.transformMat4(pos, pos, kart.mat);
@@ -105,18 +123,37 @@ window.Item = function(scene, owner, type) {
function release(forward) {
//release the item, either forward or back
- if (t.canBeHeld()) t.updateHold(owner);
+ t.holdTime = 0;
+ if (t.canBeHeld()) {
+ t.updateHold(owner);
+ updateCollision(scene);
+ }
+ t.enablePhysics = true;
if (t.controller.release) t.controller.release(forward);
else {
//default drop and throw. just here for template purposes
- if (forward >= 0) {
- var dir = owner.physicalDir;
- vec3.zero(t.vel);
+ if (forward > 0) {
+ nitroAudio.playSound(218, {volume: 2}, 0, owner);
+ var dir = owner.driftOff / 4;
+
+ //offset the kart's drift offset (on direction). add y component
+ var vel = [-Math.sin(dir)*throwVelocity, Math.tan(throwAngle) * throwVelocity, Math.cos(dir)*throwVelocity];
+ var z = [0, 0, 0];
+
+ //make relative to the kart's orientation
+ vec3.transformMat4(vel, vel, owner.mat);
+ vec3.transformMat4(z, z, owner.mat);
+ vec3.sub(vel, vel, z);
+ var v2 = vec3.scale([], owner.vel, 2);
+ vec3.add(vel, vel, v2);
+
+ t.vel = vel;
} else {
- vec3.zero(t.vel);
+ t.vel = vec3.create();
+ t.safeKart = null;
}
}
- this.held = false;
+ t.held = false;
}
function canBeHeld() {
@@ -124,7 +161,8 @@ window.Item = function(scene, owner, type) {
}
function canBeDropped() {
- return t.controller.canBeDropped || true;
+ if (t.controller.canBeDropped == null) return true;
+ return t.controller.canBeDropped;
}
function isDestructive() {
@@ -132,13 +170,22 @@ window.Item = function(scene, owner, type) {
}
function isSolid() {
- return t.controller.isSolid || true;
+ if (t.controller.isSolid == null) return true;
+ return t.controller.isSolid;
}
function finalize() {
//kill instantly
+ if (t.controller.onDie) t.controller.onDie(true);
t.deadTimer = deadTimerLength;
scene.items.removeItem(t);
+ t.dead = true;
+ }
+
+ function intensityMax(targ, vec) {
+ if (Math.abs(vec[0]) > Math.abs(targ[0])*0.5) targ[0] = vec[0];
+ if (Math.abs(vec[1]) > Math.abs(targ[1])*0.5) targ[1] = vec[1];
+ if (Math.abs(vec[2]) > Math.abs(targ[2])*0.5) targ[2] = vec[2];
}
function collide(item) {
@@ -152,15 +199,19 @@ window.Item = function(scene, owner, type) {
if (item.isDestructive() || t.isDestructive()) {
//mutual destruction. other side will deal with how they handle the collision
t.deadTimer++;
+ item.deadTimer++;
} else if (item.isSolid() && t.isSolid()) {
//bounce off other items that are not destructive
//set our velocity to move away (not too intensely)
//(only apply if our id is before, to avoid double adding the velocity)
if (t.id < item.id) {
- var diff = vec3.sub([], t.pos, item.pos);
- vec3.scale(diff, diff, 0.5);
- vec3.add(t.vel, t.vel, diff);
- vec3.sub(item.vel, item.vel, diff);
+ var diff = vec3.sub(working, t.pos, item.pos);
+ vec3.scale(diff, diff, 0.33);
+ intensityMax(t.vel, diff);
+ vec3.scale(diff, diff, -1);
+ intensityMax(item.vel, diff);
+ //vec3.add(t.vel, t.vel, diff);
+ //vec3.sub(item.vel, item.vel, diff);
t.enablePhysics = true;
item.enablePhysics = true;
}
@@ -175,77 +226,131 @@ window.Item = function(scene, owner, type) {
function update(scene) {
if (t.controller.update) t.controller.update(scene);
+ if (t.holdTime > 0 && t.holdTime-- > 7) {
+ if (t.holdTime == 7) {
+ nitroAudio.playSound(231, {volume: 2}, 0, owner);
+ }
+ return;
+ }
+ if (t.pos[2] < -10000) finalize(); //out of bounds failsafe
if (t.deadTimer > 0) {
+ if (t.deadTimer == 1 && t.controller.onDie) t.controller.onDie(false);
t.deadTimer++;
- if (t.deadTimer >= 20) t.finalize();
+ t.sprMat = mat4.create();
+ mat4.translate(t.sprMat, t.sprMat, [t.deadTimer/50, Math.sin((t.deadTimer/30) * Math.PI) * 0.5, 0]);
+ mat4.rotateZ(t.sprMat, t.sprMat, (t.deadTimer/-15) * Math.PI);
+ if (t.deadTimer >= 30) t.finalize();
return;
}
+ if (t.held) {
+ t.updateHold(owner);
+ }
+
+ var hitSafe = false;
//search for player collisions, collisions with other items
for (var i=0; i 0.01) {
- var result = lsc.raycast(posSeg, velSeg, scene.kcl, 0.05, ignoreList);
- if (result != null) {
- if (t.controller.colResponse) t.controller.colResponse(posSeg, velSeg, result, ignoreList)
- else colResponse(posSeg, velSeg, result, ignoreList)
- remainingT -= result.t;
- if (remainingT > 0.01) {
- velSeg = vec3.scale(vec3.create(), t.vel, remainingT);
- }
- } else {
- vec3.add(posSeg, posSeg, velSeg);
- remainingT = 0;
+ if (t.holdTime == 0) { //avoid mutual item destruction on the first frame
+ for (var i=0; i 0) t.groundTime++;
+
+ if (t.stuckTo != null) {
+ if (t.stuckTo.moveWith != null) t.stuckTo.moveWith(t);
+ t.enablePhysics = true;
+ t.stuckTo = null;
+ }
+
+ if (t.enablePhysics) {
+ updateCollision(scene);
}
}
+ function updateCollision(scene) {
+ if (!t.held) {
+ vec3.add(t.vel, t.vel, t.gravity);
+ vec3.scale(t.vel, t.vel, t.airResist);
+ }
+
+ //by default, items use raycast collision against the world (rather than ellipse)
+ //this speeds things up considerably
+
+ var steps = 0;
+ var remainingT = 1;
+ var velSeg = vec3.clone(t.vel);
+ var posSeg = vec3.clone(t.pos);
+ var ignoreList = [];
+ while (steps++ < 10 && remainingT > 0.01) {
+ var result = lsc.raycast(posSeg, velSeg, scene, 0.05, ignoreList);
+ if (result != null) {
+ if (t.controller.colResponse && !t.held) t.controller.colResponse(posSeg, velSeg, result, ignoreList)
+ else colResponse(posSeg, velSeg, result, ignoreList)
+ remainingT -= result.t;
+ if (remainingT > 0.01) {
+ velSeg = vec3.scale(velSeg, t.vel, remainingT);
+ }
+ } else {
+ vec3.add(posSeg, posSeg, velSeg);
+ remainingT = 0;
+ }
+ }
+ t.pos = posSeg;
+ }
+
function draw(mvMatrix, pMatrix) {
+ if (t.holdTime > 7) return;
if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1-(t.deadTimer/deadTimerLength)]); //fade out
if (t.controller.draw) {
t.controller.draw(mvMatrix, pMatrix);
} else {
- var mat = mat4.translate(mat4.create(), mvMatrix, vec3.add(vec3.create(), t.pos, [0, 3, 0]));
+ var mat = mat4.translate(mat4.create(), mvMatrix, vec3.add(vec3.create(), t.pos, [0, t.colRadius * t.xyScale[1], 0]));
spritify(mat);
- mat4.scale(mat, mat, [16, 16, 16]);
+ var scale = 6*t.colRadius * (1 - t.holdTime/7);
+ mat4.scale(mat, mat, [scale, scale, scale]);
- scene.gameRes.items[type].draw(mat, pMatrix);
+ var mdl = scene.gameRes.items[type];
+ //apply our custom mat (in sprite space), if it exists
+ //used for destruction animation, scaling
+ if (t.sprMat) {
+ var oldMat = mdl.baseMat;
+ mdl.setBaseMat(t.sprMat);
+ mdl.draw(mat, pMatrix);
+ mdl.setBaseMat(oldMat);
+ } else {
+ mdl.draw(mat, pMatrix);
+ }
}
if (t.deadTimer > 0) nitroRender.setColMult([1, 1, 1, 1]);
}
@@ -272,13 +377,28 @@ window.Item = function(scene, owner, type) {
//normally, item collision with a wall cause a perfect reflection of the velocity.
var proj = vec3.dot(t.vel, n) * 2;
vec3.sub(t.vel, t.vel, vec3.scale(vec3.create(), n, proj));
-
+ t.safeKart = null;
+ } else if (colType == MKDS_COLTYPE.OOB || colType == MKDS_COLTYPE.FALL) {
+ if (t.deadTimer == 0) t.deadTimer++;
} else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
//sliding plane
- var proj = vec3.dot(t.vel, n) * (1 + t.floorBounce);
+ var bounce = t.held ? 0 : t.floorBounce;
+ var proj = vec3.dot(t.vel, n) * (1 + bounce);
vec3.sub(t.vel, t.vel, vec3.scale(vec3.create(), n, proj));
- if (t.floorBounce == 0 || Math.abs(proj) < t.minBounceVel) t.enablePhysics = false;
+ if (!t.held && (t.floorBounce == 0 || Math.abs(proj) < t.minBounceVel)) {
+ t.vel[0] = 0;
+ t.vel[1] = 0;
+ t.vel[2] = 0;
+ t.enablePhysics = false;
+ if (t.groundTime == 0) {
+ t.groundTime = 1;
+ if (t.controller.onRest) {
+ t.controller.onRest(n);
+ }
+ }
+ }
+ t.stuckTo = dat.object;
} else {
adjustPos = false;
ignoreList.push(plane);
diff --git a/code/entities/itembox.js b/code/entities/itembox.js
index 2a8f0f4..62b605a 100644
--- a/code/entities/itembox.js
+++ b/code/entities/itembox.js
@@ -98,12 +98,15 @@ window.ItemBox = function(obji, scene) {
}
function sndUpdate(view) {
+ /*
t.soundProps.pos = vec3.transformMat4([], t.pos, view);
if (t.soundProps.lastPos != null) t.soundProps.vel = vec3.sub([], t.soundProps.pos, t.soundProps.lastPos);
else t.soundProps.vel = [0, 0, 0];
+ */
t.soundProps.lastPos = t.soundProps.pos;
+ t.soundProps.pos = t.pos; //todo: reintroduce doppler via emulation
- t.soundProps.refDistance = 192/1024;
+ t.soundProps.refDistance = 192;
t.soundProps.rolloffFactor = 1;
}
diff --git a/code/entities/items/bananas.js b/code/entities/items/bananas.js
index 3fccf8d..77f62fc 100644
--- a/code/entities/items/bananas.js
+++ b/code/entities/items/bananas.js
@@ -1,15 +1,42 @@
window.BananaC = function(item, scene, type) {
+ var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = false;
- item.minBounceVel = 0;
+ item.floorBounce = 0;
this.collideKart = collideKart;
+ this.onRest = onRest;
+ this.update = update;
function collideKart(kart) {
- item.deadTimerLength = 20;
+ item.deadTimer = 1;
kart.damage(MKDSCONST.DAMAGE_SPIN);
}
+
+ function onRest(normal) {
+ nitroAudio.playSound(219, {volume: 2}, 0, item);
+ }
+
+ function update(argument) {
+ if (!item.held && item.colRadius < 6) {
+ item.colRadius += 0.2;
+ if (item.colRadius > 6) item.colRadius = 6;
+ }
+ if (item.groundTime < 30) {
+ var t = (1-item.groundTime/29);
+ var s = Math.sin(item.groundTime * Math.PI/14);
+
+ var sprMat = mat4.create();
+ mat4.translate(sprMat, sprMat, [0, -1/6, 0]);
+ mat4.scale(sprMat, sprMat, [1 + s * 0.6 * t, 1 - s * 0.6 * t, 1]);
+ mat4.translate(sprMat, sprMat, [0, 1/6, 0]);
+
+ item.sprMat = sprMat;
+ } else {
+ item.sprMat = null;
+ }
+ }
}
window.BananaGroupC = function(item, scene, type) {
@@ -28,27 +55,89 @@ window.BananaGroupC = function(item, scene, type) {
}
window.FakeBoxC = function(item, scene, type) {
+ var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = false;
- this.isSolid = true;
- var model = scene.gameRes.fakeBox;
+ this.isSolid = false;
+ item.floorBounce = 0;
+ item.airResist = 0.98;
+ this.collideKart = collideKart;
+ this.onRest = onRest;
+ this.update = update;
this.draw = draw;
- function draw(view, pMatrix) {
- mat4.translate(mat, view, t.pos);
- mat4.translate(mat, view, [0, 16, 0]);
+ this.xyScale = [1,1];
+ this.dir = 0;
+
+ function collideKart(kart) {
+ item.deadTimer = 1;
+ nitroAudio.playSound(250, {volume: 2}, 0, item);
+ kart.damage(MKDSCONST.DAMAGE_FLIP);
+ }
+
+ function onRest(normal) {
+ nitroAudio.playSound(251, {volume: 2}, 0, item);
+ }
+
+ function update(argument) {
+ if (item.held) {
+ t.dir = -(item.owner.physicalDir + item.owner.driftOff / 4);
+ }
+ if (!item.held && item.colRadius < 8) {
+ item.colRadius += 0.2;
+ if (item.colRadius > 8) item.colRadius = 8;
+ }
+ if (item.groundTime < 20) {
+ var linear = (1-item.groundTime/19);
+ var s = Math.sin(item.groundTime * Math.PI/8);
+
+ t.xyScale = [1 + s * 0.25 * linear, 1 - s * 0.25 * linear];
+ } else {
+ t.xyScale = [1,1];
+ }
+ }
+
+ function draw(mvMatrix, pMatrix) {
+ var mat = mat4.translate(mat4.create(), mvMatrix, vec3.add(vec3.create(), item.pos, [0, item.colRadius*1.5 * t.xyScale[1], 0]));
+
+ var scale = 2*item.colRadius * (1 - item.holdTime/7);
+ mat4.scale(mat, mat, [scale*t.xyScale[0], scale*t.xyScale[1], scale*t.xyScale[0]]);
+ mat4.rotateY(mat, mat, t.dir);
+ mat4.rotateZ(mat, mat, Math.PI/-6);
+ mat4.rotateY(mat, mat, Math.PI/6);
+ mat4.rotateX(mat, mat, Math.PI/-6);
- /* adjust to make it rest on a corner
- if (t.angle[2] != 0) mat4.rotateZ(mat, mat, t.angle[2]*(Math.PI/180));
- if (t.angle[1] != 0) mat4.rotateY(mat, mat, t.angle[1]*(Math.PI/180));
- if (t.angle[0] != 0) mat4.rotateX(mat, mat, t.angle[0]*(Math.PI/180));
- */
-
- mat4.scale(mat, mat, vec3.scale([], t.scale, 16));
- model.draw(mat, pMatrix, animMat);
+ var mdl = scene.gameRes.items.fakeBox;
+ mdl.draw(mat, pMatrix);
}
}
-window.BombC = null;
+window.BombC = function(item, scene, type) {
+ var t = this;
+ this.canBeHeld = true;
+ this.canBeDropped = true;
+ this.isDestructive = true;
+
+ this.isExploding = false;
+
+ this.collideKart = collideKart;
+ this.onRest = onRest;
+ this.update = update;
+
+ function collideKart(kart) {
+ item.deadTimer = 1;
+ kart.damage(MKDSCONST.DAMAGE_EXPLODE);
+ }
+
+ function onRest(normal) {
+ }
+
+ function update(argument) {
+ if (!item.held && item.colRadius < 6) {
+ item.colRadius += 0.2;
+ if (item.colRadius > 6) item.colRadius = 6;
+ }
+ }
+}
\ No newline at end of file
diff --git a/code/entities/items/shells.js b/code/entities/items/shells.js
index 58b64c9..e277d0a 100644
--- a/code/entities/items/shells.js
+++ b/code/entities/items/shells.js
@@ -1,7 +1,97 @@
window.GreenShellC = function(item, scene) {
+ var t = this;
this.canBeHeld = true;
this.canBeDropped = true;
this.isDestructive = true;
+ this.angle = 0;
+ this.speed = 6; //base speed + kart speed
+ this.sound = null;
+ this.soundCooldown = 0;
+ item.colRadius = 3;
+
+ var minimumMove = 0.17;
+ this.gravity = [0, -0.17, 0]; //100% confirmed by me messing around with the gravity value in mkds
+
+ this.collideKart = collideKart;
+ this.update = update;
+ this.release = release;
+ this.onDie = onDie;
+ this.colResponse = colResponse;
+
+ function release(forward) {
+ t.sound = nitroAudio.playSound(215, {volume: 1.5}, 0, item);
+ t.speed = 6;
+ t.angle = item.owner.physicalDir;
+ if (forward < 0) {
+ t.angle += Math.PI;
+ t.angle %= Math.PI*2;
+ } else {
+ t.speed += item.owner.speed;
+ }
+ }
+
+ function onDie(final) {
+ if (!final) {
+ nitroAudio.playSound(214, {volume: 2}, 0, item);
+ }
+ if (t.sound) {
+ nitroAudio.instaKill(t.sound);
+ t.sound = null;
+ }
+ }
+
+ function collideKart(kart) {
+ item.deadTimer = 1;
+ kart.damage(MKDSCONST.DAMAGE_FLIP);
+ }
+
+ function update(scene) {
+ item.vel = [Math.sin(t.angle)*t.speed, item.vel[1], -Math.cos(t.angle)*t.speed]
+ vec3.add(item.vel, item.vel, t.gravity);
+ if (this.soundCooldown > 0) this.soundCooldown--;
+ }
+
+ function colResponse(pos, pvel, dat, ignoreList) {
+ var plane = dat.plane;
+ var colType = (plane.CollisionType>>8)&31;
+ vec3.add(pos, pos, vec3.scale(vec3.create(), pvel, dat.t));
+
+ var n = dat.normal;
+ vec3.normalize(n, n);
+ var gravS = Math.sqrt(vec3.dot(t.gravity, t.gravity));
+ var angle = Math.acos(vec3.dot(vec3.scale(vec3.create(), t.gravity, -1/gravS), n));
+ var adjustPos = true
+
+ if (MKDS_COLTYPE.GROUP_WALL.indexOf(colType) != -1) { //wall
+ //shell reflection code - slide y vel across plane, bounce on xz
+ if (this.soundCooldown <= 0) {
+ nitroAudio.playSound(213, {volume: 2.5}, 0, item);
+ this.soundCooldown = 30;
+ }
+ vec3.add(item.vel, vec3.scale(vec3.create(), n, -2*(vec3.dot(item.vel, n)/vec3.dot(n,n))), item.vel);
+ item.vel[1] = 0;
+
+ var v = item.vel;
+ t.angle = Math.atan2(v[0], -v[2]);
+ } else if (colType == MKDS_COLTYPE.OOB || colType == MKDS_COLTYPE.FALL) {
+ if (item.deadTimer == 0) item.deadTimer++;
+ } else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
+ //sliding plane
+ var proj = vec3.dot(item.vel, n);
+ vec3.sub(item.vel, item.vel, vec3.scale(vec3.create(), n, proj));
+ } else {
+ adjustPos = false;
+ ignoreList.push(plane);
+ }
+
+ var rVelMag = Math.sqrt(vec3.dot(item.vel, item.vel));
+ vec3.scale(item.vel, item.vel, t.speed/rVelMag); //force speed to shell speed for green shells.
+
+ if (adjustPos) { //move back from plane slightly
+ vec3.add(pos, pos, vec3.scale(vec3.create(), n, minimumMove));
+ }
+
+ }
}
window.RedShellC = function(item, scene) {
@@ -11,11 +101,100 @@ window.RedShellC = function(item, scene) {
}
window.ShellGroupC = function(item, scene, type) {
- this.canBeHeld = false;
- this.canBeDropped = 'func';
+ var t = this;
+ this.canBeHeld = "func";
+ this.canBeDropped = "func";
this.rotationPeriod = 45;
+ item.colRadius = -Infinity;
this.draw = draw;
+ this.update = update;
+ this.release = release;
+ this.onDie = onDie;
+
+ this.children = [];
+
+ var itemType = "koura_g";
+ var itemCount = 3;
+
+ if (type.length > 0) {
+ var typeParse = type.split("-");
+ if (typeParse.length == 1) {
+ itemType = type;
+ } else if (typeParse.length == 2 && !isNaN(typeParse[1]-0)) {
+ itemType = typeParse[0];
+ itemCount = typeParse[1]-0;
+ }
+ }
+
+ this.phase = 0;
+ var spinDist = 6;
+
+ init();
+
+ function init() {
+ t.remaining = itemCount;
+ item.holdPos = [0, 0, 0];
+ //create children
+ for (var i=0; i 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 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
diff --git a/code/entities/kart.js b/code/entities/kart.js
index 3438d56..b85783c 100644
--- a/code/entities/kart.js
+++ b/code/entities/kart.js
@@ -73,6 +73,11 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
this.getPosition = getPosition;
this.playCharacterSound = playCharacterSound;
+ //functions for external objects to trigger
+ this.damage = damage;
+ this.damageTime = 0;
+ this.damageType = -1;
+
this.trackAttach = null; //a normal for the kart to attach to (loop)
this.boostMT = 0;
this.boostNorm = 0;
@@ -399,7 +404,8 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
mat4.rotateX(mat, mat, c.angle[0]*(-Math.PI/180));
k.pos = vec3.clone(c2.pos);
- vec3.add(k.pos, k.pos, vec3.transformMat4([], [0,16,16], mat));
+ vec3.add(k.pos, k.pos, vec3.transformMat4([], [0,16,32], mat));
+ k.airTime = 4;
k.physicalDir = (180-c2.angle[1])*(Math.PI/180);
k.angle = k.physicalDir;
@@ -438,7 +444,6 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
}
} else { //default kart mode
-
if (k.OOB > 0) {
playCharacterSound(0);
var current = checkpoints[k.checkPointNumber];
@@ -466,180 +471,176 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
var top = params.topSpeed*effect.topSpeedMul; //if you let go of accel, drift ends anyway, so always accel in drift.
var boosting = (k.boostNorm + k.boostMT)>0;
-
- if (boosting) {
- var top2
- if (k.boostNorm>0){
- top2 = params.topSpeed*1.3;
- k.boostNorm--;
- } else {
- top2 = params.topSpeed*((effect.topSpeedMul >= 1)?1.3:effect.topSpeedMul);
- }
-
- if (k.boostMT>0) {
- k.boostMT--;
- }
-
- if (k.speed <= top2) {
- k.speed += 1;
- if (k.speed > top2) k.speed = top2;
- } else {
- k.speed *= 0.95;
- }
- }
-
- //kart controls
- if (k.drifting) {
- if ((onGround) && !(input.accel && input.drift && (k.speed > 2 || !k.driftLanded))) {
- //end drift, execute miniturbo
- k.drifting = false;
- clearWheelParticles();
- if (sounds.powerslide != null) {
- nitroAudio.instaKill(sounds.powerslide);
- sounds.powerslide = null;
- }
- if (k.driftPSMode == 3) {
- k.boostMT = params.miniTurbo;
- }
- k.driftPSMode = 0;
- k.driftPSTick = 0;
- }
-
- if (k.driftMode == 0) {
- if (input.turn > 0.30) {
- k.driftMode = 2;
- } else if (input.turn < -0.30) {
- k.driftMode = 1;
- }
- } else {
- if (k.driftLanded) {
- var change = (((k.driftMode-1.5)*Math.PI/1.5)-k.driftOff)*0.05;
- k.driftOff += change;
- k.physicalDir -= change;
+ if (k.specialControlHandler != null) k.specialControlHandler();
+ else {
+ if (boosting) {
+ var top2
+ if (k.boostNorm>0){
+ top2 = params.topSpeed*1.3;
+ k.boostNorm--;
+ } else {
+ top2 = params.topSpeed*((effect.topSpeedMul >= 1)?1.3:effect.topSpeedMul);
}
- //if we're above the initial y position, add a constant turn with a period of 180 frames.
- if (!k.driftLanded && k.ylock>=0) {
- k.physicalDir += (Math.PI*2/180)*(k.driftMode*2-3);
+ if (k.boostMT>0) {
+ k.boostMT--;
+ }
+
+ if (k.speed <= top2) {
+ k.speed += 1;
+ if (k.speed > top2) k.speed = top2;
+ } else {
+ k.speed *= 0.95;
}
}
- if (onGround) {
- if (!k.driftLanded) {
- if (k.driftMode == 0) {
- k.drifting = false;
- clearWheelParticles();
+ //kart controls
+ if (k.drifting) {
+ if ((onGround) && !(input.accel && input.drift && (k.speed > 2 || !k.driftLanded))) {
+ //end drift, execute miniturbo
+ endDrift();
+ if (k.driftPSMode == 3) {
+ k.boostMT = params.miniTurbo;
}
- else {
- k.driftPSMode = 0;
- k.driftPSTick = 0;
- k.driftLanded = true;
- if (k.drifting) setWheelParticles(20, 1); //20 = smoke, 1 = drift priority
+ k.driftPSMode = 0;
+ k.driftPSTick = 0;
+ }
+
+ if (k.driftMode == 0) {
+ if (input.turn > 0.30) {
+ k.driftMode = 2;
+ } else if (input.turn < -0.30) {
+ k.driftMode = 1;
+ }
+ } else {
+ if (k.driftLanded) {
+ var change = (((k.driftMode-1.5)*Math.PI/1.5)-k.driftOff)*0.05;
+ k.driftOff += change;
+ k.physicalDir -= change;
+ }
+
+ //if we're above the initial y position, add a constant turn with a period of 180 frames.
+ if (!k.driftLanded && k.ylock>=0) {
+ k.physicalDir += (Math.PI*2/180)*(k.driftMode*2-3);
}
}
- if (k.drifting) {
+ if (onGround) {
+ if (!k.driftLanded) {
+ if (k.driftMode == 0) {
+ endDrift();
+ }
+ else {
+ k.driftPSMode = 0;
+ k.driftPSTick = 0;
+ k.driftLanded = true;
+ if (k.drifting) setWheelParticles(20, 1); //20 = smoke, 1 = drift priority
+ }
+ }
+ if (k.drifting) {
+
+ if (!boosting) {
+ if (k.speed <= top) {
+ k.speed += (k.speed/top > params.driftAccelSwitch)?params.driftAccel2:params.driftAccel1;
+ if (k.speed > top) k.speed = top;
+ } else {
+ k.speed *= 0.95;
+ }
+ }
+
+ var turn = ((k.driftMode == 1)?(input.turn-1):(input.turn+1))/2;
+
+ k.physicalDir += params.driftTurnRate*turn+((k.driftMode == 1)?-1:1)*(50/32768)*Math.PI; //what is this mystery number i hear you ask? well my friend, this is the turn rate for outward drift.
+
+ //miniturbo code
+ if (input.turn != 0) {
+ var inward = ((input.turn>0) == k.driftMode-1); //if we're turning
+
+ switch (k.driftPSMode) {
+ case 0: //dpad away from direction for 10 frames
+ if (!inward) k.driftPSTick++;
+ else if (k.driftPSTick > 9) {
+ k.driftPSMode++;
+ k.driftPSTick = 1;
+
+ //play blue spark sound, flare
+ setWheelParticles(126, 2); //126 = blue flare, 2 = flare priority
+ var blue = nitroAudio.playSound(210, {}, 0, k);
+ blue.gainN.gain.value = 2;
+
+ } else k.driftPSTick = 0;
+ break;
+ case 1: //dpad toward direction for 10 frames
+ if (inward) k.driftPSTick++;
+ else if (k.driftPSTick > 9) {
+ k.driftPSMode++;
+ k.driftPSTick = 1;
+
+ } else k.driftPSTick = 0;
+ break;
+ case 2: //dpad away from direction for 10 frames
+ if (!inward) k.driftPSTick++;
+ else if (k.driftPSTick > 9) {
+ k.driftPSMode++;
+ k.driftPSTick = 1;
+ //play red sparks sound, full MT!
+ setWheelParticles(22, 2); //22 = red flare, 2 = flare priority
+ setWheelParticles(17, 1); //17 = red mt, 1 = drift priority ... 18 is sparks that come out - but their mode is not working yet (spark mode)
+ sounds.powerslide = nitroAudio.playSound(209, {}, 0, k);
+ sounds.powerslide.gainN.gain.value = 2;
+ } else k.driftPSTick = 0;
+ break;
+ case 3: //turbo charged
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!k.drifting) {
+ if (onGround) {
+ var effect = params.colParam[groundEffect];
if (!boosting) {
- if (k.speed <= top) {
- k.speed += (k.speed/top > params.driftAccelSwitch)?params.driftAccel2:params.driftAccel1;
- if (k.speed > top) k.speed = top;
+ if (input.accel) {
+ if (k.speed <= top) {
+ k.speed += (k.speed/top > params.accelSwitch)?params.accel2:params.accel1;
+ if (k.speed > top) k.speed = top;
+ } else {
+ k.speed *= 0.95;
+ }
} else {
- k.speed *= 0.95;
+ k.speed *= params.decel;
}
}
- var turn = ((k.driftMode == 1)?(input.turn-1):(input.turn+1))/2;
-
- k.physicalDir += params.driftTurnRate*turn+((k.driftMode == 1)?-1:1)*(50/32768)*Math.PI; //what is this mystery number i hear you ask? well my friend, this is the turn rate for outward drift.
-
- //miniturbo code
- if (input.turn != 0) {
- var inward = ((input.turn>0) == k.driftMode-1); //if we're turning
-
- switch (k.driftPSMode) {
- case 0: //dpad away from direction for 10 frames
- if (!inward) k.driftPSTick++;
- else if (k.driftPSTick > 9) {
- k.driftPSMode++;
- k.driftPSTick = 1;
-
- //play blue spark sound, flare
- setWheelParticles(126, 2); //126 = blue flare, 2 = flare priority
- var blue = nitroAudio.playSound(210, {}, 0, k);
- blue.gainN.gain.value = 2;
-
- } else k.driftPSTick = 0;
- break;
- case 1: //dpad toward direction for 10 frames
- if (inward) k.driftPSTick++;
- else if (k.driftPSTick > 9) {
- k.driftPSMode++;
- k.driftPSTick = 1;
-
- } else k.driftPSTick = 0;
- break;
- case 2: //dpad away from direction for 10 frames
- if (!inward) k.driftPSTick++;
- else if (k.driftPSTick > 9) {
- k.driftPSMode++;
- k.driftPSTick = 1;
- //play red sparks sound, full MT!
- setWheelParticles(22, 2); //22 = red flare, 2 = flare priority
- setWheelParticles(17, 1); //17 = red mt, 1 = drift priority
- sounds.powerslide = nitroAudio.playSound(209, {}, 0, k);
- sounds.powerslide.gainN.gain.value = 2;
- } else k.driftPSTick = 0;
- break;
- case 3: //turbo charged
- break;
- }
+ if ((input.accel && k.speed >= 0) || (k.speed > 0.1)) {
+ k.physicalDir += params.turnRate*input.turn;
+ } else if ( k.speed < -0.1) {
+ k.physicalDir -= params.turnRate*input.turn;
}
- }
- }
- }
- if (!k.drifting) {
- if (onGround) {
- var effect = params.colParam[groundEffect];
- if (!boosting) {
- if (input.accel) {
- if (k.speed <= top) {
- k.speed += (k.speed/top > params.accelSwitch)?params.accel2:params.accel1;
- if (k.speed > top) k.speed = top;
- } else {
- k.speed *= 0.95;
- }
- } else {
- k.speed *= params.decel;
+ if (input.drift) {
+ ylvel = 1.25;
+ k.vel[1] += 1.25;
+ k.airTime = 4;
+ k.drifting = true;
+ k.driftLanded = false;
+ k.driftMode = 0;
+ k.ylock = 0;
+ onGround = false;
+
+ var boing = nitroAudio.playSound(207, {transpose: -4}, 0, k);
+ boing.gainN.gain.value = 2;
+ }
+ } else {
+ if (input.drift) {
+ ylvel = 0;
+ k.drifting = true;
+ k.driftLanded = false;
+ k.driftMode = 0;
+ k.ylock = -0.001;
}
- }
-
- if ((input.accel && k.speed >= 0) || (k.speed > 0.1)) {
- k.physicalDir += params.turnRate*input.turn;
- } else if ( k.speed < -0.1) {
- k.physicalDir -= params.turnRate*input.turn;
- }
-
- if (input.drift) {
- ylvel = 1.25;
- k.vel[1] += 1.25;
- k.airTime = 4;
- k.drifting = true;
- k.driftLanded = false;
- k.driftMode = 0;
- k.ylock = 0;
- onGround = false;
-
- var boing = nitroAudio.playSound(207, {transpose: -4}, 0, k);
- boing.gainN.gain.value = 2;
- }
- } else {
- if (input.drift) {
- ylvel = 0;
- k.drifting = true;
- k.driftLanded = false;
- k.driftMode = 0;
- k.ylock = -0.001;
}
}
}
@@ -708,8 +709,6 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
//move kart.
-
-
var steps = 0;
var remainingT = 1;
var baseVel = k.vel;
@@ -754,6 +753,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
var mat = mat4.create();
mat4.translate(mat, mat, k.pos);
k.mat = mat4.mul(mat, mat, k.basis);
+ if (k.damageMat != null) mat4.mul(mat, mat, k.damageMat);
if (input.item) {
scene.items.addItem(0, scene.karts.indexOf(k), {})
@@ -763,6 +763,83 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
positionChanged(lastPos, k.pos);
}
+ function endDrift() {
+ k.drifting = false;
+ clearWheelParticles();
+ if (sounds.powerslide != null) {
+ nitroAudio.instaKill(sounds.powerslide);
+ sounds.powerslide = null;
+ }
+ }
+
+ function damage(damageType) {
+ if (k.damageType >= damageType) {
+ return; //we are already damaged
+ }
+ //TODO: check invuln state
+ k.specialControlHandler = damagedControls;
+ playCharacterSound((damageType == 0) ? 1 : 0);
+ k.damageType = damageType;
+ k.ylock = 0;
+
+ k.anim.setAnim(k.charRes.spinA);
+ k.animMode = "spin";
+
+ if (k.drifting) {
+ endDrift();
+ }
+ k.boostMT = 0;
+ k.boostNorm = 0;
+
+ switch (damageType) {
+ case 0:
+ k.damageTime = 40;
+ break;
+ case 1:
+ k.damageTime = 80;
+ k.vel[1] += 3;
+ ylvel = 3;
+ k.airTime = 4;
+ break;
+ case 2:
+ k.damageTime = 105;
+ k.vel[1] += 8;
+ ylvel = 8;
+ k.airTime = 4;
+ break;
+ }
+ }
+
+ function damagedControls(kart) {
+ if (--k.damageTime == 0) {
+ k.anim.setAnim(k.charRes.driveA);
+ k.animMode = "drive";
+ k.specialControlHandler = null;
+ k.damageType = -1;
+ k.damageMat = null;
+ }
+ vec3.scale(k.vel, k.vel, 0.98);
+ k.speed *= 0.98;
+
+ var total = 40;
+ switch (k.damageType) {
+ case 1:
+ total = 80;
+ break;
+ case 2:
+ total = 105;
+ break;
+ }
+ var anim = 1 - (k.damageTime / total);
+
+ k.damageMat = mat4.create();
+ var flip = ((k.damageType%2) == 1)? 1 : -1;
+ var animOff = Math.min(1, anim*1.75);
+ mat4.rotateX(k.damageMat, k.damageMat, Math.PI*2 * animOff * k.damageType * flip);
+ if (k.damageType == 0) mat4.rotateY(k.damageMat, k.damageMat, Math.PI*-2 * anim);
+ else mat4.rotateY(k.damageMat, k.damageMat, Math.PI/12 * Math.sin(animOff*Math.PI));
+ }
+
function triggerCannon(id) {
if (k.cannon != null) return;
k.cannon = id;
@@ -923,7 +1000,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
k.kartColVel[1] = 0;
//play this kart's horn
- nitroAudio.playSound(192 + charRes.sndOff/14, { volume: 2 }, 0, k);
+ nitroAudio.playSound(193 + charRes.sndOff/14, { volume: 2 }, 0, k);
}
function fixDir(dir) {
@@ -940,6 +1017,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
function updateKartSound(mode, input) {
+ if (!k.local) return; //for now, don't play kart sounds from other racers.
var turn = (onGround && !k.drifting)?(1-Math.abs(input.turn)/11):1;
var transpose = (mode == 0)?0:(22*turn*Math.min(1.3, k.speed/params.topSpeed));
@@ -1028,12 +1106,15 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
function sndUpdate(view) {
+ /*
k.soundProps.pos = vec3.transformMat4([], k.pos, view);
if (k.soundProps.lastPos != null) k.soundProps.vel = vec3.sub([], k.soundProps.pos, k.soundProps.lastPos);
else k.soundProps.vel = [0, 0, 0];
+ */
k.soundProps.lastPos = k.soundProps.pos;
+ k.soundProps.pos = k.pos; //todo: reintroduce doppler via emulation
- k.soundProps.refDistance = 192/1024;
+ k.soundProps.refDistance = 192;
k.soundProps.rolloffFactor = 1;
var calcVol = (k.soundProps.refDistance / (k.soundProps.refDistance + k.soundProps.rolloffFactor * (Math.sqrt(vec3.dot(k.soundProps.pos, k.soundProps.pos)) - k.soundProps.refDistance)));
@@ -1080,7 +1161,7 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
var angle = Math.acos(vec3.dot(vec3.scale(vec3.create(), k.gravity, -1/gravS), n));
var adjustPos = true;
- if (colType == MKDS_COLTYPE.OOB || colType == MKDS_COLTYPE.FALL) {
+ if (MKDS_COLTYPE.GROUP_OOB.indexOf(colType) != -1) {
k.OOB = 1;
}
@@ -1101,12 +1182,18 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
}
vec3.sub(k.vel, k.vel, vec3.scale(vec3.create(), adjN, proj));
+ if (colType == MKDS_COLTYPE.KNOCKBACK_DAMAGE && k.damageType == -1) {
+ if (dat.object.vel) vec3.add(k.vel, k.vel, dat.object.vel);
+ vec3.add(k.vel, k.vel, vec3.scale(vec3.create(), adjN, 1.25));
+ k.damage(MKDSCONST.DAMAGE_FLIP);
+ }
//convert back to angle + speed to keep change to kart vel
var v = k.vel;
k.speed = Math.sqrt(v[0]*v[0]+v[2]*v[2]);
k.angle = Math.atan2(v[0], -v[2]);
+ stuckTo = dat.object;
} else if (MKDS_COLTYPE.GROUP_ROAD.indexOf(colType) != -1) {
//sliding plane
if (MKDS_COLTYPE.GROUP_BOOST.indexOf(colType) != -1) {
@@ -1117,7 +1204,8 @@ window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) {
if (k.vel[1] > 0) k.vel[1] = 0;
var proj = vec3.dot(k.vel, an);
- if (!stick && proj < -4 && k.vel[1] < -2) { proj -= 1.5; }
+ if (k.damageType > 0) proj *= 1.7;
+ else if (!stick && proj < -4 && k.vel[1] < -2) { proj -= 1.5; }
vec3.sub(k.vel, k.vel, vec3.scale(vec3.create(), an, proj));
if (stick) {
diff --git a/code/entities/kartItems.js b/code/entities/kartItems.js
index 0d1f69e..50834ab 100644
--- a/code/entities/kartItems.js
+++ b/code/entities/kartItems.js
@@ -2,19 +2,20 @@
window.KartItems = function(kart, scene) {
var t = this;
- t.heldItem = null; //of type Item
+ t.heldItem = null; //held item, or item that is bound to us. (bound items have hold type 'func', eg. triple shell)
t.currentItem = null; //string name for item
t.specificItem = null;
t.empty = true;
t.cycleTime = 0;
t.totalTime = 230;
+
var maxItemTime = 230;
var minItemTime = 80;
var carouselSfx = null;
var lastItemState = false;
var holdAppearDelay = 15;
- var hurtExplodeDelay = 105 //turn right slightly, huge double backflip, small bounces.
+ var hurtExplodeDelay = 105; //turn right slightly, huge double backflip, small bounces.
var hurtFlipDelay = 80; //turn right slightly, bounce twice, forward flip
var hurtSpinDelay = 40; //counter clockwise spin
@@ -43,8 +44,22 @@ window.KartItems = function(kart, scene) {
}
}
+ function createItem() {
+ var item = scene.items.createItem(t.currentItem, kart);
+ return item;
+ }
+
+ function release(input) {
+ if (t.heldItem != null) {
+ t.heldItem.release(input.airTurn);
+ }
+ t.heldItem = null;
+ kart.playCharacterSound(7);
+ }
+
function update(input) {
var pressed = (input.item && !lastItemState);
+ var released = (lastItemState && !input.item);
if (!t.empty) {
if (t.currentItem == null) {
//carousel
@@ -53,24 +68,48 @@ window.KartItems = function(kart, scene) {
if (carouselSfx != null) nitroAudio.kill(carouselSfx);
//decide on an item
- var item = "koura_g";
+ var item = "banana"; //koura_g, banana, f_box, koura_group, koura_group-bomb-7
sfx((specialItems.indexOf(item) == -1) ? 63 : 64);
t.currentItem = item;
} else {
//if item button is pressed, we speed up the carousel
- if (pressed) {
+ if (pressed && t.heldItem == null) {
t.totalTime = Math.max(minItemTime, t.totalTime - 20);
}
}
- } else {
+ } else if (t.heldItem == null) {
if (pressed) {
//fire?
- t.currentItem = null;
- t.empty = true;
- kart.playCharacterSound(7);
+ t.heldItem = createItem();
+ //t.currentItem = null;
+ //t.empty = true;
+
+ if (t.heldItem.canBeHeld()) {
+ //begin holding
+ } else {
+ release(input);
+ }
+ pressed = false;
}
}
+ }
+ //todo: if held item has been destroyed, stop holding it.
+
+ if (t.heldItem != null) {
+ if (t.heldItem.dead) {
+ t.heldItem = null;
+ } else {
+ //t.heldItem.updateHold(kart);
+ if (released) {
+ if (t.heldItem.canBeHeld() !== 'func') release(input);
+ } else if (pressed) {
+ //special release: triple shells, bananas. object stays bound when released
+ t.heldItem.release(input.airTurn);
+ kart.playCharacterSound(7);
+ if (t.heldItem.dead) t.heldItem = null;
+ }
+ }
}
lastItemState = input.item;
}
diff --git a/code/entities/objDatabase.js b/code/entities/objDatabase.js
index 3a6504a..270934b 100644
--- a/code/entities/objDatabase.js
+++ b/code/entities/objDatabase.js
@@ -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)
diff --git a/code/entities/rotatingGear.js b/code/entities/rotatingGear.js
index 2186d9b..7c72cf4 100644
--- a/code/entities/rotatingGear.js
+++ b/code/entities/rotatingGear.js
@@ -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;
- }*/
}
}
\ No newline at end of file
diff --git a/code/entities/shell.js b/code/entities/shell.js
index 9dc2238..8d8a660 100644
--- a/code/entities/shell.js
+++ b/code/entities/shell.js
@@ -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);
diff --git a/code/entities/soundMaker.js b/code/entities/soundMaker.js
index 5fac83a..e082a7d 100644
--- a/code/entities/soundMaker.js
+++ b/code/entities/soundMaker.js
@@ -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>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 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>5)&31)*f), Math.round(((col>>10)&31)*f), 255];
+ }
+
+ function readChar(view, offset) {
+ return String.fromCharCode(view.getUint8(offset));
+ }
+}
\ No newline at end of file
diff --git a/code/formats/2d/ncgr.js b/code/formats/2d/ncgr.js
new file mode 100644
index 0000000..13fc839
--- /dev/null
+++ b/code/formats/2d/ncgr.js
@@ -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>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));
+ }
+}
\ No newline at end of file
diff --git a/code/formats/2d/nclr.js b/code/formats/2d/nclr.js
new file mode 100644
index 0000000..37e0375
--- /dev/null
+++ b/code/formats/2d/nclr.js
@@ -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= 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>5)&31)*f), Math.round(((col>>10)&31)*f), 255];
+ }
+
+ function readChar(view, offset) {
+ return String.fromCharCode(view.getUint8(offset));
+ }
+}
\ No newline at end of file
diff --git a/code/formats/2d/nscr.js b/code/formats/2d/nscr.js
new file mode 100644
index 0000000..23e8f23
--- /dev/null
+++ b/code/formats/2d/nscr.js
@@ -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>5)&31)*f), Math.round(((col>>10)&31)*f), 255];
+ }
+
+ function readChar(view, offset) {
+ return String.fromCharCode(view.getUint8(offset));
+ }
+}
\ No newline at end of file
diff --git a/code/formats/kartoffsetdata.js b/code/formats/kartoffsetdata.js
index 8fb0516..874e102 100644
--- a/code/formats/kartoffsetdata.js
+++ b/code/formats/kartoffsetdata.js
@@ -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);
diff --git a/code/formats/kcl.js b/code/formats/kcl.js
index ed7f93e..5a21fcb 100644
--- a/code/formats/kcl.js
+++ b/code/formats/kcl.js
@@ -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) {
diff --git a/code/formats/narc.js b/code/formats/narc.js
index fec076f..f4ebc54 100644
--- a/code/formats/narc.js
+++ b/code/formats/narc.js
@@ -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 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> (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;
+ }
+
}
\ No newline at end of file
diff --git a/code/formats/nitro.js b/code/formats/nitro.js
index 1a14f41..cb36112 100644
--- a/code/formats/nitro.js
+++ b/code/formats/nitro.js
@@ -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);
}
diff --git a/code/formats/nsbca.js b/code/formats/nsbca.js
index 3174932..b2666b7 100644
--- a/code/formats/nsbca.js
+++ b/code/formats/nsbca.js
@@ -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],
diff --git a/code/formats/nsbmd.js b/code/formats/nsbmd.js
index b266e85..aaff7cf 100644
--- a/code/formats/nsbmd.js
+++ b/code/formats/nsbmd.js
@@ -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 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.
diff --git a/code/formats/nsbta.js b/code/formats/nsbta.js
index d9ab0e0..ca29c40 100644
--- a/code/formats/nsbta.js
+++ b/code/formats/nsbta.js
@@ -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>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)
diff --git a/code/formats/spa.js b/code/formats/spa.js
index c1a6dee..faa6fc0 100644
--- a/code/formats/spa.js
+++ b/code/formats/spa.js
@@ -1,548 +1,557 @@
-//
-// spa.js
-//--------------------
-// Reads spa files. Based off of code from MJDS Course Modifier, which was very incomplete but at least got the textures.
-// Reverse engineered most of the emitter stuff.
-// by RHY3756547
-//
-
-window.spa = function(input) {
- var t = this;
- this.load = load;
- this.getTexture = getTexture;
-
- var colourBuffer;
-
- function load(input) {
- colourBuffer = new Uint32Array(4);
- var view = new DataView(input);
- var header = null;
- var offset = 0;
-
- var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
- if (stamp != " APS") throw "SPA invalid. Expected ' APS', found "+stamp;
- offset += 4;
-
- var version = readChar(view, offset)+readChar(view, offset+1)+readChar(view, offset+2)+readChar(view, offset+3);
- offset += 4;
-
- var particleCount = view.getUint16(offset, true);
- var particleTexCount = view.getUint16(offset+2, true);
- var unknown = view.getUint32(offset+4, true);
- var unknown2 = view.getUint32(offset+8, true);
- var unknown3 = view.getUint32(offset+12, true);
-
- var firstTexOffset = view.getUint32(offset+16, true);
- var pad = view.getUint32(offset+20, true);
-
- offset += 24;
- if (version == "12_1") {
- t.particles = [];
- for (var i=0; i1 means more than one particle will appear.
- obj.areaSpread = view.getInt32(off+0x14, true)/4096; //x and z
- //^ particle count?
- obj.unknown3 = view.getInt32(off+0x18, true)/4096; //unknown (does not change anything for grass)
-
- //not sure what this vector is for. grass it's (0, 1, 0), smoke it's (-0.706787109375, 0, -0.707275390625) billboard alignment vector? (it's a bit crazy for powerslide)
- obj.vector = [view.getInt16(off+0x1C, true)/4096, view.getInt16(off+0x1E, true)/4096, view.getInt16(off+0x20, true)/4096];
- obj.color = view.getUint16(off+0x22, true); //15 bit, usually 32767 for white.
- obj.randomxz = view.getUint32(off+0x24, true)/4096; //random xz velocity intensity
- obj.velocity = view.getUint32(off+0x28, true)/4096; //initial velocity related (along predefined vector)
- obj.size = view.getUint32(off+0x2C, true)/4096; //size
- obj.aspect = view.getUint16(off+0x30, true) / 4096; //aspect
-
- //frame delay before activation (x2)
- //rotational velocity from (x2)
- //rotational velocity to (x2)
- obj.delay = view.getUint16(off+0x32, true);
- obj.rotVelFrom = view.getInt16(off+0x34, true);
- obj.rotVelTo = view.getInt16(off+0x36, true);
-
- obj.scX = view.getInt16(off+0x38, true)/0x8000; //??? (0) //scale center offset?
- obj.scY = view.getInt16(off+0x3A, true)/0x8000; //??? (4B) //scale center offset?
- obj.emitterLifetime = view.getUint16(off+0x3C, true); //stop emitting particles after this many frames
- obj.duration = view.getUint16(off+0x3E, true);
-
- obj.varScale = view.getUint8(off+0x40, true);
- obj.varDuration = view.getUint8(off+0x42, true);
- obj.varUnk1 = view.getUint8(off+0x44, true); //usually like 1-8
- obj.varUnk2 = view.getUint8(off+0x46, true); //usually like 128 (hahaa)
-
- obj.frequency = view.getUint8(off+0x44, true); //create particle every n frames
- obj.opacity = view.getUint8(off+0x45, true); //opacity (0-1F)
- obj.yOffIntensity = view.getUint8(off+0x46, true); //y offset intensity (seems to include updraft and gravity. 124 for smoke, 120 for grass. 128 is probably 1x)
- obj.textureId = view.getUint8(off+0x47, true);
- obj.unknown21 = view.getUint32(off+0x48, true); //negative number makes grass disappear (1 for grass, smoke)
- obj.unknown22 = view.getUint32(off+0x4C, true); //some numbers make grass disappear (0x458d00 for grass, 0x74725f60 for smoke)
- obj.xScaleDelta = view.getInt16(off+0x50, true)/4096; //x scale delta for some reason. usually 0
- obj.yScaleDelta = view.getInt16(off+0x52, true)/4096; //y scale delta for some reason. usually 0
- obj.unknown25 = view.getUint32(off+0x54, true); //FFFFFFFF makes run at half framerate. idk? usually 0
- off += 0x58;
-
- if ((obj.flag & ParticleFlags.ScaleAnim) != 0)
- {
- obj.scaleAnim = [];
- //1.000 (doesn't seem to do anything important, but occasionally is between start and end)
- //start scale
- //end scale
- //???? (seems to affect the interpolation. cubic params?)
- //flags (1: random scale for one frame? everything above it might be cubic params)
- //???? (0x4B)
-
- obj.scaleAnim = {
- unkBase: view.getUint16(off, true)/4096,
- scaleFrom: view.getUint16(off+2, true)/4096,
- scaleTo: view.getUint16(off+4, true)/4096,
- fromZeroTime: view.getUint8(off+6, true)/0xFF, //time to dedicate to an animation from zero size
- holdTime: view.getUint8(off+7, true)/0xFF, //time to dedicate to holding state at the end.
- flagParam: view.getUint16(off+8, true),
- unk4b: view.getUint16(off+10, true),
- };
- off += 12;
- }
- if ((obj.flag & ParticleFlags.ColorAnimation) != 0)
- {
- obj.colorAnim = {
- colorFrom: view.getUint16(off, true), //color from
- colorTo: view.getUint16(off+2, true), //color to (seems to be same as base color)
- framePct: view.getUint16(off+4, true), //frame pct to become color to (FFFF means always from, 8000 is about the middle)
- unknown: view.getUint16(off+6, true), //unknown, 00FF for fire?
- flags: view.getUint32(off+8, true), //flags (1: binary select color, 4: smooth blend)
- };
- off += 12;
- }
- if ((obj.flag & ParticleFlags.OpacityAnimation) != 0)
- {
- //opacity
-
- //intensity x2 (0FFF to 0000. smoke is 0bff. 1000 breaks it, i'm assuming it pushes opacity from 1f to 20 (overflow to 0))
- //random flicker
- //unknown (negative byte breaks it)
- //startfade x2
- //cubic param? x2
- obj.opacityAnim = {
- intensity: view.getUint16(off, true),
- random: view.getUint8(off+2, true),
- unk: view.getUint8(off+3, true),
- startFade: view.getUint16(off+4, true), //0-FFFF. seems to be the pct of duration where the anim starts.
- param: view.getUint16(off+6, true),
- }
- off += 8;
- }
- if ((obj.flag & ParticleFlags.TextureAnimation) != 0)
- {
- var textures = [];
- for (var i=0; i<8; i++) textures[i] = view.getUint8(off+i);
- obj.texAnim = {
- textures: textures,
- frames: view.getUint8(off+8),
- unknown1: view.getUint8(off+9), //128 - duration of particle. 37 - blue spark? (7 frames for 7 duration effect)
- unknown2: view.getUint16(off+10, true), //1 - random frame?
- }
- off += 12;
- }
- if ((obj.flag & ParticleFlags.Bit16) != 0)
- {
- obj.Bit16 = [];
- for (var i=0; i<20; i++) obj.Bit16[i] = view.getUint8(off+i);
- off += 20;
- }
- if ((obj.flag & ParticleFlags.Gravity) != 0)
- {
-
- //gravity
- //x wind
- //gravity (signed 16, -1 is down, leaves are FFEA (-22/4096))
- //z wind
- //pad?
- obj.gravity = [
- view.getInt16(off, true)/4096,
- view.getInt16(off+2, true)/4096,
- view.getInt16(off+4, true)/4096,
- view.getInt16(off+6, true)/4096, //pad, should be ignored by vec3 ops
- ];
-
- off += 8;
- }
- if ((obj.flag & ParticleFlags.Bit25) != 0)
- {
- //seems to be 4 int 16s typically in some kind of pattern.
- obj.Bit25 = [];
- for (var i=0; i<8; i++) obj.Bit25[i] = view.getUint8(off+i);
- off += 8;
- }
- if ((obj.flag & ParticleFlags.Bit26) != 0)
- {
- obj.Bit26 = [];
- for (var i=0; i<16; i++) obj.Bit26[i] = view.getUint8(off+i);
- off += 16;
- }
- if ((obj.flag & ParticleFlags.Bit27) != 0)
- {
- obj.Bit27 = [];
- for (var i=0; i<4; i++) obj.Bit27[i] = view.getUint8(off+i);
- off += 4;
- }
- if ((obj.flag & ParticleFlags.Bit28) != 0)
- {
- obj.Bit28 = [];
- for (var i=0; i<8; i++) obj.Bit28[i] = view.getUint8(off+i);
- off += 8;
- }
- if ((obj.flag & ParticleFlags.Bit29) != 0)
- {
- obj.Bit29 = [];
- for (var i=0; i<16; i++) obj.Bit29[i] = view.getUint8(off+i);
- off += 16;
- }
-
- obj.nextOff = off;
- return obj;
- }
-
- function readParticleTexture(view, off) {
- var obj = {};
- obj.stamp = readChar(view, off+0x0)+readChar(view, off+0x1)+readChar(view, off+0x2)+readChar(view, off+0x3);
- if (obj.stamp != " TPS") throw "SPT invalid (particle texture in SPA). Expected ' TPS', found "+obj.stamp;
-
- var flags = view.getUint16(off+4, true);
- obj.info = {
- pal0trans: true,//z(flags>>3)&1, //weirdly different format
- format: ((flags)&7),
- height: 8 << ((flags>>8)&0xF),
- width: 8 << ((flags>>4)&0xF),
- repeatX: (flags>>12)&1,
- repeatY: (flags>>13)&1,
- flipX: (flags>>14)&1,
- flipY: (flags>>15)&1,
- }
- obj.flags = flags;
-
- obj.unknown = view.getUint16(off+6, true);
- obj.texDataLength = view.getUint32(off+8, true);
- obj.palOff = view.getUint32(off+0xC, true);
- obj.palDataLength = view.getUint32(off+0x10, true);
- obj.unknown2 = view.getUint32(off+0x14, true);
- obj.unknown3 = view.getUint32(off+0x18, true);
- obj.unknown4 = view.getUint32(off+0x1C, true);
-
- obj.texData = view.buffer.slice(off+32, off+32+obj.texDataLength);
- off += 32+obj.texDataLength;
- obj.palData = view.buffer.slice(off, off+obj.palDataLength);
-
- obj.nextOff = off+obj.palDataLength;
-
- //var test = readTexWithPal(obj.info, obj);
- //document.body.appendChild(test);
-
- return obj;
- }
-
-
-//modified from NSBTX.js - should probably refactor to use be generic between both
-
- function readTexWithPal(tex, data) {
- var format = tex.format;
- var trans = tex.pal0trans;
-
- if (format == 5) return readCompressedTex(tex, data); //compressed 4x4 texture, different processing entirely
-
- var off = 0;//tex.texOffset;
- var palView = new DataView(data.palData);
- var texView = new DataView(data.texData);
- var palOff = 0;//pal.palOffset;
-
- var canvas = document.createElement("canvas");
- canvas.width = tex.width;
- canvas.height = tex.height;
- var ctx = canvas.getContext("2d");
- var img = ctx.getImageData(0, 0, tex.width, tex.height);
-
- var total = tex.width*tex.height;
- var databuf;
- for (var i=0; i>5)*(255/7);
-
- } else if (format == 2) { //2 bit pal
- if (i%4 == 0) databuf = texView.getUint8(off++);
- col = readPalColour(palView, palOff, (databuf>>((i%4)*2))&3, trans)
-
- } else if (format == 3) { //4 bit pal
- if (i%2 == 0) {
- databuf = texView.getUint8(off++);
- col = readPalColour(palView, palOff, databuf&15, trans)
- } else {
- col = readPalColour(palView, palOff, databuf>>4, trans)
- }
-
- } else if (format == 4) { //8 bit pal
- col = readPalColour(palView, palOff, texView.getUint8(off++), trans)
-
- } else if (format == 6) { //A5I3 encoding. 5 bits alpha 3 bits pal index
- var dat = texView.getUint8(off++)
- col = readPalColour(palView, palOff, dat&7, trans);
- col[3] = (dat>>3)*(255/31);
-
- } else if (format == 7) { //raw color data
- col = texView.getUint16(off, true);
- colourBuffer[0] = Math.round(((col&31)/31)*255)
- colourBuffer[1] = Math.round((((col>>5)&31)/31)*255)
- colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
- colourBuffer[3] = Math.round((col>>15)*255);
- col = colourBuffer;
- off += 2;
-
- } else {
- console.log("texture format is none, ignoring")
- return canvas;
- }
- img.data.set(col, i*4);
- }
- ctx.putImageData(img, 0, 0)
- return canvas;
- }
-
- 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;
- var texView = new DataView(compData); //real texture data - 32 bits per 4x4 block (one byte per 4px horizontal line, each descending 1px)
- var compView = new DataView(compInfoData); //view into compression info - informs of pallete and parameters.
- var palView = new DataView(data.palData); //view into the texture pallete
- var compOff = off/2; //info is 2 bytes per block, so the offset is half that of the tex offset.
- var palOff = 0;//pal.palOffset;
- var transColor = new Uint8Array([0, 0, 0, 0]); //transparent black
-
- var canvas = document.createElement("canvas");
- canvas.width = tex.width;
- canvas.height = tex.height;
- var ctx = canvas.getContext("2d");
- var img = ctx.getImageData(0, 0, tex.width, tex.height);
-
- var w = tex.width>>2; //iterate over blocks, block w and h is /4.
- var h = tex.height>>2;
-
- for (var y=0; y> 14) & 3);
-
- var finalPo = palOff+addr*4;
- var imgoff = x*4+(y*w*16);
- for (var iy=0; iy<4; iy++) {
- var dat = texView.getUint8(off++);
- for (var ix=0; ix<4; ix++) { //iterate over horiz lines
- var part = (dat>>(ix*2))&3;
- var col;
-
- switch (mode) {
- case 0: //value 3 is transparent, otherwise pal colour
- if (part == 3) col = transColor;
- else col = readPalColour(palView, finalPo, part);
- break;
- case 1: //average mode - colour 2 is average of 1st two, 3 is transparent. 0&1 are normal.
- if (part == 3) col = transColor;
- else if (part == 2) col = readFractionalPal(palView, finalPo, 0.5);
- else col = readPalColour(palView, finalPo, part);
- break;
- case 2: //pal colour
- col = readPalColour(palView, finalPo, part);
- break;
- case 3: //5/8 3/8 mode - colour 2 is 5/8 of col0 plus 3/8 of col1, 3 is 3/8 of col0 plus 5/8 of col1. 0&1 are normal.
- if (part == 3) col = readFractionalPal(palView, finalPo, 3/8);
- else if (part == 2) col = readFractionalPal(palView, finalPo, 5/8);
- else col = readPalColour(palView, finalPo, part);
- break;
- }
-
- img.data.set(col, (imgoff++)*4)
- }
- imgoff += tex.width-4;
- }
- compOff += 2; //align off to next block
- }
- }
-
- ctx.putImageData(img, 0, 0)
- return canvas;
- }
-
- function readPalColour(view, palOff, ind, pal0trans) {
- var col = view.getUint16(palOff+ind*2, true);
- var f = 255/31;
- colourBuffer[0] = Math.round((col&31)*f)
- colourBuffer[1] = Math.round(((col>>5)&31)*f)
- colourBuffer[2] = Math.round(((col>>10)&31)*f)
- colourBuffer[3] = (pal0trans && ind == 0)?0:255;
- return colourBuffer;
- }
-
- function readFractionalPal(view, palOff, i) {
- var col = view.getUint16(palOff, true);
- var col2 = view.getUint16(palOff+2, true);
- var ni = 1-i;
- var f = 255/31;
- colourBuffer[0] = Math.round((col&31)*f*i + (col2&31)*f*ni)
- colourBuffer[1] = Math.round(((col>>5)&31)*f*i + ((col2>>5)&31)*f*ni)
- colourBuffer[2] = Math.round(((col>>10)&31)*f*i + ((col2>>10)&31)*f*ni)
- colourBuffer[3] = 255;
- return colourBuffer;
- }
-
- //end NSBTX
-
-
- function readChar(view, offset) {
- return String.fromCharCode(view.getUint8(offset));
- }
-
- if (input != null) {
- load(input);
- }
+//
+// spa.js
+//--------------------
+// Reads spa files. Based off of code from MJDS Course Modifier, which was very incomplete but at least got the textures.
+// Reverse engineered most of the emitter stuff.
+// by RHY3756547
+//
+
+window.spa = function(input) {
+ var t = this;
+ this.load = load;
+ this.getTexture = getTexture;
+
+ var colourBuffer;
+
+ function load(input) {
+ colourBuffer = new Uint32Array(4);
+ var view = new DataView(input);
+ var header = null;
+ var offset = 0;
+
+ var stamp = readChar(view, 0x0)+readChar(view, 0x1)+readChar(view, 0x2)+readChar(view, 0x3);
+ if (stamp != " APS") throw "SPA invalid. Expected ' APS', found "+stamp;
+ offset += 4;
+
+ var version = readChar(view, offset)+readChar(view, offset+1)+readChar(view, offset+2)+readChar(view, offset+3);
+ offset += 4;
+
+ var particleCount = view.getUint16(offset, true);
+ var particleTexCount = view.getUint16(offset+2, true);
+ var unknown = view.getUint32(offset+4, true);
+ var unknown2 = view.getUint32(offset+8, true);
+ var unknown3 = view.getUint32(offset+12, true);
+
+ var firstTexOffset = view.getUint32(offset+16, true);
+ var pad = view.getUint32(offset+20, true);
+
+ offset += 24;
+ if (version == "12_1") {
+ t.particles = [];
+ for (var i=0; i1 means more than one particle will appear.
+ obj.areaSpread = view.getInt32(off+0x14, true)/4096; //x and z
+ //^ particle count?
+ obj.unknown3 = view.getInt32(off+0x18, true)/4096; //unknown (does not change anything for grass)
+
+ //not sure what this vector is for. grass it's (0, 1, 0), smoke it's (-0.706787109375, 0, -0.707275390625) billboard alignment vector? (it's a bit crazy for powerslide)
+ obj.vector = [view.getInt16(off+0x1C, true)/4096, view.getInt16(off+0x1E, true)/4096, view.getInt16(off+0x20, true)/4096];
+ obj.color = view.getUint16(off+0x22, true); //15 bit, usually 32767 for white.
+ obj.randomxz = view.getUint32(off+0x24, true)/4096; //random xz velocity intensity
+ obj.velocity = view.getUint32(off+0x28, true)/4096; //initial velocity related (along predefined vector)
+ obj.size = view.getUint32(off+0x2C, true)/4096; //size
+ obj.aspect = view.getUint16(off+0x30, true) / 4096; //aspect
+
+ //frame delay before activation (x2)
+ //rotational velocity from (x2)
+ //rotational velocity to (x2)
+ obj.delay = view.getUint16(off+0x32, true);
+ obj.rotVelFrom = view.getInt16(off+0x34, true);
+ obj.rotVelTo = view.getInt16(off+0x36, true);
+
+ obj.scX = view.getInt16(off+0x38, true)/0x8000; //??? (0) //scale center offset?
+ obj.scY = view.getInt16(off+0x3A, true)/0x8000; //??? (4B) //scale center offset?
+ obj.emitterLifetime = view.getUint16(off+0x3C, true); //stop emitting particles after this many frames
+ obj.duration = view.getUint16(off+0x3E, true);
+
+ obj.varScale = view.getUint8(off+0x40, true);
+ obj.varDuration = view.getUint8(off+0x42, true);
+ obj.varUnk1 = view.getUint8(off+0x44, true); //usually like 1-8
+ obj.varUnk2 = view.getUint8(off+0x46, true); //usually like 128 (hahaa)
+
+ obj.frequency = view.getUint8(off+0x44, true); //create particle every n frames
+ obj.opacity = view.getUint8(off+0x45, true); //opacity (0-1F)
+ obj.yOffIntensity = view.getUint8(off+0x46, true); //y offset intensity (seems to include updraft and gravity. 124 for smoke, 120 for grass. 128 is probably 1x)
+ obj.textureId = view.getUint8(off+0x47, true);
+ obj.unknown21 = view.getUint32(off+0x48, true); //negative number makes grass disappear (1 for grass, smoke)
+ obj.unknown22 = view.getUint32(off+0x4C, true); //some numbers make grass disappear (0x458d00 for grass, 0x74725f60 for smoke)
+ obj.xScaleDelta = view.getInt16(off+0x50, true)/4096; //x scale delta for some reason. usually 0
+ obj.yScaleDelta = view.getInt16(off+0x52, true)/4096; //y scale delta for some reason. usually 0
+ obj.unknown25 = view.getUint32(off+0x54, true); //FFFFFFFF makes run at half framerate. idk? usually 0
+ off += 0x58;
+
+ if ((obj.flag & ParticleFlags.ScaleAnim) != 0)
+ {
+ obj.scaleAnim = [];
+ //1.000 (doesn't seem to do anything important, but occasionally is between start and end)
+ //start scale
+ //end scale
+ //???? (seems to affect the interpolation. cubic params?)
+ //flags (1: random scale for one frame? everything above it might be cubic params)
+ //???? (0x4B)
+
+ obj.scaleAnim = {
+ unkBase: view.getUint16(off, true)/4096,
+ scaleFrom: view.getUint16(off+2, true)/4096,
+ scaleTo: view.getUint16(off+4, true)/4096,
+ fromZeroTime: view.getUint8(off+6, true)/0xFF, //time to dedicate to an animation from zero size
+ holdTime: view.getUint8(off+7, true)/0xFF, //time to dedicate to holding state at the end.
+ flagParam: view.getUint16(off+8, true),
+ unk4b: view.getUint16(off+10, true),
+ };
+ off += 12;
+ }
+ if ((obj.flag & ParticleFlags.ColorAnimation) != 0)
+ {
+ obj.colorAnim = {
+ colorFrom: view.getUint16(off, true), //color from
+ colorTo: view.getUint16(off+2, true), //color to (seems to be same as base color)
+ framePct: view.getUint16(off+4, true), //frame pct to become color to (FFFF means always from, 8000 is about the middle)
+ unknown: view.getUint16(off+6, true), //unknown, 00FF for fire?
+ flags: view.getUint32(off+8, true), //flags (1: binary select color, 4: smooth blend)
+ };
+ off += 12;
+ }
+ if ((obj.flag & ParticleFlags.OpacityAnimation) != 0)
+ {
+ //opacity
+
+ //intensity x2 (0FFF to 0000. smoke is 0bff. 1000 breaks it, i'm assuming it pushes opacity from 1f to 20 (overflow to 0))
+ //random flicker
+ //unknown (negative byte breaks it)
+ //startfade x2
+ //cubic param? x2
+ obj.opacityAnim = {
+ intensity: view.getUint16(off, true),
+ random: view.getUint8(off+2, true),
+ unk: view.getUint8(off+3, true),
+ startFade: view.getUint16(off+4, true), //0-FFFF. seems to be the pct of duration where the anim starts.
+ param: view.getUint16(off+6, true),
+ }
+ off += 8;
+ }
+ if ((obj.flag & ParticleFlags.TextureAnimation) != 0)
+ {
+ var textures = [];
+ for (var i=0; i<8; i++) textures[i] = view.getUint8(off+i);
+ obj.texAnim = {
+ textures: textures,
+ frames: view.getUint8(off+8),
+ unknown1: view.getUint8(off+9), //128 - duration of particle. 37 - blue spark? (7 frames for 7 duration effect)
+ unknown2: view.getUint16(off+10, true), //1 - random frame?
+ }
+ off += 12;
+ }
+ if ((obj.flag & ParticleFlags.Bit16) != 0)
+ {
+ obj.Bit16 = [];
+ for (var i=0; i<20; i++) obj.Bit16[i] = view.getUint8(off+i);
+ off += 20;
+ }
+ if ((obj.flag & ParticleFlags.Gravity) != 0)
+ {
+
+ //gravity
+ //x wind
+ //gravity (signed 16, -1 is down, leaves are FFEA (-22/4096))
+ //z wind
+ //pad?
+ obj.gravity = [
+ view.getInt16(off, true)/4096,
+ view.getInt16(off+2, true)/4096,
+ view.getInt16(off+4, true)/4096,
+ view.getInt16(off+6, true)/4096, //pad, should be ignored by vec3 ops
+ ];
+
+ off += 8;
+ }
+ if ((obj.flag & ParticleFlags.Bit25) != 0)
+ {
+ //seems to be 4 int 16s typically in some kind of pattern.
+ obj.Bit25 = [];
+ for (var i=0; i<8; i++) obj.Bit25[i] = view.getUint8(off+i);
+ off += 8;
+ }
+ if ((obj.flag & ParticleFlags.Bit26) != 0)
+ {
+ obj.Bit26 = [];
+ for (var i=0; i<16; i++) obj.Bit26[i] = view.getUint8(off+i);
+ off += 16;
+ }
+ if ((obj.flag & ParticleFlags.Bit27) != 0)
+ {
+ obj.Bit27 = [];
+ for (var i=0; i<4; i++) obj.Bit27[i] = view.getUint8(off+i);
+ off += 4;
+ }
+ if ((obj.flag & ParticleFlags.Bit28) != 0)
+ {
+ obj.Bit28 = [];
+ for (var i=0; i<8; i++) obj.Bit28[i] = view.getUint8(off+i);
+ off += 8;
+ }
+ if ((obj.flag & ParticleFlags.Bit29) != 0)
+ {
+ obj.Bit29 = [];
+ for (var i=0; i<16; i++) obj.Bit29[i] = view.getUint8(off+i);
+ off += 16;
+ }
+
+ obj.nextOff = off;
+ return obj;
+ }
+
+ function readParticleTexture(view, off) {
+ var obj = {};
+ obj.stamp = readChar(view, off+0x0)+readChar(view, off+0x1)+readChar(view, off+0x2)+readChar(view, off+0x3);
+ if (obj.stamp != " TPS") throw "SPT invalid (particle texture in SPA). Expected ' TPS', found "+obj.stamp;
+
+ var flags = view.getUint16(off+4, true);
+ obj.info = {
+ pal0trans: true,//z(flags>>3)&1, //weirdly different format
+ format: ((flags)&7),
+ height: 8 << ((flags>>8)&0xF),
+ width: 8 << ((flags>>4)&0xF),
+ repeatX: (flags>>12)&1,
+ repeatY: (flags>>13)&1,
+ flipX: (flags>>14)&1,
+ flipY: (flags>>15)&1,
+ }
+ obj.flags = flags;
+
+ obj.unknown = view.getUint16(off+6, true);
+ obj.texDataLength = view.getUint32(off+8, true);
+ obj.palOff = view.getUint32(off+0xC, true);
+ obj.palDataLength = view.getUint32(off+0x10, true);
+ obj.unknown2 = view.getUint32(off+0x14, true);
+ obj.unknown3 = view.getUint32(off+0x18, true);
+ obj.unknown4 = view.getUint32(off+0x1C, true);
+
+ obj.texData = view.buffer.slice(off+32, off+32+obj.texDataLength);
+ off += 32+obj.texDataLength;
+ obj.palData = view.buffer.slice(off, off+obj.palDataLength);
+
+ obj.nextOff = off+obj.palDataLength;
+
+ //var test = readTexWithPal(obj.info, obj);
+ //document.body.appendChild(test);
+
+ return obj;
+ }
+
+
+//modified from NSBTX.js - should probably refactor to use be generic between both
+
+ function readTexWithPal(tex, data) {
+ var format = tex.format;
+ var trans = tex.pal0trans;
+
+ if (format == 5) return readCompressedTex(tex, data); //compressed 4x4 texture, different processing entirely
+
+ var off = 0;//tex.texOffset;
+ var palView = new DataView(data.palData);
+ var texView = new DataView(data.texData);
+ var palOff = 0;//pal.palOffset;
+
+ var canvas = document.createElement("canvas");
+ canvas.width = tex.width;
+ canvas.height = tex.height;
+ var ctx = canvas.getContext("2d");
+ var img = ctx.getImageData(0, 0, tex.width, tex.height);
+
+ var total = tex.width*tex.height;
+ var databuf;
+ for (var i=0; i>5)*(255/7);
+ premultiply(col);
+
+ } else if (format == 2) { //2 bit pal
+ if (i%4 == 0) databuf = texView.getUint8(off++);
+ col = readPalColour(palView, palOff, (databuf>>((i%4)*2))&3, trans)
+
+ } else if (format == 3) { //4 bit pal
+ if (i%2 == 0) {
+ databuf = texView.getUint8(off++);
+ col = readPalColour(palView, palOff, databuf&15, trans)
+ } else {
+ col = readPalColour(palView, palOff, databuf>>4, trans)
+ }
+
+ } else if (format == 4) { //8 bit pal
+ col = readPalColour(palView, palOff, texView.getUint8(off++), trans)
+
+ } else if (format == 6) { //A5I3 encoding. 5 bits alpha 3 bits pal index
+ 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);
+ colourBuffer[0] = Math.round(((col&31)/31)*255)
+ colourBuffer[1] = Math.round((((col>>5)&31)/31)*255)
+ colourBuffer[2] = Math.round((((col>>10)&31)/31)*255)
+ colourBuffer[3] = Math.round((col>>15)*255);
+ col = colourBuffer;
+ premultiply(col);
+ off += 2;
+
+ } else {
+ console.log("texture format is none, ignoring")
+ return canvas;
+ }
+ img.data.set(col, i*4);
+ }
+ ctx.putImageData(img, 0, 0)
+ 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;
+ var texView = new DataView(compData); //real texture data - 32 bits per 4x4 block (one byte per 4px horizontal line, each descending 1px)
+ var compView = new DataView(compInfoData); //view into compression info - informs of pallete and parameters.
+ var palView = new DataView(data.palData); //view into the texture pallete
+ var compOff = off/2; //info is 2 bytes per block, so the offset is half that of the tex offset.
+ var palOff = 0;//pal.palOffset;
+ var transColor = new Uint8Array([0, 0, 0, 0]); //transparent black
+
+ var canvas = document.createElement("canvas");
+ canvas.width = tex.width;
+ canvas.height = tex.height;
+ var ctx = canvas.getContext("2d");
+ var img = ctx.getImageData(0, 0, tex.width, tex.height);
+
+ var w = tex.width>>2; //iterate over blocks, block w and h is /4.
+ var h = tex.height>>2;
+
+ for (var y=0; y> 14) & 3);
+
+ var finalPo = palOff+addr*4;
+ var imgoff = x*4+(y*w*16);
+ for (var iy=0; iy<4; iy++) {
+ var dat = texView.getUint8(off++);
+ for (var ix=0; ix<4; ix++) { //iterate over horiz lines
+ var part = (dat>>(ix*2))&3;
+ var col;
+
+ switch (mode) {
+ case 0: //value 3 is transparent, otherwise pal colour
+ if (part == 3) col = transColor;
+ else col = readPalColour(palView, finalPo, part);
+ break;
+ case 1: //average mode - colour 2 is average of 1st two, 3 is transparent. 0&1 are normal.
+ if (part == 3) col = transColor;
+ else if (part == 2) col = readFractionalPal(palView, finalPo, 0.5);
+ else col = readPalColour(palView, finalPo, part);
+ break;
+ case 2: //pal colour
+ col = readPalColour(palView, finalPo, part);
+ break;
+ case 3: //5/8 3/8 mode - colour 2 is 5/8 of col0 plus 3/8 of col1, 3 is 3/8 of col0 plus 5/8 of col1. 0&1 are normal.
+ if (part == 3) col = readFractionalPal(palView, finalPo, 3/8);
+ else if (part == 2) col = readFractionalPal(palView, finalPo, 5/8);
+ else col = readPalColour(palView, finalPo, part);
+ break;
+ }
+
+ img.data.set(col, (imgoff++)*4)
+ }
+ imgoff += tex.width-4;
+ }
+ compOff += 2; //align off to next block
+ }
+ }
+
+ ctx.putImageData(img, 0, 0)
+ return canvas;
+ }
+
+ function readPalColour(view, palOff, ind, pal0trans) {
+ var col = view.getUint16(palOff+ind*2, true);
+ var f = 255/31;
+ colourBuffer[0] = Math.round((col&31)*f)
+ colourBuffer[1] = Math.round(((col>>5)&31)*f)
+ colourBuffer[2] = Math.round(((col>>10)&31)*f)
+ colourBuffer[3] = (pal0trans && ind == 0)?0:255;
+ return colourBuffer;
+ }
+
+ function readFractionalPal(view, palOff, i) {
+ var col = view.getUint16(palOff, true);
+ var col2 = view.getUint16(palOff+2, true);
+ var ni = 1-i;
+ var f = 255/31;
+ colourBuffer[0] = Math.round((col&31)*f*i + (col2&31)*f*ni)
+ colourBuffer[1] = Math.round(((col>>5)&31)*f*i + ((col2>>5)&31)*f*ni)
+ colourBuffer[2] = Math.round(((col>>10)&31)*f*i + ((col2>>10)&31)*f*ni)
+ colourBuffer[3] = 255;
+ return colourBuffer;
+ }
+
+ //end NSBTX
+
+
+ function readChar(view, offset) {
+ return String.fromCharCode(view.getUint8(offset));
+ }
+
+ if (input != null) {
+ load(input);
+ }
}
\ No newline at end of file
diff --git a/code/render/nitroAnimator.js b/code/render/nitroAnimator.js
index 63c743a..cbe9208 100644
--- a/code/render/nitroAnimator.js
+++ b/code/render/nitroAnimator.js
@@ -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 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;
}
diff --git a/code/render/nitroRender.js b/code/render/nitroRender.js
index ff8d368..0344b9c 100644
--- a/code/render/nitroRender.js
+++ b/code/render/nitroRender.js
@@ -223,6 +223,8 @@ window.nitroRender = new function() {
this.init = function(ctx) {
gl = ctx;
this.gl = gl;
+ this.billboardMat = mat4.create();
+ this.yBillboardMat = mat4.create();
shaders = nitroShaders.compileShaders(gl);
@@ -233,7 +235,7 @@ window.nitroRender = new function() {
this.prepareShader = function() {
//prepares the shader so no redundant calls have to be made. Should be called upon every program change.
gl.enable(gl.BLEND);
- gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
this.last = {};
gl.activeTexture(gl.TEXTURE0);
gl.uniform1i(this.nitroShader.samplerUniform, 0);
@@ -261,6 +263,12 @@ window.nitroRender = new function() {
this.prepareShader();
}
+ this.setShadBias = function(bias) {
+ var shader = shaders[1];
+ gl.uniform1f(shader.shadOffUniform, bias);
+ gl.uniform1f(shader.farShadOffUniform, bias);
+ }
+
this.resetShadOff = function() {
var shader = shaders[1];
gl.uniform1f(shader.shadOffUniform, 0.00005+((mobile)?0.0005:0));
@@ -372,7 +380,7 @@ window.nitroRender = new function() {
};
-function nitroModel(bmd, btx, remap) {
+function nitroModel(bmd, btx) {
var bmd = bmd;
this.bmd = bmd;
var thisObj = this;
@@ -399,7 +407,9 @@ function nitroModel(bmd, btx, remap) {
this.draw = draw;
this.drawPoly = externDrawPoly;
this.drawModel = externDrawModel;
+ this.getBoundingCollisionModel = getBoundingCollisionModel;
this.getCollisionModel = getCollisionModel;
+ this.baseMat = mat4.create();
modelBuffers = []
this.modelBuffers = modelBuffers;
@@ -409,10 +419,6 @@ function nitroModel(bmd, btx, remap) {
matBuf.push({built: false, dat: new Float32Array(31*16)});
}
- if (remap != null) {
- setTextureRemap(remap);
- }
-
if (btx != null) {
loadTexture(btx);
} else if (bmd.tex != null) {
@@ -461,41 +467,47 @@ function nitroModel(bmd, btx, remap) {
var model = models[j];
var mat = model.materials.objectData
for (var i=0; i max[j]) max[j] = tri[j];
+ }
+ }
+ //create the bounding box
+ out = [
+ { //top
+ Vertices: [[max[0], max[1], max[2]], [max[0], max[1], min[2]], [min[0], max[1], min[2]]],
+ Normal: [0, 1, 0]
+ },
+ {
+ Vertices: [[min[0], max[1], min[2]], [min[0], max[1], max[2]], [max[0], max[1], max[2]]],
+ Normal: [0, 1, 0]
+ },
+
+ { //bottom
+ Vertices: [[min[0], min[1], min[2]], [max[0], min[1], min[2]], [max[0], min[1], max[2]] ],
+ Normal: [0, -1, 0]
+ },
+ {
+ Vertices: [[max[0], min[1], max[2]], [min[0], min[1], max[2]], [min[0], min[1], min[2]] ],
+ Normal: [0, -1, 0]
+ },
+
+ { //back (farthest z)
+ Vertices: [[max[0], max[1], max[2]], [max[0], min[1], max[2]], [min[0], min[1], max[2]]],
+ Normal: [0, 0, 1]
+ },
+ {
+ Vertices: [[min[0], min[1], max[2]], [min[0], max[1], max[2]], [max[0], max[1], max[2]]],
+ Normal: [0, 0, 1]
+ },
+
+ { //front (closest z)
+ Vertices: [[min[0], min[1], min[2]], [max[0], min[1], min[2]], [max[0], max[1], min[2]]],
+ Normal: [0, 0, -1]
+ },
+ {
+ Vertices: [[max[0], max[1], min[2]], [min[0], max[1], min[2]], [min[0], min[1], min[2]]],
+ Normal: [0, 0, -1]
+ },
+
+ { //right (pos x)
+ Vertices: [[max[0], max[1], max[2]], [max[0], min[1], max[2]], [max[0], min[1], min[2]]],
+ Normal: [1, 0, 0]
+ },
+ {
+ Vertices: [[max[0], min[1], min[2]], [max[0], max[1], min[2]], [max[0], max[1], max[2]]],
+ Normal: [1, 0, 0]
+ },
+
+ { //left (neg x)
+ Vertices: [[-max[0], min[1], min[2]], [-max[0], min[1], max[2]], [-max[0], max[1], max[2]]],
+ Normal: [-1, 0, 0]
+ },
+ {
+ Vertices: [[-max[0], max[1], max[2]], [-max[0], max[1], min[2]], [-max[0], min[1], min[2]]],
+ Normal: [-1, 0, 0]
+ },
+ ]
+ out.push()
+ return {dat:out, scale:model.head.scale};
+ }
+
+ function getCollisionModel(modelind, polyind, colType) { //simple func to get collision model for a model. used when I'm too lazy to define my own... REQUIRES TRI MODE ACTIVE!
if (collisionModel[modelind] == null) collisionModel[modelind] = [];
if (collisionModel[modelind][polyind] != null) return collisionModel[modelind][polyind];
var model = bmd.modelData.objectData[modelind];
@@ -617,14 +707,16 @@ function nitroModel(bmd, btx, remap) {
var off = 0;
for (var i=0; i=0; i--) {
if (offFrame >= anim.frames[i].time) {
+ loadMatTex(model.materials.objectData[pmat], btx == null ? bmd.tex : btx, anim.frames[i]);
+ /*
tex[pmat] = cacheTex(btx == null ? bmd.tex : btx, anim.frames[i].tex, anim.frames[i].mat, model.materials.objectData[pmat]);
+ */
+ break;
}
}
}
@@ -692,7 +788,7 @@ function nitroModel(bmd, btx, remap) {
}
var material = model.materials.objectData[pmat];
- nitroRender.setAlpha(material.alpha)
+ nitroRender.setAlpha(material.alpha);
if (texAnim != null) {
//generate and send texture matrix from data
@@ -703,9 +799,7 @@ function nitroModel(bmd, btx, remap) {
if (animNum != -1) {
//we got a match! it's wonderful :')
var anim = anims.objectData[animNum];
- var mat = mat3.create(); //material texture mat is ignored
- mat3.scale(mat, mat, [anim.scaleS[(texFrame>>anim.frameStep.scaleS)%anim.scaleS.length], anim.scaleT[(texFrame>>anim.frameStep.scaleT)%anim.scaleT.length]]);
- mat3.translate(mat, mat, [-anim.translateS[(texFrame>>anim.frameStep.translateS)%anim.translateS.length], anim.translateT[(texFrame>>anim.frameStep.translateT)%anim.translateT.length]]) //for some mystery reason I need to negate the S translation
+ var mat = matAtFrame(texFrame, anim);
gl.uniformMatrix3fv(shader.texMatrixUniform, false, mat);
} else {
gl.uniformMatrix3fv(shader.texMatrixUniform, false, material.texMat);
@@ -717,24 +811,52 @@ function nitroModel(bmd, btx, remap) {
drawModelBuffer(modelBuffers[modelind][polyind], gl, shader);
}
+ function frameLerp(frame, step, values) {
+ if (values.length == 1) return values[0];
+ var i = (frame / (1 << step)) % 1;
+ var len = values.length
+ if (step > 0) len -= 1;
+ var frame1 = (frame>>step)%len;
+ var from = values[frame1];
+ var to = values[frame1+1] || values[frame1];
+ return to * i + from * (1-i);
+ }
+
+ function matAtFrame(frame, anim) {
+ var mat = mat3.create(); //material texture mat is ignored
+
+ mat3.scale(mat, mat, [frameLerp(frame, anim.frameStep.scaleS, anim.scaleS), frameLerp(frame, anim.frameStep.scaleT, anim.scaleT)]);
+ mat3.translate(mat, mat, [-frameLerp(frame, anim.frameStep.translateS, anim.translateS), frameLerp(frame, anim.frameStep.translateT, anim.translateT)]);
+
+ return mat;
+ }
+
function generateMatrixStack(model, targ) { //this generates a matrix stack with the default bones. use nitroAnimator to pass custom matrix stacks using nsbca animations.
var matrices = [];
var objs = model.objects.objectData;
var cmds = model.commands;
- var curMat = mat4.create();
+ var curMat = mat4.clone(thisObj.baseMat);
var lastStackID = 0;
+ var highestUsed = -1;
for (var i=0; i highestUsed) highestUsed = lastStackID;
} else {
matrices[lastStackID] = mat4.clone(curMat);
}
@@ -742,10 +864,14 @@ function nitroModel(bmd, btx, remap) {
model.lastStackID = lastStackID;
+ var scale = [model.head.scale, model.head.scale, model.head.scale];
targ.set(matBufEmpty);
var off=0;
- for (var i=0; i<31; i++) {
- if (matrices[i] != null) targ.set(matrices[i], off);
+ for (var i=0; i<=highestUsed; i++) {
+ if (matrices[i] != null) {
+ mat4.scale(matrices[i], matrices[i], scale);
+ targ.set(matrices[i], off);
+ }
off += 16;
}
diff --git a/code/render/nitroShaders.js b/code/render/nitroShaders.js
index 2ecd22a..7ee4a0b 100644
--- a/code/render/nitroShaders.js
+++ b/code/render/nitroShaders.js
@@ -113,7 +113,8 @@ window.nitroShaders = new (function() {
}\n\
\n\
void main(void) {\n\
- vec4 col = texture2D(uSampler, vTextureCoord)*color;\n\
+ vec4 colorPM = vec4(color.rgb * color.a, color.a);\n\
+ vec4 col = texture2D(uSampler, vTextureCoord)*colorPM;\n\
\n\
vec2 ldNorm = abs((lightDist.xy)-vec2(0.5, 0.5));\n\
float dist = max(ldNorm.x, ldNorm.y);\n\
diff --git a/code/ui/race3DUI.js b/code/ui/race3DUI.js
index f8d5ed5..e7d15de 100644
--- a/code/ui/race3DUI.js
+++ b/code/ui/race3DUI.js
@@ -32,22 +32,25 @@ window.Race3DUI = function(scene, type, animStart) {
var params = {
"count": [ //offset 21 down
- -128/1024, 128/1024, -(192-11)/1024, 11/1024
+ -128/1024, 128/1024, -(96)/1024, 96/1024
],
"start": [ //offset 86 up
- -128/1024, 128/1024, -(192+66)/1024, -66/1024
+ -128/1024, 128/1024, -(96)/1024, 96/1024
],
"goal": [ //why are these all so different?
- -128/1024, 128/1024, -(512 + 64)/1024, -(512 - 128)/1024
+ -128/1024, 128/1024, -(96)/1024, 96/1024
+ //-128/1024, 128/1024, -(512 + 64)/1024, -(512 - 128)/1024
],
//animations seem completely broken for these two (quickly files off screen after start)
//right now the vertical range of the viewport is large to try figure out where the hell it's going?
"win": [
- -128/1024, 128/1024, -(1024)/1024, 1024/1024
+ -128/1024, 128/1024, -(96)/1024, 96/1024
+ //-128/1024, 128/1024, -(1024)/1024, 1024/1024
],
"lose": [
- -128/1024, 128/1024, -(1024)/1024, 1024/1024
+ -128/1024, 128/1024, -(96)/1024, 96/1024
+ //-128/1024, 128/1024, -(1024)/1024, 1024/1024
],
}
diff --git a/index.html b/index.html
index d112d77..030ee08 100644
--- a/index.html
+++ b/index.html
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file