Update with lighting system, and after test playing Projection Revolution.

This commit is contained in:
Retera 2020-10-06 02:19:25 -04:00
parent b0b0745af9
commit 843b04b4a8
56 changed files with 1121 additions and 428 deletions

View File

@ -1,24 +1,26 @@
// This is the Warsmash INI file for Project Revolution
// PRSCMOD
[DataSources]
Count=7
Count=9
Type00=MPQ
Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq"
Path00="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq"
Type01=MPQ
Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq"
Path01="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq"
Type02=MPQ
Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq"
Path02="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq"
Type03=MPQ
Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq"
Type04=Folder
Path04="..\..\resources"
Type05=Folder
Path05="E:\Backups\Warsmash\Data"
Path03="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq"
Type04=MPQ
Path04="E:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq"
Type05=MPQ
Path05="E:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq"
Type06=Folder
Path06="."
Path06="E:\Games\Warcraft III Project Revolution\ProjectRevolusmash"
Type07=Folder
Path07="..\..\resources"
Type08=Folder
Path08="E:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps"
[Map]
//FilePath="CombatUnitTests.w3x"
//FilePath="PitchRoll.w3x"
FilePath="PeonStartingBase.w3x"
//FilePath="PlayerPeasants.w3m"
//FilePath="FireLord.w3x"
//FilePath="Maps\Campaign\NightElf03.w3m"
FilePath="ProjectRevolusmash.w3x"

View File

@ -0,0 +1,25 @@
[DataSources]
Count=7
Type00=MPQ
Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq"
Type01=MPQ
Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq"
Type02=MPQ
Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq"
Type03=MPQ
Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq"
Type04=Folder
Path04="..\..\resources"
Type05=Folder
Path05="E:\Backups\Warsmash\Data"
Type06=Folder
Path06="."
[Map]
//FilePath="CombatUnitTests.w3x"
//FilePath="PitchRoll.w3x"
FilePath="PeonStartingBase.w3x"
//FilePath="DungeonGoldMine.w3m"
//FilePath="PlayerPeasants.w3m"
//FilePath="FireLord.w3x"
//FilePath="Maps\Campaign\NightElf03.w3m"

View File

@ -12,6 +12,10 @@ Type04=Folder
Path04="."
[Map]
FilePath="PitchRoll.w3x"
//FilePath="PitchRoll.w3x"
//FilePath="ReforgedGeorgeVacation.w3x"
//FilePath="Maps\Campaign\NightElf03.w3m"
//FilePath="Maps\Campaign\NightElf03.w3m"
//FilePath="PrivateDontShare/Cult 8.w3x"
//FilePath="TorchLight2.w3x"
FilePath="OrcAssault.w3x"
//FilePath="PeonStartingBase.w3x"

View File

@ -14,6 +14,7 @@ import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
@ -227,15 +228,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
Gdx.input.setInputProcessor(this);
// final Music music = Gdx.audio
// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\Undead2.mp3"));
// music.setVolume(0.2f);
// music.setLooping(true);
// music.play();
this.minimap = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f);
final float worldWidth = (this.viewer.terrain.columns - 1);
final float worldHeight = this.viewer.terrain.rows - 1;
final Rectangle playableMapArea = this.viewer.terrain.getPlayableMapArea();
final float worldWidth = playableMapArea.getWidth();
final float worldHeight = playableMapArea.getHeight();
final float worldSize = Math.max(worldWidth, worldHeight);
final float minimapFilledWidth = (worldWidth / worldSize) * this.minimap.width;
final float minimapFilledHeight = (worldHeight / worldSize) * this.minimap.height;
@ -264,6 +260,16 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
new RootFrameListener() {
@Override
public void onCreate(final GameUI rootFrame) {
WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame);
final String musicField = rootFrame.getSkinField("Music_V1");
final String[] musics = musicField.split(";");
final String musicPath = musics[(int) (Math.random() * musics.length)];
final Music music = Gdx.audio.newMusic(
new DataSourceFileHandle(WarsmashGdxMapGame.this.viewer.dataSource, musicPath));
music.setVolume(0.2f);
music.setLooping(true);
music.play();
}
});
this.meleeUI.main();
@ -272,6 +278,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
updateUIScene();
this.meleeUI.resize();
resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
}
private void updateUIScene() {

View File

@ -54,6 +54,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
private final FreeTypeFontParameter fontParam;
private final Map<String, UIFrame> nameToFrame = new HashMap<>();
private final Viewport fdfCoordinateResolutionDummyViewport;
private final DataTable skinData;
public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport,
final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final War3MapViewer modelViewer) {
@ -74,6 +75,23 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
this.fontGenerator = fontGenerator;
this.fontParam = new FreeTypeFontParameter();
this.fdfCoordinateResolutionDummyViewport = new FitViewport(0.8f, 0.6f);
this.skinData = new DataTable(modelViewer.getWorldEditStrings());
try {
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\CommandFunc.txt")) {
this.skinData.readTXT(miscDataTxtStream, true);
}
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\CommandStrings.txt")) {
this.skinData.readTXT(miscDataTxtStream, true);
}
if (this.dataSource.has("war3mapSkin.txt")) {
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapSkin.txt")) {
this.skinData.readTXT(miscDataTxtStream, true);
}
}
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
public static Element loadSkin(final DataSource dataSource, final String skin) {
@ -146,6 +164,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
return file;
}
public DataTable getSkinData() {
return this.skinData;
}
public UIFrame createFrame(final String name, final UIFrame owner, final int priority, final int createContext) {
final FrameDefinition frameDefinition = this.templates.getFrame(name);
if (frameDefinition.getFrameClass() == FrameClass.Frame) {

View File

@ -23,6 +23,9 @@ public class TextureFrame extends AbstractRenderableFrame {
@Override
protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) {
if (this.texture == null) {
return;
}
batch.draw(this.texture, this.renderBounds.x, this.renderBounds.y, this.renderBounds.width,
this.renderBounds.height);
}
@ -42,6 +45,10 @@ public class TextureFrame extends AbstractRenderableFrame {
}
public void setTexture(final Texture texture) {
if (texture == null) {
this.texture = null;
return;
}
final TextureRegion texRegion;
if (this.texCoord != null) {
texRegion = new TextureRegion(texture, this.texCoord.getX(), this.texCoord.getZ(), this.texCoord.getY(),

View File

@ -3,7 +3,6 @@ package com.etheller.warsmash.parsers.w3x.w3i;
import java.io.IOException;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.War3ID;
import com.google.common.io.LittleEndianDataInputStream;
import com.google.common.io.LittleEndianDataOutputStream;
@ -11,7 +10,7 @@ import com.google.common.io.LittleEndianDataOutputStream;
* A player.
*/
public class Player {
private War3ID id;
private int id;
private int type;
private int race;
private int isFixedStartPosition;
@ -23,7 +22,7 @@ public class Player {
private long enemyHighPrioritiesFlags;
public void load(final LittleEndianDataInputStream stream, final int version) throws IOException {
this.id = ParseUtils.readWar3ID(stream);
this.id = (int) ParseUtils.readUInt32(stream);
this.type = stream.readInt();
this.race = stream.readInt();
this.isFixedStartPosition = stream.readInt();
@ -38,7 +37,7 @@ public class Player {
}
public void save(final LittleEndianDataOutputStream stream) throws IOException {
ParseUtils.writeWar3ID(stream, this.id);
ParseUtils.writeUInt32(stream, this.id);
stream.writeInt(this.type);
stream.writeInt(this.race);
stream.writeInt(this.isFixedStartPosition);
@ -52,7 +51,7 @@ public class Player {
return 33 + this.name.length();
}
public War3ID getId() {
public int getId() {
return this.id;
}

View File

@ -15,7 +15,7 @@ import java.util.Set;
import com.etheller.warsmash.util.StringBundle;
public class DataTable implements ObjectData {
private static final boolean DEBUG = false;
public static boolean DEBUG = false;
Map<StringKey, Element> dataTable = new LinkedHashMap<>();
@ -182,7 +182,7 @@ public class DataTable implements ObjectData {
if (input.startsWith("O;")) {
continue;
}
if (input.contains("X1;")) {
if (input.contains("X1;") || input.endsWith(";X1")) {
rowStartCount++;
col = 0;
}

View File

@ -19,6 +19,7 @@ import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
/**
* Uses AWT stuff
@ -26,14 +27,27 @@ import com.etheller.warsmash.datasources.DataSource;
*/
public final class ImageUtils {
private static final int BYTES_PER_PIXEL = 4;
public static final String DEFAULT_ICON_PATH = "ReplaceableTextures\\CommandButtons\\BTNTemp.blp";
public static Texture getBLPTexture(final DataSource dataSource, final String path) {
try {
try (final InputStream resourceAsStream = dataSource.getResourceAsStream(path)) {
if (resourceAsStream == null) {
throw new IllegalStateException("missing resource: " + path);
if ((resourceAsStream == null) || path.endsWith(".tga")) {
final String tgaPath = path.substring(0, path.length() - 4) + ".tga";
try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) {
if (tgaStream == null) {
throw new IllegalStateException("missing resource: " + path);
}
else {
return ImageUtils.getTexture(TgaFile.readTGA(tgaPath, tgaStream));
}
}
}
return ImageUtils.getTexture(ImageIO.read(resourceAsStream));
final BufferedImage image = ImageIO.read(resourceAsStream);
if (image == null) {
throw new IllegalStateException("corrupt resource: " + path);
}
return ImageUtils.getTexture(image);
}
}

View File

@ -49,7 +49,8 @@ public class MappedData {
final MappedDataRow mapped = this.map.get(name);
for (int j = 0, k = header.size(); j < k; j++) {
String key = (String) header.get(j);
final Object headerObj = header.get(j);
String key = headerObj == null ? null : headerObj.toString();
// UnitBalance.slk doesn't define the name of one row.
if (key == null) {

View File

@ -175,4 +175,15 @@ public class ParseUtils {
stream.write(nameBytes);
stream.write(0);
}
public static float[] flipRGB(final float[] color) {
final float r = color[0];
color[0] = color[2];
color[2] = r;
return color;
}
public static float[] newFlippedRGB(final float[] color) {
return new float[] { color[2], color[1], color[0] };
}
}

View File

@ -27,6 +27,7 @@ public enum RenderMathUtils {
public static final float[] FLOAT_VEC3_ZERO = new float[] { 0, 0, 0 };
public static final float[] FLOAT_QUAT_DEFAULT = new float[] { 0, 0, 0, 1 };
public static final float[] FLOAT_VEC3_ONE = new float[] { 1, 1, 1 };
public static final float HALF_PI = (float) (Math.PI / 2.0);
// copied from ghostwolf and
// https://www.blend4web.com/api_doc/libs_gl-matrix2.js.html

View File

@ -104,7 +104,7 @@ public abstract class ModelViewer {
}
public Scene addSimpleScene() {
final Scene scene = new SimpleScene(this);
final Scene scene = new SimpleScene(this, createLightManager(true));
this.scenes.add(scene);
@ -112,7 +112,7 @@ public abstract class ModelViewer {
}
public WorldScene addWorldScene() {
final WorldScene scene = new WorldScene(this);
final WorldScene scene = new WorldScene(this, createLightManager(false));
this.scenes.add(scene);
@ -367,5 +367,5 @@ public abstract class ModelViewer {
System.err.println("error, this, InvalidHandler, FailedToLoad");
}
public abstract SceneLightManager createLightManager();
public abstract SceneLightManager createLightManager(boolean simple);
}

View File

@ -60,7 +60,7 @@ public abstract class Scene {
public boolean alpha = false;
private final SceneLightManager lightManager;
public Scene(final ModelViewer viewer) {
public Scene(final ModelViewer viewer, final SceneLightManager lightManager) {
final CanvasProvider canvas = viewer.canvas;
this.viewer = viewer;
this.camera = new Camera();
@ -87,7 +87,7 @@ public abstract class Scene {
this.visibleCells = 0;
this.visibleInstances = 0;
this.lightManager = this.viewer.createLightManager();
this.lightManager = lightManager;
}
public boolean enableAudio() {
@ -218,6 +218,8 @@ public abstract class Scene {
this.emitterObjectUpdater.update(dt);
this.updatedParticles = this.emitterObjectUpdater.objects.size();
this.lightManager.update();
}
protected abstract void innerUpdate(float dt, int frame);

View File

@ -4,4 +4,6 @@ public interface SceneLightManager {
public void add(final SceneLightInstance lightInstance);
public void remove(final SceneLightInstance lightInstance);
public void update();
}

View File

@ -55,4 +55,63 @@ public class Shaders {
" return vec3(quat_transform(q, v.xy), v.z);\r\n" + //
" }\r\n" + //
" ";
public static String lightSystem(final String normalName, final String positionName, final String lightTexture,
final String lightCount, final boolean terrain) {
return " vec3 lightFactor = vec3(0.0,0.0,0.0);\r\n" + //
" float lightIndex = 0.0;\r\n" + //
" for(float lightIndex = 0.0; lightIndex < " + lightCount + "; lightIndex += 1.0) {\r\n" + //
" float rowPos = (lightIndex + 0.5) / " + lightCount + ";\r\n" + //
" vec4 lightPosition = texture2D(" + lightTexture + ", vec2(0.125, rowPos));\r\n" + //
" vec3 lightExtra = texture2D(" + lightTexture + ", vec2(0.375, rowPos)).xyz;\r\n" + //
" vec4 lightColor = texture2D(" + lightTexture + ", vec2(0.625, rowPos));\r\n" + //
" vec4 lightAmbColor = texture2D(" + lightTexture + ", vec2(0.875, rowPos));\r\n" + //
" if(lightExtra.x > 1.5) {\r\n" + //
" // Ambient light;\r\n" + //
" float dist = length(" + positionName + " - vec3(lightPosition." + (terrain ? "xyw" : "xyz")
+ "));\r\n" + //
" float attenuationStart = lightExtra.y;\r\n" + //
" float attenuationEnd = lightExtra.z;\r\n" + //
" if( dist <= attenuationEnd ) {\r\n" + //
" float attenuationDist = clamp((dist-attenuationStart), 0.001, (attenuationEnd-attenuationStart));\r\n"
+ //
" float attenuationFactor = 1.0/(attenuationDist);\r\n" + //
" lightFactor += attenuationFactor * lightAmbColor.a * lightAmbColor.rgb;\r\n" + //
" \r\n" + //
" }\r\n" + //
" } else if(lightExtra.x > 0.5) {\r\n" + //
" // Directional (sun) light;\r\n" + //
" vec3 lightDirection = vec3(lightPosition.xyz);\r\n" + //
" vec3 lightFactorContribution = lightColor.a * lightColor.rgb * clamp(dot(" + normalName
+ ", lightDirection), 0.0, 1.0);\r\n" + //
" if(lightFactorContribution.r > 1.0 || lightFactorContribution.g > 1.0 || lightFactorContribution.b > 1.0) {\r\n"
+ //
" lightFactorContribution = normalize(lightFactorContribution);\r\n" + //
" }\r\n" + //
" lightFactor += lightFactorContribution + lightAmbColor.a * lightAmbColor.rgb;\r\n" + //
" } else {\r\n" + //
" // Omnidirectional light;\r\n" + //
" vec3 deltaBtwn = " + positionName + " - lightPosition.xyz;\r\n" + //
" float dist = length(" + positionName + " - vec3(lightPosition." + (terrain ? "xyw" : "xyz")
+ "));\r\n" + //
" float attenuationStart = lightExtra.y;\r\n" + //
" float attenuationEnd = lightExtra.z;\r\n" + //
" if( dist <= attenuationEnd ) {\r\n" + //
" float attenuationDist = clamp((dist-attenuationStart), 0.001, (attenuationEnd-attenuationStart));\r\n"
+ //
" float attenuationFactor = 1.0/(attenuationDist);\r\n" + //
" vec3 lightDirection = -deltaBtwn;\r\n" + //
" vec3 lightFactorContribution = attenuationFactor * lightColor.a * lightColor.rgb * clamp(dot("
+ normalName + ", lightDirection), 0.0, 1.0);\r\n" + //
" if(lightFactorContribution.r > 1.0 || lightFactorContribution.g > 1.0 || lightFactorContribution.b > 1.0) {\r\n"
+ //
" lightFactorContribution = normalize(lightFactorContribution);\r\n" + //
" }\r\n" + //
" lightFactor += lightFactorContribution + attenuationFactor * lightAmbColor.a * lightAmbColor.rgb;\r\n"
+ //
" \r\n" + //
" }\r\n" + //
" }\r\n" + //
" }\r\n";
}
}

View File

@ -6,8 +6,8 @@ import java.util.List;
public class SimpleScene extends Scene {
private final List<ModelInstance> allInstances = new ArrayList<>();
public SimpleScene(final ModelViewer viewer) {
super(viewer);
public SimpleScene(final ModelViewer viewer, final SceneLightManager lightManager) {
super(viewer, lightManager);
this.visibleCells = 1;
this.visibleInstances = 0;
}

View File

@ -8,7 +8,8 @@ import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
public abstract class SkeletalNode extends GenericNode {
protected static final Vector3 locationHeap = new Vector3();
protected static final Vector3 cameraRayHeap = new Vector3();
protected static final Vector3 billboardAxisHeap = new Vector3();
protected static final Quaternion rotationHeap = new Quaternion();
protected static final Quaternion rotationHeap2 = new Quaternion();
protected static final Vector3 scalingHeap = new Vector3();
@ -16,9 +17,9 @@ public abstract class SkeletalNode extends GenericNode {
public UpdatableObject object;
public boolean billboarded;
public final boolean billboardedX;
public final boolean billboardedY;
public final boolean billboardedZ;
public boolean billboardedX;
public boolean billboardedY;
public boolean billboardedZ;
public SkeletalNode() {
this.pivot = new Vector3();
@ -98,6 +99,36 @@ public abstract class SkeletalNode extends GenericNode {
this.convertBasis(computedRotation);
}
else if (this.billboardedX) {
final Camera camera = scene.camera;
computedRotation = rotationHeap;
cameraRayHeap.set(camera.billboardedVectors[6]);
computedRotation.set(this.parent.inverseWorldRotation);
computedRotation.transform(cameraRayHeap);
billboardAxisHeap.set(1, 0, 0);
final float angle = (float) Math.atan2(cameraRayHeap.z, cameraRayHeap.y);
computedRotation.setFromAxisRad(billboardAxisHeap, angle);
}
else if (this.billboardedY) {
final Camera camera = scene.camera;
computedRotation = rotationHeap;
cameraRayHeap.set(camera.billboardedVectors[6]);
computedRotation.set(this.parent.inverseWorldRotation);
computedRotation.transform(cameraRayHeap);
billboardAxisHeap.set(0, 1, 0);
final float angle = (float) Math.atan2(cameraRayHeap.z, -cameraRayHeap.x);
computedRotation.setFromAxisRad(billboardAxisHeap, angle);
}
else if (this.billboardedZ) {
final Camera camera = scene.camera;
computedRotation = rotationHeap;
cameraRayHeap.set(camera.billboardedVectors[6]);
computedRotation.set(this.parent.inverseWorldRotation);
computedRotation.transform(cameraRayHeap);
billboardAxisHeap.set(0, 0, 1);
final float angle = (float) Math.atan2(cameraRayHeap.y, cameraRayHeap.x);
computedRotation.setFromAxisRad(billboardAxisHeap, angle);
}
else {
computedRotation = this.localRotation;
}

View File

@ -22,8 +22,8 @@ public class WorldScene extends Scene {
public Grid grid;
public WorldScene(final ModelViewer viewer) {
super(viewer);
public WorldScene(final ModelViewer viewer, final SceneLightManager lightManager) {
super(viewer, lightManager);
this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000);
}

View File

@ -28,7 +28,7 @@ public class DataTexture {
this.reserve(width, height);
}
private void reserve(final int width, final int height) {
public void reserve(final int width, final int height) {
if ((this.width < width) || (this.height < height)) {
final GL20 gl = this.gl;

View File

@ -63,9 +63,9 @@ public class BatchGroup extends GenericGroup {
final DataTexture boneTexture = instance.boneTexture;
final DataTexture unitLightsTexture = lightManager.getUnitLightsTexture();
unitLightsTexture.bind(16);
shader.setUniformi("u_lightTexture", 16);
shader.setUniformf("u_lightCount", unitLightsTexture.getHeight());
unitLightsTexture.bind(14);
shader.setUniformi("u_lightTexture", 14);
shader.setUniformf("u_lightCount", unitLightsTexture.getHeight() - 0.5f);
// Instances of models with no bones don't have a bone texture.
if (boneTexture != null) {
@ -103,6 +103,7 @@ public class BatchGroup extends GenericGroup {
shader.setUniform4fv("u_geosetColor", geosetColor, 0, geosetColor.length);
shader.setUniformf("u_layerAlpha", layerAlpha);
shader.setUniformf("u_unshaded", layer.unshaded);
shader.setUniform2fv("u_uvTrans", uvAnim, 0, 2);
shader.setUniform2fv("u_uvRot", uvAnim, 2, 2);

View File

@ -106,7 +106,7 @@ public class Geoset {
public void bind(final ShaderProgram shader, final int coordId) {
// TODO use indices instead of strings for attributes
shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset);
// shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset);
shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset);
shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8));
shader.setVertexAttribute("a_bones", 4, this.openGLSkinType, false, this.skinStride, this.skinOffset);
shader.setVertexAttribute("a_boneNumber", 1, this.openGLSkinType, false, this.skinStride,

View File

@ -18,13 +18,15 @@ public class Light extends GenericObject {
case 0:
this.type = Type.OMNIDIRECTIONAL;
break;
case 2:
case 1:
this.type = Type.DIRECTIONAL;
break;
default:
case 1:
case 2:
this.type = Type.AMBIENT;
break;
default:
this.type = Type.DIRECTIONAL;
break;
}
this.attenuation = light.getAttenuation();
this.color = light.getColor();
@ -61,6 +63,11 @@ public class Light extends GenericObject {
return this.getVectorValue(out, AnimationMap.KLBC.getWar3id(), sequence, frame, counter, this.ambientColor);
}
@Override
public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KLAV.getWar3id(), sequence, frame, counter, 1);
}
public static enum Type {
// Omnidirectional light used for in-game sun
OMNIDIRECTIONAL,

View File

@ -2,11 +2,15 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.nio.FloatBuffer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SceneLightInstance;
import com.etheller.warsmash.viewer5.UpdatableObject;
public class LightInstance implements UpdatableObject, SceneLightInstance {
private static final Matrix4 matrix4Heap = new Matrix4();
private static final Vector3 vector3Heap = new Vector3();
private static final float[] vectorHeap = new float[3];
private static final float[] scalarHeap = new float[1];
protected final MdxNode node;
@ -41,15 +45,28 @@ public class LightInstance implements UpdatableObject, SceneLightInstance {
final float ambientColorRed = vectorHeap[0];
final float ambientColorGreen = vectorHeap[1];
final float ambientColorBlue = vectorHeap[2];
floatBuffer.put(offset, this.node.worldLocation.x);
floatBuffer.put(offset + 1, this.node.worldLocation.y);
floatBuffer.put(offset + 2, this.node.worldLocation.z);
switch (this.light.getType()) {
case AMBIENT:
case OMNIDIRECTIONAL:
floatBuffer.put(offset, this.node.worldLocation.x);
floatBuffer.put(offset + 1, this.node.worldLocation.y);
floatBuffer.put(offset + 2, this.node.worldLocation.z);
break;
case DIRECTIONAL:
vector3Heap.set(0, 0, 1);
this.node.localRotation.transform(vector3Heap);
vector3Heap.nor();
floatBuffer.put(offset, vector3Heap.x);
floatBuffer.put(offset + 1, vector3Heap.y);
floatBuffer.put(offset + 2, vector3Heap.z);
break;
}
// I use some padding to make the memory structure of the light be a 4x4 float
// grid, when somebody who actually has experience with this stuff comes along
// to change this to something smart, maybe they'll remove the padding if it's
// not necessary. I'm basing how I implement this on how Ghostwolf did
// BoneTexture
floatBuffer.put(offset + 3, 0);
floatBuffer.put(offset + 3, this.instance.worldLocation.z);
floatBuffer.put(offset + 4, this.light.getType().ordinal());
floatBuffer.put(offset + 5, attenuationStart);
floatBuffer.put(offset + 6, attenuationEnd);
@ -66,16 +83,20 @@ public class LightInstance implements UpdatableObject, SceneLightInstance {
@Override
public void update(final float dt, final boolean visible) {
this.visible = visible;
}
public void update(final Scene scene) {
if (this.loadedInScene != this.visible) {
if (this.visible) {
scene.addLight(this);
}
else {
scene.removeLight(this);
this.light.getVisibility(scalarHeap, this.instance.sequence, this.instance.frame, this.instance.counter);
this.visible = scalarHeap[0] > 0;
if (scene != null) {
if (this.loadedInScene != this.visible) {
if (this.visible) {
scene.addLight(this);
}
else {
scene.removeLight(this);
}
this.loadedInScene = this.visible;
}
}
}

View File

@ -242,13 +242,16 @@ public class MdxComplexInstance extends ModelInstance {
/// TODO: single-axis billboarding
if (genericObject.billboarded != 0) {
node.billboarded = true;
} // else if (genericObject.billboardedX) {
// node.billboardedX = true;
// } else if (genericObject.billboardedY) {
// node.billboardedY = true;
// } else if (genericObject.billboardedZ) {
// node.billboardedZ = true;
// }
}
else if (genericObject.billboardedX != 0) {
node.billboardedX = true;
}
else if (genericObject.billboardedY != 0) {
node.billboardedY = true;
}
else if (genericObject.billboardedZ != 0) {
node.billboardedZ = true;
}
if (object != null) {
node.object = object;
@ -724,10 +727,16 @@ public class MdxComplexInstance extends ModelInstance {
*
* @param ray
*/
public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection) {
public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh) {
final MdxModel mdxModel = (MdxModel) this.model;
final List<CollisionShape> collisionShapes = mdxModel.collisionShapes;
if (collisionShapes.isEmpty()) {
for (final CollisionShape collisionShape : collisionShapes) {
final MdxNode mdxNode = this.nodes[collisionShape.index];
if (collisionShape.checkIntersect(ray, mdxNode, intersection)) {
return true;
}
}
if (collisionShapes.isEmpty() || alwaysUseMesh) {
for (final Geoset geoset : mdxModel.geosets) {
if (!geoset.unselectable) {
geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter);
@ -741,14 +750,6 @@ public class MdxComplexInstance extends ModelInstance {
}
}
}
else {
for (final CollisionShape collisionShape : collisionShapes) {
final MdxNode mdxNode = this.nodes[collisionShape.index];
if (collisionShape.checkIntersect(ray, mdxNode, intersection)) {
return true;
}
}
}
return false;
}

View File

@ -24,13 +24,13 @@ public class MdxHandler extends ModelHandler {
viewer.addHandler(new BlpHandler());
viewer.addHandler(new TgaHandler());
Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplexUnshaded, MdxShaders.fsComplex);
Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplexUnshaded,
Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplex, MdxShaders.fsComplex);
Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex,
MdxShaders.fsComplex);
Shaders.complexShadowMap = viewer.webGL.createShaderProgram(MdxShaders.vsComplexUnshaded,
Shaders.complexShadowMap = viewer.webGL.createShaderProgram(MdxShaders.vsComplex,
MdxShaders.fsComplexShadowMap);
Shaders.extendedShadowMap = viewer.webGL.createShaderProgram(
"#define EXTENDED_BONES\r\n" + MdxShaders.vsComplexUnshaded, MdxShaders.fsComplexShadowMap);
"#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap);
Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles);
Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple);
// Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd);

View File

@ -91,6 +91,7 @@ public class MdxShaders {
" uniform vec2 u_uvRot;\r\n" + //
" uniform float u_uvScale;\r\n" + //
" uniform bool u_hasBones;\r\n" + //
" uniform bool u_unshaded;\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec3 a_normal;\r\n" + //
" attribute vec2 a_uv;\r\n" + //
@ -103,6 +104,8 @@ public class MdxShaders {
" varying vec4 v_color;\r\n" + //
" varying vec4 v_uvTransRot;\r\n" + //
" varying float v_uvScale;\r\n" + //
" uniform sampler2D u_lightTexture;\r\n" + //
" uniform float u_lightCount;\r\n" + //
Shaders.boneTexture + "\r\n" + //
" void transform(inout vec3 position, inout vec3 normal) {\r\n" + //
" // For the broken models out there, since the game supports this.\r\n" + //
@ -110,8 +113,8 @@ public class MdxShaders {
" vec4 position4 = vec4(position, 1.0);\r\n" + //
" vec4 normal4 = vec4(normal, 0.0);\r\n" + //
" mat4 bone;\r\n" + //
" vec4 p;\r\n" + //
" vec4 n;\r\n" + //
" vec4 p = vec4(0.0,0.0,0.0,0.0);\r\n" + //
" vec4 n = vec4(0.0,0.0,0.0,0.0);\r\n" + //
" for (int i = 0; i < 4; i++) {\r\n" + //
" if (a_bones[i] > 0.0) {\r\n" + //
" bone = fetchMatrix(a_bones[i] - 1.0, 0.0);\r\n" + //
@ -130,6 +133,8 @@ public class MdxShaders {
" #endif\r\n" + //
" position = p.xyz / a_boneNumber;\r\n" + //
" normal = normalize(n.xyz);\r\n" + //
" } else {\r\n" + //
" position.x += 100.0;\r\n" + //
" }\r\n" + //
"\r\n" + //
" }\r\n" + //
@ -144,65 +149,10 @@ public class MdxShaders {
" v_uvTransRot = vec4(u_uvTrans, u_uvRot);\r\n" + //
" v_uvScale = u_uvScale;\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }";
public static final String vsComplexUnshaded = " uniform mat4 u_mvp;\r\n" + //
" uniform vec4 u_vertexColor;\r\n" + //
" uniform vec4 u_geosetColor;\r\n" + //
" uniform float u_layerAlpha;\r\n" + //
" uniform vec2 u_uvTrans;\r\n" + //
" uniform vec2 u_uvRot;\r\n" + //
" uniform float u_uvScale;\r\n" + //
" uniform bool u_hasBones;\r\n" + //
" attribute vec3 a_position;\r\n" + //
" attribute vec2 a_uv;\r\n" + //
" attribute vec4 a_bones;\r\n" + //
" #ifdef EXTENDED_BONES\r\n" + //
" attribute vec4 a_extendedBones;\r\n" + //
" #endif\r\n" + //
" attribute float a_boneNumber;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying vec4 v_color;\r\n" + //
" varying vec4 v_uvTransRot;\r\n" + //
" varying float v_uvScale;\r\n" + //
Shaders.boneTexture + "\r\n" + //
" void transform(inout vec3 position) {\r\n" + //
" // For the broken models out there, since the game supports this.\r\n" + //
" if (a_boneNumber > 0.0) {\r\n" + //
" vec4 position4 = vec4(position, 1.0);\r\n" + //
" mat4 bone;\r\n" + //
" vec4 p = vec4(0.0,0.0,0.0,0.0);\r\n" + //
" for (int i = 0; i < 4; i++) {\r\n" + //
" if (a_bones[i] > 0.0) {\r\n" + //
" bone = fetchMatrix(a_bones[i] - 1.0, 0.0);\r\n" + //
" p += bone * position4;\r\n" + //
" }\r\n" + //
" }\r\n" + //
" #ifdef EXTENDED_BONES\r\n" + //
" for (int i = 0; i < 4; i++) {\r\n" + //
" if (a_extendedBones[i] > 0.0) {\r\n" + //
" bone = fetchMatrix(a_extendedBones[i] - 1.0, 0.0);\r\n" + //
" p += bone * position4;\r\n" + //
" }\r\n" + //
" }\r\n" + //
" #endif\r\n" + //
" position = p.xyz / a_boneNumber;\r\n" + //
// " position.x *= fetchMatrix(0.0, 0.0)[0][0];\r\n" + //
" } else {\r\n" + //
" position.x += 100.0;\r\n" + //
" if(!u_unshaded) {\r\n" + //
Shaders.lightSystem("normal", "position", "u_lightTexture", "u_lightCount", false) + "\r\n" + //
" v_color.xyz *= clamp(lightFactor, 0.0, 1.0);\r\n" + //
" }\r\n" + //
"\r\n" + //
" }\r\n" + //
" void main() {\r\n" + //
" vec3 position = a_position;\r\n" + //
" if (u_hasBones) {\r\n" + //
" transform(position);\r\n" + //
" }\r\n" + //
" v_uv = a_uv;\r\n" + //
" v_color = u_vertexColor * u_geosetColor.bgra * vec4(1.0, 1.0, 1.0, u_layerAlpha);\r\n" + //
" v_uvTransRot = vec4(u_uvTrans, u_uvRot);\r\n" + //
" v_uvScale = u_uvScale;\r\n" + //
" gl_Position = u_mvp * vec4(position, 1.0);\r\n" + //
" }";
public static final String fsComplex = Shaders.quatTransform + "\r\n\r\n" + //
@ -231,9 +181,6 @@ public class MdxShaders {
" if (u_filterMode >= 5.0 && color.a < 0.02) {\r\n" + //
" discard;\r\n" + //
" }\r\n" + //
" // if (!u_unshaded) {\r\n" + //
" // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + //
" // }\r\n" + //
" gl_FragColor = color;\r\n" + //
" }";
@ -245,6 +192,7 @@ public class MdxShaders {
" varying vec4 v_color;\r\n" + //
" varying vec4 v_uvTransRot;\r\n" + //
" varying float v_uvScale;\r\n" + //
" varying vec3 v_normal;\r\n" + //
// " layout(location = 0) out float fragmentdepth;\r\n" + //
" void main() {\r\n" + //
" vec2 uv = v_uv;\r\n" + //

View File

@ -12,7 +12,7 @@ public class MdxViewer extends ModelViewer {
}
@Override
public SceneLightManager createLightManager() {
public SceneLightManager createLightManager(final boolean simple) {
// TODO Auto-generated method stub
return null;
}

View File

@ -3,7 +3,9 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.Arrays;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.parsers.mdlx.timeline.Timeline;
import com.etheller.warsmash.util.ParseUtils;
import com.etheller.warsmash.util.RenderMathUtils;
public final class SdSequence<TYPE> {
@ -30,9 +32,9 @@ public final class SdSequence<TYPE> {
final int interpolationType = sd.interpolationType;
final long[] frames = timeline.getFrames();
final TYPE[] values = timeline.getValues();
final TYPE[] inTans = timeline.getInTans();
final TYPE[] outTans = timeline.getOutTans();
final TYPE[] values = getValues(timeline);
final TYPE[] inTans = getInTans(timeline);
final TYPE[] outTans = getOutTans(timeline);
final TYPE defval = sd.defval;
// When using a global sequence, where the first key is outside of the
@ -47,7 +49,12 @@ public final class SdSequence<TYPE> {
// This fixes problems spread over many models, e.g. HeroMountainKing
// (compare in WE and in Magos).
if (isGlobalSequence && (frames.length > 0) && (frames[0] > end)) {
framesBuilder.add(frames[0]);
if (start == end) {
framesBuilder.add(start);
}
else {
framesBuilder.add(frames[0]);
}
valuesBuilder.add(values[0]);
}
@ -127,6 +134,33 @@ public final class SdSequence<TYPE> {
this.outTans = outTansBuilder.toArray(arrayDescriptor.create(outTansBuilder.size()));
}
private TYPE[] getValues(final Timeline<TYPE> timeline) {
final TYPE[] values = timeline.getValues();
return fixTimelineArray(timeline, values);
}
private TYPE[] getOutTans(final Timeline<TYPE> timeline) {
final TYPE[] outTans = timeline.getOutTans();
return fixTimelineArray(timeline, outTans);
}
private TYPE[] getInTans(final Timeline<TYPE> timeline) {
final TYPE[] inTans = timeline.getInTans();
return fixTimelineArray(timeline, inTans);
}
private TYPE[] fixTimelineArray(final Timeline<TYPE> timeline, final TYPE[] values) {
if (timeline.getName().equals(AnimationMap.KLAC.getWar3id())
|| timeline.getName().equals(AnimationMap.KLBC.getWar3id())) {
final float[][] flippedColorData = new float[values.length][3];
for (int i = 0; i < values.length; i++) {
flippedColorData[i] = ParseUtils.newFlippedRGB((float[]) values[i]);
}
return (TYPE[]) flippedColorData;
}
return values;
}
// private TYPE[] makeArray(final int size) {
// return (TYPE[]) new Object[size];
// }

View File

@ -0,0 +1,26 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
public class Destructable extends Doodad {
private final float life;
public Destructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type,
final float maxPitch, final float maxRoll, final float life) {
super(map, model, row, doodad, type, maxPitch, maxRoll);
this.life = life;
}
@Override
public PrimaryTag getAnimation() {
if (this.life <= 0) {
return PrimaryTag.DEATH;
}
return super.getAnimation();
}
}

View File

@ -6,7 +6,10 @@ 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.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
public class Doodad {
private static final int SAMPLE_RADIUS = 4;
@ -30,6 +33,7 @@ public class Doodad {
}
else {
instance = model.addInstance();
((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP);
}
instance.move(doodad.getLocation());
@ -50,8 +54,8 @@ public class Doodad {
final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians));
final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY);
final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY);
pitch = Math.min(maxPitch,
(float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2));
pitch = Math.max(-maxPitch, Math.min(maxPitch,
(float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2)));
final double leftOfFacingAngle = facingRadians + (Math.PI / 2);
final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle));
final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle));
@ -59,8 +63,8 @@ public class Doodad {
final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle));
final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY);
final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY);
roll = Math.min(maxRoll,
(float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2));
roll = Math.max(-maxRoll, Math.min(maxRoll,
(float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2)));
instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians));
instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch));
instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll));
@ -86,4 +90,8 @@ public class Doodad {
this.instance = instance;
this.row = row;
}
public PrimaryTag getAnimation() {
return PrimaryTag.STAND;
}
}

View File

@ -26,10 +26,12 @@ public class SplatModel {
public final float[] color;
private final List<float[]> locations;
private final List<SplatMover> splatInstances;
private final boolean unshaded;
public SplatModel(final GL30 gl, final Texture texture, final List<float[]> locations, final float[] centerOffset,
final List<Consumer<SplatMover>> unitMapping) {
final List<Consumer<SplatMover>> unitMapping, final boolean unshaded) {
this.texture = texture;
this.unshaded = unshaded;
this.batches = new ArrayList<>();
this.color = new float[] { 1, 1, 1, 1 };
@ -205,6 +207,7 @@ public class SplatModel {
gl.glActiveTexture(GL30.GL_TEXTURE1);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.texture.getGlHandle());
shader.setUniformi("u_show_lighting", this.unshaded ? 0 : 1);
shader.setUniform4fv("u_color", this.color, 0, 4);
for (final Batch b : this.batches) {

View File

@ -1,107 +1,13 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.viewer5.SceneLightInstance;
import com.etheller.warsmash.viewer5.SceneLightManager;
import com.etheller.warsmash.viewer5.gl.DataTexture;
import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance;
public class W3xSceneLightManager implements SceneLightManager {
public final List<LightInstance> lights;
private FloatBuffer lightDataCopyHeap;
private final DataTexture unitLightsTexture;
private final DataTexture terrainLightsTexture;
private final War3MapViewer viewer;
private int terrainLightCount;
private int unitLightCount;
public interface W3xSceneLightManager {
public DataTexture getUnitLightsTexture();
public W3xSceneLightManager(final War3MapViewer viewer) {
this.viewer = viewer;
this.lights = new ArrayList<>();
this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1);
this.terrainLightsTexture = new DataTexture(viewer.gl, 4, 4, 1);
this.lightDataCopyHeap = ByteBuffer.allocateDirect(16 * 1 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
}
public int getUnitLightCount();
@Override
public void add(final SceneLightInstance lightInstance) {
// TODO redesign to avoid cast
final LightInstance mdxLight = (LightInstance) lightInstance;
this.lights.add(mdxLight);
rebind();
}
public DataTexture getTerrainLightsTexture();
@Override
public void remove(final SceneLightInstance lightInstance) {
// TODO redesign to avoid cast
final LightInstance mdxLight = (LightInstance) lightInstance;
this.lights.remove(mdxLight);
rebind();
}
private void rebind() {
final int numberOfLights = this.lights.size() + 1;
final int bytesNeeded = numberOfLights * 4 * 16;
if (bytesNeeded > (this.lightDataCopyHeap.capacity() * 4)) {
this.lightDataCopyHeap = ByteBuffer.allocateDirect(bytesNeeded).order(ByteOrder.nativeOrder())
.asFloatBuffer();
}
this.unitLightCount = 0;
this.lightDataCopyHeap.clear();
int offset = 0;
if (this.viewer.dncUnit != null) {
if (!this.viewer.dncUnit.lights.isEmpty()) {
this.viewer.dncUnit.lights.get(0).bind(0, this.lightDataCopyHeap);
offset += 16;
this.unitLightCount++;
}
}
for (final LightInstance light : this.lights) {
light.bind(offset, this.lightDataCopyHeap);
offset += 16;
this.unitLightCount++;
}
this.lightDataCopyHeap.flip();
this.unitLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 16, this.unitLightCount);
this.terrainLightCount = 0;
this.lightDataCopyHeap.clear();
offset = 0;
if (this.viewer.dncTerrain != null) {
if (!this.viewer.dncTerrain.lights.isEmpty()) {
this.viewer.dncTerrain.lights.get(0).bind(0, this.lightDataCopyHeap);
offset += 16;
this.terrainLightCount++;
}
}
for (final LightInstance light : this.lights) {
light.bind(offset, this.lightDataCopyHeap);
offset += 16;
this.terrainLightCount++;
}
this.lightDataCopyHeap.flip();
this.terrainLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 16, this.terrainLightCount);
}
public DataTexture getUnitLightsTexture() {
return this.unitLightsTexture;
}
public int getUnitLightCount() {
return this.unitLightCount;
}
public DataTexture getTerrainLightsTexture() {
return this.terrainLightsTexture;
}
public int getTerrainLightCount() {
return this.terrainLightCount;
}
public int getTerrainLightCount();
}

View File

@ -0,0 +1,90 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.viewer5.SceneLightInstance;
import com.etheller.warsmash.viewer5.SceneLightManager;
import com.etheller.warsmash.viewer5.gl.DataTexture;
import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance;
public class W3xScenePortraitLightManager implements SceneLightManager, W3xSceneLightManager {
public final List<LightInstance> lights;
private FloatBuffer lightDataCopyHeap;
private final DataTexture unitLightsTexture;
private final War3MapViewer viewer;
private int unitLightCount;
public W3xScenePortraitLightManager(final War3MapViewer viewer) {
this.viewer = viewer;
this.lights = new ArrayList<>();
this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1);
this.lightDataCopyHeap = ByteBuffer.allocateDirect(16 * 1 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
}
@Override
public void add(final SceneLightInstance lightInstance) {
// TODO redesign to avoid cast
final LightInstance mdxLight = (LightInstance) lightInstance;
this.lights.add(mdxLight);
}
@Override
public void remove(final SceneLightInstance lightInstance) {
// TODO redesign to avoid cast
final LightInstance mdxLight = (LightInstance) lightInstance;
this.lights.remove(mdxLight);
}
@Override
public void update() {
final int numberOfLights = this.lights.size() + 1;
final int bytesNeeded = numberOfLights * 4 * 16;
if (bytesNeeded > (this.lightDataCopyHeap.capacity() * 4)) {
this.lightDataCopyHeap = ByteBuffer.allocateDirect(bytesNeeded).order(ByteOrder.nativeOrder())
.asFloatBuffer();
this.unitLightsTexture.reserve(4, numberOfLights);
}
this.unitLightCount = 0;
this.lightDataCopyHeap.clear();
int offset = 0;
if (this.viewer.dncTarget != null) {
if (!this.viewer.dncTarget.lights.isEmpty()) {
this.viewer.dncTarget.lights.get(0).bind(0, this.lightDataCopyHeap);
offset += 16;
this.unitLightCount++;
}
}
for (final LightInstance light : this.lights) {
light.bind(offset, this.lightDataCopyHeap);
offset += 16;
this.unitLightCount++;
}
this.lightDataCopyHeap.limit(offset);
this.unitLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 4, this.unitLightCount);
}
@Override
public DataTexture getUnitLightsTexture() {
return this.unitLightsTexture;
}
@Override
public int getUnitLightCount() {
return this.unitLightCount;
}
@Override
public DataTexture getTerrainLightsTexture() {
return null;
}
@Override
public int getTerrainLightCount() {
return 0;
}
}

View File

@ -0,0 +1,112 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import com.etheller.warsmash.viewer5.SceneLightInstance;
import com.etheller.warsmash.viewer5.SceneLightManager;
import com.etheller.warsmash.viewer5.gl.DataTexture;
import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance;
public class W3xSceneWorldLightManager implements SceneLightManager, W3xSceneLightManager {
public final List<LightInstance> lights;
private FloatBuffer lightDataCopyHeap;
private final DataTexture unitLightsTexture;
private final DataTexture terrainLightsTexture;
private final War3MapViewer viewer;
private int terrainLightCount;
private int unitLightCount;
public W3xSceneWorldLightManager(final War3MapViewer viewer) {
this.viewer = viewer;
this.lights = new ArrayList<>();
this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1);
this.terrainLightsTexture = new DataTexture(viewer.gl, 4, 4, 1);
this.lightDataCopyHeap = ByteBuffer.allocateDirect(16 * 1 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
}
@Override
public void add(final SceneLightInstance lightInstance) {
// TODO redesign to avoid cast
final LightInstance mdxLight = (LightInstance) lightInstance;
this.lights.add(mdxLight);
}
@Override
public void remove(final SceneLightInstance lightInstance) {
// TODO redesign to avoid cast
final LightInstance mdxLight = (LightInstance) lightInstance;
this.lights.remove(mdxLight);
}
@Override
public void update() {
final int numberOfLights = this.lights.size() + 1;
final int bytesNeeded = numberOfLights * 4 * 16;
if (bytesNeeded > (this.lightDataCopyHeap.capacity() * 4)) {
this.lightDataCopyHeap = ByteBuffer.allocateDirect(bytesNeeded).order(ByteOrder.nativeOrder())
.asFloatBuffer();
this.unitLightsTexture.reserve(4, numberOfLights);
this.terrainLightsTexture.reserve(4, numberOfLights);
}
this.unitLightCount = 0;
this.lightDataCopyHeap.clear();
int offset = 0;
if (this.viewer.dncUnit != null) {
if (!this.viewer.dncUnit.lights.isEmpty()) {
this.viewer.dncUnit.lights.get(0).bind(0, this.lightDataCopyHeap);
offset += 16;
this.unitLightCount++;
}
}
for (final LightInstance light : this.lights) {
light.bind(offset, this.lightDataCopyHeap);
offset += 16;
this.unitLightCount++;
}
this.lightDataCopyHeap.limit(offset);
this.unitLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 4, this.unitLightCount);
this.terrainLightCount = 0;
this.lightDataCopyHeap.clear();
offset = 0;
if (this.viewer.dncTerrain != null) {
if (!this.viewer.dncTerrain.lights.isEmpty()) {
this.viewer.dncTerrain.lights.get(0).bind(0, this.lightDataCopyHeap);
offset += 16;
this.terrainLightCount++;
}
}
for (final LightInstance light : this.lights) {
light.bind(offset, this.lightDataCopyHeap);
offset += 16;
this.terrainLightCount++;
}
this.lightDataCopyHeap.limit(offset);
this.terrainLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 4, this.terrainLightCount);
}
@Override
public DataTexture getUnitLightsTexture() {
return this.unitLightsTexture;
}
@Override
public int getUnitLightCount() {
return this.unitLightCount;
}
@Override
public DataTexture getTerrainLightsTexture() {
return this.terrainLightsTexture;
}
@Override
public int getTerrainLightCount() {
return this.terrainLightCount;
}
}

View File

@ -1,5 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import com.etheller.warsmash.viewer5.Shaders;
public class W3xShaders {
public static final class UberSplat {
private UberSplat() {
@ -12,12 +14,15 @@ public class W3xShaders {
" uniform vec2 u_size;\r\n" + //
" uniform vec2 u_shadowPixel;\r\n" + //
" uniform vec2 u_centerOffset;\r\n" + //
" uniform sampler2D lightTexture;\r\n" + //
" uniform float lightCount;\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" + //
" varying float a_positionHeight;\r\n" + //
" varying vec3 shadeColor;\r\n" + //
" const float normalDist = 0.25;\r\n" + //
" void main() {\r\n" + //
" vec2 halfPixel = u_pixel * 0.5;\r\n" + //
@ -34,8 +39,11 @@ public class W3xShaders {
" 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" + //
" vec3 myposition = vec3(a_position.xy, height * 128.0 + a_position.z);\r\n" + //
" gl_Position = u_mvp * vec4(myposition.xyz, 1.0);\r\n" + //
" a_positionHeight = a_position.z;\r\n" + //
Shaders.lightSystem("v_normal", "myposition", "lightTexture", "lightCount", true) + "\r\n" + //
" shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + //
" }\r\n" + //
" ";
@ -43,10 +51,12 @@ public class W3xShaders {
" uniform sampler2D u_texture;\r\n" + //
" uniform sampler2D u_shadowMap;\r\n" + //
" uniform vec4 u_color;\r\n" + //
" uniform bool u_show_lighting;\r\n" + //
" varying vec2 v_uv;\r\n" + //
" varying vec2 v_suv;\r\n" + //
" varying vec3 v_normal;\r\n" + //
" varying float a_positionHeight;\r\n" + //
" varying vec3 shadeColor;\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" + //
@ -59,6 +69,9 @@ public class W3xShaders {
" if (a_positionHeight <= 4.0) {;\r\n" + //
" color.xyz *= 1.0 - shadow;\r\n" + //
" };\r\n" + //
" if (u_show_lighting) {;\r\n" + //
" color.xyz *= shadeColor;\r\n" + //
" };\r\n" + //
" gl_FragColor = color;\r\n" + //
" }\r\n" + //
" ";

View File

@ -33,6 +33,7 @@ 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.fdf.GameUI;
import com.etheller.warsmash.parsers.w3x.War3Map;
import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo;
import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData;
@ -147,7 +148,7 @@ public class War3MapViewer extends ModelViewer {
public Terrain terrain;
public int renderPathing = 0;
public int renderLighting = 0;
public int renderLighting = 1;
public List<SplatModel> selModels = new ArrayList<>();
public List<RenderUnit> selected = new ArrayList<>();
@ -158,6 +159,7 @@ public class War3MapViewer extends ModelViewer {
private MdxComplexInstance confirmationInstance;
public MdxComplexInstance dncUnit;
public MdxComplexInstance dncTerrain;
public MdxComplexInstance dncTarget;
public CSimulation simulation;
private float updateTime;
@ -172,6 +174,7 @@ public class War3MapViewer extends ModelViewer {
private final List<SelectionCircleSize> selectionCircleSizes = new ArrayList<>();
private final Map<CUnit, RenderUnit> unitToRenderPeer = new HashMap<>();
private GameUI gameUI;
public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) {
super(dataSource, canvas);
@ -260,6 +263,11 @@ public class War3MapViewer extends ModelViewer {
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) {
this.miscData.readTXT(miscDataTxtStream, true);
}
if (this.dataSource.has("war3mapMisc.txt")) {
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) {
this.miscData.readTXT(miscDataTxtStream, true);
}
}
this.unitGlobalStrings = new DataTable(worldEditStrings);
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) {
this.unitGlobalStrings.readTXT(miscDataTxtStream, true);
@ -339,8 +347,8 @@ public class War3MapViewer extends ModelViewer {
throw new RuntimeException(e);
}
setDataSource(tilesetSource);
final WorldEditStrings worldEditStrings = new WorldEditStrings(this.dataSource);
loadSLKs(worldEditStrings);
this.worldEditStrings = new WorldEditStrings(this.dataSource);
loadSLKs(this.worldEditStrings);
this.solverParams.tileset = Character.toLowerCase(tileset);
@ -351,8 +359,8 @@ public class War3MapViewer extends ModelViewer {
final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource);
this.worldEditData = standardObjectData.getWorldEditData();
this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, worldEditStrings,
this, this.worldEditData);
this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource,
this.worldEditStrings, this, this.worldEditData);
final float[] centerOffset = terrainData.getCenterOffset();
final int[] mapSize = terrainData.getMapSize();
@ -595,7 +603,13 @@ public class War3MapViewer extends ModelViewer {
model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams);
}
this.doodads.add(new Doodad(this, model, row, doodad, type, maxPitch, maxRoll));
if (type == WorldEditorDataType.DESTRUCTIBLES) {
this.doodads
.add(new Destructable(this, model, row, doodad, type, maxPitch, maxRoll, doodad.getLife()));
}
else {
this.doodads.add(new Doodad(this, model, row, doodad, type, maxPitch, maxRoll));
}
}
}
@ -763,16 +777,18 @@ public class War3MapViewer extends ModelViewer {
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);
if (this.mapMpq.has(texture)) {
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 });
unitShadowSplat = this.terrain.splats.get(texture);
}
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 });
unitShadowSplat = this.terrain.splats.get(texture);
}
final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0);
@ -890,8 +906,11 @@ public class War3MapViewer extends ModelViewer {
final ModelInstance instance = item.instance;
if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) {
final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance;
if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) {
SequenceUtils.randomStandSequence(mdxComplexInstance);
if ((mdxComplexInstance.sequence == -1)
|| (mdxComplexInstance.sequenceEnded && (((MdxModel) mdxComplexInstance.model).sequences
.get(mdxComplexInstance.sequence).getFlags() == 0))) {
SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY,
true);
}
}
}
@ -904,8 +923,13 @@ public class War3MapViewer extends ModelViewer {
}
this.dncTerrain.setFrameByRatio(
this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours());
this.dncTerrain.update(rawDeltaTime, null);
this.dncUnit.setFrameByRatio(
this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours());
this.dncUnit.update(rawDeltaTime, null);
this.dncTarget.setFrameByRatio(
this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours());
this.dncTarget.update(rawDeltaTime, null);
}
}
@ -995,7 +1019,7 @@ public class War3MapViewer extends ModelViewer {
final String path = entry.getKey();
final Splat locations = entry.getValue();
final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null),
locations.locations, this.terrain.centerOffset, locations.unitMapping);
locations.locations, this.terrain.centerOffset, locations.unitMapping, true);
model.color[0] = 0;
model.color[1] = 1;
model.color[2] = 0;
@ -1037,7 +1061,8 @@ public class War3MapViewer extends ModelViewer {
for (final RenderUnit unit : this.units) {
final MdxComplexInstance instance = unit.instance;
if (instance.isVisible(this.worldScene.camera)
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap,
unit.getSimulationUnit().getUnitType().isBuilding())
&& !unit.getSimulationUnit().isDead()) {
entity = unit;
}
@ -1075,8 +1100,8 @@ public class War3MapViewer extends ModelViewer {
RenderUnit entity = null;
for (final RenderUnit unit : this.units) {
final MdxComplexInstance instance = unit.instance;
if (instance.isVisible(this.worldScene.camera)
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) {
if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap,
intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding())) {
entity = unit;
}
}
@ -1149,6 +1174,7 @@ public class War3MapViewer extends ModelViewer {
private static final int MAXIMUM_ACCEPTED = 1 << 30;
private float selectionCircleScaleFactor;
private DataTable worldEditData;
private WorldEditStrings worldEditStrings;
/**
* Returns a power of two size for the given target capacity.
@ -1255,6 +1281,12 @@ public class War3MapViewer extends ModelViewer {
this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance();
this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
this.dncUnit.setSequence(0);
final MdxModel targetDNCModel = (MdxModel) load(
mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT,
null);
this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance();
this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
this.dncTarget.setSequence(0);
}
private static String mdx(String mdxPath) {
@ -1268,7 +1300,27 @@ public class War3MapViewer extends ModelViewer {
}
@Override
public SceneLightManager createLightManager() {
return new W3xSceneLightManager(this);
public SceneLightManager createLightManager(final boolean simple) {
if (simple) {
return new W3xScenePortraitLightManager(this);
}
else {
return new W3xSceneWorldLightManager(this);
}
}
public WorldEditStrings getWorldEditStrings() {
return this.worldEditStrings;
}
public void setGameUI(final GameUI gameUI) {
this.gameUI = gameUI;
for (final RenderUnit unit : this.units) {
unit.initAbilityUI(this);
}
}
public GameUI getGameUI() {
return this.gameUI;
}
}

View File

@ -87,18 +87,18 @@ public class CliffMesh {
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.uvBuffer);
this.gl.glVertexAttribPointer(1, 2, 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.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.instanceBuffer);
this.gl.glVertexAttribPointer(2, 4, GL20.GL_FLOAT, false, 0, 0);
this.gl.glVertexAttribDivisor(2, 1);
this.gl.glVertexAttribPointer(3, 4, GL20.GL_FLOAT, false, 0, 0);
this.gl.glVertexAttribDivisor(3, 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(2, 0); // ToDo use vao
this.gl.glVertexAttribDivisor(3, 0); // ToDo use vao
this.renderJobs.clear();
}

View File

@ -10,6 +10,7 @@ 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.handlers.tga.TgaFile;
public class GroundTexture {
public int id;
@ -19,43 +20,61 @@ public class GroundTexture {
public GroundTexture(final String path, final DataSource dataSource, final GL30 gl) throws IOException {
if (path.toLowerCase().endsWith(".blp")) {
try (InputStream stream = dataSource.getResourceAsStream(path)) {
final BufferedImage image = ImageIO.read(stream);
if (image == null) {
throw new IllegalStateException("Missing ground texture: " + path);
}
final Buffer buffer = ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image));
final int width = image.getWidth();
final int height = image.getHeight();
this.tileSize = (int) (height * 0.25);
this.extended = (width > height);
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, 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);
gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, width);
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
buffer.position(((y * this.tileSize * width) + (x * this.tileSize)) * 4);
gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x, this.tileSize, this.tileSize,
1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer);
if (this.extended) {
buffer.position(((y * this.tileSize * width) + ((x + 4) * this.tileSize)) * 4);
gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x + 16, this.tileSize,
this.tileSize, 1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer);
if (stream == null) {
final String tgaPath = path.substring(0, path.length() - 4) + ".tga";
try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) {
if (tgaStream != null) {
final BufferedImage tgaData = TgaFile.readTGA(tgaPath, tgaStream);
loadImage(path, gl, tgaData);
}
else {
throw new IllegalStateException("Missing ground texture: " + path);
}
}
}
gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, 0);
gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY);
else {
final BufferedImage image = ImageIO.read(stream);
loadImage(path, gl, image);
}
}
}
}
private void loadImage(final String path, final GL30 gl, final BufferedImage image) {
if (image == null) {
throw new IllegalStateException("Missing ground texture: " + path);
}
final Buffer buffer = ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image));
final int width = image.getWidth();
final int height = image.getHeight();
this.tileSize = (int) (height * 0.25);
this.extended = (width > height);
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, 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);
gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, width);
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
buffer.position(((y * this.tileSize * width) + (x * this.tileSize)) * 4);
gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x, this.tileSize, this.tileSize, 1,
GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer);
if (this.extended) {
buffer.position(((y * this.tileSize * width) + ((x + 4) * this.tileSize)) * 4);
gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x + 16, this.tileSize,
this.tileSize, 1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer);
}
}
}
gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, 0);
gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY);
}
}

View File

@ -41,12 +41,15 @@ import com.etheller.warsmash.viewer5.Camera;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.RawOpenGLTextureResource;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.gl.DataTexture;
import com.etheller.warsmash.viewer5.gl.Extensions;
import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover;
import com.etheller.warsmash.viewer5.handlers.w3x.Variations;
import com.etheller.warsmash.viewer5.handlers.w3x.W3xSceneLightManager;
import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
@ -174,9 +177,16 @@ public class Terrain {
final char tileset = w3eFile.getTileset();
final Element waterInfo = this.waterTable.get(tileset + "Sha");
this.waterHeightOffset = waterInfo.getFieldFloatValue("height");
this.waterTextureCount = waterInfo.getFieldValue("numTex");
this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f;
if (waterInfo != null) {
this.waterHeightOffset = waterInfo.getFieldFloatValue("height");
this.waterTextureCount = waterInfo.getFieldValue("numTex");
this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f;
}
else {
this.waterHeightOffset = 0;
this.waterTextureCount = 0;
this.waterIncreasePerFrame = 0;
}
loadWaterColor(this.minShallowColor, "Smin", waterInfo);
loadWaterColor(this.maxShallowColor, "Smax", waterInfo);
@ -213,6 +223,9 @@ public class Terrain {
// Ground textures
for (final War3ID groundTile : w3eFile.getGroundTiles()) {
final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue());
if (terrainTileInfo == null) {
throw new RuntimeException("No terrain info for: " + 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));
@ -231,9 +244,25 @@ public class Terrain {
final String texDir = cliffInfo.getField("texDir");
final String texFile = cliffInfo.getField("texFile");
try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) {
final BufferedImage image = ImageIO.read(imageStream);
if (image == null) {
throw new IllegalStateException("Missing cliff texture: " + texDir + "\\" + texFile + texturesExt);
final BufferedImage image;
if (imageStream == null) {
final String tgaPath = texDir + "\\" + texFile + ".tga";
try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) {
if (tgaStream != null) {
image = TgaFile.readTGA(tgaPath, tgaStream);
}
else {
throw new IllegalStateException(
"Missing cliff texture: " + texDir + "\\" + texFile + texturesExt);
}
}
}
else {
image = ImageIO.read(imageStream);
if (image == null) {
throw new IllegalStateException(
"Missing cliff texture: " + texDir + "\\" + texFile + texturesExt);
}
}
this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(),
ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)),
@ -796,7 +825,7 @@ public class Terrain {
private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) {
for (int i = 0; i < colorTags.length; i++) {
final String colorTag = colorTags[i];
out[i] = waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f;
out[i] = waterInfo == null ? 0.0f : waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f;
}
}
@ -856,6 +885,13 @@ public class Terrain {
gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]);
gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]);
final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager();
final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture();
unitLightsTexture.bind(21);
gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21);
gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f);
gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false,
dynamicShadowManager.getDepthBiasMVP().val, 0);
@ -949,6 +985,13 @@ public class Terrain {
gl.glActiveTexture(GL30.GL_TEXTURE2);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap);
final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager();
final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture();
terrainLightsTexture.bind(21);
gl.glUniform1i(shader.getUniformLocation("lightTexture"), 21);
gl.glUniform1f(shader.getUniformLocation("lightCount"), terrainLightsTexture.getHeight() - 0.5f);
// Render the cliffs
for (final SplatModel splat : this.uberSplatModels) {
splat.render(gl, shader);
@ -976,6 +1019,13 @@ public class Terrain {
gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]);
gl.glUniform4fv(9, 1, this.shaderMapBounds, 0);
final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager();
final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture();
unitLightsTexture.bind(21);
gl.glUniform1i(this.waterShader.getUniformLocation("lightTexture"), 21);
gl.glUniform1f(this.waterShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f);
gl.glActiveTexture(GL30.GL_TEXTURE0);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight);
gl.glActiveTexture(GL30.GL_TEXTURE1);
@ -1025,6 +1075,13 @@ public class Terrain {
gl.glUniform1i(1, this.viewer.renderPathing);
gl.glUniform1i(2, this.viewer.renderLighting);
final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager();
final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture();
unitLightsTexture.bind(21);
gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21);
gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f);
this.cliffShader.setUniformi("shadowMap", 2);
gl.glActiveTexture(GL30.GL_TEXTURE2);
gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap);
@ -1266,7 +1323,7 @@ public class Terrain {
final SplatModel splatModel = new SplatModel(Gdx.gl30,
(Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset,
splat.unitMapping);
splat.unitMapping, false);
splatModel.color[3] = splat.opacity;
this.uberSplatModels.add(splatModel);
}

View File

@ -1,5 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.w3x.environment;
import com.etheller.warsmash.viewer5.Shaders;
/**
* Mostly copied from HiveWE!
*/
@ -12,8 +14,8 @@ 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 = 2) in vec4 vOffset;\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" + //
@ -21,12 +23,15 @@ public class TerrainShaders {
"layout (binding = 3) uniform sampler2D shadowMap;\r\n" + //
"layout (location = 3) uniform float centerOffsetX;\r\n" + //
"layout (location = 4) uniform float centerOffsetY;\r\n" + //
"layout (location = 5) uniform sampler2D lightTexture;\r\n" + //
"layout (location = 6) uniform float lightCount;\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" + //
"layout (location = 3) out vec3 position;\r\n" + //
"layout (location = 4) out vec2 v_suv;\r\n" + //
"layout (location = 5) out vec3 shadeColor;\r\n" + //
"\r\n" + //
"void main() {\r\n" + //
" pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + //
@ -52,9 +57,11 @@ public class TerrainShaders {
" 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" + //
" vec3 terrain_normal = normalize(vNormal);//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + //
"\r\n" + //
" Normal = terrain_normal;\r\n" + //
Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightCount", true) + "\r\n" + //
" shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + //
"}";
public static final String frag = "#version 450 core\r\n" + //
@ -70,6 +77,7 @@ public class TerrainShaders {
"layout (location = 1) in vec3 Normal;\r\n" + //
"layout (location = 2) in vec2 pathing_map_uv;\r\n" + //
"layout (location = 4) in vec2 v_suv;\r\n" + //
"layout (location = 5) in vec3 shadeColor;\r\n" + //
"\r\n" + //
"out vec4 color;\r\n" + //
"\r\n" + //
@ -79,10 +87,7 @@ public class TerrainShaders {
" float shadow = texture2D(shadowMap, v_suv).r;\r\n" + //
" color.rgb *= (1.0 - shadow);\r\n" + //
" if (show_lighting) {\r\n" + //
" vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + //
" light_direction = normalize(light_direction);\r\n" + //
"\r\n" + //
" color.rgb *= clamp(dot(Normal, light_direction) + 0.45, 0, 1);\r\n" + //
" color.rgb *= shadeColor;\r\n" + //
" }\r\n" + //
"\r\n" + //
" uvec4 byte = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0);\r\n" + //
@ -128,14 +133,16 @@ public class TerrainShaders {
"layout (binding = 2) uniform usampler2D terrain_texture_list;\r\n" + //
"layout (location = 4) uniform float centerOffsetX;\r\n" + //
"layout (location = 5) uniform float centerOffsetY;\r\n" + //
"layout (location = 7) uniform sampler2D lightTexture;\r\n" + //
"layout (location = 8) uniform float lightCount;\r\n" + //
"\r\n" + //
"layout (location = 0) out vec2 UV;\r\n" + //
"layout (location = 1) out flat uvec4 texture_indices;\r\n" + //
"layout (location = 2) out vec2 pathing_map_uv;\r\n" + //
"layout (location = 3) out vec3 normal;\r\n" + //
"layout (location = 4) out vec3 position;\r\n" + //
"layout (location = 5) out vec3 ShadowCoord;\r\n" + //
"layout (location = 6) out vec2 v_suv;\r\n" + //
"layout (location = 7) out vec3 shadeColor;\r\n" + //
"\r\n" + //
"void main() { \r\n" + //
" ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + //
@ -149,15 +156,16 @@ public class TerrainShaders {
" float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + //
" float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + //
" float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + //
" normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + //
" vec3 normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + //
"\r\n" + //
" UV = vec2(vPosition.x, 1 - vPosition.y);\r\n" + //
" texture_indices = texelFetch(terrain_texture_list, pos, 0);\r\n" + //
" pathing_map_uv = (vPosition + pos) * 4; \r\n" + //
"\r\n" + //
" // Cliff culling\r\n" + //
" position = vec3((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, height.r*128.0);\r\n"
" vec3 positionWorld = vec3((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, height.r*128.0);\r\n"
+ //
" position = positionWorld;\r\n" + //
" gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n"
+ //
" ShadowCoord = (((texture_indices.a & 32768) == 0) ? DepthBiasMVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0)).xyz;\r\n"
@ -165,6 +173,8 @@ public class TerrainShaders {
" v_suv = (vPosition + pos) / size;\r\n" + //
" position.x = (position.x - centerOffsetX) / (size.x * 128.0);\r\n" + //
" position.y = (position.y - centerOffsetY) / (size.y * 128.0);\r\n" + //
Shaders.lightSystem("normal", "positionWorld", "lightTexture", "lightCount", true) + "\r\n" + //
" shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + //
"}";
public static final String frag = "#version 450 core\r\n" + //
@ -197,10 +207,10 @@ public class TerrainShaders {
"layout (location = 0) in vec2 UV;\r\n" + //
"layout (location = 1) in flat uvec4 texture_indices;\r\n" + //
"layout (location = 2) in vec2 pathing_map_uv;\r\n" + //
"layout (location = 3) in vec3 normal;\r\n" + //
"layout (location = 4) in vec3 position;\r\n" + //
"layout (location = 5) in vec3 ShadowCoord;\r\n" + //
"layout (location = 6) in vec2 v_suv;\r\n" + //
"layout (location = 7) in vec3 shadeColor;\r\n" + //
"\r\n" + //
"layout (location = 0) out vec4 color;\r\n" + //
// "layout (location = 1) out vec4 position;\r\n" + //
@ -263,14 +273,12 @@ public class TerrainShaders {
// " if ( texture2D(shadowMap, ShadowCoord.xy).z > ShadowCoord.z ) {\r\n" + //
// " visibility = 0.5;\r\n" + //
// " }\r\n" + //
" color = vec4(color.xyz * (1.0 - shadow), 1.0);\r\n" + //
"\r\n" + //
// " if (show_lighting) {\r\n" + //
// " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + //
// " light_direction = normalize(light_direction);\r\n" + //
// "\r\n" + //
// " color.rgb *= clamp(dot(normal, light_direction) + 0.45, 0, 1);\r\n" + //
// " }\r\n" + //
" if (show_lighting) {\r\n" + //
" color = vec4(color.xyz * (1.0 - shadow) * shadeColor, 1.0);\r\n" + //
" } else {\r\n" + //
" color = vec4(color.xyz * (1.0 - shadow), 1.0);\r\n" + //
" }\r\n" + //
// "\r\n" + //
// " if (show_pathing_map) {\r\n" + //
// " uint byte_static = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0).r;\r\n" + //
@ -415,10 +423,13 @@ public class TerrainShaders {
"layout (location = 3) uniform vec4 deep_color_min;\r\n" + //
"layout (location = 4) uniform vec4 deep_color_max;\r\n" + //
"layout (location = 5) uniform float water_offset;\r\n" + //
"layout (location = 10) uniform sampler2D lightTexture;\r\n" + //
"layout (location = 11) uniform float lightCount;\r\n" + //
"\r\n" + //
"out vec2 UV;\r\n" + //
"out vec4 Color;\r\n" + //
"out vec2 position;\r\n" + //
"out vec3 shadeColor;\r\n" + //
"\r\n" + //
"const float min_depth = 10.f / 128;\r\n" + //
"const float deeplevel = 64.f / 128;\r\n" + //
@ -437,8 +448,9 @@ public class TerrainShaders {
"\r\n" + //
" position = vec2((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY);\r\n"
+ //
" gl_Position = is_water ? MVP * vec4(position.xy, water_height*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n"
+ //
" vec4 myposition = vec4(position.xy, water_height*128.0, 1);\r\n" + //
" vec3 Normal = vec3(0,0,1);\r\n" + //
" gl_Position = is_water ? MVP * myposition : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + //
"\r\n" + //
" UV = vec2((vPosition.x + pos.x%2)/2.0, (vPosition.y + pos.y%2)/2.0);\r\n" + //
"\r\n" + //
@ -451,6 +463,8 @@ public class TerrainShaders {
" value = clamp(value - deeplevel, 0.f, maxdepth - deeplevel) / (maxdepth - deeplevel);\r\n" + //
" Color = deep_color_min * (1.f - value) + deep_color_max * value;\r\n" + //
" }\r\n" + //
Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightCount", true) + "\r\n" + //
" shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + //
" }";
public static final String frag = "#version 450 core\r\n" + //
@ -465,13 +479,14 @@ public class TerrainShaders {
"in vec2 UV;\r\n" + //
"in vec4 Color;\r\n" + //
"in vec2 position;\r\n" + //
"in vec3 shadeColor;\r\n" + //
"\r\n" + //
"out vec4 outColor;\r\n" + //
"\r\n" + //
"void main() {\r\n" + //
" vec2 d2 = min(position - mapBounds.xy, mapBounds.zw - position);\r\n" + //
" float d1 = clamp(min(d2.x, d2.y) / 64.0 + 1.0, 0.0, 1.0) * 0.8 + 0.2;;\r\n" + //
" outColor = texture(water_textures, vec3(UV, current_texture)) * vec4(Color.rgb * d1, Color.a);\r\n"
" outColor = texture(water_textures, vec3(UV, current_texture)) * vec4(Color.rgb * d1 * shadeColor, Color.a);\r\n"
+ //
"}";
}

View File

@ -8,7 +8,9 @@ import java.util.Queue;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.parsers.fdf.GameUI;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.RenderMathUtils;
@ -34,6 +36,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityP
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop;
public class RenderUnit {
private static final War3ID ABILITY_MOVE = War3ID.fromString("Amov");
private static final War3ID ABILITY_STOP = War3ID.fromString("Astp");
private static final War3ID ABILITY_HOLD_POSITION = War3ID.fromString("Ahol");
private static final War3ID ABILITY_PATROL = War3ID.fromString("Apat");
private static final War3ID ABILITY_ATTACK = War3ID.fromString("Aatk");
private static final Quaternion tempQuat = new Quaternion();
private static final War3ID RED = War3ID.fromString("uclr");
private static final War3ID GREEN = War3ID.fromString("uclg");
@ -133,33 +141,44 @@ public class RenderUnit {
this.row = row;
this.soundset = soundset;
for (final CAbility ability : simulationUnit.getAbilities()) {
}
public void initAbilityUI(final War3MapViewer map) {
for (final CAbility ability : this.simulationUnit.getAbilities()) {
if (ability instanceof CAbilityMove) {
final GameUI gameUI = map.getGameUI();
final Element moveCommand = gameUI.getSkinData().get("CmdMove");
final String moveCommandArt = gameUI.getSkinField(moveCommand.getField("Art"));
this.commandCardIcons.add(new CommandCardIcon(0, 0,
ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNMove.blp"),
ability.getOrderId()));
ImageUtils.getBLPTexture(map.dataSource, moveCommandArt), ability.getOrderId()));
}
else if (ability instanceof CAbilityAttack) {
final GameUI gameUI = map.getGameUI();
final Element command = gameUI.getSkinData().get("CmdAttack");
final String commandArt = gameUI.getSkinField(command.getField("Art"));
this.commandCardIcons.add(new CommandCardIcon(3, 0,
ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNAttack.blp"),
ability.getOrderId()));
ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId()));
}
else if (ability instanceof CAbilityHoldPosition) {
this.commandCardIcons
.add(new CommandCardIcon(2, 0,
ImageUtils.getBLPTexture(map.dataSource,
"ReplaceableTextures\\CommandButtons\\BTNHoldPosition.blp"),
ability.getOrderId()));
final GameUI gameUI = map.getGameUI();
final Element command = gameUI.getSkinData().get("CmdHoldPos");
final String commandArt = gameUI.getSkinField(command.getField("Art"));
this.commandCardIcons.add(new CommandCardIcon(2, 0,
ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId()));
}
else if (ability instanceof CAbilityPatrol) {
final GameUI gameUI = map.getGameUI();
final Element command = gameUI.getSkinData().get("CmdPatrol");
final String commandArt = gameUI.getSkinField(command.getField("Art"));
this.commandCardIcons.add(new CommandCardIcon(0, 1,
ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNPatrol.blp"),
ability.getOrderId()));
ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId()));
}
else if (ability instanceof CAbilityStop) {
final GameUI gameUI = map.getGameUI();
final Element command = gameUI.getSkinData().get("CmdStop");
final String commandArt = gameUI.getSkinField(command.getField("Art"));
this.commandCardIcons.add(new CommandCardIcon(1, 0,
ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNStop.blp"),
ability.getOrderId()));
ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId()));
}
}
}
@ -225,7 +244,7 @@ public class RenderUnit {
}
if (boneCorpse && !this.boneCorpse) {
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE,
map.simulation.getGameplayConstants().getBoneDecayTime(), true);
this.simulationUnit.getEndingDecayTime(map.simulation), true);
}
else if (corpse && !this.corpse) {
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH,

View File

@ -23,6 +23,7 @@ public class CGameplayConstants {
private final float duskTimeGameHours;
private final float gameDayHours;
private final float gameDayLength;
private final float structureDecayTime;
public CGameplayConstants(final DataTable parsedDataTable) {
final Element miscData = parsedDataTable.get("Misc");
@ -31,6 +32,7 @@ public class CGameplayConstants {
this.maxCollisionRadius = miscData.getFieldFloatValue("MaxCollisionRadius");
this.decayTime = miscData.getFieldFloatValue("DecayTime");
this.boneDecayTime = miscData.getFieldFloatValue("BoneDecayTime");
this.structureDecayTime = miscData.getFieldFloatValue("StructureDecayTime");
this.bulletDeathTime = miscData.getFieldFloatValue("BulletDeathTime");
this.closeEnoughRange = miscData.getFieldFloatValue("CloseEnoughRange");
@ -105,4 +107,8 @@ public class CGameplayConstants {
public float getDuskTimeGameHours() {
return this.duskTimeGameHours;
}
public float getStructureDecayTime() {
return this.structureDecayTime;
}
}

View File

@ -62,7 +62,7 @@ public class CSimulation {
for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) {
if (i < playerInfos.size()) {
final Player playerInfo = playerInfos.get(i);
this.players.add(new CPlayer(playerInfo.getId().getValue(), CMapControl.values()[playerInfo.getType()],
this.players.add(new CPlayer(playerInfo.getId(), CMapControl.values()[playerInfo.getType()],
playerInfo.getName(), CRace.parseRace(playerInfo.getRace()), playerInfo.getStartLocation()));
}
else {

View File

@ -16,10 +16,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer;
public class CUnit extends CWidget {
private static final Rectangle tempRect = new Rectangle();
private War3ID typeId;
private float facing; // degrees
private float mana;
@ -51,6 +54,8 @@ public class CUnit extends CWidget {
// questionable -- it already was -- but I meant for those to inform us
// which fields shouldn't be persisted if we do game state save later
private transient CUnitStateNotifier stateNotifier = new CUnitStateNotifier();
private final float acquisitionRange;
private transient static AutoAttackTargetFinderEnum autoAttackTargetFinderEnum = new AutoAttackTargetFinderEnum();
public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life,
final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana,
@ -67,6 +72,7 @@ public class CUnit extends CWidget {
this.flyHeight = unitType.getDefaultFlyingHeight();
this.unitType = unitType;
this.classifications.addAll(unitType.getClassifications());
this.acquisitionRange = unitType.getDefaultAcquisitionRange();
}
public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) {
@ -172,9 +178,8 @@ public class CUnit extends CWidget {
this.deathTurnTick = gameTurnTick;
}
}
else if (game
.getGameTurnTick() > (this.deathTurnTick + (int) (game.getGameplayConstants().getBoneDecayTime()
/ WarsmashConstants.SIMULATION_STEP_TIME))) {
else if (game.getGameTurnTick() > (this.deathTurnTick
+ (int) (getEndingDecayTime(game) / WarsmashConstants.SIMULATION_STEP_TIME))) {
return true;
}
}
@ -189,9 +194,33 @@ public class CUnit extends CWidget {
this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true);
}
}
else {
// check to auto acquire targets
if (!this.unitType.getAttacks().isEmpty()) {
if (this.collisionRectangle != null) {
tempRect.set(this.collisionRectangle);
}
else {
tempRect.set(this.getX(), this.getY(), 0, 0);
}
final float halfSize = this.acquisitionRange;
tempRect.x -= halfSize;
tempRect.y -= halfSize;
tempRect.width += halfSize * 2;
tempRect.height += halfSize * 2;
game.getWorldCollision().enumUnitsInRect(tempRect, autoAttackTargetFinderEnum.reset(game, this));
}
}
return false;
}
public float getEndingDecayTime(final CSimulation game) {
if (this.unitType.isBuilding()) {
return game.getGameplayConstants().getStructureDecayTime();
}
return game.getGameplayConstants().getBoneDecayTime();
}
public void order(final COrder order, final boolean queue) {
if (isDead()) {
return;
@ -349,8 +378,23 @@ public class CUnit extends CWidget {
this.life -= trueDamage;
simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType());
this.stateNotifier.lifeChanged();
if (!wasDead && isDead() && !this.unitType.isBuilding()) {
kill(simulation);
if (isDead()) {
if (!wasDead && !this.unitType.isBuilding()) {
kill(simulation);
}
}
else {
if (this.currentOrder == null) {
if (!simulation.getPlayer(getPlayerIndex()).hasAlliance(source.getPlayerIndex(),
CAllianceType.PASSIVE)) {
for (final CUnitAttack attack : this.unitType.getAttacks()) {
if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) {
this.order(new CAttackOrder(this, attack, source), false);
break;
}
}
}
}
}
}
@ -478,4 +522,35 @@ public class CUnit extends CWidget {
}
return false;
}
public boolean isMovementDisabled() {
return this.unitType.isBuilding();
}
private static final class AutoAttackTargetFinderEnum implements CUnitEnumFunction {
private CSimulation game;
private CUnit source;
private AutoAttackTargetFinderEnum reset(final CSimulation game, final CUnit source) {
this.game = game;
this.source = source;
return this;
}
@Override
public boolean call(final CUnit unit) {
if (!this.game.getPlayer(this.source.getPlayerIndex()).hasAlliance(unit.getPlayerIndex(),
CAllianceType.PASSIVE)) {
for (final CUnitAttack attack : this.source.unitType.getAttacks()) {
if (this.source.canReach(unit, this.source.acquisitionRange)
&& unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed())
&& (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) {
this.source.order(new CAttackOrder(this.source, attack, unit), false);
return true;
}
}
}
return false;
}
}
}

View File

@ -1,5 +1,11 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
public interface CUnitEnumFunction {
/**
* Operates on a unit, returning true if we should stop execution.
*
* @param unit
* @return
*/
boolean call(CUnit unit);
}

View File

@ -33,12 +33,15 @@ public class CUnitType {
// game data? can we store it in a cleaner way?
private final BufferedImage buildingPathingPixelMap;
private final EnumSet<CTargetType> targetedAs;
private final float defaultAcquisitionRange;
private final float minimumAttackRange;
public CUnitType(final String name, final boolean isBldg, final MovementType movementType,
final float defaultFlyingHeight, final float collisionSize,
final EnumSet<CUnitClassification> classifications, final List<CUnitAttack> attacks, final String armorType,
final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ,
final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet<CTargetType> targetedAs) {
final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet<CTargetType> targetedAs,
final float defaultAcquisitionRange, final float minimumAttackRange) {
this.name = name;
this.building = isBldg;
this.movementType = movementType;
@ -54,6 +57,8 @@ public class CUnitType {
this.buildingPathingPixelMap = buildingPathingPixelMap;
this.deathTime = deathTime;
this.targetedAs = targetedAs;
this.defaultAcquisitionRange = defaultAcquisitionRange;
this.minimumAttackRange = minimumAttackRange;
}
public String getName() {
@ -115,4 +120,12 @@ public class CUnitType {
public EnumSet<CTargetType> getTargetedAs() {
return this.targetedAs;
}
public float getDefaultAcquisitionRange() {
return this.defaultAcquisitionRange;
}
public float getMinimumAttackRange() {
return this.minimumAttackRange;
}
}

View File

@ -37,6 +37,10 @@ public enum CAttackType implements CodeKeyType {
}
public static CAttackType parseAttackType(final String attackTypeString) {
return valueOf(attackTypeString.toUpperCase());
final String upperCaseAttackType = attackTypeString.toUpperCase();
if ("SEIGE".equals(upperCaseAttackType)) {
return SIEGE;
}
return valueOf(upperCaseAttackType);
}
}

View File

@ -24,6 +24,10 @@ public enum CDefenseType implements CodeKeyType {
}
public static CDefenseType parseDefenseType(final String typeString) {
return valueOf(typeString.toUpperCase());
final String upperCaseTypeString = typeString.toUpperCase();
if (upperCaseTypeString.equals("HEAVY")) {
return LARGE;
}
return valueOf(upperCaseTypeString);
}
}

View File

@ -1,11 +1,27 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data;
import com.etheller.warsmash.units.manager.MutableObjectData;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.War3ID;
public class CAbilityData {
private static final War3ID ABILITY_ICON = War3ID.fromString("aart");
private final MutableObjectData abilityData;
public CAbilityData(final MutableObjectData abilityData) {
this.abilityData = abilityData;
}
public String getIconPath(final War3ID id, final int level) {
final MutableGameObject mutableGameObject = this.abilityData.get(id);
if (mutableGameObject == null) {
return ImageUtils.DEFAULT_ICON_PATH;
}
final String iconPath = mutableGameObject.getFieldAsString(ABILITY_ICON, level);
if ((iconPath == null) || "".equals(iconPath)) {
return ImageUtils.DEFAULT_ICON_PATH;
}
return iconPath;
}
}

View File

@ -103,6 +103,9 @@ public class CUnitData {
private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2");
private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w");
private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq");
private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn");
private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz");
private static final War3ID DEATH_TYPE = War3ID.fromString("udea");
@ -160,6 +163,8 @@ public class CUnitData {
final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0);
final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
final String unitName = unitType.getFieldAsString(NAME, 0);
final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0);
final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0);
final EnumSet<CTargetType> targetedAs = CTargetType
.parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0));
final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0);
@ -269,7 +274,7 @@ public class CUnitData {
final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0);
unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications,
attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime,
targetedAs);
targetedAs, acquisitionRange, minimumAttackRange);
this.unitIdToUnitType.put(typeId, unitTypeInstance);
}
return unitTypeInstance;

View File

@ -29,11 +29,16 @@ public class CAttackOrder implements COrder {
}
private void createMoveOrder(final CUnit unit, final CWidget target) {
if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) {
this.moveOrder = new CMoveOrder(unit, (CUnit) target);
if (!unit.isMovementDisabled()) { // TODO: Check mobility instead
if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) {
this.moveOrder = new CMoveOrder(unit, (CUnit) target);
}
else {
this.moveOrder = new CMoveOrder(unit, target.getX(), target.getY());
}
}
else {
this.moveOrder = new CMoveOrder(unit, target.getX(), target.getY());
this.moveOrder = null;
}
}
@ -51,7 +56,11 @@ public class CAttackOrder implements COrder {
*/)) {
range += this.unitAttack.getRangeMotionBuffer();
}
if (!this.unit.canReach(this.target, range)) {
if (!this.unit.canReach(this.target, range)
|| (this.unit.distance(this.target) < this.unit.getUnitType().getMinimumAttackRange())) {
if (this.moveOrder == null) {
return true;
}
if (this.moveOrder.update(simulation)) {
return true; // we just cant reach them
}
@ -61,46 +70,51 @@ public class CAttackOrder implements COrder {
return false;
}
this.wasInRange = true;
final float prevX = this.unit.getX();
final float prevY = this.unit.getY();
final float deltaY = this.target.getY() - prevY;
final float deltaX = this.target.getX() - prevX;
final double goalAngleRad = Math.atan2(deltaY, deltaX);
float goalAngle = (float) Math.toDegrees(goalAngleRad);
if (goalAngle < 0) {
goalAngle += 360;
}
float facing = this.unit.getFacing();
float delta = goalAngle - facing;
final float propulsionWindow = simulation.getGameplayConstants().getAttackHalfAngle();
final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId());
if (delta < -180) {
delta = 360 + delta;
}
if (delta > 180) {
delta = -360 + delta;
}
final float absDelta = Math.abs(delta);
if ((absDelta <= 1.0) && (absDelta != 0)) {
this.unit.setFacing(goalAngle);
}
else {
float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate);
if (absDelta < Math.abs(angleToAdd)) {
angleToAdd = delta;
if (!this.unit.isMovementDisabled()) {
final float prevX = this.unit.getX();
final float prevY = this.unit.getY();
final float deltaY = this.target.getY() - prevY;
final float deltaX = this.target.getX() - prevX;
final double goalAngleRad = Math.atan2(deltaY, deltaX);
float goalAngle = (float) Math.toDegrees(goalAngleRad);
if (goalAngle < 0) {
goalAngle += 360;
}
float facing = this.unit.getFacing();
float delta = goalAngle - facing;
final float propulsionWindow = simulation.getGameplayConstants().getAttackHalfAngle();
final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId());
if (delta < -180) {
delta = 360 + delta;
}
if (delta > 180) {
delta = -360 + delta;
}
final float absDelta = Math.abs(delta);
if ((absDelta <= 1.0) && (absDelta != 0)) {
this.unit.setFacing(goalAngle);
}
else {
float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate);
if (absDelta < Math.abs(angleToAdd)) {
angleToAdd = delta;
}
facing += angleToAdd;
this.unit.setFacing(facing);
}
if (absDelta < propulsionWindow) {
this.wasWithinPropWindow = true;
}
else {
// If this happens, the unit is facing the wrong way, and has to turn before
// moving.
this.wasWithinPropWindow = false;
}
facing += angleToAdd;
this.unit.setFacing(facing);
}
if (absDelta < propulsionWindow) {
this.wasWithinPropWindow = true;
}
else {
// If this happens, the unit is facing the wrong way, and has to turn before
// moving.
this.wasWithinPropWindow = false;
this.wasWithinPropWindow = true;
}
final int cooldownEndTime = this.unit.getCooldownEndTime();
@ -108,9 +122,22 @@ public class CAttackOrder implements COrder {
if (this.wasWithinPropWindow) {
if (this.damagePointLaunchTime != 0) {
if (currentTurnTick >= this.damagePointLaunchTime) {
final int minDamage = this.unitAttack.getMinDamage();
final int maxDamage = this.unitAttack.getMaxDamage();
final int damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage;
int minDamage = this.unitAttack.getMinDamage();
final int maxDamage = Math.max(0, this.unitAttack.getMaxDamage());
if (minDamage > maxDamage) {
minDamage = maxDamage;
}
final int damage;
if (maxDamage == 0) {
damage = 0;
}
else if (minDamage == maxDamage) {
damage = minDamage;
}
else {
damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage;
}
System.out.println(damage + " from " + minDamage + " to " + maxDamage);
this.unitAttack.launch(simulation, this.unit, this.target, damage);
this.damagePointLaunchTime = 0;
}

View File

@ -86,6 +86,7 @@ public class MeleeUI implements CUnitStateListener {
// TODO remove this & replace with FDF
private final Texture activeButtonTexture;
private UIFrame inventoryCover;
public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator,
final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) {
@ -110,6 +111,7 @@ public class MeleeUI implements CUnitStateListener {
// =================================
this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport,
this.fontGenerator, this.uiScene, this.war3MapViewer);
this.rootFrameListener.onCreate(this.rootFrame);
try {
this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc");
}
@ -142,11 +144,10 @@ public class MeleeUI implements CUnitStateListener {
this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0);
this.resourceBarLumberText.setText("150");
this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0);
this.resourceBarSupplyText.setText("153/100");
this.resourceBarSupplyText.setColor(Color.RED);
this.resourceBarSupplyText.setText("12/100");
this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0);
this.resourceBarUpkeepText.setText("High Upkeep");
this.resourceBarUpkeepText.setColor(Color.RED);
this.resourceBarUpkeepText.setText("No Upkeep");
this.resourceBarUpkeepText.setColor(Color.GREEN);
// Create the Time Indicator (clock)
this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0);
@ -195,6 +196,8 @@ public class MeleeUI implements CUnitStateListener {
this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0);
this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0);
this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0);
this.rootFrame.positionBounds(this.uiViewport);
selectUnit(null);
}

View File

@ -84,9 +84,9 @@ public class DesktopLauncher {
config.gles30ContextMajorVersion = 3;
config.gles30ContextMinorVersion = 3;
config.samples = 16;
config.vSyncEnabled = false;
config.foregroundFPS = 0;
config.backgroundFPS = 0;
// config.vSyncEnabled = false;
// config.foregroundFPS = 0;
// config.backgroundFPS = 0;
if ((arg.length > 0) && "-windowed".equals(arg[0])) {
config.fullscreen = false;
}

View File

@ -1,2 +1,3 @@
UI\FrameDef\SmashUI\TimeOfDayIndicator.fdf
UI\FrameDef\SmashUI\UnitPortrait.fdf
UI\FrameDef\SmashUI\InventoryCover.fdf

View File

@ -0,0 +1,14 @@
Frame "SIMPLEFRAME" "SmashConsoleInventoryCover" {
DecorateFileNames,
SetAllPoints,
// The top of the UI console
Texture "SmashConsoleInventoryCoverTexture" {
File "ConsoleInventoryCoverTexture",
Width 0.128,
Height 0.256,
AlphaMode "ALPHAKEY",
Anchor BOTTOMLEFT,0.472,0.0,
}
}