fix minor bugs and improve moving platform broadphase

This commit is contained in:
RandomityGuy 2022-12-24 18:04:53 +05:30
parent 76a26db37d
commit 7228dcfa8d
9 changed files with 245 additions and 189 deletions

View file

@ -1307,7 +1307,7 @@ class Marble extends GameObject {
var tempTimeState = timeState.clone(); var tempTimeState = timeState.clone();
tempState.currentAttemptTime = piTime; tempState.currentAttemptTime = piTime;
tempState.dt = timeStep; tempState.dt = timeStep;
this.level.callCollisionHandlers(cast this, tempTimeState); this.level.callCollisionHandlers(cast this, tempTimeState, pos, newPos, rot, quat);
} }
if (contacts.length != 0) if (contacts.length != 0)

View file

@ -1063,6 +1063,8 @@ class MarbleWorld extends Scheduler {
val = Util.getKeyForButton(Settings.controlsSettings.powerup); val = Util.getKeyForButton(Settings.controlsSettings.powerup);
if (funcdata[1] == "freelook") if (funcdata[1] == "freelook")
val = Util.getKeyForButton(Settings.controlsSettings.freelook); val = Util.getKeyForButton(Settings.controlsSettings.freelook);
if (funcdata[1] == "useblast")
val = Util.getKeyForButton(Settings.controlsSettings.blast);
} }
start = val.length + pos; start = val.length + pos;
text = pre + val + post; text = pre + val + post;
@ -1106,13 +1108,18 @@ class MarbleWorld extends Scheduler {
this.playGui.formatGemCounter(this.gemCount, this.totalGems); this.playGui.formatGemCounter(this.gemCount, this.totalGems);
} }
public function callCollisionHandlers(marble:Marble, timeState:TimeState) { public function callCollisionHandlers(marble:Marble, timeState:TimeState, start:Vector, end:Vector, startQuat:Quat, endQuat:Quat) {
var gjkSphere = new collision.gjk.Sphere(); var expansion = marble._radius + 0.2;
gjkSphere.position = marble.getAbsPos().getPosition(); var minP = new Vector(Math.min(start.x, end.x) - expansion, Math.min(start.y, end.y) - expansion, Math.min(start.z, end.z) - expansion);
gjkSphere.radius = marble._radius; var maxP = new Vector(Math.max(start.x, end.x) + expansion, Math.max(start.y, end.y) + expansion, Math.max(start.z, end.z) + expansion);
var box = Bounds.fromPoints(minP.toPoint(), maxP.toPoint());
var marbleHitbox = new Bounds();
marbleHitbox.addSpherePos(0, 0, 0, marble._radius);
marbleHitbox.transform(startQuat.toMatrix());
marbleHitbox.transform(endQuat.toMatrix());
marbleHitbox.offset(end.x, end.y, end.z);
var spherebounds = new Bounds();
spherebounds.addSpherePos(gjkSphere.position.x, gjkSphere.position.y, gjkSphere.position.z, gjkSphere.radius);
// spherebounds.addSpherePos(gjkCapsule.p2.x, gjkCapsule.p2.y, gjkCapsule.p2.z, gjkCapsule.radius); // spherebounds.addSpherePos(gjkCapsule.p2.x, gjkCapsule.p2.y, gjkCapsule.p2.z, gjkCapsule.radius);
// var contacts = this.collisionWorld.radiusSearch(marble.getAbsPos().getPosition(), marble._radius); // var contacts = this.collisionWorld.radiusSearch(marble.getAbsPos().getPosition(), marble._radius);
var contacts = marble.contactEntities; var contacts = marble.contactEntities;
@ -1123,18 +1130,20 @@ class MarbleWorld extends Scheduler {
if (contact.go is DtsObject) { if (contact.go is DtsObject) {
var shape:DtsObject = cast contact.go; var shape:DtsObject = cast contact.go;
shape.onMarbleInside(timeState); if (contact.boundingBox.collide(marbleHitbox)) {
if (!this.shapeOrTriggerInside.contains(contact.go)) { shape.onMarbleInside(timeState);
this.shapeOrTriggerInside.push(contact.go); if (!this.shapeOrTriggerInside.contains(contact.go)) {
shape.onMarbleEnter(timeState); this.shapeOrTriggerInside.push(contact.go);
shape.onMarbleEnter(timeState);
}
inside.push(contact.go);
} }
inside.push(contact.go);
} }
if (contact.go is Trigger) { if (contact.go is Trigger) {
var trigger:Trigger = cast contact.go; var trigger:Trigger = cast contact.go;
var triggeraabb = trigger.collider.boundingBox; var triggeraabb = trigger.collider.boundingBox;
if (triggeraabb.collide(spherebounds)) { if (triggeraabb.collide(marbleHitbox)) {
trigger.onMarbleInside(timeState); trigger.onMarbleInside(timeState);
if (!this.shapeOrTriggerInside.contains(contact.go)) { if (!this.shapeOrTriggerInside.contains(contact.go)) {
this.shapeOrTriggerInside.push(contact.go); this.shapeOrTriggerInside.push(contact.go);
@ -1154,11 +1163,11 @@ class MarbleWorld extends Scheduler {
} }
if (this.finishTime == null) { if (this.finishTime == null) {
if (spherebounds.collide(this.endPad.finishBounds)) { if (marbleHitbox.collide(this.endPad.finishBounds)) {
var padUp = this.endPad.getAbsPos().up(); var padUp = this.endPad.getAbsPos().up();
padUp = padUp.multiply(10); padUp = padUp.multiply(10);
var checkBounds = spherebounds.clone(); var checkBounds = box.clone();
checkBounds.zMin -= 10; checkBounds.zMin -= 10;
checkBounds.zMax += 10; checkBounds.zMax += 10;
var checkBoundsCenter = checkBounds.getCenter(); var checkBoundsCenter = checkBounds.getCenter();

View file

@ -1,183 +1,233 @@
package collision; package collision;
import h3d.col.Bounds; import h3d.col.Bounds;
import h3d.Vector;
// https://github.com/Sopiro/DynamicBVH/blob/master/src/aabbtree.ts interface IBVHObject {
var boundingBox:Bounds;
function rayCast(rayOrigin:Vector, rayDirection:Vector):Array<octree.IOctreeObject.RayIntersectionData>;
}
@:publicFields @:publicFields
class BVHNode { class BVHNode<T:IBVHObject> {
var id:Int;
var parent:BVHNode<T>;
var child1:BVHNode<T>;
var child2:BVHNode<T>;
var isLeaf:Bool;
var bounds:Bounds; var bounds:Bounds;
var objects:Array<CollisionSurface>; var object:T;
var objectBounds:Bounds; // total bounds for objects stored in THIS node
var left:BVHNode;
var right:BVHNode;
var surfaceArea:Float;
public function new(bounds:Bounds) { public function new(bounds:Bounds) {
this.bounds = bounds.clone(); this.bounds = bounds.clone();
surfaceArea = this.bounds.xSize * this.bounds.ySize + this.bounds.xSize * this.bounds.zSize + this.bounds.ySize * this.bounds.zSize; surfaceArea = this.bounds.xSize * this.bounds.ySize + this.bounds.xSize * this.bounds.zSize + this.bounds.ySize * this.bounds.zSize;
} }
function getSplitCost(objs:Array<{obj:CollisionSurface, centroid:h3d.col.Point}>, axis:Int) { class BVHTree<T:IBVHObject> {
// Pick best axis to split var nodeId:Int = 0;
switch (axis) { var root:BVHNode<T>;
case 0:
objs.sort((x, y) -> x.centroid.x > y.centroid.x ? 1 : -1);
case 1:
objs.sort((x, y) -> x.centroid.y > y.centroid.y ? 1 : -1);
case 2:
objs.sort((x, y) -> x.centroid.z > y.centroid.z ? 1 : -1);
};
var leftObjects = objs.slice(0, Math.ceil(objs.length / 2)); public function new() {}
var rightObjects = objs.slice(Math.ceil(objs.length / 2));
var leftAABB = new Bounds();
var rightAABB = new Bounds();
for (o in leftObjects)
leftAABB.add(o.obj.boundingBox);
for (o in rightObjects)
rightAABB.add(o.obj.boundingBox);
var leftSA = leftAABB.xSize * leftAABB.ySize + leftAABB.xSize * leftAABB.zSize + leftAABB.ySize * leftAABB.zSize;
var rightSA = rightAABB.xSize * rightAABB.ySize + rightAABB.xSize * rightAABB.zSize + rightAABB.ySize * rightAABB.zSize;
var splitCost = leftSA + rightSA;
var bestSplit = {
cost: splitCost,
left: leftObjects,
right: rightObjects,
leftBounds: leftAABB,
rightBounds: rightAABB,
axis: axis
};
return bestSplit;
}
public function split() { function update() {
// Splitting first time var invalidNodes = [];
// Calculate the centroids of all objects this.traverse(node -> {
var objs = objects.map(x -> { if (node.isLeaf) {
x.generateBoundingBox(); var entity = node.object;
return {obj: x, centroid: x.boundingBox.getCenter()}; var tightAABB = entity.boundingBox;
});
// Find the best split cost if (node.bounds.containsBounds(tightAABB)) {
var costs = [getSplitCost(objs, 0), getSplitCost(objs, 1), getSplitCost(objs, 2)]; return;
costs.sort((x, y) -> x.cost > y.cost ? 1 : -1); }
var bestSplit = costs[0];
// Sort the objects according to where they should go public function split() {
var leftObjs = []; // Splitting first time
var rightObjs = []; // Calculate the centroids of all objects
var intersectObjs = []; var objs = objects.map(x -> {
for (o in bestSplit.left.concat(bestSplit.right)) { x.generateBoundingBox();
var inleft = bestSplit.leftBounds.containsBounds(o.obj.boundingBox); return {obj: x, centroid: x.boundingBox.getCenter()};
var inright = bestSplit.rightBounds.containsBounds(o.obj.boundingBox); });
if (inleft && inright) { for (node in invalidNodes) {
intersectObjs.push(o.obj); this.remove(node);
} else if (inleft) { this.add(node.object);
leftObjs.push(o.obj); }
} else if (inright) {
rightObjs.push(o.obj);
}
}
// Only one side has objects, egh public function add(entity:T) {
if (leftObjs.length == 0 || rightObjs.length == 0) { // Enlarged AABB
var thisobjs = leftObjs.concat(rightObjs).concat(intersectObjs); var aabb = entity.boundingBox;
this.objects = thisobjs;
this.objectBounds = new Bounds();
for (o in thisobjs)
this.objectBounds.add(o.boundingBox);
return;
}
// Make the child nodes var newNode = new BVHNode();
var leftBounds = new Bounds(); newNode.id = this.nodeId++;
var rightBounds = new Bounds(); newNode.bounds = aabb;
for (o in leftObjs) newNode.object = entity;
leftBounds.add(o.boundingBox); newNode.isLeaf = true;
for (o in rightObjs)
rightBounds.add(o.boundingBox);
left = new BVHNode(leftBounds);
right = new BVHNode(rightBounds);
left.objects = leftObjs;
right.objects = rightObjs;
this.objects = intersectObjs;
this.objectBounds = new Bounds();
for (o in intersectObjs)
this.objectBounds.add(o.boundingBox);
left.split(); if (this.root == null) {
right.split(); this.root = newNode;
} return newNode;
}
public function boundingSearch(searchbox:Bounds) { // Make the child nodes
if (this.bounds.containsBounds(searchbox) || this.bounds.collide(searchbox)) { var leftBounds = new Bounds();
var intersects = []; var rightBounds = new Bounds();
if (this.left != null && this.right != null) { for (o in leftObjs)
intersects = intersects.concat(this.left.boundingSearch(searchbox)); leftBounds.add(o.boundingBox);
intersects = intersects.concat(this.right.boundingSearch(searchbox)); for (o in rightObjs)
} rightBounds.add(o.boundingBox);
if (this.objectBounds.collide(searchbox) || this.objectBounds.containsBounds(searchbox)) { left = new BVHNode(leftBounds);
for (o in this.objects) { right = new BVHNode(rightBounds);
if (o.boundingBox.containsBounds(searchbox) || o.boundingBox.collide(searchbox)) left.objects = leftObjs;
intersects.push(o); right.objects = rightObjs;
} this.objects = intersectObjs;
} this.objectBounds = new Bounds();
return intersects; for (o in intersectObjs)
} else { this.objectBounds.add(o.boundingBox);
return [];
}
}
public function rayCast(origin:Vector, direction:Vector) { left.split();
var ray = h3d.col.Ray.fromValues(origin.x, origin.y, origin.z, direction.x, direction.y, direction.z); right.split();
if (ray.collide(this.bounds)) { }
var intersects = [];
if (this.left != null && this.right != null) {
intersects = intersects.concat(this.left.rayCast(origin, direction));
intersects = intersects.concat(this.right.rayCast(origin, direction));
}
if (ray.collide(this.objectBounds)) {
for (o in this.objects) {
if (ray.collide(o.boundingBox))
intersects = intersects.concat(o.rayCast(origin, direction));
}
}
return intersects;
} else {
return [];
}
}
}
class BVHTree { public function boundingSearch(searchbox:Bounds) {
public var bounds:Bounds; if (this.bounds.containsBounds(searchbox) || this.bounds.collide(searchbox)) {
var intersects = [];
if (this.left != null && this.right != null) {
intersects = intersects.concat(this.left.boundingSearch(searchbox));
intersects = intersects.concat(this.right.boundingSearch(searchbox));
}
if (this.objectBounds.collide(searchbox) || this.objectBounds.containsBounds(searchbox)) {
for (o in this.objects) {
if (o.boundingBox.containsBounds(searchbox) || o.boundingBox.collide(searchbox))
intersects.push(o);
}
}
return intersects;
} else {
return [];
}
}
var surfaces:Array<CollisionSurface> = []; function reset() {
this.nodeId = 0;
this.root = null;
}
var root:BVHNode; // BFS tree traversal
function traverse(callback:(node:BVHNode<T>) -> Void) {
var q = [this.root];
public function new(bounds:Bounds) { while (q.length != 0) {
this.bounds = bounds.clone(); var current = q.shift();
} if (current == null) {
break;
}
public function insert(surf:CollisionSurface) { callback(current);
surfaces.push(surf);
}
public function build() { if (!current.isLeaf) {
root = new BVHNode(bounds); if (current.child1 != null)
// Add all children q.push(current.child1);
root.objects = this.surfaces; if (current.child2 != null)
root.split(); q.push(current.child2);
} }
}
}
public function boundingSearch(searchbox:Bounds) { public function remove(node:BVHNode<T>) {
return this.root.boundingSearch(searchbox); var parent = node.parent;
}
public function rayCast(origin:Vector, direction:Vector) { if (parent != null) {
return this.root.rayCast(origin, direction); var sibling = parent.child1 == node ? parent.child2 : parent.child1;
}
} if (parent.parent != null) {
sibling.parent = parent.parent;
if (parent.parent.child1 == parent) {
parent.parent.child1 = sibling;
} else {
parent.parent.child2 = sibling;
}
}
return intersects;
} else {
return [];
}
}
function rotate(node:BVHNode<T>) {
if (node.parent == null) {
return;
}
var parent = node.parent;
var sibling = parent.child1 == node ? 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;
class BVHTree {
public var bounds:Bounds;
var surfaces:Array<CollisionSurface> = [];
var root:BVHNode;
public function new(bounds:Bounds) {
this.bounds = bounds.clone();
}
public function insert(surf:CollisionSurface) {
surfaces.push(surf);
}
public function build() {
root = new BVHNode(bounds);
// Add all children
root.objects = this.surfaces;
root.split();
}
public function boundingSearch(searchbox:Bounds) {
var res = [];
if (this.root == null)
return res;
var q = [this.root];
while (q.length != 0) {
var current = q.shift();
if (current.bounds.containsBounds(searchbox) || current.bounds.collide(searchbox)) {
if (current.isLeaf) {
res.push(current.object);
} else {
if (current.child1 != null)
q.push(current.child1);
if (current.child2 != null)
q.push(current.child2);
}
}
}
return res;
}
public function rayCast(origin:Vector, direction:Vector) {
var res = [];
if (this.root == null)
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));
} else {
if (current.child1 != null)
q.push(current.child1);
if (current.child2 != null)
q.push(current.child2);
}
}
}
return res;
}
}

View file

@ -1,5 +1,6 @@
package collision; package collision;
import collision.BVHTree.IBVHObject;
import src.MarbleGame; import src.MarbleGame;
import src.TimeState; import src.TimeState;
import h3d.Matrix; import h3d.Matrix;
@ -10,7 +11,7 @@ import h3d.Vector;
import h3d.col.Sphere; import h3d.col.Sphere;
import h3d.col.Bounds; import h3d.col.Bounds;
class BoxCollisionEntity extends CollisionEntity { class BoxCollisionEntity extends CollisionEntity implements IBVHObject {
var bounds:Bounds; var bounds:Bounds;
var _dbgEntity:h3d.scene.Object; var _dbgEntity:h3d.scene.Object;

View file

@ -1,5 +1,6 @@
package collision; package collision;
import collision.BVHTree.IBVHObject;
import src.TimeState; import src.TimeState;
import src.GameObject; import src.GameObject;
import dif.math.Point3F; import dif.math.Point3F;
@ -14,12 +15,12 @@ import h3d.col.Bounds;
import src.PathedInterior; import src.PathedInterior;
import src.Util; import src.Util;
class CollisionEntity implements IOctreeObject { class CollisionEntity implements IOctreeObject implements IBVHObject {
public var boundingBox:Bounds; public var boundingBox:Bounds;
public var octree:Octree; public var octree:Octree;
public var bvh:BVHTree; public var bvh:BVHTree<CollisionSurface>;
public var surfaces:Array<CollisionSurface>; public var surfaces:Array<CollisionSurface>;

View file

@ -4,28 +4,22 @@ import h3d.Matrix;
import h3d.col.Bounds; import h3d.col.Bounds;
import octree.IOctreeObject; import octree.IOctreeObject;
import h3d.Vector; import h3d.Vector;
import collision.BVHTree.IBVHObject;
class CollisionSurface implements IOctreeObject { class CollisionSurface implements IOctreeObject implements IBVHObject {
public var priority:Int; public var priority:Int;
public var position:Int; public var position:Int;
public var boundingBox:Bounds; public var boundingBox:Bounds;
public var points:Array<Vector>; public var points:Array<Vector>;
public var normals:Array<Vector>; public var normals:Array<Vector>;
public var indices:Array<Int>; public var indices:Array<Int>;
public var friction:Float = 1; public var friction:Float = 1;
public var restitution:Float = 1; public var restitution:Float = 1;
public var force:Float = 0; public var force:Float = 0;
public var edgeData:Array<Int>; public var edgeData:Array<Int>;
public var edgeConcavities:Array<Bool>; public var edgeConcavities:Array<Bool>;
public var originalIndices:Array<Int>; public var originalIndices:Array<Int>;
public var originalSurfaceIndex:Int; public var originalSurfaceIndex:Int;
public var key:Bool = false; public var key:Bool = false;
public function new() {} public function new() {}

View file

@ -11,9 +11,11 @@ class CollisionWorld {
public var octree:Octree; public var octree:Octree;
public var entities:Array<CollisionEntity> = []; public var entities:Array<CollisionEntity> = [];
public var dynamicEntities:Array<CollisionEntity> = []; public var dynamicEntities:Array<CollisionEntity> = [];
public var dynamicBVH:BVHTree<CollisionEntity>;
public function new() { public function new() {
this.octree = new Octree(); this.octree = new Octree();
this.dynamicBVH = new BVHTree();
} }
public function sphereIntersection(spherecollision:SphereCollisionEntity, timeState:TimeState) { public function sphereIntersection(spherecollision:SphereCollisionEntity, timeState:TimeState) {
@ -44,7 +46,8 @@ class CollisionWorld {
} }
} }
for (obj in dynamicEntities) { var dynSearch = dynamicBVH.boundingSearch(box);
for (obj in dynSearch) {
if (obj != spherecollision) { if (obj != spherecollision) {
if (obj.boundingBox.collide(box) && obj.go.isCollideable) if (obj.boundingBox.collide(box) && obj.go.isCollideable)
contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState)); contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState));
@ -72,20 +75,14 @@ class CollisionWorld {
contacts.push(entity); contacts.push(entity);
} }
for (obj in dynamicEntities) { contacts = contacts.concat(dynamicBVH.boundingSearch(box));
if (obj.boundingBox.collide(box))
contacts.push(obj);
}
return contacts; return contacts;
} }
public function boundingSearch(bounds:Bounds, useCache:Bool = true) { public function boundingSearch(bounds:Bounds, useCache:Bool = true) {
var contacts = this.octree.boundingSearch(bounds, useCache).map(x -> cast(x, CollisionEntity)); var contacts = this.octree.boundingSearch(bounds, useCache).map(x -> cast(x, CollisionEntity));
for (obj in dynamicEntities) { contacts = contacts.concat(dynamicBVH.boundingSearch(bounds));
if (obj.boundingBox.collide(bounds))
contacts.push(obj);
}
return contacts; return contacts;
} }
@ -117,6 +114,7 @@ class CollisionWorld {
public function addMovingEntity(entity:CollisionEntity) { public function addMovingEntity(entity:CollisionEntity) {
this.dynamicEntities.push(entity); this.dynamicEntities.push(entity);
this.dynamicBVH.add(entity);
} }
public function updateTransform(entity:CollisionEntity) { public function updateTransform(entity:CollisionEntity) {

View file

@ -411,6 +411,9 @@ class PlayGui {
} else if (powerupIdentifier == "Helicopter") { } else if (powerupIdentifier == "Helicopter") {
powerupImageObject = new DtsObject(); powerupImageObject = new DtsObject();
powerupImageObject.dtsPath = "data/shapes/images/helicopter.dts"; powerupImageObject.dtsPath = "data/shapes/images/helicopter.dts";
} else if (powerupIdentifier == "MegaMarble") {
powerupImageObject = new DtsObject();
powerupImageObject.dtsPath = "data/shapes/items/megamarble.dts";
} else { } else {
powerupIdentifier = ""; powerupIdentifier = "";
this.powerupImageObject = null; this.powerupImageObject = null;

View file

@ -139,7 +139,7 @@ class Octree {
return intersections; return intersections;
} }
public function boundingSearch(bounds:Bounds, useCache:Bool = true) { public function boundingSearch(bounds:Bounds, useCache:Bool = false) {
var intersections = []; var intersections = [];
if (useCache) { if (useCache) {
if (this.prevBoundSearch != null) { if (this.prevBoundSearch != null) {