|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
// Magicbane Emulator Project © 2013 - 2022
|
|
|
|
// www.magicbane.com
|
|
|
|
|
|
|
|
package engine.InterestManagement;
|
|
|
|
|
|
|
|
import engine.Enum;
|
|
|
|
import engine.gameManager.DbManager;
|
|
|
|
import engine.gameManager.ZoneManager;
|
|
|
|
import engine.math.Vector2f;
|
|
|
|
import engine.math.Vector3fImmutable;
|
|
|
|
import engine.objects.AbstractWorldObject;
|
|
|
|
import engine.objects.Zone;
|
|
|
|
import engine.server.MBServerStatics;
|
|
|
|
import engine.util.MapLoader;
|
|
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
|
|
|
|
import javax.imageio.ImageIO;
|
|
|
|
import java.awt.*;
|
|
|
|
import java.awt.image.BufferedImage;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.sql.ResultSet;
|
|
|
|
import java.sql.SQLException;
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
|
|
public class HeightMap {
|
|
|
|
|
|
|
|
// Class variables
|
|
|
|
|
|
|
|
// Heightmap data for all zones.
|
|
|
|
|
|
|
|
public static final HashMap<Integer, HeightMap> heightmapByLoadNum = new HashMap<>();
|
|
|
|
|
|
|
|
// Bootstrap Tracking
|
|
|
|
|
|
|
|
public static int heightMapsCreated = 0;
|
|
|
|
public static HeightMap PlayerCityHeightMap;
|
|
|
|
|
|
|
|
// Heightmap data for this heightmap
|
|
|
|
|
|
|
|
public BufferedImage heightmapImage;
|
|
|
|
|
|
|
|
private int heightMapID;
|
|
|
|
private int maxHeight;
|
|
|
|
private int fullExtentsX;
|
|
|
|
private int fullExtentsY;
|
|
|
|
|
|
|
|
private float bucketWidthX;
|
|
|
|
private float bucketWidthY;
|
|
|
|
private int zoneLoadID;
|
|
|
|
private float seaLevel = 0;
|
|
|
|
private float outsetX;
|
|
|
|
private float outsetZ;
|
|
|
|
private int[][] pixelColorValues;
|
|
|
|
|
|
|
|
public HeightMap(ResultSet rs) throws SQLException {
|
|
|
|
|
|
|
|
this.heightMapID = rs.getInt("heightMapID");
|
|
|
|
this.maxHeight = rs.getInt("maxHeight");
|
|
|
|
int halfExtentsX = rs.getInt("xRadius");
|
|
|
|
int halfExtentsY = rs.getInt("zRadius");
|
|
|
|
this.zoneLoadID = rs.getInt("zoneLoadID");
|
|
|
|
this.seaLevel = rs.getFloat("seaLevel");
|
|
|
|
this.outsetX = rs.getFloat("outsetX");
|
|
|
|
this.outsetZ = rs.getFloat("outsetZ");
|
|
|
|
|
|
|
|
|
|
|
|
// Cache the full extents to avoid the calculation
|
|
|
|
|
|
|
|
this.fullExtentsX = halfExtentsX * 2;
|
|
|
|
this.fullExtentsY = halfExtentsY * 2;
|
|
|
|
|
|
|
|
this.heightmapImage = null;
|
|
|
|
File imageFile = new File(MBServerStatics.DEFAULT_DATA_DIR + "heightmaps/" + this.heightMapID + ".bmp");
|
|
|
|
|
|
|
|
// early exit if no image file was found. Will log in caller.
|
|
|
|
|
|
|
|
if (!imageFile.exists())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// load the heightmap image.
|
|
|
|
|
|
|
|
try {
|
|
|
|
this.heightmapImage = ImageIO.read(imageFile);
|
|
|
|
} catch (IOException e) {
|
|
|
|
Logger.error("***Error loading heightmap data for heightmap " + this.heightMapID + e.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
// We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin.
|
|
|
|
|
|
|
|
this.heightmapImage = MapLoader.flipImage(this.heightmapImage);
|
|
|
|
|
|
|
|
// Calculate the data we do not load from table
|
|
|
|
|
|
|
|
float numOfBuckets = this.heightmapImage.getWidth() - 1;
|
|
|
|
float calculatedWidth = this.fullExtentsX / numOfBuckets;
|
|
|
|
this.bucketWidthX = calculatedWidth;
|
|
|
|
this.bucketWidthY = this.bucketWidthX; // This makes no sense.
|
|
|
|
|
|
|
|
// Generate pixel array from image data
|
|
|
|
|
|
|
|
generatePixelData();
|
|
|
|
|
|
|
|
HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this);
|
|
|
|
|
|
|
|
heightMapsCreated++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Created for PlayerCities
|
|
|
|
public HeightMap() {
|
|
|
|
|
|
|
|
this.heightMapID = 999999;
|
|
|
|
this.maxHeight = 5; // for real...
|
|
|
|
int halfExtentsX = (int) Enum.CityBoundsType.ZONE.extents;
|
|
|
|
int halfExtentsY = (int) Enum.CityBoundsType.ZONE.extents;
|
|
|
|
this.zoneLoadID = 0;
|
|
|
|
this.seaLevel = 0;
|
|
|
|
this.outsetX = 128;
|
|
|
|
this.outsetZ = 128;
|
|
|
|
|
|
|
|
|
|
|
|
// Cache the full extents to avoid the calculation
|
|
|
|
|
|
|
|
this.fullExtentsX = halfExtentsX * 2;
|
|
|
|
this.fullExtentsY = halfExtentsY * 2;
|
|
|
|
|
|
|
|
|
|
|
|
// load the heightmap image.
|
|
|
|
|
|
|
|
|
|
|
|
// We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin.
|
|
|
|
|
|
|
|
this.heightmapImage = null;
|
|
|
|
|
|
|
|
// Calculate the data we do not load from table
|
|
|
|
|
|
|
|
this.bucketWidthX = 1;
|
|
|
|
this.bucketWidthY = 1;
|
|
|
|
|
|
|
|
this.pixelColorValues = new int[this.fullExtentsX + 1][this.fullExtentsY+1];
|
|
|
|
|
|
|
|
for (int y = 0; y <= this.fullExtentsY; y++) {
|
|
|
|
for (int x = 0; x <= this.fullExtentsX; x++) {
|
|
|
|
pixelColorValues[x][y] = 255;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
public HeightMap(Zone zone) {
|
|
|
|
|
|
|
|
this.heightMapID = 999999;
|
|
|
|
this.maxHeight = 0;
|
|
|
|
int halfExtentsX = (int) zone.getBounds().getHalfExtents().x;
|
|
|
|
int halfExtentsY = (int) zone.getBounds().getHalfExtents().y;
|
|
|
|
this.zoneLoadID = 0;
|
|
|
|
this.seaLevel = 0;
|
|
|
|
this.outsetX = 0;
|
|
|
|
this.outsetZ = 0;
|
|
|
|
|
|
|
|
// Cache the full extents to avoid the calculation
|
|
|
|
|
|
|
|
this.fullExtentsX = halfExtentsX * 2;
|
|
|
|
this.fullExtentsY = halfExtentsY * 2;
|
|
|
|
|
|
|
|
|
|
|
|
// We needed to flip the image as OpenGL and Shadowbane both use the bottom left corner as origin.
|
|
|
|
|
|
|
|
this.heightmapImage = null;
|
|
|
|
|
|
|
|
// Calculate the data we do not load from table
|
|
|
|
|
|
|
|
this.bucketWidthX = 1;
|
|
|
|
this.bucketWidthY = 1;
|
|
|
|
|
|
|
|
this.pixelColorValues = new int[this.fullExtentsX+1][this.fullExtentsY+1];
|
|
|
|
|
|
|
|
for (int y = 0; y <= this.fullExtentsY; y++) {
|
|
|
|
for (int x = 0; x <= this.fullExtentsX; x++) {
|
|
|
|
pixelColorValues[x][y] = 255;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void GeneratePlayerCityHeightMap() {
|
|
|
|
|
|
|
|
HeightMap.PlayerCityHeightMap = new HeightMap();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void GenerateCustomHeightMap(Zone zone) {
|
|
|
|
|
|
|
|
HeightMap heightMap = new HeightMap(zone);
|
|
|
|
|
|
|
|
HeightMap.heightmapByLoadNum.put(zone.getLoadNum(), heightMap);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public Vector2f getGridSquare(Vector2f zoneLoc) {
|
|
|
|
|
|
|
|
if (zoneLoc.x < 0)
|
|
|
|
zoneLoc.setX(0);
|
|
|
|
|
|
|
|
if (zoneLoc.x > this.fullExtentsX - 1)
|
|
|
|
zoneLoc.setX((this.fullExtentsX - 1) + .9999999f);
|
|
|
|
|
|
|
|
if (zoneLoc.y < 0)
|
|
|
|
zoneLoc.setY(0);
|
|
|
|
|
|
|
|
if (zoneLoc.y > this.fullExtentsY - 1)
|
|
|
|
zoneLoc.setY((this.fullExtentsY - 1) + .9999999f);
|
|
|
|
|
|
|
|
float xBucket = (zoneLoc.x / this.bucketWidthX);
|
|
|
|
float yBucket = (zoneLoc.y / this.bucketWidthY);
|
|
|
|
|
|
|
|
return new Vector2f(xBucket, yBucket);
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getInterpolatedTerrainHeight(Vector2f zoneLoc) {
|
|
|
|
|
|
|
|
Vector2f gridSquare;
|
|
|
|
|
|
|
|
if (zoneLoc.x < 0 || zoneLoc.x > this.fullExtentsX)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (zoneLoc.y < 0 || zoneLoc.y > this.fullExtentsY)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
int maxX = (int) (this.fullExtentsX / this.bucketWidthX);
|
|
|
|
int maxY = (int) (this.fullExtentsY / this.bucketWidthY);
|
|
|
|
|
|
|
|
//flip the Y so it grabs from the bottom left instead of top left.
|
|
|
|
//zoneLoc.setY(maxZoneHeight - zoneLoc.y);
|
|
|
|
|
|
|
|
gridSquare = getGridSquare(zoneLoc);
|
|
|
|
|
|
|
|
int gridX = (int) gridSquare.x;
|
|
|
|
int gridY = (int) (gridSquare.y);
|
|
|
|
|
|
|
|
if (gridX > maxX)
|
|
|
|
gridX = maxX;
|
|
|
|
if (gridY > maxY)
|
|
|
|
gridY = maxY;
|
|
|
|
|
|
|
|
float offsetX = (gridSquare.x - gridX);
|
|
|
|
float offsetY = gridSquare.y - gridY;
|
|
|
|
|
|
|
|
//get height of the 4 vertices.
|
|
|
|
|
|
|
|
float topLeftHeight = 0;
|
|
|
|
float topRightHeight = 0;
|
|
|
|
float bottomLeftHeight = 0;
|
|
|
|
float bottomRightHeight = 0;
|
|
|
|
|
|
|
|
int nextY = gridY +1;
|
|
|
|
int nextX = gridX + 1;
|
|
|
|
|
|
|
|
if (nextY > maxY)
|
|
|
|
nextY = gridY;
|
|
|
|
|
|
|
|
if (nextX > maxX)
|
|
|
|
nextX = gridX;
|
|
|
|
|
|
|
|
topLeftHeight = pixelColorValues[gridX][gridY];
|
|
|
|
topRightHeight = pixelColorValues[nextX][gridY];
|
|
|
|
bottomLeftHeight = pixelColorValues[gridX][nextY];
|
|
|
|
bottomRightHeight = pixelColorValues[nextX][nextY];
|
|
|
|
|
|
|
|
float interpolatedHeight;
|
|
|
|
|
|
|
|
interpolatedHeight = topRightHeight * (1 - offsetY) * (offsetX);
|
|
|
|
interpolatedHeight += (bottomRightHeight * offsetY * offsetX);
|
|
|
|
interpolatedHeight += (bottomLeftHeight * (1 - offsetX) * offsetY);
|
|
|
|
interpolatedHeight += (topLeftHeight * (1 - offsetX) * (1 - offsetY));
|
|
|
|
|
|
|
|
interpolatedHeight *= (float) this.maxHeight / 256; // Scale height
|
|
|
|
|
|
|
|
return interpolatedHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static float getWorldHeight(AbstractWorldObject worldObject) {
|
|
|
|
|
|
|
|
Vector2f parentLoc = new Vector2f(-1, -1);
|
|
|
|
Zone currentZone = ZoneManager.findSmallestZone(worldObject.getLoc());
|
|
|
|
|
|
|
|
if (currentZone == null)
|
|
|
|
return worldObject.getAltitude();
|
|
|
|
|
|
|
|
Zone parentZone = currentZone.getParent();
|
|
|
|
HeightMap heightMap = currentZone.getHeightMap();
|
|
|
|
|
|
|
|
//find the next parents heightmap if the currentzone heightmap is null.
|
|
|
|
|
|
|
|
while (heightMap == null) {
|
|
|
|
|
|
|
|
if (currentZone == ZoneManager.getSeaFloor()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
currentZone = currentZone.getParent();
|
|
|
|
heightMap = currentZone.getHeightMap();
|
|
|
|
|
|
|
|
parentZone = currentZone.getParent();
|
|
|
|
}
|
|
|
|
if ((heightMap == null) || (currentZone == ZoneManager.getSeaFloor())) {
|
|
|
|
|
|
|
|
return currentZone.getAbsY() + worldObject.getAltitude();
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector2f zoneLoc = ZoneManager.worldToZoneSpace(worldObject.getLoc(), currentZone);
|
|
|
|
Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldObject.getLoc(), currentZone);
|
|
|
|
|
|
|
|
if ((parentZone != null) && (parentZone.getHeightMap() != null)) {
|
|
|
|
parentLoc = ZoneManager.worldToZoneSpace(worldObject.getLoc(), parentZone);
|
|
|
|
}
|
|
|
|
|
|
|
|
float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
|
|
|
|
|
|
|
|
float worldAltitude = currentZone.getWorldAltitude();
|
|
|
|
|
|
|
|
float realWorldAltitude = interaltitude + worldAltitude;
|
|
|
|
|
|
|
|
//OUTSET
|
|
|
|
|
|
|
|
|
|
|
|
if (parentZone != null) {
|
|
|
|
|
|
|
|
|
|
|
|
float parentXRadius = currentZone.getBounds().getHalfExtents().x;
|
|
|
|
float parentZRadius = currentZone.getBounds().getHalfExtents().y;
|
|
|
|
|
|
|
|
|
|
|
|
float offsetX = Math.abs((localLocFromCenter.x / parentXRadius));
|
|
|
|
float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius));
|
|
|
|
|
|
|
|
float bucketScaleX = heightMap.outsetX / parentXRadius;
|
|
|
|
float bucketScaleZ = heightMap.outsetZ / parentZRadius;
|
|
|
|
|
|
|
|
|
|
|
|
if (bucketScaleX <= 0.40000001) {
|
|
|
|
bucketScaleX = heightMap.outsetZ / parentXRadius;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bucketScaleX > 0.40000001)
|
|
|
|
bucketScaleX = 0.40000001f;
|
|
|
|
|
|
|
|
if (bucketScaleZ <= 0.40000001) {
|
|
|
|
bucketScaleZ = heightMap.outsetX / parentZRadius;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bucketScaleZ > 0.40000001)
|
|
|
|
bucketScaleZ = 0.40000001f;
|
|
|
|
|
|
|
|
float outsideGridSizeX = 1 - bucketScaleX; //32/256
|
|
|
|
float outsideGridSizeZ = 1 - bucketScaleZ;
|
|
|
|
float weight;
|
|
|
|
|
|
|
|
double scale;
|
|
|
|
|
|
|
|
|
|
|
|
if (offsetX > outsideGridSizeX && offsetX > offsetZ) {
|
|
|
|
weight = (offsetX - outsideGridSizeX) / bucketScaleX;
|
|
|
|
scale = Math.atan2((.5 - weight) * 3.1415927, 1);
|
|
|
|
|
|
|
|
float scaleChild = (float) ((scale + 1) * .5);
|
|
|
|
float scaleParent = 1 - scaleChild;
|
|
|
|
|
|
|
|
|
|
|
|
float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
|
|
|
|
float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
|
|
|
|
|
|
|
|
|
|
|
|
parentCenterAltitude += currentZone.getYCoord();
|
|
|
|
parentCenterAltitude += interaltitude;
|
|
|
|
|
|
|
|
float firstScale = parentAltitude * scaleParent;
|
|
|
|
float secondScale = parentCenterAltitude * scaleChild;
|
|
|
|
float outsetALt = firstScale + secondScale;
|
|
|
|
|
|
|
|
outsetALt += currentZone.getParent().getWorldAltitude();
|
|
|
|
realWorldAltitude = outsetALt;
|
|
|
|
|
|
|
|
} else if (offsetZ > outsideGridSizeZ) {
|
|
|
|
|
|
|
|
weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ;
|
|
|
|
scale = Math.atan2((.5 - weight) * 3.1415927, 1);
|
|
|
|
|
|
|
|
float scaleChild = (float) ((scale + 1) * .5);
|
|
|
|
float scaleParent = 1 - scaleChild;
|
|
|
|
float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
|
|
|
|
float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
|
|
|
|
|
|
|
|
parentCenterAltitude += currentZone.getYCoord();
|
|
|
|
parentCenterAltitude += interaltitude;
|
|
|
|
float firstScale = parentAltitude * scaleParent;
|
|
|
|
float secondScale = parentCenterAltitude * scaleChild;
|
|
|
|
float outsetALt = firstScale + secondScale;
|
|
|
|
|
|
|
|
outsetALt += currentZone.getParent().getWorldAltitude();
|
|
|
|
realWorldAltitude = outsetALt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return realWorldAltitude;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static float getWorldHeight(Vector3fImmutable worldLoc) {
|
|
|
|
|
|
|
|
Vector2f parentLoc = new Vector2f(-1, -1);
|
|
|
|
Zone currentZone = ZoneManager.findSmallestZone(worldLoc);
|
|
|
|
|
|
|
|
if (currentZone == null)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
Zone parentZone = currentZone.getParent();
|
|
|
|
HeightMap heightMap = currentZone.getHeightMap();
|
|
|
|
//find the next parents heightmap if the currentzone heightmap is null.
|
|
|
|
while (heightMap == null) {
|
|
|
|
|
|
|
|
if (currentZone == ZoneManager.getSeaFloor()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
currentZone = currentZone.getParent();
|
|
|
|
heightMap = currentZone.getHeightMap();
|
|
|
|
|
|
|
|
parentZone = currentZone.getParent();
|
|
|
|
}
|
|
|
|
if ((heightMap == null) || (currentZone == ZoneManager.getSeaFloor())) {
|
|
|
|
|
|
|
|
return currentZone.getAbsY();
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector2f zoneLoc = ZoneManager.worldToZoneSpace(worldLoc, currentZone);
|
|
|
|
Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldLoc, currentZone);
|
|
|
|
|
|
|
|
if ((parentZone != null) && (parentZone.getHeightMap() != null)) {
|
|
|
|
parentLoc = ZoneManager.worldToZoneSpace(worldLoc, parentZone);
|
|
|
|
}
|
|
|
|
|
|
|
|
float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
|
|
|
|
|
|
|
|
float worldAltitude = currentZone.getWorldAltitude();
|
|
|
|
|
|
|
|
float realWorldAltitude = interaltitude + worldAltitude;
|
|
|
|
|
|
|
|
//OUTSET
|
|
|
|
|
|
|
|
|
|
|
|
if (parentZone != null) {
|
|
|
|
|
|
|
|
// if (currentZone.getHeightMap() != null && parentZone.getHeightMap() != null && parentZone.getParent() != null && parentZone.getParent().getHeightMap() != null)
|
|
|
|
// return realWorldAltitude;
|
|
|
|
|
|
|
|
float parentXRadius = currentZone.getBounds().getHalfExtents().x;
|
|
|
|
float parentZRadius = currentZone.getBounds().getHalfExtents().y;
|
|
|
|
|
|
|
|
float offsetX = Math.abs((localLocFromCenter.x / parentXRadius));
|
|
|
|
float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius));
|
|
|
|
|
|
|
|
float bucketScaleX = heightMap.outsetX / parentXRadius;
|
|
|
|
float bucketScaleZ = heightMap.outsetZ / parentZRadius;
|
|
|
|
|
|
|
|
float outsideGridSizeX = 1 - bucketScaleX; //32/256
|
|
|
|
float outsideGridSizeZ = 1 - bucketScaleZ;
|
|
|
|
float weight;
|
|
|
|
|
|
|
|
double scale;
|
|
|
|
|
|
|
|
|
|
|
|
if (offsetX > outsideGridSizeX && offsetX > offsetZ) {
|
|
|
|
weight = (offsetX - outsideGridSizeX) / bucketScaleX;
|
|
|
|
scale = Math.atan2((.5 - weight) * 3.1415927, 1);
|
|
|
|
|
|
|
|
float scaleChild = (float) ((scale + 1) * .5);
|
|
|
|
float scaleParent = 1 - scaleChild;
|
|
|
|
|
|
|
|
|
|
|
|
float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
|
|
|
|
float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
|
|
|
|
|
|
|
|
|
|
|
|
parentCenterAltitude += currentZone.getYCoord();
|
|
|
|
parentCenterAltitude += interaltitude;
|
|
|
|
|
|
|
|
float firstScale = parentAltitude * scaleParent;
|
|
|
|
float secondScale = parentCenterAltitude * scaleChild;
|
|
|
|
float outsetALt = firstScale + secondScale;
|
|
|
|
|
|
|
|
outsetALt += currentZone.getParent().getWorldAltitude();
|
|
|
|
realWorldAltitude = outsetALt;
|
|
|
|
|
|
|
|
} else if (offsetZ > outsideGridSizeZ) {
|
|
|
|
|
|
|
|
weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ;
|
|
|
|
scale = Math.atan2((.5 - weight) * 3.1415927, 1);
|
|
|
|
|
|
|
|
float scaleChild = (float) ((scale + 1) * .5);
|
|
|
|
float scaleParent = 1 - scaleChild;
|
|
|
|
float parentAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(parentLoc);
|
|
|
|
float parentCenterAltitude = parentZone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone));
|
|
|
|
|
|
|
|
parentCenterAltitude += currentZone.getYCoord();
|
|
|
|
parentCenterAltitude += interaltitude;
|
|
|
|
float firstScale = parentAltitude * scaleParent;
|
|
|
|
float secondScale = parentCenterAltitude * scaleChild;
|
|
|
|
float outsetALt = firstScale + secondScale;
|
|
|
|
|
|
|
|
outsetALt += currentZone.getParent().getWorldAltitude();
|
|
|
|
realWorldAltitude = outsetALt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return realWorldAltitude;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getInterpolatedTerrainHeight(Vector3fImmutable zoneLoc3f) {
|
|
|
|
|
|
|
|
Vector2f zoneLoc = new Vector2f(zoneLoc3f.x, zoneLoc3f.z);
|
|
|
|
|
|
|
|
Vector2f gridSquare;
|
|
|
|
|
|
|
|
if (zoneLoc.x < 0 || zoneLoc.x > this.fullExtentsX)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (zoneLoc.y < 0 || zoneLoc.y > this.fullExtentsY)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
//flip the Y so it grabs from the bottom left instead of top left.
|
|
|
|
//zoneLoc.setY(maxZoneHeight - zoneLoc.y);
|
|
|
|
|
|
|
|
gridSquare = getGridSquare(zoneLoc);
|
|
|
|
|
|
|
|
int gridX = (int) gridSquare.x;
|
|
|
|
int gridY = (int) (gridSquare.y);
|
|
|
|
|
|
|
|
float offsetX = (gridSquare.x - gridX);
|
|
|
|
float offsetY = gridSquare.y - gridY;
|
|
|
|
|
|
|
|
//get height of the 4 vertices.
|
|
|
|
|
|
|
|
float topLeftHeight = pixelColorValues[gridX][gridY];
|
|
|
|
float topRightHeight = pixelColorValues[gridX + 1][gridY];
|
|
|
|
float bottomLeftHeight = pixelColorValues[gridX][gridY + 1];
|
|
|
|
float bottomRightHeight = pixelColorValues[gridX + 1][gridY + 1];
|
|
|
|
|
|
|
|
float interpolatedHeight;
|
|
|
|
|
|
|
|
interpolatedHeight = topRightHeight * (1 - offsetY) * (offsetX);
|
|
|
|
interpolatedHeight += (bottomRightHeight * offsetY * offsetX);
|
|
|
|
interpolatedHeight += (bottomLeftHeight * (1 - offsetX) * offsetY);
|
|
|
|
interpolatedHeight += (topLeftHeight * (1 - offsetX) * (1 - offsetY));
|
|
|
|
|
|
|
|
interpolatedHeight *= (float) this.maxHeight / 256; // Scale height
|
|
|
|
|
|
|
|
return interpolatedHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static float getOutsetHeight(float interpolatedAltitude, Zone zone, Vector3fImmutable worldLocation) {
|
|
|
|
|
|
|
|
Vector2f parentLoc;
|
|
|
|
float outsetALt = 0;
|
|
|
|
|
|
|
|
if (zone.getParent() == null || zone.getParent().getHeightMap() == null)
|
|
|
|
return interpolatedAltitude + zone.getWorldAltitude();
|
|
|
|
|
|
|
|
if (zone.getParent() != null && zone.getParent().getHeightMap() != null) {
|
|
|
|
|
|
|
|
parentLoc = ZoneManager.worldToZoneSpace(worldLocation, zone.getParent());
|
|
|
|
|
|
|
|
Vector3fImmutable localLocFromCenter = ZoneManager.worldToLocal(worldLocation, zone);
|
|
|
|
|
|
|
|
float parentXRadius = zone.getBounds().getHalfExtents().x;
|
|
|
|
float parentZRadius = zone.getBounds().getHalfExtents().y;
|
|
|
|
|
|
|
|
float bucketScaleX = zone.getHeightMap().outsetX / parentXRadius;
|
|
|
|
float bucketScaleZ = zone.getHeightMap().outsetZ / parentZRadius;
|
|
|
|
|
|
|
|
float outsideGridSizeX = 1 - bucketScaleX; //32/256
|
|
|
|
float outsideGridSizeZ = 1 - bucketScaleZ;
|
|
|
|
|
|
|
|
float weight;
|
|
|
|
double scale;
|
|
|
|
|
|
|
|
float offsetX = Math.abs((localLocFromCenter.x / parentXRadius));
|
|
|
|
float offsetZ = Math.abs((localLocFromCenter.z / parentZRadius));
|
|
|
|
|
|
|
|
if (offsetX > outsideGridSizeX && offsetX > offsetZ) {
|
|
|
|
weight = (offsetX - outsideGridSizeX) / bucketScaleX;
|
|
|
|
scale = Math.atan2((.5 - weight) * 3.1415927, 1);
|
|
|
|
|
|
|
|
float scaleChild = (float) ((scale + 1) * .5);
|
|
|
|
float scaleParent = 1 - scaleChild;
|
|
|
|
|
|
|
|
float parentAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(parentLoc);
|
|
|
|
float parentCenterAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(zone.getLoc(), zone.getParent()));
|
|
|
|
|
|
|
|
parentCenterAltitude += zone.getYCoord();
|
|
|
|
parentCenterAltitude += interpolatedAltitude;
|
|
|
|
|
|
|
|
float firstScale = parentAltitude * scaleParent;
|
|
|
|
float secondScale = parentCenterAltitude * scaleChild;
|
|
|
|
outsetALt = firstScale + secondScale;
|
|
|
|
|
|
|
|
outsetALt += zone.getParent().getAbsY();
|
|
|
|
|
|
|
|
} else if (offsetZ > outsideGridSizeZ) {
|
|
|
|
|
|
|
|
weight = (offsetZ - outsideGridSizeZ) / bucketScaleZ;
|
|
|
|
scale = Math.atan2((.5 - weight) * 3.1415927, 1);
|
|
|
|
|
|
|
|
float scaleChild = (float) ((scale + 1) * .5);
|
|
|
|
float scaleParent = 1 - scaleChild;
|
|
|
|
float parentAltitude = zone.getParent().getHeightMap().getInterpolatedTerrainHeight(parentLoc);
|
|
|
|
float parentCenterAltitude = zone.getHeightMap().getInterpolatedTerrainHeight(ZoneManager.worldToZoneSpace(zone.getLoc(), zone));
|
|
|
|
|
|
|
|
parentCenterAltitude += zone.getYCoord();
|
|
|
|
parentCenterAltitude += interpolatedAltitude;
|
|
|
|
|
|
|
|
float firstScale = parentAltitude * scaleParent;
|
|
|
|
float secondScale = parentCenterAltitude * scaleChild;
|
|
|
|
outsetALt = firstScale + secondScale;
|
|
|
|
|
|
|
|
outsetALt += zone.getParent().getAbsY();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return outsetALt;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void generatePixelData() {
|
|
|
|
|
|
|
|
Color color;
|
|
|
|
|
|
|
|
// Generate altitude lookup table for this heightmap
|
|
|
|
|
|
|
|
this.pixelColorValues = new int[this.heightmapImage.getWidth()][this.heightmapImage.getHeight()];
|
|
|
|
|
|
|
|
for (int y = 0; y < this.heightmapImage.getHeight(); y++) {
|
|
|
|
for (int x = 0; x < this.heightmapImage.getWidth(); x++) {
|
|
|
|
|
|
|
|
color = new Color(this.heightmapImage.getRGB(x, y));
|
|
|
|
pixelColorValues[x][y] = color.getRed();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Vector2f getGridOffset(Vector2f gridSquare) {
|
|
|
|
|
|
|
|
int floorX = (int) gridSquare.x;
|
|
|
|
int floorY = (int) gridSquare.y;
|
|
|
|
|
|
|
|
return new Vector2f(gridSquare.x - floorX, gridSquare.y - floorY);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getScaledHeightForColor(float color) {
|
|
|
|
|
|
|
|
return (color / 256) * this.maxHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void loadAlHeightMaps() {
|
|
|
|
|
|
|
|
// Load the heightmaps into staging hashmap keyed by HashMapID
|
|
|
|
|
|
|
|
DbManager.HeightMapQueries.LOAD_ALL_HEIGHTMAPS();
|
|
|
|
|
|
|
|
//generate static player city heightmap.
|
|
|
|
|
|
|
|
HeightMap.GeneratePlayerCityHeightMap();
|
|
|
|
|
|
|
|
|
|
|
|
// Clear all heightmap image data as it's no longer needed.
|
|
|
|
|
|
|
|
for (HeightMap heightMap : HeightMap.heightmapByLoadNum.values()) {
|
|
|
|
heightMap.heightmapImage = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Logger.info(HeightMap.heightmapByLoadNum.size() + " Heightmaps cached.");
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getBucketWidthX() {
|
|
|
|
return bucketWidthX;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getBucketWidthY() {
|
|
|
|
return bucketWidthY;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getHeightMapID() {
|
|
|
|
return heightMapID;
|
|
|
|
}
|
|
|
|
|
|
|
|
public BufferedImage getHeightmapImage() {
|
|
|
|
return heightmapImage;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getSeaLevel() {
|
|
|
|
return seaLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean isLocUnderwater(Vector3fImmutable currentLoc) {
|
|
|
|
|
|
|
|
float localAltitude = HeightMap.getWorldHeight(currentLoc);
|
|
|
|
Zone zone = ZoneManager.findSmallestZone(currentLoc);
|
|
|
|
|
|
|
|
if (localAltitude < zone.getSeaLevel())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|