mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2026-01-02 13:22:23 +00:00
separate out marble logic and apply optimizations from mbu branch, prepare for netcode
This commit is contained in:
parent
e09cd21ac8
commit
59f43d93eb
54 changed files with 1860 additions and 832 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<Resource<Texture>> = [];
|
||||
var soundResources:Array<Resource<Sound>> = [];
|
||||
|
||||
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() {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T> {
|
||||
var l:ReusableList<T>;
|
||||
var i = 0;
|
||||
|
||||
public function new(l:ReusableList<T>) {
|
||||
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<T> {
|
||||
var array:Array<T>;
|
||||
|
||||
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<T> {
|
||||
return new ReusableListIterator<T>(this);
|
||||
}
|
||||
}
|
||||
|
||||
class InstanceManager {
|
||||
var objects:Array<Array<MeshBatchInfo>> = [];
|
||||
|
||||
var objectMap:Map<String, Int> = [];
|
||||
var scene:Scene;
|
||||
var opaqueinstances = new ReusableList<MeshInstance>();
|
||||
var transparentinstances = new ReusableList<MeshInstance>();
|
||||
|
||||
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) {
|
||||
|
|
|
|||
401
src/Marble.hx
401
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<Vector>;
|
||||
var n:Vector;
|
||||
}
|
||||
|
||||
@:publicFields
|
||||
@:structInit
|
||||
class MarbleTestMoveResult {
|
||||
var position:Vector;
|
||||
var t:Float;
|
||||
var found:Bool;
|
||||
var foundContacts:Array<MarbleTestMoveFoundContact>;
|
||||
var lastContactPos:Null<Vector>;
|
||||
var lastContactNormal:Null<Vector>;
|
||||
var foundMarbles:Array<SphereCollisionEntity>;
|
||||
}
|
||||
|
||||
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<CollisionInfo> = [];
|
||||
public var bestContact:CollisionInfo;
|
||||
public var contactEntities:Array<CollisionEntity> = [];
|
||||
|
|
@ -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<Float> = null;
|
||||
var teleportDisableTime:Null<Float> = 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<MarbleTestMoveFoundContact> = [];
|
||||
|
||||
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<Vector>,
|
||||
n:Vector
|
||||
}>) {
|
||||
function nudgeToContacts(position:Vector, radius:Float, foundContacts:Array<MarbleTestMoveFoundContact>, foundMarbles:Array<SphereCollisionEntity>) {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class MarbleWorld extends Scheduler {
|
|||
public var pathedInteriors:Array<PathedInterior> = [];
|
||||
public var marbles:Array<Marble> = [];
|
||||
public var dtsObjects:Array<DtsObject> = [];
|
||||
public var powerUps:Array<PowerUp> = [];
|
||||
public var forceObjects:Array<ForceObject> = [];
|
||||
public var triggers:Array<Trigger> = [];
|
||||
public var gems:Array<Gem> = [];
|
||||
|
|
@ -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<Resource<h3d.mat.Texture>> = [];
|
||||
var soundResources:Array<Resource<Sound>> = [];
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, Particles> = [];
|
||||
var particleGroups:Map<String, src.ParticlesMesh.ParticlesMesh> = [];
|
||||
var particles:Array<Particle> = [];
|
||||
|
||||
var emitters:Array<ParticleEmitter> = [];
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
396
src/ParticlesMesh.hx
Normal file
396
src/ParticlesMesh.hx
Normal file
|
|
@ -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<Float>;
|
||||
|
||||
// -------------------------
|
||||
|
||||
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<h2d.Tile>;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ class Polygon extends MeshPrimitive {
|
|||
public var uvs:Array<Float>;
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
src/Util.hx
24
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)));
|
||||
|
|
|
|||
|
|
@ -5,36 +5,159 @@ import h3d.col.Bounds;
|
|||
|
||||
interface IBVHObject {
|
||||
var boundingBox:Bounds;
|
||||
function rayCast(rayOrigin:Vector, rayDirection:Vector):Array<octree.IOctreeObject.RayIntersectionData>;
|
||||
var key:Int;
|
||||
function rayCast(rayOrigin:Vector, rayDirection:Vector, results:Array<octree.IOctreeObject.RayIntersectionData>):Void;
|
||||
}
|
||||
|
||||
@:generic
|
||||
@:publicFields
|
||||
class BVHNode<T:IBVHObject> {
|
||||
var id:Int;
|
||||
var parent:BVHNode<T>;
|
||||
var child1:BVHNode<T>;
|
||||
var child2:BVHNode<T>;
|
||||
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<T>) {
|
||||
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<T>) {
|
||||
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<T>) {
|
||||
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<T:IBVHObject> {
|
||||
var nodeId:Int = 0;
|
||||
var root:BVHNode<T>;
|
||||
var nodes:Array<BVHNode<T>> = [];
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function allocateNode():BVHNode<T> {
|
||||
var node = new BVHNode<T>();
|
||||
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<T:IBVHObject> {
|
|||
// 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<T:IBVHObject> {
|
|||
|
||||
// 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<T:IBVHObject> {
|
|||
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<T>) -> 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<T>) {
|
||||
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<T:IBVHObject> {
|
|||
}
|
||||
|
||||
function rotate(node:BVHNode<T>) {
|
||||
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<T:IBVHObject> {
|
|||
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<T:IBVHObject> {
|
|||
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<T:IBVHObject> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<octree.IOctreeObject.RayIntersectionData>) {
|
||||
// TEMP cause bruh
|
||||
return [];
|
||||
}
|
||||
|
||||
public override function sphereIntersection(collisionEntity:SphereCollisionEntity, timeState:TimeState) {
|
||||
|
|
|
|||
|
|
@ -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<Bool>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<CollisionSurface>;
|
||||
|
||||
public var surfaces:Array<CollisionSurface>;
|
||||
var grid:Grid;
|
||||
|
||||
public var surfaces:Array<CollisionSurface>;
|
||||
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<RayIntersectionData> {
|
||||
public function rayCast(rayOrigin:Vector, rayDirection:Vector, results:Array<RayIntersectionData>) {
|
||||
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<RayIntersectionData> = [];
|
||||
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<RayIntersectionData> = [];
|
||||
// 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;
|
||||
// }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
}
|
||||
|
|
|
|||
21
src/collision/CollisionPool.hx
Normal file
21
src/collision/CollisionPool.hx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package collision;
|
||||
|
||||
class CollisionPool {
|
||||
static var pool:Array<CollisionInfo> = [];
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Int>;
|
||||
public var originalSurfaceIndex:Int;
|
||||
public var transformKeys:Array<Int>;
|
||||
public var key:Int = 0;
|
||||
|
||||
var _transformedPoints:Array<Float>;
|
||||
var _transformedNormals:Array<Float>;
|
||||
|
|
@ -107,8 +139,7 @@ class CollisionSurface implements IOctreeObject implements IBVHObject {
|
|||
normals.push(z);
|
||||
}
|
||||
|
||||
public function rayCast(rayOrigin:Vector, rayDirection:Vector):Array<RayIntersectionData> {
|
||||
var intersections = [];
|
||||
public function rayCast(rayOrigin:Vector, rayDirection:Vector, intersections:Array<RayIntersectionData>) {
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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<CollisionEntity>;
|
||||
var contacts:Array<CollisionInfo>;
|
||||
}
|
||||
|
||||
class CollisionWorld {
|
||||
public var staticWorld:CollisionEntity;
|
||||
public var octree:Octree;
|
||||
public var entities:Array<CollisionEntity> = [];
|
||||
public var dynamicEntities:Array<CollisionEntity> = [];
|
||||
public var dynamicOctree:Octree;
|
||||
|
||||
public var marbleEntities:Array<SphereCollisionEntity> = [];
|
||||
|
||||
var dynamicEntitySet:Map<CollisionEntity, Bool> = [];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Int, Array<Int>> = 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<Array<Int>> = [];
|
||||
|
||||
var surfaces:Array<CollisionSurface> = [];
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<octree.IOctreeObject.RayIntersectionData>) {
|
||||
// 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));
|
||||
|
|
|
|||
|
|
@ -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<RayIntersectionData>;
|
||||
function rayCast(rayOrigin:Vector, rayDirection:Vector, resultSet:Array<RayIntersectionData>):Void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OctreeNode> = 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<OctreeIntersection>) {
|
||||
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<IOctreeElement>) {
|
||||
if (this.bounds.collide(bounds)) {
|
||||
public function boundingSearch(b:Bounds, intersections:Array<IOctreeElement>) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package octree;
|
||||
|
||||
@:generic
|
||||
class PriorityQueue<T> {
|
||||
var queue:Array<PriorityQueueNode<T>>;
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ class PriorityQueue<T> {
|
|||
|
||||
public function enqueue(val:T, priority:Float) {
|
||||
var node = new PriorityQueueNode<T>(val, priority);
|
||||
if (this.queue == null) {
|
||||
if (this.queue == null || this.queue.length == 0) {
|
||||
this.queue = [node];
|
||||
} else {
|
||||
if (this.queue[0].priority >= priority) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package octree;
|
||||
|
||||
@:generic
|
||||
class PriorityQueueNode<T> {
|
||||
public var value:T;
|
||||
public var priority:Float;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 <func:bind mousefire> 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 <func:bind mousefire> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue