// // kart.js //-------------------- // Entity type for karts. // by RHY3756547 // // includes: gl-matrix.js (glMatrix 2.0) // /formats/kcl.js // window.Kart = function(pos, angle, speed, kartN, charN, controller, scene) { var k = this; var minimumMove = 0.05; var MAXSPEED = 24; var BOOSTTIME = 90; var kartSoundBase = 170; var COLBOUNCE_TIME = 20; var COLBOUNCE_STRENGTH = 4; var params = scene.gameRes.kartPhys.karts[kartN]; var offsets = scene.gameRes.kartOff.karts[kartN]; this.wheelClass = (offsets.name[10] == "L")?2:((offsets.name[10] == "M")?1:0); this.local = controller.local; this.active = true; this.preboost = true; //supplimentary controllers this.items = new KartItems(this, scene); this.soundProps = {}; this.pos = pos; this.angle = angle; this.vel = vec3.create(); this.weight = params.weight; this.params = params; this.kartBounce = kartBounce; this.speed = speed; this.drifting = false; this.driftMode = 0; //1 left, 2 right, 0 undecided this.driftLanded = false; //if we haven't landed then apply a constant turn. //powerslide info: to advance to the next mode you need to hold the same button for 10 or more frames. Mode 0 starts facing drift direction, 1 is other way, 2 is returning (mini spark), 3 is other way, 4 is returning (turbo spark) this.driftPSTick = 0; this.driftPSMode = 0; this.kartTargetNormal = [0, 1, 0]; this.kartNormal = [0, 1, 0]; this.airTime = 0; this.controller = controller; this.driftOff = 0; this.physicalDir = angle; this.mat = mat4.create(); this.basis = mat4.create(); this.ylock = 0; this.cannon = null; this.gravity = [0, -0.17, 0]; //100% confirmed by me messing around with the gravity value in mkds. for sticky surface and loop should modify to face plane until in air this.update = update; this.sndUpdate = sndUpdate; this.draw = draw; this.drawKart = drawKart; this.drawWheels = drawWheels; this.drawChar = drawChar; 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; this.kartColVel = vec3.create(); this.kartColTimer = 0; this.kartWallTimer = 0; this.charSoundTimer = 0; this.placement = 0; this.lastPlacement = 0; var charRes = scene.gameRes.getChar(charN); var kartRes = scene.gameRes.getKart(kartN); var kartPolys = []; var kObj = kartRes.bmd.modelData.objectData[0]; for (var i=0; i1) { k.wheelParticles[i-2].offset = vec3.scale(k.wheelParticles[i-2].offset, vec3.add(k.wheelParticles[i-2].offset, offsets.wheels[i], [k.wheelClass*(i-2.5)*-2, (-params.colRadius)-k.wheelClass*2, 0]), 1/16); } } var scale = 16; var pos = vec3.clone(offsets.chars[charN]); if (groundAnim != -1) pos[1] *= charGroundAnim[groundAnim]; var cmat = mat4.translate(drawMat.character, mat, vec3.scale([], pos, 16)) mat4.scale(cmat, cmat, [scale, scale, scale]); if (k.animMode == "drive") k.animMat = k.anim.setFrame(0, 0, k.driveAnimF); else k.animMat = k.anim.setFrame(0, 0, k.animFrame++); updateMat = false; } function drawChar(view, pMatrix) { charRes.model.draw(drawMat.character, pMatrix, k.animMat); } function drawKart(view, pMatrix, gl) { if (updateMat) recalcMat(view); //if we're in simple shadows mode, draw the kart's stencil shadow. if (false) { //gl.enable(gl.CULL_FACE); //culling is fun! gl.clear(gl.STENCIL_BUFFER_BIT); //gl.cullFace(gl.FRONT); gl.colorMask(false, false, false, false); gl.depthMask(false); gl.enable(gl.STENCIL_TEST); gl.stencilMask(0xFF); gl.stencilFunc(gl.ALWAYS, 1, 0xFF); gl.stencilOp(gl.KEEP, gl.INCR, gl.KEEP); kartRes.shadVol.draw(drawMat.kart, pMatrix, simpleMatStack); gl.colorMask(true, true, true, true) //gl.cullFace(gl.BACK); gl.stencilFunc(gl.LESS , 0, 0xFF); gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); kartRes.shadVol.draw(drawMat.kart, pMatrix, simpleMatStack); gl.disable(gl.STENCIL_TEST); //gl.disable(gl.CULL_FACE); gl.depthMask(true); } for (var i=0; i= hitGroundAnim.length) groundAnim = -1; } onGround = (k.airTime < 5); kartAnim = (kartAnim+1)%8; var input = k.controller.fetchInput(); k.lastInput = input; k.items.update(input); if (input.turn > 0.3) { if (k.driveAnimF > 0) k.driveAnimF--; } else if (input.turn < -0.3) { if (k.driveAnimF < 28) k.driveAnimF++; } else { if (k.driveAnimF > 14) k.driveAnimF--; else if (k.driveAnimF < 14) k.driveAnimF++; } //update sounds var newSoundMode = soundMode; if (input.accel) { if (soundMode == 0 || soundMode == 6) newSoundMode = 2; if (soundMode == 4) newSoundMode = 3; } else { if (soundMode != 0) { if (soundMode == 2 || soundMode == 3) newSoundMode = 4; if (newSoundMode == 4 && k.speed < 0.5) newSoundMode = 0; } } if (k.boostMT+k.boostNorm > 0) { if (k.boostNorm == BOOSTTIME || k.boostMT == params.miniTurbo) { if (sounds.boostSoundTrig) { if (sounds.boost != null) nitroAudio.instaKill(sounds.boost); sounds.boost = nitroAudio.playSound(160, {}, 0, k); sounds.boost.gainN.gain.value = 2; sounds.boostSoundTrig = false; } } else { sounds.boostSoundTrig = true; } } else if (sounds.boost != null) { nitroAudio.kill(sounds.boost); sounds.boost = null; } var isMoving = onGround && k.speed > 0.5; if (isMoving) { if (lastCollided != sounds.lastTerrain || lastBE != sounds.lastBE || sounds.drive == null) { if (sounds.drive != null) nitroAudio.kill(sounds.drive); if (lastColSounds.drive != null) { sounds.drive = nitroAudio.playSound(lastColSounds.drive, {}, 0, k); sounds.drive.gainN.gain.value = 2; } } if (k.drifting && k.driftLanded) { if (lastCollided != sounds.lastTerrain || lastBE != sounds.lastBE || sounds.drift == null) { if (sounds.drift != null) nitroAudio.kill(sounds.drift); if (lastColSounds.drift != null) { sounds.drift = nitroAudio.playSound(lastColSounds.drift, {}, 0, k); } } } else if (sounds.drift != null) { nitroAudio.kill(sounds.drift); sounds.drift = null; } sounds.lastTerrain = lastCollided; sounds.lastBE = lastBE; } else { if (sounds.drift != null) { nitroAudio.kill(sounds.drift); sounds.drift = null; } if (sounds.drive != null) { nitroAudio.kill(sounds.drive); sounds.drive = null; } } k.wheelParticles[0].pause = !isMoving; k.wheelParticles[1].pause = !isMoving; //end sound update if (k.preboost) { } else if (k.cannon != null) { //when cannon is active, we fly forward at max move speed until we get to the cannon point. var c = scene.nkm.sections["KTPC"].entries[k.cannon]; if (c.id1 != -1 && c.id2 != -1) { var c2 = scene.nkm.sections["KTPC"].entries[c.id2]; c = c2; var mat = mat4.create(); mat4.rotateY(mat, mat, c.angle[1]*(Math.PI/180)); 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,32], mat)); k.airTime = 4; k.physicalDir = (180-c2.angle[1])*(Math.PI/180); k.angle = k.physicalDir; k.cannon = null; } else { var mat = mat4.create(); mat4.rotateY(mat, mat, c.angle[1]*(Math.PI/180)); if (true) { //vertical angle from position? airship fortress is impossible otherwise //var c2 = scene.nkm.sections["KTPC"].entries[c.id2]; var diff = vec3.sub([], c.pos, k.pos); var dAdj = Math.sqrt(diff[0]*diff[0] + diff[2]*diff[2]); var dHyp = Math.sqrt(diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2]); mat4.rotateX(mat, mat, ((diff[1] > 0) ? -1 : 1) * Math.acos(dAdj/dHyp)); } else { mat4.rotateX(mat, mat, c.angle[0]*(-Math.PI/180)); } var forward = [0, 0, 1]; var up = [0, 1, 0]; k.vel = vec3.scale([], vec3.transformMat4(forward, forward, mat), MAXSPEED); k.speed = Math.min(k.speed+1, MAXSPEED); vec3.add(k.pos, k.pos, k.vel); k.physicalDir = (180-c.angle[1])*(Math.PI/180); k.angle = k.physicalDir; k.kartTargetNormal = vec3.transformMat4(up, up, mat); k.airTime = 0; var planeConst = -vec3.dot(c.pos, forward); var cannonDist = vec3.dot(k.pos, forward) + planeConst; if (cannonDist > 0) { k.cannon = null; //leaving cannon state k.speed = params.topSpeed; k.vel = vec3.scale([], vec3.transformMat4(forward, forward, mat), k.speed); } } } else { //default kart mode if (k.OOB > 0) { playCharacterSound(0); var current = checkpoints[k.checkPointNumber]; var respawn; if (current == null) respawn = respawns[(Math.random() * respawns.length) | 0]; //todo: deterministic else respawn = respawns[current.respawn]; k.physicalDir = (180-respawn.angle[1])*(Math.PI/180); k.angle = k.physicalDir; k.speed = 0; k.vel = vec3.create(); k.pos = vec3.clone(respawn.pos); vec3.add(k.pos, k.pos, [0,16,0]); if (k.controller.setRouteID != null) k.controller.setRouteID(respawn.id1); k.OOB = 0; } var groundEffect = 0; if (lastCollided != -1) { groundEffect = MKDS_COLTYPE.PHYS_MAP[lastCollided]; if (groundEffect == null) groundEffect = 0; } var effect = params.colParam[groundEffect]; var top = params.topSpeed*effect.topSpeedMul; //if you let go of accel, drift ends anyway, so always accel in drift. var boosting = (k.boostNorm + k.boostMT)>0; if (k.specialControlHandler != null) k.specialControlHandler(); else { if (boosting) { var top2 if (k.boostNorm>0){ 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 endDrift(); if (k.driftPSMode == 3) { k.boostMT = params.miniTurbo; } k.driftPSMode = 0; k.driftPSTick = 0; } if (k.driftMode == 0) { if (input.turn > 0.30) { k.driftMode = 2; } else if (input.turn < -0.30) { k.driftMode = 1; } } else { if (k.driftLanded) { var change = (((k.driftMode-1.5)*Math.PI/1.5)-k.driftOff)*0.05; k.driftOff += change; k.physicalDir -= change; } //if we're above the initial y position, add a constant turn with a period of 180 frames. if (!k.driftLanded && k.ylock>=0) { k.physicalDir += (Math.PI*2/180)*(k.driftMode*2-3); } } 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 (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.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; } } } } k.physicalDir = fixDir(k.physicalDir); if (k.driftOff != 0 && (!k.drifting || !k.driftLanded)) { if (k.driftOff > 0) { k.physicalDir += params.driftOffRestore; k.driftOff -= params.driftOffRestore; if (k.driftOff < 0) k.driftOff = 0; } else { k.physicalDir -= params.driftOffRestore; k.driftOff += params.driftOffRestore; if (k.driftOff > 0) k.driftOff = 0; } } checkKartCollision(scene); if (!onGround) { this.kartTargetNormal = [0, 1, 0]; if (k.physBasis != null) vec3.transformMat4(this.kartTargetNormal,this.kartTargetNormal,k.physBasis.mat); vec3.add(k.vel, k.vel, k.gravity) if (k.ylock >= 0) { ylvel += k.gravity[1]; k.ylock += ylvel; } /* if (k.kartColTimer == COLBOUNCE_TIME) { vec3.add(k.vel, k.vel, k.kartColVel); } */ } else { k.angle += dirDiff(k.physicalDir, k.angle)*effect.handling; k.angle += dirDiff(k.physicalDir, k.angle)*effect.handling; //applying this twice appears to be identical to the original k.angle = fixDir(k.angle); //reduce our forward speed by how much of our velocity is not going forwards var factor = Math.sin(k.physicalDir)*Math.sin(k.angle) + Math.cos(k.physicalDir)*Math.cos(k.angle); k.speed *= 1 - ((1-factor) * (1 - k.params.decel)); //var reducedSpeed = k.vel[0]*Math.sin(k.angle) + k.vel[2]*(-Math.cos(k.angle)); //reducedSpeed = ((reducedSpeed < 0) ? -1 : 1) * Math.sqrt(Math.abs(reducedSpeed)); k.vel[1] += k.gravity[1]; k.vel = [Math.sin(k.angle)*k.speed, k.vel[1], -Math.cos(k.angle)*k.speed] //k.speed = reducedSpeed; /* if (k.kartColTimer > 0) { vec3.add(k.vel, k.vel, vec3.scale([], k.kartColVel, k.kartColTimer/10)) } */ } if (k.kartColTimer > 0) k.kartColTimer--; if (k.kartWallTimer > 0) k.kartWallTimer--; if (k.charSoundTimer > 0) k.charSoundTimer--; wheelTurn += k.speed/16; wheelTurn = fixDir(wheelTurn); k.airTime++; //end kart controls //move kart on moving platforms (no collision, will be corrected by next step) if (stuckTo != null) { if (stuckTo.moveWith != null) stuckTo.moveWith(k); stuckTo = null; } //move kart. var steps = 0; var remainingT = 1; var baseVel = k.vel; if (k.physBasis != null) { if (k.physBasis.time-- < 0) exitBasis(); else { baseVel = vec3.transformMat4([], baseVel, k.physBasis.mat); k.vel[1] = -1; } } var velSeg = vec3.clone(baseVel); if (k.kartColTimer > 0) { vec3.add(velSeg, velSeg, vec3.scale([], k.kartColVel, k.kartColTimer/COLBOUNCE_TIME)); } var posSeg = vec3.clone(k.pos); var ignoreList = []; while (steps++ < 10 && remainingT > 0.01) { var result = lsc.sweepEllipse(posSeg, velSeg, scene, [params.colRadius, params.colRadius, params.colRadius], ignoreList); if (result != null) { colResponse(posSeg, velSeg, result, ignoreList) remainingT -= result.t; if (remainingT > 0.01) { if (k.physBasis != null) { baseVel = vec3.transformMat4([], k.vel, k.physBasis.mat); } velSeg = vec3.scale(vec3.create(), baseVel, remainingT); } } else { vec3.add(posSeg, posSeg, velSeg); remainingT = 0; } } k.pos = posSeg; } //interpolate visual normal roughly to target var rate = onGround?0.15:0.025; k.kartNormal[0] += (k.kartTargetNormal[0]-k.kartNormal[0])*rate; k.kartNormal[1] += (k.kartTargetNormal[1]-k.kartNormal[1])*rate; k.kartNormal[2] += (k.kartTargetNormal[2]-k.kartNormal[2])*rate; vec3.normalize(k.kartNormal, k.kartNormal); k.basis = buildBasis(); 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), {}) } updateKartSound(newSoundMode, input); positionChanged(lastPos, k.pos); } function endDrift() { k.drifting = false; clearWheelParticles(); if (sounds.powerslide != null) { nitroAudio.instaKill(sounds.powerslide); sounds.powerslide = null; } } function damage(damageType) { if (k.damageType >= damageType) { return; //we are already damaged } //TODO: check invuln state k.specialControlHandler = damagedControls; playCharacterSound((damageType == 0) ? 1 : 0); k.damageType = damageType; k.ylock = 0; k.anim.setAnim(k.charRes.spinA); k.animMode = "spin"; if (k.drifting) { endDrift(); } k.boostMT = 0; k.boostNorm = 0; switch (damageType) { case 0: k.damageTime = 40; break; case 1: k.damageTime = 80; k.vel[1] += 3; ylvel = 3; k.airTime = 4; break; case 2: k.damageTime = 105; k.vel[1] += 8; ylvel = 8; k.airTime = 4; break; } } function damagedControls(kart) { if (--k.damageTime == 0) { k.anim.setAnim(k.charRes.driveA); k.animMode = "drive"; k.specialControlHandler = null; k.damageType = -1; k.damageMat = null; } vec3.scale(k.vel, k.vel, 0.98); k.speed *= 0.98; var total = 40; switch (k.damageType) { case 1: total = 80; break; case 2: total = 105; break; } var anim = 1 - (k.damageTime / total); k.damageMat = mat4.create(); var flip = ((k.damageType%2) == 1)? 1 : -1; var animOff = Math.min(1, anim*1.75); mat4.rotateX(k.damageMat, k.damageMat, Math.PI*2 * animOff * k.damageType * flip); if (k.damageType == 0) mat4.rotateY(k.damageMat, k.damageMat, Math.PI*-2 * anim); else mat4.rotateY(k.damageMat, k.damageMat, Math.PI/12 * Math.sin(animOff*Math.PI)); } function triggerCannon(id) { if (k.cannon != null) return; k.cannon = id; var c = scene.nkm.sections["KTPC"].entries[k.cannon]; if (c.id1 != -1 && c.id2 != -1) { nitroAudio.playSound(345, {volume: 2.5}, 0, k); } else { nitroAudio.playSound(347, {volume: 2.5}, 0, k); if (k.local) { if (c.id2 == 0) { nitroAudio.playSound(380, {volume: 2}, 0, null); //airship fortress } else { nitroAudio.playSound(456, {volume: 2}, 0, null); //waluigi } } } } function playCharacterSound(sound, volume) { //0 - hit //1 - hit spin //2 - hit ?? hit grnd //3 - hit banana? race start? //4 - hit spin? //5 - good pass? //6 - good OK! //7 - use item //8 - hit someone? //9 = win //10 = alright //11 = bad //12 = good record //13 = bad record if (volume == null) volume = 1; nitroAudio.playSound(sound + charRes.sndOff, {volume: 2*volume}, 2, k); } function clearWheelParticles(prio) { for (var i=0; i<2; i++) { if (prio == null) { //clear all specials k.wheelParticles[i].clearEmitter(1); //drift mode //k.wheelParticles[i].clearEmitter(2); //drift flare (blue mt, red big flash) } else { k.wheelParticles[i].clearEmitter(0); } } } function setWheelParticles(id, prio) { for (var i=0; i<2; i++) { k.wheelParticles[i].setEmitter(id, prio); } } function genFutureChecks() { //all future points that var chosen = {} var current = checkpoints[k.checkPointNumber]; var expectedSection = current.nextSection; futureChecks = []; for (var i=k.checkPointNumber+1; i 0 && lineDot < vec2.sqrLen(lineCheck) && dot < 0 && dotOld >= 0) { k.checkPointNumber = futureChecks[i]; genFutureChecks(); break; } } if (!k.passedKTP2 && forwardCrossedKTP(passLine, oldPos, pos)) k.passedKTP2 = true; if (k.passedKTP2 && futureChecks.length == 0) { //we can finish the lap if (forwardCrossedKTP(startLine, oldPos, pos)) { k.lapNumber++; k.checkPointNumber = 0; k.passedKTP2 = 0; futureChecks = [1]; scene.lapAdvance(k); } } } function getPosition() { if (checkpoints.length == 0 || futureChecks.length == 0) return 0; var check = checkpoints[futureChecks[0]]; var dist = vec2.sub([], [check.x1, check.z1], [k.pos[0], k.pos[2]]); var dot = vec2.dot(dist, [check.sinus, check.cosinus]); return k.checkPointNumber + (1-(Math.abs(dot)/(0xFFFF))) + k.lapNumber*checkpoints.length; } function forwardCrossedKTP(ktp, oldPos, pos) { var distOld = vec2.sub([], [ktp.pos[0], ktp.pos[2]], [oldPos[0], oldPos[2]]); var dist = vec2.sub([], [ktp.pos[0], ktp.pos[2]], [pos[0], pos[2]]); var ang = (ktp.angle[1]/180)*Math.PI; var sinus = Math.sin(ang); var cosinus = Math.cos(ang); var dot = vec2.dot(dist, [sinus, cosinus]); var dotOld = vec2.dot(distOld, [sinus, cosinus]); return (dot < 0 && dotOld >= 0); } function checkKartCollision(scene) { //check collision with other karts. Really simple. for (var i=0; i0 || ok.boostMT>0)?2:1)*((k.boostNorm>0 || k.boostMT>0)?0.5:1); //as well as side bounce also add velocity difference if other vel > mine. vec3.sub(k.kartColVel, k.pos, ok.pos); vec3.normalize(k.kartColVel, k.kartColVel); vec3.scale(k.kartColVel, k.kartColVel, weightMul); if (vec3.length(k.vel) < vec3.length(ok.vel)) vec3.add(k.kartColVel, k.kartColVel, vec3.sub([], ok.vel, k.vel)); k.kartColVel[1] = 0; } function fixDir(dir) { return posMod(dir, Math.PI*2); } function dirDiff(dir1, dir2) { var d = fixDir(dir1-dir2); return (d>Math.PI)?(-2*Math.PI+d):d; } function posMod(i, n) { return (i % n + n) % n; } 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)); sounds.transpose += (transpose-sounds.transpose)/15; if (mode != soundMode) { soundMode = mode; if (sounds.kart != null) nitroAudio.instaKill(sounds.kart); sounds.kart = nitroAudio.playSound(kartSoundBase+soundMode, {transpose:sounds.transpose, volume:1}, 0, k); //if (mode == 3) sounds.kart.gainN.gain.value = 0.5; } else { sounds.kart.seq.setTranspose(sounds.transpose); } } function buildBasis() { //order y, x, z var dir = k.physicalDir+k.driftOff+(Math.sin((COLBOUNCE_TIME-k.kartColTimer)/3)*(Math.PI/6)*(k.kartColTimer/COLBOUNCE_TIME)); var forward = [Math.sin(dir), 0, -Math.cos(dir)]; var side = [-Math.cos(dir), 0, -Math.sin(dir)]; if (k.physBasis != null) { vec3.transformMat4(forward, forward, k.physBasis.mat); vec3.transformMat4(side, side, k.physBasis.mat); } var basis = gramShmidt(k.kartNormal, side, forward); var temp = basis[0]; basis[0] = basis[1]; basis[1] = temp; //todo: cleanup return [ basis[0][0], basis[0][1], basis[0][2], 0, basis[1][0], basis[1][1], basis[1][2], 0, basis[2][0], basis[2][1], basis[2][2], 0, 0, 0, 0, 1 ] } function enterBasis(normal) { //establish a new basis for the kart velocity based on this normal. //used by looping and sticky track surface types. //first let's get the forward direction in our current basis var dir = k.angle; var forward, side; if (k.physBasis != null) { forward = vec3.transformMat4([], [- Math.sin(dir), 0, Math.cos(dir)], k.physBasis.mat); side = vec3.transformMat4([], [Math.cos(dir), 0, Math.sin(dir)], k.physBasis.mat); } else { forward = [-Math.sin(dir), 0, Math.cos(dir)]; side = [Math.cos(dir), 0, Math.sin(dir)]; } var basis = gramShmidt(normal, side, forward); var temp = basis[0]; basis[0] = basis[1]; basis[1] = temp; //todo: cleanup var m4 = [ basis[0][0], basis[0][1], basis[0][2], 0, basis[1][0], basis[1][1], basis[1][2], 0, basis[2][0], basis[2][1], basis[2][2], 0, 0, 0, 0, 1 ]; k.physicalDir = dirDiff(k.physicalDir, k.angle); k.angle = 0; //our front direction is now aligned with z. k.vel = [Math.sin(k.angle)*k.speed, k.vel[1], -Math.cos(k.angle)*k.speed]; k.physBasis = { mat: m4, inv: mat4.invert([], m4), normal: normal, time: 15, loop: false }; } function exitBasis() { //return to a normal y up, z forward basis. var v = vec3.transformMat4([], k.vel, k.physBasis.mat); k.physicalDir = dirDiff(k.physicalDir, k.angle); k.angle = Math.atan2(v[0], -v[2]); k.physicalDir += k.angle; k.vel = v; k.physBasis = null; } 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; 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))); } function gramShmidt(v1, v2, v3) { var u1 = v1; var u2 = vec3.sub([0, 0, 0], v2, project(u1, v2)); var u3 = vec3.sub([0, 0, 0], vec3.sub([0, 0, 0], v3, project(u1, v3)), project(u2, v3)); return [vec3.normalize(u1, u1), vec3.normalize(u2, u2), vec3.normalize(u3, u3)] } function colSound(collision, effect) { if (MKDS_COLTYPE.SOUNDMAP[collision] == null) return {}; return MKDS_COLTYPE.SOUNDMAP[collision][effect] || {}; } function colParticle(collision, effect) { if (MKDS_COLTYPE.SOUNDMAP[collision] == null) return null return MKDS_COLTYPE.SOUNDMAP[collision][effect].particle || null; } function project(u, v) { return vec3.scale([], u, (vec3.dot(u, v)/vec3.dot(u, u))) } function colResponse(pos, pvel, dat, ignoreList) { var plane = dat.plane; var colType = (plane.CollisionType>>8)&31; var colBE = (plane.CollisionType>>5)&7; var change = (colType != lastCollided); lastCollided = colType; lastBE = colBE; lastColSounds = colSound(lastCollided, colBE); var n = vec3.normalize([], dat.normal); var an = n; if (k.physBasis != null) { an = vec3.transformMat4([], n, k.physBasis.inv); } var gravS = Math.sqrt(vec3.dot(k.gravity, k.gravity)); var angle = Math.acos(vec3.dot(vec3.scale(vec3.create(), k.gravity, -1/gravS), n)); var adjustPos = true; if (MKDS_COLTYPE.GROUP_OOB.indexOf(colType) != -1) { k.OOB = 1; } if (MKDS_COLTYPE.GROUP_WALL.indexOf(colType) != -1) { //wall //sliding plane, except normal is transformed to be entirely on the xz plane (cannot ride on top of wall, treated as vertical) var xz = Math.sqrt(an[0]*an[0]+an[2]*an[2]) var adjN = [an[0]/xz, 0, an[2]/xz] var proj = vec3.dot(k.vel, adjN); if (proj < -1) { if (k.kartWallTimer == 0) { if (lastColSounds.hit != null) nitroAudio.playSound(lastColSounds.hit, {volume:1}, 0, k) var colObj = {pos:pos, vel:[0,0,0], mat: mat4.fromTranslation([], pos)}; scene.particles.push(new NitroEmitter(scene, colObj, 13)); scene.particles.push(new NitroEmitter(scene, colObj, 14)); } k.kartWallTimer = 15; } 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) { k.boostNorm = BOOSTTIME; } var stick = (colType == MKDS_COLTYPE.STICKY || colType == MKDS_COLTYPE.LOOP); if (k.vel[1] > 0) k.vel[1] = 0; var proj = vec3.dot(k.vel, an); 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) { enterBasis(dat.pNormal); k.physBasis.loop = colType == MKDS_COLTYPE.LOOP; } else { if (k.physBasis != null) exitBasis(); } k.kartTargetNormal = dat.pNormal; if (change) { var particle = colParticle(lastCollided, colBE); if (particle == null) clearWheelParticles(0); else setWheelParticles(particle, 0); } if (!onGround && !stick) { groundAnim = 0; if (lastColSounds.land != null) nitroAudio.playSound(lastColSounds.land, {volume:1}, 0, k) } k.airTime = 0; stuckTo = dat.object; } else if (colType == MKDS_COLTYPE.CANNON) { //cannon!! triggerCannon(colBE); } else { adjustPos = false; ignoreList.push(plane); } //vec3.add(pos, pos, vec3.scale(vec3.create(), n, minimumMove)); //move away from plane slightly if (adjustPos) { //move back from plane slightly //vec3.add(pos, pos, vec3.scale(vec3.create(), n, minimumMove)); vec3.add(pos, pos, vec3.scale(vec3.create(), pvel, dat.t)); vec3.add(pos, vec3.scale([], n, params.colRadius+minimumMove), dat.colPoint); /*if (dat.embedded) { } else { var velMag = Math.sqrt(vec3.dot(pvel, pvel)); if (velMag*dat.t > minimumMove) { vec3.add(pos, pos, vec3.scale(vec3.create(), pvel, dat.t-(minimumMove/velMag))); } else { //do not move, too close } }*/ } else { vec3.add(pos, pos, vec3.scale(vec3.create(), pvel, dat.t)); } } }