More work on handler code

This commit is contained in:
Retera 2020-01-13 01:28:46 -06:00
parent 005dd375ad
commit c132b0d984
78 changed files with 3899 additions and 244 deletions

View File

@ -1,8 +1,8 @@
package com.etheller.warsmash;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import javax.imageio.ImageIO;
@ -13,20 +13,20 @@ import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.FolderDataSource;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.War3ID;
import com.hiveworkshop.wc3.mpq.Codebase;
import com.hiveworkshop.wc3.mpq.FileCodebase;
public class WarsmashGdxGame extends ApplicationAdapter {
private SpriteBatch batch;
private BitmapFont font;
private Codebase codebase;
private DataSource codebase;
private Texture texture;
@Override
public void create() {
this.codebase = new FileCodebase(new File("C:/MPQBuild/War3.mpq/war3.mpq"));
this.codebase = new FolderDataSource(Paths.get("C:/MPQBuild/War3.mpq/war3.mpq"));
final War3ID id = War3ID.fromString("ipea");
try {

View File

@ -0,0 +1,13 @@
package com.etheller.warsmash.common;
/**
* These will probably change the further I get from the source material I am
* copying from.
*/
public enum FetchDataTypeName {
IMAGE,
TEXT,
SLK,
ARRAY_BUFFER,
BLOB;
}

View File

@ -0,0 +1,7 @@
package com.etheller.warsmash.common;
import java.io.InputStream;
public interface LoadGenericCallback {
Object call(InputStream data); // TODO typing
}

View File

@ -0,0 +1,153 @@
package com.etheller.warsmash.datasources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class CompoundDataSource implements DataSource {
private final List<DataSource> mpqList = new ArrayList<>();
public CompoundDataSource(final List<DataSourceDescriptor> dataSourceDescriptors) {
if (dataSourceDescriptors != null) {
for (final DataSourceDescriptor descriptor : dataSourceDescriptors) {
this.mpqList.add(descriptor.createDataSource());
}
}
}
Map<String, File> cache = new HashMap<>();
@Override
public File getFile(final String filepath) {
if (this.cache.containsKey(filepath)) {
return this.cache.get(filepath);
}
try {
for (int i = this.mpqList.size() - 1; i >= 0; i--) {
final DataSource mpq = this.mpqList.get(i);
final File tempProduct = mpq.getFile(filepath);
if (tempProduct != null) {
this.cache.put(filepath, tempProduct);
return tempProduct;
}
}
}
catch (final IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (filepath.toLowerCase(Locale.US).endsWith(".blp")) {
return getFile(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds");
}
if (filepath.toLowerCase(Locale.US).endsWith(".tif")) {
return getFile(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds");
}
return null;
}
@Override
public InputStream getResourceAsStream(final String filepath) {
try {
for (int i = this.mpqList.size() - 1; i >= 0; i--) {
final DataSource mpq = this.mpqList.get(i);
final InputStream resourceAsStream = mpq.getResourceAsStream(filepath);
if (resourceAsStream != null) {
return resourceAsStream;
}
}
}
catch (final IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (filepath.toLowerCase(Locale.US).endsWith(".blp")) {
return getResourceAsStream(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds");
}
if (filepath.toLowerCase(Locale.US).endsWith(".tif")) {
return getResourceAsStream(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds");
}
return null;
}
@Override
public boolean has(final String filepath) {
if (this.cache.containsKey(filepath)) {
return true;
}
for (int i = this.mpqList.size() - 1; i >= 0; i--) {
final DataSource mpq = this.mpqList.get(i);
if (mpq.has(filepath)) {
return true;
}
}
if (filepath.toLowerCase(Locale.US).endsWith(".blp")) {
return has(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds");
}
if (filepath.toLowerCase(Locale.US).endsWith(".tif")) {
return has(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds");
}
return false;
}
public void refresh(final List<DataSourceDescriptor> dataSourceDescriptors) {
for (final DataSource dataSource : this.mpqList) {
try {
dataSource.close();
}
catch (final NullPointerException e) {
e.printStackTrace();
}
catch (final IOException e) {
e.printStackTrace();
}
}
this.cache.clear();
this.mpqList.clear();
if (dataSourceDescriptors != null) {
for (final DataSourceDescriptor descriptor : dataSourceDescriptors) {
this.mpqList.add(descriptor.createDataSource());
}
}
}
public interface LoadedMPQ {
void unload();
boolean hasListfile();
boolean has(String path);
}
public Set<String> getMergedListfile() {
final Set<String> listfile = new HashSet<>();
for (final DataSource mpqGuy : this.mpqList) {
final Collection<String> dataSourceListfile = mpqGuy.getListfile();
if (dataSourceListfile != null) {
for (final String element : dataSourceListfile) {
listfile.add(element);
}
}
}
return listfile;
}
@Override
public Collection<String> getListfile() {
return getMergedListfile();
}
@Override
public void close() throws IOException {
for (final DataSource mpqGuy : this.mpqList) {
mpqGuy.close();
}
}
}

View File

@ -316,4 +316,61 @@ public class Geoset implements MdlxBlock, Chunk {
}
return size;
}
public float[] getVertices() {
return this.vertices;
}
public float[] getNormals() {
return this.normals;
}
public long[] getFaceTypeGroups() {
return this.faceTypeGroups;
}
public long[] getFaceGroups() {
return this.faceGroups;
}
public int[] getFaces() {
return this.faces;
}
public short[] getVertexGroups() {
return this.vertexGroups;
}
public long[] getMatrixGroups() {
return this.matrixGroups;
}
public long[] getMatrixIndices() {
return this.matrixIndices;
}
public long getMaterialId() {
return this.materialId;
}
public long getSelectionGroup() {
return this.selectionGroup;
}
public long getSelectionFlags() {
return this.selectionFlags;
}
public Extent getExtent() {
return this.extent;
}
public Extent[] getSequenceExtents() {
return this.sequenceExtents;
}
public float[][] getUvSets() {
return this.uvSets;
}
}

View File

@ -163,4 +163,29 @@ public class Light extends GenericObject {
public long getByteLength() {
return 48 + super.getByteLength();
}
public int getType() {
return this.type;
}
public float[] getAttenuation() {
return this.attenuation;
}
public float[] getColor() {
return this.color;
}
public float getIntensity() {
return this.intensity;
}
public float[] getAmbientColor() {
return this.ambientColor;
}
public float getAmbientIntensity() {
return this.ambientIntensity;
}
}

View File

@ -190,4 +190,33 @@ public class ParticleEmitter extends GenericObject {
public long getByteLength() {
return 288 + super.getByteLength();
}
public float getEmissionRate() {
return this.emissionRate;
}
public float getGravity() {
return this.gravity;
}
public float getLongitude() {
return this.longitude;
}
public float getLatitude() {
return this.latitude;
}
public String getPath() {
return this.path;
}
public float getLifeSpan() {
return this.lifeSpan;
}
public float getSpeed() {
return this.speed;
}
}

View File

@ -0,0 +1,303 @@
package com.etheller.warsmash.units;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import com.etheller.warsmash.util.WorldEditStrings;
public class DataTable implements ObjectData {
private static final boolean DEBUG = false;
Map<StringKey, Element> dataTable = new LinkedHashMap<>();
private final WorldEditStrings worldEditStrings;
public DataTable(final WorldEditStrings worldEditStrings) {
this.worldEditStrings = worldEditStrings;
}
@Override
public String getLocalizedString(final String key) {
return this.worldEditStrings.getString(key);
}
@Override
public Set<String> keySet() {
final Set<String> outputKeySet = new HashSet<>();
final Set<StringKey> internalKeySet = this.dataTable.keySet();
for (final StringKey key : internalKeySet) {
outputKeySet.add(key.getString());
}
return outputKeySet;
}
public void readTXT(final InputStream inputStream) {
try {
readTXT(inputStream, false);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
public void readTXT(final File f) {
readTXT(f, false);
}
public void readTXT(final File f, final boolean canProduce) {
try {
readTXT(new FileInputStream(f), canProduce);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
public void readSLK(final File f) {
try {
readSLK(new FileInputStream(f));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
public void readTXT(final InputStream txt, final boolean canProduce) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(txt, "utf-8"));
// BOM marker will only appear on the very beginning
reader.mark(4);
if ('\ufeff' != reader.read()) {
reader.reset(); // not the BOM marker
}
String input = "";
Element currentUnit = null;
final boolean first = true;
while ((input = reader.readLine()) != null) {
if (DEBUG) {
System.out.println(input);
}
if (input.startsWith("//")) {
continue;
}
if (input.startsWith("[") && input.contains("]")) {
final int start = input.indexOf("[") + 1;
final int end = input.indexOf("]");
final String newKey = input.substring(start, end);
final String newKeyBase = newKey;
currentUnit = this.dataTable.get(new StringKey(newKey));
if (currentUnit == null) {
currentUnit = new Element(newKey, this);
if (canProduce) {
currentUnit = new LMUnit(newKey, this);
this.dataTable.put(new StringKey(newKey), currentUnit);
}
}
}
else if (input.contains("=")) {
final int eIndex = input.indexOf("=");
final String fieldValue = input.substring(eIndex + 1);
int fieldIndex = 0;
final StringBuilder builder = new StringBuilder();
boolean withinQuotedString = false;
final String fieldName = input.substring(0, eIndex);
for (int i = 0; i < fieldValue.length(); i++) {
final char c = fieldValue.charAt(i);
if (c == '\"') {
withinQuotedString = !withinQuotedString;
}
else if (!withinQuotedString && (c == ',')) {
currentUnit.setField(fieldName, builder.toString().trim(), fieldIndex++);
builder.setLength(0); // empty buffer
}
else {
builder.append(c);
}
}
if (builder.length() > 0) {
if (currentUnit == null) {
System.out.println("null for " + input);
}
currentUnit.setField(fieldName, builder.toString().trim(), fieldIndex++);
}
}
}
reader.close();
}
public void readSLK(final InputStream txt) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(txt, "utf-8"));
String input = "";
Element currentUnit = null;
input = reader.readLine();
if (!input.contains("ID")) {
System.err.println("Formatting of SLK is unusual.");
}
input = reader.readLine();
while (input.startsWith("P;") || input.startsWith("F;")) {
input = reader.readLine();
}
final int yIndex = input.indexOf("Y") + 1;
final int xIndex = input.indexOf("X") + 1;
int colCount = 0;
int rowCount = 0;
boolean flipMode = false;
if (xIndex > yIndex) {
colCount = Integer.parseInt(input.substring(xIndex, input.lastIndexOf(";")));
rowCount = Integer.parseInt(input.substring(yIndex, xIndex - 2));
}
else {
rowCount = Integer.parseInt(input.substring(yIndex, input.lastIndexOf(";")));
colCount = Integer.parseInt(input.substring(xIndex, yIndex - 2));
flipMode = true;
}
int rowStartCount = 0;
final String[] dataNames = new String[colCount];
int col = 0;
int lastFieldId = 0;
while ((input = reader.readLine()) != null) {
if (DEBUG) {
System.out.println(input);
}
if (input.startsWith("E")) {
break;
}
if (input.startsWith("O;")) {
continue;
}
if (input.contains("X1;")) {
rowStartCount++;
col = 0;
}
else {
col++;
}
String kInput;
if (input.startsWith("F;")) {
kInput = reader.readLine();
if (DEBUG) {
System.out.println(kInput);
}
}
else {
kInput = input;
}
if (rowStartCount <= 1) {
final int subXIndex = input.indexOf("X");
final int subYIndex = input.indexOf("Y");
if ((subYIndex >= 0) && (subYIndex < subXIndex)) {
final int eIndex = kInput.indexOf("K");
final int fieldIdEndIndex = kInput != input ? input.length() : eIndex - 1;
if ((eIndex == -1) || (kInput.charAt(eIndex - 1) != ';')) {
continue;
}
final int fieldId;
if (subXIndex < 0) {
if (lastFieldId == 0) {
rowStartCount++;
}
fieldId = lastFieldId + 1;
}
else {
fieldId = Integer.parseInt(input.substring(subXIndex + 1, fieldIdEndIndex));
}
final int quotationIndex = kInput.indexOf("\"");
if (quotationIndex == -1) {
dataNames[fieldId - 1] = kInput.substring(eIndex + 1);
}
else {
dataNames[fieldId - 1] = kInput.substring(quotationIndex + 1, kInput.lastIndexOf("\""));
}
lastFieldId = fieldId;
continue;
}
else {
int eIndex = kInput.indexOf("K");
if ((eIndex == -1) || (kInput.charAt(eIndex - 1) != ';')) {
continue;
}
final int fieldId;
if (subXIndex < 0) {
if (lastFieldId == 0) {
rowStartCount++;
}
fieldId = lastFieldId + 1;
}
else {
if (flipMode && input.contains("Y") && (input == kInput)) {
eIndex = Math.min(subYIndex, eIndex);
}
final int fieldIdEndIndex = kInput != input ? input.length() : eIndex - 1;
fieldId = Integer.parseInt(input.substring(subXIndex + 1, fieldIdEndIndex));
}
final int quotationIndex = kInput.indexOf("\"");
if (quotationIndex == -1) {
dataNames[fieldId - 1] = kInput.substring(eIndex + 1);
}
else {
dataNames[fieldId - 1] = kInput.substring(quotationIndex + 1, kInput.lastIndexOf("\""));
}
lastFieldId = fieldId;
continue;
}
}
if (input.contains("X1;") || ((input != kInput) && input.endsWith("X1"))) {
final int start = kInput.indexOf("\"") + 1;
final int end = kInput.lastIndexOf("\"");
if ((start - 1) != end) {
final String newKey = kInput.substring(start, end);
currentUnit = this.dataTable.get(new StringKey(newKey));
if (currentUnit == null) {
currentUnit = new Element(newKey, this);
this.dataTable.put(new StringKey(newKey), currentUnit);
}
}
}
else if (kInput.contains("K")) {
final int subXIndex = input.indexOf("X");
int eIndex = kInput.indexOf("K");
if (flipMode && kInput.contains("Y")) {
eIndex = Math.min(kInput.indexOf("Y"), eIndex);
}
final int fieldIdEndIndex = kInput != input ? input.length() : eIndex - 1;
final int fieldId = (subXIndex == -1) || (subXIndex > fieldIdEndIndex) ? 1
: Integer.parseInt(input.substring(subXIndex + 1, fieldIdEndIndex));
String fieldValue = kInput.substring(eIndex + 1);
if ((fieldValue.length() > 1) && fieldValue.startsWith("\"") && fieldValue.endsWith("\"")) {
fieldValue = fieldValue.substring(1, fieldValue.length() - 1);
}
if (dataNames[fieldId - 1] != null) {
currentUnit.setField(dataNames[fieldId - 1], fieldValue);
}
}
}
reader.close();
}
@Override
public Element get(final String id) {
return this.dataTable.get(new StringKey(id));
}
@Override
public void setValue(final String id, final String field, final String value) {
get(id).setField(field, value);
}
public void put(final String id, final Element e) {
this.dataTable.put(new StringKey(id), e);
}
}

View File

@ -0,0 +1,217 @@
package com.etheller.warsmash.units;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
public class Element extends HashedGameObject {
public Element(final String id, final DataTable table) {
super(id, table);
}
public List<GameObject> builds() {
return getFieldAsList("Builds", this.parentTable);
}
public List<GameObject> requires() {
final List<GameObject> requirements = getFieldAsList("Requires", this.parentTable);
final List<Integer> reqLvls = requiresLevels();
return requirements;
}
public List<Integer> requiresLevels() {
final String stringList = getField("Requiresamount");
final String[] listAsArray = stringList.split(",");
final LinkedList<Integer> output = new LinkedList<>();
if ((listAsArray != null) && (listAsArray.length > 0) && !listAsArray[0].equals("")) {
for (final String levelString : listAsArray) {
final Integer level = Integer.parseInt(levelString);
if (level != null) {
output.add(level);
}
}
}
return output;
}
public List<GameObject> parents() {
return getFieldAsList("Parents", this.parentTable);
}
public List<GameObject> children() {
return getFieldAsList("Children", this.parentTable);
}
public List<GameObject> requiredBy() {
return getFieldAsList("RequiredBy", this.parentTable);
}
public List<GameObject> trains() {
return getFieldAsList("Trains", this.parentTable);
}
public List<GameObject> upgrades() {
return getFieldAsList("Upgrade", this.parentTable);
}
public List<GameObject> researches() {
return getFieldAsList("Researches", this.parentTable);
}
public List<GameObject> dependencyOr() {
return getFieldAsList("DependencyOr", this.parentTable);
}
public List<GameObject> abilities() {
return getFieldAsList("abilList", this.parentTable);
}
HashMap<String, List<Element>> hashedLists = new HashMap<>();
@Override
public String toString() {
return getField("Name");
}
public int getTechTier() {
final String tier = getField("Custom Field: TechTier");
if (tier == null) {
return -1;
}
return Integer.parseInt(tier);
}
public void setTechTier(final int i) {
setField("Custom Field: TechTier", i + "");
}
public int getTechDepth() {
final String tier = getField("Custom Field: TechDepth");
if (tier == null) {
return -1;
}
return Integer.parseInt(tier);
}
public void setTechDepth(final int i) {
setField("Custom Field: TechDepth", i + "");
}
public String getIconPath() {
String artField = getField("Art");
if (artField.indexOf(',') != -1) {
artField = artField.substring(0, artField.indexOf(','));
}
return artField;
}
public String getUnitId() {
return this.id;
}
@Override
public String getName() {
String name = getField("Name");
boolean nameKnown = name.length() >= 1;
if (!nameKnown && !getField("code").equals(this.id) && (getField("code").length() >= 4)) {
final Element other = (Element) this.parentTable.get(getField("code").substring(0, 4));
if (other != null) {
name = other.getName();
nameKnown = true;
}
}
if (!nameKnown && (getField("EditorName").length() > 1)) {
name = getField("EditorName");
nameKnown = true;
}
if (!nameKnown && (getField("Editorname").length() > 1)) {
name = getField("Editorname");
nameKnown = true;
}
if (!nameKnown && (getField("BuffTip").length() > 1)) {
name = getField("BuffTip");
nameKnown = true;
}
if (!nameKnown && (getField("Bufftip").length() > 1)) {
name = getField("Bufftip");
nameKnown = true;
}
if (nameKnown && name.startsWith("WESTRING")) {
if (!name.contains(" ")) {
name = this.parentTable.getLocalizedString(name);
}
else {
final String[] names = name.split(" ");
name = "";
for (final String subName : names) {
if (name.length() > 0) {
name += " ";
}
if (subName.startsWith("WESTRING")) {
name += this.parentTable.getLocalizedString(subName);
}
else {
name += subName;
}
}
}
if (name.startsWith("\"") && name.endsWith("\"")) {
name = name.substring(1, name.length() - 1);
}
setField("Name", name);
}
if (!nameKnown) {
name = this.parentTable.getLocalizedString("WESTRING_UNKNOWN") + " '" + getUnitId() + "'";
}
if (getField("campaign").startsWith("1") && Character.isUpperCase(getUnitId().charAt(0))) {
name = getField("Propernames");
if (name.contains(",")) {
name = name.split(",")[0];
}
}
String suf = getField("EditorSuffix");
if ((suf.length() > 0) && !suf.equals("_")) {
if (suf.startsWith("WESTRING")) {
suf = this.parentTable.getLocalizedString(suf);
}
if (!suf.startsWith(" ")) {
name += " ";
}
name += suf;
}
return name;
}
public void addParent(final String parentId) {
String parentField = getField("Parents");
if (!parentField.contains(parentId)) {
parentField = parentField + "," + parentId;
setField("Parents", parentField);
}
}
public void addChild(final String parentId) {
String parentField = getField("Children");
if (!parentField.contains(parentId)) {
parentField = parentField + "," + parentId;
setField("Children", parentField);
}
}
public void addRequiredBy(final String parentId) {
String parentField = getField("RequiredBy");
if (!parentField.contains(parentId)) {
parentField = parentField + "," + parentId;
setField("RequiredBy", parentField);
}
}
public void addResearches(final String parentId) {
String parentField = getField("Researches");
if (!parentField.contains(parentId)) {
parentField = parentField + "," + parentId;
setField("Researches", parentField);
}
}
}

View File

@ -0,0 +1,30 @@
package com.etheller.warsmash.units;
import java.util.List;
import java.util.Set;
public interface GameObject {
public void setField(String field, String value);
public void setField(String field, String value, int index);
public String getField(String field);
public String getField(String field, int index);
public int getFieldValue(String field);
public int getFieldValue(String field, int index);
public List<? extends GameObject> getFieldAsList(String field, ObjectData objectData);
public String getId();
public ObjectData getTable();
public String getName();
public Set<String> keySet();
}

View File

@ -0,0 +1,241 @@
package com.etheller.warsmash.units;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public abstract class HashedGameObject implements GameObject {
HashMap<StringKey, List<String>> fields = new HashMap<>();
String id;
ObjectData parentTable;
transient HashMap<String, List<GameObject>> hashedLists = new HashMap<>();
public HashedGameObject(final String id, final ObjectData table) {
this.id = id;
this.parentTable = table;
}
@Override
public void setField(final String field, final String value) {
final StringKey key = new StringKey(field);
List<String> list = this.fields.get(key);
if (list == null) {
list = new ArrayList<>();
this.fields.put(key, list);
list.add(value);
}
else {
list.set(0, value);
}
}
@Override
public String getField(final String field) {
final String value = "";
if (this.fields.get(new StringKey(field)) != null) {
final List<String> list = this.fields.get(new StringKey(field));
final StringBuilder sb = new StringBuilder();
if (list != null) {
for (final String str : list) {
if (sb.length() != 0) {
sb.append(',');
}
sb.append(str);
}
return sb.toString();
}
}
return value;
}
public boolean hasField(final String field) {
return this.fields.containsKey(new StringKey(field));
}
@Override
public int getFieldValue(final String field) {
int i = 0;
try {
i = Integer.parseInt(getField(field));
}
catch (final NumberFormatException e) {
}
return i;
}
@Override
public void setField(final String field, final String value, final int index) {
final StringKey key = new StringKey(field);
List<String> list = this.fields.get(key);
if (list == null) {
if (index == 0) {
list = new ArrayList<>();
this.fields.put(key, list);
list.add(value);
}
else {
throw new IndexOutOfBoundsException();
}
}
else {
if (list.size() == index) {
list.add(value);
}
else {
list.set(index, value);
}
}
}
@Override
public String getField(final String field, final int index) {
String value = "";
if (this.fields.get(new StringKey(field)) != null) {
final List<String> list = this.fields.get(new StringKey(field));
if (list != null) {
if (list.size() > index) {
value = list.get(index);
}
}
}
return value;
}
@Override
public int getFieldValue(final String field, final int index) {
int i = 0;
try {
i = Integer.parseInt(getField(field, index));
}
catch (final NumberFormatException e) {
}
return i;
}
@Override
public List<GameObject> getFieldAsList(final String field, final ObjectData parentTable) {
List<GameObject> fieldAsList;
fieldAsList = new ArrayList<>();
final String stringList = getField(field);
final String[] listAsArray = stringList.split(",");
if ((listAsArray != null) && (listAsArray.length > 0)) {
for (final String buildingId : listAsArray) {
final GameObject referencedUnit = parentTable.get(buildingId);
if (referencedUnit != null) {
fieldAsList.add(referencedUnit);
}
}
}
return fieldAsList;
}
@Override
public String toString() {
return getField("Name");
}
@Override
public String getId() {
return this.id;
}
@Override
public String getName() {
String name = getField("Name");
boolean nameKnown = name.length() >= 1;
if (!nameKnown && !getField("code").equals(this.id) && (getField("code").length() >= 4)) {
final GameObject other = this.parentTable.get(getField("code").substring(0, 4));
if (other != null) {
name = other.getName();
nameKnown = true;
}
}
if (!nameKnown && (getField("EditorName").length() > 1)) {
name = getField("EditorName");
nameKnown = true;
}
if (!nameKnown && (getField("Editorname").length() > 1)) {
name = getField("Editorname");
nameKnown = true;
}
if (!nameKnown && (getField("BuffTip").length() > 1)) {
name = getField("BuffTip");
nameKnown = true;
}
if (!nameKnown && (getField("Bufftip").length() > 1)) {
name = getField("Bufftip");
nameKnown = true;
}
if (nameKnown && name.startsWith("WESTRING")) {
if (!name.contains(" ")) {
name = this.parentTable.getLocalizedString(name);
}
else {
final String[] names = name.split(" ");
name = "";
for (final String subName : names) {
if (name.length() > 0) {
name += " ";
}
if (subName.startsWith("WESTRING")) {
name += this.parentTable.getLocalizedString(subName);
}
else {
name += subName;
}
}
}
if (name.startsWith("\"") && name.endsWith("\"")) {
name = name.substring(1, name.length() - 1);
}
setField("Name", name);
}
if (!nameKnown) {
name = this.parentTable.getLocalizedString("WESTRING_UNKNOWN") + " '" + getId() + "'";
}
if (getField("campaign").startsWith("1") && Character.isUpperCase(getId().charAt(0))) {
name = getField("Propernames");
if (name.contains(",")) {
name = name.split(",")[0];
}
}
String suf = getField("EditorSuffix");
if ((suf.length() > 0) && !suf.equals("_")) {
if (suf.startsWith("WESTRING")) {
suf = this.parentTable.getLocalizedString(suf);
}
if (!suf.startsWith(" ")) {
name += " ";
}
name += suf;
}
return name;
}
public void addToList(final String parentId, final String list) {
String parentField = getField(list);
if (!parentField.contains(parentId)) {
parentField = parentField + "," + parentId;
setField(list, parentField);
}
}
@Override
public ObjectData getTable() {
return this.parentTable;
}
@Override
public Set<String> keySet() {
final Set<String> keySet = new HashSet<>();
for (final StringKey key : this.fields.keySet()) {
keySet.add(key.getString());
}
return keySet;
}
}

View File

@ -0,0 +1,12 @@
package com.etheller.warsmash.units;
import java.util.LinkedHashMap;
public class LMUnit extends Element {
public LMUnit(final String id, final DataTable table) {
super(id, table);
this.fields = new LinkedHashMap<>();
}
}

View File

@ -0,0 +1,13 @@
package com.etheller.warsmash.units;
import java.util.Set;
public interface ObjectData {
GameObject get(String id);
void setValue(String id, String field, String value);
Set<String> keySet();
String getLocalizedString(String key);
}

View File

@ -0,0 +1,610 @@
package com.etheller.warsmash.units;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.util.WorldEditStrings;
public class StandardObjectData {
private WorldEditStrings worldEditStrings;
private DataSource source;
public StandardObjectData(final DataSource dataSource) {
this.source = dataSource;
this.worldEditStrings = new WorldEditStrings(dataSource);
}
public WarcraftData getStandardUnits() {
final DataTable profile = new DataTable(this.worldEditStrings);
final DataTable unitAbilities = new DataTable(this.worldEditStrings);
final DataTable unitBalance = new DataTable(this.worldEditStrings);
final DataTable unitData = new DataTable(this.worldEditStrings);
final DataTable unitUI = new DataTable(this.worldEditStrings);
final DataTable unitWeapons = new DataTable(this.worldEditStrings);
try {
profile.readTXT(this.source.getResourceAsStream("Units\\CampaignUnitFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\CampaignUnitStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\HumanUnitFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\HumanUnitStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NeutralUnitFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NeutralUnitStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NightElfUnitFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NightElfUnitStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\OrcUnitFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\OrcUnitStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\UndeadUnitFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\UndeadUnitStrings.txt"), true);
unitAbilities.readSLK(this.source.getResourceAsStream("Units\\UnitAbilities.slk"));
unitBalance.readSLK(this.source.getResourceAsStream("Units\\UnitBalance.slk"));
unitData.readSLK(this.source.getResourceAsStream("Units\\UnitData.slk"));
unitUI.readSLK(this.source.getResourceAsStream("Units\\UnitUI.slk"));
unitWeapons.readSLK(this.source.getResourceAsStream("Units\\UnitWeapons.slk"));
final InputStream unitSkin = this.source.getResourceAsStream("Units\\UnitSkin.txt");
if (unitSkin != null) {
profile.readTXT(unitSkin, true);
}
}
catch (final IOException e) {
throw new RuntimeException(e);
}
final WarcraftData units = new WarcraftData();
units.add(profile, "Profile", false);
units.add(unitAbilities, "UnitAbilities", true);
units.add(unitBalance, "UnitBalance", true);
units.add(unitData, "UnitData", true);
units.add(unitUI, "UnitUI", true);
units.add(unitWeapons, "UnitWeapons", true);
return units;
}
public WarcraftData getStandardItems() {
final DataTable profile = new DataTable(this.worldEditStrings);
final DataTable itemData = new DataTable(this.worldEditStrings);
try {
profile.readTXT(this.source.getResourceAsStream("Units\\ItemFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\ItemStrings.txt"), true);
itemData.readSLK(this.source.getResourceAsStream("Units\\ItemData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
final WarcraftData units = new WarcraftData();
units.add(profile, "Profile", false);
units.add(itemData, "ItemData", true);
return units;
}
public WarcraftData getStandardDestructables() {
final DataTable destructableData = new DataTable(this.worldEditStrings);
try {
destructableData.readSLK(this.source.getResourceAsStream("Units\\DestructableData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
final WarcraftData units = new WarcraftData();
units.add(destructableData, "DestructableData", true);
return units;
}
public WarcraftData getStandardDoodads() {
final DataTable destructableData = new DataTable(this.worldEditStrings);
try {
destructableData.readSLK(this.source.getResourceAsStream("Doodads\\Doodads.slk"));
final InputStream unitSkin = this.source.getResourceAsStream("Doodads\\DoodadSkins.txt");
if (unitSkin != null) {
destructableData.readTXT(unitSkin, true);
}
}
catch (final IOException e) {
throw new RuntimeException(e);
}
final WarcraftData units = new WarcraftData();
units.add(destructableData, "DoodadData", true);
return units;
}
public DataTable getStandardUnitMeta() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readSLK(this.source.getResourceAsStream("Units\\UnitMetaData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public DataTable getStandardDestructableMeta() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readSLK(this.source.getResourceAsStream("Units\\DestructableMetaData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public DataTable getStandardDoodadMeta() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readSLK(this.source.getResourceAsStream("Doodads\\DoodadMetaData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public WarcraftData getStandardAbilities() {
final DataTable profile = new DataTable(this.worldEditStrings);
final DataTable abilityData = new DataTable(this.worldEditStrings);
try {
profile.readTXT(this.source.getResourceAsStream("Units\\CampaignAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\CampaignAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\CommonAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\CommonAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\HumanAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\HumanAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NeutralAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NeutralAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NightElfAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NightElfAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\OrcAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\OrcAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\UndeadAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\UndeadAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityStrings.txt"), true);
abilityData.readSLK(this.source.getResourceAsStream("Units\\AbilityData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
final WarcraftData abilities = new WarcraftData();
abilities.add(profile, "Profile", false);
abilities.add(abilityData, "AbilityData", true);
return abilities;
}
public WarcraftData getStandardAbilityBuffs() {
final DataTable profile = new DataTable(this.worldEditStrings);
final DataTable abilityData = new DataTable(this.worldEditStrings);
try {
profile.readTXT(this.source.getResourceAsStream("Units\\CampaignAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\CampaignAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\CommonAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\CommonAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\HumanAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\HumanAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NeutralAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NeutralAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NightElfAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NightElfAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\OrcAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\OrcAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\UndeadAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\UndeadAbilityStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityStrings.txt"), true);
abilityData.readSLK(this.source.getResourceAsStream("Units\\AbilityBuffData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
final WarcraftData abilities = new WarcraftData();
abilities.add(profile, "Profile", false);
abilities.add(abilityData, "AbilityData", true);
return abilities;
}
public WarcraftData getStandardUpgrades() {
final DataTable profile = new DataTable(this.worldEditStrings);
final DataTable upgradeData = new DataTable(this.worldEditStrings);
try {
profile.readTXT(this.source.getResourceAsStream("Units\\CampaignUpgradeFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\CampaignUpgradeStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\HumanUpgradeFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\HumanUpgradeStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NeutralUpgradeFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NeutralUpgradeStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NightElfUpgradeFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\NightElfUpgradeStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\OrcUpgradeFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\OrcUpgradeStrings.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\UndeadUpgradeFunc.txt"), true);
profile.readTXT(this.source.getResourceAsStream("Units\\UndeadUpgradeStrings.txt"), true);
upgradeData.readSLK(this.source.getResourceAsStream("Units\\UpgradeData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
final WarcraftData units = new WarcraftData();
units.add(profile, "Profile", false);
units.add(upgradeData, "UpgradeData", true);
return units;
}
public DataTable getStandardUpgradeMeta() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readSLK(this.source.getResourceAsStream("Units\\UpgradeMetaData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public DataTable getStandardUpgradeEffectMeta() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readSLK(this.source.getResourceAsStream("Units\\UpgradeEffectMetaData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public DataTable getStandardAbilityMeta() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readSLK(this.source.getResourceAsStream("Units\\AbilityMetaData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public DataTable getStandardAbilityBuffMeta() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readSLK(this.source.getResourceAsStream("Units\\AbilityBuffMetaData.slk"));
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public DataTable getUnitEditorData() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readTXT(this.source.getResourceAsStream("UI\\UnitEditorData.txt"), true);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public DataTable getWorldEditData() {
final DataTable unitMetaData = new DataTable(this.worldEditStrings);
try {
unitMetaData.readTXT(this.source.getResourceAsStream("UI\\WorldEditData.txt"), true);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return unitMetaData;
}
public static class WarcraftData implements ObjectData {
WorldEditStrings worldEditStrings;
List<DataTable> tables = new ArrayList<>();
Map<StringKey, DataTable> tableMap = new HashMap<>();
Map<StringKey, WarcraftObject> units = new HashMap<>();
public WarcraftData(final WorldEditStrings worldEditStrings) {
this.worldEditStrings = worldEditStrings;
}
@Override
public String getLocalizedString(final String key) {
return this.worldEditStrings.getString(key);
}
public void add(final DataTable data, final String name, final boolean canMake) {
this.tableMap.put(new StringKey(name), data);
this.tables.add(data);
if (canMake) {
for (final String id : data.keySet()) {
if (!this.units.containsKey(new StringKey(id))) {
this.units.put(new StringKey(id), new WarcraftObject(data.get(id).getId(), this));
}
}
}
}
public WarcraftData() {
}
public List<DataTable> getTables() {
return this.tables;
}
public void setTables(final List<DataTable> tables) {
this.tables = tables;
}
public DataTable getTable(final String tableName) {
return this.tableMap.get(new StringKey(tableName));
}
@Override
public GameObject get(final String id) {
return this.units.get(new StringKey(id));
}
@Override
public void setValue(final String id, final String field, final String value) {
get(id).setField(field, value);
}
@Override
public Set<String> keySet() {
final Set<String> keySet = new HashSet<>();
for (final StringKey key : this.units.keySet()) {
keySet.add(key.getString());
}
return keySet;
}
public void cloneUnit(final String parentId, final String cloneId) {
for (final DataTable table : this.tables) {
final Element parentEntry = table.get(parentId);
final LMUnit cloneUnit = new LMUnit(cloneId, table);
for (final String key : parentEntry.keySet()) {
cloneUnit.setField(key, parentEntry.getField(key));
}
table.put(cloneId, cloneUnit);
}
this.units.put(new StringKey(cloneId), new WarcraftObject(cloneId, this));
}
}
public static class WarcraftObject implements GameObject {
String id;
WarcraftData dataSource;
public WarcraftObject(final String id, final WarcraftData dataSource) {
this.id = id;
this.dataSource = dataSource;
}
@Override
public void setField(final String field, final String value, final int index) {
for (final DataTable table : this.dataSource.getTables()) {
final Element element = table.get(this.id);
if ((element != null) && element.hasField(field)) {
element.setField(field, value, index);
return;
}
}
}
@Override
public String getField(final String field, final int index) {
for (final DataTable table : this.dataSource.getTables()) {
final Element element = table.get(this.id);
if ((element != null) && element.hasField(field)) {
return element.getField(field, index);
}
}
return "";
}
@Override
public int getFieldValue(final String field, final int index) {
for (final DataTable table : this.dataSource.getTables()) {
final Element element = table.get(this.id);
if ((element != null) && element.hasField(field)) {
return element.getFieldValue(field, index);
}
}
return 0;
}
@Override
public void setField(final String field, final String value) {
for (final DataTable table : this.dataSource.getTables()) {
final Element element = table.get(this.id);
if ((element != null) && element.hasField(field)) {
element.setField(field, value);
return;
}
}
throw new IllegalArgumentException("no field");
}
@Override
public String getField(final String field) {
for (final DataTable table : this.dataSource.getTables()) {
final Element element = table.get(this.id);
if ((element != null) && element.hasField(field)) {
return element.getField(field);
}
}
return "";
}
@Override
public int getFieldValue(final String field) {
for (final DataTable table : this.dataSource.getTables()) {
final Element element = table.get(this.id);
if ((element != null) && element.hasField(field)) {
return element.getFieldValue(field);
}
}
return 0;
}
/*
* (non-Javadoc) I'm not entirely sure this is still safe to use
*
* @see com.hiveworkshop.wc3.units.GameObject#getFieldAsList(java.lang. String)
*/
@Override
public List<? extends GameObject> getFieldAsList(final String field, final ObjectData objectData) {
for (final DataTable table : this.dataSource.getTables()) {
final Element element = table.get(this.id);
if ((element != null) && element.hasField(field)) {
return element.getFieldAsList(field, objectData);
}
}
return new ArrayList<>();// empty list if not found
}
@Override
public String getId() {
return this.id;
}
@Override
public ObjectData getTable() {
return this.dataSource;
}
// @Override
// public String getName() {
// return dataSource.profile.get(id).getName();
// }
@Override
public String getName() {
String name = getField("Name");
boolean nameKnown = name.length() >= 1;
if (!nameKnown && !getField("code").equals(this.id) && (getField("code").length() >= 4)) {
final WarcraftObject other = (WarcraftObject) this.dataSource.get(getField("code").substring(0, 4));
if (other != null) {
name = other.getName();
nameKnown = true;
}
}
if (!nameKnown && (getField("EditorName").length() > 1)) {
name = getField("EditorName");
nameKnown = true;
}
if (!nameKnown && (getField("Editorname").length() > 1)) {
name = getField("Editorname");
nameKnown = true;
}
if (!nameKnown && (getField("BuffTip").length() > 1)) {
name = getField("BuffTip");
nameKnown = true;
}
if (!nameKnown && (getField("Bufftip").length() > 1)) {
name = getField("Bufftip");
nameKnown = true;
}
if (nameKnown && name.startsWith("WESTRING")) {
if (!name.contains(" ")) {
name = this.dataSource.getLocalizedString(name);
}
else {
final String[] names = name.split(" ");
name = "";
for (final String subName : names) {
if (name.length() > 0) {
name += " ";
}
if (subName.startsWith("WESTRING")) {
name += this.dataSource.getLocalizedString(subName);
}
else {
name += subName;
}
}
}
if (name.startsWith("\"") && name.endsWith("\"")) {
name = name.substring(1, name.length() - 1);
}
setField("Name", name);
}
if (!nameKnown) {
name = this.dataSource.getLocalizedString("WESTRING_UNKNOWN") + " '" + getId() + "'";
}
if (getField("campaign").startsWith("1") && Character.isUpperCase(getId().charAt(0))) {
name = getField("Propernames");
if (name.contains(",")) {
name = name.split(",")[0];
}
}
String suf = getField("EditorSuffix");
if ((suf.length() > 0) && !suf.equals("_")) {
if (suf.startsWith("WESTRING")) {
suf = this.dataSource.getLocalizedString(suf);
}
if (!suf.startsWith(" ")) {
name += " ";
}
name += suf;
}
return name;
}
BufferedImage storedImage = null;
String storedImagePath = null;
@Override
public Set<String> keySet() {
final Set<String> keySet = new HashSet<>();
for (final DataTable table : this.dataSource.tables) {
keySet.addAll(table.get(this.id).keySet());
}
return keySet;
}
}
private StandardObjectData() {
}
}

View File

@ -0,0 +1,54 @@
package com.etheller.warsmash.units;
/**
* A hashable wrapper object for a String that can be used as the key in a
* hashtable, but which disregards case as a key -- except that it will remember
* case if directly asked for its value. The game needs this to be able to show
* the original case of a string to the user in the editor, while still doing
* map lookups in a case insensitive way.
*
* @author Eric
*
*/
public final class StringKey {
private final String string;
public StringKey(final String string) {
this.string = string;
}
public String getString() {
return this.string;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (prime * result) + ((this.string.toLowerCase() == null) ? 0 : this.string.toLowerCase().hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final StringKey other = (StringKey) obj;
if (this.string == null) {
if (other.string != null) {
return false;
}
}
else if (!this.string.equalsIgnoreCase(other.string)) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,84 @@
package com.etheller.warsmash.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IniFile {
private static final Pattern NAME_PATTERN = Pattern.compile("^\\[(.+?)\\].*");
private static final Pattern DATA_PATTERN = Pattern.compile("^(.+?)=(.*?)$");
public final Map<String, String> properties = new HashMap<>();
public final Map<String, Map<String, String>> sections = new HashMap<>();
public IniFile(final String buffer) {
if (buffer != null) {
this.load(buffer);
}
}
public void load(final String buffer) {
// All properties added until a section is reached are added to the properties
// map.
// Once a section is reached, any further properties will be added to it until
// matching another section, etc.
Map<String, String> section = this.properties;
// Below: using \n instead of \r\n because its not reading directly from the
// actual file, but instead from a Java translated thing
for (final String line : buffer.split("\n")) {
// INI defines comments as starting with a semicolon ';'.
// However, Warcraft 3 INI files use normal C comments '//'.
// In addition, Warcraft 3 files have empty lines.
// Therefore, ignore any line matching any of these conditions.
if ((line.length() != 0) && !line.startsWith("//") && !line.startsWith(";")) {
final Matcher matcher = NAME_PATTERN.matcher(line);
if (matcher.matches()) {
final String name = matcher.group(1).trim().toLowerCase();
section = this.sections.get(name);
if (section == null) {
section = new HashMap<>();
this.sections.put(name, section);
}
}
else {
final Matcher dataMatcher = DATA_PATTERN.matcher(line);
if (dataMatcher.matches()) {
section.put(dataMatcher.group(1).toLowerCase(), dataMatcher.group(2));
}
}
}
}
}
public String save() {
final List<String> lines = new ArrayList<>();
for (final Map.Entry<String, String> entry : this.properties.entrySet()) {
lines.add(entry.getKey() + "=" + entry.getValue());
}
for (final Map.Entry<String, Map<String, String>> sectionData : this.sections.entrySet()) {
lines.add("[" + sectionData.getKey() + "]");
for (final Map.Entry<String, String> entry : sectionData.getValue().entrySet()) {
lines.add(entry.getKey() + "=" + entry.getValue());
}
}
return String.join("\r\n", lines);
}
public Map<String, String> getSection(final String name) {
return this.sections.get(name.toLowerCase());
}
}

View File

@ -0,0 +1,92 @@
package com.etheller.warsmash.util;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A structure that holds mapped data from INI and SLK files.
*
* In the case of SLK files, the first row is expected to hold the names of the
* columns.
*/
public class MappedData {
private final Map<String, MappedDataRow> map = new HashMap<>();
public MappedData(final String buffer) {
if (buffer != null) {
this.load(buffer);
}
}
/**
* Load data from an SLK file or an INI file.
*
* Note that this may override previous properties!
*/
public void load(final String buffer) {
if (buffer.startsWith("ID;")) {
final SlkFile file = new SlkFile(buffer);
final List<List<Object>> rows = file.rows;
final List<Object> header = rows.get(0);
for (int i = 1, l = rows.size(); i < l; i++) {
final List<Object> row = rows.get(i);
if (row != null) {
String name = (String) row.get(0);
if (name != null) {
name = name.toLowerCase();
if (!this.map.containsKey(name)) {
this.map.put(name, new MappedDataRow());
}
final MappedDataRow mapped = this.map.get(name);
for (int j = 0, k = header.size(); j < k; j++) {
String key = (String) header.get(j);
// UnitBalance.slk doesn't define the name of one row.
if (key == null) {
key = "column" + j;
}
mapped.put(key, row.get(j));
}
}
}
}
}
else {
final IniFile file = new IniFile(buffer);
final Map<String, Map<String, String>> sections = file.sections;
for (final Map.Entry<String, Map<String, String>> rowAndProperties : sections.entrySet()) {
final String row = rowAndProperties.getKey();
if (!this.map.containsKey(row)) {
this.map.put(row, new MappedDataRow());
}
final MappedDataRow mapped = this.map.get(row);
for (final Map.Entry<String, String> nameAndProperty : rowAndProperties.getValue().entrySet()) {
mapped.put(nameAndProperty.getKey(), nameAndProperty.getValue());
}
}
}
}
public MappedDataRow getRow(final String key) {
return this.map.get(key.toLowerCase());
}
public Object getProperty(final String key, final String name) {
return this.map.get(key.toLowerCase()).get(name);
}
public void setRow(final String key, final MappedDataRow values) {
this.map.put(key.toLowerCase(), values);
}
}

View File

@ -0,0 +1,7 @@
package com.etheller.warsmash.util;
import java.util.HashMap;
public class MappedDataRow extends HashMap<String, Object> {
}

View File

@ -0,0 +1,70 @@
package com.etheller.warsmash.util;
import java.util.ArrayList;
import java.util.List;
public class SlkFile {
public List<List<Object>> rows;
public SlkFile(final String buffer) {
if (buffer != null) {
this.load(buffer);
}
}
public void load(final String buffer) {
if (!buffer.startsWith("ID")) {
throw new RuntimeException("WrongMagicNumber");
}
int x = 0;
int y = 0;
for (final String line : buffer.split("\n")) {
// The B command is supposed to define the total number of columns and rows,
// however in UbetSplatData.slk it gives wrong information
// Therefore, just ignore it, since JavaScript arrays grow as they want either
// way
if (line.charAt(0) != 'B') {
for (final String token : line.split(";")) {
final char op = token.charAt(0);
final String valueString = token.substring(1).trim();
final Object value;
if (op == 'X') {
x = Integer.parseInt(valueString, 10) - 1;
}
else if (op == 'Y') {
y = Integer.parseInt(valueString, 10) - 1;
}
else if (op == 'K') {
while (y >= this.rows.size()) {
this.rows.add(null);
}
if (this.rows.get(y) == null) {
this.rows.set(y, new ArrayList<>());
}
if (valueString.charAt('0') == '"') {
value = valueString.substring(1, valueString.length() - 1);
}
else if ("TRUE".equals(valueString)) {
value = true;
}
else if ("FALSE".equals(valueString)) {
value = false;
}
else {
value = Float.parseFloat(valueString);
}
final List<Object> row = this.rows.get(y);
while (x >= row.size()) {
row.add(null);
}
row.set(x, value);
}
}
}
}
}
}

View File

@ -0,0 +1,20 @@
package com.etheller.warsmash.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Test {
public static void main(final String[] args) {
final Pattern pattern = Pattern.compile("^\\[(.+?)\\]");
final Matcher matcher = pattern.matcher("[boat] // ocean");
if (matcher.matches()) {
final String name = matcher.group(1).trim().toLowerCase();
System.out.println(name);
}
else {
System.out.println("no match");
}
}
}

View File

@ -0,0 +1,71 @@
package com.etheller.warsmash.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import com.etheller.warsmash.datasources.DataSource;
public class WorldEditStrings {
private ResourceBundle bundle;
private ResourceBundle bundlegs;
public WorldEditStrings(final DataSource dataSource) {
try (InputStream fis = dataSource.getResourceAsStream("UI\\WorldEditStrings.txt");
InputStreamReader reader = new InputStreamReader(fis, "utf-8")) {
this.bundle = new PropertyResourceBundle(reader);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
try (InputStream fis = dataSource.getResourceAsStream("UI\\WorldEditGameStrings.txt");
InputStreamReader reader = new InputStreamReader(fis, "utf-8")) {
this.bundlegs = new PropertyResourceBundle(reader);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
}
public String getString(String string) {
try {
while (string.toUpperCase().startsWith("WESTRING")) {
string = internalGetString(string);
}
return string;
}
catch (final MissingResourceException exc) {
try {
return this.bundlegs.getString(string.toUpperCase());
}
catch (final MissingResourceException exc2) {
return string;
}
}
}
private String internalGetString(final String key) {
try {
String string = this.bundle.getString(key.toUpperCase());
if ((string.charAt(0) == '"') && (string.length() >= 2) && (string.charAt(string.length() - 1) == '"')) {
string = string.substring(1, string.length() - 1);
}
return string;
}
catch (final MissingResourceException exc) {
return this.bundlegs.getString(key.toUpperCase());
}
}
public String getStringCaseSensitive(final String key) {
try {
return this.bundle.getString(key);
}
catch (final MissingResourceException exc) {
return this.bundlegs.getString(key);
}
}
}

View File

@ -0,0 +1,17 @@
package com.etheller.warsmash.viewer5;
import com.badlogic.gdx.audio.Sound;
public class AudioBufferSource {
public Sound buffer;
public void connect(final AudioPanner panner) {
}
public void start(final int value) {
if (this.buffer != null) {
this.buffer.play(1);
}
}
}

View File

@ -3,6 +3,8 @@ package com.etheller.warsmash.viewer5;
public class AudioContext {
private boolean running = false;
public Listener listener = new Listener();
public AudioDestination destination = new AudioDestination() {
};
public void suspend() {
this.running = false;
@ -44,4 +46,22 @@ public class AudioContext {
}
}
public AudioPanner createPanner() {
return new AudioPanner() {
@Override
public void setPosition(final float x, final float y, final float z) {
System.err.println("audio panner set position not implemented");
}
@Override
public void connect(final AudioDestination destination) {
System.err.println("audio panner connect dest not implemented");
}
};
}
public AudioBufferSource createBufferSource() {
return new AudioBufferSource();
}
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer5;
public interface AudioDestination {
}

View File

@ -0,0 +1,10 @@
package com.etheller.warsmash.viewer5;
public abstract class AudioPanner {
public abstract void setPosition(float x, float y, float z);
public float maxDistance;
public float refDistance;
public abstract void connect(AudioDestination destination);
}

View File

@ -7,5 +7,9 @@ public abstract class EmittedObject<MODEL_INSTANCE extends ModelInstance, EMITTE
public EMITTER emitter;
public int index;
public EmittedObject(final EMITTER emitter) {
this.emitter = emitter;
}
protected abstract void bind(int flags);
}

View File

@ -0,0 +1,35 @@
package com.etheller.warsmash.viewer5;
import java.io.InputStream;
import com.etheller.warsmash.common.LoadGenericCallback;
public final class GenericResource extends Resource {
public Object data; // TODO this likely won't work, just brainstorming until I get to the part of
// using the data
private final LoadGenericCallback callback;
public GenericResource(final ModelViewer viewer, final String extension, final PathSolver pathSolver,
final String fetchUrl, final LoadGenericCallback callback) {
super(viewer, extension, pathSolver, fetchUrl);
this.callback = callback;
}
@Override
protected void lateLoad() {
}
@Override
protected void load(final InputStream src, final Object options) {
this.data = this.callback.call(src);
}
@Override
protected void error(final Exception e) {
}
}

View File

@ -0,0 +1,14 @@
package com.etheller.warsmash.viewer5;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public abstract class HandlerResource<HANDLER extends ResourceHandler> extends Resource {
public final HANDLER handler;
public HandlerResource(final ModelViewer viewer, final String extension, final PathSolver pathSolver,
final String fetchUrl, final HANDLER handler) {
super(viewer, extension, pathSolver, fetchUrl);
this.handler = handler;
}
}

View File

@ -6,13 +6,13 @@ import java.util.List;
import com.etheller.warsmash.viewer5.handlers.ModelHandler;
import com.etheller.warsmash.viewer5.handlers.ModelInstanceDescriptor;
public abstract class Model<HANDLER extends ModelHandler> extends Resource<HANDLER> {
public abstract class Model<HANDLER extends ModelHandler> extends HandlerResource<HANDLER> {
public Bounds bounds;
public List<ModelInstance> preloadedInstances;
public Model(final HANDLER handler, final ModelViewer viewer, final String extension, final PathSolver pathSolver,
final String fetchUrl) {
super(viewer, handler, extension, pathSolver, fetchUrl);
super(viewer, extension, pathSolver, fetchUrl, handler);
this.bounds = new Bounds();
this.preloadedInstances = new ArrayList<>();
}

View File

@ -11,7 +11,10 @@ import java.util.Set;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.etheller.warsmash.common.FetchDataTypeName;
import com.etheller.warsmash.common.LoadGenericCallback;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.viewer5.gl.ClientBuffer;
import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
@ -19,8 +22,8 @@ import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
public class ModelViewer {
private final DataSource dataSource;
public final CanvasProvider canvas;
public List<Resource<?>> resources;
public Map<String, Resource<?>> fetchCache;
public List<Resource> resources;
public Map<String, Resource> fetchCache;
public int frameTime;
public GL20 gl;
public WebGL webGL;
@ -30,7 +33,8 @@ public class ModelViewer {
private int updatedParticles;
public int frame;
public final int rectBuffer;
private final boolean enableAudio;
public ClientBuffer buffer;
public boolean audioEnabled;
private final Map<Model, List<TextureMapper>> textureMappers;
private final Set<ResourceHandler> handlers;
@ -51,6 +55,7 @@ public class ModelViewer {
this.frame = 0;
this.rectBuffer = this.gl.glGenBuffer();
this.buffer = new ClientBuffer(this.gl);
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.rectBuffer);
final ByteBuffer temp = ByteBuffer.allocate(6);
temp.put((byte) 0);
@ -61,10 +66,15 @@ public class ModelViewer {
temp.put((byte) 3);
temp.clear();
this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, temp.capacity(), temp, GL20.GL_STATIC_DRAW);
this.enableAudio = false;
this.audioEnabled = false;
this.textureMappers = new HashMap<Model, List<TextureMapper>>();
}
public boolean enableAudio() {
this.audioEnabled = true;
return this.audioEnabled;
}
public boolean addHandler(ResourceHandler handler) {
if (handler != null) {
@ -115,7 +125,7 @@ public class ModelViewer {
return null;
}
public Resource<?> load(final String src, final PathSolver pathSolver, final Object solverParams) {
public Resource load(final String src, final PathSolver pathSolver, final Object solverParams) {
String finalSrc = src;
String extension = "";
boolean isFetch = false;
@ -139,7 +149,7 @@ public class ModelViewer {
// Is there a handler for this file type?
if (handlerAndDataType != null) {
if (isFetch) {
final Resource<?> resource = this.fetchCache.get(finalSrc);
final Resource resource = this.fetchCache.get(finalSrc);
if (resource != null) {
return resource;
@ -147,7 +157,7 @@ public class ModelViewer {
}
final ResourceHandler handler = (ResourceHandler) handlerAndDataType[0];
final Resource<?> resource = handler.construct(new ResourceHandlerConstructionParams(this, handler,
final Resource resource = handler.construct(new ResourceHandlerConstructionParams(this, handler,
extension, pathSolver, isFetch ? finalSrc : ""));
this.resources.add(resource);
@ -179,10 +189,55 @@ public class ModelViewer {
return this.fetchCache.containsKey(key);
}
public Resource<?> get(final String key) {
public Resource get(final String key) {
return this.fetchCache.get(key);
}
/**
* Load something generic.
*
* Unlike load(), this does not use handlers or construct any internal objects.
*
* `dataType` can be one of: `"image"`, `"string"`, `"arrayBuffer"`, `"blob"`.
*
* If `callback` isn't given, the resource's `data` is the fetch data, according
* to `dataType`.
*
* If `callback` is given, the resource's `data` is the value returned by it
* when called with the fetch data.
*
* If `callback` returns a promise, the resource's `data` will be whatever the
* promise resolved to.
*/
public GenericResource loadGeneric(final String path, final FetchDataTypeName dataType,
final LoadGenericCallback callback) {
final Resource cachedResource = this.fetchCache.get(path);
if (cachedResource != null) {
// Technically also non-generic resources can be returned here, since the fetch
// cache is shared.
// That being said, this should be used for generic resources, and it makes the
// typing a lot easier.
return (GenericResource) cachedResource;
}
final GenericResource resource = new GenericResource(this, null, null, path, callback);
this.resources.add(resource);
this.fetchCache.put(path, resource);
// TODO this is a synchronous hack, skipped some Ghostwolf code
try {
resource.loadData(this.dataSource.getResourceAsStream(path), null);
}
catch (final IOException e) {
throw new IllegalStateException("Unable to load data: " + path);
}
return resource;
}
public void updateAndRender() {
this.update();
this.startFrame();
@ -191,7 +246,7 @@ public class ModelViewer {
// public Resource loadGeneric(String path, String dataType, )
public boolean unload(final Resource<?> resource) {
public boolean unload(final Resource resource) {
// TODO Auto-generated method stub
final String fetchUrl = resource.fetchUrl;
if (!"".equals(fetchUrl)) {

View File

@ -2,11 +2,8 @@ package com.etheller.warsmash.viewer5;
import java.io.InputStream;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public abstract class Resource<HANDLER extends ResourceHandler> {
public abstract class Resource {
public final ModelViewer viewer;
public final HANDLER handler;
public final String extension;
public final String fetchUrl;
public boolean ok;
@ -14,10 +11,9 @@ public abstract class Resource<HANDLER extends ResourceHandler> {
public final PathSolver pathSolver;
public final Object solverParams = null;
public Resource(final ModelViewer viewer, final HANDLER handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
public Resource(final ModelViewer viewer, final String extension, final PathSolver pathSolver,
final String fetchUrl) {
this.viewer = viewer;
this.handler = handler;
this.extension = extension;
this.pathSolver = pathSolver;
this.fetchUrl = fetchUrl;

View File

@ -35,8 +35,8 @@ public class Scene {
public int visibleCells;
public int visibleInstances;
public int updatedParticles;
private boolean audioEnabled;
private AudioContext audioContext;
public boolean audioEnabled;
public AudioContext audioContext;
private final List<ModelInstance> instances;
private final int currentInstance;

View File

@ -2,12 +2,12 @@ package com.etheller.warsmash.viewer5;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
public abstract class Texture extends Resource<ResourceHandler> {
public abstract class Texture extends HandlerResource<ResourceHandler> {
private com.badlogic.gdx.graphics.Texture gdxTexture;
public Texture(final ModelViewer viewer, final ResourceHandler handler, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
super(viewer, handler, extension, pathSolver, fetchUrl);
super(viewer, extension, pathSolver, fetchUrl, handler);
}
public void setGdxTexture(final com.badlogic.gdx.graphics.Texture gdxTexture) {

View File

@ -0,0 +1,54 @@
package com.etheller.warsmash.viewer5.gl;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import com.badlogic.gdx.graphics.GL20;
public class ClientBuffer {
private final GL20 gl;
private final int buffer;
private int size;
private ByteBuffer arrayBuffer;
public ByteBuffer byteView;
public FloatBuffer floatView;
public ClientBuffer(final GL20 gl) {
this(gl, 4);
}
public ClientBuffer(final GL20 gl, final int size) {
this.gl = gl;
this.buffer = gl.glGenBuffer();
this.arrayBuffer = null;
this.reserve(size);
}
public void reserve(final int size) {
if (this.size < size) {
// Ensure the size is on a 4 byte boundary.
this.size = (int) Math.ceil(size / 4.) * 4;
this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.buffer);
this.arrayBuffer = ByteBuffer.allocate(this.size);
this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, this.size, this.arrayBuffer, GL20.GL_DYNAMIC_DRAW);
this.byteView = this.arrayBuffer;
this.floatView = this.arrayBuffer.asFloatBuffer();
}
}
public void bindAndUpdate() {
bindAndUpdate(this.size);
}
public void bindAndUpdate(final int size) {
final GL20 gl = this.gl;
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.buffer);
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, size, this.byteView);
}
}

View File

@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers;
import java.util.List;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Resource;
import com.etheller.warsmash.viewer5.HandlerResource;
public abstract class ResourceHandler {
public ResourceHandler handler;
@ -12,5 +12,5 @@ public abstract class ResourceHandler {
public abstract boolean load(ModelViewer modelViewer);
public abstract Resource<?> construct(ResourceHandlerConstructionParams params);
public abstract HandlerResource<?> construct(ResourceHandlerConstructionParams params);
}

View File

@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.blp;
import java.util.ArrayList;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Resource;
import com.etheller.warsmash.viewer5.HandlerResource;
import com.etheller.warsmash.viewer5.handlers.ResourceHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
@ -20,7 +20,7 @@ public class BlpHandler extends ResourceHandler {
}
@Override
public Resource<?> construct(final ResourceHandlerConstructionParams params) {
public HandlerResource<?> construct(final ResourceHandlerConstructionParams params) {
return new BlpTexture(params.getViewer(), params.getHandler(), params.getExtension(), params.getPathSolver(),
params.getFetchUrl());
}

View File

@ -12,22 +12,26 @@ import com.etheller.warsmash.util.War3ID;
public class AnimatedObject {
public MdxModel model;
public Map<War3ID, Sd<?>> timelines;
public Map<String, byte[]> variants;
public AnimatedObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.AnimatedObject object) {
this.model = model;
this.timelines = new HashMap<>();
this.variants = new HashMap<>();
for (final Timeline<?> timeline : object.getTimelines()) {
this.timelines.put(timeline.getName(), createTypedSd(model, timeline));
}
}
public int getScalarValue(final float[] out, final War3ID name, final MdxComplexInstance instance,
final float defaultValue) {
final Sd<?> animation = this.timelines.get(name);
public int getScalarValue(final float[] out, final War3ID name, final int sequence, final int frame,
final int counter, final float defaultValue) {
if (sequence != -1) {
final Sd<?> animation = this.timelines.get(name);
if (animation instanceof ScalarSd) {
return ((ScalarSd) animation).getValue(out, instance);
if (animation instanceof ScalarSd) {
return ((ScalarSd) animation).getValue(out, sequence, frame, counter);
}
}
out[0] = defaultValue;
@ -35,12 +39,14 @@ public class AnimatedObject {
return -1;
}
public int getScalarValue(final long[] out, final War3ID name, final MdxComplexInstance instance,
final long defaultValue) {
final Sd<?> animation = this.timelines.get(name);
public int getScalarValue(final long[] out, final War3ID name, final int sequence, final int frame,
final int counter, final long defaultValue) {
if (sequence != -1) {
final Sd<?> animation = this.timelines.get(name);
if (animation instanceof UInt32Sd) {
return ((UInt32Sd) animation).getValue(out, instance);
if (animation instanceof UInt32Sd) {
return ((UInt32Sd) animation).getValue(out, sequence, frame, counter);
}
}
out[0] = defaultValue;
@ -48,12 +54,14 @@ public class AnimatedObject {
return -1;
}
public int getVectorValue(final float[] out, final War3ID name, final MdxComplexInstance instance,
final float[] defaultValue) {
final Sd<?> animation = this.timelines.get(name);
public int getVectorValue(final float[] out, final War3ID name, final int sequence, final int frame,
final int counter, final float[] defaultValue) {
if (sequence != -1) {
final Sd<?> animation = this.timelines.get(name);
if (animation instanceof VectorSd) {
return ((VectorSd) animation).getValue(out, instance);
if (animation instanceof VectorSd) {
return ((VectorSd) animation).getValue(out, sequence, frame, counter);
}
}
System.arraycopy(defaultValue, 0, out, 0, 3);
@ -61,12 +69,14 @@ public class AnimatedObject {
return -1;
}
public int getQuadValue(final float[] out, final War3ID name, final MdxComplexInstance instance,
final float[] defaultValue) {
final Sd<?> animation = this.timelines.get(name);
public int getQuadValue(final float[] out, final War3ID name, final int sequence, final int frame,
final int counter, final float[] defaultValue) {
if (sequence != -1) {
final Sd<?> animation = this.timelines.get(name);
if (animation instanceof QuaternionSd) {
return ((QuaternionSd) animation).getValue(out, instance);
if (animation instanceof QuaternionSd) {
return ((QuaternionSd) animation).getValue(out, sequence, frame, counter);
}
}
System.arraycopy(defaultValue, 0, out, 0, 4);
@ -74,6 +84,38 @@ public class AnimatedObject {
return -1;
}
public void addVariants(final War3ID name, final String variantName) {
final Sd<?> timeline = this.timelines.get(name);
final int sequences = this.model.getSequences().size();
final byte[] variants = new byte[sequences];
if (timeline != null) {
for (int i = 0; i < sequences; i++) {
if (timeline.isVariant(i)) {
variants[i] = 1;
}
}
}
this.variants.put(variantName, variants);
}
public void addVariantIntersection(final String[] names, final String variantName) {
final int sequences = this.model.getSequences().size();
final byte[] variants = new byte[sequences];
for (int i = 0; i < sequences; i++) {
for (final String name : names) {
final byte[] variantsAtName = this.variants.get(name);
if ((variantsAtName != null) && (variantsAtName[i] != 0)) {
variants[i] = 1;
}
}
}
this.variants.put(variantName, variants);
}
public boolean isVariant(final War3ID name, final int sequence) {
final Sd<?> timeline = this.timelines.get(name);

View File

@ -24,7 +24,7 @@ public class Attachment extends GenericObject {
}
@Override
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KATV.getWar3id(), instance, 1);
public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KATV.getWar3id(), sequence, frame, counter, 1);
}
}

View File

@ -25,7 +25,8 @@ public class AttachmentInstance {
final MdxComplexInstance internalInstance = this.internalInstance;
if (internalInstance.model.ok) {
this.attachment.getVisibility(visbilityHeap, this.instance);
this.attachment.getVisibility(visbilityHeap, this.instance.sequence, this.instance.frame,
this.instance.counter);
if (visbilityHeap[0] > 0.1) {
// The parent instance might not actually be in a scene.

View File

@ -1,6 +1,6 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class Batch {
public class Batch implements GenericIndexed {
public int index;
public Geoset geoset;
public Layer layer;
@ -13,4 +13,9 @@ public class Batch {
this.isExtended = isExtended;
}
@Override
public int getIndex() {
return this.index;
}
}

View File

@ -1,6 +1,5 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.graphics.GL20;
@ -9,16 +8,14 @@ import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
public class BatchGroup {
public class BatchGroup extends GenericGroup {
private final MdxModel model;
private final boolean isExtended;
private final List<Integer> objects;
public final boolean isExtended;
public BatchGroup(final MdxModel model, final boolean isExtended) {
this.model = model;
this.isExtended = isExtended;
this.objects = new ArrayList<>(); // TODO IntArrayList
}
public void render(final MdxComplexInstance instance) {

View File

@ -11,9 +11,9 @@ public class Bone extends GenericObject {
}
@Override
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) {
if (this.geosetAnimation != null) {
return this.geosetAnimation.getAlpha(out, instance);
return this.geosetAnimation.getAlpha(out, sequence, frame, counter);
}
out[0] = 1;

View File

@ -22,16 +22,16 @@ public class Camera extends AnimatedObject {
this.targetPosition = camera.getTargetPosition();
}
public int getPositionTranslation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KCTR.getWar3id(), instance, this.position);
public int getPositionTranslation(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KCTR.getWar3id(), sequence, frame, counter, this.position);
}
public int getTargetTranslation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KTTR.getWar3id(), instance, this.targetPosition);
public int getTargetTranslation(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KTTR.getWar3id(), sequence, frame, counter, this.targetPosition);
}
public int getRotation(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KCRL.getWar3id(), instance, 0);
public int getRotation(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KCRL.getWar3id(), sequence, frame, counter, 0);
}
}

View File

@ -1,8 +1,5 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
@ -10,15 +7,12 @@ import com.etheller.warsmash.viewer5.Model;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SkeletalNode;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public class EmitterGroup {
public class EmitterGroup extends GenericGroup {
private final MdxModel model;
private final List<Integer> objects;
public EmitterGroup(final MdxModel model) {
this.model = model;
this.objects = new ArrayList<>();
}
public void render(final MdxComplexInstance instance) {
@ -55,10 +49,17 @@ public class EmitterGroup {
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 1);
for (final int index : this.objects) {
GeometryEmitterFuncs.renderEmitter((MdxEmitter<?, ?, ?>) nodes[index].object, shader);
}
}
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_tail"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_color"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_health"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p3"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p2"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p1"), 0);
Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p0"), 0);
protected abstract void renderEmitter(EmitterObject emitter, ShaderProgram shader);
}
}

View File

@ -1,11 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.EmittedObject;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public abstract class EventObjectEmitter<EMITTER_OBJECT extends EventObjectEmitterObject, EMITTED_OBJECT extends EmittedObject<MdxComplexInstance, ? extends MdxEmitter<MdxComplexInstance, EMITTER_OBJECT, EMITTED_OBJECT>>>
extends MdxEmitter<MdxComplexInstance, EMITTER_OBJECT, EMITTED_OBJECT> {
private final int number = 0;
private static final long[] valueHeap = { 0L };
private long lastValue = 0;
public EventObjectEmitter(final MdxComplexInstance instance, final EMITTER_OBJECT emitterObject) {
super(instance, emitterObject);
@ -18,7 +19,15 @@ public abstract class EventObjectEmitter<EMITTER_OBJECT extends EventObjectEmitt
if (instance.allowParticleSpawn) {
final EMITTER_OBJECT emitterObject = this.emitterObject;
emitterObject.getV
emitterObject.getValue(valueHeap, instance);
final long value = valueHeap[0];
if ((value == 1) && (value != this.lastValue)) {
this.currentEmission += 1;
}
this.lastValue = value;
}
}

View File

@ -1,34 +1,85 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.files.FileHandle;
import com.etheller.warsmash.common.FetchDataTypeName;
import com.etheller.warsmash.common.LoadGenericCallback;
import com.etheller.warsmash.util.MappedData;
import com.etheller.warsmash.util.MappedDataRow;
import com.etheller.warsmash.viewer5.GenericResource;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public abstract class EventObjectEmitterObject extends GenericObject implements EmitterObject {
private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() {
@Override
public Object call(final InputStream data) {
final StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) {
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append("\n");
}
}
catch (final UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
catch (final IOException e) {
throw new RuntimeException(e);
}
return new MappedData(stringBuilder.toString());
}
};
private static final LoadGenericCallback decodedDataCallback = new LoadGenericCallback() {
@Override
public Object call(final InputStream data) {
final FileHandle temp = new FileHandle("sound.wav") {
@Override
public InputStream read() {
return data;
};
};
return Gdx.audio.newSound(temp);
}
};
private int geometryEmitterType = -1;
private final String type;
private final String id;
private final long[] keyFrames;
private long globalSequence;
private final long[] defval = { 1 };
private MdxModel internalModel;
private Texture internalTexture;
private float[][] colors;
private float[] intervalTimes;
private float scale;
private int columns;
private int rows;
private float lifeSpan;
private int blendSrc;
private int blendDst;
private float[][] intervals;
private float distanceCutoff;
public MdxModel internalModel;
public Texture internalTexture;
public float[][] colors;
public float[] intervalTimes;
public float scale;
public int columns;
public int rows;
public float lifeSpan;
public int blendSrc;
public int blendDst;
public float[][] intervals;
public float distanceCutoff;
private float maxDistance;
private float minDistance;
public float minDistance;
private float pitch;
private float pitchVariance;
private float volume;
// private AudioBuffer[] decodedBuffers;
public List<Sound> decodedBuffers;
/**
* If this is an SPL/UBR emitter object, ok will be set to true if the tables
* are loaded.
@ -38,7 +89,7 @@ public abstract class EventObjectEmitterObject extends GenericObject implements
*
* The particles will simply be black.
*/
private final boolean ok = false;
private boolean ok = false;
public EventObjectEmitterObject(final MdxModel model,
final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) {
@ -64,6 +115,189 @@ public abstract class EventObjectEmitterObject extends GenericObject implements
this.type = type;
this.id = id;
this.keyFrames = eventObject.getKeyFrames();
final int globalSequenceId = eventObject.getGlobalSequenceId();
if (globalSequenceId != -1) {
this.globalSequence = model.getGlobalSequences().get(globalSequenceId);
}
final List<GenericResource> tables = new ArrayList<>();
final PathSolver pathSolver = model.pathSolver;
final Object solverParams = model.solverParams;
if ("SPN".equals(type)) {
tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc,
FetchDataTypeName.SLK, mappedDataCallback));
}
else if ("SPL".equals(type)) {
tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc,
FetchDataTypeName.SLK, mappedDataCallback));
}
else if ("UBR".equals(type)) {
tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\UberSplatData.slk", solverParams).finalSrc,
FetchDataTypeName.SLK, mappedDataCallback));
}
else if ("SND".equals(type)) {
if (!model.reforged) {
tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimLookups.slk", solverParams).finalSrc,
FetchDataTypeName.SLK, mappedDataCallback));
}
tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimSounds.slk", solverParams).finalSrc,
FetchDataTypeName.SLK, mappedDataCallback));
}
else {
// Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named
// "Point01".
return;
}
// TODO I am scrapping some async stuff with promises here from the JS and
// calling load
this.load(tables);
}
private void load(final List<GenericResource> tables) {
final MappedData firstTable = (MappedData) tables.get(0).data;
final MappedDataRow row = firstTable.getRow(this.id);
if (row != null) {
final MdxModel model = this.model;
final ModelViewer viewer = model.viewer;
final PathSolver pathSolver = model.pathSolver;
if ("SPN".equals(this.type)) {
this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"),
pathSolver, model.solverParams);
if (this.internalModel != null) {
// TODO javascript async code removed here
// this.internalModel.whenLoaded((model) => this.ok = model.ok)
this.ok = this.internalModel.ok;
}
}
else if ("SPL".equals(this.type) || "UBR".equals(this.type)) {
final String texturesExt = model.reforged ? ".dds" : ".blp";
this.internalTexture = (Texture) viewer.load(
"ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver,
model.solverParams);
this.scale = (Float) row.get("Scale");
this.colors = new float[][] {
{ ((Float) row.get("StartR")).floatValue(), ((Float) row.get("StartG")).floatValue(),
((Float) row.get("StartB")).floatValue(), ((Float) row.get("StartA")).floatValue() },
{ ((Float) row.get("MiddleR")).floatValue(), ((Float) row.get("MiddleG")).floatValue(),
((Float) row.get("MiddleB")).floatValue(), ((Float) row.get("MiddleA")).floatValue() },
{ ((Float) row.get("EndR")).floatValue(), ((Float) row.get("EndG")).floatValue(),
((Float) row.get("EndB")).floatValue(), ((Float) row.get("EndA")).floatValue() } };
if ("SPL".equals(this.type)) {
this.columns = ((Number) row.get("Columns")).intValue();
this.rows = ((Number) row.get("Rows")).intValue();
this.lifeSpan = ((Number) row.get("Lifespan")).floatValue()
+ ((Number) row.get("Decay")).floatValue();
this.intervals = new float[][] {
{ ((Float) row.get("UVLifespanStart")).floatValue(),
((Float) row.get("UVLifespanEnd")).floatValue(),
((Float) row.get("LifespanRepeat")).floatValue() },
{ ((Float) row.get("UVDecayStart")).floatValue(),
((Float) row.get("UVDecayEnd")).floatValue(),
((Float) row.get("DecayRepeat")).floatValue() }, };
}
else {
this.columns = 1;
this.rows = 1;
this.lifeSpan = ((Number) row.get("BirthTime")).floatValue()
+ ((Number) row.get("PauseTime")).floatValue() + ((Number) row.get("Decay")).floatValue();
this.intervalTimes = new float[] { ((Number) row.get("BirthTime")).floatValue(),
((Number) row.get("PauseTime")).floatValue(), ((Number) row.get("Decay")).floatValue() };
}
final int[] blendModes = FilterMode
.emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode
.fromId(((Number) row.get("BlendMode")).intValue()));
this.blendSrc = blendModes[0];
this.blendDst = blendModes[1];
this.ok = true;
}
else if ("SND".equals(this.type)) {
// Only load sounds if audio is enabled.
// This is mostly to save on bandwidth and loading time, especially when loading
// full maps.
if (viewer.audioEnabled) {
final MappedData animSounds = (MappedData) tables.get(1).data;
final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel"));
if (animSoundsRow != null) {
this.distanceCutoff = ((Number) animSoundsRow.get("DistanceCutoff")).floatValue();
this.maxDistance = ((Number) animSoundsRow.get("MaxDistance")).floatValue();
this.minDistance = ((Number) animSoundsRow.get("MinDistance")).floatValue();
this.pitch = ((Number) animSoundsRow.get("Pitch")).floatValue();
this.pitchVariance = ((Number) animSoundsRow.get("PitchVariance")).floatValue();
this.volume = ((Number) animSoundsRow.get("Volume")).floatValue();
final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(",");
final GenericResource[] resources = new GenericResource[fileNames.length];
for (int i = 0; i < fileNames.length; i++) {
resources[i] = viewer.loadGeneric(
pathSolver.solve(((String) animSoundsRow.get("DirectoryBase")) + fileNames[i],
model.solverParams).finalSrc,
FetchDataTypeName.ARRAY_BUFFER, decodedDataCallback);
}
// TODO JS async removed
for (final GenericResource resource : resources) {
this.decodedBuffers.add((Sound) resource.data);
}
this.ok = true;
}
}
}
else {
System.err.println("Unknown event object ID: " + this.type + this.id);
}
}
}
public int getValue(final long[] out, final MdxComplexInstance instance) {
if (this.globalSequence != -1) {
return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence);
}
else if (instance.sequence != -1) {
final long[] interval = this.model.getSequences().get(instance.sequence).getInterval();
return this.getValueAtTime(out, instance.frame, interval[0], interval[1]);
}
else {
out[0] = this.defval[0];
return -1;
}
}
public int getValueAtTime(final long[] out, final long frame, final long start, final long end) {
if ((frame >= start) && (frame <= end)) {
for (int i = this.keyFrames.length - 1; i > -1; i--) {
if (this.keyFrames[i] < start) {
out[0] = 0;
return i;
}
else if (this.keyFrames[i] <= frame) {
out[0] = 1;
return i;
}
}
}
out[0] = 0;
return -1;
}
}

View File

@ -0,0 +1,55 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.List;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.viewer5.AudioBufferSource;
import com.etheller.warsmash.viewer5.AudioContext;
import com.etheller.warsmash.viewer5.AudioPanner;
import com.etheller.warsmash.viewer5.EmittedObject;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
public class EventObjectSnd extends EmittedObject<MdxComplexInstance, EventObjectSndEmitter> {
public EventObjectSnd(final EventObjectSndEmitter emitter) {
super(emitter);
}
@Override
protected void bind(final int flags) {
final EventObjectSndEmitter emitter = this.emitter;
final MdxComplexInstance instance = emitter.instance;
final ModelViewer viewer = instance.model.viewer;
final Scene scene = instance.scene;
// Is audio enabled both viewer-wide and in this scene?
if (viewer.audioEnabled && scene.audioEnabled) {
final EventObjectEmitterObject emitterObject = emitter.emitterObject;
final MdxNode node = instance.nodes[emitterObject.index];
final AudioContext audioContext = scene.audioContext;
final List<Sound> decodedBuffers = emitterObject.decodedBuffers;
final AudioPanner panner = audioContext.createPanner();
final AudioBufferSource source = audioContext.createBufferSource();
final Vector3 location = node.worldLocation;
// Panner settings
panner.setPosition(location.x, location.y, location.z);
panner.maxDistance = emitterObject.distanceCutoff;
panner.refDistance = emitterObject.minDistance;
panner.connect(audioContext.destination);
// Source.
source.buffer = decodedBuffers.get((int) (Math.random() * decodedBuffers.size()));
source.connect(panner);
// Make a sound.
source.start(0);
}
}
@Override
public void update(final float dt) {
}
}

View File

@ -0,0 +1,14 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class EventObjectSndEmitter extends EventObjectEmitter<EventObjectEmitterObject, EventObjectSnd> {
public EventObjectSndEmitter(final MdxComplexInstance instance, final EventObjectEmitterObject emitterObject) {
super(instance, emitterObject);
}
@Override
protected EventObjectSnd createObject() {
return new EventObjectSnd(this);
}
}

View File

@ -0,0 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class EventObjectSplEmitter extends EventObjectEmitter<EventObjectEmitterObject, EventObjectSplUbr> {
public EventObjectSplEmitter(final MdxComplexInstance instance, final EventObjectEmitterObject emitterObject) {
super(instance, emitterObject);
}
@Override
protected EventObjectSplUbr createObject() {
return new EventObjectSplUbr(this);
}
}

View File

@ -1,12 +1,63 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.viewer5.EmittedObject;
public class EventObjectSplUbr<EMITTER extends MdxEmitter<?, ?, ?>> extends EmittedObject<MdxComplexInstance, EMITTER> {
private final float[] vertices = new float[12];
public class EventObjectSplUbr
extends EmittedObject<MdxComplexInstance, EventObjectEmitter<EventObjectEmitterObject, EventObjectSplUbr>> {
private static final Vector3 vertexHeap = new Vector3();
public final float[] vertices = new float[12];
public EventObjectSplUbr(final EventObjectEmitter<EventObjectEmitterObject, EventObjectSplUbr> emitter) {
super(emitter);
}
@Override
protected void bind(final int flags) {
final EventObjectEmitter<EventObjectEmitterObject, EventObjectSplUbr> emitter = this.emitter;
final MdxComplexInstance instance = emitter.instance;
final EventObjectEmitterObject emitterObject = emitter.emitterObject;
final float[] vertices = this.vertices;
final float scale = emitterObject.scale;
final MdxNode node = instance.nodes[emitterObject.index];
final Matrix4 worldMatrix = node.worldMatrix;
this.health = emitterObject.lifeSpan;
vertexHeap.x = scale;
vertexHeap.y = scale;
vertexHeap.prj(worldMatrix);
vertices[0] = vertexHeap.x;
vertices[1] = vertexHeap.y;
vertices[2] = vertexHeap.z;
vertexHeap.x = -scale;
vertexHeap.y = scale;
vertexHeap.prj(worldMatrix);
vertices[3] = vertexHeap.x;
vertices[4] = vertexHeap.y;
vertices[5] = vertexHeap.z;
vertexHeap.x = -scale;
vertexHeap.y = -scale;
vertexHeap.prj(worldMatrix);
vertices[6] = vertexHeap.x;
vertices[7] = vertexHeap.y;
vertices[8] = vertexHeap.z;
vertexHeap.x = scale;
vertexHeap.y = -scale;
vertexHeap.prj(worldMatrix);
vertices[9] = vertexHeap.x;
vertices[10] = vertexHeap.y;
vertices[11] = vertexHeap.z;
}
@Override
public void update(final float dt) {
this.health -= dt;
}
}

View File

@ -0,0 +1,48 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.viewer5.EmittedObject;
import com.etheller.warsmash.viewer5.Scene;
public class EventObjectSpn extends EmittedObject<MdxComplexInstance, EventObjectSpnEmitter> {
private final MdxComplexInstance internalInstance;
public EventObjectSpn(final EventObjectSpnEmitter emitter) {
super(emitter);
final EventObjectEmitterObject emitterObject = emitter.emitterObject;
final MdxModel internalModel = emitterObject.internalModel;
this.internalInstance = (MdxComplexInstance) internalModel.addInstance();
}
@Override
protected void bind(final int flags) {
final EventObjectSpnEmitter emitter = this.emitter;
final MdxComplexInstance instance = emitter.instance;
final Scene scene = instance.scene;
final MdxNode node = instance.nodes[emitter.emitterObject.index];
final MdxComplexInstance internalInstance = this.internalInstance;
internalInstance.setSequence(0);
internalInstance.setTransformation(node.worldLocation, node.worldRotation, node.worldScale);
internalInstance.show();
scene.addInstance(internalInstance);
this.health = 1;
}
@Override
public void update(final float dt) {
final MdxComplexInstance instance = this.internalInstance;
final MdxModel model = (MdxModel) instance.model;
// Once the sequence finishes, this event object dies
if (instance.frame >= model.getSequences().get(0).getInterval()[1]) {
this.health = 0;
instance.hide();
}
}
}

View File

@ -0,0 +1,14 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class EventObjectSpnEmitter extends EventObjectEmitter<EventObjectEmitterObject, EventObjectSpn> {
public EventObjectSpnEmitter(final MdxComplexInstance instance, final EventObjectEmitterObject emitterObject) {
super(instance, emitterObject);
}
@Override
protected EventObjectSpn createObject() {
return new EventObjectSpn(this);
}
}

View File

@ -0,0 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class EventObjectUbrEmitter extends EventObjectEmitter<EventObjectEmitterObject, EventObjectSplUbr> {
public EventObjectUbrEmitter(final MdxComplexInstance instance, final EventObjectEmitterObject emitterObject) {
super(instance, emitterObject);
}
@Override
protected EventObjectSplUbr createObject() {
return new EventObjectSplUbr(this);
}
}

View File

@ -0,0 +1,13 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.List;
public class GenericGroup {
public final List<Integer> objects;
public GenericGroup() {
this.objects = new ArrayList<>(); // TODO IntArrayList
}
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public interface GenericIndexed {
public int getIndex();
}

View File

@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.util.RenderMathUtils;
public class GenericObject extends AnimatedObject {
public class GenericObject extends AnimatedObject implements GenericIndexed {
public final int index;
public final String name;
@ -113,21 +113,24 @@ public class GenericObject extends AnimatedObject {
* Many of the generic objects have animated visibilities. This is a generic
* getter to allow the code to be consistent.
*/
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) {
out[0] = 1;
return -1;
}
public int getTranslation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KGTR.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ZERO);
public int getTranslation(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KGTR.getWar3id(), sequence, frame, counter,
RenderMathUtils.FLOAT_VEC3_ZERO);
}
public int getRotation(final float[] out, final MdxComplexInstance instance) {
return this.getQuadValue(out, AnimationMap.KGRT.getWar3id(), instance, RenderMathUtils.FLOAT_QUAT_DEFAULT);
public int getRotation(final float[] out, final int sequence, final int frame, final int counter) {
return this.getQuadValue(out, AnimationMap.KGRT.getWar3id(), sequence, frame, counter,
RenderMathUtils.FLOAT_QUAT_DEFAULT);
}
public int getScale(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KGSC.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ONE);
public int getScale(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KGSC.getWar3id(), sequence, frame, counter,
RenderMathUtils.FLOAT_VEC3_ONE);
}
public boolean isTranslationVariant(final int sequence) {
@ -156,4 +159,9 @@ public class GenericObject extends AnimatedObject {
}
}
@Override
public int getIndex() {
return this.index;
}
}

View File

@ -4,7 +4,9 @@ import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.viewer5.Camera;
@ -12,6 +14,8 @@ import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.TextureMapper;
import com.etheller.warsmash.viewer5.gl.ClientBuffer;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
//The total storage that emitted objects can use.
//This is enough to support all of the MDX geometry emitters.
@ -65,11 +69,11 @@ public class GeometryEmitterFuncs {
private static final Vector3 endHeap = new Vector3();
private static final float[] vectorTemp = new float[3];
public static void bindParticleEmitter2Buffer(final ParticleEmitter2 emitter, final ByteBuffer buffer) {
public static void bindParticleEmitter2Buffer(final ParticleEmitter2 emitter, final ClientBuffer buffer) {
final MdxComplexInstance instance = emitter.instance;
final List<Particle2> objects = emitter.objects;
final ByteBuffer byteView = buffer;
final FloatBuffer floatView = buffer.asFloatBuffer();
final ByteBuffer byteView = buffer.byteView;
final FloatBuffer floatView = buffer.floatView;
final ParticleEmitter2Object emitterObject = emitter.emitterObject;
final int modelSpace = emitterObject.modelSpace;
final float tailLength = emitterObject.tailLength;
@ -203,10 +207,10 @@ public class GeometryEmitterFuncs {
}
}
public static void bindRibbonEmitterBuffer(final RibbonEmitter emitter, final ByteBuffer buffer) {
public static void bindRibbonEmitterBuffer(final RibbonEmitter emitter, final ClientBuffer buffer) {
Ribbon object = emitter.first;
final ByteBuffer byteView = buffer;
final FloatBuffer floatView = buffer.asFloatBuffer();
final ByteBuffer byteView = buffer.byteView;
final FloatBuffer floatView = buffer.floatView;
final RibbonEmitterObject emitterObject = emitter.emitterObject;
final long columns = emitterObject.columns;
final int alive = emitter.alive;
@ -277,6 +281,154 @@ public class GeometryEmitterFuncs {
shader.setUniformf("u_rows", emitterObject.rows);
}
public static void bindEventObjectEmitterBuffer(
final EventObjectEmitter<EventObjectEmitterObject, EventObjectSplUbr> emitter, final ClientBuffer buffer) {
final List<EventObjectSplUbr> objects = emitter.objects;
final FloatBuffer floatView = buffer.floatView;
int offset = 0;
for (final EventObjectSplUbr object : objects) {
final int floatOffset = offset * FLOATS_PER_OBJECT;
final int p0Offset = floatOffset + FLOAT_OFFSET_P0;
final float[] vertices = object.vertices;
floatView.put(p0Offset + 0, vertices[0]);
floatView.put(p0Offset + 1, vertices[1]);
floatView.put(p0Offset + 2, vertices[2]);
floatView.put(p0Offset + 3, vertices[3]);
floatView.put(p0Offset + 4, vertices[4]);
floatView.put(p0Offset + 5, vertices[5]);
floatView.put(p0Offset + 6, vertices[6]);
floatView.put(p0Offset + 7, vertices[7]);
floatView.put(p0Offset + 8, vertices[8]);
floatView.put(p0Offset + 9, vertices[9]);
floatView.put(p0Offset + 10, vertices[10]);
floatView.put(p0Offset + 11, vertices[11]);
floatView.put(floatOffset + FLOAT_OFFSET_HEALTH, object.health);
offset += 1;
}
}
public static void bindEventObjectSplEmitterShader(final EventObjectSplEmitter emitter,
final ShaderProgram shader) {
final TextureMapper textureMapper = emitter.instance.textureMapper;
final EventObjectEmitterObject emitterObject = emitter.emitterObject;
final float[] intervalTimes = emitterObject.intervalTimes;
final float[][] intervals = emitterObject.intervals;
final float[][] colors = emitterObject.colors;
final MdxModel model = emitterObject.model;
final GL20 gl = model.viewer.gl;
final Texture texture = emitterObject.internalTexture;
gl.glBlendFunc(emitterObject.blendSrc, emitterObject.blendDst);
Texture finalTexture = textureMapper.get(texture);
if (finalTexture == null) {
finalTexture = texture;
}
model.viewer.webGL.bindTexture(finalTexture, 0);
shader.setUniformf("u_lifeSpan", emitterObject.lifeSpan);
shader.setUniformf("u_columns", emitterObject.columns);
shader.setUniformf("rows", emitterObject.rows);
// 3 because the uniform is shared with UBR, which has 3 values.
vectorTemp[0] = intervalTimes[0];
vectorTemp[1] = intervalTimes[1];
vectorTemp[2] = 0;
shader.setUniform3fv("u_intervalTimes", vectorTemp, 0, 3);
shader.setUniform3fv("u_intervals[0]", intervals[0], 0, 3);
shader.setUniform3fv("u_intervals[1]", intervals[1], 0, 3);
shader.setUniform3fv("u_colors[0]", colors[0], 0, 3);
shader.setUniform3fv("u_colors[1]", colors[1], 0, 3);
shader.setUniform3fv("u_colors[2]", colors[2], 0, 3);
}
public static void bindEventObjectUbrEmitterShader(final EventObjectUbrEmitter emitter,
final ShaderProgram shader) {
final TextureMapper textureMapper = emitter.instance.textureMapper;
final EventObjectEmitterObject emitterObject = emitter.emitterObject;
final float[] intervalTimes = emitterObject.intervalTimes;
final float[][] colors = emitterObject.colors;
final MdxModel model = emitterObject.model;
final GL20 gl = model.viewer.gl;
final Texture texture = emitterObject.internalTexture;
gl.glBlendFunc(emitterObject.blendSrc, emitterObject.blendDst);
Texture finalTexture = textureMapper.get(texture);
if (finalTexture == null) {
finalTexture = texture;
}
model.viewer.webGL.bindTexture(finalTexture, 0);
shader.setUniformf("u_lifeSpan", emitterObject.lifeSpan);
shader.setUniformf("u_columns", emitterObject.columns);
shader.setUniformf("rows", emitterObject.rows);
shader.setUniform3fv("u_intervalTimes", intervalTimes, 0, 3);
shader.setUniform3fv("u_colors[0]", colors[0], 0, 3);
shader.setUniform3fv("u_colors[1]", colors[1], 0, 3);
shader.setUniform3fv("u_colors[2]", colors[2], 0, 3);
}
public static void renderEmitter(final MdxEmitter<?, ?, ?> emitter, final ShaderProgram shader) {
int alive = emitter.alive;
final EmitterObject emitterObject = emitter.emitterObject;
final int emitterType = emitterObject.getGeometryEmitterType();
if (emitterType == EMITTER_RIBBON) {
alive -= 1;
}
if (alive > 0) {
final ModelViewer viewer = emitter.instance.model.viewer;
final ClientBuffer buffer = viewer.buffer;
final GL20 gl = viewer.gl;
final int size = alive * BYTES_PER_OBJECT;
switch (emitterType) {
case EMITTER_PARTICLE2:
bindParticleEmitter2Buffer((ParticleEmitter2) emitter, buffer);
bindParticleEmitter2Shader((ParticleEmitter2) emitter, shader);
break;
case EMITTER_RIBBON:
bindRibbonEmitterBuffer((RibbonEmitter) emitter, buffer);
bindRibbonEmitterShader((RibbonEmitter) emitter, shader);
break;
case EMITTER_SPLAT:
bindEventObjectEmitterBuffer((EventObjectSplEmitter) emitter, buffer);
bindEventObjectSplEmitterShader((EventObjectSplEmitter) emitter, shader);
break;
default:
bindEventObjectEmitterBuffer((EventObjectUbrEmitter) emitter, buffer);
bindEventObjectUbrEmitterShader((EventObjectUbrEmitter) emitter, shader);
break;
}
buffer.bindAndUpdate(size);
shader.setUniformi("u_emitter", emitterType);
shader.setVertexAttribute("a_p0", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P0);
shader.setVertexAttribute("a_p1", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P1);
shader.setVertexAttribute("a_p2", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P2);
shader.setVertexAttribute("a_p3", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P3);
shader.setVertexAttribute("a_health", 1, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_HEALTH);
shader.setVertexAttribute("a_color", 4, GL20.GL_UNSIGNED_BYTE, true, BYTES_PER_OBJECT, BYTE_OFFSET_COLOR);
shader.setVertexAttribute("a_tail", 1, GL20.GL_UNSIGNED_BYTE, false, BYTES_PER_OBJECT, BYTE_OFFSET_TAIL);
shader.setVertexAttribute("a_leftRightTop", 3, GL20.GL_UNSIGNED_BYTE, false, BYTES_PER_OBJECT,
BYTE_OFFSET_LEFT_RIGHT_TOP);
Gdx.gl30.glDrawArraysInstanced(GL30.GL_TRIANGLES, 0, 6, alive);
}
}
private static final float[] asFloatArray(final Vector3 vec) {
vectorTemp[0] = vec.x;
vectorTemp[1] = vec.y;

View File

@ -74,18 +74,18 @@ public class Geoset {
this.hasObjectAnim = hasAlphaAnim || hasColorAnim;
}
public int getAlpha(final float[] out, final MdxComplexInstance instance) {
public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) {
if (this.geosetAnimation != null) {
return this.geosetAnimation.getAlpha(out, instance);
return this.geosetAnimation.getAlpha(out, sequence, frame, counter);
}
out[0] = 1;
return -1;
}
public int getColor(final float[] out, final MdxComplexInstance instance) {
public int getColor(final float[] out, final int sequence, final int frame, final int counter) {
if (this.geosetAnimation != null) {
return this.geosetAnimation.getAlpha(out, instance);
return this.geosetAnimation.getAlpha(out, sequence, frame, counter);
}
Arrays.fill(out, 1);

View File

@ -19,12 +19,12 @@ public class GeosetAnimation extends AnimatedObject {
this.geosetId = geosetAnimation.getGeosetId();
}
public int getAlpha(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KGAO.getWar3id(), instance, this.alpha);
public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KGAO.getWar3id(), sequence, frame, counter, this.alpha);
}
public int getColor(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KGAC.getWar3id(), instance, this.color);
public int getColor(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KGAC.getWar3id(), sequence, frame, counter, this.color);
}
public boolean isAlphaVariant(final int sequence) {

View File

@ -0,0 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
/**
* An MDX helper.
*/
public class Helper extends GenericObject {
public Helper(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.GenericObject object,
final int index) {
super(model, object, index);
}
}

View File

@ -1,39 +1,121 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
public class Layer {
public MdxModel model;
public com.etheller.warsmash.parsers.mdlx.Layer layer;
public int layerId;
/**
* An MDX layer.
*/
public class Layer extends AnimatedObject {
public int index;
public int priorityPlane;
public int filterMode;
public int textureId;
public int coordId;
public float alpha;
public int index = -666;
public int unshaded;
public int sphereEnvironmentMap;
public int twoSided;
public int unfogged;
public int noDepthTest;
public int noDepthSet;
public boolean depthMaskValue;
public int blendSrc;
public int blendDst;
public boolean blended;
public TextureAnimation textureAnimation;
public Layer(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId,
final int priorityPlane) {
super(model, layer);
this.model = model;
this.layer = layer;
this.layerId = layerId;
this.priorityPlane = priorityPlane;
final FilterMode filterMode2 = layer.getFilterMode();
this.filterMode = filterMode2.ordinal();
final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode = layer.getFilterMode();
final int textureAnimationId = layer.getTextureAnimationId();
final GL20 gl = model.viewer.gl;
this.index = layerId;
this.priorityPlane = priorityPlane;
this.filterMode = filterMode.ordinal();
this.textureId = layer.getTextureId();
// this.coo
this.index
this.coordId = (int) layer.getCoordId();
this.alpha = layer.getAlpha();
final int flags = layer.getFlags();
this.unshaded = flags & 0x1;
this.sphereEnvironmentMap = flags & 0x2;
this.twoSided = flags & 0x10;
this.unfogged = flags & 0x20;
this.noDepthTest = flags & 0x40;
this.noDepthSet = flags & 0x80;
this.depthMaskValue = ((filterMode == com.etheller.warsmash.parsers.mdlx.Layer.FilterMode.NONE)
|| (filterMode == com.etheller.warsmash.parsers.mdlx.Layer.FilterMode.TRANSPARENT));
this.blendSrc = 0;
this.blendDst = 0;
this.blended = (filterMode.ordinal() > 1);
if (this.blended) {
final int[] result = FilterMode.layerFilterMode(filterMode);
this.blendSrc = result[0];
this.blendDst = result[1];
}
if (textureAnimationId != -1) {
final TextureAnimation textureAnimation = model.getTextureAnimations().get(textureAnimationId);
if (textureAnimation != null) {
this.textureAnimation = textureAnimation;
}
}
this.addVariants(AnimationMap.KMTA.getWar3id(), "alpha");
this.addVariants(AnimationMap.KMTF.getWar3id(), "textureId");
}
public void bind(final ShaderProgram shader) {
// TODO Auto-generated method stub
final GL20 gl = this.model.viewer.gl;
// gl.uniform1f(shader.uniforms.u_unshaded, this.unshaded);
shader.setUniformf("u_filterMode", this.filterMode);
if (this.blended) {
gl.glEnable(GL20.GL_BLEND);
gl.glBlendFunc(this.blendSrc, this.blendDst);
}
else {
gl.glDisable(GL20.GL_BLEND);
}
if (this.twoSided != 0) {
gl.glEnable(GL20.GL_CULL_FACE);
}
else {
gl.glDisable(GL20.GL_CULL_FACE);
}
if (this.noDepthTest != 0) {
gl.glDisable(GL20.GL_DEPTH_TEST);
}
else {
gl.glEnable(GL20.GL_DEPTH_TEST);
}
if (this.noDepthSet != 0) {
gl.glDepthMask(false);
}
else {
gl.glDepthMask(this.depthMaskValue);
}
}
public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KMTA.getWar3id(), sequence, frame, counter, this.alpha);
}
public int getTextureId(final long[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KMTF.getWar3id(), sequence, frame, counter, this.textureId);
}
}

View File

@ -0,0 +1,48 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
public class Light extends GenericObject {
private final int type;
private final float[] attenuation;
private final float[] color;
private final float intensity;
private final float[] ambientColor;
private final float ambientIntensity;
public Light(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Light light, final int index) {
super(model, light, index);
this.type = light.getType();
this.attenuation = light.getAttenuation();
this.color = light.getColor();
this.intensity = light.getIntensity();
this.ambientColor = light.getAmbientColor();
this.ambientIntensity = light.getAmbientIntensity();
}
public int getAttenuationStart(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KLAS.getWar3id(), sequence, frame, counter, this.attenuation[0]);
}
public int getAttenuationEnd(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KLAE.getWar3id(), sequence, frame, counter, this.attenuation[1]);
}
public int getIntensity(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KLAI.getWar3id(), sequence, frame, counter, this.intensity);
}
public int getColor(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KLAC.getWar3id(), sequence, frame, counter, this.color);
}
public int getAmbientIntensity(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KLBI.getWar3id(), sequence, frame, counter, this.ambientIntensity);
}
public int getAmbientColor(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KLBC.getWar3id(), sequence, frame, counter, this.ambientColor);
}
}

View File

@ -4,8 +4,8 @@ import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.etheller.warsmash.viewer5.HandlerResource;
import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.Resource;
import com.etheller.warsmash.viewer5.Texture;
import com.etheller.warsmash.viewer5.handlers.ModelHandler;
import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams;
@ -45,7 +45,7 @@ public class MdxHandler extends ModelHandler {
}
@Override
public Resource<?> construct(final ResourceHandlerConstructionParams params) {
public HandlerResource<?> construct(final ResourceHandlerConstructionParams params) {
return new MdxModel((MdxHandler) params.getHandler(), params.getViewer(), params.getExtension(),
params.getPathSolver(), params.getFetchUrl());
}

View File

@ -11,6 +11,31 @@ import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Texture;
public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
public boolean reforged = false;
public boolean hd = false;
public SolverParams solverParams = new SolverParams();
public String name = "";
public List<Sequence> sequences = new ArrayList<>();
public List<Integer> globalSequences = new ArrayList<>();
public List<Material> materials = new ArrayList<>();
public List<Layer> layers = new ArrayList<>();
public List<Integer> replaceables = new ArrayList<>();
public List<Texture> textures = new ArrayList<>();
public List<TextureAnimation> textureAnimations = new ArrayList<>();
public List<Geoset> geosets = new ArrayList<>();
public List<GeosetAnimation> geosetAnimations = new ArrayList<>();
public List<Bone> bones = new ArrayList<>();
public List<Light> lights = new ArrayList<>();
public List<Helper> helpers = new ArrayList<>();
public List<Attachment> attachments = new ArrayList<>();
public List<float[]> pivotPoints = new ArrayList<>();
public List<ParticleEmitterObject> particleEmitters = new ArrayList<>();
public List<ParticleEmitter2Object> particleEmitters2 = new ArrayList<>();
public List<RibbonEmitterObject> ribbonEmitters = new ArrayList<>();
public List<Camera> cameras = new ArrayList<>();
public List<EventObjectEmitterObject> eventObjects = new ArrayList<>();
public
private MdlxModel model;
public int arrayBuffer;
@ -18,9 +43,8 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
public List<Batch> batches = new ArrayList<>(); // TODO??
public List<Integer> replaceables = new ArrayList<>();
public boolean reforged = false;
public List<Object> opaqueGroups;
public List<Object> translucentGroups;
public MdxModel(final MdxHandler handler, final ModelViewer viewer, final String extension,
final PathSolver pathSolver, final String fetchUrl) {
@ -70,4 +94,18 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
throw new UnsupportedOperationException("NYI");
}
public List<TextureAnimation> getTextureAnimations() {
throw new UnsupportedOperationException("NYI");
}
public List<Geoset> getGeosets() {
throw new UnsupportedOperationException("NYI");
}
private static final class SolverParams {
public boolean reforged;
public boolean hd;
}
}

View File

@ -0,0 +1,103 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.viewer5.EmittedObject;
import com.etheller.warsmash.viewer5.Scene;
/**
* A spawned model particle.
*/
public class Particle extends EmittedObject<MdxComplexInstance, ParticleEmitter> {
private static final Quaternion rotationHeap = new Quaternion();
private static final Quaternion rotationHeap2 = new Quaternion();
private static final Vector3 velocityHeap = new Vector3();
private static final float[] latitudeHeap = new float[1];
// private static final float[] longitudeHeap = new float[1];
private static final float[] lifeSpanHeap = new float[1];
private static final float[] gravityHeap = new float[1];
private static final float[] speedHeap = new float[1];
private static final float[] tempVector = new float[3];
private final MdxComplexInstance internalInstance;
private final Vector3 velocity = new Vector3();
private float gravity;
public Particle(final ParticleEmitter emitter) {
super(emitter);
final ParticleEmitterObject emitterObject = emitter.emitterObject;
this.internalInstance = (MdxComplexInstance) emitterObject.internalModel.addInstance();
}
@Override
protected void bind(final int flags) {
final ParticleEmitter emitter = this.emitter;
final MdxComplexInstance instance = emitter.instance;
final int sequence = instance.sequence;
final int frame = instance.frame;
final int counter = instance.counter;
final Scene scene = instance.scene;
final ParticleEmitterObject emitterObject = emitter.emitterObject;
final MdxNode node = instance.nodes[emitterObject.index];
final MdxComplexInstance internalInstance = this.internalInstance;
final Vector3 scale = node.worldScale;
final Vector3 velocity = this.velocity;
emitterObject.getLatitude(latitudeHeap, sequence, frame, counter);
// longitude?? commented in ghostwolf JS
emitterObject.getLifeSpan(lifeSpanHeap, sequence, frame, counter);
emitterObject.getGravity(gravityHeap, sequence, frame, counter);
emitterObject.getSpeed(speedHeap, sequence, frame, counter);
this.health = lifeSpanHeap[0];
this.gravity = gravityHeap[0] * scale.z;
// Local rotation
rotationHeap.idt();
rotationHeap.mulLeft(rotationHeap2.setFromAxisRad(0, 0, 1,
RenderMathUtils.randomInRange((float) -Math.PI, (float) Math.PI)));
rotationHeap.mulLeft(rotationHeap2.setFromAxisRad(0, 1, 0,
RenderMathUtils.randomInRange(-latitudeHeap[0], latitudeHeap[0])));
velocity.set(RenderMathUtils.VEC3_UNIT_Z);
rotationHeap.transform(velocity);
// World rotation
node.worldRotation.transform(velocity);
// Apply speed
velocity.scl(speedHeap[0]);
// Apply the parent's scale
velocity.scl(scale);
scene.addInstance(internalInstance);
internalInstance.setTransformation(node.worldLocation, rotationHeap.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z,
RenderMathUtils.randomInRange(0, (float) Math.PI * 2)), node.worldScale);
internalInstance.setSequence(0);
internalInstance.show();
}
@Override
public void update(final float dt) {
final MdxComplexInstance internalInstance = this.internalInstance;
internalInstance.paused = false; /// Why is this here?
this.health -= dt;
if (this.health > 0) {
final Vector3 velocity = this.velocity;
velocity.z -= this.gravity * dt;
tempVector[0] = velocity.x * dt;
tempVector[1] = velocity.y * dt;
tempVector[2] = velocity.z * dt;
internalInstance.move(tempVector);
}
}
}

View File

@ -12,7 +12,6 @@ public class Particle2 extends EmittedObject<MdxComplexInstance, ParticleEmitter
public final Vector3 location = new Vector3();
public final Vector3 velocity = new Vector3();
public final Vector3 scale = new Vector3();
private final ParticleEmitter2 emitter2;
private static final Quaternion rotationHeap = new Quaternion();
private static final Quaternion rotationHeap2 = new Quaternion();
@ -24,7 +23,7 @@ public class Particle2 extends EmittedObject<MdxComplexInstance, ParticleEmitter
private static final float[] gravityHeap = new float[1];
public Particle2(final ParticleEmitter2 emitter) {
this.emitter2 = emitter;
super(emitter);
}
@Override
@ -32,12 +31,12 @@ public class Particle2 extends EmittedObject<MdxComplexInstance, ParticleEmitter
final MdxComplexInstance instance = this.emitter.instance;
final ParticleEmitter2Object emitterObject = this.emitter.emitterObject;
emitterObject.getWidth(widthHeap, instance);
emitterObject.getLength(lengthHeap, instance);
emitterObject.getLatitude(latitudeHeap, instance);
emitterObject.getVariation(variationHeap, instance);
emitterObject.getSpeed(speedHeap, instance);
emitterObject.getGravity(gravityHeap, instance);
emitterObject.getWidth(widthHeap, instance.sequence, instance.frame, instance.counter);
emitterObject.getLength(lengthHeap, instance.sequence, instance.frame, instance.counter);
emitterObject.getLatitude(latitudeHeap, instance.sequence, instance.frame, instance.counter);
emitterObject.getVariation(variationHeap, instance.sequence, instance.frame, instance.counter);
emitterObject.getSpeed(speedHeap, instance.sequence, instance.frame, instance.counter);
emitterObject.getGravity(gravityHeap, instance.sequence, instance.frame, instance.counter);
final MdxNode node = this.emitter.node;
final Vector3 pivot = node.pivot;

View File

@ -0,0 +1,34 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
public class ParticleEmitter extends MdxEmitter<MdxComplexInstance, ParticleEmitterObject, Particle> {
private static final float[] emissionRateHeap = new float[1];
public ParticleEmitter(final MdxComplexInstance instance, final ParticleEmitterObject emitterObject) {
super(instance, emitterObject);
}
@Override
protected void updateEmission(final float dt) {
final MdxComplexInstance instance = this.instance;
if (instance.allowParticleSpawn) {
final ParticleEmitterObject emitterObject = this.emitterObject;
emitterObject.getEmissionRate(emissionRateHeap, instance.sequence, instance.frame, instance.counter);
this.currentEmission += emissionRateHeap[0] * dt;
}
}
@Override
protected void emit() {
this.emitObject(0);
}
@Override
protected Particle createObject() {
return new Particle(this);
}
}

View File

@ -19,7 +19,8 @@ public class ParticleEmitter2 extends MdxEmitter<MdxComplexInstance, ParticleEmi
if (instance.allowParticleSpawn) {
final ParticleEmitter2Object emitterObject = this.emitterObject;
final int keyframe = emitterObject.getEmissionRate(emissionRateHeap, instance);
final int keyframe = emitterObject.getEmissionRate(emissionRateHeap, instance.sequence, instance.frame,
instance.counter);
if (emitterObject.squirt != 0) {
if (keyframe != this.lastEmissionKey) {

View File

@ -105,37 +105,37 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje
this.priorityPlane = emitter.getPriorityPlane();
}
public int getWidth(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2N.getWar3id(), instance, this.width);
public int getWidth(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2N.getWar3id(), sequence, frame, counter, this.width);
}
public int getLength(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2W.getWar3id(), instance, this.length);
public int getLength(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2W.getWar3id(), sequence, frame, counter, this.length);
}
public int getSpeed(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2S.getWar3id(), instance, this.speed);
public int getSpeed(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2S.getWar3id(), sequence, frame, counter, this.speed);
}
public int getLatitude(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2L.getWar3id(), instance, this.latitude);
public int getLatitude(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2L.getWar3id(), sequence, frame, counter, this.latitude);
}
public int getGravity(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2G.getWar3id(), instance, this.gravity);
public int getGravity(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2G.getWar3id(), sequence, frame, counter, this.gravity);
}
public int getEmissionRate(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2E.getWar3id(), instance, this.emissionRate);
public int getEmissionRate(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2E.getWar3id(), sequence, frame, counter, this.emissionRate);
}
@Override
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2V.getWar3id(), instance, 1);
public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2V.getWar3id(), sequence, frame, counter, 1);
}
public int getVariation(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KP2R.getWar3id(), instance, this.variation);
public int getVariation(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KP2R.getWar3id(), sequence, frame, counter, this.variation);
}
@Override

View File

@ -0,0 +1,83 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.Locale;
import com.etheller.warsmash.parsers.mdlx.AnimationMap;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public class ParticleEmitterObject extends GenericObject implements EmitterObject {
public MdxModel internalModel;
public float speed;
public float latitude;
public float longitude;
public float lifeSpan;
public float gravity;
public float emissionRate;
/**
* No need to create instances of the internal model if it didn't load.
*
* Such instances won't actually render, and who knows if the model will ever
* load?
*/
public boolean ok = false;
public ParticleEmitterObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.ParticleEmitter emitter,
final int index) {
super(model, emitter, index);
this.internalModel = (MdxModel) model.viewer.load(
emitter.getPath().replace("\\", "/").toLowerCase(Locale.US).replace(".mdl", ".mdx"), model.pathSolver,
model.solverParams);
this.speed = emitter.getSpeed();
this.latitude = emitter.getLatitude();
this.longitude = emitter.getLongitude();
this.lifeSpan = emitter.getLifeSpan();
this.gravity = emitter.getGravity();
this.emissionRate = emitter.getEmissionRate();
// Activate emitters based on this emitter object only when and if the internal
// model loads successfully.
// TODO async removed here
this.ok = this.internalModel.ok;
}
public int getSpeed(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KPES.getWar3id(), sequence, frame, counter, this.speed);
}
public int getLatitude(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KPLT.getWar3id(), sequence, frame, counter, this.latitude);
}
public int getLongitude(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KPLN.getWar3id(), sequence, frame, counter, this.longitude);
}
public int getLifeSpan(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KPEL.getWar3id(), sequence, frame, counter, this.lifeSpan);
}
public int getGravity(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KPEG.getWar3id(), sequence, frame, counter, this.gravity);
}
public int getEmissionRate(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KPEE.getWar3id(), sequence, frame, counter, this.emissionRate);
}
@Override
public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KPEV.getWar3id(), sequence, frame, counter, 1);
}
@Override
public int getGeometryEmitterType() {
throw new UnsupportedOperationException("ghostwolf doesnt have this in the JS");
}
@Override
public boolean ok() {
return this.ok;
}
}

View File

@ -18,6 +18,10 @@ public class Ribbon extends EmittedObject<MdxComplexInstance, RibbonEmitter> {
public Ribbon prev;
public Ribbon next;
public Ribbon(final RibbonEmitter emitter) {
super(emitter);
}
@Override
protected void bind(final int flags) {
final RibbonEmitter emitter = this.emitter;
@ -31,9 +35,9 @@ public class Ribbon extends EmittedObject<MdxComplexInstance, RibbonEmitter> {
this.health = emitter.emitterObject.lifeSpan;
emitterObject.getHeightBelow(vectorHeap, instance);
emitterObject.getHeightBelow(vectorHeap, instance.sequence, instance.frame, instance.counter);
belowHeap.set(vectorHeap);
emitterObject.getHeightAbove(vectorHeap, instance);
emitterObject.getHeightAbove(vectorHeap, instance.sequence, instance.frame, instance.counter);
aboveHeap.set(vectorHeap);
belowHeap.y = y - belowHeap.x;
@ -55,9 +59,6 @@ public class Ribbon extends EmittedObject<MdxComplexInstance, RibbonEmitter> {
vertices[5] = belowHeap.z;
}
public Ribbon(final RibbonEmitter emitter) {
}
@Override
public void update(final float dt) {
this.health -= dt;
@ -70,9 +71,9 @@ public class Ribbon extends EmittedObject<MdxComplexInstance, RibbonEmitter> {
final float[] vertices = this.vertices;
final float gravity = emitterObject.gravity * dt * dt;
emitterObject.getColor(colorHeap, instance);
emitterObject.getAlpha(alphaHeap, instance);
emitterObject.getTextureSlot(slotHeap, instance);
emitterObject.getColor(colorHeap, instance.sequence, instance.frame, instance.counter);
emitterObject.getAlpha(alphaHeap, instance.sequence, instance.frame, instance.counter);
emitterObject.getTextureSlot(slotHeap, instance.sequence, instance.frame, instance.counter);
vertices[1] -= gravity;
vertices[4] -= gravity;

View File

@ -40,29 +40,29 @@ public class RibbonEmitterObject extends GenericObject implements EmitterObject
this.rows = emitter.getRows();
}
public int getHeightBelow(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRHB.getWar3id(), instance, this.heightBelow);
public int getHeightBelow(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KRHB.getWar3id(), sequence, frame, counter, this.heightBelow);
}
public int getHeightAbove(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRHA.getWar3id(), instance, this.heightAbove);
public int getHeightAbove(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KRHA.getWar3id(), sequence, frame, counter, this.heightAbove);
}
public int getTextureSlot(final long[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRTX.getWar3id(), instance, 0);
public int getTextureSlot(final long[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KRTX.getWar3id(), sequence, frame, counter, 0);
}
public int getColor(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KRCO.getWar3id(), instance, this.color);
public int getColor(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KRCO.getWar3id(), sequence, frame, counter, this.color);
}
public int getAlpha(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRAL.getWar3id(), instance, this.alpha);
public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KRAL.getWar3id(), sequence, frame, counter, this.alpha);
}
@Override
public int getVisibility(final float[] out, final MdxComplexInstance instance) {
return this.getScalarValue(out, AnimationMap.KRVS.getWar3id(), instance, 1f);
public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) {
return this.getScalarValue(out, AnimationMap.KRVS.getWar3id(), sequence, frame, counter, 1f);
}
@Override

View File

@ -116,12 +116,12 @@ public abstract class Sd<TYPE> {
}
}
public int getValue(final TYPE out, final MdxComplexInstance instance) {
public int getValue(final TYPE out, final int sequence, final int frame, final int counter) {
if (this.globalSequence != null) {
return this.globalSequence.getValue(out, instance.counter % this.globalSequence.end);
return this.globalSequence.getValue(out, counter % this.globalSequence.end);
}
else if (instance.sequence != -1) {
return this.sequences.get(instance.sequence).getValue(out, instance.frame);
else if (sequence != -1) {
return this.sequences.get(sequence).getValue(out, frame);
}
else {
this.copy(out, this.defval);

View File

@ -0,0 +1,186 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.badlogic.gdx.graphics.GL20;
public class SetupGeosets {
private static final int NORMAL_BATCH = 0;
private static final int EXTENDED_BATCH = 0;
private static final int REFORGED_BATCH = 0;
public static void setupGeosets(final MdxModel model,
final List<com.etheller.warsmash.parsers.mdlx.Geoset> geosets) {
if (geosets.size() > 0) {
final GL20 gl = model.viewer.gl;
int positionBytes = 0;
int normalBytes = 0;
int uvBytes = 0;
int skinBytes = 0;
int faceBytes = 0;
final int[] batchTypes = new int[geosets.size()];
for (int i = 0, l = geosets.size(); i < l; i++) {
final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i);
if (true /* geoset.getLod() == 0 */) {
final int vertices = geoset.getVertices().length / 3;
positionBytes += vertices * 12;
normalBytes += vertices * 12;
uvBytes += geoset.getUvSets().length * vertices * 8;
if (false /* geoset.skin.length */) {
skinBytes += vertices * 8;
batchTypes[i] = REFORGED_BATCH;
}
else {
long biggestGroup = 0;
for (final long group : geoset.getMatrixGroups()) {
if (group > biggestGroup) {
biggestGroup = group;
}
}
if (biggestGroup > 4) {
skinBytes += vertices * 9;
batchTypes[i] = EXTENDED_BATCH;
}
else {
batchTypes[i] = NORMAL_BATCH;
}
}
faceBytes += geoset.getFaces().length * 4;
}
}
int positionOffset = 0;
int normalOffset = positionOffset + positionBytes;
int uvOffset = normalOffset + normalBytes;
int skinOffset = uvOffset + uvBytes;
int faceOffset = 0;
model.arrayBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer);
gl.glBufferData(GL20.GL_ARRAY_BUFFER, skinOffset + skinBytes, null, GL20.GL_STATIC_DRAW);
model.elementBuffer = gl.glGenBuffer();
gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer);
gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceBytes, null, GL20.GL_STATIC_DRAW);
for (int i = 0, l = geosets.size(); i < l; i++) {
final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i);
if (true /* geoset.lod == 0 */) {
final float[] positions = geoset.getVertices();
final float[] normals = geoset.getNormals();
final float[][] uvSets = geoset.getUvSets();
final int[] faces = geoset.getFaces();
byte[] skin = null;
final int vertices = geoset.getVertices().length / 3;
final int batchType = batchTypes[i];
if (batchType == REFORGED_BATCH) {
// skin = geoset.skin;
}
else {
final long[] matrixIndices = geoset.getMatrixIndices();
final short[] vertexGroups = geoset.getVertexGroups();
final List<long[]> matrixGroups = new ArrayList<>();
int offset = 0;
// Normally the shader supports up to 4 bones per vertex.
// This is enough for almost every existing Warcraft 3 model.
// That being said, there are a few models with geosets that need more, for
// example the Water Elemental.
// These geosets use a different shader, which support up to 8 bones per vertex.
int maxBones = 4;
if (batchType == EXTENDED_BATCH) {
maxBones = 8;
}
skin = new byte[vertices * (maxBones + 1)];
// Slice the matrix groups
for (final long size : geoset.getMatrixGroups()) {
matrixGroups.add(Arrays.copyOfRange(matrixIndices, offset, (int) (offset + size)));
offset += size;
}
// Parse the skinning.
for (int si = 0; si < vertices; si++) {
final short vertexGroup = vertexGroups[si];
final long[] matrixGroup = (vertexGroup >= matrixGroups.size()) ? null
: matrixGroups.get(vertexGroup);
offset = si * (maxBones + 1);
// Somehow in some bad models a vertex group index refers to an invalid matrix
// group.
// Such models are still loaded by the game.
if (matrixGroup != null) {
final int bones = Math.min(matrixGroup.length, maxBones);
for (int j = 0; j < bones; j++) {
skin[offset + j] = (byte) (matrixGroup[j] + 1); // 1 is added to diffrentiate
// between matrix 0, and no matrix.
}
skin[offset + maxBones] = (byte) bones;
}
}
}
final Geoset vGeoset = new Geoset(model, model.getGeosets().size(), positionOffset, normalOffset,
uvOffset, skinOffset, faceOffset, vertices, faces.length);
model.getGeosets().add(vGeoset);
if (batchType == REFORGED_BATCH) {
throw new UnsupportedOperationException("NYI");
// model.batches.add(new Reforged)
}
else {
final boolean isExtended = batchType == EXTENDED_BATCH;
for (final Layer layer : model.getMaterials().get((int) geoset.getMaterialId()).layers) {
model.batches.add(new Batch(model.batches.size(), vGeoset, layer, isExtended));
}
}
// Positions.
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, positionOffset, positions.length,
FloatBuffer.wrap(positions));
positionOffset += positions.length * 4;
// Normals.
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalOffset, normals.length, FloatBuffer.wrap(normals));
normalOffset += normals.length * 4;
// Texture coordinates.
for (final float[] uvSet : uvSets) {
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvOffset, uvSet.length, FloatBuffer.wrap(uvSet));
uvOffset += uvSet.length * 4;
}
// Skin.
gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, ByteBuffer.wrap(skin));
skinOffset += skin.length * 1;
// Faces.
gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, IntBuffer.wrap(faces));
faceOffset += faces.length * 4;
}
}
}
}
}

View File

@ -0,0 +1,121 @@
package com.etheller.warsmash.viewer5.handlers.mdx;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.etheller.warsmash.viewer5.handlers.EmitterObject;
public class SetupGroups {
public static int getPrio(final Batch object) {
return object.layer.priorityPlane;
}
public static int getPrio(final ParticleEmitter2Object object) {
return object.priorityPlane;
}
public static int getPrio(final RibbonEmitterObject object) {
return object.layer.priorityPlane;
}
public static int getPrio(final Object object) {
if (object instanceof Batch) {
return getPrio((Batch) object);
}
else if (object instanceof RibbonEmitterObject) {
return getPrio((RibbonEmitterObject) object);
}
else if (object instanceof ParticleEmitter2Object) {
return getPrio((ParticleEmitter2Object) object);
}
else {
throw new IllegalArgumentException(object.getClass().getName());
}
}
public static boolean matchingGroup(final Object group, final Object object) {
if (group instanceof BatchGroup) {
return (object instanceof Batch) && (((Batch) object).isExtended == ((BatchGroup) group).isExtended);
// } else if(group instanceof ReforgedBatch) { TODO
// return (object instanceof ReforgedBatch) && (object.material.shader === group.shader);
}
else {
// All of the emitter objects are generic objects.
return (object instanceof GenericObject);
}
}
public static GenericGroup createMatchingGroup(final MdxModel model, final Object object) {
if (object instanceof Batch) {
return new BatchGroup(model, ((Batch) object).isExtended);
// } else if(object instanceof ReforgedBatch) { TODO
// return new ReforgedBatchGroup(model, ((ReforgedBatch)object).material.shader);
}
else {
return new EmitterGroup(model);
}
}
public static void setupGroups(final MdxModel model) {
final List<Batch> opaqueBatches = new ArrayList<>();
final List<Batch> translucentBatches = new ArrayList<>();
for (final Batch batch : model.batches) {// TODO reforged
if (/* batch instanceof ReforgedBatch || */batch.layer.filterMode < 2) {
opaqueBatches.add(batch);
}
else {
translucentBatches.add(batch);
}
}
final List<Object> opaqueGroups = model.opaqueGroups;
final List<Object> translucentGroups = model.translucentGroups;
GenericGroup currentGroup = null;
for (final Batch object : opaqueBatches) {
if ((currentGroup == null) || !matchingGroup(currentGroup, object)) {
currentGroup = createMatchingGroup(model, object);
opaqueGroups.add(currentGroup);
}
currentGroup.objects.add(object.index);
}
// Sort between all of the translucent batches and emitters that have priority
// planes
final List<Object> sorted = new ArrayList<>();
sorted.addAll(translucentBatches);
sorted.addAll(model.particleEmitters2);
sorted.addAll(model.ribbonEmitters);
Collections.sort(sorted, new Comparator<Object>() {
@Override
public int compare(final Object o1, final Object o2) {
return getPrio(o1) - getPrio(o2);
}
});
// Event objects have no priority planes, so they might as well always be last.
final List<Object> objects = new ArrayList<>();
objects.addAll(sorted);
objects.addAll(model.eventObjects);
currentGroup = null;
for (final Object object : objects) { // TODO reforged
if ((object instanceof Batch /* || object instanceof ReforgedBatch */)
|| (object instanceof EmitterObject)) {
if ((currentGroup == null) || !matchingGroup(currentGroup, objects)) {
currentGroup = createMatchingGroup(model, objects);
translucentGroups.add(currentGroup);
}
currentGroup.objects.add(((GenericIndexed) object).getIndex());
}
}
}
}

View File

@ -10,16 +10,19 @@ public class TextureAnimation extends AnimatedObject {
super(model, textureAnimation);
}
public int getTranslation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KTAT.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ZERO);
public int getTranslation(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KTAT.getWar3id(), sequence, frame, counter,
RenderMathUtils.FLOAT_VEC3_ZERO);
}
public int getRotation(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KTAR.getWar3id(), instance, RenderMathUtils.FLOAT_QUAT_DEFAULT);
public int getRotation(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KTAR.getWar3id(), sequence, frame, counter,
RenderMathUtils.FLOAT_QUAT_DEFAULT);
}
public int getScale(final float[] out, final MdxComplexInstance instance) {
return this.getVectorValue(out, AnimationMap.KTAS.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ONE);
public int getScale(final float[] out, final int sequence, final int frame, final int counter) {
return this.getVectorValue(out, AnimationMap.KTAS.getWar3id(), sequence, frame, counter,
RenderMathUtils.FLOAT_VEC3_ONE);
}
public boolean isTranslationVariant(final int sequence) {

View File

@ -1,12 +0,0 @@
package com.hiveworkshop.wc3.mpq;
import java.io.File;
import java.io.InputStream;
public interface Codebase {
InputStream getResourceAsStream(String filepath);
File getFile(String filepath);
boolean has(String filepath);
}

View File

@ -1,35 +0,0 @@
package com.hiveworkshop.wc3.mpq;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class FileCodebase implements Codebase {
private final File sourceDirectory;
public FileCodebase(final File sourceDirectory) {
this.sourceDirectory = sourceDirectory;
}
@Override
public InputStream getResourceAsStream(final String filepath) {
try {
return new FileInputStream(new File(this.sourceDirectory.getPath() + File.separatorChar + filepath));
}
catch (final FileNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public File getFile(final String filepath) {
return new File(this.sourceDirectory.getPath() + File.separatorChar + filepath);
}
@Override
public boolean has(final String filepath) {
return new File(this.sourceDirectory.getPath() + File.separatorChar + filepath).exists();
}
}