forked from MagicBane/Server
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.
226 lines
8.3 KiB
226 lines
8.3 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 blend_ratio = new Vector2f(); |
|
public int heightmap; |
|
Zone zone; |
|
|
|
public Terrain(Zone zone) { |
|
|
|
this.zone = zone; |
|
this.heightmap = this.zone.template.terrain_image; |
|
|
|
// Configure PLANAR zones to use the same 16x16 pixel image |
|
// that all similar terrains share. (See JSON) |
|
|
|
if (this.zone.template.terrain_type.equals("PLANAR")) |
|
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.template.major_radius * 2; |
|
this.terrain_size.y = this.zone.template.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; |
|
|
|
// Blending configuration. These ratios are used to calculate |
|
// the blending area between child and parent terrains when |
|
// they are stitched together. |
|
|
|
Vector2f major_blend = new Vector2f(this.zone.template.max_blend / this.zone.template.major_radius, |
|
this.zone.template.min_blend / this.zone.template.major_radius); |
|
|
|
Vector2f minor_blend = new Vector2f(this.zone.template.max_blend / this.zone.template.minor_radius, |
|
this.zone.template.min_blend / this.zone.template.minor_radius); |
|
|
|
if (major_blend.y > 0.4f) |
|
blend_ratio.x = major_blend.y; |
|
else |
|
blend_ratio.x = Math.min(major_blend.x, 0.4f); |
|
|
|
if (minor_blend.y > 0.4f) |
|
blend_ratio.y = minor_blend.y; |
|
else |
|
blend_ratio.y = Math.min(minor_blend.x, 0.4f); |
|
|
|
// Scale coefficient for this terrain |
|
|
|
this.terrain_scale = this.zone.template.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 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 |
|
|
|
float blendCoefficient = terrainZone.terrain.getTerrainBlendCoefficient(terrainOffset); |
|
|
|
float terrainHeight = interpolatedChildHeight * blendCoefficient; |
|
terrainHeight += interpolatedParentTerrainHeight * (1 - blendCoefficient); |
|
|
|
return terrainHeight; |
|
|
|
} |
|
|
|
public static float getWorldHeight(Vector3fImmutable world_loc) { |
|
|
|
Zone currentZone = ZoneManager.findSmallestZone(world_loc); |
|
|
|
return getWorldHeight(currentZone, world_loc); |
|
|
|
} |
|
|
|
public Vector2f getTerrainCell(Vector2f terrain_loc) { |
|
|
|
// 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; |
|
|
|
// Early exit for guild zones |
|
|
|
if (this.zone.guild_zone) |
|
return 5.0f; |
|
|
|
// Determine terrain and offset from top left vertex |
|
|
|
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); |
|
|
|
// 4 surrounding vertices from the pixel array. |
|
|
|
short top_left_pixel = terrain_pixel_data[pixel_x][pixel_y]; |
|
short top_right_pixel = terrain_pixel_data[pixel_x + 1][pixel_y]; |
|
short bottom_left_pixel = terrain_pixel_data[pixel_x][pixel_y + 1]; |
|
short 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 getTerrainBlendCoefficient(Vector2f zone_offset) { |
|
|
|
// Normalize terrain offset |
|
|
|
Vector2f normalizedOffset = new Vector2f(Math.abs(zone_offset.x) / this.zone.template.major_radius, |
|
Math.abs(zone_offset.y) / this.zone.template.minor_radius); |
|
|
|
float blendCoefficient; |
|
|
|
if (normalizedOffset.x <= 1 - blend_ratio.x || normalizedOffset.x <= normalizedOffset.y) { |
|
|
|
if (normalizedOffset.y < 1 - blend_ratio.y) |
|
return 1; |
|
|
|
blendCoefficient = (normalizedOffset.y - (1 - blend_ratio.y)) / blend_ratio.y; |
|
} else |
|
blendCoefficient = (normalizedOffset.x - (1 - blend_ratio.x)) / blend_ratio.x; |
|
|
|
blendCoefficient = (float) Math.atan((0.5f - blendCoefficient) * PI); |
|
|
|
return (blendCoefficient + 1) * 0.5f; |
|
} |
|
}
|
|
|