separate out marble logic and apply optimizations from mbu branch, prepare for netcode

This commit is contained in:
RandomityGuy 2024-06-14 14:53:58 +05:30
parent e09cd21ac8
commit 59f43d93eb
54 changed files with 1860 additions and 832 deletions

View file

@ -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) {

View file

@ -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() {}

View file

@ -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) {

View file

@ -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) {

View file

@ -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;
});

View file

@ -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();
}

View file

@ -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
View 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;
}
}
}

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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)));

View file

@ -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);
}
}
}

View file

@ -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) {

View file

@ -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
}
}
}

View file

@ -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;
// }
}

View file

@ -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];
}
}

View file

@ -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() {}
}

View 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 = [];
}
}

View file

@ -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() {

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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));

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -1,5 +1,6 @@
package octree;
@:generic
class PriorityQueueNode<T> {
public var value:T;
public var priority:Float;

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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

View file

@ -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) {}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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));
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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;

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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) {