Start terrain refactor
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// 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.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class Terrain {
|
||||
|
||||
// Class variables
|
||||
|
||||
public static final HashMap<Integer, Terrain> heightmapByLoadNum = new HashMap<>();
|
||||
|
||||
public static final HashMap<Integer, short[][]> _pixelData = new HashMap<>();
|
||||
|
||||
// Bootstrap Tracking
|
||||
public static int heightMapsCreated = 0;
|
||||
public static Terrain playerCityTerrain;
|
||||
|
||||
// Heightmap data for this heightmap
|
||||
public final int heightMapID;
|
||||
public final int maxHeight;
|
||||
public final int fullExtentsX;
|
||||
public final int fullExtentsY;
|
||||
public final int zoneLoadID;
|
||||
public BufferedImage heightmapImage;
|
||||
public float cell_size_x;
|
||||
public float cell_size_y;
|
||||
public float seaLevel = 0;
|
||||
public short[][] pixelColorValues;
|
||||
public int cell_count_x;
|
||||
public int cell_count_y;
|
||||
public float zone_minBlend;
|
||||
public float zone_maxBlend;
|
||||
|
||||
public float terrain_scale;
|
||||
|
||||
public Terrain(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");
|
||||
|
||||
// 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) {
|
||||
Logger.error("***Error loading heightmap data for heightmap " + this.heightMapID + e);
|
||||
}
|
||||
|
||||
// Calculate the data we do not load from table
|
||||
|
||||
this.cell_count_x = this.heightmapImage.getWidth() - 1;
|
||||
this.cell_size_x = this.fullExtentsX / (float) cell_count_x;
|
||||
|
||||
this.cell_count_y = this.heightmapImage.getHeight() - 1;
|
||||
this.cell_size_y = this.fullExtentsY / (float) cell_count_y;
|
||||
|
||||
this.terrain_scale = this.maxHeight / 255f;
|
||||
|
||||
// Generate pixel array from image data
|
||||
|
||||
generatePixelData(this);
|
||||
|
||||
Terrain.heightmapByLoadNum.put(this.zoneLoadID, this);
|
||||
|
||||
heightMapsCreated++;
|
||||
}
|
||||
|
||||
//Created for PlayerCities
|
||||
public Terrain() {
|
||||
|
||||
this.heightMapID = 999999;
|
||||
this.maxHeight = 5; // for real...
|
||||
int halfExtentsX = (int) Enum.CityBoundsType.ZONE.halfExtents;
|
||||
int halfExtentsY = (int) Enum.CityBoundsType.ZONE.halfExtents;
|
||||
this.zoneLoadID = 0;
|
||||
this.seaLevel = 0;
|
||||
this.zone_minBlend = 0;
|
||||
this.zone_maxBlend = 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.cell_size_x = halfExtentsX;
|
||||
this.cell_size_y = halfExtentsY;
|
||||
|
||||
this.pixelColorValues = new short[this.fullExtentsX][this.fullExtentsY];
|
||||
|
||||
for (int y = 0; y < this.fullExtentsY; y++) {
|
||||
for (int x = 0; x < this.fullExtentsX; x++) {
|
||||
pixelColorValues[x][y] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
cell_count_x = this.pixelColorValues.length - 1;
|
||||
cell_count_y = this.pixelColorValues[0].length - 1;
|
||||
this.terrain_scale = this.maxHeight / 255f;
|
||||
}
|
||||
|
||||
public Terrain(Zone zone) {
|
||||
|
||||
this.heightMapID = 999999;
|
||||
this.maxHeight = 0;
|
||||
int halfExtentsX = (int) zone.bounds.getHalfExtents().x;
|
||||
int halfExtentsY = (int) zone.bounds.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.cell_size_x = halfExtentsX;
|
||||
this.cell_size_y = halfExtentsY;
|
||||
|
||||
this.pixelColorValues = new short[this.fullExtentsX][this.fullExtentsY];
|
||||
|
||||
for (int y = 0; y < this.fullExtentsY; y++) {
|
||||
for (int x = 0; x < this.fullExtentsX; x++) {
|
||||
pixelColorValues[x][y] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
cell_count_x = this.pixelColorValues.length - 1;
|
||||
cell_count_y = this.pixelColorValues[0].length - 1;
|
||||
this.terrain_scale = this.maxHeight / 255f;
|
||||
}
|
||||
|
||||
public static void GeneratePlayerCityHeightMap() {
|
||||
|
||||
Terrain.playerCityTerrain = new Terrain();
|
||||
|
||||
}
|
||||
|
||||
public static void GenerateCustomHeightMap(Zone zone) {
|
||||
|
||||
Terrain heightMap = new Terrain(zone);
|
||||
|
||||
Terrain.heightmapByLoadNum.put(zone.template, heightMap);
|
||||
|
||||
}
|
||||
|
||||
public static Zone getNextZoneWithTerrain(Zone zone) {
|
||||
|
||||
Zone terrain_zone = zone;
|
||||
|
||||
if (zone.getHeightMap() != null)
|
||||
return zone;
|
||||
|
||||
if (zone.equals(ZoneManager.getSeaFloor()))
|
||||
return zone;
|
||||
|
||||
while (terrain_zone.getHeightMap() == null)
|
||||
terrain_zone = terrain_zone.parent;
|
||||
|
||||
return terrain_zone;
|
||||
}
|
||||
|
||||
public static float getWorldHeight(Zone currentZone, Vector3fImmutable worldLoc) {
|
||||
|
||||
Zone terrainZone;
|
||||
|
||||
// Seafloor is rather flat.
|
||||
|
||||
if (currentZone == ZoneManager.getSeaFloor())
|
||||
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.
|
||||
|
||||
terrainZone = getNextZoneWithTerrain(currentZone);
|
||||
|
||||
// Transform world loc into zone space coordinate system
|
||||
|
||||
Vector2f terrainLoc = ZoneManager.worldToZoneSpace(worldLoc, terrainZone);
|
||||
|
||||
// Interpolate height for this position using pixel array.
|
||||
|
||||
float interpolatedTerrainHeight = terrainZone.getHeightMap().getInterpolatedTerrainHeight(terrainLoc);
|
||||
interpolatedTerrainHeight += terrainZone.worldAltitude;
|
||||
|
||||
return interpolatedTerrainHeight;
|
||||
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
Terrain.GeneratePlayerCityHeightMap();
|
||||
|
||||
Logger.info(Terrain.heightmapByLoadNum.size() + " Heightmaps cached.");
|
||||
|
||||
// Load pixel data for heightmaps
|
||||
|
||||
try (Stream<Path> filePathStream = Files.walk(Paths.get(ConfigManager.DEFAULT_DATA_DIR + "heightmaps/TARGA/"))) {
|
||||
filePathStream.forEach(filePath -> {
|
||||
|
||||
if (Files.isRegularFile(filePath)) {
|
||||
File imageFile = filePath.toFile();
|
||||
|
||||
try {
|
||||
BufferedImage heightmapImage = ImageIO.read(imageFile);
|
||||
|
||||
// Generate pixel data for this heightmap. RPG channels are all the same
|
||||
// in this greyscale TGA heightmap. We will choose red.
|
||||
|
||||
short[][] colorData = new short[heightmapImage.getWidth()][heightmapImage.getHeight()];
|
||||
|
||||
for (int y = 0; y < heightmapImage.getHeight(); y++)
|
||||
for (int x = 0; x < heightmapImage.getWidth(); x++) {
|
||||
|
||||
Color color = new Color(heightmapImage.getRGB(x, y));
|
||||
colorData[x][y] = (short) color.getRed();
|
||||
}
|
||||
|
||||
// Insert color data into lookup table
|
||||
|
||||
int heightMapID = Integer.parseInt(imageFile.getName().substring(0, imageFile.getName().lastIndexOf(".")));
|
||||
_pixelData.put(heightMapID, colorData);
|
||||
|
||||
} catch (IOException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
}); // Try with resources block
|
||||
|
||||
} catch (IOException e) {
|
||||
Logger.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void generatePixelData(Terrain terrain) {
|
||||
|
||||
Color color;
|
||||
|
||||
// Generate altitude lookup table for this heightmap
|
||||
|
||||
terrain.pixelColorValues = new short[terrain.heightmapImage.getWidth()][terrain.heightmapImage.getHeight()];
|
||||
|
||||
for (int y = 0; y < terrain.heightmapImage.getHeight(); y++) {
|
||||
for (int x = 0; x < terrain.heightmapImage.getWidth(); x++) {
|
||||
|
||||
color = new Color(terrain.heightmapImage.getRGB(x, y));
|
||||
terrain.pixelColorValues[x][y] = (short) color.getRed();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Vector2f getTerrainCell(Vector2f terrainLoc) {
|
||||
|
||||
float terrainCell_x = terrainLoc.x / this.cell_size_x;
|
||||
float terrainCell_y = terrainLoc.y / this.cell_size_y;
|
||||
|
||||
// Clamp values when standing directly on max pole
|
||||
|
||||
if (terrainCell_x >= this.cell_count_x)
|
||||
terrainCell_x = terrainCell_x - 1;
|
||||
|
||||
if (terrainCell_y >= this.cell_count_y)
|
||||
terrainCell_y = terrainCell_x - 1;
|
||||
|
||||
return new Vector2f(terrainCell_x, terrainCell_y);
|
||||
}
|
||||
|
||||
public float getInterpolatedTerrainHeight(Vector2f terrainLoc) {
|
||||
|
||||
float interpolatedHeight;
|
||||
|
||||
Vector2f terrain_cell = getTerrainCell(terrainLoc);
|
||||
|
||||
int gridX = (int) Math.floor(terrain_cell.x);
|
||||
int gridY = (int) Math.floor(terrain_cell.y);
|
||||
|
||||
float offsetX = terrain_cell.x % 1;
|
||||
float offsetY = terrain_cell.y % 1;
|
||||
|
||||
//get 4 surrounding vertices from the pixel array.
|
||||
|
||||
float topLeftHeight;
|
||||
float topRightHeight;
|
||||
float bottomLeftHeight;
|
||||
float bottomRightHeight;
|
||||
|
||||
topLeftHeight = pixelColorValues[gridX][gridY];
|
||||
topRightHeight = pixelColorValues[gridX + 1][gridY];
|
||||
bottomLeftHeight = pixelColorValues[gridX][gridY + 1];
|
||||
bottomRightHeight = pixelColorValues[gridX + 1][gridY + 1];
|
||||
|
||||
// Interpolate between the 4 vertices
|
||||
|
||||
interpolatedHeight = topRightHeight * (1 - offsetY) * (offsetX);
|
||||
interpolatedHeight += (bottomRightHeight * offsetY * offsetX);
|
||||
interpolatedHeight += (bottomLeftHeight * (1 - offsetX) * offsetY);
|
||||
interpolatedHeight += (topLeftHeight * (1 - offsetX) * (1 - offsetY));
|
||||
|
||||
interpolatedHeight *= this.terrain_scale; // Scale height
|
||||
|
||||
return interpolatedHeight;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user