Terrain as separate file

This commit is contained in:
Retera 2020-01-21 20:55:38 -06:00
parent e998367f77
commit bfab516832
6 changed files with 933 additions and 629 deletions

View File

@ -184,7 +184,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
// }
}
private final float cameraSpeed = 100.0f;
private final float cameraSpeed = 10.0f;
private final Vector2 cameraVelocity = new Vector2();
@Override

View File

@ -10,8 +10,8 @@ import com.google.common.io.LittleEndianDataOutputStream;
* A tile corner.
*/
public class Corner {
private int groundHeight;
private int waterHeight;
private float groundHeight;
private float waterHeight;
private int mapEdge;
private int ramp;
private int blight;
@ -24,10 +24,10 @@ public class Corner {
private int layerHeight;
public void load(final LittleEndianDataInputStream stream) throws IOException {
this.groundHeight = (stream.readShort() - 8192) / 512;
this.groundHeight = (stream.readShort() - 8192) / (float) 512;
final short waterAndEdge = stream.readShort();
this.waterHeight = ((waterAndEdge & 0x3FFF) - 8192) / 512;
this.waterHeight = ((waterAndEdge & 0x3FFF) - 8192) / (float) 512;
this.mapEdge = waterAndEdge & 0x4000;
final short textureAndFlags = ParseUtils.readUInt8(stream);
@ -52,19 +52,19 @@ public class Corner {
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
stream.writeShort((this.groundHeight * 512) + 8192);
stream.writeShort((this.waterHeight + 8192 + this.mapEdge) << 14);
stream.writeShort((short) ((this.groundHeight * 512f) + 8192f));
stream.writeShort((short) ((this.waterHeight * 512f) + 8192f + (this.mapEdge << 14)));
ParseUtils.writeUInt8(stream, (short) ((this.ramp << 4) | (this.blight << 5) | (this.water << 6)
| (this.boundary << 7) | this.groundTexture));
ParseUtils.writeUInt8(stream, (short) ((this.cliffVariation << 5) | this.groundVariation));
ParseUtils.writeUInt8(stream, (short) ((this.cliffTexture << 4) + this.layerHeight));
}
public int getGroundHeight() {
public float getGroundHeight() {
return this.groundHeight;
}
public int getWaterHeight() {
public float getWaterHeight() {
return this.waterHeight;
}
@ -107,4 +107,12 @@ public class Corner {
public int getLayerHeight() {
return this.layerHeight;
}
public float computeFinalGroundHeight() {
return (this.groundHeight + this.layerHeight) - 2.0f;
}
public float computeFinalWaterHeight(final float waterOffset) {
return this.waterHeight + waterOffset;
}
}

View File

@ -41,5 +41,258 @@ public class HiveWEShaders {
"\r\n" + //
" Normal = terrain_normal;\r\n" + //
"}";
public static final String frag = "#version 450 core\r\n" + //
"\r\n" + //
"layout (binding = 0) uniform sampler2DArray cliff_textures;\r\n" + //
"layout (binding = 2) uniform usampler2D pathing_map_static;\r\n" + //
"\r\n" + //
"layout (location = 1) uniform bool show_pathing_map_static;\r\n" + //
"layout (location = 2) uniform bool show_lighting;\r\n" + //
"\r\n" + //
"layout (location = 0) in vec3 UV;\r\n" + //
"layout (location = 1) in vec3 Normal;\r\n" + //
"layout (location = 2) in vec2 pathing_map_uv;\r\n" + //
"\r\n" + //
"out vec4 color;\r\n" + //
"\r\n" + //
"void main() {\r\n" + //
" color = texture(cliff_textures, UV);\r\n" + //
"\r\n" + //
" if (show_lighting) {\r\n" + //
" vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + //
" light_direction = normalize(light_direction);\r\n" + //
"\r\n" + //
" color.rgb *= clamp(dot(Normal, light_direction) + 0.45, 0, 1);\r\n" + //
" }\r\n" + //
"\r\n" + //
" uvec4 byte = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0);\r\n" + //
" if (show_pathing_map_static) {\r\n" + //
" vec4 pathing_color = vec4(min(byte.r & 2, 1), min(byte.r & 4, 1), min(byte.r & 8, 1), 0.25);\r\n"
+ //
" color = length(pathing_color.rgb) > 0 ? color * 0.75 + pathing_color * 0.5 : color;\r\n" + //
" }\r\n" + //
"}";
}
public static final class Terrain {
private Terrain() {
}
public static final String vert = "#version 450 core\r\n" + //
"\r\n" + //
"layout (location = 0) in vec2 vPosition;\r\n" + //
"layout (location = 1) uniform mat4 MVP;\r\n" + //
"\r\n" + //
"layout (binding = 0) uniform sampler2D height_texture;\r\n" + //
"layout (binding = 1) uniform sampler2D height_cliff_texture;\r\n" + //
"layout (binding = 2) uniform usampler2D terrain_texture_list;\r\n" + //
"\r\n" + //
"layout (location = 0) out vec2 UV;\r\n" + //
"layout (location = 1) out flat uvec4 texture_indices;\r\n" + //
"layout (location = 2) out vec2 pathing_map_uv;\r\n" + //
"layout (location = 3) out vec3 normal;\r\n" + //
"\r\n" + //
"void main() { \r\n" + //
" ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + //
" ivec2 pos = ivec2(gl_InstanceID % size.x, gl_InstanceID / size.x);\r\n" + //
"\r\n" + //
" ivec2 height_pos = ivec2(vPosition + pos);\r\n" + //
" vec4 height = texelFetch(height_cliff_texture, height_pos, 0);\r\n" + //
"\r\n" + //
" ivec3 off = ivec3(1, 1, 0);\r\n" + //
" float hL = texelFetch(height_texture, height_pos - off.xz, 0).r;\r\n" + //
" float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + //
" float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + //
" float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + //
" normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + //
"\r\n" + //
" UV = vec2(vPosition.x, 1 - vPosition.y);\r\n" + //
" texture_indices = texelFetch(terrain_texture_list, pos, 0);\r\n" + //
" pathing_map_uv = (vPosition + pos) * 4; \r\n" + //
"\r\n" + //
" // Cliff culling\r\n" + //
" gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4(vPosition + pos, height.r, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n"
+ //
"}";
public static final String frag = "#version 450 core\r\n" + //
"\r\n" + //
"layout (location = 2) uniform bool show_pathing_map;\r\n" + //
"layout (location = 3) uniform bool show_lighting;\r\n" + //
"\r\n" + //
"layout (binding = 3) uniform sampler2DArray sample0;\r\n" + //
"layout (binding = 4) uniform sampler2DArray sample1;\r\n" + //
"layout (binding = 5) uniform sampler2DArray sample2;\r\n" + //
"layout (binding = 6) uniform sampler2DArray sample3;\r\n" + //
"layout (binding = 7) uniform sampler2DArray sample4;\r\n" + //
"layout (binding = 8) uniform sampler2DArray sample5;\r\n" + //
"layout (binding = 9) uniform sampler2DArray sample6;\r\n" + //
"layout (binding = 10) uniform sampler2DArray sample7;\r\n" + //
"layout (binding = 11) uniform sampler2DArray sample8;\r\n" + //
"layout (binding = 12) uniform sampler2DArray sample9;\r\n" + //
"layout (binding = 13) uniform sampler2DArray sample10;\r\n" + //
"layout (binding = 14) uniform sampler2DArray sample11;\r\n" + //
"layout (binding = 15) uniform sampler2DArray sample12;\r\n" + //
"layout (binding = 16) uniform sampler2DArray sample13;\r\n" + //
"layout (binding = 17) uniform sampler2DArray sample14;\r\n" + //
"layout (binding = 18) uniform sampler2DArray sample15;\r\n" + //
"layout (binding = 19) uniform sampler2DArray sample16;\r\n" + //
"\r\n" + //
"layout (binding = 20) uniform usampler2D pathing_map_static;\r\n" + //
"layout (binding = 21) uniform usampler2D pathing_map_dynamic;\r\n" + //
"\r\n" + //
"layout (location = 0) in vec2 UV;\r\n" + //
"layout (location = 1) in flat uvec4 texture_indices;\r\n" + //
"layout (location = 2) in vec2 pathing_map_uv;\r\n" + //
"layout (location = 3) in vec3 normal;\r\n" + //
"\r\n" + //
"layout (location = 0) out vec4 color;\r\n" + //
"layout (location = 1) out vec4 position;\r\n" + //
"\r\n" + //
"vec4 get_fragment(uint id, vec3 uv) {\r\n" + //
" vec2 dx = dFdx(uv.xy);\r\n" + //
" vec2 dy = dFdy(uv.xy);\r\n" + //
"\r\n" + //
" switch(id) {\r\n" + //
" case 0:\r\n" + //
" return textureGrad(sample0, uv, dx, dy);\r\n" + //
" case 1:\r\n" + //
" return textureGrad(sample1, uv, dx, dy);\r\n" + //
" case 2:\r\n" + //
" return textureGrad(sample2, uv, dx, dy);\r\n" + //
" case 3:\r\n" + //
" return textureGrad(sample3, uv, dx, dy);\r\n" + //
" case 4:\r\n" + //
" return textureGrad(sample4, uv, dx, dy);\r\n" + //
" case 5:\r\n" + //
" return textureGrad(sample5, uv, dx, dy);\r\n" + //
" case 6:\r\n" + //
" return textureGrad(sample6, uv, dx, dy);\r\n" + //
" case 7:\r\n" + //
" return textureGrad(sample7, uv, dx, dy);\r\n" + //
" case 8:\r\n" + //
" return textureGrad(sample8, uv, dx, dy);\r\n" + //
" case 9:\r\n" + //
" return textureGrad(sample9, uv, dx, dy);\r\n" + //
" case 10:\r\n" + //
" return textureGrad(sample10, uv, dx, dy);\r\n" + //
" case 11:\r\n" + //
" return textureGrad(sample11, uv, dx, dy);\r\n" + //
" case 12:\r\n" + //
" return textureGrad(sample12, uv, dx, dy);\r\n" + //
" case 13:\r\n" + //
" return textureGrad(sample13, uv, dx, dy);\r\n" + //
" case 14:\r\n" + //
" return textureGrad(sample14, uv, dx, dy);\r\n" + //
" case 15:\r\n" + //
" return textureGrad(sample15, uv, dx, dy);\r\n" + //
" case 16:\r\n" + //
" return textureGrad(sample16, uv, dx, dy);\r\n" + //
" case 17:\r\n" + //
" return vec4(0, 0, 0, 0);\r\n" + //
" }\r\n" + //
"}\r\n" + //
"\r\n" + //
"\r\n" + //
"void main() {\r\n" + //
" color = get_fragment(texture_indices.a & 31, vec3(UV, texture_indices.a >> 5));\r\n" + //
" color = color * color.a + get_fragment(texture_indices.b & 31, vec3(UV, texture_indices.b >> 5)) * (1 - color.a);\r\n"
+ //
" color = color * color.a + get_fragment(texture_indices.g & 31, vec3(UV, texture_indices.g >> 5)) * (1 - color.a);\r\n"
+ //
" color = color * color.a + get_fragment(texture_indices.r & 31, vec3(UV, texture_indices.r >> 5)) * (1 - color.a);\r\n"
+ //
"\r\n" + //
" if (show_lighting) {\r\n" + //
" vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + //
" light_direction = normalize(light_direction);\r\n" + //
"\r\n" + //
" color.rgb *= clamp(dot(normal, light_direction) + 0.45, 0, 1);\r\n" + //
" }\r\n" + //
"\r\n" + //
" uint byte_static = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0).r;\r\n" + //
" uint byte_dynamic = texelFetch(pathing_map_dynamic, ivec2(pathing_map_uv), 0).r;\r\n" + //
" if (show_pathing_map) {\r\n" + //
" uint final = byte_static.r | byte_dynamic.r;\r\n" + //
"\r\n" + //
" vec4 pathing_static_color = vec4((final & 2) >> 1, (final & 4) >> 2, (final & 8) >> 3, 0.25);\r\n"
+ //
"\r\n" + //
" color = length(pathing_static_color.rgb) > 0 ? color * 0.75 + pathing_static_color * 0.5 : color;\r\n"
+ //
" }\r\n" + //
"}";
}
public static final class Water {
private Water() {
}
public static final String vert = "#version 450 core\r\n" + //
"\r\n" + //
"layout (location = 0) in vec2 vPosition;\r\n" + //
"\r\n" + //
"layout (binding = 0) uniform sampler2D water_height_texture;\r\n" + //
"layout (binding = 1) uniform sampler2D ground_height_texture;\r\n" + //
"layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + //
"\r\n" + //
"layout (location = 0) uniform mat4 MVP;\r\n" + //
"layout (location = 1) uniform vec4 shallow_color_min;\r\n" + //
"layout (location = 2) uniform vec4 shallow_color_max;\r\n" + //
"layout (location = 3) uniform vec4 deep_color_min;\r\n" + //
"layout (location = 4) uniform vec4 deep_color_max;\r\n" + //
"layout (location = 5) uniform float water_offset;\r\n" + //
"\r\n" + //
"out vec2 UV;\r\n" + //
"out vec4 Color;\r\n" + //
"\r\n" + //
"const float min_depth = 10.f / 128;\r\n" + //
"const float deeplevel = 64.f / 128;\r\n" + //
"const float maxdepth = 72.f / 128;\r\n" + //
"\r\n" + //
"void main() { \r\n" + //
" ivec2 size = textureSize(water_height_texture, 0) - 1;\r\n" + //
" ivec2 pos = ivec2(gl_InstanceID % size.x, gl_InstanceID / size.x);\r\n" + //
" ivec2 height_pos = ivec2(vPosition + pos);\r\n" + //
" float water_height = texelFetch(water_height_texture, height_pos, 0).r + water_offset;\r\n" + //
"\r\n" + //
" bool is_water = texelFetch(water_exists_texture, pos, 0).r > 0\r\n" + //
" || texelFetch(water_exists_texture, pos + ivec2(1, 0), 0).r > 0\r\n" + //
" || texelFetch(water_exists_texture, pos + ivec2(1, 1), 0).r > 0\r\n" + //
" || texelFetch(water_exists_texture, pos + ivec2(0, 1), 0).r > 0;\r\n" + //
"\r\n" + //
" gl_Position = is_water ? MVP * vec4(vPosition + pos, water_height, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n"
+ //
"\r\n" + //
" UV = vec2(vPosition.x, 1 - vPosition.y);\r\n" + //
"\r\n" + //
" float ground_height = texelFetch(ground_height_texture, height_pos, 0).r;\r\n" + //
" float value = clamp(water_height - ground_height, 0.f, 1.f);\r\n" + //
" if (value <= deeplevel) {\r\n" + //
" value = max(0.f, value - min_depth) / (deeplevel - min_depth);\r\n" + //
" Color = shallow_color_min * (1.f - value) + shallow_color_max * value;\r\n" + //
" } else {\r\n" + //
" value = clamp(value - deeplevel, 0.f, maxdepth - deeplevel) / (maxdepth - deeplevel);\r\n" + //
" Color = deep_color_min * (1.f - value) + deep_color_max * value;\r\n" + //
" }\r\n" + //
" }";
public static final String frag = "#version 450 core\r\n" + //
"\r\n" + //
"layout (binding = 3) uniform sampler2DArray water_textures;\r\n" + //
"layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + //
"\r\n" + //
"\r\n" + //
"layout (location = 6) uniform int current_texture;\r\n" + //
"\r\n" + //
"in vec2 UV;\r\n" + //
"in vec4 Color;\r\n" + //
"\r\n" + //
"out vec4 outColor;\r\n" + //
"\r\n" + //
"void main() {\r\n" + //
" outColor = texture(water_textures, vec3(UV, current_texture)) * Color;\r\n" + //
"}";
}
}

View File

@ -13,7 +13,7 @@ public class TerrainDoodad {
public TerrainDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad) {
final float[] centerOffset = map.centerOffset;
final float[] centerOffset = map.terrain.centerOffset;
final MdxSimpleInstance instance = (MdxSimpleInstance) model.addInstance(1);
locationHeap[0] = (doodad.getLocation()[0] * 128) + centerOffset[0] + 128;

View File

@ -6,16 +6,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.common.FetchDataTypeName;
import com.etheller.warsmash.common.LoadGenericCallback;
import com.etheller.warsmash.datasources.DataSource;
@ -23,18 +15,12 @@ import com.etheller.warsmash.parsers.w3x.War3Map;
import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo;
import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData;
import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo;
import com.etheller.warsmash.parsers.w3x.w3e.Corner;
import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e;
import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.StandardObjectData;
import com.etheller.warsmash.units.manager.MutableObjectData;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.util.MappedData;
import com.etheller.warsmash.util.MappedDataRow;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.CanvasProvider;
import com.etheller.warsmash.viewer5.GenericResource;
@ -43,36 +29,20 @@ import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain;
public class War3MapViewer extends ModelViewer {
private static final float[] sizeHeap = new float[2];
private static final War3ID sloc = War3ID.fromString("sloc");
private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation();
private static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation();
private static final Vector3 normalHeap1 = new Vector3();
private static final Vector3 normalHeap2 = new Vector3();
public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation();
public PathSolver wc3PathSolver = PathSolver.DEFAULT;
public SolverParams solverParams = new SolverParams();
public ShaderProgram groundShader;
public ShaderProgram waterShader;
public ShaderProgram cliffShader;
public Scene worldScene;
public float waterIndex;
public float waterIncreasePerFrame;
public float waterHeightOffset;
public List<Texture> waterTextures = new ArrayList<>();
public float[] maxDeepColor = new float[4];
public float[] minDeepColor = new float[4];
public float[] maxShallowColor = new float[4];
public float[] minShallowColor = new float[4];
public boolean anyReady;
public boolean terrainCliffsAndWaterLoaded;
public MappedData terrainData = new MappedData();
@ -92,28 +62,8 @@ public class War3MapViewer extends ModelViewer {
public MappedData unitMetaData = new MappedData();
public List<Unit> units = new ArrayList<>();
public boolean unitsReady;
public List<Texture> tilesetTextures = new ArrayList<>();
public List<Texture> cliffTextures = new ArrayList<>();
public List<TerrainModel> cliffModels = new ArrayList<>();
public War3Map mapMpq;
public PathSolver mapPathSolver = PathSolver.DEFAULT;
public Corner[][] corners;
public float[] centerOffset = new float[2];
public int[] mapSize = new int[2];
public List<MappedDataRow> tilesets = new ArrayList<>(); // TODO
public int blightTextureIndex = -1;
public List<MappedDataRow> cliffTilesets = new ArrayList<>();
public int columns;
public int rows;
public int vertexBuffer;
public int faceBuffer;
public int instanceBuffer;
public int textureBuffer;
public int variationBuffer;
public int waterBuffer;
public int heightMap;
public int waterHeightMap;
public int cliffHeightMap;
private final DataSource gameDataSource;
@ -127,9 +77,7 @@ public class War3MapViewer extends ModelViewer {
this.wc3PathSolver = PathSolver.DEFAULT;
this.groundShader = this.webGL.createShaderProgram(W3xShaders.Ground.vert, W3xShaders.Ground.frag);
this.waterShader = this.webGL.createShaderProgram(W3xShaders.Water.vert, W3xShaders.Water.frag);
this.cliffShader = this.webGL.createShaderProgram(W3xShaders.Cliffs.vert, W3xShaders.Cliffs.frag);
this.terrain = new Terrain(webGL);
this.worldScene = this.addScene();
@ -219,9 +167,10 @@ public class War3MapViewer extends ModelViewer {
final float[] centerOffset = terrainData.getCenterOffset();
final int[] mapSize = terrainData.getMapSize();
this.corners = terrainData.getCorners();
System.arraycopy(centerOffset, 0, this.centerOffset, 0, centerOffset.length);
System.arraycopy(mapSize, 0, this.mapSize, 0, mapSize.length);
this.terrain.load(terrainData, centerOffset, mapSize, this);
this.terrainReady = true;
this.anyReady = true;
this.cliffsReady = true;
// Override the grid based on the map.
this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128,
@ -252,248 +201,6 @@ public class War3MapViewer extends ModelViewer {
}
private void loadTerrainCliffsAndWater(final War3MapW3e w3e) {
final String texturesExt = this.solverParams.reforged ? ".dds" : ".blp";
final char tileset = w3e.getTileset();
for (final War3ID groundTile : w3e.getGroundTiles()) {
final MappedDataRow row = this.terrainData.getRow(groundTile.asStringValue());
this.tilesets.add(row);
this.tilesetTextures
.add((Texture) this.load(row.get("dir").toString() + "\\" + row.get("file") + texturesExt,
this.mapPathSolver, this.solverParams));
}
final StandardObjectData standardObjectData = new StandardObjectData(this.mapMpq.getCompoundDataSource());
final DataTable worldEditData = standardObjectData.getWorldEditData();
final Element tilesets = worldEditData.get("TileSets");
this.blightTextureIndex = this.tilesetTextures.size();
this.tilesetTextures
.add((Texture) this.load(tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt,
this.mapPathSolver, this.solverParams));
for (final War3ID cliffTile : w3e.getCliffTiles()) {
final MappedDataRow row = this.cliffTypesData.getRow(cliffTile.asStringValue());
this.cliffTilesets.add(row);
this.cliffTextures
.add((Texture) this.load(row.get("texDir").toString() + "\\" + row.get("texFile") + texturesExt,
this.mapPathSolver, this.solverParams));
}
final MappedDataRow waterRow = this.waterData.getRow(tileset + "Sha");
this.waterHeightOffset = ((Number) waterRow.get("height")).floatValue();
this.waterIncreasePerFrame = ((Number) waterRow.get("texRate")).intValue() / (float) 60;
this.waterTextures.clear();
this.maxDeepColor[0] = ((Number) waterRow.get("Dmax_R")).floatValue();
this.maxDeepColor[1] = ((Number) waterRow.get("Dmax_G")).floatValue();
this.maxDeepColor[2] = ((Number) waterRow.get("Dmax_B")).floatValue();
this.maxDeepColor[3] = ((Number) waterRow.get("Dmax_A")).floatValue();
this.minDeepColor[0] = ((Number) waterRow.get("Dmin_R")).floatValue();
this.minDeepColor[1] = ((Number) waterRow.get("Dmin_G")).floatValue();
this.minDeepColor[2] = ((Number) waterRow.get("Dmin_B")).floatValue();
this.minDeepColor[3] = ((Number) waterRow.get("Dmin_A")).floatValue();
this.maxShallowColor[0] = ((Number) waterRow.get("Smax_R")).floatValue();
this.maxShallowColor[1] = ((Number) waterRow.get("Smax_G")).floatValue();
this.maxShallowColor[2] = ((Number) waterRow.get("Smax_B")).floatValue();
this.maxShallowColor[3] = ((Number) waterRow.get("Smax_A")).floatValue();
this.minShallowColor[0] = ((Number) waterRow.get("Smin_R")).floatValue();
this.minShallowColor[1] = ((Number) waterRow.get("Smin_G")).floatValue();
this.minShallowColor[2] = ((Number) waterRow.get("Smin_B")).floatValue();
this.minShallowColor[3] = ((Number) waterRow.get("Smin_A")).floatValue();
for (int i = 0, l = ((Number) waterRow.get("numTex")).intValue(); i < l; i++) {
this.waterTextures.add(
(Texture) this.load(waterRow.get("texFile").toString() + ((i < 10) ? "0" : "") + i + texturesExt,
this.mapPathSolver, this.solverParams));
}
final GL20 gl = this.gl;
final Corner[][] corners = w3e.getCorners();
final int columns = this.mapSize[0];
final int rows = this.mapSize[1];
final float[] centerOffset = this.centerOffset;
final int instanceCount = (columns - 1) * (rows - 1);
final float[] cliffHeights = new float[columns * rows];
final float[] cornerHeights = new float[columns * rows];
final float[] waterHeights = new float[columns * rows];
final short[] cornerTextures = new short[instanceCount * 4];
final short[] cornerVariations = new short[instanceCount * 4];
final short[] waterFlags = new short[instanceCount];
int instance = 0;
final Map<String, CliffInfo> cliffs = new HashMap<>();
this.columns = columns - 1;
this.rows = rows - 1;
for (int y = 0; y < rows; y++) {
for (int x = 0; x < columns; x++) {
final Corner bottomLeft = corners[y][x];
final int index = (y * columns) + x;
cliffHeights[index] = bottomLeft.getGroundHeight();
cornerHeights[index] = (bottomLeft.getGroundHeight() + bottomLeft.getLayerHeight()) - 2;
waterHeights[index] = bottomLeft.getWaterHeight();
if ((y < (rows - 1)) && (x < (columns - 1))) {
// Water can be used with cliffs and normal corners, so store water state
// regardless.
waterFlags[instance] = this.isWater(x, y);
// Is this a cliff, or a normal corner?
if (this.isCliff(x, y)) {
final int bottomLeftLayer = bottomLeft.getLayerHeight();
final int bottomRightLayer = corners[y][x + 1].getLayerHeight();
final int topLeftLayer = corners[y + 1][x].getLayerHeight();
final int topRightLayer = corners[y + 1][x + 1].getLayerHeight();
final int base = Math.min(Math.min(bottomLeftLayer, bottomRightLayer),
Math.min(topLeftLayer, topRightLayer));
final String fileName = this.cliffFileName(bottomLeftLayer, bottomRightLayer, topLeftLayer,
topRightLayer, base);
if (!"AAAA".equals(fileName)) {
int cliffTexture = bottomLeft.getCliffTexture();
// ?
if (cliffTexture == 15) {
cliffTexture = 1;
}
final MappedDataRow cliffRow = this.cliffTilesets.get(cliffTexture);
final String dir = cliffRow.get("cliffModelDir").toString();
final String path = "Doodads\\Terrain\\" + dir + "\\" + dir + fileName
+ Variations.getCliffVariation(dir, fileName, bottomLeft.getCliffVariation())
+ ".mdx";
if (!cliffs.containsKey(path)) {
cliffs.put(path, new CliffInfo());
}
cliffs.get(path).locations.add(new float[] { ((x + 1) * 128) + centerOffset[0],
(y * 128) + centerOffset[1], (base - 2) * 128 });
cliffs.get(path).textures.add(cliffTexture);
}
}
else {
final int bottomLeftTexture = this.cornerTexture(x, y);
final int bottomRightTexture = this.cornerTexture(x + 1, y);
final int topLeftTexture = this.cornerTexture(x, y + 1);
final int topRightTexture = this.cornerTexture(x + 1, y + 1);
final LinkedHashSet<Integer> texturesUnique = new LinkedHashSet<>();
texturesUnique.add(bottomLeftTexture);
texturesUnique.add(bottomRightTexture);
texturesUnique.add(topLeftTexture);
texturesUnique.add(topRightTexture);
final List<Integer> textures = new ArrayList<>(texturesUnique);
Collections.sort(textures);
int texture = textures.remove(0);
cornerTextures[instance * 4] = (short) (texture + 1);
cornerVariations[instance * 4] = this.getVariation(texture, bottomLeft.getGroundVariation());
for (int i = 0, l = textures.size(); i < l; i++) {
int bitset = 0;
texture = textures.get(i);
if (bottomRightTexture == texture) {
bitset |= 0b0001;
}
if (bottomLeftTexture == texture) {
bitset |= 0b0010;
}
if (topRightTexture == texture) {
bitset |= 0b0100;
}
if (topLeftTexture == texture) {
bitset |= 0b1000;
}
cornerTextures[(instance * 4) + 1 + i] = (short) (texture + 1);
cornerVariations[(instance * 4) + 1 + i] = (short) (bitset);
}
}
instance += 1;
}
}
}
this.vertexBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, 8 * 4, RenderMathUtils.wrap(new float[] { 0, 0, 1, 0, 0, 1, 1, 1 }),
GL20.GL_STATIC_DRAW);
this.faceBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer);
gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 6, RenderMathUtils.wrap(new byte[] { 0, 1, 2, 1, 3, 2 }),
GL20.GL_STATIC_DRAW);
this.cliffHeightMap = gl.glGenTexture();
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap);
this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT,
RenderMathUtils.wrap(cliffHeights));
this.heightMap = gl.glGenTexture();
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap);
this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT,
RenderMathUtils.wrap(cornerHeights));
this.waterHeightMap = gl.glGenTexture();
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap);
this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT,
RenderMathUtils.wrap(waterHeights));
this.instanceBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer);
final float[] instanceBufferData = new float[instanceCount];
for (int i = 0; i < instanceBufferData.length; i++) {
instanceBufferData[i] = i;
}
gl.glBufferData(GL20.GL_ARRAY_BUFFER, instanceBufferData.length * 4, RenderMathUtils.wrap(instanceBufferData),
GL20.GL_STATIC_DRAW);
this.textureBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerTextures.length, RenderMathUtils.wrap(cornerTextures),
GL20.GL_STATIC_DRAW);
this.variationBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerVariations.length, RenderMathUtils.wrap(cornerVariations),
GL20.GL_STATIC_DRAW);
this.waterBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, waterFlags.length, RenderMathUtils.wrap(waterFlags), GL20.GL_STATIC_DRAW);
this.terrainReady = true;
this.anyReady = true;
final ShaderProgram cliffShader = this.cliffShader;
this.cliffModels.clear();
for (final Map.Entry<String, CliffInfo> entry : cliffs.entrySet()) {
final String path = entry.getKey();
final CliffInfo cliffInfo = entry.getValue();
final GenericResource resource = this.loadMapGeneric(path, FetchDataTypeName.ARRAY_BUFFER,
streamDataCallback);
this.cliffModels.add(new TerrainModel(this, (InputStream) resource.data, cliffInfo.locations,
cliffInfo.textures, cliffShader));
}
this.cliffsReady = true;
}
@ -629,11 +336,7 @@ public class War3MapViewer extends ModelViewer {
@Override
public void update() {
if (this.anyReady) {
this.waterIndex += this.waterIncreasePerFrame;
if (this.waterIndex >= this.waterTextures.size()) {
this.waterIndex = 0;
}
this.terrain.update();
super.update();
@ -656,325 +359,14 @@ public class War3MapViewer extends ModelViewer {
final Scene worldScene = this.worldScene;
worldScene.startFrame();
this.renderGround();
this.renderCliffs();
this.terrain.renderGround(this.gl, this.webGL, worldScene);
this.terrain.renderCliffs(this.gl, this.webGL, worldScene);
worldScene.renderOpaque();
this.renderWater();
this.terrain.renderWater(this.gl, this.webGL, worldScene);
worldScene.renderTranslucent();
}
}
public void renderGround() {
if (this.terrainReady) {
final GL20 gl = this.gl;
final WebGL webgl = this.webGL;
final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays;
final ShaderProgram shader = this.groundShader;
final List<Texture> tilesetTextures = this.tilesetTextures;
final int instanceAttrib = shader.getAttributeLocation("a_InstanceID");
final int positionAttrib = shader.getAttributeLocation("a_position");
final int texturesAttrib = shader.getAttributeLocation("a_textures");
final int variationsAttrib = shader.getAttributeLocation("a_variations");
final int tilesetCount = tilesetTextures.size();
gl.glEnable(GL20.GL_BLEND);
gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
webgl.useShaderProgram(shader);
shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix);
shader.setUniform2fv("u_offset", this.centerOffset, 0, 2);
sizeHeap[0] = this.columns;
sizeHeap[1] = this.rows;
shader.setUniform2fv("u_size", sizeHeap, 0, 2);
shader.setUniformi("u_heightMap", 15);
gl.glActiveTexture(GL20.GL_TEXTURE15);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer);
shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer);
shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0);
instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer);
shader.setVertexAttribute(texturesAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0);
instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 1);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer);
shader.setVertexAttribute(variationsAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0);
instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 1);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer);
shader.setUniformi("u_baseTileset", 0);
for (int i = 0, l = Math.min(tilesetCount, 15); i < l; i++) {
final int isExtended = (tilesetTextures.get(i).getWidth() > tilesetTextures.get(i).getHeight()) ? 1 : 0;
shader.setUniformf("u_extended[" + i + "]", isExtended);
shader.setUniformi("u_tilesets[" + i + "]", i);
tilesetTextures.get(i).bind(i);
}
instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0,
this.rows * this.columns);
if (tilesetCount > 15) {
shader.setUniformi("u_baseTileset", 15);
for (int i = 0, l = tilesetCount - 15; i < l; i++) {
final int isExtended = (tilesetTextures.get(i + 15).getWidth() > tilesetTextures.get(i + 15)
.getHeight()) ? 1 : 0;
shader.setUniformf("u_extended[" + i + "]", isExtended);
tilesetTextures.get(i + 15).bind(i);
}
instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0,
this.rows * this.columns);
}
instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 0);
instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 0);
instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0);
}
}
public void renderWater() {
if (this.terrainReady) {
final GL20 gl = this.gl;
final WebGL webgl = this.webGL;
final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays;
final ShaderProgram shader = this.waterShader;
final int instanceAttrib = shader.getAttributeLocation("a_InstanceID");
final int positionAttrib = shader.getAttributeLocation("a_position");
final int isWaterAttrib = shader.getAttributeLocation("a_isWater");
gl.glDepthMask(false);
gl.glEnable(GL20.GL_BLEND);
gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
webgl.useShaderProgram(shader);
shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix);
shader.setUniform2fv("u_offset", this.centerOffset, 0, 2);
sizeHeap[0] = this.columns;
sizeHeap[1] = this.rows;
shader.setUniform2fv("u_size", sizeHeap, 0, 2);
shader.setUniformi("u_heightMap", 0);
shader.setUniformi("u_waterHeightMap", 1);
shader.setUniformi("u_waterTexture", 2);
shader.setUniformf("u_offsetHeight", this.waterHeightOffset);
shader.setUniform4fv("u_maxDeepColor", this.maxDeepColor, 0, 4);
shader.setUniform4fv("u_minDeepColor", this.minDeepColor, 0, 4);
shader.setUniform4fv("u_maxShallowColor", this.maxShallowColor, 0, 4);
shader.setUniform4fv("u_minShallowColor", this.minShallowColor, 0, 4);
gl.glActiveTexture(GL20.GL_TEXTURE0);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap);
gl.glActiveTexture(GL20.GL_TEXTURE1);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap);
this.waterTextures.get((int) this.waterIndex).bind(2);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer);
shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer);
shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0);
instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer);
shader.setVertexAttribute(isWaterAttrib, 1, GL20.GL_UNSIGNED_BYTE, false, 1, 0);
instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 1);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer);
instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0,
this.rows * this.columns);
instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 0);
instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0);
}
}
public void renderCliffs() {
if (this.cliffsReady) {
final GL20 gl = this.gl;
final WebGL webGL = this.webGL;
final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays;
final ShaderProgram shader = this.cliffShader;
gl.glDisable(GL20.GL_BLEND);
webGL.useShaderProgram(shader);
shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix);
shader.setUniformi("u_heightMap", 0);
shader.setUniformf("u_pixel[0]", 1 / (float) (this.columns + 1));
shader.setUniformf("u_pixel[1]", 1 / (float) (this.rows + 1));
shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2);
shader.setUniformi("u_texture1", 1);
shader.setUniformi("u_texture2", 2);
gl.glActiveTexture(GL20.GL_TEXTURE0);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap);
gl.glActiveTexture(GL20.GL_TEXTURE1);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(0).getGlTarget());
if (this.cliffTextures.size() > 1) {
gl.glActiveTexture(GL20.GL_TEXTURE2);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(1).getGlTarget());
}
// Set instanced attributes.
for (final TerrainModel cliff : this.cliffModels) {
cliff.render(shader);
}
}
}
public String cliffFileName(final int bottomLeftLayer, final int bottomRightLayer, final int topLeftLayer,
final int topRightLayer, final int base) {
return Character.toString((char) ((65 + bottomLeftLayer) - base))
+ Character.toString((char) ((65 + topLeftLayer) - base))
+ Character.toString((char) ((65 + topRightLayer) - base))
+ Character.toString((char) ((65 + bottomRightLayer) - base));
}
public short getVariation(final int groundTexture, final int variation) {
final Texture texture = this.tilesetTextures.get(groundTexture);
// Extended ?
if (texture.getWidth() > texture.getHeight()) {
if (variation < 16) {
return (short) (16 + variation);
}
else if (variation == 16) {
return 15;
}
else {
return 0;
}
}
else {
if (variation == 0) {
return 0;
}
else {
return 15;
}
}
}
public boolean isCliff(final int column, final int row) {
if ((column < 1) || (column > (this.columns - 1)) || (row < 1) || (row > (this.rows - 1))) {
return false;
}
final Corner[][] corners = this.corners;
final int bottomLeft = corners[row][column].getLayerHeight();
final int bottomRight = corners[row][column + 1].getLayerHeight();
final int topLeft = corners[row + 1][column].getLayerHeight();
final int topRight = corners[row + 1][column + 1].getLayerHeight();
return (bottomLeft != bottomRight) || (bottomLeft != topLeft) || (bottomLeft != topRight);
}
public short isWater(final int column, final int row) {
return ((this.corners[row][column].getWater() != 0) || (this.corners[row][column + 1].getWater() != 0)
|| (this.corners[row + 1][column].getWater() != 0)
|| (this.corners[row + 1][column + 1].getWater() != 0)) ? (short) 1 : (short) 0;
}
public int cliffGroundIndex(final int whichCliff) {
final String whichTile = this.cliffTilesets.get(whichCliff).get("groundTile").toString();
final List<MappedDataRow> tilesets = this.tilesets;
for (int i = 0, l = tilesets.size(); i < l; i++) {
if (tilesets.get(i).get("tileID").toString().equals(whichTile)) {
return i;
}
}
throw new IllegalArgumentException(Integer.toString(whichCliff));
}
public int cornerTexture(final int column, final int row) {
final Corner[][] corners = this.corners;
final int columns = this.columns;
final int rows = this.rows;
for (int y = -1; y < 1; y++) {
for (int x = -1; x < 1; x++) {
if (((column + x) > 0) && ((column + x) < (columns - 1)) && ((row + y) > 0)
&& ((row + y) < (rows - 1))) {
if (this.isCliff(column + x, row + y)) {
int texture = corners[row + y][column + x].getCliffTexture();
if (texture == 15) {
texture = 1;
}
return this.cliffGroundIndex(texture);
}
}
}
}
final Corner corner = corners[row][column];
if (corner.getBlight() != 0) {
return this.blightTextureIndex;
}
return corner.getGroundTexture();
}
public Vector3 groundNormal(final Vector3 out, int x, int y) {
final float[] centerOffset = this.centerOffset;
final int[] mapSize = this.mapSize;
x = (int) ((x - centerOffset[0]) / 128);
y = (int) ((y - centerOffset[1]) / 128);
final int cellX = x;
final int cellY = y;
// See if this coordinate is in the map
if ((cellX >= 0) && (cellX < (mapSize[0] - 1)) && (cellY >= 0) && (cellY < (mapSize[1] - 1))) {
// See http://gamedev.stackexchange.com/a/24574
final Corner[][] corners = this.corners;
final int bottomLeft = corners[cellY][cellX].getGroundHeight();
final int bottomRight = corners[cellY][cellX + 1].getGroundHeight();
final int topLeft = corners[cellY + 1][cellX].getGroundHeight();
final int topRight = corners[cellY + 1][cellX + 1].getGroundHeight();
final int sqX = x - cellX;
final int sqY = y - cellY;
if ((sqX + sqY) < 1) {
normalHeap1.set(1, 0, bottomRight - bottomLeft);
normalHeap2.set(0, 1, topLeft - bottomLeft);
}
else {
normalHeap1.set(-1, 0, topRight - topLeft);
normalHeap2.set(0, 1, topRight - bottomRight);
}
out.set(normalHeap1.crs(normalHeap2)).nor();
}
else {
out.set(0, 0, 1);
}
return out;
}
private static final class MappedDataCallbackImplementation implements LoadGenericCallback {
@Override
public Object call(final InputStream data) {
@ -1039,6 +431,7 @@ public class War3MapViewer extends ModelViewer {
}
private static final int MAXIMUM_ACCEPTED = 1 << 30;
public final Terrain terrain;
/**
* Returns a power of two size for the given target capacity.

View File

@ -0,0 +1,650 @@
package com.etheller.warsmash.viewer5.handlers.w3x.environment;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.common.FetchDataTypeName;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.parsers.w3x.w3e.Corner;
import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.StandardObjectData;
import com.etheller.warsmash.util.MappedDataRow;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.util.WorldEditStrings;
import com.etheller.warsmash.viewer5.GenericResource;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.w3x.TerrainModel;
import com.etheller.warsmash.viewer5.handlers.w3x.Variations;
import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer.CliffInfo;
public class Terrain {
private static final float[] sizeHeap = new float[2];
private static final Vector3 normalHeap1 = new Vector3();
private static final Vector3 normalHeap2 = new Vector3();
public ShaderProgram groundShader;
public ShaderProgram waterShader;
public ShaderProgram cliffShader;
public float waterIndex;
public float waterIncreasePerFrame;
public float waterHeightOffset;
public List<Texture> waterTextures = new ArrayList<>();
public float[] maxDeepColor = new float[4];
public float[] minDeepColor = new float[4];
public float[] maxShallowColor = new float[4];
public float[] minShallowColor = new float[4];
public List<Texture> tilesetTextures = new ArrayList<>();
public List<Texture> cliffTextures = new ArrayList<>();
public List<TerrainModel> cliffModels = new ArrayList<>();
public Corner[][] corners;
public float[] centerOffset = new float[2];
public int[] mapSize = new int[2];
public List<MappedDataRow> tilesets = new ArrayList<>(); // TODO
public int blightTextureIndex = -1;
public List<MappedDataRow> cliffTilesets = new ArrayList<>();
public int columns;
public int rows;
public int vertexBuffer;
public int faceBuffer;
public int instanceBuffer;
public int textureBuffer;
public int variationBuffer;
public int waterBuffer;
public int heightMap;
public int waterHeightMap;
public int cliffHeightMap;
public Terrain(final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings)
throws IOException {
final DataTable terrainTable = new DataTable(worldEditStrings);
try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) {
terrainTable.readSLK(terrainSlkStream);
}
final DataTable cliffTable = new DataTable(worldEditStrings);
try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) {
cliffTable.readSLK(cliffSlkStream);
}
this.groundShader = webGL.createShaderProgram(W3xShaders.Ground.vert, W3xShaders.Ground.frag);
this.waterShader = webGL.createShaderProgram(W3xShaders.Water.vert, W3xShaders.Water.frag);
this.cliffShader = webGL.createShaderProgram(W3xShaders.Cliffs.vert, W3xShaders.Cliffs.frag);
}
public void load(final War3MapW3e w3e, final float[] centerOffset, final int[] mapSize,
final War3MapViewer viewer) {
this.corners = w3e.getCorners();
System.arraycopy(centerOffset, 0, this.centerOffset, 0, centerOffset.length);
System.arraycopy(mapSize, 0, this.mapSize, 0, mapSize.length);
final String texturesExt = viewer.solverParams.reforged ? ".dds" : ".blp";
final char tileset = w3e.getTileset();
for (final War3ID groundTile : w3e.getGroundTiles()) {
final MappedDataRow row = viewer.terrainData.getRow(groundTile.asStringValue());
this.tilesets.add(row);
this.tilesetTextures
.add((Texture) viewer.load(row.get("dir").toString() + "\\" + row.get("file") + texturesExt,
viewer.mapPathSolver, viewer.solverParams));
}
final StandardObjectData standardObjectData = new StandardObjectData(viewer.mapMpq.getCompoundDataSource());
final DataTable worldEditData = standardObjectData.getWorldEditData();
final Element tilesets = worldEditData.get("TileSets");
this.blightTextureIndex = this.tilesetTextures.size();
this.tilesetTextures
.add((Texture) viewer.load(tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt,
viewer.mapPathSolver, viewer.solverParams));
for (final War3ID cliffTile : w3e.getCliffTiles()) {
final MappedDataRow row = viewer.cliffTypesData.getRow(cliffTile.asStringValue());
this.cliffTilesets.add(row);
this.cliffTextures
.add((Texture) viewer.load(row.get("texDir").toString() + "\\" + row.get("texFile") + texturesExt,
viewer.mapPathSolver, viewer.solverParams));
}
final MappedDataRow waterRow = viewer.waterData.getRow(tileset + "Sha");
this.waterHeightOffset = ((Number) waterRow.get("height")).floatValue();
this.waterIncreasePerFrame = ((Number) waterRow.get("texRate")).intValue() / (float) 60;
this.waterTextures.clear();
this.maxDeepColor[0] = ((Number) waterRow.get("Dmax_R")).floatValue();
this.maxDeepColor[1] = ((Number) waterRow.get("Dmax_G")).floatValue();
this.maxDeepColor[2] = ((Number) waterRow.get("Dmax_B")).floatValue();
this.maxDeepColor[3] = ((Number) waterRow.get("Dmax_A")).floatValue();
this.minDeepColor[0] = ((Number) waterRow.get("Dmin_R")).floatValue();
this.minDeepColor[1] = ((Number) waterRow.get("Dmin_G")).floatValue();
this.minDeepColor[2] = ((Number) waterRow.get("Dmin_B")).floatValue();
this.minDeepColor[3] = ((Number) waterRow.get("Dmin_A")).floatValue();
this.maxShallowColor[0] = ((Number) waterRow.get("Smax_R")).floatValue();
this.maxShallowColor[1] = ((Number) waterRow.get("Smax_G")).floatValue();
this.maxShallowColor[2] = ((Number) waterRow.get("Smax_B")).floatValue();
this.maxShallowColor[3] = ((Number) waterRow.get("Smax_A")).floatValue();
this.minShallowColor[0] = ((Number) waterRow.get("Smin_R")).floatValue();
this.minShallowColor[1] = ((Number) waterRow.get("Smin_G")).floatValue();
this.minShallowColor[2] = ((Number) waterRow.get("Smin_B")).floatValue();
this.minShallowColor[3] = ((Number) waterRow.get("Smin_A")).floatValue();
for (int i = 0, l = ((Number) waterRow.get("numTex")).intValue(); i < l; i++) {
this.waterTextures.add(
(Texture) viewer.load(waterRow.get("texFile").toString() + ((i < 10) ? "0" : "") + i + texturesExt,
viewer.mapPathSolver, viewer.solverParams));
}
final GL20 gl = viewer.gl;
final Corner[][] corners = w3e.getCorners();
final int columns = this.mapSize[0];
final int rows = this.mapSize[1];
final int instanceCount = (columns - 1) * (rows - 1);
final float[] cliffHeights = new float[columns * rows];
final float[] cornerHeights = new float[columns * rows];
final float[] waterHeights = new float[columns * rows];
final short[] cornerTextures = new short[instanceCount * 4];
final short[] cornerVariations = new short[instanceCount * 4];
final short[] waterFlags = new short[instanceCount];
int instance = 0;
final Map<String, CliffInfo> cliffs = new HashMap<>();
this.columns = columns - 1;
this.rows = rows - 1;
for (int y = 0; y < rows; y++) {
for (int x = 0; x < columns; x++) {
final Corner bottomLeft = corners[y][x];
final int index = (y * columns) + x;
cliffHeights[index] = bottomLeft.getGroundHeight();
cornerHeights[index] = (bottomLeft.getGroundHeight() + bottomLeft.getLayerHeight()) - 2;
waterHeights[index] = bottomLeft.getWaterHeight();
if ((y < (rows - 1)) && (x < (columns - 1))) {
// Water can be used with cliffs and normal corners, so store water state
// regardless.
waterFlags[instance] = this.isWater(x, y);
// Is this a cliff, or a normal corner?
if (this.isCliff(x, y)) {
final int bottomLeftLayer = bottomLeft.getLayerHeight();
final int bottomRightLayer = corners[y][x + 1].getLayerHeight();
final int topLeftLayer = corners[y + 1][x].getLayerHeight();
final int topRightLayer = corners[y + 1][x + 1].getLayerHeight();
final int base = Math.min(Math.min(bottomLeftLayer, bottomRightLayer),
Math.min(topLeftLayer, topRightLayer));
final String fileName = this.cliffFileName(bottomLeftLayer, bottomRightLayer, topLeftLayer,
topRightLayer, base);
if (!"AAAA".equals(fileName)) {
int cliffTexture = bottomLeft.getCliffTexture();
// ?
if (cliffTexture == 15) {
cliffTexture = 1;
}
final MappedDataRow cliffRow = this.cliffTilesets.get(cliffTexture);
final String dir = cliffRow.get("cliffModelDir").toString();
final String path = "Doodads\\Terrain\\" + dir + "\\" + dir + fileName
+ Variations.getCliffVariation(dir, fileName, bottomLeft.getCliffVariation())
+ ".mdx";
if (!cliffs.containsKey(path)) {
cliffs.put(path, new CliffInfo());
}
cliffs.get(path).locations.add(new float[] { ((x + 1) * 128) + centerOffset[0],
(y * 128) + centerOffset[1], (base - 2) * 128 });
cliffs.get(path).textures.add(cliffTexture);
}
}
else {
final int bottomLeftTexture = this.cornerTexture(x, y);
final int bottomRightTexture = this.cornerTexture(x + 1, y);
final int topLeftTexture = this.cornerTexture(x, y + 1);
final int topRightTexture = this.cornerTexture(x + 1, y + 1);
final LinkedHashSet<Integer> texturesUnique = new LinkedHashSet<>();
texturesUnique.add(bottomLeftTexture);
texturesUnique.add(bottomRightTexture);
texturesUnique.add(topLeftTexture);
texturesUnique.add(topRightTexture);
final List<Integer> textures = new ArrayList<>(texturesUnique);
Collections.sort(textures);
int texture = textures.remove(0);
cornerTextures[instance * 4] = (short) (texture + 1);
cornerVariations[instance * 4] = this.getVariation(texture, bottomLeft.getGroundVariation());
for (int i = 0, l = textures.size(); i < l; i++) {
int bitset = 0;
texture = textures.get(i);
if (bottomRightTexture == texture) {
bitset |= 0b0001;
}
if (bottomLeftTexture == texture) {
bitset |= 0b0010;
}
if (topRightTexture == texture) {
bitset |= 0b0100;
}
if (topLeftTexture == texture) {
bitset |= 0b1000;
}
cornerTextures[(instance * 4) + 1 + i] = (short) (texture + 1);
cornerVariations[(instance * 4) + 1 + i] = (short) (bitset);
}
}
instance += 1;
}
}
}
this.vertexBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, 8 * 4, RenderMathUtils.wrap(new float[] { 0, 0, 1, 0, 0, 1, 1, 1 }),
GL20.GL_STATIC_DRAW);
this.faceBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer);
gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 6, RenderMathUtils.wrap(new byte[] { 0, 1, 2, 1, 3, 2 }),
GL20.GL_STATIC_DRAW);
this.cliffHeightMap = gl.glGenTexture();
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap);
viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT,
RenderMathUtils.wrap(cliffHeights));
this.heightMap = gl.glGenTexture();
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap);
viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT,
RenderMathUtils.wrap(cornerHeights));
this.waterHeightMap = gl.glGenTexture();
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap);
viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST);
gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT,
RenderMathUtils.wrap(waterHeights));
this.instanceBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer);
final float[] instanceBufferData = new float[instanceCount];
for (int i = 0; i < instanceBufferData.length; i++) {
instanceBufferData[i] = i;
}
gl.glBufferData(GL20.GL_ARRAY_BUFFER, instanceBufferData.length * 4, RenderMathUtils.wrap(instanceBufferData),
GL20.GL_STATIC_DRAW);
this.textureBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerTextures.length, RenderMathUtils.wrap(cornerTextures),
GL20.GL_STATIC_DRAW);
this.variationBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerVariations.length, RenderMathUtils.wrap(cornerVariations),
GL20.GL_STATIC_DRAW);
this.waterBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, waterFlags.length, RenderMathUtils.wrap(waterFlags), GL20.GL_STATIC_DRAW);
final ShaderProgram cliffShader = this.cliffShader;
this.cliffModels.clear();
for (final Map.Entry<String, CliffInfo> entry : cliffs.entrySet()) {
final String path = entry.getKey();
final CliffInfo cliffInfo = entry.getValue();
final GenericResource resource = viewer.loadMapGeneric(path, FetchDataTypeName.ARRAY_BUFFER,
viewer.streamDataCallback);
this.cliffModels.add(new TerrainModel(viewer, (InputStream) resource.data, cliffInfo.locations,
cliffInfo.textures, cliffShader));
}
}
public boolean isCliff(final int column, final int row) {
if ((column < 1) || (column > (this.columns - 1)) || (row < 1) || (row > (this.rows - 1))) {
return false;
}
final Corner[][] corners = this.corners;
final int bottomLeft = corners[row][column].getLayerHeight();
final int bottomRight = corners[row][column + 1].getLayerHeight();
final int topLeft = corners[row + 1][column].getLayerHeight();
final int topRight = corners[row + 1][column + 1].getLayerHeight();
return (bottomLeft != bottomRight) || (bottomLeft != topLeft) || (bottomLeft != topRight);
}
public short isWater(final int column, final int row) {
return ((this.corners[row][column].getWater() != 0) || (this.corners[row][column + 1].getWater() != 0)
|| (this.corners[row + 1][column].getWater() != 0)
|| (this.corners[row + 1][column + 1].getWater() != 0)) ? (short) 1 : (short) 0;
}
public String cliffFileName(final int bottomLeftLayer, final int bottomRightLayer, final int topLeftLayer,
final int topRightLayer, final int base) {
return Character.toString((char) ((65 + bottomLeftLayer) - base))
+ Character.toString((char) ((65 + topLeftLayer) - base))
+ Character.toString((char) ((65 + topRightLayer) - base))
+ Character.toString((char) ((65 + bottomRightLayer) - base));
}
public short getVariation(final int groundTexture, final int variation) {
final Texture texture = this.tilesetTextures.get(groundTexture);
// Extended ?
if (texture.getWidth() > texture.getHeight()) {
if (variation < 16) {
return (short) (16 + variation);
}
else if (variation == 16) {
return 15;
}
else {
return 0;
}
}
else {
if (variation == 0) {
return 0;
}
else {
return 15;
}
}
}
public int cliffGroundIndex(final int whichCliff) {
final String whichTile = this.cliffTilesets.get(whichCliff).get("groundTile").toString();
final List<MappedDataRow> tilesets = this.tilesets;
for (int i = 0, l = tilesets.size(); i < l; i++) {
if (tilesets.get(i).get("tileID").toString().equals(whichTile)) {
return i;
}
}
throw new IllegalArgumentException(Integer.toString(whichCliff));
}
public int cornerTexture(final int column, final int row) {
final Corner[][] corners = this.corners;
final int columns = this.columns;
final int rows = this.rows;
for (int y = -1; y < 1; y++) {
for (int x = -1; x < 1; x++) {
if (((column + x) > 0) && ((column + x) < (columns - 1)) && ((row + y) > 0)
&& ((row + y) < (rows - 1))) {
if (this.isCliff(column + x, row + y)) {
int texture = corners[row + y][column + x].getCliffTexture();
if (texture == 15) {
texture = 1;
}
return this.cliffGroundIndex(texture);
}
}
}
}
final Corner corner = corners[row][column];
if (corner.getBlight() != 0) {
return this.blightTextureIndex;
}
return corner.getGroundTexture();
}
public void update() {
this.waterIndex += this.waterIncreasePerFrame;
if (this.waterIndex >= this.waterTextures.size()) {
this.waterIndex = 0;
}
}
public void renderGround(final GL20 gl, final WebGL webgl, final Scene worldScene) {
final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays;
final ShaderProgram shader = this.groundShader;
final List<Texture> tilesetTextures = this.tilesetTextures;
final int instanceAttrib = shader.getAttributeLocation("a_InstanceID");
final int positionAttrib = shader.getAttributeLocation("a_position");
final int texturesAttrib = shader.getAttributeLocation("a_textures");
final int variationsAttrib = shader.getAttributeLocation("a_variations");
final int tilesetCount = tilesetTextures.size();
gl.glEnable(GL20.GL_BLEND);
gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
webgl.useShaderProgram(shader);
shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix);
shader.setUniform2fv("u_offset", this.centerOffset, 0, 2);
sizeHeap[0] = this.columns;
sizeHeap[1] = this.rows;
shader.setUniform2fv("u_size", sizeHeap, 0, 2);
shader.setUniformi("u_heightMap", 15);
gl.glActiveTexture(GL20.GL_TEXTURE15);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer);
shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer);
shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0);
instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer);
shader.setVertexAttribute(texturesAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0);
instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 1);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer);
shader.setVertexAttribute(variationsAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0);
instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 1);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer);
shader.setUniformi("u_baseTileset", 0);
for (int i = 0, l = Math.min(tilesetCount, 15); i < l; i++) {
final int isExtended = (tilesetTextures.get(i).getWidth() > tilesetTextures.get(i).getHeight()) ? 1 : 0;
shader.setUniformf("u_extended[" + i + "]", isExtended);
shader.setUniformi("u_tilesets[" + i + "]", i);
tilesetTextures.get(i).bind(i);
}
instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0,
this.rows * this.columns);
if (tilesetCount > 15) {
shader.setUniformi("u_baseTileset", 15);
for (int i = 0, l = tilesetCount - 15; i < l; i++) {
final int isExtended = (tilesetTextures.get(i + 15).getWidth() > tilesetTextures.get(i + 15)
.getHeight()) ? 1 : 0;
shader.setUniformf("u_extended[" + i + "]", isExtended);
tilesetTextures.get(i + 15).bind(i);
}
instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0,
this.rows * this.columns);
}
instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 0);
instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 0);
instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0);
}
public void renderWater(final GL20 gl, final WebGL webgl, final Scene worldScene) {
final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays;
final ShaderProgram shader = this.waterShader;
final int instanceAttrib = shader.getAttributeLocation("a_InstanceID");
final int positionAttrib = shader.getAttributeLocation("a_position");
final int isWaterAttrib = shader.getAttributeLocation("a_isWater");
gl.glDepthMask(false);
gl.glEnable(GL20.GL_BLEND);
gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
webgl.useShaderProgram(shader);
shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix);
shader.setUniform2fv("u_offset", this.centerOffset, 0, 2);
sizeHeap[0] = this.columns;
sizeHeap[1] = this.rows;
shader.setUniform2fv("u_size", sizeHeap, 0, 2);
shader.setUniformi("u_heightMap", 0);
shader.setUniformi("u_waterHeightMap", 1);
shader.setUniformi("u_waterTexture", 2);
shader.setUniformf("u_offsetHeight", this.waterHeightOffset);
shader.setUniform4fv("u_maxDeepColor", this.maxDeepColor, 0, 4);
shader.setUniform4fv("u_minDeepColor", this.minDeepColor, 0, 4);
shader.setUniform4fv("u_maxShallowColor", this.maxShallowColor, 0, 4);
shader.setUniform4fv("u_minShallowColor", this.minShallowColor, 0, 4);
gl.glActiveTexture(GL20.GL_TEXTURE0);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap);
gl.glActiveTexture(GL20.GL_TEXTURE1);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap);
this.waterTextures.get((int) this.waterIndex).bind(2);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer);
shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer);
shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0);
instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1);
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer);
shader.setVertexAttribute(isWaterAttrib, 1, GL20.GL_UNSIGNED_BYTE, false, 1, 0);
instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 1);
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer);
instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0,
this.rows * this.columns);
instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 0);
instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0);
}
public void renderCliffs(final GL20 gl, final WebGL webGL, final Scene worldScene) {
final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays;
final ShaderProgram shader = this.cliffShader;
gl.glDisable(GL20.GL_BLEND);
webGL.useShaderProgram(shader);
shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix);
shader.setUniformi("u_heightMap", 0);
shader.setUniformf("u_pixel[0]", 1 / (float) (this.columns + 1));
shader.setUniformf("u_pixel[1]", 1 / (float) (this.rows + 1));
shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2);
shader.setUniformi("u_texture1", 1);
shader.setUniformi("u_texture2", 2);
gl.glActiveTexture(GL20.GL_TEXTURE0);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap);
gl.glActiveTexture(GL20.GL_TEXTURE1);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(0).getGlTarget());
if (this.cliffTextures.size() > 1) {
gl.glActiveTexture(GL20.GL_TEXTURE2);
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(1).getGlTarget());
}
// Set instanced attributes.
for (final TerrainModel cliff : this.cliffModels) {
cliff.render(shader);
}
}
public Vector3 groundNormal(final Vector3 out, int x, int y) {
final float[] centerOffset = this.centerOffset;
final int[] mapSize = this.mapSize;
x = (int) ((x - centerOffset[0]) / 128);
y = (int) ((y - centerOffset[1]) / 128);
final int cellX = x;
final int cellY = y;
// See if this coordinate is in the map
if ((cellX >= 0) && (cellX < (mapSize[0] - 1)) && (cellY >= 0) && (cellY < (mapSize[1] - 1))) {
// See http://gamedev.stackexchange.com/a/24574
final Corner[][] corners = this.corners;
final float bottomLeft = corners[cellY][cellX].getGroundHeight();
final float bottomRight = corners[cellY][cellX + 1].getGroundHeight();
final float topLeft = corners[cellY + 1][cellX].getGroundHeight();
final float topRight = corners[cellY + 1][cellX + 1].getGroundHeight();
final int sqX = x - cellX;
final int sqY = y - cellY;
if ((sqX + sqY) < 1) {
normalHeap1.set(1, 0, bottomRight - bottomLeft);
normalHeap2.set(0, 1, topLeft - bottomLeft);
}
else {
normalHeap1.set(-1, 0, topRight - topLeft);
normalHeap2.set(0, 1, topRight - bottomRight);
}
out.set(normalHeap1.crs(normalHeap2)).nor();
}
else {
out.set(0, 0, 1);
}
return out;
}
}