|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
// Magicbane Emulator Project © 2013 - 2022
|
|
|
|
// www.magicbane.com
|
|
|
|
|
|
|
|
package engine.InterestManagement;
|
|
|
|
|
|
|
|
import engine.gameManager.ZoneManager;
|
|
|
|
import engine.math.Vector2f;
|
|
|
|
import engine.math.Vector3fImmutable;
|
|
|
|
import engine.objects.Zone;
|
|
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
|
|
|
import static java.lang.Math.PI;
|
|
|
|
|
|
|
|
public class Terrain {
|
|
|
|
|
|
|
|
// Class variables
|
|
|
|
|
|
|
|
public static final HashMap<Integer, short[][]> _heightmap_pixel_cache = new HashMap<>();
|
|
|
|
public short[][] terrain_pixel_data;
|
|
|
|
public Vector2f terrain_size = new Vector2f();
|
|
|
|
public Vector2f cell_size = new Vector2f();
|
|
|
|
public Vector2f cell_count = new Vector2f();
|
|
|
|
public float terrain_scale;
|
|
|
|
public float min_blend;
|
|
|
|
public float max_blend;
|
|
|
|
public int heightmap;
|
|
|
|
Zone zone;
|
|
|
|
|
|
|
|
public Terrain(Zone zone) {
|
|
|
|
|
|
|
|
this.zone = zone;
|
|
|
|
|
|
|
|
this.heightmap = this.zone.terrain_image;
|
|
|
|
|
|
|
|
// Configure PLANAR zones to use the same
|
|
|
|
// 16x16 pixel image that all other flat
|
|
|
|
// terrains share.
|
|
|
|
|
|
|
|
if (this.zone.terrain_type.equals("PLANAR"))
|
|
|
|
this.heightmap = 1006300;
|
|
|
|
|
|
|
|
// Load pixel data for this terrain from cache
|
|
|
|
|
|
|
|
this.terrain_pixel_data = Terrain._heightmap_pixel_cache.get(heightmap);
|
|
|
|
|
|
|
|
if (terrain_pixel_data == null)
|
|
|
|
Logger.error("Pixel map empty for zone: " + this.zone.getObjectUUID() + ":" + this.zone.zoneName);
|
|
|
|
|
|
|
|
// Configure terrain based on zone properties
|
|
|
|
|
|
|
|
this.terrain_size.x = this.zone.major_radius * 2;
|
|
|
|
this.terrain_size.y = this.zone.minor_radius * 2;
|
|
|
|
|
|
|
|
this.cell_count.x = this.terrain_pixel_data.length - 1;
|
|
|
|
this.cell_count.y = this.terrain_pixel_data[0].length - 1;
|
|
|
|
|
|
|
|
this.cell_size.x = terrain_size.x / this.cell_count.x;
|
|
|
|
this.cell_size.y = terrain_size.y / this.cell_count.y;
|
|
|
|
|
|
|
|
this.max_blend = this.zone.max_blend / this.zone.major_radius;
|
|
|
|
this.min_blend = this.zone.min_blend / this.zone.minor_radius;
|
|
|
|
|
|
|
|
this.terrain_scale = this.zone.terrain_max_y / 255f;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Zone getNextZoneWithTerrain(Zone zone) {
|
|
|
|
|
|
|
|
// Not all zones have a terrain. Some are for display only
|
|
|
|
// and heights returned are from the parent heightmap. This
|
|
|
|
// is controlled in the JSON via the has_terrain_gen field.
|
|
|
|
|
|
|
|
Zone terrain_zone = zone;
|
|
|
|
|
|
|
|
if (zone == null)
|
|
|
|
return ZoneManager.seaFloor;
|
|
|
|
|
|
|
|
if (zone.terrain != null)
|
|
|
|
return zone;
|
|
|
|
|
|
|
|
if (zone.equals(ZoneManager.seaFloor))
|
|
|
|
return zone;
|
|
|
|
|
|
|
|
while (terrain_zone.terrain == null)
|
|
|
|
terrain_zone = terrain_zone.parent;
|
|
|
|
|
|
|
|
return terrain_zone;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static float getWorldHeight(Zone currentZone, Vector3fImmutable worldLoc) {
|
|
|
|
|
|
|
|
Zone terrainZone;
|
|
|
|
|
|
|
|
// Retrieve the next zone with a terrain defined.
|
|
|
|
|
|
|
|
terrainZone = getNextZoneWithTerrain(currentZone);
|
|
|
|
|
|
|
|
// Transform world loc into zone space coordinate system
|
|
|
|
|
|
|
|
Vector2f terrainLoc = ZoneManager.worldToZoneSpace(worldLoc, terrainZone);
|
|
|
|
|
|
|
|
// Interpolate height for this position in terrain
|
|
|
|
|
|
|
|
float interpolatedChildHeight = terrainZone.terrain.getInterpolatedTerrainHeight(terrainLoc);
|
|
|
|
interpolatedChildHeight += terrainZone.worldAltitude;
|
|
|
|
|
|
|
|
return interpolatedChildHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static float getWorldHeight(Vector3fImmutable worldLoc) {
|
|
|
|
|
|
|
|
Zone currentZone = ZoneManager.findSmallestZone(worldLoc);
|
|
|
|
|
|
|
|
if (currentZone == null)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return getWorldHeight(currentZone, worldLoc);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public Vector2f getTerrainCell(Vector2f terrainLoc) {
|
|
|
|
|
|
|
|
Vector2f terrain_cell = new Vector2f(terrainLoc.x / this.cell_size.x, terrainLoc.y / this.cell_size.y);
|
|
|
|
|
|
|
|
// Clamp values when standing directly on pole
|
|
|
|
|
|
|
|
terrain_cell.x = Math.max(0, Math.min(this.cell_count.x - 1, terrain_cell.x));
|
|
|
|
terrain_cell.y = Math.max(0, Math.min(this.cell_count.y - 1, terrain_cell.y));
|
|
|
|
|
|
|
|
return terrain_cell;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getInterpolatedTerrainHeight(Vector2f terrainLoc) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
float interpolatedHeight;
|
|
|
|
|
|
|
|
Vector2f terrain_cell = getTerrainCell(terrainLoc);
|
|
|
|
|
|
|
|
int pixel_x = (int) Math.floor(terrain_cell.x);
|
|
|
|
int pixel_t = (int) Math.floor(terrain_cell.y);
|
|
|
|
|
|
|
|
Vector2f pixel_offset = new Vector2f(terrain_cell.x % 1, terrain_cell.y % 1);
|
|
|
|
|
|
|
|
//get 4 surrounding vertices from the pixel array.
|
|
|
|
|
|
|
|
float topLeftHeight;
|
|
|
|
float topRightHeight;
|
|
|
|
float bottomLeftHeight;
|
|
|
|
float bottomRightHeight;
|
|
|
|
|
|
|
|
topLeftHeight = terrain_pixel_data[pixel_x][pixel_t];
|
|
|
|
topRightHeight = terrain_pixel_data[pixel_x + 1][pixel_t];
|
|
|
|
bottomLeftHeight = terrain_pixel_data[pixel_x][pixel_t + 1];
|
|
|
|
bottomRightHeight = terrain_pixel_data[pixel_x + 1][pixel_t + 1];
|
|
|
|
|
|
|
|
// Interpolate between the 4 vertices
|
|
|
|
|
|
|
|
interpolatedHeight = topLeftHeight * (1 - pixel_offset.x) * (1 - pixel_offset.y);
|
|
|
|
interpolatedHeight += topRightHeight * (1 - pixel_offset.y) * (pixel_offset.x);
|
|
|
|
interpolatedHeight += (bottomLeftHeight * (1 - pixel_offset.x) * pixel_offset.y);
|
|
|
|
interpolatedHeight += (bottomRightHeight * pixel_offset.y * pixel_offset.x);
|
|
|
|
|
|
|
|
interpolatedHeight *= this.terrain_scale; // Scale height
|
|
|
|
|
|
|
|
return interpolatedHeight;
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
Logger.error(this.zone.zoneName + ":" + this.zone.getObjectUUID() + e);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public float terrainBlend(Vector2f terrainLoc) {
|
|
|
|
|
|
|
|
// Normalize terrain loc
|
|
|
|
|
|
|
|
Vector2f normalizedLoc = new Vector2f(terrainLoc.x / this.terrain_size.x,
|
|
|
|
terrainLoc.y / terrain_size.y);
|
|
|
|
|
|
|
|
float minp = this.zone.min_blend / this.zone.major_radius;
|
|
|
|
float maxp = this.zone.max_blend / this.zone.major_radius;
|
|
|
|
|
|
|
|
float minpy = this.zone.min_blend / this.zone.minor_radius;
|
|
|
|
float maxpy = this.zone.max_blend / this.zone.minor_radius;
|
|
|
|
|
|
|
|
float xval;
|
|
|
|
|
|
|
|
if (minp > 0.4f)
|
|
|
|
xval = minp;
|
|
|
|
else
|
|
|
|
xval = Math.min(maxp, 0.4f);
|
|
|
|
|
|
|
|
float yval;
|
|
|
|
|
|
|
|
if (minpy > 0.4f)
|
|
|
|
yval = minpy;
|
|
|
|
else
|
|
|
|
yval = Math.min(maxpy, 0.4f);
|
|
|
|
|
|
|
|
float value;
|
|
|
|
|
|
|
|
if (normalizedLoc.x <= 1 - xval || normalizedLoc.x <= normalizedLoc.y) {
|
|
|
|
|
|
|
|
if (normalizedLoc.y < 1 - yval)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
value = (normalizedLoc.y - (1 - yval)) / yval;
|
|
|
|
} else
|
|
|
|
value = (normalizedLoc.x - (1 - xval)) / xval;
|
|
|
|
|
|
|
|
value = (float) Math.atan((0.5f - value) * PI);
|
|
|
|
|
|
|
|
return (value + 1) * 0.5f;
|
|
|
|
}
|
|
|
|
}
|