Files
Server/src/engine/InterestManagement/Terrain.java
T

239 lines
8.6 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.gameManager.ZoneManager;
import engine.math.Vector2f;
import engine.math.Vector3fImmutable;
import engine.objects.Zone;
2023-10-08 22:44:34 -04:00
import org.pmw.tinylog.Logger;
2023-10-08 09:49:49 -04:00
2022-04-30 09:41:17 -04:00
import java.util.HashMap;
2023-10-09 07:05:54 -04:00
import static java.lang.Math.PI;
2024-04-26 08:09:07 -04:00
// MB Dev Notes:
// The Terrain class handles lookups into the Heightmap data.
// It supports all current maps along with the differences in
// their parenting configuration.
//
// Heightmap images are stored on disk in /mb.data/heightmaps/
2023-10-08 09:18:43 -04:00
public class Terrain {
2023-10-08 09:49:49 -04:00
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();
2023-10-08 09:04:15 -04:00
public float terrain_scale;
2023-10-25 13:01:21 -04:00
public Vector2f blend_values = new Vector2f();
2023-10-15 16:40:31 -04:00
public Vector2f blend_ratio = new Vector2f();
2023-10-08 22:36:26 -04:00
public int heightmap;
2023-10-09 07:05:54 -04:00
Zone zone;
2023-10-08 09:04:15 -04:00
2023-10-08 20:26:37 -04:00
public Terrain(Zone zone) {
2023-10-08 22:49:13 -04:00
this.zone = zone;
2023-10-20 17:01:42 -04:00
this.heightmap = this.zone.template.terrain_image;
2023-10-08 22:36:26 -04:00
2023-10-12 06:19:22 -04:00
// Configure PLANAR zones to use the same 16x16 pixel image
2023-10-15 16:46:48 -04:00
// that all similar terrains share. (See JSON)
2023-10-08 22:36:26 -04:00
2023-10-20 17:01:42 -04:00
if (this.zone.template.terrain_type.equals("PLANAR"))
2023-10-15 17:39:02 -04:00
this.heightmap = 1006300; // all 0
2023-10-08 22:36:26 -04:00
2023-10-11 11:32:43 -04:00
// Load pixel data for this terrain from cache
2023-10-08 20:26:37 -04:00
2023-10-08 22:40:31 -04:00
this.terrain_pixel_data = Terrain._heightmap_pixel_cache.get(heightmap);
2023-10-08 21:50:27 -04:00
2023-10-08 22:44:34 -04:00
if (terrain_pixel_data == null)
2023-10-11 11:32:43 -04:00
Logger.error("Pixel map empty for zone: " + this.zone.getObjectUUID() + ":" + this.zone.zoneName);
// Configure terrain based on zone properties
2023-10-20 17:22:57 -04:00
this.terrain_size.x = this.zone.major_radius * 2;
this.terrain_size.y = this.zone.minor_radius * 2;
2023-10-08 22:44:34 -04:00
2023-11-12 11:13:27 -05:00
this.cell_count.x = this.terrain_pixel_data.length - 1;
this.cell_count.y = this.terrain_pixel_data[0].length - 1;
2023-10-08 21:50:27 -04:00
this.cell_size.x = terrain_size.x / this.cell_count.x;
this.cell_size.y = terrain_size.y / this.cell_count.y;
2022-04-30 09:41:17 -04:00
2023-10-15 17:23:16 -04:00
// Blending configuration. These ratios are used to calculate
// the blending area between child and parent terrains when
2023-10-15 17:22:05 -04:00
// they are stitched together.
2023-10-11 17:00:01 -04:00
2023-10-25 13:01:21 -04:00
this.blend_values.x = this.zone.template.max_blend;
this.blend_values.y = this.zone.template.min_blend;
2023-10-11 17:00:01 -04:00
2023-10-25 13:01:21 -04:00
Vector2f major_blend = new Vector2f(this.blend_values.x / this.zone.major_radius,
this.blend_values.y / this.zone.major_radius);
2023-10-22 11:48:15 -04:00
2023-10-25 13:01:21 -04:00
Vector2f minor_blend = new Vector2f(this.blend_values.x / this.zone.minor_radius,
this.blend_values.y / this.zone.minor_radius);
2023-10-15 16:40:31 -04:00
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);
2023-10-09 07:05:54 -04:00
2023-10-15 17:23:16 -04:00
// Scale coefficient for this terrain
2023-11-12 11:21:30 -05:00
this.terrain_scale = this.zone.template.terrain_max_y / 255f;
2022-04-30 09:41:17 -04:00
}
2023-03-22 05:41:39 -04:00
public static Zone getNextZoneWithTerrain(Zone zone) {
2023-10-11 11:32:43 -04:00
// 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.
2023-10-08 09:09:50 -04:00
Zone terrain_zone = zone;
2023-03-22 05:41:39 -04:00
2023-10-09 06:14:06 -04:00
if (zone == null)
2023-10-09 06:16:25 -04:00
return ZoneManager.seaFloor;
2023-10-09 06:14:06 -04:00
2023-10-08 09:59:13 -04:00
if (zone.terrain != null)
2023-03-22 05:41:39 -04:00
return zone;
2023-10-09 06:16:25 -04:00
if (zone.equals(ZoneManager.seaFloor))
2023-03-22 05:41:39 -04:00
return zone;
2023-10-08 09:59:13 -04:00
while (terrain_zone.terrain == null)
2023-10-08 09:09:50 -04:00
terrain_zone = terrain_zone.parent;
2023-03-22 05:41:39 -04:00
2023-10-08 09:09:50 -04:00
return terrain_zone;
2023-03-22 05:41:39 -04:00
}
2023-10-11 17:14:40 -04:00
public static float getWorldHeight(Zone zone, Vector3fImmutable world_loc) {
2022-04-30 09:41:17 -04:00
2024-04-28 13:56:03 -04:00
// Retrieve the next zone up in the tree with a terrain defined.
2023-09-16 13:18:36 -04:00
2023-10-11 17:14:40 -04:00
Zone terrainZone = getNextZoneWithTerrain(zone);
2023-10-15 13:31:31 -04:00
Zone parentZone = getNextZoneWithTerrain(zone.parent);
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-10-11 17:14:40 -04:00
Vector2f terrainLoc = ZoneManager.worldToTerrainSpace(world_loc, terrainZone);
2023-10-25 13:51:54 -04:00
Vector2f parentLoc = ZoneManager.worldToTerrainSpace(world_loc, parentZone);
2023-09-11 11:01:09 -04:00
2023-10-15 14:30:58 -04:00
// Offset from origin needed for blending function
Vector2f terrainOffset = ZoneManager.worldToZoneOffset(world_loc, terrainZone);
// Interpolate height for this position in both terrains
2022-04-30 09:41:17 -04:00
2023-10-09 07:47:50 -04:00
float interpolatedChildHeight = terrainZone.terrain.getInterpolatedTerrainHeight(terrainLoc);
2023-10-12 06:19:22 -04:00
interpolatedChildHeight += terrainZone.global_height;
2022-04-30 09:41:17 -04:00
2023-10-12 06:33:22 -04:00
float interpolatedParentTerrainHeight = parentZone.terrain.getInterpolatedTerrainHeight(parentLoc);
interpolatedParentTerrainHeight += parentZone.global_height;
// Blend between terrains
2023-10-15 16:40:31 -04:00
float blendCoefficient = terrainZone.terrain.getTerrainBlendCoefficient(terrainOffset);
2023-10-15 14:55:19 -04:00
2023-10-15 16:40:31 -04:00
float terrainHeight = interpolatedChildHeight * blendCoefficient;
terrainHeight += interpolatedParentTerrainHeight * (1 - blendCoefficient);
2023-10-15 14:55:19 -04:00
return terrainHeight;
2023-10-12 06:33:22 -04:00
2023-09-20 11:40:29 -04:00
}
2023-10-11 17:14:40 -04:00
public static float getWorldHeight(Vector3fImmutable world_loc) {
2022-04-30 09:41:17 -04:00
2023-10-11 17:14:40 -04:00
Zone currentZone = ZoneManager.findSmallestZone(world_loc);
2022-04-30 09:41:17 -04:00
2023-10-11 17:14:40 -04:00
return getWorldHeight(currentZone, world_loc);
2022-04-30 09:41:17 -04:00
}
2023-10-11 17:14:40 -04:00
public Vector2f getTerrainCell(Vector2f terrain_loc) {
2023-07-15 09:23:48 -04:00
2023-10-12 06:12:40 -04:00
// Calculate terrain cell with offset
2023-10-11 17:14:40 -04:00
Vector2f terrain_cell = new Vector2f(terrain_loc.x / this.cell_size.x, terrain_loc.y / this.cell_size.y);
2023-07-15 09:23:48 -04:00
2023-10-11 11:23:20 -04:00
// Clamp values when standing directly on pole
2023-07-15 09:23:48 -04:00
2023-10-11 11:23:20 -04:00
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));
2023-10-07 20:47:35 -04:00
2023-10-08 09:49:49 -04:00
return terrain_cell;
2023-07-15 09:23:48 -04:00
}
2023-10-11 17:14:40 -04:00
public float getInterpolatedTerrainHeight(Vector2f terrain_loc) {
2023-07-15 09:23:48 -04:00
2023-10-11 17:14:40 -04:00
float interpolatedHeight;
2023-07-15 09:23:48 -04:00
2023-10-15 16:46:48 -04:00
// Early exit for guild zones
if (this.zone.guild_zone)
return 5.0f;
2023-10-17 07:56:31 -04:00
// Determine terrain and offset from top left vertex
2023-10-11 17:14:40 -04:00
Vector2f terrain_cell = getTerrainCell(terrain_loc);
2023-07-15 09:23:48 -04:00
2023-10-11 17:14:40 -04:00
int pixel_x = (int) Math.floor(terrain_cell.x);
int pixel_y = (int) Math.floor(terrain_cell.y);
2023-10-08 09:04:15 -04:00
2023-10-11 17:14:40 -04:00
Vector2f pixel_offset = new Vector2f(terrain_cell.x % 1, terrain_cell.y % 1);
2023-07-15 09:23:48 -04:00
2023-10-12 06:12:40 -04:00
// 4 surrounding vertices from the pixel array.
2023-07-15 09:23:48 -04:00
2023-10-17 07:56:31 -04:00
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];
2023-07-15 09:23:48 -04:00
2023-10-11 17:14:40 -04:00
// Interpolate between the 4 vertices
2023-09-20 14:20:22 -04:00
2023-10-11 17:14:40 -04:00
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);
2023-07-15 09:23:48 -04:00
2023-10-11 17:14:40 -04:00
interpolatedHeight *= this.terrain_scale; // Scale height
2023-07-15 09:23:48 -04:00
2023-10-11 17:14:40 -04:00
return interpolatedHeight;
2023-10-11 10:53:58 -04:00
2023-07-15 09:23:48 -04:00
}
2023-10-09 07:05:54 -04:00
2023-10-15 16:40:31 -04:00
public float getTerrainBlendCoefficient(Vector2f zone_offset) {
2023-10-09 07:05:54 -04:00
2023-10-11 17:14:40 -04:00
// Normalize terrain offset
2023-10-09 07:05:54 -04:00
2023-10-20 17:01:42 -04:00
Vector2f normalizedOffset = new Vector2f(Math.abs(zone_offset.x) / this.zone.template.major_radius,
Math.abs(zone_offset.y) / this.zone.template.minor_radius);
2023-10-09 07:05:54 -04:00
2023-10-15 17:28:20 -04:00
float blendCoefficient;
2023-10-09 07:05:54 -04:00
2023-10-15 16:40:31 -04:00
if (normalizedOffset.x <= 1 - blend_ratio.x || normalizedOffset.x <= normalizedOffset.y) {
2023-10-09 07:05:54 -04:00
2023-10-15 16:40:31 -04:00
if (normalizedOffset.y < 1 - blend_ratio.y)
2023-10-09 07:05:54 -04:00
return 1;
2023-10-15 17:28:20 -04:00
blendCoefficient = (normalizedOffset.y - (1 - blend_ratio.y)) / blend_ratio.y;
2023-10-09 07:05:54 -04:00
} else
2023-10-15 17:28:20 -04:00
blendCoefficient = (normalizedOffset.x - (1 - blend_ratio.x)) / blend_ratio.x;
2023-10-09 07:05:54 -04:00
2023-10-15 17:28:20 -04:00
blendCoefficient = (float) Math.atan((0.5f - blendCoefficient) * PI);
2023-10-09 07:05:54 -04:00
2023-10-15 17:28:20 -04:00
return (blendCoefficient + 1) * 0.5f;
2023-10-09 07:05:54 -04:00
}
2022-04-30 09:41:17 -04:00
}