Add splats and camera target height based on terrain and other stuff

This commit is contained in:
Retera 2020-01-25 12:00:26 -06:00
parent 67e37244bd
commit 960f6b9738
32 changed files with 824 additions and 573 deletions

View File

@ -0,0 +1,29 @@
package com.etheller.warsmash;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.MpqDataSourceDescriptor;
public class TestMain {
public static void main(final String[] args) {
final MpqDataSourceDescriptor desc = new MpqDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127\\Z.mpq");
final DataSource createDataSource = desc.createDataSource();
try {
final InputStream cliffZ = createDataSource.getResourceAsStream("ReplaceableTextures\\Cliff\\Cliff0.blp");
final BufferedImage img = ImageIO.read(cliffZ);
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));
}
catch (final IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

View File

@ -74,7 +74,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
this.cameraManager.setupCamera(scene);
// this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx",
this.mainModel = (MdxModel) this.viewer.load("Units\\NightElf\\DruidOfTheClaw\\DruidOfTheClaw_Portrait.mdx",
this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\RisingWaterDoodad\\RisingWaterDoodad.mdx",
new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
@ -82,7 +82,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
}
}, null);
this.modelCamera = this.mainModel.cameras.get(0);
// this.modelCamera = this.mainModel.cameras.get(0);
this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0);

View File

@ -60,21 +60,21 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
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 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))
Arrays.<DataSourceDescriptor>asList(war3mpq, /* war3xLocalmpq, */ testingFolder, currentFolder))
.createDataSource();
this.viewer = new War3MapViewer(this.codebase, this);
try {
this.viewer.loadMap("ReforgedGeorgeVacation.w3x");
this.viewer.loadMap("(2)BootyBay.w3m");
}
catch (final IOException e) {
throw new RuntimeException(e);
@ -101,7 +101,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
}, null);
this.portraitCameraManager.modelCamera = this.portraitModel.cameras.get(0);
this.portraitScene.camera.viewport(new Rectangle(100, 0, 100, 100));
this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48));
this.portraitInstance = (MdxComplexInstance) this.portraitModel.addInstance(0);
@ -120,10 +120,15 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
Gdx.input.setInputProcessor(this);
}
private int frame = 0;
@Override
public void render() {
final float deltaTime = Gdx.graphics.getDeltaTime();
Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO);
this.cameraManager.target.add(this.cameraVelocity.x, this.cameraVelocity.y, 0);
this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0);
this.cameraManager.target.z = this.viewer.terrain.getGroundHeight(this.cameraManager.target.x,
this.cameraManager.target.y);
this.cameraManager.updateCamera();
this.portraitCameraManager.updateCamera();
this.viewer.updateAndRender();
@ -138,6 +143,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
this.portraitInstance
.setSequence((this.portraitInstance.sequence + 1) % this.portraitModel.getSequences().size());
}
if ((this.frame++ % 1000) == 0) {
System.out.println(Gdx.graphics.getFramesPerSecond());
}
}
@Override
@ -156,6 +165,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
@Override
public void resize(final int width, final int height) {
super.resize(width, height);
this.tempRect.x = 0;
this.tempRect.y = 0;
this.tempRect.width = width;
@ -246,9 +256,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
WarsmashGdxMapGame.this.cameraPositionTemp[1], WarsmashGdxMapGame.this.cameraPositionTemp[2]);
this.target.add(WarsmashGdxMapGame.this.cameraTargetTemp[0],
WarsmashGdxMapGame.this.cameraTargetTemp[1], WarsmashGdxMapGame.this.cameraTargetTemp[2]);
this.camera.perspective(this.modelCamera.fieldOfView,
Gdx.graphics.getWidth() / (float) Gdx.graphics.getHeight(), this.modelCamera.nearClippingPlane,
this.modelCamera.farClippingPlane);
this.camera.perspective(this.modelCamera.fieldOfView, this.camera.getAspect(),
this.modelCamera.nearClippingPlane, this.modelCamera.farClippingPlane);
}
this.camera.moveToAndFace(this.position, this.target, this.worldUp);
@ -259,7 +268,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
// }
}
private final float cameraSpeed = 10.0f;
private final float cameraSpeed = 4096.0f; // per second
private final Vector2 cameraVelocity = new Vector2();
private Scene portraitScene;
@ -304,6 +313,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
@Override
public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) {
System.out.println(screenX + "," + screenY);
return false;
}

View File

@ -53,7 +53,8 @@ public class FolderDataSource implements DataSource {
if ("".equals(filepath)) {
return false; // special case for folder data source, dont do this
}
return Files.exists(this.folderPath.resolve(filepath));
final Path resolvedPath = this.folderPath.resolve(filepath);
return Files.exists(resolvedPath) && !Files.isDirectory(resolvedPath);
}
@Override

View File

@ -0,0 +1,49 @@
package com.etheller.warsmash.datasources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class SubdirDataSource implements DataSource {
private final DataSource dataSource;
private final String subdir;
public SubdirDataSource(final DataSource dataSource, final String subdir) {
this.dataSource = dataSource;
this.subdir = subdir;
}
@Override
public File getFile(final String filepath) throws IOException {
return this.dataSource.getFile(this.subdir + filepath);
}
@Override
public InputStream getResourceAsStream(final String filepath) throws IOException {
return this.dataSource.getResourceAsStream(this.subdir + filepath);
}
@Override
public boolean has(final String filepath) {
return this.dataSource.has(this.subdir + filepath);
}
@Override
public Collection<String> getListfile() {
final List<String> results = new ArrayList<>();
for (final String x : this.dataSource.getListfile()) {
if (x.startsWith(this.subdir)) {
results.add(x.substring(this.subdir.length()));
}
}
return results;
}
@Override
public void close() throws IOException {
this.dataSource.close();
}
}

View File

@ -451,6 +451,11 @@ public enum RenderMathUtils {
return out;
}
// ==== All of the following "wrap" calls are horribly inefficient. Eventually
// they should be removed entirely with better design.
// Until that happens, be sure to only call them during setup and not while the
// simulation is live. Otherwise you'll probably get some
// bad lag (and memory leaks?).
public static ShortBuffer wrapFaces(final int[] faces) {
final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).order(ByteOrder.nativeOrder())
.asShortBuffer();
@ -517,4 +522,36 @@ public enum RenderMathUtils {
wrapper.clear();
return wrapper;
}
public static Buffer wrap(final List<float[]> vertices) {
if (vertices.isEmpty()) {
return null;
}
final int expectedNumberOfFloats = vertices.get(0).length;
final FloatBuffer wrapper = ByteBuffer.allocateDirect(vertices.size() * expectedNumberOfFloats * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
for (final float[] subArray : vertices) {
for (final float f : subArray) {
wrapper.put(f);
}
}
wrapper.clear();
return wrapper;
}
public static Buffer wrapFaces(final List<int[]> indices) {
if (indices.isEmpty()) {
return null;
}
final int expectedNumberOfValues = indices.get(0).length;
final ShortBuffer wrapper = ByteBuffer.allocateDirect(indices.size() * expectedNumberOfValues * 2)
.order(ByteOrder.nativeOrder()).asShortBuffer();
for (final int[] subArray : indices) {
for (final int value : subArray) {
wrapper.put((short) value);
}
}
wrapper.clear();
return wrapper;
}
}

View File

@ -2,4 +2,5 @@ package com.etheller.warsmash.util;
public class WarsmashConstants {
public static final int MAX_PLAYERS = 28;
public static final int REPLACEABLE_TEXTURE_LIMIT = 64;
}

View File

@ -146,6 +146,10 @@ public class Camera {
this.dirty = true;
}
public float getAspect() {
return this.aspect;
}
public void setLocation(final Vector3 location) {
this.location.set(location);

View File

@ -124,4 +124,6 @@ public abstract class ModelInstance extends Node {
public abstract void load();
protected abstract RenderBatch getBatch(TextureMapper textureMapper2);
public abstract void setReplaceableTexture(int replaceableTextureId, String replaceableTextureFile);
}

View File

@ -177,6 +177,9 @@ public class ModelViewer {
// TODO this is a synchronous hack, skipped some Ghostwolf code
try {
if (!this.dataSource.has(finalSrc)) {
System.err.println("Attempting to load non-existant file: " + finalSrc);
}
resource.loadData(this.dataSource.getResourceAsStream(finalSrc), null);
}
catch (final IOException e) {

View File

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

View File

@ -29,6 +29,7 @@ public abstract class RawOpenGLTextureResource extends Texture {
private int wrapT = GL20.GL_CLAMP_TO_EDGE;
private final int magFilter = GL20.GL_LINEAR;
private final int minFilter = GL20.GL_LINEAR;
private ByteBuffer data;
public RawOpenGLTextureResource(final ModelViewer viewer, final String extension, final PathSolver pathSolver,
final String fetchUrl, final ResourceHandler handler) {
@ -124,6 +125,7 @@ public abstract class RawOpenGLTextureResource extends Texture {
}
buffer.flip();
this.data = buffer;
gl.glBindTexture(GL20.GL_TEXTURE_2D, this.handle);
@ -140,4 +142,21 @@ public abstract class RawOpenGLTextureResource extends Texture {
// }
}
/**
* I really don't like holding the reference to the original buffer like this.
* Seems wasteful. It's already on the GPU. However, while porting some code for
* shadow maps I hit a point where I really finally felt obligated to add this
* (there is some code in the Terrain stuff that should've had this, but
* doesn't, and does its own texture management as a result).
*
* So, as a note to future authors, please reinvent the system such that this
* cached buffer data is only stored for shadow maps and terrain textures or
* whatever. Right now, this holds a reference to these guys on every texture,
* on every unit, on every doodad, etc. Java will not be able to garbage collect
* them because we hold on to the buffer in "update()".
*/
public ByteBuffer getData() {
return this.data;
}
}

View File

@ -2,6 +2,4 @@ package com.etheller.warsmash.viewer5.gl;
public class Extensions {
public static ANGLEInstancedArrays angleInstancedArrays;
public static int GL_BGRA;
}

View File

@ -4,6 +4,7 @@ import java.util.List;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
@ -26,8 +27,6 @@ public class BatchGroup extends GenericGroup {
final MdxModel model = this.model;
final List<Texture> textures = model.getTextures();
final MdxHandler handler = model.handler;
final List<Texture> teamColors = MdxHandler.teamColors;
final List<Texture> teamGlows = MdxHandler.teamGlows;
final List<Batch> batches = model.batches;
final List<Integer> replaceables = model.replaceables;
final ModelViewer viewer = model.viewer;
@ -95,11 +94,9 @@ public class BatchGroup extends GenericGroup {
final Integer replaceable = replaceables.get(layerTexture); // TODO is this OK?
Texture texture;
if (replaceable == 1) {
texture = teamColors.get(instance.teamColor);
}
else if (replaceable == 2) {
texture = teamGlows.get(instance.teamColor);
if ((replaceable > 0) && (replaceable < WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT)
&& (instance.replaceableTextures[replaceable] != null)) {
texture = instance.replaceableTextures[replaceable];
}
else {
texture = textures.get(layerTexture);

View File

@ -7,6 +7,7 @@ import java.util.List;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.Camera;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
@ -77,7 +78,6 @@ public class GeometryEmitterFuncs {
final ParticleEmitter2Object emitterObject = emitter.emitterObject;
final int modelSpace = emitterObject.modelSpace;
final float tailLength = emitterObject.tailLength;
final int teamColor = instance.teamColor;
int offset = 0;
for (int objectIndex = 0; objectIndex < emitter.alive; objectIndex++) {
@ -131,7 +131,7 @@ public class GeometryEmitterFuncs {
floatView.put(floatOffset + FLOAT_OFFSET_HEALTH, object.health);
byteView.put(byteOffset + BYTE_OFFSET_TAIL, (byte) tail);
byteView.put(byteOffset + BYTE_OFFSET_TEAM_COLOR, (byte) teamColor);
byteView.put(byteOffset + BYTE_OFFSET_TEAM_COLOR, (byte) 0);
offset += 1;
}
@ -154,15 +154,9 @@ public class GeometryEmitterFuncs {
gl.glBlendFunc(emitterObject.blendSrc, emitterObject.blendDst);
if (replaceableId == 1) {
final List<Texture> teamColors = model.reforged ? MdxHandler.reforgedTeamColors : MdxHandler.teamColors;
texture = teamColors.get(instance.teamColor);
}
else if (replaceableId == 2) {
final List<Texture> teamGlows = model.reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows;
texture = teamGlows.get(instance.teamColor);
if ((replaceableId > 0) && (replaceableId < WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT)
&& (instance.replaceableTextures[(int) replaceableId] != null)) {
texture = instance.replaceableTextures[(int) replaceableId];
}
else {
texture = emitterObject.internalTexture;

View File

@ -10,9 +10,11 @@ import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.GenericNode;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.Node;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.RenderBatch;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SkeletalNode;
@ -43,7 +45,6 @@ public class MdxComplexInstance extends ModelInstance {
public int sequence = -1;
public int sequenceLoopMode = 0;
public boolean sequenceEnded = false;
public int teamColor = 0;
public float[] vertexColor = { 1, 1, 1, 1 };
// Particles do not spawn when the sequence is -1, or when the sequence finished
// and it's not repeating
@ -59,7 +60,7 @@ public class MdxComplexInstance extends ModelInstance {
public Matrix4[] worldMatrices;
public FloatBuffer worldMatricesCopyHeap;
public DataTexture boneTexture;
public Texture[] replaceableTextures = new Texture[64];
public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT];
public MdxComplexInstance(final MdxModel model) {
super(model);
@ -572,11 +573,21 @@ public class MdxComplexInstance extends ModelInstance {
* Set the team color of this instance.
*/
public MdxComplexInstance setTeamColor(final int id) {
this.teamColor = id;
this.replaceableTextures[1] = (Texture) this.model.viewer.load(
"ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp",
PathSolver.DEFAULT, null);
this.replaceableTextures[2] = (Texture) this.model.viewer.load(
"ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp",
PathSolver.DEFAULT, null);
return this;
}
@Override
public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) {
this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile,
PathSolver.DEFAULT, null);
}
/**
* Set the vertex color of this instance.
*/

View File

@ -1,26 +1,16 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.HandlerResource;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.ModelHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
import com.etheller.warsmash.viewer5.handlers.blp.BlpHandler;
public class MdxHandler extends ModelHandler {
// Team color/glow textures, shared between all models, but loaded with the
// first model that uses them.
public static final List<Texture> teamColors = new ArrayList<>();
public static final List<Texture> teamGlows = new ArrayList<>();
public static final List<Texture> reforgedTeamColors = new ArrayList<>();
public static final List<Texture> reforgedTeamGlows = new ArrayList<>();
public MdxHandler() {
this.extensions = new ArrayList<>();
this.extensions.add(new String[] { ".mdx", "arrayBuffer" });

View File

@ -9,7 +9,6 @@ import com.badlogic.gdx.graphics.GL20;
import com.etheller.warsmash.parsers.mdlx.Extent;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
@ -131,7 +130,6 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
}
final GL20 gl = viewer.gl;
boolean usingTeamTextures = false;
// Textures.
for (final com.etheller.warsmash.parsers.mdlx.Texture texture : parser.getTextures()) {
@ -140,16 +138,20 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
final int flags = texture.getFlags();
if (replaceableId != 0) {
path = "ReplaceableTextures\\" + ReplaceableIds.getPathString(replaceableId) + ".blp";
if ((replaceableId == 1) || (replaceableId == 2)) {
usingTeamTextures = true;
}
// TODO This uses dumb, stupid, terrible, no-good hardcoded replaceable IDs
// instead of the real system, because currently MdxSimpleInstance is not
// supporting it correctly.
final String idString = ((replaceableId == 1) || (replaceableId == 2)) ? ReplaceableIds.getIdString(0)
: "";
path = "ReplaceableTextures\\" + ReplaceableIds.getPathString(replaceableId) + idString + ".blp";
}
if (reforged && !path.endsWith(".dds")) {
path = path.substring(0, path.length() - 4) + ".dds";
}
else if ("".equals(path)) {
path = "Textures\\white.blp";
}
final Texture viewerTexture = (Texture) viewer.load(path, pathSolver, solverParams);
@ -168,24 +170,6 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
this.textures.add(viewerTexture);
}
// Start loading the team color and glow textures if this model uses them and
// they weren't loaded previously.
if (usingTeamTextures) {
final List<Texture> teamColors = reforged ? MdxHandler.reforgedTeamColors : MdxHandler.teamColors;
final List<Texture> teamGlows = reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows;
if (teamColors.isEmpty()) {
for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) {
final String id = ReplaceableIds.getIdString(i);
teamColors.add((Texture) viewer.load("ReplaceableTextures\\TeamColor\\TeamColor" + id + texturesExt,
pathSolver, solverParams));
teamGlows.add((Texture) viewer.load("ReplaceableTextures\\TeamGlow\\TeamGlow" + id + texturesExt,
pathSolver, solverParams));
}
}
}
// Geoset animations
for (final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation : parser.getGeosetAnimations()) {
this.geosetAnimations.add(new GeosetAnimation(this, geosetAnimation));

View File

@ -1,11 +1,15 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.BatchedInstance;
import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.RenderBatch;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.TextureMapper;
public class MdxSimpleInstance extends BatchedInstance {
public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT];
public MdxSimpleInstance(final Model model) {
super(model);
@ -36,4 +40,9 @@ public class MdxSimpleInstance extends BatchedInstance {
return new MdxRenderBatch(this.scene, this.model, textureMapper);
}
@Override
public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) {
this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile,
PathSolver.DEFAULT, null);
}
}

View File

@ -106,11 +106,11 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje
}
public int getWidth(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2N.getWar3id(), sequence, frame, counter, this.width);
return this.getScalarValue(out, AnimationMap.KP2N.getWar3id(), sequence, frame, counter, this.length);
}
public int getLength(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2W.getWar3id(), sequence, frame, counter, this.length);
return this.getScalarValue(out, AnimationMap.KP2W.getWar3id(), sequence, frame, counter, this.width);
}
public int getSpeed(final float[] out, final int sequence, final int frame, final int counter) {

View File

@ -13,8 +13,8 @@ public class ReplaceableIds {
for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) {
ID_TO_STR.put(Long.valueOf(i), String.format("%2d", i).replace(' ', '0'));
}
REPLACEABLE_ID_TO_STR.put(Long.valueOf(1), "TeamColor\\TeamColor00");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(2), "TeamGlow\\TeamGlow00");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(1), "TeamColor\\TeamColor");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(2), "TeamGlow\\TeamGlow");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(11), "Cliff\\Cliff0");
REPLACEABLE_ID_TO_STR.put(Long.valueOf(21), ""); // Used by all cursor models (HumanCursor, OrcCursor,
// UndeadCursor, NightElfCursor)

View File

@ -4,10 +4,13 @@ import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.ModelInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
public class Doodad {
private static final War3ID TEX_FILE = War3ID.fromString("btxf");
private static final War3ID TEX_ID = War3ID.fromString("btxi");
private final ModelInstance instance;
private final MutableGameObject row;
@ -30,6 +33,17 @@ public class Doodad {
final float defScale = row.readSLKTagFloat("defScale");
instance.uniformScale(defScale);
}
if (type == WorldEditorDataType.DESTRUCTIBLES) {
// TODO destructables need to be their own type, game simulation, etc
String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0);
final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0);
if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) {
if (!replaceableTextureFile.toLowerCase().endsWith(".blp")) {
replaceableTextureFile += ".blp";
}
instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile);
}
}
instance.setScene(map.worldScene);
this.instance = instance;

View File

@ -0,0 +1,138 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.Texture;
/**
* TODO this is copied from RivSoft stuff.
* https://github.com/d07RiV/wc3data/blob/3435e9728663825d892693318d0a0bb823dfad8c/src/mdx/viewer/handlers/w3x/splatmodel.js
*
* Shouldn't this just be a geomtry shader that takes X/Y/Texture as input and
* renders a splat, so that we can simply change the X/Y attribute values and
* move around the unit selection circles without memory allocations? For now I
* plan to simply port the RivSoft stuff, and come back later.
*/
public class SplatModel {
private static final int MAX_VERTICES = 65000;
private final Texture texture;
private final List<Batch> batches;
public final float[] color;
public SplatModel(final GL30 gl, final Texture texture, final List<float[]> locations, final float[] centerOffset) {
this.texture = texture;
this.batches = new ArrayList<>();
this.color = new float[] { 1, 1, 1, 1 };
final List<float[]> vertices = new ArrayList<>();
final List<float[]> uvs = new ArrayList<>();
final List<int[]> indices = new ArrayList<>();
final int instances = locations.size();
for (int idx = 0; idx < instances; ++idx) {
final float[] locs = locations.get(idx);
final float x0 = locs[0];
final float y0 = locs[1];
final float x1 = locs[2];
final float y1 = locs[3];
final float zoffs = locs[4];
final int ix0 = (int) Math.floor((x0 - centerOffset[0]) / 128.0);
final int ix1 = (int) Math.ceil((x1 - centerOffset[0]) / 128.0);
final int iy0 = (int) Math.floor((y0 - centerOffset[1]) / 128.0);
final int iy1 = (int) Math.ceil((y1 - centerOffset[1]) / 128.0);
final float newVerts = ((iy1 - iy0) + 1) * ((ix1 - ix0) + 1);
if (newVerts > MAX_VERTICES) {
continue;
}
int start = vertices.size();
final int step = (ix1 - ix0) + 1;
if ((start + newVerts) > MAX_VERTICES) {
this.addBatch(gl, vertices, uvs, indices);
vertices.clear();
uvs.clear();
indices.clear();
start = 0;
}
for (int iy = iy0; iy <= iy1; ++iy) {
final float y = (iy * 128.0f) + centerOffset[1];
for (int ix = ix0; ix <= ix1; ++ix) {
final float x = (ix * 128.0f) + centerOffset[0];
vertices.add(new float[] { x, y, zoffs });
uvs.add(new float[] { (x - x0) / (x1 - x0), 1.0f - ((y - y0) / (y1 - y0)) });
}
}
for (int i = 0; i < (iy1 - iy0); ++i) {
for (int j = 0; j < (ix1 - ix0); ++j) {
final int i0 = start + (i * step) + j;
indices.add(new int[] { i0, i0 + 1, i0 + step, i0 + 1, i0 + step + 1, i0 + step });
}
}
}
if (indices.size() > 0) {
this.addBatch(gl, vertices, uvs, indices);
}
}
private void addBatch(final GL30 gl, final List<float[]> vertices, final List<float[]> uvs,
final List<int[]> indices) {
final int uvsOffset = vertices.size() * 3 * 4;
final int vertexBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, vertexBuffer);
gl.glBufferData(GL30.GL_ARRAY_BUFFER, uvsOffset + (uvs.size() * 4 * 2), null, GL30.GL_STATIC_DRAW);
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, 0, vertices.size() * 4 * 5, RenderMathUtils.wrap(vertices));
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, uvsOffset, uvs.size() * 4 * 2, RenderMathUtils.wrap(uvs));
final int faceBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, faceBuffer);
gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, indices.size() * 6 * 2, RenderMathUtils.wrapFaces(indices),
GL30.GL_STATIC_DRAW);
this.batches.add(new Batch(uvsOffset, vertexBuffer, faceBuffer, indices.size() * 6));
}
public void render(final GL30 gl, final ShaderProgram shader) {
// Texture
gl.glActiveTexture(GL30.GL_TEXTURE1);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.texture.getGlHandle());
shader.setUniform4fv("u_color", this.color, 0, 4);
for (final Batch b : this.batches) {
// Vertices
gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, b.vertexBuffer);
shader.setVertexAttribute("a_position", 3, GL30.GL_FLOAT, false, 12, 0);
shader.setVertexAttribute("a_uv", 2, GL30.GL_FLOAT, false, 8, b.uvsOffset);
// Faces.
gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, b.faceBuffer);
// Draw
gl.glDrawElements(GL30.GL_TRIANGLES, b.elements, GL30.GL_UNSIGNED_SHORT, 0);
}
}
private static final class Batch {
private final int uvsOffset;
private final int vertexBuffer;
private final int faceBuffer;
private final int elements;
public Batch(final int uvsOffset, final int vertexBuffer, final int faceBuffer, final int elements) {
this.uvsOffset = uvsOffset;
this.vertexBuffer = vertexBuffer;
this.faceBuffer = faceBuffer;
this.elements = elements;
}
}
}

View File

@ -1,178 +0,0 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.WarsmashGdxGame;
import com.etheller.warsmash.parsers.mdlx.Geoset;
import com.etheller.warsmash.parsers.mdlx.MdlxModel;
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();
private final War3MapViewer viewer;
private final int vertexBuffer;
private final int faceBuffer;
private final int normalsOffset;
private final int uvsOffset;
private final int elements;
private final int locationAndTextureBuffer;
private final int texturesOffset;
private final int instances;
private final int vao;
public TerrainModel(final War3MapViewer viewer, final InputStream modelInput, final List<float[]> locations,
final List<Integer> textures, final ShaderProgram shader) {
final GL20 gl = viewer.gl;
final WebGL webgl = viewer.webGL;
final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays;
final MdlxModel parser;
try {
parser = new MdlxModel(modelInput);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
final Geoset geoset = parser.getGeosets().get(0);
final float[] vertices = geoset.getVertices();
final float[] normals = geoset.getNormals();
final float[] uvs = geoset.getUvSets()[0];
final int[] faces = geoset.getFaces();
final int normalsOffset = vertices.length * 4;
final int uvsOffset = normalsOffset + (normals.length * 4);
int vao;
GL_TEMP_BUFFER.clear();
Gdx.gl30.glGenVertexArrays(1, GL_TEMP_BUFFER);
vao = GL_TEMP_BUFFER.get(0);
Gdx.gl30.glBindVertexArray(vao);
final int vertexBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, vertexBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, uvsOffset + (uvs.length * 4), null, GL20.GL_STATIC_DRAW);
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, vertices.length * 4, RenderMathUtils.wrap(vertices));
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalsOffset, normals.length * 4, RenderMathUtils.wrap(normals));
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvsOffset, uvs.length * 4, RenderMathUtils.wrap(uvs));
shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0);
shader.enableVertexAttribute("a_position");
shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, normalsOffset);
shader.enableVertexAttribute("a_normal");
shader.setVertexAttribute("a_uv", 3, GL20.GL_FLOAT, false, 0, uvsOffset);
shader.enableVertexAttribute("a_uv");
final int texturesOffset = locations.size() * 3 * 4;
final int locationAndTextureBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, locationAndTextureBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, texturesOffset + textures.size(), null, GL20.GL_STATIC_DRAW);
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, locations.size() * 3 * 4, wrapVectors(locations));
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, texturesOffset, textures.size(), wrapTexIndices(textures));
shader.setVertexAttribute("a_instancePosition", 3, GL20.GL_FLOAT, false, 0, 0);
shader.enableVertexAttribute("a_instancePosition");
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_instancePosition"), 1);
shader.setVertexAttribute("a_instanceTexture", 1, GL20.GL_UNSIGNED_BYTE, false, 0, texturesOffset);
shader.enableVertexAttribute("a_instanceTexture");
instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_instanceTexture"), 1);
final int faceBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, faceBuffer);
gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, faces.length * 2, RenderMathUtils.wrapFaces(faces),
GL20.GL_STATIC_DRAW);
WarsmashGdxGame.bindDefaultVertexArray();
this.viewer = viewer;
this.vertexBuffer = vertexBuffer;
this.faceBuffer = faceBuffer;
this.normalsOffset = normalsOffset;
this.uvsOffset = uvsOffset;
this.elements = faces.length;
this.locationAndTextureBuffer = locationAndTextureBuffer;
this.texturesOffset = texturesOffset;
this.instances = locations.size() / 3;
this.vao = vao;
}
private Buffer wrapTexIndices(final List<Integer> textures) {
final ByteBuffer wrapper = ByteBuffer.allocateDirect(textures.size()).order(ByteOrder.nativeOrder());
for (final Integer texture : textures) {
wrapper.put(texture.byteValue());
}
wrapper.clear();
return wrapper;
}
private Buffer wrapVectors(final List<float[]> locations) {
final FloatBuffer wrapper = ByteBuffer.allocateDirect(locations.size() * 12).order(ByteOrder.nativeOrder())
.asFloatBuffer();
for (final float[] vector : locations) {
wrapper.put(vector[0]);
wrapper.put(vector[1]);
wrapper.put(vector[2]);
}
wrapper.clear();
return wrapper;
}
public void render(final ShaderProgram shader) {
final War3MapViewer viewer = this.viewer;
final GL20 gl = viewer.gl;
final WebGL webGL = viewer.webGL;
final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays;
Gdx.gl30.glBindVertexArray(this.vao);
instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, 0,
this.instances);
WarsmashGdxGame.bindDefaultVertexArray();
}
}

View File

@ -2,22 +2,34 @@ package com.etheller.warsmash.viewer5.handlers.w3x;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
public class Unit {
private static final War3ID IS_BLDG = War3ID.fromString("ubdg");
private static final War3ID RED = War3ID.fromString("uclr");
private static final War3ID GREEN = War3ID.fromString("uclg");
private static final War3ID BLUE = War3ID.fromString("uclb");
private static final War3ID MODEL_SCALE = War3ID.fromString("usca");
private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh");
private static final War3ID ITEM_MODEL_SCALE = War3ID.fromString("isca");
private static final War3ID ITEM_RED = War3ID.fromString("iclr");
private static final War3ID ITEM_GREEN = War3ID.fromString("iclg");
private static final War3ID ITEM_BLUE = War3ID.fromString("iclb");
private static final float[] heapZ = new float[3];
public final MdxComplexInstance instance;
public final MutableGameObject row;
public Unit(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit) {
final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final WorldEditorDataType type) {
final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance();
instance.move(unit.getLocation());
float angle;
if ((row != null) && row.readSLKTagBoolean("isBldg")) {
if ((row != null) && row.getFieldAsBoolean(IS_BLDG, 0)) {
angle = (float) Math.toRadians(270.0f);
}
else {
@ -30,12 +42,28 @@ public class Unit {
instance.setScene(map.worldScene);
if (row != null) {
heapZ[2] = row.readSLKTagFloat("moveHeight");
heapZ[2] = row.getFieldAsFloat(MOVE_HEIGHT, 0);
instance.move(heapZ);
instance.setVertexColor(new float[] { (row.readSLKTagInt("red")) / 255f,
(row.readSLKTagInt("green")) / 255f, (row.readSLKTagInt("blue")) / 255f });
instance.uniformScale(row.readSLKTagFloat("modelScale"));
War3ID red;
War3ID green;
War3ID blue;
War3ID scale;
if (type == WorldEditorDataType.UNITS) {
scale = MODEL_SCALE;
red = RED;
green = GREEN;
blue = BLUE;
}
else {
scale = ITEM_MODEL_SCALE;
red = ITEM_RED;
green = ITEM_GREEN;
blue = ITEM_BLUE;
}
instance.setVertexColor(new float[] { (row.getFieldAsInteger(red, 0)) / 255f,
(row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f });
instance.uniformScale(row.getFieldAsFloat(scale, 0));
}

View File

@ -1,251 +1,60 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
public class W3xShaders {
public static final class Cliffs {
private Cliffs() {
public static final class UberSplat {
private UberSplat() {
}
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).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_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.5) {\r\n" + //
" return texture2D(u_tilesets[0], uv);\r\n" + //
" } else if (tileset <= 1.5) {\r\n" + //
" return texture2D(u_tilesets[1], uv);\r\n" + //
" } else if (tileset <= 2.5) {\r\n" + //
" return texture2D(u_tilesets[2], uv);\r\n" + //
" } else if (tileset <= 3.5) {\r\n" + //
" return texture2D(u_tilesets[3], uv);\r\n" + //
" } else if (tileset <= 4.5) {\r\n" + //
" return texture2D(u_tilesets[4], uv);\r\n" + //
" } else if (tileset <= 5.5) {\r\n" + //
" return texture2D(u_tilesets[5], uv);\r\n" + //
" } else if (tileset <= 6.5) {\r\n" + //
" return texture2D(u_tilesets[6], uv);\r\n" + //
" } else if (tileset <= 7.5) {\r\n" + //
" return texture2D(u_tilesets[7], uv);\r\n" + //
" } else if (tileset <= 8.5) {\r\n" + //
" return texture2D(u_tilesets[8], uv);\r\n" + //
" } else if (tileset <= 9.5) {\r\n" + //
" return texture2D(u_tilesets[9], uv);\r\n" + //
" } else if (tileset <= 10.5) {\r\n" + //
" return texture2D(u_tilesets[10], uv);\r\n" + //
" } else if (tileset <= 11.5) {\r\n" + //
" return texture2D(u_tilesets[11], uv);\r\n" + //
" } else if (tileset <= 12.5) {\r\n" + //
" return texture2D(u_tilesets[12], uv);\r\n" + //
" } else if (tileset <= 13.5) {\r\n" + //
" return texture2D(u_tilesets[13], uv);\r\n" + //
" } else if (tileset <= 14.5) {\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" + //
"}";
public static final String vert = "\r\n" + //
"uniform mat4 u_VP;\r\n" + //
"uniform sampler2D u_heightMap;\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" + //
"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"
" uniform mat4 u_mvp;\r\n" + //
" uniform sampler2D u_heightMap;\r\n" + //
" uniform vec2 u_pixel;\r\n" + //
" uniform vec2 u_size;\r\n" + //
" uniform vec2 u_shadowPixel;\r\n" + //
" uniform vec2 u_centerOffset;\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec2 a_uv;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying vec2 v_suv;\r\n" + //
" varying vec3 v_normal;\r\n" + //
" const float normalDist = 0.25;\r\n" + //
" void main() {\r\n" + //
" vec2 halfPixel = u_pixel * 0.5;\r\n" + //
" vec2 base = (a_position.xy - u_centerOffset) / 128.0;\r\n" + //
" float height = texture2D(u_heightMap, base * u_pixel + halfPixel).r;\r\n" + //
" float hL = texture2D(u_heightMap, vec2(base - vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\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 {
private Water() {
}
" float hR = texture2D(u_heightMap, vec2(base + vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n"
+ //
" float hD = texture2D(u_heightMap, vec2(base - vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n"
+ //
" float hU = texture2D(u_heightMap, vec2(base + vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n"
+ //
" v_normal = normalize(vec3(hL - hR, hD - hU, normalDist * 2.0));\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_suv = base / u_size;\r\n" + //
" gl_Position = u_mvp * vec4(a_position.xy, height * 128.0 + a_position.z, 1.0);\r\n" + //
" }\r\n" + //
" ";
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).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" + //
" 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" + //
" uniform sampler2D u_texture;\r\n" + //
" uniform sampler2D u_shadowMap;\r\n" + //
" uniform vec4 u_color;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying vec2 v_suv;\r\n" + //
" varying vec3 v_normal;\r\n" + //
" const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + //
" void main() {\r\n" + //
" if (any(bvec4(lessThan(v_uv, vec2(0.0)), greaterThan(v_uv, vec2(1.0))))) {\r\n" + //
" discard;\r\n" + //
" }\r\n" + //
" vec4 color = texture2D(u_texture, clamp(v_uv, 0.0, 1.0)).rgba * u_color;\r\n" + //
" float shadow = texture2D(u_shadowMap, v_suv).r;\r\n" + //
" color.xyz *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + //
" color.xyz *= 1.0 - shadow;\r\n" + //
" gl_FragColor = color;\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

@ -18,12 +18,14 @@ import com.etheller.warsmash.common.LoadGenericCallback;
import com.etheller.warsmash.datasources.CompoundDataSource;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.MpqDataSource;
import com.etheller.warsmash.datasources.SubdirDataSource;
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.War3MapW3e;
import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.manager.MutableObjectData;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
@ -42,12 +44,20 @@ 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;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat;
import mpq.MPQArchive;
import mpq.MPQException;
public class War3MapViewer extends ModelViewer {
private static final War3ID UNIT_FILE = War3ID.fromString("umdl");
private static final War3ID UBER_SPLAT = War3ID.fromString("uubs");
private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu");
private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx");
private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy");
private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw");
private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh");
private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb");
private static final War3ID ITEM_FILE = War3ID.fromString("ifil");
private static final War3ID sloc = War3ID.fromString("sloc");
private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation();
@ -96,7 +106,6 @@ public class War3MapViewer extends ModelViewer {
this.worldScene = this.addScene();
loadSLKs();
}
public void loadSLKs() {
@ -161,6 +170,7 @@ public class War3MapViewer extends ModelViewer {
}
public void loadMap(final String mapFilePath) throws IOException {
loadSLKs();
final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath);
this.mapMpq = war3Map;
@ -175,23 +185,31 @@ public class War3MapViewer extends ModelViewer {
tileset = w3iFile.getTileset();
final DataSource tilesetSource;
DataSource tilesetSource;
try {
// Slightly complex. Here's the theory:
// 1.) Copy map into RAM
// 2.) Setup a Data Source that will read assets
// from either the map or the game, giving the map priority.
SeekableByteChannel sbc;
try (InputStream mapStream = war3Map.getCompoundDataSource().getResourceAsStream(tileset + ".mpq")) {
final byte[] mapData = IOUtils.toByteArray(mapStream);
sbc = new SeekableInMemoryByteChannel(mapData);
final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc);
tilesetSource = new CompoundDataSource(
Arrays.asList(war3Map.getCompoundDataSource(), internalMpqContentsDataSource));
final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource();
try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) {
if (mapStream == null) {
tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource,
new SubdirDataSource(compoundDataSource, tileset + ".mpq/")));
}
else {
final byte[] mapData = IOUtils.toByteArray(mapStream);
sbc = new SeekableInMemoryByteChannel(mapData);
final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc);
tilesetSource = new CompoundDataSource(
Arrays.asList(compoundDataSource, internalMpqContentsDataSource));
}
}
catch (final IOException exc) {
tilesetSource = new CompoundDataSource(
Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/")));
}
}
catch (final IOException e) {
throw new RuntimeException(e);
}
catch (final MPQException e) {
throw new RuntimeException(e);
@ -202,8 +220,8 @@ public class War3MapViewer extends ModelViewer {
final War3MapW3e terrainData = this.mapMpq.readEnvironment();
this.terrain = new Terrain(terrainData, this.webGL, this.dataSource, new WorldEditStrings(this.dataSource),
this);
this.terrain = new Terrain(terrainData, w3iFile, this.webGL, this.dataSource,
new WorldEditStrings(this.dataSource), this);
final float[] centerOffset = terrainData.getCenterOffset();
final int[] mapSize = terrainData.getMapSize();
@ -264,7 +282,7 @@ public class War3MapViewer extends ModelViewer {
String file = row.readSLKTag("file");
final int numVar = row.readSLKTagInt("numVar");
if (file.endsWith(".mdx")) {
if (file.endsWith(".mdx") || file.endsWith(".mdl")) {
file = file.substring(0, file.length() - 4);
}
@ -337,25 +355,28 @@ public class War3MapViewer extends ModelViewer {
String path = null;
// Hardcoded?
WorldEditorDataType type = null;
if (sloc.equals(unit.getId())) {
path = "Objects\\StartLocation\\StartLocation.mdx";
type = null; /// ??????
}
else {
row = modifications.getUnits().get(unit.getId());
if (row == null) {
row = modifications.getItems().get(unit.getId());
path = row.getFieldAsString(ITEM_FILE, 0);
if (row != null) {
type = WorldEditorDataType.ITEM;
path = row.getFieldAsString(ITEM_FILE, 0);
if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) {
path = path.substring(0, path.length() - 4);
}
if (row.readSLKTagInt("fileVerFlags") == 2) {
path += "_V1";
}
if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) {
path = path.substring(0, path.length() - 4);
}
path += ".mdx";
path += ".mdx";
}
}
else {
type = WorldEditorDataType.UNITS;
path = row.getFieldAsString(UNIT_FILE, 0);
if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) {
@ -366,19 +387,61 @@ public class War3MapViewer extends ModelViewer {
}
path += ".mdx";
final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0);
if (uberSplat != null) {
final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat);
if (uberSplatInfo != null) {
final String texturePath = uberSplatInfo.getField("Dir") + "\\"
+ uberSplatInfo.getField("file") + ".blp";
if (!this.terrain.splats.containsKey(texturePath)) {
this.terrain.splats.put(texturePath, new Splat());
}
final float x = unit.getLocation()[0];
final float y = unit.getLocation()[1];
final float s = uberSplatInfo.getFieldFloatValue("Scale");
this.terrain.splats.get(texturePath).locations
.add(new float[] { x - s, y - s, x + s, y + s, 1 });
}
}
final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0);
if ((unitShadow != null) && !"_".equals(unitShadow)) {
final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp";
final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0);
final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0);
final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0);
final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0);
if (!this.terrain.splats.containsKey(texture)) {
final Splat splat = new Splat();
splat.opacity = 0.5f;
this.terrain.splats.put(texture, splat);
}
final float x = unit.getLocation()[0] - shadowX;
final float y = unit.getLocation()[1] - shadowY;
this.terrain.splats.get(texture).locations
.add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 });
}
final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0);
if ((buildingShadow != null) && !"_".equals(buildingShadow)) {
this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]);
}
}
}
if (path != null) {
final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams);
this.units.add(new Unit(this, model, row, unit));
this.units.add(new Unit(this, model, row, unit, type));
}
else {
System.err.println("Unknown unit ID: " + unit.getId());
}
}
this.terrain.loadSplats();
this.unitsReady = true;
this.anyReady = true;
}
@ -410,8 +473,9 @@ public class War3MapViewer extends ModelViewer {
worldScene.startFrame();
this.terrain.renderGround();
// this.terrain.renderCliffs();
this.terrain.renderCliffs();
worldScene.renderOpaque();
this.terrain.renderUberSplats();
this.terrain.renderWater();
worldScene.renderTranslucent();

View File

@ -60,8 +60,10 @@ public class CliffMesh {
public void renderQueue(final float[] position) {
if (this.renderJobs.remaining() < 4) {
final FloatBuffer newRenderJobs = ByteBuffer.allocateDirect(this.renderJobs.capacity() * 2)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
final int newCapacity = this.renderJobs.capacity() * 2;
final FloatBuffer newRenderJobs = ByteBuffer.allocateDirect(newCapacity * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer();
newRenderJobs.clear();
this.renderJobs.flip();
newRenderJobs.put(this.renderJobs);
this.renderJobs = newRenderJobs;
@ -79,32 +81,24 @@ public class CliffMesh {
this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, this.renderJobs.remaining() * 4, this.renderJobs,
GL20.GL_DYNAMIC_DRAW);
this.gl.glEnableVertexAttribArray(0);
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer);
this.gl.glVertexAttribPointer(0, 3, GL20.GL_FLOAT, false, 0, 0);
this.gl.glEnableVertexAttribArray(1);
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.uvBuffer);
this.gl.glVertexAttribPointer(1, 2, GL20.GL_FLOAT, false, 0, 0);
this.gl.glEnableVertexAttribArray(2);
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer);
this.gl.glVertexAttribPointer(2, 3, GL20.GL_FLOAT, false, 0, 0);
// this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer);
// this.gl.glVertexAttribPointer(2, 3, GL20.GL_FLOAT, false, 0, 0);
this.gl.glEnableVertexAttribArray(3);
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer);
this.gl.glVertexAttribPointer(3, 4, GL20.GL_FLOAT, false, 0, 0);
this.gl.glVertexAttribDivisor(3, 1);
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer);
this.gl.glVertexAttribPointer(2, 4, GL20.GL_FLOAT, false, 0, 0);
this.gl.glVertexAttribDivisor(2, 1);
this.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer);
this.gl.glDrawElementsInstanced(GL20.GL_TRIANGLES, this.indices, GL30.GL_UNSIGNED_SHORT, 0,
this.renderJobs.remaining() / 4);
this.gl.glVertexAttribDivisor(3, 0); // ToDo use vao
this.gl.glDisableVertexAttribArray(0);
this.gl.glDisableVertexAttribArray(1);
this.gl.glDisableVertexAttribArray(2);
this.gl.glDisableVertexAttribArray(3);
this.gl.glVertexAttribDivisor(2, 0); // ToDo use vao
this.renderJobs.clear();
}

View File

@ -10,7 +10,6 @@ import javax.imageio.ImageIO;
import com.badlogic.gdx.graphics.GL30;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.viewer5.gl.Extensions;
public class GroundTexture {
public int id;
@ -31,7 +30,7 @@ public class GroundTexture {
this.id = gl.glGenTexture();
gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.id);
gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.tileSize, this.tileSize,
this.extended ? 32 : 16, 0, Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null);
this.extended ? 32 : 16, 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE);

View File

@ -14,6 +14,8 @@ import java.util.TreeSet;
import javax.imageio.ImageIO;
import org.apache.commons.compress.utils.IOUtils;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
@ -24,6 +26,7 @@ import com.badlogic.gdx.math.Vector3;
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.parsers.w3x.w3i.War3MapW3i;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.StandardObjectData;
@ -32,9 +35,13 @@ import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.util.WorldEditStrings;
import com.etheller.warsmash.viewer5.Camera;
import com.etheller.warsmash.viewer5.gl.Extensions;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.RawOpenGLTextureResource;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel;
import com.etheller.warsmash.viewer5.handlers.w3x.Variations;
import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
public class Terrain {
@ -92,8 +99,18 @@ public class Terrain {
private final War3MapViewer viewer;
public float[] centerOffset;
private final WebGL webGL;
private final ShaderProgram uberSplatShader;
public final DataTable uberSplatTable;
public Terrain(final War3MapW3e w3eFile, final WebGL webGL, final DataSource dataSource,
private final List<SplatModel> uberSplatModels;
private int shadowMap;
public final Map<String, Splat> splats = new HashMap<>();
public final Map<String, List<float[]>> shadows = new HashMap<>();
public final Map<String, Texture> shadowTextures = new HashMap<>();
private final int[] mapBounds;
private final int[] mapSize;
public Terrain(final War3MapW3e w3eFile, final War3MapW3i w3iFile, final WebGL webGL, final DataSource dataSource,
final WorldEditStrings worldEditStrings, final War3MapViewer viewer) throws IOException {
this.webGL = webGL;
this.viewer = viewer;
@ -136,6 +153,10 @@ public class Terrain {
try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) {
this.waterTable.readSLK(waterSlkStream);
}
this.uberSplatTable = new DataTable(worldEditStrings);
try (InputStream uberSlkStream = dataSource.getResourceAsStream("Splats\\UberSplatData.slk")) {
this.uberSplatTable.readSLK(uberSlkStream);
}
final char tileset = w3eFile.getTileset();
final Element waterInfo = this.waterTable.get(tileset + "Sha");
@ -147,16 +168,31 @@ public class Terrain {
loadWaterColor(this.maxShallowColor, "Smax", waterInfo);
loadWaterColor(this.minDeepColor, "Dmin", waterInfo);
loadWaterColor(this.maxDeepColor, "Dmax", waterInfo);
for (int i = 0; i < 3; i++) {
if (this.minDeepColor[i] > this.maxDeepColor[i]) {
this.maxDeepColor[i] = this.minDeepColor[i];
}
}
// Cliff Meshes
final Map<String, Integer> cliffVars = Variations.CLIFF_VARS;
Map<String, Integer> cliffVars = Variations.CLIFF_VARS;
for (final Map.Entry<String, Integer> cliffVar : cliffVars.entrySet()) {
final Integer maxVariations = cliffVar.getValue();
for (int variation = 0; variation <= maxVariations; variation++) {
final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx";
this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30));
this.pathToCliff.put(cliffVar.getKey() + variation, this.cliffMeshes.size() - 1);
this.pathToCliff.put("Cliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1);
}
}
cliffVars = Variations.CITY_CLIFF_VARS;
for (final Map.Entry<String, Integer> cliffVar : cliffVars.entrySet()) {
final Integer maxVariations = cliffVar.getValue();
for (int variation = 0; variation <= maxVariations; variation++) {
final String fileName = "Doodads\\Terrain\\CityCliffs\\CityCliffs" + cliffVar.getKey() + variation
+ ".mdx";
this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30));
this.pathToCliff.put("CityCliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1);
}
}
@ -165,7 +201,7 @@ public class Terrain {
final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue());
final String dir = terrainTileInfo.getField("dir");
final String file = terrainTileInfo.getField("file");
this.groundTextures.add(new GroundTexture(dir + "/" + file + texturesExt, dataSource, Gdx.gl30));
this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30));
this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1);
}
@ -182,10 +218,11 @@ public class Terrain {
final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue());
final String texDir = cliffInfo.getField("texDir");
final String texFile = cliffInfo.getField("texFile");
try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "/" + texFile + texturesExt)) {
try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) {
final BufferedImage image = ImageIO.read(imageStream);
this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(),
ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image))));
ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)),
cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir")));
}
this.cliffTexturesSize = Math.max(this.cliffTexturesSize,
this.cliffTextures.get(this.cliffTextures.size() - 1).width);
@ -222,8 +259,8 @@ public class Terrain {
this.groundHeight = gl.glGenTexture();
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight);
gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST);
gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST);
gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR);
gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR);
gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT,
RenderMathUtils.wrap(this.groundHeights));
@ -244,13 +281,13 @@ public class Terrain {
this.cliffTextureArray = gl.glGenTexture();
gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray);
gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize,
this.cliffTextures.size(), 0, Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null);
this.cliffTextures.size(), 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0);
int sub = 0;
for (final UnloadedTexture i : this.cliffTextures) {
gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, Extensions.GL_BGRA,
gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, GL30.GL_RGBA,
GL30.GL_UNSIGNED_BYTE, i.data);
sub += 1;
}
@ -276,8 +313,8 @@ public class Terrain {
// Water textures
this.waterTextureArray = gl.glGenTexture();
gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray);
gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, 128, 128, this.waterTextureCount, 0,
Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null);
gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0,
GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0);
@ -294,8 +331,7 @@ public class Terrain {
}
gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1,
GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE,
ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)));
GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image));
}
}
gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY);
@ -306,9 +342,14 @@ public class Terrain {
this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag);
this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag);
this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag);
// TODO collision bodies (?)
this.centerOffset = w3eFile.getCenterOffset();
this.uberSplatModels = new ArrayList<>();
this.mapBounds = w3iFile.getCameraBoundsComplements();
this.mapSize = w3eFile.getMapSize();
}
private void updateGroundHeights(final Rectangle area) {
@ -403,6 +444,10 @@ public class Terrain {
final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight())
&& (topRight.getLayerHeight() >= topLeft.getLayerHeight());
int bottomLeftCliffTex = bottomLeft.getCliffTexture();
if (bottomLeftCliffTex == 15) {
bottomLeftCliffTex -= 14;
}
if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2)))
&& !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) {
final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0))
@ -424,7 +469,8 @@ public class Terrain {
+ ((bottomRight.getLayerHeight() - base)
* (bottomRight.getRamp() != 0 ? -4 : 1)));
fileName = "Doodads\\Terrain\\CliffTrans\\CliffTrans" + fileName + "0.mdx";
final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir;
fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx";
if (this.dataSource.has(fileName)) {
if (!this.pathToCliff.containsKey(fileName)) {
@ -441,7 +487,7 @@ public class Terrain {
}
}
this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingDown ? 0 : 1))),
this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))),
(j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName)));
bottomLeft.romp = true;
@ -475,7 +521,10 @@ public class Terrain {
}
// Clamp to within max variations
fileName += Variations.getCliffVariation("Cliffs", fileName, bottomLeft.getCliffVariation());
fileName = this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir + fileName
+ Variations.getCliffVariation(this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir,
fileName, bottomLeft.getCliffVariation());
if (!this.pathToCliff.containsKey(fileName)) {
throw new IllegalArgumentException("No such pathToCliff entry: " + fileName);
}
@ -693,12 +742,51 @@ public class Terrain {
}
public void renderUberSplats() {
final GL30 gl = Gdx.gl30;
final WebGL webGL = this.webGL;
final ShaderProgram shader = this.uberSplatShader;
gl.glDepthMask(false);
gl.glEnable(GL30.GL_BLEND);
gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA);
gl.glBlendEquation(GL30.GL_FUNC_ADD);
webGL.useShaderProgram(this.uberSplatShader);
shader.setUniformMatrix("u_mvp", this.camera.viewProjectionMatrix);
shader.setUniformi("u_heightMap", 0);
sizeHeap[0] = this.columns - 1;
sizeHeap[1] = this.rows - 1;
shader.setUniform2fv("u_size", sizeHeap, 0, 2);
sizeHeap[0] = 1 / (float) this.columns;
sizeHeap[1] = 1 / (float) this.rows;
shader.setUniform2fv("u_pixel", sizeHeap, 0, 2);
shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2);
shader.setUniformi("u_texture", 1);
shader.setUniformi("u_shadowMap", 2);
gl.glActiveTexture(GL30.GL_TEXTURE0);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight);
gl.glActiveTexture(GL30.GL_TEXTURE2);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap);
// Render the cliffs
for (final SplatModel splat : this.uberSplatModels) {
splat.render(gl, shader);
}
}
public void renderWater() {
// Render water
this.webGL.useShaderProgram(this.waterShader);
final GL30 gl = Gdx.gl30;
gl.glDepthMask(false);
gl.glDisable(GL30.GL_CULL_FACE);
gl.glEnable(GL30.GL_BLEND);
gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA);
gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0);
gl.glUniform4fv(1, 1, this.minShallowColor, 0);
@ -748,13 +836,12 @@ public class Terrain {
this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap);
}
this.cliffShader.begin();
this.webGL.useShaderProgram(this.cliffShader);
final GL30 gl = Gdx.gl30;
// WC3 models are 128x too large
tempMatrix.set(this.camera.viewProjectionMatrix);
tempMatrix.scale(1 / 128f, 1 / 128f, 1 / 128f);
gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0);
gl.glUniform1i(1, this.viewer.renderPathing);
gl.glUniform1i(2, this.viewer.renderLighting);
@ -765,10 +852,102 @@ public class Terrain {
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight);
// gl.glActiveTexture(GL30.GL_TEXTURE2);
for (final CliffMesh i : this.cliffMeshes) {
gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]);
gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]);
i.render();
}
}
public void addShadow(final String file, final float x, final float y) {
if (!this.shadows.containsKey(file)) {
final String path = "ReplaceableTextures\\Shadows\\" + file + ".blp";
this.shadows.put(file, new ArrayList<>());
this.shadowTextures.put(file, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null));
}
this.shadows.get(file).add(new float[] { x, y });
}
public void initShadows() throws IOException {
final GL30 gl = Gdx.gl30;
final float[] centerOffset = this.centerOffset;
final int columns = (this.columns - 1) * 4;
final int rows = (this.rows - 1) * 4;
final int shadowSize = columns * rows;
final byte[] shadowData = new byte[columns * rows];
if (this.viewer.mapMpq.has("war3map.shd")) {
final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd");
final byte[] buffer = IOUtils.toByteArray(shadowSource);
for (int i = 0; i < shadowSize; i++) {
shadowData[i] = (byte) (buffer[i] / 2);
}
}
for (final Map.Entry<String, Texture> fileAndTexture : this.shadowTextures.entrySet()) {
final String file = fileAndTexture.getKey();
final Texture texture = fileAndTexture.getValue();
final int width = texture.getWidth();
final int height = texture.getHeight();
final int ox = (int) Math.round(width * 0.3);
final int oy = (int) Math.round(height * 0.7);
for (final float[] location : this.shadows.get(file)) {
final int x0 = (int) Math.floor((location[0] - centerOffset[0]) / 32.0) - ox;
final int y0 = (int) Math.floor((location[1] - centerOffset[1]) / 32.0) + oy;
for (int y = 0; y < height; ++y) {
if (((y0 - y) < 0) || ((y0 - y) >= rows)) {
continue;
}
for (int x = 0; x < width; ++x) {
if (((x0 + x) < 0) || ((x0 + x) >= columns)) {
continue;
}
if (((RawOpenGLTextureResource) texture).getData().get((((y * width) + x) * 4) + 3) != 0) {
shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128;
}
}
}
}
}
final byte outsideArea = (byte) 204;
final int x0 = this.mapBounds[0] * 4, x1 = (this.mapSize[0] - this.mapBounds[1] - 1) * 4,
y0 = this.mapBounds[2] * 4, y1 = (this.mapSize[1] - this.mapBounds[3] - 1) * 4;
for (int y = y0; y < y1; ++y) {
for (int x = x0; x < x1; ++x) {
final RenderCorner c = this.corners[x >> 2][y >> 2];
if (c.getBoundary() != 0) {
shadowData[(y * columns) + x] = outsideArea;
}
}
}
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < x0; ++x) {
shadowData[(y * columns) + x] = outsideArea;
}
for (int x = x1; x < columns; ++x) {
shadowData[(y * columns) + x] = outsideArea;
}
}
for (int x = x0; x < x1; ++x) {
for (int y = 0; y < y0; ++y) {
shadowData[(y * columns) + x] = outsideArea;
}
for (int y = y1; y < rows; ++y) {
shadowData[(y * columns) + x] = outsideArea;
}
}
this.shadowMap = gl.glGenBuffer();
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap);
gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR);
gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR);
gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE);
gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE,
RenderMathUtils.wrap(shadowData));
}
// public Vector3 groundNormal(final Vector3 out, int x, int y) {
// final float[] centerOffset = this.centerOffset;
// final int[] mapSize = this.mapSize;
@ -813,12 +992,62 @@ public class Terrain {
private final int width;
private final int height;
private final Buffer data;
private final String cliffModelDir;
private final String rampModelDir;
public UnloadedTexture(final int width, final int height, final Buffer data) {
public UnloadedTexture(final int width, final int height, final Buffer data, final String cliffModelDir,
final String rampModelDir) {
this.width = width;
this.height = height;
this.data = data;
this.cliffModelDir = cliffModelDir;
this.rampModelDir = rampModelDir;
}
}
public float getGroundHeight(final float x, final float y) {
final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f;
final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f;
final int cellX = (int) userCellSpaceX;
final int cellY = (int) userCellSpaceY;
if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) {
final float bottomLeft = this.corners[cellX][cellY].computeFinalGroundHeight();
final float bottomRight = this.corners[cellX][cellY].computeFinalGroundHeight();
final float topLeft = this.corners[cellX][cellY].computeFinalGroundHeight();
final float topRight = this.corners[cellX][cellY].computeFinalGroundHeight();
final float sqX = userCellSpaceX - cellX;
final float sqY = userCellSpaceY - cellY;
float height;
if ((sqX + sqY) < 1) {
height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY);
}
else {
height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX));
}
return height * 128.0f;
}
return 0;
}
public static final class Splat {
public List<float[]> locations = new ArrayList<>();
public float opacity = 1;
}
public void loadSplats() throws IOException {
for (final Map.Entry<String, Splat> entry : this.splats.entrySet()) {
final String path = entry.getKey();
final Splat splat = entry.getValue();
final SplatModel splatModel = new SplatModel(Gdx.gl30,
(Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset);
splatModel.color[3] = splat.opacity;
this.uberSplatModels.add(splatModel);
}
}
}

View File

@ -1,5 +1,8 @@
package com.etheller.warsmash.viewer5.handlers.w3x.environment;
/**
* Mostly copied from HiveWE!
*/
public class TerrainShaders {
public static final class Cliffs {
private Cliffs() {
@ -9,12 +12,14 @@ public class TerrainShaders {
"\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" + //
// "layout (location = 2) in vec3 vNormal;\r\n" + //
"layout (location = 2) 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" + //
"layout (location = 3) uniform float centerOffsetX;\r\n" + //
"layout (location = 4) uniform float centerOffsetY;\r\n" + //
"\r\n" + //
"layout (location = 0) out vec3 UV;\r\n" + //
"layout (location = 1) out vec3 Normal;\r\n" + //
@ -24,11 +29,14 @@ public class TerrainShaders {
" 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"
" float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128.0) / 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"
" vec4 myposition = vec4((vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128 ), 1);\r\n"
+ //
" myposition.x += centerOffsetX;\r\n" + //
" myposition.y += centerOffsetY;\r\n" + //
" gl_Position = MVP * myposition;\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" + //
@ -273,7 +281,7 @@ public class TerrainShaders {
" gl_Position = is_water ? MVP * vec4((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, water_height*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n"
+ //
"\r\n" + //
" UV = vec2(vPosition.x, vPosition.y);\r\n" + //
" UV = vec2((vPosition.x + pos.x%2)/2.0, (vPosition.y + pos.y%2)/2.0);\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" + //

View File

@ -1,6 +1,5 @@
package com.etheller.warsmash.desktop;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL33;
@ -30,7 +29,6 @@ public class DesktopLauncher {
GL31.glDrawArraysInstanced(mode, first, count, instanceCount);
}
};
Extensions.GL_BGRA = GL12.GL_BGRA;
final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.useGL30 = true;
config.gles30ContextMajorVersion = 3;