Public Repository for the Magicbane Shadowbane Emulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

385 lines
11 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.InterestManagement;
import engine.Enum;
import engine.gameManager.ConfigManager;
import engine.gameManager.DbManager;
import engine.gameManager.ZoneManager;
import engine.math.Vector2f;
import engine.math.Vector3fImmutable;
import engine.objects.Zone;
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
public static float SCALEVALUE = 1.0f / 256;
// 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;
1 year ago
private final int heightMapID;
private final int maxHeight;
private final int fullExtentsX;
private final int fullExtentsY;
private float bucketWidthX;
private float bucketWidthY;
1 year ago
private final int zoneLoadID;
private float seaLevel = 0;
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");
// Cache the full extents to avoid the calculation
this.fullExtentsX = halfExtentsX * 2;
this.fullExtentsY = halfExtentsY * 2;
this.heightmapImage = null;
File imageFile = new File(ConfigManager.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) {
1 year ago
Logger.error("***Error loading heightmap data for heightmap " + this.heightMapID + e);
}
// Calculate the data we do not load from table
float numOfBucketsX = this.heightmapImage.getWidth() - 1;
float calculatedWidthX = this.fullExtentsX / numOfBucketsX;
this.bucketWidthX = calculatedWidthX;
float numOfBucketsY = this.heightmapImage.getHeight() - 1;
float calculatedWidthY = this.fullExtentsY / numOfBucketsY;
this.bucketWidthX = calculatedWidthY;
// Generate pixel array from image data
generatePixelData(this);
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;
// Cache the full extents to avoid the calculation
this.fullExtentsX = halfExtentsX * 2;
this.fullExtentsY = halfExtentsY * 2;
this.heightmapImage = null;
// Calculate the data we do not load from table
this.bucketWidthX = halfExtentsX;
this.bucketWidthY = halfExtentsY;
this.pixelColorValues = new int[this.fullExtentsX][this.fullExtentsY];
for (int y = 0; y < this.fullExtentsY; y++) {
for (int x = 0; x < this.fullExtentsX; x++) {
pixelColorValues[x][y] = 255;
}
}
}
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;
// Cache the full extents to avoid the calculation
this.fullExtentsX = halfExtentsX * 2;
this.fullExtentsY = halfExtentsY * 2;
this.heightmapImage = null;
// Calculate the data we do not load from table
this.bucketWidthX = halfExtentsX;
this.bucketWidthY = halfExtentsY;
this.pixelColorValues = new int[this.fullExtentsX][this.fullExtentsY];
for (int y = 0; y < this.fullExtentsY; y++) {
for (int x = 0; x < this.fullExtentsX; x++) {
pixelColorValues[x][y] = 0;
}
}
}
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 static Zone getNextZoneWithTerrain(Zone zone) {
Zone nextZone = zone;
if (zone.getHeightMap() != null)
return zone;
if (zone.equals(ZoneManager.getSeaFloor()))
return zone;
while (nextZone.getHeightMap() == null)
nextZone = nextZone.getParent();
return nextZone;
}
public static float getWorldHeight(Zone currentZone, Vector3fImmutable worldLoc) {
if (currentZone == null)
return 0;
currentZone = getNextZoneWithTerrain(currentZone);
if (currentZone == ZoneManager.getSeaFloor())
return currentZone.getAbsY();
HeightMap heightMap = currentZone.getHeightMap();
if ((heightMap == null) || (currentZone == ZoneManager.getSeaFloor()))
return currentZone.getAbsY();
Vector2f zoneLoc = ZoneManager.worldToZoneSpace(worldLoc, currentZone);
float interaltitude = currentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
float worldAltitude = currentZone.worldAltitude;
float realWorldAltitude = interaltitude + worldAltitude;
return realWorldAltitude;
}
public static float getWorldHeight(Vector3fImmutable worldLoc) {
Zone currentZone = ZoneManager.findSmallestZone(worldLoc);
if (currentZone == null)
return 0;
return getWorldHeight(currentZone, worldLoc);
}
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 static boolean isLocUnderwater(Vector3fImmutable currentLoc) {
float localAltitude = HeightMap.getWorldHeight(currentLoc);
Zone zone = ZoneManager.findSmallestZone(currentLoc);
1 year ago
return localAltitude < zone.getSeaLevel();
}
private static void generatePixelData(HeightMap heightMap) {
Color color;
// Generate altitude lookup table for this heightmap
heightMap.pixelColorValues = new int[heightMap.heightmapImage.getWidth()][heightMap.heightmapImage.getHeight()];
for (int y = 0; y < heightMap.heightmapImage.getHeight(); y++) {
for (int x = 0; x < heightMap.heightmapImage.getWidth(); x++) {
color = new Color(heightMap.heightmapImage.getRGB(x, y));
heightMap.pixelColorValues[x][y] = color.getRed();
}
}
}
public Vector2f getGridSquare(Vector2f zoneLoc) {
if (zoneLoc.x < 0)
zoneLoc.setX(0);
if (zoneLoc.x >= this.fullExtentsX)
zoneLoc.setX(this.fullExtentsX);
if (zoneLoc.y < 0)
zoneLoc.setY(0);
if (zoneLoc.y > this.fullExtentsY)
zoneLoc.setY(this.fullExtentsY);
// Flip Y coordinates
zoneLoc.setY(this.fullExtentsY - zoneLoc.y);
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);
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 * SCALEVALUE; // Scale height
return interpolatedHeight;
}
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;
}
}