From 9aafd8b5416180f62b8bea2bdff514e14ec19305 Mon Sep 17 00:00:00 2001 From: RandomityGuy <31925790+RandomityGuy@users.noreply.github.com> Date: Fri, 8 Mar 2024 01:36:36 +0530 Subject: [PATCH] performance improvement --- src/InstanceManager.hx | 11 ++- src/Marble.hx | 10 ++- src/MarbleWorld.hx | 26 +++--- src/collision/CollisionEntity.hx | 5 +- src/collision/CollisionHull.hx | 3 +- src/collision/CollisionInfo.hx | 2 - src/collision/CollisionPool.hx | 21 +++++ src/collision/SphereCollisionEntity.hx | 3 +- src/modes/HuntMode.hx | 3 +- src/net/ClientConnection.hx | 25 ++++++ src/net/MarblePredictionStore.hx | 3 +- src/net/Net.hx | 2 +- src/octree/Octree.hx | 30 ++++--- src/octree/OctreeNode.hx | 108 ++++++++++++++----------- 14 files changed, 164 insertions(+), 88 deletions(-) create mode 100644 src/collision/CollisionPool.hx diff --git a/src/InstanceManager.hx b/src/InstanceManager.hx index ddd4baef..3514b46b 100644 --- a/src/InstanceManager.hx +++ b/src/InstanceManager.hx @@ -26,6 +26,8 @@ class MeshBatchInfo { var meshbatch:MeshBatch; var transparencymeshbatch:MeshBatch; var mesh:Mesh; + var dtsShader:DtsTexture; + var glowPassDtsShader:DtsTexture; public function new() {} } @@ -81,7 +83,7 @@ class InstanceManager { 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; // minfo.meshbatch.material.mainPass.getShader(DtsTexture); var subOpacity = 1.0; if (dtsShader != null) { if (instance.gameObject.animateSubObjectOpacities) { @@ -112,7 +114,7 @@ class InstanceManager { // handle the glow pass too var glowPass = minfo.meshbatch.material.getPass("glow"); if (glowPass != null) { - dtsShader = glowPass.getShader(DtsTexture); + dtsShader = minfo.glowPassDtsShader; if (dtsShader != null) dtsShader.currentOpacity = instance.gameObject.currentOpacity * subOpacity; } @@ -124,7 +126,7 @@ class InstanceManager { 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; } @@ -218,6 +220,8 @@ class InstanceManager { for (shader in addshaders) gpass.addShader(shader); + minfo.glowPassDtsShader = gpass.getShader(DtsTexture); + minfo.meshbatch.material.addPass(gpass); } var refractPass = mat.getPass("refract"); @@ -279,6 +283,7 @@ class InstanceManager { minfo.meshbatch.material.addPass(gpass); } + minfo.dtsShader = minfo.meshbatch.material.mainPass.getShader(DtsTexture); // var dtsshader = mat.mainPass.getShader(DtsTexture); // if (dtsshader != null) { // minfo.meshbatch.material.mainPass.removeShader(minfo.meshbatch.material.textureShader); diff --git a/src/Marble.hx b/src/Marble.hx index 50e387be..b06af672 100644 --- a/src/Marble.hx +++ b/src/Marble.hx @@ -1,5 +1,6 @@ package src; +import collision.CollisionPool; import net.NetPacket.MarbleNetFlags; import net.BitStream.OutputBitStream; import net.ClientConnection; @@ -532,9 +533,10 @@ class Marble extends GameObject { matWorker.run(); } - function findContacts(collisiomWorld:CollisionWorld, timeState:TimeState) { + function findContacts(collisionWorld:CollisionWorld, timeState:TimeState) { this.contacts = queuedContacts; - var c = collisiomWorld.sphereIntersection(this.collider, timeState); + CollisionPool.clear(); + var c = collisionWorld.sphereIntersection(this.collider, timeState); this.contactEntities = c.foundEntities; contacts = contacts.concat(c.contacts); } @@ -1763,7 +1765,7 @@ class Marble extends GameObject { if (this.controllable && this.mode != Finish && !MarbleGame.instance.paused && !this.level.isWatching && !this.level.isReplayingMovement) { if (Net.isClient) { var axis = getMarbleAxis()[1]; - move = Net.clientConnection.moveManager.recordMove(cast this, axis, timeState); + move = Net.clientConnection.recordMove(cast this, axis, timeState); } else if (Net.isHost) { var axis = getMarbleAxis()[1]; var innerMove = recordMove(); @@ -1772,7 +1774,7 @@ class Marble extends GameObject { } var moveId = 65535; if (!this.controllable && this.connection != null && Net.isHost) { - var nextMove = this.connection.moveManager.getNextMove(); + var nextMove = this.connection.getNextMove(); // trace('Moves left: ${@:privateAccess this.connection.moveManager.queuedMoves.length}'); if (nextMove == null) { var axis = getMarbleAxis()[1]; diff --git a/src/MarbleWorld.hx b/src/MarbleWorld.hx index eccb4f7f..e44cb001 100644 --- a/src/MarbleWorld.hx +++ b/src/MarbleWorld.hx @@ -1,5 +1,6 @@ package src; +import collision.CollisionPool; import net.GemPredictionStore; import modes.HuntMode; import net.NetPacket.MarbleNetFlags; @@ -236,7 +237,7 @@ class MarbleWorld extends Scheduler { var oobSchedule:Float; - var _cubemapNeedsUpdate:Bool = false; + var _instancesNeedsUpdate:Bool = false; var lock:Bool = false; @@ -1076,7 +1077,7 @@ class MarbleWorld extends Scheduler { if (!lastMoves.ourMoveApplied) { var ourMove = lastMoves.myMarbleUpdate; if (ourMove != null) { - var ourMoveStruct = Net.clientConnection.moveManager.acknowledgeMove(ourMove.move.id, timeState); + var ourMoveStruct = Net.clientConnection.acknowledgeMove(ourMove.move.id, timeState); lastMoves.ourMoveApplied = true; for (client => arr in lastMoves.otherMarbleUpdates) { var lastMove = null; @@ -1157,11 +1158,11 @@ class MarbleWorld extends Scheduler { var ourLastMove = lastMoves.myMarbleUpdate; if (ourLastMove == null || marbleNeedsPrediction == 0) return -1; - var ackLag = @:privateAccess Net.clientConnection.moveManager.queuedMoves.length; + var ackLag = @:privateAccess Net.clientConnection.getQueuedMovesLength(); var ourLastMoveTime = ourLastMove.serverTicks; - var ourQueuedMoves = @:privateAccess Net.clientConnection.moveManager.queuedMoves.copy(); + var ourQueuedMoves = @:privateAccess Net.clientConnection.getQueuedMoves().copy(); var qm = ourQueuedMoves[0]; var advanceTimeState = qm != null ? qm.timeState.clone() : timeState.clone(); @@ -1506,14 +1507,12 @@ class MarbleWorld extends Scheduler { marble.update(timeState, collisionWorld, this.pathedInteriors); } } - _cubemapNeedsUpdate = true; + _instancesNeedsUpdate = true; Renderer.dirtyBuffers = true; if (this.rewinding) { // 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); @@ -1555,13 +1554,16 @@ class MarbleWorld extends Scheduler { asyncLoadResources(); if (this.playGui != null && _ready) this.playGui.render(e); - if (this.marble != null && this.marble.cubemapRenderer != null && _ready) { - ProfilerUI.measure("renderCubemap"); - if (_cubemapNeedsUpdate) { + if (_instancesNeedsUpdate) { + ProfilerUI.measure("updateInstances"); + this.instanceManager.render(); + if (this.marble != null && this.marble.cubemapRenderer != null && _ready) { + ProfilerUI.measure("renderCubemap"); + this.marble.cubemapRenderer.position.load(this.marble.getAbsPos().getPosition()); this.marble.cubemapRenderer.render(e); - _cubemapNeedsUpdate = false; } + _instancesNeedsUpdate = false; } } @@ -2298,6 +2300,8 @@ class MarbleWorld extends Scheduler { } } + CollisionPool.freeMemory(); + radar.dispose(); if (this.playGui != null) diff --git a/src/collision/CollisionEntity.hx b/src/collision/CollisionEntity.hx index cebc020b..ac4ae707 100644 --- a/src/collision/CollisionEntity.hx +++ b/src/collision/CollisionEntity.hx @@ -64,10 +64,12 @@ 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 // this.bvh.build(); } @@ -240,9 +242,10 @@ class CollisionEntity implements IOctreeObject implements IBVHObject { // if (testDot > bestDot) { // bestDot = testDot; - var cinfo = new CollisionInfo(); + var cinfo = CollisionPool.alloc(); cinfo.normal = normal.clone(); cinfo.point = closest.clone(); + cinfo.collider = null; // cinfo.collider = this; cinfo.velocity = this.velocity.clone(); cinfo.contactDistance = Math.sqrt(contactDist); diff --git a/src/collision/CollisionHull.hx b/src/collision/CollisionHull.hx index 2e9f59e2..3b8bdd17 100644 --- a/src/collision/CollisionHull.hx +++ b/src/collision/CollisionHull.hx @@ -40,10 +40,11 @@ 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; diff --git a/src/collision/CollisionInfo.hx b/src/collision/CollisionInfo.hx index 7992608b..20a0ba3a 100644 --- a/src/collision/CollisionInfo.hx +++ b/src/collision/CollisionInfo.hx @@ -10,12 +10,10 @@ class CollisionInfo { 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() {} } diff --git a/src/collision/CollisionPool.hx b/src/collision/CollisionPool.hx new file mode 100644 index 00000000..94ecbfbc --- /dev/null +++ b/src/collision/CollisionPool.hx @@ -0,0 +1,21 @@ +package collision; + +class CollisionPool { + static var pool:Array = []; + 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 = []; + } +} diff --git a/src/collision/SphereCollisionEntity.hx b/src/collision/SphereCollisionEntity.hx index 99459507..1d53f90b 100644 --- a/src/collision/SphereCollisionEntity.hx +++ b/src/collision/SphereCollisionEntity.hx @@ -85,7 +85,7 @@ 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; @@ -95,7 +95,6 @@ class SphereCollisionEntity extends CollisionEntity { 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(); diff --git a/src/modes/HuntMode.hx b/src/modes/HuntMode.hx index de0d9e20..b7263a61 100644 --- a/src/modes/HuntMode.hx +++ b/src/modes/HuntMode.hx @@ -573,7 +573,8 @@ class HuntMode extends NullMode { "data/skies/gemCubemapUp3.png", 'data/shapes/items/red.gem.png', "data/skies/gemCubemapUp.png", - 'sound/gem_collect.wav' + 'sound/gem_collect.wav', + 'sound/opponent_gem_collect.wav' ]; } diff --git a/src/net/ClientConnection.hx b/src/net/ClientConnection.hx index e6f87eba..664e2750 100644 --- a/src/net/ClientConnection.hx +++ b/src/net/ClientConnection.hx @@ -4,6 +4,7 @@ import haxe.io.Bytes; import datachannel.RTCPeerConnection; import datachannel.RTCDataChannel; import net.MoveManager; +import src.TimeState; enum abstract GameplayState(Int) from Int to Int { var UNKNOWN; @@ -55,5 +56,29 @@ abstract class GameConnection { state = GameplayState.GAME; } + public function queueMove(m:NetMove) { + moveManager.queueMove(m); + } + + public inline function acknowledgeMove(m:Int, timeState:TimeState) { + return moveManager.acknowledgeMove(m, timeState); + } + + public inline function getQueuedMoves() { + return @:privateAccess moveManager.queuedMoves; + } + + public inline function getQueuedMovesLength() { + return moveManager.getQueueSize(); + } + + public function recordMove(marble:src.Marble, motionDir:h3d.Vector, timeState:TimeState) { + return moveManager.recordMove(marble, motionDir, timeState); + } + + public function getNextMove() { + return moveManager.getNextMove(); + } + public function sendBytes(b:haxe.io.Bytes) {} } diff --git a/src/net/MarblePredictionStore.hx b/src/net/MarblePredictionStore.hx index 07407aaa..b31fc401 100644 --- a/src/net/MarblePredictionStore.hx +++ b/src/net/MarblePredictionStore.hx @@ -30,7 +30,8 @@ class MarblePrediction { if (p.netFlags != 0) subs += 1; // if (p.powerUpId != powerupItemId) - // subs += 1; // temp + if (tick % 10 == 0) + subs += 1; // temp // if (isControl) // subs += Math.abs(blastAmount - p.blastAmount); return subs; diff --git a/src/net/Net.hx b/src/net/Net.hx index bdf32112..58ffd33e 100644 --- a/src/net/Net.hx +++ b/src/net/Net.hx @@ -343,7 +343,7 @@ class Net { var movePacket = new MarbleMovePacket(); movePacket.deserialize(input); var cc = clientIdMap[movePacket.clientId]; - cc.moveManager.queueMove(movePacket.move); + cc.queueMove(movePacket.move); case PowerupPickup: var powerupPickupPacket = new PowerupPickupPacket(); diff --git a/src/octree/Octree.hx b/src/octree/Octree.hx index 23529497..7ad6152a 100644 --- a/src/octree/Octree.hx +++ b/src/octree/Octree.hx @@ -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; } diff --git a/src/octree/OctreeNode.hx b/src/octree/OctreeNode.hx index 9ea946d9..4e1a8a49 100644 --- a/src/octree/OctreeNode.hx +++ b/src/octree/OctreeNode.hx @@ -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 = 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,24 +150,53 @@ 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) { 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) { @@ -181,42 +219,22 @@ class OctreeNode implements IOctreeElement { } } - public function boundingSearch(bounds:Bounds, intersections:Array) { - if (this.bounds.collide(bounds)) { + public function boundingSearch(b:Bounds, intersections:Array) { + 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; }