diff --git a/src/CameraController.hx b/src/CameraController.hx index c1a0e6b8..90332107 100644 --- a/src/CameraController.hx +++ b/src/CameraController.hx @@ -1,5 +1,6 @@ package src; +import src.Util; import h3d.Quat; import sdl.Cursor; import sdl.Sdl; @@ -51,6 +52,11 @@ class CameraController extends Object { var screenHeight:Int; var screenWidth:Int; + var lastCamPos:Vector; + + public var oob:Bool = false; + public var finish:Bool = false; + public function new(marble:Marble) { super(); this.marble = marble; @@ -132,9 +138,15 @@ class CameraController extends Object { q.z = vr.z; return q; } - var orientationQuat = level.getOrientationQuat(currentTime); + 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 * 0.6; + } + var up = new Vector(0, 0, 1); up.transform(orientationQuat.toMatrix()); camera.up = up; @@ -195,6 +207,13 @@ class CameraController extends Object { var toPos = targetpos.add(directionVec); camera.pos = toPos; + if (oob) { + camera.pos = lastCamPos; + camera.target = targetpos.add(cameraVerticalTranslation); + } + + if (!oob) + lastCamPos = camera.pos; // camera.target = null; // camera.target = targetpos.add(cameraVerticalTranslation); // this.x = targetpos.x + directionVec.x; diff --git a/src/Main.hx b/src/Main.hx index ab9de451..b8176e12 100644 --- a/src/Main.hx +++ b/src/Main.hx @@ -52,7 +52,7 @@ class Main extends hxd.App { override function init() { super.init(); - var ltr = File.getContent("data/missions/beginner/finale.mis"); + var ltr = File.getContent("data/missions/beginner/movement.mis"); var mfp = new MisParser(ltr); var mis = mfp.parse(); @@ -62,13 +62,10 @@ class Main extends hxd.App { world = new MarbleWorld(s3d, s2d, mission); world.init(); + world.start(); // s3d.camera. - - var marble = new Marble(); - marble.controllable = true; - world.addMarble(marble); - marble.setPosition(5, 0, 5); + // marble.setPosition(5, 0, 5); // var marble2 = new Marble(); // world.addMarble(marble2); diff --git a/src/Marble.hx b/src/Marble.hx index 1a248a70..bae3f39d 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1,5 +1,6 @@ package src; +import shapes.StartPad; import src.TimeState; import src.ParticleSystem.ParticleEmitter; import src.ParticleSystem.ParticleData; @@ -40,6 +41,12 @@ class Move { public function new() {} } +enum Mode { + Start; + Play; + Finish; +} + final bounceParticleOptions:ParticleEmitterOptions = { ejectionPeriod: 80, ambientVelocity: new Vector(0, 0, 0.0), @@ -143,6 +150,10 @@ class Marble extends GameObject { var trailEmitterData:ParticleData; var trailEmitterNode:ParticleEmitter; + public var mode:Mode = Play; + + public var startPad:StartPad; + public function new() { super(); var geom = Sphere.defaultUnitSphere(); @@ -221,7 +232,9 @@ class Marble extends GameObject { function getExternalForces(currentTime:Float, m:Move, dt:Float) { var gWorkGravityDir = this.level.currentUp.multiply(-1); - var A = gWorkGravityDir.multiply(this._gravity); + var A = new Vector(); + if (this.mode != Finish) + A = gWorkGravityDir.multiply(this._gravity); if (currentTime - this.helicopterEnableTime < 5) { A = A.multiply(0.25); } @@ -231,7 +244,7 @@ class Marble extends GameObject { A = A.add(force.multiply(1 / _mass)); } } - if (contacts.length != 0) { + if (contacts.length != 0 && this.mode != Start) { var contactForce = 0.0; var contactNormal = new Vector(); var forceObjectCount = 0; @@ -500,7 +513,9 @@ class Marble extends GameObject { var AFriction = new Vector(0, 0, 0); if (vAtCMag != 0) { slipping = true; - var friction = this._kineticFriction * bestContact.friction; + var friction = 0.0; + if (this.mode != Start) + friction = this._kineticFriction * bestContact.friction; var angAMagnitude = 5 * friction * bestNormalForce / (2 * this._radius); var AMagnitude = bestNormalForce * friction; var totalDeltaV = (angAMagnitude * this._radius + AMagnitude) * dt; @@ -528,10 +543,14 @@ class Marble extends GameObject { } var Aadd = aControl.cross(bestContact.normal.multiply(this._radius)); var aAtCMag = aadd.cross(bestContact.normal.multiply(-this._radius)).add(Aadd).length(); - var friction2 = this._staticFriction * bestContact.friction; + var friction2 = 0.0; + if (mode != Start) + friction2 = this._staticFriction * bestContact.friction; if (aAtCMag > friction2 * bestNormalForce) { - friction2 = this._kineticFriction * bestContact.friction; + friction2 = 0; + if (mode != Start) + friction2 = this._kineticFriction * bestContact.friction; Aadd = Aadd.multiply(friction2 * bestNormalForce / aAtCMag); } A = A.add(Aadd); @@ -724,6 +743,15 @@ class Marble extends GameObject { var pos = this.getAbsPos().getPosition(); + if (mode == Start) { + var startPadNormal = this.startPad.getAbsPos().up(); + this.velocity = startPadNormal.multiply(this.velocity.dot(startPadNormal)); + } + + if (mode == Finish) { + this.velocity = this.velocity.multiply(0.9); + } + var newPos = pos.add(this.velocity.multiply(timeStep)); var rot = this.getRotationQuat(); var quat = new Quat(); @@ -755,7 +783,7 @@ class Marble extends GameObject { public function update(timeState:TimeState, collisionWorld:CollisionWorld, pathedInteriors:Array) { var move = new Move(); move.d = new Vector(); - if (this.controllable) { + if (this.controllable && this.mode != Finish) { if (Key.isDown(Key.W)) { move.d.x -= 1; } @@ -822,4 +850,14 @@ class Marble extends GameObject { public function enableHelicopter(time:Float) { this.helicopterEnableTime = time; } + + public override function reset() { + this.velocity = new Vector(); + this.collider.velocity = new Vector(); + this.omega = new Vector(); + this.superBounceEnableTime = Math.NEGATIVE_INFINITY; + this.shockAbsorberEnableTime = Math.NEGATIVE_INFINITY; + this.helicopterEnableTime = Math.NEGATIVE_INFINITY; + this.lastContactNormal = new Vector(0, 0, 1); + } } diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 963f2d91..5ac00bea 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -75,6 +75,7 @@ class MarbleWorld extends Scheduler { public var sky:Sky; var endPadElement:MissionElementStaticShape; + var endPad:EndPad; public var scene:Scene; public var scene2d:h2d.Scene; @@ -86,6 +87,8 @@ class MarbleWorld extends Scheduler { public var outOfBounds:Bool = false; public var outOfBoundsTime:TimeState; public var finishTime:TimeState; + public var finishPitch:Float; + public var finishYaw:Float; public var totalGems:Int = 0; public var gemCount:Int = 0; @@ -130,8 +133,10 @@ class MarbleWorld extends Scheduler { scanMission(this.mission.root); this.initScene(); + this.initMarble(); this.addSimGroup(this.mission.root); + this.endPad.generateCollider(); this.playGui.formatGemCounter(this.gemCount, this.totalGems); } @@ -173,6 +178,12 @@ class MarbleWorld extends Scheduler { scene.addChild(sky); } + public function initMarble() { + var marble = new Marble(); + marble.controllable = true; + this.addMarble(marble); + } + public function start() { restart(); for (interior in this.interiors) @@ -186,7 +197,26 @@ class MarbleWorld extends Scheduler { this.timeState.gameplayClock = 0; this.bonusTime = 0; this.outOfBounds = false; - this.marble.camera.CameraPitch = 0.45; + this.finishTime = null; + + var startquat = this.getStartPositionAndOrientation(); + + this.marble.setPosition(startquat.position.x, startquat.position.y, startquat.position.z + 3); + this.marble.collider.transform.setPosition(startquat.position); + this.marble.reset(); + + var euler = startquat.quat.toEuler(); + this.marble.camera.CameraYaw = euler.z - Math.PI / 2; + this.marble.camera.CameraPitch = -0.45; + this.marble.camera.oob = false; + this.marble.mode = Start; + this.marble.startPad = cast startquat.pad; + sky.follow = marble; + + var missionInfo:MissionElementScriptObject = cast this.mission.root.elements.filter((element) -> element._type == MissionElementType.ScriptObject + && element._name == "MissionInfo")[0]; + if (missionInfo.starthelptext != "") + displayHelp(missionInfo.starthelptext); // Show the start help text for (shape in dtsObjects) shape.reset(); @@ -216,6 +246,7 @@ class MarbleWorld extends Scheduler { } if ((this.timeState.currentAttemptTime >= 3.5) && (this.timeState.currentAttemptTime < 5.5)) { this.playGui.setCenterText('go'); + this.marble.mode = Play; } if (this.timeState.currentAttemptTime >= 5.5) { this.playGui.setCenterText('none'); @@ -225,6 +256,25 @@ class MarbleWorld extends Scheduler { } } + function getStartPositionAndOrientation() { + // The player is spawned at the last start pad in the mission file. + var startPad = this.dtsObjects.filter(x -> x is StartPad).pop(); + var position:Vector; + var quat:Quat = new Quat(); + if (startPad != null) { + // If there's a start pad, start there + position = startPad.getAbsPos().getPosition(); + quat = startPad.getRotationQuat().clone(); + } else { + position = new Vector(0, 0, 300); + } + return { + position: position, + quat: quat, + pad: startPad + }; + } + public function addSimGroup(simGroup:MissionElementSimGroup) { if (simGroup.elements.filter((element) -> element._type == MissionElementType.PathedInterior).length != 0) { // Create the pathed interior @@ -311,9 +361,11 @@ class MarbleWorld extends Scheduler { if (dataBlockLowerCase == "") {} // Make sure we don't do anything if there's no data block else if (dataBlockLowerCase == "startpad") shape = new StartPad(); - else if (dataBlockLowerCase == "endpad") + else if (dataBlockLowerCase == "endpad") { shape = new EndPad(); - else if (dataBlockLowerCase == "signfinish") + if (element == endPadElement) + endPad = cast shape; + } else if (dataBlockLowerCase == "signfinish") shape = new SignFinish(); else if (StringTools.startsWith(dataBlockLowerCase, "signplain")) shape = new SignPlain(element); @@ -555,15 +607,18 @@ class MarbleWorld extends Scheduler { this.timeState.dt = dt; this.timeState.currentAttemptTime += dt; this.timeState.timeSinceLoad += dt; - if (this.bonusTime != 0) { + if (this.bonusTime != 0 && this.timeState.currentAttemptTime >= 3.5) { this.bonusTime -= dt; if (this.bonusTime < 0) { this.timeState.gameplayClock -= this.bonusTime; this.bonusTime = 0; } } else { - this.timeState.gameplayClock += dt; + if (this.timeState.currentAttemptTime >= 3.5) + this.timeState.gameplayClock += dt; } + if (finishTime != null) + this.timeState.gameplayClock = finishTime.gameplayClock; playGui.formatTimer(this.timeState.gameplayClock); } @@ -631,6 +686,20 @@ class MarbleWorld extends Scheduler { var contactsphere = new SphereCollisionEntity(marble); contactsphere.velocity = new Vector(); + var spherebounds = new Bounds(); + var center = marble.collider.transform.getPosition(); + var radius = marble._radius; + spherebounds.xMin = center.x - radius; + spherebounds.yMin = center.y - radius; + spherebounds.zMin = center.z - radius; + spherebounds.xMax = center.x + radius; + spherebounds.yMax = center.y + radius; + spherebounds.zMax = center.z + radius; + + var gjkSphere = new collision.gjk.Sphere(); + gjkSphere.position = center; + gjkSphere.radius = radius; + for (contact in contacts) { if (contact.go != marble) { if (contact.go is DtsObject) { @@ -659,17 +728,7 @@ class MarbleWorld extends Scheduler { var trigger:Trigger = cast contact.go; var triggeraabb = trigger.collider.boundingBox; - var box = new Bounds(); - var center = marble.collider.transform.getPosition(); - var radius = marble._radius; - box.xMin = center.x - radius; - box.yMin = center.y - radius; - box.zMin = center.z - radius; - box.xMax = center.x + radius; - box.yMax = center.y + radius; - box.zMax = center.z + radius; - - if (triggeraabb.collide(box)) { + if (triggeraabb.collide(spherebounds)) { trigger.onMarbleInside(timeState); if (!this.shapeOrTriggerInside.contains(contact.go)) { this.shapeOrTriggerInside.push(contact.go); @@ -688,9 +747,32 @@ class MarbleWorld extends Scheduler { } } + if (spherebounds.collide(this.endPad.finishBounds)) { + if (collision.gjk.GJK.gjk(gjkSphere, this.endPad.finishCollider) != null) { + touchFinish(); + } + } this.shapeImmunity = newImmunity; } + function touchFinish() { + if (this.finishTime != null + || (this.outOfBounds && this.timeState.currentAttemptTime - this.outOfBoundsTime.currentAttemptTime >= 0.5)) + return; + + if (this.gemCount < this.totalGems) { + // AudioManager.play('missinggems.wav'); + displayAlert("You can't finish without all the gems!!"); + } else { + this.endPad.spawnFirework(this.timeState); + this.finishTime = this.timeState.clone(); + this.marble.mode = Finish; + this.marble.camera.finish = true; + this.finishYaw = this.marble.camera.CameraYaw; + this.finishPitch = this.marble.camera.CameraPitch; + } + } + public function pickUpPowerUp(powerUp:PowerUp) { if (this.marble.heldPowerup == powerUp) return false; @@ -763,6 +845,8 @@ class MarbleWorld extends Scheduler { // this.updateCamera(this.timeState); // Update the camera at the point of OOB-ing this.outOfBounds = true; this.outOfBoundsTime = this.timeState.clone(); + this.marble.camera.oob = true; + sky.follow = null; // this.oobCameraPosition = camera.position.clone(); playGui.setCenterText('outofbounds'); // AudioManager.play('whoosh.wav'); diff --git a/src/shapes/EndPad.hx b/src/shapes/EndPad.hx index 3a633ab1..a4278f44 100644 --- a/src/shapes/EndPad.hx +++ b/src/shapes/EndPad.hx @@ -1,5 +1,12 @@ package shapes; +import h3d.Quat; +import h3d.mat.Material; +import h3d.scene.Mesh; +import h3d.prim.Polygon; +import h3d.col.Bounds; +import h2d.col.Matrix; +import collision.gjk.ConvexHull; import src.TimeState; import collision.CollisionInfo; import src.Util; @@ -14,7 +21,9 @@ import h3d.Vector; class EndPad extends DtsObject { var fireworks:Array = []; - var isEntered:Bool = false; + + var finishCollider:ConvexHull; + var finishBounds:Bounds; public function new() { super(); @@ -24,12 +33,12 @@ class EndPad extends DtsObject { this.identifier = "EndPad"; } - override function onMarbleContact(timeState:TimeState, ?contact:CollisionInfo) { - if (!isEntered) { - isEntered = true; - spawnFirework(timeState); - } - } + // override function onMarbleContact(timeState:TimeState, ?contact:CollisionInfo) { + // if (!isEntered) { + // isEntered = true; + // spawnFirework(timeState); + // } + // } function spawnFirework(time:TimeState) { var firework = new Firework(this.getAbsPos().getPosition(), time.timeSinceLoad, this.level); @@ -37,6 +46,44 @@ class EndPad extends DtsObject { // AudioManager.play(this.sounds[0], 1, AudioManager.soundGain, this.worldPosition); } + function generateCollider() { + var height = 4.8; + var radius = 1.7; + var vertices = []; + + for (i in 0...2) { + for (j in 0...64) { + var angle = j / 64 * Math.PI * 2; + var x = Math.cos(angle); + var z = Math.sin(angle); + + vertices.push(new Vector(x * radius * this.scaleX, (i != 0 ? 4.8 : 0) * 1, z * radius * this.scaleY)); + } + } + finishCollider = new ConvexHull(vertices); + + finishBounds = new Bounds(); + for (vert in vertices) + finishBounds.addPoint(vert.toPoint()); + + var rotQuat = new Quat(); + rotQuat.initRotation(0, Math.PI / 2, 0); + var rotMat = rotQuat.toMatrix(); + + var tform = this.getAbsPos().clone(); + tform.prependRotation(Math.PI / 2, 0, 0); + + finishCollider.transform = tform; + + finishBounds.transform(tform); + + // var polygon = new Polygon(vertices.map(x -> x.toPoint())); + // polygon.addNormals(); + // polygon.addUVs(); + // var collidermesh = new Mesh(polygon, Material.create(), this.level.scene); + // collidermesh.setTransform(tform); + } + override function update(timeState:TimeState) { super.update(timeState); diff --git a/src/shapes/Tornado.hx b/src/shapes/Tornado.hx index c9791bb0..38469896 100644 --- a/src/shapes/Tornado.hx +++ b/src/shapes/Tornado.hx @@ -38,4 +38,11 @@ class Tornado extends ForceObject { }, ]; } + + public override function init(level:src.MarbleWorld) { + super.init(level); + for (material in this.materials) { + // material.mainPass.setPassName("overlay"); + } + } }