Files
lakebane/src/engine/InterestManagement/HeightMap.java
T

379 lines
12 KiB
Java
Raw Normal View History

2022-04-30 09:41:17 -04:00
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.InterestManagement;
import engine.Enum;
2023-06-07 14:53:15 -04:00
import engine.gameManager.ConfigManager;
2022-04-30 09:41:17 -04:00
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;
2023-09-20 13:37:43 -04:00
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
2022-04-30 09:41:17 -04:00
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
2023-09-20 13:37:43 -04:00
import java.util.stream.Stream;
2022-04-30 09:41:17 -04:00
public class HeightMap {
// Class variables
public static final HashMap<Integer, HeightMap> heightmapByLoadNum = new HashMap<>();
2023-09-20 11:59:43 -04:00
2023-10-08 09:04:15 -04:00
public static final HashMap<Integer, short[][]> _pixelData = new HashMap<>();
2022-04-30 09:41:17 -04:00
// Bootstrap Tracking
public static int heightMapsCreated = 0;
public static HeightMap PlayerCityHeightMap;
// Heightmap data for this heightmap
2023-09-17 12:53:56 -04:00
public final int heightMapID;
public final int maxHeight;
public final int fullExtentsX;
public final int fullExtentsY;
2023-09-17 14:00:00 -04:00
public final int zoneLoadID;
public BufferedImage heightmapImage;
2023-10-08 09:04:15 -04:00
public float cell_size_x;
public float cell_size_y;
2023-09-17 12:53:56 -04:00
public float seaLevel = 0;
2023-10-08 09:04:15 -04:00
public short[][] pixelColorValues;
public int cell_count_x;
public int cell_count_y;
2023-09-17 07:42:46 -04:00
public float zone_minBlend;
public float zone_maxBlend;
2023-10-08 09:04:15 -04:00
public float terrain_scale;
2022-04-30 09:41:17 -04:00
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.zone_minBlend = rs.getFloat("outsetZ");
this.zone_maxBlend = rs.getFloat("outsetX");
2022-04-30 09:41:17 -04:00
// Cache the full extents to avoid the calculation
this.fullExtentsX = halfExtentsX * 2;
this.fullExtentsY = halfExtentsY * 2;
this.heightmapImage = null;
2023-06-07 14:53:15 -04:00
File imageFile = new File(ConfigManager.DEFAULT_DATA_DIR + "heightmaps/" + this.heightMapID + ".bmp");
2022-04-30 09:41:17 -04:00
// 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) {
2023-09-14 12:29:26 -04:00
Logger.error("***Error loading heightmap data for heightmap " + this.heightMapID + e);
2022-04-30 09:41:17 -04:00
}
// Calculate the data we do not load from table
2023-10-08 09:04:15 -04:00
this.cell_count_x = this.heightmapImage.getWidth() - 1;
this.cell_size_x = this.fullExtentsX / (float) cell_count_x;
2023-09-16 08:01:46 -04:00
2023-10-08 09:04:15 -04:00
this.cell_count_y = this.heightmapImage.getHeight() - 1;
this.cell_size_y = this.fullExtentsY / (float) cell_count_y;
this.terrain_scale = this.maxHeight / 255f;
2022-04-30 09:41:17 -04:00
// Generate pixel array from image data
2023-09-14 12:28:36 -04:00
generatePixelData(this);
2022-04-30 09:41:17 -04:00
HeightMap.heightmapByLoadNum.put(this.zoneLoadID, this);
heightMapsCreated++;
}
//Created for PlayerCities
public HeightMap() {
this.heightMapID = 999999;
this.maxHeight = 5; // for real...
2023-09-17 12:23:06 -04:00
int halfExtentsX = (int) Enum.CityBoundsType.ZONE.halfExtents;
int halfExtentsY = (int) Enum.CityBoundsType.ZONE.halfExtents;
2022-04-30 09:41:17 -04:00
this.zoneLoadID = 0;
this.seaLevel = 0;
2023-09-18 00:05:53 -04:00
this.zone_minBlend = 0;
this.zone_maxBlend = 0;
2022-04-30 09:41:17 -04:00
// 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
2023-10-08 09:04:15 -04:00
this.cell_size_x = halfExtentsX;
this.cell_size_y = halfExtentsY;
2022-04-30 09:41:17 -04:00
2023-10-08 09:04:15 -04:00
this.pixelColorValues = new short[this.fullExtentsX][this.fullExtentsY];
2022-04-30 09:41:17 -04:00
2023-09-11 14:06:22 -04:00
for (int y = 0; y < this.fullExtentsY; y++) {
for (int x = 0; x < this.fullExtentsX; x++) {
2023-09-12 15:33:52 -04:00
pixelColorValues[x][y] = 255;
2022-04-30 09:41:17 -04:00
}
}
2023-10-08 09:04:15 -04:00
cell_count_x = this.pixelColorValues.length - 1;
cell_count_y = this.pixelColorValues[0].length - 1;
this.terrain_scale = this.maxHeight / 255f;
2022-04-30 09:41:17 -04:00
}
2023-07-15 09:23:48 -04:00
2022-04-30 09:41:17 -04:00
public HeightMap(Zone zone) {
this.heightMapID = 999999;
this.maxHeight = 0;
2023-09-20 15:43:01 -04:00
int halfExtentsX = (int) zone.bounds.getHalfExtents().x;
int halfExtentsY = (int) zone.bounds.getHalfExtents().y;
2022-04-30 09:41:17 -04:00
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
2023-10-08 09:04:15 -04:00
this.cell_size_x = halfExtentsX;
this.cell_size_y = halfExtentsY;
2022-04-30 09:41:17 -04:00
2023-10-08 09:04:15 -04:00
this.pixelColorValues = new short[this.fullExtentsX][this.fullExtentsY];
2022-04-30 09:41:17 -04:00
2023-09-11 14:06:22 -04:00
for (int y = 0; y < this.fullExtentsY; y++) {
for (int x = 0; x < this.fullExtentsX; x++) {
2023-09-11 13:40:47 -04:00
pixelColorValues[x][y] = 0;
2022-04-30 09:41:17 -04:00
}
}
2023-10-08 09:04:15 -04:00
cell_count_x = this.pixelColorValues.length - 1;
cell_count_y = this.pixelColorValues[0].length - 1;
this.terrain_scale = this.maxHeight / 255f;
2022-04-30 09:41:17 -04:00
}
public static void GeneratePlayerCityHeightMap() {
HeightMap.PlayerCityHeightMap = new HeightMap();
}
2023-07-15 09:23:48 -04:00
2022-04-30 09:41:17 -04:00
public static void GenerateCustomHeightMap(Zone zone) {
HeightMap heightMap = new HeightMap(zone);
2023-09-20 16:06:57 -04:00
HeightMap.heightmapByLoadNum.put(zone.template, heightMap);
2022-04-30 09:41:17 -04:00
}
2023-03-22 05:41:39 -04:00
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)
2023-09-20 15:43:01 -04:00
nextZone = nextZone.parent;
2023-03-22 05:41:39 -04:00
return nextZone;
}
2023-09-11 11:01:09 -04:00
public static float getWorldHeight(Zone currentZone, Vector3fImmutable worldLoc) {
2022-04-30 09:41:17 -04:00
2023-09-16 13:18:36 -04:00
Zone heightMapZone;
2022-04-30 09:41:17 -04:00
2023-09-16 13:18:36 -04:00
// Seafloor is rather flat.
2022-04-30 09:41:17 -04:00
2023-03-22 05:41:39 -04:00
if (currentZone == ZoneManager.getSeaFloor())
2023-09-16 13:18:36 -04:00
return currentZone.worldAltitude;
// Retrieve the next zone with a heightmap attached.
// Zones without a heightmap use the next zone up the
// tree to calculate heights from.
heightMapZone = getNextZoneWithTerrain(currentZone);
2023-03-22 05:41:39 -04:00
2023-09-16 13:18:36 -04:00
// Transform world loc into zone space coordinate system
2022-04-30 09:41:17 -04:00
2023-09-16 13:18:36 -04:00
Vector2f zoneLoc = ZoneManager.worldToZoneSpace(worldLoc, heightMapZone);
2023-09-11 11:01:09 -04:00
2023-09-16 13:18:36 -04:00
// Interpolate height for this position using pixel array.
2022-04-30 09:41:17 -04:00
2023-09-16 13:18:36 -04:00
float interpolatedTerrainHeight = heightMapZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc);
2023-09-18 03:19:00 -04:00
interpolatedTerrainHeight += heightMapZone.worldAltitude;
2022-04-30 09:41:17 -04:00
2023-10-07 20:04:26 -04:00
return interpolatedTerrainHeight;
2023-09-20 11:40:29 -04:00
}
2022-04-30 09:41:17 -04:00
public static float getWorldHeight(Vector3fImmutable worldLoc) {
Zone currentZone = ZoneManager.findSmallestZone(worldLoc);
if (currentZone == null)
return 0;
2023-10-08 09:04:15 -04:00
2023-09-11 11:01:09 -04:00
return getWorldHeight(currentZone, worldLoc);
2022-04-30 09:41:17 -04:00
}
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();
2023-07-15 09:23:48 -04:00
2022-04-30 09:41:17 -04:00
Logger.info(HeightMap.heightmapByLoadNum.size() + " Heightmaps cached.");
2023-09-20 12:46:41 -04:00
2023-09-20 13:59:32 -04:00
// Load pixel data for heightmaps
2023-09-20 12:46:41 -04:00
2023-09-20 13:37:43 -04:00
try (Stream<Path> filePathStream = Files.walk(Paths.get(ConfigManager.DEFAULT_DATA_DIR + "heightmaps/TARGA/"))) {
2023-09-20 13:06:18 -04:00
filePathStream.forEach(filePath -> {
if (Files.isRegularFile(filePath)) {
File imageFile = filePath.toFile();
try {
BufferedImage heightmapImage = ImageIO.read(imageFile);
2023-09-20 13:59:32 -04:00
2023-09-20 17:20:54 -04:00
// Generate pixel data for this heightmap. RPG channels are all the same
2023-09-20 13:59:32 -04:00
// in this greyscale TGA heightmap. We will choose red.
2023-10-08 09:04:15 -04:00
short[][] colorData = new short[heightmapImage.getWidth()][heightmapImage.getHeight()];
2023-09-20 13:59:32 -04:00
2023-09-20 17:24:41 -04:00
for (int y = 0; y < heightmapImage.getHeight(); y++)
2023-09-20 13:59:32 -04:00
for (int x = 0; x < heightmapImage.getWidth(); x++) {
Color color = new Color(heightmapImage.getRGB(x, y));
2023-10-08 09:04:15 -04:00
colorData[x][y] = (short) color.getRed();
2023-09-20 13:59:32 -04:00
}
// Insert color data into lookup table
2023-09-20 16:24:00 -04:00
int heightMapID = Integer.parseInt(imageFile.getName().substring(0, imageFile.getName().lastIndexOf(".")));
_pixelData.put(heightMapID, colorData);
2023-09-20 13:59:32 -04:00
2023-09-20 13:06:18 -04:00
} catch (IOException e) {
2023-09-20 17:21:34 -04:00
Logger.error(e);
2023-09-20 13:06:18 -04:00
}
}
2023-09-20 17:26:00 -04:00
}); // Try with resources block
2023-09-20 12:46:41 -04:00
} catch (IOException e) {
Logger.error(e);
}
2023-09-20 13:37:43 -04:00
2022-04-30 09:41:17 -04:00
}
2023-09-14 12:29:26 -04:00
private static void generatePixelData(HeightMap heightMap) {
Color color;
// Generate altitude lookup table for this heightmap
2023-10-08 09:04:15 -04:00
heightMap.pixelColorValues = new short[heightMap.heightmapImage.getWidth()][heightMap.heightmapImage.getHeight()];
2023-09-14 12:29:26 -04:00
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));
2023-10-08 09:04:15 -04:00
heightMap.pixelColorValues[x][y] = (short) color.getRed();
2023-09-14 12:29:26 -04:00
}
}
2023-07-15 09:23:48 -04:00
}
public Vector2f getGridSquare(Vector2f zoneLoc) {
2023-10-08 09:04:15 -04:00
float xBucket = zoneLoc.x / this.cell_size_x;
float yBucket = zoneLoc.y / this.cell_size_y;
2023-07-15 09:23:48 -04:00
2023-10-08 09:04:15 -04:00
// Clamp values when standing directly on max pole
2023-07-15 09:23:48 -04:00
2023-10-08 09:04:15 -04:00
if (xBucket >= this.cell_count_x)
2023-10-07 20:55:11 -04:00
xBucket = xBucket - 1;
2023-10-07 20:47:35 -04:00
2023-10-08 09:04:15 -04:00
if (yBucket >= this.cell_count_y)
2023-10-07 20:55:11 -04:00
yBucket = xBucket - 1;
2023-10-07 20:47:35 -04:00
2023-07-15 09:23:48 -04:00
return new Vector2f(xBucket, yBucket);
}
2023-10-08 09:04:15 -04:00
public float getInterpolatedTerrainHeight(Vector2f terrainLoc) {
2023-07-15 09:23:48 -04:00
2023-09-20 14:20:22 -04:00
float interpolatedHeight;
2023-07-15 09:23:48 -04:00
2023-10-08 09:04:15 -04:00
Vector2f gridSquare = getGridSquare(terrainLoc);
2023-07-15 09:23:48 -04:00
2023-10-08 09:04:15 -04:00
int gridX = (int) Math.floor(gridSquare.x);
int gridY = (int) Math.floor(gridSquare.y);
float offsetX = gridSquare.x % 1;
float offsetY = gridSquare.y % 1;
2023-07-15 09:23:48 -04:00
2023-09-20 14:20:22 -04:00
//get 4 surrounding vertices from the pixel array.
2023-07-15 09:23:48 -04:00
2023-09-20 14:14:55 -04:00
float topLeftHeight;
float topRightHeight;
float bottomLeftHeight;
float bottomRightHeight;
2023-07-15 09:23:48 -04:00
topLeftHeight = pixelColorValues[gridX][gridY];
2023-09-20 14:20:22 -04:00
topRightHeight = pixelColorValues[gridX + 1][gridY];
bottomLeftHeight = pixelColorValues[gridX][gridY + 1];
bottomRightHeight = pixelColorValues[gridX + 1][gridY + 1];
2023-07-15 09:23:48 -04:00
2023-09-20 14:20:22 -04:00
// Interpolate between the 4 vertices
2023-07-15 09:23:48 -04:00
interpolatedHeight = topRightHeight * (1 - offsetY) * (offsetX);
interpolatedHeight += (bottomRightHeight * offsetY * offsetX);
interpolatedHeight += (bottomLeftHeight * (1 - offsetX) * offsetY);
interpolatedHeight += (topLeftHeight * (1 - offsetX) * (1 - offsetY));
2023-10-08 09:04:15 -04:00
interpolatedHeight *= this.terrain_scale; // Scale height
2023-07-15 09:23:48 -04:00
return interpolatedHeight;
}
2022-04-30 09:41:17 -04:00
}