diff --git a/src/engine/collisionEngine/CollisionManager.java b/src/engine/collisionEngine/CollisionManager.java new file mode 100644 index 00000000..2d56e0e3 --- /dev/null +++ b/src/engine/collisionEngine/CollisionManager.java @@ -0,0 +1,10 @@ +package engine.collisionEngine; + +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.HashMap; + +public class CollisionManager { + public static HashMap> mesh_triangles; + public static HashMap> structure_meshes; +} diff --git a/src/engine/collisionEngine/Mesh.java b/src/engine/collisionEngine/Mesh.java new file mode 100644 index 00000000..1b14d1ed --- /dev/null +++ b/src/engine/collisionEngine/Mesh.java @@ -0,0 +1,125 @@ +package engine.collisionEngine; + +import engine.gameManager.BuildingManager; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.objects.AbstractCharacter; +import engine.objects.Building; + +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; + +public class Mesh { + public int parentUUID; + public MeshData meshData; + private int meshID; + private Rectangle2D.Float bounds; + private ArrayList triangles; + private Vector3f mesh_loc; + private Vector3f mesh_ref; + private Vector3f mesh_end; + private float mesh_maxY; + private float mesh_minY; + + public Mesh(MeshData data, int parentUUID){ + this.meshData = data; + this.parentUUID = parentUUID; + this.meshID = data.meshID; + this.BakeTriangles(); + this.BakeBounds(); + } + + public void BakeTriangles(){ + + if(CollisionManager.mesh_triangles.containsKey(this.meshID) == false) + return; // no triangle data to bake + + this.triangles = new ArrayList<>(); + + Building building = BuildingManager.getBuilding(this.parentUUID); + if(building == null) + return; // can't continue without a building to base location offsets from + + Vector3f adjustedBuildingLoc = new Vector3f(building.loc.x,building.loc.y, building.loc.z * -1); + int rotDegrees = (int)Math.toDegrees(building.getBounds().getQuaternion().angleY); + this.mesh_loc = Vector3f.rotateAroundPoint(adjustedBuildingLoc.add(meshData.mesh_loc),adjustedBuildingLoc,rotDegrees); + this.mesh_ref = Vector3f.rotateAroundPoint(adjustedBuildingLoc.add(meshData.mesh_ref),adjustedBuildingLoc,rotDegrees); + this.mesh_end = Vector3f.rotateAroundPoint(adjustedBuildingLoc.add(meshData.mesh_end),adjustedBuildingLoc,rotDegrees); + this.mesh_minY = adjustedBuildingLoc.y + this.meshData.mesh_minY; + this.mesh_maxY = adjustedBuildingLoc.y + this.meshData.mesh_maxY; + + for(MeshTriangle tri : CollisionManager.mesh_triangles.get(this.meshID)){ + + Vector3f point1 = this.mesh_loc.add(new Vector3f(tri.point1.x,this.mesh_loc.y,tri.point1.y)); + Vector3f point2 = this.mesh_loc.add(new Vector3f(tri.point2.x,this.mesh_loc.y,tri.point2.y)); + Vector3f point3 = this.mesh_loc.add(new Vector3f(tri.point3.x,this.mesh_loc.y,tri.point3.y)); + + Vector3f rotatedPoint1 = Vector3f.rotateAroundPoint(point1,this.mesh_loc,rotDegrees); + Vector3f rotatedPoint2 = Vector3f.rotateAroundPoint(point2,this.mesh_loc,rotDegrees); + Vector3f rotatedPoint3 = Vector3f.rotateAroundPoint(point3,this.mesh_loc,rotDegrees); + + MeshTriangle newTri = new MeshTriangle(); + + newTri.point1 = new Point2D.Float(rotatedPoint1.x,rotatedPoint1.z); + newTri.point2 = new Point2D.Float(rotatedPoint2.x,rotatedPoint2.z); + newTri.point3 = new Point2D.Float(rotatedPoint3.x,rotatedPoint3.z); + + newTri.sides = new ArrayList<>(); + + newTri.sides.add(new Line2D.Float(newTri.point1,newTri.point2)); + newTri.sides.add(new Line2D.Float(newTri.point2,newTri.point3)); + newTri.sides.add(new Line2D.Float(newTri.point3,newTri.point1)); + + this.triangles.add(newTri); + } + } + + public void BakeBounds(){ + float width = this.mesh_ref.x - this.mesh_end.x; + float height = this.mesh_ref.z - this.mesh_end.z; + this.bounds = new Rectangle2D.Float(this.mesh_ref.x,this.mesh_ref.z,width,height); + } + + public boolean collides(AbstractCharacter mover, Vector3fImmutable destination){ + + if(mover == null) + return false; + + Line2D.Float line = new Line2D.Float(new Point2D.Float(mover.loc.x,mover.loc.z * -1),new Point2D.Float(destination.x,destination.z * -1)); + float footHeight = mover.loc.y; + float headHeight = mover.loc.y + mover.characterHeight; + + if(line.intersects(this.bounds) == false) + return false; // character path doesn't cross over this mesh + + if(footHeight > this.mesh_maxY || headHeight < this.mesh_minY) + return false; //character is either above or below the bounds of this mesh + + for(MeshTriangle tri : this.triangles) + if(tri.collides(line)) + return true; //character's path directly hits part of this mesh + + return false; + } + + public boolean losBlocked(AbstractCharacter looker, Vector3fImmutable target){ + + float headHeight = looker.loc.y + looker.characterHeight; + float targetAlt = target.y; + + Line2D.Float eyeLine = new Line2D.Float(new Point2D.Float(looker.loc.x,looker.loc.z * -1),new Point2D.Float(target.x,target.z * -1)); + + if(eyeLine.intersects(this.bounds) == false) + return false; // character eye-line doesn't cross over this mesh + + if(targetAlt > this.mesh_maxY && headHeight > this.mesh_maxY) + return false; // both characters are above this mesh + + if(targetAlt < this.mesh_maxY && headHeight < this.mesh_maxY) + return false; // both characters are below this mesh + + return true; + } +} diff --git a/src/engine/collisionEngine/MeshData.java b/src/engine/collisionEngine/MeshData.java new file mode 100644 index 00000000..9bcc7c8e --- /dev/null +++ b/src/engine/collisionEngine/MeshData.java @@ -0,0 +1,23 @@ +package engine.collisionEngine; + +import engine.math.Vector3f; + +public class MeshData { + public int propID; + public int meshID; + public Vector3f mesh_loc; + public Vector3f mesh_ref; + public Vector3f mesh_end; + public float mesh_maxY; + public float mesh_minY; + + public MeshData(int propID,int meshID, Vector3f meshLoc, Vector3f meshRef, Vector3f meshEnd, float maxY, float minY){ + this.propID = propID; + this.meshID = meshID; + this.mesh_loc = meshLoc; + this.mesh_ref = meshRef; + this.mesh_end = meshEnd; + this.mesh_maxY = maxY; + this.mesh_minY = minY; + } +} diff --git a/src/engine/collisionEngine/MeshTriangle.java b/src/engine/collisionEngine/MeshTriangle.java new file mode 100644 index 00000000..b3e02682 --- /dev/null +++ b/src/engine/collisionEngine/MeshTriangle.java @@ -0,0 +1,20 @@ +package engine.collisionEngine; + +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.util.ArrayList; + +public class MeshTriangle { + public Point2D.Float point1; + public Point2D.Float point2; + public Point2D.Float point3; + public ArrayList sides; + + public boolean collides(Line2D.Float line){ + for(Line2D.Float side : sides) + if(line.intersectsLine(side)) + return true; + + return false; + } +} diff --git a/src/engine/db/handlers/dbBuildingHandler.java b/src/engine/db/handlers/dbBuildingHandler.java index 375dbf17..b27caa6f 100644 --- a/src/engine/db/handlers/dbBuildingHandler.java +++ b/src/engine/db/handlers/dbBuildingHandler.java @@ -13,13 +13,18 @@ import engine.Enum; import engine.Enum.DbObjectType; import engine.Enum.ProtectionState; import engine.Enum.TaxType; +import engine.collisionEngine.CollisionManager; +import engine.collisionEngine.MeshData; +import engine.collisionEngine.MeshTriangle; import engine.gameManager.BuildingManager; import engine.gameManager.DbManager; +import engine.math.Vector3f; import engine.math.Vector3fImmutable; import engine.objects.*; import org.joda.time.DateTime; import org.pmw.tinylog.Logger; +import java.awt.geom.Point2D; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -848,5 +853,69 @@ public class dbBuildingHandler extends dbHandlerBase { } return false; } + public boolean LOAD_STRUCTURE_MESHES() { + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `final_structure_meshes`;")) { + + ResultSet rs = preparedStatement.executeQuery(); + + CollisionManager.structure_meshes = new HashMap<>(); + while (rs.next()) { + int propId = rs.getInt("propID"); + int meshId = rs.getInt("meshID"); + Vector3f meshLoc = new Vector3f(rs.getFloat("locX"),rs.getFloat("locY"),rs.getFloat("locZ")); + Vector3f meshRef = new Vector3f(rs.getFloat("refX"),rs.getFloat("refY"),rs.getFloat("refZ")); + Vector3f meshEnd = new Vector3f(rs.getFloat("endX"),rs.getFloat("endY"),rs.getFloat("endZ")); + float maxY = rs.getFloat("maxY"); + float minY = rs.getFloat("minY"); + MeshData data = new MeshData(propId,meshId,meshLoc,meshRef,meshEnd,maxY,minY); + if(CollisionManager.structure_meshes.containsKey(propId)){ + CollisionManager.structure_meshes.get(propId).add(data); + } else{ + ArrayList addList = new ArrayList<>(); + addList.add(data); + CollisionManager.structure_meshes.put(propId,addList); + } + } + return (preparedStatement.executeUpdate() > 0); + } catch (SQLException e) { + Logger.error(e); + } + return false; + } + public boolean LOAD_MESH_TRIANGLES() { + + try (Connection connection = DbManager.getConnection(); + PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `final_mesh_triangles`;")) { + + ResultSet rs = preparedStatement.executeQuery(); + + CollisionManager.mesh_triangles = new HashMap<>(); + while (rs.next()) { + int meshId = rs.getInt("meshID"); + Point2D.Float point1 = new Point2D.Float(rs.getFloat("P1X"), rs.getFloat("P1Z")); + Point2D.Float point2 = new Point2D.Float(rs.getFloat("P2X"), rs.getFloat("P2Z")); + Point2D.Float point3 = new Point2D.Float(rs.getFloat("P3X"), rs.getFloat("P3Z")); + + MeshTriangle newTri = new MeshTriangle(); + newTri.point1 = point1; + newTri.point2 = point2; + newTri.point3 = point3; + + if(CollisionManager.mesh_triangles.containsKey(meshId)){ + CollisionManager.mesh_triangles.get(meshId).add(newTri); + } else{ + ArrayList addList = new ArrayList<>(); + addList.add(newTri); + CollisionManager.mesh_triangles.put(meshId,addList); + } + + } + return (preparedStatement.executeUpdate() > 0); + } catch (SQLException e) { + Logger.error(e); + } + return false; + } } diff --git a/src/engine/gameManager/BuildingManager.java b/src/engine/gameManager/BuildingManager.java index bde12730..78d84114 100644 --- a/src/engine/gameManager/BuildingManager.java +++ b/src/engine/gameManager/BuildingManager.java @@ -14,6 +14,9 @@ import engine.Enum.BuildingGroup; import engine.Enum.GameObjectType; import engine.InterestManagement.InterestManager; import engine.InterestManagement.WorldGrid; +import engine.collisionEngine.CollisionManager; +import engine.collisionEngine.Mesh; +import engine.collisionEngine.MeshData; import engine.job.JobContainer; import engine.job.JobScheduler; import engine.jobs.UpgradeBuildingJob; @@ -943,6 +946,8 @@ public enum BuildingManager { cleanupHirelings(building); building.isDeranking.compareAndSet(true, false); + + BakeColliders(building); } public static Building getBuildingAtLocation(Vector3fImmutable loc) { @@ -960,4 +965,17 @@ public enum BuildingManager { return null; } + public static void BakeColliders(Building building){ + int propID = building.meshUUID; + + if(CollisionManager.structure_meshes.containsKey(propID) == false) { + Logger.error("Bake for PropID: " + propID + " Failed"); + return; + } + building.buildingMeshes = new ArrayList<>(); + for(MeshData data : CollisionManager.structure_meshes.get(propID)){ + building.buildingMeshes.add(new Mesh(data,building.getObjectUUID())); + } + } + } diff --git a/src/engine/objects/AbstractCharacter.java b/src/engine/objects/AbstractCharacter.java index 03aebf86..1d4d7872 100644 --- a/src/engine/objects/AbstractCharacter.java +++ b/src/engine/objects/AbstractCharacter.java @@ -45,6 +45,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; public abstract class AbstractCharacter extends AbstractWorldObject { + public float characterHeight = 0; protected CharacterItemManager charItemManager; private final ReentrantReadWriteLock healthLock = new ReentrantReadWriteLock(); public short level; diff --git a/src/engine/objects/Building.java b/src/engine/objects/Building.java index b288f0e2..b64e2318 100644 --- a/src/engine/objects/Building.java +++ b/src/engine/objects/Building.java @@ -14,6 +14,7 @@ import engine.Enum.*; import engine.InterestManagement.RealmMap; import engine.InterestManagement.Terrain; import engine.InterestManagement.WorldGrid; +import engine.collisionEngine.Mesh; import engine.db.archive.CityRecord; import engine.db.archive.DataWarehouse; import engine.db.archive.MineRecord; @@ -99,6 +100,8 @@ public class Building extends AbstractWorldObject { private ConcurrentHashMap condemned; private ArrayList children = null; + public ArrayList buildingMeshes; + /** * ResultSet Constructor */ @@ -1003,6 +1006,8 @@ public class Building extends AbstractWorldObject { if (this.upgradeDateTime != null) BuildingManager.submitUpgradeJob(this); + + BuildingManager.BakeColliders(this); } public synchronized boolean setOwner(AbstractCharacter newOwner) { diff --git a/src/engine/objects/PlayerCharacter.java b/src/engine/objects/PlayerCharacter.java index d68c16cd..4caa24f0 100644 --- a/src/engine/objects/PlayerCharacter.java +++ b/src/engine/objects/PlayerCharacter.java @@ -165,7 +165,6 @@ public class PlayerCharacter extends AbstractCharacter { private boolean wasTripped75 = false; private boolean wasTripped50 = false; private boolean wasTripped25 = false; - private float characterHeight = 0; private boolean lastSwimming = false; private boolean isTeleporting = false; private boolean dirtyLoad = true; @@ -5427,10 +5426,6 @@ public class PlayerCharacter extends AbstractCharacter { this.lastStamUpdateTime = System.currentTimeMillis(); } - public float getCharacterHeight() { - return characterHeight; - } - public boolean isEnteredWorld() { return enteredWorld; } diff --git a/src/engine/server/world/WorldServer.java b/src/engine/server/world/WorldServer.java index 39de164a..3ebb3f38 100644 --- a/src/engine/server/world/WorldServer.java +++ b/src/engine/server/world/WorldServer.java @@ -315,6 +315,10 @@ public class WorldServer { Logger.info("Initializing PowersManager."); PowersManager.initPowersManager(true); + Logger.info("Loading Collider Data"); + DbManager.BuildingQueries.LOAD_MESH_TRIANGLES(); + DbManager.BuildingQueries.LOAD_STRUCTURE_MESHES(); + Logger.info("Loading granted Skills for Runes"); DbManager.SkillsBaseQueries.LOAD_ALL_RUNE_SKILLS();