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.

223 lines
7.8 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// 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 {
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 Vector2f major_blend = new Vector2f();
public Vector2f minor_blend = new Vector2f();
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 similar terrains share. (See JSON) Guild zones
// use an inverted clone of this heightmap.
if (this.zone.terrain_type.equals("PLANAR"))
if (this.zone.guild_zone)
this.heightmap = 1006301; // all 255
else
this.heightmap = 1006300; // all 0
// 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;
1 year ago
// Blending and height scaling configuration
1 year ago
this.major_blend.x = this.zone.max_blend / this.zone.major_radius;
this.major_blend.y = this.zone.min_blend / this.zone.major_radius;
this.minor_blend.x = this.zone.max_blend / this.zone.minor_radius;
this.minor_blend.y = 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)
1 year ago
return ZoneManager.seaFloor;
if (zone.terrain != null)
return zone;
1 year ago
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 zone, Vector3fImmutable world_loc) {
// Retrieve the next zone with a terrain defined.
Zone terrainZone = getNextZoneWithTerrain(zone);
Zone parentZone = getNextZoneWithTerrain(zone.parent);
// Transform world loc into zone space coordinate system
Vector2f terrainLoc = ZoneManager.worldToTerrainSpace(world_loc, terrainZone);
Vector2f parentLoc = ZoneManager.worldToTerrainSpace(world_loc, parentZone);
// Offset from origin needed for blending function
Vector2f terrainOffset = ZoneManager.worldToZoneOffset(world_loc, terrainZone);
// Interpolate height for this position in both terrains
float interpolatedChildHeight = terrainZone.terrain.getInterpolatedTerrainHeight(terrainLoc);
interpolatedChildHeight += terrainZone.global_height;
float interpolatedParentTerrainHeight = parentZone.terrain.getInterpolatedTerrainHeight(parentLoc);
interpolatedParentTerrainHeight += parentZone.global_height;
// Blend between terrains
1 year ago
return interpolatedChildHeight * (1 - terrainZone.terrain.terrainBlend(terrainOffset));
}
public static float getWorldHeight(Vector3fImmutable world_loc) {
Zone currentZone = ZoneManager.findSmallestZone(world_loc);
if (currentZone == null)
return 0;
return getWorldHeight(currentZone, world_loc);
}
public Vector2f getTerrainCell(Vector2f terrain_loc) {
1 year ago
// Calculate terrain cell with offset
Vector2f terrain_cell = new Vector2f(terrain_loc.x / this.cell_size.x, terrain_loc.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 terrain_loc) {
float interpolatedHeight;
Vector2f terrain_cell = getTerrainCell(terrain_loc);
int pixel_x = (int) Math.floor(terrain_cell.x);
int pixel_y = (int) Math.floor(terrain_cell.y);
Vector2f pixel_offset = new Vector2f(terrain_cell.x % 1, terrain_cell.y % 1);
1 year ago
// 4 surrounding vertices from the pixel array.
1 year ago
float top_left_pixel = terrain_pixel_data[pixel_x][pixel_y];
float top_right_pixel = terrain_pixel_data[pixel_x + 1][pixel_y];
float bottom_left_pixel = terrain_pixel_data[pixel_x][pixel_y + 1];
float bottom_right_pixel = terrain_pixel_data[pixel_x + 1][pixel_y + 1];
// Interpolate between the 4 vertices
interpolatedHeight = top_left_pixel * (1 - pixel_offset.x) * (1 - pixel_offset.y);
interpolatedHeight += top_right_pixel * (1 - pixel_offset.y) * (pixel_offset.x);
interpolatedHeight += (bottom_left_pixel * (1 - pixel_offset.x) * pixel_offset.y);
interpolatedHeight += (bottom_right_pixel * pixel_offset.y * pixel_offset.x);
interpolatedHeight *= this.terrain_scale; // Scale height
return interpolatedHeight;
}
public float terrainBlend(Vector2f zone_offset) {
// Normalize terrain offset
Vector2f normalizedOffset = new Vector2f(Math.abs(zone_offset.x) / this.zone.major_radius,
Math.abs(zone_offset.y) / this.zone.minor_radius);
float xval;
1 year ago
if (this.major_blend.y > 0.4f)
xval = this.major_blend.y;
else
1 year ago
xval = Math.min(this.major_blend.x, 0.4f);
float yval;
1 year ago
if (this.minor_blend.y > 0.4f)
yval = this.minor_blend.y;
else
1 year ago
yval = Math.min(this.minor_blend.x, 0.4f);
float value;
if (normalizedOffset.x <= 1 - xval || normalizedOffset.x <= normalizedOffset.y) {
if (normalizedOffset.y < 1 - yval)
return 1;
value = (normalizedOffset.y - (1 - yval)) / yval;
} else
value = (normalizedOffset.x - (1 - xval)) / xval;
value = (float) Math.atan((0.5f - value) * PI);
return (value + 1) * 0.5f;
}
}