From ba5f06a40d67625a3d026f484824f8546f8b43e2 Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Sun, 4 Dec 2022 14:41:04 +0530 Subject: [PATCH] fix magnet sound persist, use BVH for interior CD (cleaner, similar perf), cull instances not in frustum, overall FPS improvements --- src/InstanceManager.hx | 18 ++- src/Marble.hx | 6 +- src/Sky.hx | 3 + src/collision/BVHTree.hx | 183 +++++++++++++++++++++++++++++++ src/collision/CollisionEntity.hx | 15 +-- src/shaders/CubemapRenderer.hx | 10 +- src/shapes/Magnet.hx | 12 +- 7 files changed, 226 insertions(+), 21 deletions(-) create mode 100644 src/collision/BVHTree.hx diff --git a/src/InstanceManager.hx b/src/InstanceManager.hx index 1e6032ad..76d511b2 100644 --- a/src/InstanceManager.hx +++ b/src/InstanceManager.hx @@ -1,5 +1,6 @@ package src; +import h3d.prim.Instanced; import h3d.shader.pbr.PropsValues; import shaders.Billboard; import shaders.DtsTexture; @@ -37,8 +38,21 @@ class InstanceManager { public function update(dt:Float) { for (meshes in objects) { for (minfo in meshes) { + var visibleinstances = []; + // 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()); + if (scene.camera.frustum.hasBounds(objBounds)) { + visibleinstances.push(inst); + } + } + } + + // Emit non culled primitives if (minfo.meshbatch != null) { - var opaqueinstances = minfo.instances.filter(x -> x.gameObject.currentOpacity == 1); + 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); @@ -53,7 +67,7 @@ class InstanceManager { } } if (minfo.transparencymeshbatch != null) { - var transparentinstances = minfo.instances.filter(x -> x.gameObject.currentOpacity != 1); + var transparentinstances = visibleinstances.filter(x -> x.gameObject.currentOpacity != 1); minfo.transparencymeshbatch.begin(transparentinstances.length); for (instance in transparentinstances) { // Non opaque shit var dtsShader = minfo.transparencymeshbatch.material.mainPass.getShader(DtsTexture); diff --git a/src/Marble.hx b/src/Marble.hx index 85392639..33682ec8 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -248,7 +248,7 @@ class Marble extends GameObject { // mat.mainPass.culling = None; if (Settings.optionsSettings.reflectiveMarble) { - this.cubemapRenderer = new CubemapRenderer(level.scene); + this.cubemapRenderer = new CubemapRenderer(level.scene, level.sky); mat.mainPass.addShader(new MarbleReflection(this.cubemapRenderer.cubemap)); } } @@ -833,7 +833,7 @@ class Marble extends GameObject { + relLocalVel.z * deltaT * 2, radius * 1.1); - var surfaces = obj.grid == null ? obj.octree.boundingSearch(boundThing).map(x -> cast x) : obj.grid.boundingSearch(boundThing); + var surfaces = obj.bvh == null ? obj.octree.boundingSearch(boundThing).map(x -> cast x) : obj.bvh.boundingSearch(boundThing); for (surf in surfaces) { var surface:CollisionSurface = cast surf; @@ -1118,7 +1118,7 @@ class Marble extends GameObject { boundThing.addSpherePos(localpos.x + relLocalVel.x * dt * 2, localpos.y + relLocalVel.y * dt * 2, localpos.z + relLocalVel.z * dt * 2, radius * 1.1); - var surfaces = obj.grid == null ? obj.octree.boundingSearch(boundThing).map(x -> cast x) : obj.grid.boundingSearch(boundThing); + var surfaces = obj.bvh == null ? obj.octree.boundingSearch(boundThing).map(x -> cast x) : obj.bvh.boundingSearch(boundThing); var tform = obj.transform.clone(); diff --git a/src/Sky.hx b/src/Sky.hx index 0d0d1b9d..100bea75 100644 --- a/src/Sky.hx +++ b/src/Sky.hx @@ -20,6 +20,8 @@ import src.ResourceLoaderWorker; class Sky extends Object { public var dmlPath:String; + public var cubemap:Texture; + var imageResources:Array> = []; public function new() { @@ -51,6 +53,7 @@ class Sky extends Object { var shad = new Skybox(texture); skyMesh.material.mainPass.addShader(shad); skyMesh.material.mainPass.depthWrite = false; + cubemap = texture; onFinish(); }); // skyMesh.material.shadows = false; diff --git a/src/collision/BVHTree.hx b/src/collision/BVHTree.hx new file mode 100644 index 00000000..25652712 --- /dev/null +++ b/src/collision/BVHTree.hx @@ -0,0 +1,183 @@ +package collision; + +import h3d.col.Bounds; +import h3d.Vector; + +// https://github.com/Sopiro/DynamicBVH/blob/master/src/aabbtree.ts + +@:publicFields +class BVHNode { + 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; + + 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); + }; + + 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 split() { + // Splitting first time + // Calculate the centroids of all objects + var objs = objects.map(x -> { + x.generateBoundingBox(); + return {obj: x, centroid: x.boundingBox.getCenter()}; + }); + + // 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]; + + // 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); + } + } + + // 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; + } + + // 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); + + left.split(); + right.split(); + } + + 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 []; + } + } + + 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 []; + } + } +} + +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) { + return this.root.boundingSearch(searchbox); + } + + public function rayCast(origin:Vector, direction:Vector) { + return this.root.rayCast(origin, direction); + } +} diff --git a/src/collision/CollisionEntity.hx b/src/collision/CollisionEntity.hx index db488390..4b00870d 100644 --- a/src/collision/CollisionEntity.hx +++ b/src/collision/CollisionEntity.hx @@ -19,7 +19,7 @@ class CollisionEntity implements IOctreeObject { public var octree:Octree; - public var grid:Grid; + public var bvh:BVHTree; public var surfaces:Array; @@ -52,13 +52,14 @@ class CollisionEntity implements IOctreeObject { } } - // Generates the grid + // Generates the bvh public function finalize() { this.generateBoundingBox(); - this.grid = new Grid(this.boundingBox); + this.bvh = new BVHTree(this.boundingBox); for (surface in this.surfaces) { - this.grid.insert(surface); + this.bvh.insert(surface); } + this.bvh.build(); } public function setTransform(transform:Matrix) { @@ -102,7 +103,7 @@ class CollisionEntity implements IOctreeObject { var rStart = rayOrigin.clone(); rStart.transform(invMatrix); var rDir = rayDirection.transformed3x3(invMatrix); - if (grid == null) { + if (bvh == null) { var intersections = octree.raycast(rStart, rDir); var iData:Array = []; for (i in intersections) { @@ -113,7 +114,7 @@ class CollisionEntity implements IOctreeObject { } return iData; } else { - var intersections = this.grid.rayCast(rStart, rDir); + var intersections = this.bvh.rayCast(rStart, rDir); for (i in intersections) { i.point.transform(transform); i.normal.transform3x3(transform); @@ -146,7 +147,7 @@ class CollisionEntity implements IOctreeObject { sphereBounds.addSpherePos(position.x, position.y, position.z, radius * 1.1); sphereBounds.transform(invMatrix); sphereBounds.addSpherePos(localPos.x, localPos.y, localPos.z, radius * 1.1); - var surfaces = grid == null ? octree.boundingSearch(sphereBounds).map(x -> cast x) : grid.boundingSearch(sphereBounds); + var surfaces = bvh == null ? octree.boundingSearch(sphereBounds).map(x -> cast x) : bvh.boundingSearch(sphereBounds); var tform = transform.clone(); // tform.setPosition(tform.getPosition().add(this.velocity.multiply(timeState.dt))); diff --git a/src/shaders/CubemapRenderer.hx b/src/shaders/CubemapRenderer.hx index 291930d4..774fb47e 100644 --- a/src/shaders/CubemapRenderer.hx +++ b/src/shaders/CubemapRenderer.hx @@ -1,5 +1,6 @@ package shaders; +import src.Sky; import h3d.Vector; import h3d.scene.Scene; import h3d.Engine; @@ -8,18 +9,17 @@ import h3d.mat.Texture; class CubemapRenderer { public var cubemap:Texture; - + public var sky:Sky; public var position:Vector; var camera:Camera; - var scene:Scene; - var nextFaceToRender:Int; - public function new(scene:Scene) { + public function new(scene:Scene, sky:Sky) { this.scene = scene; - this.cubemap = new Texture(128, 128, [Cube, Dynamic, Target]); + this.sky = sky; + this.cubemap = new Texture(128, 128, [Cube, Dynamic, Target], h3d.mat.Data.TextureFormat.RGB8); this.camera = new Camera(90, 1, 1, 0.02); this.position = new Vector(); this.nextFaceToRender = 0; diff --git a/src/shapes/Magnet.hx b/src/shapes/Magnet.hx index 3434c749..b4569373 100644 --- a/src/shapes/Magnet.hx +++ b/src/shapes/Magnet.hx @@ -43,10 +43,14 @@ class Magnet extends ForceObject { public override function update(timeState:src.TimeState) { super.update(timeState); - var seffect = this.soundChannel.getEffect(Spatialization); - seffect.position = this.getAbsPos().getPosition(); + if (this.soundChannel != null) { + var seffect = this.soundChannel.getEffect(Spatialization); + seffect.position = this.getAbsPos().getPosition(); + seffect.fadeDistance = 15; + // seffect.maxDistance = 5; - if (this.soundChannel.pause) - this.soundChannel.pause = false; + if (this.soundChannel.pause) + this.soundChannel.pause = false; + } } }