diff --git a/src/Marble.hx b/src/Marble.hx index 01e69398..99c14a7f 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1307,7 +1307,7 @@ class Marble extends GameObject { var tempTimeState = timeState.clone(); tempState.currentAttemptTime = piTime; tempState.dt = timeStep; - this.level.callCollisionHandlers(cast this, tempTimeState); + this.level.callCollisionHandlers(cast this, tempTimeState, pos, newPos, rot, quat); } if (contacts.length != 0) diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index e385f436..48b7ef1c 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1063,6 +1063,8 @@ class MarbleWorld extends Scheduler { val = Util.getKeyForButton(Settings.controlsSettings.powerup); if (funcdata[1] == "freelook") val = Util.getKeyForButton(Settings.controlsSettings.freelook); + if (funcdata[1] == "useblast") + val = Util.getKeyForButton(Settings.controlsSettings.blast); } start = val.length + pos; text = pre + val + post; @@ -1106,13 +1108,18 @@ class MarbleWorld extends Scheduler { this.playGui.formatGemCounter(this.gemCount, this.totalGems); } - public function callCollisionHandlers(marble:Marble, timeState:TimeState) { - var gjkSphere = new collision.gjk.Sphere(); - gjkSphere.position = marble.getAbsPos().getPosition(); - gjkSphere.radius = marble._radius; + public function callCollisionHandlers(marble:Marble, timeState:TimeState, start:Vector, end:Vector, startQuat:Quat, endQuat:Quat) { + var expansion = marble._radius + 0.2; + 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); + 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); // var contacts = this.collisionWorld.radiusSearch(marble.getAbsPos().getPosition(), marble._radius); var contacts = marble.contactEntities; @@ -1123,18 +1130,20 @@ class MarbleWorld extends Scheduler { if (contact.go is DtsObject) { var shape:DtsObject = cast contact.go; - shape.onMarbleInside(timeState); - if (!this.shapeOrTriggerInside.contains(contact.go)) { - this.shapeOrTriggerInside.push(contact.go); - shape.onMarbleEnter(timeState); + if (contact.boundingBox.collide(marbleHitbox)) { + shape.onMarbleInside(timeState); + if (!this.shapeOrTriggerInside.contains(contact.go)) { + this.shapeOrTriggerInside.push(contact.go); + shape.onMarbleEnter(timeState); + } + inside.push(contact.go); } - inside.push(contact.go); } if (contact.go is Trigger) { var trigger:Trigger = cast contact.go; var triggeraabb = trigger.collider.boundingBox; - if (triggeraabb.collide(spherebounds)) { + if (triggeraabb.collide(marbleHitbox)) { trigger.onMarbleInside(timeState); if (!this.shapeOrTriggerInside.contains(contact.go)) { this.shapeOrTriggerInside.push(contact.go); @@ -1154,11 +1163,11 @@ class MarbleWorld extends Scheduler { } if (this.finishTime == null) { - if (spherebounds.collide(this.endPad.finishBounds)) { + if (marbleHitbox.collide(this.endPad.finishBounds)) { var padUp = this.endPad.getAbsPos().up(); padUp = padUp.multiply(10); - var checkBounds = spherebounds.clone(); + var checkBounds = box.clone(); checkBounds.zMin -= 10; checkBounds.zMax += 10; var checkBoundsCenter = checkBounds.getCenter(); diff --git a/src/collision/BVHTree.hx b/src/collision/BVHTree.hx index 25652712..e83c0a8b 100644 --- a/src/collision/BVHTree.hx +++ b/src/collision/BVHTree.hx @@ -1,183 +1,233 @@ package collision; 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; +} @:publicFields -class BVHNode { +class BVHNode { + var id:Int; + var parent:BVHNode; + var child1:BVHNode; + var child2:BVHNode; + var isLeaf:Bool; var bounds:Bounds; - var objects:Array; - var objectBounds:Bounds; // total bounds for objects stored in THIS node - var left:BVHNode; - var right:BVHNode; - var surfaceArea:Float; + var object:T; public function new(bounds:Bounds) { this.bounds = bounds.clone(); 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) { - // Pick best axis to split - switch (axis) { - 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); - }; + class BVHTree { + var nodeId:Int = 0; + var root:BVHNode; - var leftObjects = objs.slice(0, Math.ceil(objs.length / 2)); - 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 new() {} - public function split() { - // Splitting first time - // Calculate the centroids of all objects - var objs = objects.map(x -> { - x.generateBoundingBox(); - return {obj: x, centroid: x.boundingBox.getCenter()}; - }); + function update() { + var invalidNodes = []; + this.traverse(node -> { + if (node.isLeaf) { + var entity = node.object; + var tightAABB = entity.boundingBox; - // Find the best split cost - var costs = [getSplitCost(objs, 0), getSplitCost(objs, 1), getSplitCost(objs, 2)]; - costs.sort((x, y) -> x.cost > y.cost ? 1 : -1); - var bestSplit = costs[0]; + if (node.bounds.containsBounds(tightAABB)) { + return; + } - // Sort the objects according to where they should go - var leftObjs = []; - var rightObjs = []; - var intersectObjs = []; - for (o in bestSplit.left.concat(bestSplit.right)) { - var inleft = bestSplit.leftBounds.containsBounds(o.obj.boundingBox); - var inright = bestSplit.rightBounds.containsBounds(o.obj.boundingBox); - if (inleft && inright) { - intersectObjs.push(o.obj); - } else if (inleft) { - leftObjs.push(o.obj); - } else if (inright) { - rightObjs.push(o.obj); - } - } + public function split() { + // Splitting first time + // Calculate the centroids of all objects + var objs = objects.map(x -> { + x.generateBoundingBox(); + return {obj: x, centroid: x.boundingBox.getCenter()}; + }); + for (node in invalidNodes) { + this.remove(node); + this.add(node.object); + } - // Only one side has objects, egh - if (leftObjs.length == 0 || rightObjs.length == 0) { - var thisobjs = leftObjs.concat(rightObjs).concat(intersectObjs); - this.objects = thisobjs; - this.objectBounds = new Bounds(); - for (o in thisobjs) - this.objectBounds.add(o.boundingBox); - return; - } + public function add(entity:T) { + // Enlarged AABB + var aabb = entity.boundingBox; - // Make the child nodes - var leftBounds = new Bounds(); - var rightBounds = new Bounds(); - for (o in leftObjs) - leftBounds.add(o.boundingBox); - 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); + var newNode = new BVHNode(); + newNode.id = this.nodeId++; + newNode.bounds = aabb; + newNode.object = entity; + newNode.isLeaf = true; - left.split(); - right.split(); - } + if (this.root == null) { + this.root = newNode; + return newNode; + } - public function boundingSearch(searchbox: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 []; - } - } + // Make the child nodes + var leftBounds = new Bounds(); + var rightBounds = new Bounds(); + for (o in leftObjs) + leftBounds.add(o.boundingBox); + 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); - public function rayCast(origin:Vector, direction:Vector) { - var ray = h3d.col.Ray.fromValues(origin.x, origin.y, origin.z, direction.x, direction.y, direction.z); - 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 []; - } - } -} + left.split(); + right.split(); + } -class BVHTree { - public var bounds:Bounds; + public function boundingSearch(searchbox: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 = []; + function reset() { + this.nodeId = 0; + this.root = null; + } - var root:BVHNode; + // BFS tree traversal + function traverse(callback:(node:BVHNode) -> Void) { + var q = [this.root]; - public function new(bounds:Bounds) { - this.bounds = bounds.clone(); - } + while (q.length != 0) { + var current = q.shift(); + if (current == null) { + break; + } - public function insert(surf:CollisionSurface) { - surfaces.push(surf); - } + callback(current); - public function build() { - root = new BVHNode(bounds); - // Add all children - root.objects = this.surfaces; - root.split(); - } + if (!current.isLeaf) { + if (current.child1 != null) + q.push(current.child1); + if (current.child2 != null) + q.push(current.child2); + } + } + } - public function boundingSearch(searchbox:Bounds) { - return this.root.boundingSearch(searchbox); - } + public function remove(node:BVHNode) { + var parent = node.parent; - public function rayCast(origin:Vector, direction:Vector) { - return this.root.rayCast(origin, direction); - } -} + if (parent != null) { + 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) { + 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 = []; + + 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; + } + } diff --git a/src/collision/BoxCollisionEntity.hx b/src/collision/BoxCollisionEntity.hx index 9f1edd55..c8b37950 100644 --- a/src/collision/BoxCollisionEntity.hx +++ b/src/collision/BoxCollisionEntity.hx @@ -1,5 +1,6 @@ package collision; +import collision.BVHTree.IBVHObject; import src.MarbleGame; import src.TimeState; import h3d.Matrix; @@ -10,7 +11,7 @@ import h3d.Vector; import h3d.col.Sphere; import h3d.col.Bounds; -class BoxCollisionEntity extends CollisionEntity { +class BoxCollisionEntity extends CollisionEntity implements IBVHObject { var bounds:Bounds; var _dbgEntity:h3d.scene.Object; diff --git a/src/collision/CollisionEntity.hx b/src/collision/CollisionEntity.hx index 3a0fa86c..e4d449ee 100644 --- a/src/collision/CollisionEntity.hx +++ b/src/collision/CollisionEntity.hx @@ -1,5 +1,6 @@ package collision; +import collision.BVHTree.IBVHObject; import src.TimeState; import src.GameObject; import dif.math.Point3F; @@ -14,12 +15,12 @@ import h3d.col.Bounds; import src.PathedInterior; import src.Util; -class CollisionEntity implements IOctreeObject { +class CollisionEntity implements IOctreeObject implements IBVHObject { public var boundingBox:Bounds; public var octree:Octree; - public var bvh:BVHTree; + public var bvh:BVHTree; public var surfaces:Array; diff --git a/src/collision/CollisionSurface.hx b/src/collision/CollisionSurface.hx index 608beaf1..c4a6915c 100644 --- a/src/collision/CollisionSurface.hx +++ b/src/collision/CollisionSurface.hx @@ -4,28 +4,22 @@ import h3d.Matrix; import h3d.col.Bounds; import octree.IOctreeObject; import h3d.Vector; +import collision.BVHTree.IBVHObject; -class CollisionSurface implements IOctreeObject { +class CollisionSurface implements IOctreeObject implements IBVHObject { public var priority:Int; public var position:Int; - public var boundingBox:Bounds; - public var points:Array; public var normals:Array; public var indices:Array; - public var friction:Float = 1; public var restitution:Float = 1; public var force:Float = 0; - public var edgeData:Array; - public var edgeConcavities:Array; public var originalIndices:Array; - public var originalSurfaceIndex:Int; - public var key:Bool = false; public function new() {} diff --git a/src/collision/CollisionWorld.hx b/src/collision/CollisionWorld.hx index ad976c97..c2d88b60 100644 --- a/src/collision/CollisionWorld.hx +++ b/src/collision/CollisionWorld.hx @@ -11,9 +11,11 @@ class CollisionWorld { public var octree:Octree; public var entities:Array = []; public var dynamicEntities:Array = []; + public var dynamicBVH:BVHTree; public function new() { this.octree = new Octree(); + this.dynamicBVH = new BVHTree(); } 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.boundingBox.collide(box) && obj.go.isCollideable) contacts = contacts.concat(obj.sphereIntersection(spherecollision, timeState)); @@ -72,20 +75,14 @@ class CollisionWorld { contacts.push(entity); } - for (obj in dynamicEntities) { - if (obj.boundingBox.collide(box)) - contacts.push(obj); - } + contacts = contacts.concat(dynamicBVH.boundingSearch(box)); return contacts; } public function boundingSearch(bounds:Bounds, useCache:Bool = true) { var contacts = this.octree.boundingSearch(bounds, useCache).map(x -> cast(x, CollisionEntity)); - for (obj in dynamicEntities) { - if (obj.boundingBox.collide(bounds)) - contacts.push(obj); - } + contacts = contacts.concat(dynamicBVH.boundingSearch(bounds)); return contacts; } @@ -117,6 +114,7 @@ class CollisionWorld { public function addMovingEntity(entity:CollisionEntity) { this.dynamicEntities.push(entity); + this.dynamicBVH.add(entity); } public function updateTransform(entity:CollisionEntity) { diff --git a/src/gui/PlayGui.hx b/src/gui/PlayGui.hx index 1473b8eb..b9ead291 100644 --- a/src/gui/PlayGui.hx +++ b/src/gui/PlayGui.hx @@ -411,6 +411,9 @@ class PlayGui { } else if (powerupIdentifier == "Helicopter") { powerupImageObject = new DtsObject(); powerupImageObject.dtsPath = "data/shapes/images/helicopter.dts"; + } else if (powerupIdentifier == "MegaMarble") { + powerupImageObject = new DtsObject(); + powerupImageObject.dtsPath = "data/shapes/items/megamarble.dts"; } else { powerupIdentifier = ""; this.powerupImageObject = null; diff --git a/src/octree/Octree.hx b/src/octree/Octree.hx index 447ff45b..6dbd258a 100644 --- a/src/octree/Octree.hx +++ b/src/octree/Octree.hx @@ -139,7 +139,7 @@ class Octree { return intersections; } - public function boundingSearch(bounds:Bounds, useCache:Bool = true) { + public function boundingSearch(bounds:Bounds, useCache:Bool = false) { var intersections = []; if (useCache) { if (this.prevBoundSearch != null) {