diff --git a/src/CameraController.hx b/src/CameraController.hx index f6eaefae..19f8e2fb 100644 --- a/src/CameraController.hx +++ b/src/CameraController.hx @@ -222,9 +222,8 @@ class CameraController extends Object { if (this.finish) { // Make the camera spin around slowly - CameraPitch = Util.lerp(this.level.finishPitch, 0.45, - Util.clamp((this.level.timeState.currentAttemptTime - this.level.finishTime.currentAttemptTime) / 0.3, 0, 1)); - CameraYaw = this.level.finishYaw - (this.level.timeState.currentAttemptTime - this.level.finishTime.currentAttemptTime) / -1.2; + CameraPitch = this.level.finishPitch; + CameraYaw = this.level.finishYaw; } if (!this.level.isWatching) { @@ -236,7 +235,7 @@ class CameraController extends Object { CameraYaw = this.level.replay.currentPlaybackFrame.cameraYaw; } - var marblePosition = level.marble.getAbsPos().getPosition(); + var marblePosition = level.marble.collider.transform.getPosition(); var up = new Vector(0, 0, 1); up.transform(orientationQuat.toMatrix()); var directionVector = new Vector(1, 0, 0); diff --git a/src/Marble.hx b/src/Marble.hx index ad2d4c0b..10048d00 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -265,6 +265,10 @@ class Marble extends GameObject { var helicopterSound:Channel; var playedSounds = []; + var finishAnimVelocity:Vector; + var finishAnimPosition:Vector; + var finishAnimTime:Float = 0; + public var mode:Mode = Play; public var startPad:StartPad; @@ -425,6 +429,7 @@ class Marble extends GameObject { this.isUltra = isUltra; this.collider = new SphereCollisionEntity(cast this); + this.collider.setTransform(this.getAbsPos()); this.addChild(marbleDts); @@ -512,7 +517,7 @@ class Marble extends GameObject { A = A.multiply(0.25); } for (obj in level.forceObjects) { - var force = cast(obj, ForceObject).getForce(this.getAbsPos().getPosition()); + var force = cast(obj, ForceObject).getForce(this.collider.transform.getPosition()); A = A.add(force.multiply(1 / _mass)); } if (contacts.length != 0 && this.mode != Start) { @@ -850,7 +855,7 @@ class Marble extends GameObject { function bounceEmitter(speed:Float, normal:Vector) { if (this.bounceEmitDelay == 0 && this._minBounceSpeed <= speed) { this.level.particleManager.createEmitter(bounceParticleOptions, this.bounceEmitterData, - this.getAbsPos().getPosition().sub(normal.multiply(_radius)), null, new Vector(1, 1, 1).add(normal.multiply(-0.8))); + this.collider.transform.getPosition().sub(normal.multiply(_radius)), null, new Vector(1, 1, 1).add(normal.multiply(-0.8))); this.bounceEmitDelay = 0.3; } } @@ -911,15 +916,15 @@ class Marble extends GameObject { function updateRollSound(time:TimeState, contactPct:Float, slipAmount:Float) { var rSpat = rollSound.getEffect(Spatialization); - rSpat.position = this.getAbsPos().getPosition(); + rSpat.position = this.collider.transform.getPosition(); if (this.rollMegaSound != null) { var rmspat = this.rollMegaSound.getEffect(Spatialization); - rmspat.position = this.getAbsPos().getPosition(); + rmspat.position = this.collider.transform.getPosition(); } var sSpat = slipSound.getEffect(Spatialization); - sSpat.position = this.getAbsPos().getPosition(); + sSpat.position = this.collider.transform.getPosition(); var rollVel = bestContact != null ? this.velocity.sub(bestContact.velocity) : this.velocity; var scale = rollVel.length(); @@ -989,7 +994,9 @@ class Marble extends GameObject { position: position, t: deltaT, found: false, - foundContacts: [] + foundContacts: [], + lastContactPos: null, + lastContactNormal: null }; } var searchbox = new Bounds(); @@ -1109,6 +1116,7 @@ class Marble extends GameObject { finalT = collisionTime; currentFinalPos = position.add(relVel.multiply(finalT)); found = true; + lastContactPos = currentFinalPos.clone(); // iterationFound = true; i += 3; // Debug.drawSphere(currentFinalPos, radius); @@ -1319,7 +1327,9 @@ class Marble extends GameObject { position: position, t: finalT, found: found, - foundContacts: testTriangles + foundContacts: testTriangles, + lastContactPos: lastContactPos, + lastContactNormal: position.sub(lastContactPos).normalized(), }; } @@ -1328,7 +1338,7 @@ class Marble extends GameObject { searchbox.addSpherePos(this.x, this.y, this.z, _radius); searchbox.addSpherePos(this.x + velocity.x * dt * 2, this.y + velocity.y * dt * 2, this.z + velocity.z * dt * 2, _radius); - var position = this.getAbsPos().getPosition(); + var position = this.collider.transform.getPosition(); var foundObjs = this.level.collisionWorld.boundingSearch(searchbox); // var foundObjs = this.contactEntities; @@ -1618,7 +1628,7 @@ class Marble extends GameObject { velocity.w = 0; - var pos = this.getAbsPos().getPosition(); + var pos = this.collider.transform.getPosition(); this.prevPos = pos.clone(); var tdiff = timeStep; @@ -1653,43 +1663,6 @@ class Marble extends GameObject { } } - // var intersectT = intersectData.t; - // if (intersectData.found && intersectT > 0.001) { - // var diff = timeStep - intersectT; - // this.velocity = this.velocity.sub(A.multiply(diff)); - // this.omega = this.omega.sub(a.multiply(diff)); - // // var mo = new h3d.prim.Sphere(); - // // mo.addNormals(); - // // mo.scale(_radius); - // // var mCol = new h3d.scene.Mesh(mo); - // // mCol.setPosition(intersectData.position.x, intersectData.position.y, intersectData.position.z); - // // this.level.scene.addChild(mCol); - // timeStep = intersectT; - // } - - // var posAdd = this.velocity.multiply(timeStep); - // var expectedPos = pos.add(posAdd); - // var newPos = nudgeToContacts(expectedPos, _radius); - - // if (mode == Start) { - // var upVec = this.level.currentUp; - // var startpadNormal = startPad.getAbsPos().up(); - // this.velocity = upVec.multiply(this.velocity.dot(upVec)); - // // Apply contact forces in startPad up direction if upVec is not startpad up, fixes the weird startpad shit in pinball wizard - // if (upVec.dot(startpadNormal) < 0.95) { - // for (contact in contacts) { - // var normF = contact.normal.multiply(contact.normalForce); - // var startpadF = startpadNormal.multiply(normF.dot(startpadNormal)); - // var upF = upVec.multiply(normF.dot(upVec)); - // this.velocity = this.velocity.add(startpadF.multiply(timeStep / 4)); - // } - // } - // } - - // if (mode == Finish) { - // this.velocity = this.velocity.multiply(0.925); - // } - var rot = this.getRotationQuat(); var quat = new Quat(); quat.initRotation(omega.x * timeStep, omega.y * timeStep, omega.z * timeStep); @@ -1858,6 +1831,11 @@ class Marble extends GameObject { this.updateTeleporterState(timeState); + this.updateFinishAnimation(timeState.dt); + if (this.mode == Finish) { + this.setPosition(this.finishAnimPosition.x, this.finishAnimPosition.y, this.finishAnimPosition.z); + } + this.trailEmitter(); if (bounceEmitDelay > 0) bounceEmitDelay -= timeState.dt; @@ -1867,6 +1845,81 @@ class Marble extends GameObject { // this.camera.target.load(this.getAbsPos().getPosition().toPoint()); } + public function updateFinishAnimation(dt:Float) { + if (this.mode == Finish) { + var trans = @:privateAccess this.level.endPad.getAbsPos(); + var around = trans.getPosition(); + var forceEffect = trans.up(); + around = around.add(forceEffect); + var offset = finishAnimPosition.sub(around); + + static var animTimeAccumulator = 0.0; + animTimeAccumulator += dt; + while (animTimeAccumulator >= 1 / 60.0) { + animTimeAccumulator -= (1 / 60.0); + + finishAnimVelocity.scale(0.95); + finishAnimVelocity = finishAnimVelocity.add(forceEffect.multiply(0.2)); + + var dist = offset.length(); + + finishAnimVelocity = finishAnimVelocity.sub(offset.multiply(0.35)); + + if (dist > 1.5) { + var outward = offset.multiply(1 / dist); + var outforce = outward.dot(finishAnimVelocity); + if (outforce > 0) { + finishAnimVelocity = finishAnimVelocity.sub(outward.multiply(outforce * 0.75)); + var noodles = offset.multiply(outforce * 0.5); + noodles = finishAnimVelocity.sub(noodles); + if (noodles.lengthSq() <= 0.1) { + var cross = outward.cross(forceEffect); + if (cross.lengthSq() <= 0.1) { + noodles = trans.front().clone(); + } else { + noodles = cross.normalized(); + } + } else { + noodles.normalize(); + } + + finishAnimVelocity = finishAnimVelocity.add(noodles.multiply(outforce * 0.25)); + } + } + + var addVel = new Vector(Math.sin(finishAnimTime * 3), Math.sin(finishAnimTime * 3 + 2.45), Math.sin(finishAnimTime * 1.5 + 1.2)); + finishAnimVelocity = finishAnimVelocity.add(addVel.multiply(2.5 / 60.0)); + + var pos = finishAnimPosition.add(finishAnimVelocity.multiply(1 / 60.0)); + + var position = finishAnimPosition.clone(); + var velocity = pos.sub(finishAnimPosition); + var time = 1.0; + var i = 0; + var totalTime = time; + do { + if (totalTime <= 1e-8) + break; + var tm = testMove(velocity, position, time, _radius, true); + if (!tm.found) + break; + + i++; + totalTime -= tm.t; + time = totalTime; + pos = tm.position; + var norm = tm.lastContactNormal; + var newAround = norm.multiply(1.5); + newAround.scale(finishAnimVelocity.dot(norm)); + finishAnimVelocity = finishAnimVelocity.sub(newAround); + } while (i < 4); + + finishAnimPosition = pos; + finishAnimTime += (1 / 60.0); + } + } + } + public function updatePowerupStates(currentTime:Float, dt:Float) { if (currentTime - this.shockAbsorberEnableTime < 5) { this.shockabsorberSound.pause = false; @@ -1977,6 +2030,20 @@ class Marble extends GameObject { } } + public function setMode(mode:Mode) { + this.mode = mode; + if (mode == Finish) { + this.finishAnimPosition = this.collider.transform.getPosition().clone(); + this.finishAnimVelocity = this.velocity.clone(); + this.finishAnimTime = 0; + } + } + + public function setMarblePosition(x:Float, y:Float, z:Float) { + this.collider.transform.setPosition(new Vector(x, y, z)); + this.setPosition(x, y, z); + } + public override function reset() { this.velocity = new Vector(); this.collider.velocity = new Vector(); @@ -1997,6 +2064,7 @@ class Marble extends GameObject { this.teleporting = false; this.teleportDisableTime = null; this.teleportEnableTime = null; + this.finishAnimTime = 0; if (this._radius != this._prevRadius) { this._radius = this._prevRadius; this._marbleScale = this._renderScale = this._defaultScale; diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 8122279a..0ed902cf 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -491,10 +491,7 @@ class MarbleWorld extends Scheduler { var startquat = this.getStartPositionAndOrientation(); - this.marble.setPosition(startquat.position.x, startquat.position.y, startquat.position.z + 0.727843); - var oldtransform = this.marble.collider.transform.clone(); - oldtransform.setPosition(startquat.position); - this.marble.collider.setTransform(oldtransform); + this.marble.setMarblePosition(startquat.position.x, startquat.position.y, startquat.position.z + 0.727843); this.marble.reset(); var euler = startquat.quat.toEuler(); @@ -505,7 +502,7 @@ class MarbleWorld extends Scheduler { this.marble.camera.nextCameraYaw = euler.z + Math.PI / 2; this.marble.camera.oob = false; this.marble.camera.finish = false; - this.marble.mode = Start; + this.marble.setMode(Start); this.marble.startPad = cast startquat.pad; sky.follow = marble.camera; @@ -535,15 +532,15 @@ class MarbleWorld extends Scheduler { return; // We will update state manually if (this.timeState.currentAttemptTime < 0.5) { this.playGui.setCenterText('none'); - this.marble.mode = Start; + this.marble.setMode(Start); } if ((this.timeState.currentAttemptTime >= 0.5) && (this.timeState.currentAttemptTime < 3.5)) { this.playGui.setCenterText('none'); - this.marble.mode = Start; + this.marble.setMode(Start); } if (this.timeState.currentAttemptTime >= 3.5 && this.finishTime == null) { this.playGui.setCenterText('none'); - this.marble.mode = Play; + this.marble.setMode(Play); } } @@ -1412,7 +1409,7 @@ class MarbleWorld extends Scheduler { } else { this.endPad.spawnFirework(this.timeState); this.finishTime = this.timeState.clone(); - this.marble.mode = Finish; + this.marble.setMode(Finish); this.marble.camera.finish = true; this.finishYaw = this.marble.camera.CameraYaw; this.finishPitch = this.marble.camera.CameraPitch; @@ -1673,7 +1670,7 @@ class MarbleWorld extends Scheduler { // Determine where to spawn the marble var offset = new Vector(0, 0, 0.727843); var mpos = this.currentCheckpoint.getAbsPos().getPosition().add(offset); - this.marble.setPosition(mpos.x, mpos.y, mpos.z); + this.marble.setMarblePosition(mpos.x, mpos.y, mpos.z); marble.velocity.load(new Vector(0, 0, 0)); marble.omega.load(new Vector(0, 0, 0)); Console.log('Respawn:'); diff --git a/src/rewind/RewindManager.hx b/src/rewind/RewindManager.hx index 0dab02c1..4c2bf9b3 100644 --- a/src/rewind/RewindManager.hx +++ b/src/rewind/RewindManager.hx @@ -85,7 +85,7 @@ class RewindManager { public function applyFrame(rf:RewindFrame) { level.timeState = rf.timeState.clone(); - level.marble.setPosition(rf.marblePosition.x, rf.marblePosition.y, rf.marblePosition.z); + level.marble.setMarblePosition(rf.marblePosition.x, rf.marblePosition.y, rf.marblePosition.z); level.marble.setRotationQuat(rf.marbleOrientation.clone()); level.marble.velocity.set(rf.marbleVelocity.x, rf.marbleVelocity.y, rf.marbleVelocity.z); level.marble.omega.set(rf.marbleAngularVelocity.x, rf.marbleAngularVelocity.y, rf.marbleAngularVelocity.z);