Visually able to see units and glitchy terrain now

This commit is contained in:
Retera 2020-01-21 20:20:37 -06:00
parent 015b04c371
commit e998367f77
29 changed files with 1446 additions and 129 deletions

View File

@ -35,7 +35,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
private ModelViewer viewer;
private MdxModel model;
private CameraManager cameraManager;
private static int VAO;
public static int VAO;
private final Rectangle tempRect = new Rectangle();
private BitmapFont font;

View File

@ -0,0 +1,253 @@
package com.etheller.warsmash;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Arrays;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.DataSourceDescriptor;
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
import com.etheller.warsmash.viewer5.Camera;
import com.etheller.warsmash.viewer5.CanvasProvider;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor {
private DataSource codebase;
private War3MapViewer viewer;
private CameraManager cameraManager;
private final Rectangle tempRect = new Rectangle();
private BitmapFont font;
private SpriteBatch batch;
@Override
public void create() {
final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4);
tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
final IntBuffer temp = tempByteBuffer.asIntBuffer();
Gdx.gl30.glGenVertexArrays(1, temp);
WarsmashGdxGame.VAO = temp.get(0);
Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO);
final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER);
System.err.println("Renderer: " + renderer);
// final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127");
final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq");
final FolderDataSourceDescriptor war3xLocalmpq = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\War3xLocal.mpq\\enus-war3local.mpq");
final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor(
"D:\\NEEDS_ORGANIZING\\MPQBuild\\Test");
final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor(".");
this.codebase = new CompoundDataSourceDescriptor(
Arrays.<DataSourceDescriptor>asList(war3mpq, war3xLocalmpq, testingFolder, currentFolder))
.createDataSource();
this.viewer = new War3MapViewer(this.codebase, this);
try {
this.viewer.loadMap("ReforgedGeorgeVacation.w3x");
}
catch (final IOException e) {
throw new RuntimeException(e);
}
this.cameraManager = new CameraManager();
this.cameraManager.setupCamera(this.viewer.worldScene);
System.out.println("Loaded");
Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); // TODO remove white background
this.font = new BitmapFont();
this.batch = new SpriteBatch();
Gdx.input.setInputProcessor(this);
}
@Override
public void render() {
Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO);
this.cameraManager.target.add(this.cameraVelocity.x, this.cameraVelocity.y, 0);
this.cameraManager.updateCamera();
this.viewer.updateAndRender();
// gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset);
// this.batch.begin();
// this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0);
// this.batch.end();
}
@Override
public void dispose() {
}
@Override
public float getWidth() {
return Gdx.graphics.getWidth();
}
@Override
public float getHeight() {
return Gdx.graphics.getHeight();
}
@Override
public void resize(final int width, final int height) {
this.tempRect.width = width;
this.tempRect.height = height;
this.cameraManager.camera.viewport(this.tempRect);
}
class CameraManager {
private CanvasProvider canvas;
private Camera camera;
private float moveSpeed;
private float rotationSpeed;
private float zoomFactor;
private float horizontalAngle;
private float verticalAngle;
private float distance;
private Vector3 position;
private Vector3 target;
private Vector3 worldUp;
private Vector3 vecHeap;
private Quaternion quatHeap;
private Quaternion quatHeap2;
// An orbit camera setup example.
// Left mouse button controls the orbit itself.
// The right mouse button allows to move the camera and the point it's looking
// at on the XY plane.
// Scrolling zooms in and out.
private void setupCamera(final Scene scene) {
this.canvas = scene.viewer.canvas;
this.camera = scene.camera;
this.moveSpeed = 2;
this.rotationSpeed = (float) (Math.PI / 180);
this.zoomFactor = 0.1f;
this.horizontalAngle = 0;// (float) (Math.PI / 2);
this.verticalAngle = (float) (Math.PI / 4);
this.distance = 1600;
this.position = new Vector3();
this.target = new Vector3(0, 0, 50);
this.worldUp = new Vector3(0, 0, 1);
this.vecHeap = new Vector3();
this.quatHeap = new Quaternion();
this.quatHeap2 = new Quaternion();
updateCamera();
// cameraUpdate();
}
private void updateCamera() {
// Limit the vertical angle so it doesn't flip.
// Since the camera uses a quaternion, flips don't matter to it, but this feels
// better.
this.verticalAngle = (float) Math.min(Math.max(0.01, this.verticalAngle), Math.PI - 0.01);
this.quatHeap.idt();
this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle);
this.quatHeap2.idt();
this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle);
this.quatHeap.mul(this.quatHeap2);
this.position.set(0, 0, 1);
this.quatHeap.transform(this.position);
this.position.scl(this.distance);
this.position = this.position.add(this.target);
this.camera.moveToAndFace(this.position, this.target, this.worldUp);
}
// private void cameraUpdate() {
//
// }
}
private final float cameraSpeed = 100.0f;
private final Vector2 cameraVelocity = new Vector2();
@Override
public boolean keyDown(final int keycode) {
if (keycode == Input.Keys.LEFT) {
this.cameraVelocity.x = -this.cameraSpeed;
}
else if (keycode == Input.Keys.RIGHT) {
this.cameraVelocity.x = this.cameraSpeed;
}
else if (keycode == Input.Keys.DOWN) {
this.cameraVelocity.y = -this.cameraSpeed;
}
else if (keycode == Input.Keys.UP) {
this.cameraVelocity.y = this.cameraSpeed;
}
return true;
}
@Override
public boolean keyUp(final int keycode) {
if (keycode == Input.Keys.LEFT) {
this.cameraVelocity.x = 0;
}
else if (keycode == Input.Keys.RIGHT) {
this.cameraVelocity.x = 0;
}
else if (keycode == Input.Keys.DOWN) {
this.cameraVelocity.y = 0;
}
else if (keycode == Input.Keys.UP) {
this.cameraVelocity.y = 0;
}
return true;
}
@Override
public boolean keyTyped(final char character) {
return false;
}
@Override
public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) {
return false;
}
@Override
public boolean touchUp(final int screenX, final int screenY, final int pointer, final int button) {
return false;
}
@Override
public boolean touchDragged(final int screenX, final int screenY, final int pointer) {
return false;
}
@Override
public boolean mouseMoved(final int screenX, final int screenY) {
return false;
}
@Override
public boolean scrolled(final int amount) {
return false;
}
}

View File

@ -624,6 +624,25 @@ public final class MutableObjectData {
return this.parentWC3Object.getFieldValue(key) == 1;
}
public int readSLKTagInt(final String key) {
if (MutableObjectData.this.metaNameToMetaId.containsKey(key)) {
return getFieldAsInteger(MutableObjectData.this.metaNameToMetaId.get(key), 0);
}
return this.parentWC3Object.getFieldValue(key);
}
public float readSLKTagFloat(final String key) {
if (MutableObjectData.this.metaNameToMetaId.containsKey(key)) {
return getFieldAsFloat(MutableObjectData.this.metaNameToMetaId.get(key), 0);
}
try {
return Float.parseFloat(this.parentWC3Object.getField(key));
}
catch (final NumberFormatException exc) {
return Float.NaN;
}
}
public String getName() {
String name = getFieldAsString(MutableObjectData.this.editorData.getNameField(),
MutableObjectData.this.worldEditorDataType == WorldEditorDataType.UPGRADES ? 1 : 0);

View File

@ -56,7 +56,7 @@ public class MappedData {
key = "column" + j;
}
mapped.put(key, row.get(j));
mapped.put(key, j < row.size() ? row.get(j) : null);
}
}
}

View File

@ -321,8 +321,8 @@ public enum RenderMathUtils {
return -1;
}
public static int testCell(final Vector4[] planes, final int left, final int right, final int bottom, final int top,
int first) {
public static int testCell(final Vector4[] planes, final float left, final float right, final float bottom,
final float top, int first) {
if (first == -1) {
first = 0;
}
@ -340,7 +340,7 @@ public enum RenderMathUtils {
return -1;
}
public static float distance2Plane2(final Vector4 plane, final int px, final int py) {
public static float distance2Plane2(final Vector4 plane, final float px, final float py) {
return (plane.x * px) + (plane.y * py) + plane.w;
}

View File

@ -1,5 +1,5 @@
package com.etheller.warsmash.util;
public class WarsmashConstants {
public static final int MAX_PLAYERS = 16;
public static final int MAX_PLAYERS = 28;
}

View File

@ -45,10 +45,17 @@ public abstract class GdxTextureResource extends Texture {
return this.gdxTexture.glTarget;
}
@Override
public int getGlHandle() {
return this.gdxTexture.getTextureObjectHandle();
}
@Override
public void setWrapS(final boolean wrapS) {
this.gdxTexture.setWrap(wrapS ? TextureWrap.Repeat : TextureWrap.ClampToEdge, this.gdxTexture.getVWrap());
}
@Override
public void setWrapT(final boolean wrapT) {
this.gdxTexture.setWrap(this.gdxTexture.getUWrap(), wrapT ? TextureWrap.Repeat : TextureWrap.ClampToEdge);
}

View File

@ -13,7 +13,8 @@ public class Grid {
private final int rows;
final GridCell[] cells;
public Grid(final float x, float y, final int width, final int depth, final int cellWidth, final int cellDepth) {
public Grid(final float x, final float y, final int width, final int depth, final int cellWidth,
final int cellDepth) {
final int columns = width / cellWidth;
final int rows = depth / cellDepth;
@ -29,10 +30,10 @@ public class Grid {
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
final int left = x + (columns * cellWidth);
final int right = left + cellWidth;
final int bottom = y = row * cellDepth;
final int top = bottom + cellDepth;
final float left = x + (columns * cellWidth);
final float right = left + cellWidth;
final float bottom = y + (row * cellDepth);
final float top = bottom + cellDepth;
this.cells[(row * columns) + column] = new GridCell(left, right, bottom, top);
}

View File

@ -6,15 +6,15 @@ import java.util.List;
import com.etheller.warsmash.util.RenderMathUtils;
public class GridCell {
public final int left;
public final int right;
public final int bottom;
public final int top;
public final float left;
public final float right;
public final float bottom;
public final float top;
public int plane;
final List<ModelInstance> instances;
public final boolean visible;
public GridCell(final int left, final int right, final int bottom, final int top) {
public GridCell(final float left, final float right, final float bottom, final float top) {
this.left = left;
this.right = right;
this.bottom = bottom;

View File

@ -93,6 +93,9 @@ public abstract class ModelInstance extends Node {
}
public boolean isVisible(final Camera camera) {
if (true) {
return true;
}
final float x = this.worldLocation.x;
final float y = this.worldLocation.y;
final float z = this.worldLocation.z;

View File

@ -45,7 +45,7 @@ public class ModelViewer {
this.resources = new ArrayList<>();
this.fetchCache = new HashMap<>();
this.handlers = new HashSet<ResourceHandler>();
this.frameTime = 1000 / 6;
this.frameTime = 1000 / 60;
this.gl = Gdx.gl;
this.webGL = new WebGL(this.gl);
this.scenes = new ArrayList<>();

View File

@ -17,7 +17,7 @@ public abstract class Node extends GenericNode {
public Node() {
this.pivot = new Vector3();
this.localLocation = new Vector3();
this.localRotation = new Quaternion(0, 0, 0, 1);
this.localRotation = new Quaternion();
this.localScale = new Vector3(1, 1, 1);
this.worldLocation = new Vector3();
this.worldRotation = new Quaternion();

View File

@ -11,7 +11,11 @@ public interface PathSolver {
public static final PathSolver DEFAULT = new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
final int dotIndex = src.lastIndexOf('.');
if (dotIndex == -1) {
throw new IllegalStateException("unable to resolve: " + src);
}
return new SolvedPath(src, src.substring(dotIndex), true);
}
};
}

View File

@ -71,6 +71,11 @@ public abstract class RawOpenGLTextureResource extends Texture {
return this.target;
}
@Override
public int getGlHandle() {
return this.handle;
}
@Override
public void setWrapS(final boolean wrapS) {
this.wrapS = wrapS ? GL20.GL_REPEAT : GL20.GL_CLAMP_TO_EDGE;

View File

@ -7,6 +7,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.math.Rectangle;
/**
@ -43,6 +44,15 @@ public class Scene {
public final EmittedObjectUpdater emitterObjectUpdater;
public final Map<TextureMapper, RenderBatch> batches;
public final Comparator<ModelInstance> instanceDepthComparator;
/**
* Similar to WebGL's own `alpha` parameter.
*
* If false, the scene will be cleared before rendering, meaning that scenes
* behind it won't be visible through it.
*
* If true, alpha works as usual.
*/
public boolean alpha = false;
public Scene(final ModelViewer viewer) {
final CanvasProvider canvas = viewer.canvas;
@ -185,8 +195,8 @@ public class Scene {
if (cell.isVisible(this.camera)) {
this.visibleCells += 1;
for (int i = 0, l = cell.instances.size(); i < l; i++) {
final ModelInstance instance = cell.instances.get(i);
for (final ModelInstance instance : new ArrayList<>(cell.instances)) {
// final ModelInstance instance = cell.instances.get(i);
if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) {
instance.cullFrame = frame;
@ -232,6 +242,23 @@ public class Scene {
this.updatedParticles = this.emitterObjectUpdater.objects.size();
}
public void startFrame() {
final GL20 gl = this.viewer.gl;
final Rectangle viewport = this.camera.rect;
// Set the viewport
gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height);
// Allow to render only in the viewport
gl.glScissor((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height);
// If this scene doesn't want alpha, clear it.
if (!this.alpha) {
gl.glDepthMask(true);
gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
}
}
public void renderOpaque() {
final Rectangle viewport = this.camera.rect;
this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height);

View File

@ -85,9 +85,9 @@ public abstract class SkeletalNode extends GenericNode {
computedScaling = this.localScale;
final Vector3 parentScale = this.parent.worldScale;
this.worldScale.x = parentScale.x * this.worldScale.x;
this.worldScale.y = parentScale.y * this.worldScale.y;
this.worldScale.z = parentScale.z * this.worldScale.z;
this.worldScale.x = parentScale.x * this.localScale.x;
this.worldScale.y = parentScale.y * this.localScale.y;
this.worldScale.z = parentScale.z * this.localScale.z;
}
if (this.billboarded) {

View File

@ -19,6 +19,8 @@ public abstract class Texture extends HandlerResource<ResourceHandler> {
public abstract int getGlTarget();
public abstract int getGlHandle();
public abstract void setWrapS(final boolean wrapS);
public abstract void setWrapT(final boolean wrapT);

View File

@ -53,7 +53,7 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje
this.columns = emitter.getColumns();
this.rows = emitter.getRows();
if (this.replaceableId == 0) {
if (replaceableId == 0) {
this.internalTexture = model.getTextures().get(emitter.getTextureId());
}
else if ((replaceableId == 1) || (replaceableId == 2)) {

View File

@ -1,29 +1,29 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.util.MappedDataRow;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.BatchedInstance;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
public class Doodad {
private final BatchedInstance instance;
private final MappedDataRow row;
private final ModelInstance instance;
private final MutableGameObject row;
public Doodad(final War3MapViewer map, final MdxModel model, final MappedDataRow row,
public Doodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad) {
final boolean isSimple = ((Number) row.get("lightweight")).intValue() == 1;
BatchedInstance instance;
final boolean isSimple = row.readSLKTagBoolean("lightweight");
ModelInstance instance;
if (isSimple) {
instance = (BatchedInstance) model.addInstance(1);
instance = model.addInstance(1);
}
else {
instance = (BatchedInstance) model.addInstance();
instance = model.addInstance();
}
instance.move(doodad.getLocation());
instance.rotateLocal(new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, doodad.getAngle()));
instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, doodad.getAngle()));
instance.scale(doodad.getScale());
instance.setScene(map.worldScene);

View File

@ -0,0 +1,45 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
public class HiveWEShaders {
public static final class Cliffs {
private Cliffs() {
}
public static final String vert = "#version 450 core\r\n" + //
"\r\n" + //
"layout (location = 0) in vec3 vPosition;\r\n" + //
"layout (location = 1) in vec2 vUV;\r\n" + //
"layout (location = 2) in vec3 vNormal;\r\n" + //
"layout (location = 3) in vec4 vOffset;\r\n" + //
"\r\n" + //
"layout (location = 0) uniform mat4 MVP;\r\n" + //
"\r\n" + //
"layout (binding = 1) uniform sampler2D height_texture;\r\n" + //
"\r\n" + //
"layout (location = 0) out vec3 UV;\r\n" + //
"layout (location = 1) out vec3 Normal;\r\n" + //
"layout (location = 2) out vec2 pathing_map_uv;\r\n" + //
"\r\n" + //
"void main() {\r\n" + //
" pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + //
" \r\n" + //
" ivec2 size = textureSize(height_texture, 0);\r\n" + //
" float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128) / vec2(size)).r;\r\n"
+ //
"\r\n" + //
" gl_Position = MVP * vec4(vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128, 1);\r\n"
+ //
" UV = vec3(vUV, vOffset.a);\r\n" + //
"\r\n" + //
" ivec2 height_pos = ivec2(vOffset.xy + vec2(vPosition.x + 128, vPosition.y) / 128);\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" + //
" vec3 terrain_normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + //
"\r\n" + //
" Normal = terrain_normal;\r\n" + //
"}";
}
}

View File

@ -47,6 +47,9 @@ public class StandSequence {
final int sequencesLeft = filtered.size() - i;
final int random = (int) (i + Math.floor(Math.random() * sequencesLeft));
if (sequencesLeft <= 0) {
return new IndexedSequence(null, 0);
}
final IndexedSequence sequence = filtered.get(random);
return sequence;

View File

@ -1,7 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.util.MappedDataRow;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance;
@ -9,9 +9,9 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance;
public class TerrainDoodad {
private static final float[] locationHeap = new float[3];
private final MdxSimpleInstance instance;
private final MappedDataRow row;
private final MutableGameObject row;
public TerrainDoodad(final War3MapViewer map, final MdxModel model, final MappedDataRow row,
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 MdxSimpleInstance instance = (MdxSimpleInstance) model.addInstance(1);
@ -20,8 +20,7 @@ public class TerrainDoodad {
locationHeap[0] = (doodad.getLocation()[1] * 128) + centerOffset[1] + 128;
instance.move(locationHeap);
instance.rotateLocal(
new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, ((Number) row.get("fixedRot")).floatValue()));
instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, row.readSLKTagFloat("fixedRot")));
instance.setScene(map.worldScene);
this.instance = instance;

View File

@ -19,6 +19,37 @@ import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.etheller.warsmash.viewer5.gl.WebGL;
/*
*
*
PuffTheMagicDragonIsNoMoreToday at 9:06 PM
that being said I think we call the tiles corners or whatever, since we store the points rather than the quads
but at the same time there's also per-quad data xDS
ReteraToday at 9:06 PM
yeah, I've seen the corner class go by several times while transcribing this latest bit to java
hmmm
PuffTheMagicDragonIsNoMoreToday at 9:07 PM
some things are per-corner, some per-tile
note that the existing code only somewhat takes care of cliff/terrain doodads, and it's not tested much
ReteraToday at 9:10 PM
well, I'll probably rip some stuff off of HiveWE too
that was what I was thinking I'd probably do if it was necessary
PuffTheMagicDragonIsNoMoreToday at 9:11 PM
last time I checked it wasn't implemented there, but that was a long time ago
basically you want to not have ground tiles where the doodads exist, and to know where that is you need the pathing texture used by the doodads
ReteraToday at 9:12 PM
oh yea
makes sense
PuffTheMagicDragonIsNoMoreToday at 9:13 PM
they also can't be supported by my hacky TerrainModel or whatever it was called, since at least some of them require blending
*
*/
public class TerrainModel {
private static final IntBuffer GL_TEMP_BUFFER = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder())
.asIntBuffer();

View File

@ -1,7 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.util.MappedDataRow;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
@ -9,25 +9,34 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
public class Unit {
private static final float[] heapZ = new float[3];
public final MdxComplexInstance instance;
public final MappedDataRow row;
public final MutableGameObject row;
public Unit(final War3MapViewer map, final MdxModel model, final MappedDataRow row,
public Unit(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit) {
final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance();
instance.move(unit.getLocation());
instance.rotateLocal(new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, unit.getAngle()));
float angle;
if ((row != null) && row.readSLKTagBoolean("isBldg")) {
angle = (float) Math.toRadians(270.0f);
}
else {
angle = unit.getAngle();
}
// instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle);
instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle));
instance.scale(unit.getScale());
instance.setTeamColor(unit.getPlayer());
instance.setScene(map.worldScene);
if (row != null) {
heapZ[0] = (((Number) row.get("moveHeight")).floatValue());
heapZ[2] = row.readSLKTagFloat("moveHeight");
instance.move(heapZ);
instance.setVertexColor(new float[] { ((Number) row.get("red")).intValue() / 255f,
((Number) row.get("green")).intValue() / 255f, ((Number) row.get("blue")).intValue() / 255f });
instance.uniformScale(((Number) row.get("modelScale")).floatValue());
instance.setVertexColor(new float[] { (row.readSLKTagInt("red")) / 255f,
(row.readSLKTagInt("green")) / 255f, (row.readSLKTagInt("blue")) / 255f });
instance.uniformScale(row.readSLKTagFloat("modelScale"));
}
this.instance = instance;

View File

@ -0,0 +1,154 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.util.HashMap;
import java.util.Map;
public class Variations {
private static final Map<String, Integer> CLIFF_VARS;
private static final Map<String, Integer> CITY_CLIFF_VARS;
static {
final Map<String, Integer> cliffVariations = new HashMap<>();
cliffVariations.put("AAAB", 1);
cliffVariations.put("AAAC", 1);
cliffVariations.put("AABA", 1);
cliffVariations.put("AABB", 2);
cliffVariations.put("AABC", 0);
cliffVariations.put("AACA", 1);
cliffVariations.put("AACB", 0);
cliffVariations.put("AACC", 1);
cliffVariations.put("ABAA", 1);
cliffVariations.put("ABAB", 1);
cliffVariations.put("ABAC", 0);
cliffVariations.put("ABBA", 2);
cliffVariations.put("ABBB", 1);
cliffVariations.put("ABBC", 0);
cliffVariations.put("ABCA", 0);
cliffVariations.put("ABCB", 0);
cliffVariations.put("ABCC", 0);
cliffVariations.put("ACAA", 1);
cliffVariations.put("ACAB", 0);
cliffVariations.put("ACAC", 1);
cliffVariations.put("ACBA", 0);
cliffVariations.put("ACBB", 0);
cliffVariations.put("ACBC", 0);
cliffVariations.put("ACCA", 1);
cliffVariations.put("ACCB", 0);
cliffVariations.put("ACCC", 1);
cliffVariations.put("BAAA", 1);
cliffVariations.put("BAAB", 1);
cliffVariations.put("BAAC", 0);
cliffVariations.put("BABA", 1);
cliffVariations.put("BABB", 1);
cliffVariations.put("BABC", 0);
cliffVariations.put("BACA", 0);
cliffVariations.put("BACB", 0);
cliffVariations.put("BACC", 0);
cliffVariations.put("BBAA", 1);
cliffVariations.put("BBAB", 1);
cliffVariations.put("BBAC", 0);
cliffVariations.put("BBBA", 1);
cliffVariations.put("BBCA", 0);
cliffVariations.put("BCAA", 0);
cliffVariations.put("BCAB", 0);
cliffVariations.put("BCAC", 0);
cliffVariations.put("BCBA", 0);
cliffVariations.put("BCCA", 0);
cliffVariations.put("CAAA", 1);
cliffVariations.put("CAAB", 0);
cliffVariations.put("CAAC", 1);
cliffVariations.put("CABA", 0);
cliffVariations.put("CABB", 0);
cliffVariations.put("CABC", 0);
cliffVariations.put("CACA", 1);
cliffVariations.put("CACB", 0);
cliffVariations.put("CACC", 1);
cliffVariations.put("CBAA", 0);
cliffVariations.put("CBAB", 0);
cliffVariations.put("CBAC", 0);
cliffVariations.put("CBBA", 0);
cliffVariations.put("CBCA", 0);
cliffVariations.put("CCAA", 1);
cliffVariations.put("CCAB", 0);
cliffVariations.put("CCAC", 1);
cliffVariations.put("CCBA", 0);
cliffVariations.put("CCCA", 1);
CLIFF_VARS = cliffVariations;
final Map<String, Integer> cityCliffVariations = new HashMap<>();
cityCliffVariations.put("AAAB", 2);
cityCliffVariations.put("AAAC", 1);
cityCliffVariations.put("AABA", 1);
cityCliffVariations.put("AABB", 3);
cityCliffVariations.put("AABC", 0);
cityCliffVariations.put("AACA", 1);
cityCliffVariations.put("AACB", 0);
cityCliffVariations.put("AACC", 3);
cityCliffVariations.put("ABAA", 1);
cityCliffVariations.put("ABAB", 2);
cityCliffVariations.put("ABAC", 0);
cityCliffVariations.put("ABBA", 3);
cityCliffVariations.put("ABBB", 0);
cityCliffVariations.put("ABBC", 0);
cityCliffVariations.put("ABCA", 0);
cityCliffVariations.put("ABCB", 0);
cityCliffVariations.put("ABCC", 0);
cityCliffVariations.put("ACAA", 1);
cityCliffVariations.put("ACAB", 0);
cityCliffVariations.put("ACAC", 2);
cityCliffVariations.put("ACBA", 0);
cityCliffVariations.put("ACBB", 0);
cityCliffVariations.put("ACBC", 0);
cityCliffVariations.put("ACCA", 3);
cityCliffVariations.put("ACCB", 0);
cityCliffVariations.put("ACCC", 1);
cityCliffVariations.put("BAAA", 1);
cityCliffVariations.put("BAAB", 3);
cityCliffVariations.put("BAAC", 0);
cityCliffVariations.put("BABA", 2);
cityCliffVariations.put("BABB", 0);
cityCliffVariations.put("BABC", 0);
cityCliffVariations.put("BACA", 0);
cityCliffVariations.put("BACB", 0);
cityCliffVariations.put("BACC", 0);
cityCliffVariations.put("BBAA", 3);
cityCliffVariations.put("BBAB", 1);
cityCliffVariations.put("BBAC", 0);
cityCliffVariations.put("BBBA", 1);
cityCliffVariations.put("BBCA", 0);
cityCliffVariations.put("BCAA", 0);
cityCliffVariations.put("BCAB", 0);
cityCliffVariations.put("BCAC", 0);
cityCliffVariations.put("BCBA", 0);
cityCliffVariations.put("BCCA", 0);
cityCliffVariations.put("CAAA", 1);
cityCliffVariations.put("CAAB", 0);
cityCliffVariations.put("CAAC", 3);
cityCliffVariations.put("CABA", 0);
cityCliffVariations.put("CABB", 0);
cityCliffVariations.put("CABC", 0);
cityCliffVariations.put("CACA", 2);
cityCliffVariations.put("CACB", 0);
cityCliffVariations.put("CACC", 1);
cityCliffVariations.put("CBAA", 0);
cityCliffVariations.put("CBAB", 0);
cityCliffVariations.put("CBAC", 0);
cityCliffVariations.put("CBBA", 0);
cityCliffVariations.put("CBCA", 0);
cityCliffVariations.put("CCAA", 3);
cityCliffVariations.put("CCAB", 0);
cityCliffVariations.put("CCAC", 1);
cityCliffVariations.put("CCBA", 0);
cityCliffVariations.put("CCCA", 1);
CITY_CLIFF_VARS = cityCliffVariations;
}
public static int getCliffVariation(final String dir, final String tag, final int variation) {
if ("Cliffs".equals(dir)) {
return Math.min(variation, CLIFF_VARS.get(tag));
}
else {
return Math.min(variation, CITY_CLIFF_VARS.get(tag));
}
}
}

View File

@ -11,7 +11,7 @@ public class W3xShaders {
"uniform vec2 u_pixel;\r\n" + //
"uniform vec2 u_centerOffset;\r\n" + //
"attribute vec3 a_position;\r\n" + //
"attribute vec3 a_normal;\r\n" + //
// "attribute vec3 a_normal;\r\n" + //
"attribute vec2 a_uv;\r\n" + //
"attribute vec3 a_instancePosition;\r\n" + //
"attribute float a_instanceTexture;\r\n" + //
@ -25,16 +25,16 @@ public class W3xShaders {
" // The bottom left corner of the map tile this vertex is on.\r\n" + //
" vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + //
" // Get the 4 closest heights in the height map.\r\n" + //
" float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + //
" float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + //
" float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + //
" float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + //
" float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).r;\r\n" + //
" float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).r;\r\n" + //
" float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).r;\r\n" + //
" float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).r;\r\n" + //
" \r\n" + //
" // Do a bilinear interpolation between the heights to get the final value.\r\n" + //
" float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + //
" float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + //
" float height = mix(bottom, top, a_position.y / 128.0);\r\n" + //
" v_normal = a_normal;\r\n" + //
// " v_normal = a_normal;\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_texture = a_instanceTexture;\r\n" + //
" v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + //
@ -47,7 +47,7 @@ public class W3xShaders {
"precision mediump float;\r\n" + //
"uniform sampler2D u_texture1;\r\n" + //
"uniform sampler2D u_texture2;\r\n" + //
"varying vec3 v_normal;\r\n" + //
// "varying vec3 v_normal;\r\n" + //
"varying vec2 v_uv;\r\n" + //
"varying float v_texture;\r\n" + //
"varying vec3 v_position;\r\n" + //
@ -81,35 +81,35 @@ public class W3xShaders {
"varying vec3 v_normal;\r\n" + //
"const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + //
"vec4 sample(float tileset, vec2 uv) {\r\n" + //
" if (tileset == 0.0) {\r\n" + //
" if (tileset <= 0.5) {\r\n" + //
" return texture2D(u_tilesets[0], uv);\r\n" + //
" } else if (tileset == 1.0) {\r\n" + //
" } else if (tileset <= 1.5) {\r\n" + //
" return texture2D(u_tilesets[1], uv);\r\n" + //
" } else if (tileset == 2.0) {\r\n" + //
" } else if (tileset <= 2.5) {\r\n" + //
" return texture2D(u_tilesets[2], uv);\r\n" + //
" } else if (tileset == 3.0) {\r\n" + //
" } else if (tileset <= 3.5) {\r\n" + //
" return texture2D(u_tilesets[3], uv);\r\n" + //
" } else if (tileset == 4.0) {\r\n" + //
" } else if (tileset <= 4.5) {\r\n" + //
" return texture2D(u_tilesets[4], uv);\r\n" + //
" } else if (tileset == 5.0) {\r\n" + //
" } else if (tileset <= 5.5) {\r\n" + //
" return texture2D(u_tilesets[5], uv);\r\n" + //
" } else if (tileset == 6.0) {\r\n" + //
" } else if (tileset <= 6.5) {\r\n" + //
" return texture2D(u_tilesets[6], uv);\r\n" + //
" } else if (tileset == 7.0) {\r\n" + //
" } else if (tileset <= 7.5) {\r\n" + //
" return texture2D(u_tilesets[7], uv);\r\n" + //
" } else if (tileset == 8.0) {\r\n" + //
" } else if (tileset <= 8.5) {\r\n" + //
" return texture2D(u_tilesets[8], uv);\r\n" + //
" } else if (tileset == 9.0) {\r\n" + //
" } else if (tileset <= 9.5) {\r\n" + //
" return texture2D(u_tilesets[9], uv);\r\n" + //
" } else if (tileset == 10.0) {\r\n" + //
" } else if (tileset <= 10.5) {\r\n" + //
" return texture2D(u_tilesets[10], uv);\r\n" + //
" } else if (tileset == 11.0) {\r\n" + //
" } else if (tileset <= 11.5) {\r\n" + //
" return texture2D(u_tilesets[11], uv);\r\n" + //
" } else if (tileset == 12.0) {\r\n" + //
" } else if (tileset <= 12.5) {\r\n" + //
" return texture2D(u_tilesets[12], uv);\r\n" + //
" } else if (tileset == 13.0) {\r\n" + //
" } else if (tileset <= 13.5) {\r\n" + //
" return texture2D(u_tilesets[13], uv);\r\n" + //
" } else if (tileset == 14.0) {\r\n" + //
" } else if (tileset <= 14.5) {\r\n" + //
" return texture2D(u_tilesets[14], uv);\r\n" + //
" }\r\n" + //
"}\r\n" + //
@ -130,45 +130,66 @@ public class W3xShaders {
" }\r\n" + //
" // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + //
" gl_FragColor = color;\r\n" + //
"}\r\n" + //
"";
"}";
public static final String vert = "\r\n" + //
"uniform mat4 u_VP;\r\n" + //
"uniform sampler2D u_heightMap;\r\n" + //
"uniform vec2 u_pixel;\r\n" + //
"uniform vec2 u_centerOffset;\r\n" + //
"attribute vec3 a_position;\r\n" + //
"attribute vec3 a_normal;\r\n" + //
"attribute vec2 a_uv;\r\n" + //
"attribute vec3 a_instancePosition;\r\n" + //
"attribute float a_instanceTexture;\r\n" + //
"uniform vec2 u_size;\r\n" + //
"uniform vec2 u_offset;\r\n" + //
"uniform bool u_extended[14];\r\n" + //
"uniform float u_baseTileset;\r\n" + //
"attribute vec2 a_position;\r\n" + //
"attribute float a_InstanceID;\r\n" + //
"attribute vec4 a_textures;\r\n" + //
"attribute vec4 a_variations;\r\n" + //
"varying vec4 v_tilesets;\r\n" + //
"varying vec2 v_uv[4];\r\n" + //
"varying vec3 v_normal;\r\n" + //
"varying vec2 v_uv;\r\n" + //
"varying float v_texture;\r\n" + //
"varying vec3 v_position;\r\n" + //
"void main() {\r\n" + //
" // Half of a pixel in the cliff height map.\r\n" + //
" vec2 halfPixel = u_pixel * 0.5;\r\n" + //
" // The bottom left corner of the map tile this vertex is on.\r\n" + //
" vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + //
" // Get the 4 closest heights in the height map.\r\n" + //
" float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + //
" float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + //
" float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + //
" float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + //
" \r\n" + //
" // Do a bilinear interpolation between the heights to get the final value.\r\n" + //
" float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + //
" float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + //
" float height = mix(bottom, top, a_position.y / 128.0);\r\n" + //
" v_normal = a_normal;\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_texture = a_instanceTexture;\r\n" + //
" v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + //
" gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + //
"vec2 getCell(float variation) {\r\n" + //
" if (variation < 16.0) {\r\n" + //
" return vec2(mod(variation, 4.0), floor(variation / 4.0));\r\n" + //
" } else {\r\n" + //
" variation -= 16.0;\r\n" + //
" return vec2(4.0 + mod(variation, 4.0), floor(variation / 4.0));\r\n" + //
" }\r\n" + //
"}\r\n" + //
"";
"vec2 getUV(vec2 position, bool extended, float variation) {\r\n" + //
" vec2 cell = getCell(variation);\r\n" + //
" vec2 cellSize = vec2(extended ? 0.125 : 0.25, 0.25);\r\n" + //
" vec2 uv = vec2(position.x, 1.0 - position.y);\r\n" + //
" vec2 pixelSize = vec2(1.0 / 512.0, 1.0 / 256.0); /// Note: hardcoded to 512x256 for now.\r\n" + //
" return clamp((cell + uv) * cellSize, cell * cellSize + pixelSize, (cell + 1.0) * cellSize - pixelSize); \r\n"
+ //
"}\r\n" + //
"void main() {\r\n" + //
" vec4 textures = a_textures - u_baseTileset;\r\n" + //
" \r\n" + //
" if (textures[0] > 0.0 || textures[1] > 0.0 || textures[2] > 0.0 || textures[3] > 0.0) {\r\n" + //
" v_tilesets = textures;\r\n" + //
" v_uv[0] = getUV(a_position, u_extended[int(textures[0]) - 1], a_variations[0]);\r\n" + //
" v_uv[1] = getUV(a_position, u_extended[int(textures[1]) - 1], a_variations[1]);\r\n" + //
" v_uv[2] = getUV(a_position, u_extended[int(textures[2]) - 1], a_variations[2]);\r\n" + //
" v_uv[3] = getUV(a_position, u_extended[int(textures[3]) - 1], a_variations[3]);\r\n" + //
" vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + //
" vec2 base = corner + a_position;\r\n" + //
" float height = texture2D(u_heightMap, base / u_size).r;\r\n" + //
" float hL = texture2D(u_heightMap, vec2(base - vec2(1.0, 0.0)) / (u_size)).r;\r\n" + //
" float hR = texture2D(u_heightMap, vec2(base + vec2(1.0, 0.0)) / (u_size)).r;\r\n" + //
" float hD = texture2D(u_heightMap, vec2(base - vec2(0.0, 1.0)) / (u_size)).r;\r\n" + //
" float hU = texture2D(u_heightMap, vec2(base + vec2(0.0, 1.0)) / (u_size)).r;\r\n" + //
" v_normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + //
" gl_Position = u_VP * vec4(base * 128.0 + u_offset, height * 128.0, 1.0);\r\n" + //
" } else {\r\n" + //
" v_tilesets = vec4(0.0);\r\n" + //
" v_uv[0] = vec2(0.0);\r\n" + //
" v_uv[1] = vec2(0.0);\r\n" + //
" v_uv[2] = vec2(0.0);\r\n" + //
" v_uv[3] = vec2(0.0);\r\n" + //
" v_normal = vec3(0.0);\r\n" + //
" gl_Position = vec4(0.0);\r\n" + //
" }\r\n" + //
"}";
}
public static final class Water {
@ -208,8 +229,8 @@ public class W3xShaders {
" v_uv = a_position;\r\n" + //
" vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + //
" vec2 base = corner + a_position;\r\n" + //
" float height = texture2D(u_heightMap, base / u_size).a;\r\n" + //
" float waterHeight = texture2D(u_waterHeightMap, base / u_size).a + u_offsetHeight;\r\n" + //
" float height = texture2D(u_heightMap, base / u_size).r;\r\n" + //
" float waterHeight = texture2D(u_waterHeightMap, base / u_size).r + u_offsetHeight;\r\n" + //
" float value = clamp(waterHeight - height, 0.0, 1.0);\r\n" + //
" if (value <= deepLevel) {\r\n" + //
" value = max(0.0, value - minDepth) / (deepLevel - minDepth);\r\n" + //

View File

@ -0,0 +1,230 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
public class W3xShadersWebGLDeprecated {
public static final class Cliffs {
private Cliffs() {
}
public static final String vert = "\r\n" + //
"uniform mat4 u_VP;\r\n" + //
"uniform sampler2D u_heightMap;\r\n" + //
"uniform vec2 u_pixel;\r\n" + //
"uniform vec2 u_centerOffset;\r\n" + //
"attribute vec3 a_position;\r\n" + //
"attribute vec3 a_normal;\r\n" + //
"attribute vec2 a_uv;\r\n" + //
"attribute vec3 a_instancePosition;\r\n" + //
"attribute float a_instanceTexture;\r\n" + //
"varying vec3 v_normal;\r\n" + //
"varying vec2 v_uv;\r\n" + //
"varying float v_texture;\r\n" + //
"varying vec3 v_position;\r\n" + //
"void main() {\r\n" + //
" // Half of a pixel in the cliff height map.\r\n" + //
" vec2 halfPixel = u_pixel * 0.5;\r\n" + //
" // The bottom left corner of the map tile this vertex is on.\r\n" + //
" vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + //
" // Get the 4 closest heights in the height map.\r\n" + //
" float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + //
" float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + //
" float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + //
" float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + //
" \r\n" + //
" // Do a bilinear interpolation between the heights to get the final value.\r\n" + //
" float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + //
" float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + //
" float height = mix(bottom, top, a_position.y / 128.0);\r\n" + //
" v_normal = a_normal;\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_texture = a_instanceTexture;\r\n" + //
" v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + //
" gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + //
"}\r\n" + //
"";
public static final String frag = "\r\n" + //
"// #extension GL_OES_standard_derivatives : enable\r\n" + //
"precision mediump float;\r\n" + //
"uniform sampler2D u_texture1;\r\n" + //
"uniform sampler2D u_texture2;\r\n" + //
"varying vec3 v_normal;\r\n" + //
"varying vec2 v_uv;\r\n" + //
"varying float v_texture;\r\n" + //
"varying vec3 v_position;\r\n" + //
"// const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + //
"vec4 sample(int texture, vec2 uv) {\r\n" + //
" if (texture == 0) {\r\n" + //
" return texture2D(u_texture1, uv);\r\n" + //
" } else {\r\n" + //
" return texture2D(u_texture2, uv);\r\n" + //
" }\r\n" + //
"}\r\n" + //
"void main() {\r\n" + //
" vec4 color = sample(int(v_texture), v_uv);\r\n" + //
" // vec3 faceNormal = cross(dFdx(v_position), dFdy(v_position));\r\n" + //
" // vec3 normal = normalize((faceNormal + v_normal) * 0.5);\r\n" + //
" // color *= clamp(dot(normal, lightDirection) + 0.45, 0.1, 1.0);\r\n" + //
" gl_FragColor = color;\r\n" + //
"}\r\n" + //
"";
}
public static final class Ground {
private Ground() {
}
public static final String frag = "\r\n" + //
"precision mediump float;\r\n" + //
"uniform sampler2D u_tilesets[15];\r\n" + //
"varying vec4 v_tilesets;\r\n" + //
"varying vec2 v_uv[4];\r\n" + //
"varying vec3 v_normal;\r\n" + //
"const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + //
"vec4 sample(float tileset, vec2 uv) {\r\n" + //
" if (tileset == 0.0) {\r\n" + //
" return texture2D(u_tilesets[0], uv);\r\n" + //
" } else if (tileset == 1.0) {\r\n" + //
" return texture2D(u_tilesets[1], uv);\r\n" + //
" } else if (tileset == 2.0) {\r\n" + //
" return texture2D(u_tilesets[2], uv);\r\n" + //
" } else if (tileset == 3.0) {\r\n" + //
" return texture2D(u_tilesets[3], uv);\r\n" + //
" } else if (tileset == 4.0) {\r\n" + //
" return texture2D(u_tilesets[4], uv);\r\n" + //
" } else if (tileset == 5.0) {\r\n" + //
" return texture2D(u_tilesets[5], uv);\r\n" + //
" } else if (tileset == 6.0) {\r\n" + //
" return texture2D(u_tilesets[6], uv);\r\n" + //
" } else if (tileset == 7.0) {\r\n" + //
" return texture2D(u_tilesets[7], uv);\r\n" + //
" } else if (tileset == 8.0) {\r\n" + //
" return texture2D(u_tilesets[8], uv);\r\n" + //
" } else if (tileset == 9.0) {\r\n" + //
" return texture2D(u_tilesets[9], uv);\r\n" + //
" } else if (tileset == 10.0) {\r\n" + //
" return texture2D(u_tilesets[10], uv);\r\n" + //
" } else if (tileset == 11.0) {\r\n" + //
" return texture2D(u_tilesets[11], uv);\r\n" + //
" } else if (tileset == 12.0) {\r\n" + //
" return texture2D(u_tilesets[12], uv);\r\n" + //
" } else if (tileset == 13.0) {\r\n" + //
" return texture2D(u_tilesets[13], uv);\r\n" + //
" } else if (tileset == 14.0) {\r\n" + //
" return texture2D(u_tilesets[14], uv);\r\n" + //
" }\r\n" + //
"}\r\n" + //
"vec4 blend(vec4 color, float tileset, vec2 uv) {\r\n" + //
" vec4 texel = sample(tileset, uv);\r\n" + //
" return mix(color, texel, texel.a);\r\n" + //
"}\r\n" + //
"void main() {\r\n" + //
" vec4 color = sample(v_tilesets[0] - 1.0, v_uv[0]);\r\n" + //
" if (v_tilesets[1] > 0.5) {\r\n" + //
" color = blend(color, v_tilesets[1] - 1.0, v_uv[1]);\r\n" + //
" }\r\n" + //
" if (v_tilesets[2] > 0.5) {\r\n" + //
" color = blend(color, v_tilesets[2] - 1.0, v_uv[2]);\r\n" + //
" }\r\n" + //
" if (v_tilesets[3] > 0.5) {\r\n" + //
" color = blend(color, v_tilesets[3] - 1.0, v_uv[3]);\r\n" + //
" }\r\n" + //
" // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + //
" gl_FragColor = color;\r\n" + //
"}\r\n" + //
"";
public static final String vert = "\r\n" + //
"uniform mat4 u_VP;\r\n" + //
"uniform sampler2D u_heightMap;\r\n" + //
"uniform vec2 u_pixel;\r\n" + //
"uniform vec2 u_centerOffset;\r\n" + //
"attribute vec3 a_position;\r\n" + //
"attribute vec3 a_normal;\r\n" + //
"attribute vec2 a_uv;\r\n" + //
"attribute vec3 a_instancePosition;\r\n" + //
"attribute float a_instanceTexture;\r\n" + //
"varying vec3 v_normal;\r\n" + //
"varying vec2 v_uv;\r\n" + //
"varying float v_texture;\r\n" + //
"varying vec3 v_position;\r\n" + //
"void main() {\r\n" + //
" // Half of a pixel in the cliff height map.\r\n" + //
" vec2 halfPixel = u_pixel * 0.5;\r\n" + //
" // The bottom left corner of the map tile this vertex is on.\r\n" + //
" vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + //
" // Get the 4 closest heights in the height map.\r\n" + //
" float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + //
" float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + //
" float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + //
" float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + //
" \r\n" + //
" // Do a bilinear interpolation between the heights to get the final value.\r\n" + //
" float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + //
" float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + //
" float height = mix(bottom, top, a_position.y / 128.0);\r\n" + //
" v_normal = a_normal;\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_texture = a_instanceTexture;\r\n" + //
" v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + //
" gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + //
"}\r\n" + //
"";
}
public static final class Water {
private Water() {
}
public static final String frag = "\r\n" + //
"precision mediump float;\r\n" + //
"uniform sampler2D u_waterTexture;\r\n" + //
"varying vec2 v_uv;\r\n" + //
"varying vec4 v_color;\r\n" + //
"void main() {\r\n" + //
" gl_FragColor = texture2D(u_waterTexture, v_uv) * v_color;\r\n" + //
"}\r\n" + //
"";
public static final String vert = "\r\n" + //
"uniform mat4 u_VP;\r\n" + //
"uniform sampler2D u_heightMap;\r\n" + //
"uniform sampler2D u_waterHeightMap;\r\n" + //
"uniform vec2 u_size;\r\n" + //
"uniform vec2 u_offset;\r\n" + //
"uniform float u_offsetHeight;\r\n" + //
"uniform vec4 u_minDeepColor;\r\n" + //
"uniform vec4 u_maxDeepColor;\r\n" + //
"uniform vec4 u_minShallowColor;\r\n" + //
"uniform vec4 u_maxShallowColor;\r\n" + //
"attribute vec2 a_position;\r\n" + //
"attribute float a_InstanceID;\r\n" + //
"attribute float a_isWater;\r\n" + //
"varying vec2 v_uv;\r\n" + //
"varying vec4 v_color;\r\n" + //
"const float minDepth = 10.0 / 128.0;\r\n" + //
"const float deepLevel = 64.0 / 128.0;\r\n" + //
"const float maxDepth = 72.0 / 128.0;\r\n" + //
"void main() {\r\n" + //
" if (a_isWater > 0.5) {\r\n" + //
" v_uv = a_position;\r\n" + //
" vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + //
" vec2 base = corner + a_position;\r\n" + //
" float height = texture2D(u_heightMap, base / u_size).a;\r\n" + //
" float waterHeight = texture2D(u_waterHeightMap, base / u_size).a + u_offsetHeight;\r\n" + //
" float value = clamp(waterHeight - height, 0.0, 1.0);\r\n" + //
" if (value <= deepLevel) {\r\n" + //
" value = max(0.0, value - minDepth) / (deepLevel - minDepth);\r\n" + //
" v_color = mix(u_minShallowColor, u_maxShallowColor, value) / 255.0;\r\n" + //
" } else {\r\n" + //
" value = clamp(value - deepLevel, 0.0, maxDepth - deepLevel) / (maxDepth - deepLevel);\r\n" + //
" v_color = mix(u_minDeepColor, u_maxDeepColor, value) / 255.0;\r\n" + //
" }\r\n" + //
" gl_Position = u_VP * vec4(base * 128.0 + u_offset, waterHeight * 128.0, 1.0);\r\n" + //
" } else {\r\n" + //
" v_uv = vec2(0.0);\r\n" + //
" v_color = vec4(0.0);\r\n" + //
" gl_Position = vec4(0.0);\r\n" + //
" }\r\n" + //
"}\r\n" + //
"";
}
}

View File

@ -13,6 +13,7 @@ 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;
@ -21,12 +22,16 @@ import com.etheller.warsmash.datasources.DataSource;
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;
@ -34,28 +39,34 @@ import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.CanvasProvider;
import com.etheller.warsmash.viewer5.GenericResource;
import com.etheller.warsmash.viewer5.Grid;
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;
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 PathSolver wc3PathSolver;
public PathSolver wc3PathSolver = PathSolver.DEFAULT;
public SolverParams solverParams = new SolverParams();
public ShaderProgram groundShader;
public ShaderProgram waterShader;
public ShaderProgram cliffShader;
public Scene worldScene;
public int waterIndex;
public int waterIncreasePerFrame;
public float waterIndex;
public float waterIncreasePerFrame;
public float waterHeightOffset;
public List<Texture> waterTextures = new ArrayList<>();
public float[] maxDeepColor = new float[4];
@ -85,7 +96,7 @@ public class War3MapViewer extends ModelViewer {
public List<Texture> cliffTextures = new ArrayList<>();
public List<TerrainModel> cliffModels = new ArrayList<>();
public War3Map mapMpq;
public PathSolver mapPathSolver;
public PathSolver mapPathSolver = PathSolver.DEFAULT;
public Corner[][] corners;
public float[] centerOffset = new float[2];
public int[] mapSize = new int[2];
@ -126,11 +137,11 @@ public class War3MapViewer extends ModelViewer {
}
public void loadSLKs() {
final GenericResource terrain = this.loadMapGeneric("Terrain\\Terrain.slk", FetchDataTypeName.SLK,
final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK,
stringDataCallback);
final GenericResource cliffTypes = this.loadMapGeneric("Terrain\\CliffTypes.slk", FetchDataTypeName.SLK,
final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK,
stringDataCallback);
final GenericResource water = this.loadMapGeneric("Terrain\\Water.slk", FetchDataTypeName.SLK,
final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK,
stringDataCallback);
// == when loaded, which is always in our system ==
@ -190,6 +201,9 @@ public class War3MapViewer extends ModelViewer {
final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath);
this.mapMpq = war3Map;
setDataSource(war3Map.getCompoundDataSource());
// loadSLKs();
final PathSolver wc3PathSolver = this.wc3PathSolver;
@ -245,8 +259,9 @@ public class War3MapViewer extends ModelViewer {
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, null, this.solverParams));
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());
@ -254,8 +269,9 @@ public class War3MapViewer extends ModelViewer {
final Element tilesets = worldEditData.get("TileSets");
this.blightTextureIndex = this.tilesetTextures.size();
this.tilesetTextures.add((Texture) this.load(tilesets.getField(Character.toString(tileset)) + texturesExt, null,
this.solverParams));
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());
@ -263,13 +279,13 @@ public class War3MapViewer extends ModelViewer {
this.cliffTilesets.add(row);
this.cliffTextures
.add((Texture) this.load(row.get("texDir").toString() + "\\" + row.get("texFile") + texturesExt,
null, this.solverParams));
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() / 60;
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();
@ -291,7 +307,7 @@ public class War3MapViewer extends ModelViewer {
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,
null, this.solverParams));
this.mapPathSolver, this.solverParams));
}
final GL20 gl = this.gl;
@ -307,7 +323,7 @@ public class War3MapViewer extends ModelViewer {
final short[] cornerTextures = new short[instanceCount * 4];
final short[] cornerVariations = new short[instanceCount * 4];
final short[] waterFlags = new short[instanceCount];
final int instance = 0;
int instance = 0;
final Map<String, CliffInfo> cliffs = new HashMap<>();
this.columns = columns - 1;
@ -339,7 +355,7 @@ public class War3MapViewer extends ModelViewer {
topRightLayer, base);
if (!"AAAA".equals(fileName)) {
final int cliffTexture = bottomLeft.getCliffTexture();
int cliffTexture = bottomLeft.getCliffTexture();
// ?
if (cliffTexture == 15) {
@ -349,7 +365,8 @@ public class War3MapViewer extends ModelViewer {
final MappedDataRow cliffRow = this.cliffTilesets.get(cliffTexture);
final String dir = cliffRow.get("cliffModelDir").toString();
final String path = "Doodads\\Terrain\\" + dir + "\\" + dir + fileName
+ bottomLeft.getCliffVariation() + ".mdx";
+ Variations.getCliffVariation(dir, fileName, bottomLeft.getCliffVariation())
+ ".mdx";
if (!cliffs.containsKey(path)) {
cliffs.put(path, new CliffInfo());
@ -423,19 +440,19 @@ public class War3MapViewer extends ModelViewer {
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, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT,
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, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT,
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, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT,
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();
@ -483,13 +500,481 @@ public class War3MapViewer extends ModelViewer {
private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException {
final War3MapDoo dooFile = this.mapMpq.readDoodads();
this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(),
WorldEditorDataType.DOODADS);
this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(),
WorldEditorDataType.DESTRUCTIBLES);
final War3MapDoo doo = this.mapMpq.readDoodads();
for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) {
MutableGameObject row = modifications.getDoodads().get(doodad.getId());
if (row == null) {
row = modifications.getDestructibles().get(doodad.getId());
}
String file = row.readSLKTag("file");
final int numVar = row.readSLKTagInt("numVar");
if (file.endsWith(".mdx")) {
file = file.substring(0, file.length() - 4);
}
String fileVar = file;
file += ".mdx";
if (numVar > 1) {
fileVar += Math.min(doodad.getVariation(), numVar - 1);
}
fileVar += ".mdx";
// First see if the model is local.
// Doodads referring to local models may have invalid variations, so if the
// variation doesn't exist, try without a variation.
String path;
if (this.mapMpq.has(fileVar)) {
path = fileVar;
}
else {
path = file;
}
MdxModel model;
if (this.mapMpq.has(path)) {
model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams);
}
else {
model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams);
}
this.doodads.add(new Doodad(this, model, row, doodad));
}
// Cliff/Terrain doodads.
for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) {
final MutableGameObject row = modifications.getDoodads().get(doodad.getId());
String file = row.readSLKTag("file");
if (file.toLowerCase().endsWith(".mdl")) {
file = file.substring(0, file.length() - 4);
}
if (!file.toLowerCase().endsWith(".mdx")) {
file += ".mdx";
}
final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams);
this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad));
}
this.doodadsReady = true;
this.anyReady = true;
}
private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) {
// TODO Auto-generated method stub
private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2,
final MutableObjectData destructibles, final WorldEditorDataType dataType) {
// TODO condense ported MappedData from Ghostwolf and MutableObjectData from
// Retera
}
private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException {
final War3Map mpq = this.mapMpq;
final War3MapUnitsDoo dooFile = mpq.readUnits();
// Collect the units and items data.
for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) {
MutableGameObject row = null;
String path = null;
// Hardcoded?
if (sloc.equals(unit.getId())) {
path = "Objects\\StartLocation\\StartLocation.mdx";
}
else {
row = modifications.getUnits().get(unit.getId());
if (row == null) {
row = modifications.getItems().get(unit.getId());
}
if (row != null) {
path = row.readSLKTag("file");
if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) {
path = path.substring(0, path.length() - 4);
}
if (row.readSLKTagInt("fileVerFlags") == 2) {
path += "_V1";
}
path += ".mdx";
}
}
if (path != null) {
final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams);
this.units.add(new Unit(this, model, row, unit));
}
else {
System.err.println("Unknown unit ID: " + unit.getId());
}
}
this.unitsReady = true;
this.anyReady = true;
}
@Override
public void update() {
if (this.anyReady) {
this.waterIndex += this.waterIncreasePerFrame;
if (this.waterIndex >= this.waterTextures.size()) {
this.waterIndex = 0;
}
super.update();
final List<ModelInstance> instances = this.worldScene.instances;
for (final ModelInstance instance : instances) {
if (instance instanceof MdxComplexInstance) {
final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance;
if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) {
StandSequence.randomStandSequence(mdxComplexInstance);
}
}
}
}
}
@Override
public void render() {
if (this.anyReady) {
final Scene worldScene = this.worldScene;
worldScene.startFrame();
this.renderGround();
this.renderCliffs();
worldScene.renderOpaque();
this.renderWater();
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) {
@ -514,6 +999,9 @@ public class War3MapViewer extends ModelViewer {
private static final class StringDataCallbackImplementation implements LoadGenericCallback {
@Override
public Object call(final InputStream data) {
if (data == null) {
System.err.println("data null");
}
final StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) {
String line;
@ -549,4 +1037,20 @@ public class War3MapViewer extends ModelViewer {
public List<float[]> locations = new ArrayList<>();
public List<Integer> textures = new ArrayList<>();
}
private static final int MAXIMUM_ACCEPTED = 1 << 30;
/**
* Returns a power of two size for the given target capacity.
*/
private static final int pow2GreaterThan(final int capacity) {
int numElements = capacity - 1;
numElements |= numElements >>> 1;
numElements |= numElements >>> 2;
numElements |= numElements >>> 4;
numElements |= numElements >>> 8;
numElements |= numElements >>> 16;
return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1;
}
}

View File

@ -5,7 +5,7 @@ import org.lwjgl.opengl.GL33;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.etheller.warsmash.WarsmashGdxGame;
import com.etheller.warsmash.WarsmashGdxMapGame;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.etheller.warsmash.viewer5.gl.Extensions;
@ -37,6 +37,6 @@ public class DesktopLauncher {
// final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode();
// config.width = desktopDisplayMode.width;
// config.height = desktopDisplayMode.height;
new LwjglApplication(new WarsmashGdxGame(), config);
new LwjglApplication(new WarsmashGdxMapGame(), config);
}
}