diff --git a/src/CameraController.hx b/src/CameraController.hx index 1338d045..b815acc6 100644 --- a/src/CameraController.hx +++ b/src/CameraController.hx @@ -187,8 +187,7 @@ class CameraController extends Object { CameraYaw = Util.lerp(CameraYaw, nextCameraYaw, lerpt); CameraPitch = Util.lerp(CameraPitch, nextCameraPitch, lerpt); - CameraPitch = Math.max(-Math.PI / 2 + Math.PI / 4, - Math.min(Math.PI / 2 - 0.0001, CameraPitch)); // Util.clamp(CameraPitch, -Math.PI / 12, Math.PI / 2); + CameraPitch = Math.max(-Math.PI / 2 + Math.PI / 4, Math.min(Math.PI / 2 - 0.0001, CameraPitch)); // Util.clamp(CameraPitch, -Math.PI / 12, Math.PI / 2); function getRotQuat(v1:Vector, v2:Vector) { function orthogonal(v:Vector) { @@ -257,7 +256,7 @@ class CameraController extends Object { camera.target = marblePosition.add(cameraVerticalTranslation); var closeness = 0.1; - var rayCastOrigin = marblePosition.add(level.currentUp.multiply(marble._radius)); + var rayCastOrigin = marblePosition.add(level.marble.currentUp.multiply(marble._radius)); var processedShapes = []; for (i in 0...3) { diff --git a/src/GameObject.hx b/src/GameObject.hx index c3deaff6..1d16d7af 100644 --- a/src/GameObject.hx +++ b/src/GameObject.hx @@ -6,6 +6,7 @@ import h3d.scene.Object; import src.Resource; import h3d.mat.Texture; import hxd.res.Sound; +import src.Marble; class GameObject extends Object { public var identifier:String; @@ -17,13 +18,13 @@ class GameObject extends Object { var textureResources:Array> = []; var soundResources:Array> = []; - public function onMarbleContact(time:TimeState, ?contact:CollisionInfo) {} + public function onMarbleContact(marble:Marble, time:TimeState, ?contact:CollisionInfo) {} - public function onMarbleInside(time:TimeState) {} + public function onMarbleInside(marble:Marble, time:TimeState) {} - public function onMarbleEnter(time:TimeState) {} + public function onMarbleEnter(marble:Marble, time:TimeState) {} - public function onMarbleLeave(time:TimeState) {} + public function onMarbleLeave(marble:Marble, time:TimeState) {} public function onLevelStart() {} diff --git a/src/InstanceManager.hx b/src/InstanceManager.hx index 424eec96..7f1ac41c 100644 --- a/src/InstanceManager.hx +++ b/src/InstanceManager.hx @@ -25,6 +25,8 @@ class MeshBatchInfo { var meshbatch:MeshBatch; var transparencymeshbatch:MeshBatch; var mesh:Mesh; + var dtsShader:DtsTexture; + var baseBounds:h3d.col.Bounds; public function new() {} } @@ -40,45 +42,119 @@ class MeshInstance { } } +@:generic +class ReusableListIterator { + var l:ReusableList; + var i = 0; + + public function new(l:ReusableList) { + this.l = l; + } + + public inline function hasNext() { + return i != l.length; + } + + public inline function next() { + var ret = @:privateAccess l.array[i]; + i += 1; + return ret; + } +} + +@:allow(ReusableListIterator) +@:generic +class ReusableList { + var array:Array; + + public var length:Int = 0; + + public inline function new() { + array = []; + } + + public inline function push(item:T) { + if (array.length == length) { + array.push(item); + length += 1; + } else { + array[length] = item; + length += 1; + } + } + + public inline function clear() { + length = 0; + } + + public inline function iterator():ReusableListIterator { + return new ReusableListIterator(this); + } +} + class InstanceManager { var objects:Array> = []; var objectMap:Map = []; var scene:Scene; + var opaqueinstances = new ReusableList(); + var transparentinstances = new ReusableList(); public function new(scene:Scene) { this.scene = scene; } public function render() { - var renderFrustums = [scene.camera.frustum]; + static var tmpBounds = new h3d.col.Bounds(); + var renderFrustum = scene.camera.frustum; + var doFrustumCheck = true; // This sucks holy shit - if (MarbleGame.instance.world.marble != null && MarbleGame.instance.world.marble.cubemapRenderer != null) - renderFrustums = renderFrustums.concat(MarbleGame.instance.world.marble.cubemapRenderer.getCameraFrustums()); + doFrustumCheck = MarbleGame.instance.world != null && MarbleGame.instance.world.marble.cubemapRenderer != null; + var cameraFrustrums = doFrustumCheck ? MarbleGame.instance.world.marble.cubemapRenderer.getCameraFrustums() : null; for (meshes in objects) { for (minfo in meshes) { - var visibleinstances = []; + opaqueinstances.clear(); + transparentinstances.clear(); // Culling if (minfo.meshbatch != null || minfo.transparencymeshbatch != null) { for (inst in minfo.instances) { - var objBounds = @:privateAccess cast(minfo.meshbatch.primitive, Instanced).baseBounds.clone(); - objBounds.transform(inst.emptyObj.getAbsPos()); - for (frustum in renderFrustums) { - if (frustum.hasBounds(objBounds)) { - visibleinstances.push(inst); - break; + // for (frustum in renderFrustums) { + // if (frustum.hasBounds(objBounds)) { + + tmpBounds.load(minfo.baseBounds); + tmpBounds.transform(inst.emptyObj.getAbsPos()); + + if (cameraFrustrums == null && !renderFrustum.hasBounds(tmpBounds)) + continue; + + if (cameraFrustrums != null) { + var found = false; + for (frustrum in cameraFrustrums) { + if (frustrum.hasBounds(tmpBounds)) { + found = true; + break; + } } + if (!found) + continue; } + + if (inst.gameObject.currentOpacity == 1) + opaqueinstances.push(inst); + else if (inst.gameObject.currentOpacity != 0) + transparentinstances.push(inst); + // break; + // } + // } } } // Emit non culled primitives if (minfo.meshbatch != null) { - var opaqueinstances = visibleinstances.filter(x -> x.gameObject.currentOpacity == 1); minfo.meshbatch.begin(opaqueinstances.length); for (instance in opaqueinstances) { // Draw the opaque shit first - var dtsShader = minfo.meshbatch.material.mainPass.getShader(DtsTexture); + var dtsShader = minfo.dtsShader; if (dtsShader != null) { dtsShader.currentOpacity = instance.gameObject.currentOpacity; } @@ -91,10 +167,9 @@ class InstanceManager { } } if (minfo.transparencymeshbatch != null) { - var transparentinstances = visibleinstances.filter(x -> x.gameObject.currentOpacity != 1 && x.gameObject.currentOpacity != 0); // Filter out all zero opacity things too minfo.transparencymeshbatch.begin(transparentinstances.length); for (instance in transparentinstances) { // Non opaque shit - var dtsShader = minfo.transparencymeshbatch.material.mainPass.getShader(DtsTexture); + var dtsShader = minfo.dtsShader; if (dtsShader != null) { dtsShader.currentOpacity = instance.gameObject.currentOpacity; } @@ -144,6 +219,7 @@ class InstanceManager { minfo.instances = [new MeshInstance(obj, object)]; minfo.meshbatch = isMesh ? new MeshBatch(cast(cast(obj, Mesh).primitive), cast(cast(obj, Mesh)).material.clone(), scene) : null; minfo.mesh = isMesh ? cast obj : null; + minfo.baseBounds = isMesh ? @:privateAccess cast(minfo.meshbatch.primitive, Instanced).baseBounds : null; if (isMesh) { var mat = cast(obj, Mesh).material; @@ -153,6 +229,7 @@ class InstanceManager { minfo.meshbatch.material.mainPass.addShader(dtsshader); minfo.meshbatch.material.mainPass.culling = mat.mainPass.culling; minfo.meshbatch.material.mainPass.depthWrite = mat.mainPass.depthWrite; + minfo.dtsShader = dtsshader; } var phongshader = mat.mainPass.getShader(PhongMaterial); if (phongshader != null) { diff --git a/src/Marble.hx b/src/Marble.hx index de48f218..957dd4ac 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1,5 +1,6 @@ package src; +import collision.CollisionPool; import collision.CollisionHull; import dif.Plane; import shaders.marble.ClassicGlass; @@ -174,6 +175,25 @@ final blastMaxParticleOptions:ParticleEmitterOptions = { } } +@:publicFields +@:structInit +class MarbleTestMoveFoundContact { + var v:Array; + var n:Vector; +} + +@:publicFields +@:structInit +class MarbleTestMoveResult { + var position:Vector; + var t:Float; + var found:Bool; + var foundContacts:Array; + var lastContactPos:Null; + var lastContactNormal:Null; + var foundMarbles:Array; +} + class Marble extends GameObject { public var camera:CameraController; public var cameraObject:Object; @@ -185,6 +205,7 @@ class Marble extends GameObject { public var omega:Vector; public var level:MarbleWorld; + public var collisionWorld:CollisionWorld; public var _radius = 0.2; @@ -206,6 +227,8 @@ class Marble extends GameObject { var minVelocityBounceSoft = 2.5; var minVelocityBounceHard = 12.0; var bounceMinGain = 0.2; + var maxBlastRepulse = 60.0; + var blastRepulseDist = 10.0; public var _bounceRestitution = 0.5; @@ -219,6 +242,15 @@ class Marble extends GameObject { public var _mass:Float = 1; + var physicsAccumulator:Float = 0; + var oldPos:Vector; + var newPos:Vector; + var prevRot:Quat; + var posStore:Vector; + var lastRenderPos:Vector; + var netSmoothOffset:Vector; + var netCorrected:Bool; + public var contacts:Array = []; public var bestContact:CollisionInfo; public var contactEntities:Array = []; @@ -227,7 +259,13 @@ class Marble extends GameObject { var appliedImpulses:Array<{impulse:Vector, contactImpulse:Bool}> = []; public var heldPowerup:PowerUp; + public var lastContactPosition:Vector; public var lastContactNormal:Vector; + public var currentUp = new Vector(0, 0, 1); + + public var outOfBounds:Bool = false; + public var outOfBoundsTime:TimeState; + public var oobSchedule:Float; var forcefield:DtsObject; var helicopter:DtsObject; @@ -236,6 +274,17 @@ class Marble extends GameObject { var helicopterEnableTime:Float = -1e8; var megaMarbleEnableTime:Float = -1e8; + public var helicopterUseTick:Int = 0; + public var megaMarbleUseTick:Int = 0; + public var shockAbsorberUseTick:Int = 0; + public var superBounceUseTick:Int = 0; + + public var blastAmount:Float = 0; + public var blastTicks:Int = 0; + public var blastUseTick:Int = 0; // blast is 12 ticks long + + var blastPerc:Float = 0.0; + var teleportEnableTime:Null = null; var teleportDisableTime:Null = null; var bounceEmitDelay:Float = 0; @@ -268,6 +317,14 @@ class Marble extends GameObject { public var cubemapRenderer:CubemapRenderer; + var moveMotionDir:Vector; + var lastMove:Move; + var isNetUpdate:Bool = false; + var netFlags:Int = 0; + var serverTicks:Int; + var recvServerTick:Int; + var serverUsePowerup:Bool; + public function new() { super(); @@ -310,9 +367,16 @@ class Marble extends GameObject { public function init(level:MarbleWorld, onFinish:Void->Void) { this.level = level; + if (this.level != null) + this.collisionWorld = this.level.collisionWorld; var isUltra = level.mission.game.toLowerCase() == "ultra"; + this.posStore = new Vector(); + this.lastRenderPos = new Vector(); + this.netSmoothOffset = new Vector(); + this.netCorrected = false; + var marbleDts = new DtsObject(); Console.log("Marble: " + Settings.optionsSettings.marbleModel + " (" + Settings.optionsSettings.marbleSkin + ")"); marbleDts.dtsPath = Settings.optionsSettings.marbleModel; @@ -470,6 +534,7 @@ class Marble extends GameObject { function findContacts(collisiomWorld:CollisionWorld, timeState:TimeState) { this.contacts = queuedContacts; + CollisionPool.clear(); var c = collisiomWorld.sphereIntersection(this.collider, timeState); this.contactEntities = c.foundEntities; contacts = contacts.concat(c.contacts); @@ -481,29 +546,50 @@ class Marble extends GameObject { public function getMarbleAxis() { var motiondir = new Vector(0, -1, 0); - motiondir.transform(Matrix.R(0, 0, camera.CameraYaw)); - motiondir.transform(level.newOrientationQuat.toMatrix()); - var updir = this.level.currentUp; - var sidedir = motiondir.cross(updir); + // if (level.isReplayingMovement) + // return level.currentInputMoves[1].marbleAxes; + if (this.controllable && !this.isNetUpdate) { + motiondir.transform(Matrix.R(0, 0, camera.CameraYaw)); + motiondir.transform(level.newOrientationQuat.toMatrix()); + var updir = this.currentUp; + var sidedir = motiondir.cross(updir); - sidedir.normalize(); - motiondir = updir.cross(sidedir); - return [sidedir, motiondir, updir]; + sidedir.normalize(); + motiondir = updir.cross(sidedir); + return [sidedir, motiondir, updir]; + } else { + if (moveMotionDir != null) + motiondir = moveMotionDir; + var updir = this.currentUp; + var sidedir = motiondir.cross(updir); + return [sidedir, motiondir, updir]; + } } - function getExternalForces(currentTime:Float, m:Move, dt:Float) { + function getExternalForces(timeState:TimeState, m:Move) { if (this.mode == Finish) return this.velocity.multiply(-16); - var gWorkGravityDir = this.level.currentUp.multiply(-1); + var gWorkGravityDir = this.currentUp.multiply(-1); var A = new Vector(); A = gWorkGravityDir.multiply(this._gravity); - if (currentTime - this.helicopterEnableTime < 5) { - A = A.multiply(0.25); + var helicopter = isHelicopterEnabled(timeState); + if (helicopter) { + A.load(A.multiply(0.25)); } - for (obj in level.forceObjects) { - var force = cast(obj, ForceObject).getForce(this.getAbsPos().getPosition()); - A = A.add(force.multiply(1 / _mass)); + if (this.level != null) { + var mass = this.getMass(); + for (obj in level.forceObjects) { + var force = cast(obj, ForceObject).getForce(this.collider.transform.getPosition()); + A.load(A.add(force.multiply(1 / mass))); + } + for (marble in level.marbles) { + if ((marble != cast this) && !marble._firstTick) { + var force = marble.getForce(this.collider.transform.getPosition(), timeState.ticks); + A.load(A.add(force.multiply(1 / mass))); + } + } } + if (contacts.length != 0 && this.mode != Start) { var contactForce = 0.0; var contactNormal = new Vector(); @@ -535,13 +621,14 @@ class Marble extends GameObject { if (forceObjectCount != 0) { contactNormal.normalize(); - var a = contactForce / this._mass; + var a = contactForce / this.getMass(); + var dot = this.velocity.dot(contactNormal); if (a > dot) { if (dot > 0) a -= dot; - A = A.add(contactNormal.multiply(a / dt)); + A.load(A.add(contactNormal.multiply(a / timeState.dt))); } } } @@ -551,16 +638,18 @@ class Marble extends GameObject { var motionDir = axes[1]; var upDir = axes[2]; var airAccel = this._airAccel; - if (currentTime - this.helicopterEnableTime < 5) { + if (helicopter) { airAccel *= 2; } - A = A.add(sideDir.multiply(m.d.x).add(motionDir.multiply(m.d.y)).multiply(airAccel)); + A.load(A.add(sideDir.multiply(m.d.x).add(motionDir.multiply(m.d.y)).multiply(airAccel))); } return A; } function computeMoveForces(m:Move, aControl:Vector, desiredOmega:Vector) { - var currentGravityDir = this.level.currentUp.multiply(-1); + if (this.currentUp == null) + this.currentUp = new Vector(0, 0, 1); + var currentGravityDir = this.currentUp.multiply(-1); var R = currentGravityDir.multiply(-this._radius); var rollVelocity = this.omega.cross(R); var axes = this.getMarbleAxis(); @@ -625,30 +714,38 @@ class Marble extends GameObject { } if (noBounce) { - this.velocity = this.velocity.sub(surfaceVel); + this.velocity.load(this.velocity.sub(surfaceVel)); } else if (contacts[i].collider != null) { var otherMarble:Marble = cast contacts[i].collider.go; - var ourMass = this._mass; - var theirMass = otherMarble._mass; + var ourMass = this.getMass(); + var theirMass = otherMarble.getMass(); var bounce = Math.max(this._bounceRestitution, otherMarble._bounceRestitution); var dp = this.velocity.multiply(ourMass).sub(otherMarble.velocity.multiply(theirMass)); var normP = contacts[i].normal.multiply(dp.dot(contacts[i].normal)); - normP = normP.multiply(1 + bounce); + normP.scale(1 + bounce); - otherMarble.velocity = otherMarble.velocity.add(normP.multiply(1 / theirMass)); - contacts[i].velocity = otherMarble.velocity; + velocity.load(velocity.sub(normP.multiply(1 / ourMass))); + if (Math.isNaN(velocity.lengthSq())) { + velocity.set(0, 0, 0); + } + + otherMarble.velocity.load(otherMarble.velocity.add(normP.multiply(1 / theirMass))); + if (Math.isNaN(otherMarble.velocity.lengthSq())) { + otherMarble.velocity.set(0, 0, 0); + } + contacts[i].velocity.load(otherMarble.velocity); } else { if (contacts[i].velocity.length() == 0 && !surfaceSlide && surfaceDot > -this._maxDotSlide * velLen) { - this.velocity = this.velocity.sub(surfaceVel); + this.velocity.load(this.velocity.sub(surfaceVel)); this.velocity.normalize(); - this.velocity = this.velocity.multiply(velLen); + this.velocity.load(this.velocity.multiply(velLen)); surfaceSlide = true; } else if (surfaceDot >= -this._minBounceVel) { - this.velocity = this.velocity.sub(surfaceVel); + this.velocity.load(this.velocity.sub(surfaceVel)); } else { var restitution = this._bounceRestitution; if (currentTime - this.superBounceEnableTime < 5) { @@ -665,7 +762,7 @@ class Marble extends GameObject { bounceEmitter(sVel.length() * restitution, contacts[i].normal); - vAtC = vAtC.sub(contacts[i].normal.multiply(contacts[i].normal.dot(sVel))); + vAtC.load(vAtC.sub(contacts[i].normal.multiply(contacts[i].normal.dot(sVel)))); var vAtCMag = vAtC.length(); if (vAtCMag != 0) { @@ -678,11 +775,11 @@ class Marble extends GameObject { var vAtCDir = vAtC.multiply(1 / vAtCMag); var deltaOmega = contacts[i].normal.cross(vAtCDir).multiply(angVMagnitude); - this.omega = this.omega.add(deltaOmega); + this.omega.load(this.omega.add(deltaOmega)); - this.velocity = this.velocity.sub(deltaOmega.cross(contacts[i].normal.multiply(_radius))); + this.velocity.load(this.velocity.sub(deltaOmega.cross(contacts[i].normal.multiply(_radius)))); } - this.velocity = this.velocity.add(velocityAdd); + this.velocity.load(this.velocity.add(velocityAdd)); } } @@ -710,7 +807,7 @@ class Marble extends GameObject { for (j in 0...contacts.length) { var dir2 = dir.add(contacts[j].normal); if (dir2.lengthSq() < 0.01) { - dir2 = dir2.add(contacts[j].normal); + dir2.load(dir2.add(contacts[j].normal)); } dir = dir2; dir.normalize(); @@ -728,11 +825,11 @@ class Marble extends GameObject { soFar += (dist - outVel * timeToSeparate) / timeToSeparate / contacts[k].normal.dot(dir); } } - // if (soFar < -25) - // soFar = -25; - // if (soFar > 25) - // soFar = 25; - this.velocity = this.velocity.add(dir.multiply(soFar)); + if (soFar < -25) + soFar = -25; + if (soFar > 25) + soFar = 25; + this.velocity.load(this.velocity.add(dir.multiply(soFar))); } // } @@ -742,7 +839,7 @@ class Marble extends GameObject { function applyContactForces(dt:Float, m:Move, isCentered:Bool, aControl:Vector, desiredOmega:Vector, A:Vector) { var a = new Vector(); this._slipAmount = 0; - var gWorkGravityDir = this.level.currentUp.multiply(-1); + var gWorkGravityDir = this.currentUp.multiply(-1); var bestSurface = -1; var bestNormalForce = 0.0; for (i in 0...contacts.length) { @@ -763,7 +860,7 @@ class Marble extends GameObject { sv = 0; } if (sv < this._jumpImpulse) { - this.velocity = this.velocity.add(bestContact.normal.multiply((this._jumpImpulse - sv))); + this.velocity.load(this.velocity.add(bestContact.normal.multiply((this._jumpImpulse - sv)))); if (!playedSounds.contains("data/sound/jump.wav")) { AudioManager.playSound(ResourceLoader.getResource("data/sound/jump.wav", ResourceLoader.getAudio, this.soundResources)); playedSounds.push("data/sound/jump.wav"); @@ -800,8 +897,8 @@ class Marble extends GameObject { slipping = false; } var vAtCDir = vAtC.multiply(1 / vAtCMag); - aFriction = bestContact.normal.multiply(-1).cross(vAtCDir.multiply(-1)).multiply(angAMagnitude); - AFriction = vAtCDir.multiply(-AMagnitude); + aFriction.load(bestContact.normal.multiply(-1).cross(vAtCDir.multiply(-1)).multiply(angAMagnitude)); + AFriction.load(vAtCDir.multiply(-AMagnitude)); this._slipAmount = vAtCMag - totalDeltaV; } if (!slipping) { @@ -825,7 +922,7 @@ class Marble extends GameObject { friction2 = 0; if (mode != Start) friction2 = this._kineticFriction * bestContact.friction; - Aadd = Aadd.multiply(friction2 * bestNormalForce / aAtCMag); + Aadd.load(Aadd.multiply(friction2 * bestNormalForce / aAtCMag)); } A.set(A.x + Aadd.x, A.y + Aadd.y, A.z + Aadd.z); a.set(a.x + aadd.x, a.y + aadd.y, a.z + aadd.z); @@ -983,29 +1080,60 @@ class Marble extends GameObject { position: position, t: deltaT, found: false, - foundContacts: [] + foundContacts: [], + lastContactPos: null, + lastContactNormal: null, + foundMarbles: [], }; } var searchbox = new Bounds(); searchbox.addSpherePos(position.x, position.y, position.z, _radius); searchbox.addSpherePos(position.x + velocity.x * deltaT, position.y + velocity.y * deltaT, position.z + velocity.z * deltaT, _radius); - var foundObjs = this.level.collisionWorld.boundingSearch(searchbox); + var foundObjs = this.collisionWorld.boundingSearch(searchbox); var finalT = deltaT; var found = false; var lastContactPos = new Vector(); - var testTriangles = []; + var testTriangles:Array = []; var finalContacts = []; + var foundMarbles = []; + + // Marble-Marble + var nextPos = position.add(velocity.multiply(deltaT)); + for (marble in this.collisionWorld.marbleEntities) { + if (marble == this.collider) + continue; + var otherPosition = marble.transform.getPosition(); + var isec = Collision.capsuleSphereNearestOverlap(position, nextPos, _radius, otherPosition, marble.radius); + if (isec.result) { + foundMarbles.push(marble); + isec.t *= deltaT; + if (isec.t >= finalT) { + var vel = position.add(velocity.multiply(finalT)).sub(otherPosition); + vel.normalize(); + var newVelLen = this.velocity.sub(marble.velocity).dot(vel); + if (newVelLen < 0.0) { + finalT = isec.t; + + var posDiff = nextPos.sub(position).multiply(isec.t); + var p = posDiff.add(position); + lastContactNormal = p.sub(otherPosition); + lastContactNormal.normalize(); + lastContactPos = p.sub(lastContactNormal.multiply(_radius)); + } + } + } + } // for (iter in 0...10) { // var iterationFound = false; for (obj in foundObjs) { // Its an MP so bruh - if (!obj.go.isCollideable) + if (obj.go != null && !obj.go.isCollideable) continue; var invMatrix = @:privateAccess obj.invTransform; @@ -1031,7 +1159,8 @@ class Marble extends GameObject { Math.max(Math.max(sphereRadius.x, sphereRadius.y), sphereRadius.z) * 2); var currentFinalPos = position.add(relVel.multiply(finalT)); // localpos.add(relLocalVel.multiply(finalT)); - var surfaces = obj.bvh == null ? obj.octree.boundingSearch(boundThing).map(x -> cast x) : obj.bvh.boundingSearch(boundThing); + var surfaces = @:privateAccess obj.grid != null ? @:privateAccess obj.grid.boundingSearch(boundThing) : (obj.bvh == null ? obj.octree.boundingSearch(boundThing) + .map(x -> cast x) : obj.bvh.boundingSearch(boundThing)); for (surf in surfaces) { var surface:CollisionSurface = cast surf; @@ -1044,16 +1173,17 @@ class Marble extends GameObject { // var v0 = surface.points[surface.indices[i]].transformed(tform); // var v = surface.points[surface.indices[i + 1]].transformed(tform); // var v2 = surface.points[surface.indices[i + 2]].transformed(tform); - var v0 = verts.v1; - var v = verts.v2; - var v2 = verts.v3; + var v0 = new Vector(verts.v1x, verts.v1y, verts.v1z); + var v = new Vector(verts.v2x, verts.v2y, verts.v2z); + var v2 = new Vector(verts.v3x, verts.v3y, verts.v3z); // var v0 = surface.points[surface.indices[i]].transformed(obj.transform); // var v = surface.points[surface.indices[i + 1]].transformed(obj.transform); // var v2 = surface.points[surface.indices[i + 2]].transformed(obj.transform); - var triangleVerts = [v0, v, v2]; + // var triangleVerts = [v0, v, v2]; - var surfaceNormal = verts.n; // surface.normals[surface.indices[i]].transformed3x3(obj.transform).normalized(); + var surfaceNormal = new Vector(verts.nx, verts.ny, + verts.nz); // surface.normals[surface.indices[i]].transformed3x3(obj.transform).normalized(); if (obj is DtsObject) surfaceNormal.multiply(-1); var surfaceD = -surfaceNormal.dot(v0); @@ -1069,8 +1199,8 @@ class Marble extends GameObject { // var v2T = v2.transformed(obj.transform); // var vN = surfaceNormal.transformed3x3(obj.transform); testTriangles.push({ - v: [v0, v, v2], - n: surfaceNormal, + v: [v0.clone(), v.clone(), v2.clone()], + n: surfaceNormal.clone(), }); // Time until collision with the plane @@ -1079,22 +1209,6 @@ class Marble extends GameObject { // Are we going to touch the plane during this time step? if (collisionTime >= 0.000001 && finalT >= collisionTime) { var collisionPoint = position.add(relVel.multiply(collisionTime)); - // var lastPoint = v2; - // var j = 0; - // while (j < 3) { - // var testPoint = surface.points[surface.indices[i + j]]; - // if (testPoint != lastPoint) { - // var a = surfaceNormal; - // var b = lastPoint.sub(testPoint); - // var planeNorm = b.cross(a); - // var planeD = -planeNorm.dot(testPoint); - // lastPoint = testPoint; - // // if we are on the far side of the edge - // if (planeNorm.dot(collisionPoint) + planeD >= 0.0) - // break; - // } - // j++; - // } // If we're inside the poly, just get the position if (Collision.PointInTriangle(collisionPoint, v0, v, v2)) { finalT = collisionTime; @@ -1107,8 +1221,9 @@ class Marble extends GameObject { } } // We *might* be colliding with an edge + var triangleVerts = [v0.clone(), v.clone(), v2.clone()]; - var lastVert = v2; + var lastVert = v2.clone(); var radSq = radius * radius; for (iter in 0...3) { @@ -1129,7 +1244,7 @@ class Marble extends GameObject { // If it's not quadratic or has no solution, ignore this edge. if (a == 0.0 || discriminant < 0.0) { - lastVert = thisVert; + lastVert.load(thisVert); continue; } @@ -1155,14 +1270,6 @@ class Marble extends GameObject { // Check if the collision hasn't already happened if (edgeCollisionTime >= 0.000001) { - // if (edgeCollisionTime < 0.000001) { - // edgeCollisionTime = edgeCollisionTime2; - // } - // if (edgeCollisionTime < 0.00001) - // continue; - // if (edgeCollisionTime > finalT) - // continue; - var edgeLen = vertDiff.length(); var relativeCollisionPos = position.add(relVel.multiply(edgeCollisionTime)).sub(thisVert); @@ -1171,7 +1278,7 @@ class Marble extends GameObject { // If the collision happens outside the boundaries of the edge, ignore this edge. if (-radius > distanceAlongEdge || edgeLen + radius < distanceAlongEdge) { - lastVert = thisVert; + lastVert.load(thisVert); continue; } @@ -1180,7 +1287,7 @@ class Marble extends GameObject { finalT = edgeCollisionTime; currentFinalPos = position.add(relVel.multiply(finalT)); lastContactPos = vertDiff.multiply(distanceAlongEdge / edgeLen).add(thisVert); - lastVert = thisVert; + lastVert.load(thisVert); found = true; // Debug.drawSphere(currentFinalPos, radius); // iterationFound = true; @@ -1235,14 +1342,14 @@ class Marble extends GameObject { // We still need to check the other corner ... // Build one last quadratic equation to solve for the collision time - posVertDiff = position.sub(lastVert); + var posVertDiff = position.sub(lastVert); b = 2 * posVertDiff.dot(relVel); c = posVertDiff.lengthSq() - radSq; discriminant = b * b - (4 * a * c); // If it's not quadratic or has no solution, then skip this corner if (a == 0.0 || discriminant < 0.0) { - lastVert = thisVert; + lastVert.load(thisVert); continue; } @@ -1261,7 +1368,7 @@ class Marble extends GameObject { } if (edgeCollisionTime2 <= 0.0001 || finalT <= edgeCollisionTime) { - lastVert = thisVert; + lastVert.load(thisVert); continue; } @@ -1269,7 +1376,7 @@ class Marble extends GameObject { edgeCollisionTime = 0; if (edgeCollisionTime < 0.000001) { - lastVert = thisVert; + lastVert.load(thisVert); continue; } @@ -1277,7 +1384,7 @@ class Marble extends GameObject { currentFinalPos = position.add(relVel.multiply(finalT)); // Debug.drawSphere(currentFinalPos, radius); - lastVert = thisVert; + lastVert.load(thisVert); found = true; // iterationFound = true; } @@ -1294,30 +1401,18 @@ class Marble extends GameObject { var finalPosition = position.add(deltaPosition); position = finalPosition; - // for (testTri in testTriangles) { - // var tsi = Collision.TriangleSphereIntersection(testTri.v[0], testTri.v[1], testTri.v[2], testTri.n, finalPosition, radius, testTri.edge, - // testTri.concavity); - // if (tsi.result) { - // var contact = new CollisionInfo(); - // contact.point = tsi.point; - // contact.normal = tsi.normal; - // contact.contactDistance = tsi.point.distance(position); - // finalContacts.push(contact); - // } - // } - return { position: position, t: finalT, found: found, - foundContacts: testTriangles + foundContacts: testTriangles, + lastContactPos: lastContactPos, + lastContactNormal: position.sub(lastContactPos).normalized(), + foundMarbles: foundMarbles }; } - function nudgeToContacts(position:Vector, radius:Float, foundContacts:Array<{ - v:Array, - n:Vector - }>) { + function nudgeToContacts(position:Vector, radius:Float, foundContacts:Array, foundMarbles:Array) { var it = 0; var concernedContacts = foundContacts; // PathedInteriors have their own nudge logic var prevResolved = 0; @@ -1350,7 +1445,7 @@ class Marble extends GameObject { // Nudge to the surface of the contact plane Debug.drawTriangle(testTri.v[0], testTri.v[1], testTri.v[2]); Debug.drawSphere(position, radius); - position = position.add(separatingDistance.multiply(radius - distToContactPlane - 0.005)); + position.load(position.add(separatingDistance.multiply(radius - distToContactPlane - 0.005))); resolved++; } } @@ -1376,6 +1471,14 @@ class Marble extends GameObject { prevResolved = resolved; it++; } while (true && it < 10); + for (marble in foundMarbles) { + var marblePosition = marble.transform.getPosition(); + var dist = marblePosition.distance(position); + if (dist < radius + marble.radius + 0.001) { + var separatingDistance = position.sub(marblePosition).normalized(); + position.load(position.add(separatingDistance.multiply(radius + marble.radius + 0.001 - dist))); + } + } return position; } @@ -1431,8 +1534,18 @@ class Marble extends GameObject { var isCentered = this.computeMoveForces(m, aControl, desiredOmega); stoppedPaths = this.velocityCancel(timeState.currentAttemptTime, timeStep, isCentered, false, stoppedPaths, pathedInteriors); - var A = this.getExternalForces(timeState.currentAttemptTime, m, timeStep); + var A = this.getExternalForces(tempState, m); var a = this.applyContactForces(timeStep, m, isCentered, aControl, desiredOmega, A); + + // NaN check so OpenAL doesn't freak out + if (Math.isNaN(A.lengthSq())) { + A.set(0, 0, 0); + } + + if (Math.isNaN(a.lengthSq())) { + a.set(0, 0, 0); + } + this.velocity.set(this.velocity.x + A.x * timeStep, this.velocity.y + A.y * timeStep, this.velocity.z + A.z * timeStep); this.omega.set(this.omega.x + a.x * timeStep, this.omega.y + a.y * timeStep, this.omega.z + a.z * timeStep); if (this.mode == Start) { @@ -1472,7 +1585,7 @@ class Marble extends GameObject { } var expectedPos = finalPosData.position; // var newPos = expectedPos; - var newPos = nudgeToContacts(expectedPos, _radius, finalPosData.foundContacts); + var newPos = nudgeToContacts(expectedPos, _radius, finalPosData.foundContacts, finalPosData.foundMarbles); if (this.velocity.lengthSq() > 1e-8) { var posDiff = newPos.sub(expectedPos); @@ -1541,13 +1654,14 @@ class Marble extends GameObject { this.setPosition(newPos.x, newPos.y, newPos.z); this.collider.setTransform(totMatrix); + this.collisionWorld.updateTransform(this.collider); this.collider.velocity = this.velocity; - if (this.heldPowerup != null && m.powerup && !this.level.outOfBounds) { + if (this.heldPowerup != null && m.powerup && !this.outOfBounds) { var pTime = timeState.clone(); pTime.dt = timeStep; pTime.currentAttemptTime = passedTime; - this.heldPowerup.use(pTime); + this.heldPowerup.use(this, pTime); this.heldPowerup = null; if (this.level.isRecording) { this.level.replay.recordPowerupPickup(null); @@ -1723,39 +1837,82 @@ class Marble extends GameObject { } } + public function getMass() { + if (this.level == null) + return 1; + if (this.level.timeState.currentAttemptTime - this.megaMarbleEnableTime < 10) { + return 5; + } else { + return 1; + } + } + public function useBlast() { if (this.level.blastAmount < 0.2 || this.level.game != "ultra") return; - var impulse = this.level.currentUp.multiply(Math.max(Math.sqrt(this.level.blastAmount), this.level.blastAmount) * 10); + var impulse = this.currentUp.multiply(Math.max(Math.sqrt(this.level.blastAmount), this.level.blastAmount) * 10); this.applyImpulse(impulse); AudioManager.playSound(ResourceLoader.getResource('data/sound/blast.wav', ResourceLoader.getAudio, this.soundResources)); this.level.particleManager.createEmitter(this.level.blastAmount > 1 ? blastMaxParticleOptions : blastParticleOptions, this.level.blastAmount > 1 ? blastMaxEmitterData : blastEmitterData, this.getAbsPos().getPosition(), () -> { - this.getAbsPos().getPosition().add(this.level.currentUp.multiply(-this._radius * 0.4)); + this.getAbsPos().getPosition().add(this.currentUp.multiply(-this._radius * 0.4)); }, - new Vector(1, 1, - 1).add(new Vector(Math.abs(this.level.currentUp.x), Math.abs(this.level.currentUp.y), Math.abs(this.level.currentUp.z)).multiply(-0.8))); + new Vector(1, 1, 1).add(new Vector(Math.abs(this.currentUp.x), Math.abs(this.currentUp.y), Math.abs(this.currentUp.z)).multiply(-0.8))); this.level.blastAmount = 0; } + public function getForce(position:Vector, tick:Int) { + var retForce = new Vector(); + if (tick - blastUseTick >= 12) + return retForce; + var delta = position.sub(newPos); + var deltaLen = delta.length(); + + var maxDist = Math.max(blastRepulseDist, blastRepulseDist * blastPerc); + var maxRepulse = maxBlastRepulse * blastPerc; + + if (deltaLen > maxDist) + return retForce; + + if (deltaLen >= 0.05) { + var dist = 0.0; + if (deltaLen >= 1.0) + dist = (1.0 / deltaLen - 1.0 / maxDist) * maxRepulse; + else + dist = maxRepulse / deltaLen; + + retForce.load(retForce.add(delta.multiply(dist))); + } else { + retForce.load(retForce.add(this.currentUp.multiply(maxRepulse))); + } + return retForce; + } + public function applyImpulse(impulse:Vector, contactImpulse:Bool = false) { this.appliedImpulses.push({impulse: impulse, contactImpulse: contactImpulse}); } - public function enableSuperBounce(time:Float) { - this.superBounceEnableTime = time; + public function enableSuperBounce(timeState:TimeState) { + this.superBounceEnableTime = timeState.currentAttemptTime; } - public function enableShockAbsorber(time:Float) { - this.shockAbsorberEnableTime = time; + public function enableShockAbsorber(timeState:TimeState) { + this.shockAbsorberEnableTime = timeState.currentAttemptTime; } - public function enableHelicopter(time:Float) { - this.helicopterEnableTime = time; + public function enableHelicopter(timeState:TimeState) { + this.helicopterEnableTime = timeState.currentAttemptTime; } - public function enableMegaMarble(time:Float) { - this.megaMarbleEnableTime = time; + inline function isHelicopterEnabled(timeState:TimeState) { + if (this.level == null) + return false; + + return timeState.currentAttemptTime - this.helicopterEnableTime < 5; + } + + public function enableMegaMarble(timeState:TimeState) { + this.megaMarbleEnableTime = timeState.currentAttemptTime; } function updateTeleporterState(time:TimeState) { diff --git a/src/MarbleGame.hx b/src/MarbleGame.hx index d9cb6695..5d5a89ab 100644 --- a/src/MarbleGame.hx +++ b/src/MarbleGame.hx @@ -238,7 +238,7 @@ class MarbleGame { getWorld().setCursorLock(true); }, (sender) -> { canvas.popDialog(exitGameDlg); - getWorld().restart(true); + getWorld().restart(w.marble, true); // world.setCursorLock(true); paused = !paused; }); diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index 8281b5cd..31b3871f 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -114,6 +114,7 @@ class MarbleWorld extends Scheduler { public var pathedInteriors:Array = []; public var marbles:Array = []; public var dtsObjects:Array = []; + public var powerUps:Array = []; public var forceObjects:Array = []; public var triggers:Array = []; public var gems:Array = []; @@ -141,10 +142,6 @@ class MarbleWorld extends Scheduler { public var game:String; public var marble:Marble; - public var worldOrientation:Quat; - public var currentUp = new Vector(0, 0, 1); - public var outOfBounds:Bool = false; - public var outOfBoundsTime:TimeState; public var finishTime:TimeState; public var finishPitch:Float; public var finishYaw:Float; @@ -198,14 +195,13 @@ class MarbleWorld extends Scheduler { var _loadingLength:Int = 0; var _resourcesLoaded:Int = 0; - var _cubemapNeedsUpdate:Bool = true; var textureResources:Array> = []; var soundResources:Array> = []; var oobSchedule:Float; - var oobSchedule2:Float; + var _instancesNeedsUpdate:Bool = false; var lock:Bool = false; public function new(scene:Scene, scene2d:h2d.Scene, mission:Mission, record:Bool = false) { @@ -215,7 +211,7 @@ class MarbleWorld extends Scheduler { this.game = mission.game.toLowerCase(); this.replay = new Replay(mission.path, mission.isClaMission ? mission.id : 0); this.isRecording = record; - this.rewindManager = new RewindManager(this); + this.rewindManager = new RewindManager(cast this); } public function init() { @@ -440,14 +436,14 @@ class MarbleWorld extends Scheduler { public function start() { Console.log("LEVEL START"); - restart(true); + restart(this.marble, true); for (interior in this.interiors) interior.onLevelStart(); for (shape in this.dtsObjects) shape.onLevelStart(); } - public function restart(full:Bool = false) { + public function restart(marble:Marble, full:Bool = false) { Console.log("LEVEL RESTART"); if (!full && this.currentCheckpoint != null) { this.loadCheckpointState(); @@ -464,10 +460,11 @@ class MarbleWorld extends Scheduler { this.timeState.currentAttemptTime = 0; this.timeState.gameplayClock = 0; + this.timeState.ticks = 0; this.bonusTime = 0; - this.outOfBounds = false; + this.marble.outOfBounds = false; this.blastAmount = 0; - this.outOfBoundsTime = null; + this.marble.outOfBoundsTime = null; this.finishTime = null; if (this.alarmSound != null) { this.alarmSound.stop(); @@ -527,6 +524,7 @@ class MarbleWorld extends Scheduler { } } } + this.cancel(this.marble.oobSchedule); var startquat = this.getStartPositionAndOrientation(); @@ -558,11 +556,12 @@ class MarbleWorld extends Scheduler { for (interior in this.interiors) interior.reset(); - this.currentUp = new Vector(0, 0, 1); + this.setUp(this.marble, startquat.up, this.timeState, true); + this.deselectPowerUp(this.marble); this.orientationChangeTime = -1e8; this.oldOrientationQuat = new Quat(); this.newOrientationQuat = new Quat(); - this.deselectPowerUp(); + this.deselectPowerUp(this.marble); AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn.wav', ResourceLoader.getAudio, this.soundResources)); @@ -592,7 +591,7 @@ class MarbleWorld extends Scheduler { } public function updateGameState() { - if (this.outOfBounds) + if (this.marble.outOfBounds) return; // We will update state manually if (this.timeState.currentAttemptTime < 0.5 && this.finishTime == null) { this.playGui.setCenterText('none'); @@ -631,7 +630,8 @@ class MarbleWorld extends Scheduler { return { position: position, quat: quat, - pad: startPad + pad: startPad, + up: new Vector(0, 0, 1) }; } @@ -951,10 +951,10 @@ class MarbleWorld extends Scheduler { } public function addMarble(marble:Marble, onFinish:Void->Void) { - this.marbles.push(marble); marble.level = cast this; if (marble.controllable) { marble.init(cast this, () -> { + this.marbles.push(marble); this.scene.addChild(marble.camera); this.marble = marble; // Ugly hack @@ -972,7 +972,7 @@ class MarbleWorld extends Scheduler { public function performRestart() { this.respawnPressedTime = timeState.timeSinceLoad; - this.restart(); + this.restart(this.marble); if (!this.isWatching) { Settings.playStatistics.respawns++; @@ -1063,7 +1063,7 @@ class MarbleWorld extends Scheduler { && !this.isWatching && this.finishTime == null) { if (timeState.timeSinceLoad - this.respawnPressedTime > 1.5) { - this.restart(true); + this.restart(this.marble, true); this.respawnPressedTime = Math.POSITIVE_INFINITY; return; } @@ -1088,7 +1088,7 @@ class MarbleWorld extends Scheduler { // Replay gravity if (this.isWatching) { if (this.replay.currentPlaybackFrame.gravityChange) { - this.setUp(this.replay.currentPlaybackFrame.gravity, timeState, this.replay.currentPlaybackFrame.gravityInstant); + this.setUp(this.marble, this.replay.currentPlaybackFrame.gravity, timeState, this.replay.currentPlaybackFrame.gravityInstant); } if (this.replay.currentPlaybackFrame.powerupPickup != null) { this.pickUpPowerUpReplay(this.replay.currentPlaybackFrame.powerupPickup); @@ -1112,8 +1112,7 @@ class MarbleWorld extends Scheduler { // Update camera separately marble.camera.update(timeState.currentAttemptTime, realDt); } - ProfilerUI.measure("updateInstances"); - this.instanceManager.render(); + ProfilerUI.measure("updateParticles"); if (this.rewinding) { this.particleManager.update(1000 * timeState.timeSinceLoad, -realDt * rewindManager.timeScale); @@ -1124,11 +1123,11 @@ class MarbleWorld extends Scheduler { ProfilerUI.measure("updateAudio"); AudioManager.update(this.scene); - if (this.outOfBounds + if (this.marble.outOfBounds && this.finishTime == null && (Key.isDown(Settings.controlsSettings.powerup) || Gamepad.isDown(Settings.gamepadSettings.powerup)) && !this.isWatching) { - this.restart(); + this.restart(this.marble); return; } @@ -1141,7 +1140,7 @@ class MarbleWorld extends Scheduler { if (!this.rewinding && Settings.optionsSettings.rewindEnabled) this.rewindManager.recordFrame(); - _cubemapNeedsUpdate = true; + _instancesNeedsUpdate = true; this.updateTexts(); } @@ -1151,11 +1150,14 @@ class MarbleWorld extends Scheduler { asyncLoadResources(); if (this.playGui != null && _ready) this.playGui.render(e); - if (this.marble != null && this.marble.cubemapRenderer != null && _cubemapNeedsUpdate && _ready) { - _cubemapNeedsUpdate = false; + if (this.marble != null && this.marble.cubemapRenderer != null && _instancesNeedsUpdate && _ready) { this.marble.cubemapRenderer.position.load(this.marble.getAbsPos().getPosition()); this.marble.cubemapRenderer.render(e, 0.002); } + if (_instancesNeedsUpdate) { + _instancesNeedsUpdate = false; + this.instanceManager.render(); + } } var postInited = false; @@ -1362,7 +1364,7 @@ class MarbleWorld extends Scheduler { this.helpTextTimeState = this.timeState.timeSinceLoad; } - public function pickUpGem(gem:Gem) { + public function pickUpGem(marble:src.Marble, gem:Gem) { this.gemCount++; var string:String; @@ -1419,10 +1421,10 @@ class MarbleWorld extends Scheduler { var shape:DtsObject = cast contact.go; if (contact.boundingBox.collide(box)) { - shape.onMarbleInside(timeState); + shape.onMarbleInside(marble, timeState); if (!this.shapeOrTriggerInside.contains(contact.go)) { this.shapeOrTriggerInside.push(contact.go); - shape.onMarbleEnter(timeState); + shape.onMarbleEnter(marble, timeState); } inside.push(contact.go); } @@ -1432,10 +1434,10 @@ class MarbleWorld extends Scheduler { var triggeraabb = trigger.collider.boundingBox; if (triggeraabb.collide(box)) { - trigger.onMarbleInside(timeState); + trigger.onMarbleInside(marble, timeState); if (!this.shapeOrTriggerInside.contains(contact.go)) { this.shapeOrTriggerInside.push(contact.go); - trigger.onMarbleEnter(timeState); + trigger.onMarbleEnter(marble, timeState); } inside.push(contact.go); } @@ -1446,7 +1448,7 @@ class MarbleWorld extends Scheduler { for (object in shapeOrTriggerInside) { if (!inside.contains(object)) { this.shapeOrTriggerInside.remove(object); - object.onMarbleLeave(timeState); + object.onMarbleLeave(marble, timeState); } } @@ -1520,7 +1522,7 @@ class MarbleWorld extends Scheduler { function touchFinish() { if (this.finishTime != null - || (this.outOfBounds && this.timeState.currentAttemptTime - this.outOfBoundsTime.currentAttemptTime >= 0.5)) + || (this.marble.outOfBounds && this.timeState.currentAttemptTime - this.marble.outOfBoundsTime.currentAttemptTime >= 0.5)) return; if (this.gemCount < this.totalGems) { @@ -1593,7 +1595,7 @@ class MarbleWorld extends Scheduler { }, (sender) -> { var restartGameCode = () -> { MarbleGame.canvas.popDialog(egg); - this.restart(true); + this.restart(this.marble, true); #if js pointercontainer.hidden = true; #end @@ -1640,26 +1642,30 @@ class MarbleWorld extends Scheduler { return true; } - public function pickUpPowerUp(powerUp:PowerUp) { + public function pickUpPowerUp(marble:Marble, powerUp:PowerUp) { if (powerUp == null) return false; - if (this.marble.heldPowerup != null) - if (this.marble.heldPowerup.identifier == powerUp.identifier) + if (marble.heldPowerup != null) + if (marble.heldPowerup.identifier == powerUp.identifier) return false; Console.log("PowerUp pickup: " + powerUp.identifier); - this.marble.heldPowerup = powerUp; - this.playGui.setPowerupImage(powerUp.identifier); - MarbleGame.instance.touchInput.powerupButton.setEnabled(true); - if (this.isRecording) { - this.replay.recordPowerupPickup(powerUp); + marble.heldPowerup = powerUp; + if (this.marble == marble) { + this.playGui.setPowerupImage(powerUp.identifier); + MarbleGame.instance.touchInput.powerupButton.setEnabled(true); + if (this.isRecording) { + this.replay.recordPowerupPickup(powerUp); + } } return true; } - public function deselectPowerUp() { - this.marble.heldPowerup = null; - this.playGui.setPowerupImage(""); - MarbleGame.instance.touchInput.powerupButton.setEnabled(false); + public function deselectPowerUp(marble:Marble) { + marble.heldPowerup = null; + if (this.marble == marble) { + this.playGui.setPowerupImage(""); + MarbleGame.instance.touchInput.powerupButton.setEnabled(false); + } } public function addBonusTime(t:Float) { @@ -1681,63 +1687,67 @@ class MarbleWorld extends Scheduler { return q; } - public function setUp(vec:Vector, timeState:TimeState, instant:Bool = false) { - this.currentUp = vec; - var currentQuat = this.getOrientationQuat(timeState.currentAttemptTime); - var oldUp = new Vector(0, 0, 1); - oldUp.transform(currentQuat.toMatrix()); + public function setUp(marble:Marble, vec:Vector, timeState:TimeState, instant:Bool = false) { + if (marble.currentUp == vec) + return; + marble.currentUp = vec; + if (marble == this.marble) { + var currentQuat = this.getOrientationQuat(timeState.currentAttemptTime); + var oldUp = new Vector(0, 0, 1); + oldUp.transform(currentQuat.toMatrix()); - function getRotQuat(v1:Vector, v2:Vector) { - function orthogonal(v:Vector) { - var x = Math.abs(v.x); - var y = Math.abs(v.y); - var z = Math.abs(v.z); - var other = x < y ? (x < z ? new Vector(1, 0, 0) : new Vector(0, 0, 1)) : (y < z ? new Vector(0, 1, 0) : new Vector(0, 0, 1)); - return v.cross(other); - } + function getRotQuat(v1:Vector, v2:Vector) { + function orthogonal(v:Vector) { + var x = Math.abs(v.x); + var y = Math.abs(v.y); + var z = Math.abs(v.z); + var other = x < y ? (x < z ? new Vector(1, 0, 0) : new Vector(0, 0, 1)) : (y < z ? new Vector(0, 1, 0) : new Vector(0, 0, 1)); + return v.cross(other); + } - var u = v1.normalized(); - var v = v2.normalized(); - if (u.dot(v) == -1) { + var u = v1.normalized(); + var v = v2.normalized(); + if (u.dot(v) == -1) { + var q = new Quat(); + var o = orthogonal(u).normalized(); + q.x = o.x; + q.y = o.y; + q.z = o.z; + q.w = 0; + return q; + } + var half = u.add(v).normalized(); var q = new Quat(); - var o = orthogonal(u).normalized(); - q.x = o.x; - q.y = o.y; - q.z = o.z; - q.w = 0; + q.w = u.dot(half); + var vr = u.cross(half); + q.x = vr.x; + q.y = vr.y; + q.z = vr.z; return q; } - var half = u.add(v).normalized(); - var q = new Quat(); - q.w = u.dot(half); - var vr = u.cross(half); - q.x = vr.x; - q.y = vr.y; - q.z = vr.z; - return q; + + var quatChange = getRotQuat(oldUp, vec); + // Instead of calculating the new quat from nothing, calculate it from the last one to guarantee the shortest possible rotation. + // quatChange.initMoveTo(oldUp, vec); + quatChange.multiply(quatChange, currentQuat); + + if (this.isRecording) { + this.replay.recordGravity(vec, instant); + } + + this.newOrientationQuat = quatChange; + this.oldOrientationQuat = currentQuat; + this.orientationChangeTime = instant ? -1e8 : timeState.currentAttemptTime; } - - var quatChange = getRotQuat(oldUp, vec); - // Instead of calculating the new quat from nothing, calculate it from the last one to guarantee the shortest possible rotation. - // quatChange.initMoveTo(oldUp, vec); - quatChange.multiply(quatChange, currentQuat); - - if (this.isRecording) { - this.replay.recordGravity(vec, instant); - } - - this.newOrientationQuat = quatChange; - this.oldOrientationQuat = currentQuat; - this.orientationChangeTime = instant ? -1e8 : timeState.currentAttemptTime; } - public function goOutOfBounds() { - if (this.outOfBounds || this.finishTime != null) + public function goOutOfBounds(marble:Marble) { + if (marble.outOfBounds || this.finishTime != null) return; // 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; + marble.outOfBounds = true; + marble.outOfBoundsTime = this.timeState.clone(); + marble.camera.oob = true; if (!this.isWatching) { Settings.playStatistics.oobs++; if (!Settings.levelStatistics.exists(mission.path)) { @@ -1761,8 +1771,8 @@ class MarbleWorld extends Scheduler { playGui.setCenterText('none'); return null; }); - this.oobSchedule2 = this.schedule(this.timeState.currentAttemptTime + 2.5, () -> { - this.restart(); + marble.oobSchedule = this.schedule(this.timeState.currentAttemptTime + 2.5, () -> { + this.restart(marble); return null; }); } @@ -1782,12 +1792,12 @@ class MarbleWorld extends Scheduler { disableOob = trigger.disableOOB; } // (shape.srcElement as any) ?.disableOob || trigger?.element.disableOob; - if (disableOob && this.outOfBounds) + if (disableOob && this.marble.outOfBounds) return; // The checkpoint is configured to not work when the player is already OOB this.currentCheckpoint = shape; this.currentCheckpointTrigger = trigger; this.checkpointCollectedGems.clear(); - this.checkpointUp = this.currentUp.clone(); + this.checkpointUp = this.marble.currentUp.clone(); this.cheeckpointBlast = this.blastAmount; // Remember all gems that were collected up to this point for (gem in this.gems) { @@ -1863,10 +1873,10 @@ class MarbleWorld extends Scheduler { // In this case, we set the gravity to the relative "up" vector of the checkpoint shape. var up = new Vector(0, 0, 1); up.transform(this.currentCheckpoint.obj.getRotationQuat().toMatrix()); - this.setUp(up, this.timeState, true); + this.setUp(this.marble, up, this.timeState, true); } else { // Otherwise, we restore gravity to what was stored. - this.setUp(this.checkpointUp, this.timeState, true); + this.setUp(this.marble, this.checkpointUp, this.timeState, true); } // Restore gem states for (gem in this.gems) { @@ -1878,11 +1888,11 @@ class MarbleWorld extends Scheduler { this.playGui.formatGemCounter(this.gemCount, this.totalGems); this.playGui.setCenterText('none'); this.clearSchedule(); - this.outOfBounds = false; - this.deselectPowerUp(); // Always deselect first + this.marble.outOfBounds = false; + this.deselectPowerUp(this.marble); // Always deselect first // Wait a bit to select the powerup to prevent immediately using it incase the user skipped the OOB screen by clicking if (this.checkpointHeldPowerup != null) - this.schedule(this.timeState.currentAttemptTime + 0.5, () -> this.pickUpPowerUp(this.checkpointHeldPowerup)); + this.pickUpPowerUp(this.marble, this.checkpointHeldPowerup); AudioManager.playSound(ResourceLoader.getResource('data/sound/spawn.wav', ResourceLoader.getAudio, this.soundResources)); } @@ -1975,6 +1985,7 @@ class MarbleWorld extends Scheduler { dtsObject.dispose(); } dtsObjects = null; + powerUps = []; for (trigger in this.triggers) { trigger.dispose(); } diff --git a/src/ParticleSystem.hx b/src/ParticleSystem.hx index cf0d2bc1..9ef36a1d 100644 --- a/src/ParticleSystem.hx +++ b/src/ParticleSystem.hx @@ -1,11 +1,9 @@ package src; import shaders.DtsTexture; -import h3d.parts.Particles; import h3d.Matrix; import src.TimeState; import h3d.prim.UV; -import h3d.parts.Data.BlendMode; import src.MarbleWorld; import src.Util; import h3d.mat.Data.Wrap; @@ -33,7 +31,7 @@ class ParticleData { @:publicFields class Particle { - public var part:h3d.parts.Particle; + public var part:src.ParticlesMesh.ParticleElement; var data:ParticleData; var manager:ParticleManager; @@ -61,15 +59,15 @@ class Particle { this.lifeTime = this.o.lifetime + this.o.lifetimeVariance * (Math.random() * 2 - 1); this.initialSpin = Util.lerp(this.o.spinRandomMin, this.o.spinRandomMax, Math.random()); - this.part = new h3d.parts.Particle(); + this.part = new src.ParticlesMesh.ParticleElement(); } public function update(time:Float, dt:Float) { var t = dt; var a = this.acc; - a = a.sub(this.vel.multiply(this.o.dragCoefficient)); - this.vel = this.vel.add(a.multiply(dt)); - this.position = this.position.add(this.vel.multiply(dt)); + a.load(a.sub(this.vel.multiply(this.o.dragCoefficient))); + this.vel.load(this.vel.add(a.multiply(dt))); + this.position.load(this.position.add(this.vel.multiply(dt))); this.currentAge += dt; @@ -137,8 +135,7 @@ class Particle { var t = (completion - this.o.times[indexLow]) / (this.o.times[indexHigh] - this.o.times[indexLow]); // Adjust color - var color = Util.lerpThreeVectors(this.o.colors[indexLow], this.o.colors[indexHigh], t); - this.color = color; + this.color = Util.lerpThreeVectors(this.o.colors[indexLow], this.o.colors[indexHigh], t); // this.material.opacity = color.a * * 1.5; // Adjusted because additive mixing can be kind of extreme // Adjust sizing @@ -150,6 +147,7 @@ class Particle { this.part.r = this.color.r; this.part.g = this.color.g; this.part.b = this.color.b; + this.part.a = this.color.a; this.part.ratio = 1; this.part.size = this.scale / 2; } @@ -257,7 +255,7 @@ class ParticleEmitter { this.currentWaitPeriod = this.o.ejectionPeriod; var pos = this.getPosAtTime(time).clone(); if (this.o.spawnOffset != null) - pos = pos.add(this.o.spawnOffset()); // Call the spawnOffset function if it's there + pos.load(pos.add(this.o.spawnOffset())); // Call the spawnOffset function if it's there // This isn't necessarily uniform but it's fine for the purpose. var randomPointOnSphere = new Vector(Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1).normalized(); randomPointOnSphere.x *= this.spawnSphereSquish.x; @@ -298,7 +296,7 @@ class ParticleManager { var scene:Scene; var currentTime:Float; - var particleGroups:Map = []; + var particleGroups:Map = []; var particles:Array = []; var emitters:Array = []; @@ -321,7 +319,7 @@ class ParticleManager { if (particleGroups.exists(particleData.identifier)) { particleGroups[particleData.identifier].add(particle.part); } else { - var pGroup = new Particles(particle.data.texture, this.scene); + var pGroup = new src.ParticlesMesh.ParticlesMesh(particle.data.texture, this.scene); pGroup.hasColor = true; pGroup.material.setDefaultProps("ui"); // var pdts = new DtsTexture(pGroup.material.texture); diff --git a/src/ParticlesMesh.hx b/src/ParticlesMesh.hx new file mode 100644 index 00000000..ed90a18b --- /dev/null +++ b/src/ParticlesMesh.hx @@ -0,0 +1,396 @@ +package src; + +private class ParticleIterator { + var p:ParticleElement; + + public inline function new(p) { + this.p = p; + } + + public inline function hasNext() { + return p != null; + } + + public inline function next() { + var v = p; + p = p.next; + return v; + } +} + +enum SortMode { + Front; + Back; + Sort; + InvSort; +} + +class ParticleElement { + public var parts:ParticlesMesh; + + public var x:Float; + public var y:Float; + public var z:Float; + + public var w:Float; // used for sorting + + public var r:Float; + public var g:Float; + public var b:Float; + public var a:Float; + public var alpha(get, set):Float; + + public var frame:Int; + + public var size:Float; + public var ratio:Float; + public var rotation:Float; + + public var prev:ParticleElement; + public var next:ParticleElement; + + // --- Particle emitter --- + public var time:Float; + public var lifeTimeFactor:Float; + + public var dx:Float; + public var dy:Float; + public var dz:Float; + + public var fx:Float; + public var fy:Float; + public var fz:Float; + + public var randIndex = 0; + public var randValues:Array; + + // ------------------------- + + public function new() { + r = 1; + g = 1; + b = 1; + a = 1; + frame = 0; + } + + inline function get_alpha() + return a; + + inline function set_alpha(v) + return a = v; + + public function setColor(color:Int, alpha = 1.) { + a = alpha; + r = ((color >> 16) & 0xFF) / 255.; + g = ((color >> 8) & 0xFF) / 255.; + b = (color & 0xFF) / 255.; + } + + public function remove() { + if (parts != null) { + @:privateAccess parts.kill(this); + parts = null; + } + } + + public function rand():Float { + if (randValues == null) + randValues = []; + if (randValues.length <= randIndex) + randValues.push(Math.random()); + return randValues[randIndex++]; + } +} + +class ParticlesMesh extends h3d.scene.Mesh { + var pshader:h3d.shader.ParticleShader; + + public var frames:Array; + public var count(default, null):Int = 0; + public var hasColor(default, set):Bool; + public var sortMode:SortMode; + public var globalSize:Float = 1; + + var head:ParticleElement; + var tail:ParticleElement; + var pool:ParticleElement; + + var tmp:h3d.Vector; + var tmpBuf:hxd.FloatBuffer; + var buffer:h3d.Buffer; + var bufferSize:Int = 0; + + public function new(?texture, ?parent) { + super(null, null, parent); + material.props = material.getDefaultProps("particles3D"); + sortMode = Back; + pshader = new h3d.shader.ParticleShader(); + pshader.isAbsolute = true; + material.mainPass.addShader(pshader); + material.mainPass.dynamicParameters = true; + material.texture = texture; + tmp = new h3d.Vector(); + } + + function set_hasColor(b) { + var c = material.mainPass.getShader(h3d.shader.VertexColorAlpha); + if (b) { + if (c == null) + material.mainPass.addShader(new h3d.shader.VertexColorAlpha()); + } else { + if (c != null) + material.mainPass.removeShader(c); + } + return hasColor = b; + } + + /** + Offset all existing particles by the given values. + **/ + public function offsetParticles(dx:Float, dy:Float, dz = 0.) { + var p = head; + while (p != null) { + p.x += dx; + p.y += dy; + p.z += dz; + p = p.next; + } + } + + public function clear() { + while (head != null) + kill(head); + } + + public function alloc() { + var p = emitParticle(); + if (posChanged) + syncPos(); + p.parts = this; + p.x = absPos.tx; + p.y = absPos.ty; + p.z = absPos.tz; + p.rotation = 0; + p.ratio = 1; + p.size = 1; + p.r = p.g = p.b = p.a = 1; + return p; + } + + public function add(p) { + emitParticle(p); + return p; + } + + function emitParticle(?p) { + if (p == null) { + if (pool == null) + p = new ParticleElement(); + else { + p = pool; + pool = p.next; + } + } + count++; + switch (sortMode) { + case Front, Sort, InvSort: + if (head == null) { + p.next = null; + head = tail = p; + } else { + head.prev = p; + p.next = head; + head = p; + } + case Back: + if (head == null) { + p.next = null; + head = tail = p; + } else { + tail.next = p; + p.prev = tail; + p.next = null; + tail = p; + } + } + return p; + } + + function kill(p:ParticleElement) { + if (p.prev == null) + head = p.next + else + p.prev.next = p.next; + if (p.next == null) + tail = p.prev + else + p.next.prev = p.prev; + p.prev = null; + p.next = pool; + pool = p; + count--; + } + + function sort(list:ParticleElement) { + return haxe.ds.ListSort.sort(list, function(p1, p2) return p1.w < p2.w ? 1 : -1); + } + + function sortInv(list:ParticleElement) { + return haxe.ds.ListSort.sort(list, function(p1, p2) return p1.w < p2.w ? -1 : 1); + } + + public inline function getParticles() { + return new ParticleIterator(head); + } + + @:access(h2d.Tile) + @:noDebug + override function draw(ctx:h3d.scene.RenderContext) { + if (head == null) + return; + switch (sortMode) { + case Sort, InvSort: + var p = head; + var m = ctx.camera.m; + while (p != null) { + p.w = (p.x * m._13 + p.y * m._23 + p.z * m._33 + m._43) / (p.x * m._14 + p.y * m._24 + p.z * m._34 + m._44); + p = p.next; + } + head = sortMode == Sort ? sort(head) : sortInv(head); + tail = head.prev; + head.prev = null; + default: + } + if (tmpBuf == null) + tmpBuf = new hxd.FloatBuffer(); + var pos = 0; + var p = head; + var tmp = tmpBuf; + var surface = 0.; + if (frames == null || frames.length == 0) { + var t = material.texture == null ? h2d.Tile.fromColor(0xFF00FF) : h2d.Tile.fromTexture(material.texture); + frames = [t]; + } + material.texture = frames[0].getTexture(); + + while (p != null) { + var f = frames[p.frame]; + if (f == null) + f = frames[0]; + var ratio = p.size * p.ratio * (f.height / f.width); + + if (pos >= tmp.length) { + tmp.grow(tmp.length + 40 + (hasColor ? 16 : 0)); + } + + tmp[pos++] = p.x; + tmp[pos++] = p.y; + tmp[pos++] = p.z; + tmp[pos++] = p.size; + tmp[pos++] = ratio; + tmp[pos++] = p.rotation; + // delta + tmp[pos++] = -0.5; + tmp[pos++] = -0.5; + // UV + tmp[pos++] = f.u; + tmp[pos++] = f.v2; + // RBGA + if (hasColor) { + tmp[pos++] = p.r; + tmp[pos++] = p.g; + tmp[pos++] = p.b; + tmp[pos++] = p.a; + } + + tmp[pos++] = p.x; + tmp[pos++] = p.y; + tmp[pos++] = p.z; + tmp[pos++] = p.size; + tmp[pos++] = ratio; + tmp[pos++] = p.rotation; + tmp[pos++] = -0.5; + tmp[pos++] = 0.5; + tmp[pos++] = f.u; + tmp[pos++] = f.v; + if (hasColor) { + tmp[pos++] = p.r; + tmp[pos++] = p.g; + tmp[pos++] = p.b; + tmp[pos++] = p.a; + } + + tmp[pos++] = p.x; + tmp[pos++] = p.y; + tmp[pos++] = p.z; + tmp[pos++] = p.size; + tmp[pos++] = ratio; + tmp[pos++] = p.rotation; + tmp[pos++] = 0.5; + tmp[pos++] = -0.5; + tmp[pos++] = f.u2; + tmp[pos++] = f.v2; + if (hasColor) { + tmp[pos++] = p.r; + tmp[pos++] = p.g; + tmp[pos++] = p.b; + tmp[pos++] = p.a; + } + + tmp[pos++] = p.x; + tmp[pos++] = p.y; + tmp[pos++] = p.z; + tmp[pos++] = p.size; + tmp[pos++] = ratio; + tmp[pos++] = p.rotation; + tmp[pos++] = 0.5; + tmp[pos++] = 0.5; + tmp[pos++] = f.u2; + tmp[pos++] = f.v; + if (hasColor) { + tmp[pos++] = p.r; + tmp[pos++] = p.g; + tmp[pos++] = p.b; + tmp[pos++] = p.a; + } + + p = p.next; + } + + if (pos != 0) { + var stride = 10; + if (hasColor) + stride += 4; + if (buffer == null) { + buffer = h3d.Buffer.ofSubFloats(tmp, stride, Std.int(pos / stride), [Quads, Dynamic, RawFormat]); + bufferSize = Std.int(pos / stride); + } else { + var len = Std.int(pos / stride); + if (bufferSize < len) { + buffer.dispose(); + buffer = h3d.Buffer.ofSubFloats(tmp, stride, Std.int(pos / stride), [Quads, Dynamic, RawFormat]); + bufferSize = Std.int(pos / stride); + } else { + buffer.uploadVector(tmp, 0, len); + } + } + if (pshader.is3D) + pshader.size.set(globalSize, globalSize); + else + pshader.size.set(globalSize * ctx.engine.height / ctx.engine.width * 4, globalSize * 4); + ctx.uploadParams(); + var verts = Std.int(pos / stride); + var vertsPerTri = 2; + ctx.engine.renderQuadBuffer(buffer, 0, verts >> 1); // buffer, 0, Std.int(pos / stride)); + } + } + + override function onRemove() { + super.onRemove(); + if (buffer != null) { + buffer.dispose(); + buffer = null; + } + } +} diff --git a/src/Polygon.hx b/src/Polygon.hx index 8f9d0ab5..02bcf7b5 100644 --- a/src/Polygon.hx +++ b/src/Polygon.hx @@ -11,6 +11,8 @@ class Polygon extends MeshPrimitive { public var uvs:Array; public var idx:hxd.IndexBuffer; + var bounds:h3d.col.Bounds; + var scaled = 1.; var translatedX = 0.; var translatedY = 0.; @@ -44,10 +46,16 @@ class Polygon extends MeshPrimitive { } override function getBounds() { - var b = new h3d.col.Bounds(); - for (i in 0...Std.int(points.length / 3)) - b.addPoint(new Point(points[i * 3], points[i * 3 + 1], points[i * 3 + 2])); - return b; + if (bounds == null) { + var b = new h3d.col.Bounds(); + var i = 0; + while (i < points.length) { + b.addPoint(new h3d.col.Point(points[i], points[i + 1], points[i + 2])); + i += 3; + } + bounds = b; + } + return bounds; } override function alloc(engine:h3d.Engine) { diff --git a/src/TimeState.hx b/src/TimeState.hx index a90f9af0..bed80cf3 100644 --- a/src/TimeState.hx +++ b/src/TimeState.hx @@ -6,6 +6,8 @@ class TimeState { var currentAttemptTime:Float; var gameplayClock:Float; var dt:Float; + var ticks:Int; // How many 32ms ticks have happened + var subframe:Float; public function new() {} @@ -15,6 +17,8 @@ class TimeState { n.currentAttemptTime = this.currentAttemptTime; n.gameplayClock = this.gameplayClock; n.dt = this.dt; + n.ticks = ticks; + n.subframe = subframe; return n; } } diff --git a/src/Util.hx b/src/Util.hx index 5325e923..76eb0b9c 100644 --- a/src/Util.hx +++ b/src/Util.hx @@ -11,18 +11,18 @@ import h3d.Vector; import src.Settings; class Util { - public static function mat3x3equal(a:Matrix, b:Matrix) { + public static inline function mat3x3equal(a:Matrix, b:Matrix) { return a._11 == b._11 && a._12 == b._12 && a._13 == b._13 && a._21 == b._21 && a._22 == b._22 && a._23 == b._23 && a._31 == b._31 && a._32 == b._32 && a._33 == b._33; } - public static function adjustedMod(a:Float, n:Float) { + public static inline function adjustedMod(a:Float, n:Float) { var r1 = a % n; var r2 = (r1 + n) % n; return r2; } - public static function clamp(value:Float, min:Float, max:Float) { + public static inline function clamp(value:Float, min:Float, max:Float) { if (value < min) return min; if (value > max) @@ -30,11 +30,11 @@ class Util { return value; } - public static function lerp(a:Float, b:Float, t:Float) { + public static inline function lerp(a:Float, b:Float, t:Float) { return a + (b - a) * t; } - public static function catmullRom(t:Float, p0:Float, p1:Float, p2:Float, p3:Float) { + public static inline function catmullRom(t:Float, p0:Float, p1:Float, p2:Float, p3:Float) { var point = t * t * t * ((-1) * p0 + 3 * p1 - 3 * p2 + p3) / 2; point += t * t * (2 * p0 - 5 * p1 + 4 * p2 - p3) / 2; point += t * ((-1) * p0 + p2) / 2; @@ -42,11 +42,11 @@ class Util { return point; } - public static function lerpThreeVectors(v1:Vector, v2:Vector, t:Float) { + public static inline function lerpThreeVectors(v1:Vector, v2:Vector, t:Float) { return new Vector(lerp(v1.x, v2.x, t), lerp(v1.y, v2.y, t), lerp(v1.z, v2.z, t), lerp(v1.w, v2.w, t)); } - public static function rotateImage(bitmap:hxd.Pixels, angle:Float) { + public static inline function rotateImage(bitmap:hxd.Pixels, angle:Float) { var curpixels = bitmap.clone(); if (angle == Math.PI / 2) for (x in 0...curpixels.width) { @@ -337,7 +337,7 @@ class Util { '${(hours > 0 ? (hoursTen > 0 ? '${hoursTen}' : '') +'${hoursOne}' + ':' : '')}${minutesTen}${minutesOne}:${secondsTen}${secondsOne}.${hundredthTen}${hundredthOne}${thousandth}'; } - public static function getKeyForButton(button:Int) { + public static inline function getKeyForButton(button:Int) { var keyName = Key.getKeyName(button); if (keyName == "MouseLeft") keyName = "the Left Mouse Button"; @@ -350,7 +350,7 @@ class Util { return keyName; } - public static function getKeyForButton2(button:Int) { + public static inline function getKeyForButton2(button:Int) { var keyName = Key.getKeyName(button); if (keyName == "MouseLeft") keyName = "Left Mouse"; @@ -408,7 +408,7 @@ class Util { #end } - public static function isSafari() { + public static inline function isSafari() { #if js var reg = ~/^((?!chrome|android).)*safari/; return reg.match(js.Browser.navigator.userAgent); @@ -418,7 +418,7 @@ class Util { #end } - public static function isInFullscreen() { + public static inline function isInFullscreen() { #if js return (js.Browser.window.innerHeight == js.Browser.window.screen.height || (js.Browser.window.screen.orientation.type == js.html.OrientationType.PORTRAIT_PRIMARY @@ -431,7 +431,7 @@ class Util { #end } - public static function toASCII(bytes:haxe.io.Bytes) { + public static inline function toASCII(bytes:haxe.io.Bytes) { var totBytes = new BytesBuffer(); for (i in 0...bytes.length) { var utfbytes = Bytes.ofString(String.fromCharCode(bytes.get(i))); diff --git a/src/collision/BVHTree.hx b/src/collision/BVHTree.hx index 013021b3..2364519e 100644 --- a/src/collision/BVHTree.hx +++ b/src/collision/BVHTree.hx @@ -5,36 +5,159 @@ import h3d.col.Bounds; interface IBVHObject { var boundingBox:Bounds; - function rayCast(rayOrigin:Vector, rayDirection:Vector):Array; + var key:Int; + function rayCast(rayOrigin:Vector, rayDirection:Vector, results:Array):Void; } +@:generic @:publicFields class BVHNode { - var id:Int; - var parent:BVHNode; - var child1:BVHNode; - var child2:BVHNode; + var index:Int; + var parent:Int = -1; + var child1:Int = -1; + var child2:Int = -1; var isLeaf:Bool; - var bounds:Bounds; var object:T; + var xMin:Float = 0; + var yMin:Float = 0; + var zMin:Float = 0; + var xMax:Float = 0; + var yMax:Float = 0; + var zMax:Float = 0; public function new() {} + + public inline function containsBounds(b:Bounds) { + return xMin <= b.xMin && yMin <= b.yMin && zMin <= b.zMin && xMax >= b.xMax && yMax >= b.yMax && zMax >= b.zMax; + } + + public inline function setBounds(b:Bounds) { + xMin = b.xMin; + yMin = b.yMin; + zMin = b.zMin; + xMax = b.xMax; + yMax = b.yMax; + zMax = b.zMax; + } + + public inline function setBoundsFromNode(b:BVHNode) { + xMin = b.xMin; + yMin = b.yMin; + zMin = b.zMin; + xMax = b.xMax; + yMax = b.yMax; + zMax = b.zMax; + } + + public inline function getBounds() { + return Bounds.fromValues(xMin, yMin, zMin, xMax - xMin, yMax - yMin, zMax - zMin); + } + + public inline function add(b:Bounds) { + if (b.xMin < xMin) + xMin = b.xMin; + if (b.xMax > xMax) + xMax = b.xMax; + if (b.yMin < yMin) + yMin = b.yMin; + if (b.yMax > yMax) + yMax = b.yMax; + if (b.zMin < zMin) + zMin = b.zMin; + if (b.zMax > zMax) + zMax = b.zMax; + } + + public inline function addNodeBounds(b:BVHNode) { + if (b.xMin < xMin) + xMin = b.xMin; + if (b.xMax > xMax) + xMax = b.xMax; + if (b.yMin < yMin) + yMin = b.yMin; + if (b.yMax > yMax) + yMax = b.yMax; + if (b.zMin < zMin) + zMin = b.zMin; + if (b.zMax > zMax) + zMax = b.zMax; + } + + public inline function collide(b:Bounds) { + return !(xMin > b.xMax || yMin > b.yMax || zMin > b.zMax || xMax < b.xMin || yMax < b.yMin || zMax < b.zMin); + } + + public inline function getExpansionCost(b:BVHNode) { + var xm = xMin; + var ym = yMin; + var zm = zMin; + var xp = xMax; + var yp = yMax; + var zp = zMax; + if (b.xMin < xm) + xm = b.xMin; + if (b.xMax > xp) + xp = b.xMax; + if (b.yMin < ym) + ym = b.yMin; + if (b.yMax > yp) + yp = b.yMax; + if (b.zMin < zm) + zm = b.zMin; + if (b.zMax > zp) + zp = b.zMax; + var xs = xp - xm; + var ys = yp - ym; + var zs = zp - zm; + return xs * ys + ys * zs + xs * zs; + } + + public inline function collideRay(r:h3d.col.Ray):Bool { + var dx = 1 / r.lx; + var dy = 1 / r.ly; + var dz = 1 / r.lz; + var t1 = (xMin - r.px) * dx; + var t2 = (xMax - r.px) * dx; + var t3 = (yMin - r.py) * dy; + var t4 = (yMax - r.py) * dy; + var t5 = (zMin - r.pz) * dz; + var t6 = (zMax - r.pz) * dz; + var tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); + var tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); + if (tmax < 0) { + // t = tmax; + return false; + } else if (tmin > tmax) { + // t = tmax; + return false; + } else { + // t = tmin; + return true; + } + } } class BVHTree { - var nodeId:Int = 0; var root:BVHNode; + var nodes:Array> = []; public function new() {} + public function allocateNode():BVHNode { + var node = new BVHNode(); + var index = this.nodes.length; + node.index = index; + this.nodes.push(node); + return node; + } + public function update() { var invalidNodes = []; this.traverse(node -> { if (node.isLeaf) { var entity = node.object; - var tightAABB = entity.boundingBox; - if (node.bounds.containsBounds(tightAABB)) { + if (node.containsBounds(entity.boundingBox)) { return; } @@ -51,9 +174,8 @@ class BVHTree { // Enlarged AABB var aabb = entity.boundingBox; - var newNode = new BVHNode(); - newNode.id = this.nodeId++; - newNode.bounds = aabb; + var newNode = allocateNode(); + newNode.setBounds(aabb); newNode.object = entity; newNode.isLeaf = true; @@ -64,17 +186,17 @@ class BVHTree { // Find the best sibling for the new leaf var bestSibling = this.root; - var bestCostBox = this.root.bounds.clone(); + var bestCostBox = this.root.getBounds(); bestCostBox.add(aabb); var bestCost = bestCostBox.xSize * bestCostBox.ySize + bestCostBox.xSize * bestCostBox.zSize + bestCostBox.ySize * bestCostBox.zSize; - var q = [{p1: this.root, p2: 0.0}]; + var q = [{p1: this.root.index, p2: 0.0}]; while (q.length != 0) { var front = q.shift(); - var current = front.p1; + var current = nodes[front.p1]; var inheritedCost = front.p2; - var combined = current.bounds.clone(); + var combined = current.getBounds(); combined.add(aabb); var directCost = combined.xSize * combined.ySize + combined.xSize * combined.zSize + combined.ySize * combined.zSize; @@ -83,123 +205,125 @@ class BVHTree { bestCost = costForCurrent; bestSibling = current; } + var xs = (current.xMax - current.xMin); + var ys = (current.yMax - current.yMin); + var zs = (current.zMax - current.zMin); - inheritedCost += directCost - - (current.bounds.xSize * current.bounds.ySize + current.bounds.xSize * current.bounds.zSize + current.bounds.ySize * current.bounds.zSize); + inheritedCost += directCost - (xs * ys + xs * zs + ys * zs); var aabbCost = aabb.xSize * aabb.ySize + aabb.xSize * aabb.zSize + aabb.ySize * aabb.zSize; var lowerBoundCost = aabbCost + inheritedCost; if (lowerBoundCost < bestCost) { if (!current.isLeaf) { - if (current.child1 != null) + if (current.child1 != -1) q.push({p1: current.child1, p2: inheritedCost}); - if (current.child2 != null) + if (current.child2 != -1) q.push({p1: current.child2, p2: inheritedCost}); } } } // Create a new parent - var oldParent = bestSibling.parent; - var newParent = new BVHNode(); - newParent.id = this.nodeId++; - newParent.parent = oldParent; - newParent.bounds = bestSibling.bounds.clone(); - newParent.bounds.add(aabb); + var oldParent = bestSibling.parent != -1 ? nodes[bestSibling.parent] : null; + var newParent = allocateNode(); + newParent.parent = oldParent != null ? oldParent.index : -1; + newParent.setBoundsFromNode(bestSibling); + newParent.add(aabb); newParent.isLeaf = false; if (oldParent != null) { - if (oldParent.child1 == bestSibling) { - oldParent.child1 = newParent; + if (oldParent.child1 == bestSibling.index) { + oldParent.child1 = newParent.index; } else { - oldParent.child2 = newParent; + oldParent.child2 = newParent.index; } - newParent.child1 = bestSibling; - newParent.child2 = newNode; - bestSibling.parent = newParent; - newNode.parent = newParent; + newParent.child1 = bestSibling.index; + newParent.child2 = newNode.index; + bestSibling.parent = newParent.index; + newNode.parent = newParent.index; } else { - newParent.child1 = bestSibling; - newParent.child2 = newNode; - bestSibling.parent = newParent; - newNode.parent = newParent; + newParent.child1 = bestSibling.index; + newParent.child2 = newNode.index; + bestSibling.parent = newParent.index; + newNode.parent = newParent.index; this.root = newParent; } // Walk back up the tree refitting ancestors' AABB and applying rotations - var ancestor = newNode.parent; + var ancestor = newNode.parent != -1 ? nodes[newNode.parent] : null; while (ancestor != null) { var child1 = ancestor.child1; var child2 = ancestor.child2; - ancestor.bounds = new Bounds(); - if (child1 != null) - ancestor.bounds.add(child1.bounds); - if (child2 != null) - ancestor.bounds.add(child2.bounds); + if (child1 != -1) + ancestor.addNodeBounds(nodes[child1]); + if (child2 != -1) + ancestor.addNodeBounds(nodes[child2]); this.rotate(ancestor); - ancestor = ancestor.parent; + ancestor = nodes[ancestor.parent]; } return newNode; } function reset() { - this.nodeId = 0; + this.nodes = []; this.root = null; } // BFS tree traversal function traverse(callback:(node:BVHNode) -> Void) { - var q = [this.root]; + var q = [this.root.index]; while (q.length != 0) { var current = q.shift(); if (current == null) { break; } + var currentnode = nodes[current]; + callback(currentnode); - callback(current); - - if (!current.isLeaf) { - if (current.child1 != null) - q.push(current.child1); - if (current.child2 != null) - q.push(current.child2); + if (!currentnode.isLeaf) { + if (currentnode.child1 != -1) + q.push(currentnode.child1); + if (currentnode.child2 != -1) + q.push(currentnode.child2); } } } public function remove(node:BVHNode) { - var parent = node.parent; + var parent = node.parent != -1 ? nodes[node.parent] : null; if (parent != null) { - var sibling = parent.child1 == node ? parent.child2 : parent.child1; + var sibling = parent.child1 == node.index ? parent.child2 : parent.child1; + var siblingnode = nodes[sibling]; - if (parent.parent != null) { - sibling.parent = parent.parent; - if (parent.parent.child1 == parent) { - parent.parent.child1 = sibling; + if (parent.parent != -1) { + siblingnode.parent = parent.parent; + if (nodes[parent.parent].child1 == parent.index) { + nodes[parent.parent].child1 = sibling; } else { - parent.parent.child2 = sibling; + nodes[parent.parent].child2 = sibling; } } else { - this.root = sibling; - sibling.parent = null; + this.root = siblingnode; + siblingnode.parent = -1; } - var ancestor = sibling.parent; - while (ancestor != null) { - var child1 = ancestor.child1; - var child2 = ancestor.child2; + var ancestor = siblingnode.parent; + while (ancestor != -1) { + var ancestornode = nodes[ancestor]; + var child1 = nodes[ancestornode.child1]; + var child2 = nodes[ancestornode.child2]; - ancestor.bounds = child1.bounds.clone(); - ancestor.bounds.add(child2.bounds); - ancestor = ancestor.parent; + ancestornode.setBoundsFromNode(child1); + ancestornode.addNodeBounds(child2); + ancestor = ancestornode.parent; } } else { if (this.root == node) { @@ -209,33 +333,30 @@ class BVHTree { } function rotate(node:BVHNode) { - if (node.parent == null) { + if (node.parent == -1) { return; } - var parent = node.parent; - var sibling = parent.child1 == node ? parent.child2 : parent.child1; + var parent = nodes[node.parent]; + var sibling = nodes[parent.child1 == node.index ? parent.child2 : parent.child1]; var costDiffs = []; - var nodeArea = node.bounds.xSize * node.bounds.ySize + node.bounds.zSize * node.bounds.ySize + node.bounds.xSize * node.bounds.zSize; + var nxs = node.xMax - node.xMin; + var nys = node.yMax - node.yMin; + var nzs = node.zMax - node.zMin; + var nodeArea = nxs * nys + nzs * nys + nxs * nzs; - var ch1 = sibling.bounds.clone(); - ch1.add(node.child1.bounds); - costDiffs.push(ch1.xSize * ch1.ySize + ch1.zSize * ch1.ySize + ch1.xSize * ch1.zSize - nodeArea); - var ch2 = sibling.bounds.clone(); - ch2.add(node.child2.bounds); - costDiffs.push(ch2.xSize * ch2.ySize + ch2.zSize * ch2.ySize + ch2.xSize * ch2.zSize - nodeArea); + costDiffs.push(sibling.getExpansionCost(nodes[node.child1]) - nodeArea); + costDiffs.push(sibling.getExpansionCost(nodes[node.child2]) - nodeArea); if (!sibling.isLeaf) { - var siblingArea = sibling.bounds.xSize * sibling.bounds.ySize + sibling.bounds.zSize * sibling.bounds.ySize - + sibling.bounds.xSize * sibling.bounds.zSize; - if (sibling.child1 != null) { - var ch3 = node.bounds.clone(); - ch3.add(sibling.child1.bounds); - costDiffs.push(ch3.xSize * ch3.ySize + ch3.zSize * ch3.ySize + ch3.xSize * ch3.zSize - siblingArea); + var sxs = sibling.xMax - sibling.xMin; + var sys = sibling.yMax - sibling.yMin; + var szs = sibling.zMax - sibling.zMin; + var siblingArea = sxs * sys + sys * szs + sxs * szs; + if (sibling.child1 != -1) { + costDiffs.push(node.getExpansionCost(nodes[sibling.child1]) - siblingArea); } - if (sibling.child2 != null) { - var ch4 = node.bounds.clone(); - ch4.add(sibling.child2.bounds); - costDiffs.push(ch4.xSize * ch4.ySize + ch4.zSize * ch4.ySize + ch4.xSize * ch4.zSize - siblingArea); + if (sibling.child2 != -1) { + costDiffs.push(node.getExpansionCost(nodes[sibling.child2]) - siblingArea); } } @@ -249,67 +370,67 @@ class BVHTree { if (costDiffs[bestDiffIndex] < 0.0) { switch (bestDiffIndex) { case 0: - if (parent.child1 == sibling) { + if (parent.child1 == sibling.index) { parent.child1 = node.child2; } else { parent.child2 = node.child2; } - if (node.child2 != null) { - node.child2.parent = parent; + if (node.child2 != -1) { + nodes[node.child2].parent = parent.index; } - node.child2 = sibling; - sibling.parent = node; - node.bounds = sibling.bounds.clone(); - if (node.child1 != null) { - node.bounds.add(node.child1.bounds); + node.child2 = sibling.index; + sibling.parent = node.index; + node.setBoundsFromNode(sibling); + if (node.child1 != -1) { + node.addNodeBounds(nodes[node.child1]); } case 1: - if (parent.child1 == sibling) { + if (parent.child1 == sibling.index) { parent.child1 = node.child1; } else { parent.child2 = node.child1; } - if (node.child1 != null) { - node.child1.parent = parent; + if (node.child1 != -1) { + nodes[node.child1].parent = parent.index; } - node.child1 = sibling; - sibling.parent = node; - node.bounds = sibling.bounds.clone(); - if (node.child2 != null) { - node.bounds.add(node.child2.bounds); + node.child1 = sibling.index; + sibling.parent = node.index; + node.setBoundsFromNode(sibling); + if (node.child2 != -1) { + node.addNodeBounds(nodes[node.child2]); } case 2: - if (parent.child1 == node) { + if (parent.child1 == node.index) { parent.child1 = sibling.child2; } else { parent.child2 = sibling.child2; } - if (sibling.child2 != null) { - sibling.child2.parent = parent; + if (sibling.child2 != -1) { + nodes[sibling.child2].parent = parent.index; } - sibling.child2 = node; - node.parent = sibling; - sibling.bounds = node.bounds.clone(); - if (sibling.child2 != null) { - sibling.bounds.add(sibling.child2.bounds); + sibling.child2 = node.index; + node.parent = sibling.index; + sibling.setBoundsFromNode(node); + if (sibling.child2 != -1) { + sibling.addNodeBounds(nodes[sibling.child2]); } case 3: - if (parent.child1 == node) { + if (parent.child1 == node.index) { parent.child1 = sibling.child1; } else { parent.child2 = sibling.child1; } - if (sibling.child1 != null) { - sibling.child1.parent = parent; + if (sibling.child1 != -1) { + nodes[sibling.child1].parent = parent.index; } - sibling.child1 = node; - node.parent = sibling; - sibling.bounds = node.bounds.clone(); - if (sibling.child1 != null) { - sibling.bounds.add(sibling.child1.bounds); + sibling.child1 = node.index; + node.parent = sibling.index; + sibling.setBoundsFromNode(node); + if (sibling.child1 != -1) { + sibling.addNodeBounds(nodes[sibling.child1]); } } } @@ -320,19 +441,21 @@ class BVHTree { if (this.root == null) return res; - var q = [this.root]; + var q = [this.root.index]; + var qptr = 0; - while (q.length != 0) { - var current = q.shift(); + while (qptr != q.length) { + var current = q[qptr++]; + var currentnode = this.nodes[current]; - if (current.bounds.containsBounds(searchbox) || current.bounds.collide(searchbox)) { - if (current.isLeaf) { - res.push(current.object); + if (currentnode.containsBounds(searchbox) || currentnode.collide(searchbox)) { + if (currentnode.isLeaf) { + res.push(currentnode.object); } else { - if (current.child1 != null) - q.push(current.child1); - if (current.child2 != null) - q.push(current.child2); + if (currentnode.child1 != -1) + q.push(currentnode.child1); + if (currentnode.child2 != -1) + q.push(currentnode.child2); } } } @@ -346,17 +469,19 @@ class BVHTree { return res; var ray = h3d.col.Ray.fromValues(origin.x, origin.y, origin.z, direction.x, direction.y, direction.z); - var q = [this.root]; - while (q.length != 0) { - var current = q.shift(); - if (ray.collide(current.bounds)) { - if (current.isLeaf) { - res = res.concat(current.object.rayCast(origin, direction)); + var q = [this.root.index]; + var qptr = 0; + while (qptr != q.length) { + var current = q[qptr++]; + var currentnode = this.nodes[current]; + if (currentnode.collideRay(ray)) { + if (currentnode.isLeaf) { + currentnode.object.rayCast(origin, direction, res); } else { - if (current.child1 != null) - q.push(current.child1); - if (current.child2 != null) - q.push(current.child2); + if (currentnode.child1 != -1) + q.push(currentnode.child1); + if (currentnode.child2 != -1) + q.push(currentnode.child2); } } } diff --git a/src/collision/BoxCollisionEntity.hx b/src/collision/BoxCollisionEntity.hx index f529d7e9..9332e26e 100644 --- a/src/collision/BoxCollisionEntity.hx +++ b/src/collision/BoxCollisionEntity.hx @@ -48,9 +48,8 @@ class BoxCollisionEntity extends CollisionEntity implements IBVHObject { } } - public override function rayCast(rayOrigin:Vector, rayDirection:Vector) { + public override function rayCast(rayOrigin:Vector, rayDirection:Vector, results:Array) { // TEMP cause bruh - return []; } public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) { diff --git a/src/collision/Collision.hx b/src/collision/Collision.hx index 5bd2c4df..9bfefef9 100644 --- a/src/collision/Collision.hx +++ b/src/collision/Collision.hx @@ -20,11 +20,17 @@ typedef CPSSResult = { var c2:Vector; } -typedef ITSResult = { - var result:Bool; +@:publicFields +class ITSResult { + var result:Bool = false; var normal:Vector; var point:Vector; - var resIdx:Int; + var resIdx:Int = -1; + + public inline function new() { + this.point = new Vector(); + this.normal = new Vector(); + } } class Collision { @@ -45,7 +51,7 @@ class Collision { return p; } - public static function ClosestPointLine(start:Vector, end:Vector, center:Vector) { + public static inline function ClosestPointLine(start:Vector, end:Vector, center:Vector) { var d = end.sub(start); var v = center.sub(start); var t = v.dot(d) / d.lengthSq(); @@ -65,12 +71,7 @@ class Collision { edgeConcavities:Array) { var radiusSq = radius * radius; - var res:ITSResult = { - result: false, - point: null, - normal: null, - resIdx: 0 - }; + var res = new ITSResult(); var pnorm = normal.clone(); var d = -v0.dot(pnorm); @@ -104,12 +105,12 @@ class Collision { var chosenEdge = 0; // Bitfield - var chosenPt:Vector; + var chosenPt = new Vector(); if (r1.distanceSq(center) < r2.distanceSq(center)) { - chosenPt = r1; + chosenPt.load(r1); chosenEdge = 1; } else { - chosenPt = r2; + chosenPt.load(r2); chosenEdge = 2; } if (chosenPt.distanceSq(center) < r3.distanceSq(center)) @@ -144,20 +145,10 @@ class Collision { return res; } - public static function TriangleSphereIntersection(A:Vector, B:Vector, C:Vector, N:Vector, P:Vector, r:Float) { - var res:ITSResult = { - result: false, - point: null, - normal: null, - resIdx: -1 - }; - - var v0 = A; - var v1 = B; - var v2 = C; - A = A.sub(P); - B = B.sub(P); - C = C.sub(P); + public static inline function TriangleSphereIntersection(v0:Vector, v1:Vector, v2:Vector, N:Vector, P:Vector, r:Float, point:Vector, normal:Vector) { + var A = v0.sub(P); + var B = v1.sub(P); + var C = v2.sub(P); var ca = C.sub(A); var ba = B.sub(A); var radiusSq = r * r; @@ -165,7 +156,7 @@ class Collision { var aDotCp = A.dot(cp); var cpLenSq = cp.lengthSq(); if (aDotCp * aDotCp > radiusSq * cpLenSq) { - return res; + return false; } var aSq = A.dot(A); @@ -176,13 +167,13 @@ class Collision { var cSq = C.dot(C); if (aSq > radiusSq && aDotB > aSq && aDotC > aSq) { - return res; + return false; } if (bSq > radiusSq && aDotB > bSq && bDotC > bSq) { - return res; + return false; } if (cSq > radiusSq && aDotC > cSq && bDotC > cSq) { - return res; + return false; } var cSubB = C.sub(B); @@ -198,13 +189,13 @@ class Collision { var rhs3 = B.multiply(aSubCSq).sub(cTest); if (aTest.dot(aTest) > radiusSq * baSq * baSq && aTest.dot(rhs) > 0) { - return res; + return false; } if (bTest.dot(bTest) > radiusSq * cSubBSq * cSubBSq && bTest.dot(rhs2) > 0) { - return res; + return false; } if (cTest.dot(cTest) > radiusSq * aSubCSq * aSubCSq && cTest.dot(rhs3) > 0) { - return res; + return false; } var lhs = P.sub(v0); @@ -217,10 +208,9 @@ class Collision { var d2 = (baSq * lhsCa - baca * lhsBa) / len; if (1 - d1 - d2 >= 0 && d1 >= 0 && d2 >= 0) { - res.result = true; - res.normal = N.clone(); - res.point = P.sub(N.multiply(P.sub(v0).dot(N))); - res.resIdx = 0; + normal.load(N); + point.load(P.sub(N.multiply(P.sub(v0).dot(N)))); + return true; } else { var closestPt = P.sub(N.multiply(P.sub(v0).dot(N))); var r1 = ClosestPointLine(v0, v1, closestPt); @@ -229,24 +219,22 @@ class Collision { var chosenEdge = 0; // Bitfield - var chosenPt:Vector; + var chosenPt:Vector = new Vector(); if (r1.distanceSq(P) < r2.distanceSq(P)) { - chosenPt = r1; + chosenPt.load(r1); chosenEdge = 1; } else { - chosenPt = r2; + chosenPt.load(r2); chosenEdge = 2; } if (chosenPt.distanceSq(P) < r3.distanceSq(P)) - res.point = chosenPt; + point.load(chosenPt); else { chosenEdge = 4; - res.point = r3; + point.load(r3); } - res.normal = P.sub(res.point).normalized(); - res.result = true; - - res.resIdx = chosenEdge; + normal.load(P.sub(point).normalized()); + return true; // if (res.normal.dot(N) > 0.8) { // // Internal edge @@ -263,7 +251,6 @@ class Collision { // } // } } - return res; } public static function IntersectSegmentCapsule(segStart:Vector, segEnd:Vector, capStart:Vector, capEnd:Vector, radius:Float) { @@ -477,4 +464,51 @@ class Collision { } return null; } + + public static function capsuleSphereNearestOverlap(a0:Vector, a1:Vector, radA:Float, b:Vector, radB:Float) { + var V = a1.sub(a0); + var A0B = a0.sub(b); + var d1 = A0B.dot(V); + var d2 = A0B.dot(A0B); + var d3 = V.dot(V); + var R2 = (radA + radB) * (radA + radB); + if (d2 < R2) { + // starting in collision state + return { + result: true, + t: 0.0 + } + } + if (d3 < 0.01) // no movement, and don't start in collision state, so no collision + return { + result: false, + t: 0.0 + } + + var b24ac = Math.sqrt(d1 * d1 - d2 * d3 + d3 * R2); + var t1 = (-d1 - b24ac) / d3; + if (t1 > 0 && t1 < 1.0) { + return { + result: true, + t: t1 + } + } + var t2 = (-d1 + b24ac) / d3; + if (t2 > 0 && t2 < 1.0) { + return { + result: true, + t: t2 + } + } + if (t1 < 0 && t2 > 0) { + return { + result: true, + t: 0.0 + } + } + return { + result: false, + t: 0.0 + } + } } diff --git a/src/collision/CollisionEntity.hx b/src/collision/CollisionEntity.hx index 1fec7c22..41349ea8 100644 --- a/src/collision/CollisionEntity.hx +++ b/src/collision/CollisionEntity.hx @@ -1,5 +1,6 @@ package collision; +import collision.Collision.ITSResult; import collision.BVHTree.IBVHObject; import src.TimeState; import src.GameObject; @@ -24,8 +25,9 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { public var bvh:BVHTree; - public var surfaces:Array; + var grid:Grid; + public var surfaces:Array; public var priority:Int; public var position:Int; public var velocity:Vector = new Vector(); @@ -35,12 +37,16 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { var invTransform:Matrix; public var go:GameObject; + public var correctNormals:Bool = false; public var userData:Int; public var fastTransform:Bool = false; + public var isWorldStatic:Bool = false; var _transformKey:Int = 0; + public var key:Int = 0; + var _dbgEntity:h3d.scene.Mesh; public function new(go:GameObject) { @@ -61,10 +67,19 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { // Generates the bvh public function finalize() { this.generateBoundingBox(); + #if hl this.bvh = new BVHTree(); for (surface in this.surfaces) { this.bvh.add(surface); } + #end + var bbox = new Bounds(); + for (surface in this.surfaces) + bbox.add(surface.boundingBox); + this.grid = new Grid(bbox); + for (surface in this.surfaces) + this.grid.insert(surface); + this.grid.build(); // this.bvh.build(); } @@ -126,33 +141,32 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { } } - public function rayCast(rayOrigin:Vector, rayDirection:Vector):Array { + public function rayCast(rayOrigin:Vector, rayDirection:Vector, results:Array) { var invMatrix = invTransform; var invTPos = invMatrix.clone(); invTPos.transpose(); var rStart = rayOrigin.clone(); rStart.transform(invMatrix); var rDir = rayDirection.transformed3x3(invMatrix); - if (bvh == null) { - var intersections = octree.raycast(rStart, rDir); - var iData:Array = []; - for (i in intersections) { - i.point.transform(transform); - i.normal.transform3x3(invTPos); - i.normal.normalize(); - iData.push({point: i.point, normal: i.normal, object: i.object}); - } - return iData; - } else { - var intersections = this.bvh.rayCast(rStart, rDir); - for (i in intersections) { - i.point.transform(transform); - i.normal.transform3x3(invTPos); - i.normal.normalize(); - } - - return intersections; + // if (bvh == null) { + // var intersections = grid.rayCast(rStart, rDir); // octree.raycast(rStart, rDir); + // // var iData:Array = []; + // for (i in intersections) { + // i.point.transform(transform); + // i.normal.transform3x3(invTPos); + // i.normal.normalize(); + // // iData.push({point: i.point, normal: i.normal, object: i.object}); + // } + // return intersections; // iData; + // } else { + var intersections = grid.rayCast(rStart, rDir); // this.bvh.rayCast(rStart, rDir); + for (i in intersections) { + i.point.transform(transform); + i.normal.transform3x3(invTPos); + i.normal.normalize(); + results.push(i); } + // } } public function getElementType() { @@ -178,13 +192,18 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { var invScale = invMatrix.getScale(); var sphereRadius = new Vector(radius * invScale.x, radius * invScale.y, radius * invScale.z); sphereBounds.addSpherePos(localPos.x, localPos.y, localPos.z, Math.max(Math.max(sphereRadius.x, sphereRadius.y), sphereRadius.z) * 1.1); - var surfaces = bvh == null ? octree.boundingSearch(sphereBounds).map(x -> cast x) : bvh.boundingSearch(sphereBounds); + var surfaces = grid.boundingSearch(sphereBounds); // bvh == null ? octree.boundingSearch(sphereBounds).map(x -> cast x) : bvh.boundingSearch(sphereBounds); var invtform = invMatrix.clone(); invtform.transpose(); var tform = transform.clone(); // tform.setPosition(tform.getPosition().add(this.velocity.multiply(timeState.dt))); + if (isWorldStatic) { + tform.load(Matrix.I()); + invtform.load(Matrix.I()); + } + var contacts = []; for (obj in surfaces) { @@ -199,20 +218,31 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { // var v0 = surface.points[surface.indices[i]].transformed(tform); // var v = surface.points[surface.indices[i + 1]].transformed(tform); // var v2 = surface.points[surface.indices[i + 2]].transformed(tform); - var v0 = verts.v1; - var v = verts.v2; - var v2 = verts.v3; + var v0 = new Vector(verts.v1x, verts.v1y, verts.v1z); + var v = new Vector(verts.v2x, verts.v2y, verts.v2z); + var v2 = new Vector(verts.v3x, verts.v3y, verts.v3z); - var surfacenormal = verts.n; // surface.normals[surface.indices[i]].transformed3x3(transform).normalized(); + var surfacenormal = new Vector(verts.nx, verts.ny, verts.nz); // surface.normals[surface.indices[i]].transformed3x3(transform).normalized(); - var res = Collision.TriangleSphereIntersection(v0, v, v2, surfacenormal, position, radius); - var closest = res.point; + if (correctNormals) { + var vn = v.sub(v0).cross(v2.sub(v0)).normalized().multiply(-1); + var vdot = vn.dot(surfacenormal); + if (vdot < 0.95) { + v.set(verts.v3x, verts.v3y, verts.v3z); + v2.set(verts.v2x, verts.v2y, verts.v2z); + + surfacenormal.load(vn); + } + } + + var closest = new Vector(); + var normal = new Vector(); + var res = Collision.TriangleSphereIntersection(v0, v, v2, surfacenormal, position, radius, closest, normal); // var closest = Collision.ClosestPtPointTriangle(position, radius, v0, v, v2, surfacenormal); - if (closest != null) { + if (res) { var contactDist = closest.distanceSq(position); - if (contactDist <= radius * radius && contactDist > 0.0225) { - var normal = res.normal; - + // Debug.drawTriangle(v0, v, v2); + if (contactDist <= radius * radius) { if (position.sub(closest).dot(surfacenormal) > 0) { normal.normalize(); @@ -221,11 +251,12 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { // if (testDot > bestDot) { // bestDot = testDot; - var cinfo = new CollisionInfo(); - cinfo.normal = normal; - cinfo.point = closest; + var cinfo = CollisionPool.alloc(); + cinfo.normal.load(normal); + cinfo.point.load(closest); + cinfo.collider = null; // cinfo.collider = this; - cinfo.velocity = this.velocity.clone(); + cinfo.velocity.load(this.velocity); cinfo.contactDistance = Math.sqrt(contactDist); cinfo.otherObject = this.go; // cinfo.penetration = radius - (position.sub(closest).dot(normal)); @@ -233,7 +264,8 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { cinfo.force = surface.force; cinfo.friction = surface.friction; contacts.push(cinfo); - this.go.onMarbleContact(timeState, cinfo); + if (this.go != null) + this.go.onMarbleContact(collisionEntity.marble, timeState, cinfo); // surfaceBestContact = cinfo; // } } diff --git a/src/collision/CollisionHull.hx b/src/collision/CollisionHull.hx index 27533661..3b8bdd17 100644 --- a/src/collision/CollisionHull.hx +++ b/src/collision/CollisionHull.hx @@ -40,16 +40,17 @@ class CollisionHull extends CollisionEntity { var pt = GJK.gjk(sph, this.hull).epa; if (pt != null) { - var cinfo = new CollisionInfo(); + var cinfo = CollisionPool.alloc(); cinfo.normal = pt.normalized(); cinfo.point = sph.position.sub(pt); cinfo.velocity = velocity; + cinfo.collider = null; cinfo.contactDistance = sph.radius + pt.length(); cinfo.restitution = restitution; cinfo.otherObject = this.go; cinfo.friction = friction; cinfo.force = force; - this.go.onMarbleContact(timeState, cinfo); + this.go.onMarbleContact(collisionEntity.marble, timeState, cinfo); return [cinfo]; } } diff --git a/src/collision/CollisionInfo.hx b/src/collision/CollisionInfo.hx index 7992608b..0a82d60b 100644 --- a/src/collision/CollisionInfo.hx +++ b/src/collision/CollisionInfo.hx @@ -4,18 +4,16 @@ import src.GameObject; import h3d.Vector; class CollisionInfo { - public var point:Vector; - public var normal:Vector; - public var velocity:Vector; + public var point:Vector = new Vector(); + public var normal:Vector = new Vector(); + public var velocity:Vector = new Vector(); public var collider:CollisionEntity; public var otherObject:GameObject; public var friction:Float; - public var vAtCMag:Float; public var normalForce:Float; public var restitution:Float; public var contactDistance:Float; public var force:Float; - public var penetration:Float; public function new() {} } diff --git a/src/collision/CollisionPool.hx b/src/collision/CollisionPool.hx new file mode 100644 index 00000000..94ecbfbc --- /dev/null +++ b/src/collision/CollisionPool.hx @@ -0,0 +1,21 @@ +package collision; + +class CollisionPool { + static var pool:Array = []; + static var currentPtr = 0; + + public static function alloc() { + if (pool.length <= currentPtr) { + pool.push(new CollisionInfo()); + } + return pool[currentPtr++]; + } + + public static function clear() { + currentPtr = 0; + } + + public static function freeMemory() { + pool = []; + } +} diff --git a/src/collision/CollisionSurface.hx b/src/collision/CollisionSurface.hx index ec367f61..e7c23f65 100644 --- a/src/collision/CollisionSurface.hx +++ b/src/collision/CollisionSurface.hx @@ -6,6 +6,37 @@ import octree.IOctreeObject; import h3d.Vector; import collision.BVHTree.IBVHObject; +@:publicFields +class TransformedCollisionTriangle { + var v1x:Float; + var v1y:Float; + var v1z:Float; + var v2x:Float; + var v2y:Float; + var v2z:Float; + var v3x:Float; + var v3y:Float; + var v3z:Float; + var nx:Float; + var ny:Float; + var nz:Float; + + inline public function new(v1:Vector, v2:Vector, v3:Vector, n:Vector) { + v1x = v1.x; + v1y = v1.y; + v1z = v1.z; + v2x = v2.x; + v2y = v2.y; + v2z = v2.z; + v3x = v3.x; + v3y = v3.y; + v3z = v3.z; + nx = n.x; + ny = n.y; + nz = n.z; + } +} + class CollisionSurface implements IOctreeObject implements IBVHObject { public var priority:Int; public var position:Int; @@ -19,6 +50,7 @@ class CollisionSurface implements IOctreeObject implements IBVHObject { public var originalIndices:Array; public var originalSurfaceIndex:Int; public var transformKeys:Array; + public var key:Int = 0; var _transformedPoints:Array; var _transformedNormals:Array; @@ -107,8 +139,7 @@ class CollisionSurface implements IOctreeObject implements IBVHObject { normals.push(z); } - public function rayCast(rayOrigin:Vector, rayDirection:Vector):Array { - var intersections = []; + public function rayCast(rayOrigin:Vector, rayDirection:Vector, intersections:Array) { var i = 0; while (i < indices.length) { var p1 = getPoint(indices[i]); @@ -125,7 +156,6 @@ class CollisionSurface implements IOctreeObject implements IBVHObject { } i += 3; } - return intersections; } public function support(direction:Vector, transform:Matrix) { @@ -147,7 +177,7 @@ class CollisionSurface implements IOctreeObject implements IBVHObject { return furthestVertex; } - public function transformTriangle(idx:Int, tform:Matrix, invtform:Matrix, key:Int) { + public inline function transformTriangle(idx:Int, tform:Matrix, invtform:Matrix, key:Int) { if (_transformedPoints == null) { _transformedPoints = points.copy(); } @@ -182,12 +212,46 @@ class CollisionSurface implements IOctreeObject implements IBVHObject { _transformedPoints[p3 * 3 + 2] = pt.z; transformKeys[p3] = key; } - return { - v1: new Vector(_transformedPoints[p1 * 3], _transformedPoints[p1 * 3 + 1], _transformedPoints[p1 * 3 + 2]), - v2: new Vector(_transformedPoints[p2 * 3], _transformedPoints[p2 * 3 + 1], _transformedPoints[p2 * 3 + 2]), - v3: new Vector(_transformedPoints[p3 * 3], _transformedPoints[p3 * 3 + 1], _transformedPoints[p3 * 3 + 2]), - n: new Vector(_transformedNormals[p1 * 3], _transformedNormals[p1 * 3 + 1], _transformedNormals[p1 * 3 + 2]) - }; + return new TransformedCollisionTriangle(new Vector(_transformedPoints[p1 * 3], _transformedPoints[p1 * 3 + 1], _transformedPoints[p1 * 3 + 2]), + new Vector(_transformedPoints[p2 * 3], _transformedPoints[p2 * 3 + 1], _transformedPoints[p2 * 3 + 2]), + new Vector(_transformedPoints[p3 * 3], _transformedPoints[p3 * 3 + 1], _transformedPoints[p3 * 3 + 2]), + new Vector(_transformedNormals[p1 * 3], _transformedNormals[p1 * 3 + 1], _transformedNormals[p1 * 3 + 2])); + } + + public inline function getTriangle(idx:Int) { + var p1 = indices[idx]; + var p2 = indices[idx + 1]; + var p3 = indices[idx + 2]; + + return new TransformedCollisionTriangle(getPoint(p1), getPoint(p2), getPoint(p3), getNormal(p1)); + } + + public function getTransformed(m:Matrix, invtform:Matrix) { + var tformed = new CollisionSurface(); + tformed.points = this.points.copy(); + tformed.normals = this.normals.copy(); + tformed.indices = this.indices.copy(); + tformed.friction = this.friction; + tformed.force = this.force; + tformed.restitution = this.restitution; + tformed.transformKeys = this.transformKeys.copy(); + + for (i in 0...Std.int(points.length / 3)) { + var v = getPoint(i); + var v2 = v.transformed(m); + tformed.points[i * 3] = v2.x; + tformed.points[i * 3 + 1] = v2.y; + tformed.points[i * 3 + 2] = v2.z; + + var n = getNormal(i); + var n2 = n.transformed3x3(invtform).normalized(); + tformed.normals[i * 3] = n2.x; + tformed.normals[i * 3 + 1] = n2.y; + tformed.normals[i * 3 + 2] = n2.z; + } + tformed.generateBoundingBox(); + + return tformed; } public function dispose() { diff --git a/src/collision/CollisionWorld.hx b/src/collision/CollisionWorld.hx index 1ad34fc3..f07e89d8 100644 --- a/src/collision/CollisionWorld.hx +++ b/src/collision/CollisionWorld.hx @@ -1,5 +1,6 @@ package collision; +import h3d.Matrix; import src.MarbleGame; import src.TimeState; import h3d.col.Bounds; @@ -7,20 +8,31 @@ import h3d.col.Sphere; import h3d.Vector; import octree.Octree; +@:structInit +@:publicFields +class SphereIntersectionResult { + var foundEntities:Array; + var contacts:Array; +} + class CollisionWorld { + public var staticWorld:CollisionEntity; public var octree:Octree; public var entities:Array = []; public var dynamicEntities:Array = []; public var dynamicOctree:Octree; + public var marbleEntities:Array = []; + var dynamicEntitySet:Map = []; public function new() { this.octree = new Octree(); this.dynamicOctree = new Octree(); + this.staticWorld = new CollisionEntity(null); } - public function sphereIntersection(spherecollision:SphereCollisionEntity, timeState:TimeState) { + public function sphereIntersection(spherecollision:SphereCollisionEntity, timeState:TimeState):SphereIntersectionResult { var position = spherecollision.transform.getPosition(); var radius = spherecollision.radius; // var velocity = spherecollision.velocity; @@ -48,13 +60,37 @@ class CollisionWorld { } } - var dynSearch = dynamicOctree.boundingSearch(box).map(x -> cast(x, CollisionEntity)); + // if (marbleEntities.length > 1) { + // marbleSap.recompute(); + // var sapCollisions = marbleSap.getIntersections(spherecollision); + // for (obj in sapCollisions) { + // if (obj.go.isCollideable) { + // contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState)); + // } + // } + // } + + // contacts = contacts.concat(this.staticWorld.sphereIntersection(spherecollision, timeState)); + + var dynSearch = dynamicOctree.boundingSearch(box); for (obj in dynSearch) { if (obj != spherecollision) { - if (obj.boundingBox.collide(box) && obj.go.isCollideable) - contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState)); + var col = cast(obj, CollisionEntity); + if (col.boundingBox.collide(box) && col.go.isCollideable) + contacts = contacts.concat(col.sphereIntersection(spherecollision, timeState)); } } + + // for (marb in marbleEntities) { + // if (marb != spherecollision) { + // if (spherecollision.go.isCollideable) { + // var isecs = marb.sphereIntersection(spherecollision, timeState); + // if (isecs.length > 0) + // foundEntities.push(marb); + // contacts = contacts.concat(isecs); + // } + // } + // } return {foundEntities: foundEntities, contacts: contacts}; } @@ -98,11 +134,19 @@ class CollisionWorld { + rayDirection.x * rayLength, rayStart.y + rayDirection.y * rayLength, rayStart.z + rayDirection.z * rayLength); - var objs = this.octree.boundingSearch(bounds).concat(dynamicOctree.boundingSearch(bounds)).map(x -> cast(x, CollisionEntity)); + var objs = this.octree.boundingSearch(bounds); + var dynObjs = dynamicOctree.boundingSearch(bounds); var results = []; for (obj in objs) { - results = results.concat(obj.rayCast(rayStart, rayDirection)); + var oo = cast(obj, CollisionEntity); + oo.rayCast(rayStart, rayDirection, results); } + + for (obj in dynObjs) { + var oo = cast(obj, CollisionEntity); + oo.rayCast(rayStart, rayDirection, results); + } + // results = results.concat(this.staticWorld.rayCast(rayStart, rayDirection)); return results; } @@ -114,12 +158,26 @@ class CollisionWorld { // [entity.boundingBox.xSize, entity.boundingBox.ySize, entity.boundingBox.zSize], entity); } + public function addMarbleEntity(entity:SphereCollisionEntity) { + this.marbleEntities.push(entity); + } + + public function removeMarbleEntity(entity:SphereCollisionEntity) { + this.marbleEntities.remove(entity); + } + public function addMovingEntity(entity:CollisionEntity) { this.dynamicEntities.push(entity); this.dynamicOctree.insert(entity); this.dynamicEntitySet.set(entity, true); } + public function removeMovingEntity(entity:CollisionEntity) { + this.dynamicEntities.remove(entity); + this.dynamicOctree.remove(entity); + this.dynamicEntitySet.remove(entity); + } + public function updateTransform(entity:CollisionEntity) { if (!dynamicEntitySet.exists(entity)) { this.octree.update(entity); @@ -128,6 +186,17 @@ class CollisionWorld { } } + public function addStaticInterior(entity:CollisionEntity, transform:Matrix) { + var invTform = transform.getInverse(); + for (surf in entity.surfaces) { + staticWorld.addSurface(surf.getTransformed(transform, invTform)); + } + } + + public function finalizeStaticGeometry() { + this.staticWorld.finalize(); + } + public function dispose() { for (e in entities) { e.dispose(); @@ -140,5 +209,7 @@ class CollisionWorld { dynamicEntities = null; dynamicOctree = null; dynamicEntitySet = null; + staticWorld.dispose(); + staticWorld = null; } } diff --git a/src/collision/Grid.hx b/src/collision/Grid.hx index cd713ec8..75459b1c 100644 --- a/src/collision/Grid.hx +++ b/src/collision/Grid.hx @@ -9,16 +9,24 @@ class Grid { public var cellSize:Vector; // The dimensions of one cell - public var CELL_DIV = new Vector(12, 12, 12); // split the bounds into cells of dimensions 1/16th of the corresponding dimensions of the bounds + static var CELL_SIZE = 16; - var map:Map> = new Map(); + public var CELL_DIV = new Vector(CELL_SIZE, CELL_SIZE); // split the bounds into cells of dimensions 1/16th of the corresponding dimensions of the bounds + + var cells:Array> = []; var surfaces:Array = []; + var searchKey:Int = 0; public function new(bounds:Bounds) { this.bounds = bounds.clone(); - this.cellSize = new Vector(bounds.xSize / CELL_DIV.x, bounds.ySize / CELL_DIV.y, bounds.zSize / CELL_DIV.z); + this.cellSize = new Vector(bounds.xSize / CELL_DIV.x, bounds.ySize / CELL_DIV.y); + for (i in 0...CELL_SIZE) { + for (j in 0...CELL_SIZE) { + this.cells.push([]); + } + } } public function insert(surface:CollisionSurface) { @@ -26,61 +34,80 @@ class Grid { if (this.bounds.containsBounds(surface.boundingBox)) { var idx = this.surfaces.length; this.surfaces.push(surface); - - var xStart = Math.floor((surface.boundingBox.xMin - bounds.xMin) / this.cellSize.x); - var yStart = Math.floor((surface.boundingBox.yMin - bounds.yMin) / this.cellSize.y); - var zStart = Math.floor((surface.boundingBox.zMin - bounds.zMin) / this.cellSize.z); - var xEnd = Math.ceil((surface.boundingBox.xMax - bounds.xMin) / this.cellSize.x) + 1; - var yEnd = Math.ceil((surface.boundingBox.yMax - bounds.yMin) / this.cellSize.y) + 1; - var zEnd = Math.ceil((surface.boundingBox.zMax - bounds.zMin) / this.cellSize.z) + 1; - - // Insert the surface references from [xStart, yStart, zStart] to [xEnd, yEnd, zEnd] into the map - for (i in xStart...xEnd) { - for (j in yStart...yEnd) { - for (k in zStart...zEnd) { - var hash = hashVector(i, j, k); - if (!this.map.exists(hash)) { - this.map.set(hash, []); - } - this.map.get(hash).push(idx); - } - } - } } else { throw new Exception("Surface is not contained in the grid's bounds"); } } + public function build() { + for (i in 0...CELL_SIZE) { + var minX = this.bounds.xMin; + var maxX = this.bounds.xMin; + minX += i * this.cellSize.x; + maxX += (i + 1) * this.cellSize.x; + for (j in 0...CELL_SIZE) { + var minY = this.bounds.yMin; + var maxY = this.bounds.yMin; + minY += j * this.cellSize.y; + maxY += (j + 1) * this.cellSize.y; + + var binRect = new h2d.col.Bounds(); + binRect.xMin = minX; + binRect.yMin = minY; + binRect.xMax = maxX; + binRect.yMax = maxY; + + for (idx in 0...this.surfaces.length) { + var surface = this.surfaces[idx]; + var hullRect = new h2d.col.Bounds(); + hullRect.xMin = surface.boundingBox.xMin; + hullRect.yMin = surface.boundingBox.yMin; + hullRect.xMax = surface.boundingBox.xMax; + hullRect.yMax = surface.boundingBox.yMax; + + if (hullRect.intersects(binRect)) { + this.cells[16 * i + j].push(idx); + } + } + } + } + } + // searchbox should be in LOCAL coordinates public function boundingSearch(searchbox:Bounds) { - var xStart = Math.floor((searchbox.xMin - bounds.xMin) / this.cellSize.x); - var yStart = Math.floor((searchbox.yMin - bounds.yMin) / this.cellSize.y); - var zStart = Math.floor((searchbox.zMin - bounds.zMin) / this.cellSize.z); - var xEnd = Math.ceil((searchbox.xMax - bounds.xMin) / this.cellSize.x) + 1; - var yEnd = Math.ceil((searchbox.yMax - bounds.yMin) / this.cellSize.y) + 1; - var zEnd = Math.ceil((searchbox.zMax - bounds.zMin) / this.cellSize.z) + 1; + var queryMinX = Math.max(searchbox.xMin, bounds.xMin); + var queryMinY = Math.max(searchbox.yMin, bounds.yMin); + var queryMaxX = Math.min(searchbox.xMax, bounds.xMax); + var queryMaxY = Math.min(searchbox.yMax, bounds.yMax); + var xStart = Math.floor((queryMinX - bounds.xMin) / this.cellSize.x); + var yStart = Math.floor((queryMinY - bounds.yMin) / this.cellSize.y); + var xEnd = Math.ceil((queryMaxX - bounds.xMin) / this.cellSize.x); + var yEnd = Math.ceil((queryMaxY - bounds.yMin) / this.cellSize.y); + + if (xStart < 0) + xStart = 0; + if (yStart < 0) + yStart = 0; + if (xEnd > CELL_SIZE) + xEnd = CELL_SIZE; + if (yEnd > CELL_SIZE) + yEnd = CELL_SIZE; var foundSurfaces = []; - for (surf in this.surfaces) { - surf.key = false; - } + searchKey++; // Insert the surface references from [xStart, yStart, zStart] to [xEnd, yEnd, zEnd] into the map for (i in xStart...xEnd) { for (j in yStart...yEnd) { - for (k in zStart...zEnd) { - var hash = hashVector(i, j, k); - if (this.map.exists(hash)) { - var surfs = this.map.get(hash); - for (surf in surfs) { - if (surfaces[surf].key) - continue; - if (searchbox.containsBounds(surfaces[surf].boundingBox) || searchbox.collide(surfaces[surf].boundingBox)) { - foundSurfaces.push(surfaces[surf]); - surfaces[surf].key = true; - } - } + for (surfIdx in cells[16 * i + j]) { + var surf = surfaces[surfIdx]; + if (surf.key == searchKey) + continue; + surf.key = searchKey; + if (searchbox.containsBounds(surf.boundingBox) || searchbox.collide(surf.boundingBox)) { + foundSurfaces.push(surf); + surf.key = searchKey; } } } @@ -101,48 +128,31 @@ class Grid { var cell = origin.sub(this.bounds.getMin().toVector()); cell.x /= this.cellSize.x; cell.y /= this.cellSize.y; - cell.z /= this.cellSize.z; - var stepX, outX, X = Math.floor(cell.x); var stepY, outY, Y = Math.floor(cell.y); - var stepZ, outZ, Z = Math.floor(cell.z); - - if ((X < 0) || (X >= CELL_DIV.x) || (Y < 0) || (Y >= CELL_DIV.y) || (Z < 0) || (Z >= CELL_DIV.z)) + if ((X < 0) || (X >= CELL_DIV.x) || (Y < 0) || (Y >= CELL_DIV.y)) return []; - var cb = new Vector(); - if (direction.x > 0) { stepX = 1; outX = CELL_DIV.x; - cb.x = this.bounds.getMin().x + (X + 1) * this.cellSize.x; + cb.x = this.bounds.xMin + (X + 1) * this.cellSize.x; } else { stepX = -1; outX = -1; - cb.x = this.bounds.getMin().x + X * this.cellSize.x; + cb.x = this.bounds.xMin + X * this.cellSize.x; } if (direction.y > 0.0) { stepY = 1; outY = CELL_DIV.y; - cb.y = this.bounds.getMin().y + (Y + 1) * this.cellSize.y; + cb.y = this.bounds.yMin + (Y + 1) * this.cellSize.y; } else { stepY = -1; outY = -1; - cb.y = this.bounds.getMin().y + Y * this.cellSize.y; + cb.y = this.bounds.yMin + Y * this.cellSize.y; } - if (direction.z > 0.0) { - stepZ = 1; - outZ = CELL_DIV.z; - cb.z = this.bounds.getMin().z + (Z + 1) * this.cellSize.z; - } else { - stepZ = -1; - outZ = -1; - cb.z = this.bounds.getMin().z + Z * this.cellSize.z; - } - var tmax = new Vector(); var tdelta = new Vector(); - var rxr, ryr, rzr; if (direction.x != 0) { rxr = 1.0 / direction.x; @@ -156,58 +166,29 @@ class Grid { tdelta.y = this.cellSize.y * stepY * ryr; } else tmax.y = 1000000; - if (direction.z != 0) { - rzr = 1.0 / direction.z; - tmax.z = (cb.z - origin.z) * rzr; - tdelta.z = this.cellSize.z * stepZ * rzr; - } else - tmax.z = 1000000; - - for (surf in this.surfaces) { - surf.key = false; - } - + searchKey++; var results = []; - while (true) { - var hash = hashVector(X, Y, Z); - if (this.map.exists(hash)) { - var currentSurfaces = this.map.get(hash).map(x -> this.surfaces[x]); - - for (surf in currentSurfaces) { - if (surf.key) - continue; - results = results.concat(surf.rayCast(origin, direction)); - surf.key = true; - } + var cell = cells[16 * X + Y]; + for (idx in cell) { + var surf = surfaces[idx]; + if (surf.key == searchKey) + continue; + surf.key = searchKey; + surf.rayCast(origin, direction, results); } if (tmax.x < tmax.y) { - if (tmax.x < tmax.z) { - X = X + stepX; - if (X == outX) - break; - tmax.x += tdelta.x; - } else { - Z = Z + stepZ; - if (Z == outZ) - break; - tmax.z += tdelta.z; - } + X = X + stepX; + if (X == outX) + break; + tmax.x += tdelta.x; } else { - if (tmax.y < tmax.z) { - Y = Y + stepY; - if (Y == outY) - break; - tmax.y += tdelta.y; - } else { - Z = Z + stepZ; - if (Z == outZ) - break; - tmax.z += tdelta.z; - } + Y = Y + stepY; + if (Y == outY) + break; + tmax.y += tdelta.y; } } - return results; } } diff --git a/src/collision/SphereCollisionEntity.hx b/src/collision/SphereCollisionEntity.hx index 5bb97230..5173d65b 100644 --- a/src/collision/SphereCollisionEntity.hx +++ b/src/collision/SphereCollisionEntity.hx @@ -27,8 +27,9 @@ class SphereCollisionEntity extends CollisionEntity { public override function generateBoundingBox() { var boundingBox = new Bounds(); var pos = transform.getPosition(); - boundingBox.addSpherePos(pos.x, pos.y, pos.z, radius); - boundingBox.transform3x3(transform); + boundingBox.addSpherePos(0, 0, 0, radius); + boundingBox.transform(transform); + this.boundingBox = boundingBox; if (Debug.drawBounds) { @@ -68,9 +69,8 @@ class SphereCollisionEntity extends CollisionEntity { } } - public override function rayCast(rayOrigin:Vector, rayDirection:Vector) { + public override function rayCast(rayOrigin:Vector, rayDirection:Vector, results:Array) { // TEMP cause bruh - return []; } public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) { @@ -85,26 +85,25 @@ class SphereCollisionEntity extends CollisionEntity { if (otherRadius * otherRadius * 1.01 > otherDist.lengthSq()) { var normDist = otherDist.normalized(); - var contact = new CollisionInfo(); + var contact = CollisionPool.alloc(); contact.collider = this; contact.friction = 1; contact.restitution = 1; - contact.velocity = this.velocity; + contact.velocity = this.velocity.clone(); contact.otherObject = this.go; contact.point = position.add(normDist); contact.normal = normDist.multiply(-1); contact.force = 0; contact.contactDistance = contact.point.distance(position); - contact.penetration = radius - (position.sub(contact.point).dot(contact.normal)); contacts.push(contact); // var othercontact = new CollisionInfo(); // othercontact.collider = collisionEntity; // othercontact.friction = 1; // othercontact.restitution = 1; - // othercontact.velocity = this.velocity; - // othercontact.point = thispos.add(position).multiply(0.5); - // othercontact.normal = contact.point.sub(position).normalized(); + // othercontact.velocity = collisionEntity.velocity.clone(); + // othercontact.point = thispos.sub(normDist); + // othercontact.normal = normDist.clone(); // othercontact.contactDistance = contact.point.distance(position); // othercontact.force = 0; // othercontact.penetration = this.radius - (thispos.sub(othercontact.point).dot(othercontact.normal)); diff --git a/src/octree/IOctreeObject.hx b/src/octree/IOctreeObject.hx index 479547cb..4343b5c3 100644 --- a/src/octree/IOctreeObject.hx +++ b/src/octree/IOctreeObject.hx @@ -3,7 +3,9 @@ package octree; import h3d.Vector; import h3d.col.Bounds; -typedef RayIntersectionData = { +@:publicFields +@:structInit +class RayIntersectionData { var point:Vector; var normal:Vector; var object:IOctreeObject; @@ -11,5 +13,5 @@ typedef RayIntersectionData = { interface IOctreeObject extends IOctreeElement { var boundingBox:Bounds; - function rayCast(rayOrigin:Vector, rayDirection:Vector):Array; + function rayCast(rayOrigin:Vector, rayDirection:Vector, resultSet:Array):Void; } diff --git a/src/octree/Octree.hx b/src/octree/Octree.hx index 23529497..7ad6152a 100644 --- a/src/octree/Octree.hx +++ b/src/octree/Octree.hx @@ -18,10 +18,6 @@ class Octree { public function new() { this.root = new OctreeNode(this, 0); - // Init the octree to a 1x1x1 cube - this.root.bounds = new Bounds(); - this.root.bounds.xMin = this.root.bounds.yMin = this.root.bounds.zMin = 0; - this.root.bounds.xMax = this.root.bounds.yMax = this.root.bounds.zMax = 1; this.objectToNode = new Map(); } @@ -79,22 +75,24 @@ class Octree { count++; } } - averagePoint = averagePoint.multiply(1 / count); // count should be greater than 0, because that's why we're growing in the first place. + averagePoint.load(averagePoint.multiply(1 / count)); // count should be greater than 0, because that's why we're growing in the first place. // Determine the direction from the root center to the determined point - var rootCenter = this.root.bounds.getCenter().toVector(); + var rootCenter = new Vector((this.root.xMax + this.root.xMin) / 2, (this.root.yMax + this.root.yMin) / 2, (this.root.zMax + this.root.zMin) / 2); var direction = averagePoint.sub(rootCenter); // Determine the "direction of growth" // Create a new root. The current root will become a quadrant in this new root. var newRoot = new OctreeNode(this, this.root.depth - 1); - newRoot.bounds = this.root.bounds.clone(); - newRoot.bounds.xSize *= 2; - newRoot.bounds.ySize *= 2; - newRoot.bounds.zSize *= 2; + newRoot.xMin = this.root.xMin; + newRoot.yMin = this.root.yMin; + newRoot.zMin = this.root.zMin; + newRoot.xMax = 2 * this.root.xMax - this.root.xMin; + newRoot.yMax = 2 * this.root.yMax - this.root.yMin; + newRoot.zMax = 2 * this.root.zMax - this.root.zMin; if (direction.x < 0) - newRoot.bounds.xMin -= this.root.bounds.xSize; + newRoot.xMin -= this.root.xMax - this.root.xMin; if (direction.y < 0) - newRoot.bounds.yMin -= this.root.bounds.ySize; + newRoot.yMin -= this.root.yMax - this.root.yMin; if (direction.z < 0) - newRoot.bounds.zMin -= this.root.bounds.zSize; + newRoot.zMin -= this.root.zMax - this.root.zMin; if (this.root.count > 0) { var octantIndex = ((direction.x < 0) ? 1 : 0) + ((direction.y < 0) ? 2 : 0) + ((direction.z < 0) ? 4 : 0); newRoot.createOctants(); @@ -108,12 +106,12 @@ class Octree { /** Tries to shrink the octree if large parts of the octree are empty. */ public function shrink() { - if (this.root.bounds.xSize < 1 || this.root.bounds.ySize < 1 || this.root.bounds.zSize < 1 || this.root.objects.length > 0) + if (this.root.xMax - this.root.xMin < 1 || this.root.yMax - this.root.yMin < 1 || this.root.zMax - this.root.zMin < 1 || this.root.objects.length > 0) return; if (this.root.count == 0) { // Reset to default empty octree - this.root.bounds.xMin = this.root.bounds.yMin = this.root.bounds.zMin = 0; - this.root.bounds.xMax = this.root.bounds.yMax = this.root.bounds.zMin = 1; + this.root.xMin = this.root.yMin = this.root.zMin = 0; + this.root.xMax = this.root.yMax = this.root.zMax = 1; this.root.depth = 0; return; } diff --git a/src/octree/OctreeNode.hx b/src/octree/OctreeNode.hx index 9ea946d9..38f0f7fc 100644 --- a/src/octree/OctreeNode.hx +++ b/src/octree/OctreeNode.hx @@ -13,7 +13,13 @@ class OctreeNode implements IOctreeElement { public var position:Int; /** The min corner of the bounding box. */ - public var bounds:Bounds; + public var xMin:Float; + + public var yMin:Float; + public var zMin:Float; + public var xMax:Float; + public var yMax:Float; + public var zMax:Float; /** The size of the bounding box on all three axes. This forces the bounding box to be a cube. */ public var octants:Array = null; @@ -29,6 +35,12 @@ class OctreeNode implements IOctreeElement { public function new(octree:Octree, depth:Int) { this.octree = octree; this.depth = depth; + this.xMin = 0; + this.yMin = 0; + this.zMin = 0; + this.xMax = 1; + this.yMax = 1; + this.zMax = 1; } public function insert(object:IOctreeObject) { @@ -80,16 +92,13 @@ class OctreeNode implements IOctreeElement { for (i in 0...8) { var newNode = new OctreeNode(this.octree, this.depth + 1); newNode.parent = this; - var newSize = this.bounds.getSize().multiply(1 / 2); - newNode.bounds = this.bounds.clone(); - newNode.bounds.setMin(new Point(this.bounds.xMin - + newSize.x * ((i & 1) >> 0), this.bounds.yMin - + newSize.y * ((i & 2) >> 1), - this.bounds.zMin - + newSize.z * ((i & 4) >> 2))); - newNode.bounds.xSize = newSize.x; - newNode.bounds.ySize = newSize.y; - newNode.bounds.zSize = newSize.z; + var newSize = new Vector(xMax - xMin, yMax - yMin, zMax - zMin); + newNode.xMin = this.xMin + newSize.x * ((i & 1) >> 0); + newNode.yMin = this.yMin + newSize.y * ((i & 2) >> 1); + newNode.zMin = this.zMin + newSize.z * ((i & 4) >> 2); + newNode.xMax = newNode.xMin + newSize.x; + newNode.yMax = newNode.yMin + newSize.y; + newNode.zMax = newNode.zMin + newSize.z; this.octants.push(newNode); } } @@ -141,28 +150,58 @@ class OctreeNode implements IOctreeElement { this.octants = null; // ...then devare the octants } - public function largerThan(object:IOctreeObject) { - return this.bounds.containsBounds(object.boundingBox); + public inline function largerThan(object:IOctreeObject) { + return xMin <= object.boundingBox.xMin && yMin <= object.boundingBox.yMin && zMin <= object.boundingBox.zMin && xMax >= object.boundingBox.xMax + && yMax >= object.boundingBox.yMax && zMax >= object.boundingBox.zMax; // return this.size > (box.xMax - box.xMin) && this.size > (box.yMax - box.yMin) && this.size > (box.zMax - box.zMin); } - public function containsCenter(object:IOctreeObject) { - return this.bounds.contains(object.boundingBox.getCenter()); + public inline function containsCenter(object:IOctreeObject) { + return this.containsPoint2(object.boundingBox.getCenter()); } - public function containsPoint(point:Vector) { - return this.bounds.contains(point.toPoint()); + public inline function containsPoint(p:Vector) { + return p.x >= xMin && p.x < xMax && p.y >= yMin && p.y < yMax && p.z >= zMin && p.z < zMax; + } + + public inline function containsPoint2(p:h3d.col.Point) { + return p.x >= xMin && p.x < xMax && p.y >= yMin && p.y < yMax && p.z >= zMin && p.z < zMax; + } + + inline function rayIntersection(r:Ray, bestMatch:Bool):Float { + var minTx = (xMin - r.px) / r.lx; + var minTy = (yMin - r.py) / r.ly; + var minTz = (zMin - r.pz) / r.lz; + var maxTx = (xMax - r.px) / r.lx; + var maxTy = (yMax - r.py) / r.ly; + var maxTz = (zMax - r.pz) / r.lz; + + var realMinTx = Math.min(minTx, maxTx); + var realMinTy = Math.min(minTy, maxTy); + var realMinTz = Math.min(minTz, maxTz); + var realMaxTx = Math.max(minTx, maxTx); + var realMaxTy = Math.max(minTy, maxTy); + var realMaxTz = Math.max(minTz, maxTz); + + var minmax = Math.min(Math.min(realMaxTx, realMaxTy), realMaxTz); + var maxmin = Math.max(Math.max(realMinTx, realMinTy), realMinTz); + + if (minmax < maxmin) + return -1; + + return maxmin; } public function raycast(rayOrigin:Vector, rayDirection:Vector, intersections:Array) { var ray = Ray.fromValues(rayOrigin.x, rayOrigin.y, rayOrigin.z, rayDirection.x, rayDirection.y, rayDirection.z); // Construct the loose bounding box of this node (2x in size, with the regular bounding box in the center) - if (this.bounds.rayIntersection(ray, true) == -1) + if (rayIntersection(ray, true) == -1) return; for (obj in this.objects) { - var iSecs = obj.rayCast(rayOrigin, rayDirection); + var iSecs = []; + obj.rayCast(rayOrigin, rayDirection, iSecs); for (intersection in iSecs) { var intersectionData = new OctreeIntersection(); intersectionData.distance = rayOrigin.distance(intersection.point); @@ -181,42 +220,22 @@ class OctreeNode implements IOctreeElement { } } - public function boundingSearch(bounds:Bounds, intersections:Array) { - if (this.bounds.collide(bounds)) { + public function boundingSearch(b:Bounds, intersections:Array) { + if (!(xMin > b.xMax || yMin > b.yMax || zMin > b.zMax || xMax < b.xMin || yMax < b.yMin || zMax < b.zMin)) { for (obj in this.objects) { - if (obj.boundingBox.collide(bounds)) + if (obj.boundingBox.collide(b)) intersections.push(obj); } if (octants != null) { for (octant in this.octants) - octant.boundingSearch(bounds, intersections); + octant.boundingSearch(b, intersections); } } } - public function getClosestPoint(point:Vector) { - var closest = new Vector(); - if (this.bounds.xMin > point.x) - closest.x = this.bounds.xMin; - else if (this.bounds.xMax < point.x) - closest.x = this.bounds.xMax; - else - closest.x = point.x; - - if (this.bounds.yMin > point.y) - closest.y = this.bounds.yMin; - else if (this.bounds.yMax < point.y) - closest.y = this.bounds.yMax; - else - closest.y = point.y; - - if (this.bounds.zMin > point.z) - closest.z = this.bounds.zMin; - else if (this.bounds.zMax < point.z) - closest.z = this.bounds.zMax; - else - closest.z = point.z; - + public inline function getClosestPoint(point:Vector) { + var closest = new Vector(Math.min(Math.max(this.xMin, point.x), this.xMax), Math.min(Math.max(this.yMin, point.y), this.yMax), + Math.min(Math.max(this.zMin, point.z), this.zMax)); return closest; } diff --git a/src/octree/PriorityQueue.hx b/src/octree/PriorityQueue.hx index c8367b3b..a68ee36c 100644 --- a/src/octree/PriorityQueue.hx +++ b/src/octree/PriorityQueue.hx @@ -1,5 +1,6 @@ package octree; +@:generic class PriorityQueue { var queue:Array>; @@ -12,7 +13,7 @@ class PriorityQueue { public function enqueue(val:T, priority:Float) { var node = new PriorityQueueNode(val, priority); - if (this.queue == null) { + if (this.queue == null || this.queue.length == 0) { this.queue = [node]; } else { if (this.queue[0].priority >= priority) { diff --git a/src/octree/PriorityQueueNode.hx b/src/octree/PriorityQueueNode.hx index b9fc74d7..9ef34c02 100644 --- a/src/octree/PriorityQueueNode.hx +++ b/src/octree/PriorityQueueNode.hx @@ -1,5 +1,6 @@ package octree; +@:generic class PriorityQueueNode { public var value:T; public var priority:Float; diff --git a/src/rewind/RewindManager.hx b/src/rewind/RewindManager.hx index 7a3837f8..ff31a576 100644 --- a/src/rewind/RewindManager.hx +++ b/src/rewind/RewindManager.hx @@ -50,7 +50,7 @@ class RewindManager { level.marble.helicopterEnableTime, @:privateAccess level.marble.megaMarbleEnableTime ]; - rf.currentUp = level.currentUp.clone(); + rf.currentUp = level.marble.currentUp.clone(); rf.lastContactNormal = level.marble.lastContactNormal.clone(); rf.mpStates = level.pathedInteriors.map(x -> { return { @@ -98,8 +98,8 @@ class RewindManager { } rf.blastAmt = level.blastAmount; rf.oobState = { - oob: level.outOfBounds, - timeState: level.outOfBoundsTime != null ? level.outOfBoundsTime.clone() : null + oob: level.marble.outOfBounds, + timeState: level.marble.outOfBoundsTime != null ? level.marble.outOfBoundsTime.clone() : null }; rf.checkpointState = { currentCheckpoint: @:privateAccess level.currentCheckpoint, @@ -126,13 +126,13 @@ class RewindManager { if (level.marble.heldPowerup == null) { if (rf.marblePowerup != null) { - level.pickUpPowerUp(rf.marblePowerup); + level.pickUpPowerUp(level.marble, rf.marblePowerup); } } else { if (rf.marblePowerup == null) { - level.deselectPowerUp(); + level.deselectPowerUp(level.marble); } else { - level.pickUpPowerUp(rf.marblePowerup); + level.pickUpPowerUp(level.marble, rf.marblePowerup); } } @@ -147,8 +147,10 @@ class RewindManager { @:privateAccess level.marble.helicopterEnableTime = rf.activePowerupStates[2]; @:privateAccess level.marble.megaMarbleEnableTime = rf.activePowerupStates[3]; - if (level.currentUp.x != rf.currentUp.x || level.currentUp.y != rf.currentUp.y || level.currentUp.z != rf.currentUp.z) { - level.setUp(rf.currentUp, level.timeState); + if (level.marble.currentUp.x != rf.currentUp.x + || level.marble.currentUp.y != rf.currentUp.y + || level.marble.currentUp.z != rf.currentUp.z) { + level.setUp(level.marble, rf.currentUp, level.timeState); // Hacky things @:privateAccess level.orientationChangeTime = level.timeState.currentAttemptTime - 300; var oldorient = level.newOrientationQuat; @@ -162,7 +164,7 @@ class RewindManager { @:privateAccess level.orientationChangeTime = -1e8; } - level.currentUp.set(rf.currentUp.x, rf.currentUp.y, rf.currentUp.z); + level.marble.currentUp.set(rf.currentUp.x, rf.currentUp.y, rf.currentUp.z); level.marble.lastContactNormal.set(rf.lastContactNormal.x, rf.lastContactNormal.y, rf.lastContactNormal.z); for (i in 0...rf.mpStates.length) { level.pathedInteriors[i].currentTime = rf.mpStates[i].curState.currentTime; @@ -207,14 +209,14 @@ class RewindManager { if (!rf.oobState.oob) { @:privateAccess level.cancel(level.oobSchedule); - @:privateAccess level.cancel(level.oobSchedule2); + @:privateAccess level.cancel(level.marble.oobSchedule); } else { - level.goOutOfBounds(); + level.goOutOfBounds(level.marble); } - level.outOfBounds = rf.oobState.oob; + level.marble.outOfBounds = rf.oobState.oob; level.marble.camera.oob = rf.oobState.oob; - level.outOfBoundsTime = rf.oobState.timeState != null ? rf.oobState.timeState.clone() : null; + level.marble.outOfBoundsTime = rf.oobState.timeState != null ? rf.oobState.timeState.clone() : null; level.blastAmount = rf.blastAmt; @:privateAccess level.checkpointUp = rf.checkpointState.checkpointUp; @:privateAccess level.checkpointCollectedGems = rf.checkpointState.checkpointCollectedGems; diff --git a/src/shaders/CubemapRenderer.hx b/src/shaders/CubemapRenderer.hx index 54261e5d..b49dfdb9 100644 --- a/src/shaders/CubemapRenderer.hx +++ b/src/shaders/CubemapRenderer.hx @@ -16,22 +16,32 @@ class CubemapRenderer { var camera:Camera; var scene:Scene; var nextFaceToRender:Int; + var facesPerRender:Int = 2; + var updateFps:Float = 360.0; // 6 faces in (1/60) seconds, 1 face in (1/360) seconds + var lastRenderTime:Float = 0; + var usingSky:Bool = false; public function new(scene:Scene, sky:Sky) { this.scene = scene; this.sky = sky; this.cubemap = new Texture(128, 128, [Cube, Dynamic, Target], h3d.mat.Data.TextureFormat.RGB8); - this.cubemap.depthBuffer = new h3d.mat.DepthBuffer(128, 128, h3d.mat.DepthBuffer.DepthFormat.Depth24); + this.cubemap.depthBuffer = new h3d.mat.DepthBuffer(128, 128, h3d.mat.DepthBuffer.DepthFormat.Depth16); this.camera = new Camera(90, 1, 1, 0.02, scene.camera.zFar); this.position = new Vector(); this.nextFaceToRender = 0; } public function render(e:Engine, budget:Float = 1e8) { + var start = haxe.Timer.stamp(); + if (start - lastRenderTime > facesPerRender * 1.0 / updateFps) { + lastRenderTime = start; + } else { + return; + } + var scenecam = scene.camera; scene.camera = camera; - var start = haxe.Timer.stamp(); var renderedFaces = 0; for (i in 0...6) { @@ -39,6 +49,7 @@ class CubemapRenderer { e.pushTarget(cubemap, index); this.camera.setCubeMap(index, position); + this.camera.update(); e.clear(0, 1); scene.render(e); e.popTarget(); diff --git a/src/shapes/AbstractBumper.hx b/src/shapes/AbstractBumper.hx index 01b0c69b..62006ed0 100644 --- a/src/shapes/AbstractBumper.hx +++ b/src/shapes/AbstractBumper.hx @@ -27,8 +27,8 @@ class AbstractBumper extends DtsObject { return completion; } - override function onMarbleContact(time:TimeState, ?contact:CollisionInfo) { - super.onMarbleContact(time, contact); + override function onMarbleContact(marble:src.Marble, time:TimeState, ?contact:CollisionInfo) { + super.onMarbleContact(marble, time, contact); if (time.timeSinceLoad - this.lastContactTime <= 0) return; var currentCompletion = this.getCurrentCompletion(time); diff --git a/src/shapes/AntiGravity.hx b/src/shapes/AntiGravity.hx index 948dbbf1..7d359580 100644 --- a/src/shapes/AntiGravity.hx +++ b/src/shapes/AntiGravity.hx @@ -20,23 +20,23 @@ class AntiGravity extends PowerUp { this.cooldownDuration = Math.NEGATIVE_INFINITY; } - public function pickUp():Bool { + public function pickUp(marble:src.Marble):Bool { var direction = new Vector(0, 0, -1); direction.transform(this.getRotationQuat().toMatrix()); - return !direction.equals(this.level.currentUp); + return !direction.equals(marble.currentUp); } - public function use(timeState:TimeState) { + public function use(marble:src.Marble, timeState:TimeState) { if (!this.level.rewinding) { var direction = new Vector(0, 0, -1); direction.transform(this.getRotationQuat().toMatrix()); - this.level.setUp(direction, timeState); + if (marble == level.marble) + this.level.setUp(marble, direction, timeState); + else { + // @:privateAccess marble.netFlags |= MarbleNetFlags.GravityChange; + marble.currentUp.load(direction); + } } - // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity - // if (!this.level.rewinding) - // AudioManager.play(this.sounds[1]); - // this.level.particles.createEmitter(superJumpParticleOptions, null, () => Util.vecOimoToThree(marble.body.getPosition())); - // this.level.deselectPowerUp(); } public override function init(level:MarbleWorld, onFinish:Void->Void) { diff --git a/src/shapes/Blast.hx b/src/shapes/Blast.hx index cbba68ea..3d168a47 100644 --- a/src/shapes/Blast.hx +++ b/src/shapes/Blast.hx @@ -26,11 +26,11 @@ class Blast extends PowerUp { }); } - public function pickUp():Bool { + public function pickUp(marble:src.Marble):Bool { return true; } - public function use(timeState:TimeState) { + public function use(marble:src.Marble, timeState:TimeState) { this.level.blastAmount = 1.03; } } diff --git a/src/shapes/Checkpoint.hx b/src/shapes/Checkpoint.hx index c9187d3c..9617c562 100644 --- a/src/shapes/Checkpoint.hx +++ b/src/shapes/Checkpoint.hx @@ -28,7 +28,7 @@ class Checkpoint extends DtsObject { }); } - public override function onMarbleContact(time:src.TimeState, ?contact:CollisionInfo) { + public override function onMarbleContact(marble:src.Marble, time:src.TimeState, ?contact:CollisionInfo) { this.level.saveCheckpointState({ obj: this, elem: this.element diff --git a/src/shapes/EasterEgg.hx b/src/shapes/EasterEgg.hx index f26cbadf..88797451 100644 --- a/src/shapes/EasterEgg.hx +++ b/src/shapes/EasterEgg.hx @@ -1,5 +1,6 @@ package shapes; +import src.Marble; import src.Settings; import mis.MissionElement.MissionElementItem; import src.ResourceLoader; @@ -13,9 +14,10 @@ class EasterEgg extends PowerUp { this.identifier = "EasterEgg"; this.pickUpName = "Easter Egg"; this.autoUse = true; + this.cooldownDuration = 1e8; } - public function pickUp():Bool { + public function pickUp(marble:Marble):Bool { var found:Bool = false; if (Settings.easterEggs.exists(this.level.mission.path)) { found = true; @@ -40,5 +42,5 @@ class EasterEgg extends PowerUp { }); } - public function use(timeState:src.TimeState) {} + public function use(marble:Marble, timeState:src.TimeState) {} } diff --git a/src/shapes/Gem.hx b/src/shapes/Gem.hx index 302e1027..458d4290 100644 --- a/src/shapes/Gem.hx +++ b/src/shapes/Gem.hx @@ -7,9 +7,12 @@ import src.TimeState; import src.DtsObject; import src.ResourceLoaderWorker; import src.ResourceLoader; +import src.Marble; class Gem extends DtsObject { public var pickedUp:Bool; + public var netIndex:Int; + public var pickUpClient:Int = -1; var gemColor:String; @@ -51,18 +54,19 @@ class Gem extends DtsObject { } } - override function onMarbleInside(timeState:TimeState) { - super.onMarbleInside(timeState); + override function onMarbleInside(marble:Marble, timeState:TimeState) { + super.onMarbleInside(marble, timeState); if (this.pickedUp || this.level.rewinding) return; this.pickedUp = true; this.setOpacity(0); // Hide the gem - this.level.pickUpGem(this); + this.level.pickUpGem(marble, this); // this.level.replay.recordMarbleInside(this); } override function reset() { this.pickedUp = false; + this.pickUpClient = -1; this.setOpacity(1); } } diff --git a/src/shapes/Helicopter.hx b/src/shapes/Helicopter.hx index e0cd4d28..0efc5595 100644 --- a/src/shapes/Helicopter.hx +++ b/src/shapes/Helicopter.hx @@ -6,6 +6,7 @@ import src.TimeState; import src.DtsObject; import src.AudioManager; import src.MarbleWorld; +import src.Marble; class Helicopter extends PowerUp { public function new(element:MissionElementItem) { @@ -27,13 +28,12 @@ class Helicopter extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - var marble = this.level.marble; - marble.enableHelicopter(timeState.currentAttemptTime); - this.level.deselectPowerUp(); + public function use(marble:Marble, timeState:TimeState) { + marble.enableHelicopter(timeState); + this.level.deselectPowerUp(marble); } } diff --git a/src/shapes/LandMine.hx b/src/shapes/LandMine.hx index b6b20c18..1b893693 100644 --- a/src/shapes/LandMine.hx +++ b/src/shapes/LandMine.hx @@ -124,7 +124,7 @@ class LandMine extends DtsObject { }); } - override function onMarbleContact(timeState:TimeState, ?contact:CollisionInfo) { + override function onMarbleContact(marble:src.Marble, timeState:TimeState, ?contact:CollisionInfo) { if (this.isCollideable && !this.level.rewinding) { // marble.velocity = marble.velocity.add(vec); this.disappearTime = timeState.timeSinceLoad; @@ -136,7 +136,6 @@ class LandMine extends DtsObject { this.level.particleManager.createEmitter(landMineSmokeParticle, landMineSmokeParticleData, this.getAbsPos().getPosition()); this.level.particleManager.createEmitter(landMineSparksParticle, landMineSparkParticleData, this.getAbsPos().getPosition()); - var marble = this.level.marble; var minePos = this.getAbsPos().getPosition(); var off = marble.getAbsPos().getPosition().sub(minePos); diff --git a/src/shapes/MegaMarble.hx b/src/shapes/MegaMarble.hx index 2e1132a2..cc3332d9 100644 --- a/src/shapes/MegaMarble.hx +++ b/src/shapes/MegaMarble.hx @@ -34,13 +34,14 @@ class MegaMarble extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:src.Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - this.level.marble.enableMegaMarble(timeState.currentAttemptTime); - this.level.deselectPowerUp(); - AudioManager.playSound(ResourceLoader.getResource('data/sound/dosuperjump.wav', ResourceLoader.getAudio, this.soundResources)); + public function use(marble:src.Marble, timeState:TimeState) { + marble.enableMegaMarble(timeState); + this.level.deselectPowerUp(marble); + if (this.level.marble == marble && @:privateAccess !marble.isNetUpdate) + AudioManager.playSound(ResourceLoader.getResource('data/sound/dosuperjump.wav', ResourceLoader.getAudio, this.soundResources)); } } diff --git a/src/shapes/Nuke.hx b/src/shapes/Nuke.hx index a1607092..47e154ff 100644 --- a/src/shapes/Nuke.hx +++ b/src/shapes/Nuke.hx @@ -121,7 +121,7 @@ class Nuke extends DtsObject { }); } - override function onMarbleContact(timeState:TimeState, ?contact:CollisionInfo) { + override function onMarbleContact(marble:src.Marble, timeState:TimeState, ?contact:CollisionInfo) { if (this.isCollideable && !this.level.rewinding) { // marble.velocity = marble.velocity.add(vec); this.disappearTime = timeState.timeSinceLoad; @@ -133,7 +133,6 @@ class Nuke extends DtsObject { this.level.particleManager.createEmitter(nukeSmokeParticle, nukeSmokeParticleData, this.getAbsPos().getPosition()); this.level.particleManager.createEmitter(nukeSparksParticle, nukeSparkParticleData, this.getAbsPos().getPosition()); - var marble = this.level.marble; var minePos = this.getAbsPos().getPosition(); var dtsCenter = this.dts.bounds.center(); // dtsCenter.x = -dtsCenter.x; diff --git a/src/shapes/PowerUp.hx b/src/shapes/PowerUp.hx index f3ddf45b..b1d39372 100644 --- a/src/shapes/PowerUp.hx +++ b/src/shapes/PowerUp.hx @@ -7,6 +7,7 @@ import src.TimeState; import src.Util; import h3d.Vector; import src.DtsObject; +import src.Marble; abstract class PowerUp extends DtsObject { public var lastPickUpTime:Float = -1; @@ -15,6 +16,11 @@ abstract class PowerUp extends DtsObject { public var pickUpName:String; public var element:MissionElementItem; public var pickupSound:Sound; + public var netIndex:Int; + + // Net + var pickupClient:Int = -1; + var pickupTicks:Int = -1; var customPickupMessage:String = null; @@ -26,27 +32,29 @@ abstract class PowerUp extends DtsObject { this.element = element; } - public override function onMarbleInside(timeState:TimeState) { + public override function onMarbleInside(marble:Marble, timeState:TimeState) { var pickupable = this.lastPickUpTime == -1 || (timeState.currentAttemptTime - this.lastPickUpTime) >= this.cooldownDuration; if (!pickupable) return; - if (this.pickUp()) { + if (this.pickUp(marble)) { // this.level.replay.recordMarbleInside(this); this.lastPickUpTime = timeState.currentAttemptTime; if (this.autoUse) - this.use(timeState); + this.use(marble, timeState); - if (customPickupMessage != null) - this.level.displayAlert(customPickupMessage); - else - this.level.displayAlert('You picked up a ${this.pickUpName}!'); - if (this.element.showhelponpickup == "1" && !this.autoUse) - this.level.displayHelp('Press to use the ${this.pickUpName}!'); + if (level.marble == marble && @:privateAccess !marble.isNetUpdate) { + if (customPickupMessage != null) + this.level.displayAlert(customPickupMessage); + else + this.level.displayAlert('You picked up a ${this.pickUpName}!'); + if (this.element.showhelponpickup == "1" && !this.autoUse) + this.level.displayHelp('Press to use the ${this.pickUpName}!'); - if (pickupSound != null && !this.level.rewinding) { - AudioManager.playSound(pickupSound); + if (pickupSound != null && !this.level.rewinding) { + AudioManager.playSound(pickupSound); + } } } } @@ -62,11 +70,13 @@ abstract class PowerUp extends DtsObject { this.setOpacity(opacity); } - public abstract function pickUp():Bool; + public abstract function pickUp(marble:Marble):Bool; - public abstract function use(timeState:TimeState):Void; + public abstract function use(marble:Marble, timeState:TimeState):Void; public override function reset() { this.lastPickUpTime = Math.NEGATIVE_INFINITY; + this.pickupClient = -1; + this.pickupTicks = -1; } } diff --git a/src/shapes/PushButton.hx b/src/shapes/PushButton.hx index 5046ebe7..983f2024 100644 --- a/src/shapes/PushButton.hx +++ b/src/shapes/PushButton.hx @@ -40,8 +40,8 @@ class PushButton extends DtsObject { return completion; } - override function onMarbleContact(time:TimeState, ?contact:CollisionInfo) { - super.onMarbleContact(time, contact); + override function onMarbleContact(marble:src.Marble, time:TimeState, ?contact:CollisionInfo) { + super.onMarbleContact(marble, time, contact); if (time.timeSinceLoad - this.lastContactTime <= 0) return; // The trapdoor is queued to open, so don't do anything. var currentCompletion = this.getCurrentCompletion(time); diff --git a/src/shapes/RandomPowerup.hx b/src/shapes/RandomPowerup.hx index 6f88ef87..e805472d 100644 --- a/src/shapes/RandomPowerup.hx +++ b/src/shapes/RandomPowerup.hx @@ -59,7 +59,7 @@ class RandomPowerup extends PowerUp { }); } - public function pickUp():Bool { + public function pickUp(marble:src.Marble):Bool { while (true) { var r = Std.random(6); if (this.level.isWatching) @@ -88,7 +88,7 @@ class RandomPowerup extends PowerUp { } pow.level = this.level; - if (pow.pickUp()) { + if (pow.pickUp(marble)) { this.cooldownDuration = pow.cooldownDuration; this.pickUpName = pow.pickUpName; if (this.level.isRecording) @@ -99,7 +99,7 @@ class RandomPowerup extends PowerUp { return true; } - public function use(time:TimeState) { + public function use(marble:src.Marble, time:TimeState) { if (this.wasTimeTravel) this.level.addBonusTime(5); } diff --git a/src/shapes/ShockAbsorber.hx b/src/shapes/ShockAbsorber.hx index 71a104c0..61253e63 100644 --- a/src/shapes/ShockAbsorber.hx +++ b/src/shapes/ShockAbsorber.hx @@ -25,13 +25,12 @@ class ShockAbsorber extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:src.Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - var marble = this.level.marble; - marble.enableShockAbsorber(timeState.currentAttemptTime); - this.level.deselectPowerUp(); + public function use(marble:src.Marble, timeState:TimeState) { + marble.enableShockAbsorber(timeState); + this.level.deselectPowerUp(marble); } } diff --git a/src/shapes/SuperBounce.hx b/src/shapes/SuperBounce.hx index a8a1ed04..c814e2e3 100644 --- a/src/shapes/SuperBounce.hx +++ b/src/shapes/SuperBounce.hx @@ -16,8 +16,8 @@ class SuperBounce extends PowerUp { this.pickUpName = "Marble Recoil PowerUp"; } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:src.Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } public override function init(level:MarbleWorld, onFinish:Void->Void) { @@ -29,13 +29,12 @@ class SuperBounce extends PowerUp { }); } - public function use(timeState:TimeState) { - var marble = this.level.marble; - marble.enableSuperBounce(timeState.currentAttemptTime); + public function use(marble:src.Marble, timeState:TimeState) { + marble.enableSuperBounce(timeState); // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) // AudioManager.play(this.sounds[1]); // this.level.particles.createEmitter(superJumpParticleOptions, null, () => Util.vecOimoToThree(marble.body.getPosition())); - this.level.deselectPowerUp(); + this.level.deselectPowerUp(marble); } } diff --git a/src/shapes/SuperJump.hx b/src/shapes/SuperJump.hx index a423f180..1a89fc19 100644 --- a/src/shapes/SuperJump.hx +++ b/src/shapes/SuperJump.hx @@ -57,18 +57,19 @@ class SuperJump extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:src.Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - var marble = this.level.marble; - marble.velocity = marble.velocity.add(this.level.currentUp.multiply(20)); - this.level.particleManager.createEmitter(superJumpParticleOptions, this.sjEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); + public function use(marble:src.Marble, timeState:TimeState) { + marble.velocity.load(marble.velocity.add(marble.currentUp.multiply(20))); + if (@:privateAccess !marble.isNetUpdate) + this.level.particleManager.createEmitter(superJumpParticleOptions, this.sjEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) - AudioManager.playSound(ResourceLoader.getResource("data/sound/dosuperjump.wav", ResourceLoader.getAudio, this.soundResources)); + if (level.marble == marble && @:privateAccess !marble.isNetUpdate) + AudioManager.playSound(ResourceLoader.getResource("data/sound/dosuperjump.wav", ResourceLoader.getAudio, this.soundResources)); // this.level.particles.createEmitter(superJumpParticleOptions, null, () => Util.vecOimoToThree(marble.body.getPosition())); - this.level.deselectPowerUp(); + this.level.deselectPowerUp(marble); } } diff --git a/src/shapes/SuperSpeed.hx b/src/shapes/SuperSpeed.hx index fb323891..76de19ab 100644 --- a/src/shapes/SuperSpeed.hx +++ b/src/shapes/SuperSpeed.hx @@ -63,30 +63,32 @@ class SuperSpeed extends PowerUp { }); } - public function pickUp():Bool { - return this.level.pickUpPowerUp(this); + public function pickUp(marble:src.Marble):Bool { + return this.level.pickUpPowerUp(marble, this); } - public function use(timeState:TimeState) { - var marble = this.level.marble; + public function use(marble:src.Marble, timeState:TimeState) { var movementVector = marble.getMarbleAxis()[0]; - // Okay, so super speed directionality is a bit strange. In general, the direction is based on the normal vector of the last surface you had contact with. + var boostVec = movementVector.clone(); - // var quat = level.newOrientationQuat; - // movementVector.applyQuaternion(quat); + var contactDot = movementVector.dot(marble.lastContactNormal); + boostVec.load(boostVec.sub(marble.lastContactNormal.multiply(contactDot))); + if (boostVec.lengthSq() > 0.01) { + boostVec.normalize(); + } else { + boostVec.load(movementVector); + } - var quat2 = new Quat(); - // Determine the necessary rotation to rotate the up vector to the contact normal. - quat2.initMoveTo(this.level.currentUp, marble.lastContactNormal); - movementVector.transform(quat2.toMatrix()); - marble.velocity = marble.velocity.add(movementVector.multiply(-25)); + marble.velocity.load(marble.velocity.add(boostVec.multiply(-25))); // marble.body.addLinearVelocity(Util.vecThreeToOimo(movementVector).scale(24.7)); // Whirligig's determined value // marble.body.addLinearVelocity(this.level.currentUp.scale(20)); // Simply add to vertical velocity // if (!this.level.rewinding) - AudioManager.playSound(ResourceLoader.getResource("data/sound/dosuperspeed.wav", ResourceLoader.getAudio, this.soundResources)); - this.level.particleManager.createEmitter(superSpeedParticleOptions, this.ssEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); - this.level.deselectPowerUp(); + if (level.marble == marble && @:privateAccess !marble.isNetUpdate) + AudioManager.playSound(ResourceLoader.getResource("data/sound/dosuperspeed.wav", ResourceLoader.getAudio, this.soundResources)); + if (@:privateAccess !marble.isNetUpdate) + this.level.particleManager.createEmitter(superSpeedParticleOptions, this.ssEmitterParticleData, null, () -> marble.getAbsPos().getPosition()); + this.level.deselectPowerUp(marble); } } diff --git a/src/shapes/TimeTravel.hx b/src/shapes/TimeTravel.hx index 226539f0..f4bbc7fb 100644 --- a/src/shapes/TimeTravel.hx +++ b/src/shapes/TimeTravel.hx @@ -38,11 +38,11 @@ class TimeTravel extends PowerUp { }); } - public function pickUp():Bool { + public function pickUp(marble:src.Marble):Bool { return true; } - public function use(time:TimeState) { + public function use(marble:src.Marble, time:TimeState) { if (!this.level.rewinding) level.addBonusTime(this.timeBonus); } diff --git a/src/shapes/Trapdoor.hx b/src/shapes/Trapdoor.hx index 6c8b155f..0ada0d77 100644 --- a/src/shapes/Trapdoor.hx +++ b/src/shapes/Trapdoor.hx @@ -64,8 +64,8 @@ class Trapdoor extends DtsObject { return completion; } - override function onMarbleContact(time:TimeState, ?contact:CollisionInfo) { - super.onMarbleContact(time, contact); + override function onMarbleContact(marble:src.Marble, time:TimeState, ?contact:CollisionInfo) { + super.onMarbleContact(marble, time, contact); if (time.timeSinceLoad - this.lastContactTime <= 0) return; // The trapdoor is queued to open, so don't do anything. var currentCompletion = this.getCurrentCompletion(time); diff --git a/src/triggers/CheckpointTrigger.hx b/src/triggers/CheckpointTrigger.hx index b670d018..012d6aaf 100644 --- a/src/triggers/CheckpointTrigger.hx +++ b/src/triggers/CheckpointTrigger.hx @@ -23,8 +23,8 @@ class CheckpointTrigger extends Trigger { }); } - public override function onMarbleEnter(time:src.TimeState) { - super.onMarbleEnter(time); + public override function onMarbleEnter(marble:src.Marble, time:src.TimeState) { + super.onMarbleEnter(marble, time); var shape = this.level.namedObjects.get(this.element.respawnpoint); if (shape == null) return; diff --git a/src/triggers/HelpTrigger.hx b/src/triggers/HelpTrigger.hx index a5282f86..ec2f6f30 100644 --- a/src/triggers/HelpTrigger.hx +++ b/src/triggers/HelpTrigger.hx @@ -5,9 +5,11 @@ import src.ResourceLoader; import src.AudioManager; class HelpTrigger extends Trigger { - override function onMarbleEnter(timeState:TimeState) { - AudioManager.playSound(ResourceLoader.getResource('data/sound/infotutorial.wav', ResourceLoader.getAudio, this.soundResources)); - this.level.displayHelp(this.element.text); + override function onMarbleEnter(marble:src.Marble, timeState:TimeState) { + if (marble == this.level.marble) { + AudioManager.playSound(ResourceLoader.getResource('data/sound/infotutorial.wav', ResourceLoader.getAudio, this.soundResources)); + this.level.displayHelp(this.element.text); + } // this.level.replay.recordMarbleEnter(this); } diff --git a/src/triggers/InBoundsTrigger.hx b/src/triggers/InBoundsTrigger.hx index a4160f09..5997bdf9 100644 --- a/src/triggers/InBoundsTrigger.hx +++ b/src/triggers/InBoundsTrigger.hx @@ -4,8 +4,8 @@ import src.TimeState; import src.ResourceLoader; class InBoundsTrigger extends Trigger { - override function onMarbleLeave(timeState:TimeState) { - this.level.goOutOfBounds(); + override function onMarbleLeave(marble:src.Marble, timeState:TimeState) { + this.level.goOutOfBounds(marble); // this.level.replay.recordMarbleLeave(this); } diff --git a/src/triggers/MustChangeTrigger.hx b/src/triggers/MustChangeTrigger.hx index a9035dc6..377b883d 100644 --- a/src/triggers/MustChangeTrigger.hx +++ b/src/triggers/MustChangeTrigger.hx @@ -13,7 +13,7 @@ class MustChangeTrigger extends Trigger { this.interior = interior; } - public override function onMarbleEnter(time:TimeState) { + public override function onMarbleEnter(marble:src.Marble, time:TimeState) { var ttime = MisParser.parseNumber(this.element.targettime); if (ttime > 0) ttime /= 1000; diff --git a/src/triggers/OutOfBoundsTrigger.hx b/src/triggers/OutOfBoundsTrigger.hx index 22c976c0..44321146 100644 --- a/src/triggers/OutOfBoundsTrigger.hx +++ b/src/triggers/OutOfBoundsTrigger.hx @@ -4,8 +4,8 @@ import src.TimeState; import src.ResourceLoader; class OutOfBoundsTrigger extends Trigger { - override function onMarbleInside(time:TimeState) { - this.level.goOutOfBounds(); + override function onMarbleInside(marble:src.Marble, time:TimeState) { + this.level.goOutOfBounds(marble); // this.level.replay.recordMarbleInside(this); } diff --git a/src/triggers/TeleportTrigger.hx b/src/triggers/TeleportTrigger.hx index 018fc588..419383a6 100644 --- a/src/triggers/TeleportTrigger.hx +++ b/src/triggers/TeleportTrigger.hx @@ -20,9 +20,9 @@ class TeleportTrigger extends Trigger { this.delay = MisParser.parseNumber(element.delay) / 1000; } - override function onMarbleEnter(time:src.TimeState) { + override function onMarbleEnter(marble:src.Marble, time:src.TimeState) { this.exitTime = null; - this.level.marble.setCloaking(true, time); + marble.setCloaking(true, time); if (this.entryTime != null) return; this.entryTime = time.currentAttemptTime; @@ -30,9 +30,9 @@ class TeleportTrigger extends Trigger { AudioManager.playSound(ResourceLoader.getResource("data/sound/teleport.wav", ResourceLoader.getAudio, this.soundResources)); } - override function onMarbleLeave(time:src.TimeState) { + override function onMarbleLeave(marble:src.Marble, time:src.TimeState) { this.exitTime = time.currentAttemptTime; - this.level.marble.setCloaking(false, time); + marble.setCloaking(false, time); } public override function update(timeState:src.TimeState) {